|
8 | 8 | use BookStack\Entities\Models\Entity; |
9 | 9 | use BookStack\Entities\Models\Page; |
10 | 10 | use BookStack\Entities\Queries\EntityQueries; |
| 11 | +use BookStack\Sorting\BookSortMap; |
| 12 | +use BookStack\Sorting\BookSortMapItem; |
11 | 13 | use Illuminate\Support\Collection; |
12 | 14 |
|
13 | 15 | class BookContents |
@@ -103,211 +105,4 @@ protected function getPages(bool $showDrafts = false, bool $getPageContent = fal |
103 | 105 |
|
104 | 106 | return $query->where('book_id', '=', $this->book->id)->get(); |
105 | 107 | } |
106 | | - |
107 | | - /** |
108 | | - * Sort the books content using the given sort map. |
109 | | - * Returns a list of books that were involved in the operation. |
110 | | - * |
111 | | - * @returns Book[] |
112 | | - */ |
113 | | - public function sortUsingMap(BookSortMap $sortMap): array |
114 | | - { |
115 | | - // Load models into map |
116 | | - $modelMap = $this->loadModelsFromSortMap($sortMap); |
117 | | - |
118 | | - // Sort our changes from our map to be chapters first |
119 | | - // Since they need to be process to ensure book alignment for child page changes. |
120 | | - $sortMapItems = $sortMap->all(); |
121 | | - usort($sortMapItems, function (BookSortMapItem $itemA, BookSortMapItem $itemB) { |
122 | | - $aScore = $itemA->type === 'page' ? 2 : 1; |
123 | | - $bScore = $itemB->type === 'page' ? 2 : 1; |
124 | | - |
125 | | - return $aScore - $bScore; |
126 | | - }); |
127 | | - |
128 | | - // Perform the sort |
129 | | - foreach ($sortMapItems as $item) { |
130 | | - $this->applySortUpdates($item, $modelMap); |
131 | | - } |
132 | | - |
133 | | - /** @var Book[] $booksInvolved */ |
134 | | - $booksInvolved = array_values(array_filter($modelMap, function (string $key) { |
135 | | - return str_starts_with($key, 'book:'); |
136 | | - }, ARRAY_FILTER_USE_KEY)); |
137 | | - |
138 | | - // Update permissions of books involved |
139 | | - foreach ($booksInvolved as $book) { |
140 | | - $book->rebuildPermissions(); |
141 | | - } |
142 | | - |
143 | | - return $booksInvolved; |
144 | | - } |
145 | | - |
146 | | - /** |
147 | | - * Using the given sort map item, detect changes for the related model |
148 | | - * and update it if required. Changes where permissions are lacking will |
149 | | - * be skipped and not throw an error. |
150 | | - * |
151 | | - * @param array<string, Entity> $modelMap |
152 | | - */ |
153 | | - protected function applySortUpdates(BookSortMapItem $sortMapItem, array $modelMap): void |
154 | | - { |
155 | | - /** @var BookChild $model */ |
156 | | - $model = $modelMap[$sortMapItem->type . ':' . $sortMapItem->id] ?? null; |
157 | | - if (!$model) { |
158 | | - return; |
159 | | - } |
160 | | - |
161 | | - $priorityChanged = $model->priority !== $sortMapItem->sort; |
162 | | - $bookChanged = $model->book_id !== $sortMapItem->parentBookId; |
163 | | - $chapterChanged = ($model instanceof Page) && $model->chapter_id !== $sortMapItem->parentChapterId; |
164 | | - |
165 | | - // Stop if there's no change |
166 | | - if (!$priorityChanged && !$bookChanged && !$chapterChanged) { |
167 | | - return; |
168 | | - } |
169 | | - |
170 | | - $currentParentKey = 'book:' . $model->book_id; |
171 | | - if ($model instanceof Page && $model->chapter_id) { |
172 | | - $currentParentKey = 'chapter:' . $model->chapter_id; |
173 | | - } |
174 | | - |
175 | | - $currentParent = $modelMap[$currentParentKey] ?? null; |
176 | | - /** @var Book $newBook */ |
177 | | - $newBook = $modelMap['book:' . $sortMapItem->parentBookId] ?? null; |
178 | | - /** @var ?Chapter $newChapter */ |
179 | | - $newChapter = $sortMapItem->parentChapterId ? ($modelMap['chapter:' . $sortMapItem->parentChapterId] ?? null) : null; |
180 | | - |
181 | | - if (!$this->isSortChangePermissible($sortMapItem, $model, $currentParent, $newBook, $newChapter)) { |
182 | | - return; |
183 | | - } |
184 | | - |
185 | | - // Action the required changes |
186 | | - if ($bookChanged) { |
187 | | - $model->changeBook($newBook->id); |
188 | | - } |
189 | | - |
190 | | - if ($model instanceof Page && $chapterChanged) { |
191 | | - $model->chapter_id = $newChapter->id ?? 0; |
192 | | - } |
193 | | - |
194 | | - if ($priorityChanged) { |
195 | | - $model->priority = $sortMapItem->sort; |
196 | | - } |
197 | | - |
198 | | - if ($chapterChanged || $priorityChanged) { |
199 | | - $model->save(); |
200 | | - } |
201 | | - } |
202 | | - |
203 | | - /** |
204 | | - * Check if the current user has permissions to apply the given sorting change. |
205 | | - * Is quite complex since items can gain a different parent change. Acts as a: |
206 | | - * - Update of old parent element (Change of content/order). |
207 | | - * - Update of sorted/moved element. |
208 | | - * - Deletion of element (Relative to parent upon move). |
209 | | - * - Creation of element within parent (Upon move to new parent). |
210 | | - */ |
211 | | - protected function isSortChangePermissible(BookSortMapItem $sortMapItem, BookChild $model, ?Entity $currentParent, ?Entity $newBook, ?Entity $newChapter): bool |
212 | | - { |
213 | | - // Stop if we can't see the current parent or new book. |
214 | | - if (!$currentParent || !$newBook) { |
215 | | - return false; |
216 | | - } |
217 | | - |
218 | | - $hasNewParent = $newBook->id !== $model->book_id || ($model instanceof Page && $model->chapter_id !== ($sortMapItem->parentChapterId ?? 0)); |
219 | | - if ($model instanceof Chapter) { |
220 | | - $hasPermission = userCan('book-update', $currentParent) |
221 | | - && userCan('book-update', $newBook) |
222 | | - && userCan('chapter-update', $model) |
223 | | - && (!$hasNewParent || userCan('chapter-create', $newBook)) |
224 | | - && (!$hasNewParent || userCan('chapter-delete', $model)); |
225 | | - |
226 | | - if (!$hasPermission) { |
227 | | - return false; |
228 | | - } |
229 | | - } |
230 | | - |
231 | | - if ($model instanceof Page) { |
232 | | - $parentPermission = ($currentParent instanceof Chapter) ? 'chapter-update' : 'book-update'; |
233 | | - $hasCurrentParentPermission = userCan($parentPermission, $currentParent); |
234 | | - |
235 | | - // This needs to check if there was an intended chapter location in the original sort map |
236 | | - // rather than inferring from the $newChapter since that variable may be null |
237 | | - // due to other reasons (Visibility). |
238 | | - $newParent = $sortMapItem->parentChapterId ? $newChapter : $newBook; |
239 | | - if (!$newParent) { |
240 | | - return false; |
241 | | - } |
242 | | - |
243 | | - $hasPageEditPermission = userCan('page-update', $model); |
244 | | - $newParentInRightLocation = ($newParent instanceof Book || ($newParent instanceof Chapter && $newParent->book_id === $newBook->id)); |
245 | | - $newParentPermission = ($newParent instanceof Chapter) ? 'chapter-update' : 'book-update'; |
246 | | - $hasNewParentPermission = userCan($newParentPermission, $newParent); |
247 | | - |
248 | | - $hasDeletePermissionIfMoving = (!$hasNewParent || userCan('page-delete', $model)); |
249 | | - $hasCreatePermissionIfMoving = (!$hasNewParent || userCan('page-create', $newParent)); |
250 | | - |
251 | | - $hasPermission = $hasCurrentParentPermission |
252 | | - && $newParentInRightLocation |
253 | | - && $hasNewParentPermission |
254 | | - && $hasPageEditPermission |
255 | | - && $hasDeletePermissionIfMoving |
256 | | - && $hasCreatePermissionIfMoving; |
257 | | - |
258 | | - if (!$hasPermission) { |
259 | | - return false; |
260 | | - } |
261 | | - } |
262 | | - |
263 | | - return true; |
264 | | - } |
265 | | - |
266 | | - /** |
267 | | - * Load models from the database into the given sort map. |
268 | | - * |
269 | | - * @return array<string, Entity> |
270 | | - */ |
271 | | - protected function loadModelsFromSortMap(BookSortMap $sortMap): array |
272 | | - { |
273 | | - $modelMap = []; |
274 | | - $ids = [ |
275 | | - 'chapter' => [], |
276 | | - 'page' => [], |
277 | | - 'book' => [], |
278 | | - ]; |
279 | | - |
280 | | - foreach ($sortMap->all() as $sortMapItem) { |
281 | | - $ids[$sortMapItem->type][] = $sortMapItem->id; |
282 | | - $ids['book'][] = $sortMapItem->parentBookId; |
283 | | - if ($sortMapItem->parentChapterId) { |
284 | | - $ids['chapter'][] = $sortMapItem->parentChapterId; |
285 | | - } |
286 | | - } |
287 | | - |
288 | | - $pages = $this->queries->pages->visibleForList()->whereIn('id', array_unique($ids['page']))->get(); |
289 | | - /** @var Page $page */ |
290 | | - foreach ($pages as $page) { |
291 | | - $modelMap['page:' . $page->id] = $page; |
292 | | - $ids['book'][] = $page->book_id; |
293 | | - if ($page->chapter_id) { |
294 | | - $ids['chapter'][] = $page->chapter_id; |
295 | | - } |
296 | | - } |
297 | | - |
298 | | - $chapters = $this->queries->chapters->visibleForList()->whereIn('id', array_unique($ids['chapter']))->get(); |
299 | | - /** @var Chapter $chapter */ |
300 | | - foreach ($chapters as $chapter) { |
301 | | - $modelMap['chapter:' . $chapter->id] = $chapter; |
302 | | - $ids['book'][] = $chapter->book_id; |
303 | | - } |
304 | | - |
305 | | - $books = $this->queries->books->visibleForList()->whereIn('id', array_unique($ids['book']))->get(); |
306 | | - /** @var Book $book */ |
307 | | - foreach ($books as $book) { |
308 | | - $modelMap['book:' . $book->id] = $book; |
309 | | - } |
310 | | - |
311 | | - return $modelMap; |
312 | | - } |
313 | 108 | } |
0 commit comments