X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/4656c12f6dafc9256cd3dd8de3065ec12feaa23f..refs/pull/205/head:/app/Repos/PageRepo.php diff --git a/app/Repos/PageRepo.php b/app/Repos/PageRepo.php index 4c3512fa7..8cd5c35a9 100644 --- a/app/Repos/PageRepo.php +++ b/app/Repos/PageRepo.php @@ -3,9 +3,12 @@ use Activity; use BookStack\Book; use BookStack\Chapter; +use BookStack\Entity; use BookStack\Exceptions\NotFoundException; +use BookStack\Services\FileService; use Carbon\Carbon; use DOMDocument; +use DOMXPath; use Illuminate\Support\Str; use BookStack\Page; use BookStack\PageRevision; @@ -14,14 +17,17 @@ class PageRepo extends EntityRepo { protected $pageRevision; + protected $tagRepo; /** * PageRepo constructor. * @param PageRevision $pageRevision + * @param TagRepo $tagRepo */ - public function __construct(PageRevision $pageRevision) + public function __construct(PageRevision $pageRevision, TagRepo $tagRepo) { $this->pageRevision = $pageRevision; + $this->tagRepo = $tagRepo; parent::__construct(); } @@ -32,7 +38,7 @@ class PageRepo extends EntityRepo */ private function pageQuery($allowDrafts = false) { - $query = $this->restrictionService->enforcePageRestrictions($this->page, 'view'); + $query = $this->permissionService->enforcePageRestrictions($this->page, 'view'); if (!$allowDrafts) { $query = $query->where('draft', '=', false); } @@ -43,7 +49,7 @@ class PageRepo extends EntityRepo * Get a page via a specific ID. * @param $id * @param bool $allowDrafts - * @return mixed + * @return Page */ public function getById($id, $allowDrafts = false) { @@ -76,7 +82,7 @@ class PageRepo extends EntityRepo { $revision = $this->pageRevision->where('slug', '=', $pageSlug) ->whereHas('page', function ($query) { - $this->restrictionService->enforcePageRestrictions($query); + $this->permissionService->enforcePageRestrictions($query); }) ->where('type', '=', 'version') ->where('book_slug', '=', $bookSlug)->orderBy('created_at', 'desc') @@ -106,31 +112,6 @@ class PageRepo extends EntityRepo return $this->page->where('slug', '=', $slug)->where('book_id', '=', $bookId)->count(); } - /** - * 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; - } - - /** * Publish a draft page to make it a normal page. * Sets the slug and updates the content. @@ -142,35 +123,72 @@ class PageRepo extends EntityRepo { $draftPage->fill($input); + // Save page tags if present + if (isset($input['tags'])) { + $this->tagRepo->saveTagsToEntity($draftPage, $input['tags']); + } + $draftPage->slug = $this->findSuitableSlug($draftPage->name, $draftPage->book->id); $draftPage->html = $this->formatHtml($input['html']); $draftPage->text = strip_tags($draftPage->html); $draftPage->draft = false; $draftPage->save(); + $this->saveRevision($draftPage, 'Initial Publish'); + return $draftPage; } /** * Get a new draft page instance. * @param Book $book - * @param Chapter|null $chapter + * @param Chapter|bool $chapter * @return static */ - public function getDraftPage(Book $book, $chapter) + public function getDraftPage(Book $book, $chapter = false) { $page = $this->page->newInstance(); $page->name = 'New Page'; - $page->created_by = auth()->user()->id; - $page->updated_by = auth()->user()->id; + $page->created_by = user()->id; + $page->updated_by = user()->id; $page->draft = true; if ($chapter) $page->chapter_id = $chapter->id; $book->pages()->save($page); + $this->permissionService->buildJointPermissionsForEntity($page); return $page; } + /** + * Parse te headers on the page to get a navigation menu + * @param Page $page + * @return array + */ + public function getPageNav(Page $page) + { + if ($page->html == '') return null; + libxml_use_internal_errors(true); + $doc = new DOMDocument(); + $doc->loadHTML(mb_convert_encoding($page->html, 'HTML-ENTITIES', 'UTF-8')); + $xPath = new DOMXPath($doc); + $headers = $xPath->query("//h1|//h2|//h3|//h4|//h5|//h6"); + + if (is_null($headers)) return null; + + $tree = []; + foreach ($headers as $header) { + $text = $header->nodeValue; + $tree[] = [ + 'nodeName' => strtolower($header->nodeName), + 'level' => intval(str_replace('h', '', $header->nodeName)), + 'link' => '#' . $header->getAttribute('id'), + 'text' => strlen($text) > 30 ? substr($text, 0, 27) . '...' : $text + ]; + } + return $tree; + } + /** * Formats a page's html to be tagged correctly * within the system. @@ -241,8 +259,9 @@ class PageRepo extends EntityRepo public function getBySearch($term, $whereTerms = [], $count = 20, $paginationAppends = []) { $terms = $this->prepareSearchTerms($term); - $pages = $this->restrictionService->enforcePageRestrictions($this->page->fullTextSearchQuery(['name', 'text'], $terms, $whereTerms)) - ->paginate($count)->appends($paginationAppends); + $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), '/'))); @@ -297,27 +316,37 @@ class PageRepo extends EntityRepo */ 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); - } + // 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($input['name'], $book_id, $page->id); } + // Save page tags if present + if (isset($input['tags'])) { + $this->tagRepo->saveTagsToEntity($page, $input['tags']); + } + // Update with new details - $userId = auth()->user()->id; + $userId = user()->id; $page->fill($input); $page->html = $this->formatHtml($input['html']); $page->text = strip_tags($page->html); + if (setting('app-editor') !== 'markdown') $page->markdown = ''; $page->updated_by = $userId; $page->save(); // Remove all update drafts for this user & page. $this->userUpdateDraftsQuery($page, $userId)->delete(); + // Save a revision after updating + if ($oldHtml !== $input['html'] || $oldName !== $input['name'] || $input['summary'] !== null) { + $this->saveRevision($page, $input['summary']); + } + return $page; } @@ -335,7 +364,7 @@ class PageRepo extends EntityRepo $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->updated_by = user()->id; $page->save(); return $page; } @@ -343,23 +372,28 @@ class PageRepo extends EntityRepo /** * Saves a page revision into the system. * @param Page $page + * @param null|string $summary * @return $this */ - public function saveRevision(Page $page) + public function saveRevision(Page $page, $summary = null) { - $revision = $this->pageRevision->fill($page->toArray()); + $revision = $this->pageRevision->newInstance($page->toArray()); + if (setting('app-editor') !== 'markdown') $revision->markdown = ''; $revision->page_id = $page->id; $revision->slug = $page->slug; $revision->book_slug = $page->book->slug; - $revision->created_by = auth()->user()->id; + $revision->created_by = user()->id; $revision->created_at = $page->updated_at; $revision->type = 'version'; + $revision->summary = $summary; $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; } @@ -371,7 +405,7 @@ class PageRepo extends EntityRepo */ public function saveUpdateDraft(Page $page, $data = []) { - $userId = auth()->user()->id; + $userId = user()->id; $drafts = $this->userUpdateDraftsQuery($page, $userId)->get(); if ($drafts->count() > 0) { @@ -386,6 +420,8 @@ class PageRepo extends EntityRepo } $draft->fill($data); + if (setting('app-editor') !== 'markdown') $draft->markdown = ''; + $draft->save(); return $draft; } @@ -500,7 +536,7 @@ class PageRepo extends EntityRepo $query = $this->pageRevision->where('type', '=', 'update_draft') ->where('page_id', '=', $page->id) ->where('updated_at', '>', $page->updated_at) - ->where('created_by', '!=', auth()->user()->id) + ->where('created_by', '!=', user()->id) ->with('createdBy'); if ($minRange !== null) { @@ -513,7 +549,7 @@ class PageRepo extends EntityRepo /** * Gets a single revision via it's id. * @param $id - * @return mixed + * @return PageRevision */ public function getRevisionById($id) { @@ -553,16 +589,33 @@ class PageRepo extends EntityRepo return $page; } + + /** + * Change the page's parent to the given entity. + * @param Page $page + * @param Entity $parent + */ + public function changePageParent(Page $page, Entity $parent) + { + $book = $parent->isA('book') ? $parent : $parent->book; + $page->chapter_id = $parent->isA('chapter') ? $parent->id : 0; + $page->save(); + $page = $this->changeBook($book->id, $page); + $page->load('book'); + $this->permissionService->buildJointPermissionsForEntity($book); + } + /** * Gets a suitable slug for the resource - * @param $name - * @param $bookId + * @param string $name + * @param int $bookId * @param bool|false $currentId * @return string */ public function findSuitableSlug($name, $bookId, $currentId = false) { $slug = Str::slug($name); + if ($slug === "") $slug = substr(md5(rand(1, 500)), 0, 5); while ($this->doesSlugExist($slug, $bookId, $currentId)) { $slug .= '-' . substr(md5(rand(1, 500)), 0, 3); } @@ -573,12 +626,21 @@ class PageRepo extends EntityRepo * Destroy a given page along with its dependencies. * @param $page */ - public function destroy($page) + public function destroy(Page $page) { Activity::removeEntity($page); $page->views()->delete(); + $page->tags()->delete(); $page->revisions()->delete(); - $page->restrictions()->delete(); + $page->permissions()->delete(); + $this->permissionService->deleteJointPermissionsForEntity($page); + + // Delete AttachedFiles + $fileService = app(FileService::class); + foreach ($page->files as $file) { + $fileService->deleteFile($file); + } + $page->delete(); }