From 61577cf6bf2419d5bce5adc31a636a623118bfa3 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 21 Feb 2016 12:53:58 +0000 Subject: [PATCH] Added entity-specific search results pages. Cleaned & Fixed search results bugs Added search result pages for pages, chapters and books. Limited the results on the global search as it just listed out an infinate amount. Fixed styling on new detailed page listings and also removed the 'bars' from the side to create a cleaner view. Fixed bad sql fulltext query format that may have thrown off searches. Reduced the number of database queries down a thousand or so. --- app/Entity.php | 13 +-- app/Http/Controllers/SearchController.php | 74 +++++++++++++++- app/Http/routes.php | 3 + app/Repos/BookRepo.php | 7 +- app/Repos/ChapterRepo.php | 7 +- app/Repos/PageRepo.php | 9 +- resources/views/chapters/list-item.blade.php | 2 +- resources/views/pages/list-item.blade.php | 4 +- resources/views/search/all.blade.php | 43 +++++----- .../views/search/entity-search-list.blade.php | 18 ++++ tests/EntitySearchTest.php | 85 +++++++++++++++++++ tests/EntityTest.php | 57 ------------- 12 files changed, 222 insertions(+), 100 deletions(-) create mode 100644 resources/views/search/entity-search-list.blade.php create mode 100644 tests/EntitySearchTest.php 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