1 <?php namespace BookStack\Services;
7 use BookStack\SearchTerm;
8 use Illuminate\Database\Connection;
9 use Illuminate\Database\Query\JoinClause;
14 protected $searchTerm;
21 * SearchService constructor.
22 * @param SearchTerm $searchTerm
24 * @param Chapter $chapter
26 * @param Connection $db
28 public function __construct(SearchTerm $searchTerm, Book $book, Chapter $chapter, Page $page, Connection $db)
30 $this->searchTerm = $searchTerm;
32 $this->chapter = $chapter;
37 public function searchEntities($searchString, $entityType = 'all')
39 // TODO - Add Tag Searches
40 // TODO - Add advanced custom column searches
41 // TODO - Add exact match searches ("")
43 $termArray = explode(' ', $searchString);
45 $subQuery = $this->db->table('search_terms')->select('entity_id', 'entity_type', \DB::raw('SUM(score) as score'));
46 $subQuery->where(function($query) use ($termArray) {
47 foreach ($termArray as $inputTerm) {
48 $query->orWhere('term', 'like', $inputTerm .'%');
52 $subQuery = $subQuery->groupBy('entity_type', 'entity_id');
53 $pageSelect = $this->db->table('pages as e')->join(\DB::raw('(' . $subQuery->toSql() . ') as s'), function(JoinClause $join) {
54 $join->on('e.id', '=', 's.entity_id');
55 })->selectRaw('e.*, s.score')->orderBy('score', 'desc');
56 $pageSelect->mergeBindings($subQuery);
57 dd($pageSelect->toSql());
58 // TODO - Continue from here
62 * Index the given entity.
63 * @param Entity $entity
65 public function indexEntity(Entity $entity)
67 $this->deleteEntityTerms($entity);
68 $nameTerms = $this->generateTermArrayFromText($entity->name, 5);
69 $bodyTerms = $this->generateTermArrayFromText($entity->getText(), 1);
70 $terms = array_merge($nameTerms, $bodyTerms);
71 $entity->searchTerms()->createMany($terms);
75 * Index multiple Entities at once
76 * @param Entity[] $entities
78 protected function indexEntities($entities) {
80 foreach ($entities as $entity) {
81 $nameTerms = $this->generateTermArrayFromText($entity->name, 5);
82 $bodyTerms = $this->generateTermArrayFromText($entity->getText(), 1);
83 foreach (array_merge($nameTerms, $bodyTerms) as $term) {
84 $term['entity_id'] = $entity->id;
85 $term['entity_type'] = $entity->getMorphClass();
89 $this->searchTerm->insert($terms);
93 * Delete and re-index the terms for all entities in the system.
95 public function indexAllEntities()
97 $this->searchTerm->truncate();
99 // Chunk through all books
100 $this->book->chunk(500, function ($books) {
101 $this->indexEntities($books);
104 // Chunk through all chapters
105 $this->chapter->chunk(500, function ($chapters) {
106 $this->indexEntities($chapters);
109 // Chunk through all pages
110 $this->page->chunk(500, function ($pages) {
111 $this->indexEntities($pages);
116 * Delete related Entity search terms.
117 * @param Entity $entity
119 public function deleteEntityTerms(Entity $entity)
121 $entity->searchTerms()->delete();
125 * Create a scored term array from the given text.
127 * @param float|int $scoreAdjustment
130 protected function generateTermArrayFromText($text, $scoreAdjustment = 1)
132 $tokenMap = []; // {TextToken => OccurrenceCount}
133 $splitText = explode(' ', $text);
134 foreach ($splitText as $token) {
135 if ($token === '') continue;
136 if (!isset($tokenMap[$token])) $tokenMap[$token] = 0;
141 foreach ($tokenMap as $token => $count) {
144 'score' => $count * $scoreAdjustment