From 241278226f8a3c7296e900f4d1f3f47d3148ed80 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 1 Jan 2017 16:57:47 +0000 Subject: [PATCH] Refactored search and slug repo components --- app/Entity.php | 10 ++- app/Http/Controllers/ChapterController.php | 2 +- app/Http/Controllers/PageController.php | 1 + app/Http/Controllers/SearchController.php | 42 +++++----- app/Page.php | 2 + app/Repos/BookRepo.php | 56 +------------ app/Repos/ChapterRepo.php | 59 +------------- app/Repos/EntityRepo.php | 77 ++++++++++++++++++ app/Repos/PageRepo.php | 93 +--------------------- 9 files changed, 113 insertions(+), 229 deletions(-) diff --git a/app/Entity.php b/app/Entity.php index 186059f00..e8deddf0a 100644 --- a/app/Entity.php +++ b/app/Entity.php @@ -4,6 +4,8 @@ class Entity extends Ownable { + protected $fieldsToSearch = ['name', 'description']; + /** * Compares this entity to another given entity. * Matches by comparing class and id. @@ -157,7 +159,7 @@ class Entity extends Ownable * @param string[] array $wheres * @return mixed */ - public function fullTextSearchQuery($fieldsToSearch, $terms, $wheres = []) + public function fullTextSearchQuery($terms, $wheres = []) { $exactTerms = []; $fuzzyTerms = []; @@ -181,16 +183,16 @@ class Entity extends Ownable // Perform fulltext search if relevant terms exist. if ($isFuzzy) { $termString = implode(' ', $fuzzyTerms); - $fields = implode(',', $fieldsToSearch); + $fields = implode(',', $this->fieldsToSearch); $search = $search->selectRaw('*, MATCH(name) AGAINST(? IN BOOLEAN MODE) AS title_relevance', [$termString]); $search = $search->whereRaw('MATCH(' . $fields . ') AGAINST(? IN BOOLEAN MODE)', [$termString]); } // Ensure at least one exact term matches if in search if (count($exactTerms) > 0) { - $search = $search->where(function ($query) use ($exactTerms, $fieldsToSearch) { + $search = $search->where(function ($query) use ($exactTerms) { foreach ($exactTerms as $exactTerm) { - foreach ($fieldsToSearch as $field) { + foreach ($this->fieldsToSearch as $field) { $query->orWhere($field, 'like', $exactTerm); } } diff --git a/app/Http/Controllers/ChapterController.php b/app/Http/Controllers/ChapterController.php index e71ed4d98..d239b08cc 100644 --- a/app/Http/Controllers/ChapterController.php +++ b/app/Http/Controllers/ChapterController.php @@ -118,7 +118,7 @@ class ChapterController extends Controller $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug); $this->checkOwnablePermission('chapter-update', $chapter); if ($chapter->name !== $request->get('name')) { - $chapter->slug = $this->chapterRepo->findSuitableSlug($request->get('name'), $chapter->book->id, $chapter->id); + $chapter->slug = $this->entityRepo->findSuitableSlug('chapter', $request->get('name'), $chapter->id, $chapter->book->id); } $chapter->fill($request->all()); $chapter->updated_by = user()->id; diff --git a/app/Http/Controllers/PageController.php b/app/Http/Controllers/PageController.php index 0d6678e04..5a33ecb37 100644 --- a/app/Http/Controllers/PageController.php +++ b/app/Http/Controllers/PageController.php @@ -26,6 +26,7 @@ class PageController extends Controller /** * PageController constructor. + * @param EntityRepo $entityRepo * @param PageRepo $pageRepo * @param BookRepo $bookRepo * @param ChapterRepo $chapterRepo diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index bb70b0f88..37aaccece 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -1,30 +1,22 @@ pageRepo = $pageRepo; - $this->bookRepo = $bookRepo; - $this->chapterRepo = $chapterRepo; + $this->entityRepo = $entityRepo; $this->viewService = $viewService; parent::__construct(); } @@ -42,9 +34,9 @@ class SearchController extends Controller } $searchTerm = $request->get('term'); $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); + $pages = $this->entityRepo->getBySearch('page', $searchTerm, [], 20, $paginationAppends); + $books = $this->entityRepo->getBySearch('book', $searchTerm, [], 10, $paginationAppends); + $chapters = $this->entityRepo->getBySearch('chapter', $searchTerm, [], 10, $paginationAppends); $this->setPageTitle(trans('entities.search_for_term', ['term' => $searchTerm])); return view('search/all', [ 'pages' => $pages, @@ -65,7 +57,7 @@ class SearchController extends Controller $searchTerm = $request->get('term'); $paginationAppends = $request->only('term'); - $pages = $this->pageRepo->getBySearch($searchTerm, [], 20, $paginationAppends); + $pages = $this->entityRepo->getBySearch('page', $searchTerm, [], 20, $paginationAppends); $this->setPageTitle(trans('entities.search_page_for_term', ['term' => $searchTerm])); return view('search/entity-search-list', [ 'entities' => $pages, @@ -85,7 +77,7 @@ class SearchController extends Controller $searchTerm = $request->get('term'); $paginationAppends = $request->only('term'); - $chapters = $this->chapterRepo->getBySearch($searchTerm, [], 20, $paginationAppends); + $chapters = $this->entityRepo->getBySearch('chapter', $searchTerm, [], 20, $paginationAppends); $this->setPageTitle(trans('entities.search_chapter_for_term', ['term' => $searchTerm])); return view('search/entity-search-list', [ 'entities' => $chapters, @@ -105,7 +97,7 @@ class SearchController extends Controller $searchTerm = $request->get('term'); $paginationAppends = $request->only('term'); - $books = $this->bookRepo->getBySearch($searchTerm, 20, $paginationAppends); + $books = $this->entityRepo->getBySearch('book', $searchTerm, [], 20, $paginationAppends); $this->setPageTitle(trans('entities.search_book_for_term', ['term' => $searchTerm])); return view('search/entity-search-list', [ 'entities' => $books, @@ -128,8 +120,8 @@ class SearchController extends Controller } $searchTerm = $request->get('term'); $searchWhereTerms = [['book_id', '=', $bookId]]; - $pages = $this->pageRepo->getBySearch($searchTerm, $searchWhereTerms); - $chapters = $this->chapterRepo->getBySearch($searchTerm, $searchWhereTerms); + $pages = $this->entityRepo->getBySearch('page', $searchTerm, $searchWhereTerms); + $chapters = $this->entityRepo->getBySearch('chapter', $searchTerm, $searchWhereTerms); return view('search/book', ['pages' => $pages, 'chapters' => $chapters, 'searchTerm' => $searchTerm]); } @@ -148,9 +140,11 @@ class SearchController extends Controller // Search for entities otherwise show most popular if ($searchTerm !== false) { - if ($entityTypes->contains('page')) $entities = $entities->merge($this->pageRepo->getBySearch($searchTerm)->items()); - if ($entityTypes->contains('chapter')) $entities = $entities->merge($this->chapterRepo->getBySearch($searchTerm)->items()); - if ($entityTypes->contains('book')) $entities = $entities->merge($this->bookRepo->getBySearch($searchTerm)->items()); + foreach (['page', 'chapter', 'book'] as $entityType) { + if ($entityTypes->contains($entityType)) { + $entities = $entities->merge($this->entityRepo->getBySearch($entityType, $searchTerm)->items()); + } + } $entities = $entities->sortByDesc('title_relevance'); } else { $entityNames = $entityTypes->map(function ($type) { diff --git a/app/Page.php b/app/Page.php index 38f95a3b1..b24e7778a 100644 --- a/app/Page.php +++ b/app/Page.php @@ -9,6 +9,8 @@ class Page extends Entity protected $with = ['book']; + protected $fieldsToSearch = ['name', 'text']; + /** * Converts this page into a simplified array. * @return mixed diff --git a/app/Repos/BookRepo.php b/app/Repos/BookRepo.php index ebfda3fa4..3043d2916 100644 --- a/app/Repos/BookRepo.php +++ b/app/Repos/BookRepo.php @@ -27,7 +27,7 @@ class BookRepo extends EntityRepo public function createFromInput($input) { $book = $this->book->newInstance($input); - $book->slug = $this->findSuitableSlug($book->name); + $book->slug = $this->findSuitableSlug('book', $book->name); $book->created_by = user()->id; $book->updated_by = user()->id; $book->save(); @@ -44,7 +44,7 @@ class BookRepo extends EntityRepo public function updateFromInput(Book $book, $input) { if ($book->name !== $input['name']) { - $book->slug = $this->findSuitableSlug($input['name'], $book->id); + $book->slug = $this->findSuitableSlug('book', $input['name'], $book->id); } $book->fill($input); $book->updated_by = user()->id; @@ -83,36 +83,6 @@ class BookRepo extends EntityRepo return $lastElem ? $lastElem->priority + 1 : 0; } - /** - * @param string $slug - * @param bool|false $currentId - * @return bool - */ - public function doesSlugExist($slug, $currentId = false) - { - $query = $this->book->where('slug', '=', $slug); - if ($currentId) { - $query = $query->where('id', '!=', $currentId); - } - return $query->count() > 0; - } - - /** - * Provides a suitable slug for the given book name. - * Ensures the returned slug is unique in the system. - * @param string $name - * @param bool|false $currentId - * @return string - */ - public function findSuitableSlug($name, $currentId = false) - { - $slug = $this->nameToSlug($name); - while ($this->doesSlugExist($slug, $currentId)) { - $slug .= '-' . substr(md5(rand(1, 500)), 0, 3); - } - return $slug; - } - /** * Get all child objects of a book. * Returns a sorted collection of Pages and Chapters. @@ -166,26 +136,4 @@ class BookRepo extends EntityRepo }); } - /** - * Get books by search term. - * @param $term - * @param int $count - * @param array $paginationAppends - * @return mixed - */ - public function getBySearch($term, $count = 20, $paginationAppends = []) - { - $terms = $this->prepareSearchTerms($term); - $bookQuery = $this->permissionService->enforceBookRestrictions($this->book->fullTextSearchQuery(['name', 'description'], $terms)); - $bookQuery = $this->addAdvancedSearchQueries($bookQuery, $term); - $books = $bookQuery->paginate($count)->appends($paginationAppends); - $words = join('|', explode(' ', preg_quote(trim($term), '/'))); - foreach ($books as $book) { - //highlight - $result = preg_replace('#' . $words . '#iu', "\$0", $book->getExcerpt(100)); - $book->searchSnippet = $result; - } - return $books; - } - } \ No newline at end of file diff --git a/app/Repos/ChapterRepo.php b/app/Repos/ChapterRepo.php index 861bb72fc..afbf312da 100644 --- a/app/Repos/ChapterRepo.php +++ b/app/Repos/ChapterRepo.php @@ -45,7 +45,7 @@ class ChapterRepo extends EntityRepo public function createFromInput($input, Book $book) { $chapter = $this->chapter->newInstance($input); - $chapter->slug = $this->findSuitableSlug($chapter->name, $book->id); + $chapter->slug = $this->findSuitableSlug('chapter', $chapter->name, false, $book->id); $chapter->created_by = user()->id; $chapter->updated_by = user()->id; $chapter = $book->chapters()->save($chapter); @@ -72,38 +72,6 @@ class ChapterRepo extends EntityRepo $chapter->delete(); } - /** - * Check if a chapter's slug exists. - * @param $slug - * @param $bookId - * @param bool|false $currentId - * @return bool - */ - public function doesSlugExist($slug, $bookId, $currentId = false) - { - $query = $this->chapter->where('slug', '=', $slug)->where('book_id', '=', $bookId); - if ($currentId) { - $query = $query->where('id', '!=', $currentId); - } - return $query->count() > 0; - } - - /** - * Finds a suitable slug for the provided name. - * Checks database to prevent duplicate slugs. - * @param $name - * @param $bookId - * @param bool|false $currentId - * @return string - */ - public function findSuitableSlug($name, $bookId, $currentId = false) - { - $slug = $this->nameToSlug($name); - while ($this->doesSlugExist($slug, $bookId, $currentId)) { - $slug .= '-' . substr(md5(rand(1, 500)), 0, 3); - } - return $slug; - } /** * Get a new priority value for a new page to be added @@ -117,29 +85,6 @@ class ChapterRepo extends EntityRepo return $lastPage !== null ? $lastPage->priority + 1 : 0; } - /** - * Get chapters by the given search term. - * @param string $term - * @param array $whereTerms - * @param int $count - * @param array $paginationAppends - * @return mixed - */ - public function getBySearch($term, $whereTerms = [], $count = 20, $paginationAppends = []) - { - $terms = $this->prepareSearchTerms($term); - $chapterQuery = $this->permissionService->enforceChapterRestrictions($this->chapter->fullTextSearchQuery(['name', 'description'], $terms, $whereTerms)); - $chapterQuery = $this->addAdvancedSearchQueries($chapterQuery, $term); - $chapters = $chapterQuery->paginate($count)->appends($paginationAppends); - $words = join('|', explode(' ', preg_quote(trim($term), '/'))); - foreach ($chapters as $chapter) { - //highlight - $result = preg_replace('#' . $words . '#iu', "\$0", $chapter->getExcerpt(100)); - $chapter->searchSnippet = $result; - } - return $chapters; - } - /** * Changes the book relation of this chapter. * @param $bookId @@ -155,7 +100,7 @@ class ChapterRepo extends EntityRepo $activity->book_id = $bookId; $activity->save(); } - $chapter->slug = $this->findSuitableSlug($chapter->name, $bookId, $chapter->id); + $chapter->slug = $this->findSuitableSlug('chapter', $chapter->name, $chapter->id, $bookId); $chapter->save(); // Update all child pages foreach ($chapter->pages as $page) { diff --git a/app/Repos/EntityRepo.php b/app/Repos/EntityRepo.php index 19beebc77..40fdea54e 100644 --- a/app/Repos/EntityRepo.php +++ b/app/Repos/EntityRepo.php @@ -238,6 +238,83 @@ class EntityRepo ->skip($count * $page)->take($count)->get(); } + public function getBySearch($type, $term, $whereTerms = [], $count = 20, $paginationAppends = []) + { + $terms = $this->prepareSearchTerms($term); + $q = $this->permissionService->enforceChapterRestrictions($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', "\$0", $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', "\$0", $entity->getExcerpt(100)); + $entity->searchSnippet = $result; + } + return $entities; + } + + /** + * Find a suitable slug for an entity. + * @param string $type + * @param string $name + * @param bool|integer $currentId + * @param bool|integer $bookId Only pass if type is not a book + * @return string + */ + public function findSuitableSlug($type, $name, $currentId = false, $bookId = false) + { + $slug = $this->nameToSlug($name); + while ($this->slugExists($type, $slug, $currentId, $bookId)) { + $slug .= '-' . substr(md5(rand(1, 500)), 0, 3); + } + return $slug; + } + + /** + * Check if a slug already exists in the database. + * @param string $type + * @param string $slug + * @param bool|integer $currentId + * @param bool|integer $bookId + * @return bool + */ + protected function slugExists($type, $slug, $currentId = false, $bookId = false) + { + $query = $this->getEntity($type)->where('slug', '=', $slug); + if (strtolower($type) === 'page' || strtolower($type) === 'chapter') { + $query = $query->where('book_id', '=', $bookId); + } + if ($currentId) $query = $query->where('id', '!=', $currentId); + return $query->count() > 0; + } + /** * Updates entity restrictions from a request * @param $request diff --git a/app/Repos/PageRepo.php b/app/Repos/PageRepo.php index f16ea6b6d..699e6ecc5 100644 --- a/app/Repos/PageRepo.php +++ b/app/Repos/PageRepo.php @@ -65,17 +65,6 @@ class PageRepo extends EntityRepo return $revision !== null ? $revision->page : null; } - /** - * Get a new Page instance from the given input. - * @param $input - * @return Page - */ - public function newFromInput($input) - { - $page = $this->page->fill($input); - return $page; - } - /** * Count the pages with a particular slug within a book. * @param $slug @@ -103,7 +92,7 @@ class PageRepo extends EntityRepo $this->tagRepo->saveTagsToEntity($draftPage, $input['tags']); } - $draftPage->slug = $this->findSuitableSlug($draftPage->name, $draftPage->book->id); + $draftPage->slug = $this->findSuitableSlug('page', $draftPage->name, false, $draftPage->book->id); $draftPage->html = $this->formatHtml($input['html']); $draftPage->text = strip_tags($draftPage->html); $draftPage->draft = false; @@ -222,50 +211,6 @@ class PageRepo extends EntityRepo } - /** - * Gets pages by a search term. - * Highlights page content for showing in results. - * @param string $term - * @param array $whereTerms - * @param int $count - * @param array $paginationAppends - * @return mixed - */ - public function getBySearch($term, $whereTerms = [], $count = 20, $paginationAppends = []) - { - $terms = $this->prepareSearchTerms($term); - $pageQuery = $this->permissionService->enforcePageRestrictions($this->page->fullTextSearchQuery(['name', 'text'], $terms, $whereTerms)); - $pageQuery = $this->addAdvancedSearchQueries($pageQuery, $term); - $pages = $pageQuery->paginate($count)->appends($paginationAppends); - - // Add highlights to page text. - $words = join('|', explode(' ', preg_quote(trim($term), '/'))); - //lookahead/behind assertions ensures cut between words - $s = '\s\x00-/:-@\[-`{-~'; //character set for start/end of words - - foreach ($pages 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', "\$0", $result); - if (strlen($result) < 5) { - $result = $page->getExcerpt(80); - } - $page->searchSnippet = $result; - } - return $pages; - } - /** * Search for image usage. * @param $imageString @@ -297,7 +242,7 @@ class PageRepo extends EntityRepo // Prevent slug being updated if no name change if ($page->name !== $input['name']) { - $page->slug = $this->findSuitableSlug($input['name'], $book_id, $page->id); + $page->slug = $this->findSuitableSlug('page', $input['name'], $page->id, $book_id); } // Save page tags if present @@ -337,7 +282,7 @@ class PageRepo extends EntityRepo $this->saveRevision($page); $revision = $this->getRevisionById($revisionId); $page->fill($revision->toArray()); - $page->slug = $this->findSuitableSlug($page->name, $book->id, $page->id); + $page->slug = $this->findSuitableSlug('page', $page->name, $page->id, $book->id); $page->text = strip_tags($page->html); $page->updated_by = user()->id; $page->save(); @@ -529,20 +474,6 @@ class PageRepo extends EntityRepo return $this->pageRevision->findOrFail($id); } - /** - * Checks if a slug exists within a book already. - * @param $slug - * @param $bookId - * @param bool|false $currentId - * @return bool - */ - public function doesSlugExist($slug, $bookId, $currentId = false) - { - $query = $this->page->where('slug', '=', $slug)->where('book_id', '=', $bookId); - if ($currentId) $query = $query->where('id', '!=', $currentId); - return $query->count() > 0; - } - /** * Changes the related book for the specified page. * Changes the book id of any relations to the page that store the book id. @@ -557,7 +488,7 @@ class PageRepo extends EntityRepo $activity->book_id = $bookId; $activity->save(); } - $page->slug = $this->findSuitableSlug($page->name, $bookId, $page->id); + $page->slug = $this->findSuitableSlug('page', $page->name, $page->id, $bookId); $page->save(); return $page; } @@ -578,22 +509,6 @@ class PageRepo extends EntityRepo $this->permissionService->buildJointPermissionsForEntity($book); } - /** - * Gets a suitable slug for the resource - * @param string $name - * @param int $bookId - * @param bool|false $currentId - * @return string - */ - public function findSuitableSlug($name, $bookId, $currentId = false) - { - $slug = $this->nameToSlug($name); - while ($this->doesSlugExist($slug, $bookId, $currentId)) { - $slug .= '-' . substr(md5(rand(1, 500)), 0, 3); - } - return $slug; - } - /** * Destroy a given page along with its dependencies. * @param $page