From: Dan Brown Date: Sat, 5 Mar 2016 18:21:44 +0000 (+0000) Subject: Merge branch 'custom_role_system' X-Git-Tag: v0.7.6~1^2~3^2~2 X-Git-Url: http://source.bookstackapp.com/bookstack/commitdiff_plain/80865b30a5d6a82e86d21e272ae58977d4430a64 Merge branch 'custom_role_system' Conflicts: app/Repos/BookRepo.php app/Repos/ChapterRepo.php app/Repos/PageRepo.php --- 80865b30a5d6a82e86d21e272ae58977d4430a64 diff --cc app/Repos/BookRepo.php index afd802337,4ae7cc062..816db4cf0 --- a/app/Repos/BookRepo.php +++ b/app/Repos/BookRepo.php @@@ -226,17 -243,8 +241,17 @@@ class BookRep */ public function getBySearch($term, $count = 20, $paginationAppends = []) { - $terms = explode(' ', $term); + preg_match_all('/"(.*?)"/', $term, $matches); + if (count($matches[1]) > 0) { + $terms = $matches[1]; + $term = trim(preg_replace('/"(.*?)"/', '', $term)); + } else { + $terms = []; + } + if (!empty($term)) { + $terms = array_merge($terms, explode(' ', $term)); + } - $books = $this->book->fullTextSearchQuery(['name', 'description'], $terms) + $books = $this->restrictionService->enforceBookRestrictions($this->book->fullTextSearchQuery(['name', 'description'], $terms)) ->paginate($count)->appends($paginationAppends); $words = join('|', explode(' ', preg_quote(trim($term), '/'))); foreach ($books as $book) { diff --cc app/Repos/BookRepo.php.orig index 000000000,000000000..4b9709fe6 new file mode 100644 --- /dev/null +++ b/app/Repos/BookRepo.php.orig @@@ -1,0 -1,0 +1,295 @@@ ++book = $book; ++ $this->pageRepo = $pageRepo; ++ $this->chapterRepo = $chapterRepo; ++ $this->restrictionService = $restrictionService; ++ } ++ ++ /** ++ * Base query for getting books. ++ * Takes into account any restrictions. ++ * @return mixed ++ */ ++ private function bookQuery() ++ { ++ return $this->restrictionService->enforceBookRestrictions($this->book, 'view'); ++ } ++ ++ /** ++ * Get the book that has the given id. ++ * @param $id ++ * @return mixed ++ */ ++ public function getById($id) ++ { ++ return $this->bookQuery()->findOrFail($id); ++ } ++ ++ /** ++ * Get all books, Limited by count. ++ * @param int $count ++ * @return mixed ++ */ ++ public function getAll($count = 10) ++ { ++ $bookQuery = $this->bookQuery()->orderBy('name', 'asc'); ++ if (!$count) return $bookQuery->get(); ++ return $bookQuery->take($count)->get(); ++ } ++ ++ /** ++ * Get all books paginated. ++ * @param int $count ++ * @return mixed ++ */ ++ public function getAllPaginated($count = 10) ++ { ++ return $this->bookQuery() ++ ->orderBy('name', 'asc')->paginate($count); ++ } ++ ++ ++ /** ++ * Get the latest books. ++ * @param int $count ++ * @return mixed ++ */ ++ public function getLatest($count = 10) ++ { ++ return $this->bookQuery()->orderBy('created_at', 'desc')->take($count)->get(); ++ } ++ ++ /** ++ * Gets the most recently viewed for a user. ++ * @param int $count ++ * @param int $page ++ * @return mixed ++ */ ++ public function getRecentlyViewed($count = 10, $page = 0) ++ { ++ // TODO restrict ++ return Views::getUserRecentlyViewed($count, $page, $this->book); ++ } ++ ++ /** ++ * Gets the most viewed books. ++ * @param int $count ++ * @param int $page ++ * @return mixed ++ */ ++ public function getPopular($count = 10, $page = 0) ++ { ++ // TODO - Restrict ++ return Views::getPopular($count, $page, $this->book); ++ } ++ ++ /** ++ * Get a book by slug ++ * @param $slug ++ * @return mixed ++ * @throws NotFoundException ++ */ ++ public function getBySlug($slug) ++ { ++ $book = $this->bookQuery()->where('slug', '=', $slug)->first(); ++ if ($book === null) throw new NotFoundException('Book not found'); ++ return $book; ++ } ++ ++ /** ++ * Checks if a book exists. ++ * @param $id ++ * @return bool ++ */ ++ public function exists($id) ++ { ++ return $this->bookQuery()->where('id', '=', $id)->exists(); ++ } ++ ++ /** ++ * Get a new book instance from request input. ++ * @param $input ++ * @return Book ++ */ ++ public function newFromInput($input) ++ { ++ return $this->book->newInstance($input); ++ } ++ ++ /** ++ * Destroy a book identified by the given slug. ++ * @param $bookSlug ++ */ ++ public function destroyBySlug($bookSlug) ++ { ++ $book = $this->getBySlug($bookSlug); ++ foreach ($book->pages as $page) { ++ $this->pageRepo->destroy($page); ++ } ++ foreach ($book->chapters as $chapter) { ++ $this->chapterRepo->destroy($chapter); ++ } ++ $book->views()->delete(); ++ $book->restrictions()->delete(); ++ $book->delete(); ++ } ++ ++ /** ++ * Get the next child element priority. ++ * @param Book $book ++ * @return int ++ */ ++ public function getNewPriority($book) ++ { ++ $lastElem = $this->getChildren($book)->pop(); ++ 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) ++ { ++ $originalSlug = Str::slug($name); ++ $slug = $originalSlug; ++ $count = 2; ++ while ($this->doesSlugExist($slug, $currentId)) { ++ $slug = $originalSlug . '-' . $count; ++ $count++; ++ } ++ return $slug; ++ } ++ ++ /** ++ * Get all child objects of a book. ++ * Returns a sorted collection of Pages and Chapters. ++ * Loads the bookslug onto child elements to prevent access database access for getting the slug. ++ * @param Book $book ++ * @return mixed ++ */ ++ public function getChildren(Book $book) ++ { ++ $pageQuery = $book->pages()->where('chapter_id', '=', 0); ++ $pageQuery = $this->restrictionService->enforcePageRestrictions($pageQuery, 'view'); ++ $pages = $pageQuery->get(); ++ ++ $chapterQuery = $book->chapters()->with(['pages' => function($query) { ++ $this->restrictionService->enforcePageRestrictions($query, 'view'); ++ }]); ++ $chapterQuery = $this->restrictionService->enforceChapterRestrictions($chapterQuery, 'view'); ++ $chapters = $chapterQuery->get(); ++ $children = $pages->merge($chapters); ++ $bookSlug = $book->slug; ++ $children->each(function ($child) use ($bookSlug) { ++ $child->setAttribute('bookSlug', $bookSlug); ++ if ($child->isA('chapter')) { ++ $child->pages->each(function ($page) use ($bookSlug) { ++ $page->setAttribute('bookSlug', $bookSlug); ++ }); ++ } ++ }); ++ return $children->sortBy('priority'); ++ } ++ ++ /** ++ * Get books by search term. ++ * @param $term ++ * @param int $count ++ * @param array $paginationAppends ++ * @return mixed ++ */ ++ public function getBySearch($term, $count = 20, $paginationAppends = []) ++ { ++<<<<<<< HEAD ++ preg_match_all('/"(.*?)"/', $term, $matches); ++ if (count($matches[1]) > 0) { ++ $terms = $matches[1]; ++ $term = trim(preg_replace('/"(.*?)"/', '', $term)); ++ } else { ++ $terms = []; ++ } ++ if (!empty($term)) { ++ $terms = array_merge($terms, explode(' ', $term)); ++ } ++ $books = $this->book->fullTextSearchQuery(['name', 'description'], $terms) ++======= ++ $terms = explode(' ', $term); ++ $books = $this->restrictionService->enforceBookRestrictions($this->book->fullTextSearchQuery(['name', 'description'], $terms)) ++>>>>>>> custom_role_system ++ ->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; ++ } ++ ++ /** ++ * Updates books restrictions from a request ++ * @param $request ++ * @param $book ++ */ ++ public function updateRestrictionsFromRequest($request, $book) ++ { ++ // TODO - extract into shared repo ++ $book->restricted = $request->has('restricted') && $request->get('restricted') === 'true'; ++ $book->restrictions()->delete(); ++ if ($request->has('restrictions')) { ++ foreach ($request->get('restrictions') as $roleId => $restrictions) { ++ foreach ($restrictions as $action => $value) { ++ $book->restrictions()->create([ ++ 'role_id' => $roleId, ++ 'action' => strtolower($action) ++ ]); ++ } ++ } ++ } ++ $book->save(); ++ } ++ ++} diff --cc app/Repos/ChapterRepo.php index 542cdd532,095596a60..6868bbf89 --- a/app/Repos/ChapterRepo.php +++ b/app/Repos/ChapterRepo.php @@@ -131,17 -156,8 +156,17 @@@ class ChapterRep */ public function getBySearch($term, $whereTerms = [], $count = 20, $paginationAppends = []) { - $terms = explode(' ', $term); + preg_match_all('/"(.*?)"/', $term, $matches); + if (count($matches[1]) > 0) { + $terms = $matches[1]; + $term = trim(preg_replace('/"(.*?)"/', '', $term)); + } else { + $terms = []; + } + if (!empty($term)) { + $terms = array_merge($terms, explode(' ', $term)); + } - $chapters = $this->chapter->fullTextSearchQuery(['name', 'description'], $terms, $whereTerms) + $chapters = $this->restrictionService->enforceChapterRestrictions($this->chapter->fullTextSearchQuery(['name', 'description'], $terms, $whereTerms)) ->paginate($count)->appends($paginationAppends); $words = join('|', explode(' ', preg_quote(trim($term), '/'))); foreach ($chapters as $chapter) { diff --cc app/Repos/ChapterRepo.php.orig index 000000000,000000000..be4a4e6b5 new file mode 100644 --- /dev/null +++ b/app/Repos/ChapterRepo.php.orig @@@ -1,0 -1,0 +1,226 @@@ ++chapter = $chapter; ++ $this->restrictionService = $restrictionService; ++ } ++ ++ /** ++ * Base query for getting chapters, Takes restrictions into account. ++ * @return mixed ++ */ ++ private function chapterQuery() ++ { ++ return $this->restrictionService->enforceChapterRestrictions($this->chapter, 'view'); ++ } ++ ++ /** ++ * Check if an id exists. ++ * @param $id ++ * @return bool ++ */ ++ public function idExists($id) ++ { ++ return $this->chapterQuery()->where('id', '=', $id)->count() > 0; ++ } ++ ++ /** ++ * Get a chapter by a specific id. ++ * @param $id ++ * @return mixed ++ */ ++ public function getById($id) ++ { ++ return $this->chapterQuery()->findOrFail($id); ++ } ++ ++ /** ++ * Get all chapters. ++ * @return \Illuminate\Database\Eloquent\Collection|static[] ++ */ ++ public function getAll() ++ { ++ return $this->chapterQuery()->all(); ++ } ++ ++ /** ++ * Get a chapter that has the given slug within the given book. ++ * @param $slug ++ * @param $bookId ++ * @return mixed ++ * @throws NotFoundException ++ */ ++ public function getBySlug($slug, $bookId) ++ { ++ $chapter = $this->chapterQuery()->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first(); ++ if ($chapter === null) throw new NotFoundException('Chapter not found'); ++ return $chapter; ++ } ++ ++ /** ++ * Get the child items for a chapter ++ * @param Chapter $chapter ++ */ ++ public function getChildren(Chapter $chapter) ++ { ++ return $this->restrictionService->enforcePageRestrictions($chapter->pages())->get(); ++ } ++ ++ /** ++ * Create a new chapter from request input. ++ * @param $input ++ * @return $this ++ */ ++ public function newFromInput($input) ++ { ++ return $this->chapter->fill($input); ++ } ++ ++ /** ++ * Destroy a chapter and its relations by providing its slug. ++ * @param Chapter $chapter ++ */ ++ public function destroy(Chapter $chapter) ++ { ++ if (count($chapter->pages) > 0) { ++ foreach ($chapter->pages as $page) { ++ $page->chapter_id = 0; ++ $page->save(); ++ } ++ } ++ Activity::removeEntity($chapter); ++ $chapter->views()->delete(); ++ $chapter->restrictions()->delete(); ++ $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 = Str::slug($name); ++ while ($this->doesSlugExist($slug, $bookId, $currentId)) { ++ $slug .= '-' . substr(md5(rand(1, 500)), 0, 3); ++ } ++ return $slug; ++ } ++ ++ /** ++ * 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 = [], $count = 20, $paginationAppends = []) ++ { ++<<<<<<< HEAD ++ preg_match_all('/"(.*?)"/', $term, $matches); ++ if (count($matches[1]) > 0) { ++ $terms = $matches[1]; ++ $term = trim(preg_replace('/"(.*?)"/', '', $term)); ++ } else { ++ $terms = []; ++ } ++ if (!empty($term)) { ++ $terms = array_merge($terms, explode(' ', $term)); ++ } ++ $chapters = $this->chapter->fullTextSearchQuery(['name', 'description'], $terms, $whereTerms) ++======= ++ $terms = explode(' ', $term); ++ $chapters = $this->restrictionService->enforceChapterRestrictions($this->chapter->fullTextSearchQuery(['name', 'description'], $terms, $whereTerms)) ++>>>>>>> custom_role_system ++ ->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 ++ * @param Chapter $chapter ++ * @return Chapter ++ */ ++ public function changeBook($bookId, Chapter $chapter) ++ { ++ $chapter->book_id = $bookId; ++ foreach ($chapter->activity as $activity) { ++ $activity->book_id = $bookId; ++ $activity->save(); ++ } ++ $chapter->slug = $this->findSuitableSlug($chapter->name, $bookId, $chapter->id); ++ $chapter->save(); ++ return $chapter; ++ } ++ ++ /** ++ * Updates pages restrictions from a request ++ * @param $request ++ * @param $chapter ++ */ ++ public function updateRestrictionsFromRequest($request, $chapter) ++ { ++ // TODO - extract into shared repo ++ $chapter->restricted = $request->has('restricted') && $request->get('restricted') === 'true'; ++ $chapter->restrictions()->delete(); ++ if ($request->has('restrictions')) { ++ foreach($request->get('restrictions') as $roleId => $restrictions) { ++ foreach ($restrictions as $action => $value) { ++ $chapter->restrictions()->create([ ++ 'role_id' => $roleId, ++ 'action' => strtolower($action) ++ ]); ++ } ++ } ++ } ++ $chapter->save(); ++ } ++ ++} diff --cc app/Repos/PageRepo.php index bfaff79ae,f3933af69..3d675183e --- a/app/Repos/PageRepo.php +++ b/app/Repos/PageRepo.php @@@ -201,17 -190,8 +200,17 @@@ class PageRep */ public function getBySearch($term, $whereTerms = [], $count = 20, $paginationAppends = []) { - $terms = explode(' ', $term); + preg_match_all('/"(.*?)"/', $term, $matches); + if (count($matches[1]) > 0) { + $terms = $matches[1]; + $term = trim(preg_replace('/"(.*?)"/', '', $term)); + } else { + $terms = []; + } + if (!empty($term)) { + $terms = array_merge($terms, explode(' ', $term)); + } - $pages = $this->page->fullTextSearchQuery(['name', 'text'], $terms, $whereTerms) + $pages = $this->restrictionService->enforcePageRestrictions($this->page->fullTextSearchQuery(['name', 'text'], $terms, $whereTerms)) ->paginate($count)->appends($paginationAppends); // Add highlights to page text. diff --cc app/Repos/PageRepo.php.orig index 000000000,000000000..c1e02b501 new file mode 100644 --- /dev/null +++ b/app/Repos/PageRepo.php.orig @@@ -1,0 -1,0 +1,437 @@@ ++page = $page; ++ $this->pageRevision = $pageRevision; ++ $this->restrictionService = $restrictionService; ++ } ++ ++ /** ++ * Base query for getting pages, Takes restrictions into account. ++ * @return mixed ++ */ ++ private function pageQuery() ++ { ++ return $this->restrictionService->enforcePageRestrictions($this->page, 'view'); ++ } ++ ++ /** ++ * Get a page via a specific ID. ++ * @param $id ++ * @return mixed ++ */ ++ public function getById($id) ++ { ++ return $this->pageQuery()->findOrFail($id); ++ } ++ ++ /** ++ * Get a page identified by the given slug. ++ * @param $slug ++ * @param $bookId ++ * @return mixed ++ * @throws NotFoundException ++ */ ++ public function getBySlug($slug, $bookId) ++ { ++ $page = $this->pageQuery()->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first(); ++ if ($page === null) throw new NotFoundException('Page not found'); ++ return $page; ++ } ++ ++ /** ++ * Search through page revisions and retrieve ++ * the last page in the current book that ++ * has a slug equal to the one given. ++ * @param $pageSlug ++ * @param $bookSlug ++ * @return null | Page ++ */ ++ public function findPageUsingOldSlug($pageSlug, $bookSlug) ++ { ++ $revision = $this->pageRevision->where('slug', '=', $pageSlug) ++ ->whereHas('page', function($query) { ++ $this->restrictionService->enforcePageRestrictions($query); ++ }) ++ ->where('book_slug', '=', $bookSlug)->orderBy('created_at', 'desc') ++ ->with('page')->first(); ++ 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; ++ } ++ ++ ++ /** ++ * Save a new page into the system. ++ * Input validation must be done beforehand. ++ * @param array $input ++ * @param Book $book ++ * @param int $chapterId ++ * @return Page ++ */ ++ public function saveNew(array $input, Book $book, $chapterId = null) ++ { ++ $page = $this->newFromInput($input); ++ $page->slug = $this->findSuitableSlug($page->name, $book->id); ++ ++ if ($chapterId) $page->chapter_id = $chapterId; ++ ++ $page->html = $this->formatHtml($input['html']); ++ $page->text = strip_tags($page->html); ++ $page->created_by = auth()->user()->id; ++ $page->updated_by = auth()->user()->id; ++ ++ $book->pages()->save($page); ++ return $page; ++ } ++ ++ /** ++ * Formats a page's html to be tagged correctly ++ * within the system. ++ * @param string $htmlText ++ * @return string ++ */ ++ protected function formatHtml($htmlText) ++ { ++ if($htmlText == '') return $htmlText; ++ libxml_use_internal_errors(true); ++ $doc = new \DOMDocument(); ++ $doc->loadHTML(mb_convert_encoding($htmlText, 'HTML-ENTITIES', 'UTF-8')); ++ ++ $container = $doc->documentElement; ++ $body = $container->childNodes->item(0); ++ $childNodes = $body->childNodes; ++ ++ // Ensure no duplicate ids are used ++ $idArray = []; ++ ++ foreach ($childNodes as $index => $childNode) { ++ /** @var \DOMElement $childNode */ ++ if (get_class($childNode) !== 'DOMElement') continue; ++ ++ // Overwrite id if not a BookStack custom id ++ if ($childNode->hasAttribute('id')) { ++ $id = $childNode->getAttribute('id'); ++ if (strpos($id, 'bkmrk') === 0 && array_search($id, $idArray) === false) { ++ $idArray[] = $id; ++ continue; ++ }; ++ } ++ ++ // Create an unique id for the element ++ // Uses the content as a basis to ensure output is the same every time ++ // the same content is passed through. ++ $contentId = 'bkmrk-' . substr(strtolower(preg_replace('/\s+/', '-', trim($childNode->nodeValue))), 0, 20); ++ $newId = urlencode($contentId); ++ $loopIndex = 0; ++ while (in_array($newId, $idArray)) { ++ $newId = urlencode($contentId . '-' . $loopIndex); ++ $loopIndex++; ++ } ++ ++ $childNode->setAttribute('id', $newId); ++ $idArray[] = $newId; ++ } ++ ++ // Generate inner html as a string ++ $html = ''; ++ foreach ($childNodes as $childNode) { ++ $html .= $doc->saveHTML($childNode); ++ } ++ ++ return $html; ++ } ++ ++ ++ /** ++ * 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 = []) ++ { ++<<<<<<< HEAD ++ preg_match_all('/"(.*?)"/', $term, $matches); ++ if (count($matches[1]) > 0) { ++ $terms = $matches[1]; ++ $term = trim(preg_replace('/"(.*?)"/', '', $term)); ++ } else { ++ $terms = []; ++ } ++ if (!empty($term)) { ++ $terms = array_merge($terms, explode(' ', $term)); ++ } ++ $pages = $this->page->fullTextSearchQuery(['name', 'text'], $terms, $whereTerms) ++======= ++ $terms = explode(' ', $term); ++ $pages = $this->restrictionService->enforcePageRestrictions($this->page->fullTextSearchQuery(['name', 'text'], $terms, $whereTerms)) ++>>>>>>> custom_role_system ++ ->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 ++ * @return mixed ++ */ ++ public function searchForImage($imageString) ++ { ++ $pages = $this->pageQuery()->where('html', 'like', '%' . $imageString . '%')->get(); ++ foreach ($pages as $page) { ++ $page->url = $page->getUrl(); ++ $page->html = ''; ++ $page->text = ''; ++ } ++ return count($pages) > 0 ? $pages : false; ++ } ++ ++ /** ++ * Updates a page with any fillable data and saves it into the database. ++ * @param Page $page ++ * @param int $book_id ++ * @param string $input ++ * @return Page ++ */ ++ public function updatePage(Page $page, $book_id, $input) ++ { ++ // Save a revision before updating ++ if ($page->html !== $input['html'] || $page->name !== $input['name']) { ++ $this->saveRevision($page); ++ } ++ ++ // Prevent slug being updated if no name change ++ if ($page->name !== $input['name']) { ++ $page->slug = $this->findSuitableSlug($input['name'], $book_id, $page->id); ++ } ++ ++ // Update with new details ++ $page->fill($input); ++ $page->html = $this->formatHtml($input['html']); ++ $page->text = strip_tags($page->html); ++ $page->updated_by = auth()->user()->id; ++ $page->save(); ++ return $page; ++ } ++ ++ /** ++ * Restores a revision's content back into a page. ++ * @param Page $page ++ * @param Book $book ++ * @param int $revisionId ++ * @return Page ++ */ ++ public function restoreRevision(Page $page, Book $book, $revisionId) ++ { ++ $this->saveRevision($page); ++ $revision = $this->getRevisionById($revisionId); ++ $page->fill($revision->toArray()); ++ $page->slug = $this->findSuitableSlug($page->name, $book->id, $page->id); ++ $page->text = strip_tags($page->html); ++ $page->updated_by = auth()->user()->id; ++ $page->save(); ++ return $page; ++ } ++ ++ /** ++ * Saves a page revision into the system. ++ * @param Page $page ++ * @return $this ++ */ ++ public function saveRevision(Page $page) ++ { ++ $revision = $this->pageRevision->fill($page->toArray()); ++ $revision->page_id = $page->id; ++ $revision->slug = $page->slug; ++ $revision->book_slug = $page->book->slug; ++ $revision->created_by = auth()->user()->id; ++ $revision->created_at = $page->updated_at; ++ $revision->save(); ++ // Clear old revisions ++ if ($this->pageRevision->where('page_id', '=', $page->id)->count() > 50) { ++ $this->pageRevision->where('page_id', '=', $page->id) ++ ->orderBy('created_at', 'desc')->skip(50)->take(5)->delete(); ++ } ++ return $revision; ++ } ++ ++ /** ++ * Gets a single revision via it's id. ++ * @param $id ++ * @return mixed ++ */ ++ public function getRevisionById($id) ++ { ++ 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. ++ * @param int $bookId ++ * @param Page $page ++ * @return Page ++ */ ++ public function changeBook($bookId, Page $page) ++ { ++ $page->book_id = $bookId; ++ foreach ($page->activity as $activity) { ++ $activity->book_id = $bookId; ++ $activity->save(); ++ } ++ $page->slug = $this->findSuitableSlug($page->name, $bookId, $page->id); ++ $page->save(); ++ return $page; ++ } ++ ++ /** ++ * Gets a suitable slug for the resource ++ * @param $name ++ * @param $bookId ++ * @param bool|false $currentId ++ * @return string ++ */ ++ public function findSuitableSlug($name, $bookId, $currentId = false) ++ { ++ $slug = Str::slug($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 ++ */ ++ public function destroy($page) ++ { ++ Activity::removeEntity($page); ++ $page->views()->delete(); ++ $page->revisions()->delete(); ++ $page->restrictions()->delete(); ++ $page->delete(); ++ } ++ ++ /** ++ * Get the latest pages added to the system. ++ * @param $count ++ */ ++ public function getRecentlyCreatedPaginated($count = 20) ++ { ++ return $this->pageQuery()->orderBy('created_at', 'desc')->paginate($count); ++ } ++ ++ /** ++ * Get the latest pages added to the system. ++ * @param $count ++ */ ++ public function getRecentlyUpdatedPaginated($count = 20) ++ { ++ return $this->pageQuery()->orderBy('updated_at', 'desc')->paginate($count); ++ } ++ ++ /** ++ * Updates pages restrictions from a request ++ * @param $request ++ * @param $page ++ */ ++ public function updateRestrictionsFromRequest($request, $page) ++ { ++ // TODO - extract into shared repo ++ $page->restricted = $request->has('restricted') && $request->get('restricted') === 'true'; ++ $page->restrictions()->delete(); ++ if ($request->has('restrictions')) { ++ foreach($request->get('restrictions') as $roleId => $restrictions) { ++ foreach ($restrictions as $action => $value) { ++ $page->restrictions()->create([ ++ 'role_id' => $roleId, ++ 'action' => strtolower($action) ++ ]); ++ } ++ } ++ } ++ $page->save(); ++ } ++ ++}