mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-08-28 13:12:05 +00:00
Merge branch 'master' into nwalke-update_site_color
This commit is contained in:
commit
0774ecc89c
@ -12,8 +12,17 @@ DB_PASSWORD=database_user_password
|
|||||||
# Cache and session
|
# Cache and session
|
||||||
CACHE_DRIVER=file
|
CACHE_DRIVER=file
|
||||||
SESSION_DRIVER=file
|
SESSION_DRIVER=file
|
||||||
|
# If using Memcached, comment the above and uncomment these
|
||||||
|
#CACHE_DRIVER=memcached
|
||||||
|
#SESSION_DRIVER=memcached
|
||||||
QUEUE_DRIVER=sync
|
QUEUE_DRIVER=sync
|
||||||
|
|
||||||
|
# Memcached settings
|
||||||
|
# If using a UNIX socket path for the host, set the port to 0
|
||||||
|
# This follows the following format: HOST:PORT:WEIGHT
|
||||||
|
# For multiple servers separate with a comma
|
||||||
|
MEMCACHED_SERVERS=127.0.0.1:11211:100
|
||||||
|
|
||||||
# Storage
|
# Storage
|
||||||
STORAGE_TYPE=local
|
STORAGE_TYPE=local
|
||||||
# Amazon S3 Config
|
# Amazon S3 Config
|
||||||
|
@ -15,15 +15,11 @@ class Activity extends Model
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the entity for this activity.
|
* Get the entity for this activity.
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
public function entity()
|
public function entity()
|
||||||
{
|
{
|
||||||
if ($this->entity_id) {
|
if ($this->entity_type === '') $this->entity_type = null;
|
||||||
return $this->morphTo('entity')->first();
|
return $this->morphTo('entity');
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,14 +1,9 @@
|
|||||||
<?php
|
<?php namespace BookStack;
|
||||||
|
|
||||||
namespace BookStack;
|
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
abstract class Entity extends Ownable
|
||||||
|
|
||||||
abstract class Entity extends Model
|
|
||||||
{
|
{
|
||||||
|
|
||||||
use Ownable;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compares this entity to another given entity.
|
* Compares this entity to another given entity.
|
||||||
* Matches by comparing class and id.
|
* Matches by comparing class and id.
|
||||||
@ -53,13 +48,31 @@ abstract class Entity extends Model
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get View objects for this entity.
|
* Get View objects for this entity.
|
||||||
* @return mixed
|
|
||||||
*/
|
*/
|
||||||
public function views()
|
public function views()
|
||||||
{
|
{
|
||||||
return $this->morphMany('BookStack\View', 'viewable');
|
return $this->morphMany('BookStack\View', 'viewable');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get this entities restrictions.
|
||||||
|
*/
|
||||||
|
public function restrictions()
|
||||||
|
{
|
||||||
|
return $this->morphMany('BookStack\Restriction', 'restrictable');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this entity has a specific restriction set against it.
|
||||||
|
* @param $role_id
|
||||||
|
* @param $action
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasRestriction($role_id, $action)
|
||||||
|
{
|
||||||
|
return $this->restrictions->where('role_id', $role_id)->where('action', $action)->count() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows checking of the exact class, Used to check entity type.
|
* Allows checking of the exact class, Used to check entity type.
|
||||||
* Cleaner method for is_a.
|
* Cleaner method for is_a.
|
||||||
@ -72,23 +85,14 @@ abstract class Entity extends Model
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the class name.
|
* Gets a limited-length version of the entities name.
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public static function getClassName()
|
|
||||||
{
|
|
||||||
return strtolower(array_slice(explode('\\', static::class), -1, 1)[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*Gets a limited-length version of the entities name.
|
|
||||||
* @param int $length
|
* @param int $length
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getShortName($length = 25)
|
public function getShortName($length = 25)
|
||||||
{
|
{
|
||||||
if(strlen($this->name) <= $length) return $this->name;
|
if (strlen($this->name) <= $length) return $this->name;
|
||||||
return substr($this->name, 0, $length-3) . '...';
|
return substr($this->name, 0, $length - 3) . '...';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -100,20 +104,38 @@ abstract class Entity extends Model
|
|||||||
*/
|
*/
|
||||||
public static function fullTextSearchQuery($fieldsToSearch, $terms, $wheres = [])
|
public static function fullTextSearchQuery($fieldsToSearch, $terms, $wheres = [])
|
||||||
{
|
{
|
||||||
$termString = '';
|
$exactTerms = [];
|
||||||
foreach ($terms as $term) {
|
foreach ($terms as $key => $term) {
|
||||||
$termString .= htmlentities($term) . '* ';
|
$term = htmlentities($term, ENT_QUOTES);
|
||||||
|
$term = preg_replace('/[+\-><\(\)~*\"@]+/', ' ', $term);
|
||||||
|
if (preg_match('/\s/', $term)) {
|
||||||
|
$exactTerms[] = '%' . $term . '%';
|
||||||
|
$term = '"' . $term . '"';
|
||||||
|
} else {
|
||||||
|
$term = '' . $term . '*';
|
||||||
}
|
}
|
||||||
|
if ($term !== '*') $terms[$key] = $term;
|
||||||
|
}
|
||||||
|
$termString = implode(' ', $terms);
|
||||||
$fields = implode(',', $fieldsToSearch);
|
$fields = implode(',', $fieldsToSearch);
|
||||||
$termStringEscaped = \DB::connection()->getPdo()->quote($termString);
|
$search = static::selectRaw('*, MATCH(name) AGAINST(? IN BOOLEAN MODE) AS title_relevance', [$termString]);
|
||||||
$search = static::addSelect(\DB::raw('*, MATCH(name) AGAINST('.$termStringEscaped.' IN BOOLEAN MODE) AS title_relevance'));
|
|
||||||
$search = $search->whereRaw('MATCH(' . $fields . ') AGAINST(? IN BOOLEAN MODE)', [$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) {
|
||||||
|
foreach ($exactTerms as $exactTerm) {
|
||||||
|
foreach ($fieldsToSearch as $field) {
|
||||||
|
$query->orWhere($field, 'like', $exactTerm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Add additional where terms
|
// Add additional where terms
|
||||||
foreach ($wheres as $whereTerm) {
|
foreach ($wheres as $whereTerm) {
|
||||||
$search->where($whereTerm[0], $whereTerm[1], $whereTerm[2]);
|
$search->where($whereTerm[0], $whereTerm[1], $whereTerm[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load in relations
|
// Load in relations
|
||||||
if (static::isA('page')) {
|
if (static::isA('page')) {
|
||||||
$search = $search->with('book', 'chapter', 'createdBy', 'updatedBy');
|
$search = $search->with('book', 'chapter', 'createdBy', 'updatedBy');
|
||||||
|
@ -56,7 +56,8 @@ class Handler extends ExceptionHandler
|
|||||||
// Which will include the basic message to point the user roughly to the cause.
|
// Which will include the basic message to point the user roughly to the cause.
|
||||||
if (($e instanceof PrettyException || $e->getPrevious() instanceof PrettyException) && !config('app.debug')) {
|
if (($e instanceof PrettyException || $e->getPrevious() instanceof PrettyException) && !config('app.debug')) {
|
||||||
$message = ($e instanceof PrettyException) ? $e->getMessage() : $e->getPrevious()->getMessage();
|
$message = ($e instanceof PrettyException) ? $e->getMessage() : $e->getPrevious()->getMessage();
|
||||||
return response()->view('errors/500', ['message' => $message], 500);
|
$code = ($e->getCode() === 0) ? 500 : $e->getCode();
|
||||||
|
return response()->view('errors/' . $code, ['message' => $message], $code);
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent::render($request, $e);
|
return parent::render($request, $e);
|
||||||
|
14
app/Exceptions/NotFoundException.php
Normal file
14
app/Exceptions/NotFoundException.php
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?php namespace BookStack\Exceptions;
|
||||||
|
|
||||||
|
|
||||||
|
class NotFoundException extends PrettyException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NotFoundException constructor.
|
||||||
|
* @param string $message
|
||||||
|
*/
|
||||||
|
public function __construct($message = 'Item not found')
|
||||||
|
{
|
||||||
|
parent::__construct($message, 404);
|
||||||
|
}
|
||||||
|
}
|
6
app/Exceptions/PermissionsException.php
Normal file
6
app/Exceptions/PermissionsException.php
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?php namespace BookStack\Exceptions;
|
||||||
|
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class PermissionsException extends Exception {}
|
@ -3,6 +3,7 @@
|
|||||||
namespace BookStack\Http\Controllers;
|
namespace BookStack\Http\Controllers;
|
||||||
|
|
||||||
use Activity;
|
use Activity;
|
||||||
|
use BookStack\Repos\UserRepo;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
@ -19,18 +20,21 @@ class BookController extends Controller
|
|||||||
protected $bookRepo;
|
protected $bookRepo;
|
||||||
protected $pageRepo;
|
protected $pageRepo;
|
||||||
protected $chapterRepo;
|
protected $chapterRepo;
|
||||||
|
protected $userRepo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BookController constructor.
|
* BookController constructor.
|
||||||
* @param BookRepo $bookRepo
|
* @param BookRepo $bookRepo
|
||||||
* @param PageRepo $pageRepo
|
* @param PageRepo $pageRepo
|
||||||
* @param ChapterRepo $chapterRepo
|
* @param ChapterRepo $chapterRepo
|
||||||
|
* @param UserRepo $userRepo
|
||||||
*/
|
*/
|
||||||
public function __construct(BookRepo $bookRepo, PageRepo $pageRepo, ChapterRepo $chapterRepo)
|
public function __construct(BookRepo $bookRepo, PageRepo $pageRepo, ChapterRepo $chapterRepo, UserRepo $userRepo)
|
||||||
{
|
{
|
||||||
$this->bookRepo = $bookRepo;
|
$this->bookRepo = $bookRepo;
|
||||||
$this->pageRepo = $pageRepo;
|
$this->pageRepo = $pageRepo;
|
||||||
$this->chapterRepo = $chapterRepo;
|
$this->chapterRepo = $chapterRepo;
|
||||||
|
$this->userRepo = $userRepo;
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +59,7 @@ class BookController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function create()
|
public function create()
|
||||||
{
|
{
|
||||||
$this->checkPermission('book-create');
|
$this->checkPermission('book-create-all');
|
||||||
$this->setPageTitle('Create New Book');
|
$this->setPageTitle('Create New Book');
|
||||||
return view('books/create');
|
return view('books/create');
|
||||||
}
|
}
|
||||||
@ -68,7 +72,7 @@ class BookController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
{
|
{
|
||||||
$this->checkPermission('book-create');
|
$this->checkPermission('book-create-all');
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'name' => 'required|string|max:255',
|
'name' => 'required|string|max:255',
|
||||||
'description' => 'string|max:1000'
|
'description' => 'string|max:1000'
|
||||||
@ -105,8 +109,8 @@ class BookController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function edit($slug)
|
public function edit($slug)
|
||||||
{
|
{
|
||||||
$this->checkPermission('book-update');
|
|
||||||
$book = $this->bookRepo->getBySlug($slug);
|
$book = $this->bookRepo->getBySlug($slug);
|
||||||
|
$this->checkOwnablePermission('book-update', $book);
|
||||||
$this->setPageTitle('Edit Book ' . $book->getShortName());
|
$this->setPageTitle('Edit Book ' . $book->getShortName());
|
||||||
return view('books/edit', ['book' => $book, 'current' => $book]);
|
return view('books/edit', ['book' => $book, 'current' => $book]);
|
||||||
}
|
}
|
||||||
@ -120,8 +124,8 @@ class BookController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function update(Request $request, $slug)
|
public function update(Request $request, $slug)
|
||||||
{
|
{
|
||||||
$this->checkPermission('book-update');
|
|
||||||
$book = $this->bookRepo->getBySlug($slug);
|
$book = $this->bookRepo->getBySlug($slug);
|
||||||
|
$this->checkOwnablePermission('book-update', $book);
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'name' => 'required|string|max:255',
|
'name' => 'required|string|max:255',
|
||||||
'description' => 'string|max:1000'
|
'description' => 'string|max:1000'
|
||||||
@ -141,8 +145,8 @@ class BookController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function showDelete($bookSlug)
|
public function showDelete($bookSlug)
|
||||||
{
|
{
|
||||||
$this->checkPermission('book-delete');
|
|
||||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||||
|
$this->checkOwnablePermission('book-delete', $book);
|
||||||
$this->setPageTitle('Delete Book ' . $book->getShortName());
|
$this->setPageTitle('Delete Book ' . $book->getShortName());
|
||||||
return view('books/delete', ['book' => $book, 'current' => $book]);
|
return view('books/delete', ['book' => $book, 'current' => $book]);
|
||||||
}
|
}
|
||||||
@ -154,8 +158,8 @@ class BookController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function sort($bookSlug)
|
public function sort($bookSlug)
|
||||||
{
|
{
|
||||||
$this->checkPermission('book-update');
|
|
||||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||||
|
$this->checkOwnablePermission('book-update', $book);
|
||||||
$bookChildren = $this->bookRepo->getChildren($book);
|
$bookChildren = $this->bookRepo->getChildren($book);
|
||||||
$books = $this->bookRepo->getAll(false);
|
$books = $this->bookRepo->getAll(false);
|
||||||
$this->setPageTitle('Sort Book ' . $book->getShortName());
|
$this->setPageTitle('Sort Book ' . $book->getShortName());
|
||||||
@ -177,15 +181,14 @@ class BookController extends Controller
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves an array of sort mapping to pages and chapters.
|
* Saves an array of sort mapping to pages and chapters.
|
||||||
*
|
|
||||||
* @param string $bookSlug
|
* @param string $bookSlug
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||||
*/
|
*/
|
||||||
public function saveSort($bookSlug, Request $request)
|
public function saveSort($bookSlug, Request $request)
|
||||||
{
|
{
|
||||||
$this->checkPermission('book-update');
|
|
||||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||||
|
$this->checkOwnablePermission('book-update', $book);
|
||||||
|
|
||||||
// Return if no map sent
|
// Return if no map sent
|
||||||
if (!$request->has('sort-tree')) {
|
if (!$request->has('sort-tree')) {
|
||||||
@ -223,17 +226,48 @@ class BookController extends Controller
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the specified book from storage.
|
* Remove the specified book from storage.
|
||||||
*
|
|
||||||
* @param $bookSlug
|
* @param $bookSlug
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
public function destroy($bookSlug)
|
public function destroy($bookSlug)
|
||||||
{
|
{
|
||||||
$this->checkPermission('book-delete');
|
|
||||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||||
|
$this->checkOwnablePermission('book-delete', $book);
|
||||||
Activity::addMessage('book_delete', 0, $book->name);
|
Activity::addMessage('book_delete', 0, $book->name);
|
||||||
Activity::removeEntity($book);
|
Activity::removeEntity($book);
|
||||||
$this->bookRepo->destroyBySlug($bookSlug);
|
$this->bookRepo->destroyBySlug($bookSlug);
|
||||||
return redirect('/books');
|
return redirect('/books');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the Restrictions view.
|
||||||
|
* @param $bookSlug
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
|
*/
|
||||||
|
public function showRestrict($bookSlug)
|
||||||
|
{
|
||||||
|
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||||
|
$this->checkOwnablePermission('restrictions-manage', $book);
|
||||||
|
$roles = $this->userRepo->getRestrictableRoles();
|
||||||
|
return view('books/restrictions', [
|
||||||
|
'book' => $book,
|
||||||
|
'roles' => $roles
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the restrictions for this book.
|
||||||
|
* @param $bookSlug
|
||||||
|
* @param $bookSlug
|
||||||
|
* @param Request $request
|
||||||
|
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||||
|
*/
|
||||||
|
public function restrict($bookSlug, Request $request)
|
||||||
|
{
|
||||||
|
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||||
|
$this->checkOwnablePermission('restrictions-manage', $book);
|
||||||
|
$this->bookRepo->updateRestrictionsFromRequest($request, $book);
|
||||||
|
session()->flash('success', 'Page Restrictions Updated');
|
||||||
|
return redirect($book->getUrl());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,9 @@
|
|||||||
<?php
|
<?php namespace BookStack\Http\Controllers;
|
||||||
|
|
||||||
namespace BookStack\Http\Controllers;
|
|
||||||
|
|
||||||
use Activity;
|
use Activity;
|
||||||
|
use BookStack\Repos\UserRepo;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use BookStack\Http\Requests;
|
use BookStack\Http\Requests;
|
||||||
use BookStack\Http\Controllers\Controller;
|
|
||||||
use BookStack\Repos\BookRepo;
|
use BookStack\Repos\BookRepo;
|
||||||
use BookStack\Repos\ChapterRepo;
|
use BookStack\Repos\ChapterRepo;
|
||||||
use Views;
|
use Views;
|
||||||
@ -17,20 +13,22 @@ class ChapterController extends Controller
|
|||||||
|
|
||||||
protected $bookRepo;
|
protected $bookRepo;
|
||||||
protected $chapterRepo;
|
protected $chapterRepo;
|
||||||
|
protected $userRepo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ChapterController constructor.
|
* ChapterController constructor.
|
||||||
* @param $bookRepo
|
* @param BookRepo $bookRepo
|
||||||
* @param $chapterRepo
|
* @param ChapterRepo $chapterRepo
|
||||||
|
* @param UserRepo $userRepo
|
||||||
*/
|
*/
|
||||||
public function __construct(BookRepo $bookRepo, ChapterRepo $chapterRepo)
|
public function __construct(BookRepo $bookRepo, ChapterRepo $chapterRepo, UserRepo $userRepo)
|
||||||
{
|
{
|
||||||
$this->bookRepo = $bookRepo;
|
$this->bookRepo = $bookRepo;
|
||||||
$this->chapterRepo = $chapterRepo;
|
$this->chapterRepo = $chapterRepo;
|
||||||
|
$this->userRepo = $userRepo;
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the form for creating a new chapter.
|
* Show the form for creating a new chapter.
|
||||||
* @param $bookSlug
|
* @param $bookSlug
|
||||||
@ -38,8 +36,8 @@ class ChapterController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function create($bookSlug)
|
public function create($bookSlug)
|
||||||
{
|
{
|
||||||
$this->checkPermission('chapter-create');
|
|
||||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||||
|
$this->checkOwnablePermission('chapter-create', $book);
|
||||||
$this->setPageTitle('Create New Chapter');
|
$this->setPageTitle('Create New Chapter');
|
||||||
return view('chapters/create', ['book' => $book, 'current' => $book]);
|
return view('chapters/create', ['book' => $book, 'current' => $book]);
|
||||||
}
|
}
|
||||||
@ -52,12 +50,13 @@ class ChapterController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function store($bookSlug, Request $request)
|
public function store($bookSlug, Request $request)
|
||||||
{
|
{
|
||||||
$this->checkPermission('chapter-create');
|
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'name' => 'required|string|max:255'
|
'name' => 'required|string|max:255'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||||
|
$this->checkOwnablePermission('chapter-create', $book);
|
||||||
|
|
||||||
$chapter = $this->chapterRepo->newFromInput($request->all());
|
$chapter = $this->chapterRepo->newFromInput($request->all());
|
||||||
$chapter->slug = $this->chapterRepo->findSuitableSlug($chapter->name, $book->id);
|
$chapter->slug = $this->chapterRepo->findSuitableSlug($chapter->name, $book->id);
|
||||||
$chapter->priority = $this->bookRepo->getNewPriority($book);
|
$chapter->priority = $this->bookRepo->getNewPriority($book);
|
||||||
@ -81,7 +80,14 @@ class ChapterController extends Controller
|
|||||||
$sidebarTree = $this->bookRepo->getChildren($book);
|
$sidebarTree = $this->bookRepo->getChildren($book);
|
||||||
Views::add($chapter);
|
Views::add($chapter);
|
||||||
$this->setPageTitle($chapter->getShortName());
|
$this->setPageTitle($chapter->getShortName());
|
||||||
return view('chapters/show', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter, 'sidebarTree' => $sidebarTree]);
|
$pages = $this->chapterRepo->getChildren($chapter);
|
||||||
|
return view('chapters/show', [
|
||||||
|
'book' => $book,
|
||||||
|
'chapter' => $chapter,
|
||||||
|
'current' => $chapter,
|
||||||
|
'sidebarTree' => $sidebarTree,
|
||||||
|
'pages' => $pages
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -92,9 +98,9 @@ class ChapterController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function edit($bookSlug, $chapterSlug)
|
public function edit($bookSlug, $chapterSlug)
|
||||||
{
|
{
|
||||||
$this->checkPermission('chapter-update');
|
|
||||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||||
$chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
|
$chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
|
||||||
|
$this->checkOwnablePermission('chapter-update', $chapter);
|
||||||
$this->setPageTitle('Edit Chapter' . $chapter->getShortName());
|
$this->setPageTitle('Edit Chapter' . $chapter->getShortName());
|
||||||
return view('chapters/edit', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter]);
|
return view('chapters/edit', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter]);
|
||||||
}
|
}
|
||||||
@ -108,9 +114,9 @@ class ChapterController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function update(Request $request, $bookSlug, $chapterSlug)
|
public function update(Request $request, $bookSlug, $chapterSlug)
|
||||||
{
|
{
|
||||||
$this->checkPermission('chapter-update');
|
|
||||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||||
$chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
|
$chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
|
||||||
|
$this->checkOwnablePermission('chapter-update', $chapter);
|
||||||
$chapter->fill($request->all());
|
$chapter->fill($request->all());
|
||||||
$chapter->slug = $this->chapterRepo->findSuitableSlug($chapter->name, $book->id, $chapter->id);
|
$chapter->slug = $this->chapterRepo->findSuitableSlug($chapter->name, $book->id, $chapter->id);
|
||||||
$chapter->updated_by = auth()->user()->id;
|
$chapter->updated_by = auth()->user()->id;
|
||||||
@ -127,9 +133,9 @@ class ChapterController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function showDelete($bookSlug, $chapterSlug)
|
public function showDelete($bookSlug, $chapterSlug)
|
||||||
{
|
{
|
||||||
$this->checkPermission('chapter-delete');
|
|
||||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||||
$chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
|
$chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
|
||||||
|
$this->checkOwnablePermission('chapter-delete', $chapter);
|
||||||
$this->setPageTitle('Delete Chapter' . $chapter->getShortName());
|
$this->setPageTitle('Delete Chapter' . $chapter->getShortName());
|
||||||
return view('chapters/delete', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter]);
|
return view('chapters/delete', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter]);
|
||||||
}
|
}
|
||||||
@ -142,11 +148,46 @@ class ChapterController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function destroy($bookSlug, $chapterSlug)
|
public function destroy($bookSlug, $chapterSlug)
|
||||||
{
|
{
|
||||||
$this->checkPermission('chapter-delete');
|
|
||||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||||
$chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
|
$chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
|
||||||
|
$this->checkOwnablePermission('chapter-delete', $chapter);
|
||||||
Activity::addMessage('chapter_delete', $book->id, $chapter->name);
|
Activity::addMessage('chapter_delete', $book->id, $chapter->name);
|
||||||
$this->chapterRepo->destroy($chapter);
|
$this->chapterRepo->destroy($chapter);
|
||||||
return redirect($book->getUrl());
|
return redirect($book->getUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the Restrictions view.
|
||||||
|
* @param $bookSlug
|
||||||
|
* @param $chapterSlug
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
|
*/
|
||||||
|
public function showRestrict($bookSlug, $chapterSlug)
|
||||||
|
{
|
||||||
|
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||||
|
$chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
|
||||||
|
$this->checkOwnablePermission('restrictions-manage', $chapter);
|
||||||
|
$roles = $this->userRepo->getRestrictableRoles();
|
||||||
|
return view('chapters/restrictions', [
|
||||||
|
'chapter' => $chapter,
|
||||||
|
'roles' => $roles
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the restrictions for this chapter.
|
||||||
|
* @param $bookSlug
|
||||||
|
* @param $chapterSlug
|
||||||
|
* @param Request $request
|
||||||
|
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||||
|
*/
|
||||||
|
public function restrict($bookSlug, $chapterSlug, Request $request)
|
||||||
|
{
|
||||||
|
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||||
|
$chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
|
||||||
|
$this->checkOwnablePermission('restrictions-manage', $chapter);
|
||||||
|
$this->chapterRepo->updateRestrictionsFromRequest($request, $chapter);
|
||||||
|
session()->flash('success', 'Page Restrictions Updated');
|
||||||
|
return redirect($chapter->getUrl());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace BookStack\Http\Controllers;
|
namespace BookStack\Http\Controllers;
|
||||||
|
|
||||||
|
use BookStack\Ownable;
|
||||||
use HttpRequestException;
|
use HttpRequestException;
|
||||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||||
use Illuminate\Http\Exception\HttpResponseException;
|
use Illuminate\Http\Exception\HttpResponseException;
|
||||||
@ -61,21 +62,19 @@ abstract class Controller extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On a permission error redirect to home and display
|
* On a permission error redirect to home and display.
|
||||||
* the error as a notification.
|
* the error as a notification.
|
||||||
*/
|
*/
|
||||||
protected function showPermissionError()
|
protected function showPermissionError()
|
||||||
{
|
{
|
||||||
Session::flash('error', trans('errors.permission'));
|
Session::flash('error', trans('errors.permission'));
|
||||||
throw new HttpResponseException(
|
$response = request()->wantsJson() ? response()->json(['error' => trans('errors.permissionJson')], 403) : redirect('/');
|
||||||
redirect('/')
|
throw new HttpResponseException($response);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks for a permission.
|
* Checks for a permission.
|
||||||
*
|
* @param string $permissionName
|
||||||
* @param $permissionName
|
|
||||||
* @return bool|\Illuminate\Http\RedirectResponse
|
* @return bool|\Illuminate\Http\RedirectResponse
|
||||||
*/
|
*/
|
||||||
protected function checkPermission($permissionName)
|
protected function checkPermission($permissionName)
|
||||||
@ -83,10 +82,21 @@ abstract class Controller extends BaseController
|
|||||||
if (!$this->currentUser || !$this->currentUser->can($permissionName)) {
|
if (!$this->currentUser || !$this->currentUser->can($permissionName)) {
|
||||||
$this->showPermissionError();
|
$this->showPermissionError();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the current user's permissions against an ownable item.
|
||||||
|
* @param $permission
|
||||||
|
* @param Ownable $ownable
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function checkOwnablePermission($permission, Ownable $ownable)
|
||||||
|
{
|
||||||
|
if (userCan($permission, $ownable)) return true;
|
||||||
|
return $this->showPermissionError();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a user has a permission or bypass if the callback is true.
|
* Check if a user has a permission or bypass if the callback is true.
|
||||||
* @param $permissionName
|
* @param $permissionName
|
||||||
|
@ -24,7 +24,6 @@ class HomeController extends Controller
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the homepage.
|
* Display the homepage.
|
||||||
*
|
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
public function index()
|
public function index()
|
||||||
|
@ -64,7 +64,7 @@ class ImageController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function uploadByType($type, Request $request)
|
public function uploadByType($type, Request $request)
|
||||||
{
|
{
|
||||||
$this->checkPermission('image-create');
|
$this->checkPermission('image-create-all');
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'file' => 'image|mimes:jpeg,gif,png'
|
'file' => 'image|mimes:jpeg,gif,png'
|
||||||
]);
|
]);
|
||||||
@ -90,7 +90,7 @@ class ImageController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function getThumbnail($id, $width, $height, $crop)
|
public function getThumbnail($id, $width, $height, $crop)
|
||||||
{
|
{
|
||||||
$this->checkPermission('image-create');
|
$this->checkPermission('image-create-all');
|
||||||
$image = $this->imageRepo->getById($id);
|
$image = $this->imageRepo->getById($id);
|
||||||
$thumbnailUrl = $this->imageRepo->getThumbnail($image, $width, $height, $crop == 'false');
|
$thumbnailUrl = $this->imageRepo->getThumbnail($image, $width, $height, $crop == 'false');
|
||||||
return response()->json(['url' => $thumbnailUrl]);
|
return response()->json(['url' => $thumbnailUrl]);
|
||||||
@ -104,11 +104,11 @@ class ImageController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function update($imageId, Request $request)
|
public function update($imageId, Request $request)
|
||||||
{
|
{
|
||||||
$this->checkPermission('image-update');
|
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'name' => 'required|min:2|string'
|
'name' => 'required|min:2|string'
|
||||||
]);
|
]);
|
||||||
$image = $this->imageRepo->getById($imageId);
|
$image = $this->imageRepo->getById($imageId);
|
||||||
|
$this->checkOwnablePermission('image-update', $image);
|
||||||
$image = $this->imageRepo->updateImageDetails($image, $request->all());
|
$image = $this->imageRepo->updateImageDetails($image, $request->all());
|
||||||
return response()->json($image);
|
return response()->json($image);
|
||||||
}
|
}
|
||||||
@ -123,8 +123,8 @@ class ImageController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function destroy(PageRepo $pageRepo, Request $request, $id)
|
public function destroy(PageRepo $pageRepo, Request $request, $id)
|
||||||
{
|
{
|
||||||
$this->checkPermission('image-delete');
|
|
||||||
$image = $this->imageRepo->getById($id);
|
$image = $this->imageRepo->getById($id);
|
||||||
|
$this->checkOwnablePermission('image-delete', $image);
|
||||||
|
|
||||||
// Check if this image is used on any pages
|
// Check if this image is used on any pages
|
||||||
$isForced = ($request->has('force') && ($request->get('force') === 'true') || $request->get('force') === true);
|
$isForced = ($request->has('force') && ($request->get('force') === 'true') || $request->get('force') === true);
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
<?php
|
<?php namespace BookStack\Http\Controllers;
|
||||||
|
|
||||||
namespace BookStack\Http\Controllers;
|
|
||||||
|
|
||||||
use Activity;
|
use Activity;
|
||||||
|
use BookStack\Exceptions\NotFoundException;
|
||||||
|
use BookStack\Repos\UserRepo;
|
||||||
use BookStack\Services\ExportService;
|
use BookStack\Services\ExportService;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use BookStack\Http\Requests;
|
use BookStack\Http\Requests;
|
||||||
use BookStack\Repos\BookRepo;
|
use BookStack\Repos\BookRepo;
|
||||||
use BookStack\Repos\ChapterRepo;
|
use BookStack\Repos\ChapterRepo;
|
||||||
@ -21,6 +19,7 @@ class PageController extends Controller
|
|||||||
protected $bookRepo;
|
protected $bookRepo;
|
||||||
protected $chapterRepo;
|
protected $chapterRepo;
|
||||||
protected $exportService;
|
protected $exportService;
|
||||||
|
protected $userRepo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PageController constructor.
|
* PageController constructor.
|
||||||
@ -28,19 +27,20 @@ class PageController extends Controller
|
|||||||
* @param BookRepo $bookRepo
|
* @param BookRepo $bookRepo
|
||||||
* @param ChapterRepo $chapterRepo
|
* @param ChapterRepo $chapterRepo
|
||||||
* @param ExportService $exportService
|
* @param ExportService $exportService
|
||||||
|
* @param UserRepo $userRepo
|
||||||
*/
|
*/
|
||||||
public function __construct(PageRepo $pageRepo, BookRepo $bookRepo, ChapterRepo $chapterRepo, ExportService $exportService)
|
public function __construct(PageRepo $pageRepo, BookRepo $bookRepo, ChapterRepo $chapterRepo, ExportService $exportService, UserRepo $userRepo)
|
||||||
{
|
{
|
||||||
$this->pageRepo = $pageRepo;
|
$this->pageRepo = $pageRepo;
|
||||||
$this->bookRepo = $bookRepo;
|
$this->bookRepo = $bookRepo;
|
||||||
$this->chapterRepo = $chapterRepo;
|
$this->chapterRepo = $chapterRepo;
|
||||||
$this->exportService = $exportService;
|
$this->exportService = $exportService;
|
||||||
|
$this->userRepo = $userRepo;
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the form for creating a new page.
|
* Show the form for creating a new page.
|
||||||
*
|
|
||||||
* @param $bookSlug
|
* @param $bookSlug
|
||||||
* @param bool $chapterSlug
|
* @param bool $chapterSlug
|
||||||
* @return Response
|
* @return Response
|
||||||
@ -48,23 +48,22 @@ class PageController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function create($bookSlug, $chapterSlug = false)
|
public function create($bookSlug, $chapterSlug = false)
|
||||||
{
|
{
|
||||||
$this->checkPermission('page-create');
|
|
||||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||||
$chapter = $chapterSlug ? $this->chapterRepo->getBySlug($chapterSlug, $book->id) : false;
|
$chapter = $chapterSlug ? $this->chapterRepo->getBySlug($chapterSlug, $book->id) : false;
|
||||||
|
$parent = $chapter ? $chapter : $book;
|
||||||
|
$this->checkOwnablePermission('page-create', $parent);
|
||||||
$this->setPageTitle('Create New Page');
|
$this->setPageTitle('Create New Page');
|
||||||
return view('pages/create', ['book' => $book, 'chapter' => $chapter]);
|
return view('pages/create', ['book' => $book, 'chapter' => $chapter]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store a newly created page in storage.
|
* Store a newly created page in storage.
|
||||||
*
|
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @param $bookSlug
|
* @param $bookSlug
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
public function store(Request $request, $bookSlug)
|
public function store(Request $request, $bookSlug)
|
||||||
{
|
{
|
||||||
$this->checkPermission('page-create');
|
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'name' => 'required|string|max:255'
|
'name' => 'required|string|max:255'
|
||||||
]);
|
]);
|
||||||
@ -72,6 +71,8 @@ class PageController extends Controller
|
|||||||
$input = $request->all();
|
$input = $request->all();
|
||||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||||
$chapterId = ($request->has('chapter') && $this->chapterRepo->idExists($request->get('chapter'))) ? $request->get('chapter') : null;
|
$chapterId = ($request->has('chapter') && $this->chapterRepo->idExists($request->get('chapter'))) ? $request->get('chapter') : null;
|
||||||
|
$parent = $chapterId !== null ? $this->chapterRepo->getById($chapterId) : $book;
|
||||||
|
$this->checkOwnablePermission('page-create', $parent);
|
||||||
$input['priority'] = $this->bookRepo->getNewPriority($book);
|
$input['priority'] = $this->bookRepo->getNewPriority($book);
|
||||||
|
|
||||||
$page = $this->pageRepo->saveNew($input, $book, $chapterId);
|
$page = $this->pageRepo->saveNew($input, $book, $chapterId);
|
||||||
@ -84,7 +85,6 @@ class PageController extends Controller
|
|||||||
* Display the specified page.
|
* Display the specified page.
|
||||||
* If the page is not found via the slug the
|
* If the page is not found via the slug the
|
||||||
* revisions are searched for a match.
|
* revisions are searched for a match.
|
||||||
*
|
|
||||||
* @param $bookSlug
|
* @param $bookSlug
|
||||||
* @param $pageSlug
|
* @param $pageSlug
|
||||||
* @return Response
|
* @return Response
|
||||||
@ -95,7 +95,7 @@ class PageController extends Controller
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
$page = $this->pageRepo->getBySlug($pageSlug, $book->id);
|
$page = $this->pageRepo->getBySlug($pageSlug, $book->id);
|
||||||
} catch (NotFoundHttpException $e) {
|
} catch (NotFoundException $e) {
|
||||||
$page = $this->pageRepo->findPageUsingOldSlug($pageSlug, $bookSlug);
|
$page = $this->pageRepo->findPageUsingOldSlug($pageSlug, $bookSlug);
|
||||||
if ($page === null) abort(404);
|
if ($page === null) abort(404);
|
||||||
return redirect($page->getUrl());
|
return redirect($page->getUrl());
|
||||||
@ -109,23 +109,21 @@ class PageController extends Controller
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the form for editing the specified page.
|
* Show the form for editing the specified page.
|
||||||
*
|
|
||||||
* @param $bookSlug
|
* @param $bookSlug
|
||||||
* @param $pageSlug
|
* @param $pageSlug
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
public function edit($bookSlug, $pageSlug)
|
public function edit($bookSlug, $pageSlug)
|
||||||
{
|
{
|
||||||
$this->checkPermission('page-update');
|
|
||||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||||
$page = $this->pageRepo->getBySlug($pageSlug, $book->id);
|
$page = $this->pageRepo->getBySlug($pageSlug, $book->id);
|
||||||
|
$this->checkOwnablePermission('page-update', $page);
|
||||||
$this->setPageTitle('Editing Page ' . $page->getShortName());
|
$this->setPageTitle('Editing Page ' . $page->getShortName());
|
||||||
return view('pages/edit', ['page' => $page, 'book' => $book, 'current' => $page]);
|
return view('pages/edit', ['page' => $page, 'book' => $book, 'current' => $page]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the specified page in storage.
|
* Update the specified page in storage.
|
||||||
*
|
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @param $bookSlug
|
* @param $bookSlug
|
||||||
* @param $pageSlug
|
* @param $pageSlug
|
||||||
@ -133,12 +131,12 @@ class PageController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function update(Request $request, $bookSlug, $pageSlug)
|
public function update(Request $request, $bookSlug, $pageSlug)
|
||||||
{
|
{
|
||||||
$this->checkPermission('page-update');
|
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'name' => 'required|string|max:255'
|
'name' => 'required|string|max:255'
|
||||||
]);
|
]);
|
||||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||||
$page = $this->pageRepo->getBySlug($pageSlug, $book->id);
|
$page = $this->pageRepo->getBySlug($pageSlug, $book->id);
|
||||||
|
$this->checkOwnablePermission('page-update', $page);
|
||||||
$this->pageRepo->updatePage($page, $book->id, $request->all());
|
$this->pageRepo->updatePage($page, $book->id, $request->all());
|
||||||
Activity::add($page, 'page_update', $book->id);
|
Activity::add($page, 'page_update', $book->id);
|
||||||
return redirect($page->getUrl());
|
return redirect($page->getUrl());
|
||||||
@ -164,9 +162,9 @@ class PageController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function showDelete($bookSlug, $pageSlug)
|
public function showDelete($bookSlug, $pageSlug)
|
||||||
{
|
{
|
||||||
$this->checkPermission('page-delete');
|
|
||||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||||
$page = $this->pageRepo->getBySlug($pageSlug, $book->id);
|
$page = $this->pageRepo->getBySlug($pageSlug, $book->id);
|
||||||
|
$this->checkOwnablePermission('page-delete', $page);
|
||||||
$this->setPageTitle('Delete Page ' . $page->getShortName());
|
$this->setPageTitle('Delete Page ' . $page->getShortName());
|
||||||
return view('pages/delete', ['book' => $book, 'page' => $page, 'current' => $page]);
|
return view('pages/delete', ['book' => $book, 'page' => $page, 'current' => $page]);
|
||||||
}
|
}
|
||||||
@ -181,9 +179,9 @@ class PageController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function destroy($bookSlug, $pageSlug)
|
public function destroy($bookSlug, $pageSlug)
|
||||||
{
|
{
|
||||||
$this->checkPermission('page-delete');
|
|
||||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||||
$page = $this->pageRepo->getBySlug($pageSlug, $book->id);
|
$page = $this->pageRepo->getBySlug($pageSlug, $book->id);
|
||||||
|
$this->checkOwnablePermission('page-delete', $page);
|
||||||
Activity::addMessage('page_delete', $book->id, $page->name);
|
Activity::addMessage('page_delete', $book->id, $page->name);
|
||||||
$this->pageRepo->destroy($page);
|
$this->pageRepo->destroy($page);
|
||||||
return redirect($book->getUrl());
|
return redirect($book->getUrl());
|
||||||
@ -229,9 +227,9 @@ class PageController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function restoreRevision($bookSlug, $pageSlug, $revisionId)
|
public function restoreRevision($bookSlug, $pageSlug, $revisionId)
|
||||||
{
|
{
|
||||||
$this->checkPermission('page-update');
|
|
||||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||||
$page = $this->pageRepo->getBySlug($pageSlug, $book->id);
|
$page = $this->pageRepo->getBySlug($pageSlug, $book->id);
|
||||||
|
$this->checkOwnablePermission('page-update', $page);
|
||||||
$page = $this->pageRepo->restoreRevision($page, $book, $revisionId);
|
$page = $this->pageRepo->restoreRevision($page, $book, $revisionId);
|
||||||
Activity::add($page, 'page_restore', $book->id);
|
Activity::add($page, 'page_restore', $book->id);
|
||||||
return redirect($page->getUrl());
|
return redirect($page->getUrl());
|
||||||
@ -315,4 +313,39 @@ class PageController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the Restrictions view.
|
||||||
|
* @param $bookSlug
|
||||||
|
* @param $pageSlug
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
|
*/
|
||||||
|
public function showRestrict($bookSlug, $pageSlug)
|
||||||
|
{
|
||||||
|
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||||
|
$page = $this->pageRepo->getBySlug($pageSlug, $book->id);
|
||||||
|
$this->checkOwnablePermission('restrictions-manage', $page);
|
||||||
|
$roles = $this->userRepo->getRestrictableRoles();
|
||||||
|
return view('pages/restrictions', [
|
||||||
|
'page' => $page,
|
||||||
|
'roles' => $roles
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the restrictions for this page.
|
||||||
|
* @param $bookSlug
|
||||||
|
* @param $pageSlug
|
||||||
|
* @param Request $request
|
||||||
|
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||||
|
*/
|
||||||
|
public function restrict($bookSlug, $pageSlug, Request $request)
|
||||||
|
{
|
||||||
|
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||||
|
$page = $this->pageRepo->getBySlug($pageSlug, $book->id);
|
||||||
|
$this->checkOwnablePermission('restrictions-manage', $page);
|
||||||
|
$this->pageRepo->updateRestrictionsFromRequest($request, $page);
|
||||||
|
session()->flash('success', 'Page Restrictions Updated');
|
||||||
|
return redirect($page->getUrl());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
129
app/Http/Controllers/PermissionController.php
Normal file
129
app/Http/Controllers/PermissionController.php
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
<?php namespace BookStack\Http\Controllers;
|
||||||
|
|
||||||
|
use BookStack\Exceptions\PermissionsException;
|
||||||
|
use BookStack\Repos\PermissionsRepo;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use BookStack\Http\Requests;
|
||||||
|
|
||||||
|
class PermissionController extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
protected $permissionsRepo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PermissionController constructor.
|
||||||
|
* @param PermissionsRepo $permissionsRepo
|
||||||
|
*/
|
||||||
|
public function __construct(PermissionsRepo $permissionsRepo)
|
||||||
|
{
|
||||||
|
$this->permissionsRepo = $permissionsRepo;
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a listing of the roles in the system.
|
||||||
|
*/
|
||||||
|
public function listRoles()
|
||||||
|
{
|
||||||
|
$this->checkPermission('user-roles-manage');
|
||||||
|
$roles = $this->permissionsRepo->getAllRoles();
|
||||||
|
return view('settings/roles/index', ['roles' => $roles]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form to create a new role
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
|
*/
|
||||||
|
public function createRole()
|
||||||
|
{
|
||||||
|
$this->checkPermission('user-roles-manage');
|
||||||
|
return view('settings/roles/create');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a new role in the system.
|
||||||
|
* @param Request $request
|
||||||
|
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||||
|
*/
|
||||||
|
public function storeRole(Request $request)
|
||||||
|
{
|
||||||
|
$this->checkPermission('user-roles-manage');
|
||||||
|
$this->validate($request, [
|
||||||
|
'display_name' => 'required|min:3|max:200',
|
||||||
|
'description' => 'max:250'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->permissionsRepo->saveNewRole($request->all());
|
||||||
|
session()->flash('success', 'Role successfully created');
|
||||||
|
return redirect('/settings/roles');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for editing a user role.
|
||||||
|
* @param $id
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
|
*/
|
||||||
|
public function editRole($id)
|
||||||
|
{
|
||||||
|
$this->checkPermission('user-roles-manage');
|
||||||
|
$role = $this->permissionsRepo->getRoleById($id);
|
||||||
|
return view('settings/roles/edit', ['role' => $role]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a user role.
|
||||||
|
* @param $id
|
||||||
|
* @param Request $request
|
||||||
|
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||||
|
*/
|
||||||
|
public function updateRole($id, Request $request)
|
||||||
|
{
|
||||||
|
$this->checkPermission('user-roles-manage');
|
||||||
|
$this->validate($request, [
|
||||||
|
'display_name' => 'required|min:3|max:200',
|
||||||
|
'description' => 'max:250'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->permissionsRepo->updateRole($id, $request->all());
|
||||||
|
session()->flash('success', 'Role successfully updated');
|
||||||
|
return redirect('/settings/roles');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the view to delete a role.
|
||||||
|
* Offers the chance to migrate users.
|
||||||
|
* @param $id
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
|
*/
|
||||||
|
public function showDeleteRole($id)
|
||||||
|
{
|
||||||
|
$this->checkPermission('user-roles-manage');
|
||||||
|
$role = $this->permissionsRepo->getRoleById($id);
|
||||||
|
$roles = $this->permissionsRepo->getAllRolesExcept($role);
|
||||||
|
$blankRole = $role->newInstance(['display_name' => 'Don\'t migrate users']);
|
||||||
|
$roles->prepend($blankRole);
|
||||||
|
return view('settings/roles/delete', ['role' => $role, 'roles' => $roles]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a role from the system,
|
||||||
|
* Migrate from a previous role if set.
|
||||||
|
* @param $id
|
||||||
|
* @param Request $request
|
||||||
|
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||||
|
*/
|
||||||
|
public function deleteRole($id, Request $request)
|
||||||
|
{
|
||||||
|
$this->checkPermission('user-roles-manage');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->permissionsRepo->deleteRole($id, $request->get('migrate_role_id'));
|
||||||
|
} catch (PermissionsException $e) {
|
||||||
|
session()->flash('error', $e->getMessage());
|
||||||
|
return redirect()->back();
|
||||||
|
}
|
||||||
|
|
||||||
|
session()->flash('success', 'Role successfully deleted');
|
||||||
|
return redirect('/settings/roles');
|
||||||
|
}
|
||||||
|
}
|
@ -17,7 +17,7 @@ class SettingController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
$this->checkPermission('settings-update');
|
$this->checkPermission('settings-manage');
|
||||||
$this->setPageTitle('Settings');
|
$this->setPageTitle('Settings');
|
||||||
return view('settings/index');
|
return view('settings/index');
|
||||||
}
|
}
|
||||||
@ -32,7 +32,7 @@ class SettingController extends Controller
|
|||||||
public function update(Request $request)
|
public function update(Request $request)
|
||||||
{
|
{
|
||||||
$this->preventAccessForDemoUsers();
|
$this->preventAccessForDemoUsers();
|
||||||
$this->checkPermission('settings-update');
|
$this->checkPermission('settings-manage');
|
||||||
|
|
||||||
// Cycles through posted settings and update them
|
// Cycles through posted settings and update them
|
||||||
foreach($request->all() as $name => $value) {
|
foreach($request->all() as $name => $value) {
|
||||||
|
@ -35,7 +35,8 @@ class UserController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
$users = $this->user->all();
|
$this->checkPermission('users-manage');
|
||||||
|
$users = $this->userRepo->getAllUsers();
|
||||||
$this->setPageTitle('Users');
|
$this->setPageTitle('Users');
|
||||||
return view('users/index', ['users' => $users]);
|
return view('users/index', ['users' => $users]);
|
||||||
}
|
}
|
||||||
@ -46,7 +47,7 @@ class UserController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function create()
|
public function create()
|
||||||
{
|
{
|
||||||
$this->checkPermission('user-create');
|
$this->checkPermission('users-manage');
|
||||||
$authMethod = config('auth.method');
|
$authMethod = config('auth.method');
|
||||||
return view('users/create', ['authMethod' => $authMethod]);
|
return view('users/create', ['authMethod' => $authMethod]);
|
||||||
}
|
}
|
||||||
@ -58,11 +59,10 @@ class UserController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
{
|
{
|
||||||
$this->checkPermission('user-create');
|
$this->checkPermission('users-manage');
|
||||||
$validationRules = [
|
$validationRules = [
|
||||||
'name' => 'required',
|
'name' => 'required',
|
||||||
'email' => 'required|email|unique:users,email',
|
'email' => 'required|email|unique:users,email'
|
||||||
'role' => 'required|exists:roles,id'
|
|
||||||
];
|
];
|
||||||
|
|
||||||
$authMethod = config('auth.method');
|
$authMethod = config('auth.method');
|
||||||
@ -84,7 +84,11 @@ class UserController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
$user->save();
|
$user->save();
|
||||||
$user->attachRoleId($request->get('role'));
|
|
||||||
|
if ($request->has('roles')) {
|
||||||
|
$roles = $request->get('roles');
|
||||||
|
$user->roles()->sync($roles);
|
||||||
|
}
|
||||||
|
|
||||||
// Get avatar from gravatar and save
|
// Get avatar from gravatar and save
|
||||||
if (!config('services.disable_services')) {
|
if (!config('services.disable_services')) {
|
||||||
@ -104,7 +108,7 @@ class UserController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function edit($id, SocialAuthService $socialAuthService)
|
public function edit($id, SocialAuthService $socialAuthService)
|
||||||
{
|
{
|
||||||
$this->checkPermissionOr('user-update', function () use ($id) {
|
$this->checkPermissionOr('users-manage', function () use ($id) {
|
||||||
return $this->currentUser->id == $id;
|
return $this->currentUser->id == $id;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -125,7 +129,7 @@ class UserController extends Controller
|
|||||||
public function update(Request $request, $id)
|
public function update(Request $request, $id)
|
||||||
{
|
{
|
||||||
$this->preventAccessForDemoUsers();
|
$this->preventAccessForDemoUsers();
|
||||||
$this->checkPermissionOr('user-update', function () use ($id) {
|
$this->checkPermissionOr('users-manage', function () use ($id) {
|
||||||
return $this->currentUser->id == $id;
|
return $this->currentUser->id == $id;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -133,8 +137,7 @@ class UserController extends Controller
|
|||||||
'name' => 'min:2',
|
'name' => 'min:2',
|
||||||
'email' => 'min:2|email|unique:users,email,' . $id,
|
'email' => 'min:2|email|unique:users,email,' . $id,
|
||||||
'password' => 'min:5|required_with:password_confirm',
|
'password' => 'min:5|required_with:password_confirm',
|
||||||
'password-confirm' => 'same:password|required_with:password',
|
'password-confirm' => 'same:password|required_with:password'
|
||||||
'role' => 'exists:roles,id'
|
|
||||||
], [
|
], [
|
||||||
'password-confirm.required_with' => 'Password confirmation required'
|
'password-confirm.required_with' => 'Password confirmation required'
|
||||||
]);
|
]);
|
||||||
@ -143,8 +146,9 @@ class UserController extends Controller
|
|||||||
$user->fill($request->all());
|
$user->fill($request->all());
|
||||||
|
|
||||||
// Role updates
|
// Role updates
|
||||||
if ($this->currentUser->can('user-update') && $request->has('role')) {
|
if (userCan('users-manage') && $request->has('roles')) {
|
||||||
$user->attachRoleId($request->get('role'));
|
$roles = $request->get('roles');
|
||||||
|
$user->roles()->sync($roles);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Password updates
|
// Password updates
|
||||||
@ -154,11 +158,12 @@ class UserController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
// External auth id updates
|
// External auth id updates
|
||||||
if ($this->currentUser->can('user-update') && $request->has('external_auth_id')) {
|
if ($this->currentUser->can('users-manage') && $request->has('external_auth_id')) {
|
||||||
$user->external_auth_id = $request->get('external_auth_id');
|
$user->external_auth_id = $request->get('external_auth_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
$user->save();
|
$user->save();
|
||||||
|
session()->flash('success', 'User successfully updated');
|
||||||
return redirect('/settings/users');
|
return redirect('/settings/users');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,7 +174,7 @@ class UserController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function delete($id)
|
public function delete($id)
|
||||||
{
|
{
|
||||||
$this->checkPermissionOr('user-delete', function () use ($id) {
|
$this->checkPermissionOr('users-manage', function () use ($id) {
|
||||||
return $this->currentUser->id == $id;
|
return $this->currentUser->id == $id;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -186,7 +191,7 @@ class UserController extends Controller
|
|||||||
public function destroy($id)
|
public function destroy($id)
|
||||||
{
|
{
|
||||||
$this->preventAccessForDemoUsers();
|
$this->preventAccessForDemoUsers();
|
||||||
$this->checkPermissionOr('user-delete', function () use ($id) {
|
$this->checkPermissionOr('users-manage', function () use ($id) {
|
||||||
return $this->currentUser->id == $id;
|
return $this->currentUser->id == $id;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -19,6 +19,8 @@ Route::group(['middleware' => 'auth'], function () {
|
|||||||
Route::delete('/{id}', 'BookController@destroy');
|
Route::delete('/{id}', 'BookController@destroy');
|
||||||
Route::get('/{slug}/sort-item', 'BookController@getSortItem');
|
Route::get('/{slug}/sort-item', 'BookController@getSortItem');
|
||||||
Route::get('/{slug}', 'BookController@show');
|
Route::get('/{slug}', 'BookController@show');
|
||||||
|
Route::get('/{bookSlug}/restrict', 'BookController@showRestrict');
|
||||||
|
Route::put('/{bookSlug}/restrict', 'BookController@restrict');
|
||||||
Route::get('/{slug}/delete', 'BookController@showDelete');
|
Route::get('/{slug}/delete', 'BookController@showDelete');
|
||||||
Route::get('/{bookSlug}/sort', 'BookController@sort');
|
Route::get('/{bookSlug}/sort', 'BookController@sort');
|
||||||
Route::put('/{bookSlug}/sort', 'BookController@saveSort');
|
Route::put('/{bookSlug}/sort', 'BookController@saveSort');
|
||||||
@ -32,6 +34,8 @@ Route::group(['middleware' => 'auth'], function () {
|
|||||||
Route::get('/{bookSlug}/page/{pageSlug}/export/plaintext', 'PageController@exportPlainText');
|
Route::get('/{bookSlug}/page/{pageSlug}/export/plaintext', 'PageController@exportPlainText');
|
||||||
Route::get('/{bookSlug}/page/{pageSlug}/edit', 'PageController@edit');
|
Route::get('/{bookSlug}/page/{pageSlug}/edit', 'PageController@edit');
|
||||||
Route::get('/{bookSlug}/page/{pageSlug}/delete', 'PageController@showDelete');
|
Route::get('/{bookSlug}/page/{pageSlug}/delete', 'PageController@showDelete');
|
||||||
|
Route::get('/{bookSlug}/page/{pageSlug}/restrict', 'PageController@showRestrict');
|
||||||
|
Route::put('/{bookSlug}/page/{pageSlug}/restrict', 'PageController@restrict');
|
||||||
Route::put('/{bookSlug}/page/{pageSlug}', 'PageController@update');
|
Route::put('/{bookSlug}/page/{pageSlug}', 'PageController@update');
|
||||||
Route::delete('/{bookSlug}/page/{pageSlug}', 'PageController@destroy');
|
Route::delete('/{bookSlug}/page/{pageSlug}', 'PageController@destroy');
|
||||||
|
|
||||||
@ -47,6 +51,8 @@ Route::group(['middleware' => 'auth'], function () {
|
|||||||
Route::get('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@show');
|
Route::get('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@show');
|
||||||
Route::put('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@update');
|
Route::put('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@update');
|
||||||
Route::get('/{bookSlug}/chapter/{chapterSlug}/edit', 'ChapterController@edit');
|
Route::get('/{bookSlug}/chapter/{chapterSlug}/edit', 'ChapterController@edit');
|
||||||
|
Route::get('/{bookSlug}/chapter/{chapterSlug}/restrict', 'ChapterController@showRestrict');
|
||||||
|
Route::put('/{bookSlug}/chapter/{chapterSlug}/restrict', 'ChapterController@restrict');
|
||||||
Route::get('/{bookSlug}/chapter/{chapterSlug}/delete', 'ChapterController@showDelete');
|
Route::get('/{bookSlug}/chapter/{chapterSlug}/delete', 'ChapterController@showDelete');
|
||||||
Route::delete('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@destroy');
|
Route::delete('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@destroy');
|
||||||
|
|
||||||
@ -87,6 +93,7 @@ Route::group(['middleware' => 'auth'], function () {
|
|||||||
Route::group(['prefix' => 'settings'], function() {
|
Route::group(['prefix' => 'settings'], function() {
|
||||||
Route::get('/', 'SettingController@index');
|
Route::get('/', 'SettingController@index');
|
||||||
Route::post('/', 'SettingController@update');
|
Route::post('/', 'SettingController@update');
|
||||||
|
|
||||||
// Users
|
// Users
|
||||||
Route::get('/users', 'UserController@index');
|
Route::get('/users', 'UserController@index');
|
||||||
Route::get('/users/create', 'UserController@create');
|
Route::get('/users/create', 'UserController@create');
|
||||||
@ -95,6 +102,15 @@ Route::group(['middleware' => 'auth'], function () {
|
|||||||
Route::get('/users/{id}', 'UserController@edit');
|
Route::get('/users/{id}', 'UserController@edit');
|
||||||
Route::put('/users/{id}', 'UserController@update');
|
Route::put('/users/{id}', 'UserController@update');
|
||||||
Route::delete('/users/{id}', 'UserController@destroy');
|
Route::delete('/users/{id}', 'UserController@destroy');
|
||||||
|
|
||||||
|
// Roles
|
||||||
|
Route::get('/roles', 'PermissionController@listRoles');
|
||||||
|
Route::get('/roles/new', 'PermissionController@createRole');
|
||||||
|
Route::post('/roles/new', 'PermissionController@storeRole');
|
||||||
|
Route::get('/roles/delete/{id}', 'PermissionController@showDeleteRole');
|
||||||
|
Route::delete('/roles/delete/{id}', 'PermissionController@deleteRole');
|
||||||
|
Route::get('/roles/{id}', 'PermissionController@editRole');
|
||||||
|
Route::put('/roles/{id}', 'PermissionController@updateRole');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -1,14 +1,9 @@
|
|||||||
<?php
|
<?php namespace BookStack;
|
||||||
|
|
||||||
namespace BookStack;
|
|
||||||
|
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
use Images;
|
use Images;
|
||||||
|
|
||||||
class Image extends Model
|
class Image extends Ownable
|
||||||
{
|
{
|
||||||
use Ownable;
|
|
||||||
|
|
||||||
protected $fillable = ['name'];
|
protected $fillable = ['name'];
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
<?php namespace BookStack;
|
<?php namespace BookStack;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
trait Ownable
|
abstract class Ownable extends Model
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Relation for the user that created this entity.
|
* Relation for the user that created this entity.
|
||||||
@ -20,4 +21,14 @@ trait Ownable
|
|||||||
{
|
{
|
||||||
return $this->belongsTo('BookStack\User', 'updated_by');
|
return $this->belongsTo('BookStack\User', 'updated_by');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the class name.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function getClassName()
|
||||||
|
{
|
||||||
|
return strtolower(array_slice(explode('\\', static::class), -1, 1)[0]);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -13,4 +13,14 @@ class Permission extends Model
|
|||||||
{
|
{
|
||||||
return $this->belongsToMany('BookStack\Permissions');
|
return $this->belongsToMany('BookStack\Permissions');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the permission object by name.
|
||||||
|
* @param $roleName
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public static function getByName($name)
|
||||||
|
{
|
||||||
|
return static::where('name', '=', $name)->first();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,11 +28,17 @@ class CustomFacadeProvider extends ServiceProvider
|
|||||||
public function register()
|
public function register()
|
||||||
{
|
{
|
||||||
$this->app->bind('activity', function() {
|
$this->app->bind('activity', function() {
|
||||||
return new ActivityService($this->app->make('BookStack\Activity'));
|
return new ActivityService(
|
||||||
|
$this->app->make('BookStack\Activity'),
|
||||||
|
$this->app->make('BookStack\Services\RestrictionService')
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->app->bind('views', function() {
|
$this->app->bind('views', function() {
|
||||||
return new ViewService($this->app->make('BookStack\View'));
|
return new ViewService(
|
||||||
|
$this->app->make('BookStack\View'),
|
||||||
|
$this->app->make('BookStack\Services\RestrictionService')
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->app->bind('setting', function() {
|
$this->app->bind('setting', function() {
|
||||||
@ -41,6 +47,7 @@ class CustomFacadeProvider extends ServiceProvider
|
|||||||
$this->app->make('Illuminate\Contracts\Cache\Repository')
|
$this->app->make('Illuminate\Contracts\Cache\Repository')
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->app->bind('images', function() {
|
$this->app->bind('images', function() {
|
||||||
return new ImageService(
|
return new ImageService(
|
||||||
$this->app->make('Intervention\Image\ImageManager'),
|
$this->app->make('Intervention\Image\ImageManager'),
|
||||||
|
@ -1,28 +1,35 @@
|
|||||||
<?php namespace BookStack\Repos;
|
<?php namespace BookStack\Repos;
|
||||||
|
|
||||||
use Activity;
|
use BookStack\Exceptions\NotFoundException;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use BookStack\Book;
|
use BookStack\Book;
|
||||||
use Views;
|
use Views;
|
||||||
|
|
||||||
class BookRepo
|
class BookRepo extends EntityRepo
|
||||||
{
|
{
|
||||||
|
|
||||||
protected $book;
|
|
||||||
protected $pageRepo;
|
protected $pageRepo;
|
||||||
protected $chapterRepo;
|
protected $chapterRepo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BookRepo constructor.
|
* BookRepo constructor.
|
||||||
* @param Book $book
|
|
||||||
* @param PageRepo $pageRepo
|
* @param PageRepo $pageRepo
|
||||||
* @param ChapterRepo $chapterRepo
|
* @param ChapterRepo $chapterRepo
|
||||||
*/
|
*/
|
||||||
public function __construct(Book $book, PageRepo $pageRepo, ChapterRepo $chapterRepo)
|
public function __construct(PageRepo $pageRepo, ChapterRepo $chapterRepo)
|
||||||
{
|
{
|
||||||
$this->book = $book;
|
|
||||||
$this->pageRepo = $pageRepo;
|
$this->pageRepo = $pageRepo;
|
||||||
$this->chapterRepo = $chapterRepo;
|
$this->chapterRepo = $chapterRepo;
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base query for getting books.
|
||||||
|
* Takes into account any restrictions.
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
private function bookQuery()
|
||||||
|
{
|
||||||
|
return $this->restrictionService->enforceBookRestrictions($this->book, 'view');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -32,7 +39,7 @@ class BookRepo
|
|||||||
*/
|
*/
|
||||||
public function getById($id)
|
public function getById($id)
|
||||||
{
|
{
|
||||||
return $this->book->findOrFail($id);
|
return $this->bookQuery()->findOrFail($id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -42,7 +49,7 @@ class BookRepo
|
|||||||
*/
|
*/
|
||||||
public function getAll($count = 10)
|
public function getAll($count = 10)
|
||||||
{
|
{
|
||||||
$bookQuery = $this->book->orderBy('name', 'asc');
|
$bookQuery = $this->bookQuery()->orderBy('name', 'asc');
|
||||||
if (!$count) return $bookQuery->get();
|
if (!$count) return $bookQuery->get();
|
||||||
return $bookQuery->take($count)->get();
|
return $bookQuery->take($count)->get();
|
||||||
}
|
}
|
||||||
@ -54,7 +61,8 @@ class BookRepo
|
|||||||
*/
|
*/
|
||||||
public function getAllPaginated($count = 10)
|
public function getAllPaginated($count = 10)
|
||||||
{
|
{
|
||||||
return $this->book->orderBy('name', 'asc')->paginate($count);
|
return $this->bookQuery()
|
||||||
|
->orderBy('name', 'asc')->paginate($count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -65,7 +73,7 @@ class BookRepo
|
|||||||
*/
|
*/
|
||||||
public function getLatest($count = 10)
|
public function getLatest($count = 10)
|
||||||
{
|
{
|
||||||
return $this->book->orderBy('created_at', 'desc')->take($count)->get();
|
return $this->bookQuery()->orderBy('created_at', 'desc')->take($count)->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -94,11 +102,12 @@ class BookRepo
|
|||||||
* Get a book by slug
|
* Get a book by slug
|
||||||
* @param $slug
|
* @param $slug
|
||||||
* @return mixed
|
* @return mixed
|
||||||
|
* @throws NotFoundException
|
||||||
*/
|
*/
|
||||||
public function getBySlug($slug)
|
public function getBySlug($slug)
|
||||||
{
|
{
|
||||||
$book = $this->book->where('slug', '=', $slug)->first();
|
$book = $this->bookQuery()->where('slug', '=', $slug)->first();
|
||||||
if ($book === null) abort(404);
|
if ($book === null) throw new NotFoundException('Book not found');
|
||||||
return $book;
|
return $book;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +118,7 @@ class BookRepo
|
|||||||
*/
|
*/
|
||||||
public function exists($id)
|
public function exists($id)
|
||||||
{
|
{
|
||||||
return $this->book->where('id', '=', $id)->exists();
|
return $this->bookQuery()->where('id', '=', $id)->exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -119,17 +128,7 @@ class BookRepo
|
|||||||
*/
|
*/
|
||||||
public function newFromInput($input)
|
public function newFromInput($input)
|
||||||
{
|
{
|
||||||
return $this->book->fill($input);
|
return $this->book->newInstance($input);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Count the amount of books that have a specific slug.
|
|
||||||
* @param $slug
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function countBySlug($slug)
|
|
||||||
{
|
|
||||||
return $this->book->where('slug', '=', $slug)->count();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -146,6 +145,7 @@ class BookRepo
|
|||||||
$this->chapterRepo->destroy($chapter);
|
$this->chapterRepo->destroy($chapter);
|
||||||
}
|
}
|
||||||
$book->views()->delete();
|
$book->views()->delete();
|
||||||
|
$book->restrictions()->delete();
|
||||||
$book->delete();
|
$book->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,8 +202,15 @@ class BookRepo
|
|||||||
*/
|
*/
|
||||||
public function getChildren(Book $book)
|
public function getChildren(Book $book)
|
||||||
{
|
{
|
||||||
$pages = $book->pages()->where('chapter_id', '=', 0)->get();
|
$pageQuery = $book->pages()->where('chapter_id', '=', 0);
|
||||||
$chapters = $book->chapters()->with('pages')->get();
|
$pageQuery = $this->restrictionService->enforcePageRestrictions($pageQuery, 'view');
|
||||||
|
$pages = $pageQuery->get();
|
||||||
|
|
||||||
|
$chapterQuery = $book->chapters()->with(['pages' => function($query) {
|
||||||
|
$this->restrictionService->enforcePageRestrictions($query, 'view');
|
||||||
|
}]);
|
||||||
|
$chapterQuery = $this->restrictionService->enforceChapterRestrictions($chapterQuery, 'view');
|
||||||
|
$chapters = $chapterQuery->get();
|
||||||
$children = $pages->merge($chapters);
|
$children = $pages->merge($chapters);
|
||||||
$bookSlug = $book->slug;
|
$bookSlug = $book->slug;
|
||||||
$children->each(function ($child) use ($bookSlug) {
|
$children->each(function ($child) use ($bookSlug) {
|
||||||
@ -226,8 +233,8 @@ class BookRepo
|
|||||||
*/
|
*/
|
||||||
public function getBySearch($term, $count = 20, $paginationAppends = [])
|
public function getBySearch($term, $count = 20, $paginationAppends = [])
|
||||||
{
|
{
|
||||||
$terms = explode(' ', $term);
|
$terms = $this->prepareSearchTerms($term);
|
||||||
$books = $this->book->fullTextSearchQuery(['name', 'description'], $terms)
|
$books = $this->restrictionService->enforceBookRestrictions($this->book->fullTextSearchQuery(['name', 'description'], $terms))
|
||||||
->paginate($count)->appends($paginationAppends);
|
->paginate($count)->appends($paginationAppends);
|
||||||
$words = join('|', explode(' ', preg_quote(trim($term), '/')));
|
$words = join('|', explode(' ', preg_quote(trim($term), '/')));
|
||||||
foreach ($books as $book) {
|
foreach ($books as $book) {
|
||||||
|
@ -2,21 +2,19 @@
|
|||||||
|
|
||||||
|
|
||||||
use Activity;
|
use Activity;
|
||||||
|
use BookStack\Exceptions\NotFoundException;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use BookStack\Chapter;
|
use BookStack\Chapter;
|
||||||
|
|
||||||
class ChapterRepo
|
class ChapterRepo extends EntityRepo
|
||||||
{
|
{
|
||||||
|
|
||||||
protected $chapter;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ChapterRepo constructor.
|
* Base query for getting chapters, Takes restrictions into account.
|
||||||
* @param $chapter
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function __construct(Chapter $chapter)
|
private function chapterQuery()
|
||||||
{
|
{
|
||||||
$this->chapter = $chapter;
|
return $this->restrictionService->enforceChapterRestrictions($this->chapter, 'view');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -26,7 +24,7 @@ class ChapterRepo
|
|||||||
*/
|
*/
|
||||||
public function idExists($id)
|
public function idExists($id)
|
||||||
{
|
{
|
||||||
return $this->chapter->where('id', '=', $id)->count() > 0;
|
return $this->chapterQuery()->where('id', '=', $id)->count() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,7 +34,7 @@ class ChapterRepo
|
|||||||
*/
|
*/
|
||||||
public function getById($id)
|
public function getById($id)
|
||||||
{
|
{
|
||||||
return $this->chapter->findOrFail($id);
|
return $this->chapterQuery()->findOrFail($id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -45,7 +43,7 @@ class ChapterRepo
|
|||||||
*/
|
*/
|
||||||
public function getAll()
|
public function getAll()
|
||||||
{
|
{
|
||||||
return $this->chapter->all();
|
return $this->chapterQuery()->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -53,14 +51,24 @@ class ChapterRepo
|
|||||||
* @param $slug
|
* @param $slug
|
||||||
* @param $bookId
|
* @param $bookId
|
||||||
* @return mixed
|
* @return mixed
|
||||||
|
* @throws NotFoundException
|
||||||
*/
|
*/
|
||||||
public function getBySlug($slug, $bookId)
|
public function getBySlug($slug, $bookId)
|
||||||
{
|
{
|
||||||
$chapter = $this->chapter->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first();
|
$chapter = $this->chapterQuery()->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first();
|
||||||
if ($chapter === null) abort(404);
|
if ($chapter === null) throw new NotFoundException('Chapter not found');
|
||||||
return $chapter;
|
return $chapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the child items for a chapter
|
||||||
|
* @param Chapter $chapter
|
||||||
|
*/
|
||||||
|
public function getChildren(Chapter $chapter)
|
||||||
|
{
|
||||||
|
return $this->restrictionService->enforcePageRestrictions($chapter->pages())->get();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new chapter from request input.
|
* Create a new chapter from request input.
|
||||||
* @param $input
|
* @param $input
|
||||||
@ -85,6 +93,7 @@ class ChapterRepo
|
|||||||
}
|
}
|
||||||
Activity::removeEntity($chapter);
|
Activity::removeEntity($chapter);
|
||||||
$chapter->views()->delete();
|
$chapter->views()->delete();
|
||||||
|
$chapter->restrictions()->delete();
|
||||||
$chapter->delete();
|
$chapter->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,7 +132,7 @@ class ChapterRepo
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get chapters by the given search term.
|
* Get chapters by the given search term.
|
||||||
* @param $term
|
* @param string $term
|
||||||
* @param array $whereTerms
|
* @param array $whereTerms
|
||||||
* @param int $count
|
* @param int $count
|
||||||
* @param array $paginationAppends
|
* @param array $paginationAppends
|
||||||
@ -131,8 +140,8 @@ class ChapterRepo
|
|||||||
*/
|
*/
|
||||||
public function getBySearch($term, $whereTerms = [], $count = 20, $paginationAppends = [])
|
public function getBySearch($term, $whereTerms = [], $count = 20, $paginationAppends = [])
|
||||||
{
|
{
|
||||||
$terms = explode(' ', $term);
|
$terms = $this->prepareSearchTerms($term);
|
||||||
$chapters = $this->chapter->fullTextSearchQuery(['name', 'description'], $terms, $whereTerms)
|
$chapters = $this->restrictionService->enforceChapterRestrictions($this->chapter->fullTextSearchQuery(['name', 'description'], $terms, $whereTerms))
|
||||||
->paginate($count)->appends($paginationAppends);
|
->paginate($count)->appends($paginationAppends);
|
||||||
$words = join('|', explode(' ', preg_quote(trim($term), '/')));
|
$words = join('|', explode(' ', preg_quote(trim($term), '/')));
|
||||||
foreach ($chapters as $chapter) {
|
foreach ($chapters as $chapter) {
|
||||||
|
@ -1,28 +1,43 @@
|
|||||||
<?php namespace BookStack\Repos;
|
<?php namespace BookStack\Repos;
|
||||||
|
|
||||||
|
|
||||||
use BookStack\Book;
|
use BookStack\Book;
|
||||||
use BookStack\Chapter;
|
use BookStack\Chapter;
|
||||||
|
use BookStack\Entity;
|
||||||
use BookStack\Page;
|
use BookStack\Page;
|
||||||
|
use BookStack\Services\RestrictionService;
|
||||||
|
|
||||||
class EntityRepo
|
class EntityRepo
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Book $book
|
||||||
|
*/
|
||||||
public $book;
|
public $book;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Chapter
|
||||||
|
*/
|
||||||
public $chapter;
|
public $chapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Page
|
||||||
|
*/
|
||||||
public $page;
|
public $page;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* EntityService constructor.
|
* @var RestrictionService
|
||||||
* @param $book
|
|
||||||
* @param $chapter
|
|
||||||
* @param $page
|
|
||||||
*/
|
*/
|
||||||
public function __construct(Book $book, Chapter $chapter, Page $page)
|
protected $restrictionService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EntityService constructor.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->book = $book;
|
$this->book = app(Book::class);
|
||||||
$this->chapter = $chapter;
|
$this->chapter = app(Chapter::class);
|
||||||
$this->page = $page;
|
$this->page = app(Page::class);
|
||||||
|
$this->restrictionService = app(RestrictionService::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -32,7 +47,8 @@ class EntityRepo
|
|||||||
*/
|
*/
|
||||||
public function getRecentlyCreatedBooks($count = 20, $page = 0)
|
public function getRecentlyCreatedBooks($count = 20, $page = 0)
|
||||||
{
|
{
|
||||||
return $this->book->orderBy('created_at', 'desc')->skip($page*$count)->take($count)->get();
|
return $this->restrictionService->enforceBookRestrictions($this->book)
|
||||||
|
->orderBy('created_at', 'desc')->skip($page * $count)->take($count)->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -43,7 +59,8 @@ class EntityRepo
|
|||||||
*/
|
*/
|
||||||
public function getRecentlyUpdatedBooks($count = 20, $page = 0)
|
public function getRecentlyUpdatedBooks($count = 20, $page = 0)
|
||||||
{
|
{
|
||||||
return $this->book->orderBy('updated_at', 'desc')->skip($page*$count)->take($count)->get();
|
return $this->restrictionService->enforceBookRestrictions($this->book)
|
||||||
|
->orderBy('updated_at', 'desc')->skip($page * $count)->take($count)->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -53,7 +70,8 @@ class EntityRepo
|
|||||||
*/
|
*/
|
||||||
public function getRecentlyCreatedPages($count = 20, $page = 0)
|
public function getRecentlyCreatedPages($count = 20, $page = 0)
|
||||||
{
|
{
|
||||||
return $this->page->orderBy('created_at', 'desc')->skip($page*$count)->take($count)->get();
|
return $this->restrictionService->enforcePageRestrictions($this->page)
|
||||||
|
->orderBy('created_at', 'desc')->skip($page * $count)->take($count)->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -64,7 +82,50 @@ class EntityRepo
|
|||||||
*/
|
*/
|
||||||
public function getRecentlyUpdatedPages($count = 20, $page = 0)
|
public function getRecentlyUpdatedPages($count = 20, $page = 0)
|
||||||
{
|
{
|
||||||
return $this->page->orderBy('updated_at', 'desc')->skip($page*$count)->take($count)->get();
|
return $this->restrictionService->enforcePageRestrictions($this->page)
|
||||||
|
->orderBy('updated_at', 'desc')->skip($page * $count)->take($count)->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates entity restrictions from a request
|
||||||
|
* @param $request
|
||||||
|
* @param Entity $entity
|
||||||
|
*/
|
||||||
|
public function updateRestrictionsFromRequest($request, Entity $entity)
|
||||||
|
{
|
||||||
|
$entity->restricted = $request->has('restricted') && $request->get('restricted') === 'true';
|
||||||
|
$entity->restrictions()->delete();
|
||||||
|
if ($request->has('restrictions')) {
|
||||||
|
foreach ($request->get('restrictions') as $roleId => $restrictions) {
|
||||||
|
foreach ($restrictions as $action => $value) {
|
||||||
|
$entity->restrictions()->create([
|
||||||
|
'role_id' => $roleId,
|
||||||
|
'action' => strtolower($action)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$entity->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare a string of search terms by turning
|
||||||
|
* it into an array of terms.
|
||||||
|
* Keeps quoted terms together.
|
||||||
|
* @param $termString
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function prepareSearchTerms($termString)
|
||||||
|
{
|
||||||
|
preg_match_all('/"(.*?)"/', $termString, $matches);
|
||||||
|
if (count($matches[1]) > 0) {
|
||||||
|
$terms = $matches[1];
|
||||||
|
$termString = trim(preg_replace('/"(.*?)"/', '', $termString));
|
||||||
|
} else {
|
||||||
|
$terms = [];
|
||||||
|
}
|
||||||
|
if (!empty($termString)) $terms = array_merge($terms, explode(' ', $termString));
|
||||||
|
return $terms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,39 +3,32 @@
|
|||||||
|
|
||||||
use Activity;
|
use Activity;
|
||||||
use BookStack\Book;
|
use BookStack\Book;
|
||||||
use BookStack\Chapter;
|
use BookStack\Exceptions\NotFoundException;
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use BookStack\Page;
|
use BookStack\Page;
|
||||||
use BookStack\PageRevision;
|
use BookStack\PageRevision;
|
||||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
|
||||||
|
|
||||||
class PageRepo
|
class PageRepo extends EntityRepo
|
||||||
{
|
{
|
||||||
protected $page;
|
|
||||||
protected $pageRevision;
|
protected $pageRevision;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PageRepo constructor.
|
* PageRepo constructor.
|
||||||
* @param Page $page
|
|
||||||
* @param PageRevision $pageRevision
|
* @param PageRevision $pageRevision
|
||||||
*/
|
*/
|
||||||
public function __construct(Page $page, PageRevision $pageRevision)
|
public function __construct(PageRevision $pageRevision)
|
||||||
{
|
{
|
||||||
$this->page = $page;
|
|
||||||
$this->pageRevision = $pageRevision;
|
$this->pageRevision = $pageRevision;
|
||||||
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a page id exists.
|
* Base query for getting pages, Takes restrictions into account.
|
||||||
* @param $id
|
* @return mixed
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
public function idExists($id)
|
private function pageQuery()
|
||||||
{
|
{
|
||||||
return $this->page->where('page_id', '=', $id)->count() > 0;
|
return $this->restrictionService->enforcePageRestrictions($this->page, 'view');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -45,16 +38,7 @@ class PageRepo
|
|||||||
*/
|
*/
|
||||||
public function getById($id)
|
public function getById($id)
|
||||||
{
|
{
|
||||||
return $this->page->findOrFail($id);
|
return $this->pageQuery()->findOrFail($id);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all pages.
|
|
||||||
* @return \Illuminate\Database\Eloquent\Collection|static[]
|
|
||||||
*/
|
|
||||||
public function getAll()
|
|
||||||
{
|
|
||||||
return $this->page->all();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -62,11 +46,12 @@ class PageRepo
|
|||||||
* @param $slug
|
* @param $slug
|
||||||
* @param $bookId
|
* @param $bookId
|
||||||
* @return mixed
|
* @return mixed
|
||||||
|
* @throws NotFoundException
|
||||||
*/
|
*/
|
||||||
public function getBySlug($slug, $bookId)
|
public function getBySlug($slug, $bookId)
|
||||||
{
|
{
|
||||||
$page = $this->page->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first();
|
$page = $this->pageQuery()->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first();
|
||||||
if ($page === null) throw new NotFoundHttpException('Page not found');
|
if ($page === null) throw new NotFoundException('Page not found');
|
||||||
return $page;
|
return $page;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,6 +66,9 @@ class PageRepo
|
|||||||
public function findPageUsingOldSlug($pageSlug, $bookSlug)
|
public function findPageUsingOldSlug($pageSlug, $bookSlug)
|
||||||
{
|
{
|
||||||
$revision = $this->pageRevision->where('slug', '=', $pageSlug)
|
$revision = $this->pageRevision->where('slug', '=', $pageSlug)
|
||||||
|
->whereHas('page', function($query) {
|
||||||
|
$this->restrictionService->enforcePageRestrictions($query);
|
||||||
|
})
|
||||||
->where('book_slug', '=', $bookSlug)->orderBy('created_at', 'desc')
|
->where('book_slug', '=', $bookSlug)->orderBy('created_at', 'desc')
|
||||||
->with('page')->first();
|
->with('page')->first();
|
||||||
return $revision !== null ? $revision->page : null;
|
return $revision !== null ? $revision->page : null;
|
||||||
@ -201,8 +189,8 @@ class PageRepo
|
|||||||
*/
|
*/
|
||||||
public function getBySearch($term, $whereTerms = [], $count = 20, $paginationAppends = [])
|
public function getBySearch($term, $whereTerms = [], $count = 20, $paginationAppends = [])
|
||||||
{
|
{
|
||||||
$terms = explode(' ', $term);
|
$terms = $this->prepareSearchTerms($term);
|
||||||
$pages = $this->page->fullTextSearchQuery(['name', 'text'], $terms, $whereTerms)
|
$pages = $this->restrictionService->enforcePageRestrictions($this->page->fullTextSearchQuery(['name', 'text'], $terms, $whereTerms))
|
||||||
->paginate($count)->appends($paginationAppends);
|
->paginate($count)->appends($paginationAppends);
|
||||||
|
|
||||||
// Add highlights to page text.
|
// Add highlights to page text.
|
||||||
@ -240,7 +228,7 @@ class PageRepo
|
|||||||
*/
|
*/
|
||||||
public function searchForImage($imageString)
|
public function searchForImage($imageString)
|
||||||
{
|
{
|
||||||
$pages = $this->page->where('html', 'like', '%' . $imageString . '%')->get();
|
$pages = $this->pageQuery()->where('html', 'like', '%' . $imageString . '%')->get();
|
||||||
foreach ($pages as $page) {
|
foreach ($pages as $page) {
|
||||||
$page->url = $page->getUrl();
|
$page->url = $page->getUrl();
|
||||||
$page->html = '';
|
$page->html = '';
|
||||||
@ -386,6 +374,7 @@ class PageRepo
|
|||||||
Activity::removeEntity($page);
|
Activity::removeEntity($page);
|
||||||
$page->views()->delete();
|
$page->views()->delete();
|
||||||
$page->revisions()->delete();
|
$page->revisions()->delete();
|
||||||
|
$page->restrictions()->delete();
|
||||||
$page->delete();
|
$page->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,7 +384,7 @@ class PageRepo
|
|||||||
*/
|
*/
|
||||||
public function getRecentlyCreatedPaginated($count = 20)
|
public function getRecentlyCreatedPaginated($count = 20)
|
||||||
{
|
{
|
||||||
return $this->page->orderBy('created_at', 'desc')->paginate($count);
|
return $this->pageQuery()->orderBy('created_at', 'desc')->paginate($count);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -404,7 +393,7 @@ class PageRepo
|
|||||||
*/
|
*/
|
||||||
public function getRecentlyUpdatedPaginated($count = 20)
|
public function getRecentlyUpdatedPaginated($count = 20)
|
||||||
{
|
{
|
||||||
return $this->page->orderBy('updated_at', 'desc')->paginate($count);
|
return $this->pageQuery()->orderBy('updated_at', 'desc')->paginate($count);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
142
app/Repos/PermissionsRepo.php
Normal file
142
app/Repos/PermissionsRepo.php
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
<?php namespace BookStack\Repos;
|
||||||
|
|
||||||
|
|
||||||
|
use BookStack\Exceptions\PermissionsException;
|
||||||
|
use BookStack\Permission;
|
||||||
|
use BookStack\Role;
|
||||||
|
use Setting;
|
||||||
|
|
||||||
|
class PermissionsRepo
|
||||||
|
{
|
||||||
|
|
||||||
|
protected $permission;
|
||||||
|
protected $role;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PermissionsRepo constructor.
|
||||||
|
* @param $permission
|
||||||
|
* @param $role
|
||||||
|
*/
|
||||||
|
public function __construct(Permission $permission, Role $role)
|
||||||
|
{
|
||||||
|
$this->permission = $permission;
|
||||||
|
$this->role = $role;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the user roles from the system.
|
||||||
|
* @return \Illuminate\Database\Eloquent\Collection|static[]
|
||||||
|
*/
|
||||||
|
public function getAllRoles()
|
||||||
|
{
|
||||||
|
return $this->role->all();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the roles except for the provided one.
|
||||||
|
* @param Role $role
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getAllRolesExcept(Role $role)
|
||||||
|
{
|
||||||
|
return $this->role->where('id', '!=', $role->id)->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a role via its ID.
|
||||||
|
* @param $id
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getRoleById($id)
|
||||||
|
{
|
||||||
|
return $this->role->findOrFail($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save a new role into the system.
|
||||||
|
* @param array $roleData
|
||||||
|
* @return Role
|
||||||
|
*/
|
||||||
|
public function saveNewRole($roleData)
|
||||||
|
{
|
||||||
|
$role = $this->role->newInstance($roleData);
|
||||||
|
$role->name = str_replace(' ', '-', strtolower($roleData['display_name']));
|
||||||
|
// Prevent duplicate names
|
||||||
|
while ($this->role->where('name', '=', $role->name)->count() > 0) {
|
||||||
|
$role->name .= strtolower(str_random(2));
|
||||||
|
}
|
||||||
|
$role->save();
|
||||||
|
|
||||||
|
$permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : [];
|
||||||
|
$this->assignRolePermissions($role, $permissions);
|
||||||
|
return $role;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates an existing role.
|
||||||
|
* Ensure Admin role always has all permissions.
|
||||||
|
* @param $roleId
|
||||||
|
* @param $roleData
|
||||||
|
*/
|
||||||
|
public function updateRole($roleId, $roleData)
|
||||||
|
{
|
||||||
|
$role = $this->role->findOrFail($roleId);
|
||||||
|
$permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : [];
|
||||||
|
$this->assignRolePermissions($role, $permissions);
|
||||||
|
|
||||||
|
if ($role->name === 'admin') {
|
||||||
|
$permissions = $this->permission->all()->pluck('id')->toArray();
|
||||||
|
$role->permissions()->sync($permissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
$role->fill($roleData);
|
||||||
|
$role->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign an list of permission names to an role.
|
||||||
|
* @param Role $role
|
||||||
|
* @param array $permissionNameArray
|
||||||
|
*/
|
||||||
|
public function assignRolePermissions(Role $role, $permissionNameArray = [])
|
||||||
|
{
|
||||||
|
$permissions = [];
|
||||||
|
$permissionNameArray = array_values($permissionNameArray);
|
||||||
|
if ($permissionNameArray && count($permissionNameArray) > 0) {
|
||||||
|
$permissions = $this->permission->whereIn('name', $permissionNameArray)->pluck('id')->toArray();
|
||||||
|
}
|
||||||
|
$role->permissions()->sync($permissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a role from the system.
|
||||||
|
* Check it's not an admin role or set as default before deleting.
|
||||||
|
* If an migration Role ID is specified the users assign to the current role
|
||||||
|
* will be added to the role of the specified id.
|
||||||
|
* @param $roleId
|
||||||
|
* @param $migrateRoleId
|
||||||
|
* @throws PermissionsException
|
||||||
|
*/
|
||||||
|
public function deleteRole($roleId, $migrateRoleId)
|
||||||
|
{
|
||||||
|
$role = $this->role->findOrFail($roleId);
|
||||||
|
|
||||||
|
// Prevent deleting admin role or default registration role.
|
||||||
|
if ($role->name === 'admin') {
|
||||||
|
throw new PermissionsException('The admin role cannot be deleted');
|
||||||
|
} else if ($role->id == Setting::get('registration-role')) {
|
||||||
|
throw new PermissionsException('This role cannot be deleted while set as the default registration role.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($migrateRoleId) {
|
||||||
|
$newRole = $this->role->find($migrateRoleId);
|
||||||
|
if ($newRole) {
|
||||||
|
$users = $role->users->pluck('id')->toArray();
|
||||||
|
$newRole->users()->sync($users);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$role->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -42,6 +42,15 @@ class UserRepo
|
|||||||
return $this->user->findOrFail($id);
|
return $this->user->findOrFail($id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the users with their permissions.
|
||||||
|
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||||
|
*/
|
||||||
|
public function getAllUsers()
|
||||||
|
{
|
||||||
|
return $this->user->with('roles', 'avatar')->orderBy('name', 'asc')->get();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new user and attaches a role to them.
|
* Creates a new user and attaches a role to them.
|
||||||
* @param array $data
|
* @param array $data
|
||||||
@ -69,7 +78,7 @@ class UserRepo
|
|||||||
public function attachDefaultRole($user)
|
public function attachDefaultRole($user)
|
||||||
{
|
{
|
||||||
$roleId = Setting::get('registration-role');
|
$roleId = Setting::get('registration-role');
|
||||||
if ($roleId === false) $roleId = $this->role->getDefault()->id;
|
if ($roleId === false) $roleId = $this->role->first()->id;
|
||||||
$user->attachRoleId($roleId);
|
$user->attachRoleId($roleId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,15 +89,10 @@ class UserRepo
|
|||||||
*/
|
*/
|
||||||
public function isOnlyAdmin(User $user)
|
public function isOnlyAdmin(User $user)
|
||||||
{
|
{
|
||||||
if ($user->role->name != 'admin') {
|
if (!$user->roles->pluck('name')->contains('admin')) return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$adminRole = $this->role->where('name', '=', 'admin')->first();
|
|
||||||
if (count($adminRole->users) > 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
$adminRole = $this->role->getRole('admin');
|
||||||
|
if ($adminRole->users->count() > 1) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,4 +164,14 @@ class UserRepo
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the roles which can be given restricted access to
|
||||||
|
* other entities in the system.
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getRestrictableRoles()
|
||||||
|
{
|
||||||
|
return $this->role->where('name', '!=', 'admin')->get();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
21
app/Restriction.php
Normal file
21
app/Restriction.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace BookStack;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Restriction extends Model
|
||||||
|
{
|
||||||
|
|
||||||
|
protected $fillable = ['role_id', 'action'];
|
||||||
|
public $timestamps = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all this restriction's attached entity.
|
||||||
|
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
|
||||||
|
*/
|
||||||
|
public function restrictable()
|
||||||
|
{
|
||||||
|
return $this->morphTo();
|
||||||
|
}
|
||||||
|
}
|
25
app/Role.php
25
app/Role.php
@ -6,11 +6,8 @@ use Illuminate\Database\Eloquent\Model;
|
|||||||
|
|
||||||
class Role extends Model
|
class Role extends Model
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Sets the default role name for newly registered users.
|
protected $fillable = ['display_name', 'description'];
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected static $default = 'viewer';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The roles that belong to the role.
|
* The roles that belong to the role.
|
||||||
@ -28,6 +25,15 @@ class Role extends Model
|
|||||||
return $this->belongsToMany('BookStack\Permission');
|
return $this->belongsToMany('BookStack\Permission');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this role has a permission.
|
||||||
|
* @param $permission
|
||||||
|
*/
|
||||||
|
public function hasPermission($permission)
|
||||||
|
{
|
||||||
|
return $this->permissions->pluck('name')->contains($permission);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a permission to this role.
|
* Add a permission to this role.
|
||||||
* @param Permission $permission
|
* @param Permission $permission
|
||||||
@ -37,15 +43,6 @@ class Role extends Model
|
|||||||
$this->permissions()->attach($permission->id);
|
$this->permissions()->attach($permission->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get an instance of the default role.
|
|
||||||
* @return Role
|
|
||||||
*/
|
|
||||||
public static function getDefault()
|
|
||||||
{
|
|
||||||
return static::getRole(static::$default);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the role object for the specified role.
|
* Get the role object for the specified role.
|
||||||
* @param $roleName
|
* @param $roleName
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<?php namespace BookStack\Services;
|
<?php namespace BookStack\Services;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use BookStack\Activity;
|
use BookStack\Activity;
|
||||||
use BookStack\Entity;
|
use BookStack\Entity;
|
||||||
use Session;
|
use Session;
|
||||||
@ -9,14 +8,17 @@ class ActivityService
|
|||||||
{
|
{
|
||||||
protected $activity;
|
protected $activity;
|
||||||
protected $user;
|
protected $user;
|
||||||
|
protected $restrictionService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ActivityService constructor.
|
* ActivityService constructor.
|
||||||
* @param $activity
|
* @param Activity $activity
|
||||||
|
* @param RestrictionService $restrictionService
|
||||||
*/
|
*/
|
||||||
public function __construct(Activity $activity)
|
public function __construct(Activity $activity, RestrictionService $restrictionService)
|
||||||
{
|
{
|
||||||
$this->activity = $activity;
|
$this->activity = $activity;
|
||||||
|
$this->restrictionService = $restrictionService;
|
||||||
$this->user = auth()->user();
|
$this->user = auth()->user();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,8 +88,10 @@ class ActivityService
|
|||||||
*/
|
*/
|
||||||
public function latest($count = 20, $page = 0)
|
public function latest($count = 20, $page = 0)
|
||||||
{
|
{
|
||||||
$activityList = $this->activity->orderBy('created_at', 'desc')
|
$activityList = $this->restrictionService
|
||||||
->skip($count * $page)->take($count)->get();
|
->filterRestrictedEntityRelations($this->activity, 'activities', 'entity_id', 'entity_type')
|
||||||
|
->orderBy('created_at', 'desc')->skip($count * $page)->take($count)->get();
|
||||||
|
|
||||||
return $this->filterSimilar($activityList);
|
return $this->filterSimilar($activityList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
272
app/Services/RestrictionService.php
Normal file
272
app/Services/RestrictionService.php
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
<?php namespace BookStack\Services;
|
||||||
|
|
||||||
|
use BookStack\Entity;
|
||||||
|
|
||||||
|
class RestrictionService
|
||||||
|
{
|
||||||
|
|
||||||
|
protected $userRoles;
|
||||||
|
protected $isAdmin;
|
||||||
|
protected $currentAction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RestrictionService constructor.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
$this->userRoles = $user ? auth()->user()->roles->pluck('id') : [];
|
||||||
|
$this->isAdmin = $user ? auth()->user()->hasRole('admin') : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if an entity has a restriction set upon it.
|
||||||
|
* @param Entity $entity
|
||||||
|
* @param $action
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function checkIfEntityRestricted(Entity $entity, $action)
|
||||||
|
{
|
||||||
|
if ($this->isAdmin) return true;
|
||||||
|
$this->currentAction = $action;
|
||||||
|
$baseQuery = $entity->where('id', '=', $entity->id);
|
||||||
|
if ($entity->isA('page')) {
|
||||||
|
return $this->pageRestrictionQuery($baseQuery)->count() > 0;
|
||||||
|
} elseif ($entity->isA('chapter')) {
|
||||||
|
return $this->chapterRestrictionQuery($baseQuery)->count() > 0;
|
||||||
|
} elseif ($entity->isA('book')) {
|
||||||
|
return $this->bookRestrictionQuery($baseQuery)->count() > 0;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add restrictions for a page query
|
||||||
|
* @param $query
|
||||||
|
* @param string $action
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function enforcePageRestrictions($query, $action = 'view')
|
||||||
|
{
|
||||||
|
if ($this->isAdmin) return $query;
|
||||||
|
$this->currentAction = $action;
|
||||||
|
return $this->pageRestrictionQuery($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base query for restricting pages.
|
||||||
|
* @param $query
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
private function pageRestrictionQuery($query)
|
||||||
|
{
|
||||||
|
return $query->where(function ($parentWhereQuery) {
|
||||||
|
|
||||||
|
$parentWhereQuery
|
||||||
|
// (Book & chapter & page) or (Book & page & NO CHAPTER) unrestricted
|
||||||
|
->where(function ($query) {
|
||||||
|
$query->where(function ($query) {
|
||||||
|
$query->whereExists(function ($query) {
|
||||||
|
$query->select('*')->from('chapters')
|
||||||
|
->whereRaw('chapters.id=pages.chapter_id')
|
||||||
|
->where('restricted', '=', false);
|
||||||
|
})->whereExists(function ($query) {
|
||||||
|
$query->select('*')->from('books')
|
||||||
|
->whereRaw('books.id=pages.book_id')
|
||||||
|
->where('restricted', '=', false);
|
||||||
|
})->where('restricted', '=', false);
|
||||||
|
})->orWhere(function ($query) {
|
||||||
|
$query->where('restricted', '=', false)->where('chapter_id', '=', 0)
|
||||||
|
->whereExists(function ($query) {
|
||||||
|
$query->select('*')->from('books')
|
||||||
|
->whereRaw('books.id=pages.book_id')
|
||||||
|
->where('restricted', '=', false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
// Page unrestricted, Has no chapter & book has accepted restrictions
|
||||||
|
->orWhere(function ($query) {
|
||||||
|
$query->where('restricted', '=', false)
|
||||||
|
->whereExists(function ($query) {
|
||||||
|
$query->select('*')->from('chapters')
|
||||||
|
->whereRaw('chapters.id=pages.chapter_id');
|
||||||
|
}, 'and', true)
|
||||||
|
->whereExists(function ($query) {
|
||||||
|
$query->select('*')->from('books')
|
||||||
|
->whereRaw('books.id=pages.book_id')
|
||||||
|
->whereExists(function ($query) {
|
||||||
|
$this->checkRestrictionsQuery($query, 'books', 'Book');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
// Page unrestricted, Has an unrestricted chapter & book has accepted restrictions
|
||||||
|
->orWhere(function ($query) {
|
||||||
|
$query->where('restricted', '=', false)
|
||||||
|
->whereExists(function ($query) {
|
||||||
|
$query->select('*')->from('chapters')
|
||||||
|
->whereRaw('chapters.id=pages.chapter_id')->where('restricted', '=', false);
|
||||||
|
})
|
||||||
|
->whereExists(function ($query) {
|
||||||
|
$query->select('*')->from('books')
|
||||||
|
->whereRaw('books.id=pages.book_id')
|
||||||
|
->whereExists(function ($query) {
|
||||||
|
$this->checkRestrictionsQuery($query, 'books', 'Book');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
// Page unrestricted, Has a chapter with accepted permissions
|
||||||
|
->orWhere(function ($query) {
|
||||||
|
$query->where('restricted', '=', false)
|
||||||
|
->whereExists(function ($query) {
|
||||||
|
$query->select('*')->from('chapters')
|
||||||
|
->whereRaw('chapters.id=pages.chapter_id')
|
||||||
|
->where('restricted', '=', true)
|
||||||
|
->whereExists(function ($query) {
|
||||||
|
$this->checkRestrictionsQuery($query, 'chapters', 'Chapter');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
// Page has accepted permissions
|
||||||
|
->orWhereExists(function ($query) {
|
||||||
|
$this->checkRestrictionsQuery($query, 'pages', 'Page');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add on permission restrictions to a chapter query.
|
||||||
|
* @param $query
|
||||||
|
* @param string $action
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function enforceChapterRestrictions($query, $action = 'view')
|
||||||
|
{
|
||||||
|
if ($this->isAdmin) return $query;
|
||||||
|
$this->currentAction = $action;
|
||||||
|
return $this->chapterRestrictionQuery($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base query for restricting chapters.
|
||||||
|
* @param $query
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
private function chapterRestrictionQuery($query)
|
||||||
|
{
|
||||||
|
return $query->where(function ($parentWhereQuery) {
|
||||||
|
|
||||||
|
$parentWhereQuery
|
||||||
|
// Book & chapter unrestricted
|
||||||
|
->where(function ($query) {
|
||||||
|
$query->where('restricted', '=', false)->whereExists(function ($query) {
|
||||||
|
$query->select('*')->from('books')
|
||||||
|
->whereRaw('books.id=chapters.book_id')
|
||||||
|
->where('restricted', '=', false);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
// Chapter unrestricted & book has accepted restrictions
|
||||||
|
->orWhere(function ($query) {
|
||||||
|
$query->where('restricted', '=', false)
|
||||||
|
->whereExists(function ($query) {
|
||||||
|
$query->select('*')->from('books')
|
||||||
|
->whereRaw('books.id=chapters.book_id')
|
||||||
|
->whereExists(function ($query) {
|
||||||
|
$this->checkRestrictionsQuery($query, 'books', 'Book');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
// Chapter has accepted permissions
|
||||||
|
->orWhereExists(function ($query) {
|
||||||
|
$this->checkRestrictionsQuery($query, 'chapters', 'Chapter');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add restrictions to a book query.
|
||||||
|
* @param $query
|
||||||
|
* @param string $action
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function enforceBookRestrictions($query, $action = 'view')
|
||||||
|
{
|
||||||
|
if ($this->isAdmin) return $query;
|
||||||
|
$this->currentAction = $action;
|
||||||
|
return $this->bookRestrictionQuery($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base query for restricting books.
|
||||||
|
* @param $query
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
private function bookRestrictionQuery($query)
|
||||||
|
{
|
||||||
|
return $query->where(function ($parentWhereQuery) {
|
||||||
|
$parentWhereQuery
|
||||||
|
->where('restricted', '=', false)
|
||||||
|
->orWhere(function ($query) {
|
||||||
|
$query->where('restricted', '=', true)->whereExists(function ($query) {
|
||||||
|
$this->checkRestrictionsQuery($query, 'books', 'Book');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter items that have entities set a a polymorphic relation.
|
||||||
|
* @param $query
|
||||||
|
* @param string $tableName
|
||||||
|
* @param string $entityIdColumn
|
||||||
|
* @param string $entityTypeColumn
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function filterRestrictedEntityRelations($query, $tableName, $entityIdColumn, $entityTypeColumn)
|
||||||
|
{
|
||||||
|
if ($this->isAdmin) return $query;
|
||||||
|
$this->currentAction = 'view';
|
||||||
|
$tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn, 'entityTypeColumn' => $entityTypeColumn];
|
||||||
|
return $query->where(function ($query) use ($tableDetails) {
|
||||||
|
$query->where(function ($query) use (&$tableDetails) {
|
||||||
|
$query->where($tableDetails['entityTypeColumn'], '=', 'BookStack\Page')
|
||||||
|
->whereExists(function ($query) use (&$tableDetails) {
|
||||||
|
$query->select('*')->from('pages')->whereRaw('pages.id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
|
||||||
|
->where(function ($query) {
|
||||||
|
$this->pageRestrictionQuery($query);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})->orWhere(function ($query) use (&$tableDetails) {
|
||||||
|
$query->where($tableDetails['entityTypeColumn'], '=', 'BookStack\Book')->whereExists(function ($query) use (&$tableDetails) {
|
||||||
|
$query->select('*')->from('books')->whereRaw('books.id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
|
||||||
|
->where(function ($query) {
|
||||||
|
$this->bookRestrictionQuery($query);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})->orWhere(function ($query) use (&$tableDetails) {
|
||||||
|
$query->where($tableDetails['entityTypeColumn'], '=', 'BookStack\Chapter')->whereExists(function ($query) use (&$tableDetails) {
|
||||||
|
$query->select('*')->from('chapters')->whereRaw('chapters.id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
|
||||||
|
->where(function ($query) {
|
||||||
|
$this->chapterRestrictionQuery($query);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The query to check the restrictions on an entity.
|
||||||
|
* @param $query
|
||||||
|
* @param $tableName
|
||||||
|
* @param $modelName
|
||||||
|
*/
|
||||||
|
private function checkRestrictionsQuery($query, $tableName, $modelName)
|
||||||
|
{
|
||||||
|
$query->select('*')->from('restrictions')
|
||||||
|
->whereRaw('restrictions.restrictable_id=' . $tableName . '.id')
|
||||||
|
->where('restrictions.restrictable_type', '=', 'BookStack\\' . $modelName)
|
||||||
|
->where('restrictions.action', '=', $this->currentAction)
|
||||||
|
->whereIn('restrictions.role_id', $this->userRoles);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -9,15 +9,18 @@ class ViewService
|
|||||||
|
|
||||||
protected $view;
|
protected $view;
|
||||||
protected $user;
|
protected $user;
|
||||||
|
protected $restrictionService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ViewService constructor.
|
* ViewService constructor.
|
||||||
* @param $view
|
* @param View $view
|
||||||
|
* @param RestrictionService $restrictionService
|
||||||
*/
|
*/
|
||||||
public function __construct(View $view)
|
public function __construct(View $view, RestrictionService $restrictionService)
|
||||||
{
|
{
|
||||||
$this->view = $view;
|
$this->view = $view;
|
||||||
$this->user = auth()->user();
|
$this->user = auth()->user();
|
||||||
|
$this->restrictionService = $restrictionService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,7 +30,7 @@ class ViewService
|
|||||||
*/
|
*/
|
||||||
public function add(Entity $entity)
|
public function add(Entity $entity)
|
||||||
{
|
{
|
||||||
if($this->user === null) return 0;
|
if ($this->user === null) return 0;
|
||||||
$view = $entity->views()->where('user_id', '=', $this->user->id)->first();
|
$view = $entity->views()->where('user_id', '=', $this->user->id)->first();
|
||||||
// Add view if model exists
|
// Add view if model exists
|
||||||
if ($view) {
|
if ($view) {
|
||||||
@ -54,11 +57,12 @@ class ViewService
|
|||||||
public function getPopular($count = 10, $page = 0, $filterModel = false)
|
public function getPopular($count = 10, $page = 0, $filterModel = false)
|
||||||
{
|
{
|
||||||
$skipCount = $count * $page;
|
$skipCount = $count * $page;
|
||||||
$query = $this->view->select('id', 'viewable_id', 'viewable_type', \DB::raw('SUM(views) as view_count'))
|
$query = $this->restrictionService->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type')
|
||||||
|
->select('id', 'viewable_id', 'viewable_type', \DB::raw('SUM(views) as view_count'))
|
||||||
->groupBy('viewable_id', 'viewable_type')
|
->groupBy('viewable_id', 'viewable_type')
|
||||||
->orderBy('view_count', 'desc');
|
->orderBy('view_count', 'desc');
|
||||||
|
|
||||||
if($filterModel) $query->where('viewable_type', '=', get_class($filterModel));
|
if ($filterModel) $query->where('viewable_type', '=', get_class($filterModel));
|
||||||
|
|
||||||
$views = $query->with('viewable')->skip($skipCount)->take($count)->get();
|
$views = $query->with('viewable')->skip($skipCount)->take($count)->get();
|
||||||
$viewedEntities = $views->map(function ($item) {
|
$viewedEntities = $views->map(function ($item) {
|
||||||
@ -76,15 +80,17 @@ class ViewService
|
|||||||
*/
|
*/
|
||||||
public function getUserRecentlyViewed($count = 10, $page = 0, $filterModel = false)
|
public function getUserRecentlyViewed($count = 10, $page = 0, $filterModel = false)
|
||||||
{
|
{
|
||||||
if($this->user === null) return collect();
|
if ($this->user === null) return collect();
|
||||||
$skipCount = $count * $page;
|
$skipCount = $count * $page;
|
||||||
$query = $this->view->where('user_id', '=', auth()->user()->id);
|
$query = $this->restrictionService
|
||||||
|
->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type');
|
||||||
|
|
||||||
if ($filterModel) $query->where('viewable_type', '=', get_class($filterModel));
|
if ($filterModel) $query = $query->where('viewable_type', '=', get_class($filterModel));
|
||||||
|
$query = $query->where('user_id', '=', auth()->user()->id);
|
||||||
|
|
||||||
$views = $query->with('viewable')->orderBy('updated_at', 'desc')->skip($skipCount)->take($count)->get();
|
$views = $query->with('viewable')->orderBy('updated_at', 'desc')->skip($skipCount)->take($count)->get();
|
||||||
$viewedEntities = $views->map(function ($item) {
|
$viewedEntities = $views->map(function ($item) {
|
||||||
return $item->viewable()->getResults();
|
return $item->viewable;
|
||||||
});
|
});
|
||||||
return $viewedEntities;
|
return $viewedEntities;
|
||||||
}
|
}
|
||||||
|
44
app/User.php
44
app/User.php
@ -14,21 +14,18 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The database table used by the model.
|
* The database table used by the model.
|
||||||
*
|
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $table = 'users';
|
protected $table = 'users';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The attributes that are mass assignable.
|
* The attributes that are mass assignable.
|
||||||
*
|
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $fillable = ['name', 'email', 'image_id'];
|
protected $fillable = ['name', 'email', 'image_id'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The attributes excluded from the model's JSON form.
|
* The attributes excluded from the model's JSON form.
|
||||||
*
|
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $hidden = ['password', 'remember_token'];
|
protected $hidden = ['password', 'remember_token'];
|
||||||
@ -50,10 +47,6 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Permissions and roles
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The roles that belong to the user.
|
* The roles that belong to the user.
|
||||||
*/
|
*/
|
||||||
@ -62,21 +55,30 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
|||||||
return $this->belongsToMany('BookStack\Role');
|
return $this->belongsToMany('BookStack\Role');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRoleAttribute()
|
/**
|
||||||
|
* Check if the user has a role.
|
||||||
|
* @param $role
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function hasRole($role)
|
||||||
{
|
{
|
||||||
return $this->roles()->with('permissions')->first();
|
return $this->roles->pluck('name')->contains($role);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the user's permissions from their role.
|
* Get all permissions belonging to a the current user.
|
||||||
|
* @param bool $cache
|
||||||
|
* @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
|
||||||
*/
|
*/
|
||||||
private function loadPermissions()
|
public function permissions($cache = true)
|
||||||
{
|
{
|
||||||
if (isset($this->permissions)) return;
|
if(isset($this->permissions) && $cache) return $this->permissions;
|
||||||
$this->load('roles.permissions');
|
$this->load('roles.permissions');
|
||||||
$permissions = $this->roles[0]->permissions;
|
$permissions = $this->roles->map(function($role) {
|
||||||
$permissionsArray = $permissions->pluck('name')->all();
|
return $role->permissions;
|
||||||
$this->permissions = $permissionsArray;
|
})->flatten()->unique();
|
||||||
|
$this->permissions = $permissions;
|
||||||
|
return $permissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -86,11 +88,8 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
|||||||
*/
|
*/
|
||||||
public function can($permissionName)
|
public function can($permissionName)
|
||||||
{
|
{
|
||||||
if ($this->email == 'guest') {
|
if ($this->email === 'guest') return false;
|
||||||
return false;
|
return $this->permissions()->pluck('name')->contains($permissionName);
|
||||||
}
|
|
||||||
$this->loadPermissions();
|
|
||||||
return array_search($permissionName, $this->permissions) !== false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -108,12 +107,11 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
|||||||
*/
|
*/
|
||||||
public function attachRoleId($id)
|
public function attachRoleId($id)
|
||||||
{
|
{
|
||||||
$this->roles()->sync([$id]);
|
$this->roles()->attach($id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the social account associated with this user.
|
* Get the social account associated with this user.
|
||||||
*
|
|
||||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||||
*/
|
*/
|
||||||
public function socialAccounts()
|
public function socialAccounts()
|
||||||
@ -138,8 +136,6 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the user's avatar,
|
* Returns the user's avatar,
|
||||||
* Uses Gravatar as the avatar service.
|
|
||||||
*
|
|
||||||
* @param int $size
|
* @param int $size
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
if (! function_exists('versioned_asset')) {
|
if (!function_exists('versioned_asset')) {
|
||||||
/**
|
/**
|
||||||
* Get the path to a versioned file.
|
* Get the path to a versioned file.
|
||||||
*
|
*
|
||||||
@ -28,3 +28,34 @@ if (! function_exists('versioned_asset')) {
|
|||||||
throw new InvalidArgumentException("File {$file} not defined in asset manifest.");
|
throw new InvalidArgumentException("File {$file} not defined in asset manifest.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the current user has a permission.
|
||||||
|
* If an ownable element is passed in the permissions are checked against
|
||||||
|
* that particular item.
|
||||||
|
* @param $permission
|
||||||
|
* @param \BookStack\Ownable $ownable
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
function userCan($permission, \BookStack\Ownable $ownable = null)
|
||||||
|
{
|
||||||
|
if (!auth()->check()) return false;
|
||||||
|
if ($ownable === null) {
|
||||||
|
return auth()->user() && auth()->user()->can($permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check permission on ownable item
|
||||||
|
$permissionBaseName = strtolower($permission) . '-';
|
||||||
|
$hasPermission = false;
|
||||||
|
if (auth()->user()->can($permissionBaseName . 'all')) $hasPermission = true;
|
||||||
|
if (auth()->user()->can($permissionBaseName . 'own') && $ownable->createdBy && $ownable->createdBy->id === auth()->user()->id) $hasPermission = true;
|
||||||
|
|
||||||
|
if (!$ownable instanceof \BookStack\Entity) return $hasPermission;
|
||||||
|
|
||||||
|
// Check restrictions on the entitiy
|
||||||
|
$restrictionService = app('BookStack\Services\RestrictionService');
|
||||||
|
$explodedPermission = explode('-', $permission);
|
||||||
|
$action = end($explodedPermission);
|
||||||
|
$hasAccess = $restrictionService->checkIfEntityRestricted($ownable, $action);
|
||||||
|
return $hasAccess && $hasPermission;
|
||||||
|
}
|
@ -1,5 +1,18 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
// MEMCACHED - Split out configuration into an array
|
||||||
|
if (env('CACHE_DRIVER') === 'memcached') {
|
||||||
|
$memcachedServerKeys = ['host', 'port', 'weight'];
|
||||||
|
$memcachedServers = explode(',', trim(env('MEMCACHED_SERVERS', '127.0.0.1:11211:100'), ','));
|
||||||
|
foreach ($memcachedServers as $index => $memcachedServer) {
|
||||||
|
$memcachedServerDetails = explode(':', $memcachedServer);
|
||||||
|
$components = count($memcachedServerDetails);
|
||||||
|
if ($components < 2) $memcachedServerDetails[] = '11211';
|
||||||
|
if ($components < 3) $memcachedServerDetails[] = '100';
|
||||||
|
$memcachedServers[$index] = array_combine($memcachedServerKeys, $memcachedServerDetails);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -49,11 +62,7 @@ return [
|
|||||||
|
|
||||||
'memcached' => [
|
'memcached' => [
|
||||||
'driver' => 'memcached',
|
'driver' => 'memcached',
|
||||||
'servers' => [
|
'servers' => env('CACHE_DRIVER') === 'memcached' ? $memcachedServers : [],
|
||||||
[
|
|
||||||
'host' => '127.0.0.1', 'port' => 11211, 'weight' => 100,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
|
|
||||||
'redis' => [
|
'redis' => [
|
||||||
|
@ -17,6 +17,7 @@ $factory->define(BookStack\User::class, function ($faker) {
|
|||||||
'email' => $faker->email,
|
'email' => $faker->email,
|
||||||
'password' => str_random(10),
|
'password' => str_random(10),
|
||||||
'remember_token' => str_random(10),
|
'remember_token' => str_random(10),
|
||||||
|
'email_confirmed' => 1
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -45,3 +46,10 @@ $factory->define(BookStack\Page::class, function ($faker) {
|
|||||||
'text' => strip_tags($html)
|
'text' => strip_tags($html)
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$factory->define(BookStack\Role::class, function ($faker) {
|
||||||
|
return [
|
||||||
|
'display_name' => $faker->sentence(3),
|
||||||
|
'description' => $faker->sentence(10)
|
||||||
|
];
|
||||||
|
});
|
@ -0,0 +1,99 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class UpdatePermissionsAndRoles extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
// Get roles with permissions we need to change
|
||||||
|
$adminRole = \BookStack\Role::getRole('admin');
|
||||||
|
$editorRole = \BookStack\Role::getRole('editor');
|
||||||
|
|
||||||
|
// Delete old permissions
|
||||||
|
$permissions = \BookStack\Permission::all();
|
||||||
|
$permissions->each(function ($permission) {
|
||||||
|
$permission->delete();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create & attach new admin permissions
|
||||||
|
$permissionsToCreate = [
|
||||||
|
'settings-manage' => 'Manage Settings',
|
||||||
|
'users-manage' => 'Manage Users',
|
||||||
|
'user-roles-manage' => 'Manage Roles & Permissions',
|
||||||
|
'restrictions-manage-all' => 'Manage All Entity Restrictions',
|
||||||
|
'restrictions-manage-own' => 'Manage Entity Restrictions On Own Content'
|
||||||
|
];
|
||||||
|
foreach ($permissionsToCreate as $name => $displayName) {
|
||||||
|
$newPermission = new \BookStack\Permission();
|
||||||
|
$newPermission->name = $name;
|
||||||
|
$newPermission->display_name = $displayName;
|
||||||
|
$newPermission->save();
|
||||||
|
$adminRole->attachPermission($newPermission);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create & attach new entity permissions
|
||||||
|
$entities = ['Book', 'Page', 'Chapter', 'Image'];
|
||||||
|
$ops = ['Create All', 'Create Own', 'Update All', 'Update Own', 'Delete All', 'Delete Own'];
|
||||||
|
foreach ($entities as $entity) {
|
||||||
|
foreach ($ops as $op) {
|
||||||
|
$newPermission = new \BookStack\Permission();
|
||||||
|
$newPermission->name = strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op));
|
||||||
|
$newPermission->display_name = $op . ' ' . $entity . 's';
|
||||||
|
$newPermission->save();
|
||||||
|
$adminRole->attachPermission($newPermission);
|
||||||
|
if ($editorRole !== null) $editorRole->attachPermission($newPermission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
// Get roles with permissions we need to change
|
||||||
|
$adminRole = \BookStack\Role::getRole('admin');
|
||||||
|
|
||||||
|
// Delete old permissions
|
||||||
|
$permissions = \BookStack\Permission::all();
|
||||||
|
$permissions->each(function ($permission) {
|
||||||
|
$permission->delete();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create default CRUD permissions and allocate to admins and editors
|
||||||
|
$entities = ['Book', 'Page', 'Chapter', 'Image'];
|
||||||
|
$ops = ['Create', 'Update', 'Delete'];
|
||||||
|
foreach ($entities as $entity) {
|
||||||
|
foreach ($ops as $op) {
|
||||||
|
$newPermission = new \BookStack\Permission();
|
||||||
|
$newPermission->name = strtolower($entity) . '-' . strtolower($op);
|
||||||
|
$newPermission->display_name = $op . ' ' . $entity . 's';
|
||||||
|
$newPermission->save();
|
||||||
|
$adminRole->attachPermission($newPermission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create admin permissions
|
||||||
|
$entities = ['Settings', 'User'];
|
||||||
|
$ops = ['Create', 'Update', 'Delete'];
|
||||||
|
foreach ($entities as $entity) {
|
||||||
|
foreach ($ops as $op) {
|
||||||
|
$newPermission = new \BookStack\Permission();
|
||||||
|
$newPermission->name = strtolower($entity) . '-' . strtolower($op);
|
||||||
|
$newPermission->display_name = $op . ' ' . $entity;
|
||||||
|
$newPermission->save();
|
||||||
|
$adminRole->attachPermission($newPermission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class AddEntityAccessControls extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('images', function (Blueprint $table) {
|
||||||
|
$table->integer('uploaded_to')->default(0);
|
||||||
|
$table->index('uploaded_to');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('books', function (Blueprint $table) {
|
||||||
|
$table->boolean('restricted')->default(false);
|
||||||
|
$table->index('restricted');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('chapters', function (Blueprint $table) {
|
||||||
|
$table->boolean('restricted')->default(false);
|
||||||
|
$table->index('restricted');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('pages', function (Blueprint $table) {
|
||||||
|
$table->boolean('restricted')->default(false);
|
||||||
|
$table->index('restricted');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('restrictions', function(Blueprint $table) {
|
||||||
|
$table->increments('id');
|
||||||
|
$table->integer('restrictable_id');
|
||||||
|
$table->string('restrictable_type');
|
||||||
|
$table->integer('role_id');
|
||||||
|
$table->string('action');
|
||||||
|
$table->index('role_id');
|
||||||
|
$table->index('action');
|
||||||
|
$table->index(['restrictable_id', 'restrictable_type']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('images', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('uploaded_to');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('books', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('restricted');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('chapters', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('restricted');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Schema::table('pages', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('restricted');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::drop('restrictions');
|
||||||
|
}
|
||||||
|
}
|
@ -12,7 +12,7 @@ class DummyContentSeeder extends Seeder
|
|||||||
public function run()
|
public function run()
|
||||||
{
|
{
|
||||||
$user = factory(BookStack\User::class, 1)->create();
|
$user = factory(BookStack\User::class, 1)->create();
|
||||||
$role = \BookStack\Role::getDefault();
|
$role = \BookStack\Role::getRole('editor');
|
||||||
$user->attachRole($role);
|
$user->attachRole($role);
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
</filter>
|
</filter>
|
||||||
<php>
|
<php>
|
||||||
<env name="APP_ENV" value="testing"/>
|
<env name="APP_ENV" value="testing"/>
|
||||||
|
<env name="APP_DEBUG" value="false"/>
|
||||||
<env name="CACHE_DRIVER" value="array"/>
|
<env name="CACHE_DRIVER" value="array"/>
|
||||||
<env name="SESSION_DRIVER" value="array"/>
|
<env name="SESSION_DRIVER" value="array"/>
|
||||||
<env name="QUEUE_DRIVER" value="sync"/>
|
<env name="QUEUE_DRIVER" value="sync"/>
|
||||||
|
@ -118,6 +118,7 @@ module.exports = function (ngApp, events) {
|
|||||||
page++;
|
page++;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.fetchData = fetchData;
|
$scope.fetchData = fetchData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -130,12 +131,16 @@ module.exports = function (ngApp, events) {
|
|||||||
$http.put(url, this.selectedImage).then((response) => {
|
$http.put(url, this.selectedImage).then((response) => {
|
||||||
events.emit('success', 'Image details updated');
|
events.emit('success', 'Image details updated');
|
||||||
}, (response) => {
|
}, (response) => {
|
||||||
|
if (response.status === 422) {
|
||||||
var errors = response.data;
|
var errors = response.data;
|
||||||
var message = '';
|
var message = '';
|
||||||
Object.keys(errors).forEach((key) => {
|
Object.keys(errors).forEach((key) => {
|
||||||
message += errors[key].join('\n');
|
message += errors[key].join('\n');
|
||||||
});
|
});
|
||||||
events.emit('error', message);
|
events.emit('error', message);
|
||||||
|
} else if (response.status === 403) {
|
||||||
|
events.emit('error', response.data.error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -158,6 +163,8 @@ module.exports = function (ngApp, events) {
|
|||||||
// Pages failure
|
// Pages failure
|
||||||
if (response.status === 400) {
|
if (response.status === 400) {
|
||||||
$scope.dependantPages = response.data;
|
$scope.dependantPages = response.data;
|
||||||
|
} else if (response.status === 403) {
|
||||||
|
events.emit('error', response.data.error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -167,7 +174,7 @@ module.exports = function (ngApp, events) {
|
|||||||
* @param stringDate
|
* @param stringDate
|
||||||
* @returns {Date}
|
* @returns {Date}
|
||||||
*/
|
*/
|
||||||
$scope.getDate = function(stringDate) {
|
$scope.getDate = function (stringDate) {
|
||||||
return new Date(stringDate);
|
return new Date(stringDate);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -87,6 +87,9 @@ header {
|
|||||||
padding-top: $-s;
|
padding-top: $-s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.dropdown-container {
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
form.search-box {
|
form.search-box {
|
||||||
|
@ -95,13 +95,14 @@
|
|||||||
|
|
||||||
// Sidebar list
|
// Sidebar list
|
||||||
.book-tree {
|
.book-tree {
|
||||||
padding: $-xl 0 0 0;
|
padding: $-l 0 0 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
transition: ease-in-out 240ms;
|
transition: ease-in-out 240ms;
|
||||||
transition-property: right, border;
|
transition-property: right, border;
|
||||||
border-left: 0px solid #FFF;
|
border-left: 0px solid #FFF;
|
||||||
|
background-color: #FFF;
|
||||||
&.fixed {
|
&.fixed {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
@ -8,4 +8,5 @@ return [
|
|||||||
|
|
||||||
// Pages
|
// Pages
|
||||||
'permission' => 'You do not have permission to access the requested page.',
|
'permission' => 'You do not have permission to access the requested page.',
|
||||||
|
'permissionJson' => 'You do not have permission to perform the requested action.'
|
||||||
];
|
];
|
@ -62,7 +62,7 @@
|
|||||||
<div class="float right">
|
<div class="float right">
|
||||||
<div class="links text-center">
|
<div class="links text-center">
|
||||||
<a href="/books"><i class="zmdi zmdi-book"></i>Books</a>
|
<a href="/books"><i class="zmdi zmdi-book"></i>Books</a>
|
||||||
@if(isset($currentUser) && $currentUser->can('settings-update'))
|
@if(isset($currentUser) && $currentUser->can('settings-manage'))
|
||||||
<a href="/settings"><i class="zmdi zmdi-settings"></i>Settings</a>
|
<a href="/settings"><i class="zmdi zmdi-settings"></i>Settings</a>
|
||||||
@endif
|
@endif
|
||||||
@if(!isset($signedIn) || !$signedIn)
|
@if(!isset($signedIn) || !$signedIn)
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<div class="col-xs-1"></div>
|
<div class="col-xs-1"></div>
|
||||||
<div class="col-xs-11 faded">
|
<div class="col-xs-11 faded">
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
@if($currentUser->can('book-create'))
|
@if($currentUser->can('book-create-all'))
|
||||||
<a href="/books/create" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>Add new book</a>
|
<a href="/books/create" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>Add new book</a>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@ -30,8 +30,10 @@
|
|||||||
{!! $books->render() !!}
|
{!! $books->render() !!}
|
||||||
@else
|
@else
|
||||||
<p class="text-muted">No books have been created.</p>
|
<p class="text-muted">No books have been created.</p>
|
||||||
|
@if(userCan('books-create-all'))
|
||||||
<a href="/books/create" class="text-pos"><i class="zmdi zmdi-edit"></i>Create one now</a>
|
<a href="/books/create" class="text-pos"><i class="zmdi zmdi-edit"></i>Create one now</a>
|
||||||
@endif
|
@endif
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-4 col-sm-offset-1">
|
<div class="col-sm-4 col-sm-offset-1">
|
||||||
<div id="recents">
|
<div id="recents">
|
||||||
|
23
resources/views/books/restrictions.blade.php
Normal file
23
resources/views/books/restrictions.blade.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
@extends('base')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
|
||||||
|
<div class="faded-small toolbar">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12 faded">
|
||||||
|
<div class="breadcrumbs">
|
||||||
|
<a href="{{$book->getUrl()}}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $book->getShortName() }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="container" ng-non-bindable>
|
||||||
|
<h1>Book Restrictions</h1>
|
||||||
|
@include('form/restriction-form', ['model' => $book])
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@stop
|
@ -2,23 +2,35 @@
|
|||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
|
|
||||||
<div class="faded-small toolbar" ng-non-bindable>
|
<div class="faded-small toolbar">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<div class="action-buttons faded">
|
<div class="action-buttons faded">
|
||||||
@if($currentUser->can('page-create'))
|
@if(userCan('page-create', $book))
|
||||||
<a href="{{$book->getUrl() . '/page/create'}}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i> New Page</a>
|
<a href="{{$book->getUrl() . '/page/create'}}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i> New Page</a>
|
||||||
@endif
|
@endif
|
||||||
@if($currentUser->can('chapter-create'))
|
@if(userCan('chapter-create', $book))
|
||||||
<a href="{{$book->getUrl() . '/chapter/create'}}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i> New Chapter</a>
|
<a href="{{$book->getUrl() . '/chapter/create'}}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i> New Chapter</a>
|
||||||
@endif
|
@endif
|
||||||
@if($currentUser->can('book-update'))
|
@if(userCan('book-update', $book))
|
||||||
<a href="{{$book->getEditUrl()}}" class="text-primary text-button"><i class="zmdi zmdi-edit"></i>Edit</a>
|
<a href="{{$book->getEditUrl()}}" class="text-primary text-button"><i class="zmdi zmdi-edit"></i>Edit</a>
|
||||||
<a href="{{ $book->getUrl() }}/sort" class="text-primary text-button"><i class="zmdi zmdi-sort"></i>Sort</a>
|
|
||||||
@endif
|
@endif
|
||||||
@if($currentUser->can('book-delete'))
|
@if(userCan('book-update', $book) || userCan('restrictions-manage', $book) || userCan('book-delete', $book))
|
||||||
<a href="{{ $book->getUrl() }}/delete" class="text-neg text-button"><i class="zmdi zmdi-delete"></i>Delete</a>
|
<div dropdown class="dropdown-container">
|
||||||
|
<a dropdown-toggle class="text-primary text-button"><i class="zmdi zmdi-more-vert"></i></a>
|
||||||
|
<ul>
|
||||||
|
@if(userCan('book-update', $book))
|
||||||
|
<li><a href="{{ $book->getUrl() }}/sort" class="text-primary"><i class="zmdi zmdi-sort"></i>Sort</a></li>
|
||||||
|
@endif
|
||||||
|
@if(userCan('restrictions-manage', $book))
|
||||||
|
<li><a href="{{$book->getUrl()}}/restrict" class="text-primary"><i class="zmdi zmdi-lock-outline"></i>Restrict</a></li>
|
||||||
|
@endif
|
||||||
|
@if(userCan('book-delete', $book))
|
||||||
|
<li><a href="{{ $book->getUrl() }}/delete" class="text-neg"><i class="zmdi zmdi-delete"></i>Delete</a></li>
|
||||||
|
@endif
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -75,6 +87,15 @@
|
|||||||
|
|
||||||
<div class="col-md-4 col-md-offset-1">
|
<div class="col-md-4 col-md-offset-1">
|
||||||
<div class="margin-top large"></div>
|
<div class="margin-top large"></div>
|
||||||
|
@if($book->restricted)
|
||||||
|
<p class="text-muted">
|
||||||
|
@if(userCan('restrictions-manage', $book))
|
||||||
|
<a href="{{ $book->getUrl() }}/restrict"><i class="zmdi zmdi-lock-outline"></i>Book Restricted</a>
|
||||||
|
@else
|
||||||
|
<i class="zmdi zmdi-lock-outline"></i>Book Restricted
|
||||||
|
@endif
|
||||||
|
</p>
|
||||||
|
@endif
|
||||||
<div class="search-box">
|
<div class="search-box">
|
||||||
<form ng-submit="searchBook($event)">
|
<form ng-submit="searchBook($event)">
|
||||||
<input ng-model="searchTerm" ng-change="checkSearchForm()" type="text" name="term" placeholder="Search This Book">
|
<input ng-model="searchTerm" ng-change="checkSearchForm()" type="text" name="term" placeholder="Search This Book">
|
||||||
|
24
resources/views/chapters/restrictions.blade.php
Normal file
24
resources/views/chapters/restrictions.blade.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
@extends('base')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
|
||||||
|
<div class="faded-small toolbar">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12 faded">
|
||||||
|
<div class="breadcrumbs">
|
||||||
|
<a href="{{$chapter->book->getUrl()}}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $chapter->book->getShortName() }}</a>
|
||||||
|
<span class="sep">»</span>
|
||||||
|
<a href="{{ $chapter->getUrl() }}" class="text-chapter text-button"><i class="zmdi zmdi-collection-bookmark"></i>{{$chapter->getShortName()}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container" ng-non-bindable>
|
||||||
|
<h1>Chapter Restrictions</h1>
|
||||||
|
@include('form/restriction-form', ['model' => $chapter])
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@stop
|
@ -12,13 +12,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-8 faded">
|
<div class="col-md-8 faded">
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
@if($currentUser->can('chapter-create'))
|
@if(userCan('page-create', $chapter))
|
||||||
<a href="{{$chapter->getUrl() . '/create-page'}}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>New Page</a>
|
<a href="{{$chapter->getUrl() . '/create-page'}}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>New Page</a>
|
||||||
@endif
|
@endif
|
||||||
@if($currentUser->can('chapter-update'))
|
@if(userCan('chapter-update', $chapter))
|
||||||
<a href="{{$chapter->getUrl() . '/edit'}}" class="text-primary text-button"><i class="zmdi zmdi-edit"></i>Edit</a>
|
<a href="{{$chapter->getUrl() . '/edit'}}" class="text-primary text-button"><i class="zmdi zmdi-edit"></i>Edit</a>
|
||||||
@endif
|
@endif
|
||||||
@if($currentUser->can('chapter-delete'))
|
@if(userCan('restrictions-manage', $chapter))
|
||||||
|
<a href="{{$chapter->getUrl()}}/restrict" class="text-primary text-button"><i class="zmdi zmdi-lock-outline"></i>Restrict</a>
|
||||||
|
@endif
|
||||||
|
@if(userCan('chapter-delete', $chapter))
|
||||||
<a href="{{$chapter->getUrl() . '/delete'}}" class="text-neg text-button"><i class="zmdi zmdi-delete"></i>Delete</a>
|
<a href="{{$chapter->getUrl() . '/delete'}}" class="text-neg text-button"><i class="zmdi zmdi-delete"></i>Delete</a>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@ -34,10 +37,10 @@
|
|||||||
<h1>{{ $chapter->name }}</h1>
|
<h1>{{ $chapter->name }}</h1>
|
||||||
<p class="text-muted">{{ $chapter->description }}</p>
|
<p class="text-muted">{{ $chapter->description }}</p>
|
||||||
|
|
||||||
@if(count($chapter->pages) > 0)
|
@if(count($pages) > 0)
|
||||||
<div class="page-list">
|
<div class="page-list">
|
||||||
<hr>
|
<hr>
|
||||||
@foreach($chapter->pages as $page)
|
@foreach($pages as $page)
|
||||||
@include('pages/list-item', ['page' => $page])
|
@include('pages/list-item', ['page' => $page])
|
||||||
<hr>
|
<hr>
|
||||||
@endforeach
|
@endforeach
|
||||||
@ -60,6 +63,29 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3 col-md-offset-1">
|
<div class="col-md-3 col-md-offset-1">
|
||||||
|
<div class="margin-top large"></div>
|
||||||
|
@if($book->restricted || $chapter->restricted)
|
||||||
|
<div class="text-muted">
|
||||||
|
|
||||||
|
@if($book->restricted)
|
||||||
|
@if(userCan('restrictions-manage', $book))
|
||||||
|
<a href="{{ $book->getUrl() }}/restrict"><i class="zmdi zmdi-lock-outline"></i>Book Restricted</a>
|
||||||
|
@else
|
||||||
|
<i class="zmdi zmdi-lock-outline"></i>Book Restricted
|
||||||
|
@endif
|
||||||
|
<br>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($chapter->restricted)
|
||||||
|
@if(userCan('restrictions-manage', $chapter))
|
||||||
|
<a href="{{ $chapter->getUrl() }}/restrict"><i class="zmdi zmdi-lock-outline"></i>Chapter Restricted</a>
|
||||||
|
@else
|
||||||
|
<i class="zmdi zmdi-lock-outline"></i>Chapter Restricted
|
||||||
|
@endif
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
@include('pages/sidebar-tree-list', ['book' => $book, 'sidebarTree' => $sidebarTree])
|
@include('pages/sidebar-tree-list', ['book' => $book, 'sidebarTree' => $sidebarTree])
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1 class="text-muted">Page Not Found</h1>
|
<h1 class="text-muted">{{ $message or 'Page Not Found' }}</h1>
|
||||||
<p>Sorry, The page you were looking for could not be found.</p>
|
<p>Sorry, The page you were looking for could not be found.</p>
|
||||||
<a href="/" class="button">Return To Home</a>
|
<a href="/" class="button">Return To Home</a>
|
||||||
</div>
|
</div>
|
||||||
|
12
resources/views/form/checkbox.blade.php
Normal file
12
resources/views/form/checkbox.blade.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
<label>
|
||||||
|
<input value="true" id="{{$name}}" type="checkbox" name="{{$name}}"
|
||||||
|
@if($errors->has($name)) class="neg" @endif
|
||||||
|
@if(old($name) || (!old() && isset($model) && $model->$name)) checked="checked" @endif
|
||||||
|
>
|
||||||
|
{{ $label }}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
@if($errors->has($name))
|
||||||
|
<div class="text-neg text-small">{{ $errors->first($name) }}</div>
|
||||||
|
@endif
|
7
resources/views/form/restriction-checkbox.blade.php
Normal file
7
resources/views/form/restriction-checkbox.blade.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
<label>
|
||||||
|
<input value="true" id="{{$name}}[{{$role->id}}][{{$action}}]" type="checkbox" name="{{$name}}[{{$role->id}}][{{$action}}]"
|
||||||
|
@if(old($name .'.'.$role->id.'.'.$action) || (!old() && isset($model) && $model->hasRestriction($role->id, $action))) checked="checked" @endif
|
||||||
|
>
|
||||||
|
{{ $label }}
|
||||||
|
</label>
|
29
resources/views/form/restriction-form.blade.php
Normal file
29
resources/views/form/restriction-form.blade.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<form action="{{ $model->getUrl() }}/restrict" method="POST">
|
||||||
|
{!! csrf_field() !!}
|
||||||
|
<input type="hidden" name="_method" value="PUT">
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
@include('form/checkbox', ['name' => 'restricted', 'label' => 'Restrict this ' . $model->getClassName()])
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="table">
|
||||||
|
<tr>
|
||||||
|
<th>Role</th>
|
||||||
|
<th @if($model->isA('page')) colspan="3" @else colspan="4" @endif>Actions</th>
|
||||||
|
</tr>
|
||||||
|
@foreach($roles as $role)
|
||||||
|
<tr>
|
||||||
|
<td>{{ $role->display_name }}</td>
|
||||||
|
<td>@include('form/restriction-checkbox', ['name'=>'restrictions', 'label' => 'View', 'action' => 'view'])</td>
|
||||||
|
@if(!$model->isA('page'))
|
||||||
|
<td>@include('form/restriction-checkbox', ['name'=>'restrictions', 'label' => 'Create', 'action' => 'create'])</td>
|
||||||
|
@endif
|
||||||
|
<td>@include('form/restriction-checkbox', ['name'=>'restrictions', 'label' => 'Update', 'action' => 'update'])</td>
|
||||||
|
<td>@include('form/restriction-checkbox', ['name'=>'restrictions', 'label' => 'Delete', 'action' => 'delete'])</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<a href="{{ $model->getUrl() }}" class="button muted">Cancel</a>
|
||||||
|
<button type="submit" class="button pos">Save Restrictions</button>
|
||||||
|
</form>
|
14
resources/views/form/role-checkboxes.blade.php
Normal file
14
resources/views/form/role-checkboxes.blade.php
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
@foreach($roles as $role)
|
||||||
|
<label>
|
||||||
|
<input value="{{ $role->id }}" id="{{$name}}-{{$role->name}}" type="checkbox" name="{{$name}}[{{$role->name}}]"
|
||||||
|
@if($errors->has($name)) class="neg" @endif
|
||||||
|
@if(old($name . '.' . $role->name) || (!old('name') && isset($model) && $model->hasRole($role->name))) checked="checked" @endif
|
||||||
|
>
|
||||||
|
{{ $role->display_name }}
|
||||||
|
</label>
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
@if($errors->has($name))
|
||||||
|
<div class="text-neg text-small">{{ $errors->first($name) }}</div>
|
||||||
|
@endif
|
@ -33,10 +33,14 @@
|
|||||||
|
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<h3><a class="no-color" href="/pages/recently-created">Recently Created Pages</a></h3>
|
<h3><a class="no-color" href="/pages/recently-created">Recently Created Pages</a></h3>
|
||||||
|
<div id="recently-created-pages">
|
||||||
@include('partials/entity-list', ['entities' => $recentlyCreatedPages, 'style' => 'compact'])
|
@include('partials/entity-list', ['entities' => $recentlyCreatedPages, 'style' => 'compact'])
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3><a class="no-color" href="/pages/recently-updated">Recently Updated Pages</a></h3>
|
<h3><a class="no-color" href="/pages/recently-updated">Recently Updated Pages</a></h3>
|
||||||
@include('partials/entity-list', ['entities' => $recentlyCreatedPages, 'style' => 'compact'])
|
<div id="recently-updated-pages">
|
||||||
|
@include('partials/entity-list', ['entities' => $recentlyUpdatedPages, 'style' => 'compact'])
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-4" id="recent-activity">
|
<div class="col-sm-4" id="recent-activity">
|
||||||
|
31
resources/views/pages/restrictions.blade.php
Normal file
31
resources/views/pages/restrictions.blade.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
@extends('base')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
|
||||||
|
<div class="faded-small toolbar">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12 faded">
|
||||||
|
<div class="breadcrumbs">
|
||||||
|
<a href="{{$page->book->getUrl()}}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $page->book->getShortName() }}</a>
|
||||||
|
@if($page->hasChapter())
|
||||||
|
<span class="sep">»</span>
|
||||||
|
<a href="{{ $page->chapter->getUrl() }}" class="text-chapter text-button">
|
||||||
|
<i class="zmdi zmdi-collection-bookmark"></i>
|
||||||
|
{{$page->chapter->getShortName()}}
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
<span class="sep">»</span>
|
||||||
|
<a href="{{$page->getUrl()}}" class="text-book text-button"><i class="zmdi zmdi-file"></i>{{ $page->getShortName() }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container" ng-non-bindable>
|
||||||
|
<h1>Page Restrictions</h1>
|
||||||
|
@include('form/restriction-form', ['model' => $page])
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@stop
|
@ -22,17 +22,20 @@
|
|||||||
<span dropdown class="dropdown-container">
|
<span dropdown class="dropdown-container">
|
||||||
<div dropdown-toggle class="text-button text-primary"><i class="zmdi zmdi-open-in-new"></i>Export</div>
|
<div dropdown-toggle class="text-button text-primary"><i class="zmdi zmdi-open-in-new"></i>Export</div>
|
||||||
<ul class="wide">
|
<ul class="wide">
|
||||||
<li><a href="{{$page->getUrl() . '/export/html'}}" target="_blank">Contained Web File <span class="text-muted float right">.html</span></a></li>
|
<li><a href="{{$page->getUrl()}}/export/html" target="_blank">Contained Web File <span class="text-muted float right">.html</span></a></li>
|
||||||
<li><a href="{{$page->getUrl() . '/export/pdf'}}" target="_blank">PDF File <span class="text-muted float right">.pdf</span></a></li>
|
<li><a href="{{$page->getUrl()}}/export/pdf" target="_blank">PDF File <span class="text-muted float right">.pdf</span></a></li>
|
||||||
<li><a href="{{$page->getUrl() . '/export/plaintext'}}" target="_blank">Plain Text File <span class="text-muted float right">.txt</span></a></li>
|
<li><a href="{{$page->getUrl()}}/export/plaintext" target="_blank">Plain Text File <span class="text-muted float right">.txt</span></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
@if($currentUser->can('page-update'))
|
@if(userCan('page-update', $page))
|
||||||
<a href="{{$page->getUrl() . '/revisions'}}" class="text-primary text-button"><i class="zmdi zmdi-replay"></i>Revisions</a>
|
<a href="{{$page->getUrl()}}/revisions" class="text-primary text-button"><i class="zmdi zmdi-replay"></i>Revisions</a>
|
||||||
<a href="{{$page->getUrl() . '/edit'}}" class="text-primary text-button" ><i class="zmdi zmdi-edit"></i>Edit</a>
|
<a href="{{$page->getUrl()}}/edit" class="text-primary text-button" ><i class="zmdi zmdi-edit"></i>Edit</a>
|
||||||
@endif
|
@endif
|
||||||
@if($currentUser->can('page-delete'))
|
@if(userCan('restrictions-manage', $page))
|
||||||
<a href="{{$page->getUrl() . '/delete'}}" class="text-neg text-button"><i class="zmdi zmdi-delete"></i>Delete</a>
|
<a href="{{$page->getUrl()}}/restrict" class="text-primary text-button"><i class="zmdi zmdi-lock-outline"></i>Restrict</a>
|
||||||
|
@endif
|
||||||
|
@if(userCan('page-delete', $page))
|
||||||
|
<a href="{{$page->getUrl()}}/delete" class="text-neg text-button"><i class="zmdi zmdi-delete"></i>Delete</a>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -67,7 +70,38 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3 print-hidden">
|
<div class="col-md-3 print-hidden">
|
||||||
|
<div class="margin-top large"></div>
|
||||||
|
@if($book->restricted || ($page->chapter && $page->chapter->restricted) || $page->restricted)
|
||||||
|
<div class="text-muted">
|
||||||
|
|
||||||
|
@if($book->restricted)
|
||||||
|
@if(userCan('restrictions-manage', $book))
|
||||||
|
<a href="{{ $book->getUrl() }}/restrict"><i class="zmdi zmdi-lock-outline"></i>Book restricted</a>
|
||||||
|
@else
|
||||||
|
<i class="zmdi zmdi-lock-outline"></i>Book restricted
|
||||||
|
@endif
|
||||||
|
<br>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($page->chapter && $page->chapter->restricted)
|
||||||
|
@if(userCan('restrictions-manage', $page->chapter))
|
||||||
|
<a href="{{ $page->chapter->getUrl() }}/restrict"><i class="zmdi zmdi-lock-outline"></i>Chapter restricted</a>
|
||||||
|
@else
|
||||||
|
<i class="zmdi zmdi-lock-outline"></i>Chapter restricted
|
||||||
|
@endif
|
||||||
|
<br>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($page->restricted)
|
||||||
|
@if(userCan('restrictions-manage', $page))
|
||||||
|
<a href="{{ $page->getUrl() }}/restrict"><i class="zmdi zmdi-lock-outline"></i>Page restricted</a>
|
||||||
|
@else
|
||||||
|
<i class="zmdi zmdi-lock-outline"></i>Page restricted
|
||||||
|
@endif
|
||||||
|
<br>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
@include('pages/sidebar-tree-list', ['book' => $book, 'sidebarTree' => $sidebarTree])
|
@include('pages/sidebar-tree-list', ['book' => $book, 'sidebarTree' => $sidebarTree])
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
{{ $activity->getText() }}
|
{{ $activity->getText() }}
|
||||||
|
|
||||||
@if($activity->entity())
|
@if($activity->entity)
|
||||||
<a href="{{ $activity->entity()->getUrl() }}">{{ $activity->entity()->name }}</a>
|
<a href="{{ $activity->entity->getUrl() }}">{{ $activity->entity->name }}</a>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@if($activity->extra) "{{$activity->extra}}" @endif
|
@if($activity->extra) "{{$activity->extra}}" @endif
|
||||||
|
@ -59,7 +59,7 @@
|
|||||||
<select id="setting-registration-role" name="setting-registration-role" @if($errors->has('setting-registration-role')) class="neg" @endif>
|
<select id="setting-registration-role" name="setting-registration-role" @if($errors->has('setting-registration-role')) class="neg" @endif>
|
||||||
@foreach(\BookStack\Role::all() as $role)
|
@foreach(\BookStack\Role::all() as $role)
|
||||||
<option value="{{$role->id}}"
|
<option value="{{$role->id}}"
|
||||||
@if(\Setting::get('registration-role', \BookStack\Role::getDefault()->id) == $role->id) selected @endif
|
@if(\Setting::get('registration-role', \BookStack\Role::first()->id) == $role->id) selected @endif
|
||||||
>
|
>
|
||||||
{{ $role->display_name }}
|
{{ $role->display_name }}
|
||||||
</option>
|
</option>
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
<div class="col-md-12 setting-nav">
|
<div class="col-md-12 setting-nav">
|
||||||
<a href="/settings" @if($selected == 'settings') class="selected text-button" @endif><i class="zmdi zmdi-settings"></i>Settings</a>
|
<a href="/settings" @if($selected == 'settings') class="selected text-button" @endif><i class="zmdi zmdi-settings"></i>Settings</a>
|
||||||
<a href="/settings/users" @if($selected == 'users') class="selected text-button" @endif><i class="zmdi zmdi-accounts"></i>Users</a>
|
<a href="/settings/users" @if($selected == 'users') class="selected text-button" @endif><i class="zmdi zmdi-accounts"></i>Users</a>
|
||||||
|
<a href="/settings/roles" @if($selected == 'roles') class="selected text-button" @endif><i class="zmdi zmdi-lock-open"></i>Roles</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
3
resources/views/settings/roles/checkbox.blade.php
Normal file
3
resources/views/settings/roles/checkbox.blade.php
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<input type="checkbox" name="permissions[{{ $permission }}]"
|
||||||
|
@if(old('permissions.'.$permission, false)|| (!old('display_name', false) && (isset($role) && $role->hasPermission($permission)))) checked="checked" @endif
|
||||||
|
value="true">
|
15
resources/views/settings/roles/create.blade.php
Normal file
15
resources/views/settings/roles/create.blade.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
@extends('base')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
|
||||||
|
@include('settings/navbar', ['selected' => 'roles'])
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<h1>Create New Role</h1>
|
||||||
|
|
||||||
|
<form action="/settings/roles/new" method="POST">
|
||||||
|
@include('settings/roles/form')
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@stop
|
28
resources/views/settings/roles/delete.blade.php
Normal file
28
resources/views/settings/roles/delete.blade.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
@extends('base')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
|
||||||
|
@include('settings/navbar', ['selected' => 'roles'])
|
||||||
|
|
||||||
|
<div class="container small" ng-non-bindable>
|
||||||
|
<h1>Delete Role</h1>
|
||||||
|
<p>This will delete the role with the name '{{$role->display_name}}'.</p>
|
||||||
|
|
||||||
|
<form action="/settings/roles/delete/{{$role->id}}" method="POST">
|
||||||
|
{!! csrf_field() !!}
|
||||||
|
<input type="hidden" name="_method" value="DELETE">
|
||||||
|
|
||||||
|
@if($role->users->count() > 0)
|
||||||
|
<div class="form-group">
|
||||||
|
<p>This role has {{$role->users->count()}} users assigned to it. If you would like to migrate the users from this role select a new role below.</p>
|
||||||
|
@include('form/role-select', ['options' => $roles, 'name' => 'migration_role_id'])
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<p class="text-neg">Are you sure you want to delete this role?</p>
|
||||||
|
<a href="/settings/roles/{{ $role->id }}" class="button">Cancel</a>
|
||||||
|
<button type="submit" class="button neg">Confirm</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@stop
|
24
resources/views/settings/roles/edit.blade.php
Normal file
24
resources/views/settings/roles/edit.blade.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
@extends('base')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
|
||||||
|
@include('settings/navbar', ['selected' => 'roles'])
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<h1>Edit Role <small> {{ $role->display_name }}</small></h1>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<p></p>
|
||||||
|
<a href="/settings/roles/delete/{{ $role->id }}" class="button neg float right">Delete Role</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="/settings/roles/{{ $role->id }}" method="POST">
|
||||||
|
<input type="hidden" name="_method" value="PUT">
|
||||||
|
@include('settings/roles/form', ['model' => $role])
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@stop
|
117
resources/views/settings/roles/form.blade.php
Normal file
117
resources/views/settings/roles/form.blade.php
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
{!! csrf_field() !!}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h3>Role Details</h3>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">Role Name</label>
|
||||||
|
@include('form/text', ['name' => 'display_name'])
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">Short Role Description</label>
|
||||||
|
@include('form/text', ['name' => 'description'])
|
||||||
|
</div>
|
||||||
|
<h3>System Permissions</h3>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label> @include('settings/roles/checkbox', ['permission' => 'users-manage']) Manage users</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label>@include('settings/roles/checkbox', ['permission' => 'user-roles-manage']) Manage user roles</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="even">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label>@include('settings/roles/checkbox', ['permission' => 'restrictions-manage-all']) Manage all restrictions</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label>@include('settings/roles/checkbox', ['permission' => 'restrictions-manage-own']) Manage restrictions on own content</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="even">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>@include('settings/roles/checkbox', ['permission' => 'settings-manage']) Manage app settings</label>
|
||||||
|
</div>
|
||||||
|
<hr class="even">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
|
||||||
|
<h3>Asset Permissions</h3>
|
||||||
|
<p>
|
||||||
|
These permissions control default access to the assets within the system. <br>
|
||||||
|
Restrictions on Books, Chapters and Pages will override these permissions.
|
||||||
|
</p>
|
||||||
|
<table class="table">
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th>Create</th>
|
||||||
|
<th>Edit</th>
|
||||||
|
<th>Delete</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Books</td>
|
||||||
|
<td>
|
||||||
|
<label>@include('settings/roles/checkbox', ['permission' => 'book-create-all']) All</label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<label>@include('settings/roles/checkbox', ['permission' => 'book-update-own']) Own</label>
|
||||||
|
<label>@include('settings/roles/checkbox', ['permission' => 'book-update-all']) All</label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<label>@include('settings/roles/checkbox', ['permission' => 'book-delete-own']) Own</label>
|
||||||
|
<label>@include('settings/roles/checkbox', ['permission' => 'book-delete-all']) All</label>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Chapters</td>
|
||||||
|
<td>
|
||||||
|
<label>@include('settings/roles/checkbox', ['permission' => 'chapter-create-own']) Own</label>
|
||||||
|
<label>@include('settings/roles/checkbox', ['permission' => 'chapter-create-all']) All</label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<label>@include('settings/roles/checkbox', ['permission' => 'chapter-update-own']) Own</label>
|
||||||
|
<label>@include('settings/roles/checkbox', ['permission' => 'chapter-update-all']) All</label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<label>@include('settings/roles/checkbox', ['permission' => 'chapter-delete-own']) Own</label>
|
||||||
|
<label>@include('settings/roles/checkbox', ['permission' => 'chapter-delete-all']) All</label>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Pages</td>
|
||||||
|
<td>
|
||||||
|
<label>@include('settings/roles/checkbox', ['permission' => 'page-create-own']) Own</label>
|
||||||
|
<label>@include('settings/roles/checkbox', ['permission' => 'page-create-all']) All</label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<label>@include('settings/roles/checkbox', ['permission' => 'page-update-own']) Own</label>
|
||||||
|
<label>@include('settings/roles/checkbox', ['permission' => 'page-update-all']) All</label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<label>@include('settings/roles/checkbox', ['permission' => 'page-delete-own']) Own</label>
|
||||||
|
<label>@include('settings/roles/checkbox', ['permission' => 'page-delete-all']) All</label>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Images</td>
|
||||||
|
<td>@include('settings/roles/checkbox', ['permission' => 'image-create-all'])</td>
|
||||||
|
<td>
|
||||||
|
<label>@include('settings/roles/checkbox', ['permission' => 'image-update-own']) Own</label>
|
||||||
|
<label>@include('settings/roles/checkbox', ['permission' => 'image-update-all']) All</label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<label>@include('settings/roles/checkbox', ['permission' => 'image-delete-own']) Own</label>
|
||||||
|
<label>@include('settings/roles/checkbox', ['permission' => 'image-delete-all']) All</label>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="/settings/roles" class="button muted">Cancel</a>
|
||||||
|
<button type="submit" class="button pos">Save Role</button>
|
31
resources/views/settings/roles/index.blade.php
Normal file
31
resources/views/settings/roles/index.blade.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
@extends('base')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
|
||||||
|
@include('settings/navbar', ['selected' => 'roles'])
|
||||||
|
|
||||||
|
<div class="container small">
|
||||||
|
|
||||||
|
<h1>User Roles</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="/settings/roles/new" class="text-pos"><i class="zmdi zmdi-lock-open"></i>Add new role</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<table class="table">
|
||||||
|
<tr>
|
||||||
|
<th>Role Name</th>
|
||||||
|
<th></th>
|
||||||
|
<th class="text-right">Users</th>
|
||||||
|
</tr>
|
||||||
|
@foreach($roles as $role)
|
||||||
|
<tr>
|
||||||
|
<td><a href="/settings/roles/{{ $role->id }}">{{ $role->display_name }}</a></td>
|
||||||
|
<td>{{ $role->description }}</td>
|
||||||
|
<td class="text-right">{{ $role->users->count() }}</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@stop
|
@ -3,21 +3,21 @@
|
|||||||
@include('form.text', ['name' => 'name'])
|
@include('form.text', ['name' => 'name'])
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if($currentUser->can('user-update'))
|
@if(userCan('users-manage'))
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="email">Email</label>
|
<label for="email">Email</label>
|
||||||
@include('form.text', ['name' => 'email'])
|
@include('form.text', ['name' => 'email'])
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@if($currentUser->can('user-update'))
|
@if(userCan('users-manage'))
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="role">User Role</label>
|
<label for="role">User Role</label>
|
||||||
@include('form.role-select', ['name' => 'role', 'options' => \BookStack\Role::all(), 'displayKey' => 'display_name'])
|
@include('form/role-checkboxes', ['name' => 'roles', 'roles' => \BookStack\Role::all()])
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@if($currentUser->can('user-update'))
|
@if(userCan('users-manage'))
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="external_auth_id">External Authentication ID</label>
|
<label for="external_auth_id">External Authentication ID</label>
|
||||||
@include('form.text', ['name' => 'external_auth_id'])
|
@include('form.text', ['name' => 'external_auth_id'])
|
||||||
|
@ -8,10 +8,10 @@
|
|||||||
@include('form.text', ['name' => 'email'])
|
@include('form.text', ['name' => 'email'])
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if($currentUser->can('user-update'))
|
@if(userCan('users-manage'))
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="role">User Role</label>
|
<label for="role">User Role</label>
|
||||||
@include('form.role-select', ['name' => 'role', 'options' => \BookStack\Role::all(), 'displayKey' => 'display_name'])
|
@include('form/role-checkboxes', ['name' => 'roles', 'roles' => \BookStack\Role::all()])
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<div class="container small" ng-non-bindable>
|
<div class="container small" ng-non-bindable>
|
||||||
<h1>Users</h1>
|
<h1>Users</h1>
|
||||||
@if($currentUser->can('user-create'))
|
@if(userCan('users-manage'))
|
||||||
<p>
|
<p>
|
||||||
<a href="/settings/users/create" class="text-pos"><i class="zmdi zmdi-account-add"></i>Add new user</a>
|
<a href="/settings/users/create" class="text-pos"><i class="zmdi zmdi-account-add"></i>Add new user</a>
|
||||||
</p>
|
</p>
|
||||||
@ -18,30 +18,32 @@
|
|||||||
<th></th>
|
<th></th>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Email</th>
|
<th>Email</th>
|
||||||
<th>User Type</th>
|
<th>User Roles</th>
|
||||||
</tr>
|
</tr>
|
||||||
@foreach($users as $user)
|
@foreach($users as $user)
|
||||||
<tr>
|
<tr>
|
||||||
<td style="line-height: 0;"><img class="avatar med" src="{{$user->getAvatar(40)}}" alt="{{$user->name}}"></td>
|
<td style="line-height: 0;"><img class="avatar med" src="{{$user->getAvatar(40)}}" alt="{{$user->name}}"></td>
|
||||||
<td>
|
<td>
|
||||||
@if($currentUser->can('user-update') || $currentUser->id == $user->id)
|
@if(userCan('users-manage') || $currentUser->id == $user->id)
|
||||||
<a href="/settings/users/{{$user->id}}">
|
<a href="/settings/users/{{$user->id}}">
|
||||||
@endif
|
@endif
|
||||||
{{ $user->name }}
|
{{ $user->name }}
|
||||||
@if($currentUser->can('user-update') || $currentUser->id == $user->id)
|
@if(userCan('users-manage') || $currentUser->id == $user->id)
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@if($currentUser->can('user-update') || $currentUser->id == $user->id)
|
@if(userCan('users-manage') || $currentUser->id == $user->id)
|
||||||
<a href="/settings/users/{{$user->id}}">
|
<a href="/settings/users/{{$user->id}}">
|
||||||
@endif
|
@endif
|
||||||
{{ $user->email }}
|
{{ $user->email }}
|
||||||
@if($currentUser->can('user-update') || $currentUser->id == $user->id)
|
@if(userCan('users-manage') || $currentUser->id == $user->id)
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
<td>{{ $user->role->display_name }}</td>
|
<td>
|
||||||
|
<small> {{ $user->roles->implode('display_name', ', ') }}</small>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@endforeach
|
@endforeach
|
||||||
</table>
|
</table>
|
||||||
|
@ -133,12 +133,12 @@ class AuthTest extends TestCase
|
|||||||
->click('Add new user')
|
->click('Add new user')
|
||||||
->type($user->name, '#name')
|
->type($user->name, '#name')
|
||||||
->type($user->email, '#email')
|
->type($user->email, '#email')
|
||||||
->select(2, '#role')
|
->check('roles[admin]')
|
||||||
->type($user->password, '#password')
|
->type($user->password, '#password')
|
||||||
->type($user->password, '#password-confirm')
|
->type($user->password, '#password-confirm')
|
||||||
->press('Save')
|
->press('Save')
|
||||||
->seeInDatabase('users', $user->toArray())
|
|
||||||
->seePageIs('/settings/users')
|
->seePageIs('/settings/users')
|
||||||
|
->seeInDatabase('users', $user->toArray())
|
||||||
->see($user->name);
|
->see($user->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,4 +225,22 @@ class EntityTest extends TestCase
|
|||||||
->seePageIs($newPageUrl);
|
->seePageIs($newPageUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_recently_updated_pages_on_home()
|
||||||
|
{
|
||||||
|
$page = \BookStack\Page::orderBy('updated_at', 'asc')->first();
|
||||||
|
$this->asAdmin()->visit('/')
|
||||||
|
->dontSeeInElement('#recently-updated-pages', $page->name);
|
||||||
|
$this->visit($page->getUrl() . '/edit')
|
||||||
|
->press('Save Page')
|
||||||
|
->visit('/')
|
||||||
|
->seeInElement('#recently-updated-pages', $page->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_recently_created_pages_on_home()
|
||||||
|
{
|
||||||
|
$entityChain = $this->createEntityChainBelongingToUser($this->getNewUser());
|
||||||
|
$this->asAdmin()->visit('/')
|
||||||
|
->seeInElement('#recently-created-pages', $entityChain['page']->name);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
407
tests/RestrictionsTest.php
Normal file
407
tests/RestrictionsTest.php
Normal file
@ -0,0 +1,407 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class RestrictionsTest extends TestCase
|
||||||
|
{
|
||||||
|
protected $user;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
$this->user = $this->getNewUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manually set some restrictions on an entity.
|
||||||
|
* @param \BookStack\Entity $entity
|
||||||
|
* @param $actions
|
||||||
|
*/
|
||||||
|
protected function setEntityRestrictions(\BookStack\Entity $entity, $actions)
|
||||||
|
{
|
||||||
|
$entity->restricted = true;
|
||||||
|
$entity->restrictions()->delete();
|
||||||
|
$role = $this->user->roles->first();
|
||||||
|
foreach ($actions as $action) {
|
||||||
|
$entity->restrictions()->create([
|
||||||
|
'role_id' => $role->id,
|
||||||
|
'action' => strtolower($action)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$entity->save();
|
||||||
|
$entity->load('restrictions');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_book_view_restriction()
|
||||||
|
{
|
||||||
|
$book = \BookStack\Book::first();
|
||||||
|
$bookPage = $book->pages->first();
|
||||||
|
$bookChapter = $book->chapters->first();
|
||||||
|
|
||||||
|
$bookUrl = $book->getUrl();
|
||||||
|
$this->actingAs($this->user)
|
||||||
|
->visit($bookUrl)
|
||||||
|
->seePageIs($bookUrl);
|
||||||
|
|
||||||
|
$this->setEntityRestrictions($book, []);
|
||||||
|
|
||||||
|
$this->forceVisit($bookUrl)
|
||||||
|
->see('Book not found');
|
||||||
|
$this->forceVisit($bookPage->getUrl())
|
||||||
|
->see('Book not found');
|
||||||
|
$this->forceVisit($bookChapter->getUrl())
|
||||||
|
->see('Book not found');
|
||||||
|
|
||||||
|
$this->setEntityRestrictions($book, ['view']);
|
||||||
|
|
||||||
|
$this->visit($bookUrl)
|
||||||
|
->see($book->name);
|
||||||
|
$this->visit($bookPage->getUrl())
|
||||||
|
->see($bookPage->name);
|
||||||
|
$this->visit($bookChapter->getUrl())
|
||||||
|
->see($bookChapter->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_book_create_restriction()
|
||||||
|
{
|
||||||
|
$book = \BookStack\Book::first();
|
||||||
|
|
||||||
|
$bookUrl = $book->getUrl();
|
||||||
|
$this->actingAs($this->user)
|
||||||
|
->visit($bookUrl)
|
||||||
|
->seeInElement('.action-buttons', 'New Page')
|
||||||
|
->seeInElement('.action-buttons', 'New Chapter');
|
||||||
|
|
||||||
|
$this->setEntityRestrictions($book, ['view', 'delete', 'update']);
|
||||||
|
|
||||||
|
$this->forceVisit($bookUrl . '/chapter/create')
|
||||||
|
->see('You do not have permission')->seePageIs('/');
|
||||||
|
$this->forceVisit($bookUrl . '/page/create')
|
||||||
|
->see('You do not have permission')->seePageIs('/');
|
||||||
|
$this->visit($bookUrl)->dontSeeInElement('.action-buttons', 'New Page')
|
||||||
|
->dontSeeInElement('.action-buttons', 'New Chapter');
|
||||||
|
|
||||||
|
$this->setEntityRestrictions($book, ['view', 'create']);
|
||||||
|
|
||||||
|
$this->visit($bookUrl . '/chapter/create')
|
||||||
|
->type('test chapter', 'name')
|
||||||
|
->type('test description for chapter', 'description')
|
||||||
|
->press('Save Chapter')
|
||||||
|
->seePageIs($bookUrl . '/chapter/test-chapter');
|
||||||
|
$this->visit($bookUrl . '/page/create')
|
||||||
|
->type('test page', 'name')
|
||||||
|
->type('test content', 'html')
|
||||||
|
->press('Save Page')
|
||||||
|
->seePageIs($bookUrl . '/page/test-page');
|
||||||
|
$this->visit($bookUrl)->seeInElement('.action-buttons', 'New Page')
|
||||||
|
->seeInElement('.action-buttons', 'New Chapter');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_book_update_restriction()
|
||||||
|
{
|
||||||
|
$book = \BookStack\Book::first();
|
||||||
|
$bookPage = $book->pages->first();
|
||||||
|
$bookChapter = $book->chapters->first();
|
||||||
|
|
||||||
|
$bookUrl = $book->getUrl();
|
||||||
|
$this->actingAs($this->user)
|
||||||
|
->visit($bookUrl . '/edit')
|
||||||
|
->see('Edit Book');
|
||||||
|
|
||||||
|
$this->setEntityRestrictions($book, ['view', 'delete']);
|
||||||
|
|
||||||
|
$this->forceVisit($bookUrl . '/edit')
|
||||||
|
->see('You do not have permission')->seePageIs('/');
|
||||||
|
$this->forceVisit($bookPage->getUrl() . '/edit')
|
||||||
|
->see('You do not have permission')->seePageIs('/');
|
||||||
|
$this->forceVisit($bookChapter->getUrl() . '/edit')
|
||||||
|
->see('You do not have permission')->seePageIs('/');
|
||||||
|
|
||||||
|
$this->setEntityRestrictions($book, ['view', 'update']);
|
||||||
|
|
||||||
|
$this->visit($bookUrl . '/edit')
|
||||||
|
->seePageIs($bookUrl . '/edit');
|
||||||
|
$this->visit($bookPage->getUrl() . '/edit')
|
||||||
|
->seePageIs($bookPage->getUrl() . '/edit');
|
||||||
|
$this->visit($bookChapter->getUrl() . '/edit')
|
||||||
|
->see('Edit Chapter');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_book_delete_restriction()
|
||||||
|
{
|
||||||
|
$book = \BookStack\Book::first();
|
||||||
|
$bookPage = $book->pages->first();
|
||||||
|
$bookChapter = $book->chapters->first();
|
||||||
|
|
||||||
|
$bookUrl = $book->getUrl();
|
||||||
|
$this->actingAs($this->user)
|
||||||
|
->visit($bookUrl . '/delete')
|
||||||
|
->see('Delete Book');
|
||||||
|
|
||||||
|
$this->setEntityRestrictions($book, ['view', 'update']);
|
||||||
|
|
||||||
|
$this->forceVisit($bookUrl . '/delete')
|
||||||
|
->see('You do not have permission')->seePageIs('/');
|
||||||
|
$this->forceVisit($bookPage->getUrl() . '/delete')
|
||||||
|
->see('You do not have permission')->seePageIs('/');
|
||||||
|
$this->forceVisit($bookChapter->getUrl() . '/delete')
|
||||||
|
->see('You do not have permission')->seePageIs('/');
|
||||||
|
|
||||||
|
$this->setEntityRestrictions($book, ['view', 'delete']);
|
||||||
|
|
||||||
|
$this->visit($bookUrl . '/delete')
|
||||||
|
->seePageIs($bookUrl . '/delete')->see('Delete Book');
|
||||||
|
$this->visit($bookPage->getUrl() . '/delete')
|
||||||
|
->seePageIs($bookPage->getUrl() . '/delete')->see('Delete Page');
|
||||||
|
$this->visit($bookChapter->getUrl() . '/delete')
|
||||||
|
->see('Delete Chapter');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_chapter_view_restriction()
|
||||||
|
{
|
||||||
|
$chapter = \BookStack\Chapter::first();
|
||||||
|
$chapterPage = $chapter->pages->first();
|
||||||
|
|
||||||
|
$chapterUrl = $chapter->getUrl();
|
||||||
|
$this->actingAs($this->user)
|
||||||
|
->visit($chapterUrl)
|
||||||
|
->seePageIs($chapterUrl);
|
||||||
|
|
||||||
|
$this->setEntityRestrictions($chapter, []);
|
||||||
|
|
||||||
|
$this->forceVisit($chapterUrl)
|
||||||
|
->see('Chapter not found');
|
||||||
|
$this->forceVisit($chapterPage->getUrl())
|
||||||
|
->see('Page not found');
|
||||||
|
|
||||||
|
$this->setEntityRestrictions($chapter, ['view']);
|
||||||
|
|
||||||
|
$this->visit($chapterUrl)
|
||||||
|
->see($chapter->name);
|
||||||
|
$this->visit($chapterPage->getUrl())
|
||||||
|
->see($chapterPage->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_chapter_create_restriction()
|
||||||
|
{
|
||||||
|
$chapter = \BookStack\Chapter::first();
|
||||||
|
|
||||||
|
$chapterUrl = $chapter->getUrl();
|
||||||
|
$this->actingAs($this->user)
|
||||||
|
->visit($chapterUrl)
|
||||||
|
->seeInElement('.action-buttons', 'New Page');
|
||||||
|
|
||||||
|
$this->setEntityRestrictions($chapter, ['view', 'delete', 'update']);
|
||||||
|
|
||||||
|
$this->forceVisit($chapterUrl . '/create-page')
|
||||||
|
->see('You do not have permission')->seePageIs('/');
|
||||||
|
$this->visit($chapterUrl)->dontSeeInElement('.action-buttons', 'New Page');
|
||||||
|
|
||||||
|
$this->setEntityRestrictions($chapter, ['view', 'create']);
|
||||||
|
|
||||||
|
|
||||||
|
$this->visit($chapterUrl . '/create-page')
|
||||||
|
->type('test page', 'name')
|
||||||
|
->type('test content', 'html')
|
||||||
|
->press('Save Page')
|
||||||
|
->seePageIs($chapter->book->getUrl() . '/page/test-page');
|
||||||
|
$this->visit($chapterUrl)->seeInElement('.action-buttons', 'New Page');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_chapter_update_restriction()
|
||||||
|
{
|
||||||
|
$chapter = \BookStack\Chapter::first();
|
||||||
|
$chapterPage = $chapter->pages->first();
|
||||||
|
|
||||||
|
$chapterUrl = $chapter->getUrl();
|
||||||
|
$this->actingAs($this->user)
|
||||||
|
->visit($chapterUrl . '/edit')
|
||||||
|
->see('Edit Chapter');
|
||||||
|
|
||||||
|
$this->setEntityRestrictions($chapter, ['view', 'delete']);
|
||||||
|
|
||||||
|
$this->forceVisit($chapterUrl . '/edit')
|
||||||
|
->see('You do not have permission')->seePageIs('/');
|
||||||
|
$this->forceVisit($chapterPage->getUrl() . '/edit')
|
||||||
|
->see('You do not have permission')->seePageIs('/');
|
||||||
|
|
||||||
|
$this->setEntityRestrictions($chapter, ['view', 'update']);
|
||||||
|
|
||||||
|
$this->visit($chapterUrl . '/edit')
|
||||||
|
->seePageIs($chapterUrl . '/edit')->see('Edit Chapter');
|
||||||
|
$this->visit($chapterPage->getUrl() . '/edit')
|
||||||
|
->seePageIs($chapterPage->getUrl() . '/edit');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_chapter_delete_restriction()
|
||||||
|
{
|
||||||
|
$chapter = \BookStack\Chapter::first();
|
||||||
|
$chapterPage = $chapter->pages->first();
|
||||||
|
|
||||||
|
$chapterUrl = $chapter->getUrl();
|
||||||
|
$this->actingAs($this->user)
|
||||||
|
->visit($chapterUrl . '/delete')
|
||||||
|
->see('Delete Chapter');
|
||||||
|
|
||||||
|
$this->setEntityRestrictions($chapter, ['view', 'update']);
|
||||||
|
|
||||||
|
$this->forceVisit($chapterUrl . '/delete')
|
||||||
|
->see('You do not have permission')->seePageIs('/');
|
||||||
|
$this->forceVisit($chapterPage->getUrl() . '/delete')
|
||||||
|
->see('You do not have permission')->seePageIs('/');
|
||||||
|
|
||||||
|
$this->setEntityRestrictions($chapter, ['view', 'delete']);
|
||||||
|
|
||||||
|
$this->visit($chapterUrl . '/delete')
|
||||||
|
->seePageIs($chapterUrl . '/delete')->see('Delete Chapter');
|
||||||
|
$this->visit($chapterPage->getUrl() . '/delete')
|
||||||
|
->seePageIs($chapterPage->getUrl() . '/delete')->see('Delete Page');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_page_view_restriction()
|
||||||
|
{
|
||||||
|
$page = \BookStack\Page::first();
|
||||||
|
|
||||||
|
$pageUrl = $page->getUrl();
|
||||||
|
$this->actingAs($this->user)
|
||||||
|
->visit($pageUrl)
|
||||||
|
->seePageIs($pageUrl);
|
||||||
|
|
||||||
|
$this->setEntityRestrictions($page, ['update', 'delete']);
|
||||||
|
|
||||||
|
$this->forceVisit($pageUrl)
|
||||||
|
->see('Page not found');
|
||||||
|
|
||||||
|
$this->setEntityRestrictions($page, ['view']);
|
||||||
|
|
||||||
|
$this->visit($pageUrl)
|
||||||
|
->see($page->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_page_update_restriction()
|
||||||
|
{
|
||||||
|
$page = \BookStack\Chapter::first();
|
||||||
|
|
||||||
|
$pageUrl = $page->getUrl();
|
||||||
|
$this->actingAs($this->user)
|
||||||
|
->visit($pageUrl . '/edit')
|
||||||
|
->seeInField('name', $page->name);
|
||||||
|
|
||||||
|
$this->setEntityRestrictions($page, ['view', 'delete']);
|
||||||
|
|
||||||
|
$this->forceVisit($pageUrl . '/edit')
|
||||||
|
->see('You do not have permission')->seePageIs('/');
|
||||||
|
|
||||||
|
$this->setEntityRestrictions($page, ['view', 'update']);
|
||||||
|
|
||||||
|
$this->visit($pageUrl . '/edit')
|
||||||
|
->seePageIs($pageUrl . '/edit')->seeInField('name', $page->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_page_delete_restriction()
|
||||||
|
{
|
||||||
|
$page = \BookStack\Page::first();
|
||||||
|
|
||||||
|
$pageUrl = $page->getUrl();
|
||||||
|
$this->actingAs($this->user)
|
||||||
|
->visit($pageUrl . '/delete')
|
||||||
|
->see('Delete Page');
|
||||||
|
|
||||||
|
$this->setEntityRestrictions($page, ['view', 'update']);
|
||||||
|
|
||||||
|
$this->forceVisit($pageUrl . '/delete')
|
||||||
|
->see('You do not have permission')->seePageIs('/');
|
||||||
|
|
||||||
|
$this->setEntityRestrictions($page, ['view', 'delete']);
|
||||||
|
|
||||||
|
$this->visit($pageUrl . '/delete')
|
||||||
|
->seePageIs($pageUrl . '/delete')->see('Delete Page');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_book_restriction_form()
|
||||||
|
{
|
||||||
|
$book = \BookStack\Book::first();
|
||||||
|
$this->asAdmin()->visit($book->getUrl() . '/restrict')
|
||||||
|
->see('Book Restrictions')
|
||||||
|
->check('restricted')
|
||||||
|
->check('restrictions[2][view]')
|
||||||
|
->press('Save Restrictions')
|
||||||
|
->seeInDatabase('books', ['id' => $book->id, 'restricted' => true])
|
||||||
|
->seeInDatabase('restrictions', [
|
||||||
|
'restrictable_id' => $book->id,
|
||||||
|
'restrictable_type' => 'BookStack\Book',
|
||||||
|
'role_id' => '2',
|
||||||
|
'action' => 'view'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_chapter_restriction_form()
|
||||||
|
{
|
||||||
|
$chapter = \BookStack\Chapter::first();
|
||||||
|
$this->asAdmin()->visit($chapter->getUrl() . '/restrict')
|
||||||
|
->see('Chapter Restrictions')
|
||||||
|
->check('restricted')
|
||||||
|
->check('restrictions[2][update]')
|
||||||
|
->press('Save Restrictions')
|
||||||
|
->seeInDatabase('chapters', ['id' => $chapter->id, 'restricted' => true])
|
||||||
|
->seeInDatabase('restrictions', [
|
||||||
|
'restrictable_id' => $chapter->id,
|
||||||
|
'restrictable_type' => 'BookStack\Chapter',
|
||||||
|
'role_id' => '2',
|
||||||
|
'action' => 'update'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_page_restriction_form()
|
||||||
|
{
|
||||||
|
$page = \BookStack\Page::first();
|
||||||
|
$this->asAdmin()->visit($page->getUrl() . '/restrict')
|
||||||
|
->see('Page Restrictions')
|
||||||
|
->check('restricted')
|
||||||
|
->check('restrictions[2][delete]')
|
||||||
|
->press('Save Restrictions')
|
||||||
|
->seeInDatabase('pages', ['id' => $page->id, 'restricted' => true])
|
||||||
|
->seeInDatabase('restrictions', [
|
||||||
|
'restrictable_id' => $page->id,
|
||||||
|
'restrictable_type' => 'BookStack\Page',
|
||||||
|
'role_id' => '2',
|
||||||
|
'action' => 'delete'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_restricted_pages_not_visible_in_book_navigation_on_pages()
|
||||||
|
{
|
||||||
|
$chapter = \BookStack\Chapter::first();
|
||||||
|
$page = $chapter->pages->first();
|
||||||
|
$page2 = $chapter->pages[2];
|
||||||
|
|
||||||
|
$this->setEntityRestrictions($page, []);
|
||||||
|
|
||||||
|
$this->actingAs($this->user)
|
||||||
|
->visit($page2->getUrl())
|
||||||
|
->dontSeeInElement('.sidebar-page-list', $page->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_restricted_pages_not_visible_in_book_navigation_on_chapters()
|
||||||
|
{
|
||||||
|
$chapter = \BookStack\Chapter::first();
|
||||||
|
$page = $chapter->pages->first();
|
||||||
|
|
||||||
|
$this->setEntityRestrictions($page, []);
|
||||||
|
|
||||||
|
$this->actingAs($this->user)
|
||||||
|
->visit($chapter->getUrl())
|
||||||
|
->dontSeeInElement('.sidebar-page-list', $page->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_restricted_pages_not_visible_on_chapter_pages()
|
||||||
|
{
|
||||||
|
$chapter = \BookStack\Chapter::first();
|
||||||
|
$page = $chapter->pages->first();
|
||||||
|
|
||||||
|
$this->setEntityRestrictions($page, []);
|
||||||
|
|
||||||
|
$this->actingAs($this->user)
|
||||||
|
->visit($chapter->getUrl())
|
||||||
|
->dontSee($page->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
510
tests/RolesTest.php
Normal file
510
tests/RolesTest.php
Normal file
@ -0,0 +1,510 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class RolesTest extends TestCase
|
||||||
|
{
|
||||||
|
protected $user;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
$this->user = $this->getNewBlankUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Give the given user some permissions.
|
||||||
|
* @param \BookStack\User $user
|
||||||
|
* @param array $permissions
|
||||||
|
*/
|
||||||
|
protected function giveUserPermissions(\BookStack\User $user, $permissions = [])
|
||||||
|
{
|
||||||
|
$newRole = $this->createNewRole($permissions);
|
||||||
|
$user->attachRole($newRole);
|
||||||
|
$user->load('roles');
|
||||||
|
$user->permissions(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new basic role for testing purposes.
|
||||||
|
* @param array $permissions
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
protected function createNewRole($permissions = [])
|
||||||
|
{
|
||||||
|
$permissionRepo = app('BookStack\Repos\PermissionsRepo');
|
||||||
|
$roleData = factory(\BookStack\Role::class)->make()->toArray();
|
||||||
|
$roleData['permissions'] = array_flip($permissions);
|
||||||
|
return $permissionRepo->saveNewRole($roleData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_admin_can_see_settings()
|
||||||
|
{
|
||||||
|
$this->asAdmin()->visit('/settings')->see('Settings');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_cannot_delete_admin_role()
|
||||||
|
{
|
||||||
|
$adminRole = \BookStack\Role::getRole('admin');
|
||||||
|
$deletePageUrl = '/settings/roles/delete/' . $adminRole->id;
|
||||||
|
$this->asAdmin()->visit($deletePageUrl)
|
||||||
|
->press('Confirm')
|
||||||
|
->seePageIs($deletePageUrl)
|
||||||
|
->see('cannot be deleted');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_role_cannot_be_deleted_if_default()
|
||||||
|
{
|
||||||
|
$newRole = $this->createNewRole();
|
||||||
|
$this->setSettings(['registration-role' => $newRole->id]);
|
||||||
|
|
||||||
|
$deletePageUrl = '/settings/roles/delete/' . $newRole->id;
|
||||||
|
$this->asAdmin()->visit($deletePageUrl)
|
||||||
|
->press('Confirm')
|
||||||
|
->seePageIs($deletePageUrl)
|
||||||
|
->see('cannot be deleted');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_role_create_update_delete_flow()
|
||||||
|
{
|
||||||
|
$testRoleName = 'Test Role';
|
||||||
|
$testRoleDesc = 'a little test description';
|
||||||
|
$testRoleUpdateName = 'An Super Updated role';
|
||||||
|
|
||||||
|
// Creation
|
||||||
|
$this->asAdmin()->visit('/settings')
|
||||||
|
->click('Roles')
|
||||||
|
->seePageIs('/settings/roles')
|
||||||
|
->click('Add new role')
|
||||||
|
->type('Test Role', 'display_name')
|
||||||
|
->type('A little test description', 'description')
|
||||||
|
->press('Save Role')
|
||||||
|
->seeInDatabase('roles', ['display_name' => $testRoleName, 'name' => 'test-role', 'description' => $testRoleDesc])
|
||||||
|
->seePageIs('/settings/roles');
|
||||||
|
// Updating
|
||||||
|
$this->asAdmin()->visit('/settings/roles')
|
||||||
|
->see($testRoleDesc)
|
||||||
|
->click($testRoleName)
|
||||||
|
->type($testRoleUpdateName, '#display_name')
|
||||||
|
->press('Save Role')
|
||||||
|
->seeInDatabase('roles', ['display_name' => $testRoleUpdateName, 'name' => 'test-role', 'description' => $testRoleDesc])
|
||||||
|
->seePageIs('/settings/roles');
|
||||||
|
// Deleting
|
||||||
|
$this->asAdmin()->visit('/settings/roles')
|
||||||
|
->click($testRoleUpdateName)
|
||||||
|
->click('Delete Role')
|
||||||
|
->see($testRoleUpdateName)
|
||||||
|
->press('Confirm')
|
||||||
|
->seePageIs('/settings/roles')
|
||||||
|
->dontSee($testRoleUpdateName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_manage_user_permission()
|
||||||
|
{
|
||||||
|
$this->actingAs($this->user)->visit('/')->visit('/settings/users')
|
||||||
|
->seePageIs('/');
|
||||||
|
$this->giveUserPermissions($this->user, ['users-manage']);
|
||||||
|
$this->actingAs($this->user)->visit('/')->visit('/settings/users')
|
||||||
|
->seePageIs('/settings/users');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_user_roles_manage_permission()
|
||||||
|
{
|
||||||
|
$this->actingAs($this->user)->visit('/')->visit('/settings/roles')
|
||||||
|
->seePageIs('/')->visit('/settings/roles/1')->seePageIs('/');
|
||||||
|
$this->giveUserPermissions($this->user, ['user-roles-manage']);
|
||||||
|
$this->actingAs($this->user)->visit('/settings/roles')
|
||||||
|
->seePageIs('/settings/roles')->click('Admin')
|
||||||
|
->see('Edit Role');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_settings_manage_permission()
|
||||||
|
{
|
||||||
|
$this->actingAs($this->user)->visit('/')->visit('/settings')
|
||||||
|
->seePageIs('/');
|
||||||
|
$this->giveUserPermissions($this->user, ['settings-manage']);
|
||||||
|
$this->actingAs($this->user)->visit('/')->visit('/settings')
|
||||||
|
->seePageIs('/settings')->press('Save Settings')->see('Settings Saved');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_restrictions_manage_all_permission()
|
||||||
|
{
|
||||||
|
$page = \BookStack\Page::take(1)->get()->first();
|
||||||
|
$this->actingAs($this->user)->visit($page->getUrl())
|
||||||
|
->dontSee('Restrict')
|
||||||
|
->visit($page->getUrl() . '/restrict')
|
||||||
|
->seePageIs('/');
|
||||||
|
$this->giveUserPermissions($this->user, ['restrictions-manage-all']);
|
||||||
|
$this->actingAs($this->user)->visit($page->getUrl())
|
||||||
|
->see('Restrict')
|
||||||
|
->click('Restrict')
|
||||||
|
->see('Page Restrictions')->seePageIs($page->getUrl() . '/restrict');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_restrictions_manage_own_permission()
|
||||||
|
{
|
||||||
|
$otherUsersPage = \BookStack\Page::take(1)->get()->first();
|
||||||
|
$content = $this->createEntityChainBelongingToUser($this->user);
|
||||||
|
// Check can't restrict other's content
|
||||||
|
$this->actingAs($this->user)->visit($otherUsersPage->getUrl())
|
||||||
|
->dontSee('Restrict')
|
||||||
|
->visit($otherUsersPage->getUrl() . '/restrict')
|
||||||
|
->seePageIs('/');
|
||||||
|
// Check can't restrict own content
|
||||||
|
$this->actingAs($this->user)->visit($content['page']->getUrl())
|
||||||
|
->dontSee('Restrict')
|
||||||
|
->visit($content['page']->getUrl() . '/restrict')
|
||||||
|
->seePageIs('/');
|
||||||
|
|
||||||
|
$this->giveUserPermissions($this->user, ['restrictions-manage-own']);
|
||||||
|
|
||||||
|
// Check can't restrict other's content
|
||||||
|
$this->actingAs($this->user)->visit($otherUsersPage->getUrl())
|
||||||
|
->dontSee('Restrict')
|
||||||
|
->visit($otherUsersPage->getUrl() . '/restrict')
|
||||||
|
->seePageIs('/');
|
||||||
|
// Check can restrict own content
|
||||||
|
$this->actingAs($this->user)->visit($content['page']->getUrl())
|
||||||
|
->see('Restrict')
|
||||||
|
->click('Restrict')
|
||||||
|
->seePageIs($content['page']->getUrl() . '/restrict');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check a standard entity access permission
|
||||||
|
* @param string $permission
|
||||||
|
* @param array $accessUrls Urls that are only accessible after having the permission
|
||||||
|
* @param array $visibles Check this text, In the buttons toolbar, is only visible with the permission
|
||||||
|
* @param null $callback
|
||||||
|
*/
|
||||||
|
private function checkAccessPermission($permission, $accessUrls = [], $visibles = [])
|
||||||
|
{
|
||||||
|
foreach ($accessUrls as $url) {
|
||||||
|
$this->actingAs($this->user)->visit('/')->visit($url)
|
||||||
|
->seePageIs('/');
|
||||||
|
}
|
||||||
|
foreach ($visibles as $url => $text) {
|
||||||
|
$this->actingAs($this->user)->visit('/')->visit($url)
|
||||||
|
->dontSeeInElement('.action-buttons',$text);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->giveUserPermissions($this->user, [$permission]);
|
||||||
|
|
||||||
|
foreach ($accessUrls as $url) {
|
||||||
|
$this->actingAs($this->user)->visit('/')->visit($url)
|
||||||
|
->seePageIs($url);
|
||||||
|
}
|
||||||
|
foreach ($visibles as $url => $text) {
|
||||||
|
$this->actingAs($this->user)->visit('/')->visit($url)
|
||||||
|
->see($text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_books_create_all_permissions()
|
||||||
|
{
|
||||||
|
$this->checkAccessPermission('book-create-all', [
|
||||||
|
'/books/create'
|
||||||
|
], [
|
||||||
|
'/books' => 'Add new book'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->visit('/books/create')
|
||||||
|
->type('test book', 'name')
|
||||||
|
->type('book desc', 'description')
|
||||||
|
->press('Save Book')
|
||||||
|
->seePageIs('/books/test-book');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_books_edit_own_permission()
|
||||||
|
{
|
||||||
|
$otherBook = \BookStack\Book::take(1)->get()->first();
|
||||||
|
$ownBook = $this->createEntityChainBelongingToUser($this->user)['book'];
|
||||||
|
$this->checkAccessPermission('book-update-own', [
|
||||||
|
$ownBook->getUrl() . '/edit'
|
||||||
|
], [
|
||||||
|
$ownBook->getUrl() => 'Edit'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->visit($otherBook->getUrl())
|
||||||
|
->dontSeeInElement('.action-buttons', 'Edit')
|
||||||
|
->visit($otherBook->getUrl() . '/edit')
|
||||||
|
->seePageIs('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_books_edit_all_permission()
|
||||||
|
{
|
||||||
|
$otherBook = \BookStack\Book::take(1)->get()->first();
|
||||||
|
$this->checkAccessPermission('book-update-all', [
|
||||||
|
$otherBook->getUrl() . '/edit'
|
||||||
|
], [
|
||||||
|
$otherBook->getUrl() => 'Edit'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_books_delete_own_permission()
|
||||||
|
{
|
||||||
|
$this->giveUserPermissions($this->user, ['book-update-all']);
|
||||||
|
$otherBook = \BookStack\Book::take(1)->get()->first();
|
||||||
|
$ownBook = $this->createEntityChainBelongingToUser($this->user)['book'];
|
||||||
|
$this->checkAccessPermission('book-delete-own', [
|
||||||
|
$ownBook->getUrl() . '/delete'
|
||||||
|
], [
|
||||||
|
$ownBook->getUrl() => 'Delete'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->visit($otherBook->getUrl())
|
||||||
|
->dontSeeInElement('.action-buttons', 'Delete')
|
||||||
|
->visit($otherBook->getUrl() . '/delete')
|
||||||
|
->seePageIs('/');
|
||||||
|
$this->visit($ownBook->getUrl())->visit($ownBook->getUrl() . '/delete')
|
||||||
|
->press('Confirm')
|
||||||
|
->seePageIs('/books')
|
||||||
|
->dontSee($ownBook->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_books_delete_all_permission()
|
||||||
|
{
|
||||||
|
$this->giveUserPermissions($this->user, ['book-update-all']);
|
||||||
|
$otherBook = \BookStack\Book::take(1)->get()->first();
|
||||||
|
$this->checkAccessPermission('book-delete-all', [
|
||||||
|
$otherBook->getUrl() . '/delete'
|
||||||
|
], [
|
||||||
|
$otherBook->getUrl() => 'Delete'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->visit($otherBook->getUrl())->visit($otherBook->getUrl() . '/delete')
|
||||||
|
->press('Confirm')
|
||||||
|
->seePageIs('/books')
|
||||||
|
->dontSee($otherBook->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_chapter_create_own_permissions()
|
||||||
|
{
|
||||||
|
$book = \BookStack\Book::take(1)->get()->first();
|
||||||
|
$ownBook = $this->createEntityChainBelongingToUser($this->user)['book'];
|
||||||
|
$baseUrl = $ownBook->getUrl() . '/chapter';
|
||||||
|
$this->checkAccessPermission('chapter-create-own', [
|
||||||
|
$baseUrl . '/create'
|
||||||
|
], [
|
||||||
|
$ownBook->getUrl() => 'New Chapter'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->visit($baseUrl . '/create')
|
||||||
|
->type('test chapter', 'name')
|
||||||
|
->type('chapter desc', 'description')
|
||||||
|
->press('Save Chapter')
|
||||||
|
->seePageIs($baseUrl . '/test-chapter');
|
||||||
|
|
||||||
|
$this->visit($book->getUrl())
|
||||||
|
->dontSeeInElement('.action-buttons', 'New Chapter')
|
||||||
|
->visit($book->getUrl() . '/chapter/create')
|
||||||
|
->seePageIs('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_chapter_create_all_permissions()
|
||||||
|
{
|
||||||
|
$book = \BookStack\Book::take(1)->get()->first();
|
||||||
|
$baseUrl = $book->getUrl() . '/chapter';
|
||||||
|
$this->checkAccessPermission('chapter-create-all', [
|
||||||
|
$baseUrl . '/create'
|
||||||
|
], [
|
||||||
|
$book->getUrl() => 'New Chapter'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->visit($baseUrl . '/create')
|
||||||
|
->type('test chapter', 'name')
|
||||||
|
->type('chapter desc', 'description')
|
||||||
|
->press('Save Chapter')
|
||||||
|
->seePageIs($baseUrl . '/test-chapter');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_chapter_edit_own_permission()
|
||||||
|
{
|
||||||
|
$otherChapter = \BookStack\Chapter::take(1)->get()->first();
|
||||||
|
$ownChapter = $this->createEntityChainBelongingToUser($this->user)['chapter'];
|
||||||
|
$this->checkAccessPermission('chapter-update-own', [
|
||||||
|
$ownChapter->getUrl() . '/edit'
|
||||||
|
], [
|
||||||
|
$ownChapter->getUrl() => 'Edit'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->visit($otherChapter->getUrl())
|
||||||
|
->dontSeeInElement('.action-buttons', 'Edit')
|
||||||
|
->visit($otherChapter->getUrl() . '/edit')
|
||||||
|
->seePageIs('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_chapter_edit_all_permission()
|
||||||
|
{
|
||||||
|
$otherChapter = \BookStack\Chapter::take(1)->get()->first();
|
||||||
|
$this->checkAccessPermission('chapter-update-all', [
|
||||||
|
$otherChapter->getUrl() . '/edit'
|
||||||
|
], [
|
||||||
|
$otherChapter->getUrl() => 'Edit'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_chapter_delete_own_permission()
|
||||||
|
{
|
||||||
|
$this->giveUserPermissions($this->user, ['chapter-update-all']);
|
||||||
|
$otherChapter = \BookStack\Chapter::take(1)->get()->first();
|
||||||
|
$ownChapter = $this->createEntityChainBelongingToUser($this->user)['chapter'];
|
||||||
|
$this->checkAccessPermission('chapter-delete-own', [
|
||||||
|
$ownChapter->getUrl() . '/delete'
|
||||||
|
], [
|
||||||
|
$ownChapter->getUrl() => 'Delete'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$bookUrl = $ownChapter->book->getUrl();
|
||||||
|
$this->visit($otherChapter->getUrl())
|
||||||
|
->dontSeeInElement('.action-buttons', 'Delete')
|
||||||
|
->visit($otherChapter->getUrl() . '/delete')
|
||||||
|
->seePageIs('/');
|
||||||
|
$this->visit($ownChapter->getUrl())->visit($ownChapter->getUrl() . '/delete')
|
||||||
|
->press('Confirm')
|
||||||
|
->seePageIs($bookUrl)
|
||||||
|
->dontSeeInElement('.book-content', $ownChapter->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_chapter_delete_all_permission()
|
||||||
|
{
|
||||||
|
$this->giveUserPermissions($this->user, ['chapter-update-all']);
|
||||||
|
$otherChapter = \BookStack\Chapter::take(1)->get()->first();
|
||||||
|
$this->checkAccessPermission('chapter-delete-all', [
|
||||||
|
$otherChapter->getUrl() . '/delete'
|
||||||
|
], [
|
||||||
|
$otherChapter->getUrl() => 'Delete'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$bookUrl = $otherChapter->book->getUrl();
|
||||||
|
$this->visit($otherChapter->getUrl())->visit($otherChapter->getUrl() . '/delete')
|
||||||
|
->press('Confirm')
|
||||||
|
->seePageIs($bookUrl)
|
||||||
|
->dontSeeInElement('.book-content', $otherChapter->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_page_create_own_permissions()
|
||||||
|
{
|
||||||
|
$book = \BookStack\Book::take(1)->get()->first();
|
||||||
|
$chapter = \BookStack\Chapter::take(1)->get()->first();
|
||||||
|
|
||||||
|
$entities = $this->createEntityChainBelongingToUser($this->user);
|
||||||
|
$ownBook = $entities['book'];
|
||||||
|
$ownChapter = $entities['chapter'];
|
||||||
|
|
||||||
|
$baseUrl = $ownBook->getUrl() . '/page';
|
||||||
|
|
||||||
|
$this->checkAccessPermission('page-create-own', [
|
||||||
|
$baseUrl . '/create',
|
||||||
|
$ownChapter->getUrl() . '/create-page'
|
||||||
|
], [
|
||||||
|
$ownBook->getUrl() => 'New Page',
|
||||||
|
$ownChapter->getUrl() => 'New Page'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->visit($baseUrl . '/create')
|
||||||
|
->type('test page', 'name')
|
||||||
|
->type('page desc', 'html')
|
||||||
|
->press('Save Page')
|
||||||
|
->seePageIs($baseUrl . '/test-page');
|
||||||
|
|
||||||
|
$this->visit($book->getUrl())
|
||||||
|
->dontSeeInElement('.action-buttons', 'New Page')
|
||||||
|
->visit($book->getUrl() . '/page/create')
|
||||||
|
->seePageIs('/');
|
||||||
|
$this->visit($chapter->getUrl())
|
||||||
|
->dontSeeInElement('.action-buttons', 'New Page')
|
||||||
|
->visit($chapter->getUrl() . '/create-page')
|
||||||
|
->seePageIs('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_page_create_all_permissions()
|
||||||
|
{
|
||||||
|
$book = \BookStack\Book::take(1)->get()->first();
|
||||||
|
$chapter = \BookStack\Chapter::take(1)->get()->first();
|
||||||
|
$baseUrl = $book->getUrl() . '/page';
|
||||||
|
$this->checkAccessPermission('page-create-all', [
|
||||||
|
$baseUrl . '/create',
|
||||||
|
$chapter->getUrl() . '/create-page'
|
||||||
|
], [
|
||||||
|
$book->getUrl() => 'New Page',
|
||||||
|
$chapter->getUrl() => 'New Page'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->visit($baseUrl . '/create')
|
||||||
|
->type('test page', 'name')
|
||||||
|
->type('page desc', 'html')
|
||||||
|
->press('Save Page')
|
||||||
|
->seePageIs($baseUrl . '/test-page');
|
||||||
|
|
||||||
|
$this->visit($chapter->getUrl() . '/create-page')
|
||||||
|
->type('new test page', 'name')
|
||||||
|
->type('page desc', 'html')
|
||||||
|
->press('Save Page')
|
||||||
|
->seePageIs($baseUrl . '/new-test-page');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_page_edit_own_permission()
|
||||||
|
{
|
||||||
|
$otherPage = \BookStack\Page::take(1)->get()->first();
|
||||||
|
$ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
|
||||||
|
$this->checkAccessPermission('page-update-own', [
|
||||||
|
$ownPage->getUrl() . '/edit'
|
||||||
|
], [
|
||||||
|
$ownPage->getUrl() => 'Edit'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->visit($otherPage->getUrl())
|
||||||
|
->dontSeeInElement('.action-buttons', 'Edit')
|
||||||
|
->visit($otherPage->getUrl() . '/edit')
|
||||||
|
->seePageIs('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_page_edit_all_permission()
|
||||||
|
{
|
||||||
|
$otherPage = \BookStack\Page::take(1)->get()->first();
|
||||||
|
$this->checkAccessPermission('page-update-all', [
|
||||||
|
$otherPage->getUrl() . '/edit'
|
||||||
|
], [
|
||||||
|
$otherPage->getUrl() => 'Edit'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_page_delete_own_permission()
|
||||||
|
{
|
||||||
|
$this->giveUserPermissions($this->user, ['page-update-all']);
|
||||||
|
$otherPage = \BookStack\Page::take(1)->get()->first();
|
||||||
|
$ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
|
||||||
|
$this->checkAccessPermission('page-delete-own', [
|
||||||
|
$ownPage->getUrl() . '/delete'
|
||||||
|
], [
|
||||||
|
$ownPage->getUrl() => 'Delete'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$bookUrl = $ownPage->book->getUrl();
|
||||||
|
$this->visit($otherPage->getUrl())
|
||||||
|
->dontSeeInElement('.action-buttons', 'Delete')
|
||||||
|
->visit($otherPage->getUrl() . '/delete')
|
||||||
|
->seePageIs('/');
|
||||||
|
$this->visit($ownPage->getUrl())->visit($ownPage->getUrl() . '/delete')
|
||||||
|
->press('Confirm')
|
||||||
|
->seePageIs($bookUrl)
|
||||||
|
->dontSeeInElement('.book-content', $ownPage->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_page_delete_all_permission()
|
||||||
|
{
|
||||||
|
$this->giveUserPermissions($this->user, ['page-update-all']);
|
||||||
|
$otherPage = \BookStack\Page::take(1)->get()->first();
|
||||||
|
$this->checkAccessPermission('page-delete-all', [
|
||||||
|
$otherPage->getUrl() . '/delete'
|
||||||
|
], [
|
||||||
|
$otherPage->getUrl() => 'Delete'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$bookUrl = $otherPage->book->getUrl();
|
||||||
|
$this->visit($otherPage->getUrl())->visit($otherPage->getUrl() . '/delete')
|
||||||
|
->press('Confirm')
|
||||||
|
->seePageIs($bookUrl)
|
||||||
|
->dontSeeInElement('.book-content', $otherPage->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||||
|
use Symfony\Component\DomCrawler\Crawler;
|
||||||
|
|
||||||
class TestCase extends Illuminate\Foundation\Testing\TestCase
|
class TestCase extends Illuminate\Foundation\Testing\TestCase
|
||||||
{
|
{
|
||||||
@ -32,7 +33,8 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase
|
|||||||
public function asAdmin()
|
public function asAdmin()
|
||||||
{
|
{
|
||||||
if($this->admin === null) {
|
if($this->admin === null) {
|
||||||
$this->admin = \BookStack\User::find(1);
|
$adminRole = \BookStack\Role::getRole('admin');
|
||||||
|
$this->admin = $adminRole->users->first();
|
||||||
}
|
}
|
||||||
return $this->actingAs($this->admin);
|
return $this->actingAs($this->admin);
|
||||||
}
|
}
|
||||||
@ -78,8 +80,19 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase
|
|||||||
protected function getNewUser($attributes = [])
|
protected function getNewUser($attributes = [])
|
||||||
{
|
{
|
||||||
$user = factory(\BookStack\User::class)->create($attributes);
|
$user = factory(\BookStack\User::class)->create($attributes);
|
||||||
$userRepo = app('BookStack\Repos\UserRepo');
|
$role = \BookStack\Role::getRole('editor');
|
||||||
$userRepo->attachDefaultRole($user);
|
$user->attachRole($role);;
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quick way to create a new user without any permissions
|
||||||
|
* @param array $attributes
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
protected function getNewBlankUser($attributes = [])
|
||||||
|
{
|
||||||
|
$user = factory(\BookStack\User::class)->create($attributes);
|
||||||
return $user;
|
return $user;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,6 +123,40 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert that the current page matches a given URI.
|
||||||
|
*
|
||||||
|
* @param string $uri
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
protected function seePageUrlIs($uri)
|
||||||
|
{
|
||||||
|
$this->assertEquals(
|
||||||
|
$uri, $this->currentUri, "Did not land on expected page [{$uri}].\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do a forced visit that does not error out on exception.
|
||||||
|
* @param string $uri
|
||||||
|
* @param array $parameters
|
||||||
|
* @param array $cookies
|
||||||
|
* @param array $files
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
protected function forceVisit($uri, $parameters = [], $cookies = [], $files = [])
|
||||||
|
{
|
||||||
|
$method = 'GET';
|
||||||
|
$uri = $this->prepareUrlForRequest($uri);
|
||||||
|
$this->call($method, $uri, $parameters, $cookies, $files);
|
||||||
|
$this->clearInputs()->followRedirects();
|
||||||
|
$this->currentUri = $this->app->make('request')->fullUrl();
|
||||||
|
$this->crawler = new Crawler($this->response->getContent(), $uri);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Click the text within the selected element.
|
* Click the text within the selected element.
|
||||||
* @param $parentElement
|
* @param $parentElement
|
||||||
|
Loading…
Reference in New Issue
Block a user