]> BookStack Code Mirror - bookstack/blob - app/Http/Controllers/BookController.php
Deletion of image file on book deletion.
[bookstack] / app / Http / Controllers / BookController.php
1 <?php namespace BookStack\Http\Controllers;
2
3 use Activity;
4 use BookStack\Book;
5 use BookStack\Repos\EntityRepo;
6 use BookStack\Repos\UserRepo;
7 use BookStack\Services\ExportService;
8 use Illuminate\Http\Request;
9 use Illuminate\Http\Response;
10 use Views;
11 use File;
12
13 class BookController extends Controller
14 {
15
16     protected $entityRepo;
17     protected $userRepo;
18     protected $exportService;
19
20     /**
21      * BookController constructor.
22      * @param EntityRepo $entityRepo
23      * @param UserRepo $userRepo
24      * @param ExportService $exportService
25      */
26     public function __construct(EntityRepo $entityRepo, UserRepo $userRepo, ExportService $exportService)
27     {
28         $this->entityRepo = $entityRepo;
29         $this->userRepo = $userRepo;
30         $this->exportService = $exportService;
31         parent::__construct();
32     }
33
34     /**
35      * Display a listing of the book.
36      * @return Response
37      */
38     public function index()
39     {
40         $books = $this->entityRepo->getAllPaginated('book', 16);
41         $recents = $this->signedIn ? $this->entityRepo->getRecentlyViewed('book', 4, 0) : false;
42         $popular = $this->entityRepo->getPopular('book', 3, 0);
43         $books_display = $this->currentUser->books_display;
44         $this->setPageTitle('Books');
45         return view('books/index', ['books' => $books, 'recents' => $recents, 'popular' => $popular, 'books_display' => $books_display] );
46     }
47
48     /**
49      * Show the form for creating a new book.
50      * @return Response
51      */
52     public function create()
53     {
54         $this->checkPermission('book-create-all');
55         $this->setPageTitle(trans('entities.books_create'));
56         return view('books/create');
57     }
58
59     /**
60      * Store a newly created book in storage.
61      *
62      * @param  Request $request
63      * @return Response
64      */
65     public function store(Request $request)
66     {
67         $this->checkPermission('book-create-all');
68         $this->validate($request, [
69             'name' => 'required|string|max:255',
70             'description' => 'string|max:1000'
71         ]);
72         $image = $request->file('image');
73         $path = $this->getBookCoverURL($image);
74         $book = $this->entityRepo->createFromInput('book', $request->all());
75         $book->image = $path; 
76         $book->save();
77         Activity::add($book, 'book_create', $book->id);
78         return redirect($book->getUrl());
79     }
80
81     /**
82      * Display the specified book.
83      * @param $slug
84      * @return Response
85      */
86     public function show($slug)
87     {
88         $book = $this->entityRepo->getBySlug('book', $slug);
89         $this->checkOwnablePermission('book-view', $book);
90         $bookChildren = $this->entityRepo->getBookChildren($book);
91         Views::add($book);
92         $this->setPageTitle($book->getShortName());
93         return view('books/show', ['book' => $book, 'current' => $book, 'bookChildren' => $bookChildren]);
94     }
95
96     /**
97      * Show the form for editing the specified book.
98      * @param $slug
99      * @return Response
100      */
101     public function edit($slug)
102     {
103         $book = $this->entityRepo->getBySlug('book', $slug);
104         $this->checkOwnablePermission('book-update', $book);
105         $this->setPageTitle(trans('entities.books_edit_named',['bookName'=>$book->getShortName()]));
106         return view('books/edit', ['book' => $book, 'current' => $book]);
107     }
108
109     /**
110      * Update the specified book in storage.
111      * @param  Request $request
112      * @param          $slug
113      * @return Response
114      */
115     public function update(Request $request, $slug)
116     {
117         $book = $this->entityRepo->getBySlug('book', $slug);
118         $this->checkOwnablePermission('book-update', $book);
119         $this->validate($request, [
120             'name' => 'required|string|max:255',
121             'description' => 'string|max:1000'
122         ]);
123          $image = $request->file('image');
124          $path = $this->getBookCoverURL($image);
125          $book = $this->entityRepo->updateFromInput('book', $book, $request->all());
126          $book->image = $path; 
127          $book->save();
128          Activity::add($book, 'book_update', $book->id);
129          return redirect($book->getUrl());
130     }
131
132     /**
133      * Generate URL for book cover image
134      * @param  $image
135      * @return $path
136      */
137     private function getBookCoverURL($image)
138     {
139         if(is_null($image))
140         {
141             return;
142         }
143         else
144         {
145             $input = time().'-'.$image->getClientOriginalName();
146             $destinationPath = public_path('uploads/book/');
147             $image->move($destinationPath, $input);
148             $path = baseUrl('/uploads/book/').'/'.$input;
149             return $path;
150         }
151     }
152
153     /**
154      * Shows the page to confirm deletion
155      * @param $bookSlug
156      * @return \Illuminate\View\View
157      */
158     public function showDelete($bookSlug)
159     {
160         $book = $this->entityRepo->getBySlug('book', $bookSlug);
161         $this->checkOwnablePermission('book-delete', $book);
162         $this->setPageTitle(trans('entities.books_delete_named', ['bookName'=>$book->getShortName()]));
163         return view('books/delete', ['book' => $book, 'current' => $book]);
164     }
165
166     /**
167      * Shows the view which allows pages to be re-ordered and sorted.
168      * @param string $bookSlug
169      * @return \Illuminate\View\View
170      */
171     public function sort($bookSlug)
172     {
173         $book = $this->entityRepo->getBySlug('book', $bookSlug);
174         $this->checkOwnablePermission('book-update', $book);
175         $bookChildren = $this->entityRepo->getBookChildren($book, true);
176         $books = $this->entityRepo->getAll('book', false);
177         $this->setPageTitle(trans('entities.books_sort_named', ['bookName'=>$book->getShortName()]));
178         return view('books/sort', ['book' => $book, 'current' => $book, 'books' => $books, 'bookChildren' => $bookChildren]);
179     }
180
181     /**
182      * Shows the sort box for a single book.
183      * Used via AJAX when loading in extra books to a sort.
184      * @param $bookSlug
185      * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
186      */
187     public function getSortItem($bookSlug)
188     {
189         $book = $this->entityRepo->getBySlug('book', $bookSlug);
190         $bookChildren = $this->entityRepo->getBookChildren($book);
191         return view('books/sort-box', ['book' => $book, 'bookChildren' => $bookChildren]);
192     }
193
194     /**
195      * Saves an array of sort mapping to pages and chapters.
196      * @param  string $bookSlug
197      * @param Request $request
198      * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
199      */
200     public function saveSort($bookSlug, Request $request)
201     {
202         $book = $this->entityRepo->getBySlug('book', $bookSlug);
203         $this->checkOwnablePermission('book-update', $book);
204
205         // Return if no map sent
206         if (!$request->has('sort-tree')) {
207             return redirect($book->getUrl());
208         }
209
210         // Sort pages and chapters
211         $sortedBooks = [];
212         $updatedModels = collect();
213         $sortMap = json_decode($request->get('sort-tree'));
214         $defaultBookId = $book->id;
215
216         // Loop through contents of provided map and update entities accordingly
217         foreach ($sortMap as $bookChild) {
218             $priority = $bookChild->sort;
219             $id = intval($bookChild->id);
220             $isPage = $bookChild->type == 'page';
221             $bookId = $this->entityRepo->exists('book', $bookChild->book) ? intval($bookChild->book) : $defaultBookId;
222             $chapterId = ($isPage && $bookChild->parentChapter === false) ? 0 : intval($bookChild->parentChapter);
223             $model = $this->entityRepo->getById($isPage?'page':'chapter', $id);
224
225             // Update models only if there's a change in parent chain or ordering.
226             if ($model->priority !== $priority || $model->book_id !== $bookId || ($isPage && $model->chapter_id !== $chapterId)) {
227                 $this->entityRepo->changeBook($isPage?'page':'chapter', $bookId, $model);
228                 $model->priority = $priority;
229                 if ($isPage) $model->chapter_id = $chapterId;
230                 $model->save();
231                 $updatedModels->push($model);
232             }
233
234             // Store involved books to be sorted later
235             if (!in_array($bookId, $sortedBooks)) {
236                 $sortedBooks[] = $bookId;
237             }
238         }
239
240         // Add activity for books
241         foreach ($sortedBooks as $bookId) {
242             /** @var Book $updatedBook */
243             $updatedBook = $this->entityRepo->getById('book', $bookId);
244             $this->entityRepo->buildJointPermissionsForBook($updatedBook);
245             Activity::add($updatedBook, 'book_sort', $updatedBook->id);
246         }
247
248         return redirect($book->getUrl());
249     }
250
251     /**
252      * Remove the specified book from storage.
253      * @param $bookSlug
254      * @return Response
255      */
256     public function destroy($bookSlug)
257     {
258         $book = $this->entityRepo->getBySlug('book', $bookSlug);
259         $this->checkOwnablePermission('book-delete', $book);
260         Activity::addMessage('book_delete', 0, $book->name);
261         $file = basename($book->image);
262         File::delete('uploads/book/'.$file);
263         $this->entityRepo->destroyBook($book);
264         return redirect('/books');
265     }
266
267     /**
268      * Show the Restrictions view.
269      * @param $bookSlug
270      * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
271      */
272     public function showRestrict($bookSlug)
273     {
274         $book = $this->entityRepo->getBySlug('book', $bookSlug);
275         $this->checkOwnablePermission('restrictions-manage', $book);
276         $roles = $this->userRepo->getRestrictableRoles();
277         return view('books/restrictions', [
278             'book' => $book,
279             'roles' => $roles
280         ]);
281     }
282
283     /**
284      * Set the restrictions for this book.
285      * @param $bookSlug
286      * @param $bookSlug
287      * @param Request $request
288      * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
289      */
290     public function restrict($bookSlug, Request $request)
291     {
292         $book = $this->entityRepo->getBySlug('book', $bookSlug);
293         $this->checkOwnablePermission('restrictions-manage', $book);
294         $this->entityRepo->updateEntityPermissionsFromRequest($request, $book);
295         session()->flash('success', trans('entities.books_permissions_updated'));
296         return redirect($book->getUrl());
297     }
298
299     /**
300      * Export a book as a PDF file.
301      * @param string $bookSlug
302      * @return mixed
303      */
304     public function exportPdf($bookSlug)
305     {
306         $book = $this->entityRepo->getBySlug('book', $bookSlug);
307         $pdfContent = $this->exportService->bookToPdf($book);
308         return response()->make($pdfContent, 200, [
309             'Content-Type'        => 'application/octet-stream',
310             'Content-Disposition' => 'attachment; filename="' . $bookSlug . '.pdf'
311         ]);
312     }
313
314     /**
315      * Export a book as a contained HTML file.
316      * @param string $bookSlug
317      * @return mixed
318      */
319     public function exportHtml($bookSlug)
320     {
321         $book = $this->entityRepo->getBySlug('book', $bookSlug);
322         $htmlContent = $this->exportService->bookToContainedHtml($book);
323         return response()->make($htmlContent, 200, [
324             'Content-Type'        => 'application/octet-stream',
325             'Content-Disposition' => 'attachment; filename="' . $bookSlug . '.html'
326         ]);
327     }
328
329     /**
330      * Export a book as a plain text file.
331      * @param $bookSlug
332      * @return mixed
333      */
334     public function exportPlainText($bookSlug)
335     {
336         $book = $this->entityRepo->getBySlug('book', $bookSlug);
337         $htmlContent = $this->exportService->bookToPlainText($book);
338         return response()->make($htmlContent, 200, [
339             'Content-Type'        => 'application/octet-stream',
340             'Content-Disposition' => 'attachment; filename="' . $bookSlug . '.txt'
341         ]);
342     }
343 }