use BookStack\Chapter;
use BookStack\Entity;
use BookStack\Exceptions\NotFoundException;
+use BookStack\Exceptions\NotifyException;
use BookStack\Page;
use BookStack\PageRevision;
use BookStack\Services\AttachmentService;
* @param SearchService $searchService
*/
public function __construct(
- Book $book, Chapter $chapter, Page $page, PageRevision $pageRevision,
- ViewService $viewService, PermissionService $permissionService,
- TagRepo $tagRepo, SearchService $searchService
- )
- {
+ Book $book,
+ Chapter $chapter,
+ Page $page,
+ PageRevision $pageRevision,
+ ViewService $viewService,
+ PermissionService $permissionService,
+ TagRepo $tagRepo,
+ SearchService $searchService
+ ) {
$this->book = $book;
$this->chapter = $chapter;
$this->page = $page;
* @param bool $allowDrafts
* @return \Illuminate\Database\Query\Builder
*/
- protected function entityQuery($type, $allowDrafts = false)
+ protected function entityQuery($type, $allowDrafts = false, $permission = 'view')
{
- $q = $this->permissionService->enforceEntityRestrictions($type, $this->getEntity($type), 'view');
+ $q = $this->permissionService->enforceEntityRestrictions($type, $this->getEntity($type), $permission);
if (strtolower($type) === 'page' && !$allowDrafts) {
$q = $q->where('draft', '=', false);
}
* @param string $type
* @param integer $id
* @param bool $allowDrafts
+ * @param bool $ignorePermissions
* @return Entity
*/
- public function getById($type, $id, $allowDrafts = false)
+ public function getById($type, $id, $allowDrafts = false, $ignorePermissions = false)
{
+ if ($ignorePermissions) {
+ $entity = $this->getEntity($type);
+ return $entity->newQuery()->find($id);
+ }
return $this->entityQuery($type, $allowDrafts)->find($id);
}
$q = $this->entityQuery($type)->where('slug', '=', $slug);
if (strtolower($type) === 'chapter' || strtolower($type) === 'page') {
- $q = $q->where('book_id', '=', function($query) use ($bookSlug) {
+ $q = $q->where('book_id', '=', function ($query) use ($bookSlug) {
$query->select('id')
->from($this->book->getTable())
->where('slug', '=', $bookSlug)->limit(1);
});
}
$entity = $q->first();
- if ($entity === null) throw new NotFoundException(trans('errors.' . strtolower($type) . '_not_found'));
+ if ($entity === null) {
+ throw new NotFoundException(trans('errors.' . strtolower($type) . '_not_found'));
+ }
return $entity;
}
}
/**
- * Get all entities of a type limited by count unless count if false.
+ * Get all entities of a type with the given permission, limited by count unless count is false.
* @param string $type
* @param integer|bool $count
+ * @param string $permission
* @return Collection
*/
- public function getAll($type, $count = 20)
+ public function getAll($type, $count = 20, $permission = 'view')
{
- $q = $this->entityQuery($type)->orderBy('name', 'asc');
- if ($count !== false) $q = $q->take($count);
+ $q = $this->entityQuery($type, false, $permission)->orderBy('name', 'asc');
+ if ($count !== false) {
+ $q = $q->take($count);
+ }
return $q->get();
}
* @param int $count
* @param int $page
* @param bool|callable $additionalQuery
+ * @return Collection
*/
public function getRecentlyCreated($type, $count = 20, $page = 0, $additionalQuery = false)
{
$query = $this->permissionService->enforceEntityRestrictions($type, $this->getEntity($type))
->orderBy('created_at', 'desc');
- if (strtolower($type) === 'page') $query = $query->where('draft', '=', false);
+ if (strtolower($type) === 'page') {
+ $query = $query->where('draft', '=', false);
+ }
if ($additionalQuery !== false && is_callable($additionalQuery)) {
$additionalQuery($query);
}
* @param int $count
* @param int $page
* @param bool|callable $additionalQuery
+ * @return Collection
*/
public function getRecentlyUpdated($type, $count = 20, $page = 0, $additionalQuery = false)
{
$query = $this->permissionService->enforceEntityRestrictions($type, $this->getEntity($type))
->orderBy('updated_at', 'desc');
- if (strtolower($type) === 'page') $query = $query->where('draft', '=', false);
+ if (strtolower($type) === 'page') {
+ $query = $query->where('draft', '=', false);
+ }
if ($additionalQuery !== false && is_callable($additionalQuery)) {
$additionalQuery($query);
}
if ($rawEntity->entity_type === 'BookStack\\Page') {
$entities[$index] = $this->page->newFromBuilder($rawEntity);
if ($renderPages) {
- $entities[$index]->html = $rawEntity->description;
+ $entities[$index]->html = $rawEntity->html;
$entities[$index]->html = $this->renderPage($entities[$index]);
};
} else if ($rawEntity->entity_type === 'BookStack\\Chapter') {
$parents[$key] = $entities[$index];
$parents[$key]->setAttribute('pages', collect());
}
- if ($entities[$index]->chapter_id === 0 || $entities[$index]->chapter_id === '0') $tree[] = $entities[$index];
+ 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;
+ 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);
}
* 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)
{
if (strtolower($type) === 'page' || strtolower($type) === 'chapter') {
$query = $query->where('book_id', '=', $bookId);
}
- if ($currentId) $query = $query->where('id', '!=', $currentId);
+ if ($currentId) {
+ $query = $query->where('id', '!=', $currentId);
+ }
return $query->count() > 0;
}
*/
public function updateEntityPermissionsFromRequest($request, Entity $entity)
{
- $entity->restricted = $request->has('restricted') && $request->get('restricted') === 'true';
+ $entity->restricted = $request->get('restricted', '') === 'true';
$entity->permissions()->delete();
- if ($request->has('restrictions')) {
+
+ if ($request->filled('restrictions')) {
foreach ($request->get('restrictions') as $roleId => $restrictions) {
foreach ($restrictions as $action => $value) {
$entity->permissions()->create([
}
}
}
+
$entity->save();
$this->permissionService->buildJointPermissionsForEntity($entity);
}
/**
* Update entity details from request input.
- * Use for books and chapters
+ * Used for books and chapters
* @param string $type
* @param Entity $entityModel
* @param array $input
/**
* Alias method to update the book jointPermissions in the PermissionService.
- * @param Collection $collection collection on entities
+ * @param Book $book
*/
- public function buildJointPermissions(Collection $collection)
+ public function buildJointPermissionsForBook(Book $book)
{
- $this->permissionService->buildJointPermissionsForEntities($collection);
+ $this->permissionService->buildJointPermissionsForEntity($book);
}
/**
*/
protected function nameToSlug($name)
{
- $slug = str_replace(' ', '-', strtolower($name));
- $slug = preg_replace('/[\+\/\\\?\@\}\{\.\,\=\[\]\#\&\!\*\'\;\:\$\%]/', '', $slug);
- if ($slug === "") $slug = substr(md5(rand(1, 500)), 0, 5);
+ $slug = preg_replace('/[\+\/\\\?\@\}\{\.\,\=\[\]\#\&\!\*\'\;\:\$\%]/', '', mb_strtolower($name));
+ $slug = preg_replace('/\s{2,}/', ' ', $slug);
+ $slug = str_replace(' ', '-', $slug);
+ if ($slug === "") {
+ $slug = substr(md5(rand(1, 500)), 0, 5);
+ }
return $slug;
}
$draftPage->slug = $this->findSuitableSlug('page', $draftPage->name, false, $draftPage->book->id);
$draftPage->html = $this->formatHtml($input['html']);
- $draftPage->text = strip_tags($draftPage->html);
+ $draftPage->text = $this->pageToPlainText($draftPage);
$draftPage->draft = false;
+ $draftPage->revision_count = 1;
$draftPage->save();
$this->savePageRevision($draftPage, trans('entities.pages_initial_revision'));
-
+ $this->searchService->indexEntity($draftPage);
return $draftPage;
}
public function savePageRevision(Page $page, $summary = null)
{
$revision = $this->pageRevision->newInstance($page->toArray());
- if (setting('app-editor') !== 'markdown') $revision->markdown = '';
+ if (setting('app-editor') !== 'markdown') {
+ $revision->markdown = '';
+ }
$revision->page_id = $page->id;
$revision->slug = $page->slug;
$revision->book_slug = $page->book->slug;
$revision->created_at = $page->updated_at;
$revision->type = 'version';
$revision->summary = $summary;
+ $revision->revision_number = $page->revision_count;
$revision->save();
// Clear old revisions
*/
protected function formatHtml($htmlText)
{
- if ($htmlText == '') return $htmlText;
+ if ($htmlText == '') {
+ return $htmlText;
+ }
libxml_use_internal_errors(true);
$doc = new DOMDocument();
$doc->loadHTML(mb_convert_encoding($htmlText, 'HTML-ENTITIES', 'UTF-8'));
foreach ($childNodes as $index => $childNode) {
/** @var \DOMElement $childNode */
- if (get_class($childNode) !== 'DOMElement') continue;
+ if (get_class($childNode) !== 'DOMElement') {
+ continue;
+ }
// Overwrite id if not a BookStack custom id
if ($childNode->hasAttribute('id')) {
/**
* Render the page for viewing, Parsing and performing features such as page transclusion.
* @param Page $page
+ * @param bool $ignorePermissions
* @return mixed|string
*/
- public function renderPage(Page $page)
+ public function renderPage(Page $page, $ignorePermissions = false)
{
$content = $page->html;
$matches = [];
preg_match_all("/{{@\s?([0-9].*?)}}/", $content, $matches);
- if (count($matches[0]) === 0) return $content;
+ if (count($matches[0]) === 0) {
+ return $content;
+ }
+ $topLevelTags = ['table', 'ul', 'ol'];
foreach ($matches[1] as $index => $includeId) {
$splitInclude = explode('#', $includeId, 2);
$pageId = intval($splitInclude[0]);
- if (is_nan($pageId)) continue;
+ if (is_nan($pageId)) {
+ continue;
+ }
- $page = $this->getById('page', $pageId);
- if ($page === null) {
+ $matchedPage = $this->getById('page', $pageId, false, $ignorePermissions);
+ if ($matchedPage === null) {
$content = str_replace($matches[0][$index], '', $content);
continue;
}
if (count($splitInclude) === 1) {
- $content = str_replace($matches[0][$index], $page->html, $content);
+ $content = str_replace($matches[0][$index], $matchedPage->html, $content);
continue;
}
$doc = new DOMDocument();
- $doc->loadHTML(mb_convert_encoding('<body>'.$page->html.'</body>', 'HTML-ENTITIES', 'UTF-8'));
+ $doc->loadHTML(mb_convert_encoding('<body>'.$matchedPage->html.'</body>', 'HTML-ENTITIES', 'UTF-8'));
$matchingElem = $doc->getElementById($splitInclude[1]);
if ($matchingElem === null) {
$content = str_replace($matches[0][$index], '', $content);
continue;
}
$innerContent = '';
- foreach ($matchingElem->childNodes as $childNode) {
- $innerContent .= $doc->saveHTML($childNode);
+ $isTopLevel = in_array(strtolower($matchingElem->nodeName), $topLevelTags);
+ if ($isTopLevel) {
+ $innerContent .= $doc->saveHTML($matchingElem);
+ } else {
+ foreach ($matchingElem->childNodes as $childNode) {
+ $innerContent .= $doc->saveHTML($childNode);
+ }
}
$content = str_replace($matches[0][$index], trim($innerContent), $content);
}
return $content;
}
+ /**
+ * Get the plain text version of a page's content.
+ * @param Page $page
+ * @return string
+ */
+ public function pageToPlainText(Page $page)
+ {
+ $html = $this->renderPage($page);
+ return strip_tags($html);
+ }
+
/**
* Get a new draft page instance.
* @param Book $book
$page->updated_by = user()->id;
$page->draft = true;
- if ($chapter) $page->chapter_id = $chapter->id;
+ if ($chapter) {
+ $page->chapter_id = $chapter->id;
+ }
$book->pages()->save($page);
+ $page = $this->page->find($page->id);
$this->permissionService->buildJointPermissionsForEntity($page);
return $page;
}
*/
public function getPageNav($pageContent)
{
- if ($pageContent == '') return [];
+ if ($pageContent == '') {
+ return [];
+ }
libxml_use_internal_errors(true);
$doc = new DOMDocument();
$doc->loadHTML(mb_convert_encoding($pageContent, 'HTML-ENTITIES', 'UTF-8'));
$xPath = new DOMXPath($doc);
$headers = $xPath->query("//h1|//h2|//h3|//h4|//h5|//h6");
- if (is_null($headers)) return [];
+ if (is_null($headers)) {
+ return [];
+ }
$tree = collect([]);
foreach ($headers as $header) {
// Normalise headers if only smaller headers have been used
if (count($tree) > 0) {
$minLevel = $tree->pluck('level')->min();
- $tree = $tree->map(function($header) use ($minLevel) {
+ $tree = $tree->map(function ($header) use ($minLevel) {
$header['level'] -= ($minLevel - 2);
return $header;
});
$userId = user()->id;
$page->fill($input);
$page->html = $this->formatHtml($input['html']);
- $page->text = strip_tags($page->html);
- if (setting('app-editor') !== 'markdown') $page->markdown = '';
+ $page->text = $this->pageToPlainText($page);
+ if (setting('app-editor') !== 'markdown') {
+ $page->markdown = '';
+ }
$page->updated_by = $userId;
+ $page->revision_count++;
$page->save();
// Remove all update drafts for this user & page.
public function getUserPageDraftMessage(PageRevision $draft)
{
$message = trans('entities.pages_editing_draft_notification', ['timeDiff' => $draft->updated_at->diffForHumans()]);
- if ($draft->page->updated_at->timestamp <= $draft->updated_at->timestamp) return $message;
+ if ($draft->page->updated_at->timestamp <= $draft->updated_at->timestamp) {
+ return $message;
+ }
return $message . "\n" . trans('entities.pages_draft_edited_notification');
}
*/
public function restorePageRevision(Page $page, Book $book, $revisionId)
{
+ $page->revision_count++;
$this->savePageRevision($page);
- $revision = $this->getById('page_revision', $revisionId);
+ $revision = $page->revisions()->where('id', '=', $revisionId)->first();
$page->fill($revision->toArray());
$page->slug = $this->findSuitableSlug('page', $page->name, $page->id, $book->id);
- $page->text = strip_tags($page->html);
+ $page->text = $this->pageToPlainText($page);
$page->updated_by = user()->id;
$page->save();
$this->searchService->indexEntity($page);
if ($page->draft) {
$page->fill($data);
if (isset($data['html'])) {
- $page->text = strip_tags($data['html']);
+ $page->text = $this->pageToPlainText($page);
}
$page->save();
return $page;
}
$draft->fill($data);
- if (setting('app-editor') !== 'markdown') $draft->markdown = '';
+ if (setting('app-editor') !== 'markdown') {
+ $draft->markdown = '';
+ }
$draft->save();
return $draft;
/**
* Destroy a given page along with its dependencies.
* @param Page $page
+ * @throws NotifyException
*/
public function destroyPage(Page $page)
{
$this->permissionService->deleteJointPermissionsForEntity($page);
$this->searchService->deleteEntityTerms($page);
+ // Check if set as custom homepage
+ $customHome = setting('app-homepage', '0:');
+ if (intval($page->id) === intval(explode(':', $customHome)[0])) {
+ throw new NotifyException(trans('errors.page_custom_home_deletion'), $page->getUrl());
+ }
+
// Delete Attached Files
$attachmentService = app(AttachmentService::class);
foreach ($page->attachments as $attachment) {
$page->delete();
}
-
}
-
-
-
-
-
-
-
-
-
-
-
-