X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/07b889547d28e68e5fc8f923c166bd607da17ad7..refs/pull/2522/head:/app/Entities/Repos/PageRepo.php diff --git a/app/Entities/Repos/PageRepo.php b/app/Entities/Repos/PageRepo.php index 1aeee8dae..4c59db468 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -1,280 +1,265 @@ getBySlug('page', $pageSlug, $bookSlug); + $this->baseRepo = $baseRepo; } /** - * Search through page revisions and retrieve the last page in the - * current book that has a slug equal to the one given. - * @param string $pageSlug - * @param string $bookSlug - * @return null|Page + * Get a page by ID. + * @throws NotFoundException */ - public function getPageByOldSlug(string $pageSlug, string $bookSlug) + public function getById(int $id, array $relations = ['book']): Page { - $revision = $this->entityProvider->pageRevision->where('slug', '=', $pageSlug) - ->whereHas('page', function ($query) { - $this->permissionService->enforceEntityRestrictions('page', $query); - }) - ->where('type', '=', 'version') - ->where('book_slug', '=', $bookSlug) - ->orderBy('created_at', 'desc') - ->with('page')->first(); - return $revision !== null ? $revision->page : null; + $page = Page::visible()->with($relations)->find($id); + + if (!$page) { + throw new NotFoundException(trans('errors.page_not_found')); + } + + return $page; } /** - * Updates a page with any fillable data and saves it into the database. - * @param Page $page - * @param int $book_id - * @param array $input - * @return Page - * @throws \Exception + * Get a page its book and own slug. + * @throws NotFoundException */ - public function updatePage(Page $page, int $book_id, array $input) + public function getBySlug(string $bookSlug, string $pageSlug): Page { - // Hold the old details to compare later - $oldHtml = $page->html; - $oldName = $page->name; - - // Prevent slug being updated if no name change - if ($page->name !== $input['name']) { - $page->slug = $this->findSuitableSlug('page', $input['name'], $page->id, $book_id); - } + $page = Page::visible()->whereSlugs($bookSlug, $pageSlug)->first(); - // Save page tags if present - if (isset($input['tags'])) { - $this->tagRepo->saveTagsToEntity($page, $input['tags']); + if (!$page) { + throw new NotFoundException(trans('errors.page_not_found')); } - // Update with new details - $userId = user()->id; - $page->fill($input); - $page->html = $this->formatHtml($input['html']); - $page->text = $this->pageToPlainText($page); - if (setting('app-editor') !== 'markdown') { - $page->markdown = ''; - } - $page->updated_by = $userId; - $page->revision_count++; - $page->save(); + return $page; + } - // Remove all update drafts for this user & page. - $this->userUpdatePageDraftsQuery($page, $userId)->delete(); + /** + * Get a page by its old slug but checking the revisions table + * for the last revision that matched the given page and book slug. + */ + public function getByOldSlug(string $bookSlug, string $pageSlug): ?Page + { + $revision = PageRevision::query() + ->whereHas('page', function (Builder $query) { + $query->visible(); + }) + ->where('slug', '=', $pageSlug) + ->where('type', '=', 'version') + ->where('book_slug', '=', $bookSlug) + ->orderBy('created_at', 'desc') + ->with('page') + ->first(); + return $revision ? $revision->page : null; + } - // Save a revision after updating - if ($oldHtml !== $input['html'] || $oldName !== $input['name'] || $input['summary'] !== null) { - $this->savePageRevision($page, $input['summary']); + /** + * Get pages that have been marked as a template. + */ + public function getTemplates(int $count = 10, int $page = 1, string $search = ''): LengthAwarePaginator + { + $query = Page::visible() + ->where('template', '=', true) + ->orderBy('name', 'asc') + ->skip(($page - 1) * $count) + ->take($count); + + if ($search) { + $query->where('name', 'like', '%' . $search . '%'); } - $this->searchService->indexEntity($page); + $paginator = $query->paginate($count, ['*'], 'page', $page); + $paginator->withPath('/templates'); - return $page; + return $paginator; } /** - * Saves a page revision into the system. - * @param Page $page - * @param null|string $summary - * @return PageRevision - * @throws \Exception + * Get a parent item via slugs. */ - public function savePageRevision(Page $page, string $summary = null) + public function getParentFromSlugs(string $bookSlug, string $chapterSlug = null): Entity { - $revision = $this->entityProvider->pageRevision->newInstance($page->toArray()); - if (setting('app-editor') !== 'markdown') { - $revision->markdown = ''; + if ($chapterSlug !== null) { + return $chapter = Chapter::visible()->whereSlugs($bookSlug, $chapterSlug)->firstOrFail(); } - $revision->page_id = $page->id; - $revision->slug = $page->slug; - $revision->book_slug = $page->book->slug; - $revision->created_by = user()->id; - $revision->created_at = $page->updated_at; - $revision->type = 'version'; - $revision->summary = $summary; - $revision->revision_number = $page->revision_count; - $revision->save(); - $revisionLimit = config('app.revision_limit'); - if ($revisionLimit !== false) { - $revisionsToDelete = $this->entityProvider->pageRevision->where('page_id', '=', $page->id) - ->orderBy('created_at', 'desc')->skip(intval($revisionLimit))->take(10)->get(['id']); - if ($revisionsToDelete->count() > 0) { - $this->entityProvider->pageRevision->whereIn('id', $revisionsToDelete->pluck('id'))->delete(); - } - } + return Book::visible()->where('slug', '=', $bookSlug)->firstOrFail(); + } + /** + * Get the draft copy of the given page for the current user. + */ + public function getUserDraft(Page $page): ?PageRevision + { + $revision = $this->getUserDraftQuery($page)->first(); return $revision; } /** - * Formats a page's html to be tagged correctly within the system. - * @param string $htmlText - * @return string + * Get a new draft page belonging to the given parent entity. */ - protected function formatHtml(string $htmlText) + public function getNewDraftPage(Entity $parent) { - if ($htmlText == '') { - return $htmlText; + $page = (new Page())->forceFill([ + 'name' => trans('entities.pages_initial_name'), + 'created_by' => user()->id, + 'owned_by' => user()->id, + 'updated_by' => user()->id, + 'draft' => true, + ]); + + if ($parent instanceof Chapter) { + $page->chapter_id = $parent->id; + $page->book_id = $parent->book_id; + } else { + $page->book_id = $parent->id; } - 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; + $page->save(); + $page->refresh()->rebuildPermissions(); + return $page; + } - // Set ids on top-level nodes - $idMap = []; - foreach ($childNodes as $index => $childNode) { - $this->setUniqueId($childNode, $idMap); - } + /** + * Publish a draft page to make it a live, non-draft page. + */ + public function publishDraft(Page $draft, array $input): Page + { + $this->baseRepo->update($draft, $input); + $this->updateTemplateStatusAndContentFromInput($draft, $input); - // Ensure no duplicate ids within child items - $xPath = new DOMXPath($doc); - $idElems = $xPath->query('//body//*//*[@id]'); - foreach ($idElems as $domElem) { - $this->setUniqueId($domElem, $idMap); - } + $draft->draft = false; + $draft->revision_count = 1; + $draft->priority = $this->getNewPriority($draft); + $draft->refreshSlug(); + $draft->save(); - // Generate inner html as a string - $html = ''; - foreach ($childNodes as $childNode) { - $html .= $doc->saveHTML($childNode); - } + $this->savePageRevision($draft, trans('entities.pages_initial_revision')); + $draft->indexForSearch(); + $draft->refresh(); - return $html; + Activity::addForEntity($draft, ActivityType::PAGE_CREATE); + return $draft; } /** - * Set a unique id on the given DOMElement. - * A map for existing ID's should be passed in to check for current existence. - * @param DOMElement $element - * @param array $idMap + * Update a page in the system. */ - protected function setUniqueId($element, array &$idMap) + public function update(Page $page, array $input): Page { - if (get_class($element) !== 'DOMElement') { - return; - } + // Hold the old details to compare later + $oldHtml = $page->html; + $oldName = $page->name; - // Overwrite id if not a BookStack custom id - $existingId = $element->getAttribute('id'); - if (strpos($existingId, 'bkmrk') === 0 && !isset($idMap[$existingId])) { - $idMap[$existingId] = true; - return; + $this->updateTemplateStatusAndContentFromInput($page, $input); + $this->baseRepo->update($page, $input); + + // Update with new details + $page->revision_count++; + + if (setting('app-editor') !== 'markdown') { + $page->markdown = ''; } - // 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($element->nodeValue))), 0, 20); - $newId = urlencode($contentId); - $loopIndex = 0; + $page->save(); - while (isset($idMap[$newId])) { - $newId = urlencode($contentId . '-' . $loopIndex); - $loopIndex++; + // Remove all update drafts for this user & page. + $this->getUserDraftQuery($page)->delete(); + + // Save a revision after updating + $summary = $input['summary'] ?? null; + if ($oldHtml !== $input['html'] || $oldName !== $input['name'] || $summary !== null) { + $this->savePageRevision($page, $summary); } - $element->setAttribute('id', $newId); - $idMap[$newId] = true; + Activity::addForEntity($page, ActivityType::PAGE_UPDATE); + return $page; } - /** - * Get the plain text version of a page's content. - * @param \BookStack\Entities\Page $page - * @return string - */ - protected function pageToPlainText(Page $page) : string + protected function updateTemplateStatusAndContentFromInput(Page $page, array $input) { - $html = $this->renderPage($page, true); - return strip_tags($html); + if (isset($input['template']) && userCan('templates-manage')) { + $page->template = ($input['template'] === 'true'); + } + + $pageContent = new PageContent($page); + if (!empty($input['markdown'] ?? '')) { + $pageContent->setNewMarkdown($input['markdown']); + } else { + $pageContent->setNewHTML($input['html']); + } } /** - * Get a new draft page instance. - * @param Book $book - * @param Chapter|null $chapter - * @return \BookStack\Entities\Page - * @throws \Throwable + * Saves a page revision into the system. */ - public function getDraftPage(Book $book, Chapter $chapter = null) + protected function savePageRevision(Page $page, string $summary = null): PageRevision { - $page = $this->entityProvider->page->newInstance(); - $page->name = trans('entities.pages_initial_name'); - $page->created_by = user()->id; - $page->updated_by = user()->id; - $page->draft = true; + $revision = new PageRevision($page->getAttributes()); - if ($chapter) { - $page->chapter_id = $chapter->id; + if (setting('app-editor') !== 'markdown') { + $revision->markdown = ''; } - $book->pages()->save($page); - $page = $this->entityProvider->page->find($page->id); - $this->permissionService->buildJointPermissionsForEntity($page); - return $page; + $revision->page_id = $page->id; + $revision->slug = $page->slug; + $revision->book_slug = $page->book->slug; + $revision->created_by = user()->id; + $revision->created_at = $page->updated_at; + $revision->type = 'version'; + $revision->summary = $summary; + $revision->revision_number = $page->revision_count; + $revision->save(); + + $this->deleteOldRevisions($page); + return $revision; } /** * Save a page update draft. - * @param Page $page - * @param array $data - * @return PageRevision|Page */ - public function updatePageDraft(Page $page, array $data = []) + public function updatePageDraft(Page $page, array $input) { // If the page itself is a draft simply update that if ($page->draft) { - $page->fill($data); - if (isset($data['html'])) { - $page->text = $this->pageToPlainText($page); + if (isset($input['html'])) { + (new PageContent($page))->setNewHTML($input['html']); } + $page->fill($input); $page->save(); return $page; } // Otherwise save the data to a revision - $userId = user()->id; - $drafts = $this->userUpdatePageDraftsQuery($page, $userId)->get(); - - if ($drafts->count() > 0) { - $draft = $drafts->first(); - } else { - $draft = $this->entityProvider->pageRevision->newInstance(); - $draft->page_id = $page->id; - $draft->slug = $page->slug; - $draft->book_slug = $page->book->slug; - $draft->created_by = $userId; - $draft->type = 'update_draft'; - } - - $draft->fill($data); + $draft = $this->getPageRevisionToUpdate($page); + $draft->fill($input); if (setting('app-editor') !== 'markdown') { $draft->markdown = ''; } @@ -284,241 +269,202 @@ class PageRepo extends EntityRepo } /** - * Publish a draft page to make it a normal page. - * Sets the slug and updates the content. - * @param Page $draftPage - * @param array $input - * @return Page - * @throws \Exception + * Destroy a page from the system. + * @throws Exception */ - public function publishPageDraft(Page $draftPage, array $input) + public function destroy(Page $page) { - $draftPage->fill($input); - - // Save page tags if present - if (isset($input['tags'])) { - $this->tagRepo->saveTagsToEntity($draftPage, $input['tags']); - } - - $draftPage->slug = $this->findSuitableSlug('page', $draftPage->name, false, $draftPage->book->id); - $draftPage->html = $this->formatHtml($input['html']); - $draftPage->text = $this->pageToPlainText($draftPage); - $draftPage->draft = false; - $draftPage->revision_count = 1; - - $draftPage->save(); - $this->savePageRevision($draftPage, trans('entities.pages_initial_revision')); - $this->searchService->indexEntity($draftPage); - return $draftPage; + $trashCan = new TrashCan(); + $trashCan->softDestroyPage($page); + Activity::addForEntity($page, ActivityType::PAGE_DELETE); + $trashCan->autoClearOld(); } /** - * The base query for getting user update drafts. - * @param Page $page - * @param $userId - * @return mixed + * Restores a revision's content back into a page. */ - protected function userUpdatePageDraftsQuery(Page $page, int $userId) + public function restoreRevision(Page $page, int $revisionId): Page { - return $this->entityProvider->pageRevision->where('created_by', '=', $userId) - ->where('type', 'update_draft') - ->where('page_id', '=', $page->id) - ->orderBy('created_at', 'desc'); - } + $page->revision_count++; + $revision = $page->revisions()->where('id', '=', $revisionId)->first(); - /** - * Get the latest updated draft revision for a particular page and user. - * @param Page $page - * @param $userId - * @return PageRevision|null - */ - public function getUserPageDraft(Page $page, int $userId) - { - return $this->userUpdatePageDraftsQuery($page, $userId)->first(); - } + $page->fill($revision->toArray()); + $content = new PageContent($page); + $content->setNewHTML($revision->html); + $page->updated_by = user()->id; + $page->refreshSlug(); + $page->save(); + $page->indexForSearch(); - /** - * Get the notification message that informs the user that they are editing a draft page. - * @param PageRevision $draft - * @return string - */ - public function getUserPageDraftMessage(PageRevision $draft) - { - $message = trans('entities.pages_editing_draft_notification', ['timeDiff' => $draft->updated_at->diffForHumans()]); - if ($draft->page->updated_at->timestamp <= $draft->updated_at->timestamp) { - return $message; - } - return $message . "\n" . trans('entities.pages_draft_edited_notification'); + $summary = trans('entities.pages_revision_restored_from', ['id' => strval($revisionId), 'summary' => $revision->summary]); + $this->savePageRevision($page, $summary); + + Activity::addForEntity($page, ActivityType::PAGE_RESTORE); + return $page; } /** - * A query to check for active update drafts on a particular page. - * @param Page $page - * @param int $minRange - * @return mixed + * Move the given page into a new parent book or chapter. + * The $parentIdentifier must be a string of the following format: + * 'book:' (book:5) + * @throws MoveOperationException + * @throws PermissionsException */ - protected function activePageEditingQuery(Page $page, int $minRange = null) + public function move(Page $page, string $parentIdentifier): Entity { - $query = $this->entityProvider->pageRevision->where('type', '=', 'update_draft') - ->where('page_id', '=', $page->id) - ->where('updated_at', '>', $page->updated_at) - ->where('created_by', '!=', user()->id) - ->with('createdBy'); - - if ($minRange !== null) { - $query = $query->where('updated_at', '>=', Carbon::now()->subMinutes($minRange)); + $parent = $this->findParentByIdentifier($parentIdentifier); + if ($parent === null) { + throw new MoveOperationException('Book or chapter to move page into not found'); } - return $query; - } - - /** - * Check if a page is being actively editing. - * Checks for edits since last page updated. - * Passing in a minuted range will check for edits - * within the last x minutes. - * @param Page $page - * @param int $minRange - * @return bool - */ - public function isPageEditingActive(Page $page, int $minRange = null) - { - $draftSearch = $this->activePageEditingQuery($page, $minRange); - return $draftSearch->count() > 0; - } + if (!userCan('page-create', $parent)) { + throw new PermissionsException('User does not have permission to create a page within the new parent'); + } - /** - * Get a notification message concerning the editing activity on a particular page. - * @param Page $page - * @param int $minRange - * @return string - */ - public function getPageEditingActiveMessage(Page $page, int $minRange = null) - { - $pageDraftEdits = $this->activePageEditingQuery($page, $minRange)->get(); + $page->chapter_id = ($parent instanceof Chapter) ? $parent->id : null; + $page->changeBook($parent instanceof Book ? $parent->id : $parent->book->id); + $page->rebuildPermissions(); - $userMessage = $pageDraftEdits->count() > 1 ? trans('entities.pages_draft_edit_active.start_a', ['count' => $pageDraftEdits->count()]): trans('entities.pages_draft_edit_active.start_b', ['userName' => $pageDraftEdits->first()->createdBy->name]); - $timeMessage = $minRange === null ? trans('entities.pages_draft_edit_active.time_a') : trans('entities.pages_draft_edit_active.time_b', ['minCount'=>$minRange]); - return trans('entities.pages_draft_edit_active.message', ['start' => $userMessage, 'time' => $timeMessage]); + Activity::addForEntity($page, ActivityType::PAGE_MOVE); + return $parent; } /** - * Parse the headers on the page to get a navigation menu - * @param string $pageContent - * @return array + * Copy an existing page in the system. + * Optionally providing a new parent via string identifier and a new name. + * @throws MoveOperationException + * @throws PermissionsException */ - public function getPageNav(string $pageContent) + public function copy(Page $page, string $parentIdentifier = null, string $newName = null): Page { - if ($pageContent == '') { - return []; + $parent = $parentIdentifier ? $this->findParentByIdentifier($parentIdentifier) : $page->getParent(); + if ($parent === null) { + throw new MoveOperationException('Book or chapter to move page into not found'); } - libxml_use_internal_errors(true); - $doc = new DOMDocument(); - $doc->loadHTML(mb_convert_encoding($pageContent, 'HTML-ENTITIES', 'UTF-8')); - $xPath = new DOMXPath($doc); - $headers = $xPath->query("//h1|//h2|//h3|//h4|//h5|//h6"); - - if (is_null($headers)) { - return []; + + if (!userCan('page-create', $parent)) { + throw new PermissionsException('User does not have permission to create a page within the new parent'); } - $tree = collect([]); - foreach ($headers as $header) { - $text = $header->nodeValue; - $tree->push([ - 'nodeName' => strtolower($header->nodeName), - 'level' => intval(str_replace('h', '', $header->nodeName)), - 'link' => '#' . $header->getAttribute('id'), - 'text' => strlen($text) > 30 ? substr($text, 0, 27) . '...' : $text - ]); + $copyPage = $this->getNewDraftPage($parent); + $pageData = $page->getAttributes(); + + // Update name + if (!empty($newName)) { + $pageData['name'] = $newName; } - // Normalise headers if only smaller headers have been used - if (count($tree) > 0) { - $minLevel = $tree->pluck('level')->min(); - $tree = $tree->map(function ($header) use ($minLevel) { - $header['level'] -= ($minLevel - 2); - return $header; - }); + // Copy tags from previous page if set + if ($page->tags) { + $pageData['tags'] = []; + foreach ($page->tags as $tag) { + $pageData['tags'][] = ['name' => $tag->name, 'value' => $tag->value]; + } } - return $tree->toArray(); + + return $this->publishDraft($copyPage, $pageData); } /** - * Restores a revision's content back into a page. - * @param Page $page - * @param Book $book - * @param int $revisionId - * @return Page - * @throws \Exception + * Find a page parent entity via a identifier string in the format: + * {type}:{id} + * Example: (book:5) + * @throws MoveOperationException */ - public function restorePageRevision(Page $page, Book $book, int $revisionId) + protected function findParentByIdentifier(string $identifier): ?Entity { - $page->revision_count++; - $this->savePageRevision($page); - $revision = $page->revisions()->where('id', '=', $revisionId)->first(); - $page->fill($revision->toArray()); - $page->slug = $this->findSuitableSlug('page', $page->name, $page->id, $book->id); - $page->text = $this->pageToPlainText($page); - $page->updated_by = user()->id; - $page->save(); - $this->searchService->indexEntity($page); - return $page; + $stringExploded = explode(':', $identifier); + $entityType = $stringExploded[0]; + $entityId = intval($stringExploded[1]); + + if ($entityType !== 'book' && $entityType !== 'chapter') { + throw new MoveOperationException('Pages can only be in books or chapters'); + } + + $parentClass = $entityType === 'book' ? Book::class : Chapter::class; + return $parentClass::visible()->where('id', '=', $entityId)->first(); } /** * Change the page's parent to the given entity. - * @param Page $page - * @param Entity $parent - * @throws \Throwable */ - public function changePageParent(Page $page, Entity $parent) + protected function changeParent(Page $page, Entity $parent) { - $book = $parent->isA('book') ? $parent : $parent->book; - $page->chapter_id = $parent->isA('chapter') ? $parent->id : 0; + $book = ($parent instanceof Book) ? $parent : $parent->book; + $page->chapter_id = ($parent instanceof Chapter) ? $parent->id : 0; $page->save(); + if ($page->book->id !== $book->id) { - $page = $this->changeBook('page', $book->id, $page); + $page->changeBook($book->id); } + $page->load('book'); - $this->permissionService->buildJointPermissionsForEntity($book); + $book->rebuildPermissions(); } /** - * Create a copy of a page in a new location with a new name. - * @param \BookStack\Entities\Page $page - * @param \BookStack\Entities\Entity $newParent - * @param string $newName - * @return \BookStack\Entities\Page - * @throws \Throwable + * Get a page revision to update for the given page. + * Checks for an existing revisions before providing a fresh one. */ - public function copyPage(Page $page, Entity $newParent, string $newName = '') + protected function getPageRevisionToUpdate(Page $page): PageRevision { - $newBook = $newParent->isA('book') ? $newParent : $newParent->book; - $newChapter = $newParent->isA('chapter') ? $newParent : null; - $copyPage = $this->getDraftPage($newBook, $newChapter); - $pageData = $page->getAttributes(); + $drafts = $this->getUserDraftQuery($page)->get(); + if ($drafts->count() > 0) { + return $drafts->first(); + } - // Update name - if (!empty($newName)) { - $pageData['name'] = $newName; + $draft = new PageRevision(); + $draft->page_id = $page->id; + $draft->slug = $page->slug; + $draft->book_slug = $page->book->slug; + $draft->created_by = user()->id; + $draft->type = 'update_draft'; + return $draft; + } + + /** + * Delete old revisions, for the given page, from the system. + */ + protected function deleteOldRevisions(Page $page) + { + $revisionLimit = config('app.revision_limit'); + if ($revisionLimit === false) { + return; } - // Copy tags from previous page if set - if ($page->tags) { - $pageData['tags'] = []; - foreach ($page->tags as $tag) { - $pageData['tags'][] = ['name' => $tag->name, 'value' => $tag->value]; - } + $revisionsToDelete = PageRevision::query() + ->where('page_id', '=', $page->id) + ->orderBy('created_at', 'desc') + ->skip(intval($revisionLimit)) + ->take(10) + ->get(['id']); + if ($revisionsToDelete->count() > 0) { + PageRevision::query()->whereIn('id', $revisionsToDelete->pluck('id'))->delete(); } + } - // Set priority - if ($newParent->isA('chapter')) { - $pageData['priority'] = $this->getNewChapterPriority($newParent); - } else { - $pageData['priority'] = $this->getNewBookPriority($newParent); + /** + * Get a new priority for a page + */ + protected function getNewPriority(Page $page): int + { + $parent = $page->getParent(); + if ($parent instanceof Chapter) { + $lastPage = $parent->pages('desc')->first(); + return $lastPage ? $lastPage->priority + 1 : 0; } - return $this->publishPageDraft($copyPage, $pageData); + return (new BookContents($page->book))->getLastPriority() + 1; + } + + /** + * Get the query to find the user's draft copies of the given page. + */ + protected function getUserDraftQuery(Page $page) + { + return PageRevision::query()->where('created_by', '=', user()->id) + ->where('type', 'update_draft') + ->where('page_id', '=', $page->id) + ->orderBy('created_at', 'desc'); } }