1 <?php namespace BookStack\Http\Controllers;
3 use BookStack\Actions\View;
4 use BookStack\Entities\Tools\BookContents;
5 use BookStack\Entities\Tools\PageContent;
6 use BookStack\Entities\Tools\PageEditActivity;
7 use BookStack\Entities\Models\Page;
8 use BookStack\Entities\Repos\PageRepo;
9 use BookStack\Entities\Tools\PermissionsUpdater;
10 use BookStack\Exceptions\NotFoundException;
11 use BookStack\Exceptions\PermissionsException;
13 use Illuminate\Http\Request;
14 use Illuminate\Validation\ValidationException;
18 class PageController extends Controller
24 * PageController constructor.
26 public function __construct(PageRepo $pageRepo)
28 $this->pageRepo = $pageRepo;
32 * Show the form for creating a new page.
35 public function create(string $bookSlug, string $chapterSlug = null)
37 $parent = $this->pageRepo->getParentFromSlugs($bookSlug, $chapterSlug);
38 $this->checkOwnablePermission('page-create', $parent);
40 // Redirect to draft edit screen if signed in
41 if ($this->isSignedIn()) {
42 $draft = $this->pageRepo->getNewDraftPage($parent);
43 return redirect($draft->getUrl());
46 // Otherwise show the edit view if they're a guest
47 $this->setPageTitle(trans('entities.pages_new'));
48 return view('pages.guest-create', ['parent' => $parent]);
52 * Create a new page as a guest user.
53 * @throws ValidationException
55 public function createAsGuest(Request $request, string $bookSlug, string $chapterSlug = null)
57 $this->validate($request, [
58 'name' => 'required|string|max:255'
61 $parent = $this->pageRepo->getParentFromSlugs($bookSlug, $chapterSlug);
62 $this->checkOwnablePermission('page-create', $parent);
64 $page = $this->pageRepo->getNewDraftPage($parent);
65 $this->pageRepo->publishDraft($page, [
66 'name' => $request->get('name'),
70 return redirect($page->getUrl('/edit'));
74 * Show form to continue editing a draft page.
75 * @throws NotFoundException
77 public function editDraft(string $bookSlug, int $pageId)
79 $draft = $this->pageRepo->getById($pageId);
80 $this->checkOwnablePermission('page-create', $draft->getParent());
81 $this->setPageTitle(trans('entities.pages_edit_draft'));
83 $draftsEnabled = $this->isSignedIn();
84 $templates = $this->pageRepo->getTemplates(10);
86 return view('pages.edit', [
88 'book' => $draft->book,
90 'draftsEnabled' => $draftsEnabled,
91 'templates' => $templates,
96 * Store a new page by changing a draft into a page.
97 * @throws NotFoundException
98 * @throws ValidationException
100 public function store(Request $request, string $bookSlug, int $pageId)
102 $this->validate($request, [
103 'name' => 'required|string|max:255'
105 $draftPage = $this->pageRepo->getById($pageId);
106 $this->checkOwnablePermission('page-create', $draftPage->getParent());
108 $page = $this->pageRepo->publishDraft($draftPage, $request->all());
110 return redirect($page->getUrl());
114 * Display the specified page.
115 * If the page is not found via the slug the revisions are searched for a match.
116 * @throws NotFoundException
118 public function show(string $bookSlug, string $pageSlug)
121 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
122 } catch (NotFoundException $e) {
123 $page = $this->pageRepo->getByOldSlug($bookSlug, $pageSlug);
125 if ($page === null) {
129 return redirect($page->getUrl());
132 $this->checkOwnablePermission('page-view', $page);
134 $pageContent = (new PageContent($page));
135 $page->html = $pageContent->render();
136 $sidebarTree = (new BookContents($page->book))->getTree();
137 $pageNav = $pageContent->getNavigation($page->html);
139 // Check if page comments are enabled
140 $commentsEnabled = !setting('app-disable-comments');
141 if ($commentsEnabled) {
142 $page->load(['comments.createdBy']);
145 $chapterId = $page->getParentChapter();
146 $allPageSlugs = $this->pageRepo->getPageByChapterID($chapterId[0]->id);
148 foreach ($allPageSlugs as $slug){
149 if($pageSlug === $slug->slug){
153 $pageUrl = $this->pageRepo->getBySlug($bookSlug, $slug->slug);
154 $urlLink[] = $pageUrl->getUrl();
156 for($i=0; $i <= $currPagePos; $i++){
161 if($nextCount < count($urlLink)){
162 $nextPage = $urlLink[$nextCount];
164 if($currPagePos == $i && $currPagePos != 0){
165 $prevPage = $urlLink[$prevCount];
171 if($prevPage == "#"){
172 $disablePrev = "disabled";
174 if($nextPage == "#"){
175 $disableNxt = "disabled";
178 View::incrementFor($page);
179 $this->setPageTitle($page->getShortName());
180 return view('pages.show', [
182 'book' => $page->book,
184 'sidebarTree' => $sidebarTree,
185 'commentsEnabled' => $commentsEnabled,
186 'pageNav' => $pageNav,
187 'prevPage' => $prevPage,
188 'nextPage' => $nextPage,
189 'disablePrev' => $disablePrev,
190 'disableNxt' => $disableNxt
195 * Get page from an ajax request.
196 * @throws NotFoundException
198 public function getPageAjax(int $pageId)
200 $page = $this->pageRepo->getById($pageId);
201 $page->setHidden(array_diff($page->getHidden(), ['html', 'markdown']));
202 $page->addHidden(['book']);
203 return response()->json($page);
207 * Show the form for editing the specified page.
208 * @throws NotFoundException
210 public function edit(string $bookSlug, string $pageSlug)
212 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
213 $this->checkOwnablePermission('page-update', $page);
215 $page->isDraft = false;
216 $editActivity = new PageEditActivity($page);
218 // Check for active editing
220 if ($editActivity->hasActiveEditing()) {
221 $warnings[] = $editActivity->activeEditingMessage();
224 // Check for a current draft version for this user
225 $userDraft = $this->pageRepo->getUserDraft($page);
226 if ($userDraft !== null) {
227 $page->forceFill($userDraft->only(['name', 'html', 'markdown']));
228 $page->isDraft = true;
229 $warnings[] = $editActivity->getEditingActiveDraftMessage($userDraft);
232 if (count($warnings) > 0) {
233 $this->showWarningNotification(implode("\n", $warnings));
236 $templates = $this->pageRepo->getTemplates(10);
237 $draftsEnabled = $this->isSignedIn();
238 $this->setPageTitle(trans('entities.pages_editing_named', ['pageName' => $page->getShortName()]));
239 return view('pages.edit', [
241 'book' => $page->book,
243 'draftsEnabled' => $draftsEnabled,
244 'templates' => $templates,
249 * Update the specified page in storage.
250 * @throws ValidationException
251 * @throws NotFoundException
253 public function update(Request $request, string $bookSlug, string $pageSlug)
255 $this->validate($request, [
256 'name' => 'required|string|max:255'
258 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
259 $this->checkOwnablePermission('page-update', $page);
261 $this->pageRepo->update($page, $request->all());
263 return redirect($page->getUrl());
267 * Save a draft update as a revision.
268 * @throws NotFoundException
270 public function saveDraft(Request $request, int $pageId)
272 $page = $this->pageRepo->getById($pageId);
273 $this->checkOwnablePermission('page-update', $page);
275 if (!$this->isSignedIn()) {
276 return $this->jsonError(trans('errors.guests_cannot_save_drafts'), 500);
279 $draft = $this->pageRepo->updatePageDraft($page, $request->only(['name', 'html', 'markdown']));
281 $updateTime = $draft->updated_at->timestamp;
282 return response()->json([
283 'status' => 'success',
284 'message' => trans('entities.pages_edit_draft_save_at'),
285 'timestamp' => $updateTime
290 * Redirect from a special link url which uses the page id rather than the name.
291 * @throws NotFoundException
293 public function redirectFromLink(int $pageId)
295 $page = $this->pageRepo->getById($pageId);
296 return redirect($page->getUrl());
300 * Show the deletion page for the specified page.
301 * @throws NotFoundException
303 public function showDelete(string $bookSlug, string $pageSlug)
305 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
306 $this->checkOwnablePermission('page-delete', $page);
307 $this->setPageTitle(trans('entities.pages_delete_named', ['pageName'=>$page->getShortName()]));
308 return view('pages.delete', [
309 'book' => $page->book,
316 * Show the deletion page for the specified page.
317 * @throws NotFoundException
319 public function showDeleteDraft(string $bookSlug, int $pageId)
321 $page = $this->pageRepo->getById($pageId);
322 $this->checkOwnablePermission('page-update', $page);
323 $this->setPageTitle(trans('entities.pages_delete_draft_named', ['pageName'=>$page->getShortName()]));
324 return view('pages.delete', [
325 'book' => $page->book,
332 * Remove the specified page from storage.
333 * @throws NotFoundException
336 public function destroy(string $bookSlug, string $pageSlug)
338 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
339 $this->checkOwnablePermission('page-delete', $page);
340 $parent = $page->getParent();
342 $this->pageRepo->destroy($page);
344 return redirect($parent->getUrl());
348 * Remove the specified draft page from storage.
349 * @throws NotFoundException
352 public function destroyDraft(string $bookSlug, int $pageId)
354 $page = $this->pageRepo->getById($pageId);
356 $chapter = $page->chapter;
357 $this->checkOwnablePermission('page-update', $page);
359 $this->pageRepo->destroy($page);
361 $this->showSuccessNotification(trans('entities.pages_delete_draft_success'));
363 if ($chapter && userCan('view', $chapter)) {
364 return redirect($chapter->getUrl());
366 return redirect($book->getUrl());
370 * Show a listing of recently created pages.
372 public function showRecentlyUpdated()
374 $pages = Page::visible()->orderBy('updated_at', 'desc')
376 ->setPath(url('/pages/recently-updated'));
378 return view('common.detailed-listing-paginated', [
379 'title' => trans('entities.recently_updated_pages'),
385 * Show the view to choose a new parent to move a page into.
386 * @throws NotFoundException
388 public function showMove(string $bookSlug, string $pageSlug)
390 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
391 $this->checkOwnablePermission('page-update', $page);
392 $this->checkOwnablePermission('page-delete', $page);
393 return view('pages.move', [
394 'book' => $page->book,
400 * Does the action of moving the location of a page.
401 * @throws NotFoundException
404 public function move(Request $request, string $bookSlug, string $pageSlug)
406 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
407 $this->checkOwnablePermission('page-update', $page);
408 $this->checkOwnablePermission('page-delete', $page);
410 $entitySelection = $request->get('entity_selection', null);
411 if ($entitySelection === null || $entitySelection === '') {
412 return redirect($page->getUrl());
416 $parent = $this->pageRepo->move($page, $entitySelection);
417 } catch (Exception $exception) {
418 if ($exception instanceof PermissionsException) {
419 $this->showPermissionError();
422 $this->showErrorNotification(trans('errors.selected_book_chapter_not_found'));
423 return redirect()->back();
426 $this->showSuccessNotification(trans('entities.pages_move_success', ['parentName' => $parent->name]));
427 return redirect($page->getUrl());
431 * Show the view to copy a page.
432 * @throws NotFoundException
434 public function showCopy(string $bookSlug, string $pageSlug)
436 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
437 $this->checkOwnablePermission('page-view', $page);
438 session()->flashInput(['name' => $page->name]);
439 return view('pages.copy', [
440 'book' => $page->book,
447 * Create a copy of a page within the requested target destination.
448 * @throws NotFoundException
451 public function copy(Request $request, string $bookSlug, string $pageSlug)
453 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
454 $this->checkOwnablePermission('page-view', $page);
456 $entitySelection = $request->get('entity_selection', null) ?? null;
457 $newName = $request->get('name', null);
460 $pageCopy = $this->pageRepo->copy($page, $entitySelection, $newName);
461 } catch (Exception $exception) {
462 if ($exception instanceof PermissionsException) {
463 $this->showPermissionError();
466 $this->showErrorNotification(trans('errors.selected_book_chapter_not_found'));
467 return redirect()->back();
470 $this->showSuccessNotification(trans('entities.pages_copy_success'));
471 return redirect($pageCopy->getUrl());
475 * Show the Permissions view.
476 * @throws NotFoundException
478 public function showPermissions(string $bookSlug, string $pageSlug)
480 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
481 $this->checkOwnablePermission('restrictions-manage', $page);
482 return view('pages.permissions', [
488 * Set the permissions for this page.
489 * @throws NotFoundException
492 public function permissions(Request $request, PermissionsUpdater $permissionsUpdater, string $bookSlug, string $pageSlug)
494 $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
495 $this->checkOwnablePermission('restrictions-manage', $page);
497 $permissionsUpdater->updateFromPermissionsForm($page, $request);
499 $this->showSuccessNotification(trans('entities.pages_permissions_success'));
500 return redirect($page->getUrl());