]> BookStack Code Mirror - bookstack/blob - app/Http/Controllers/ChapterController.php
Added additional permission checks and tests for book sorts
[bookstack] / app / Http / Controllers / ChapterController.php
1 <?php
2
3 namespace BookStack\Http\Controllers;
4
5 use BookStack\Actions\View;
6 use BookStack\Entities\Models\Book;
7 use BookStack\Entities\Repos\ChapterRepo;
8 use BookStack\Entities\Tools\BookContents;
9 use BookStack\Entities\Tools\Cloner;
10 use BookStack\Entities\Tools\NextPreviousContentLocator;
11 use BookStack\Entities\Tools\PermissionsUpdater;
12 use BookStack\Exceptions\MoveOperationException;
13 use BookStack\Exceptions\NotFoundException;
14 use Illuminate\Http\Request;
15 use Illuminate\Validation\ValidationException;
16 use Throwable;
17
18 class ChapterController extends Controller
19 {
20     protected $chapterRepo;
21
22     /**
23      * ChapterController constructor.
24      */
25     public function __construct(ChapterRepo $chapterRepo)
26     {
27         $this->chapterRepo = $chapterRepo;
28     }
29
30     /**
31      * Show the form for creating a new chapter.
32      */
33     public function create(string $bookSlug)
34     {
35         $book = Book::visible()->where('slug', '=', $bookSlug)->firstOrFail();
36         $this->checkOwnablePermission('chapter-create', $book);
37
38         $this->setPageTitle(trans('entities.chapters_create'));
39
40         return view('chapters.create', ['book' => $book, 'current' => $book]);
41     }
42
43     /**
44      * Store a newly created chapter in storage.
45      *
46      * @throws ValidationException
47      */
48     public function store(Request $request, string $bookSlug)
49     {
50         $this->validate($request, [
51             'name' => ['required', 'string', 'max:255'],
52         ]);
53
54         $book = Book::visible()->where('slug', '=', $bookSlug)->firstOrFail();
55         $this->checkOwnablePermission('chapter-create', $book);
56
57         $chapter = $this->chapterRepo->create($request->all(), $book);
58
59         return redirect($chapter->getUrl());
60     }
61
62     /**
63      * Display the specified chapter.
64      */
65     public function show(string $bookSlug, string $chapterSlug)
66     {
67         $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
68         $this->checkOwnablePermission('chapter-view', $chapter);
69
70         $sidebarTree = (new BookContents($chapter->book))->getTree();
71         $pages = $chapter->getVisiblePages();
72         $nextPreviousLocator = new NextPreviousContentLocator($chapter, $sidebarTree);
73         View::incrementFor($chapter);
74
75         $this->setPageTitle($chapter->getShortName());
76
77         return view('chapters.show', [
78             'book'        => $chapter->book,
79             'chapter'     => $chapter,
80             'current'     => $chapter,
81             'sidebarTree' => $sidebarTree,
82             'pages'       => $pages,
83             'next'        => $nextPreviousLocator->getNext(),
84             'previous'    => $nextPreviousLocator->getPrevious(),
85         ]);
86     }
87
88     /**
89      * Show the form for editing the specified chapter.
90      */
91     public function edit(string $bookSlug, string $chapterSlug)
92     {
93         $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
94         $this->checkOwnablePermission('chapter-update', $chapter);
95
96         $this->setPageTitle(trans('entities.chapters_edit_named', ['chapterName' => $chapter->getShortName()]));
97
98         return view('chapters.edit', ['book' => $chapter->book, 'chapter' => $chapter, 'current' => $chapter]);
99     }
100
101     /**
102      * Update the specified chapter in storage.
103      *
104      * @throws NotFoundException
105      */
106     public function update(Request $request, string $bookSlug, string $chapterSlug)
107     {
108         $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
109         $this->checkOwnablePermission('chapter-update', $chapter);
110
111         $this->chapterRepo->update($chapter, $request->all());
112
113         return redirect($chapter->getUrl());
114     }
115
116     /**
117      * Shows the page to confirm deletion of this chapter.
118      *
119      * @throws NotFoundException
120      */
121     public function showDelete(string $bookSlug, string $chapterSlug)
122     {
123         $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
124         $this->checkOwnablePermission('chapter-delete', $chapter);
125
126         $this->setPageTitle(trans('entities.chapters_delete_named', ['chapterName' => $chapter->getShortName()]));
127
128         return view('chapters.delete', ['book' => $chapter->book, 'chapter' => $chapter, 'current' => $chapter]);
129     }
130
131     /**
132      * Remove the specified chapter from storage.
133      *
134      * @throws NotFoundException
135      * @throws Throwable
136      */
137     public function destroy(string $bookSlug, string $chapterSlug)
138     {
139         $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
140         $this->checkOwnablePermission('chapter-delete', $chapter);
141
142         $this->chapterRepo->destroy($chapter);
143
144         return redirect($chapter->book->getUrl());
145     }
146
147     /**
148      * Show the page for moving a chapter.
149      *
150      * @throws NotFoundException
151      */
152     public function showMove(string $bookSlug, string $chapterSlug)
153     {
154         $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
155         $this->setPageTitle(trans('entities.chapters_move_named', ['chapterName' => $chapter->getShortName()]));
156         $this->checkOwnablePermission('chapter-update', $chapter);
157         $this->checkOwnablePermission('chapter-delete', $chapter);
158
159         return view('chapters.move', [
160             'chapter' => $chapter,
161             'book'    => $chapter->book,
162         ]);
163     }
164
165     /**
166      * Perform the move action for a chapter.
167      *
168      * @throws NotFoundException
169      */
170     public function move(Request $request, string $bookSlug, string $chapterSlug)
171     {
172         $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
173         $this->checkOwnablePermission('chapter-update', $chapter);
174         $this->checkOwnablePermission('chapter-delete', $chapter);
175
176         $entitySelection = $request->get('entity_selection', null);
177         if ($entitySelection === null || $entitySelection === '') {
178             return redirect($chapter->getUrl());
179         }
180
181         // TODO - Check permissions against pages
182
183         try {
184             $newBook = $this->chapterRepo->move($chapter, $entitySelection);
185         } catch (MoveOperationException $exception) {
186             $this->showErrorNotification(trans('errors.selected_book_not_found'));
187
188             return redirect()->back();
189         }
190
191         $this->showSuccessNotification(trans('entities.chapter_move_success', ['bookName' => $newBook->name]));
192
193         return redirect($chapter->getUrl());
194     }
195
196     /**
197      * Show the view to copy a chapter.
198      *
199      * @throws NotFoundException
200      */
201     public function showCopy(string $bookSlug, string $chapterSlug)
202     {
203         $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
204         $this->checkOwnablePermission('chapter-view', $chapter);
205
206         session()->flashInput(['name' => $chapter->name]);
207
208         return view('chapters.copy', [
209             'book'    => $chapter->book,
210             'chapter' => $chapter,
211         ]);
212     }
213
214     /**
215      * Create a copy of a chapter within the requested target destination.
216      *
217      * @throws NotFoundException
218      * @throws Throwable
219      */
220     public function copy(Request $request, Cloner $cloner, string $bookSlug, string $chapterSlug)
221     {
222         $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
223         $this->checkOwnablePermission('chapter-view', $chapter);
224
225         $entitySelection = $request->get('entity_selection') ?: null;
226         $newParentBook = $entitySelection ? $this->chapterRepo->findParentByIdentifier($entitySelection) : $chapter->getParent();
227
228         if (is_null($newParentBook)) {
229             $this->showErrorNotification(trans('errors.selected_book_not_found'));
230
231             return redirect()->back();
232         }
233
234         $this->checkOwnablePermission('chapter-create', $newParentBook);
235
236         $newName = $request->get('name') ?: $chapter->name;
237         $chapterCopy = $cloner->cloneChapter($chapter, $newParentBook, $newName);
238         $this->showSuccessNotification(trans('entities.chapters_copy_success'));
239
240         return redirect($chapterCopy->getUrl());
241     }
242
243     /**
244      * Show the Restrictions view.
245      *
246      * @throws NotFoundException
247      */
248     public function showPermissions(string $bookSlug, string $chapterSlug)
249     {
250         $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
251         $this->checkOwnablePermission('restrictions-manage', $chapter);
252
253         return view('chapters.permissions', [
254             'chapter' => $chapter,
255         ]);
256     }
257
258     /**
259      * Set the restrictions for this chapter.
260      *
261      * @throws NotFoundException
262      */
263     public function permissions(Request $request, PermissionsUpdater $permissionsUpdater, string $bookSlug, string $chapterSlug)
264     {
265         $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
266         $this->checkOwnablePermission('restrictions-manage', $chapter);
267
268         $permissionsUpdater->updateFromPermissionsForm($chapter, $request);
269
270         $this->showSuccessNotification(trans('entities.chapters_permissions_success'));
271
272         return redirect($chapter->getUrl());
273     }
274 }