diff --git a/app/Entity.php b/app/Entity.php index 705444959..42323628a 100644 --- a/app/Entity.php +++ b/app/Entity.php @@ -98,7 +98,7 @@ abstract class Entity extends Model * @param string[] array $wheres * @return mixed */ - public static function fullTextSearch($fieldsToSearch, $terms, $wheres = []) + public static function fullTextSearchQuery($fieldsToSearch, $terms, $wheres = []) { $termString = ''; foreach ($terms as $term) { @@ -107,7 +107,7 @@ abstract class Entity extends Model $fields = implode(',', $fieldsToSearch); $termStringEscaped = \DB::connection()->getPdo()->quote($termString); $search = static::addSelect(\DB::raw('*, MATCH(name) AGAINST('.$termStringEscaped.' IN BOOLEAN MODE) AS title_relevance')); - $search = $search->whereRaw('MATCH(' . $fields . ') AGAINST(? IN BOOLEAN MODE)', [$termStringEscaped]); + $search = $search->whereRaw('MATCH(' . $fields . ') AGAINST(? IN BOOLEAN MODE)', [$termString]); // Add additional where terms foreach ($wheres as $whereTerm) { @@ -115,10 +115,13 @@ abstract class Entity extends Model } // Load in relations - if (!static::isA('book')) $search = $search->with('book'); - if (static::isA('page')) $search = $search->with('chapter'); + if (static::isA('page')) { + $search = $search->with('book', 'chapter', 'createdBy', 'updatedBy'); + } else if (static::isA('chapter')) { + $search = $search->with('book'); + } - return $search->orderBy('title_relevance', 'desc')->get(); + return $search->orderBy('title_relevance', 'desc'); } /** diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index 035de9fe6..e198dc767 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -42,11 +42,77 @@ class SearchController extends Controller return redirect()->back(); } $searchTerm = $request->get('term'); - $pages = $this->pageRepo->getBySearch($searchTerm); - $books = $this->bookRepo->getBySearch($searchTerm); - $chapters = $this->chapterRepo->getBySearch($searchTerm); + $paginationAppends = $request->only('term'); + $pages = $this->pageRepo->getBySearch($searchTerm, [], 20, $paginationAppends); + $books = $this->bookRepo->getBySearch($searchTerm, 10, $paginationAppends); + $chapters = $this->chapterRepo->getBySearch($searchTerm, [], 10, $paginationAppends); $this->setPageTitle('Search For ' . $searchTerm); - return view('search/all', ['pages' => $pages, 'books' => $books, 'chapters' => $chapters, 'searchTerm' => $searchTerm]); + return view('search/all', [ + 'pages' => $pages, + 'books' => $books, + 'chapters' => $chapters, + 'searchTerm' => $searchTerm + ]); + } + + /** + * Search only the pages in the system. + * @param Request $request + * @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View + */ + public function searchPages(Request $request) + { + if (!$request->has('term')) return redirect()->back(); + + $searchTerm = $request->get('term'); + $paginationAppends = $request->only('term'); + $pages = $this->pageRepo->getBySearch($searchTerm, [], 20, $paginationAppends); + $this->setPageTitle('Page Search For ' . $searchTerm); + return view('search/entity-search-list', [ + 'entities' => $pages, + 'title' => 'Page Search Results', + 'searchTerm' => $searchTerm + ]); + } + + /** + * Search only the chapters in the system. + * @param Request $request + * @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View + */ + public function searchChapters(Request $request) + { + if (!$request->has('term')) return redirect()->back(); + + $searchTerm = $request->get('term'); + $paginationAppends = $request->only('term'); + $chapters = $this->chapterRepo->getBySearch($searchTerm, [], 20, $paginationAppends); + $this->setPageTitle('Chapter Search For ' . $searchTerm); + return view('search/entity-search-list', [ + 'entities' => $chapters, + 'title' => 'Chapter Search Results', + 'searchTerm' => $searchTerm + ]); + } + + /** + * Search only the books in the system. + * @param Request $request + * @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View + */ + public function searchBooks(Request $request) + { + if (!$request->has('term')) return redirect()->back(); + + $searchTerm = $request->get('term'); + $paginationAppends = $request->only('term'); + $books = $this->bookRepo->getBySearch($searchTerm, 20, $paginationAppends); + $this->setPageTitle('Book Search For ' . $searchTerm); + return view('search/entity-search-list', [ + 'entities' => $books, + 'title' => 'Book Search Results', + 'searchTerm' => $searchTerm + ]); } /** diff --git a/app/Http/routes.php b/app/Http/routes.php index f753ad915..36cf2a19f 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -74,6 +74,9 @@ Route::group(['middleware' => 'auth'], function () { // Search Route::get('/search/all', 'SearchController@searchAll'); + Route::get('/search/pages', 'SearchController@searchPages'); + Route::get('/search/books', 'SearchController@searchBooks'); + Route::get('/search/chapters', 'SearchController@searchChapters'); Route::get('/search/book/{bookId}', 'SearchController@searchBook'); // Other Pages diff --git a/app/Repos/BookRepo.php b/app/Repos/BookRepo.php index a57050ce2..363a3b250 100644 --- a/app/Repos/BookRepo.php +++ b/app/Repos/BookRepo.php @@ -218,12 +218,15 @@ class BookRepo /** * Get books by search term. * @param $term + * @param int $count + * @param array $paginationAppends * @return mixed */ - public function getBySearch($term) + public function getBySearch($term, $count = 20, $paginationAppends = []) { $terms = explode(' ', $term); - $books = $this->book->fullTextSearch(['name', 'description'], $terms); + $books = $this->book->fullTextSearchQuery(['name', 'description'], $terms) + ->paginate($count)->appends($paginationAppends); $words = join('|', explode(' ', preg_quote(trim($term), '/'))); foreach ($books as $book) { //highlight diff --git a/app/Repos/ChapterRepo.php b/app/Repos/ChapterRepo.php index 3824e6982..bba6cbe7a 100644 --- a/app/Repos/ChapterRepo.php +++ b/app/Repos/ChapterRepo.php @@ -125,12 +125,15 @@ class ChapterRepo * Get chapters by the given search term. * @param $term * @param array $whereTerms + * @param int $count + * @param array $paginationAppends * @return mixed */ - public function getBySearch($term, $whereTerms = []) + public function getBySearch($term, $whereTerms = [], $count = 20, $paginationAppends = []) { $terms = explode(' ', $term); - $chapters = $this->chapter->fullTextSearch(['name', 'description'], $terms, $whereTerms); + $chapters = $this->chapter->fullTextSearchQuery(['name', 'description'], $terms, $whereTerms) + ->paginate($count)->appends($paginationAppends); $words = join('|', explode(' ', preg_quote(trim($term), '/'))); foreach ($chapters as $chapter) { //highlight diff --git a/app/Repos/PageRepo.php b/app/Repos/PageRepo.php index 0c6f4a256..494e1e9ee 100644 --- a/app/Repos/PageRepo.php +++ b/app/Repos/PageRepo.php @@ -175,14 +175,17 @@ class PageRepo /** * Gets pages by a search term. * Highlights page content for showing in results. - * @param string $term + * @param string $term * @param array $whereTerms + * @param int $count + * @param array $paginationAppends * @return mixed */ - public function getBySearch($term, $whereTerms = []) + public function getBySearch($term, $whereTerms = [], $count = 20, $paginationAppends = []) { $terms = explode(' ', $term); - $pages = $this->page->fullTextSearch(['name', 'text'], $terms, $whereTerms); + $pages = $this->page->fullTextSearchQuery(['name', 'text'], $terms, $whereTerms) + ->paginate($count)->appends($paginationAppends); // Add highlights to page text. $words = join('|', explode(' ', preg_quote(trim($term), '/'))); diff --git a/resources/views/chapters/list-item.blade.php b/resources/views/chapters/list-item.blade.php index 91f5ce688..beaf1cac2 100644 --- a/resources/views/chapters/list-item.blade.php +++ b/resources/views/chapters/list-item.blade.php @@ -10,7 +10,7 @@

{{ $chapter->getExcerpt() }}

@endif - @if(count($chapter->pages) > 0 && !isset($hidePages)) + @if(!isset($hidePages) && count($chapter->pages) > 0)

{{ count($chapter->pages) }} Pages

@foreach($chapter->pages as $page) diff --git a/resources/views/pages/list-item.blade.php b/resources/views/pages/list-item.blade.php index 5271680ab..00664ed6f 100644 --- a/resources/views/pages/list-item.blade.php +++ b/resources/views/pages/list-item.blade.php @@ -16,10 +16,10 @@ Last updated {{ $page->updated_at->diffForHumans() }} @if($page->updatedBy)by {{$page->updatedBy->name}} @endif
- {{ $page->book->getExcerpt(30) }} + {{ $page->book->getShortName(30) }}
@if($page->chapter) - {{ $page->chapter->getExcerpt(30) }} + {{ $page->chapter->getShortName(30) }} @else Page is not in a chapter @endif diff --git a/resources/views/search/all.blade.php b/resources/views/search/all.blade.php index e944ca03a..f2385a5d3 100644 --- a/resources/views/search/all.blade.php +++ b/resources/views/search/all.blade.php @@ -6,41 +6,36 @@

Search Results    {{$searchTerm}}

+

+ View all matched pages + + @if(count($chapters) > 0) +      + View all matched chapters + @endif + + @if(count($books) > 0) +      + View all matched books + @endif +

-

Matching Pages

-
- @if(count($pages) > 0) - @foreach($pages as $page) - @include('pages/list-item', ['page' => $page, 'style' => 'detailed']) -
- @endforeach - @else -

No pages matched this search

- @endif -
+

Matching Pages

+ @include('partials/entity-list', ['entities' => $pages, 'style' => 'detailed'])
@if(count($books) > 0) -

Matching Books

-
- @foreach($books as $book) - @include('books/list-item', ['book' => $book]) -
- @endforeach -
+

Matching Books

+ @include('partials/entity-list', ['entities' => $books]) @endif @if(count($chapters) > 0) -

Matching Chapters

-
- @foreach($chapters as $chapter) - @include('chapters/list-item', ['chapter' => $chapter, 'hidePages' => true]) - @endforeach -
+

Matching Chapters

+ @include('partials/entity-list', ['entities' => $chapters]) @endif
diff --git a/resources/views/search/entity-search-list.blade.php b/resources/views/search/entity-search-list.blade.php new file mode 100644 index 000000000..5ca6492ef --- /dev/null +++ b/resources/views/search/entity-search-list.blade.php @@ -0,0 +1,18 @@ +@extends('base') + +@section('content') + +
+
+ +
+

{{ $title }} {{$searchTerm}}

+ @include('partials.entity-list', ['entities' => $entities, 'style' => 'detailed']) + {!! $entities->links() !!} +
+ +
+ +
+
+@stop \ No newline at end of file diff --git a/tests/EntitySearchTest.php b/tests/EntitySearchTest.php new file mode 100644 index 000000000..6b313e7b8 --- /dev/null +++ b/tests/EntitySearchTest.php @@ -0,0 +1,85 @@ +first(); + $page = $book->pages->first(); + + $this->asAdmin() + ->visit('/') + ->type($page->name, 'term') + ->press('header-search-box-button') + ->see('Search Results') + ->see($page->name) + ->click($page->name) + ->seePageIs($page->getUrl()); + } + + public function test_invalid_page_search() + { + $this->asAdmin() + ->visit('/') + ->type('

test

', 'term') + ->press('header-search-box-button') + ->see('Search Results') + ->seeStatusCode(200); + } + + public function test_empty_search_redirects_back() + { + $this->asAdmin() + ->visit('/') + ->visit('/search/all') + ->seePageIs('/'); + } + + public function test_book_search() + { + $book = \BookStack\Book::all()->first(); + $page = $book->pages->last(); + $chapter = $book->chapters->last(); + + $this->asAdmin() + ->visit('/search/book/' . $book->id . '?term=' . urlencode($page->name)) + ->see($page->name) + + ->visit('/search/book/' . $book->id . '?term=' . urlencode($chapter->name)) + ->see($chapter->name); + } + + public function test_empty_book_search_redirects_back() + { + $book = \BookStack\Book::all()->first(); + $this->asAdmin() + ->visit('/books') + ->visit('/search/book/' . $book->id . '?term=') + ->seePageIs('/books'); + } + + + public function test_pages_search_listing() + { + $page = \BookStack\Page::all()->last(); + $this->asAdmin()->visit('/search/pages?term=' . $page->name) + ->see('Page Search Results')->see('.entity-list', $page->name); + } + + public function test_chapters_search_listing() + { + $chapter = \BookStack\Chapter::all()->last(); + $this->asAdmin()->visit('/search/chapters?term=' . $chapter->name) + ->see('Chapter Search Results')->seeInElement('.entity-list', $chapter->name); + } + + public function test_books_search_listing() + { + $book = \BookStack\Book::all()->last(); + $this->asAdmin()->visit('/search/books?term=' . $book->name) + ->see('Book Search Results')->see('.entity-list', $book->name); + } +} diff --git a/tests/EntityTest.php b/tests/EntityTest.php index 0c0a1dee4..c04bc6d7c 100644 --- a/tests/EntityTest.php +++ b/tests/EntityTest.php @@ -155,63 +155,6 @@ class EntityTest extends TestCase return $book; } - public function test_page_search() - { - $book = \BookStack\Book::all()->first(); - $page = $book->pages->first(); - - $this->asAdmin() - ->visit('/') - ->type($page->name, 'term') - ->press('header-search-box-button') - ->see('Search Results') - ->see($page->name) - ->click($page->name) - ->seePageIs($page->getUrl()); - } - - public function test_invalid_page_search() - { - $this->asAdmin() - ->visit('/') - ->type('

test

', 'term') - ->press('header-search-box-button') - ->see('Search Results') - ->seeStatusCode(200); - } - - public function test_empty_search_redirects_back() - { - $this->asAdmin() - ->visit('/') - ->visit('/search/all') - ->seePageIs('/'); - } - - public function test_book_search() - { - $book = \BookStack\Book::all()->first(); - $page = $book->pages->last(); - $chapter = $book->chapters->last(); - - $this->asAdmin() - ->visit('/search/book/' . $book->id . '?term=' . urlencode($page->name)) - ->see($page->name) - - ->visit('/search/book/' . $book->id . '?term=' . urlencode($chapter->name)) - ->see($chapter->name); - } - - public function test_empty_book_search_redirects_back() - { - $book = \BookStack\Book::all()->first(); - $this->asAdmin() - ->visit('/books') - ->visit('/search/book/' . $book->id . '?term=') - ->seePageIs('/books'); - } - - public function test_entities_viewable_after_creator_deletion() { // Create required assets and revisions