]> BookStack Code Mirror - bookstack/commitdiff
Added search term parsing and exact term matches
authorDan Brown <redacted>
Mon, 27 Mar 2017 10:57:33 +0000 (11:57 +0100)
committerDan Brown <redacted>
Mon, 27 Mar 2017 10:57:33 +0000 (11:57 +0100)
app/Entity.php
app/Page.php
app/Services/SearchService.php

index ae802e31fcf86e2c63498a67963c1f1e148d3729..52b214eb3ab80ea9296af4c968dc207a8ae3cc1b 100644 (file)
@@ -4,7 +4,7 @@
 class Entity extends Ownable
 {
 
-    protected $textField = 'description';
+    public $textField = 'description';
 
     /**
      * Compares this entity to another given entity.
index d53e02a0bab041cd882f91aaaa560f62a8abf36b..c9823e7e4ccfcb65dce71328f28fa0f4f04d40a3 100644 (file)
@@ -8,7 +8,7 @@ class Page extends Entity
     protected $simpleAttributes = ['name', 'id', 'slug'];
 
     protected $with = ['book'];
-    protected $textField = 'text';
+    public $textField = 'text';
 
     /**
      * Converts this page into a simplified array.
index ef11de72864f579c3baa25291f2a41e1c666517a..2df02bc3e254d780817fafb52e294eb2103d18e3 100644 (file)
@@ -6,7 +6,9 @@ use BookStack\Entity;
 use BookStack\Page;
 use BookStack\SearchTerm;
 use Illuminate\Database\Connection;
+use Illuminate\Database\Query\Builder;
 use Illuminate\Database\Query\JoinClause;
+use Illuminate\Support\Collection;
 
 class SearchService
 {
@@ -43,11 +45,18 @@ class SearchService
         $this->permissionService = $permissionService;
     }
 
+    /**
+     * Search all entities in the system.
+     * @param $searchString
+     * @param string $entityType
+     * @param int $page
+     * @param int $count
+     * @return Collection
+     */
     public function searchEntities($searchString, $entityType = 'all', $page = 0, $count = 20)
     {
         // TODO - Add Tag Searches
         // TODO - Add advanced custom column searches
-        // TODO - Add exact match searches ("")
         // TODO - Check drafts don't show up in results
         // TODO - Move search all page to just /search?term=cat
 
@@ -59,27 +68,89 @@ class SearchService
        return collect($bookSearch)->merge($chapterSearch)->merge($pageSearch)->sortByDesc('score');
     }
 
+    /**
+     * Search across a particular entity type.
+     * @param string $searchString
+     * @param string $entityType
+     * @param int $page
+     * @param int $count
+     * @return \Illuminate\Database\Eloquent\Collection|static[]
+     */
     public function searchEntityTable($searchString, $entityType = 'page', $page = 0, $count = 20)
     {
-        $termArray = explode(' ', $searchString);
-
-        $subQuery = $this->db->table('search_terms')->select('entity_id', 'entity_type', \DB::raw('SUM(score) as score'));
-        $subQuery->where(function($query) use ($termArray) {
-            foreach ($termArray as $inputTerm) {
-                $query->orWhere('term', 'like', $inputTerm .'%');
-            }
-        });
+        $searchTerms = $this->parseSearchString($searchString);
 
         $entity = $this->getEntity($entityType);
-        $subQuery = $subQuery->groupBy('entity_type', 'entity_id');
-        $entitySelect = $entity->newQuery()->join(\DB::raw('(' . $subQuery->toSql() . ') as s'), function(JoinClause $join) {
-            $join->on('id', '=', 'entity_id');
-        })->selectRaw($entity->getTable().'.*, s.score')->orderBy('score', 'desc')->skip($page * $count)->take($count);
-        $entitySelect->mergeBindings($subQuery);
+        $entitySelect = $entity->newQuery();
+
+        // Handle normal search terms
+        if (count($searchTerms['search']) > 0) {
+            $subQuery = $this->db->table('search_terms')->select('entity_id', 'entity_type', \DB::raw('SUM(score) as score'));
+            $subQuery->where(function(Builder $query) use ($searchTerms) {
+                foreach ($searchTerms['search'] as $inputTerm) {
+                    $query->orWhere('term', 'like', $inputTerm .'%');
+                }
+            })->groupBy('entity_type', 'entity_id');
+            $entitySelect->join(\DB::raw('(' . $subQuery->toSql() . ') as s'), function(JoinClause $join) {
+                $join->on('id', '=', 'entity_id');
+            })->selectRaw($entity->getTable().'.*, s.score')->orderBy('score', 'desc');
+            $entitySelect->mergeBindings($subQuery);
+        }
+
+        // Handle exact term matching
+        if (count($searchTerms['exact']) > 0) {
+            $entitySelect->where(function(\Illuminate\Database\Eloquent\Builder $query) use ($searchTerms, $entity) {
+                foreach ($searchTerms['exact'] as $inputTerm) {
+                    $query->where(function (\Illuminate\Database\Eloquent\Builder $query) use ($inputTerm, $entity) {
+                        $query->where('name', 'like', '%'.$inputTerm .'%')
+                            ->orWhere($entity->textField, 'like', '%'.$inputTerm .'%');
+                    });
+                }
+            });
+        }
+
+        $entitySelect->skip($page * $count)->take($count);
         $query = $this->permissionService->enforceEntityRestrictions($entityType, $entitySelect, 'view');
         return $query->get();
     }
 
+
+    /**
+     * Parse a search string into components.
+     * @param $searchString
+     * @return array
+     */
+    public function parseSearchString($searchString)
+    {
+        $terms = [
+            'search' => [],
+            'exact' => [],
+            'tags' => [],
+            'filters' => []
+        ];
+
+        $patterns = [
+            'exact' => '/"(.*?)"/',
+            'tags' => '/\[(.*?)\]/',
+            'filters' => '/\{(.*?)\}/'
+        ];
+
+        foreach ($patterns as $termType => $pattern) {
+            $matches = [];
+            preg_match_all($pattern, $searchString, $matches);
+            if (count($matches) > 0) {
+                $terms[$termType] = $matches[1];
+                $searchString = preg_replace($pattern, '', $searchString);
+            }
+        }
+
+        foreach (explode(' ', trim($searchString)) as $searchTerm) {
+            if ($searchTerm !== '') $terms['search'][] = $searchTerm;
+        }
+
+        return $terms;
+    }
+
     /**
      * Get an entity instance via type.
      * @param $type