]> BookStack Code Mirror - bookstack/blob - app/Entities/Controllers/BookController.php
a1c586f47e7ce9fca429fca433f1e41de07872f2
[bookstack] / app / Entities / Controllers / BookController.php
1 <?php
2
3 namespace BookStack\Entities\Controllers;
4
5 use BookStack\Activity\ActivityQueries;
6 use BookStack\Activity\ActivityType;
7 use BookStack\Activity\Models\View;
8 use BookStack\Activity\Tools\UserEntityWatchOptions;
9 use BookStack\Entities\Queries\BookQueries;
10 use BookStack\Entities\Queries\BookshelfQueries;
11 use BookStack\Entities\Repos\BookRepo;
12 use BookStack\Entities\Tools\BookContents;
13 use BookStack\Entities\Tools\Cloner;
14 use BookStack\Entities\Tools\HierarchyTransformer;
15 use BookStack\Entities\Tools\ShelfContext;
16 use BookStack\Exceptions\ImageUploadException;
17 use BookStack\Exceptions\NotFoundException;
18 use BookStack\Facades\Activity;
19 use BookStack\Http\Controller;
20 use BookStack\References\ReferenceFetcher;
21 use BookStack\Util\SimpleListOptions;
22 use Illuminate\Http\Request;
23 use Illuminate\Validation\ValidationException;
24 use Throwable;
25
26 class BookController extends Controller
27 {
28     public function __construct(
29         protected ShelfContext $shelfContext,
30         protected BookRepo $bookRepo,
31         protected BookQueries $queries,
32         protected BookshelfQueries $shelfQueries,
33         protected ReferenceFetcher $referenceFetcher,
34     ) {
35     }
36
37     /**
38      * Display a listing of the book.
39      */
40     public function index(Request $request)
41     {
42         $view = setting()->getForCurrentUser('books_view_type');
43         $listOptions = SimpleListOptions::fromRequest($request, 'books')->withSortOptions([
44             'name' => trans('common.sort_name'),
45             'created_at' => trans('common.sort_created_at'),
46             'updated_at' => trans('common.sort_updated_at'),
47         ]);
48
49         $books = $this->queries->visibleForListWithCover()
50             ->orderBy($listOptions->getSort(), $listOptions->getOrder())
51             ->paginate(18);
52         $recents = $this->isSignedIn() ? $this->queries->recentlyViewedForCurrentUser()->take(4)->get() : false;
53         $popular = $this->queries->popularForList()->take(4)->get();
54         $new = $this->queries->visibleForList()->orderBy('created_at', 'desc')->take(4)->get();
55
56         $this->shelfContext->clearShelfContext();
57
58         $this->setPageTitle(trans('entities.books'));
59
60         return view('books.index', [
61             'books'   => $books,
62             'recents' => $recents,
63             'popular' => $popular,
64             'new'     => $new,
65             'view'    => $view,
66             'listOptions' => $listOptions,
67         ]);
68     }
69
70     /**
71      * Show the form for creating a new book.
72      */
73     public function create(string $shelfSlug = null)
74     {
75         $this->checkPermission('book-create-all');
76
77         $bookshelf = null;
78         if ($shelfSlug !== null) {
79             $bookshelf = $this->shelfQueries->findVisibleBySlugOrFail($shelfSlug);
80             $this->checkOwnablePermission('bookshelf-update', $bookshelf);
81         }
82
83         $this->setPageTitle(trans('entities.books_create'));
84
85         return view('books.create', [
86             'bookshelf' => $bookshelf,
87         ]);
88     }
89
90     /**
91      * Store a newly created book in storage.
92      *
93      * @throws ImageUploadException
94      * @throws ValidationException
95      */
96     public function store(Request $request, string $shelfSlug = null)
97     {
98         $this->checkPermission('book-create-all');
99         $validated = $this->validate($request, [
100             'name'                => ['required', 'string', 'max:255'],
101             'description_html'    => ['string', 'max:2000'],
102             'image'               => array_merge(['nullable'], $this->getImageValidationRules()),
103             'tags'                => ['array'],
104             'default_template_id' => ['nullable', 'integer'],
105         ]);
106
107         $bookshelf = null;
108         if ($shelfSlug !== null) {
109             $bookshelf = $this->shelfQueries->findVisibleBySlugOrFail($shelfSlug);
110             $this->checkOwnablePermission('bookshelf-update', $bookshelf);
111         }
112
113         $book = $this->bookRepo->create($validated);
114
115         if ($bookshelf) {
116             $bookshelf->appendBook($book);
117             Activity::add(ActivityType::BOOKSHELF_UPDATE, $bookshelf);
118         }
119
120         return redirect($book->getUrl());
121     }
122
123     /**
124      * Display the specified book.
125      */
126     public function show(Request $request, ActivityQueries $activities, string $slug)
127     {
128         $book = $this->queries->findVisibleBySlugOrFail($slug);
129         $bookChildren = (new BookContents($book))->getTree(true);
130         $bookParentShelves = $book->shelves()->scopes('visible')->get();
131
132         View::incrementFor($book);
133         if ($request->has('shelf')) {
134             $this->shelfContext->setShelfContext(intval($request->get('shelf')));
135         }
136
137         $this->setPageTitle($book->getShortName());
138
139         return view('books.show', [
140             'book'              => $book,
141             'current'           => $book,
142             'bookChildren'      => $bookChildren,
143             'bookParentShelves' => $bookParentShelves,
144             'watchOptions'      => new UserEntityWatchOptions(user(), $book),
145             'activity'          => $activities->entityActivity($book, 20, 1),
146             'referenceCount'    => $this->referenceFetcher->getReferenceCountToEntity($book),
147         ]);
148     }
149
150     /**
151      * Show the form for editing the specified book.
152      */
153     public function edit(string $slug)
154     {
155         $book = $this->queries->findVisibleBySlugOrFail($slug);
156         $this->checkOwnablePermission('book-update', $book);
157         $this->setPageTitle(trans('entities.books_edit_named', ['bookName' => $book->getShortName()]));
158
159         return view('books.edit', ['book' => $book, 'current' => $book]);
160     }
161
162     /**
163      * Update the specified book in storage.
164      *
165      * @throws ImageUploadException
166      * @throws ValidationException
167      * @throws Throwable
168      */
169     public function update(Request $request, string $slug)
170     {
171         $book = $this->queries->findVisibleBySlugOrFail($slug);
172         $this->checkOwnablePermission('book-update', $book);
173
174         $validated = $this->validate($request, [
175             'name'                => ['required', 'string', 'max:255'],
176             'description_html'    => ['string', 'max:2000'],
177             'image'               => array_merge(['nullable'], $this->getImageValidationRules()),
178             'tags'                => ['array'],
179             'default_template_id' => ['nullable', 'integer'],
180         ]);
181
182         if ($request->has('image_reset')) {
183             $validated['image'] = null;
184         } elseif (array_key_exists('image', $validated) && is_null($validated['image'])) {
185             unset($validated['image']);
186         }
187
188         $book = $this->bookRepo->update($book, $validated);
189
190         return redirect($book->getUrl());
191     }
192
193     /**
194      * Shows the page to confirm deletion.
195      */
196     public function showDelete(string $bookSlug)
197     {
198         $book = $this->queries->findVisibleBySlugOrFail($bookSlug);
199         $this->checkOwnablePermission('book-delete', $book);
200         $this->setPageTitle(trans('entities.books_delete_named', ['bookName' => $book->getShortName()]));
201
202         return view('books.delete', ['book' => $book, 'current' => $book]);
203     }
204
205     /**
206      * Remove the specified book from the system.
207      *
208      * @throws Throwable
209      */
210     public function destroy(string $bookSlug)
211     {
212         $book = $this->queries->findVisibleBySlugOrFail($bookSlug);
213         $this->checkOwnablePermission('book-delete', $book);
214
215         $this->bookRepo->destroy($book);
216
217         return redirect('/books');
218     }
219
220     /**
221      * Show the view to copy a book.
222      *
223      * @throws NotFoundException
224      */
225     public function showCopy(string $bookSlug)
226     {
227         $book = $this->queries->findVisibleBySlugOrFail($bookSlug);
228         $this->checkOwnablePermission('book-view', $book);
229
230         session()->flashInput(['name' => $book->name]);
231
232         return view('books.copy', [
233             'book' => $book,
234         ]);
235     }
236
237     /**
238      * Create a copy of a book within the requested target destination.
239      *
240      * @throws NotFoundException
241      */
242     public function copy(Request $request, Cloner $cloner, string $bookSlug)
243     {
244         $book = $this->queries->findVisibleBySlugOrFail($bookSlug);
245         $this->checkOwnablePermission('book-view', $book);
246         $this->checkPermission('book-create-all');
247
248         $newName = $request->get('name') ?: $book->name;
249         $bookCopy = $cloner->cloneBook($book, $newName);
250         $this->showSuccessNotification(trans('entities.books_copy_success'));
251
252         return redirect($bookCopy->getUrl());
253     }
254
255     /**
256      * Convert the chapter to a book.
257      */
258     public function convertToShelf(HierarchyTransformer $transformer, string $bookSlug)
259     {
260         $book = $this->queries->findVisibleBySlugOrFail($bookSlug);
261         $this->checkOwnablePermission('book-update', $book);
262         $this->checkOwnablePermission('book-delete', $book);
263         $this->checkPermission('bookshelf-create-all');
264         $this->checkPermission('book-create-all');
265
266         $shelf = $transformer->transformBookToShelf($book);
267
268         return redirect($shelf->getUrl());
269     }
270 }