X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/c9a5c29abf163b7f907340c76f0a140bdd59b6f6..refs/pull/5676/head:/app/Entities/Tools/BookContents.php diff --git a/app/Entities/Tools/BookContents.php b/app/Entities/Tools/BookContents.php index 7fa2134b7..7dd3f3e11 100644 --- a/app/Entities/Tools/BookContents.php +++ b/app/Entities/Tools/BookContents.php @@ -8,6 +8,8 @@ use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Page; use BookStack\Entities\Queries\EntityQueries; +use BookStack\Sorting\BookSortMap; +use BookStack\Sorting\BookSortMapItem; use Illuminate\Support\Collection; class BookContents @@ -103,211 +105,4 @@ class BookContents return $query->where('book_id', '=', $this->book->id)->get(); } - - /** - * Sort the books content using the given sort map. - * Returns a list of books that were involved in the operation. - * - * @returns Book[] - */ - public function sortUsingMap(BookSortMap $sortMap): array - { - // Load models into map - $modelMap = $this->loadModelsFromSortMap($sortMap); - - // Sort our changes from our map to be chapters first - // Since they need to be process to ensure book alignment for child page changes. - $sortMapItems = $sortMap->all(); - usort($sortMapItems, function (BookSortMapItem $itemA, BookSortMapItem $itemB) { - $aScore = $itemA->type === 'page' ? 2 : 1; - $bScore = $itemB->type === 'page' ? 2 : 1; - - return $aScore - $bScore; - }); - - // Perform the sort - foreach ($sortMapItems as $item) { - $this->applySortUpdates($item, $modelMap); - } - - /** @var Book[] $booksInvolved */ - $booksInvolved = array_values(array_filter($modelMap, function (string $key) { - return str_starts_with($key, 'book:'); - }, ARRAY_FILTER_USE_KEY)); - - // Update permissions of books involved - foreach ($booksInvolved as $book) { - $book->rebuildPermissions(); - } - - return $booksInvolved; - } - - /** - * Using the given sort map item, detect changes for the related model - * and update it if required. Changes where permissions are lacking will - * be skipped and not throw an error. - * - * @param array $modelMap - */ - protected function applySortUpdates(BookSortMapItem $sortMapItem, array $modelMap): void - { - /** @var BookChild $model */ - $model = $modelMap[$sortMapItem->type . ':' . $sortMapItem->id] ?? null; - if (!$model) { - return; - } - - $priorityChanged = $model->priority !== $sortMapItem->sort; - $bookChanged = $model->book_id !== $sortMapItem->parentBookId; - $chapterChanged = ($model instanceof Page) && $model->chapter_id !== $sortMapItem->parentChapterId; - - // Stop if there's no change - if (!$priorityChanged && !$bookChanged && !$chapterChanged) { - return; - } - - $currentParentKey = 'book:' . $model->book_id; - if ($model instanceof Page && $model->chapter_id) { - $currentParentKey = 'chapter:' . $model->chapter_id; - } - - $currentParent = $modelMap[$currentParentKey] ?? null; - /** @var Book $newBook */ - $newBook = $modelMap['book:' . $sortMapItem->parentBookId] ?? null; - /** @var ?Chapter $newChapter */ - $newChapter = $sortMapItem->parentChapterId ? ($modelMap['chapter:' . $sortMapItem->parentChapterId] ?? null) : null; - - if (!$this->isSortChangePermissible($sortMapItem, $model, $currentParent, $newBook, $newChapter)) { - return; - } - - // Action the required changes - if ($bookChanged) { - $model->changeBook($newBook->id); - } - - if ($model instanceof Page && $chapterChanged) { - $model->chapter_id = $newChapter->id ?? 0; - } - - if ($priorityChanged) { - $model->priority = $sortMapItem->sort; - } - - if ($chapterChanged || $priorityChanged) { - $model->save(); - } - } - - /** - * Check if the current user has permissions to apply the given sorting change. - * Is quite complex since items can gain a different parent change. Acts as a: - * - Update of old parent element (Change of content/order). - * - Update of sorted/moved element. - * - Deletion of element (Relative to parent upon move). - * - Creation of element within parent (Upon move to new parent). - */ - protected function isSortChangePermissible(BookSortMapItem $sortMapItem, BookChild $model, ?Entity $currentParent, ?Entity $newBook, ?Entity $newChapter): bool - { - // Stop if we can't see the current parent or new book. - if (!$currentParent || !$newBook) { - return false; - } - - $hasNewParent = $newBook->id !== $model->book_id || ($model instanceof Page && $model->chapter_id !== ($sortMapItem->parentChapterId ?? 0)); - if ($model instanceof Chapter) { - $hasPermission = userCan('book-update', $currentParent) - && userCan('book-update', $newBook) - && userCan('chapter-update', $model) - && (!$hasNewParent || userCan('chapter-create', $newBook)) - && (!$hasNewParent || userCan('chapter-delete', $model)); - - if (!$hasPermission) { - return false; - } - } - - if ($model instanceof Page) { - $parentPermission = ($currentParent instanceof Chapter) ? 'chapter-update' : 'book-update'; - $hasCurrentParentPermission = userCan($parentPermission, $currentParent); - - // This needs to check if there was an intended chapter location in the original sort map - // rather than inferring from the $newChapter since that variable may be null - // due to other reasons (Visibility). - $newParent = $sortMapItem->parentChapterId ? $newChapter : $newBook; - if (!$newParent) { - return false; - } - - $hasPageEditPermission = userCan('page-update', $model); - $newParentInRightLocation = ($newParent instanceof Book || ($newParent instanceof Chapter && $newParent->book_id === $newBook->id)); - $newParentPermission = ($newParent instanceof Chapter) ? 'chapter-update' : 'book-update'; - $hasNewParentPermission = userCan($newParentPermission, $newParent); - - $hasDeletePermissionIfMoving = (!$hasNewParent || userCan('page-delete', $model)); - $hasCreatePermissionIfMoving = (!$hasNewParent || userCan('page-create', $newParent)); - - $hasPermission = $hasCurrentParentPermission - && $newParentInRightLocation - && $hasNewParentPermission - && $hasPageEditPermission - && $hasDeletePermissionIfMoving - && $hasCreatePermissionIfMoving; - - if (!$hasPermission) { - return false; - } - } - - return true; - } - - /** - * Load models from the database into the given sort map. - * - * @return array - */ - protected function loadModelsFromSortMap(BookSortMap $sortMap): array - { - $modelMap = []; - $ids = [ - 'chapter' => [], - 'page' => [], - 'book' => [], - ]; - - foreach ($sortMap->all() as $sortMapItem) { - $ids[$sortMapItem->type][] = $sortMapItem->id; - $ids['book'][] = $sortMapItem->parentBookId; - if ($sortMapItem->parentChapterId) { - $ids['chapter'][] = $sortMapItem->parentChapterId; - } - } - - $pages = $this->queries->pages->visibleForList()->whereIn('id', array_unique($ids['page']))->get(); - /** @var Page $page */ - foreach ($pages as $page) { - $modelMap['page:' . $page->id] = $page; - $ids['book'][] = $page->book_id; - if ($page->chapter_id) { - $ids['chapter'][] = $page->chapter_id; - } - } - - $chapters = $this->queries->chapters->visibleForList()->whereIn('id', array_unique($ids['chapter']))->get(); - /** @var Chapter $chapter */ - foreach ($chapters as $chapter) { - $modelMap['chapter:' . $chapter->id] = $chapter; - $ids['book'][] = $chapter->book_id; - } - - $books = $this->queries->books->visibleForList()->whereIn('id', array_unique($ids['book']))->get(); - /** @var Book $book */ - foreach ($books as $book) { - $modelMap['book:' . $book->id] = $book; - } - - return $modelMap; - } }