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']);