3 namespace BookStack\Http\Controllers;
5 use BookStack\Actions\View;
6 use BookStack\Entities\Models\Page;
7 use BookStack\Entities\Repos\PageRepo;
8 use BookStack\Entities\Tools\BookContents;
9 use BookStack\Entities\Tools\NextPreviousContentLocator;
10 use BookStack\Entities\Tools\PageContent;
11 use BookStack\Entities\Tools\PageEditActivity;
12 use BookStack\Entities\Tools\PermissionsUpdater;
13 use BookStack\Exceptions\NotFoundException;
14 use BookStack\Exceptions\PermissionsException;
16 use Illuminate\Http\Request;
17 use Illuminate\Validation\ValidationException;
20 class PageController extends Controller
25 * PageController constructor.
27 public function __construct(PageRepo $pageRepo)
29 $this->pageRepo = $pageRepo;
33 * Show the form for creating a new page.
37 public function create(string $bookSlug, string $chapterSlug = null)
39 $parent = $this->pageRepo->getParentFromSlugs($bookSlug, $chapterSlug);
40 $this->checkOwnablePermission('page-create', $parent);
42 // Redirect to draft edit screen if signed in
43 if ($this->isSignedIn()) {
44 $draft = $this->pageRepo->getNewDraftPage($parent);
46 return redirect($draft->getUrl());
49 // Otherwise show the edit view if they're a guest
50 $this->setPageTitle(trans('entities.pages_new'));
52 return view('pages.guest-create', ['parent' => $parent]);
56 * Create a new page as a guest user.
58 * @throws ValidationException
60 public function createAsGuest(Request $request, string $bookSlug, string $chapterSlug = null)
62 $this->validate($request, [
63 'name' => 'required|string|max:255',
66 $parent = $this->pageRepo->getParentFromSlugs($bookSlug, $chapterSlug);
67 $this->checkOwnablePermission('page-create', $parent);
69 $page = $this->pageRepo->getNewDraftPage($parent);
70 $this->pageRepo->publishDraft($page, [
71 'name' => $request->get('name'),
75 return redirect($page->getUrl('/edit'));
79 * Show form to continue editing a draft page.
81 * @throws NotFoundException
83 public function editDraft(string $bookSlug, int $pageId)
85 $draft = $this->pageRepo->getById($pageId);
86 $this->checkOwnablePermission('page-create', $draft->getParent());
87 $this->setPageTitle(trans('entities.pages_edit_draft'));
89 $draftsEnabled = $this->isSignedIn();
90 $templates = $this->pageRepo->getTemplates(10);
92 return view('pages.edit', [
94 'book' => $draft->book,
96 'draftsEnabled' => $draftsEnabled,
97 'templates' => $templates,
102 * Store a new page by changing a draft into a page.
104 * @throws NotFoundException
105 * @throws ValidationException
107 public function store(Request $request, string $bookSlug, int $pageId)
109 $this->validate($request, [
110 'name' => 'required|string|max:255',
112 $draftPage = $this->pageRepo->getById($pageId);
113 $this->checkOwnablePermission('page-create', $draftPage->getParent());
115 $page = $this->pageRepo->publishDraft($draftPage, $request->all());
117 return redirect($page->getUrl());
121 * Display the specified page.
122 * If the page is not found via the slug the revisions are searched for a match.
124 * @throws NotFoundException
126 public function show(string $bookSlug, string $pageSlug)
129 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
130 } catch (NotFoundException $e) {
131 $page = $this->pageRepo->getByOldSlug($bookSlug, $pageSlug);
133 if ($page === null) {
137 return redirect($page->getUrl());
140 $this->checkOwnablePermission('page-view', $page);
142 $pageContent = (new PageContent($page));
143 $page->html = $pageContent->render();
144 $sidebarTree = (new BookContents($page->book))->getTree();
145 $pageNav = $pageContent->getNavigation($page->html);
147 // Check if page comments are enabled
148 $commentsEnabled = !setting('app-disable-comments');
149 if ($commentsEnabled) {
150 $page->load(['comments.createdBy']);
153 $nextPreviousLocator = new NextPreviousContentLocator($page, $sidebarTree);
155 View::incrementFor($page);
156 $this->setPageTitle($page->getShortName());
158 return view('pages.show', [
160 'book' => $page->book,
162 'sidebarTree' => $sidebarTree,
163 'commentsEnabled' => $commentsEnabled,
164 'pageNav' => $pageNav,
165 'next' => $nextPreviousLocator->getNext(),
166 'previous' => $nextPreviousLocator->getPrevious(),
171 * Get page from an ajax request.
173 * @throws NotFoundException
175 public function getPageAjax(int $pageId)
177 $page = $this->pageRepo->getById($pageId);
178 $page->setHidden(array_diff($page->getHidden(), ['html', 'markdown']));
179 $page->addHidden(['book']);
181 return response()->json($page);
185 * Show the form for editing the specified page.
187 * @throws NotFoundException
189 public function edit(string $bookSlug, string $pageSlug)
191 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
192 $this->checkOwnablePermission('page-update', $page);
194 $page->isDraft = false;
195 $editActivity = new PageEditActivity($page);
197 // Check for active editing
199 if ($editActivity->hasActiveEditing()) {
200 $warnings[] = $editActivity->activeEditingMessage();
203 // Check for a current draft version for this user
204 $userDraft = $this->pageRepo->getUserDraft($page);
205 if ($userDraft !== null) {
206 $page->forceFill($userDraft->only(['name', 'html', 'markdown']));
207 $page->isDraft = true;
208 $warnings[] = $editActivity->getEditingActiveDraftMessage($userDraft);
211 if (count($warnings) > 0) {
212 $this->showWarningNotification(implode("\n", $warnings));
215 $templates = $this->pageRepo->getTemplates(10);
216 $draftsEnabled = $this->isSignedIn();
217 $this->setPageTitle(trans('entities.pages_editing_named', ['pageName' => $page->getShortName()]));
219 return view('pages.edit', [
221 'book' => $page->book,
223 'draftsEnabled' => $draftsEnabled,
224 'templates' => $templates,
229 * Update the specified page in storage.
231 * @throws ValidationException
232 * @throws NotFoundException
234 public function update(Request $request, string $bookSlug, string $pageSlug)
236 $this->validate($request, [
237 'name' => 'required|string|max:255',
239 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
240 $this->checkOwnablePermission('page-update', $page);
242 $this->pageRepo->update($page, $request->all());
244 return redirect($page->getUrl());
248 * Save a draft update as a revision.
250 * @throws NotFoundException
252 public function saveDraft(Request $request, int $pageId)
254 $page = $this->pageRepo->getById($pageId);
255 $this->checkOwnablePermission('page-update', $page);
257 if (!$this->isSignedIn()) {
258 return $this->jsonError(trans('errors.guests_cannot_save_drafts'), 500);
261 $draft = $this->pageRepo->updatePageDraft($page, $request->only(['name', 'html', 'markdown']));
262 $warnings = (new PageEditActivity($page))->getWarningMessagesForDraft($draft);
264 return response()->json([
265 'status' => 'success',
266 'message' => trans('entities.pages_edit_draft_save_at'),
267 'warning' => implode("\n", $warnings),
268 'timestamp' => $draft->updated_at->timestamp,
273 * Redirect from a special link url which uses the page id rather than the name.
275 * @throws NotFoundException
277 public function redirectFromLink(int $pageId)
279 $page = $this->pageRepo->getById($pageId);
281 return redirect($page->getUrl());
285 * Show the deletion page for the specified page.
287 * @throws NotFoundException
289 public function showDelete(string $bookSlug, string $pageSlug)
291 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
292 $this->checkOwnablePermission('page-delete', $page);
293 $this->setPageTitle(trans('entities.pages_delete_named', ['pageName' => $page->getShortName()]));
295 return view('pages.delete', [
296 'book' => $page->book,
303 * Show the deletion page for the specified page.
305 * @throws NotFoundException
307 public function showDeleteDraft(string $bookSlug, int $pageId)
309 $page = $this->pageRepo->getById($pageId);
310 $this->checkOwnablePermission('page-update', $page);
311 $this->setPageTitle(trans('entities.pages_delete_draft_named', ['pageName' => $page->getShortName()]));
313 return view('pages.delete', [
314 'book' => $page->book,
321 * Remove the specified page from storage.
323 * @throws NotFoundException
326 public function destroy(string $bookSlug, string $pageSlug)
328 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
329 $this->checkOwnablePermission('page-delete', $page);
330 $parent = $page->getParent();
332 $this->pageRepo->destroy($page);
334 return redirect($parent->getUrl());
338 * Remove the specified draft page from storage.
340 * @throws NotFoundException
343 public function destroyDraft(string $bookSlug, int $pageId)
345 $page = $this->pageRepo->getById($pageId);
347 $chapter = $page->chapter;
348 $this->checkOwnablePermission('page-update', $page);
350 $this->pageRepo->destroy($page);
352 $this->showSuccessNotification(trans('entities.pages_delete_draft_success'));
354 if ($chapter && userCan('view', $chapter)) {
355 return redirect($chapter->getUrl());
358 return redirect($book->getUrl());
362 * Show a listing of recently created pages.
364 public function showRecentlyUpdated()
366 $pages = Page::visible()->orderBy('updated_at', 'desc')
368 ->setPath(url('/pages/recently-updated'));
370 return view('common.detailed-listing-paginated', [
371 'title' => trans('entities.recently_updated_pages'),
372 'entities' => $pages,
377 * Show the view to choose a new parent to move a page into.
379 * @throws NotFoundException
381 public function showMove(string $bookSlug, string $pageSlug)
383 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
384 $this->checkOwnablePermission('page-update', $page);
385 $this->checkOwnablePermission('page-delete', $page);
387 return view('pages.move', [
388 'book' => $page->book,
394 * Does the action of moving the location of a page.
396 * @throws NotFoundException
399 public function move(Request $request, string $bookSlug, string $pageSlug)
401 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
402 $this->checkOwnablePermission('page-update', $page);
403 $this->checkOwnablePermission('page-delete', $page);
405 $entitySelection = $request->get('entity_selection', null);
406 if ($entitySelection === null || $entitySelection === '') {
407 return redirect($page->getUrl());
411 $parent = $this->pageRepo->move($page, $entitySelection);
412 } catch (Exception $exception) {
413 if ($exception instanceof PermissionsException) {
414 $this->showPermissionError();
417 $this->showErrorNotification(trans('errors.selected_book_chapter_not_found'));
419 return redirect()->back();
422 $this->showSuccessNotification(trans('entities.pages_move_success', ['parentName' => $parent->name]));
424 return redirect($page->getUrl());
428 * Show the view to copy a page.
430 * @throws NotFoundException
432 public function showCopy(string $bookSlug, string $pageSlug)
434 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
435 $this->checkOwnablePermission('page-view', $page);
436 session()->flashInput(['name' => $page->name]);
438 return view('pages.copy', [
439 'book' => $page->book,
445 * Create a copy of a page within the requested target destination.
447 * @throws NotFoundException
450 public function copy(Request $request, string $bookSlug, string $pageSlug)
452 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
453 $this->checkOwnablePermission('page-view', $page);
455 $entitySelection = $request->get('entity_selection', null) ?? null;
456 $newName = $request->get('name', null);
459 $pageCopy = $this->pageRepo->copy($page, $entitySelection, $newName);
460 } catch (Exception $exception) {
461 if ($exception instanceof PermissionsException) {
462 $this->showPermissionError();
465 $this->showErrorNotification(trans('errors.selected_book_chapter_not_found'));
467 return redirect()->back();
470 $this->showSuccessNotification(trans('entities.pages_copy_success'));
472 return redirect($pageCopy->getUrl());
476 * Show the Permissions view.
478 * @throws NotFoundException
480 public function showPermissions(string $bookSlug, string $pageSlug)
482 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
483 $this->checkOwnablePermission('restrictions-manage', $page);
485 return view('pages.permissions', [
491 * Set the permissions for this page.
493 * @throws NotFoundException
496 public function permissions(Request $request, PermissionsUpdater $permissionsUpdater, string $bookSlug, string $pageSlug)
498 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
499 $this->checkOwnablePermission('restrictions-manage', $page);
501 $permissionsUpdater->updateFromPermissionsForm($page, $request);
503 $this->showSuccessNotification(trans('entities.pages_permissions_success'));
505 return redirect($page->getUrl());