Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
IndexFaqHandler
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
2 / 2
13
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __invoke
100.00% covered (success)
100.00%
34 / 34
100.00% covered (success)
100.00%
1 / 1
12
1<?php
2
3/**
4 * Handler for queued FAQ indexing.
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public License,
7 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
8 * obtain one at https://mozilla.org/MPL/2.0/.
9 *
10 * @package   phpMyFAQ
11 * @author    Thorsten Rinne <thorsten@phpmyfaq.de>
12 * @copyright 2026 phpMyFAQ Team
13 * @license   https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
14 * @link      https://www.phpmyfaq.de
15 * @since     2026-02-11
16 */
17
18declare(strict_types=1);
19
20namespace phpMyFAQ\Queue\Handler;
21
22use Closure;
23use phpMyFAQ\Category;
24use phpMyFAQ\Configuration;
25use phpMyFAQ\Faq;
26use phpMyFAQ\Instance\Search\Elasticsearch;
27use phpMyFAQ\Queue\Message\IndexFaqMessage;
28use RuntimeException;
29
30final readonly class IndexFaqHandler
31{
32    public function __construct(
33        private Configuration $configuration,
34        private ?Closure $faqFactory = null,
35        private ?Closure $categoryFactory = null,
36        private ?Closure $elasticsearchFactory = null,
37    ) {
38    }
39
40    public function __invoke(IndexFaqMessage $message): void
41    {
42        if (!$this->configuration->isElasticsearchActive()) {
43            throw new RuntimeException('Elasticsearch is not configured');
44        }
45
46        $faq = null;
47        if ($this->faqFactory instanceof Closure) {
48            $createdFaq = ($this->faqFactory)();
49            if ($createdFaq instanceof Faq) {
50                $faq = $createdFaq;
51            }
52        }
53
54        $faq ??= new Faq($this->configuration);
55        $faq->getFaq($message->faqId);
56
57        if (
58            $faq->faqRecord['id'] === $message->faqId
59            && $faq->faqRecord['active'] === 'yes'
60            && $faq->faqRecord['content'] !== ''
61        ) {
62            $category = null;
63            if ($this->categoryFactory instanceof Closure) {
64                $createdCategory = ($this->categoryFactory)();
65                if ($createdCategory instanceof Category) {
66                    $category = $createdCategory;
67                }
68            }
69
70            $category ??= new Category($this->configuration);
71            $categoryId = $category->getCategoryIdFromFaq($message->faqId);
72
73            $elasticsearch = null;
74            if ($this->elasticsearchFactory instanceof Closure) {
75                $createdElasticsearch = ($this->elasticsearchFactory)();
76                if ($createdElasticsearch instanceof Elasticsearch) {
77                    $elasticsearch = $createdElasticsearch;
78                }
79            }
80
81            $elasticsearch ??= new Elasticsearch($this->configuration);
82            $elasticsearch->index([
83                'id' => (int) $faq->faqRecord['id'],
84                'lang' => $message->language !== '' ? $message->language : (string) $faq->faqRecord['lang'],
85                'solution_id' => (int) $faq->faqRecord['solution_id'],
86                'question' => (string) $faq->faqRecord['title'],
87                'answer' => (string) $faq->faqRecord['content'],
88                'keywords' => (string) $faq->faqRecord['keywords'],
89                'category_id' => $categoryId,
90            ]);
91        }
92    }
93}