diff --git a/app/Chapter.php b/app/Chapter.php
index cc5518b7a..dc23f5ebd 100644
--- a/app/Chapter.php
+++ b/app/Chapter.php
@@ -5,6 +5,8 @@ class Chapter extends Entity
{
protected $fillable = ['name', 'description', 'priority', 'book_id'];
+ protected $with = ['book'];
+
/**
* Get the book this chapter is within.
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
@@ -16,11 +18,12 @@ class Chapter extends Entity
/**
* Get the pages that this chapter contains.
+ * @param string $dir
* @return mixed
*/
- public function pages()
+ public function pages($dir = 'ASC')
{
- return $this->hasMany(Page::class)->orderBy('priority', 'ASC');
+ return $this->hasMany(Page::class)->orderBy('priority', $dir);
}
/**
diff --git a/app/Entity.php b/app/Entity.php
index 186059f00..e8deddf0a 100644
--- a/app/Entity.php
+++ b/app/Entity.php
@@ -4,6 +4,8 @@
class Entity extends Ownable
{
+ protected $fieldsToSearch = ['name', 'description'];
+
/**
* Compares this entity to another given entity.
* Matches by comparing class and id.
@@ -157,7 +159,7 @@ class Entity extends Ownable
* @param string[] array $wheres
* @return mixed
*/
- public function fullTextSearchQuery($fieldsToSearch, $terms, $wheres = [])
+ public function fullTextSearchQuery($terms, $wheres = [])
{
$exactTerms = [];
$fuzzyTerms = [];
@@ -181,16 +183,16 @@ class Entity extends Ownable
// Perform fulltext search if relevant terms exist.
if ($isFuzzy) {
$termString = implode(' ', $fuzzyTerms);
- $fields = implode(',', $fieldsToSearch);
+ $fields = implode(',', $this->fieldsToSearch);
$search = $search->selectRaw('*, MATCH(name) AGAINST(? IN BOOLEAN MODE) AS title_relevance', [$termString]);
$search = $search->whereRaw('MATCH(' . $fields . ') AGAINST(? IN BOOLEAN MODE)', [$termString]);
}
// Ensure at least one exact term matches if in search
if (count($exactTerms) > 0) {
- $search = $search->where(function ($query) use ($exactTerms, $fieldsToSearch) {
+ $search = $search->where(function ($query) use ($exactTerms) {
foreach ($exactTerms as $exactTerm) {
- foreach ($fieldsToSearch as $field) {
+ foreach ($this->fieldsToSearch as $field) {
$query->orWhere($field, 'like', $exactTerm);
}
}
diff --git a/app/Http/Controllers/AttachmentController.php b/app/Http/Controllers/AttachmentController.php
index b5e7db41e..3c325d0fe 100644
--- a/app/Http/Controllers/AttachmentController.php
+++ b/app/Http/Controllers/AttachmentController.php
@@ -2,7 +2,7 @@
use BookStack\Exceptions\FileUploadException;
use BookStack\Attachment;
-use BookStack\Repos\PageRepo;
+use BookStack\Repos\EntityRepo;
use BookStack\Services\AttachmentService;
use Illuminate\Http\Request;
@@ -10,19 +10,19 @@ class AttachmentController extends Controller
{
protected $attachmentService;
protected $attachment;
- protected $pageRepo;
+ protected $entityRepo;
/**
* AttachmentController constructor.
* @param AttachmentService $attachmentService
* @param Attachment $attachment
- * @param PageRepo $pageRepo
+ * @param EntityRepo $entityRepo
*/
- public function __construct(AttachmentService $attachmentService, Attachment $attachment, PageRepo $pageRepo)
+ public function __construct(AttachmentService $attachmentService, Attachment $attachment, EntityRepo $entityRepo)
{
$this->attachmentService = $attachmentService;
$this->attachment = $attachment;
- $this->pageRepo = $pageRepo;
+ $this->entityRepo = $entityRepo;
parent::__construct();
}
@@ -40,7 +40,7 @@ class AttachmentController extends Controller
]);
$pageId = $request->get('uploaded_to');
- $page = $this->pageRepo->getById($pageId, true);
+ $page = $this->entityRepo->getById('page', $pageId, true);
$this->checkPermission('attachment-create-all');
$this->checkOwnablePermission('page-update', $page);
@@ -70,7 +70,7 @@ class AttachmentController extends Controller
]);
$pageId = $request->get('uploaded_to');
- $page = $this->pageRepo->getById($pageId, true);
+ $page = $this->entityRepo->getById('page', $pageId, true);
$attachment = $this->attachment->findOrFail($attachmentId);
$this->checkOwnablePermission('page-update', $page);
@@ -106,7 +106,7 @@ class AttachmentController extends Controller
]);
$pageId = $request->get('uploaded_to');
- $page = $this->pageRepo->getById($pageId, true);
+ $page = $this->entityRepo->getById('page', $pageId, true);
$attachment = $this->attachment->findOrFail($attachmentId);
$this->checkOwnablePermission('page-update', $page);
@@ -134,7 +134,7 @@ class AttachmentController extends Controller
]);
$pageId = $request->get('uploaded_to');
- $page = $this->pageRepo->getById($pageId, true);
+ $page = $this->entityRepo->getById('page', $pageId, true);
$this->checkPermission('attachment-create-all');
$this->checkOwnablePermission('page-update', $page);
@@ -153,7 +153,7 @@ class AttachmentController extends Controller
*/
public function listForPage($pageId)
{
- $page = $this->pageRepo->getById($pageId, true);
+ $page = $this->entityRepo->getById('page', $pageId, true);
$this->checkOwnablePermission('page-view', $page);
return response()->json($page->attachments);
}
@@ -170,7 +170,7 @@ class AttachmentController extends Controller
'files' => 'required|array',
'files.*.id' => 'required|integer',
]);
- $page = $this->pageRepo->getById($pageId);
+ $page = $this->entityRepo->getById('page', $pageId);
$this->checkOwnablePermission('page-update', $page);
$attachments = $request->get('files');
@@ -186,7 +186,7 @@ class AttachmentController extends Controller
public function get($attachmentId)
{
$attachment = $this->attachment->findOrFail($attachmentId);
- $page = $this->pageRepo->getById($attachment->uploaded_to);
+ $page = $this->entityRepo->getById('page', $attachment->uploaded_to);
$this->checkOwnablePermission('page-view', $page);
if ($attachment->external) {
diff --git a/app/Http/Controllers/BookController.php b/app/Http/Controllers/BookController.php
index 80a6c24b3..57ac486d5 100644
--- a/app/Http/Controllers/BookController.php
+++ b/app/Http/Controllers/BookController.php
@@ -1,35 +1,26 @@
bookRepo = $bookRepo;
- $this->pageRepo = $pageRepo;
- $this->chapterRepo = $chapterRepo;
+ $this->entityRepo = $entityRepo;
$this->userRepo = $userRepo;
parent::__construct();
}
@@ -40,9 +31,9 @@ class BookController extends Controller
*/
public function index()
{
- $books = $this->bookRepo->getAllPaginated(10);
- $recents = $this->signedIn ? $this->bookRepo->getRecentlyViewed(4, 0) : false;
- $popular = $this->bookRepo->getPopular(4, 0);
+ $books = $this->entityRepo->getAllPaginated('book', 10);
+ $recents = $this->signedIn ? $this->entityRepo->getRecentlyViewed('book', 4, 0) : false;
+ $popular = $this->entityRepo->getPopular('book', 4, 0);
$this->setPageTitle('Books');
return view('books/index', ['books' => $books, 'recents' => $recents, 'popular' => $popular]);
}
@@ -71,7 +62,7 @@ class BookController extends Controller
'name' => 'required|string|max:255',
'description' => 'string|max:1000'
]);
- $book = $this->bookRepo->createFromInput($request->all());
+ $book = $this->entityRepo->createFromInput('book', $request->all());
Activity::add($book, 'book_create', $book->id);
return redirect($book->getUrl());
}
@@ -83,9 +74,9 @@ class BookController extends Controller
*/
public function show($slug)
{
- $book = $this->bookRepo->getBySlug($slug);
+ $book = $this->entityRepo->getBySlug('book', $slug);
$this->checkOwnablePermission('book-view', $book);
- $bookChildren = $this->bookRepo->getChildren($book);
+ $bookChildren = $this->entityRepo->getBookChildren($book);
Views::add($book);
$this->setPageTitle($book->getShortName());
return view('books/show', ['book' => $book, 'current' => $book, 'bookChildren' => $bookChildren]);
@@ -98,7 +89,7 @@ class BookController extends Controller
*/
public function edit($slug)
{
- $book = $this->bookRepo->getBySlug($slug);
+ $book = $this->entityRepo->getBySlug('book', $slug);
$this->checkOwnablePermission('book-update', $book);
$this->setPageTitle(trans('entities.books_edit_named',['bookName'=>$book->getShortName()]));
return view('books/edit', ['book' => $book, 'current' => $book]);
@@ -112,13 +103,13 @@ class BookController extends Controller
*/
public function update(Request $request, $slug)
{
- $book = $this->bookRepo->getBySlug($slug);
+ $book = $this->entityRepo->getBySlug('book', $slug);
$this->checkOwnablePermission('book-update', $book);
$this->validate($request, [
'name' => 'required|string|max:255',
'description' => 'string|max:1000'
]);
- $book = $this->bookRepo->updateFromInput($book, $request->all());
+ $book = $this->entityRepo->updateFromInput('book', $book, $request->all());
Activity::add($book, 'book_update', $book->id);
return redirect($book->getUrl());
}
@@ -130,7 +121,7 @@ class BookController extends Controller
*/
public function showDelete($bookSlug)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
+ $book = $this->entityRepo->getBySlug('book', $bookSlug);
$this->checkOwnablePermission('book-delete', $book);
$this->setPageTitle(trans('entities.books_delete_named', ['bookName'=>$book->getShortName()]));
return view('books/delete', ['book' => $book, 'current' => $book]);
@@ -143,10 +134,10 @@ class BookController extends Controller
*/
public function sort($bookSlug)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
+ $book = $this->entityRepo->getBySlug('book', $bookSlug);
$this->checkOwnablePermission('book-update', $book);
- $bookChildren = $this->bookRepo->getChildren($book, true);
- $books = $this->bookRepo->getAll(false);
+ $bookChildren = $this->entityRepo->getBookChildren($book, true);
+ $books = $this->entityRepo->getAll('book', false);
$this->setPageTitle(trans('entities.books_sort_named', ['bookName'=>$book->getShortName()]));
return view('books/sort', ['book' => $book, 'current' => $book, 'books' => $books, 'bookChildren' => $bookChildren]);
}
@@ -159,8 +150,8 @@ class BookController extends Controller
*/
public function getSortItem($bookSlug)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
- $bookChildren = $this->bookRepo->getChildren($book);
+ $book = $this->entityRepo->getBySlug('book', $bookSlug);
+ $bookChildren = $this->entityRepo->getBookChildren($book);
return view('books/sort-box', ['book' => $book, 'bookChildren' => $bookChildren]);
}
@@ -172,7 +163,7 @@ class BookController extends Controller
*/
public function saveSort($bookSlug, Request $request)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
+ $book = $this->entityRepo->getBySlug('book', $bookSlug);
$this->checkOwnablePermission('book-update', $book);
// Return if no map sent
@@ -191,13 +182,13 @@ class BookController extends Controller
$priority = $bookChild->sort;
$id = intval($bookChild->id);
$isPage = $bookChild->type == 'page';
- $bookId = $this->bookRepo->exists($bookChild->book) ? intval($bookChild->book) : $defaultBookId;
+ $bookId = $this->entityRepo->exists('book', $bookChild->book) ? intval($bookChild->book) : $defaultBookId;
$chapterId = ($isPage && $bookChild->parentChapter === false) ? 0 : intval($bookChild->parentChapter);
- $model = $isPage ? $this->pageRepo->getById($id) : $this->chapterRepo->getById($id);
+ $model = $this->entityRepo->getById($isPage?'page':'chapter', $id);
// Update models only if there's a change in parent chain or ordering.
if ($model->priority !== $priority || $model->book_id !== $bookId || ($isPage && $model->chapter_id !== $chapterId)) {
- $isPage ? $this->pageRepo->changeBook($bookId, $model) : $this->chapterRepo->changeBook($bookId, $model);
+ $this->entityRepo->changeBook($isPage?'page':'chapter', $bookId, $model);
$model->priority = $priority;
if ($isPage) $model->chapter_id = $chapterId;
$model->save();
@@ -212,12 +203,12 @@ class BookController extends Controller
// Add activity for books
foreach ($sortedBooks as $bookId) {
- $updatedBook = $this->bookRepo->getById($bookId);
+ $updatedBook = $this->entityRepo->getById('book', $bookId);
Activity::add($updatedBook, 'book_sort', $updatedBook->id);
}
// Update permissions on changed models
- $this->bookRepo->buildJointPermissions($updatedModels);
+ $this->entityRepo->buildJointPermissions($updatedModels);
return redirect($book->getUrl());
}
@@ -229,11 +220,10 @@ class BookController extends Controller
*/
public function destroy($bookSlug)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
+ $book = $this->entityRepo->getBySlug('book', $bookSlug);
$this->checkOwnablePermission('book-delete', $book);
Activity::addMessage('book_delete', 0, $book->name);
- Activity::removeEntity($book);
- $this->bookRepo->destroy($book);
+ $this->entityRepo->destroyBook($book);
return redirect('/books');
}
@@ -244,7 +234,7 @@ class BookController extends Controller
*/
public function showRestrict($bookSlug)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
+ $book = $this->entityRepo->getBySlug('book', $bookSlug);
$this->checkOwnablePermission('restrictions-manage', $book);
$roles = $this->userRepo->getRestrictableRoles();
return view('books/restrictions', [
@@ -262,9 +252,9 @@ class BookController extends Controller
*/
public function restrict($bookSlug, Request $request)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
+ $book = $this->entityRepo->getBySlug('book', $bookSlug);
$this->checkOwnablePermission('restrictions-manage', $book);
- $this->bookRepo->updateEntityPermissionsFromRequest($request, $book);
+ $this->entityRepo->updateEntityPermissionsFromRequest($request, $book);
session()->flash('success', trans('entities.books_permissions_updated'));
return redirect($book->getUrl());
}
diff --git a/app/Http/Controllers/ChapterController.php b/app/Http/Controllers/ChapterController.php
index 849835185..1760ee5c6 100644
--- a/app/Http/Controllers/ChapterController.php
+++ b/app/Http/Controllers/ChapterController.php
@@ -1,30 +1,26 @@
bookRepo = $bookRepo;
- $this->chapterRepo = $chapterRepo;
+ $this->entityRepo = $entityRepo;
$this->userRepo = $userRepo;
parent::__construct();
}
@@ -36,7 +32,7 @@ class ChapterController extends Controller
*/
public function create($bookSlug)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
+ $book = $this->entityRepo->getBySlug('book', $bookSlug);
$this->checkOwnablePermission('chapter-create', $book);
$this->setPageTitle(trans('entities.chapters_create'));
return view('chapters/create', ['book' => $book, 'current' => $book]);
@@ -54,12 +50,12 @@ class ChapterController extends Controller
'name' => 'required|string|max:255'
]);
- $book = $this->bookRepo->getBySlug($bookSlug);
+ $book = $this->entityRepo->getBySlug('book', $bookSlug);
$this->checkOwnablePermission('chapter-create', $book);
$input = $request->all();
- $input['priority'] = $this->bookRepo->getNewPriority($book);
- $chapter = $this->chapterRepo->createFromInput($input, $book);
+ $input['priority'] = $this->entityRepo->getNewBookPriority($book);
+ $chapter = $this->entityRepo->createFromInput('chapter', $input, $book);
Activity::add($chapter, 'chapter_create', $book->id);
return redirect($chapter->getUrl());
}
@@ -72,15 +68,14 @@ class ChapterController extends Controller
*/
public function show($bookSlug, $chapterSlug)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
- $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
+ $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
$this->checkOwnablePermission('chapter-view', $chapter);
- $sidebarTree = $this->bookRepo->getChildren($book);
+ $sidebarTree = $this->entityRepo->getBookChildren($chapter->book);
Views::add($chapter);
$this->setPageTitle($chapter->getShortName());
- $pages = $this->chapterRepo->getChildren($chapter);
+ $pages = $this->entityRepo->getChapterChildren($chapter);
return view('chapters/show', [
- 'book' => $book,
+ 'book' => $chapter->book,
'chapter' => $chapter,
'current' => $chapter,
'sidebarTree' => $sidebarTree,
@@ -96,11 +91,10 @@ class ChapterController extends Controller
*/
public function edit($bookSlug, $chapterSlug)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
- $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
+ $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
$this->checkOwnablePermission('chapter-update', $chapter);
$this->setPageTitle(trans('entities.chapters_edit_named', ['chapterName' => $chapter->getShortName()]));
- return view('chapters/edit', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter]);
+ return view('chapters/edit', ['book' => $chapter->book, 'chapter' => $chapter, 'current' => $chapter]);
}
/**
@@ -112,16 +106,15 @@ class ChapterController extends Controller
*/
public function update(Request $request, $bookSlug, $chapterSlug)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
- $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
+ $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
$this->checkOwnablePermission('chapter-update', $chapter);
if ($chapter->name !== $request->get('name')) {
- $chapter->slug = $this->chapterRepo->findSuitableSlug($request->get('name'), $book->id, $chapter->id);
+ $chapter->slug = $this->entityRepo->findSuitableSlug('chapter', $request->get('name'), $chapter->id, $chapter->book->id);
}
$chapter->fill($request->all());
$chapter->updated_by = user()->id;
$chapter->save();
- Activity::add($chapter, 'chapter_update', $book->id);
+ Activity::add($chapter, 'chapter_update', $chapter->book->id);
return redirect($chapter->getUrl());
}
@@ -133,11 +126,10 @@ class ChapterController extends Controller
*/
public function showDelete($bookSlug, $chapterSlug)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
- $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
+ $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
$this->checkOwnablePermission('chapter-delete', $chapter);
$this->setPageTitle(trans('entities.chapters_delete_named', ['chapterName' => $chapter->getShortName()]));
- return view('chapters/delete', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter]);
+ return view('chapters/delete', ['book' => $chapter->book, 'chapter' => $chapter, 'current' => $chapter]);
}
/**
@@ -148,11 +140,11 @@ class ChapterController extends Controller
*/
public function destroy($bookSlug, $chapterSlug)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
- $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
+ $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
+ $book = $chapter->book;
$this->checkOwnablePermission('chapter-delete', $chapter);
Activity::addMessage('chapter_delete', $book->id, $chapter->name);
- $this->chapterRepo->destroy($chapter);
+ $this->entityRepo->destroyChapter($chapter);
return redirect($book->getUrl());
}
@@ -164,13 +156,12 @@ class ChapterController extends Controller
* @throws \BookStack\Exceptions\NotFoundException
*/
public function showMove($bookSlug, $chapterSlug) {
- $book = $this->bookRepo->getBySlug($bookSlug);
- $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
+ $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
$this->setPageTitle(trans('entities.chapters_move_named', ['chapterName' => $chapter->getShortName()]));
$this->checkOwnablePermission('chapter-update', $chapter);
return view('chapters/move', [
'chapter' => $chapter,
- 'book' => $book
+ 'book' => $chapter->book
]);
}
@@ -183,8 +174,7 @@ class ChapterController extends Controller
* @throws \BookStack\Exceptions\NotFoundException
*/
public function move($bookSlug, $chapterSlug, Request $request) {
- $book = $this->bookRepo->getBySlug($bookSlug);
- $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
+ $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
$this->checkOwnablePermission('chapter-update', $chapter);
$entitySelection = $request->get('entity_selection', null);
@@ -199,7 +189,7 @@ class ChapterController extends Controller
$parent = false;
if ($entityType == 'book') {
- $parent = $this->bookRepo->getById($entityId);
+ $parent = $this->entityRepo->getById('book', $entityId);
}
if ($parent === false || $parent === null) {
@@ -207,7 +197,7 @@ class ChapterController extends Controller
return redirect()->back();
}
- $this->chapterRepo->changeBook($parent->id, $chapter, true);
+ $this->entityRepo->changeBook('chapter', $parent->id, $chapter, true);
Activity::add($chapter, 'chapter_move', $chapter->book->id);
session()->flash('success', trans('entities.chapter_move_success', ['bookName' => $parent->name]));
@@ -222,8 +212,7 @@ class ChapterController extends Controller
*/
public function showRestrict($bookSlug, $chapterSlug)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
- $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
+ $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
$this->checkOwnablePermission('restrictions-manage', $chapter);
$roles = $this->userRepo->getRestrictableRoles();
return view('chapters/restrictions', [
@@ -241,10 +230,9 @@ class ChapterController extends Controller
*/
public function restrict($bookSlug, $chapterSlug, Request $request)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
- $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
+ $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
$this->checkOwnablePermission('restrictions-manage', $chapter);
- $this->chapterRepo->updateEntityPermissionsFromRequest($request, $chapter);
+ $this->entityRepo->updateEntityPermissionsFromRequest($request, $chapter);
session()->flash('success', trans('entities.chapters_permissions_success'));
return redirect($chapter->getUrl());
}
diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php
index e325b9322..f4706a5c4 100644
--- a/app/Http/Controllers/HomeController.php
+++ b/app/Http/Controllers/HomeController.php
@@ -5,6 +5,7 @@ namespace BookStack\Http\Controllers;
use Activity;
use BookStack\Repos\EntityRepo;
use BookStack\Http\Requests;
+use Illuminate\Http\Response;
use Views;
class HomeController extends Controller
@@ -31,9 +32,9 @@ class HomeController extends Controller
$activity = Activity::latest(10);
$draftPages = $this->signedIn ? $this->entityRepo->getUserDraftPages(6) : [];
$recentFactor = count($draftPages) > 0 ? 0.5 : 1;
- $recents = $this->signedIn ? Views::getUserRecentlyViewed(12*$recentFactor, 0) : $this->entityRepo->getRecentlyCreatedBooks(10*$recentFactor);
- $recentlyCreatedPages = $this->entityRepo->getRecentlyCreatedPages(5);
- $recentlyUpdatedPages = $this->entityRepo->getRecentlyUpdatedPages(5);
+ $recents = $this->signedIn ? Views::getUserRecentlyViewed(12*$recentFactor, 0) : $this->entityRepo->getRecentlyCreated('book', 10*$recentFactor);
+ $recentlyCreatedPages = $this->entityRepo->getRecentlyCreated('page', 5);
+ $recentlyUpdatedPages = $this->entityRepo->getRecentlyUpdated('page', 5);
return view('home', [
'activity' => $activity,
'recents' => $recents,
diff --git a/app/Http/Controllers/ImageController.php b/app/Http/Controllers/ImageController.php
index f073bea0a..77c320e07 100644
--- a/app/Http/Controllers/ImageController.php
+++ b/app/Http/Controllers/ImageController.php
@@ -1,6 +1,7 @@
imageRepo->getById($id);
$this->checkOwnablePermission('image-delete', $image);
@@ -163,7 +164,7 @@ class ImageController extends Controller
// Check if this image is used on any pages
$isForced = ($request->has('force') && ($request->get('force') === 'true') || $request->get('force') === true);
if (!$isForced) {
- $pageSearch = $pageRepo->searchForImage($image->url);
+ $pageSearch = $entityRepo->searchForImage($image->url);
if ($pageSearch !== false) {
return response()->json($pageSearch, 400);
}
diff --git a/app/Http/Controllers/PageController.php b/app/Http/Controllers/PageController.php
index e40d7668a..6ed9fc30c 100644
--- a/app/Http/Controllers/PageController.php
+++ b/app/Http/Controllers/PageController.php
@@ -2,41 +2,31 @@
use Activity;
use BookStack\Exceptions\NotFoundException;
+use BookStack\Repos\EntityRepo;
use BookStack\Repos\UserRepo;
use BookStack\Services\ExportService;
use Carbon\Carbon;
use Illuminate\Http\Request;
-use BookStack\Http\Requests;
-use BookStack\Repos\BookRepo;
-use BookStack\Repos\ChapterRepo;
-use BookStack\Repos\PageRepo;
use Illuminate\Http\Response;
-use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Views;
use GatherContent\Htmldiff\Htmldiff;
class PageController extends Controller
{
- protected $pageRepo;
- protected $bookRepo;
- protected $chapterRepo;
+ protected $entityRepo;
protected $exportService;
protected $userRepo;
/**
* PageController constructor.
- * @param PageRepo $pageRepo
- * @param BookRepo $bookRepo
- * @param ChapterRepo $chapterRepo
+ * @param EntityRepo $entityRepo
* @param ExportService $exportService
* @param UserRepo $userRepo
*/
- public function __construct(PageRepo $pageRepo, BookRepo $bookRepo, ChapterRepo $chapterRepo, ExportService $exportService, UserRepo $userRepo)
+ public function __construct(EntityRepo $entityRepo, ExportService $exportService, UserRepo $userRepo)
{
- $this->pageRepo = $pageRepo;
- $this->bookRepo = $bookRepo;
- $this->chapterRepo = $chapterRepo;
+ $this->entityRepo = $entityRepo;
$this->exportService = $exportService;
$this->userRepo = $userRepo;
parent::__construct();
@@ -51,14 +41,14 @@ class PageController extends Controller
*/
public function create($bookSlug, $chapterSlug = null)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
- $chapter = $chapterSlug ? $this->chapterRepo->getBySlug($chapterSlug, $book->id) : null;
+ $book = $this->entityRepo->getBySlug('book', $bookSlug);
+ $chapter = $chapterSlug ? $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug) : null;
$parent = $chapter ? $chapter : $book;
$this->checkOwnablePermission('page-create', $parent);
// Redirect to draft edit screen if signed in
if ($this->signedIn) {
- $draft = $this->pageRepo->getDraftPage($book, $chapter);
+ $draft = $this->entityRepo->getDraftPage($book, $chapter);
return redirect($draft->getUrl());
}
@@ -81,13 +71,13 @@ class PageController extends Controller
'name' => 'required|string|max:255'
]);
- $book = $this->bookRepo->getBySlug($bookSlug);
- $chapter = $chapterSlug ? $this->chapterRepo->getBySlug($chapterSlug, $book->id) : null;
+ $book = $this->entityRepo->getBySlug('book', $bookSlug);
+ $chapter = $chapterSlug ? $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug) : null;
$parent = $chapter ? $chapter : $book;
$this->checkOwnablePermission('page-create', $parent);
- $page = $this->pageRepo->getDraftPage($book, $chapter);
- $this->pageRepo->publishDraft($page, [
+ $page = $this->entityRepo->getDraftPage($book, $chapter);
+ $this->entityRepo->publishPageDraft($page, [
'name' => $request->get('name'),
'html' => ''
]);
@@ -102,15 +92,14 @@ class PageController extends Controller
*/
public function editDraft($bookSlug, $pageId)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
- $draft = $this->pageRepo->getById($pageId, true);
- $this->checkOwnablePermission('page-create', $book);
+ $draft = $this->entityRepo->getById('page', $pageId, true);
+ $this->checkOwnablePermission('page-create', $draft->book);
$this->setPageTitle(trans('entities.pages_edit_draft'));
$draftsEnabled = $this->signedIn;
return view('pages/edit', [
'page' => $draft,
- 'book' => $book,
+ 'book' => $draft->book,
'isDraft' => true,
'draftsEnabled' => $draftsEnabled
]);
@@ -130,21 +119,21 @@ class PageController extends Controller
]);
$input = $request->all();
- $book = $this->bookRepo->getBySlug($bookSlug);
+ $book = $this->entityRepo->getBySlug('book', $bookSlug);
- $draftPage = $this->pageRepo->getById($pageId, true);
+ $draftPage = $this->entityRepo->getById('page', $pageId, true);
$chapterId = intval($draftPage->chapter_id);
- $parent = $chapterId !== 0 ? $this->chapterRepo->getById($chapterId) : $book;
+ $parent = $chapterId !== 0 ? $this->entityRepo->getById('chapter', $chapterId) : $book;
$this->checkOwnablePermission('page-create', $parent);
if ($parent->isA('chapter')) {
- $input['priority'] = $this->chapterRepo->getNewPriority($parent);
+ $input['priority'] = $this->entityRepo->getNewChapterPriority($parent);
} else {
- $input['priority'] = $this->bookRepo->getNewPriority($parent);
+ $input['priority'] = $this->entityRepo->getNewBookPriority($parent);
}
- $page = $this->pageRepo->publishDraft($draftPage, $input);
+ $page = $this->entityRepo->publishPageDraft($draftPage, $input);
Activity::add($page, 'page_create', $book->id);
return redirect($page->getUrl());
@@ -152,32 +141,29 @@ class PageController extends Controller
/**
* Display the specified page.
- * If the page is not found via the slug the
- * revisions are searched for a match.
+ * If the page is not found via the slug the revisions are searched for a match.
* @param string $bookSlug
* @param string $pageSlug
* @return Response
*/
public function show($bookSlug, $pageSlug)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
-
try {
- $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
+ $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
} catch (NotFoundException $e) {
- $page = $this->pageRepo->findPageUsingOldSlug($pageSlug, $bookSlug);
+ $page = $this->entityRepo->getPageByOldSlug($pageSlug, $bookSlug);
if ($page === null) abort(404);
return redirect($page->getUrl());
}
$this->checkOwnablePermission('page-view', $page);
- $sidebarTree = $this->bookRepo->getChildren($book);
- $pageNav = $this->pageRepo->getPageNav($page);
+ $sidebarTree = $this->entityRepo->getBookChildren($page->book);
+ $pageNav = $this->entityRepo->getPageNav($page);
Views::add($page);
$this->setPageTitle($page->getShortName());
- return view('pages/show', ['page' => $page, 'book' => $book,
+ return view('pages/show', ['page' => $page, 'book' => $page->book,
'current' => $page, 'sidebarTree' => $sidebarTree, 'pageNav' => $pageNav]);
}
@@ -188,7 +174,7 @@ class PageController extends Controller
*/
public function getPageAjax($pageId)
{
- $page = $this->pageRepo->getById($pageId);
+ $page = $this->entityRepo->getById('page', $pageId);
return response()->json($page);
}
@@ -200,26 +186,25 @@ class PageController extends Controller
*/
public function edit($bookSlug, $pageSlug)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
- $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
+ $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->checkOwnablePermission('page-update', $page);
$this->setPageTitle(trans('entities.pages_editing_named', ['pageName'=>$page->getShortName()]));
$page->isDraft = false;
// Check for active editing
$warnings = [];
- if ($this->pageRepo->isPageEditingActive($page, 60)) {
- $warnings[] = $this->pageRepo->getPageEditingActiveMessage($page, 60);
+ if ($this->entityRepo->isPageEditingActive($page, 60)) {
+ $warnings[] = $this->entityRepo->getPageEditingActiveMessage($page, 60);
}
// Check for a current draft version for this user
- if ($this->pageRepo->hasUserGotPageDraft($page, $this->currentUser->id)) {
- $draft = $this->pageRepo->getUserPageDraft($page, $this->currentUser->id);
+ if ($this->entityRepo->hasUserGotPageDraft($page, $this->currentUser->id)) {
+ $draft = $this->entityRepo->getUserPageDraft($page, $this->currentUser->id);
$page->name = $draft->name;
$page->html = $draft->html;
$page->markdown = $draft->markdown;
$page->isDraft = true;
- $warnings [] = $this->pageRepo->getUserPageDraftMessage($draft);
+ $warnings [] = $this->entityRepo->getUserPageDraftMessage($draft);
}
if (count($warnings) > 0) session()->flash('warning', implode("\n", $warnings));
@@ -227,7 +212,7 @@ class PageController extends Controller
$draftsEnabled = $this->signedIn;
return view('pages/edit', [
'page' => $page,
- 'book' => $book,
+ 'book' => $page->book,
'current' => $page,
'draftsEnabled' => $draftsEnabled
]);
@@ -245,11 +230,10 @@ class PageController extends Controller
$this->validate($request, [
'name' => 'required|string|max:255'
]);
- $book = $this->bookRepo->getBySlug($bookSlug);
- $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
+ $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->checkOwnablePermission('page-update', $page);
- $this->pageRepo->updatePage($page, $book->id, $request->all());
- Activity::add($page, 'page_update', $book->id);
+ $this->entityRepo->updatePage($page, $page->book->id, $request->all());
+ Activity::add($page, 'page_update', $page->book->id);
return redirect($page->getUrl());
}
@@ -261,7 +245,7 @@ class PageController extends Controller
*/
public function saveDraft(Request $request, $pageId)
{
- $page = $this->pageRepo->getById($pageId, true);
+ $page = $this->entityRepo->getById('page', $pageId, true);
$this->checkOwnablePermission('page-update', $page);
if (!$this->signedIn) {
@@ -271,11 +255,7 @@ class PageController extends Controller
], 500);
}
- if ($page->draft) {
- $draft = $this->pageRepo->updateDraftPage($page, $request->only(['name', 'html', 'markdown']));
- } else {
- $draft = $this->pageRepo->saveUpdateDraft($page, $request->only(['name', 'html', 'markdown']));
- }
+ $draft = $this->entityRepo->updatePageDraft($page, $request->only(['name', 'html', 'markdown']));
$updateTime = $draft->updated_at->timestamp;
$utcUpdateTimestamp = $updateTime + Carbon::createFromTimestamp(0)->offset;
@@ -294,7 +274,7 @@ class PageController extends Controller
*/
public function redirectFromLink($pageId)
{
- $page = $this->pageRepo->getById($pageId);
+ $page = $this->entityRepo->getById('page', $pageId);
return redirect($page->getUrl());
}
@@ -306,11 +286,10 @@ class PageController extends Controller
*/
public function showDelete($bookSlug, $pageSlug)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
- $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
+ $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->checkOwnablePermission('page-delete', $page);
$this->setPageTitle(trans('entities.pages_delete_named', ['pageName'=>$page->getShortName()]));
- return view('pages/delete', ['book' => $book, 'page' => $page, 'current' => $page]);
+ return view('pages/delete', ['book' => $page->book, 'page' => $page, 'current' => $page]);
}
@@ -323,11 +302,10 @@ class PageController extends Controller
*/
public function showDeleteDraft($bookSlug, $pageId)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
- $page = $this->pageRepo->getById($pageId, true);
+ $page = $this->entityRepo->getById('page', $pageId, true);
$this->checkOwnablePermission('page-update', $page);
$this->setPageTitle(trans('entities.pages_delete_draft_named', ['pageName'=>$page->getShortName()]));
- return view('pages/delete', ['book' => $book, 'page' => $page, 'current' => $page]);
+ return view('pages/delete', ['book' => $page->book, 'page' => $page, 'current' => $page]);
}
/**
@@ -339,12 +317,12 @@ class PageController extends Controller
*/
public function destroy($bookSlug, $pageSlug)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
- $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
+ $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
+ $book = $page->book;
$this->checkOwnablePermission('page-delete', $page);
Activity::addMessage('page_delete', $book->id, $page->name);
session()->flash('success', trans('entities.pages_delete_success'));
- $this->pageRepo->destroy($page);
+ $this->entityRepo->destroyPage($page);
return redirect($book->getUrl());
}
@@ -357,11 +335,11 @@ class PageController extends Controller
*/
public function destroyDraft($bookSlug, $pageId)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
- $page = $this->pageRepo->getById($pageId, true);
+ $page = $this->entityRepo->getById('page', $pageId, true);
+ $book = $page->book;
$this->checkOwnablePermission('page-update', $page);
session()->flash('success', trans('entities.pages_delete_draft_success'));
- $this->pageRepo->destroy($page);
+ $this->entityRepo->destroyPage($page);
return redirect($book->getUrl());
}
@@ -373,10 +351,9 @@ class PageController extends Controller
*/
public function showRevisions($bookSlug, $pageSlug)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
- $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
+ $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->setPageTitle(trans('entities.pages_revisions_named', ['pageName'=>$page->getShortName()]));
- return view('pages/revisions', ['page' => $page, 'book' => $book, 'current' => $page]);
+ return view('pages/revisions', ['page' => $page, 'book' => $page->book, 'current' => $page]);
}
/**
@@ -388,16 +365,15 @@ class PageController extends Controller
*/
public function showRevision($bookSlug, $pageSlug, $revisionId)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
- $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
- $revision = $this->pageRepo->getRevisionById($revisionId);
+ $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
+ $revision = $this->entityRepo->getById('page_revision', $revisionId, false);
$page->fill($revision->toArray());
$this->setPageTitle(trans('entities.pages_revision_named', ['pageName'=>$page->getShortName()]));
return view('pages/revision', [
'page' => $page,
- 'book' => $book,
+ 'book' => $page->book,
]);
}
@@ -410,9 +386,8 @@ class PageController extends Controller
*/
public function showRevisionChanges($bookSlug, $pageSlug, $revisionId)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
- $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
- $revision = $this->pageRepo->getRevisionById($revisionId);
+ $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
+ $revision = $this->entityRepo->getById('page_revision', $revisionId);
$prev = $revision->getPrevious();
$prevContent = ($prev === null) ? '' : $prev->html;
@@ -423,7 +398,7 @@ class PageController extends Controller
return view('pages/revision', [
'page' => $page,
- 'book' => $book,
+ 'book' => $page->book,
'diff' => $diff,
]);
}
@@ -437,11 +412,10 @@ class PageController extends Controller
*/
public function restoreRevision($bookSlug, $pageSlug, $revisionId)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
- $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
+ $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->checkOwnablePermission('page-update', $page);
- $page = $this->pageRepo->restoreRevision($page, $book, $revisionId);
- Activity::add($page, 'page_restore', $book->id);
+ $page = $this->entityRepo->restorePageRevision($page, $page->book, $revisionId);
+ Activity::add($page, 'page_restore', $page->book->id);
return redirect($page->getUrl());
}
@@ -454,8 +428,7 @@ class PageController extends Controller
*/
public function exportPdf($bookSlug, $pageSlug)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
- $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
+ $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$pdfContent = $this->exportService->pageToPdf($page);
return response()->make($pdfContent, 200, [
'Content-Type' => 'application/octet-stream',
@@ -471,8 +444,7 @@ class PageController extends Controller
*/
public function exportHtml($bookSlug, $pageSlug)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
- $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
+ $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$containedHtml = $this->exportService->pageToContainedHtml($page);
return response()->make($containedHtml, 200, [
'Content-Type' => 'application/octet-stream',
@@ -488,8 +460,7 @@ class PageController extends Controller
*/
public function exportPlainText($bookSlug, $pageSlug)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
- $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
+ $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$containedHtml = $this->exportService->pageToPlainText($page);
return response()->make($containedHtml, 200, [
'Content-Type' => 'application/octet-stream',
@@ -503,7 +474,7 @@ class PageController extends Controller
*/
public function showRecentlyCreated()
{
- $pages = $this->pageRepo->getRecentlyCreatedPaginated(20)->setPath(baseUrl('/pages/recently-created'));
+ $pages = $this->entityRepo->getRecentlyCreatedPaginated('page', 20)->setPath(baseUrl('/pages/recently-created'));
return view('pages/detailed-listing', [
'title' => trans('entities.recently_created_pages'),
'pages' => $pages
@@ -516,7 +487,7 @@ class PageController extends Controller
*/
public function showRecentlyUpdated()
{
- $pages = $this->pageRepo->getRecentlyUpdatedPaginated(20)->setPath(baseUrl('/pages/recently-updated'));
+ $pages = $this->entityRepo->getRecentlyUpdatedPaginated('page', 20)->setPath(baseUrl('/pages/recently-updated'));
return view('pages/detailed-listing', [
'title' => trans('entities.recently_updated_pages'),
'pages' => $pages
@@ -531,8 +502,7 @@ class PageController extends Controller
*/
public function showRestrict($bookSlug, $pageSlug)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
- $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
+ $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->checkOwnablePermission('restrictions-manage', $page);
$roles = $this->userRepo->getRestrictableRoles();
return view('pages/restrictions', [
@@ -550,11 +520,10 @@ class PageController extends Controller
*/
public function showMove($bookSlug, $pageSlug)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
- $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
+ $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->checkOwnablePermission('page-update', $page);
return view('pages/move', [
- 'book' => $book,
+ 'book' => $page->book,
'page' => $page
]);
}
@@ -569,8 +538,7 @@ class PageController extends Controller
*/
public function move($bookSlug, $pageSlug, Request $request)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
- $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
+ $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->checkOwnablePermission('page-update', $page);
$entitySelection = $request->get('entity_selection', null);
@@ -582,20 +550,15 @@ class PageController extends Controller
$entityType = $stringExploded[0];
$entityId = intval($stringExploded[1]);
- $parent = false;
- if ($entityType == 'chapter') {
- $parent = $this->chapterRepo->getById($entityId);
- } else if ($entityType == 'book') {
- $parent = $this->bookRepo->getById($entityId);
- }
-
- if ($parent === false || $parent === null) {
+ try {
+ $parent = $this->entityRepo->getById($entityType, $entityId);
+ } catch (\Exception $e) {
session()->flash(trans('entities.selected_book_chapter_not_found'));
return redirect()->back();
}
- $this->pageRepo->changePageParent($page, $parent);
+ $this->entityRepo->changePageParent($page, $parent);
Activity::add($page, 'page_move', $page->book->id);
session()->flash('success', trans('entities.pages_move_success', ['parentName' => $parent->name]));
@@ -611,10 +574,9 @@ class PageController extends Controller
*/
public function restrict($bookSlug, $pageSlug, Request $request)
{
- $book = $this->bookRepo->getBySlug($bookSlug);
- $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
+ $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->checkOwnablePermission('restrictions-manage', $page);
- $this->pageRepo->updateEntityPermissionsFromRequest($request, $page);
+ $this->entityRepo->updateEntityPermissionsFromRequest($request, $page);
session()->flash('success', trans('entities.pages_permissions_success'));
return redirect($page->getUrl());
}
diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php
index bb70b0f88..37aaccece 100644
--- a/app/Http/Controllers/SearchController.php
+++ b/app/Http/Controllers/SearchController.php
@@ -1,30 +1,22 @@
pageRepo = $pageRepo;
- $this->bookRepo = $bookRepo;
- $this->chapterRepo = $chapterRepo;
+ $this->entityRepo = $entityRepo;
$this->viewService = $viewService;
parent::__construct();
}
@@ -42,9 +34,9 @@ class SearchController extends Controller
}
$searchTerm = $request->get('term');
$paginationAppends = $request->only('term');
- $pages = $this->pageRepo->getBySearch($searchTerm, [], 20, $paginationAppends);
- $books = $this->bookRepo->getBySearch($searchTerm, 10, $paginationAppends);
- $chapters = $this->chapterRepo->getBySearch($searchTerm, [], 10, $paginationAppends);
+ $pages = $this->entityRepo->getBySearch('page', $searchTerm, [], 20, $paginationAppends);
+ $books = $this->entityRepo->getBySearch('book', $searchTerm, [], 10, $paginationAppends);
+ $chapters = $this->entityRepo->getBySearch('chapter', $searchTerm, [], 10, $paginationAppends);
$this->setPageTitle(trans('entities.search_for_term', ['term' => $searchTerm]));
return view('search/all', [
'pages' => $pages,
@@ -65,7 +57,7 @@ class SearchController extends Controller
$searchTerm = $request->get('term');
$paginationAppends = $request->only('term');
- $pages = $this->pageRepo->getBySearch($searchTerm, [], 20, $paginationAppends);
+ $pages = $this->entityRepo->getBySearch('page', $searchTerm, [], 20, $paginationAppends);
$this->setPageTitle(trans('entities.search_page_for_term', ['term' => $searchTerm]));
return view('search/entity-search-list', [
'entities' => $pages,
@@ -85,7 +77,7 @@ class SearchController extends Controller
$searchTerm = $request->get('term');
$paginationAppends = $request->only('term');
- $chapters = $this->chapterRepo->getBySearch($searchTerm, [], 20, $paginationAppends);
+ $chapters = $this->entityRepo->getBySearch('chapter', $searchTerm, [], 20, $paginationAppends);
$this->setPageTitle(trans('entities.search_chapter_for_term', ['term' => $searchTerm]));
return view('search/entity-search-list', [
'entities' => $chapters,
@@ -105,7 +97,7 @@ class SearchController extends Controller
$searchTerm = $request->get('term');
$paginationAppends = $request->only('term');
- $books = $this->bookRepo->getBySearch($searchTerm, 20, $paginationAppends);
+ $books = $this->entityRepo->getBySearch('book', $searchTerm, [], 20, $paginationAppends);
$this->setPageTitle(trans('entities.search_book_for_term', ['term' => $searchTerm]));
return view('search/entity-search-list', [
'entities' => $books,
@@ -128,8 +120,8 @@ class SearchController extends Controller
}
$searchTerm = $request->get('term');
$searchWhereTerms = [['book_id', '=', $bookId]];
- $pages = $this->pageRepo->getBySearch($searchTerm, $searchWhereTerms);
- $chapters = $this->chapterRepo->getBySearch($searchTerm, $searchWhereTerms);
+ $pages = $this->entityRepo->getBySearch('page', $searchTerm, $searchWhereTerms);
+ $chapters = $this->entityRepo->getBySearch('chapter', $searchTerm, $searchWhereTerms);
return view('search/book', ['pages' => $pages, 'chapters' => $chapters, 'searchTerm' => $searchTerm]);
}
@@ -148,9 +140,11 @@ class SearchController extends Controller
// Search for entities otherwise show most popular
if ($searchTerm !== false) {
- if ($entityTypes->contains('page')) $entities = $entities->merge($this->pageRepo->getBySearch($searchTerm)->items());
- if ($entityTypes->contains('chapter')) $entities = $entities->merge($this->chapterRepo->getBySearch($searchTerm)->items());
- if ($entityTypes->contains('book')) $entities = $entities->merge($this->bookRepo->getBySearch($searchTerm)->items());
+ foreach (['page', 'chapter', 'book'] as $entityType) {
+ if ($entityTypes->contains($entityType)) {
+ $entities = $entities->merge($this->entityRepo->getBySearch($entityType, $searchTerm)->items());
+ }
+ }
$entities = $entities->sortByDesc('title_relevance');
} else {
$entityNames = $entityTypes->map(function ($type) {
diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php
index 8461ed0ba..b78016688 100644
--- a/app/Http/Middleware/Authenticate.php
+++ b/app/Http/Middleware/Authenticate.php
@@ -4,8 +4,6 @@ namespace BookStack\Http\Middleware;
use Closure;
use Illuminate\Contracts\Auth\Guard;
-use BookStack\Exceptions\UserRegistrationException;
-use Setting;
class Authenticate
{
diff --git a/app/Page.php b/app/Page.php
index 3ee9e90f4..b24e7778a 100644
--- a/app/Page.php
+++ b/app/Page.php
@@ -7,6 +7,10 @@ class Page extends Entity
protected $simpleAttributes = ['name', 'id', 'slug'];
+ protected $with = ['book'];
+
+ protected $fieldsToSearch = ['name', 'text'];
+
/**
* Converts this page into a simplified array.
* @return mixed
diff --git a/app/Repos/BookRepo.php b/app/Repos/BookRepo.php
deleted file mode 100644
index b14cf0dab..000000000
--- a/app/Repos/BookRepo.php
+++ /dev/null
@@ -1,295 +0,0 @@
-pageRepo = $pageRepo;
- $this->chapterRepo = $chapterRepo;
- parent::__construct();
- }
-
- /**
- * Base query for getting books.
- * Takes into account any restrictions.
- * @return mixed
- */
- private function bookQuery()
- {
- return $this->permissionService->enforceBookRestrictions($this->book, 'view');
- }
-
- /**
- * Get the book that has the given id.
- * @param $id
- * @return mixed
- */
- public function getById($id)
- {
- return $this->bookQuery()->findOrFail($id);
- }
-
- /**
- * Get all books, Limited by count.
- * @param int $count
- * @return mixed
- */
- public function getAll($count = 10)
- {
- $bookQuery = $this->bookQuery()->orderBy('name', 'asc');
- if (!$count) return $bookQuery->get();
- return $bookQuery->take($count)->get();
- }
-
- /**
- * Get all books paginated.
- * @param int $count
- * @return mixed
- */
- public function getAllPaginated($count = 10)
- {
- return $this->bookQuery()
- ->orderBy('name', 'asc')->paginate($count);
- }
-
-
- /**
- * Get the latest books.
- * @param int $count
- * @return mixed
- */
- public function getLatest($count = 10)
- {
- return $this->bookQuery()->orderBy('created_at', 'desc')->take($count)->get();
- }
-
- /**
- * Gets the most recently viewed for a user.
- * @param int $count
- * @param int $page
- * @return mixed
- */
- public function getRecentlyViewed($count = 10, $page = 0)
- {
- return Views::getUserRecentlyViewed($count, $page, $this->book);
- }
-
- /**
- * Gets the most viewed books.
- * @param int $count
- * @param int $page
- * @return mixed
- */
- public function getPopular($count = 10, $page = 0)
- {
- return Views::getPopular($count, $page, $this->book);
- }
-
- /**
- * Get a book by slug
- * @param $slug
- * @return mixed
- * @throws NotFoundException
- */
- public function getBySlug($slug)
- {
- $book = $this->bookQuery()->where('slug', '=', $slug)->first();
- if ($book === null) throw new NotFoundException(trans('errors.book_not_found'));
- return $book;
- }
-
- /**
- * Checks if a book exists.
- * @param $id
- * @return bool
- */
- public function exists($id)
- {
- return $this->bookQuery()->where('id', '=', $id)->exists();
- }
-
- /**
- * Get a new book instance from request input.
- * @param array $input
- * @return Book
- */
- public function createFromInput($input)
- {
- $book = $this->book->newInstance($input);
- $book->slug = $this->findSuitableSlug($book->name);
- $book->created_by = user()->id;
- $book->updated_by = user()->id;
- $book->save();
- $this->permissionService->buildJointPermissionsForEntity($book);
- return $book;
- }
-
- /**
- * Update the given book from user input.
- * @param Book $book
- * @param $input
- * @return Book
- */
- public function updateFromInput(Book $book, $input)
- {
- if ($book->name !== $input['name']) {
- $book->slug = $this->findSuitableSlug($input['name'], $book->id);
- }
- $book->fill($input);
- $book->updated_by = user()->id;
- $book->save();
- $this->permissionService->buildJointPermissionsForEntity($book);
- return $book;
- }
-
- /**
- * Destroy the given book.
- * @param Book $book
- * @throws \Exception
- */
- public function destroy(Book $book)
- {
- foreach ($book->pages as $page) {
- $this->pageRepo->destroy($page);
- }
- foreach ($book->chapters as $chapter) {
- $this->chapterRepo->destroy($chapter);
- }
- $book->views()->delete();
- $book->permissions()->delete();
- $this->permissionService->deleteJointPermissionsForEntity($book);
- $book->delete();
- }
-
- /**
- * Get the next child element priority.
- * @param Book $book
- * @return int
- */
- public function getNewPriority($book)
- {
- $lastElem = $this->getChildren($book)->pop();
- return $lastElem ? $lastElem->priority + 1 : 0;
- }
-
- /**
- * @param string $slug
- * @param bool|false $currentId
- * @return bool
- */
- public function doesSlugExist($slug, $currentId = false)
- {
- $query = $this->book->where('slug', '=', $slug);
- if ($currentId) {
- $query = $query->where('id', '!=', $currentId);
- }
- return $query->count() > 0;
- }
-
- /**
- * Provides a suitable slug for the given book name.
- * Ensures the returned slug is unique in the system.
- * @param string $name
- * @param bool|false $currentId
- * @return string
- */
- public function findSuitableSlug($name, $currentId = false)
- {
- $slug = $this->nameToSlug($name);
- while ($this->doesSlugExist($slug, $currentId)) {
- $slug .= '-' . substr(md5(rand(1, 500)), 0, 3);
- }
- return $slug;
- }
-
- /**
- * 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
- * @return mixed
- */
- public function getChildren(Book $book, $filterDrafts = false)
- {
- $pageQuery = $book->pages()->where('chapter_id', '=', 0);
- $pageQuery = $this->permissionService->enforcePageRestrictions($pageQuery, 'view');
-
- if ($filterDrafts) {
- $pageQuery = $pageQuery->where('draft', '=', false);
- }
-
- $pages = $pageQuery->get();
-
- $chapterQuery = $book->chapters()->with(['pages' => function ($query) use ($filterDrafts) {
- $this->permissionService->enforcePageRestrictions($query, 'view');
- if ($filterDrafts) $query->where('draft', '=', false);
- }]);
- $chapterQuery = $this->permissionService->enforceChapterRestrictions($chapterQuery, 'view');
- $chapters = $chapterQuery->get();
- $children = $pages->values();
- foreach ($chapters as $chapter) {
- $children->push($chapter);
- }
- $bookSlug = $book->slug;
-
- $children->each(function ($child) use ($bookSlug) {
- $child->setAttribute('bookSlug', $bookSlug);
- if ($child->isA('chapter')) {
- $child->pages->each(function ($page) use ($bookSlug) {
- $page->setAttribute('bookSlug', $bookSlug);
- });
- $child->pages = $child->pages->sortBy(function ($child, $key) {
- $score = $child->priority;
- if ($child->draft) $score -= 100;
- return $score;
- });
- }
- });
-
- // Sort items with drafts first then by priority.
- return $children->sortBy(function ($child, $key) {
- $score = $child->priority;
- if ($child->isA('page') && $child->draft) $score -= 100;
- return $score;
- });
- }
-
- /**
- * Get books by search term.
- * @param $term
- * @param int $count
- * @param array $paginationAppends
- * @return mixed
- */
- public function getBySearch($term, $count = 20, $paginationAppends = [])
- {
- $terms = $this->prepareSearchTerms($term);
- $bookQuery = $this->permissionService->enforceBookRestrictions($this->book->fullTextSearchQuery(['name', 'description'], $terms));
- $bookQuery = $this->addAdvancedSearchQueries($bookQuery, $term);
- $books = $bookQuery->paginate($count)->appends($paginationAppends);
- $words = join('|', explode(' ', preg_quote(trim($term), '/')));
- foreach ($books as $book) {
- //highlight
- $result = preg_replace('#' . $words . '#iu', "\$0", $book->getExcerpt(100));
- $book->searchSnippet = $result;
- }
- return $books;
- }
-
-}
\ No newline at end of file
diff --git a/app/Repos/ChapterRepo.php b/app/Repos/ChapterRepo.php
deleted file mode 100644
index 4106f93ee..000000000
--- a/app/Repos/ChapterRepo.php
+++ /dev/null
@@ -1,226 +0,0 @@
-pageRepo = $pageRepo;
- parent::__construct();
- }
-
- /**
- * Base query for getting chapters, Takes permissions into account.
- * @return mixed
- */
- private function chapterQuery()
- {
- return $this->permissionService->enforceChapterRestrictions($this->chapter, 'view');
- }
-
- /**
- * Check if an id exists.
- * @param $id
- * @return bool
- */
- public function idExists($id)
- {
- return $this->chapterQuery()->where('id', '=', $id)->count() > 0;
- }
-
- /**
- * Get a chapter by a specific id.
- * @param $id
- * @return mixed
- */
- public function getById($id)
- {
- return $this->chapterQuery()->findOrFail($id);
- }
-
- /**
- * Get all chapters.
- * @return \Illuminate\Database\Eloquent\Collection|static[]
- */
- public function getAll()
- {
- return $this->chapterQuery()->all();
- }
-
- /**
- * Get a chapter that has the given slug within the given book.
- * @param $slug
- * @param $bookId
- * @return mixed
- * @throws NotFoundException
- */
- public function getBySlug($slug, $bookId)
- {
- $chapter = $this->chapterQuery()->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first();
- if ($chapter === null) throw new NotFoundException(trans('errors.chapter_not_found'));
- return $chapter;
- }
-
- /**
- * Get the child items for a chapter
- * @param Chapter $chapter
- */
- public function getChildren(Chapter $chapter)
- {
- $pages = $this->permissionService->enforcePageRestrictions($chapter->pages())->get();
- // Sort items with drafts first then by priority.
- return $pages->sortBy(function ($child, $key) {
- $score = $child->priority;
- if ($child->draft) $score -= 100;
- return $score;
- });
- }
-
- /**
- * Create a new chapter from request input.
- * @param $input
- * @param Book $book
- * @return Chapter
- */
- public function createFromInput($input, Book $book)
- {
- $chapter = $this->chapter->newInstance($input);
- $chapter->slug = $this->findSuitableSlug($chapter->name, $book->id);
- $chapter->created_by = user()->id;
- $chapter->updated_by = user()->id;
- $chapter = $book->chapters()->save($chapter);
- $this->permissionService->buildJointPermissionsForEntity($chapter);
- return $chapter;
- }
-
- /**
- * Destroy a chapter and its relations by providing its slug.
- * @param Chapter $chapter
- */
- public function destroy(Chapter $chapter)
- {
- if (count($chapter->pages) > 0) {
- foreach ($chapter->pages as $page) {
- $page->chapter_id = 0;
- $page->save();
- }
- }
- Activity::removeEntity($chapter);
- $chapter->views()->delete();
- $chapter->permissions()->delete();
- $this->permissionService->deleteJointPermissionsForEntity($chapter);
- $chapter->delete();
- }
-
- /**
- * Check if a chapter's slug exists.
- * @param $slug
- * @param $bookId
- * @param bool|false $currentId
- * @return bool
- */
- public function doesSlugExist($slug, $bookId, $currentId = false)
- {
- $query = $this->chapter->where('slug', '=', $slug)->where('book_id', '=', $bookId);
- if ($currentId) {
- $query = $query->where('id', '!=', $currentId);
- }
- return $query->count() > 0;
- }
-
- /**
- * Finds a suitable slug for the provided name.
- * Checks database to prevent duplicate slugs.
- * @param $name
- * @param $bookId
- * @param bool|false $currentId
- * @return string
- */
- public function findSuitableSlug($name, $bookId, $currentId = false)
- {
- $slug = $this->nameToSlug($name);
- while ($this->doesSlugExist($slug, $bookId, $currentId)) {
- $slug .= '-' . substr(md5(rand(1, 500)), 0, 3);
- }
- return $slug;
- }
-
- /**
- * Get a new priority value for a new page to be added
- * to the given chapter.
- * @param Chapter $chapter
- * @return int
- */
- public function getNewPriority(Chapter $chapter)
- {
- $lastPage = $chapter->pages->last();
- return $lastPage !== null ? $lastPage->priority + 1 : 0;
- }
-
- /**
- * Get chapters by the given search term.
- * @param string $term
- * @param array $whereTerms
- * @param int $count
- * @param array $paginationAppends
- * @return mixed
- */
- public function getBySearch($term, $whereTerms = [], $count = 20, $paginationAppends = [])
- {
- $terms = $this->prepareSearchTerms($term);
- $chapterQuery = $this->permissionService->enforceChapterRestrictions($this->chapter->fullTextSearchQuery(['name', 'description'], $terms, $whereTerms));
- $chapterQuery = $this->addAdvancedSearchQueries($chapterQuery, $term);
- $chapters = $chapterQuery->paginate($count)->appends($paginationAppends);
- $words = join('|', explode(' ', preg_quote(trim($term), '/')));
- foreach ($chapters as $chapter) {
- //highlight
- $result = preg_replace('#' . $words . '#iu', "\$0", $chapter->getExcerpt(100));
- $chapter->searchSnippet = $result;
- }
- return $chapters;
- }
-
- /**
- * Changes the book relation of this chapter.
- * @param $bookId
- * @param Chapter $chapter
- * @param bool $rebuildPermissions
- * @return Chapter
- */
- public function changeBook($bookId, Chapter $chapter, $rebuildPermissions = false)
- {
- $chapter->book_id = $bookId;
- // Update related activity
- foreach ($chapter->activity as $activity) {
- $activity->book_id = $bookId;
- $activity->save();
- }
- $chapter->slug = $this->findSuitableSlug($chapter->name, $bookId, $chapter->id);
- $chapter->save();
- // Update all child pages
- foreach ($chapter->pages as $page) {
- $this->pageRepo->changeBook($bookId, $page);
- }
-
- // Update permissions if applicable
- if ($rebuildPermissions) {
- $chapter->load('book');
- $this->permissionService->buildJointPermissionsForEntity($chapter->book);
- }
-
- return $chapter;
- }
-
-}
\ No newline at end of file
diff --git a/app/Repos/EntityRepo.php b/app/Repos/EntityRepo.php
index 7ecfb758c..95666a66a 100644
--- a/app/Repos/EntityRepo.php
+++ b/app/Repos/EntityRepo.php
@@ -3,11 +3,16 @@
use BookStack\Book;
use BookStack\Chapter;
use BookStack\Entity;
+use BookStack\Exceptions\NotFoundException;
use BookStack\Page;
+use BookStack\PageRevision;
+use BookStack\Services\AttachmentService;
use BookStack\Services\PermissionService;
-use BookStack\User;
+use BookStack\Services\ViewService;
+use Carbon\Carbon;
+use DOMDocument;
+use DOMXPath;
use Illuminate\Support\Collection;
-use Illuminate\Support\Facades\Log;
class EntityRepo
{
@@ -27,11 +32,32 @@ class EntityRepo
*/
public $page;
+ /**
+ * @var PageRevision
+ */
+ protected $pageRevision;
+
+ /**
+ * Base entity instances keyed by type
+ * @var []Entity
+ */
+ protected $entities;
+
/**
* @var PermissionService
*/
protected $permissionService;
+ /**
+ * @var ViewService
+ */
+ protected $viewService;
+
+ /**
+ * @var TagRepo
+ */
+ protected $tagRepo;
+
/**
* Acceptable operators to be used in a query
* @var array
@@ -40,26 +66,163 @@ class EntityRepo
/**
* EntityService constructor.
+ * @param Book $book
+ * @param Chapter $chapter
+ * @param Page $page
+ * @param PageRevision $pageRevision
+ * @param ViewService $viewService
+ * @param PermissionService $permissionService
+ * @param TagRepo $tagRepo
*/
- public function __construct()
+ public function __construct(
+ Book $book, Chapter $chapter, Page $page, PageRevision $pageRevision,
+ ViewService $viewService, PermissionService $permissionService, TagRepo $tagRepo
+ )
{
- $this->book = app(Book::class);
- $this->chapter = app(Chapter::class);
- $this->page = app(Page::class);
- $this->permissionService = app(PermissionService::class);
+ $this->book = $book;
+ $this->chapter = $chapter;
+ $this->page = $page;
+ $this->pageRevision = $pageRevision;
+ $this->entities = [
+ 'page' => $this->page,
+ 'chapter' => $this->chapter,
+ 'book' => $this->book,
+ 'page_revision' => $this->pageRevision
+ ];
+ $this->viewService = $viewService;
+ $this->permissionService = $permissionService;
+ $this->tagRepo = $tagRepo;
}
/**
- * Get the latest books added to the system.
+ * Get an entity instance via type.
+ * @param $type
+ * @return Entity
+ */
+ protected function getEntity($type)
+ {
+ return $this->entities[strtolower($type)];
+ }
+
+ /**
+ * Base query for searching entities via permission system
+ * @param string $type
+ * @param bool $allowDrafts
+ * @return \Illuminate\Database\Query\Builder
+ */
+ protected function entityQuery($type, $allowDrafts = false)
+ {
+ $q = $this->permissionService->enforceEntityRestrictions($type, $this->getEntity($type), 'view');
+ if (strtolower($type) === 'page' && !$allowDrafts) {
+ $q = $q->where('draft', '=', false);
+ }
+ return $q;
+ }
+
+ /**
+ * Check if an entity with the given id exists.
+ * @param $type
+ * @param $id
+ * @return bool
+ */
+ public function exists($type, $id)
+ {
+ return $this->entityQuery($type)->where('id', '=', $id)->exists();
+ }
+
+ /**
+ * Get an entity by ID
+ * @param string $type
+ * @param integer $id
+ * @param bool $allowDrafts
+ * @return Entity
+ */
+ public function getById($type, $id, $allowDrafts = false)
+ {
+ return $this->entityQuery($type, $allowDrafts)->findOrFail($id);
+ }
+
+ /**
+ * Get an entity by its url slug.
+ * @param string $type
+ * @param string $slug
+ * @param string|bool $bookSlug
+ * @return Entity
+ * @throws NotFoundException
+ */
+ public function getBySlug($type, $slug, $bookSlug = false)
+ {
+ $q = $this->entityQuery($type)->where('slug', '=', $slug);
+
+ if (strtolower($type) === 'chapter' || strtolower($type) === 'page') {
+ $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'));
+ return $entity;
+ }
+
+
+ /**
+ * Search through page revisions and retrieve the last page in the
+ * current book that has a slug equal to the one given.
+ * @param string $pageSlug
+ * @param string $bookSlug
+ * @return null|Page
+ */
+ public function getPageByOldSlug($pageSlug, $bookSlug)
+ {
+ $revision = $this->pageRevision->where('slug', '=', $pageSlug)
+ ->whereHas('page', function ($query) {
+ $this->permissionService->enforceEntityRestrictions('page', $query);
+ })
+ ->where('type', '=', 'version')
+ ->where('book_slug', '=', $bookSlug)
+ ->orderBy('created_at', 'desc')
+ ->with('page')->first();
+ return $revision !== null ? $revision->page : null;
+ }
+
+ /**
+ * Get all entities of a type limited by count unless count if false.
+ * @param string $type
+ * @param integer|bool $count
+ * @return Collection
+ */
+ public function getAll($type, $count = 20)
+ {
+ $q = $this->entityQuery($type)->orderBy('name', 'asc');
+ if ($count !== false) $q = $q->take($count);
+ return $q->get();
+ }
+
+ /**
+ * Get all entities in a paginated format
+ * @param $type
+ * @param int $count
+ * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
+ */
+ public function getAllPaginated($type, $count = 10)
+ {
+ return $this->entityQuery($type)->orderBy('name', 'asc')->paginate($count);
+ }
+
+ /**
+ * Get the most recently created entities of the given type.
+ * @param string $type
* @param int $count
* @param int $page
- * @param bool $additionalQuery
- * @return
+ * @param bool|callable $additionalQuery
*/
- public function getRecentlyCreatedBooks($count = 20, $page = 0, $additionalQuery = false)
+ public function getRecentlyCreated($type, $count = 20, $page = 0, $additionalQuery = false)
{
- $query = $this->permissionService->enforceBookRestrictions($this->book)
+ $query = $this->permissionService->enforceEntityRestrictions($type, $this->getEntity($type))
->orderBy('created_at', 'desc');
+ if (strtolower($type) === 'page') $query = $query->where('draft', '=', false);
if ($additionalQuery !== false && is_callable($additionalQuery)) {
$additionalQuery($query);
}
@@ -67,45 +230,17 @@ class EntityRepo
}
/**
- * Get the most recently updated books.
- * @param $count
- * @param int $page
- * @return mixed
- */
- public function getRecentlyUpdatedBooks($count = 20, $page = 0)
- {
- return $this->permissionService->enforceBookRestrictions($this->book)
- ->orderBy('updated_at', 'desc')->skip($page * $count)->take($count)->get();
- }
-
- /**
- * Get the latest pages added to the system.
+ * Get the most recently updated entities of the given type.
+ * @param string $type
* @param int $count
* @param int $page
- * @param bool $additionalQuery
- * @return
+ * @param bool|callable $additionalQuery
*/
- public function getRecentlyCreatedPages($count = 20, $page = 0, $additionalQuery = false)
+ public function getRecentlyUpdated($type, $count = 20, $page = 0, $additionalQuery = false)
{
- $query = $this->permissionService->enforcePageRestrictions($this->page)
- ->orderBy('created_at', 'desc')->where('draft', '=', false);
- if ($additionalQuery !== false && is_callable($additionalQuery)) {
- $additionalQuery($query);
- }
- return $query->with('book')->skip($page * $count)->take($count)->get();
- }
-
- /**
- * Get the latest chapters added to the system.
- * @param int $count
- * @param int $page
- * @param bool $additionalQuery
- * @return
- */
- public function getRecentlyCreatedChapters($count = 20, $page = 0, $additionalQuery = false)
- {
- $query = $this->permissionService->enforceChapterRestrictions($this->chapter)
- ->orderBy('created_at', 'desc');
+ $query = $this->permissionService->enforceEntityRestrictions($type, $this->getEntity($type))
+ ->orderBy('updated_at', 'desc');
+ if (strtolower($type) === 'page') $query = $query->where('draft', '=', false);
if ($additionalQuery !== false && is_callable($additionalQuery)) {
$additionalQuery($query);
}
@@ -113,16 +248,51 @@ class EntityRepo
}
/**
- * Get the most recently updated pages.
- * @param $count
+ * Get the most recently viewed entities.
+ * @param string|bool $type
+ * @param int $count
* @param int $page
* @return mixed
*/
- public function getRecentlyUpdatedPages($count = 20, $page = 0)
+ public function getRecentlyViewed($type, $count = 10, $page = 0)
{
- return $this->permissionService->enforcePageRestrictions($this->page)
- ->where('draft', '=', false)
- ->orderBy('updated_at', 'desc')->with('book')->skip($page * $count)->take($count)->get();
+ $filter = is_bool($type) ? false : $this->getEntity($type);
+ return $this->viewService->getUserRecentlyViewed($count, $page, $filter);
+ }
+
+ /**
+ * Get the latest pages added to the system with pagination.
+ * @param string $type
+ * @param int $count
+ * @return mixed
+ */
+ public function getRecentlyCreatedPaginated($type, $count = 20)
+ {
+ return $this->entityQuery($type)->orderBy('created_at', 'desc')->paginate($count);
+ }
+
+ /**
+ * Get the latest pages added to the system with pagination.
+ * @param string $type
+ * @param int $count
+ * @return mixed
+ */
+ public function getRecentlyUpdatedPaginated($type, $count = 20)
+ {
+ return $this->entityQuery($type)->orderBy('updated_at', 'desc')->paginate($count);
+ }
+
+ /**
+ * Get the most popular entities base on all views.
+ * @param string|bool $type
+ * @param int $count
+ * @param int $page
+ * @return mixed
+ */
+ public function getPopular($type, $count = 10, $page = 0)
+ {
+ $filter = is_bool($type) ? false : $this->getEntity($type);
+ return $this->viewService->getPopular($count, $page, $filter);
}
/**
@@ -138,6 +308,163 @@ class EntityRepo
->skip($count * $page)->take($count)->get();
}
+ /**
+ * 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
+ * @return mixed
+ */
+ public function getBookChildren(Book $book, $filterDrafts = false)
+ {
+ $q = $this->permissionService->bookChildrenQuery($book->id, $filterDrafts);
+ $entities = [];
+ $parents = [];
+ $tree = [];
+
+ foreach ($q as $index => $rawEntity) {
+ if ($rawEntity->entity_type === 'Bookstack\\Page') {
+ $entities[$index] = $this->page->newFromBuilder($rawEntity);
+ } 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) $tree[] = $entities[$index];
+ $entities[$index]->book = $book;
+ }
+
+ foreach ($entities as $entity) {
+ if ($entity->chapter_id === 0) continue;
+ $parentKey = 'Bookstack\\Chapter:' . $entity->chapter_id;
+ $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
+ */
+ public function getChapterChildren(Chapter $chapter)
+ {
+ return $this->permissionService->enforceEntityRestrictions('page', $chapter->pages())
+ ->orderBy('draft', 'DESC')->orderBy('priority', 'ASC')->get();
+ }
+
+ /**
+ * Search entities of a type via a given query.
+ * @param string $type
+ * @param string $term
+ * @param array $whereTerms
+ * @param int $count
+ * @param array $paginationAppends
+ * @return mixed
+ */
+ public function getBySearch($type, $term, $whereTerms = [], $count = 20, $paginationAppends = [])
+ {
+ $terms = $this->prepareSearchTerms($term);
+ $q = $this->permissionService->enforceEntityRestrictions($type, $this->getEntity($type)->fullTextSearchQuery($terms, $whereTerms));
+ $q = $this->addAdvancedSearchQueries($q, $term);
+ $entities = $q->paginate($count)->appends($paginationAppends);
+ $words = join('|', explode(' ', preg_quote(trim($term), '/')));
+
+ // Highlight page content
+ if ($type === 'page') {
+ //lookahead/behind assertions ensures cut between words
+ $s = '\s\x00-/:-@\[-`{-~'; //character set for start/end of words
+
+ foreach ($entities as $page) {
+ preg_match_all('#(?<=[' . $s . ']).{1,30}((' . $words . ').{1,30})+(?=[' . $s . '])#uis', $page->text, $matches, PREG_SET_ORDER);
+ //delimiter between occurrences
+ $results = [];
+ foreach ($matches as $line) {
+ $results[] = htmlspecialchars($line[0], 0, 'UTF-8');
+ }
+ $matchLimit = 6;
+ if (count($results) > $matchLimit) $results = array_slice($results, 0, $matchLimit);
+ $result = join('... ', $results);
+
+ //highlight
+ $result = preg_replace('#' . $words . '#iu', "\$0", $result);
+ if (strlen($result) < 5) $result = $page->getExcerpt(80);
+
+ $page->searchSnippet = $result;
+ }
+ return $entities;
+ }
+
+ // Highlight chapter/book content
+ foreach ($entities as $entity) {
+ //highlight
+ $result = preg_replace('#' . $words . '#iu', "\$0", $entity->getExcerpt(100));
+ $entity->searchSnippet = $result;
+ }
+ return $entities;
+ }
+
+ /**
+ * 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;
+ }
+
/**
* Updates entity restrictions from a request
* @param $request
@@ -260,6 +587,81 @@ class EntityRepo
return $query;
}
+ /**
+ * Create a new entity from request input.
+ * Used for books and chapters.
+ * @param string $type
+ * @param array $input
+ * @param bool|Book $book
+ * @return Entity
+ */
+ public function createFromInput($type, $input = [], $book = false)
+ {
+ $isChapter = strtolower($type) === 'chapter';
+ $entity = $this->getEntity($type)->newInstance($input);
+ $entity->slug = $this->findSuitableSlug($type, $entity->name, false, $isChapter ? $book->id : false);
+ $entity->created_by = user()->id;
+ $entity->updated_by = user()->id;
+ $isChapter ? $book->chapters()->save($entity) : $entity->save();
+ $this->permissionService->buildJointPermissionsForEntity($entity);
+ return $entity;
+ }
+
+ /**
+ * Update entity details from request input.
+ * Use for books and chapters
+ * @param string $type
+ * @param Entity $entityModel
+ * @param array $input
+ * @return Entity
+ */
+ public function updateFromInput($type, Entity $entityModel, $input = [])
+ {
+ if ($entityModel->name !== $input['name']) {
+ $entityModel->slug = $this->findSuitableSlug($type, $input['name'], $entityModel->id);
+ }
+ $entityModel->fill($input);
+ $entityModel->updated_by = user()->id;
+ $entityModel->save();
+ $this->permissionService->buildJointPermissionsForEntity($entityModel);
+ return $entityModel;
+ }
+
+ /**
+ * Change the book that an entity belongs to.
+ * @param string $type
+ * @param integer $newBookId
+ * @param Entity $entity
+ * @param bool $rebuildPermissions
+ * @return Entity
+ */
+ public function changeBook($type, $newBookId, Entity $entity, $rebuildPermissions = false)
+ {
+ $entity->book_id = $newBookId;
+ // Update related activity
+ foreach ($entity->activity as $activity) {
+ $activity->book_id = $newBookId;
+ $activity->save();
+ }
+ $entity->slug = $this->findSuitableSlug($type, $entity->name, $entity->id, $newBookId);
+ $entity->save();
+
+ // Update all child pages if a chapter
+ if (strtolower($type) === 'chapter') {
+ foreach ($entity->pages as $page) {
+ $this->changeBook('page', $newBookId, $page, false);
+ }
+ }
+
+ // Update permissions if applicable
+ if ($rebuildPermissions) {
+ $entity->load('book');
+ $this->permissionService->buildJointPermissionsForEntity($entity->book);
+ }
+
+ return $entity;
+ }
+
/**
* Alias method to update the book jointPermissions in the PermissionService.
* @param Collection $collection collection on entities
@@ -282,6 +684,463 @@ class EntityRepo
return $slug;
}
+ /**
+ * 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 publishPageDraft(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('page', $draftPage->name, false, $draftPage->book->id);
+ $draftPage->html = $this->formatHtml($input['html']);
+ $draftPage->text = strip_tags($draftPage->html);
+ $draftPage->draft = false;
+
+ $draftPage->save();
+ $this->savePageRevision($draftPage, trans('entities.pages_initial_revision'));
+
+ return $draftPage;
+ }
+
+ /**
+ * Saves a page revision into the system.
+ * @param Page $page
+ * @param null|string $summary
+ * @return PageRevision
+ */
+ public function savePageRevision(Page $page, $summary = null)
+ {
+ $revision = $this->pageRevision->newInstance($page->toArray());
+ if (setting('app-editor') !== 'markdown') $revision->markdown = '';
+ $revision->page_id = $page->id;
+ $revision->slug = $page->slug;
+ $revision->book_slug = $page->book->slug;
+ $revision->created_by = user()->id;
+ $revision->created_at = $page->updated_at;
+ $revision->type = 'version';
+ $revision->summary = $summary;
+ $revision->save();
+
+ // Clear old revisions
+ if ($this->pageRevision->where('page_id', '=', $page->id)->count() > 50) {
+ $this->pageRevision->where('page_id', '=', $page->id)
+ ->orderBy('created_at', 'desc')->skip(50)->take(5)->delete();
+ }
+
+ return $revision;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Get a new draft page instance.
+ * @param Book $book
+ * @param Chapter|bool $chapter
+ * @return Page
+ */
+ public function getDraftPage(Book $book, $chapter = false)
+ {
+ $page = $this->page->newInstance();
+ $page->name = trans('entities.pages_initial_name');
+ $page->created_by = user()->id;
+ $page->updated_by = user()->id;
+ $page->draft = true;
+
+ if ($chapter) $page->chapter_id = $chapter->id;
+
+ $book->pages()->save($page);
+ $this->permissionService->buildJointPermissionsForEntity($page);
+ return $page;
+ }
+
+ /**
+ * Search for image usage within page content.
+ * @param $imageString
+ * @return mixed
+ */
+ public function searchForImage($imageString)
+ {
+ $pages = $this->entityQuery('page')->where('html', 'like', '%' . $imageString . '%')->get();
+ foreach ($pages as $page) {
+ $page->url = $page->getUrl();
+ $page->html = '';
+ $page->text = '';
+ }
+ return count($pages) > 0 ? $pages : false;
+ }
+
+ /**
+ * Parse the headers on the page to get a navigation menu
+ * @param Page $page
+ * @return array
+ */
+ public function getPageNav(Page $page)
+ {
+ if ($page->html == '') return null;
+ libxml_use_internal_errors(true);
+ $doc = new DOMDocument();
+ $doc->loadHTML(mb_convert_encoding($page->html, 'HTML-ENTITIES', 'UTF-8'));
+ $xPath = new DOMXPath($doc);
+ $headers = $xPath->query("//h1|//h2|//h3|//h4|//h5|//h6");
+
+ if (is_null($headers)) return null;
+
+ $tree = [];
+ foreach ($headers as $header) {
+ $text = $header->nodeValue;
+ $tree[] = [
+ 'nodeName' => strtolower($header->nodeName),
+ 'level' => intval(str_replace('h', '', $header->nodeName)),
+ 'link' => '#' . $header->getAttribute('id'),
+ 'text' => strlen($text) > 30 ? substr($text, 0, 27) . '...' : $text
+ ];
+ }
+ return $tree;
+ }
+
+ /**
+ * Updates a page with any fillable data and saves it into the database.
+ * @param Page $page
+ * @param int $book_id
+ * @param array $input
+ * @return Page
+ */
+ public function updatePage(Page $page, $book_id, $input)
+ {
+ // Hold the old details to compare later
+ $oldHtml = $page->html;
+ $oldName = $page->name;
+
+ // Prevent slug being updated if no name change
+ if ($page->name !== $input['name']) {
+ $page->slug = $this->findSuitableSlug('page', $input['name'], $page->id, $book_id);
+ }
+
+ // Save page tags if present
+ if (isset($input['tags'])) {
+ $this->tagRepo->saveTagsToEntity($page, $input['tags']);
+ }
+
+ // Update with new details
+ $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->updated_by = $userId;
+ $page->save();
+
+ // Remove all update drafts for this user & page.
+ $this->userUpdatePageDraftsQuery($page, $userId)->delete();
+
+ // Save a revision after updating
+ if ($oldHtml !== $input['html'] || $oldName !== $input['name'] || $input['summary'] !== null) {
+ $this->savePageRevision($page, $input['summary']);
+ }
+
+ return $page;
+ }
+
+ /**
+ * The base query for getting user update drafts.
+ * @param Page $page
+ * @param $userId
+ * @return mixed
+ */
+ protected function userUpdatePageDraftsQuery(Page $page, $userId)
+ {
+ return $this->pageRevision->where('created_by', '=', $userId)
+ ->where('type', 'update_draft')
+ ->where('page_id', '=', $page->id)
+ ->orderBy('created_at', 'desc');
+ }
+
+ /**
+ * Checks whether a user has a draft version of a particular page or not.
+ * @param Page $page
+ * @param $userId
+ * @return bool
+ */
+ public function hasUserGotPageDraft(Page $page, $userId)
+ {
+ return $this->userUpdatePageDraftsQuery($page, $userId)->count() > 0;
+ }
+
+ /**
+ * Get the latest updated draft revision for a particular page and user.
+ * @param Page $page
+ * @param $userId
+ * @return mixed
+ */
+ public function getUserPageDraft(Page $page, $userId)
+ {
+ return $this->userUpdatePageDraftsQuery($page, $userId)->first();
+ }
+
+ /**
+ * Get the notification message that informs the user that they are editing a draft page.
+ * @param PageRevision $draft
+ * @return string
+ */
+ 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;
+ return $message . "\n" . trans('entities.pages_draft_edited_notification');
+ }
+
+ /**
+ * Check if a page is being actively editing.
+ * Checks for edits since last page updated.
+ * Passing in a minuted range will check for edits
+ * within the last x minutes.
+ * @param Page $page
+ * @param null $minRange
+ * @return bool
+ */
+ public function isPageEditingActive(Page $page, $minRange = null)
+ {
+ $draftSearch = $this->activePageEditingQuery($page, $minRange);
+ return $draftSearch->count() > 0;
+ }
+
+ /**
+ * A query to check for active update drafts on a particular page.
+ * @param Page $page
+ * @param null $minRange
+ * @return mixed
+ */
+ protected function activePageEditingQuery(Page $page, $minRange = null)
+ {
+ $query = $this->pageRevision->where('type', '=', 'update_draft')
+ ->where('page_id', '=', $page->id)
+ ->where('updated_at', '>', $page->updated_at)
+ ->where('created_by', '!=', user()->id)
+ ->with('createdBy');
+
+ if ($minRange !== null) {
+ $query = $query->where('updated_at', '>=', Carbon::now()->subMinutes($minRange));
+ }
+
+ return $query;
+ }
+
+ /**
+ * Restores a revision's content back into a page.
+ * @param Page $page
+ * @param Book $book
+ * @param int $revisionId
+ * @return Page
+ */
+ public function restorePageRevision(Page $page, Book $book, $revisionId)
+ {
+ $this->savePageRevision($page);
+ $revision = $this->getById('page_revision', $revisionId);
+ $page->fill($revision->toArray());
+ $page->slug = $this->findSuitableSlug('page', $page->name, $page->id, $book->id);
+ $page->text = strip_tags($page->html);
+ $page->updated_by = user()->id;
+ $page->save();
+ return $page;
+ }
+
+
+ /**
+ * Save a page update draft.
+ * @param Page $page
+ * @param array $data
+ * @return PageRevision|Page
+ */
+ public function updatePageDraft(Page $page, $data = [])
+ {
+ // If the page itself is a draft simply update that
+ if ($page->draft) {
+ $page->fill($data);
+ if (isset($data['html'])) {
+ $page->text = strip_tags($data['html']);
+ }
+ $page->save();
+ return $page;
+ }
+
+ // Otherwise save the data to a revision
+ $userId = user()->id;
+ $drafts = $this->userUpdatePageDraftsQuery($page, $userId)->get();
+
+ if ($drafts->count() > 0) {
+ $draft = $drafts->first();
+ } else {
+ $draft = $this->pageRevision->newInstance();
+ $draft->page_id = $page->id;
+ $draft->slug = $page->slug;
+ $draft->book_slug = $page->book->slug;
+ $draft->created_by = $userId;
+ $draft->type = 'update_draft';
+ }
+
+ $draft->fill($data);
+ if (setting('app-editor') !== 'markdown') $draft->markdown = '';
+
+ $draft->save();
+ return $draft;
+ }
+
+ /**
+ * Get a notification message concerning the editing activity on a particular page.
+ * @param Page $page
+ * @param null $minRange
+ * @return string
+ */
+ public function getPageEditingActiveMessage(Page $page, $minRange = null)
+ {
+ $pageDraftEdits = $this->activePageEditingQuery($page, $minRange)->get();
+
+ $userMessage = $pageDraftEdits->count() > 1 ? trans('entities.pages_draft_edit_active.start_a', ['count' => $pageDraftEdits->count()]): trans('entities.pages_draft_edit_active.start_b', ['userName' => $pageDraftEdits->first()->createdBy->name]);
+ $timeMessage = $minRange === null ? trans('entities.pages_draft_edit_active.time_a') : trans('entities.pages_draft_edit_active.time_b', ['minCount'=>$minRange]);
+ return trans('entities.pages_draft_edit_active.message', ['start' => $userMessage, 'time' => $timeMessage]);
+ }
+
+ /**
+ * Change the page's parent to the given entity.
+ * @param Page $page
+ * @param Entity $parent
+ */
+ public function changePageParent(Page $page, Entity $parent)
+ {
+ $book = $parent->isA('book') ? $parent : $parent->book;
+ $page->chapter_id = $parent->isA('chapter') ? $parent->id : 0;
+ $page->save();
+ if ($page->book->id !== $book->id) {
+ $page = $this->changeBook('page', $book->id, $page);
+ }
+ $page->load('book');
+ $this->permissionService->buildJointPermissionsForEntity($book);
+ }
+
+ /**
+ * Destroy the provided book and all its child entities.
+ * @param Book $book
+ */
+ public function destroyBook(Book $book)
+ {
+ foreach ($book->pages as $page) {
+ $this->destroyPage($page);
+ }
+ foreach ($book->chapters as $chapter) {
+ $this->destroyChapter($chapter);
+ }
+ \Activity::removeEntity($book);
+ $book->views()->delete();
+ $book->permissions()->delete();
+ $this->permissionService->deleteJointPermissionsForEntity($book);
+ $book->delete();
+ }
+
+ /**
+ * Destroy a chapter and its relations.
+ * @param Chapter $chapter
+ */
+ public function destroyChapter(Chapter $chapter)
+ {
+ if (count($chapter->pages) > 0) {
+ foreach ($chapter->pages as $page) {
+ $page->chapter_id = 0;
+ $page->save();
+ }
+ }
+ \Activity::removeEntity($chapter);
+ $chapter->views()->delete();
+ $chapter->permissions()->delete();
+ $this->permissionService->deleteJointPermissionsForEntity($chapter);
+ $chapter->delete();
+ }
+
+ /**
+ * Destroy a given page along with its dependencies.
+ * @param Page $page
+ */
+ public function destroyPage(Page $page)
+ {
+ \Activity::removeEntity($page);
+ $page->views()->delete();
+ $page->tags()->delete();
+ $page->revisions()->delete();
+ $page->permissions()->delete();
+ $this->permissionService->deleteJointPermissionsForEntity($page);
+
+ // Delete Attached Files
+ $attachmentService = app(AttachmentService::class);
+ foreach ($page->attachments as $attachment) {
+ $attachmentService->deleteFile($attachment);
+ }
+
+ $page->delete();
+ }
+
}
diff --git a/app/Repos/PageRepo.php b/app/Repos/PageRepo.php
deleted file mode 100644
index 14463c12d..000000000
--- a/app/Repos/PageRepo.php
+++ /dev/null
@@ -1,664 +0,0 @@
-pageRevision = $pageRevision;
- $this->tagRepo = $tagRepo;
- parent::__construct();
- }
-
- /**
- * Base query for getting pages, Takes restrictions into account.
- * @param bool $allowDrafts
- * @return mixed
- */
- private function pageQuery($allowDrafts = false)
- {
- $query = $this->permissionService->enforcePageRestrictions($this->page, 'view');
- if (!$allowDrafts) {
- $query = $query->where('draft', '=', false);
- }
- return $query;
- }
-
- /**
- * Get a page via a specific ID.
- * @param $id
- * @param bool $allowDrafts
- * @return Page
- */
- public function getById($id, $allowDrafts = false)
- {
- return $this->pageQuery($allowDrafts)->findOrFail($id);
- }
-
- /**
- * Get a page identified by the given slug.
- * @param $slug
- * @param $bookId
- * @return Page
- * @throws NotFoundException
- */
- public function getBySlug($slug, $bookId)
- {
- $page = $this->pageQuery()->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first();
- if ($page === null) throw new NotFoundException(trans('errors.page_not_found'));
- return $page;
- }
-
- /**
- * Search through page revisions and retrieve
- * the last page in the current book that
- * has a slug equal to the one given.
- * @param $pageSlug
- * @param $bookSlug
- * @return null | Page
- */
- public function findPageUsingOldSlug($pageSlug, $bookSlug)
- {
- $revision = $this->pageRevision->where('slug', '=', $pageSlug)
- ->whereHas('page', function ($query) {
- $this->permissionService->enforcePageRestrictions($query);
- })
- ->where('type', '=', 'version')
- ->where('book_slug', '=', $bookSlug)->orderBy('created_at', 'desc')
- ->with('page')->first();
- return $revision !== null ? $revision->page : null;
- }
-
- /**
- * Get a new Page instance from the given input.
- * @param $input
- * @return Page
- */
- public function newFromInput($input)
- {
- $page = $this->page->fill($input);
- return $page;
- }
-
- /**
- * Count the pages with a particular slug within a book.
- * @param $slug
- * @param $bookId
- * @return mixed
- */
- public function countBySlug($slug, $bookId)
- {
- return $this->page->where('slug', '=', $slug)->where('book_id', '=', $bookId)->count();
- }
-
- /**
- * 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();
- $this->saveRevision($draftPage, trans('entities.pages_initial_revision'));
-
- return $draftPage;
- }
-
- /**
- * Get a new draft page instance.
- * @param Book $book
- * @param Chapter|bool $chapter
- * @return Page
- */
- public function getDraftPage(Book $book, $chapter = false)
- {
- $page = $this->page->newInstance();
- $page->name = trans('entities.pages_initial_name');
- $page->created_by = user()->id;
- $page->updated_by = user()->id;
- $page->draft = true;
-
- if ($chapter) $page->chapter_id = $chapter->id;
-
- $book->pages()->save($page);
- $this->permissionService->buildJointPermissionsForEntity($page);
- return $page;
- }
-
- /**
- * Parse te headers on the page to get a navigation menu
- * @param Page $page
- * @return array
- */
- public function getPageNav(Page $page)
- {
- if ($page->html == '') return null;
- libxml_use_internal_errors(true);
- $doc = new DOMDocument();
- $doc->loadHTML(mb_convert_encoding($page->html, 'HTML-ENTITIES', 'UTF-8'));
- $xPath = new DOMXPath($doc);
- $headers = $xPath->query("//h1|//h2|//h3|//h4|//h5|//h6");
-
- if (is_null($headers)) return null;
-
- $tree = [];
- foreach ($headers as $header) {
- $text = $header->nodeValue;
- $tree[] = [
- 'nodeName' => strtolower($header->nodeName),
- 'level' => intval(str_replace('h', '', $header->nodeName)),
- 'link' => '#' . $header->getAttribute('id'),
- 'text' => strlen($text) > 30 ? substr($text, 0, 27) . '...' : $text
- ];
- }
- return $tree;
- }
-
- /**
- * 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;
- }
-
-
- /**
- * Gets pages by a search term.
- * Highlights page content for showing in results.
- * @param string $term
- * @param array $whereTerms
- * @param int $count
- * @param array $paginationAppends
- * @return mixed
- */
- public function getBySearch($term, $whereTerms = [], $count = 20, $paginationAppends = [])
- {
- $terms = $this->prepareSearchTerms($term);
- $pageQuery = $this->permissionService->enforcePageRestrictions($this->page->fullTextSearchQuery(['name', 'text'], $terms, $whereTerms));
- $pageQuery = $this->addAdvancedSearchQueries($pageQuery, $term);
- $pages = $pageQuery->paginate($count)->appends($paginationAppends);
-
- // Add highlights to page text.
- $words = join('|', explode(' ', preg_quote(trim($term), '/')));
- //lookahead/behind assertions ensures cut between words
- $s = '\s\x00-/:-@\[-`{-~'; //character set for start/end of words
-
- foreach ($pages as $page) {
- preg_match_all('#(?<=[' . $s . ']).{1,30}((' . $words . ').{1,30})+(?=[' . $s . '])#uis', $page->text, $matches, PREG_SET_ORDER);
- //delimiter between occurrences
- $results = [];
- foreach ($matches as $line) {
- $results[] = htmlspecialchars($line[0], 0, 'UTF-8');
- }
- $matchLimit = 6;
- if (count($results) > $matchLimit) {
- $results = array_slice($results, 0, $matchLimit);
- }
- $result = join('... ', $results);
-
- //highlight
- $result = preg_replace('#' . $words . '#iu', "\$0", $result);
- if (strlen($result) < 5) {
- $result = $page->getExcerpt(80);
- }
- $page->searchSnippet = $result;
- }
- return $pages;
- }
-
- /**
- * Search for image usage.
- * @param $imageString
- * @return mixed
- */
- public function searchForImage($imageString)
- {
- $pages = $this->pageQuery()->where('html', 'like', '%' . $imageString . '%')->get();
- foreach ($pages as $page) {
- $page->url = $page->getUrl();
- $page->html = '';
- $page->text = '';
- }
- return count($pages) > 0 ? $pages : false;
- }
-
- /**
- * Updates a page with any fillable data and saves it into the database.
- * @param Page $page
- * @param int $book_id
- * @param string $input
- * @return Page
- */
- public function updatePage(Page $page, $book_id, $input)
- {
- // Hold the old details to compare later
- $oldHtml = $page->html;
- $oldName = $page->name;
-
- // Prevent slug being updated if no name change
- if ($page->name !== $input['name']) {
- $page->slug = $this->findSuitableSlug($input['name'], $book_id, $page->id);
- }
-
- // Save page tags if present
- if (isset($input['tags'])) {
- $this->tagRepo->saveTagsToEntity($page, $input['tags']);
- }
-
- // Update with new details
- $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->updated_by = $userId;
- $page->save();
-
- // Remove all update drafts for this user & page.
- $this->userUpdateDraftsQuery($page, $userId)->delete();
-
- // Save a revision after updating
- if ($oldHtml !== $input['html'] || $oldName !== $input['name'] || $input['summary'] !== null) {
- $this->saveRevision($page, $input['summary']);
- }
-
- return $page;
- }
-
- /**
- * Restores a revision's content back into a page.
- * @param Page $page
- * @param Book $book
- * @param int $revisionId
- * @return Page
- */
- public function restoreRevision(Page $page, Book $book, $revisionId)
- {
- $this->saveRevision($page);
- $revision = $this->getRevisionById($revisionId);
- $page->fill($revision->toArray());
- $page->slug = $this->findSuitableSlug($page->name, $book->id, $page->id);
- $page->text = strip_tags($page->html);
- $page->updated_by = user()->id;
- $page->save();
- return $page;
- }
-
- /**
- * Saves a page revision into the system.
- * @param Page $page
- * @param null|string $summary
- * @return $this
- */
- public function saveRevision(Page $page, $summary = null)
- {
- $revision = $this->pageRevision->newInstance($page->toArray());
- if (setting('app-editor') !== 'markdown') $revision->markdown = '';
- $revision->page_id = $page->id;
- $revision->slug = $page->slug;
- $revision->book_slug = $page->book->slug;
- $revision->created_by = user()->id;
- $revision->created_at = $page->updated_at;
- $revision->type = 'version';
- $revision->summary = $summary;
- $revision->save();
-
- // Clear old revisions
- if ($this->pageRevision->where('page_id', '=', $page->id)->count() > 50) {
- $this->pageRevision->where('page_id', '=', $page->id)
- ->orderBy('created_at', 'desc')->skip(50)->take(5)->delete();
- }
-
- return $revision;
- }
-
- /**
- * Save a page update draft.
- * @param Page $page
- * @param array $data
- * @return PageRevision
- */
- public function saveUpdateDraft(Page $page, $data = [])
- {
- $userId = user()->id;
- $drafts = $this->userUpdateDraftsQuery($page, $userId)->get();
-
- if ($drafts->count() > 0) {
- $draft = $drafts->first();
- } else {
- $draft = $this->pageRevision->newInstance();
- $draft->page_id = $page->id;
- $draft->slug = $page->slug;
- $draft->book_slug = $page->book->slug;
- $draft->created_by = $userId;
- $draft->type = 'update_draft';
- }
-
- $draft->fill($data);
- if (setting('app-editor') !== 'markdown') $draft->markdown = '';
-
- $draft->save();
- return $draft;
- }
-
- /**
- * Update a draft page.
- * @param Page $page
- * @param array $data
- * @return Page
- */
- public function updateDraftPage(Page $page, $data = [])
- {
- $page->fill($data);
-
- if (isset($data['html'])) {
- $page->text = strip_tags($data['html']);
- }
-
- $page->save();
- return $page;
- }
-
- /**
- * The base query for getting user update drafts.
- * @param Page $page
- * @param $userId
- * @return mixed
- */
- private function userUpdateDraftsQuery(Page $page, $userId)
- {
- return $this->pageRevision->where('created_by', '=', $userId)
- ->where('type', 'update_draft')
- ->where('page_id', '=', $page->id)
- ->orderBy('created_at', 'desc');
- }
-
- /**
- * Checks whether a user has a draft version of a particular page or not.
- * @param Page $page
- * @param $userId
- * @return bool
- */
- public function hasUserGotPageDraft(Page $page, $userId)
- {
- return $this->userUpdateDraftsQuery($page, $userId)->count() > 0;
- }
-
- /**
- * Get the latest updated draft revision for a particular page and user.
- * @param Page $page
- * @param $userId
- * @return mixed
- */
- public function getUserPageDraft(Page $page, $userId)
- {
- return $this->userUpdateDraftsQuery($page, $userId)->first();
- }
-
- /**
- * Get the notification message that informs the user that they are editing a draft page.
- * @param PageRevision $draft
- * @return string
- */
- 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;
- return $message . "\n" . trans('entities.pages_draft_edited_notification');
- }
-
- /**
- * Check if a page is being actively editing.
- * Checks for edits since last page updated.
- * Passing in a minuted range will check for edits
- * within the last x minutes.
- * @param Page $page
- * @param null $minRange
- * @return bool
- */
- public function isPageEditingActive(Page $page, $minRange = null)
- {
- $draftSearch = $this->activePageEditingQuery($page, $minRange);
- return $draftSearch->count() > 0;
- }
-
- /**
- * Get a notification message concerning the editing activity on
- * a particular page.
- * @param Page $page
- * @param null $minRange
- * @return string
- */
- public function getPageEditingActiveMessage(Page $page, $minRange = null)
- {
- $pageDraftEdits = $this->activePageEditingQuery($page, $minRange)->get();
-
- $userMessage = $pageDraftEdits->count() > 1 ? trans('entities.pages_draft_edit_active.start_a', ['count' => $pageDraftEdits->count()]): trans('entities.pages_draft_edit_active.start_b', ['userName' => $pageDraftEdits->first()->createdBy->name]);
- $timeMessage = $minRange === null ? trans('entities.pages_draft_edit_active.time_a') : trans('entities.pages_draft_edit_active.time_b', ['minCount'=>$minRange]);
- return trans('entities.pages_draft_edit_active.message', ['start' => $userMessage, 'time' => $timeMessage]);
- }
-
- /**
- * A query to check for active update drafts on a particular page.
- * @param Page $page
- * @param null $minRange
- * @return mixed
- */
- private function activePageEditingQuery(Page $page, $minRange = null)
- {
- $query = $this->pageRevision->where('type', '=', 'update_draft')
- ->where('page_id', '=', $page->id)
- ->where('updated_at', '>', $page->updated_at)
- ->where('created_by', '!=', user()->id)
- ->with('createdBy');
-
- if ($minRange !== null) {
- $query = $query->where('updated_at', '>=', Carbon::now()->subMinutes($minRange));
- }
-
- return $query;
- }
-
- /**
- * Gets a single revision via it's id.
- * @param $id
- * @return PageRevision
- */
- public function getRevisionById($id)
- {
- return $this->pageRevision->findOrFail($id);
- }
-
- /**
- * Checks if a slug exists within a book already.
- * @param $slug
- * @param $bookId
- * @param bool|false $currentId
- * @return bool
- */
- public function doesSlugExist($slug, $bookId, $currentId = false)
- {
- $query = $this->page->where('slug', '=', $slug)->where('book_id', '=', $bookId);
- if ($currentId) $query = $query->where('id', '!=', $currentId);
- return $query->count() > 0;
- }
-
- /**
- * Changes the related book for the specified page.
- * Changes the book id of any relations to the page that store the book id.
- * @param int $bookId
- * @param Page $page
- * @return Page
- */
- public function changeBook($bookId, Page $page)
- {
- $page->book_id = $bookId;
- foreach ($page->activity as $activity) {
- $activity->book_id = $bookId;
- $activity->save();
- }
- $page->slug = $this->findSuitableSlug($page->name, $bookId, $page->id);
- $page->save();
- return $page;
- }
-
-
- /**
- * Change the page's parent to the given entity.
- * @param Page $page
- * @param Entity $parent
- */
- public function changePageParent(Page $page, Entity $parent)
- {
- $book = $parent->isA('book') ? $parent : $parent->book;
- $page->chapter_id = $parent->isA('chapter') ? $parent->id : 0;
- $page->save();
- $page = $this->changeBook($book->id, $page);
- $page->load('book');
- $this->permissionService->buildJointPermissionsForEntity($book);
- }
-
- /**
- * Gets a suitable slug for the resource
- * @param string $name
- * @param int $bookId
- * @param bool|false $currentId
- * @return string
- */
- public function findSuitableSlug($name, $bookId, $currentId = false)
- {
- $slug = $this->nameToSlug($name);
- while ($this->doesSlugExist($slug, $bookId, $currentId)) {
- $slug .= '-' . substr(md5(rand(1, 500)), 0, 3);
- }
- return $slug;
- }
-
- /**
- * Destroy a given page along with its dependencies.
- * @param $page
- */
- public function destroy(Page $page)
- {
- Activity::removeEntity($page);
- $page->views()->delete();
- $page->tags()->delete();
- $page->revisions()->delete();
- $page->permissions()->delete();
- $this->permissionService->deleteJointPermissionsForEntity($page);
-
- // Delete AttachedFiles
- $attachmentService = app(AttachmentService::class);
- foreach ($page->attachments as $attachment) {
- $attachmentService->deleteFile($attachment);
- }
-
- $page->delete();
- }
-
- /**
- * Get the latest pages added to the system.
- * @param $count
- * @return mixed
- */
- public function getRecentlyCreatedPaginated($count = 20)
- {
- return $this->pageQuery()->orderBy('created_at', 'desc')->paginate($count);
- }
-
- /**
- * Get the latest pages added to the system.
- * @param $count
- * @return mixed
- */
- public function getRecentlyUpdatedPaginated($count = 20)
- {
- return $this->pageQuery()->orderBy('updated_at', 'desc')->paginate($count);
- }
-
-}
diff --git a/app/Repos/TagRepo.php b/app/Repos/TagRepo.php
index 6e422c4f4..c6350db1a 100644
--- a/app/Repos/TagRepo.php
+++ b/app/Repos/TagRepo.php
@@ -38,7 +38,7 @@ class TagRepo
{
$entityInstance = $this->entity->getEntityInstance($entityType);
$searchQuery = $entityInstance->where('id', '=', $entityId)->with('tags');
- $searchQuery = $this->permissionService->enforceEntityRestrictions($searchQuery, $action);
+ $searchQuery = $this->permissionService->enforceEntityRestrictions($entityType, $searchQuery, $action);
return $searchQuery->first();
}
diff --git a/app/Repos/UserRepo.php b/app/Repos/UserRepo.php
index 22c92f3ce..c3546a442 100644
--- a/app/Repos/UserRepo.php
+++ b/app/Repos/UserRepo.php
@@ -168,13 +168,13 @@ class UserRepo
public function getRecentlyCreated(User $user, $count = 20)
{
return [
- 'pages' => $this->entityRepo->getRecentlyCreatedPages($count, 0, function ($query) use ($user) {
+ 'pages' => $this->entityRepo->getRecentlyCreated('page', $count, 0, function ($query) use ($user) {
$query->where('created_by', '=', $user->id);
}),
- 'chapters' => $this->entityRepo->getRecentlyCreatedChapters($count, 0, function ($query) use ($user) {
+ 'chapters' => $this->entityRepo->getRecentlyCreated('chapter', $count, 0, function ($query) use ($user) {
$query->where('created_by', '=', $user->id);
}),
- 'books' => $this->entityRepo->getRecentlyCreatedBooks($count, 0, function ($query) use ($user) {
+ 'books' => $this->entityRepo->getRecentlyCreated('book', $count, 0, function ($query) use ($user) {
$query->where('created_by', '=', $user->id);
})
];
diff --git a/app/Services/ActivityService.php b/app/Services/ActivityService.php
index e41036238..2368ba10a 100644
--- a/app/Services/ActivityService.php
+++ b/app/Services/ActivityService.php
@@ -114,7 +114,7 @@ class ActivityService
$activity = $this->permissionService
->filterRestrictedEntityRelations($query, 'activities', 'entity_id', 'entity_type')
- ->orderBy('created_at', 'desc')->skip($count * $page)->take($count)->get();
+ ->orderBy('created_at', 'desc')->with(['entity', 'user.avatar'])->skip($count * $page)->take($count)->get();
return $this->filterSimilar($activity);
}
diff --git a/app/Services/PermissionService.php b/app/Services/PermissionService.php
index bb78f0b0a..467bf95da 100644
--- a/app/Services/PermissionService.php
+++ b/app/Services/PermissionService.php
@@ -8,8 +8,9 @@ use BookStack\Ownable;
use BookStack\Page;
use BookStack\Role;
use BookStack\User;
+use Illuminate\Database\Connection;
+use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
-use Illuminate\Support\Facades\Log;
class PermissionService
{
@@ -23,6 +24,8 @@ class PermissionService
public $chapter;
public $page;
+ protected $db;
+
protected $jointPermission;
protected $role;
@@ -31,18 +34,21 @@ class PermissionService
/**
* PermissionService constructor.
* @param JointPermission $jointPermission
+ * @param Connection $db
* @param Book $book
* @param Chapter $chapter
* @param Page $page
* @param Role $role
*/
- public function __construct(JointPermission $jointPermission, Book $book, Chapter $chapter, Page $page, Role $role)
+ public function __construct(JointPermission $jointPermission, Connection $db, Book $book, Chapter $chapter, Page $page, Role $role)
{
+ $this->db = $db;
$this->jointPermission = $jointPermission;
$this->role = $role;
$this->book = $book;
$this->chapter = $chapter;
$this->page = $page;
+ // TODO - Update so admin still goes through filters
}
/**
@@ -302,6 +308,10 @@ class PermissionService
$explodedAction = explode('-', $action);
$restrictionAction = end($explodedAction);
+ if ($role->system_name === 'admin') {
+ return $this->createJointPermissionDataArray($entity, $role, $action, true, true);
+ }
+
if ($entity->isA('book')) {
if (!$entity->restricted) {
@@ -461,61 +471,77 @@ class PermissionService
return $q;
}
- /**
- * Add restrictions for a page query
- * @param $query
- * @param string $action
- * @return mixed
- */
- public function enforcePageRestrictions($query, $action = 'view')
- {
- // Prevent drafts being visible to others.
- $query = $query->where(function ($query) {
- $query->where('draft', '=', false);
- if ($this->currentUser()) {
- $query->orWhere(function ($query) {
- $query->where('draft', '=', true)->where('created_by', '=', $this->currentUser()->id);
- });
- }
- });
+ public function bookChildrenQuery($book_id, $filterDrafts = false) {
- return $this->enforceEntityRestrictions($query, $action);
- }
+ // Draft setup
+ $params = [
+ 'userId' => $this->currentUser()->id,
+ 'bookIdPage' => $book_id,
+ 'bookIdChapter' => $book_id
+ ];
+ if (!$filterDrafts) {
+ $params['userIdDrafts'] = $this->currentUser()->id;
+ }
+ // Role setup
+ $userRoles = $this->getRoles();
+ $roleBindings = [];
+ $roleValues = [];
+ foreach ($userRoles as $index => $roleId) {
+ $roleBindings[':role'.$index] = $roleId;
+ $roleValues['role'.$index] = $roleId;
+ }
+ // TODO - Clean this up, Maybe extract into a nice class for doing these kind of manual things
+ // Something which will handle the above role crap in a nice clean way
+ $roleBindingString = implode(',', array_keys($roleBindings));
+ $query = "SELECT * from (
+(SELECT 'Bookstack\\\Page' as entity_type, id, slug, name, text, '' as description, book_id, priority, chapter_id, draft FROM {$this->page->getTable()}
+ where book_id = :bookIdPage AND ". ($filterDrafts ? '(draft = 0)' : '(draft = 0 OR (draft = 1 AND created_by = :userIdDrafts))') .")
+UNION
+(SELECT 'Bookstack\\\Chapter' as entity_type, id, slug, name, '' as text, description, book_id, priority, 0 as chapter_id, 0 as draft FROM {$this->chapter->getTable()} WHERE book_id = :bookIdChapter)
+) as U WHERE (
+ SELECT COUNT(*) FROM {$this->jointPermission->getTable()} jp
+ WHERE
+ jp.entity_id=U.id AND
+ jp.entity_type=U.entity_type AND
+ jp.action = 'view' AND
+ jp.role_id IN ({$roleBindingString}) AND
+ (
+ jp.has_permission = 1 OR
+ (jp.has_permission_own = 1 AND jp.created_by = :userId)
+ )
+) > 0
+ORDER BY draft desc, priority asc";
- /**
- * Add on permission restrictions to a chapter query.
- * @param $query
- * @param string $action
- * @return mixed
- */
- public function enforceChapterRestrictions($query, $action = 'view')
- {
- return $this->enforceEntityRestrictions($query, $action);
- }
-
- /**
- * Add restrictions to a book query.
- * @param $query
- * @param string $action
- * @return mixed
- */
- public function enforceBookRestrictions($query, $action = 'view')
- {
- return $this->enforceEntityRestrictions($query, $action);
+ $this->clean();
+ return $this->db->select($query, array_replace($roleValues, $params));
}
/**
* Add restrictions for a generic entity
- * @param $query
+ * @param string $entityType
+ * @param Builder|Entity $query
* @param string $action
* @return mixed
*/
- public function enforceEntityRestrictions($query, $action = 'view')
+ public function enforceEntityRestrictions($entityType, $query, $action = 'view')
{
+ if (strtolower($entityType) === 'page') {
+ // Prevent drafts being visible to others.
+ $query = $query->where(function ($query) {
+ $query->where('draft', '=', false);
+ if ($this->currentUser()) {
+ $query->orWhere(function ($query) {
+ $query->where('draft', '=', true)->where('created_by', '=', $this->currentUser()->id);
+ });
+ }
+ });
+ }
+
if ($this->isAdmin()) {
$this->clean();
return $query;
}
+
$this->currentAction = $action;
return $this->entityRestrictionQuery($query);
}
@@ -601,7 +627,7 @@ class PermissionService
private function isAdmin()
{
if ($this->isAdminUser === null) {
- $this->isAdminUser = ($this->currentUser()->id !== null) ? $this->currentUser()->hasRole('admin') : false;
+ $this->isAdminUser = ($this->currentUser()->id !== null) ? $this->currentUser()->hasSystemRole('admin') : false;
}
return $this->isAdminUser;
diff --git a/app/Services/ViewService.php b/app/Services/ViewService.php
index 1a9ee5f70..3285745ce 100644
--- a/app/Services/ViewService.php
+++ b/app/Services/ViewService.php
@@ -5,9 +5,7 @@ use BookStack\View;
class ViewService
{
-
protected $view;
- protected $user;
protected $permissionService;
/**
@@ -18,7 +16,6 @@ class ViewService
public function __construct(View $view, PermissionService $permissionService)
{
$this->view = $view;
- $this->user = user();
$this->permissionService = $permissionService;
}
@@ -29,8 +26,9 @@ class ViewService
*/
public function add(Entity $entity)
{
- if ($this->user === null) return 0;
- $view = $entity->views()->where('user_id', '=', $this->user->id)->first();
+ $user = user();
+ if ($user === null || $user->isDefault()) return 0;
+ $view = $entity->views()->where('user_id', '=', $user->id)->first();
// Add view if model exists
if ($view) {
$view->increment('views');
@@ -39,7 +37,7 @@ class ViewService
// Otherwise create new view count
$entity->views()->save($this->view->create([
- 'user_id' => $this->user->id,
+ 'user_id' => $user->id,
'views' => 1
]));
@@ -78,13 +76,14 @@ class ViewService
*/
public function getUserRecentlyViewed($count = 10, $page = 0, $filterModel = false)
{
- if ($this->user === null) return collect();
+ $user = user();
+ if ($user === null || $user->isDefault()) return collect();
$query = $this->permissionService
->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type');
if ($filterModel) $query = $query->where('viewable_type', '=', get_class($filterModel));
- $query = $query->where('user_id', '=', user()->id);
+ $query = $query->where('user_id', '=', $user->id);
$viewables = $query->with('viewable')->orderBy('updated_at', 'desc')
->skip($count * $page)->take($count)->get()->pluck('viewable');
diff --git a/app/User.php b/app/User.php
index 09b189cbb..b5bb221e8 100644
--- a/app/User.php
+++ b/app/User.php
@@ -74,6 +74,16 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
return $this->roles->pluck('name')->contains($role);
}
+ /**
+ * Check if the user has a role.
+ * @param $role
+ * @return mixed
+ */
+ public function hasSystemRole($role)
+ {
+ return $this->roles->pluck('system_name')->contains('admin');
+ }
+
/**
* Get all permissions belonging to a the current user.
* @param bool $cache
diff --git a/tests/Entity/EntityTest.php b/tests/Entity/EntityTest.php
index 79e574cbd..9fd4eb9ad 100644
--- a/tests/Entity/EntityTest.php
+++ b/tests/Entity/EntityTest.php
@@ -168,7 +168,7 @@ class EntityTest extends TestCase
$entities = $this->createEntityChainBelongingToUser($creator, $updater);
$this->actingAs($creator);
app('BookStack\Repos\UserRepo')->destroy($creator);
- app('BookStack\Repos\PageRepo')->saveRevision($entities['page']);
+ app('BookStack\Repos\EntityRepo')->savePageRevision($entities['page']);
$this->checkEntitiesViewable($entities);
}
@@ -181,7 +181,7 @@ class EntityTest extends TestCase
$entities = $this->createEntityChainBelongingToUser($creator, $updater);
$this->actingAs($updater);
app('BookStack\Repos\UserRepo')->destroy($updater);
- app('BookStack\Repos\PageRepo')->saveRevision($entities['page']);
+ app('BookStack\Repos\EntityRepo')->savePageRevision($entities['page']);
$this->checkEntitiesViewable($entities);
}
diff --git a/tests/Entity/PageDraftTest.php b/tests/Entity/PageDraftTest.php
index 1a46e30bc..233f300ee 100644
--- a/tests/Entity/PageDraftTest.php
+++ b/tests/Entity/PageDraftTest.php
@@ -4,13 +4,13 @@
class PageDraftTest extends TestCase
{
protected $page;
- protected $pageRepo;
+ protected $entityRepo;
public function setUp()
{
parent::setUp();
$this->page = \BookStack\Page::first();
- $this->pageRepo = app('\BookStack\Repos\PageRepo');
+ $this->entityRepo = app('\BookStack\Repos\EntityRepo');
}
public function test_draft_content_shows_if_available()
@@ -20,7 +20,7 @@ class PageDraftTest extends TestCase
->dontSeeInField('html', $addedContent);
$newContent = $this->page->html . $addedContent;
- $this->pageRepo->saveUpdateDraft($this->page, ['html' => $newContent]);
+ $this->entityRepo->updatePageDraft($this->page, ['html' => $newContent]);
$this->asAdmin()->visit($this->page->getUrl() . '/edit')
->seeInField('html', $newContent);
}
@@ -33,7 +33,7 @@ class PageDraftTest extends TestCase
$newContent = $this->page->html . $addedContent;
$newUser = $this->getEditor();
- $this->pageRepo->saveUpdateDraft($this->page, ['html' => $newContent]);
+ $this->entityRepo->updatePageDraft($this->page, ['html' => $newContent]);
$this->actingAs($newUser)->visit($this->page->getUrl() . '/edit')
->dontSeeInField('html', $newContent);
}
@@ -41,7 +41,7 @@ class PageDraftTest extends TestCase
public function test_alert_message_shows_if_editing_draft()
{
$this->asAdmin();
- $this->pageRepo->saveUpdateDraft($this->page, ['html' => 'test content']);
+ $this->entityRepo->updatePageDraft($this->page, ['html' => 'test content']);
$this->asAdmin()->visit($this->page->getUrl() . '/edit')
->see('You are currently editing a draft');
}
@@ -55,7 +55,7 @@ class PageDraftTest extends TestCase
$newContent = $this->page->html . $addedContent;
$newUser = $this->getEditor();
- $this->pageRepo->saveUpdateDraft($this->page, ['html' => $newContent]);
+ $this->entityRepo->updatePageDraft($this->page, ['html' => $newContent]);
$this->actingAs($newUser)
->visit($this->page->getUrl() . '/edit')
diff --git a/tests/Entity/SortTest.php b/tests/Entity/SortTest.php
index d78b94479..4784297a2 100644
--- a/tests/Entity/SortTest.php
+++ b/tests/Entity/SortTest.php
@@ -13,8 +13,8 @@ class SortTest extends TestCase
public function test_drafts_do_not_show_up()
{
$this->asAdmin();
- $pageRepo = app('\BookStack\Repos\PageRepo');
- $draft = $pageRepo->getDraftPage($this->book);
+ $entityRepo = app('\BookStack\Repos\EntityRepo');
+ $draft = $entityRepo->getDraftPage($this->book);
$this->visit($this->book->getUrl())
->see($draft->name)
diff --git a/tests/ImageTest.php b/tests/ImageTest.php
index 031517cdb..9da12d36b 100644
--- a/tests/ImageTest.php
+++ b/tests/ImageTest.php
@@ -90,7 +90,7 @@ class ImageTest extends TestCase
'type' => 'gallery'
]);
- $this->assertFalse(file_exists(public_path($relPath)), 'Uploaded image has been deleted');
+ $this->assertFalse(file_exists(public_path($relPath)), 'Uploaded image has not been deleted as expected');
}
}
\ No newline at end of file
diff --git a/tests/Permissions/RestrictionsTest.php b/tests/Permissions/RestrictionsTest.php
index d3830cff7..cddd3206a 100644
--- a/tests/Permissions/RestrictionsTest.php
+++ b/tests/Permissions/RestrictionsTest.php
@@ -65,9 +65,9 @@ class RestrictionsTest extends TestCase
$this->forceVisit($bookUrl)
->see('Book not found');
$this->forceVisit($bookPage->getUrl())
- ->see('Book not found');
+ ->see('Page not found');
$this->forceVisit($bookChapter->getUrl())
- ->see('Book not found');
+ ->see('Chapter not found');
$this->setEntityRestrictions($book, ['view']);