+ $page = $this->newFromInput($input);
+ $page->slug = $this->findSuitableSlug($page->name, $book->id);
+
+ if ($chapterId) $page->chapter_id = $chapterId;
+
+ $page->html = $this->formatHtml($input['html']);
+ $page->text = strip_tags($page->html);
+ $page->created_by = auth()->user()->id;
+ $page->updated_by = auth()->user()->id;
+
+ $book->pages()->save($page);
+ return $page;
+ }
+
+
+ /**
+ * Publish a draft page to make it a normal page.
+ * Sets the slug and updates the content.
+ * @param Page $draftPage
+ * @param array $input
+ * @return Page
+ */
+ public function publishDraft(Page $draftPage, array $input)
+ {
+ $draftPage->fill($input);
+
+ // Save page tags if present
+ if(isset($input['tags'])) {
+ $this->tagRepo->saveTagsToEntity($draftPage, $input['tags']);
+ }
+
+ $draftPage->slug = $this->findSuitableSlug($draftPage->name, $draftPage->book->id);
+ $draftPage->html = $this->formatHtml($input['html']);
+ $draftPage->text = strip_tags($draftPage->html);
+ $draftPage->draft = false;
+
+ $draftPage->save();
+ return $draftPage;
+ }
+
+ /**
+ * Get a new draft page instance.
+ * @param Book $book
+ * @param Chapter|bool $chapter
+ * @return static
+ */
+ public function getDraftPage(Book $book, $chapter = false)
+ {
+ $page = $this->page->newInstance();
+ $page->name = 'New Page';
+ $page->created_by = auth()->user()->id;
+ $page->updated_by = auth()->user()->id;
+ $page->draft = true;
+
+ if ($chapter) $page->chapter_id = $chapter->id;
+
+ $book->pages()->save($page);
+ $this->permissionService->buildJointPermissionsForEntity($page);
+ return $page;
+ }
+
+ /**
+ * Formats a page's html to be tagged correctly
+ * within the system.
+ * @param string $htmlText
+ * @return string
+ */
+ protected function formatHtml($htmlText)
+ {
+ if ($htmlText == '') return $htmlText;
+ libxml_use_internal_errors(true);
+ $doc = new DOMDocument();
+ $doc->loadHTML(mb_convert_encoding($htmlText, 'HTML-ENTITIES', 'UTF-8'));
+
+ $container = $doc->documentElement;
+ $body = $container->childNodes->item(0);
+ $childNodes = $body->childNodes;
+
+ // Ensure no duplicate ids are used
+ $idArray = [];
+
+ foreach ($childNodes as $index => $childNode) {
+ /** @var \DOMElement $childNode */
+ if (get_class($childNode) !== 'DOMElement') continue;
+
+ // Overwrite id if not a BookStack custom id
+ if ($childNode->hasAttribute('id')) {
+ $id = $childNode->getAttribute('id');
+ if (strpos($id, 'bkmrk') === 0 && array_search($id, $idArray) === false) {
+ $idArray[] = $id;
+ continue;
+ };
+ }
+
+ // Create an unique id for the element
+ // Uses the content as a basis to ensure output is the same every time
+ // the same content is passed through.
+ $contentId = 'bkmrk-' . substr(strtolower(preg_replace('/\s+/', '-', trim($childNode->nodeValue))), 0, 20);
+ $newId = urlencode($contentId);
+ $loopIndex = 0;
+ while (in_array($newId, $idArray)) {
+ $newId = urlencode($contentId . '-' . $loopIndex);
+ $loopIndex++;
+ }
+
+ $childNode->setAttribute('id', $newId);
+ $idArray[] = $newId;
+ }
+
+ // Generate inner html as a string
+ $html = '';
+ foreach ($childNodes as $childNode) {
+ $html .= $doc->saveHTML($childNode);
+ }
+
+ return $html;