Toggle navigation
Toggle navigation
This project
Loading...
Sign in
Зуев Егор
/
wiki.dev
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Snippets
Network
Create a new issue
Commits
Issue Boards
Files
Commits
Network
Compare
Branches
Tags
Authored by
Dan Brown
2017-03-27 18:05:34 +0100
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
Commit
01cb22af37535d9d12d76a56413fdb645568972a
01cb22af
1 parent
33130533
Added tag searches and advanced filters to new search
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
144 additions
and
161 deletions
app/Repos/EntityRepo.php
app/Services/SearchService.php
resources/views/base.blade.php
routes/web.php
app/Repos/EntityRepo.php
View file @
01cb22a
...
...
@@ -65,12 +65,6 @@ class EntityRepo
protected
$searchService
;
/**
* Acceptable operators to be used in a query
* @var array
*/
protected
$queryOperators
=
[
'<='
,
'>='
,
'='
,
'<'
,
'>'
,
'like'
,
'!='
];
/**
* EntityRepo constructor.
* @param Book $book
* @param Chapter $chapter
...
...
@@ -370,56 +364,6 @@ class EntityRepo
->
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.
...
...
@@ -501,104 +445,7 @@ class EntityRepo
$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.
...
...
app/Services/SearchService.php
View file @
01cb22a
...
...
@@ -12,7 +12,6 @@ use Illuminate\Support\Collection;
class
SearchService
{
protected
$searchTerm
;
protected
$book
;
protected
$chapter
;
...
...
@@ -22,6 +21,12 @@ class SearchService
protected
$entities
;
/**
* Acceptable operators to be used in a query
* @var array
*/
protected
$queryOperators
=
[
'<='
,
'>='
,
'='
,
'<'
,
'>'
,
'like'
,
'!='
];
/**
* SearchService constructor.
* @param SearchTerm $searchTerm
* @param Book $book
...
...
@@ -55,11 +60,7 @@ class SearchService
*/
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
);
...
...
@@ -109,6 +110,19 @@ class SearchService
});
}
// 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
();
...
...
@@ -120,7 +134,7 @@ class SearchService
* @param $searchString
* @return array
*/
p
ublic
function
parseSearchString
(
$searchString
)
p
rotected
function
parseSearchString
(
$searchString
)
{
$terms
=
[
'search'
=>
[],
...
...
@@ -152,6 +166,50 @@ class SearchService
}
/**
* 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 Entity
...
...
@@ -258,4 +316,82 @@ class SearchService
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
...
...
resources/views/base.blade.php
View file @
01cb22a
...
...
@@ -47,7 +47,7 @@
</a>
</div>
<div
class=
"col-lg-4 col-sm-3 text-center"
>
<form
action=
"{{ baseUrl('/search
/all
') }}"
method=
"GET"
class=
"search-box"
>
<form
action=
"{{ baseUrl('/search') }}"
method=
"GET"
class=
"search-box"
>
<input
id=
"header-search-box-input"
type=
"text"
name=
"term"
tabindex=
"2"
value=
"{{ isset($searchTerm) ? $searchTerm : '' }}"
>
<button
id=
"header-search-box-button"
type=
"submit"
class=
"text-button"
><i
class=
"zmdi zmdi-search"
></i></button>
</form>
...
...
routes/web.php
View file @
01cb22a
...
...
@@ -123,7 +123,7 @@ Route::group(['middleware' => 'auth'], function () {
Route
::
get
(
'/link/{id}'
,
'PageController@redirectFromLink'
);
// Search
Route
::
get
(
'/search
/all
'
,
'SearchController@searchAll'
);
Route
::
get
(
'/search'
,
'SearchController@searchAll'
);
Route
::
get
(
'/search/pages'
,
'SearchController@searchPages'
);
Route
::
get
(
'/search/books'
,
'SearchController@searchBooks'
);
Route
::
get
(
'/search/chapters'
,
'SearchController@searchChapters'
);
...
...
Please
register
or
sign in
to post a comment