]> BookStack Code Mirror - bookstack/commitdiff
Added entity-specific search results pages. Cleaned & Fixed search results bugs
authorDan Brown <redacted>
Sun, 21 Feb 2016 12:53:58 +0000 (12:53 +0000)
committerDan Brown <redacted>
Sun, 21 Feb 2016 12:53:58 +0000 (12:53 +0000)
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.

12 files changed:
app/Entity.php
app/Http/Controllers/SearchController.php
app/Http/routes.php
app/Repos/BookRepo.php
app/Repos/ChapterRepo.php
app/Repos/PageRepo.php
resources/views/chapters/list-item.blade.php
resources/views/pages/list-item.blade.php
resources/views/search/all.blade.php
resources/views/search/entity-search-list.blade.php [new file with mode: 0644]
tests/EntitySearchTest.php [new file with mode: 0644]
tests/EntityTest.php

index 705444959dc382897a5ef3a6d25ba08b08c2be7c..42323628ac8c3f09319ac34aa681a6580ff34339 100644 (file)
@@ -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');
     }
 
     /**
index 035de9fe6af8286f408c5aef61624b9760a0ba6f..e198dc767913b9ac2f3ae5ffb7b8bdae00b5056f 100644 (file)
@@ -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
+        ]);
     }
 
     /**
index f753ad9150db7a52f8f560265a049c1b9ca07d35..36cf2a19f63019031f948855911c1ff0ab7fc289 100644 (file)
@@ -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
index a57050ce2bede8748ee864d42706e8b8d4b90c62..363a3b25053a613564670fa3ff3fa3311808c909 100644 (file)
@@ -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
index 3824e6982ab008047962862ed883c4fd68288a8d..bba6cbe7a134c6ac66843376bda7f279f89b46c6 100644 (file)
@@ -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
index 0c6f4a256f6b1693dc7deb79156b88c08f2b5fa7..494e1e9eec62cc7ba779dd8caf10ff5ab342845b 100644 (file)
@@ -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), '/')));
index 91f5ce68847bacb586d56b8d2d7832ab7a94c359..beaf1cac2fbb59267fc0810dbddd10ee15dbbcd8 100644 (file)
@@ -10,7 +10,7 @@
         <p class="text-muted">{{ $chapter->getExcerpt() }}</p>
     @endif
 
-    @if(count($chapter->pages) > 0 && !isset($hidePages))
+    @if(!isset($hidePages) && count($chapter->pages) > 0)
         <p class="text-muted chapter-toggle"><i class="zmdi zmdi-caret-right"></i> <i class="zmdi zmdi-file-text"></i> <span>{{ count($chapter->pages) }} Pages</span></p>
         <div class="inset-list">
             @foreach($chapter->pages as $page)
index 5271680ab04e029b626e90bf1931eeabcbd88bb8..00664ed6f1581537a6a85521e11f4ea43c6c4fec 100644 (file)
                 Last updated {{ $page->updated_at->diffForHumans() }} @if($page->updatedBy)by {{$page->updatedBy->name}} @endif
             </div>
             <div class="col-md-8">
-                <a class="text-book" href="{{ $page->book->getUrl() }}"><i class="zmdi zmdi-book"></i>{{ $page->book->getExcerpt(30) }}</a>
+                <a class="text-book" href="{{ $page->book->getUrl() }}"><i class="zmdi zmdi-book"></i>{{ $page->book->getShortName(30) }}</a>
                 <br>
                 @if($page->chapter)
-                    <a class="text-chapter" href="{{ $page->chapter->getUrl() }}"><i class="zmdi zmdi-collection-bookmark"></i>{{ $page->chapter->getExcerpt(30) }}</a>
+                    <a class="text-chapter" href="{{ $page->chapter->getUrl() }}"><i class="zmdi zmdi-collection-bookmark"></i>{{ $page->chapter->getShortName(30) }}</a>
                 @else
                     <i class="zmdi zmdi-collection-bookmark"></i> Page is not in a chapter
                 @endif
index e944ca03a4ca94a5359e3ff7d8ace0a99aa98d0d..f2385a5d3f08077f88aa77623652168b7d7c8651 100644 (file)
@@ -6,41 +6,36 @@
 
         <h1>Search Results&nbsp;&nbsp;&nbsp; <span class="text-muted">{{$searchTerm}}</span></h1>
 
+        <p>
+            <a href="/search/pages?term={{$searchTerm}}" class="text-page"><i class="zmdi zmdi-file-text"></i>View all matched pages</a>
+
+            @if(count($chapters) > 0)
+                &nbsp; &nbsp;&nbsp;
+                <a href="/search/chapters?term={{$searchTerm}}" class="text-chapter"><i class="zmdi zmdi-collection-bookmark"></i>View all matched chapters</a>
+            @endif
+
+            @if(count($books) > 0)
+                &nbsp; &nbsp;&nbsp;
+                <a href="/search/books?term={{$searchTerm}}" class="text-book"><i class="zmdi zmdi-book"></i>View all matched books</a>
+            @endif
+        </p>
         <div class="row">
 
             <div class="col-md-6">
-                <h3>Matching Pages</h3>
-                <div class="page-list">
-                    @if(count($pages) > 0)
-                        @foreach($pages as $page)
-                            @include('pages/list-item', ['page' => $page, 'style' => 'detailed'])
-                            <hr>
-                        @endforeach
-                    @else
-                        <p class="text-muted">No pages matched this search</p>
-                    @endif
-                </div>
+                <h3><a href="/search/pages?term={{$searchTerm}}" class="no-color">Matching Pages</a></h3>
+                @include('partials/entity-list', ['entities' => $pages, 'style' => 'detailed'])
             </div>
 
             <div class="col-md-5 col-md-offset-1">
 
                 @if(count($books) > 0)
-                    <h3>Matching Books</h3>
-                    <div class="page-list">
-                        @foreach($books as $book)
-                            @include('books/list-item', ['book' => $book])
-                            <hr>
-                        @endforeach
-                    </div>
+                    <h3><a href="/search/books?term={{$searchTerm}}" class="no-color">Matching Books</a></h3>
+                    @include('partials/entity-list', ['entities' => $books])
                 @endif
 
                 @if(count($chapters) > 0)
-                    <h3>Matching Chapters</h3>
-                    <div class="page-list">
-                        @foreach($chapters as $chapter)
-                            @include('chapters/list-item', ['chapter' => $chapter, 'hidePages' => true])
-                        @endforeach
-                    </div>
+                    <h3><a href="/search/chapters?term={{$searchTerm}}" class="no-color">Matching Chapters</a></h3>
+                    @include('partials/entity-list', ['entities' => $chapters])
                 @endif
 
             </div>
diff --git a/resources/views/search/entity-search-list.blade.php b/resources/views/search/entity-search-list.blade.php
new file mode 100644 (file)
index 0000000..5ca6492
--- /dev/null
@@ -0,0 +1,18 @@
+@extends('base')
+
+@section('content')
+
+    <div class="container">
+        <div class="row">
+
+            <div class="col-sm-7">
+                <h1>{{ $title }} <small>{{$searchTerm}}</small></h1>
+                @include('partials.entity-list', ['entities' => $entities, 'style' => 'detailed'])
+                {!! $entities->links() !!}
+            </div>
+
+            <div class="col-sm-4 col-sm-offset-1"></div>
+
+        </div>
+    </div>
+@stop
\ No newline at end of file
diff --git a/tests/EntitySearchTest.php b/tests/EntitySearchTest.php
new file mode 100644 (file)
index 0000000..6b313e7
--- /dev/null
@@ -0,0 +1,85 @@
+<?php
+
+use Illuminate\Support\Facades\DB;
+
+class EntitySearchTest extends TestCase
+{
+
+    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('<p>test</p>', '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);
+    }
+}
index 0c0a1dee419c0e4026b105eac7d4f6dd905eda66..c04bc6d7c850d98a1189f23d05c9ad8241ff9c65 100644 (file)
@@ -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('<p>test</p>', '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