+ /**
+ * Get all child objects of a book.
+ * Returns a sorted collection of Pages and Chapters.
+ * Loads the book slug onto child elements to prevent access database access for getting the slug.
+ * @param Book $book
+ * @param bool $filterDrafts
+ * @param bool $renderPages
+ * @return mixed
+ */
+ public function getBookChildren(Book $book, $filterDrafts = false, $renderPages = false)
+ {
+ $q = $this->permissionService->bookChildrenQuery($book->id, $filterDrafts, $renderPages)->get();
+ $entities = [];
+ $parents = [];
+ $tree = [];
+
+ foreach ($q as $index => $rawEntity) {
+ if ($rawEntity->entity_type === 'BookStack\\Page') {
+ $entities[$index] = $this->page->newFromBuilder($rawEntity);
+ if ($renderPages) {
+ $entities[$index]->html = $rawEntity->html;
+ $entities[$index]->html = $this->renderPage($entities[$index]);
+ };
+ } else if ($rawEntity->entity_type === 'BookStack\\Chapter') {
+ $entities[$index] = $this->chapter->newFromBuilder($rawEntity);
+ $key = $entities[$index]->entity_type . ':' . $entities[$index]->id;
+ $parents[$key] = $entities[$index];
+ $parents[$key]->setAttribute('pages', collect());
+ }
+ if ($entities[$index]->chapter_id === 0 || $entities[$index]->chapter_id === '0') $tree[] = $entities[$index];
+ $entities[$index]->book = $book;
+ }
+
+ foreach ($entities as $entity) {
+ if ($entity->chapter_id === 0 || $entity->chapter_id === '0') continue;
+ $parentKey = 'BookStack\\Chapter:' . $entity->chapter_id;
+ if (!isset($parents[$parentKey])) {
+ $tree[] = $entity;
+ continue;
+ }
+ $chapter = $parents[$parentKey];
+ $chapter->pages->push($entity);
+ }
+
+ return collect($tree);
+ }
+
+ /**
+ * Get the child items for a chapter sorted by priority but
+ * with draft items floated to the top.
+ * @param Chapter $chapter
+ * @return \Illuminate\Database\Eloquent\Collection|static[]
+ */
+ public function getChapterChildren(Chapter $chapter)
+ {
+ return $this->permissionService->enforceEntityRestrictions('page', $chapter->pages())
+ ->orderBy('draft', 'DESC')->orderBy('priority', 'ASC')->get();
+ }
+
+
+ /**
+ * Get the next sequential priority for a new child element in the given book.
+ * @param Book $book
+ * @return int
+ */
+ public function getNewBookPriority(Book $book)
+ {
+ $lastElem = $this->getBookChildren($book)->pop();
+ return $lastElem ? $lastElem->priority + 1 : 0;
+ }
+
+ /**
+ * Get a new priority for a new page to be added to the given chapter.
+ * @param Chapter $chapter
+ * @return int
+ */
+ public function getNewChapterPriority(Chapter $chapter)
+ {
+ $lastPage = $chapter->pages('DESC')->first();
+ return $lastPage !== null ? $lastPage->priority + 1 : 0;
+ }
+
+ /**
+ * Find a suitable slug for an entity.
+ * @param string $type
+ * @param string $name
+ * @param bool|integer $currentId
+ * @param bool|integer $bookId Only pass if type is not a book
+ * @return string
+ */
+ public function findSuitableSlug($type, $name, $currentId = false, $bookId = false)
+ {
+ $slug = $this->nameToSlug($name);
+ while ($this->slugExists($type, $slug, $currentId, $bookId)) {
+ $slug .= '-' . substr(md5(rand(1, 500)), 0, 3);
+ }
+ return $slug;
+ }
+
+ /**
+ * Check if a slug already exists in the database.
+ * @param string $type
+ * @param string $slug
+ * @param bool|integer $currentId
+ * @param bool|integer $bookId
+ * @return bool
+ */
+ protected function slugExists($type, $slug, $currentId = false, $bookId = false)
+ {
+ $query = $this->getEntity($type)->where('slug', '=', $slug);
+ if (strtolower($type) === 'page' || strtolower($type) === 'chapter') {
+ $query = $query->where('book_id', '=', $bookId);
+ }
+ if ($currentId) $query = $query->where('id', '!=', $currentId);
+ return $query->count() > 0;
+ }
+