Showing
3 changed files
with
83 additions
and
12 deletions
| ... | @@ -4,7 +4,7 @@ | ... | @@ -4,7 +4,7 @@ |
| 4 | class Entity extends Ownable | 4 | class Entity extends Ownable |
| 5 | { | 5 | { |
| 6 | 6 | ||
| 7 | - protected $textField = 'description'; | 7 | + public $textField = 'description'; |
| 8 | 8 | ||
| 9 | /** | 9 | /** |
| 10 | * Compares this entity to another given entity. | 10 | * Compares this entity to another given entity. | ... | ... |
| ... | @@ -8,7 +8,7 @@ class Page extends Entity | ... | @@ -8,7 +8,7 @@ class Page extends Entity |
| 8 | protected $simpleAttributes = ['name', 'id', 'slug']; | 8 | protected $simpleAttributes = ['name', 'id', 'slug']; |
| 9 | 9 | ||
| 10 | protected $with = ['book']; | 10 | protected $with = ['book']; |
| 11 | - protected $textField = 'text'; | 11 | + public $textField = 'text'; |
| 12 | 12 | ||
| 13 | /** | 13 | /** |
| 14 | * Converts this page into a simplified array. | 14 | * Converts this page into a simplified array. | ... | ... |
| ... | @@ -6,7 +6,9 @@ use BookStack\Entity; | ... | @@ -6,7 +6,9 @@ use BookStack\Entity; |
| 6 | use BookStack\Page; | 6 | use BookStack\Page; |
| 7 | use BookStack\SearchTerm; | 7 | use BookStack\SearchTerm; |
| 8 | use Illuminate\Database\Connection; | 8 | use Illuminate\Database\Connection; |
| 9 | +use Illuminate\Database\Query\Builder; | ||
| 9 | use Illuminate\Database\Query\JoinClause; | 10 | use Illuminate\Database\Query\JoinClause; |
| 11 | +use Illuminate\Support\Collection; | ||
| 10 | 12 | ||
| 11 | class SearchService | 13 | class SearchService |
| 12 | { | 14 | { |
| ... | @@ -43,11 +45,18 @@ class SearchService | ... | @@ -43,11 +45,18 @@ class SearchService |
| 43 | $this->permissionService = $permissionService; | 45 | $this->permissionService = $permissionService; |
| 44 | } | 46 | } |
| 45 | 47 | ||
| 48 | + /** | ||
| 49 | + * Search all entities in the system. | ||
| 50 | + * @param $searchString | ||
| 51 | + * @param string $entityType | ||
| 52 | + * @param int $page | ||
| 53 | + * @param int $count | ||
| 54 | + * @return Collection | ||
| 55 | + */ | ||
| 46 | public function searchEntities($searchString, $entityType = 'all', $page = 0, $count = 20) | 56 | public function searchEntities($searchString, $entityType = 'all', $page = 0, $count = 20) |
| 47 | { | 57 | { |
| 48 | // TODO - Add Tag Searches | 58 | // TODO - Add Tag Searches |
| 49 | // TODO - Add advanced custom column searches | 59 | // TODO - Add advanced custom column searches |
| 50 | - // TODO - Add exact match searches ("") | ||
| 51 | // TODO - Check drafts don't show up in results | 60 | // TODO - Check drafts don't show up in results |
| 52 | // TODO - Move search all page to just /search?term=cat | 61 | // TODO - Move search all page to just /search?term=cat |
| 53 | 62 | ||
| ... | @@ -59,27 +68,89 @@ class SearchService | ... | @@ -59,27 +68,89 @@ class SearchService |
| 59 | return collect($bookSearch)->merge($chapterSearch)->merge($pageSearch)->sortByDesc('score'); | 68 | return collect($bookSearch)->merge($chapterSearch)->merge($pageSearch)->sortByDesc('score'); |
| 60 | } | 69 | } |
| 61 | 70 | ||
| 71 | + /** | ||
| 72 | + * Search across a particular entity type. | ||
| 73 | + * @param string $searchString | ||
| 74 | + * @param string $entityType | ||
| 75 | + * @param int $page | ||
| 76 | + * @param int $count | ||
| 77 | + * @return \Illuminate\Database\Eloquent\Collection|static[] | ||
| 78 | + */ | ||
| 62 | public function searchEntityTable($searchString, $entityType = 'page', $page = 0, $count = 20) | 79 | public function searchEntityTable($searchString, $entityType = 'page', $page = 0, $count = 20) |
| 63 | { | 80 | { |
| 64 | - $termArray = explode(' ', $searchString); | 81 | + $searchTerms = $this->parseSearchString($searchString); |
| 65 | 82 | ||
| 83 | + $entity = $this->getEntity($entityType); | ||
| 84 | + $entitySelect = $entity->newQuery(); | ||
| 85 | + | ||
| 86 | + // Handle normal search terms | ||
| 87 | + if (count($searchTerms['search']) > 0) { | ||
| 66 | $subQuery = $this->db->table('search_terms')->select('entity_id', 'entity_type', \DB::raw('SUM(score) as score')); | 88 | $subQuery = $this->db->table('search_terms')->select('entity_id', 'entity_type', \DB::raw('SUM(score) as score')); |
| 67 | - $subQuery->where(function($query) use ($termArray) { | 89 | + $subQuery->where(function(Builder $query) use ($searchTerms) { |
| 68 | - foreach ($termArray as $inputTerm) { | 90 | + foreach ($searchTerms['search'] as $inputTerm) { |
| 69 | $query->orWhere('term', 'like', $inputTerm .'%'); | 91 | $query->orWhere('term', 'like', $inputTerm .'%'); |
| 70 | } | 92 | } |
| 71 | - }); | 93 | + })->groupBy('entity_type', 'entity_id'); |
| 72 | - | 94 | + $entitySelect->join(\DB::raw('(' . $subQuery->toSql() . ') as s'), function(JoinClause $join) { |
| 73 | - $entity = $this->getEntity($entityType); | ||
| 74 | - $subQuery = $subQuery->groupBy('entity_type', 'entity_id'); | ||
| 75 | - $entitySelect = $entity->newQuery()->join(\DB::raw('(' . $subQuery->toSql() . ') as s'), function(JoinClause $join) { | ||
| 76 | $join->on('id', '=', 'entity_id'); | 95 | $join->on('id', '=', 'entity_id'); |
| 77 | - })->selectRaw($entity->getTable().'.*, s.score')->orderBy('score', 'desc')->skip($page * $count)->take($count); | 96 | + })->selectRaw($entity->getTable().'.*, s.score')->orderBy('score', 'desc'); |
| 78 | $entitySelect->mergeBindings($subQuery); | 97 | $entitySelect->mergeBindings($subQuery); |
| 98 | + } | ||
| 99 | + | ||
| 100 | + // Handle exact term matching | ||
| 101 | + if (count($searchTerms['exact']) > 0) { | ||
| 102 | + $entitySelect->where(function(\Illuminate\Database\Eloquent\Builder $query) use ($searchTerms, $entity) { | ||
| 103 | + foreach ($searchTerms['exact'] as $inputTerm) { | ||
| 104 | + $query->where(function (\Illuminate\Database\Eloquent\Builder $query) use ($inputTerm, $entity) { | ||
| 105 | + $query->where('name', 'like', '%'.$inputTerm .'%') | ||
| 106 | + ->orWhere($entity->textField, 'like', '%'.$inputTerm .'%'); | ||
| 107 | + }); | ||
| 108 | + } | ||
| 109 | + }); | ||
| 110 | + } | ||
| 111 | + | ||
| 112 | + $entitySelect->skip($page * $count)->take($count); | ||
| 79 | $query = $this->permissionService->enforceEntityRestrictions($entityType, $entitySelect, 'view'); | 113 | $query = $this->permissionService->enforceEntityRestrictions($entityType, $entitySelect, 'view'); |
| 80 | return $query->get(); | 114 | return $query->get(); |
| 81 | } | 115 | } |
| 82 | 116 | ||
| 117 | + | ||
| 118 | + /** | ||
| 119 | + * Parse a search string into components. | ||
| 120 | + * @param $searchString | ||
| 121 | + * @return array | ||
| 122 | + */ | ||
| 123 | + public function parseSearchString($searchString) | ||
| 124 | + { | ||
| 125 | + $terms = [ | ||
| 126 | + 'search' => [], | ||
| 127 | + 'exact' => [], | ||
| 128 | + 'tags' => [], | ||
| 129 | + 'filters' => [] | ||
| 130 | + ]; | ||
| 131 | + | ||
| 132 | + $patterns = [ | ||
| 133 | + 'exact' => '/"(.*?)"/', | ||
| 134 | + 'tags' => '/\[(.*?)\]/', | ||
| 135 | + 'filters' => '/\{(.*?)\}/' | ||
| 136 | + ]; | ||
| 137 | + | ||
| 138 | + foreach ($patterns as $termType => $pattern) { | ||
| 139 | + $matches = []; | ||
| 140 | + preg_match_all($pattern, $searchString, $matches); | ||
| 141 | + if (count($matches) > 0) { | ||
| 142 | + $terms[$termType] = $matches[1]; | ||
| 143 | + $searchString = preg_replace($pattern, '', $searchString); | ||
| 144 | + } | ||
| 145 | + } | ||
| 146 | + | ||
| 147 | + foreach (explode(' ', trim($searchString)) as $searchTerm) { | ||
| 148 | + if ($searchTerm !== '') $terms['search'][] = $searchTerm; | ||
| 149 | + } | ||
| 150 | + | ||
| 151 | + return $terms; | ||
| 152 | + } | ||
| 153 | + | ||
| 83 | /** | 154 | /** |
| 84 | * Get an entity instance via type. | 155 | * Get an entity instance via type. |
| 85 | * @param $type | 156 | * @param $type | ... | ... |
-
Please register or sign in to post a comment