Dan Brown
Committed by GitHub

Merge pull request #262 from BookStackApp/entity_repo_refactor

Entity repo refactor
......@@ -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);
}
/**
......
......@@ -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);
}
}
......
......@@ -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) {
......
<?php namespace BookStack\Http\Controllers;
use Activity;
use BookStack\Repos\EntityRepo;
use BookStack\Repos\UserRepo;
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 Views;
class BookController extends Controller
{
protected $bookRepo;
protected $pageRepo;
protected $chapterRepo;
protected $entityRepo;
protected $userRepo;
/**
* BookController constructor.
* @param BookRepo $bookRepo
* @param PageRepo $pageRepo
* @param ChapterRepo $chapterRepo
* @param EntityRepo $entityRepo
* @param UserRepo $userRepo
*/
public function __construct(BookRepo $bookRepo, PageRepo $pageRepo, ChapterRepo $chapterRepo, UserRepo $userRepo)
public function __construct(EntityRepo $entityRepo, UserRepo $userRepo)
{
$this->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());
}
......
<?php namespace BookStack\Http\Controllers;
use Activity;
use BookStack\Repos\EntityRepo;
use BookStack\Repos\UserRepo;
use Illuminate\Http\Request;
use BookStack\Repos\BookRepo;
use BookStack\Repos\ChapterRepo;
use Illuminate\Http\Response;
use Views;
class ChapterController extends Controller
{
protected $bookRepo;
protected $chapterRepo;
protected $userRepo;
protected $entityRepo;
/**
* ChapterController constructor.
* @param BookRepo $bookRepo
* @param ChapterRepo $chapterRepo
* @param EntityRepo $entityRepo
* @param UserRepo $userRepo
*/
public function __construct(BookRepo $bookRepo, ChapterRepo $chapterRepo, UserRepo $userRepo)
public function __construct(EntityRepo $entityRepo, UserRepo $userRepo)
{
$this->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());
}
......
......@@ -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,
......
<?php namespace BookStack\Http\Controllers;
use BookStack\Exceptions\ImageUploadException;
use BookStack\Repos\EntityRepo;
use BookStack\Repos\ImageRepo;
use Illuminate\Filesystem\Filesystem as File;
use Illuminate\Http\Request;
......@@ -150,12 +151,12 @@ class ImageController extends Controller
/**
* Deletes an image and all thumbnail/image files
* @param PageRepo $pageRepo
* @param EntityRepo $entityRepo
* @param Request $request
* @param int $id
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(PageRepo $pageRepo, Request $request, $id)
public function destroy(EntityRepo $entityRepo, Request $request, $id)
{
$image = $this->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);
}
......
<?php namespace BookStack\Http\Controllers;
use BookStack\Repos\EntityRepo;
use BookStack\Services\ViewService;
use Illuminate\Http\Request;
use BookStack\Repos\BookRepo;
use BookStack\Repos\ChapterRepo;
use BookStack\Repos\PageRepo;
class SearchController extends Controller
{
protected $pageRepo;
protected $bookRepo;
protected $chapterRepo;
protected $entityRepo;
protected $viewService;
/**
* SearchController constructor.
* @param PageRepo $pageRepo
* @param BookRepo $bookRepo
* @param ChapterRepo $chapterRepo
* @param EntityRepo $entityRepo
* @param ViewService $viewService
*/
public function __construct(PageRepo $pageRepo, BookRepo $bookRepo, ChapterRepo $chapterRepo, ViewService $viewService)
public function __construct(EntityRepo $entityRepo, ViewService $viewService)
{
$this->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) {
......
......@@ -4,8 +4,6 @@ namespace BookStack\Http\Middleware;
use Closure;
use Illuminate\Contracts\Auth\Guard;
use BookStack\Exceptions\UserRegistrationException;
use Setting;
class Authenticate
{
......
......@@ -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
......
<?php namespace BookStack\Repos;
use Alpha\B;
use BookStack\Exceptions\NotFoundException;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Str;
use BookStack\Book;
use Views;
class BookRepo extends EntityRepo
{
protected $pageRepo;
protected $chapterRepo;
/**
* BookRepo constructor.
* @param PageRepo $pageRepo
* @param ChapterRepo $chapterRepo
*/
public function __construct(PageRepo $pageRepo, ChapterRepo $chapterRepo)
{
$this->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', "<span class=\"highlight\">\$0</span>", $book->getExcerpt(100));
$book->searchSnippet = $result;
}
return $books;
}
}
\ No newline at end of file
<?php namespace BookStack\Repos;
use Activity;
use BookStack\Book;
use BookStack\Exceptions\NotFoundException;
use Illuminate\Support\Str;
use BookStack\Chapter;
class ChapterRepo extends EntityRepo
{
protected $pageRepo;
/**
* ChapterRepo constructor.
* @param $pageRepo
*/
public function __construct(PageRepo $pageRepo)
{
$this->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', "<span class=\"highlight\">\$0</span>", $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
......@@ -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();
}
......
......@@ -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);
})
];
......
......@@ -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);
}
......
......@@ -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);
}
/**
* 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);
}
// 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 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;
......
......@@ -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');
......
......@@ -75,6 +75,16 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
}
/**
* 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
* @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
......
......@@ -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);
}
......
......@@ -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')
......
......@@ -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)
......
......@@ -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
......
......@@ -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']);
......