*/
protected $searchService;
- /**
- * Acceptable operators to be used in a query
- * @var array
- */
- protected $queryOperators = ['<=', '>=', '=', '<', '>', 'like', '!='];
-
/**
* EntityRepo constructor.
* @param Book $book
->orderBy('draft', 'DESC')->orderBy('priority', 'ASC')->get();
}
- /**
- * Search entities of a type via a given query.
- * @param string $type
- * @param string $term
- * @param array $whereTerms
- * @param int $count
- * @param array $paginationAppends
- * @return mixed
- */
- public function getBySearch($type, $term, $whereTerms = [], $count = 20, $paginationAppends = [])
- {
- $terms = $this->prepareSearchTerms($term);
- $q = $this->permissionService->enforceEntityRestrictions($type, $this->getEntity($type)->fullTextSearchQuery($terms, $whereTerms));
- $q = $this->addAdvancedSearchQueries($q, $term);
- $entities = $q->paginate($count)->appends($paginationAppends);
- $words = join('|', explode(' ', preg_quote(trim($term), '/')));
-
- // Highlight page content
- if ($type === 'page') {
- //lookahead/behind assertions ensures cut between words
- $s = '\s\x00-/:-@\[-`{-~'; //character set for start/end of words
-
- foreach ($entities as $page) {
- preg_match_all('#(?<=[' . $s . ']).{1,30}((' . $words . ').{1,30})+(?=[' . $s . '])#uis', $page->text, $matches, PREG_SET_ORDER);
- //delimiter between occurrences
- $results = [];
- foreach ($matches as $line) {
- $results[] = htmlspecialchars($line[0], 0, 'UTF-8');
- }
- $matchLimit = 6;
- if (count($results) > $matchLimit) $results = array_slice($results, 0, $matchLimit);
- $result = join('... ', $results);
-
- //highlight
- $result = preg_replace('#' . $words . '#iu', "<span class=\"highlight\">\$0</span>", $result);
- if (strlen($result) < 5) $result = $page->getExcerpt(80);
-
- $page->searchSnippet = $result;
- }
- return $entities;
- }
-
- // Highlight chapter/book content
- foreach ($entities as $entity) {
- //highlight
- $result = preg_replace('#' . $words . '#iu', "<span class=\"highlight\">\$0</span>", $entity->getExcerpt(100));
- $entity->searchSnippet = $result;
- }
- return $entities;
- }
/**
* Get the next sequential priority for a new child element in the given book.
$this->permissionService->buildJointPermissionsForEntity($entity);
}
- /**
- * Prepare a string of search terms by turning
- * it into an array of terms.
- * Keeps quoted terms together.
- * @param $termString
- * @return array
- */
- public function prepareSearchTerms($termString)
- {
- $termString = $this->cleanSearchTermString($termString);
- preg_match_all('/(".*?")/', $termString, $matches);
- $terms = [];
- if (count($matches[1]) > 0) {
- foreach ($matches[1] as $match) {
- $terms[] = $match;
- }
- $termString = trim(preg_replace('/"(.*?)"/', '', $termString));
- }
- if (!empty($termString)) $terms = array_merge($terms, explode(' ', $termString));
- return $terms;
- }
- /**
- * Removes any special search notation that should not
- * be used in a full-text search.
- * @param $termString
- * @return mixed
- */
- protected function cleanSearchTermString($termString)
- {
- // Strip tag searches
- $termString = preg_replace('/\[.*?\]/', '', $termString);
- // Reduced multiple spacing into single spacing
- $termString = preg_replace("/\s{2,}/", " ", $termString);
- return $termString;
- }
-
- /**
- * Get the available query operators as a regex escaped list.
- * @return mixed
- */
- protected function getRegexEscapedOperators()
- {
- $escapedOperators = [];
- foreach ($this->queryOperators as $operator) {
- $escapedOperators[] = preg_quote($operator);
- }
- return join('|', $escapedOperators);
- }
-
- /**
- * Parses advanced search notations and adds them to the db query.
- * @param $query
- * @param $termString
- * @return mixed
- */
- protected function addAdvancedSearchQueries($query, $termString)
- {
- $escapedOperators = $this->getRegexEscapedOperators();
- // Look for tag searches
- preg_match_all("/\[(.*?)((${escapedOperators})(.*?))?\]/", $termString, $tags);
- if (count($tags[0]) > 0) {
- $this->applyTagSearches($query, $tags);
- }
-
- return $query;
- }
-
- /**
- * Apply extracted tag search terms onto a entity query.
- * @param $query
- * @param $tags
- * @return mixed
- */
- protected function applyTagSearches($query, $tags) {
- $query->where(function($query) use ($tags) {
- foreach ($tags[1] as $index => $tagName) {
- $query->whereHas('tags', function($query) use ($tags, $index, $tagName) {
- $tagOperator = $tags[3][$index];
- $tagValue = $tags[4][$index];
- if (!empty($tagOperator) && !empty($tagValue) && in_array($tagOperator, $this->queryOperators)) {
- 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
- // on the tag values. We ensure it has a numeric value and then cast it just to be sure.
- $tagValue = (float) trim($query->getConnection()->getPdo()->quote($tagValue), "'");
- $query->where('name', '=', $tagName)->whereRaw("value ${tagOperator} ${tagValue}");
- } else {
- $query->where('name', '=', $tagName)->where('value', $tagOperator, $tagValue);
- }
- } else {
- $query->where('name', '=', $tagName);
- }
- });
- }
- });
- return $query;
- }
/**
* Create a new entity from request input.
class SearchService
{
-
protected $searchTerm;
protected $book;
protected $chapter;
protected $permissionService;
protected $entities;
+ /**
+ * Acceptable operators to be used in a query
+ * @var array
+ */
+ protected $queryOperators = ['<=', '>=', '=', '<', '>', 'like', '!='];
+
/**
* SearchService constructor.
* @param SearchTerm $searchTerm
*/
public function searchEntities($searchString, $entityType = 'all', $page = 0, $count = 20)
{
- // TODO - Add Tag Searches
- // TODO - Add advanced custom column searches
// TODO - Check drafts don't show up in results
- // TODO - Move search all page to just /search?term=cat
-
if ($entityType !== 'all') return $this->searchEntityTable($searchString, $entityType, $page, $count);
$bookSearch = $this->searchEntityTable($searchString, 'book', $page, $count);
});
}
+ // Handle tag searches
+ foreach ($searchTerms['tags'] as $inputTerm) {
+ $this->applyTagSearch($entitySelect, $inputTerm);
+ }
+
+ // Handle filters
+ foreach ($searchTerms['filters'] as $filterTerm) {
+ $splitTerm = explode(':', $filterTerm);
+ $functionName = camel_case('filter_' . $splitTerm[0]);
+ $param = count($splitTerm) > 1 ? $splitTerm[1] : '';
+ if (method_exists($this, $functionName)) $this->$functionName($entitySelect, $entity, $param);
+ }
+
$entitySelect->skip($page * $count)->take($count);
$query = $this->permissionService->enforceEntityRestrictions($entityType, $entitySelect, 'view');
return $query->get();
* @param $searchString
* @return array
*/
- public function parseSearchString($searchString)
+ protected function parseSearchString($searchString)
{
$terms = [
'search' => [],
return $terms;
}
+ /**
+ * Get the available query operators as a regex escaped list.
+ * @return mixed
+ */
+ protected function getRegexEscapedOperators()
+ {
+ $escapedOperators = [];
+ foreach ($this->queryOperators as $operator) {
+ $escapedOperators[] = preg_quote($operator);
+ }
+ return join('|', $escapedOperators);
+ }
+
+ /**
+ * Apply a tag search term onto a entity query.
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @param string $tagTerm
+ * @return mixed
+ */
+ 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) {
+ $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 (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
+ // on the tag values. We ensure it has a numeric value and then cast it just to be sure.
+ $tagValue = (float) trim($query->getConnection()->getPdo()->quote($tagValue), "'");
+ $query->whereRaw("value ${tagOperator} ${tagValue}");
+ } else {
+ $query->where('value', $tagOperator, $tagValue);
+ }
+ } else {
+ $query->where('name', '=', $tagName);
+ }
+ });
+ return $query;
+ }
+
/**
* Get an entity instance via type.
* @param $type
return $terms;
}
+
+
+
+ /**
+ * Custom entity search filters
+ */
+
+ protected function filterUpdatedAfter(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
+ {
+ 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;}
+ $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;}
+ $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;}
+ $query->where('created_at', '<', $date);
+ }
+
+ protected function filterCreatedBy(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
+ {
+ if (!is_numeric($input)) return;
+ $query->where('created_by', '=', $input);
+ }
+
+ protected function filterUpdatedBy(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
+ {
+ if (!is_numeric($input)) return;
+ $query->where('updated_by', '=', $input);
+ }
+
+ protected function filterInName(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
+ {
+ $query->where('name', 'like', '%' .$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)
+ {
+ $query->where($model->textField, 'like', '%' .$input. '%');
+ }
+
+ protected function filterIsRestricted(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
+ {
+ $query->where('restricted', '=', true);
+ }
+
+ protected function filterViewedByMe(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
+ {
+ $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->where('user_id', '=', user()->id);
+ });
+ }
+
}
\ No newline at end of file