diff --git a/app/Entities/Tools/BookContents.php b/app/Entities/Tools/BookContents.php index 9b2190ca2..96142bb7f 100644 --- a/app/Entities/Tools/BookContents.php +++ b/app/Entities/Tools/BookContents.php @@ -107,30 +107,21 @@ class BookContents } /** - * Sort the books content using the given map. - * The map is a single-dimension collection of objects in the following format: - * { - * +"id": "294" (ID of item) - * +"sort": 1 (Sort order index) - * +"parentChapter": false (ID of parent chapter, as string, or false) - * +"type": "page" (Entity type of item) - * +"book": "1" (Id of book to place item in) - * }. - * + * Sort the books content using the given sort map. * Returns a list of books that were involved in the operation. * * @throws SortOperationException */ - public function sortUsingMap(Collection $sortMap): Collection + public function sortUsingMap(BookSortMap $sortMap): Collection { // Load models into map $this->loadModelsIntoSortMap($sortMap); $booksInvolved = $this->getBooksInvolvedInSort($sortMap); // Perform the sort - $sortMap->each(function ($mapItem) { - $this->applySortUpdates($mapItem); - }); + foreach ($sortMap->all() as $item) { + $this->applySortUpdates($item); + } // Update permissions and activity. $booksInvolved->each(function (Book $book) { @@ -144,26 +135,28 @@ class BookContents * Using the given sort map item, detect changes for the related model * and update it if required. */ - protected function applySortUpdates(\stdClass $sortMapItem) + protected function applySortUpdates(BookSortMapItem $sortMapItem): void { - /** @var BookChild $model */ $model = $sortMapItem->model; + if (!$model) { + return; + } - $priorityChanged = intval($model->priority) !== intval($sortMapItem->sort); - $bookChanged = intval($model->book_id) !== intval($sortMapItem->book); - $chapterChanged = ($model instanceof Page) && intval($model->chapter_id) !== $sortMapItem->parentChapter; + $priorityChanged = $model->priority !== $sortMapItem->sort; + $bookChanged = $model->book_id !== $sortMapItem->parentBookId; + $chapterChanged = ($model instanceof Page) && $model->chapter_id !== $sortMapItem->parentChapterId; if ($bookChanged) { - $model->changeBook($sortMapItem->book); + $model->changeBook($sortMapItem->parentBookId); } if ($chapterChanged) { - $model->chapter_id = intval($sortMapItem->parentChapter); + $model->chapter_id = intval($sortMapItem->parentChapterId); $model->save(); } if ($priorityChanged) { - $model->priority = intval($sortMapItem->sort); + $model->priority = $sortMapItem->sort; $model->save(); } } @@ -171,23 +164,28 @@ class BookContents /** * Load models from the database into the given sort map. */ - protected function loadModelsIntoSortMap(Collection $sortMap): void + protected function loadModelsIntoSortMap(BookSortMap $sortMap): void { - $keyMap = $sortMap->keyBy(function (\stdClass $sortMapItem) { + $collection = collect($sortMap->all()); + + $keyMap = $collection->keyBy(function (BookSortMapItem $sortMapItem) { return $sortMapItem->type . ':' . $sortMapItem->id; }); - $pageIds = $sortMap->where('type', '=', 'page')->pluck('id'); - $chapterIds = $sortMap->where('type', '=', 'chapter')->pluck('id'); + + $pageIds = $collection->where('type', '=', 'page')->pluck('id'); + $chapterIds = $collection->where('type', '=', 'chapter')->pluck('id'); $pages = Page::visible()->whereIn('id', $pageIds)->get(); $chapters = Chapter::visible()->whereIn('id', $chapterIds)->get(); foreach ($pages as $page) { + /** @var BookSortMapItem $sortItem */ $sortItem = $keyMap->get('page:' . $page->id); $sortItem->model = $page; } foreach ($chapters as $chapter) { + /** @var BookSortMapItem $sortItem */ $sortItem = $keyMap->get('chapter:' . $chapter->id); $sortItem->model = $chapter; } @@ -199,13 +197,16 @@ class BookContents * * @throws SortOperationException */ - protected function getBooksInvolvedInSort(Collection $sortMap): Collection + protected function getBooksInvolvedInSort(BookSortMap $sortMap): Collection { - $bookIdsInvolved = collect([$this->book->id]); - $bookIdsInvolved = $bookIdsInvolved->concat($sortMap->pluck('book')); - $bookIdsInvolved = $bookIdsInvolved->concat($sortMap->pluck('model.book_id')); - $bookIdsInvolved = $bookIdsInvolved->unique()->toArray(); + $collection = collect($sortMap->all()); + $bookIdsInvolved = array_unique(array_merge( + [$this->book->id], + $collection->pluck('parentBookId')->values()->all(), + $collection->pluck('model.book_id')->values()->all(), + )); + $books = Book::hasPermission('update')->whereIn('id', $bookIdsInvolved)->get(); if (count($books) !== count($bookIdsInvolved)) { diff --git a/app/Entities/Tools/BookSortMap.php b/app/Entities/Tools/BookSortMap.php new file mode 100644 index 000000000..1ce4905f7 --- /dev/null +++ b/app/Entities/Tools/BookSortMap.php @@ -0,0 +1,45 @@ +<?php + +namespace BookStack\Entities\Tools; + +class BookSortMap +{ + /** + * @var BookSortMapItem[] + */ + protected $mapData = []; + + public function addItem(BookSortMapItem $mapItem): void + { + $this->mapData[] = $mapItem; + } + + /** + * @return BookSortMapItem[] + */ + public function all(): array + { + return $this->mapData; + } + + public static function fromJson(string $json): self + { + $map = new static(); + $mapData = json_decode($json); + + foreach ($mapData as $mapDataItem) { + $item = new BookSortMapItem( + intval($mapDataItem->id), + intval($mapDataItem->sort), + $mapDataItem->parentChapter ? intval($mapDataItem->parentChapter) : null, + $mapDataItem->type, + intval($mapDataItem->book) + ); + + $map->addItem($item); + } + + return $map; + } + +} \ No newline at end of file diff --git a/app/Entities/Tools/BookSortMapItem.php b/app/Entities/Tools/BookSortMapItem.php new file mode 100644 index 000000000..6a2abc422 --- /dev/null +++ b/app/Entities/Tools/BookSortMapItem.php @@ -0,0 +1,51 @@ +<?php + +namespace BookStack\Entities\Tools; + +use BookStack\Entities\Models\BookChild; + +class BookSortMapItem +{ + + /** + * @var int + */ + public $id; + + /** + * @var int + */ + public $sort; + + /** + * @var ?int + */ + public $parentChapterId; + + /** + * @var string + */ + public $type; + + /** + * @var int + */ + public $parentBookId; + + /** + * @var ?BookChild + */ + public $model = null; + + + public function __construct(int $id, int $sort, ?int $parentChapterId, string $type, int $parentBookId) + { + $this->id = $id; + $this->sort = $sort; + $this->parentChapterId = $parentChapterId; + $this->type = $type; + $this->parentBookId = $parentBookId; + } + + +} \ No newline at end of file diff --git a/app/Http/Controllers/BookSortController.php b/app/Http/Controllers/BookSortController.php index 010e74fa4..8fe05a9be 100644 --- a/app/Http/Controllers/BookSortController.php +++ b/app/Http/Controllers/BookSortController.php @@ -6,6 +6,7 @@ use BookStack\Actions\ActivityType; use BookStack\Entities\Models\Book; use BookStack\Entities\Repos\BookRepo; use BookStack\Entities\Tools\BookContents; +use BookStack\Entities\Tools\BookSortMap; use BookStack\Exceptions\SortOperationException; use BookStack\Facades\Activity; use Illuminate\Http\Request; @@ -59,7 +60,7 @@ class BookSortController extends Controller return redirect($book->getUrl()); } - $sortMap = collect(json_decode($request->get('sort-tree'))); + $sortMap = BookSortMap::fromJson($request->get('sort-tree')); $bookContents = new BookContents($book); $booksInvolved = collect();