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