]> BookStack Code Mirror - bookstack/blob - app/Http/Controllers/PageController.php
Add prev and next button to navigate through different pages
[bookstack] / app / Http / Controllers / PageController.php
1 <?php namespace BookStack\Http\Controllers;
2
3 use BookStack\Entities\Tools\BookContents;
4 use BookStack\Entities\Tools\PageContent;
5 use BookStack\Entities\Tools\PageEditActivity;
6 use BookStack\Entities\Models\Page;
7 use BookStack\Entities\Repos\PageRepo;
8 use BookStack\Entities\Tools\PermissionsUpdater;
9 use BookStack\Exceptions\NotFoundException;
10 use BookStack\Exceptions\NotifyException;
11 use BookStack\Exceptions\PermissionsException;
12 use Exception;
13 use Illuminate\Http\Request;
14 use Illuminate\Validation\ValidationException;
15 use Throwable;
16 use Views;
17
18 class PageController extends Controller
19 {
20
21     protected $pageRepo;
22
23     /**
24      * PageController constructor.
25      */
26     public function __construct(PageRepo $pageRepo)
27     {
28         $this->pageRepo = $pageRepo;
29     }
30
31     /**
32      * Show the form for creating a new page.
33      * @throws Throwable
34      */
35     public function create(string $bookSlug, string $chapterSlug = null)
36     {
37         $parent = $this->pageRepo->getParentFromSlugs($bookSlug, $chapterSlug);
38         $this->checkOwnablePermission('page-create', $parent);
39
40         // Redirect to draft edit screen if signed in
41         if ($this->isSignedIn()) {
42             $draft = $this->pageRepo->getNewDraftPage($parent);
43             return redirect($draft->getUrl());
44         }
45
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]);
49     }
50
51     /**
52      * Create a new page as a guest user.
53      * @throws ValidationException
54      */
55     public function createAsGuest(Request $request, string $bookSlug, string $chapterSlug = null)
56     {
57         $this->validate($request, [
58             'name' => 'required|string|max:255'
59         ]);
60
61         $parent = $this->pageRepo->getParentFromSlugs($bookSlug, $chapterSlug);
62         $this->checkOwnablePermission('page-create', $parent);
63
64         $page = $this->pageRepo->getNewDraftPage($parent);
65         $this->pageRepo->publishDraft($page, [
66             'name' => $request->get('name'),
67             'html' => ''
68         ]);
69
70         return redirect($page->getUrl('/edit'));
71     }
72
73     /**
74      * Show form to continue editing a draft page.
75      * @throws NotFoundException
76      */
77     public function editDraft(string $bookSlug, int $pageId)
78     {
79         $draft = $this->pageRepo->getById($pageId);
80         $this->checkOwnablePermission('page-create', $draft->getParent());
81         $this->setPageTitle(trans('entities.pages_edit_draft'));
82
83         $draftsEnabled = $this->isSignedIn();
84         $templates = $this->pageRepo->getTemplates(10);
85
86         return view('pages.edit', [
87             'page' => $draft,
88             'book' => $draft->book,
89             'isDraft' => true,
90             'draftsEnabled' => $draftsEnabled,
91             'templates' => $templates,
92         ]);
93     }
94
95     /**
96      * Store a new page by changing a draft into a page.
97      * @throws NotFoundException
98      * @throws ValidationException
99      */
100     public function store(Request $request, string $bookSlug, int $pageId)
101     {
102         $this->validate($request, [
103             'name' => 'required|string|max:255'
104         ]);
105         $draftPage = $this->pageRepo->getById($pageId);
106         $this->checkOwnablePermission('page-create', $draftPage->getParent());
107
108         $page = $this->pageRepo->publishDraft($draftPage, $request->all());
109
110         return redirect($page->getUrl());
111     }
112
113     /**
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
117      */
118     public function show(string $bookSlug, string $pageSlug)
119     {
120         try {
121             $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
122         } catch (NotFoundException $e) {
123             $page = $this->pageRepo->getByOldSlug($bookSlug, $pageSlug);
124
125             if ($page === null) {
126                 throw $e;
127             }
128
129             return redirect($page->getUrl());
130         }
131
132         $this->checkOwnablePermission('page-view', $page);
133
134         $pageContent = (new PageContent($page));
135         $page->html = $pageContent->render();
136         $sidebarTree = (new BookContents($page->book))->getTree();
137         $pageNav = $pageContent->getNavigation($page->html);
138
139         // Check if page comments are enabled
140         $commentsEnabled = !setting('app-disable-comments');
141         if ($commentsEnabled) {
142             $page->load(['comments.createdBy']);
143         }
144
145         $chapterId = $page->getParentChapter();
146         $allPageSlugs = $this->pageRepo->getPageByChapterID($chapterId[0]->id);
147         $pos = 0;
148         foreach ($allPageSlugs as $slug){
149             if($pageSlug === $slug->slug){
150                 $currPagePos = $pos;
151             }
152             $pos++;
153             $pageUrl = $this->pageRepo->getBySlug($bookSlug, $slug->slug);
154             $urlLink[] = $pageUrl->getUrl();
155         }
156         for($i=0; $i <= $currPagePos; $i++){
157             $nextCount = $i+1;
158             $prevCount = $i-1;
159             $prevPage = '#';
160             $nextPage = '#';
161             if($nextCount < count($urlLink)){
162                 $nextPage = $urlLink[$nextCount];
163             }
164             if($currPagePos == $i && $currPagePos != 0){
165                 $prevPage = $urlLink[$prevCount];    
166             }
167         }
168
169         $disablePrev = "";
170         $disableNxt = "";
171         if($prevPage == "#"){
172             $disablePrev = "disabled";
173         }
174         if($nextPage == "#"){
175             $disableNxt = "disabled";
176         }
177         Views::add($page);
178         $this->setPageTitle($page->getShortName());
179         return view('pages.show', [
180             'page' => $page,
181             'book' => $page->book,
182             'current' => $page,
183             'sidebarTree' => $sidebarTree,
184             'commentsEnabled' => $commentsEnabled,
185             'pageNav' => $pageNav,
186             'prevPage' => $prevPage,
187             'nextPage' => $nextPage,
188             'disablePrev' => $disablePrev,
189             'disableNxt' => $disableNxt
190         ]);
191     }
192
193     /**
194      * Get page from an ajax request.
195      * @throws NotFoundException
196      */
197     public function getPageAjax(int $pageId)
198     {
199         $page = $this->pageRepo->getById($pageId);
200         $page->setHidden(array_diff($page->getHidden(), ['html', 'markdown']));
201         $page->addHidden(['book']);
202         return response()->json($page);
203     }
204
205     /**
206      * Show the form for editing the specified page.
207      * @throws NotFoundException
208      */
209     public function edit(string $bookSlug, string $pageSlug)
210     {
211         $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
212         $this->checkOwnablePermission('page-update', $page);
213
214         $page->isDraft = false;
215         $editActivity = new PageEditActivity($page);
216
217         // Check for active editing
218         $warnings = [];
219         if ($editActivity->hasActiveEditing()) {
220             $warnings[] = $editActivity->activeEditingMessage();
221         }
222
223         // Check for a current draft version for this user
224         $userDraft = $this->pageRepo->getUserDraft($page);
225         if ($userDraft !== null) {
226             $page->forceFill($userDraft->only(['name', 'html', 'markdown']));
227             $page->isDraft = true;
228             $warnings[] = $editActivity->getEditingActiveDraftMessage($userDraft);
229         }
230
231         if (count($warnings) > 0) {
232             $this->showWarningNotification(implode("\n", $warnings));
233         }
234
235         $templates = $this->pageRepo->getTemplates(10);
236         $draftsEnabled = $this->isSignedIn();
237         $this->setPageTitle(trans('entities.pages_editing_named', ['pageName' => $page->getShortName()]));
238         return view('pages.edit', [
239             'page' => $page,
240             'book' => $page->book,
241             'current' => $page,
242             'draftsEnabled' => $draftsEnabled,
243             'templates' => $templates,
244         ]);
245     }
246
247     /**
248      * Update the specified page in storage.
249      * @throws ValidationException
250      * @throws NotFoundException
251      */
252     public function update(Request $request, string $bookSlug, string $pageSlug)
253     {
254         $this->validate($request, [
255             'name' => 'required|string|max:255'
256         ]);
257         $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
258         $this->checkOwnablePermission('page-update', $page);
259
260         $this->pageRepo->update($page, $request->all());
261
262         return redirect($page->getUrl());
263     }
264
265     /**
266      * Save a draft update as a revision.
267      * @throws NotFoundException
268      */
269     public function saveDraft(Request $request, int $pageId)
270     {
271         $page = $this->pageRepo->getById($pageId);
272         $this->checkOwnablePermission('page-update', $page);
273
274         if (!$this->isSignedIn()) {
275             return $this->jsonError(trans('errors.guests_cannot_save_drafts'), 500);
276         }
277
278         $draft = $this->pageRepo->updatePageDraft($page, $request->only(['name', 'html', 'markdown']));
279
280         $updateTime = $draft->updated_at->timestamp;
281         return response()->json([
282             'status'    => 'success',
283             'message'   => trans('entities.pages_edit_draft_save_at'),
284             'timestamp' => $updateTime
285         ]);
286     }
287
288     /**
289      * Redirect from a special link url which uses the page id rather than the name.
290      * @throws NotFoundException
291      */
292     public function redirectFromLink(int $pageId)
293     {
294         $page = $this->pageRepo->getById($pageId);
295         return redirect($page->getUrl());
296     }
297
298     /**
299      * Show the deletion page for the specified page.
300      * @throws NotFoundException
301      */
302     public function showDelete(string $bookSlug, string $pageSlug)
303     {
304         $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
305         $this->checkOwnablePermission('page-delete', $page);
306         $this->setPageTitle(trans('entities.pages_delete_named', ['pageName'=>$page->getShortName()]));
307         return view('pages.delete', [
308             'book' => $page->book,
309             'page' => $page,
310             'current' => $page
311         ]);
312     }
313
314     /**
315      * Show the deletion page for the specified page.
316      * @throws NotFoundException
317      */
318     public function showDeleteDraft(string $bookSlug, int $pageId)
319     {
320         $page = $this->pageRepo->getById($pageId);
321         $this->checkOwnablePermission('page-update', $page);
322         $this->setPageTitle(trans('entities.pages_delete_draft_named', ['pageName'=>$page->getShortName()]));
323         return view('pages.delete', [
324             'book' => $page->book,
325             'page' => $page,
326             'current' => $page
327         ]);
328     }
329
330     /**
331      * Remove the specified page from storage.
332      * @throws NotFoundException
333      * @throws Throwable
334      * @throws NotifyException
335      */
336     public function destroy(string $bookSlug, string $pageSlug)
337     {
338         $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
339         $this->checkOwnablePermission('page-delete', $page);
340         $parent = $page->getParent();
341
342         $this->pageRepo->destroy($page);
343
344         return redirect($parent->getUrl());
345     }
346
347     /**
348      * Remove the specified draft page from storage.
349      * @throws NotFoundException
350      * @throws NotifyException
351      * @throws Throwable
352      */
353     public function destroyDraft(string $bookSlug, int $pageId)
354     {
355         $page = $this->pageRepo->getById($pageId);
356         $book = $page->book;
357         $chapter = $page->chapter;
358         $this->checkOwnablePermission('page-update', $page);
359
360         $this->pageRepo->destroy($page);
361
362         $this->showSuccessNotification(trans('entities.pages_delete_draft_success'));
363
364         if ($chapter && userCan('view', $chapter)) {
365             return redirect($chapter->getUrl());
366         }
367         return redirect($book->getUrl());
368     }
369
370     /**
371      * Show a listing of recently created pages.
372      */
373     public function showRecentlyUpdated()
374     {
375         $pages = Page::visible()->orderBy('updated_at', 'desc')
376             ->paginate(20)
377             ->setPath(url('/pages/recently-updated'));
378
379         return view('pages.detailed-listing', [
380             'title' => trans('entities.recently_updated_pages'),
381             'pages' => $pages
382         ]);
383     }
384
385     /**
386      * Show the view to choose a new parent to move a page into.
387      * @throws NotFoundException
388      */
389     public function showMove(string $bookSlug, string $pageSlug)
390     {
391         $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
392         $this->checkOwnablePermission('page-update', $page);
393         $this->checkOwnablePermission('page-delete', $page);
394         return view('pages.move', [
395             'book' => $page->book,
396             'page' => $page
397         ]);
398     }
399
400     /**
401      * Does the action of moving the location of a page.
402      * @throws NotFoundException
403      * @throws Throwable
404      */
405     public function move(Request $request, string $bookSlug, string $pageSlug)
406     {
407         $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
408         $this->checkOwnablePermission('page-update', $page);
409         $this->checkOwnablePermission('page-delete', $page);
410
411         $entitySelection = $request->get('entity_selection', null);
412         if ($entitySelection === null || $entitySelection === '') {
413             return redirect($page->getUrl());
414         }
415
416         try {
417             $parent = $this->pageRepo->move($page, $entitySelection);
418         } catch (Exception $exception) {
419             if ($exception instanceof  PermissionsException) {
420                 $this->showPermissionError();
421             }
422
423             $this->showErrorNotification(trans('errors.selected_book_chapter_not_found'));
424             return redirect()->back();
425         }
426
427         $this->showSuccessNotification(trans('entities.pages_move_success', ['parentName' => $parent->name]));
428         return redirect($page->getUrl());
429     }
430
431     /**
432      * Show the view to copy a page.
433      * @throws NotFoundException
434      */
435     public function showCopy(string $bookSlug, string $pageSlug)
436     {
437         $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
438         $this->checkOwnablePermission('page-view', $page);
439         session()->flashInput(['name' => $page->name]);
440         return view('pages.copy', [
441             'book' => $page->book,
442             'page' => $page
443         ]);
444     }
445
446
447     /**
448      * Create a copy of a page within the requested target destination.
449      * @throws NotFoundException
450      * @throws Throwable
451      */
452     public function copy(Request $request, string $bookSlug, string $pageSlug)
453     {
454         $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
455         $this->checkOwnablePermission('page-view', $page);
456
457         $entitySelection = $request->get('entity_selection', null) ?? null;
458         $newName = $request->get('name', null);
459
460         try {
461             $pageCopy = $this->pageRepo->copy($page, $entitySelection, $newName);
462         } catch (Exception $exception) {
463             if ($exception instanceof  PermissionsException) {
464                 $this->showPermissionError();
465             }
466
467             $this->showErrorNotification(trans('errors.selected_book_chapter_not_found'));
468             return redirect()->back();
469         }
470
471         $this->showSuccessNotification(trans('entities.pages_copy_success'));
472         return redirect($pageCopy->getUrl());
473     }
474
475     /**
476      * Show the Permissions view.
477      * @throws NotFoundException
478      */
479     public function showPermissions(string $bookSlug, string $pageSlug)
480     {
481         $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
482         $this->checkOwnablePermission('restrictions-manage', $page);
483         return view('pages.permissions', [
484             'page'  => $page,
485         ]);
486     }
487
488     /**
489      * Set the permissions for this page.
490      * @throws NotFoundException
491      * @throws Throwable
492      */
493     public function permissions(Request $request, PermissionsUpdater $permissionsUpdater, string $bookSlug, string $pageSlug)
494     {
495         $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
496         $this->checkOwnablePermission('restrictions-manage', $page);
497
498         $permissionsUpdater->updateFromPermissionsForm($page, $request);
499
500         $this->showSuccessNotification(trans('entities.pages_permissions_success'));
501         return redirect($page->getUrl());
502     }
503 }