]> BookStack Code Mirror - bookstack/blob - app/Entities/Tools/SearchIndex.php
Fixed some mis-refactoring and split search service
[bookstack] / app / Entities / Tools / SearchIndex.php
1 <?php namespace BookStack\Entities\Tools;
2
3 use BookStack\Entities\EntityProvider;
4 use BookStack\Entities\Models\Entity;
5 use BookStack\Entities\Models\SearchTerm;
6
7 class SearchIndex
8 {
9     /**
10      * @var SearchTerm
11      */
12     protected $searchTerm;
13
14     /**
15      * @var EntityProvider
16      */
17     protected $entityProvider;
18
19
20     public function __construct(SearchTerm $searchTerm, EntityProvider $entityProvider)
21     {
22         $this->searchTerm = $searchTerm;
23         $this->entityProvider = $entityProvider;
24     }
25
26
27     /**
28      * Index the given entity.
29      */
30     public function indexEntity(Entity $entity)
31     {
32         $this->deleteEntityTerms($entity);
33         $nameTerms = $this->generateTermArrayFromText($entity->name, 5 * $entity->searchFactor);
34         $bodyTerms = $this->generateTermArrayFromText($entity->getText() ?? '', 1 * $entity->searchFactor);
35         $terms = array_merge($nameTerms, $bodyTerms);
36         foreach ($terms as $index => $term) {
37             $terms[$index]['entity_type'] = $entity->getMorphClass();
38             $terms[$index]['entity_id'] = $entity->id;
39         }
40         $this->searchTerm->newQuery()->insert($terms);
41     }
42
43     /**
44      * Index multiple Entities at once
45      * @param Entity[] $entities
46      */
47     protected function indexEntities(array $entities)
48     {
49         $terms = [];
50         foreach ($entities as $entity) {
51             $nameTerms = $this->generateTermArrayFromText($entity->name, 5 * $entity->searchFactor);
52             $bodyTerms = $this->generateTermArrayFromText($entity->getText(), 1 * $entity->searchFactor);
53             foreach (array_merge($nameTerms, $bodyTerms) as $term) {
54                 $term['entity_id'] = $entity->id;
55                 $term['entity_type'] = $entity->getMorphClass();
56                 $terms[] = $term;
57             }
58         }
59
60         $chunkedTerms = array_chunk($terms, 500);
61         foreach ($chunkedTerms as $termChunk) {
62             $this->searchTerm->newQuery()->insert($termChunk);
63         }
64     }
65
66     /**
67      * Delete and re-index the terms for all entities in the system.
68      */
69     public function indexAllEntities()
70     {
71         $this->searchTerm->newQuery()->truncate();
72
73         foreach ($this->entityProvider->all() as $entityModel) {
74             $selectFields = ['id', 'name', $entityModel->textField];
75             $entityModel->newQuery()
76                 ->withTrashed()
77                 ->select($selectFields)
78                 ->chunk(1000, function ($entities) {
79                     $this->indexEntities($entities);
80                 });
81         }
82     }
83
84     /**
85      * Delete related Entity search terms.
86      */
87     public function deleteEntityTerms(Entity $entity)
88     {
89         $entity->searchTerms()->delete();
90     }
91
92     /**
93      * Create a scored term array from the given text.
94      */
95     protected function generateTermArrayFromText(string $text, int $scoreAdjustment = 1): array
96     {
97         $tokenMap = []; // {TextToken => OccurrenceCount}
98         $splitChars = " \n\t.,!?:;()[]{}<>`'\"";
99         $token = strtok($text, $splitChars);
100
101         while ($token !== false) {
102             if (!isset($tokenMap[$token])) {
103                 $tokenMap[$token] = 0;
104             }
105             $tokenMap[$token]++;
106             $token = strtok($splitChars);
107         }
108
109         $terms = [];
110         foreach ($tokenMap as $token => $count) {
111             $terms[] = [
112                 'term' => $token,
113                 'score' => $count * $scoreAdjustment
114             ];
115         }
116
117         return $terms;
118     }
119 }