]> BookStack Code Mirror - bookstack/blobdiff - app/Services/SearchService.php
Added ability to configure email sender name
[bookstack] / app / Services / SearchService.php
index 3d1d45c3b77d47ab356285f0f45ffa9c58b90115..6786c5cf498707a8691cc7292b632cf909f800fd 100644 (file)
@@ -83,7 +83,9 @@ class SearchService
         $total = 0;
 
         foreach ($entityTypesToSearch as $entityType) {
-            if (!in_array($entityType, $entityTypes)) continue;
+            if (!in_array($entityType, $entityTypes)) {
+                continue;
+            }
             $search = $this->searchEntityTable($terms, $entityType, $page, $count);
             $total += $this->searchEntityTable($terms, $entityType, $page, $count, true);
             $results = $results->merge($search);
@@ -92,7 +94,7 @@ class SearchService
         return [
             'total' => $total,
             'count' => count($results),
-            'results' => $results->sortByDesc('score')
+            'results' => $results->sortByDesc('score')->values()
         ];
     }
 
@@ -111,7 +113,9 @@ class SearchService
 
         $results = collect();
         foreach ($entityTypesToSearch as $entityType) {
-            if (!in_array($entityType, $entityTypes)) continue;
+            if (!in_array($entityType, $entityTypes)) {
+                continue;
+            }
             $search = $this->buildEntitySearchQuery($terms, $entityType)->where('book_id', '=', $bookId)->take(20)->get();
             $results = $results->merge($search);
         }
@@ -143,7 +147,9 @@ class SearchService
     public function searchEntityTable($terms, $entityType = 'page', $page = 1, $count = 20, $getCount = false)
     {
         $query = $this->buildEntitySearchQuery($terms, $entityType);
-        if ($getCount) return $query->count();
+        if ($getCount) {
+            return $query->count();
+        }
 
         $query = $query->skip(($page-1) * $count)->take($count);
         return $query->get();
@@ -164,12 +170,12 @@ class SearchService
         if (count($terms['search']) > 0) {
             $subQuery = $this->db->table('search_terms')->select('entity_id', 'entity_type', \DB::raw('SUM(score) as score'));
             $subQuery->where('entity_type', '=', 'BookStack\\' . ucfirst($entityType));
-            $subQuery->where(function(Builder $query) use ($terms) {
+            $subQuery->where(function (Builder $query) use ($terms) {
                 foreach ($terms['search'] as $inputTerm) {
                     $query->orWhere('term', 'like', $inputTerm .'%');
                 }
             })->groupBy('entity_type', 'entity_id');
-            $entitySelect->join(\DB::raw('(' . $subQuery->toSql() . ') as s'), function(JoinClause $join) {
+            $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);
@@ -177,7 +183,7 @@ class SearchService
 
         // Handle exact term matching
         if (count($terms['exact']) > 0) {
-            $entitySelect->where(function(\Illuminate\Database\Eloquent\Builder $query) use ($terms, $entity) {
+            $entitySelect->where(function (\Illuminate\Database\Eloquent\Builder $query) use ($terms, $entity) {
                 foreach ($terms['exact'] as $inputTerm) {
                     $query->where(function (\Illuminate\Database\Eloquent\Builder $query) use ($inputTerm, $entity) {
                         $query->where('name', 'like', '%'.$inputTerm .'%')
@@ -195,7 +201,9 @@ class SearchService
         // Handle filters
         foreach ($terms['filters'] as $filterTerm => $filterValue) {
             $functionName = camel_case('filter_' . $filterTerm);
-            if (method_exists($this, $functionName)) $this->$functionName($entitySelect, $entity, $filterValue);
+            if (method_exists($this, $functionName)) {
+                $this->$functionName($entitySelect, $entity, $filterValue);
+            }
         }
 
         return $this->permissionService->enforceEntityRestrictions($entityType, $entitySelect, 'view');
@@ -234,7 +242,9 @@ class SearchService
 
         // Parse standard terms
         foreach (explode(' ', trim($searchString)) as $searchTerm) {
-            if ($searchTerm !== '') $terms['search'][] = $searchTerm;
+            if ($searchTerm !== '') {
+                $terms['search'][] = $searchTerm;
+            }
         }
 
         // Split filter values out
@@ -267,15 +277,18 @@ class SearchService
      * @param string $tagTerm
      * @return mixed
      */
-    protected function applyTagSearch(\Illuminate\Database\Eloquent\Builder $query, $tagTerm) {
+    protected function applyTagSearch(\Illuminate\Database\Eloquent\Builder $query, $tagTerm)
+    {
         preg_match("/^(.*?)((".$this->getRegexEscapedOperators().")(.*?))?$/", $tagTerm, $tagSplit);
-        $query->whereHas('tags', function(\Illuminate\Database\Eloquent\Builder $query) use ($tagSplit) {
+        $query->whereHas('tags', function (\Illuminate\Database\Eloquent\Builder $query) use ($tagSplit) {
             $tagName = $tagSplit[1];
             $tagOperator = count($tagSplit) > 2 ? $tagSplit[3] : '';
             $tagValue = count($tagSplit) > 3 ? $tagSplit[4] : '';
             $validOperator = in_array($tagOperator, $this->queryOperators);
             if (!empty($tagOperator) && !empty($tagValue) && $validOperator) {
-                if (!empty($tagName)) $query->where('name', '=', $tagName);
+                if (!empty($tagName)) {
+                    $query->where('name', '=', $tagName);
+                }
                 if (is_numeric($tagValue) && $tagOperator !== 'like') {
                     // We have to do a raw sql query for this since otherwise PDO will quote the value and MySQL will
                     // search the value as a string which prevents being able to do number-based operations
@@ -323,7 +336,8 @@ class SearchService
      * Index multiple Entities at once
      * @param Entity[] $entities
      */
-    protected function indexEntities($entities) {
+    protected function indexEntities($entities)
+    {
         $terms = [];
         foreach ($entities as $entity) {
             $nameTerms = $this->generateTermArrayFromText($entity->name, 5);
@@ -382,11 +396,15 @@ class SearchService
     protected function generateTermArrayFromText($text, $scoreAdjustment = 1)
     {
         $tokenMap = []; // {TextToken => OccurrenceCount}
-        $splitText = explode(' ', $text);
-        foreach ($splitText as $token) {
-            if ($token === '') continue;
-            if (!isset($tokenMap[$token])) $tokenMap[$token] = 0;
+        $splitChars = " \n\t.,!?:;()[]{}<>`'\"";
+        $token = strtok($text, $splitChars);
+
+        while ($token !== false) {
+            if (!isset($tokenMap[$token])) {
+                $tokenMap[$token] = 0;
+            }
             $tokenMap[$token]++;
+            $token = strtok($splitChars);
         }
 
         $terms = [];
@@ -408,43 +426,63 @@ class SearchService
 
     protected function filterUpdatedAfter(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
     {
-        try { $date = date_create($input);
-        } catch (\Exception $e) {return;}
+        try {
+            $date = date_create($input);
+        } catch (\Exception $e) {
+            return;
+        }
         $query->where('updated_at', '>=', $date);
     }
 
     protected function filterUpdatedBefore(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
     {
-        try { $date = date_create($input);
-        } catch (\Exception $e) {return;}
+        try {
+            $date = date_create($input);
+        } catch (\Exception $e) {
+            return;
+        }
         $query->where('updated_at', '<', $date);
     }
 
     protected function filterCreatedAfter(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
     {
-        try { $date = date_create($input);
-        } catch (\Exception $e) {return;}
+        try {
+            $date = date_create($input);
+        } catch (\Exception $e) {
+            return;
+        }
         $query->where('created_at', '>=', $date);
     }
 
     protected function filterCreatedBefore(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
     {
-        try { $date = date_create($input);
-        } catch (\Exception $e) {return;}
+        try {
+            $date = date_create($input);
+        } catch (\Exception $e) {
+            return;
+        }
         $query->where('created_at', '<', $date);
     }
 
     protected function filterCreatedBy(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
     {
-        if (!is_numeric($input) && $input !== 'me') return;
-        if ($input === 'me') $input = user()->id;
+        if (!is_numeric($input) && $input !== 'me') {
+            return;
+        }
+        if ($input === 'me') {
+            $input = user()->id;
+        }
         $query->where('created_by', '=', $input);
     }
 
     protected function filterUpdatedBy(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
     {
-        if (!is_numeric($input) && $input !== 'me') return;
-        if ($input === 'me') $input = user()->id;
+        if (!is_numeric($input) && $input !== 'me') {
+            return;
+        }
+        if ($input === 'me') {
+            $input = user()->id;
+        }
         $query->where('updated_by', '=', $input);
     }
 
@@ -453,7 +491,10 @@ class SearchService
         $query->where('name', 'like', '%' .$input. '%');
     }
 
-    protected function filterInTitle(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input) {$this->filterInName($query, $model, $input);}
+    protected function filterInTitle(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
+    {
+        $this->filterInName($query, $model, $input);
+    }
 
     protected function filterInBody(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
     {
@@ -467,16 +508,37 @@ class SearchService
 
     protected function filterViewedByMe(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
     {
-        $query->whereHas('views', function($query) {
+        $query->whereHas('views', function ($query) {
             $query->where('user_id', '=', user()->id);
         });
     }
 
     protected function filterNotViewedByMe(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
     {
-        $query->whereDoesntHave('views', function($query) {
+        $query->whereDoesntHave('views', function ($query) {
             $query->where('user_id', '=', user()->id);
         });
     }
 
-}
\ No newline at end of file
+    protected function filterSortBy(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
+    {
+        $functionName = camel_case('sort_by_' . $input);
+        if (method_exists($this, $functionName)) {
+            $this->$functionName($query, $model);
+        }
+    }
+
+
+    /**
+     * Sorting filter options
+     */
+
+    protected function sortByLastCommented(\Illuminate\Database\Eloquent\Builder $query, Entity $model)
+    {
+        $commentsTable = $this->db->getTablePrefix() . 'comments';
+        $morphClass = str_replace('\\', '\\\\', $model->getMorphClass());
+        $commentQuery = $this->db->raw('(SELECT c1.entity_id, c1.entity_type, c1.created_at as last_commented FROM '.$commentsTable.' c1 LEFT JOIN '.$commentsTable.' c2 ON (c1.entity_id = c2.entity_id AND c1.entity_type = c2.entity_type AND c1.created_at < c2.created_at) WHERE c1.entity_type = \''. $morphClass .'\' AND c2.created_at IS NULL) as comments');
+
+        $query->join($commentQuery, $model->getTable() . '.id', '=', 'comments.entity_id')->orderBy('last_commented', 'desc');
+    }
+}