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\Cloner;
10 use BookStack\Entities\Tools\NextPreviousContentLocator;
11 use BookStack\Entities\Tools\PageContent;
12 use BookStack\Entities\Tools\PageEditActivity;
13 use BookStack\Entities\Tools\PermissionsUpdater;
14 use BookStack\Exceptions\NotFoundException;
15 use BookStack\Exceptions\PermissionsException;
17 use Illuminate\Http\Request;
18 use Illuminate\Validation\ValidationException;
21 class PageController extends Controller
26 * PageController constructor.
28 public function __construct(PageRepo $pageRepo)
30 $this->pageRepo = $pageRepo;
34 * Show the form for creating a new page.
38 public function create(string $bookSlug, string $chapterSlug = null)
40 $parent = $this->pageRepo->getParentFromSlugs($bookSlug, $chapterSlug);
41 $this->checkOwnablePermission('page-create', $parent);
43 // Redirect to draft edit screen if signed in
44 if ($this->isSignedIn()) {
45 $draft = $this->pageRepo->getNewDraftPage($parent);
47 return redirect($draft->getUrl());
50 // Otherwise show the edit view if they're a guest
51 $this->setPageTitle(trans('entities.pages_new'));
53 return view('pages.guest-create', ['parent' => $parent]);
57 * Create a new page as a guest user.
59 * @throws ValidationException
61 public function createAsGuest(Request $request, string $bookSlug, string $chapterSlug = null)
63 $this->validate($request, [
64 'name' => ['required', 'string', 'max:255'],
67 $parent = $this->pageRepo->getParentFromSlugs($bookSlug, $chapterSlug);
68 $this->checkOwnablePermission('page-create', $parent);
70 $page = $this->pageRepo->getNewDraftPage($parent);
71 $this->pageRepo->publishDraft($page, [
72 'name' => $request->get('name'),
76 return redirect($page->getUrl('/edit'));
80 * Show form to continue editing a draft page.
82 * @throws NotFoundException
84 public function editDraft(string $bookSlug, int $pageId)
86 $draft = $this->pageRepo->getById($pageId);
87 $this->checkOwnablePermission('page-create', $draft->getParent());
88 $this->setPageTitle(trans('entities.pages_edit_draft'));
90 $draftsEnabled = $this->isSignedIn();
91 $templates = $this->pageRepo->getTemplates(10);
93 return view('pages.edit', [
95 'book' => $draft->book,
97 'draftsEnabled' => $draftsEnabled,
98 'templates' => $templates,
103 * Store a new page by changing a draft into a page.
105 * @throws NotFoundException
106 * @throws ValidationException
108 public function store(Request $request, string $bookSlug, int $pageId)
110 $this->validate($request, [
111 'name' => ['required', 'string', 'max:255'],
113 $draftPage = $this->pageRepo->getById($pageId);
114 $this->checkOwnablePermission('page-create', $draftPage->getParent());
116 $page = $this->pageRepo->publishDraft($draftPage, $request->all());
118 return redirect($page->getUrl());
122 * Display the specified page.
123 * If the page is not found via the slug the revisions are searched for a match.
125 * @throws NotFoundException
127 public function show(string $bookSlug, string $pageSlug)
130 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
131 } catch (NotFoundException $e) {
132 $page = $this->pageRepo->getByOldSlug($bookSlug, $pageSlug);
134 if ($page === null) {
138 return redirect($page->getUrl());
141 $this->checkOwnablePermission('page-view', $page);
143 $pageContent = (new PageContent($page));
144 $page->html = $pageContent->render();
145 $sidebarTree = (new BookContents($page->book))->getTree();
146 $pageNav = $pageContent->getNavigation($page->html);
148 // Check if page comments are enabled
149 $commentsEnabled = !setting('app-disable-comments');
150 if ($commentsEnabled) {
151 $page->load(['comments.createdBy']);
154 $nextPreviousLocator = new NextPreviousContentLocator($page, $sidebarTree);
156 View::incrementFor($page);
157 $this->setPageTitle($page->getShortName());
159 return view('pages.show', [
161 'book' => $page->book,
163 'sidebarTree' => $sidebarTree,
164 'commentsEnabled' => $commentsEnabled,
165 'pageNav' => $pageNav,
166 'next' => $nextPreviousLocator->getNext(),
167 'previous' => $nextPreviousLocator->getPrevious(),
172 * Get page from an ajax request.
174 * @throws NotFoundException
176 public function getPageAjax(int $pageId)
178 $page = $this->pageRepo->getById($pageId);
179 $page->setHidden(array_diff($page->getHidden(), ['html', 'markdown']));
180 $page->makeHidden(['book']);
182 return response()->json($page);
186 * Show the form for editing the specified page.
188 * @throws NotFoundException
190 public function edit(string $bookSlug, string $pageSlug)
192 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
193 $this->checkOwnablePermission('page-update', $page);
195 $page->isDraft = false;
196 $editActivity = new PageEditActivity($page);
198 // Check for active editing
200 if ($editActivity->hasActiveEditing()) {
201 $warnings[] = $editActivity->activeEditingMessage();
204 // Check for a current draft version for this user
205 $userDraft = $this->pageRepo->getUserDraft($page);
206 if ($userDraft !== null) {
207 $page->forceFill($userDraft->only(['name', 'html', 'markdown']));
208 $page->isDraft = true;
209 $warnings[] = $editActivity->getEditingActiveDraftMessage($userDraft);
212 if (count($warnings) > 0) {
213 $this->showWarningNotification(implode("\n", $warnings));
216 $templates = $this->pageRepo->getTemplates(10);
217 $draftsEnabled = $this->isSignedIn();
218 $this->setPageTitle(trans('entities.pages_editing_named', ['pageName' => $page->getShortName()]));
220 return view('pages.edit', [
222 'book' => $page->book,
224 'draftsEnabled' => $draftsEnabled,
225 'templates' => $templates,
230 * Update the specified page in storage.
232 * @throws ValidationException
233 * @throws NotFoundException
235 public function update(Request $request, string $bookSlug, string $pageSlug)
237 $this->validate($request, [
238 'name' => ['required', 'string', 'max:255'],
240 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
241 $this->checkOwnablePermission('page-update', $page);
243 $this->pageRepo->update($page, $request->all());
245 return redirect($page->getUrl());
249 * Save a draft update as a revision.
251 * @throws NotFoundException
253 public function saveDraft(Request $request, int $pageId)
255 $page = $this->pageRepo->getById($pageId);
256 $this->checkOwnablePermission('page-update', $page);
258 if (!$this->isSignedIn()) {
259 return $this->jsonError(trans('errors.guests_cannot_save_drafts'), 500);
262 $draft = $this->pageRepo->updatePageDraft($page, $request->only(['name', 'html', 'markdown']));
263 $warnings = (new PageEditActivity($page))->getWarningMessagesForDraft($draft);
265 return response()->json([
266 'status' => 'success',
267 'message' => trans('entities.pages_edit_draft_save_at'),
268 'warning' => implode("\n", $warnings),
269 'timestamp' => $draft->updated_at->timestamp,
274 * Redirect from a special link url which uses the page id rather than the name.
276 * @throws NotFoundException
278 public function redirectFromLink(int $pageId)
280 $page = $this->pageRepo->getById($pageId);
282 return redirect($page->getUrl());
286 * Show the deletion page for the specified page.
288 * @throws NotFoundException
290 public function showDelete(string $bookSlug, string $pageSlug)
292 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
293 $this->checkOwnablePermission('page-delete', $page);
294 $this->setPageTitle(trans('entities.pages_delete_named', ['pageName' => $page->getShortName()]));
296 return view('pages.delete', [
297 'book' => $page->book,
304 * Show the deletion page for the specified page.
306 * @throws NotFoundException
308 public function showDeleteDraft(string $bookSlug, int $pageId)
310 $page = $this->pageRepo->getById($pageId);
311 $this->checkOwnablePermission('page-update', $page);
312 $this->setPageTitle(trans('entities.pages_delete_draft_named', ['pageName' => $page->getShortName()]));
314 return view('pages.delete', [
315 'book' => $page->book,
322 * Remove the specified page from storage.
324 * @throws NotFoundException
327 public function destroy(string $bookSlug, string $pageSlug)
329 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
330 $this->checkOwnablePermission('page-delete', $page);
331 $parent = $page->getParent();
333 $this->pageRepo->destroy($page);
335 return redirect($parent->getUrl());
339 * Remove the specified draft page from storage.
341 * @throws NotFoundException
344 public function destroyDraft(string $bookSlug, int $pageId)
346 $page = $this->pageRepo->getById($pageId);
348 $chapter = $page->chapter;
349 $this->checkOwnablePermission('page-update', $page);
351 $this->pageRepo->destroy($page);
353 $this->showSuccessNotification(trans('entities.pages_delete_draft_success'));
355 if ($chapter && userCan('view', $chapter)) {
356 return redirect($chapter->getUrl());
359 return redirect($book->getUrl());
363 * Show a listing of recently created pages.
365 public function showRecentlyUpdated()
367 $pages = Page::visible()->orderBy('updated_at', 'desc')
369 ->setPath(url('/pages/recently-updated'));
371 $this->setPageTitle(trans('entities.recently_updated_pages'));
373 return view('common.detailed-listing-paginated', [
374 'title' => trans('entities.recently_updated_pages'),
375 'entities' => $pages,
380 * Show the view to choose a new parent to move a page into.
382 * @throws NotFoundException
384 public function showMove(string $bookSlug, string $pageSlug)
386 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
387 $this->checkOwnablePermission('page-update', $page);
388 $this->checkOwnablePermission('page-delete', $page);
390 return view('pages.move', [
391 'book' => $page->book,
397 * Does the action of moving the location of a page.
399 * @throws NotFoundException
402 public function move(Request $request, string $bookSlug, string $pageSlug)
404 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
405 $this->checkOwnablePermission('page-update', $page);
406 $this->checkOwnablePermission('page-delete', $page);
408 $entitySelection = $request->get('entity_selection', null);
409 if ($entitySelection === null || $entitySelection === '') {
410 return redirect($page->getUrl());
414 $parent = $this->pageRepo->move($page, $entitySelection);
415 } catch (PermissionsException $exception) {
416 $this->showPermissionError();
417 } catch (Exception $exception) {
418 $this->showErrorNotification(trans('errors.selected_book_chapter_not_found'));
420 return redirect()->back();
423 $this->showSuccessNotification(trans('entities.pages_move_success', ['parentName' => $parent->name]));
425 return redirect($page->getUrl());
429 * Show the view to copy a page.
431 * @throws NotFoundException
433 public function showCopy(string $bookSlug, string $pageSlug)
435 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
436 $this->checkOwnablePermission('page-view', $page);
437 session()->flashInput(['name' => $page->name]);
439 return view('pages.copy', [
440 'book' => $page->book,
446 * Create a copy of a page within the requested target destination.
448 * @throws NotFoundException
451 public function copy(Request $request, Cloner $cloner, string $bookSlug, string $pageSlug)
453 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
454 $this->checkOwnablePermission('page-view', $page);
456 $entitySelection = $request->get('entity_selection') ?: null;
457 $newParent = $entitySelection ? $this->pageRepo->findParentByIdentifier($entitySelection) : $page->getParent();
459 if (is_null($newParent)) {
460 $this->showErrorNotification(trans('errors.selected_book_chapter_not_found'));
462 return redirect()->back();
465 $this->checkOwnablePermission('page-create', $newParent);
467 $newName = $request->get('name') ?: $page->name;
468 $pageCopy = $cloner->clonePage($page, $newParent, $newName);
469 $this->showSuccessNotification(trans('entities.pages_copy_success'));
471 return redirect($pageCopy->getUrl());
475 * Show the Permissions view.
477 * @throws NotFoundException
479 public function showPermissions(string $bookSlug, string $pageSlug)
481 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
482 $this->checkOwnablePermission('restrictions-manage', $page);
484 return view('pages.permissions', [
490 * Set the permissions for this page.
492 * @throws NotFoundException
495 public function permissions(Request $request, PermissionsUpdater $permissionsUpdater, string $bookSlug, string $pageSlug)
497 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
498 $this->checkOwnablePermission('restrictions-manage', $page);
500 $permissionsUpdater->updateFromPermissionsForm($page, $request);
502 $this->showSuccessNotification(trans('entities.pages_permissions_success'));
504 return redirect($page->getUrl());