Passed
Pull Request — master (#7144)
by
unknown
16:21 queued 06:48
created

CourseDescriptionXapianIndexer   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 152
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 78
c 1
b 0
f 0
dl 0
loc 152
rs 10
wmc 26

4 Methods

Rating   Name   Duplication   Size   Complexity  
B resolveCourseAndSession() 0 24 9
A __construct() 0 5 1
C indexCourseDescription() 0 83 12
A deleteCourseDescriptionIndex() 0 24 4
1
<?php
2
3
declare(strict_types=1);
4
5
/* For licensing terms, see /license.txt */
6
7
namespace Chamilo\CoreBundle\Search\Xapian;
8
9
use Chamilo\CoreBundle\Entity\ResourceLink;
10
use Chamilo\CoreBundle\Entity\ResourceNode;
11
use Chamilo\CoreBundle\Entity\SearchEngineRef;
12
use Chamilo\CoreBundle\Settings\SettingsManager;
13
use Chamilo\CourseBundle\Entity\CCourseDescription;
14
use Doctrine\ORM\EntityManagerInterface;
15
16
/**
17
 * Handles Xapian indexing for course descriptions.
18
 */
19
final class CourseDescriptionXapianIndexer
20
{
21
    public function __construct(
22
        private readonly XapianIndexService $xapianIndexService,
23
        private readonly EntityManagerInterface $em,
24
        private readonly SettingsManager $settingsManager,
25
    ) {
26
    }
27
28
    /**
29
     * Index or reindex a course description.
30
     *
31
     * @return int|null Xapian document id or null when indexing is skipped
32
     */
33
    public function indexCourseDescription(CCourseDescription $description): ?int
34
    {
35
        // Global feature toggle
36
        $enabled = (string) $this->settingsManager->getSetting('search.search_enabled', true);
37
        if ($enabled !== 'true') {
38
            return null;
39
        }
40
41
        // Per-request flag set from the form
42
        if ($description->shouldSkipSearchIndex()) {
43
            return null;
44
        }
45
46
        $resourceNode = $description->getResourceNode();
47
        if (!$resourceNode instanceof ResourceNode) {
48
            return null;
49
        }
50
51
        // Resolve course & session
52
        [$courseId, $sessionId] = $this->resolveCourseAndSession($resourceNode);
53
54
        $title   = (string) ($description->getTitle() ?? '');
55
        $body    = (string) ($description->getContent() ?? '');
56
        $content = trim($title.' '.$body);
57
58
        $fields = [
59
            'kind'             => 'course_description',
60
            'tool'             => 'course_description',
61
            'title'            => $title,
62
            'description'      => $title,
63
            'content'          => $content,
64
            'resource_node_id' => (string) $resourceNode->getId(),
65
            'course_id'        => $courseId !== null ? (string) $courseId : '',
66
            'session_id'       => $sessionId !== null ? (string) $sessionId : '',
67
            'xapian_data'      => json_encode([
68
                'type'           => 'course_description',
69
                'description_id' => (int) $description->getIid(),
70
                'course_id'      => $courseId,
71
                'session_id'     => $sessionId,
72
            ]),
73
        ];
74
75
        $terms = ['Tcourse_description'];
76
        if ($courseId !== null) {
77
            $terms[] = 'C'.$courseId;
78
        }
79
        if ($sessionId !== null) {
80
            $terms[] = 'S'.$sessionId;
81
        }
82
83
        /** @var SearchEngineRef|null $existingRef */
84
        $existingRef = $this->em
85
            ->getRepository(SearchEngineRef::class)
86
            ->findOneBy(['resourceNodeId' => $resourceNode->getId()]);
87
88
        $existingDocId = $existingRef?->getSearchDid();
89
90
        if ($existingDocId !== null) {
91
            try {
92
                $this->xapianIndexService->deleteDocument($existingDocId);
93
            } catch (\Throwable) {
94
                // Best-effort delete
95
            }
96
        }
97
98
        try {
99
            $docId = $this->xapianIndexService->indexDocument($fields, $terms);
100
        } catch (\Throwable) {
101
            return null;
102
        }
103
104
        if ($existingRef instanceof SearchEngineRef) {
105
            $existingRef->setSearchDid($docId);
106
        } else {
107
            $existingRef = new SearchEngineRef();
108
            $existingRef->setResourceNodeId((int) $resourceNode->getId());
109
            $existingRef->setSearchDid($docId);
110
            $this->em->persist($existingRef);
111
        }
112
113
        $this->em->flush();
114
115
        return $docId;
116
    }
117
118
    public function deleteCourseDescriptionIndex(CCourseDescription $description): void
119
    {
120
        $resourceNode = $description->getResourceNode();
121
        if (!$resourceNode instanceof ResourceNode) {
122
            return;
123
        }
124
125
        /** @var SearchEngineRef|null $ref */
126
        $ref = $this->em
127
            ->getRepository(SearchEngineRef::class)
128
            ->findOneBy(['resourceNodeId' => $resourceNode->getId()]);
129
130
        if (!$ref) {
131
            return;
132
        }
133
134
        try {
135
            $this->xapianIndexService->deleteDocument($ref->getSearchDid());
136
        } catch (\Throwable) {
137
            // Best-effort delete
138
        }
139
140
        $this->em->remove($ref);
141
        $this->em->flush();
142
    }
143
144
    /**
145
     * @return array{0:int|null,1:int|null}
146
     */
147
    private function resolveCourseAndSession(ResourceNode $resourceNode): array
148
    {
149
        $courseId  = null;
150
        $sessionId = null;
151
152
        foreach ($resourceNode->getResourceLinks() as $link) {
153
            if (!$link instanceof ResourceLink) {
154
                continue;
155
            }
156
157
            if ($courseId === null && $link->getCourse()) {
158
                $courseId = $link->getCourse()->getId();
159
            }
160
161
            if ($sessionId === null && $link->getSession()) {
162
                $sessionId = $link->getSession()->getId();
163
            }
164
165
            if ($courseId !== null && $sessionId !== null) {
166
                break;
167
            }
168
        }
169
170
        return [$courseId, $sessionId];
171
    }
172
}
173