Merge branch 'custom_role_system'
Conflicts: app/Repos/BookRepo.php app/Repos/ChapterRepo.php app/Repos/PageRepo.php
Showing
72 changed files
with
2143 additions
and
297 deletions
| ... | @@ -15,15 +15,11 @@ class Activity extends Model | ... | @@ -15,15 +15,11 @@ class Activity extends Model |
| 15 | 15 | ||
| 16 | /** | 16 | /** |
| 17 | * Get the entity for this activity. | 17 | * Get the entity for this activity. |
| 18 | - * @return bool | ||
| 19 | */ | 18 | */ |
| 20 | public function entity() | 19 | public function entity() |
| 21 | { | 20 | { |
| 22 | - if ($this->entity_id) { | 21 | + if ($this->entity_type === '') $this->entity_type = null; |
| 23 | - return $this->morphTo('entity')->first(); | 22 | + return $this->morphTo('entity'); |
| 24 | - } else { | ||
| 25 | - return false; | ||
| 26 | - } | ||
| 27 | } | 23 | } |
| 28 | 24 | ||
| 29 | /** | 25 | /** | ... | ... |
| 1 | -<?php | 1 | +<?php namespace BookStack; |
| 2 | 2 | ||
| 3 | -namespace BookStack; | ||
| 4 | 3 | ||
| 5 | -use Illuminate\Database\Eloquent\Model; | 4 | +abstract class Entity extends Ownable |
| 6 | - | ||
| 7 | -abstract class Entity extends Model | ||
| 8 | { | 5 | { |
| 9 | 6 | ||
| 10 | - use Ownable; | ||
| 11 | - | ||
| 12 | /** | 7 | /** |
| 13 | * Compares this entity to another given entity. | 8 | * Compares this entity to another given entity. |
| 14 | * Matches by comparing class and id. | 9 | * Matches by comparing class and id. |
| ... | @@ -53,7 +48,6 @@ abstract class Entity extends Model | ... | @@ -53,7 +48,6 @@ abstract class Entity extends Model |
| 53 | 48 | ||
| 54 | /** | 49 | /** |
| 55 | * Get View objects for this entity. | 50 | * Get View objects for this entity. |
| 56 | - * @return mixed | ||
| 57 | */ | 51 | */ |
| 58 | public function views() | 52 | public function views() |
| 59 | { | 53 | { |
| ... | @@ -61,27 +55,37 @@ abstract class Entity extends Model | ... | @@ -61,27 +55,37 @@ abstract class Entity extends Model |
| 61 | } | 55 | } |
| 62 | 56 | ||
| 63 | /** | 57 | /** |
| 64 | - * Allows checking of the exact class, Used to check entity type. | 58 | + * Get this entities restrictions. |
| 65 | - * Cleaner method for is_a. | 59 | + */ |
| 66 | - * @param $type | 60 | + public function restrictions() |
| 61 | + { | ||
| 62 | + return $this->morphMany('BookStack\Restriction', 'restrictable'); | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + /** | ||
| 66 | + * Check if this entity has a specific restriction set against it. | ||
| 67 | + * @param $role_id | ||
| 68 | + * @param $action | ||
| 67 | * @return bool | 69 | * @return bool |
| 68 | */ | 70 | */ |
| 69 | - public static function isA($type) | 71 | + public function hasRestriction($role_id, $action) |
| 70 | { | 72 | { |
| 71 | - return static::getClassName() === strtolower($type); | 73 | + return $this->restrictions->where('role_id', $role_id)->where('action', $action)->count() > 0; |
| 72 | } | 74 | } |
| 73 | 75 | ||
| 74 | /** | 76 | /** |
| 75 | - * Gets the class name. | 77 | + * Allows checking of the exact class, Used to check entity type. |
| 76 | - * @return string | 78 | + * Cleaner method for is_a. |
| 79 | + * @param $type | ||
| 80 | + * @return bool | ||
| 77 | */ | 81 | */ |
| 78 | - public static function getClassName() | 82 | + public static function isA($type) |
| 79 | { | 83 | { |
| 80 | - return strtolower(array_slice(explode('\\', static::class), -1, 1)[0]); | 84 | + return static::getClassName() === strtolower($type); |
| 81 | } | 85 | } |
| 82 | 86 | ||
| 83 | /** | 87 | /** |
| 84 | - *Gets a limited-length version of the entities name. | 88 | + * Gets a limited-length version of the entities name. |
| 85 | * @param int $length | 89 | * @param int $length |
| 86 | * @return string | 90 | * @return string |
| 87 | */ | 91 | */ | ... | ... |
| ... | @@ -56,7 +56,8 @@ class Handler extends ExceptionHandler | ... | @@ -56,7 +56,8 @@ class Handler extends ExceptionHandler |
| 56 | // Which will include the basic message to point the user roughly to the cause. | 56 | // Which will include the basic message to point the user roughly to the cause. |
| 57 | if (($e instanceof PrettyException || $e->getPrevious() instanceof PrettyException) && !config('app.debug')) { | 57 | if (($e instanceof PrettyException || $e->getPrevious() instanceof PrettyException) && !config('app.debug')) { |
| 58 | $message = ($e instanceof PrettyException) ? $e->getMessage() : $e->getPrevious()->getMessage(); | 58 | $message = ($e instanceof PrettyException) ? $e->getMessage() : $e->getPrevious()->getMessage(); |
| 59 | - return response()->view('errors/500', ['message' => $message], 500); | 59 | + $code = ($e->getCode() === 0) ? 500 : $e->getCode(); |
| 60 | + return response()->view('errors/' . $code, ['message' => $message], $code); | ||
| 60 | } | 61 | } |
| 61 | 62 | ||
| 62 | return parent::render($request, $e); | 63 | return parent::render($request, $e); | ... | ... |
app/Exceptions/NotFoundException.php
0 → 100644
| 1 | +<?php namespace BookStack\Exceptions; | ||
| 2 | + | ||
| 3 | + | ||
| 4 | +class NotFoundException extends PrettyException { | ||
| 5 | + | ||
| 6 | + /** | ||
| 7 | + * NotFoundException constructor. | ||
| 8 | + * @param string $message | ||
| 9 | + */ | ||
| 10 | + public function __construct($message = 'Item not found') | ||
| 11 | + { | ||
| 12 | + parent::__construct($message, 404); | ||
| 13 | + } | ||
| 14 | +} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
app/Exceptions/PermissionsException.php
0 → 100644
| ... | @@ -3,6 +3,7 @@ | ... | @@ -3,6 +3,7 @@ |
| 3 | namespace BookStack\Http\Controllers; | 3 | namespace BookStack\Http\Controllers; |
| 4 | 4 | ||
| 5 | use Activity; | 5 | use Activity; |
| 6 | +use BookStack\Repos\UserRepo; | ||
| 6 | use Illuminate\Http\Request; | 7 | use Illuminate\Http\Request; |
| 7 | 8 | ||
| 8 | use Illuminate\Support\Facades\Auth; | 9 | use Illuminate\Support\Facades\Auth; |
| ... | @@ -19,18 +20,21 @@ class BookController extends Controller | ... | @@ -19,18 +20,21 @@ class BookController extends Controller |
| 19 | protected $bookRepo; | 20 | protected $bookRepo; |
| 20 | protected $pageRepo; | 21 | protected $pageRepo; |
| 21 | protected $chapterRepo; | 22 | protected $chapterRepo; |
| 23 | + protected $userRepo; | ||
| 22 | 24 | ||
| 23 | /** | 25 | /** |
| 24 | * BookController constructor. | 26 | * BookController constructor. |
| 25 | - * @param BookRepo $bookRepo | 27 | + * @param BookRepo $bookRepo |
| 26 | - * @param PageRepo $pageRepo | 28 | + * @param PageRepo $pageRepo |
| 27 | * @param ChapterRepo $chapterRepo | 29 | * @param ChapterRepo $chapterRepo |
| 30 | + * @param UserRepo $userRepo | ||
| 28 | */ | 31 | */ |
| 29 | - public function __construct(BookRepo $bookRepo, PageRepo $pageRepo, ChapterRepo $chapterRepo) | 32 | + public function __construct(BookRepo $bookRepo, PageRepo $pageRepo, ChapterRepo $chapterRepo, UserRepo $userRepo) |
| 30 | { | 33 | { |
| 31 | $this->bookRepo = $bookRepo; | 34 | $this->bookRepo = $bookRepo; |
| 32 | $this->pageRepo = $pageRepo; | 35 | $this->pageRepo = $pageRepo; |
| 33 | $this->chapterRepo = $chapterRepo; | 36 | $this->chapterRepo = $chapterRepo; |
| 37 | + $this->userRepo = $userRepo; | ||
| 34 | parent::__construct(); | 38 | parent::__construct(); |
| 35 | } | 39 | } |
| 36 | 40 | ||
| ... | @@ -55,7 +59,7 @@ class BookController extends Controller | ... | @@ -55,7 +59,7 @@ class BookController extends Controller |
| 55 | */ | 59 | */ |
| 56 | public function create() | 60 | public function create() |
| 57 | { | 61 | { |
| 58 | - $this->checkPermission('book-create'); | 62 | + $this->checkPermission('book-create-all'); |
| 59 | $this->setPageTitle('Create New Book'); | 63 | $this->setPageTitle('Create New Book'); |
| 60 | return view('books/create'); | 64 | return view('books/create'); |
| 61 | } | 65 | } |
| ... | @@ -68,9 +72,9 @@ class BookController extends Controller | ... | @@ -68,9 +72,9 @@ class BookController extends Controller |
| 68 | */ | 72 | */ |
| 69 | public function store(Request $request) | 73 | public function store(Request $request) |
| 70 | { | 74 | { |
| 71 | - $this->checkPermission('book-create'); | 75 | + $this->checkPermission('book-create-all'); |
| 72 | $this->validate($request, [ | 76 | $this->validate($request, [ |
| 73 | - 'name' => 'required|string|max:255', | 77 | + 'name' => 'required|string|max:255', |
| 74 | 'description' => 'string|max:1000' | 78 | 'description' => 'string|max:1000' |
| 75 | ]); | 79 | ]); |
| 76 | $book = $this->bookRepo->newFromInput($request->all()); | 80 | $book = $this->bookRepo->newFromInput($request->all()); |
| ... | @@ -105,8 +109,8 @@ class BookController extends Controller | ... | @@ -105,8 +109,8 @@ class BookController extends Controller |
| 105 | */ | 109 | */ |
| 106 | public function edit($slug) | 110 | public function edit($slug) |
| 107 | { | 111 | { |
| 108 | - $this->checkPermission('book-update'); | ||
| 109 | $book = $this->bookRepo->getBySlug($slug); | 112 | $book = $this->bookRepo->getBySlug($slug); |
| 113 | + $this->checkOwnablePermission('book-update', $book); | ||
| 110 | $this->setPageTitle('Edit Book ' . $book->getShortName()); | 114 | $this->setPageTitle('Edit Book ' . $book->getShortName()); |
| 111 | return view('books/edit', ['book' => $book, 'current' => $book]); | 115 | return view('books/edit', ['book' => $book, 'current' => $book]); |
| 112 | } | 116 | } |
| ... | @@ -120,10 +124,10 @@ class BookController extends Controller | ... | @@ -120,10 +124,10 @@ class BookController extends Controller |
| 120 | */ | 124 | */ |
| 121 | public function update(Request $request, $slug) | 125 | public function update(Request $request, $slug) |
| 122 | { | 126 | { |
| 123 | - $this->checkPermission('book-update'); | ||
| 124 | $book = $this->bookRepo->getBySlug($slug); | 127 | $book = $this->bookRepo->getBySlug($slug); |
| 128 | + $this->checkOwnablePermission('book-update', $book); | ||
| 125 | $this->validate($request, [ | 129 | $this->validate($request, [ |
| 126 | - 'name' => 'required|string|max:255', | 130 | + 'name' => 'required|string|max:255', |
| 127 | 'description' => 'string|max:1000' | 131 | 'description' => 'string|max:1000' |
| 128 | ]); | 132 | ]); |
| 129 | $book->fill($request->all()); | 133 | $book->fill($request->all()); |
| ... | @@ -141,8 +145,8 @@ class BookController extends Controller | ... | @@ -141,8 +145,8 @@ class BookController extends Controller |
| 141 | */ | 145 | */ |
| 142 | public function showDelete($bookSlug) | 146 | public function showDelete($bookSlug) |
| 143 | { | 147 | { |
| 144 | - $this->checkPermission('book-delete'); | ||
| 145 | $book = $this->bookRepo->getBySlug($bookSlug); | 148 | $book = $this->bookRepo->getBySlug($bookSlug); |
| 149 | + $this->checkOwnablePermission('book-delete', $book); | ||
| 146 | $this->setPageTitle('Delete Book ' . $book->getShortName()); | 150 | $this->setPageTitle('Delete Book ' . $book->getShortName()); |
| 147 | return view('books/delete', ['book' => $book, 'current' => $book]); | 151 | return view('books/delete', ['book' => $book, 'current' => $book]); |
| 148 | } | 152 | } |
| ... | @@ -154,8 +158,8 @@ class BookController extends Controller | ... | @@ -154,8 +158,8 @@ class BookController extends Controller |
| 154 | */ | 158 | */ |
| 155 | public function sort($bookSlug) | 159 | public function sort($bookSlug) |
| 156 | { | 160 | { |
| 157 | - $this->checkPermission('book-update'); | ||
| 158 | $book = $this->bookRepo->getBySlug($bookSlug); | 161 | $book = $this->bookRepo->getBySlug($bookSlug); |
| 162 | + $this->checkOwnablePermission('book-update', $book); | ||
| 159 | $bookChildren = $this->bookRepo->getChildren($book); | 163 | $bookChildren = $this->bookRepo->getChildren($book); |
| 160 | $books = $this->bookRepo->getAll(false); | 164 | $books = $this->bookRepo->getAll(false); |
| 161 | $this->setPageTitle('Sort Book ' . $book->getShortName()); | 165 | $this->setPageTitle('Sort Book ' . $book->getShortName()); |
| ... | @@ -177,15 +181,14 @@ class BookController extends Controller | ... | @@ -177,15 +181,14 @@ class BookController extends Controller |
| 177 | 181 | ||
| 178 | /** | 182 | /** |
| 179 | * Saves an array of sort mapping to pages and chapters. | 183 | * Saves an array of sort mapping to pages and chapters. |
| 180 | - * | ||
| 181 | * @param string $bookSlug | 184 | * @param string $bookSlug |
| 182 | * @param Request $request | 185 | * @param Request $request |
| 183 | * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector | 186 | * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector |
| 184 | */ | 187 | */ |
| 185 | public function saveSort($bookSlug, Request $request) | 188 | public function saveSort($bookSlug, Request $request) |
| 186 | { | 189 | { |
| 187 | - $this->checkPermission('book-update'); | ||
| 188 | $book = $this->bookRepo->getBySlug($bookSlug); | 190 | $book = $this->bookRepo->getBySlug($bookSlug); |
| 191 | + $this->checkOwnablePermission('book-update', $book); | ||
| 189 | 192 | ||
| 190 | // Return if no map sent | 193 | // Return if no map sent |
| 191 | if (!$request->has('sort-tree')) { | 194 | if (!$request->has('sort-tree')) { |
| ... | @@ -223,17 +226,48 @@ class BookController extends Controller | ... | @@ -223,17 +226,48 @@ class BookController extends Controller |
| 223 | 226 | ||
| 224 | /** | 227 | /** |
| 225 | * Remove the specified book from storage. | 228 | * Remove the specified book from storage. |
| 226 | - * | ||
| 227 | * @param $bookSlug | 229 | * @param $bookSlug |
| 228 | * @return Response | 230 | * @return Response |
| 229 | */ | 231 | */ |
| 230 | public function destroy($bookSlug) | 232 | public function destroy($bookSlug) |
| 231 | { | 233 | { |
| 232 | - $this->checkPermission('book-delete'); | ||
| 233 | $book = $this->bookRepo->getBySlug($bookSlug); | 234 | $book = $this->bookRepo->getBySlug($bookSlug); |
| 235 | + $this->checkOwnablePermission('book-delete', $book); | ||
| 234 | Activity::addMessage('book_delete', 0, $book->name); | 236 | Activity::addMessage('book_delete', 0, $book->name); |
| 235 | Activity::removeEntity($book); | 237 | Activity::removeEntity($book); |
| 236 | $this->bookRepo->destroyBySlug($bookSlug); | 238 | $this->bookRepo->destroyBySlug($bookSlug); |
| 237 | return redirect('/books'); | 239 | return redirect('/books'); |
| 238 | } | 240 | } |
| 241 | + | ||
| 242 | + /** | ||
| 243 | + * Show the Restrictions view. | ||
| 244 | + * @param $bookSlug | ||
| 245 | + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View | ||
| 246 | + */ | ||
| 247 | + public function showRestrict($bookSlug) | ||
| 248 | + { | ||
| 249 | + $book = $this->bookRepo->getBySlug($bookSlug); | ||
| 250 | + $this->checkOwnablePermission('restrictions-manage', $book); | ||
| 251 | + $roles = $this->userRepo->getRestrictableRoles(); | ||
| 252 | + return view('books/restrictions', [ | ||
| 253 | + 'book' => $book, | ||
| 254 | + 'roles' => $roles | ||
| 255 | + ]); | ||
| 256 | + } | ||
| 257 | + | ||
| 258 | + /** | ||
| 259 | + * Set the restrictions for this book. | ||
| 260 | + * @param $bookSlug | ||
| 261 | + * @param $bookSlug | ||
| 262 | + * @param Request $request | ||
| 263 | + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector | ||
| 264 | + */ | ||
| 265 | + public function restrict($bookSlug, Request $request) | ||
| 266 | + { | ||
| 267 | + $book = $this->bookRepo->getBySlug($bookSlug); | ||
| 268 | + $this->checkOwnablePermission('restrictions-manage', $book); | ||
| 269 | + $this->bookRepo->updateRestrictionsFromRequest($request, $book); | ||
| 270 | + session()->flash('success', 'Page Restrictions Updated'); | ||
| 271 | + return redirect($book->getUrl()); | ||
| 272 | + } | ||
| 239 | } | 273 | } | ... | ... |
| 1 | -<?php | 1 | +<?php namespace BookStack\Http\Controllers; |
| 2 | - | ||
| 3 | -namespace BookStack\Http\Controllers; | ||
| 4 | 2 | ||
| 5 | use Activity; | 3 | use Activity; |
| 4 | +use BookStack\Repos\UserRepo; | ||
| 6 | use Illuminate\Http\Request; | 5 | use Illuminate\Http\Request; |
| 7 | - | ||
| 8 | -use Illuminate\Support\Facades\Auth; | ||
| 9 | use BookStack\Http\Requests; | 6 | use BookStack\Http\Requests; |
| 10 | -use BookStack\Http\Controllers\Controller; | ||
| 11 | use BookStack\Repos\BookRepo; | 7 | use BookStack\Repos\BookRepo; |
| 12 | use BookStack\Repos\ChapterRepo; | 8 | use BookStack\Repos\ChapterRepo; |
| 13 | use Views; | 9 | use Views; |
| ... | @@ -17,20 +13,22 @@ class ChapterController extends Controller | ... | @@ -17,20 +13,22 @@ class ChapterController extends Controller |
| 17 | 13 | ||
| 18 | protected $bookRepo; | 14 | protected $bookRepo; |
| 19 | protected $chapterRepo; | 15 | protected $chapterRepo; |
| 16 | + protected $userRepo; | ||
| 20 | 17 | ||
| 21 | /** | 18 | /** |
| 22 | * ChapterController constructor. | 19 | * ChapterController constructor. |
| 23 | - * @param $bookRepo | 20 | + * @param BookRepo $bookRepo |
| 24 | - * @param $chapterRepo | 21 | + * @param ChapterRepo $chapterRepo |
| 22 | + * @param UserRepo $userRepo | ||
| 25 | */ | 23 | */ |
| 26 | - public function __construct(BookRepo $bookRepo, ChapterRepo $chapterRepo) | 24 | + public function __construct(BookRepo $bookRepo, ChapterRepo $chapterRepo, UserRepo $userRepo) |
| 27 | { | 25 | { |
| 28 | $this->bookRepo = $bookRepo; | 26 | $this->bookRepo = $bookRepo; |
| 29 | $this->chapterRepo = $chapterRepo; | 27 | $this->chapterRepo = $chapterRepo; |
| 28 | + $this->userRepo = $userRepo; | ||
| 30 | parent::__construct(); | 29 | parent::__construct(); |
| 31 | } | 30 | } |
| 32 | 31 | ||
| 33 | - | ||
| 34 | /** | 32 | /** |
| 35 | * Show the form for creating a new chapter. | 33 | * Show the form for creating a new chapter. |
| 36 | * @param $bookSlug | 34 | * @param $bookSlug |
| ... | @@ -38,8 +36,8 @@ class ChapterController extends Controller | ... | @@ -38,8 +36,8 @@ class ChapterController extends Controller |
| 38 | */ | 36 | */ |
| 39 | public function create($bookSlug) | 37 | public function create($bookSlug) |
| 40 | { | 38 | { |
| 41 | - $this->checkPermission('chapter-create'); | ||
| 42 | $book = $this->bookRepo->getBySlug($bookSlug); | 39 | $book = $this->bookRepo->getBySlug($bookSlug); |
| 40 | + $this->checkOwnablePermission('chapter-create', $book); | ||
| 43 | $this->setPageTitle('Create New Chapter'); | 41 | $this->setPageTitle('Create New Chapter'); |
| 44 | return view('chapters/create', ['book' => $book, 'current' => $book]); | 42 | return view('chapters/create', ['book' => $book, 'current' => $book]); |
| 45 | } | 43 | } |
| ... | @@ -52,12 +50,13 @@ class ChapterController extends Controller | ... | @@ -52,12 +50,13 @@ class ChapterController extends Controller |
| 52 | */ | 50 | */ |
| 53 | public function store($bookSlug, Request $request) | 51 | public function store($bookSlug, Request $request) |
| 54 | { | 52 | { |
| 55 | - $this->checkPermission('chapter-create'); | ||
| 56 | $this->validate($request, [ | 53 | $this->validate($request, [ |
| 57 | 'name' => 'required|string|max:255' | 54 | 'name' => 'required|string|max:255' |
| 58 | ]); | 55 | ]); |
| 59 | 56 | ||
| 60 | $book = $this->bookRepo->getBySlug($bookSlug); | 57 | $book = $this->bookRepo->getBySlug($bookSlug); |
| 58 | + $this->checkOwnablePermission('chapter-create', $book); | ||
| 59 | + | ||
| 61 | $chapter = $this->chapterRepo->newFromInput($request->all()); | 60 | $chapter = $this->chapterRepo->newFromInput($request->all()); |
| 62 | $chapter->slug = $this->chapterRepo->findSuitableSlug($chapter->name, $book->id); | 61 | $chapter->slug = $this->chapterRepo->findSuitableSlug($chapter->name, $book->id); |
| 63 | $chapter->priority = $this->bookRepo->getNewPriority($book); | 62 | $chapter->priority = $this->bookRepo->getNewPriority($book); |
| ... | @@ -81,7 +80,14 @@ class ChapterController extends Controller | ... | @@ -81,7 +80,14 @@ class ChapterController extends Controller |
| 81 | $sidebarTree = $this->bookRepo->getChildren($book); | 80 | $sidebarTree = $this->bookRepo->getChildren($book); |
| 82 | Views::add($chapter); | 81 | Views::add($chapter); |
| 83 | $this->setPageTitle($chapter->getShortName()); | 82 | $this->setPageTitle($chapter->getShortName()); |
| 84 | - return view('chapters/show', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter, 'sidebarTree' => $sidebarTree]); | 83 | + $pages = $this->chapterRepo->getChildren($chapter); |
| 84 | + return view('chapters/show', [ | ||
| 85 | + 'book' => $book, | ||
| 86 | + 'chapter' => $chapter, | ||
| 87 | + 'current' => $chapter, | ||
| 88 | + 'sidebarTree' => $sidebarTree, | ||
| 89 | + 'pages' => $pages | ||
| 90 | + ]); | ||
| 85 | } | 91 | } |
| 86 | 92 | ||
| 87 | /** | 93 | /** |
| ... | @@ -92,9 +98,9 @@ class ChapterController extends Controller | ... | @@ -92,9 +98,9 @@ class ChapterController extends Controller |
| 92 | */ | 98 | */ |
| 93 | public function edit($bookSlug, $chapterSlug) | 99 | public function edit($bookSlug, $chapterSlug) |
| 94 | { | 100 | { |
| 95 | - $this->checkPermission('chapter-update'); | ||
| 96 | $book = $this->bookRepo->getBySlug($bookSlug); | 101 | $book = $this->bookRepo->getBySlug($bookSlug); |
| 97 | $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); | 102 | $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); |
| 103 | + $this->checkOwnablePermission('chapter-update', $chapter); | ||
| 98 | $this->setPageTitle('Edit Chapter' . $chapter->getShortName()); | 104 | $this->setPageTitle('Edit Chapter' . $chapter->getShortName()); |
| 99 | return view('chapters/edit', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter]); | 105 | return view('chapters/edit', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter]); |
| 100 | } | 106 | } |
| ... | @@ -108,9 +114,9 @@ class ChapterController extends Controller | ... | @@ -108,9 +114,9 @@ class ChapterController extends Controller |
| 108 | */ | 114 | */ |
| 109 | public function update(Request $request, $bookSlug, $chapterSlug) | 115 | public function update(Request $request, $bookSlug, $chapterSlug) |
| 110 | { | 116 | { |
| 111 | - $this->checkPermission('chapter-update'); | ||
| 112 | $book = $this->bookRepo->getBySlug($bookSlug); | 117 | $book = $this->bookRepo->getBySlug($bookSlug); |
| 113 | $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); | 118 | $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); |
| 119 | + $this->checkOwnablePermission('chapter-update', $chapter); | ||
| 114 | $chapter->fill($request->all()); | 120 | $chapter->fill($request->all()); |
| 115 | $chapter->slug = $this->chapterRepo->findSuitableSlug($chapter->name, $book->id, $chapter->id); | 121 | $chapter->slug = $this->chapterRepo->findSuitableSlug($chapter->name, $book->id, $chapter->id); |
| 116 | $chapter->updated_by = auth()->user()->id; | 122 | $chapter->updated_by = auth()->user()->id; |
| ... | @@ -127,9 +133,9 @@ class ChapterController extends Controller | ... | @@ -127,9 +133,9 @@ class ChapterController extends Controller |
| 127 | */ | 133 | */ |
| 128 | public function showDelete($bookSlug, $chapterSlug) | 134 | public function showDelete($bookSlug, $chapterSlug) |
| 129 | { | 135 | { |
| 130 | - $this->checkPermission('chapter-delete'); | ||
| 131 | $book = $this->bookRepo->getBySlug($bookSlug); | 136 | $book = $this->bookRepo->getBySlug($bookSlug); |
| 132 | $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); | 137 | $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); |
| 138 | + $this->checkOwnablePermission('chapter-delete', $chapter); | ||
| 133 | $this->setPageTitle('Delete Chapter' . $chapter->getShortName()); | 139 | $this->setPageTitle('Delete Chapter' . $chapter->getShortName()); |
| 134 | return view('chapters/delete', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter]); | 140 | return view('chapters/delete', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter]); |
| 135 | } | 141 | } |
| ... | @@ -142,11 +148,46 @@ class ChapterController extends Controller | ... | @@ -142,11 +148,46 @@ class ChapterController extends Controller |
| 142 | */ | 148 | */ |
| 143 | public function destroy($bookSlug, $chapterSlug) | 149 | public function destroy($bookSlug, $chapterSlug) |
| 144 | { | 150 | { |
| 145 | - $this->checkPermission('chapter-delete'); | ||
| 146 | $book = $this->bookRepo->getBySlug($bookSlug); | 151 | $book = $this->bookRepo->getBySlug($bookSlug); |
| 147 | $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); | 152 | $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); |
| 153 | + $this->checkOwnablePermission('chapter-delete', $chapter); | ||
| 148 | Activity::addMessage('chapter_delete', $book->id, $chapter->name); | 154 | Activity::addMessage('chapter_delete', $book->id, $chapter->name); |
| 149 | $this->chapterRepo->destroy($chapter); | 155 | $this->chapterRepo->destroy($chapter); |
| 150 | return redirect($book->getUrl()); | 156 | return redirect($book->getUrl()); |
| 151 | } | 157 | } |
| 158 | + | ||
| 159 | + /** | ||
| 160 | + * Show the Restrictions view. | ||
| 161 | + * @param $bookSlug | ||
| 162 | + * @param $chapterSlug | ||
| 163 | + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View | ||
| 164 | + */ | ||
| 165 | + public function showRestrict($bookSlug, $chapterSlug) | ||
| 166 | + { | ||
| 167 | + $book = $this->bookRepo->getBySlug($bookSlug); | ||
| 168 | + $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); | ||
| 169 | + $this->checkOwnablePermission('restrictions-manage', $chapter); | ||
| 170 | + $roles = $this->userRepo->getRestrictableRoles(); | ||
| 171 | + return view('chapters/restrictions', [ | ||
| 172 | + 'chapter' => $chapter, | ||
| 173 | + 'roles' => $roles | ||
| 174 | + ]); | ||
| 175 | + } | ||
| 176 | + | ||
| 177 | + /** | ||
| 178 | + * Set the restrictions for this chapter. | ||
| 179 | + * @param $bookSlug | ||
| 180 | + * @param $chapterSlug | ||
| 181 | + * @param Request $request | ||
| 182 | + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector | ||
| 183 | + */ | ||
| 184 | + public function restrict($bookSlug, $chapterSlug, Request $request) | ||
| 185 | + { | ||
| 186 | + $book = $this->bookRepo->getBySlug($bookSlug); | ||
| 187 | + $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); | ||
| 188 | + $this->checkOwnablePermission('restrictions-manage', $chapter); | ||
| 189 | + $this->chapterRepo->updateRestrictionsFromRequest($request, $chapter); | ||
| 190 | + session()->flash('success', 'Page Restrictions Updated'); | ||
| 191 | + return redirect($chapter->getUrl()); | ||
| 192 | + } | ||
| 152 | } | 193 | } | ... | ... |
| ... | @@ -2,6 +2,7 @@ | ... | @@ -2,6 +2,7 @@ |
| 2 | 2 | ||
| 3 | namespace BookStack\Http\Controllers; | 3 | namespace BookStack\Http\Controllers; |
| 4 | 4 | ||
| 5 | +use BookStack\Ownable; | ||
| 5 | use HttpRequestException; | 6 | use HttpRequestException; |
| 6 | use Illuminate\Foundation\Bus\DispatchesJobs; | 7 | use Illuminate\Foundation\Bus\DispatchesJobs; |
| 7 | use Illuminate\Http\Exception\HttpResponseException; | 8 | use Illuminate\Http\Exception\HttpResponseException; |
| ... | @@ -61,21 +62,19 @@ abstract class Controller extends BaseController | ... | @@ -61,21 +62,19 @@ abstract class Controller extends BaseController |
| 61 | } | 62 | } |
| 62 | 63 | ||
| 63 | /** | 64 | /** |
| 64 | - * On a permission error redirect to home and display | 65 | + * On a permission error redirect to home and display. |
| 65 | * the error as a notification. | 66 | * the error as a notification. |
| 66 | */ | 67 | */ |
| 67 | protected function showPermissionError() | 68 | protected function showPermissionError() |
| 68 | { | 69 | { |
| 69 | Session::flash('error', trans('errors.permission')); | 70 | Session::flash('error', trans('errors.permission')); |
| 70 | - throw new HttpResponseException( | 71 | + $response = request()->wantsJson() ? response()->json(['error' => trans('errors.permissionJson')], 403) : redirect('/'); |
| 71 | - redirect('/') | 72 | + throw new HttpResponseException($response); |
| 72 | - ); | ||
| 73 | } | 73 | } |
| 74 | 74 | ||
| 75 | /** | 75 | /** |
| 76 | * Checks for a permission. | 76 | * Checks for a permission. |
| 77 | - * | 77 | + * @param string $permissionName |
| 78 | - * @param $permissionName | ||
| 79 | * @return bool|\Illuminate\Http\RedirectResponse | 78 | * @return bool|\Illuminate\Http\RedirectResponse |
| 80 | */ | 79 | */ |
| 81 | protected function checkPermission($permissionName) | 80 | protected function checkPermission($permissionName) |
| ... | @@ -83,11 +82,22 @@ abstract class Controller extends BaseController | ... | @@ -83,11 +82,22 @@ abstract class Controller extends BaseController |
| 83 | if (!$this->currentUser || !$this->currentUser->can($permissionName)) { | 82 | if (!$this->currentUser || !$this->currentUser->can($permissionName)) { |
| 84 | $this->showPermissionError(); | 83 | $this->showPermissionError(); |
| 85 | } | 84 | } |
| 86 | - | ||
| 87 | return true; | 85 | return true; |
| 88 | } | 86 | } |
| 89 | 87 | ||
| 90 | /** | 88 | /** |
| 89 | + * Check the current user's permissions against an ownable item. | ||
| 90 | + * @param $permission | ||
| 91 | + * @param Ownable $ownable | ||
| 92 | + * @return bool | ||
| 93 | + */ | ||
| 94 | + protected function checkOwnablePermission($permission, Ownable $ownable) | ||
| 95 | + { | ||
| 96 | + if (userCan($permission, $ownable)) return true; | ||
| 97 | + return $this->showPermissionError(); | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + /** | ||
| 91 | * Check if a user has a permission or bypass if the callback is true. | 101 | * Check if a user has a permission or bypass if the callback is true. |
| 92 | * @param $permissionName | 102 | * @param $permissionName |
| 93 | * @param $callback | 103 | * @param $callback | ... | ... |
| ... | @@ -64,7 +64,7 @@ class ImageController extends Controller | ... | @@ -64,7 +64,7 @@ class ImageController extends Controller |
| 64 | */ | 64 | */ |
| 65 | public function uploadByType($type, Request $request) | 65 | public function uploadByType($type, Request $request) |
| 66 | { | 66 | { |
| 67 | - $this->checkPermission('image-create'); | 67 | + $this->checkPermission('image-create-all'); |
| 68 | $this->validate($request, [ | 68 | $this->validate($request, [ |
| 69 | 'file' => 'image|mimes:jpeg,gif,png' | 69 | 'file' => 'image|mimes:jpeg,gif,png' |
| 70 | ]); | 70 | ]); |
| ... | @@ -90,7 +90,7 @@ class ImageController extends Controller | ... | @@ -90,7 +90,7 @@ class ImageController extends Controller |
| 90 | */ | 90 | */ |
| 91 | public function getThumbnail($id, $width, $height, $crop) | 91 | public function getThumbnail($id, $width, $height, $crop) |
| 92 | { | 92 | { |
| 93 | - $this->checkPermission('image-create'); | 93 | + $this->checkPermission('image-create-all'); |
| 94 | $image = $this->imageRepo->getById($id); | 94 | $image = $this->imageRepo->getById($id); |
| 95 | $thumbnailUrl = $this->imageRepo->getThumbnail($image, $width, $height, $crop == 'false'); | 95 | $thumbnailUrl = $this->imageRepo->getThumbnail($image, $width, $height, $crop == 'false'); |
| 96 | return response()->json(['url' => $thumbnailUrl]); | 96 | return response()->json(['url' => $thumbnailUrl]); |
| ... | @@ -104,11 +104,11 @@ class ImageController extends Controller | ... | @@ -104,11 +104,11 @@ class ImageController extends Controller |
| 104 | */ | 104 | */ |
| 105 | public function update($imageId, Request $request) | 105 | public function update($imageId, Request $request) |
| 106 | { | 106 | { |
| 107 | - $this->checkPermission('image-update'); | ||
| 108 | $this->validate($request, [ | 107 | $this->validate($request, [ |
| 109 | 'name' => 'required|min:2|string' | 108 | 'name' => 'required|min:2|string' |
| 110 | ]); | 109 | ]); |
| 111 | $image = $this->imageRepo->getById($imageId); | 110 | $image = $this->imageRepo->getById($imageId); |
| 111 | + $this->checkOwnablePermission('image-update', $image); | ||
| 112 | $image = $this->imageRepo->updateImageDetails($image, $request->all()); | 112 | $image = $this->imageRepo->updateImageDetails($image, $request->all()); |
| 113 | return response()->json($image); | 113 | return response()->json($image); |
| 114 | } | 114 | } |
| ... | @@ -123,8 +123,8 @@ class ImageController extends Controller | ... | @@ -123,8 +123,8 @@ class ImageController extends Controller |
| 123 | */ | 123 | */ |
| 124 | public function destroy(PageRepo $pageRepo, Request $request, $id) | 124 | public function destroy(PageRepo $pageRepo, Request $request, $id) |
| 125 | { | 125 | { |
| 126 | - $this->checkPermission('image-delete'); | ||
| 127 | $image = $this->imageRepo->getById($id); | 126 | $image = $this->imageRepo->getById($id); |
| 127 | + $this->checkOwnablePermission('image-delete', $image); | ||
| 128 | 128 | ||
| 129 | // Check if this image is used on any pages | 129 | // Check if this image is used on any pages |
| 130 | $isForced = ($request->has('force') && ($request->get('force') === 'true') || $request->get('force') === true); | 130 | $isForced = ($request->has('force') && ($request->get('force') === 'true') || $request->get('force') === true); | ... | ... |
| 1 | -<?php | 1 | +<?php namespace BookStack\Http\Controllers; |
| 2 | - | ||
| 3 | -namespace BookStack\Http\Controllers; | ||
| 4 | 2 | ||
| 5 | use Activity; | 3 | use Activity; |
| 4 | +use BookStack\Exceptions\NotFoundException; | ||
| 5 | +use BookStack\Repos\UserRepo; | ||
| 6 | use BookStack\Services\ExportService; | 6 | use BookStack\Services\ExportService; |
| 7 | use Illuminate\Http\Request; | 7 | use Illuminate\Http\Request; |
| 8 | - | ||
| 9 | -use Illuminate\Support\Facades\Auth; | ||
| 10 | use BookStack\Http\Requests; | 8 | use BookStack\Http\Requests; |
| 11 | use BookStack\Repos\BookRepo; | 9 | use BookStack\Repos\BookRepo; |
| 12 | use BookStack\Repos\ChapterRepo; | 10 | use BookStack\Repos\ChapterRepo; |
| ... | @@ -21,26 +19,28 @@ class PageController extends Controller | ... | @@ -21,26 +19,28 @@ class PageController extends Controller |
| 21 | protected $bookRepo; | 19 | protected $bookRepo; |
| 22 | protected $chapterRepo; | 20 | protected $chapterRepo; |
| 23 | protected $exportService; | 21 | protected $exportService; |
| 22 | + protected $userRepo; | ||
| 24 | 23 | ||
| 25 | /** | 24 | /** |
| 26 | * PageController constructor. | 25 | * PageController constructor. |
| 27 | - * @param PageRepo $pageRepo | 26 | + * @param PageRepo $pageRepo |
| 28 | - * @param BookRepo $bookRepo | 27 | + * @param BookRepo $bookRepo |
| 29 | - * @param ChapterRepo $chapterRepo | 28 | + * @param ChapterRepo $chapterRepo |
| 30 | * @param ExportService $exportService | 29 | * @param ExportService $exportService |
| 30 | + * @param UserRepo $userRepo | ||
| 31 | */ | 31 | */ |
| 32 | - public function __construct(PageRepo $pageRepo, BookRepo $bookRepo, ChapterRepo $chapterRepo, ExportService $exportService) | 32 | + public function __construct(PageRepo $pageRepo, BookRepo $bookRepo, ChapterRepo $chapterRepo, ExportService $exportService, UserRepo $userRepo) |
| 33 | { | 33 | { |
| 34 | $this->pageRepo = $pageRepo; | 34 | $this->pageRepo = $pageRepo; |
| 35 | $this->bookRepo = $bookRepo; | 35 | $this->bookRepo = $bookRepo; |
| 36 | $this->chapterRepo = $chapterRepo; | 36 | $this->chapterRepo = $chapterRepo; |
| 37 | $this->exportService = $exportService; | 37 | $this->exportService = $exportService; |
| 38 | + $this->userRepo = $userRepo; | ||
| 38 | parent::__construct(); | 39 | parent::__construct(); |
| 39 | } | 40 | } |
| 40 | 41 | ||
| 41 | /** | 42 | /** |
| 42 | * Show the form for creating a new page. | 43 | * Show the form for creating a new page. |
| 43 | - * | ||
| 44 | * @param $bookSlug | 44 | * @param $bookSlug |
| 45 | * @param bool $chapterSlug | 45 | * @param bool $chapterSlug |
| 46 | * @return Response | 46 | * @return Response |
| ... | @@ -48,23 +48,22 @@ class PageController extends Controller | ... | @@ -48,23 +48,22 @@ class PageController extends Controller |
| 48 | */ | 48 | */ |
| 49 | public function create($bookSlug, $chapterSlug = false) | 49 | public function create($bookSlug, $chapterSlug = false) |
| 50 | { | 50 | { |
| 51 | - $this->checkPermission('page-create'); | ||
| 52 | $book = $this->bookRepo->getBySlug($bookSlug); | 51 | $book = $this->bookRepo->getBySlug($bookSlug); |
| 53 | $chapter = $chapterSlug ? $this->chapterRepo->getBySlug($chapterSlug, $book->id) : false; | 52 | $chapter = $chapterSlug ? $this->chapterRepo->getBySlug($chapterSlug, $book->id) : false; |
| 53 | + $parent = $chapter ? $chapter : $book; | ||
| 54 | + $this->checkOwnablePermission('page-create', $parent); | ||
| 54 | $this->setPageTitle('Create New Page'); | 55 | $this->setPageTitle('Create New Page'); |
| 55 | return view('pages/create', ['book' => $book, 'chapter' => $chapter]); | 56 | return view('pages/create', ['book' => $book, 'chapter' => $chapter]); |
| 56 | } | 57 | } |
| 57 | 58 | ||
| 58 | /** | 59 | /** |
| 59 | * Store a newly created page in storage. | 60 | * Store a newly created page in storage. |
| 60 | - * | ||
| 61 | * @param Request $request | 61 | * @param Request $request |
| 62 | * @param $bookSlug | 62 | * @param $bookSlug |
| 63 | * @return Response | 63 | * @return Response |
| 64 | */ | 64 | */ |
| 65 | public function store(Request $request, $bookSlug) | 65 | public function store(Request $request, $bookSlug) |
| 66 | { | 66 | { |
| 67 | - $this->checkPermission('page-create'); | ||
| 68 | $this->validate($request, [ | 67 | $this->validate($request, [ |
| 69 | 'name' => 'required|string|max:255' | 68 | 'name' => 'required|string|max:255' |
| 70 | ]); | 69 | ]); |
| ... | @@ -72,6 +71,8 @@ class PageController extends Controller | ... | @@ -72,6 +71,8 @@ class PageController extends Controller |
| 72 | $input = $request->all(); | 71 | $input = $request->all(); |
| 73 | $book = $this->bookRepo->getBySlug($bookSlug); | 72 | $book = $this->bookRepo->getBySlug($bookSlug); |
| 74 | $chapterId = ($request->has('chapter') && $this->chapterRepo->idExists($request->get('chapter'))) ? $request->get('chapter') : null; | 73 | $chapterId = ($request->has('chapter') && $this->chapterRepo->idExists($request->get('chapter'))) ? $request->get('chapter') : null; |
| 74 | + $parent = $chapterId !== null ? $this->chapterRepo->getById($chapterId) : $book; | ||
| 75 | + $this->checkOwnablePermission('page-create', $parent); | ||
| 75 | $input['priority'] = $this->bookRepo->getNewPriority($book); | 76 | $input['priority'] = $this->bookRepo->getNewPriority($book); |
| 76 | 77 | ||
| 77 | $page = $this->pageRepo->saveNew($input, $book, $chapterId); | 78 | $page = $this->pageRepo->saveNew($input, $book, $chapterId); |
| ... | @@ -84,7 +85,6 @@ class PageController extends Controller | ... | @@ -84,7 +85,6 @@ class PageController extends Controller |
| 84 | * Display the specified page. | 85 | * Display the specified page. |
| 85 | * If the page is not found via the slug the | 86 | * If the page is not found via the slug the |
| 86 | * revisions are searched for a match. | 87 | * revisions are searched for a match. |
| 87 | - * | ||
| 88 | * @param $bookSlug | 88 | * @param $bookSlug |
| 89 | * @param $pageSlug | 89 | * @param $pageSlug |
| 90 | * @return Response | 90 | * @return Response |
| ... | @@ -95,7 +95,7 @@ class PageController extends Controller | ... | @@ -95,7 +95,7 @@ class PageController extends Controller |
| 95 | 95 | ||
| 96 | try { | 96 | try { |
| 97 | $page = $this->pageRepo->getBySlug($pageSlug, $book->id); | 97 | $page = $this->pageRepo->getBySlug($pageSlug, $book->id); |
| 98 | - } catch (NotFoundHttpException $e) { | 98 | + } catch (NotFoundException $e) { |
| 99 | $page = $this->pageRepo->findPageUsingOldSlug($pageSlug, $bookSlug); | 99 | $page = $this->pageRepo->findPageUsingOldSlug($pageSlug, $bookSlug); |
| 100 | if ($page === null) abort(404); | 100 | if ($page === null) abort(404); |
| 101 | return redirect($page->getUrl()); | 101 | return redirect($page->getUrl()); |
| ... | @@ -109,23 +109,21 @@ class PageController extends Controller | ... | @@ -109,23 +109,21 @@ class PageController extends Controller |
| 109 | 109 | ||
| 110 | /** | 110 | /** |
| 111 | * Show the form for editing the specified page. | 111 | * Show the form for editing the specified page. |
| 112 | - * | ||
| 113 | * @param $bookSlug | 112 | * @param $bookSlug |
| 114 | * @param $pageSlug | 113 | * @param $pageSlug |
| 115 | * @return Response | 114 | * @return Response |
| 116 | */ | 115 | */ |
| 117 | public function edit($bookSlug, $pageSlug) | 116 | public function edit($bookSlug, $pageSlug) |
| 118 | { | 117 | { |
| 119 | - $this->checkPermission('page-update'); | ||
| 120 | $book = $this->bookRepo->getBySlug($bookSlug); | 118 | $book = $this->bookRepo->getBySlug($bookSlug); |
| 121 | $page = $this->pageRepo->getBySlug($pageSlug, $book->id); | 119 | $page = $this->pageRepo->getBySlug($pageSlug, $book->id); |
| 120 | + $this->checkOwnablePermission('page-update', $page); | ||
| 122 | $this->setPageTitle('Editing Page ' . $page->getShortName()); | 121 | $this->setPageTitle('Editing Page ' . $page->getShortName()); |
| 123 | return view('pages/edit', ['page' => $page, 'book' => $book, 'current' => $page]); | 122 | return view('pages/edit', ['page' => $page, 'book' => $book, 'current' => $page]); |
| 124 | } | 123 | } |
| 125 | 124 | ||
| 126 | /** | 125 | /** |
| 127 | * Update the specified page in storage. | 126 | * Update the specified page in storage. |
| 128 | - * | ||
| 129 | * @param Request $request | 127 | * @param Request $request |
| 130 | * @param $bookSlug | 128 | * @param $bookSlug |
| 131 | * @param $pageSlug | 129 | * @param $pageSlug |
| ... | @@ -133,12 +131,12 @@ class PageController extends Controller | ... | @@ -133,12 +131,12 @@ class PageController extends Controller |
| 133 | */ | 131 | */ |
| 134 | public function update(Request $request, $bookSlug, $pageSlug) | 132 | public function update(Request $request, $bookSlug, $pageSlug) |
| 135 | { | 133 | { |
| 136 | - $this->checkPermission('page-update'); | ||
| 137 | $this->validate($request, [ | 134 | $this->validate($request, [ |
| 138 | 'name' => 'required|string|max:255' | 135 | 'name' => 'required|string|max:255' |
| 139 | ]); | 136 | ]); |
| 140 | $book = $this->bookRepo->getBySlug($bookSlug); | 137 | $book = $this->bookRepo->getBySlug($bookSlug); |
| 141 | $page = $this->pageRepo->getBySlug($pageSlug, $book->id); | 138 | $page = $this->pageRepo->getBySlug($pageSlug, $book->id); |
| 139 | + $this->checkOwnablePermission('page-update', $page); | ||
| 142 | $this->pageRepo->updatePage($page, $book->id, $request->all()); | 140 | $this->pageRepo->updatePage($page, $book->id, $request->all()); |
| 143 | Activity::add($page, 'page_update', $book->id); | 141 | Activity::add($page, 'page_update', $book->id); |
| 144 | return redirect($page->getUrl()); | 142 | return redirect($page->getUrl()); |
| ... | @@ -164,9 +162,9 @@ class PageController extends Controller | ... | @@ -164,9 +162,9 @@ class PageController extends Controller |
| 164 | */ | 162 | */ |
| 165 | public function showDelete($bookSlug, $pageSlug) | 163 | public function showDelete($bookSlug, $pageSlug) |
| 166 | { | 164 | { |
| 167 | - $this->checkPermission('page-delete'); | ||
| 168 | $book = $this->bookRepo->getBySlug($bookSlug); | 165 | $book = $this->bookRepo->getBySlug($bookSlug); |
| 169 | $page = $this->pageRepo->getBySlug($pageSlug, $book->id); | 166 | $page = $this->pageRepo->getBySlug($pageSlug, $book->id); |
| 167 | + $this->checkOwnablePermission('page-delete', $page); | ||
| 170 | $this->setPageTitle('Delete Page ' . $page->getShortName()); | 168 | $this->setPageTitle('Delete Page ' . $page->getShortName()); |
| 171 | return view('pages/delete', ['book' => $book, 'page' => $page, 'current' => $page]); | 169 | return view('pages/delete', ['book' => $book, 'page' => $page, 'current' => $page]); |
| 172 | } | 170 | } |
| ... | @@ -181,9 +179,9 @@ class PageController extends Controller | ... | @@ -181,9 +179,9 @@ class PageController extends Controller |
| 181 | */ | 179 | */ |
| 182 | public function destroy($bookSlug, $pageSlug) | 180 | public function destroy($bookSlug, $pageSlug) |
| 183 | { | 181 | { |
| 184 | - $this->checkPermission('page-delete'); | ||
| 185 | $book = $this->bookRepo->getBySlug($bookSlug); | 182 | $book = $this->bookRepo->getBySlug($bookSlug); |
| 186 | $page = $this->pageRepo->getBySlug($pageSlug, $book->id); | 183 | $page = $this->pageRepo->getBySlug($pageSlug, $book->id); |
| 184 | + $this->checkOwnablePermission('page-delete', $page); | ||
| 187 | Activity::addMessage('page_delete', $book->id, $page->name); | 185 | Activity::addMessage('page_delete', $book->id, $page->name); |
| 188 | $this->pageRepo->destroy($page); | 186 | $this->pageRepo->destroy($page); |
| 189 | return redirect($book->getUrl()); | 187 | return redirect($book->getUrl()); |
| ... | @@ -229,9 +227,9 @@ class PageController extends Controller | ... | @@ -229,9 +227,9 @@ class PageController extends Controller |
| 229 | */ | 227 | */ |
| 230 | public function restoreRevision($bookSlug, $pageSlug, $revisionId) | 228 | public function restoreRevision($bookSlug, $pageSlug, $revisionId) |
| 231 | { | 229 | { |
| 232 | - $this->checkPermission('page-update'); | ||
| 233 | $book = $this->bookRepo->getBySlug($bookSlug); | 230 | $book = $this->bookRepo->getBySlug($bookSlug); |
| 234 | $page = $this->pageRepo->getBySlug($pageSlug, $book->id); | 231 | $page = $this->pageRepo->getBySlug($pageSlug, $book->id); |
| 232 | + $this->checkOwnablePermission('page-update', $page); | ||
| 235 | $page = $this->pageRepo->restoreRevision($page, $book, $revisionId); | 233 | $page = $this->pageRepo->restoreRevision($page, $book, $revisionId); |
| 236 | Activity::add($page, 'page_restore', $book->id); | 234 | Activity::add($page, 'page_restore', $book->id); |
| 237 | return redirect($page->getUrl()); | 235 | return redirect($page->getUrl()); |
| ... | @@ -315,4 +313,39 @@ class PageController extends Controller | ... | @@ -315,4 +313,39 @@ class PageController extends Controller |
| 315 | ]); | 313 | ]); |
| 316 | } | 314 | } |
| 317 | 315 | ||
| 316 | + /** | ||
| 317 | + * Show the Restrictions view. | ||
| 318 | + * @param $bookSlug | ||
| 319 | + * @param $pageSlug | ||
| 320 | + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View | ||
| 321 | + */ | ||
| 322 | + public function showRestrict($bookSlug, $pageSlug) | ||
| 323 | + { | ||
| 324 | + $book = $this->bookRepo->getBySlug($bookSlug); | ||
| 325 | + $page = $this->pageRepo->getBySlug($pageSlug, $book->id); | ||
| 326 | + $this->checkOwnablePermission('restrictions-manage', $page); | ||
| 327 | + $roles = $this->userRepo->getRestrictableRoles(); | ||
| 328 | + return view('pages/restrictions', [ | ||
| 329 | + 'page' => $page, | ||
| 330 | + 'roles' => $roles | ||
| 331 | + ]); | ||
| 332 | + } | ||
| 333 | + | ||
| 334 | + /** | ||
| 335 | + * Set the restrictions for this page. | ||
| 336 | + * @param $bookSlug | ||
| 337 | + * @param $pageSlug | ||
| 338 | + * @param Request $request | ||
| 339 | + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector | ||
| 340 | + */ | ||
| 341 | + public function restrict($bookSlug, $pageSlug, Request $request) | ||
| 342 | + { | ||
| 343 | + $book = $this->bookRepo->getBySlug($bookSlug); | ||
| 344 | + $page = $this->pageRepo->getBySlug($pageSlug, $book->id); | ||
| 345 | + $this->checkOwnablePermission('restrictions-manage', $page); | ||
| 346 | + $this->pageRepo->updateRestrictionsFromRequest($request, $page); | ||
| 347 | + session()->flash('success', 'Page Restrictions Updated'); | ||
| 348 | + return redirect($page->getUrl()); | ||
| 349 | + } | ||
| 350 | + | ||
| 318 | } | 351 | } | ... | ... |
| 1 | +<?php namespace BookStack\Http\Controllers; | ||
| 2 | + | ||
| 3 | +use BookStack\Exceptions\PermissionsException; | ||
| 4 | +use BookStack\Repos\PermissionsRepo; | ||
| 5 | +use Illuminate\Http\Request; | ||
| 6 | +use BookStack\Http\Requests; | ||
| 7 | + | ||
| 8 | +class PermissionController extends Controller | ||
| 9 | +{ | ||
| 10 | + | ||
| 11 | + protected $permissionsRepo; | ||
| 12 | + | ||
| 13 | + /** | ||
| 14 | + * PermissionController constructor. | ||
| 15 | + * @param PermissionsRepo $permissionsRepo | ||
| 16 | + */ | ||
| 17 | + public function __construct(PermissionsRepo $permissionsRepo) | ||
| 18 | + { | ||
| 19 | + $this->permissionsRepo = $permissionsRepo; | ||
| 20 | + parent::__construct(); | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + /** | ||
| 24 | + * Show a listing of the roles in the system. | ||
| 25 | + */ | ||
| 26 | + public function listRoles() | ||
| 27 | + { | ||
| 28 | + $this->checkPermission('user-roles-manage'); | ||
| 29 | + $roles = $this->permissionsRepo->getAllRoles(); | ||
| 30 | + return view('settings/roles/index', ['roles' => $roles]); | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + /** | ||
| 34 | + * Show the form to create a new role | ||
| 35 | + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View | ||
| 36 | + */ | ||
| 37 | + public function createRole() | ||
| 38 | + { | ||
| 39 | + $this->checkPermission('user-roles-manage'); | ||
| 40 | + return view('settings/roles/create'); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + /** | ||
| 44 | + * Store a new role in the system. | ||
| 45 | + * @param Request $request | ||
| 46 | + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector | ||
| 47 | + */ | ||
| 48 | + public function storeRole(Request $request) | ||
| 49 | + { | ||
| 50 | + $this->checkPermission('user-roles-manage'); | ||
| 51 | + $this->validate($request, [ | ||
| 52 | + 'display_name' => 'required|min:3|max:200', | ||
| 53 | + 'description' => 'max:250' | ||
| 54 | + ]); | ||
| 55 | + | ||
| 56 | + $this->permissionsRepo->saveNewRole($request->all()); | ||
| 57 | + session()->flash('success', 'Role successfully created'); | ||
| 58 | + return redirect('/settings/roles'); | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + /** | ||
| 62 | + * Show the form for editing a user role. | ||
| 63 | + * @param $id | ||
| 64 | + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View | ||
| 65 | + */ | ||
| 66 | + public function editRole($id) | ||
| 67 | + { | ||
| 68 | + $this->checkPermission('user-roles-manage'); | ||
| 69 | + $role = $this->permissionsRepo->getRoleById($id); | ||
| 70 | + return view('settings/roles/edit', ['role' => $role]); | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + /** | ||
| 74 | + * Updates a user role. | ||
| 75 | + * @param $id | ||
| 76 | + * @param Request $request | ||
| 77 | + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector | ||
| 78 | + */ | ||
| 79 | + public function updateRole($id, Request $request) | ||
| 80 | + { | ||
| 81 | + $this->checkPermission('user-roles-manage'); | ||
| 82 | + $this->validate($request, [ | ||
| 83 | + 'display_name' => 'required|min:3|max:200', | ||
| 84 | + 'description' => 'max:250' | ||
| 85 | + ]); | ||
| 86 | + | ||
| 87 | + $this->permissionsRepo->updateRole($id, $request->all()); | ||
| 88 | + session()->flash('success', 'Role successfully updated'); | ||
| 89 | + return redirect('/settings/roles'); | ||
| 90 | + } | ||
| 91 | + | ||
| 92 | + /** | ||
| 93 | + * Show the view to delete a role. | ||
| 94 | + * Offers the chance to migrate users. | ||
| 95 | + * @param $id | ||
| 96 | + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View | ||
| 97 | + */ | ||
| 98 | + public function showDeleteRole($id) | ||
| 99 | + { | ||
| 100 | + $this->checkPermission('user-roles-manage'); | ||
| 101 | + $role = $this->permissionsRepo->getRoleById($id); | ||
| 102 | + $roles = $this->permissionsRepo->getAllRolesExcept($role); | ||
| 103 | + $blankRole = $role->newInstance(['display_name' => 'Don\'t migrate users']); | ||
| 104 | + $roles->prepend($blankRole); | ||
| 105 | + return view('settings/roles/delete', ['role' => $role, 'roles' => $roles]); | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + /** | ||
| 109 | + * Delete a role from the system, | ||
| 110 | + * Migrate from a previous role if set. | ||
| 111 | + * @param $id | ||
| 112 | + * @param Request $request | ||
| 113 | + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector | ||
| 114 | + */ | ||
| 115 | + public function deleteRole($id, Request $request) | ||
| 116 | + { | ||
| 117 | + $this->checkPermission('user-roles-manage'); | ||
| 118 | + | ||
| 119 | + try { | ||
| 120 | + $this->permissionsRepo->deleteRole($id, $request->get('migrate_role_id')); | ||
| 121 | + } catch (PermissionsException $e) { | ||
| 122 | + session()->flash('error', $e->getMessage()); | ||
| 123 | + return redirect()->back(); | ||
| 124 | + } | ||
| 125 | + | ||
| 126 | + session()->flash('success', 'Role successfully deleted'); | ||
| 127 | + return redirect('/settings/roles'); | ||
| 128 | + } | ||
| 129 | +} |
| ... | @@ -17,7 +17,7 @@ class SettingController extends Controller | ... | @@ -17,7 +17,7 @@ class SettingController extends Controller |
| 17 | */ | 17 | */ |
| 18 | public function index() | 18 | public function index() |
| 19 | { | 19 | { |
| 20 | - $this->checkPermission('settings-update'); | 20 | + $this->checkPermission('settings-manage'); |
| 21 | $this->setPageTitle('Settings'); | 21 | $this->setPageTitle('Settings'); |
| 22 | return view('settings/index'); | 22 | return view('settings/index'); |
| 23 | } | 23 | } |
| ... | @@ -32,7 +32,7 @@ class SettingController extends Controller | ... | @@ -32,7 +32,7 @@ class SettingController extends Controller |
| 32 | public function update(Request $request) | 32 | public function update(Request $request) |
| 33 | { | 33 | { |
| 34 | $this->preventAccessForDemoUsers(); | 34 | $this->preventAccessForDemoUsers(); |
| 35 | - $this->checkPermission('settings-update'); | 35 | + $this->checkPermission('settings-manage'); |
| 36 | 36 | ||
| 37 | // Cycles through posted settings and update them | 37 | // Cycles through posted settings and update them |
| 38 | foreach($request->all() as $name => $value) { | 38 | foreach($request->all() as $name => $value) { | ... | ... |
| ... | @@ -35,7 +35,8 @@ class UserController extends Controller | ... | @@ -35,7 +35,8 @@ class UserController extends Controller |
| 35 | */ | 35 | */ |
| 36 | public function index() | 36 | public function index() |
| 37 | { | 37 | { |
| 38 | - $users = $this->user->all(); | 38 | + $this->checkPermission('users-manage'); |
| 39 | + $users = $this->userRepo->getAllUsers(); | ||
| 39 | $this->setPageTitle('Users'); | 40 | $this->setPageTitle('Users'); |
| 40 | return view('users/index', ['users' => $users]); | 41 | return view('users/index', ['users' => $users]); |
| 41 | } | 42 | } |
| ... | @@ -46,7 +47,7 @@ class UserController extends Controller | ... | @@ -46,7 +47,7 @@ class UserController extends Controller |
| 46 | */ | 47 | */ |
| 47 | public function create() | 48 | public function create() |
| 48 | { | 49 | { |
| 49 | - $this->checkPermission('user-create'); | 50 | + $this->checkPermission('users-manage'); |
| 50 | $authMethod = config('auth.method'); | 51 | $authMethod = config('auth.method'); |
| 51 | return view('users/create', ['authMethod' => $authMethod]); | 52 | return view('users/create', ['authMethod' => $authMethod]); |
| 52 | } | 53 | } |
| ... | @@ -58,11 +59,10 @@ class UserController extends Controller | ... | @@ -58,11 +59,10 @@ class UserController extends Controller |
| 58 | */ | 59 | */ |
| 59 | public function store(Request $request) | 60 | public function store(Request $request) |
| 60 | { | 61 | { |
| 61 | - $this->checkPermission('user-create'); | 62 | + $this->checkPermission('users-manage'); |
| 62 | $validationRules = [ | 63 | $validationRules = [ |
| 63 | 'name' => 'required', | 64 | 'name' => 'required', |
| 64 | - 'email' => 'required|email|unique:users,email', | 65 | + 'email' => 'required|email|unique:users,email' |
| 65 | - 'role' => 'required|exists:roles,id' | ||
| 66 | ]; | 66 | ]; |
| 67 | 67 | ||
| 68 | $authMethod = config('auth.method'); | 68 | $authMethod = config('auth.method'); |
| ... | @@ -84,7 +84,11 @@ class UserController extends Controller | ... | @@ -84,7 +84,11 @@ class UserController extends Controller |
| 84 | } | 84 | } |
| 85 | 85 | ||
| 86 | $user->save(); | 86 | $user->save(); |
| 87 | - $user->attachRoleId($request->get('role')); | 87 | + |
| 88 | + if ($request->has('roles')) { | ||
| 89 | + $roles = $request->get('roles'); | ||
| 90 | + $user->roles()->sync($roles); | ||
| 91 | + } | ||
| 88 | 92 | ||
| 89 | // Get avatar from gravatar and save | 93 | // Get avatar from gravatar and save |
| 90 | if (!config('services.disable_services')) { | 94 | if (!config('services.disable_services')) { |
| ... | @@ -104,7 +108,7 @@ class UserController extends Controller | ... | @@ -104,7 +108,7 @@ class UserController extends Controller |
| 104 | */ | 108 | */ |
| 105 | public function edit($id, SocialAuthService $socialAuthService) | 109 | public function edit($id, SocialAuthService $socialAuthService) |
| 106 | { | 110 | { |
| 107 | - $this->checkPermissionOr('user-update', function () use ($id) { | 111 | + $this->checkPermissionOr('users-manage', function () use ($id) { |
| 108 | return $this->currentUser->id == $id; | 112 | return $this->currentUser->id == $id; |
| 109 | }); | 113 | }); |
| 110 | 114 | ||
| ... | @@ -125,7 +129,7 @@ class UserController extends Controller | ... | @@ -125,7 +129,7 @@ class UserController extends Controller |
| 125 | public function update(Request $request, $id) | 129 | public function update(Request $request, $id) |
| 126 | { | 130 | { |
| 127 | $this->preventAccessForDemoUsers(); | 131 | $this->preventAccessForDemoUsers(); |
| 128 | - $this->checkPermissionOr('user-update', function () use ($id) { | 132 | + $this->checkPermissionOr('users-manage', function () use ($id) { |
| 129 | return $this->currentUser->id == $id; | 133 | return $this->currentUser->id == $id; |
| 130 | }); | 134 | }); |
| 131 | 135 | ||
| ... | @@ -133,8 +137,7 @@ class UserController extends Controller | ... | @@ -133,8 +137,7 @@ class UserController extends Controller |
| 133 | 'name' => 'min:2', | 137 | 'name' => 'min:2', |
| 134 | 'email' => 'min:2|email|unique:users,email,' . $id, | 138 | 'email' => 'min:2|email|unique:users,email,' . $id, |
| 135 | 'password' => 'min:5|required_with:password_confirm', | 139 | 'password' => 'min:5|required_with:password_confirm', |
| 136 | - 'password-confirm' => 'same:password|required_with:password', | 140 | + 'password-confirm' => 'same:password|required_with:password' |
| 137 | - 'role' => 'exists:roles,id' | ||
| 138 | ], [ | 141 | ], [ |
| 139 | 'password-confirm.required_with' => 'Password confirmation required' | 142 | 'password-confirm.required_with' => 'Password confirmation required' |
| 140 | ]); | 143 | ]); |
| ... | @@ -143,8 +146,9 @@ class UserController extends Controller | ... | @@ -143,8 +146,9 @@ class UserController extends Controller |
| 143 | $user->fill($request->all()); | 146 | $user->fill($request->all()); |
| 144 | 147 | ||
| 145 | // Role updates | 148 | // Role updates |
| 146 | - if ($this->currentUser->can('user-update') && $request->has('role')) { | 149 | + if (userCan('users-manage') && $request->has('roles')) { |
| 147 | - $user->attachRoleId($request->get('role')); | 150 | + $roles = $request->get('roles'); |
| 151 | + $user->roles()->sync($roles); | ||
| 148 | } | 152 | } |
| 149 | 153 | ||
| 150 | // Password updates | 154 | // Password updates |
| ... | @@ -154,11 +158,12 @@ class UserController extends Controller | ... | @@ -154,11 +158,12 @@ class UserController extends Controller |
| 154 | } | 158 | } |
| 155 | 159 | ||
| 156 | // External auth id updates | 160 | // External auth id updates |
| 157 | - if ($this->currentUser->can('user-update') && $request->has('external_auth_id')) { | 161 | + if ($this->currentUser->can('users-manage') && $request->has('external_auth_id')) { |
| 158 | $user->external_auth_id = $request->get('external_auth_id'); | 162 | $user->external_auth_id = $request->get('external_auth_id'); |
| 159 | } | 163 | } |
| 160 | 164 | ||
| 161 | $user->save(); | 165 | $user->save(); |
| 166 | + session()->flash('success', 'User successfully updated'); | ||
| 162 | return redirect('/settings/users'); | 167 | return redirect('/settings/users'); |
| 163 | } | 168 | } |
| 164 | 169 | ||
| ... | @@ -169,7 +174,7 @@ class UserController extends Controller | ... | @@ -169,7 +174,7 @@ class UserController extends Controller |
| 169 | */ | 174 | */ |
| 170 | public function delete($id) | 175 | public function delete($id) |
| 171 | { | 176 | { |
| 172 | - $this->checkPermissionOr('user-delete', function () use ($id) { | 177 | + $this->checkPermissionOr('users-manage', function () use ($id) { |
| 173 | return $this->currentUser->id == $id; | 178 | return $this->currentUser->id == $id; |
| 174 | }); | 179 | }); |
| 175 | 180 | ||
| ... | @@ -186,7 +191,7 @@ class UserController extends Controller | ... | @@ -186,7 +191,7 @@ class UserController extends Controller |
| 186 | public function destroy($id) | 191 | public function destroy($id) |
| 187 | { | 192 | { |
| 188 | $this->preventAccessForDemoUsers(); | 193 | $this->preventAccessForDemoUsers(); |
| 189 | - $this->checkPermissionOr('user-delete', function () use ($id) { | 194 | + $this->checkPermissionOr('users-manage', function () use ($id) { |
| 190 | return $this->currentUser->id == $id; | 195 | return $this->currentUser->id == $id; |
| 191 | }); | 196 | }); |
| 192 | 197 | ... | ... |
| ... | @@ -19,6 +19,8 @@ Route::group(['middleware' => 'auth'], function () { | ... | @@ -19,6 +19,8 @@ Route::group(['middleware' => 'auth'], function () { |
| 19 | Route::delete('/{id}', 'BookController@destroy'); | 19 | Route::delete('/{id}', 'BookController@destroy'); |
| 20 | Route::get('/{slug}/sort-item', 'BookController@getSortItem'); | 20 | Route::get('/{slug}/sort-item', 'BookController@getSortItem'); |
| 21 | Route::get('/{slug}', 'BookController@show'); | 21 | Route::get('/{slug}', 'BookController@show'); |
| 22 | + Route::get('/{bookSlug}/restrict', 'BookController@showRestrict'); | ||
| 23 | + Route::put('/{bookSlug}/restrict', 'BookController@restrict'); | ||
| 22 | Route::get('/{slug}/delete', 'BookController@showDelete'); | 24 | Route::get('/{slug}/delete', 'BookController@showDelete'); |
| 23 | Route::get('/{bookSlug}/sort', 'BookController@sort'); | 25 | Route::get('/{bookSlug}/sort', 'BookController@sort'); |
| 24 | Route::put('/{bookSlug}/sort', 'BookController@saveSort'); | 26 | Route::put('/{bookSlug}/sort', 'BookController@saveSort'); |
| ... | @@ -32,6 +34,8 @@ Route::group(['middleware' => 'auth'], function () { | ... | @@ -32,6 +34,8 @@ Route::group(['middleware' => 'auth'], function () { |
| 32 | Route::get('/{bookSlug}/page/{pageSlug}/export/plaintext', 'PageController@exportPlainText'); | 34 | Route::get('/{bookSlug}/page/{pageSlug}/export/plaintext', 'PageController@exportPlainText'); |
| 33 | Route::get('/{bookSlug}/page/{pageSlug}/edit', 'PageController@edit'); | 35 | Route::get('/{bookSlug}/page/{pageSlug}/edit', 'PageController@edit'); |
| 34 | Route::get('/{bookSlug}/page/{pageSlug}/delete', 'PageController@showDelete'); | 36 | Route::get('/{bookSlug}/page/{pageSlug}/delete', 'PageController@showDelete'); |
| 37 | + Route::get('/{bookSlug}/page/{pageSlug}/restrict', 'PageController@showRestrict'); | ||
| 38 | + Route::put('/{bookSlug}/page/{pageSlug}/restrict', 'PageController@restrict'); | ||
| 35 | Route::put('/{bookSlug}/page/{pageSlug}', 'PageController@update'); | 39 | Route::put('/{bookSlug}/page/{pageSlug}', 'PageController@update'); |
| 36 | Route::delete('/{bookSlug}/page/{pageSlug}', 'PageController@destroy'); | 40 | Route::delete('/{bookSlug}/page/{pageSlug}', 'PageController@destroy'); |
| 37 | 41 | ||
| ... | @@ -47,6 +51,8 @@ Route::group(['middleware' => 'auth'], function () { | ... | @@ -47,6 +51,8 @@ Route::group(['middleware' => 'auth'], function () { |
| 47 | Route::get('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@show'); | 51 | Route::get('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@show'); |
| 48 | Route::put('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@update'); | 52 | Route::put('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@update'); |
| 49 | Route::get('/{bookSlug}/chapter/{chapterSlug}/edit', 'ChapterController@edit'); | 53 | Route::get('/{bookSlug}/chapter/{chapterSlug}/edit', 'ChapterController@edit'); |
| 54 | + Route::get('/{bookSlug}/chapter/{chapterSlug}/restrict', 'ChapterController@showRestrict'); | ||
| 55 | + Route::put('/{bookSlug}/chapter/{chapterSlug}/restrict', 'ChapterController@restrict'); | ||
| 50 | Route::get('/{bookSlug}/chapter/{chapterSlug}/delete', 'ChapterController@showDelete'); | 56 | Route::get('/{bookSlug}/chapter/{chapterSlug}/delete', 'ChapterController@showDelete'); |
| 51 | Route::delete('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@destroy'); | 57 | Route::delete('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@destroy'); |
| 52 | 58 | ||
| ... | @@ -87,6 +93,7 @@ Route::group(['middleware' => 'auth'], function () { | ... | @@ -87,6 +93,7 @@ Route::group(['middleware' => 'auth'], function () { |
| 87 | Route::group(['prefix' => 'settings'], function() { | 93 | Route::group(['prefix' => 'settings'], function() { |
| 88 | Route::get('/', 'SettingController@index'); | 94 | Route::get('/', 'SettingController@index'); |
| 89 | Route::post('/', 'SettingController@update'); | 95 | Route::post('/', 'SettingController@update'); |
| 96 | + | ||
| 90 | // Users | 97 | // Users |
| 91 | Route::get('/users', 'UserController@index'); | 98 | Route::get('/users', 'UserController@index'); |
| 92 | Route::get('/users/create', 'UserController@create'); | 99 | Route::get('/users/create', 'UserController@create'); |
| ... | @@ -95,6 +102,15 @@ Route::group(['middleware' => 'auth'], function () { | ... | @@ -95,6 +102,15 @@ Route::group(['middleware' => 'auth'], function () { |
| 95 | Route::get('/users/{id}', 'UserController@edit'); | 102 | Route::get('/users/{id}', 'UserController@edit'); |
| 96 | Route::put('/users/{id}', 'UserController@update'); | 103 | Route::put('/users/{id}', 'UserController@update'); |
| 97 | Route::delete('/users/{id}', 'UserController@destroy'); | 104 | Route::delete('/users/{id}', 'UserController@destroy'); |
| 105 | + | ||
| 106 | + // Roles | ||
| 107 | + Route::get('/roles', 'PermissionController@listRoles'); | ||
| 108 | + Route::get('/roles/new', 'PermissionController@createRole'); | ||
| 109 | + Route::post('/roles/new', 'PermissionController@storeRole'); | ||
| 110 | + Route::get('/roles/delete/{id}', 'PermissionController@showDeleteRole'); | ||
| 111 | + Route::delete('/roles/delete/{id}', 'PermissionController@deleteRole'); | ||
| 112 | + Route::get('/roles/{id}', 'PermissionController@editRole'); | ||
| 113 | + Route::put('/roles/{id}', 'PermissionController@updateRole'); | ||
| 98 | }); | 114 | }); |
| 99 | 115 | ||
| 100 | }); | 116 | }); | ... | ... |
| 1 | -<?php | 1 | +<?php namespace BookStack; |
| 2 | 2 | ||
| 3 | -namespace BookStack; | ||
| 4 | - | ||
| 5 | - | ||
| 6 | -use Illuminate\Database\Eloquent\Model; | ||
| 7 | use Images; | 3 | use Images; |
| 8 | 4 | ||
| 9 | -class Image extends Model | 5 | +class Image extends Ownable |
| 10 | { | 6 | { |
| 11 | - use Ownable; | ||
| 12 | 7 | ||
| 13 | protected $fillable = ['name']; | 8 | protected $fillable = ['name']; |
| 14 | 9 | ... | ... |
| 1 | <?php namespace BookStack; | 1 | <?php namespace BookStack; |
| 2 | 2 | ||
| 3 | +use Illuminate\Database\Eloquent\Model; | ||
| 3 | 4 | ||
| 4 | -trait Ownable | 5 | +abstract class Ownable extends Model |
| 5 | { | 6 | { |
| 6 | /** | 7 | /** |
| 7 | * Relation for the user that created this entity. | 8 | * Relation for the user that created this entity. |
| ... | @@ -20,4 +21,14 @@ trait Ownable | ... | @@ -20,4 +21,14 @@ trait Ownable |
| 20 | { | 21 | { |
| 21 | return $this->belongsTo('BookStack\User', 'updated_by'); | 22 | return $this->belongsTo('BookStack\User', 'updated_by'); |
| 22 | } | 23 | } |
| 24 | + | ||
| 25 | + /** | ||
| 26 | + * Gets the class name. | ||
| 27 | + * @return string | ||
| 28 | + */ | ||
| 29 | + public static function getClassName() | ||
| 30 | + { | ||
| 31 | + return strtolower(array_slice(explode('\\', static::class), -1, 1)[0]); | ||
| 32 | + } | ||
| 33 | + | ||
| 23 | } | 34 | } |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| ... | @@ -13,4 +13,14 @@ class Permission extends Model | ... | @@ -13,4 +13,14 @@ class Permission extends Model |
| 13 | { | 13 | { |
| 14 | return $this->belongsToMany('BookStack\Permissions'); | 14 | return $this->belongsToMany('BookStack\Permissions'); |
| 15 | } | 15 | } |
| 16 | + | ||
| 17 | + /** | ||
| 18 | + * Get the permission object by name. | ||
| 19 | + * @param $roleName | ||
| 20 | + * @return mixed | ||
| 21 | + */ | ||
| 22 | + public static function getByName($name) | ||
| 23 | + { | ||
| 24 | + return static::where('name', '=', $name)->first(); | ||
| 25 | + } | ||
| 16 | } | 26 | } | ... | ... |
| ... | @@ -28,11 +28,17 @@ class CustomFacadeProvider extends ServiceProvider | ... | @@ -28,11 +28,17 @@ class CustomFacadeProvider extends ServiceProvider |
| 28 | public function register() | 28 | public function register() |
| 29 | { | 29 | { |
| 30 | $this->app->bind('activity', function() { | 30 | $this->app->bind('activity', function() { |
| 31 | - return new ActivityService($this->app->make('BookStack\Activity')); | 31 | + return new ActivityService( |
| 32 | + $this->app->make('BookStack\Activity'), | ||
| 33 | + $this->app->make('BookStack\Services\RestrictionService') | ||
| 34 | + ); | ||
| 32 | }); | 35 | }); |
| 33 | 36 | ||
| 34 | $this->app->bind('views', function() { | 37 | $this->app->bind('views', function() { |
| 35 | - return new ViewService($this->app->make('BookStack\View')); | 38 | + return new ViewService( |
| 39 | + $this->app->make('BookStack\View'), | ||
| 40 | + $this->app->make('BookStack\Services\RestrictionService') | ||
| 41 | + ); | ||
| 36 | }); | 42 | }); |
| 37 | 43 | ||
| 38 | $this->app->bind('setting', function() { | 44 | $this->app->bind('setting', function() { |
| ... | @@ -41,6 +47,7 @@ class CustomFacadeProvider extends ServiceProvider | ... | @@ -41,6 +47,7 @@ class CustomFacadeProvider extends ServiceProvider |
| 41 | $this->app->make('Illuminate\Contracts\Cache\Repository') | 47 | $this->app->make('Illuminate\Contracts\Cache\Repository') |
| 42 | ); | 48 | ); |
| 43 | }); | 49 | }); |
| 50 | + | ||
| 44 | $this->app->bind('images', function() { | 51 | $this->app->bind('images', function() { |
| 45 | return new ImageService( | 52 | return new ImageService( |
| 46 | $this->app->make('Intervention\Image\ImageManager'), | 53 | $this->app->make('Intervention\Image\ImageManager'), | ... | ... |
| ... | @@ -11,18 +11,31 @@ class BookRepo | ... | @@ -11,18 +11,31 @@ class BookRepo |
| 11 | protected $book; | 11 | protected $book; |
| 12 | protected $pageRepo; | 12 | protected $pageRepo; |
| 13 | protected $chapterRepo; | 13 | protected $chapterRepo; |
| 14 | + protected $restrictionService; | ||
| 14 | 15 | ||
| 15 | /** | 16 | /** |
| 16 | * BookRepo constructor. | 17 | * BookRepo constructor. |
| 17 | * @param Book $book | 18 | * @param Book $book |
| 18 | * @param PageRepo $pageRepo | 19 | * @param PageRepo $pageRepo |
| 19 | * @param ChapterRepo $chapterRepo | 20 | * @param ChapterRepo $chapterRepo |
| 21 | + * @param RestrictionService $restrictionService | ||
| 20 | */ | 22 | */ |
| 21 | - public function __construct(Book $book, PageRepo $pageRepo, ChapterRepo $chapterRepo) | 23 | + public function __construct(Book $book, PageRepo $pageRepo, ChapterRepo $chapterRepo, RestrictionService $restrictionService) |
| 22 | { | 24 | { |
| 23 | $this->book = $book; | 25 | $this->book = $book; |
| 24 | $this->pageRepo = $pageRepo; | 26 | $this->pageRepo = $pageRepo; |
| 25 | $this->chapterRepo = $chapterRepo; | 27 | $this->chapterRepo = $chapterRepo; |
| 28 | + $this->restrictionService = $restrictionService; | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + /** | ||
| 32 | + * Base query for getting books. | ||
| 33 | + * Takes into account any restrictions. | ||
| 34 | + * @return mixed | ||
| 35 | + */ | ||
| 36 | + private function bookQuery() | ||
| 37 | + { | ||
| 38 | + return $this->restrictionService->enforceBookRestrictions($this->book, 'view'); | ||
| 26 | } | 39 | } |
| 27 | 40 | ||
| 28 | /** | 41 | /** |
| ... | @@ -32,7 +45,7 @@ class BookRepo | ... | @@ -32,7 +45,7 @@ class BookRepo |
| 32 | */ | 45 | */ |
| 33 | public function getById($id) | 46 | public function getById($id) |
| 34 | { | 47 | { |
| 35 | - return $this->book->findOrFail($id); | 48 | + return $this->bookQuery()->findOrFail($id); |
| 36 | } | 49 | } |
| 37 | 50 | ||
| 38 | /** | 51 | /** |
| ... | @@ -42,7 +55,7 @@ class BookRepo | ... | @@ -42,7 +55,7 @@ class BookRepo |
| 42 | */ | 55 | */ |
| 43 | public function getAll($count = 10) | 56 | public function getAll($count = 10) |
| 44 | { | 57 | { |
| 45 | - $bookQuery = $this->book->orderBy('name', 'asc'); | 58 | + $bookQuery = $this->bookQuery()->orderBy('name', 'asc'); |
| 46 | if (!$count) return $bookQuery->get(); | 59 | if (!$count) return $bookQuery->get(); |
| 47 | return $bookQuery->take($count)->get(); | 60 | return $bookQuery->take($count)->get(); |
| 48 | } | 61 | } |
| ... | @@ -54,7 +67,8 @@ class BookRepo | ... | @@ -54,7 +67,8 @@ class BookRepo |
| 54 | */ | 67 | */ |
| 55 | public function getAllPaginated($count = 10) | 68 | public function getAllPaginated($count = 10) |
| 56 | { | 69 | { |
| 57 | - return $this->book->orderBy('name', 'asc')->paginate($count); | 70 | + return $this->bookQuery() |
| 71 | + ->orderBy('name', 'asc')->paginate($count); | ||
| 58 | } | 72 | } |
| 59 | 73 | ||
| 60 | 74 | ||
| ... | @@ -65,7 +79,7 @@ class BookRepo | ... | @@ -65,7 +79,7 @@ class BookRepo |
| 65 | */ | 79 | */ |
| 66 | public function getLatest($count = 10) | 80 | public function getLatest($count = 10) |
| 67 | { | 81 | { |
| 68 | - return $this->book->orderBy('created_at', 'desc')->take($count)->get(); | 82 | + return $this->bookQuery()->orderBy('created_at', 'desc')->take($count)->get(); |
| 69 | } | 83 | } |
| 70 | 84 | ||
| 71 | /** | 85 | /** |
| ... | @@ -76,6 +90,7 @@ class BookRepo | ... | @@ -76,6 +90,7 @@ class BookRepo |
| 76 | */ | 90 | */ |
| 77 | public function getRecentlyViewed($count = 10, $page = 0) | 91 | public function getRecentlyViewed($count = 10, $page = 0) |
| 78 | { | 92 | { |
| 93 | + // TODO restrict | ||
| 79 | return Views::getUserRecentlyViewed($count, $page, $this->book); | 94 | return Views::getUserRecentlyViewed($count, $page, $this->book); |
| 80 | } | 95 | } |
| 81 | 96 | ||
| ... | @@ -87,6 +102,7 @@ class BookRepo | ... | @@ -87,6 +102,7 @@ class BookRepo |
| 87 | */ | 102 | */ |
| 88 | public function getPopular($count = 10, $page = 0) | 103 | public function getPopular($count = 10, $page = 0) |
| 89 | { | 104 | { |
| 105 | + // TODO - Restrict | ||
| 90 | return Views::getPopular($count, $page, $this->book); | 106 | return Views::getPopular($count, $page, $this->book); |
| 91 | } | 107 | } |
| 92 | 108 | ||
| ... | @@ -94,11 +110,12 @@ class BookRepo | ... | @@ -94,11 +110,12 @@ class BookRepo |
| 94 | * Get a book by slug | 110 | * Get a book by slug |
| 95 | * @param $slug | 111 | * @param $slug |
| 96 | * @return mixed | 112 | * @return mixed |
| 113 | + * @throws NotFoundException | ||
| 97 | */ | 114 | */ |
| 98 | public function getBySlug($slug) | 115 | public function getBySlug($slug) |
| 99 | { | 116 | { |
| 100 | - $book = $this->book->where('slug', '=', $slug)->first(); | 117 | + $book = $this->bookQuery()->where('slug', '=', $slug)->first(); |
| 101 | - if ($book === null) abort(404); | 118 | + if ($book === null) throw new NotFoundException('Book not found'); |
| 102 | return $book; | 119 | return $book; |
| 103 | } | 120 | } |
| 104 | 121 | ||
| ... | @@ -109,7 +126,7 @@ class BookRepo | ... | @@ -109,7 +126,7 @@ class BookRepo |
| 109 | */ | 126 | */ |
| 110 | public function exists($id) | 127 | public function exists($id) |
| 111 | { | 128 | { |
| 112 | - return $this->book->where('id', '=', $id)->exists(); | 129 | + return $this->bookQuery()->where('id', '=', $id)->exists(); |
| 113 | } | 130 | } |
| 114 | 131 | ||
| 115 | /** | 132 | /** |
| ... | @@ -119,17 +136,7 @@ class BookRepo | ... | @@ -119,17 +136,7 @@ class BookRepo |
| 119 | */ | 136 | */ |
| 120 | public function newFromInput($input) | 137 | public function newFromInput($input) |
| 121 | { | 138 | { |
| 122 | - return $this->book->fill($input); | 139 | + return $this->book->newInstance($input); |
| 123 | - } | ||
| 124 | - | ||
| 125 | - /** | ||
| 126 | - * Count the amount of books that have a specific slug. | ||
| 127 | - * @param $slug | ||
| 128 | - * @return mixed | ||
| 129 | - */ | ||
| 130 | - public function countBySlug($slug) | ||
| 131 | - { | ||
| 132 | - return $this->book->where('slug', '=', $slug)->count(); | ||
| 133 | } | 140 | } |
| 134 | 141 | ||
| 135 | /** | 142 | /** |
| ... | @@ -146,6 +153,7 @@ class BookRepo | ... | @@ -146,6 +153,7 @@ class BookRepo |
| 146 | $this->chapterRepo->destroy($chapter); | 153 | $this->chapterRepo->destroy($chapter); |
| 147 | } | 154 | } |
| 148 | $book->views()->delete(); | 155 | $book->views()->delete(); |
| 156 | + $book->restrictions()->delete(); | ||
| 149 | $book->delete(); | 157 | $book->delete(); |
| 150 | } | 158 | } |
| 151 | 159 | ||
| ... | @@ -202,8 +210,15 @@ class BookRepo | ... | @@ -202,8 +210,15 @@ class BookRepo |
| 202 | */ | 210 | */ |
| 203 | public function getChildren(Book $book) | 211 | public function getChildren(Book $book) |
| 204 | { | 212 | { |
| 205 | - $pages = $book->pages()->where('chapter_id', '=', 0)->get(); | 213 | + $pageQuery = $book->pages()->where('chapter_id', '=', 0); |
| 206 | - $chapters = $book->chapters()->with('pages')->get(); | 214 | + $pageQuery = $this->restrictionService->enforcePageRestrictions($pageQuery, 'view'); |
| 215 | + $pages = $pageQuery->get(); | ||
| 216 | + | ||
| 217 | + $chapterQuery = $book->chapters()->with(['pages' => function($query) { | ||
| 218 | + $this->restrictionService->enforcePageRestrictions($query, 'view'); | ||
| 219 | + }]); | ||
| 220 | + $chapterQuery = $this->restrictionService->enforceChapterRestrictions($chapterQuery, 'view'); | ||
| 221 | + $chapters = $chapterQuery->get(); | ||
| 207 | $children = $pages->merge($chapters); | 222 | $children = $pages->merge($chapters); |
| 208 | $bookSlug = $book->slug; | 223 | $bookSlug = $book->slug; |
| 209 | $children->each(function ($child) use ($bookSlug) { | 224 | $children->each(function ($child) use ($bookSlug) { |
| ... | @@ -236,7 +251,7 @@ class BookRepo | ... | @@ -236,7 +251,7 @@ class BookRepo |
| 236 | if (!empty($term)) { | 251 | if (!empty($term)) { |
| 237 | $terms = array_merge($terms, explode(' ', $term)); | 252 | $terms = array_merge($terms, explode(' ', $term)); |
| 238 | } | 253 | } |
| 239 | - $books = $this->book->fullTextSearchQuery(['name', 'description'], $terms) | 254 | + $books = $this->restrictionService->enforceBookRestrictions($this->book->fullTextSearchQuery(['name', 'description'], $terms)) |
| 240 | ->paginate($count)->appends($paginationAppends); | 255 | ->paginate($count)->appends($paginationAppends); |
| 241 | $words = join('|', explode(' ', preg_quote(trim($term), '/'))); | 256 | $words = join('|', explode(' ', preg_quote(trim($term), '/'))); |
| 242 | foreach ($books as $book) { | 257 | foreach ($books as $book) { |
| ... | @@ -247,4 +262,27 @@ class BookRepo | ... | @@ -247,4 +262,27 @@ class BookRepo |
| 247 | return $books; | 262 | return $books; |
| 248 | } | 263 | } |
| 249 | 264 | ||
| 265 | + /** | ||
| 266 | + * Updates books restrictions from a request | ||
| 267 | + * @param $request | ||
| 268 | + * @param $book | ||
| 269 | + */ | ||
| 270 | + public function updateRestrictionsFromRequest($request, $book) | ||
| 271 | + { | ||
| 272 | + // TODO - extract into shared repo | ||
| 273 | + $book->restricted = $request->has('restricted') && $request->get('restricted') === 'true'; | ||
| 274 | + $book->restrictions()->delete(); | ||
| 275 | + if ($request->has('restrictions')) { | ||
| 276 | + foreach ($request->get('restrictions') as $roleId => $restrictions) { | ||
| 277 | + foreach ($restrictions as $action => $value) { | ||
| 278 | + $book->restrictions()->create([ | ||
| 279 | + 'role_id' => $roleId, | ||
| 280 | + 'action' => strtolower($action) | ||
| 281 | + ]); | ||
| 282 | + } | ||
| 283 | + } | ||
| 284 | + } | ||
| 285 | + $book->save(); | ||
| 286 | + } | ||
| 287 | + | ||
| 250 | } | 288 | } |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
app/Repos/BookRepo.php.orig
0 → 100644
| 1 | +<?php namespace BookStack\Repos; | ||
| 2 | + | ||
| 3 | +use Activity; | ||
| 4 | +use BookStack\Exceptions\NotFoundException; | ||
| 5 | +use BookStack\Services\RestrictionService; | ||
| 6 | +use Illuminate\Support\Str; | ||
| 7 | +use BookStack\Book; | ||
| 8 | +use Views; | ||
| 9 | + | ||
| 10 | +class BookRepo | ||
| 11 | +{ | ||
| 12 | + | ||
| 13 | + protected $book; | ||
| 14 | + protected $pageRepo; | ||
| 15 | + protected $chapterRepo; | ||
| 16 | + protected $restrictionService; | ||
| 17 | + | ||
| 18 | + /** | ||
| 19 | + * BookRepo constructor. | ||
| 20 | + * @param Book $book | ||
| 21 | + * @param PageRepo $pageRepo | ||
| 22 | + * @param ChapterRepo $chapterRepo | ||
| 23 | + * @param RestrictionService $restrictionService | ||
| 24 | + */ | ||
| 25 | + public function __construct(Book $book, PageRepo $pageRepo, ChapterRepo $chapterRepo, RestrictionService $restrictionService) | ||
| 26 | + { | ||
| 27 | + $this->book = $book; | ||
| 28 | + $this->pageRepo = $pageRepo; | ||
| 29 | + $this->chapterRepo = $chapterRepo; | ||
| 30 | + $this->restrictionService = $restrictionService; | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + /** | ||
| 34 | + * Base query for getting books. | ||
| 35 | + * Takes into account any restrictions. | ||
| 36 | + * @return mixed | ||
| 37 | + */ | ||
| 38 | + private function bookQuery() | ||
| 39 | + { | ||
| 40 | + return $this->restrictionService->enforceBookRestrictions($this->book, 'view'); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + /** | ||
| 44 | + * Get the book that has the given id. | ||
| 45 | + * @param $id | ||
| 46 | + * @return mixed | ||
| 47 | + */ | ||
| 48 | + public function getById($id) | ||
| 49 | + { | ||
| 50 | + return $this->bookQuery()->findOrFail($id); | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + /** | ||
| 54 | + * Get all books, Limited by count. | ||
| 55 | + * @param int $count | ||
| 56 | + * @return mixed | ||
| 57 | + */ | ||
| 58 | + public function getAll($count = 10) | ||
| 59 | + { | ||
| 60 | + $bookQuery = $this->bookQuery()->orderBy('name', 'asc'); | ||
| 61 | + if (!$count) return $bookQuery->get(); | ||
| 62 | + return $bookQuery->take($count)->get(); | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + /** | ||
| 66 | + * Get all books paginated. | ||
| 67 | + * @param int $count | ||
| 68 | + * @return mixed | ||
| 69 | + */ | ||
| 70 | + public function getAllPaginated($count = 10) | ||
| 71 | + { | ||
| 72 | + return $this->bookQuery() | ||
| 73 | + ->orderBy('name', 'asc')->paginate($count); | ||
| 74 | + } | ||
| 75 | + | ||
| 76 | + | ||
| 77 | + /** | ||
| 78 | + * Get the latest books. | ||
| 79 | + * @param int $count | ||
| 80 | + * @return mixed | ||
| 81 | + */ | ||
| 82 | + public function getLatest($count = 10) | ||
| 83 | + { | ||
| 84 | + return $this->bookQuery()->orderBy('created_at', 'desc')->take($count)->get(); | ||
| 85 | + } | ||
| 86 | + | ||
| 87 | + /** | ||
| 88 | + * Gets the most recently viewed for a user. | ||
| 89 | + * @param int $count | ||
| 90 | + * @param int $page | ||
| 91 | + * @return mixed | ||
| 92 | + */ | ||
| 93 | + public function getRecentlyViewed($count = 10, $page = 0) | ||
| 94 | + { | ||
| 95 | + // TODO restrict | ||
| 96 | + return Views::getUserRecentlyViewed($count, $page, $this->book); | ||
| 97 | + } | ||
| 98 | + | ||
| 99 | + /** | ||
| 100 | + * Gets the most viewed books. | ||
| 101 | + * @param int $count | ||
| 102 | + * @param int $page | ||
| 103 | + * @return mixed | ||
| 104 | + */ | ||
| 105 | + public function getPopular($count = 10, $page = 0) | ||
| 106 | + { | ||
| 107 | + // TODO - Restrict | ||
| 108 | + return Views::getPopular($count, $page, $this->book); | ||
| 109 | + } | ||
| 110 | + | ||
| 111 | + /** | ||
| 112 | + * Get a book by slug | ||
| 113 | + * @param $slug | ||
| 114 | + * @return mixed | ||
| 115 | + * @throws NotFoundException | ||
| 116 | + */ | ||
| 117 | + public function getBySlug($slug) | ||
| 118 | + { | ||
| 119 | + $book = $this->bookQuery()->where('slug', '=', $slug)->first(); | ||
| 120 | + if ($book === null) throw new NotFoundException('Book not found'); | ||
| 121 | + return $book; | ||
| 122 | + } | ||
| 123 | + | ||
| 124 | + /** | ||
| 125 | + * Checks if a book exists. | ||
| 126 | + * @param $id | ||
| 127 | + * @return bool | ||
| 128 | + */ | ||
| 129 | + public function exists($id) | ||
| 130 | + { | ||
| 131 | + return $this->bookQuery()->where('id', '=', $id)->exists(); | ||
| 132 | + } | ||
| 133 | + | ||
| 134 | + /** | ||
| 135 | + * Get a new book instance from request input. | ||
| 136 | + * @param $input | ||
| 137 | + * @return Book | ||
| 138 | + */ | ||
| 139 | + public function newFromInput($input) | ||
| 140 | + { | ||
| 141 | + return $this->book->newInstance($input); | ||
| 142 | + } | ||
| 143 | + | ||
| 144 | + /** | ||
| 145 | + * Destroy a book identified by the given slug. | ||
| 146 | + * @param $bookSlug | ||
| 147 | + */ | ||
| 148 | + public function destroyBySlug($bookSlug) | ||
| 149 | + { | ||
| 150 | + $book = $this->getBySlug($bookSlug); | ||
| 151 | + foreach ($book->pages as $page) { | ||
| 152 | + $this->pageRepo->destroy($page); | ||
| 153 | + } | ||
| 154 | + foreach ($book->chapters as $chapter) { | ||
| 155 | + $this->chapterRepo->destroy($chapter); | ||
| 156 | + } | ||
| 157 | + $book->views()->delete(); | ||
| 158 | + $book->restrictions()->delete(); | ||
| 159 | + $book->delete(); | ||
| 160 | + } | ||
| 161 | + | ||
| 162 | + /** | ||
| 163 | + * Get the next child element priority. | ||
| 164 | + * @param Book $book | ||
| 165 | + * @return int | ||
| 166 | + */ | ||
| 167 | + public function getNewPriority($book) | ||
| 168 | + { | ||
| 169 | + $lastElem = $this->getChildren($book)->pop(); | ||
| 170 | + return $lastElem ? $lastElem->priority + 1 : 0; | ||
| 171 | + } | ||
| 172 | + | ||
| 173 | + /** | ||
| 174 | + * @param string $slug | ||
| 175 | + * @param bool|false $currentId | ||
| 176 | + * @return bool | ||
| 177 | + */ | ||
| 178 | + public function doesSlugExist($slug, $currentId = false) | ||
| 179 | + { | ||
| 180 | + $query = $this->book->where('slug', '=', $slug); | ||
| 181 | + if ($currentId) { | ||
| 182 | + $query = $query->where('id', '!=', $currentId); | ||
| 183 | + } | ||
| 184 | + return $query->count() > 0; | ||
| 185 | + } | ||
| 186 | + | ||
| 187 | + /** | ||
| 188 | + * Provides a suitable slug for the given book name. | ||
| 189 | + * Ensures the returned slug is unique in the system. | ||
| 190 | + * @param string $name | ||
| 191 | + * @param bool|false $currentId | ||
| 192 | + * @return string | ||
| 193 | + */ | ||
| 194 | + public function findSuitableSlug($name, $currentId = false) | ||
| 195 | + { | ||
| 196 | + $originalSlug = Str::slug($name); | ||
| 197 | + $slug = $originalSlug; | ||
| 198 | + $count = 2; | ||
| 199 | + while ($this->doesSlugExist($slug, $currentId)) { | ||
| 200 | + $slug = $originalSlug . '-' . $count; | ||
| 201 | + $count++; | ||
| 202 | + } | ||
| 203 | + return $slug; | ||
| 204 | + } | ||
| 205 | + | ||
| 206 | + /** | ||
| 207 | + * Get all child objects of a book. | ||
| 208 | + * Returns a sorted collection of Pages and Chapters. | ||
| 209 | + * Loads the bookslug onto child elements to prevent access database access for getting the slug. | ||
| 210 | + * @param Book $book | ||
| 211 | + * @return mixed | ||
| 212 | + */ | ||
| 213 | + public function getChildren(Book $book) | ||
| 214 | + { | ||
| 215 | + $pageQuery = $book->pages()->where('chapter_id', '=', 0); | ||
| 216 | + $pageQuery = $this->restrictionService->enforcePageRestrictions($pageQuery, 'view'); | ||
| 217 | + $pages = $pageQuery->get(); | ||
| 218 | + | ||
| 219 | + $chapterQuery = $book->chapters()->with(['pages' => function($query) { | ||
| 220 | + $this->restrictionService->enforcePageRestrictions($query, 'view'); | ||
| 221 | + }]); | ||
| 222 | + $chapterQuery = $this->restrictionService->enforceChapterRestrictions($chapterQuery, 'view'); | ||
| 223 | + $chapters = $chapterQuery->get(); | ||
| 224 | + $children = $pages->merge($chapters); | ||
| 225 | + $bookSlug = $book->slug; | ||
| 226 | + $children->each(function ($child) use ($bookSlug) { | ||
| 227 | + $child->setAttribute('bookSlug', $bookSlug); | ||
| 228 | + if ($child->isA('chapter')) { | ||
| 229 | + $child->pages->each(function ($page) use ($bookSlug) { | ||
| 230 | + $page->setAttribute('bookSlug', $bookSlug); | ||
| 231 | + }); | ||
| 232 | + } | ||
| 233 | + }); | ||
| 234 | + return $children->sortBy('priority'); | ||
| 235 | + } | ||
| 236 | + | ||
| 237 | + /** | ||
| 238 | + * Get books by search term. | ||
| 239 | + * @param $term | ||
| 240 | + * @param int $count | ||
| 241 | + * @param array $paginationAppends | ||
| 242 | + * @return mixed | ||
| 243 | + */ | ||
| 244 | + public function getBySearch($term, $count = 20, $paginationAppends = []) | ||
| 245 | + { | ||
| 246 | +<<<<<<< HEAD | ||
| 247 | + preg_match_all('/"(.*?)"/', $term, $matches); | ||
| 248 | + if (count($matches[1]) > 0) { | ||
| 249 | + $terms = $matches[1]; | ||
| 250 | + $term = trim(preg_replace('/"(.*?)"/', '', $term)); | ||
| 251 | + } else { | ||
| 252 | + $terms = []; | ||
| 253 | + } | ||
| 254 | + if (!empty($term)) { | ||
| 255 | + $terms = array_merge($terms, explode(' ', $term)); | ||
| 256 | + } | ||
| 257 | + $books = $this->book->fullTextSearchQuery(['name', 'description'], $terms) | ||
| 258 | +======= | ||
| 259 | + $terms = explode(' ', $term); | ||
| 260 | + $books = $this->restrictionService->enforceBookRestrictions($this->book->fullTextSearchQuery(['name', 'description'], $terms)) | ||
| 261 | +>>>>>>> custom_role_system | ||
| 262 | + ->paginate($count)->appends($paginationAppends); | ||
| 263 | + $words = join('|', explode(' ', preg_quote(trim($term), '/'))); | ||
| 264 | + foreach ($books as $book) { | ||
| 265 | + //highlight | ||
| 266 | + $result = preg_replace('#' . $words . '#iu', "<span class=\"highlight\">\$0</span>", $book->getExcerpt(100)); | ||
| 267 | + $book->searchSnippet = $result; | ||
| 268 | + } | ||
| 269 | + return $books; | ||
| 270 | + } | ||
| 271 | + | ||
| 272 | + /** | ||
| 273 | + * Updates books restrictions from a request | ||
| 274 | + * @param $request | ||
| 275 | + * @param $book | ||
| 276 | + */ | ||
| 277 | + public function updateRestrictionsFromRequest($request, $book) | ||
| 278 | + { | ||
| 279 | + // TODO - extract into shared repo | ||
| 280 | + $book->restricted = $request->has('restricted') && $request->get('restricted') === 'true'; | ||
| 281 | + $book->restrictions()->delete(); | ||
| 282 | + if ($request->has('restrictions')) { | ||
| 283 | + foreach ($request->get('restrictions') as $roleId => $restrictions) { | ||
| 284 | + foreach ($restrictions as $action => $value) { | ||
| 285 | + $book->restrictions()->create([ | ||
| 286 | + 'role_id' => $roleId, | ||
| 287 | + 'action' => strtolower($action) | ||
| 288 | + ]); | ||
| 289 | + } | ||
| 290 | + } | ||
| 291 | + } | ||
| 292 | + $book->save(); | ||
| 293 | + } | ||
| 294 | + | ||
| 295 | +} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| ... | @@ -2,6 +2,8 @@ | ... | @@ -2,6 +2,8 @@ |
| 2 | 2 | ||
| 3 | 3 | ||
| 4 | use Activity; | 4 | use Activity; |
| 5 | +use BookStack\Exceptions\NotFoundException; | ||
| 6 | +use BookStack\Services\RestrictionService; | ||
| 5 | use Illuminate\Support\Str; | 7 | use Illuminate\Support\Str; |
| 6 | use BookStack\Chapter; | 8 | use BookStack\Chapter; |
| 7 | 9 | ||
| ... | @@ -9,14 +11,26 @@ class ChapterRepo | ... | @@ -9,14 +11,26 @@ class ChapterRepo |
| 9 | { | 11 | { |
| 10 | 12 | ||
| 11 | protected $chapter; | 13 | protected $chapter; |
| 14 | + protected $restrictionService; | ||
| 12 | 15 | ||
| 13 | /** | 16 | /** |
| 14 | * ChapterRepo constructor. | 17 | * ChapterRepo constructor. |
| 15 | - * @param $chapter | 18 | + * @param Chapter $chapter |
| 19 | + * @param RestrictionService $restrictionService | ||
| 16 | */ | 20 | */ |
| 17 | - public function __construct(Chapter $chapter) | 21 | + public function __construct(Chapter $chapter, RestrictionService $restrictionService) |
| 18 | { | 22 | { |
| 19 | $this->chapter = $chapter; | 23 | $this->chapter = $chapter; |
| 24 | + $this->restrictionService = $restrictionService; | ||
| 25 | + } | ||
| 26 | + | ||
| 27 | + /** | ||
| 28 | + * Base query for getting chapters, Takes restrictions into account. | ||
| 29 | + * @return mixed | ||
| 30 | + */ | ||
| 31 | + private function chapterQuery() | ||
| 32 | + { | ||
| 33 | + return $this->restrictionService->enforceChapterRestrictions($this->chapter, 'view'); | ||
| 20 | } | 34 | } |
| 21 | 35 | ||
| 22 | /** | 36 | /** |
| ... | @@ -26,7 +40,7 @@ class ChapterRepo | ... | @@ -26,7 +40,7 @@ class ChapterRepo |
| 26 | */ | 40 | */ |
| 27 | public function idExists($id) | 41 | public function idExists($id) |
| 28 | { | 42 | { |
| 29 | - return $this->chapter->where('id', '=', $id)->count() > 0; | 43 | + return $this->chapterQuery()->where('id', '=', $id)->count() > 0; |
| 30 | } | 44 | } |
| 31 | 45 | ||
| 32 | /** | 46 | /** |
| ... | @@ -36,7 +50,7 @@ class ChapterRepo | ... | @@ -36,7 +50,7 @@ class ChapterRepo |
| 36 | */ | 50 | */ |
| 37 | public function getById($id) | 51 | public function getById($id) |
| 38 | { | 52 | { |
| 39 | - return $this->chapter->findOrFail($id); | 53 | + return $this->chapterQuery()->findOrFail($id); |
| 40 | } | 54 | } |
| 41 | 55 | ||
| 42 | /** | 56 | /** |
| ... | @@ -45,7 +59,7 @@ class ChapterRepo | ... | @@ -45,7 +59,7 @@ class ChapterRepo |
| 45 | */ | 59 | */ |
| 46 | public function getAll() | 60 | public function getAll() |
| 47 | { | 61 | { |
| 48 | - return $this->chapter->all(); | 62 | + return $this->chapterQuery()->all(); |
| 49 | } | 63 | } |
| 50 | 64 | ||
| 51 | /** | 65 | /** |
| ... | @@ -53,15 +67,25 @@ class ChapterRepo | ... | @@ -53,15 +67,25 @@ class ChapterRepo |
| 53 | * @param $slug | 67 | * @param $slug |
| 54 | * @param $bookId | 68 | * @param $bookId |
| 55 | * @return mixed | 69 | * @return mixed |
| 70 | + * @throws NotFoundException | ||
| 56 | */ | 71 | */ |
| 57 | public function getBySlug($slug, $bookId) | 72 | public function getBySlug($slug, $bookId) |
| 58 | { | 73 | { |
| 59 | - $chapter = $this->chapter->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first(); | 74 | + $chapter = $this->chapterQuery()->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first(); |
| 60 | - if ($chapter === null) abort(404); | 75 | + if ($chapter === null) throw new NotFoundException('Chapter not found'); |
| 61 | return $chapter; | 76 | return $chapter; |
| 62 | } | 77 | } |
| 63 | 78 | ||
| 64 | /** | 79 | /** |
| 80 | + * Get the child items for a chapter | ||
| 81 | + * @param Chapter $chapter | ||
| 82 | + */ | ||
| 83 | + public function getChildren(Chapter $chapter) | ||
| 84 | + { | ||
| 85 | + return $this->restrictionService->enforcePageRestrictions($chapter->pages())->get(); | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | + /** | ||
| 65 | * Create a new chapter from request input. | 89 | * Create a new chapter from request input. |
| 66 | * @param $input | 90 | * @param $input |
| 67 | * @return $this | 91 | * @return $this |
| ... | @@ -85,6 +109,7 @@ class ChapterRepo | ... | @@ -85,6 +109,7 @@ class ChapterRepo |
| 85 | } | 109 | } |
| 86 | Activity::removeEntity($chapter); | 110 | Activity::removeEntity($chapter); |
| 87 | $chapter->views()->delete(); | 111 | $chapter->views()->delete(); |
| 112 | + $chapter->restrictions()->delete(); | ||
| 88 | $chapter->delete(); | 113 | $chapter->delete(); |
| 89 | } | 114 | } |
| 90 | 115 | ||
| ... | @@ -141,7 +166,7 @@ class ChapterRepo | ... | @@ -141,7 +166,7 @@ class ChapterRepo |
| 141 | if (!empty($term)) { | 166 | if (!empty($term)) { |
| 142 | $terms = array_merge($terms, explode(' ', $term)); | 167 | $terms = array_merge($terms, explode(' ', $term)); |
| 143 | } | 168 | } |
| 144 | - $chapters = $this->chapter->fullTextSearchQuery(['name', 'description'], $terms, $whereTerms) | 169 | + $chapters = $this->restrictionService->enforceChapterRestrictions($this->chapter->fullTextSearchQuery(['name', 'description'], $terms, $whereTerms)) |
| 145 | ->paginate($count)->appends($paginationAppends); | 170 | ->paginate($count)->appends($paginationAppends); |
| 146 | $words = join('|', explode(' ', preg_quote(trim($term), '/'))); | 171 | $words = join('|', explode(' ', preg_quote(trim($term), '/'))); |
| 147 | foreach ($chapters as $chapter) { | 172 | foreach ($chapters as $chapter) { |
| ... | @@ -170,4 +195,27 @@ class ChapterRepo | ... | @@ -170,4 +195,27 @@ class ChapterRepo |
| 170 | return $chapter; | 195 | return $chapter; |
| 171 | } | 196 | } |
| 172 | 197 | ||
| 198 | + /** | ||
| 199 | + * Updates pages restrictions from a request | ||
| 200 | + * @param $request | ||
| 201 | + * @param $chapter | ||
| 202 | + */ | ||
| 203 | + public function updateRestrictionsFromRequest($request, $chapter) | ||
| 204 | + { | ||
| 205 | + // TODO - extract into shared repo | ||
| 206 | + $chapter->restricted = $request->has('restricted') && $request->get('restricted') === 'true'; | ||
| 207 | + $chapter->restrictions()->delete(); | ||
| 208 | + if ($request->has('restrictions')) { | ||
| 209 | + foreach($request->get('restrictions') as $roleId => $restrictions) { | ||
| 210 | + foreach ($restrictions as $action => $value) { | ||
| 211 | + $chapter->restrictions()->create([ | ||
| 212 | + 'role_id' => $roleId, | ||
| 213 | + 'action' => strtolower($action) | ||
| 214 | + ]); | ||
| 215 | + } | ||
| 216 | + } | ||
| 217 | + } | ||
| 218 | + $chapter->save(); | ||
| 219 | + } | ||
| 220 | + | ||
| 173 | } | 221 | } |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
app/Repos/ChapterRepo.php.orig
0 → 100644
| 1 | +<?php namespace BookStack\Repos; | ||
| 2 | + | ||
| 3 | + | ||
| 4 | +use Activity; | ||
| 5 | +use BookStack\Exceptions\NotFoundException; | ||
| 6 | +use BookStack\Services\RestrictionService; | ||
| 7 | +use Illuminate\Support\Str; | ||
| 8 | +use BookStack\Chapter; | ||
| 9 | + | ||
| 10 | +class ChapterRepo | ||
| 11 | +{ | ||
| 12 | + | ||
| 13 | + protected $chapter; | ||
| 14 | + protected $restrictionService; | ||
| 15 | + | ||
| 16 | + /** | ||
| 17 | + * ChapterRepo constructor. | ||
| 18 | + * @param Chapter $chapter | ||
| 19 | + * @param RestrictionService $restrictionService | ||
| 20 | + */ | ||
| 21 | + public function __construct(Chapter $chapter, RestrictionService $restrictionService) | ||
| 22 | + { | ||
| 23 | + $this->chapter = $chapter; | ||
| 24 | + $this->restrictionService = $restrictionService; | ||
| 25 | + } | ||
| 26 | + | ||
| 27 | + /** | ||
| 28 | + * Base query for getting chapters, Takes restrictions into account. | ||
| 29 | + * @return mixed | ||
| 30 | + */ | ||
| 31 | + private function chapterQuery() | ||
| 32 | + { | ||
| 33 | + return $this->restrictionService->enforceChapterRestrictions($this->chapter, 'view'); | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + /** | ||
| 37 | + * Check if an id exists. | ||
| 38 | + * @param $id | ||
| 39 | + * @return bool | ||
| 40 | + */ | ||
| 41 | + public function idExists($id) | ||
| 42 | + { | ||
| 43 | + return $this->chapterQuery()->where('id', '=', $id)->count() > 0; | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | + /** | ||
| 47 | + * Get a chapter by a specific id. | ||
| 48 | + * @param $id | ||
| 49 | + * @return mixed | ||
| 50 | + */ | ||
| 51 | + public function getById($id) | ||
| 52 | + { | ||
| 53 | + return $this->chapterQuery()->findOrFail($id); | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + /** | ||
| 57 | + * Get all chapters. | ||
| 58 | + * @return \Illuminate\Database\Eloquent\Collection|static[] | ||
| 59 | + */ | ||
| 60 | + public function getAll() | ||
| 61 | + { | ||
| 62 | + return $this->chapterQuery()->all(); | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + /** | ||
| 66 | + * Get a chapter that has the given slug within the given book. | ||
| 67 | + * @param $slug | ||
| 68 | + * @param $bookId | ||
| 69 | + * @return mixed | ||
| 70 | + * @throws NotFoundException | ||
| 71 | + */ | ||
| 72 | + public function getBySlug($slug, $bookId) | ||
| 73 | + { | ||
| 74 | + $chapter = $this->chapterQuery()->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first(); | ||
| 75 | + if ($chapter === null) throw new NotFoundException('Chapter not found'); | ||
| 76 | + return $chapter; | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + /** | ||
| 80 | + * Get the child items for a chapter | ||
| 81 | + * @param Chapter $chapter | ||
| 82 | + */ | ||
| 83 | + public function getChildren(Chapter $chapter) | ||
| 84 | + { | ||
| 85 | + return $this->restrictionService->enforcePageRestrictions($chapter->pages())->get(); | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | + /** | ||
| 89 | + * Create a new chapter from request input. | ||
| 90 | + * @param $input | ||
| 91 | + * @return $this | ||
| 92 | + */ | ||
| 93 | + public function newFromInput($input) | ||
| 94 | + { | ||
| 95 | + return $this->chapter->fill($input); | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | + /** | ||
| 99 | + * Destroy a chapter and its relations by providing its slug. | ||
| 100 | + * @param Chapter $chapter | ||
| 101 | + */ | ||
| 102 | + public function destroy(Chapter $chapter) | ||
| 103 | + { | ||
| 104 | + if (count($chapter->pages) > 0) { | ||
| 105 | + foreach ($chapter->pages as $page) { | ||
| 106 | + $page->chapter_id = 0; | ||
| 107 | + $page->save(); | ||
| 108 | + } | ||
| 109 | + } | ||
| 110 | + Activity::removeEntity($chapter); | ||
| 111 | + $chapter->views()->delete(); | ||
| 112 | + $chapter->restrictions()->delete(); | ||
| 113 | + $chapter->delete(); | ||
| 114 | + } | ||
| 115 | + | ||
| 116 | + /** | ||
| 117 | + * Check if a chapter's slug exists. | ||
| 118 | + * @param $slug | ||
| 119 | + * @param $bookId | ||
| 120 | + * @param bool|false $currentId | ||
| 121 | + * @return bool | ||
| 122 | + */ | ||
| 123 | + public function doesSlugExist($slug, $bookId, $currentId = false) | ||
| 124 | + { | ||
| 125 | + $query = $this->chapter->where('slug', '=', $slug)->where('book_id', '=', $bookId); | ||
| 126 | + if ($currentId) { | ||
| 127 | + $query = $query->where('id', '!=', $currentId); | ||
| 128 | + } | ||
| 129 | + return $query->count() > 0; | ||
| 130 | + } | ||
| 131 | + | ||
| 132 | + /** | ||
| 133 | + * Finds a suitable slug for the provided name. | ||
| 134 | + * Checks database to prevent duplicate slugs. | ||
| 135 | + * @param $name | ||
| 136 | + * @param $bookId | ||
| 137 | + * @param bool|false $currentId | ||
| 138 | + * @return string | ||
| 139 | + */ | ||
| 140 | + public function findSuitableSlug($name, $bookId, $currentId = false) | ||
| 141 | + { | ||
| 142 | + $slug = Str::slug($name); | ||
| 143 | + while ($this->doesSlugExist($slug, $bookId, $currentId)) { | ||
| 144 | + $slug .= '-' . substr(md5(rand(1, 500)), 0, 3); | ||
| 145 | + } | ||
| 146 | + return $slug; | ||
| 147 | + } | ||
| 148 | + | ||
| 149 | + /** | ||
| 150 | + * Get chapters by the given search term. | ||
| 151 | + * @param $term | ||
| 152 | + * @param array $whereTerms | ||
| 153 | + * @param int $count | ||
| 154 | + * @param array $paginationAppends | ||
| 155 | + * @return mixed | ||
| 156 | + */ | ||
| 157 | + public function getBySearch($term, $whereTerms = [], $count = 20, $paginationAppends = []) | ||
| 158 | + { | ||
| 159 | +<<<<<<< HEAD | ||
| 160 | + preg_match_all('/"(.*?)"/', $term, $matches); | ||
| 161 | + if (count($matches[1]) > 0) { | ||
| 162 | + $terms = $matches[1]; | ||
| 163 | + $term = trim(preg_replace('/"(.*?)"/', '', $term)); | ||
| 164 | + } else { | ||
| 165 | + $terms = []; | ||
| 166 | + } | ||
| 167 | + if (!empty($term)) { | ||
| 168 | + $terms = array_merge($terms, explode(' ', $term)); | ||
| 169 | + } | ||
| 170 | + $chapters = $this->chapter->fullTextSearchQuery(['name', 'description'], $terms, $whereTerms) | ||
| 171 | +======= | ||
| 172 | + $terms = explode(' ', $term); | ||
| 173 | + $chapters = $this->restrictionService->enforceChapterRestrictions($this->chapter->fullTextSearchQuery(['name', 'description'], $terms, $whereTerms)) | ||
| 174 | +>>>>>>> custom_role_system | ||
| 175 | + ->paginate($count)->appends($paginationAppends); | ||
| 176 | + $words = join('|', explode(' ', preg_quote(trim($term), '/'))); | ||
| 177 | + foreach ($chapters as $chapter) { | ||
| 178 | + //highlight | ||
| 179 | + $result = preg_replace('#' . $words . '#iu', "<span class=\"highlight\">\$0</span>", $chapter->getExcerpt(100)); | ||
| 180 | + $chapter->searchSnippet = $result; | ||
| 181 | + } | ||
| 182 | + return $chapters; | ||
| 183 | + } | ||
| 184 | + | ||
| 185 | + /** | ||
| 186 | + * Changes the book relation of this chapter. | ||
| 187 | + * @param $bookId | ||
| 188 | + * @param Chapter $chapter | ||
| 189 | + * @return Chapter | ||
| 190 | + */ | ||
| 191 | + public function changeBook($bookId, Chapter $chapter) | ||
| 192 | + { | ||
| 193 | + $chapter->book_id = $bookId; | ||
| 194 | + foreach ($chapter->activity as $activity) { | ||
| 195 | + $activity->book_id = $bookId; | ||
| 196 | + $activity->save(); | ||
| 197 | + } | ||
| 198 | + $chapter->slug = $this->findSuitableSlug($chapter->name, $bookId, $chapter->id); | ||
| 199 | + $chapter->save(); | ||
| 200 | + return $chapter; | ||
| 201 | + } | ||
| 202 | + | ||
| 203 | + /** | ||
| 204 | + * Updates pages restrictions from a request | ||
| 205 | + * @param $request | ||
| 206 | + * @param $chapter | ||
| 207 | + */ | ||
| 208 | + public function updateRestrictionsFromRequest($request, $chapter) | ||
| 209 | + { | ||
| 210 | + // TODO - extract into shared repo | ||
| 211 | + $chapter->restricted = $request->has('restricted') && $request->get('restricted') === 'true'; | ||
| 212 | + $chapter->restrictions()->delete(); | ||
| 213 | + if ($request->has('restrictions')) { | ||
| 214 | + foreach($request->get('restrictions') as $roleId => $restrictions) { | ||
| 215 | + foreach ($restrictions as $action => $value) { | ||
| 216 | + $chapter->restrictions()->create([ | ||
| 217 | + 'role_id' => $roleId, | ||
| 218 | + 'action' => strtolower($action) | ||
| 219 | + ]); | ||
| 220 | + } | ||
| 221 | + } | ||
| 222 | + } | ||
| 223 | + $chapter->save(); | ||
| 224 | + } | ||
| 225 | + | ||
| 226 | +} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| ... | @@ -4,6 +4,7 @@ | ... | @@ -4,6 +4,7 @@ |
| 4 | use BookStack\Book; | 4 | use BookStack\Book; |
| 5 | use BookStack\Chapter; | 5 | use BookStack\Chapter; |
| 6 | use BookStack\Page; | 6 | use BookStack\Page; |
| 7 | +use BookStack\Services\RestrictionService; | ||
| 7 | 8 | ||
| 8 | class EntityRepo | 9 | class EntityRepo |
| 9 | { | 10 | { |
| ... | @@ -11,18 +12,21 @@ class EntityRepo | ... | @@ -11,18 +12,21 @@ class EntityRepo |
| 11 | public $book; | 12 | public $book; |
| 12 | public $chapter; | 13 | public $chapter; |
| 13 | public $page; | 14 | public $page; |
| 15 | + private $restrictionService; | ||
| 14 | 16 | ||
| 15 | /** | 17 | /** |
| 16 | * EntityService constructor. | 18 | * EntityService constructor. |
| 17 | - * @param $book | 19 | + * @param Book $book |
| 18 | - * @param $chapter | 20 | + * @param Chapter $chapter |
| 19 | - * @param $page | 21 | + * @param Page $page |
| 22 | + * @param RestrictionService $restrictionService | ||
| 20 | */ | 23 | */ |
| 21 | - public function __construct(Book $book, Chapter $chapter, Page $page) | 24 | + public function __construct(Book $book, Chapter $chapter, Page $page, RestrictionService $restrictionService) |
| 22 | { | 25 | { |
| 23 | $this->book = $book; | 26 | $this->book = $book; |
| 24 | $this->chapter = $chapter; | 27 | $this->chapter = $chapter; |
| 25 | $this->page = $page; | 28 | $this->page = $page; |
| 29 | + $this->restrictionService = $restrictionService; | ||
| 26 | } | 30 | } |
| 27 | 31 | ||
| 28 | /** | 32 | /** |
| ... | @@ -32,7 +36,8 @@ class EntityRepo | ... | @@ -32,7 +36,8 @@ class EntityRepo |
| 32 | */ | 36 | */ |
| 33 | public function getRecentlyCreatedBooks($count = 20, $page = 0) | 37 | public function getRecentlyCreatedBooks($count = 20, $page = 0) |
| 34 | { | 38 | { |
| 35 | - return $this->book->orderBy('created_at', 'desc')->skip($page*$count)->take($count)->get(); | 39 | + return $this->restrictionService->enforceBookRestrictions($this->book) |
| 40 | + ->orderBy('created_at', 'desc')->skip($page*$count)->take($count)->get(); | ||
| 36 | } | 41 | } |
| 37 | 42 | ||
| 38 | /** | 43 | /** |
| ... | @@ -43,7 +48,8 @@ class EntityRepo | ... | @@ -43,7 +48,8 @@ class EntityRepo |
| 43 | */ | 48 | */ |
| 44 | public function getRecentlyUpdatedBooks($count = 20, $page = 0) | 49 | public function getRecentlyUpdatedBooks($count = 20, $page = 0) |
| 45 | { | 50 | { |
| 46 | - return $this->book->orderBy('updated_at', 'desc')->skip($page*$count)->take($count)->get(); | 51 | + return $this->restrictionService->enforceBookRestrictions($this->book) |
| 52 | + ->orderBy('updated_at', 'desc')->skip($page*$count)->take($count)->get(); | ||
| 47 | } | 53 | } |
| 48 | 54 | ||
| 49 | /** | 55 | /** |
| ... | @@ -53,7 +59,8 @@ class EntityRepo | ... | @@ -53,7 +59,8 @@ class EntityRepo |
| 53 | */ | 59 | */ |
| 54 | public function getRecentlyCreatedPages($count = 20, $page = 0) | 60 | public function getRecentlyCreatedPages($count = 20, $page = 0) |
| 55 | { | 61 | { |
| 56 | - return $this->page->orderBy('created_at', 'desc')->skip($page*$count)->take($count)->get(); | 62 | + return $this->restrictionService->enforcePageRestrictions($this->page) |
| 63 | + ->orderBy('created_at', 'desc')->skip($page*$count)->take($count)->get(); | ||
| 57 | } | 64 | } |
| 58 | 65 | ||
| 59 | /** | 66 | /** |
| ... | @@ -64,7 +71,8 @@ class EntityRepo | ... | @@ -64,7 +71,8 @@ class EntityRepo |
| 64 | */ | 71 | */ |
| 65 | public function getRecentlyUpdatedPages($count = 20, $page = 0) | 72 | public function getRecentlyUpdatedPages($count = 20, $page = 0) |
| 66 | { | 73 | { |
| 67 | - return $this->page->orderBy('updated_at', 'desc')->skip($page*$count)->take($count)->get(); | 74 | + return $this->restrictionService->enforcePageRestrictions($this->page) |
| 75 | + ->orderBy('updated_at', 'desc')->skip($page*$count)->take($count)->get(); | ||
| 68 | } | 76 | } |
| 69 | 77 | ||
| 70 | 78 | ... | ... |
| ... | @@ -4,6 +4,8 @@ | ... | @@ -4,6 +4,8 @@ |
| 4 | use Activity; | 4 | use Activity; |
| 5 | use BookStack\Book; | 5 | use BookStack\Book; |
| 6 | use BookStack\Chapter; | 6 | use BookStack\Chapter; |
| 7 | +use BookStack\Exceptions\NotFoundException; | ||
| 8 | +use BookStack\Services\RestrictionService; | ||
| 7 | use Illuminate\Http\Request; | 9 | use Illuminate\Http\Request; |
| 8 | use Illuminate\Support\Facades\Auth; | 10 | use Illuminate\Support\Facades\Auth; |
| 9 | use Illuminate\Support\Facades\Log; | 11 | use Illuminate\Support\Facades\Log; |
| ... | @@ -16,26 +18,28 @@ class PageRepo | ... | @@ -16,26 +18,28 @@ class PageRepo |
| 16 | { | 18 | { |
| 17 | protected $page; | 19 | protected $page; |
| 18 | protected $pageRevision; | 20 | protected $pageRevision; |
| 21 | + protected $restrictionService; | ||
| 19 | 22 | ||
| 20 | /** | 23 | /** |
| 21 | * PageRepo constructor. | 24 | * PageRepo constructor. |
| 22 | - * @param Page $page | 25 | + * @param Page $page |
| 23 | * @param PageRevision $pageRevision | 26 | * @param PageRevision $pageRevision |
| 27 | + * @param RestrictionService $restrictionService | ||
| 24 | */ | 28 | */ |
| 25 | - public function __construct(Page $page, PageRevision $pageRevision) | 29 | + public function __construct(Page $page, PageRevision $pageRevision, RestrictionService $restrictionService) |
| 26 | { | 30 | { |
| 27 | $this->page = $page; | 31 | $this->page = $page; |
| 28 | $this->pageRevision = $pageRevision; | 32 | $this->pageRevision = $pageRevision; |
| 33 | + $this->restrictionService = $restrictionService; | ||
| 29 | } | 34 | } |
| 30 | 35 | ||
| 31 | /** | 36 | /** |
| 32 | - * Check if a page id exists. | 37 | + * Base query for getting pages, Takes restrictions into account. |
| 33 | - * @param $id | 38 | + * @return mixed |
| 34 | - * @return bool | ||
| 35 | */ | 39 | */ |
| 36 | - public function idExists($id) | 40 | + private function pageQuery() |
| 37 | { | 41 | { |
| 38 | - return $this->page->where('page_id', '=', $id)->count() > 0; | 42 | + return $this->restrictionService->enforcePageRestrictions($this->page, 'view'); |
| 39 | } | 43 | } |
| 40 | 44 | ||
| 41 | /** | 45 | /** |
| ... | @@ -45,16 +49,7 @@ class PageRepo | ... | @@ -45,16 +49,7 @@ class PageRepo |
| 45 | */ | 49 | */ |
| 46 | public function getById($id) | 50 | public function getById($id) |
| 47 | { | 51 | { |
| 48 | - return $this->page->findOrFail($id); | 52 | + return $this->pageQuery()->findOrFail($id); |
| 49 | - } | ||
| 50 | - | ||
| 51 | - /** | ||
| 52 | - * Get all pages. | ||
| 53 | - * @return \Illuminate\Database\Eloquent\Collection|static[] | ||
| 54 | - */ | ||
| 55 | - public function getAll() | ||
| 56 | - { | ||
| 57 | - return $this->page->all(); | ||
| 58 | } | 53 | } |
| 59 | 54 | ||
| 60 | /** | 55 | /** |
| ... | @@ -62,11 +57,12 @@ class PageRepo | ... | @@ -62,11 +57,12 @@ class PageRepo |
| 62 | * @param $slug | 57 | * @param $slug |
| 63 | * @param $bookId | 58 | * @param $bookId |
| 64 | * @return mixed | 59 | * @return mixed |
| 60 | + * @throws NotFoundException | ||
| 65 | */ | 61 | */ |
| 66 | public function getBySlug($slug, $bookId) | 62 | public function getBySlug($slug, $bookId) |
| 67 | { | 63 | { |
| 68 | - $page = $this->page->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first(); | 64 | + $page = $this->pageQuery()->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first(); |
| 69 | - if ($page === null) throw new NotFoundHttpException('Page not found'); | 65 | + if ($page === null) throw new NotFoundException('Page not found'); |
| 70 | return $page; | 66 | return $page; |
| 71 | } | 67 | } |
| 72 | 68 | ||
| ... | @@ -81,6 +77,9 @@ class PageRepo | ... | @@ -81,6 +77,9 @@ class PageRepo |
| 81 | public function findPageUsingOldSlug($pageSlug, $bookSlug) | 77 | public function findPageUsingOldSlug($pageSlug, $bookSlug) |
| 82 | { | 78 | { |
| 83 | $revision = $this->pageRevision->where('slug', '=', $pageSlug) | 79 | $revision = $this->pageRevision->where('slug', '=', $pageSlug) |
| 80 | + ->whereHas('page', function($query) { | ||
| 81 | + $this->restrictionService->enforcePageRestrictions($query); | ||
| 82 | + }) | ||
| 84 | ->where('book_slug', '=', $bookSlug)->orderBy('created_at', 'desc') | 83 | ->where('book_slug', '=', $bookSlug)->orderBy('created_at', 'desc') |
| 85 | ->with('page')->first(); | 84 | ->with('page')->first(); |
| 86 | return $revision !== null ? $revision->page : null; | 85 | return $revision !== null ? $revision->page : null; |
| ... | @@ -211,7 +210,7 @@ class PageRepo | ... | @@ -211,7 +210,7 @@ class PageRepo |
| 211 | if (!empty($term)) { | 210 | if (!empty($term)) { |
| 212 | $terms = array_merge($terms, explode(' ', $term)); | 211 | $terms = array_merge($terms, explode(' ', $term)); |
| 213 | } | 212 | } |
| 214 | - $pages = $this->page->fullTextSearchQuery(['name', 'text'], $terms, $whereTerms) | 213 | + $pages = $this->restrictionService->enforcePageRestrictions($this->page->fullTextSearchQuery(['name', 'text'], $terms, $whereTerms)) |
| 215 | ->paginate($count)->appends($paginationAppends); | 214 | ->paginate($count)->appends($paginationAppends); |
| 216 | 215 | ||
| 217 | // Add highlights to page text. | 216 | // Add highlights to page text. |
| ... | @@ -249,7 +248,7 @@ class PageRepo | ... | @@ -249,7 +248,7 @@ class PageRepo |
| 249 | */ | 248 | */ |
| 250 | public function searchForImage($imageString) | 249 | public function searchForImage($imageString) |
| 251 | { | 250 | { |
| 252 | - $pages = $this->page->where('html', 'like', '%' . $imageString . '%')->get(); | 251 | + $pages = $this->pageQuery()->where('html', 'like', '%' . $imageString . '%')->get(); |
| 253 | foreach ($pages as $page) { | 252 | foreach ($pages as $page) { |
| 254 | $page->url = $page->getUrl(); | 253 | $page->url = $page->getUrl(); |
| 255 | $page->html = ''; | 254 | $page->html = ''; |
| ... | @@ -395,6 +394,7 @@ class PageRepo | ... | @@ -395,6 +394,7 @@ class PageRepo |
| 395 | Activity::removeEntity($page); | 394 | Activity::removeEntity($page); |
| 396 | $page->views()->delete(); | 395 | $page->views()->delete(); |
| 397 | $page->revisions()->delete(); | 396 | $page->revisions()->delete(); |
| 397 | + $page->restrictions()->delete(); | ||
| 398 | $page->delete(); | 398 | $page->delete(); |
| 399 | } | 399 | } |
| 400 | 400 | ||
| ... | @@ -404,7 +404,7 @@ class PageRepo | ... | @@ -404,7 +404,7 @@ class PageRepo |
| 404 | */ | 404 | */ |
| 405 | public function getRecentlyCreatedPaginated($count = 20) | 405 | public function getRecentlyCreatedPaginated($count = 20) |
| 406 | { | 406 | { |
| 407 | - return $this->page->orderBy('created_at', 'desc')->paginate($count); | 407 | + return $this->pageQuery()->orderBy('created_at', 'desc')->paginate($count); |
| 408 | } | 408 | } |
| 409 | 409 | ||
| 410 | /** | 410 | /** |
| ... | @@ -413,7 +413,30 @@ class PageRepo | ... | @@ -413,7 +413,30 @@ class PageRepo |
| 413 | */ | 413 | */ |
| 414 | public function getRecentlyUpdatedPaginated($count = 20) | 414 | public function getRecentlyUpdatedPaginated($count = 20) |
| 415 | { | 415 | { |
| 416 | - return $this->page->orderBy('updated_at', 'desc')->paginate($count); | 416 | + return $this->pageQuery()->orderBy('updated_at', 'desc')->paginate($count); |
| 417 | + } | ||
| 418 | + | ||
| 419 | + /** | ||
| 420 | + * Updates pages restrictions from a request | ||
| 421 | + * @param $request | ||
| 422 | + * @param $page | ||
| 423 | + */ | ||
| 424 | + public function updateRestrictionsFromRequest($request, $page) | ||
| 425 | + { | ||
| 426 | + // TODO - extract into shared repo | ||
| 427 | + $page->restricted = $request->has('restricted') && $request->get('restricted') === 'true'; | ||
| 428 | + $page->restrictions()->delete(); | ||
| 429 | + if ($request->has('restrictions')) { | ||
| 430 | + foreach($request->get('restrictions') as $roleId => $restrictions) { | ||
| 431 | + foreach ($restrictions as $action => $value) { | ||
| 432 | + $page->restrictions()->create([ | ||
| 433 | + 'role_id' => $roleId, | ||
| 434 | + 'action' => strtolower($action) | ||
| 435 | + ]); | ||
| 436 | + } | ||
| 437 | + } | ||
| 438 | + } | ||
| 439 | + $page->save(); | ||
| 417 | } | 440 | } |
| 418 | 441 | ||
| 419 | } | 442 | } | ... | ... |
app/Repos/PageRepo.php.orig
0 → 100644
This diff is collapsed.
Click to expand it.
app/Repos/PermissionsRepo.php
0 → 100644
| 1 | +<?php namespace BookStack\Repos; | ||
| 2 | + | ||
| 3 | + | ||
| 4 | +use BookStack\Exceptions\PermissionsException; | ||
| 5 | +use BookStack\Permission; | ||
| 6 | +use BookStack\Role; | ||
| 7 | +use Setting; | ||
| 8 | + | ||
| 9 | +class PermissionsRepo | ||
| 10 | +{ | ||
| 11 | + | ||
| 12 | + protected $permission; | ||
| 13 | + protected $role; | ||
| 14 | + | ||
| 15 | + /** | ||
| 16 | + * PermissionsRepo constructor. | ||
| 17 | + * @param $permission | ||
| 18 | + * @param $role | ||
| 19 | + */ | ||
| 20 | + public function __construct(Permission $permission, Role $role) | ||
| 21 | + { | ||
| 22 | + $this->permission = $permission; | ||
| 23 | + $this->role = $role; | ||
| 24 | + } | ||
| 25 | + | ||
| 26 | + /** | ||
| 27 | + * Get all the user roles from the system. | ||
| 28 | + * @return \Illuminate\Database\Eloquent\Collection|static[] | ||
| 29 | + */ | ||
| 30 | + public function getAllRoles() | ||
| 31 | + { | ||
| 32 | + return $this->role->all(); | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + /** | ||
| 36 | + * Get all the roles except for the provided one. | ||
| 37 | + * @param Role $role | ||
| 38 | + * @return mixed | ||
| 39 | + */ | ||
| 40 | + public function getAllRolesExcept(Role $role) | ||
| 41 | + { | ||
| 42 | + return $this->role->where('id', '!=', $role->id)->get(); | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + /** | ||
| 46 | + * Get a role via its ID. | ||
| 47 | + * @param $id | ||
| 48 | + * @return mixed | ||
| 49 | + */ | ||
| 50 | + public function getRoleById($id) | ||
| 51 | + { | ||
| 52 | + return $this->role->findOrFail($id); | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + /** | ||
| 56 | + * Save a new role into the system. | ||
| 57 | + * @param array $roleData | ||
| 58 | + * @return Role | ||
| 59 | + */ | ||
| 60 | + public function saveNewRole($roleData) | ||
| 61 | + { | ||
| 62 | + $role = $this->role->newInstance($roleData); | ||
| 63 | + $role->name = str_replace(' ', '-', strtolower($roleData['display_name'])); | ||
| 64 | + // Prevent duplicate names | ||
| 65 | + while ($this->role->where('name', '=', $role->name)->count() > 0) { | ||
| 66 | + $role->name .= strtolower(str_random(2)); | ||
| 67 | + } | ||
| 68 | + $role->save(); | ||
| 69 | + | ||
| 70 | + $permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : []; | ||
| 71 | + $this->assignRolePermissions($role, $permissions); | ||
| 72 | + return $role; | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | + /** | ||
| 76 | + * Updates an existing role. | ||
| 77 | + * Ensure Admin role always has all permissions. | ||
| 78 | + * @param $roleId | ||
| 79 | + * @param $roleData | ||
| 80 | + */ | ||
| 81 | + public function updateRole($roleId, $roleData) | ||
| 82 | + { | ||
| 83 | + $role = $this->role->findOrFail($roleId); | ||
| 84 | + $permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : []; | ||
| 85 | + $this->assignRolePermissions($role, $permissions); | ||
| 86 | + | ||
| 87 | + if ($role->name === 'admin') { | ||
| 88 | + $permissions = $this->permission->all()->pluck('id')->toArray(); | ||
| 89 | + $role->permissions()->sync($permissions); | ||
| 90 | + } | ||
| 91 | + | ||
| 92 | + $role->fill($roleData); | ||
| 93 | + $role->save(); | ||
| 94 | + } | ||
| 95 | + | ||
| 96 | + /** | ||
| 97 | + * Assign an list of permission names to an role. | ||
| 98 | + * @param Role $role | ||
| 99 | + * @param array $permissionNameArray | ||
| 100 | + */ | ||
| 101 | + public function assignRolePermissions(Role $role, $permissionNameArray = []) | ||
| 102 | + { | ||
| 103 | + $permissions = []; | ||
| 104 | + $permissionNameArray = array_values($permissionNameArray); | ||
| 105 | + if ($permissionNameArray && count($permissionNameArray) > 0) { | ||
| 106 | + $permissions = $this->permission->whereIn('name', $permissionNameArray)->pluck('id')->toArray(); | ||
| 107 | + } | ||
| 108 | + $role->permissions()->sync($permissions); | ||
| 109 | + } | ||
| 110 | + | ||
| 111 | + /** | ||
| 112 | + * Delete a role from the system. | ||
| 113 | + * Check it's not an admin role or set as default before deleting. | ||
| 114 | + * If an migration Role ID is specified the users assign to the current role | ||
| 115 | + * will be added to the role of the specified id. | ||
| 116 | + * @param $roleId | ||
| 117 | + * @param $migrateRoleId | ||
| 118 | + * @throws PermissionsException | ||
| 119 | + */ | ||
| 120 | + public function deleteRole($roleId, $migrateRoleId) | ||
| 121 | + { | ||
| 122 | + $role = $this->role->findOrFail($roleId); | ||
| 123 | + | ||
| 124 | + // Prevent deleting admin role or default registration role. | ||
| 125 | + if ($role->name === 'admin') { | ||
| 126 | + throw new PermissionsException('The admin role cannot be deleted'); | ||
| 127 | + } else if ($role->id == Setting::get('registration-role')) { | ||
| 128 | + throw new PermissionsException('This role cannot be deleted while set as the default registration role.'); | ||
| 129 | + } | ||
| 130 | + | ||
| 131 | + if ($migrateRoleId) { | ||
| 132 | + $newRole = $this->role->find($migrateRoleId); | ||
| 133 | + if ($newRole) { | ||
| 134 | + $users = $role->users->pluck('id')->toArray(); | ||
| 135 | + $newRole->users()->sync($users); | ||
| 136 | + } | ||
| 137 | + } | ||
| 138 | + | ||
| 139 | + $role->delete(); | ||
| 140 | + } | ||
| 141 | + | ||
| 142 | +} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| ... | @@ -43,6 +43,15 @@ class UserRepo | ... | @@ -43,6 +43,15 @@ class UserRepo |
| 43 | } | 43 | } |
| 44 | 44 | ||
| 45 | /** | 45 | /** |
| 46 | + * Get all the users with their permissions. | ||
| 47 | + * @return \Illuminate\Database\Eloquent\Builder|static | ||
| 48 | + */ | ||
| 49 | + public function getAllUsers() | ||
| 50 | + { | ||
| 51 | + return $this->user->with('roles', 'avatar')->orderBy('name', 'asc')->get(); | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + /** | ||
| 46 | * Creates a new user and attaches a role to them. | 55 | * Creates a new user and attaches a role to them. |
| 47 | * @param array $data | 56 | * @param array $data |
| 48 | * @return User | 57 | * @return User |
| ... | @@ -69,7 +78,7 @@ class UserRepo | ... | @@ -69,7 +78,7 @@ class UserRepo |
| 69 | public function attachDefaultRole($user) | 78 | public function attachDefaultRole($user) |
| 70 | { | 79 | { |
| 71 | $roleId = Setting::get('registration-role'); | 80 | $roleId = Setting::get('registration-role'); |
| 72 | - if ($roleId === false) $roleId = $this->role->getDefault()->id; | 81 | + if ($roleId === false) $roleId = $this->role->first()->id; |
| 73 | $user->attachRoleId($roleId); | 82 | $user->attachRoleId($roleId); |
| 74 | } | 83 | } |
| 75 | 84 | ||
| ... | @@ -80,15 +89,10 @@ class UserRepo | ... | @@ -80,15 +89,10 @@ class UserRepo |
| 80 | */ | 89 | */ |
| 81 | public function isOnlyAdmin(User $user) | 90 | public function isOnlyAdmin(User $user) |
| 82 | { | 91 | { |
| 83 | - if ($user->role->name != 'admin') { | 92 | + if (!$user->roles->pluck('name')->contains('admin')) return false; |
| 84 | - return false; | ||
| 85 | - } | ||
| 86 | - | ||
| 87 | - $adminRole = $this->role->where('name', '=', 'admin')->first(); | ||
| 88 | - if (count($adminRole->users) > 1) { | ||
| 89 | - return false; | ||
| 90 | - } | ||
| 91 | 93 | ||
| 94 | + $adminRole = $this->role->getRole('admin'); | ||
| 95 | + if ($adminRole->users->count() > 1) return false; | ||
| 92 | return true; | 96 | return true; |
| 93 | } | 97 | } |
| 94 | 98 | ||
| ... | @@ -160,4 +164,14 @@ class UserRepo | ... | @@ -160,4 +164,14 @@ class UserRepo |
| 160 | ]; | 164 | ]; |
| 161 | } | 165 | } |
| 162 | 166 | ||
| 167 | + /** | ||
| 168 | + * Get all the roles which can be given restricted access to | ||
| 169 | + * other entities in the system. | ||
| 170 | + * @return mixed | ||
| 171 | + */ | ||
| 172 | + public function getRestrictableRoles() | ||
| 173 | + { | ||
| 174 | + return $this->role->where('name', '!=', 'admin')->get(); | ||
| 175 | + } | ||
| 176 | + | ||
| 163 | } | 177 | } |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
app/Restriction.php
0 → 100644
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace BookStack; | ||
| 4 | + | ||
| 5 | +use Illuminate\Database\Eloquent\Model; | ||
| 6 | + | ||
| 7 | +class Restriction extends Model | ||
| 8 | +{ | ||
| 9 | + | ||
| 10 | + protected $fillable = ['role_id', 'action']; | ||
| 11 | + public $timestamps = false; | ||
| 12 | + | ||
| 13 | + /** | ||
| 14 | + * Get all this restriction's attached entity. | ||
| 15 | + * @return \Illuminate\Database\Eloquent\Relations\MorphTo | ||
| 16 | + */ | ||
| 17 | + public function restrictable() | ||
| 18 | + { | ||
| 19 | + return $this->morphTo(); | ||
| 20 | + } | ||
| 21 | +} |
| ... | @@ -6,11 +6,8 @@ use Illuminate\Database\Eloquent\Model; | ... | @@ -6,11 +6,8 @@ use Illuminate\Database\Eloquent\Model; |
| 6 | 6 | ||
| 7 | class Role extends Model | 7 | class Role extends Model |
| 8 | { | 8 | { |
| 9 | - /** | 9 | + |
| 10 | - * Sets the default role name for newly registered users. | 10 | + protected $fillable = ['display_name', 'description']; |
| 11 | - * @var string | ||
| 12 | - */ | ||
| 13 | - protected static $default = 'viewer'; | ||
| 14 | 11 | ||
| 15 | /** | 12 | /** |
| 16 | * The roles that belong to the role. | 13 | * The roles that belong to the role. |
| ... | @@ -29,21 +26,21 @@ class Role extends Model | ... | @@ -29,21 +26,21 @@ class Role extends Model |
| 29 | } | 26 | } |
| 30 | 27 | ||
| 31 | /** | 28 | /** |
| 32 | - * Add a permission to this role. | 29 | + * Check if this role has a permission. |
| 33 | - * @param Permission $permission | 30 | + * @param $permission |
| 34 | */ | 31 | */ |
| 35 | - public function attachPermission(Permission $permission) | 32 | + public function hasPermission($permission) |
| 36 | { | 33 | { |
| 37 | - $this->permissions()->attach($permission->id); | 34 | + return $this->permissions->pluck('name')->contains($permission); |
| 38 | } | 35 | } |
| 39 | 36 | ||
| 40 | /** | 37 | /** |
| 41 | - * Get an instance of the default role. | 38 | + * Add a permission to this role. |
| 42 | - * @return Role | 39 | + * @param Permission $permission |
| 43 | */ | 40 | */ |
| 44 | - public static function getDefault() | 41 | + public function attachPermission(Permission $permission) |
| 45 | { | 42 | { |
| 46 | - return static::getRole(static::$default); | 43 | + $this->permissions()->attach($permission->id); |
| 47 | } | 44 | } |
| 48 | 45 | ||
| 49 | /** | 46 | /** | ... | ... |
| ... | @@ -9,14 +9,17 @@ class ActivityService | ... | @@ -9,14 +9,17 @@ class ActivityService |
| 9 | { | 9 | { |
| 10 | protected $activity; | 10 | protected $activity; |
| 11 | protected $user; | 11 | protected $user; |
| 12 | + protected $restrictionService; | ||
| 12 | 13 | ||
| 13 | /** | 14 | /** |
| 14 | * ActivityService constructor. | 15 | * ActivityService constructor. |
| 15 | - * @param $activity | 16 | + * @param Activity $activity |
| 17 | + * @param RestrictionService $restrictionService | ||
| 16 | */ | 18 | */ |
| 17 | - public function __construct(Activity $activity) | 19 | + public function __construct(Activity $activity, RestrictionService $restrictionService) |
| 18 | { | 20 | { |
| 19 | $this->activity = $activity; | 21 | $this->activity = $activity; |
| 22 | + $this->restrictionService = $restrictionService; | ||
| 20 | $this->user = auth()->user(); | 23 | $this->user = auth()->user(); |
| 21 | } | 24 | } |
| 22 | 25 | ||
| ... | @@ -86,8 +89,10 @@ class ActivityService | ... | @@ -86,8 +89,10 @@ class ActivityService |
| 86 | */ | 89 | */ |
| 87 | public function latest($count = 20, $page = 0) | 90 | public function latest($count = 20, $page = 0) |
| 88 | { | 91 | { |
| 89 | - $activityList = $this->activity->orderBy('created_at', 'desc') | 92 | + $activityList = $this->restrictionService |
| 90 | - ->skip($count * $page)->take($count)->get(); | 93 | + ->filterRestrictedEntityRelations($this->activity, 'activities', 'entity_id', 'entity_type') |
| 94 | + ->orderBy('created_at', 'desc')->skip($count * $page)->take($count)->get(); | ||
| 95 | + | ||
| 91 | return $this->filterSimilar($activityList); | 96 | return $this->filterSimilar($activityList); |
| 92 | } | 97 | } |
| 93 | 98 | ... | ... |
app/Services/RestrictionService.php
0 → 100644
This diff is collapsed.
Click to expand it.
| ... | @@ -9,15 +9,18 @@ class ViewService | ... | @@ -9,15 +9,18 @@ class ViewService |
| 9 | 9 | ||
| 10 | protected $view; | 10 | protected $view; |
| 11 | protected $user; | 11 | protected $user; |
| 12 | + protected $restrictionService; | ||
| 12 | 13 | ||
| 13 | /** | 14 | /** |
| 14 | * ViewService constructor. | 15 | * ViewService constructor. |
| 15 | - * @param $view | 16 | + * @param View $view |
| 17 | + * @param RestrictionService $restrictionService | ||
| 16 | */ | 18 | */ |
| 17 | - public function __construct(View $view) | 19 | + public function __construct(View $view, RestrictionService $restrictionService) |
| 18 | { | 20 | { |
| 19 | $this->view = $view; | 21 | $this->view = $view; |
| 20 | $this->user = auth()->user(); | 22 | $this->user = auth()->user(); |
| 23 | + $this->restrictionService = $restrictionService; | ||
| 21 | } | 24 | } |
| 22 | 25 | ||
| 23 | /** | 26 | /** |
| ... | @@ -27,7 +30,7 @@ class ViewService | ... | @@ -27,7 +30,7 @@ class ViewService |
| 27 | */ | 30 | */ |
| 28 | public function add(Entity $entity) | 31 | public function add(Entity $entity) |
| 29 | { | 32 | { |
| 30 | - if($this->user === null) return 0; | 33 | + if ($this->user === null) return 0; |
| 31 | $view = $entity->views()->where('user_id', '=', $this->user->id)->first(); | 34 | $view = $entity->views()->where('user_id', '=', $this->user->id)->first(); |
| 32 | // Add view if model exists | 35 | // Add view if model exists |
| 33 | if ($view) { | 36 | if ($view) { |
| ... | @@ -47,18 +50,19 @@ class ViewService | ... | @@ -47,18 +50,19 @@ class ViewService |
| 47 | 50 | ||
| 48 | /** | 51 | /** |
| 49 | * Get the entities with the most views. | 52 | * Get the entities with the most views. |
| 50 | - * @param int $count | 53 | + * @param int $count |
| 51 | - * @param int $page | 54 | + * @param int $page |
| 52 | * @param bool|false $filterModel | 55 | * @param bool|false $filterModel |
| 53 | */ | 56 | */ |
| 54 | public function getPopular($count = 10, $page = 0, $filterModel = false) | 57 | public function getPopular($count = 10, $page = 0, $filterModel = false) |
| 55 | { | 58 | { |
| 56 | $skipCount = $count * $page; | 59 | $skipCount = $count * $page; |
| 57 | - $query = $this->view->select('id', 'viewable_id', 'viewable_type', \DB::raw('SUM(views) as view_count')) | 60 | + $query = $this->restrictionService->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type') |
| 61 | + ->select('id', 'viewable_id', 'viewable_type', \DB::raw('SUM(views) as view_count')) | ||
| 58 | ->groupBy('viewable_id', 'viewable_type') | 62 | ->groupBy('viewable_id', 'viewable_type') |
| 59 | ->orderBy('view_count', 'desc'); | 63 | ->orderBy('view_count', 'desc'); |
| 60 | 64 | ||
| 61 | - if($filterModel) $query->where('viewable_type', '=', get_class($filterModel)); | 65 | + if ($filterModel) $query->where('viewable_type', '=', get_class($filterModel)); |
| 62 | 66 | ||
| 63 | $views = $query->with('viewable')->skip($skipCount)->take($count)->get(); | 67 | $views = $query->with('viewable')->skip($skipCount)->take($count)->get(); |
| 64 | $viewedEntities = $views->map(function ($item) { | 68 | $viewedEntities = $views->map(function ($item) { |
| ... | @@ -69,22 +73,24 @@ class ViewService | ... | @@ -69,22 +73,24 @@ class ViewService |
| 69 | 73 | ||
| 70 | /** | 74 | /** |
| 71 | * Get all recently viewed entities for the current user. | 75 | * Get all recently viewed entities for the current user. |
| 72 | - * @param int $count | 76 | + * @param int $count |
| 73 | - * @param int $page | 77 | + * @param int $page |
| 74 | * @param Entity|bool $filterModel | 78 | * @param Entity|bool $filterModel |
| 75 | * @return mixed | 79 | * @return mixed |
| 76 | */ | 80 | */ |
| 77 | public function getUserRecentlyViewed($count = 10, $page = 0, $filterModel = false) | 81 | public function getUserRecentlyViewed($count = 10, $page = 0, $filterModel = false) |
| 78 | { | 82 | { |
| 79 | - if($this->user === null) return collect(); | 83 | + if ($this->user === null) return collect(); |
| 80 | $skipCount = $count * $page; | 84 | $skipCount = $count * $page; |
| 81 | - $query = $this->view->where('user_id', '=', auth()->user()->id); | 85 | + $query = $this->restrictionService |
| 86 | + ->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type'); | ||
| 82 | 87 | ||
| 83 | - if ($filterModel) $query->where('viewable_type', '=', get_class($filterModel)); | 88 | + if ($filterModel) $query = $query->where('viewable_type', '=', get_class($filterModel)); |
| 89 | + $query = $query->where('user_id', '=', auth()->user()->id); | ||
| 84 | 90 | ||
| 85 | $views = $query->with('viewable')->orderBy('updated_at', 'desc')->skip($skipCount)->take($count)->get(); | 91 | $views = $query->with('viewable')->orderBy('updated_at', 'desc')->skip($skipCount)->take($count)->get(); |
| 86 | $viewedEntities = $views->map(function ($item) { | 92 | $viewedEntities = $views->map(function ($item) { |
| 87 | - return $item->viewable()->getResults(); | 93 | + return $item->viewable; |
| 88 | }); | 94 | }); |
| 89 | return $viewedEntities; | 95 | return $viewedEntities; |
| 90 | } | 96 | } | ... | ... |
| ... | @@ -14,21 +14,18 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon | ... | @@ -14,21 +14,18 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon |
| 14 | 14 | ||
| 15 | /** | 15 | /** |
| 16 | * The database table used by the model. | 16 | * The database table used by the model. |
| 17 | - * | ||
| 18 | * @var string | 17 | * @var string |
| 19 | */ | 18 | */ |
| 20 | protected $table = 'users'; | 19 | protected $table = 'users'; |
| 21 | 20 | ||
| 22 | /** | 21 | /** |
| 23 | * The attributes that are mass assignable. | 22 | * The attributes that are mass assignable. |
| 24 | - * | ||
| 25 | * @var array | 23 | * @var array |
| 26 | */ | 24 | */ |
| 27 | protected $fillable = ['name', 'email', 'image_id']; | 25 | protected $fillable = ['name', 'email', 'image_id']; |
| 28 | 26 | ||
| 29 | /** | 27 | /** |
| 30 | * The attributes excluded from the model's JSON form. | 28 | * The attributes excluded from the model's JSON form. |
| 31 | - * | ||
| 32 | * @var array | 29 | * @var array |
| 33 | */ | 30 | */ |
| 34 | protected $hidden = ['password', 'remember_token']; | 31 | protected $hidden = ['password', 'remember_token']; |
| ... | @@ -51,10 +48,6 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon | ... | @@ -51,10 +48,6 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon |
| 51 | } | 48 | } |
| 52 | 49 | ||
| 53 | /** | 50 | /** |
| 54 | - * Permissions and roles | ||
| 55 | - */ | ||
| 56 | - | ||
| 57 | - /** | ||
| 58 | * The roles that belong to the user. | 51 | * The roles that belong to the user. |
| 59 | */ | 52 | */ |
| 60 | public function roles() | 53 | public function roles() |
| ... | @@ -62,21 +55,30 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon | ... | @@ -62,21 +55,30 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon |
| 62 | return $this->belongsToMany('BookStack\Role'); | 55 | return $this->belongsToMany('BookStack\Role'); |
| 63 | } | 56 | } |
| 64 | 57 | ||
| 65 | - public function getRoleAttribute() | 58 | + /** |
| 59 | + * Check if the user has a role. | ||
| 60 | + * @param $role | ||
| 61 | + * @return mixed | ||
| 62 | + */ | ||
| 63 | + public function hasRole($role) | ||
| 66 | { | 64 | { |
| 67 | - return $this->roles()->with('permissions')->first(); | 65 | + return $this->roles->pluck('name')->contains($role); |
| 68 | } | 66 | } |
| 69 | 67 | ||
| 70 | /** | 68 | /** |
| 71 | - * Loads the user's permissions from their role. | 69 | + * Get all permissions belonging to a the current user. |
| 70 | + * @param bool $cache | ||
| 71 | + * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough | ||
| 72 | */ | 72 | */ |
| 73 | - private function loadPermissions() | 73 | + public function permissions($cache = true) |
| 74 | { | 74 | { |
| 75 | - if (isset($this->permissions)) return; | 75 | + if(isset($this->permissions) && $cache) return $this->permissions; |
| 76 | $this->load('roles.permissions'); | 76 | $this->load('roles.permissions'); |
| 77 | - $permissions = $this->roles[0]->permissions; | 77 | + $permissions = $this->roles->map(function($role) { |
| 78 | - $permissionsArray = $permissions->pluck('name')->all(); | 78 | + return $role->permissions; |
| 79 | - $this->permissions = $permissionsArray; | 79 | + })->flatten()->unique(); |
| 80 | + $this->permissions = $permissions; | ||
| 81 | + return $permissions; | ||
| 80 | } | 82 | } |
| 81 | 83 | ||
| 82 | /** | 84 | /** |
| ... | @@ -86,11 +88,8 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon | ... | @@ -86,11 +88,8 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon |
| 86 | */ | 88 | */ |
| 87 | public function can($permissionName) | 89 | public function can($permissionName) |
| 88 | { | 90 | { |
| 89 | - if ($this->email == 'guest') { | 91 | + if ($this->email === 'guest') return false; |
| 90 | - return false; | 92 | + return $this->permissions()->pluck('name')->contains($permissionName); |
| 91 | - } | ||
| 92 | - $this->loadPermissions(); | ||
| 93 | - return array_search($permissionName, $this->permissions) !== false; | ||
| 94 | } | 93 | } |
| 95 | 94 | ||
| 96 | /** | 95 | /** |
| ... | @@ -108,12 +107,11 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon | ... | @@ -108,12 +107,11 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon |
| 108 | */ | 107 | */ |
| 109 | public function attachRoleId($id) | 108 | public function attachRoleId($id) |
| 110 | { | 109 | { |
| 111 | - $this->roles()->sync([$id]); | 110 | + $this->roles()->attach($id); |
| 112 | } | 111 | } |
| 113 | 112 | ||
| 114 | /** | 113 | /** |
| 115 | * Get the social account associated with this user. | 114 | * Get the social account associated with this user. |
| 116 | - * | ||
| 117 | * @return \Illuminate\Database\Eloquent\Relations\HasMany | 115 | * @return \Illuminate\Database\Eloquent\Relations\HasMany |
| 118 | */ | 116 | */ |
| 119 | public function socialAccounts() | 117 | public function socialAccounts() |
| ... | @@ -138,8 +136,6 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon | ... | @@ -138,8 +136,6 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon |
| 138 | 136 | ||
| 139 | /** | 137 | /** |
| 140 | * Returns the user's avatar, | 138 | * Returns the user's avatar, |
| 141 | - * Uses Gravatar as the avatar service. | ||
| 142 | - * | ||
| 143 | * @param int $size | 139 | * @param int $size |
| 144 | * @return string | 140 | * @return string |
| 145 | */ | 141 | */ | ... | ... |
| 1 | <?php | 1 | <?php |
| 2 | 2 | ||
| 3 | -if (! function_exists('versioned_asset')) { | 3 | +if (!function_exists('versioned_asset')) { |
| 4 | /** | 4 | /** |
| 5 | * Get the path to a versioned file. | 5 | * Get the path to a versioned file. |
| 6 | * | 6 | * |
| 7 | - * @param string $file | 7 | + * @param string $file |
| 8 | * @return string | 8 | * @return string |
| 9 | * | 9 | * |
| 10 | * @throws \InvalidArgumentException | 10 | * @throws \InvalidArgumentException |
| ... | @@ -27,4 +27,35 @@ if (! function_exists('versioned_asset')) { | ... | @@ -27,4 +27,35 @@ if (! function_exists('versioned_asset')) { |
| 27 | 27 | ||
| 28 | throw new InvalidArgumentException("File {$file} not defined in asset manifest."); | 28 | throw new InvalidArgumentException("File {$file} not defined in asset manifest."); |
| 29 | } | 29 | } |
| 30 | +} | ||
| 31 | + | ||
| 32 | +/** | ||
| 33 | + * Check if the current user has a permission. | ||
| 34 | + * If an ownable element is passed in the permissions are checked against | ||
| 35 | + * that particular item. | ||
| 36 | + * @param $permission | ||
| 37 | + * @param \BookStack\Ownable $ownable | ||
| 38 | + * @return mixed | ||
| 39 | + */ | ||
| 40 | +function userCan($permission, \BookStack\Ownable $ownable = null) | ||
| 41 | +{ | ||
| 42 | + if (!auth()->check()) return false; | ||
| 43 | + if ($ownable === null) { | ||
| 44 | + return auth()->user() && auth()->user()->can($permission); | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + // Check permission on ownable item | ||
| 48 | + $permissionBaseName = strtolower($permission) . '-'; | ||
| 49 | + $hasPermission = false; | ||
| 50 | + if (auth()->user()->can($permissionBaseName . 'all')) $hasPermission = true; | ||
| 51 | + if (auth()->user()->can($permissionBaseName . 'own') && $ownable->createdBy && $ownable->createdBy->id === auth()->user()->id) $hasPermission = true; | ||
| 52 | + | ||
| 53 | + if (!$ownable instanceof \BookStack\Entity) return $hasPermission; | ||
| 54 | + | ||
| 55 | + // Check restrictions on the entitiy | ||
| 56 | + $restrictionService = app('BookStack\Services\RestrictionService'); | ||
| 57 | + $explodedPermission = explode('-', $permission); | ||
| 58 | + $action = end($explodedPermission); | ||
| 59 | + $hasAccess = $restrictionService->checkIfEntityRestricted($ownable, $action); | ||
| 60 | + return $hasAccess && $hasPermission; | ||
| 30 | } | 61 | } |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| ... | @@ -17,6 +17,7 @@ $factory->define(BookStack\User::class, function ($faker) { | ... | @@ -17,6 +17,7 @@ $factory->define(BookStack\User::class, function ($faker) { |
| 17 | 'email' => $faker->email, | 17 | 'email' => $faker->email, |
| 18 | 'password' => str_random(10), | 18 | 'password' => str_random(10), |
| 19 | 'remember_token' => str_random(10), | 19 | 'remember_token' => str_random(10), |
| 20 | + 'email_confirmed' => 1 | ||
| 20 | ]; | 21 | ]; |
| 21 | }); | 22 | }); |
| 22 | 23 | ||
| ... | @@ -45,3 +46,10 @@ $factory->define(BookStack\Page::class, function ($faker) { | ... | @@ -45,3 +46,10 @@ $factory->define(BookStack\Page::class, function ($faker) { |
| 45 | 'text' => strip_tags($html) | 46 | 'text' => strip_tags($html) |
| 46 | ]; | 47 | ]; |
| 47 | }); | 48 | }); |
| 49 | + | ||
| 50 | +$factory->define(BookStack\Role::class, function ($faker) { | ||
| 51 | + return [ | ||
| 52 | + 'display_name' => $faker->sentence(3), | ||
| 53 | + 'description' => $faker->sentence(10) | ||
| 54 | + ]; | ||
| 55 | +}); | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +use Illuminate\Database\Schema\Blueprint; | ||
| 4 | +use Illuminate\Database\Migrations\Migration; | ||
| 5 | + | ||
| 6 | +class UpdatePermissionsAndRoles extends Migration | ||
| 7 | +{ | ||
| 8 | + /** | ||
| 9 | + * Run the migrations. | ||
| 10 | + * | ||
| 11 | + * @return void | ||
| 12 | + */ | ||
| 13 | + public function up() | ||
| 14 | + { | ||
| 15 | + // Get roles with permissions we need to change | ||
| 16 | + $adminRole = \BookStack\Role::getRole('admin'); | ||
| 17 | + $editorRole = \BookStack\Role::getRole('editor'); | ||
| 18 | + | ||
| 19 | + // Delete old permissions | ||
| 20 | + $permissions = \BookStack\Permission::all(); | ||
| 21 | + $permissions->each(function ($permission) { | ||
| 22 | + $permission->delete(); | ||
| 23 | + }); | ||
| 24 | + | ||
| 25 | + // Create & attach new admin permissions | ||
| 26 | + $permissionsToCreate = [ | ||
| 27 | + 'settings-manage' => 'Manage Settings', | ||
| 28 | + 'users-manage' => 'Manage Users', | ||
| 29 | + 'user-roles-manage' => 'Manage Roles & Permissions', | ||
| 30 | + 'restrictions-manage-all' => 'Manage All Entity Restrictions', | ||
| 31 | + 'restrictions-manage-own' => 'Manage Entity Restrictions On Own Content' | ||
| 32 | + ]; | ||
| 33 | + foreach ($permissionsToCreate as $name => $displayName) { | ||
| 34 | + $newPermission = new \BookStack\Permission(); | ||
| 35 | + $newPermission->name = $name; | ||
| 36 | + $newPermission->display_name = $displayName; | ||
| 37 | + $newPermission->save(); | ||
| 38 | + $adminRole->attachPermission($newPermission); | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + // Create & attach new entity permissions | ||
| 42 | + $entities = ['Book', 'Page', 'Chapter', 'Image']; | ||
| 43 | + $ops = ['Create All', 'Create Own', 'Update All', 'Update Own', 'Delete All', 'Delete Own']; | ||
| 44 | + foreach ($entities as $entity) { | ||
| 45 | + foreach ($ops as $op) { | ||
| 46 | + $newPermission = new \BookStack\Permission(); | ||
| 47 | + $newPermission->name = strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op)); | ||
| 48 | + $newPermission->display_name = $op . ' ' . $entity . 's'; | ||
| 49 | + $newPermission->save(); | ||
| 50 | + $adminRole->attachPermission($newPermission); | ||
| 51 | + if ($editorRole !== null) $editorRole->attachPermission($newPermission); | ||
| 52 | + } | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + /** | ||
| 58 | + * Reverse the migrations. | ||
| 59 | + * | ||
| 60 | + * @return void | ||
| 61 | + */ | ||
| 62 | + public function down() | ||
| 63 | + { | ||
| 64 | + // Get roles with permissions we need to change | ||
| 65 | + $adminRole = \BookStack\Role::getRole('admin'); | ||
| 66 | + | ||
| 67 | + // Delete old permissions | ||
| 68 | + $permissions = \BookStack\Permission::all(); | ||
| 69 | + $permissions->each(function ($permission) { | ||
| 70 | + $permission->delete(); | ||
| 71 | + }); | ||
| 72 | + | ||
| 73 | + // Create default CRUD permissions and allocate to admins and editors | ||
| 74 | + $entities = ['Book', 'Page', 'Chapter', 'Image']; | ||
| 75 | + $ops = ['Create', 'Update', 'Delete']; | ||
| 76 | + foreach ($entities as $entity) { | ||
| 77 | + foreach ($ops as $op) { | ||
| 78 | + $newPermission = new \BookStack\Permission(); | ||
| 79 | + $newPermission->name = strtolower($entity) . '-' . strtolower($op); | ||
| 80 | + $newPermission->display_name = $op . ' ' . $entity . 's'; | ||
| 81 | + $newPermission->save(); | ||
| 82 | + $adminRole->attachPermission($newPermission); | ||
| 83 | + } | ||
| 84 | + } | ||
| 85 | + | ||
| 86 | + // Create admin permissions | ||
| 87 | + $entities = ['Settings', 'User']; | ||
| 88 | + $ops = ['Create', 'Update', 'Delete']; | ||
| 89 | + foreach ($entities as $entity) { | ||
| 90 | + foreach ($ops as $op) { | ||
| 91 | + $newPermission = new \BookStack\Permission(); | ||
| 92 | + $newPermission->name = strtolower($entity) . '-' . strtolower($op); | ||
| 93 | + $newPermission->display_name = $op . ' ' . $entity; | ||
| 94 | + $newPermission->save(); | ||
| 95 | + $adminRole->attachPermission($newPermission); | ||
| 96 | + } | ||
| 97 | + } | ||
| 98 | + } | ||
| 99 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +use Illuminate\Database\Schema\Blueprint; | ||
| 4 | +use Illuminate\Database\Migrations\Migration; | ||
| 5 | + | ||
| 6 | +class AddEntityAccessControls extends Migration | ||
| 7 | +{ | ||
| 8 | + /** | ||
| 9 | + * Run the migrations. | ||
| 10 | + * | ||
| 11 | + * @return void | ||
| 12 | + */ | ||
| 13 | + public function up() | ||
| 14 | + { | ||
| 15 | + Schema::table('images', function (Blueprint $table) { | ||
| 16 | + $table->integer('uploaded_to')->default(0); | ||
| 17 | + $table->index('uploaded_to'); | ||
| 18 | + }); | ||
| 19 | + | ||
| 20 | + Schema::table('books', function (Blueprint $table) { | ||
| 21 | + $table->boolean('restricted')->default(false); | ||
| 22 | + $table->index('restricted'); | ||
| 23 | + }); | ||
| 24 | + | ||
| 25 | + Schema::table('chapters', function (Blueprint $table) { | ||
| 26 | + $table->boolean('restricted')->default(false); | ||
| 27 | + $table->index('restricted'); | ||
| 28 | + }); | ||
| 29 | + | ||
| 30 | + Schema::table('pages', function (Blueprint $table) { | ||
| 31 | + $table->boolean('restricted')->default(false); | ||
| 32 | + $table->index('restricted'); | ||
| 33 | + }); | ||
| 34 | + | ||
| 35 | + Schema::create('restrictions', function(Blueprint $table) { | ||
| 36 | + $table->increments('id'); | ||
| 37 | + $table->integer('restrictable_id'); | ||
| 38 | + $table->string('restrictable_type'); | ||
| 39 | + $table->integer('role_id'); | ||
| 40 | + $table->string('action'); | ||
| 41 | + $table->index('role_id'); | ||
| 42 | + $table->index('action'); | ||
| 43 | + $table->index(['restrictable_id', 'restrictable_type']); | ||
| 44 | + }); | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + /** | ||
| 48 | + * Reverse the migrations. | ||
| 49 | + * | ||
| 50 | + * @return void | ||
| 51 | + */ | ||
| 52 | + public function down() | ||
| 53 | + { | ||
| 54 | + Schema::table('images', function (Blueprint $table) { | ||
| 55 | + $table->dropColumn('uploaded_to'); | ||
| 56 | + }); | ||
| 57 | + | ||
| 58 | + Schema::table('books', function (Blueprint $table) { | ||
| 59 | + $table->dropColumn('restricted'); | ||
| 60 | + }); | ||
| 61 | + | ||
| 62 | + Schema::table('chapters', function (Blueprint $table) { | ||
| 63 | + $table->dropColumn('restricted'); | ||
| 64 | + }); | ||
| 65 | + | ||
| 66 | + | ||
| 67 | + Schema::table('pages', function (Blueprint $table) { | ||
| 68 | + $table->dropColumn('restricted'); | ||
| 69 | + }); | ||
| 70 | + | ||
| 71 | + Schema::drop('restrictions'); | ||
| 72 | + } | ||
| 73 | +} |
| ... | @@ -12,7 +12,7 @@ class DummyContentSeeder extends Seeder | ... | @@ -12,7 +12,7 @@ class DummyContentSeeder extends Seeder |
| 12 | public function run() | 12 | public function run() |
| 13 | { | 13 | { |
| 14 | $user = factory(BookStack\User::class, 1)->create(); | 14 | $user = factory(BookStack\User::class, 1)->create(); |
| 15 | - $role = \BookStack\Role::getDefault(); | 15 | + $role = \BookStack\Role::getRole('editor'); |
| 16 | $user->attachRole($role); | 16 | $user->attachRole($role); |
| 17 | 17 | ||
| 18 | 18 | ... | ... |
| ... | @@ -21,6 +21,7 @@ | ... | @@ -21,6 +21,7 @@ |
| 21 | </filter> | 21 | </filter> |
| 22 | <php> | 22 | <php> |
| 23 | <env name="APP_ENV" value="testing"/> | 23 | <env name="APP_ENV" value="testing"/> |
| 24 | + <env name="APP_DEBUG" value="false"/> | ||
| 24 | <env name="CACHE_DRIVER" value="array"/> | 25 | <env name="CACHE_DRIVER" value="array"/> |
| 25 | <env name="SESSION_DRIVER" value="array"/> | 26 | <env name="SESSION_DRIVER" value="array"/> |
| 26 | <env name="QUEUE_DRIVER" value="sync"/> | 27 | <env name="QUEUE_DRIVER" value="sync"/> | ... | ... |
| ... | @@ -118,6 +118,7 @@ module.exports = function (ngApp, events) { | ... | @@ -118,6 +118,7 @@ module.exports = function (ngApp, events) { |
| 118 | page++; | 118 | page++; |
| 119 | }); | 119 | }); |
| 120 | } | 120 | } |
| 121 | + | ||
| 121 | $scope.fetchData = fetchData; | 122 | $scope.fetchData = fetchData; |
| 122 | 123 | ||
| 123 | /** | 124 | /** |
| ... | @@ -130,12 +131,16 @@ module.exports = function (ngApp, events) { | ... | @@ -130,12 +131,16 @@ module.exports = function (ngApp, events) { |
| 130 | $http.put(url, this.selectedImage).then((response) => { | 131 | $http.put(url, this.selectedImage).then((response) => { |
| 131 | events.emit('success', 'Image details updated'); | 132 | events.emit('success', 'Image details updated'); |
| 132 | }, (response) => { | 133 | }, (response) => { |
| 133 | - var errors = response.data; | 134 | + if (response.status === 422) { |
| 134 | - var message = ''; | 135 | + var errors = response.data; |
| 135 | - Object.keys(errors).forEach((key) => { | 136 | + var message = ''; |
| 136 | - message += errors[key].join('\n'); | 137 | + Object.keys(errors).forEach((key) => { |
| 137 | - }); | 138 | + message += errors[key].join('\n'); |
| 138 | - events.emit('error', message); | 139 | + }); |
| 140 | + events.emit('error', message); | ||
| 141 | + } else if (response.status === 403) { | ||
| 142 | + events.emit('error', response.data.error); | ||
| 143 | + } | ||
| 139 | }); | 144 | }); |
| 140 | }; | 145 | }; |
| 141 | 146 | ||
| ... | @@ -158,6 +163,8 @@ module.exports = function (ngApp, events) { | ... | @@ -158,6 +163,8 @@ module.exports = function (ngApp, events) { |
| 158 | // Pages failure | 163 | // Pages failure |
| 159 | if (response.status === 400) { | 164 | if (response.status === 400) { |
| 160 | $scope.dependantPages = response.data; | 165 | $scope.dependantPages = response.data; |
| 166 | + } else if (response.status === 403) { | ||
| 167 | + events.emit('error', response.data.error); | ||
| 161 | } | 168 | } |
| 162 | }); | 169 | }); |
| 163 | }; | 170 | }; |
| ... | @@ -167,7 +174,7 @@ module.exports = function (ngApp, events) { | ... | @@ -167,7 +174,7 @@ module.exports = function (ngApp, events) { |
| 167 | * @param stringDate | 174 | * @param stringDate |
| 168 | * @returns {Date} | 175 | * @returns {Date} |
| 169 | */ | 176 | */ |
| 170 | - $scope.getDate = function(stringDate) { | 177 | + $scope.getDate = function (stringDate) { |
| 171 | return new Date(stringDate); | 178 | return new Date(stringDate); |
| 172 | }; | 179 | }; |
| 173 | 180 | ... | ... |
| ... | @@ -95,13 +95,14 @@ | ... | @@ -95,13 +95,14 @@ |
| 95 | 95 | ||
| 96 | // Sidebar list | 96 | // Sidebar list |
| 97 | .book-tree { | 97 | .book-tree { |
| 98 | - padding: $-xl 0 0 0; | 98 | + padding: $-l 0 0 0; |
| 99 | position: relative; | 99 | position: relative; |
| 100 | right: 0; | 100 | right: 0; |
| 101 | top: 0; | 101 | top: 0; |
| 102 | transition: ease-in-out 240ms; | 102 | transition: ease-in-out 240ms; |
| 103 | transition-property: right, border; | 103 | transition-property: right, border; |
| 104 | border-left: 0px solid #FFF; | 104 | border-left: 0px solid #FFF; |
| 105 | + background-color: #FFF; | ||
| 105 | &.fixed { | 106 | &.fixed { |
| 106 | position: fixed; | 107 | position: fixed; |
| 107 | top: 0; | 108 | top: 0; | ... | ... |
| ... | @@ -8,4 +8,5 @@ return [ | ... | @@ -8,4 +8,5 @@ return [ |
| 8 | 8 | ||
| 9 | // Pages | 9 | // Pages |
| 10 | 'permission' => 'You do not have permission to access the requested page.', | 10 | 'permission' => 'You do not have permission to access the requested page.', |
| 11 | + 'permissionJson' => 'You do not have permission to perform the requested action.' | ||
| 11 | ]; | 12 | ]; |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| ... | @@ -43,7 +43,7 @@ | ... | @@ -43,7 +43,7 @@ |
| 43 | <div class="float right"> | 43 | <div class="float right"> |
| 44 | <div class="links text-center"> | 44 | <div class="links text-center"> |
| 45 | <a href="/books"><i class="zmdi zmdi-book"></i>Books</a> | 45 | <a href="/books"><i class="zmdi zmdi-book"></i>Books</a> |
| 46 | - @if(isset($currentUser) && $currentUser->can('settings-update')) | 46 | + @if(isset($currentUser) && $currentUser->can('settings-manage')) |
| 47 | <a href="/settings"><i class="zmdi zmdi-settings"></i>Settings</a> | 47 | <a href="/settings"><i class="zmdi zmdi-settings"></i>Settings</a> |
| 48 | @endif | 48 | @endif |
| 49 | @if(!isset($signedIn) || !$signedIn) | 49 | @if(!isset($signedIn) || !$signedIn) | ... | ... |
| ... | @@ -8,7 +8,7 @@ | ... | @@ -8,7 +8,7 @@ |
| 8 | <div class="col-xs-1"></div> | 8 | <div class="col-xs-1"></div> |
| 9 | <div class="col-xs-11 faded"> | 9 | <div class="col-xs-11 faded"> |
| 10 | <div class="action-buttons"> | 10 | <div class="action-buttons"> |
| 11 | - @if($currentUser->can('book-create')) | 11 | + @if($currentUser->can('book-create-all')) |
| 12 | <a href="/books/create" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>Add new book</a> | 12 | <a href="/books/create" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>Add new book</a> |
| 13 | @endif | 13 | @endif |
| 14 | </div> | 14 | </div> |
| ... | @@ -30,7 +30,9 @@ | ... | @@ -30,7 +30,9 @@ |
| 30 | {!! $books->render() !!} | 30 | {!! $books->render() !!} |
| 31 | @else | 31 | @else |
| 32 | <p class="text-muted">No books have been created.</p> | 32 | <p class="text-muted">No books have been created.</p> |
| 33 | - <a href="/books/create" class="text-pos"><i class="zmdi zmdi-edit"></i>Create one now</a> | 33 | + @if(userCan('books-create-all')) |
| 34 | + <a href="/books/create" class="text-pos"><i class="zmdi zmdi-edit"></i>Create one now</a> | ||
| 35 | + @endif | ||
| 34 | @endif | 36 | @endif |
| 35 | </div> | 37 | </div> |
| 36 | <div class="col-sm-4 col-sm-offset-1"> | 38 | <div class="col-sm-4 col-sm-offset-1"> | ... | ... |
resources/views/books/restrictions.blade.php
0 → 100644
| 1 | +@extends('base') | ||
| 2 | + | ||
| 3 | +@section('content') | ||
| 4 | + | ||
| 5 | + <div class="faded-small toolbar"> | ||
| 6 | + <div class="container"> | ||
| 7 | + <div class="row"> | ||
| 8 | + <div class="col-sm-12 faded"> | ||
| 9 | + <div class="breadcrumbs"> | ||
| 10 | + <a href="{{$book->getUrl()}}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $book->getShortName() }}</a> | ||
| 11 | + </div> | ||
| 12 | + </div> | ||
| 13 | + </div> | ||
| 14 | + </div> | ||
| 15 | + </div> | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + <div class="container" ng-non-bindable> | ||
| 19 | + <h1>Book Restrictions</h1> | ||
| 20 | + @include('form/restriction-form', ['model' => $book]) | ||
| 21 | + </div> | ||
| 22 | + | ||
| 23 | +@stop |
| ... | @@ -2,23 +2,35 @@ | ... | @@ -2,23 +2,35 @@ |
| 2 | 2 | ||
| 3 | @section('content') | 3 | @section('content') |
| 4 | 4 | ||
| 5 | - <div class="faded-small toolbar" ng-non-bindable> | 5 | + <div class="faded-small toolbar"> |
| 6 | <div class="container"> | 6 | <div class="container"> |
| 7 | <div class="row"> | 7 | <div class="row"> |
| 8 | <div class="col-md-12"> | 8 | <div class="col-md-12"> |
| 9 | <div class="action-buttons faded"> | 9 | <div class="action-buttons faded"> |
| 10 | - @if($currentUser->can('page-create')) | 10 | + @if(userCan('page-create', $book)) |
| 11 | <a href="{{$book->getUrl() . '/page/create'}}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i> New Page</a> | 11 | <a href="{{$book->getUrl() . '/page/create'}}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i> New Page</a> |
| 12 | @endif | 12 | @endif |
| 13 | - @if($currentUser->can('chapter-create')) | 13 | + @if(userCan('chapter-create', $book)) |
| 14 | <a href="{{$book->getUrl() . '/chapter/create'}}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i> New Chapter</a> | 14 | <a href="{{$book->getUrl() . '/chapter/create'}}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i> New Chapter</a> |
| 15 | @endif | 15 | @endif |
| 16 | - @if($currentUser->can('book-update')) | 16 | + @if(userCan('book-update', $book)) |
| 17 | <a href="{{$book->getEditUrl()}}" class="text-primary text-button"><i class="zmdi zmdi-edit"></i>Edit</a> | 17 | <a href="{{$book->getEditUrl()}}" class="text-primary text-button"><i class="zmdi zmdi-edit"></i>Edit</a> |
| 18 | - <a href="{{ $book->getUrl() }}/sort" class="text-primary text-button"><i class="zmdi zmdi-sort"></i>Sort</a> | ||
| 19 | @endif | 18 | @endif |
| 20 | - @if($currentUser->can('book-delete')) | 19 | + @if(userCan('book-update', $book) || userCan('restrictions-manage', $book) || userCan('book-delete', $book)) |
| 21 | - <a href="{{ $book->getUrl() }}/delete" class="text-neg text-button"><i class="zmdi zmdi-delete"></i>Delete</a> | 20 | + <div dropdown class="dropdown-container"> |
| 21 | + <a dropdown-toggle class="text-primary text-button"><i class="zmdi zmdi-more-vert"></i></a> | ||
| 22 | + <ul> | ||
| 23 | + @if(userCan('book-update', $book)) | ||
| 24 | + <li><a href="{{ $book->getUrl() }}/sort" class="text-primary"><i class="zmdi zmdi-sort"></i>Sort</a></li> | ||
| 25 | + @endif | ||
| 26 | + @if(userCan('restrictions-manage', $book)) | ||
| 27 | + <li><a href="{{$book->getUrl()}}/restrict" class="text-primary"><i class="zmdi zmdi-lock-outline"></i>Restrict</a></li> | ||
| 28 | + @endif | ||
| 29 | + @if(userCan('book-delete', $book)) | ||
| 30 | + <li><a href="{{ $book->getUrl() }}/delete" class="text-neg"><i class="zmdi zmdi-delete"></i>Delete</a></li> | ||
| 31 | + @endif | ||
| 32 | + </ul> | ||
| 33 | + </div> | ||
| 22 | @endif | 34 | @endif |
| 23 | </div> | 35 | </div> |
| 24 | </div> | 36 | </div> |
| ... | @@ -75,6 +87,15 @@ | ... | @@ -75,6 +87,15 @@ |
| 75 | 87 | ||
| 76 | <div class="col-md-4 col-md-offset-1"> | 88 | <div class="col-md-4 col-md-offset-1"> |
| 77 | <div class="margin-top large"></div> | 89 | <div class="margin-top large"></div> |
| 90 | + @if($book->restricted) | ||
| 91 | + <p class="text-muted"> | ||
| 92 | + @if(userCan('restrictions-manage', $book)) | ||
| 93 | + <a href="{{ $book->getUrl() }}/restrict"><i class="zmdi zmdi-lock-outline"></i>Book Restricted</a> | ||
| 94 | + @else | ||
| 95 | + <i class="zmdi zmdi-lock-outline"></i>Book Restricted | ||
| 96 | + @endif | ||
| 97 | + </p> | ||
| 98 | + @endif | ||
| 78 | <div class="search-box"> | 99 | <div class="search-box"> |
| 79 | <form ng-submit="searchBook($event)"> | 100 | <form ng-submit="searchBook($event)"> |
| 80 | <input ng-model="searchTerm" ng-change="checkSearchForm()" type="text" name="term" placeholder="Search This Book"> | 101 | <input ng-model="searchTerm" ng-change="checkSearchForm()" type="text" name="term" placeholder="Search This Book"> | ... | ... |
| 1 | +@extends('base') | ||
| 2 | + | ||
| 3 | +@section('content') | ||
| 4 | + | ||
| 5 | + <div class="faded-small toolbar"> | ||
| 6 | + <div class="container"> | ||
| 7 | + <div class="row"> | ||
| 8 | + <div class="col-sm-12 faded"> | ||
| 9 | + <div class="breadcrumbs"> | ||
| 10 | + <a href="{{$chapter->book->getUrl()}}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $chapter->book->getShortName() }}</a> | ||
| 11 | + <span class="sep">»</span> | ||
| 12 | + <a href="{{ $chapter->getUrl() }}" class="text-chapter text-button"><i class="zmdi zmdi-collection-bookmark"></i>{{$chapter->getShortName()}}</a> | ||
| 13 | + </div> | ||
| 14 | + </div> | ||
| 15 | + </div> | ||
| 16 | + </div> | ||
| 17 | + </div> | ||
| 18 | + | ||
| 19 | + <div class="container" ng-non-bindable> | ||
| 20 | + <h1>Chapter Restrictions</h1> | ||
| 21 | + @include('form/restriction-form', ['model' => $chapter]) | ||
| 22 | + </div> | ||
| 23 | + | ||
| 24 | +@stop |
| ... | @@ -12,13 +12,16 @@ | ... | @@ -12,13 +12,16 @@ |
| 12 | </div> | 12 | </div> |
| 13 | <div class="col-md-8 faded"> | 13 | <div class="col-md-8 faded"> |
| 14 | <div class="action-buttons"> | 14 | <div class="action-buttons"> |
| 15 | - @if($currentUser->can('chapter-create')) | 15 | + @if(userCan('page-create', $chapter)) |
| 16 | <a href="{{$chapter->getUrl() . '/create-page'}}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>New Page</a> | 16 | <a href="{{$chapter->getUrl() . '/create-page'}}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>New Page</a> |
| 17 | @endif | 17 | @endif |
| 18 | - @if($currentUser->can('chapter-update')) | 18 | + @if(userCan('chapter-update', $chapter)) |
| 19 | <a href="{{$chapter->getUrl() . '/edit'}}" class="text-primary text-button"><i class="zmdi zmdi-edit"></i>Edit</a> | 19 | <a href="{{$chapter->getUrl() . '/edit'}}" class="text-primary text-button"><i class="zmdi zmdi-edit"></i>Edit</a> |
| 20 | @endif | 20 | @endif |
| 21 | - @if($currentUser->can('chapter-delete')) | 21 | + @if(userCan('restrictions-manage', $chapter)) |
| 22 | + <a href="{{$chapter->getUrl()}}/restrict" class="text-primary text-button"><i class="zmdi zmdi-lock-outline"></i>Restrict</a> | ||
| 23 | + @endif | ||
| 24 | + @if(userCan('chapter-delete', $chapter)) | ||
| 22 | <a href="{{$chapter->getUrl() . '/delete'}}" class="text-neg text-button"><i class="zmdi zmdi-delete"></i>Delete</a> | 25 | <a href="{{$chapter->getUrl() . '/delete'}}" class="text-neg text-button"><i class="zmdi zmdi-delete"></i>Delete</a> |
| 23 | @endif | 26 | @endif |
| 24 | </div> | 27 | </div> |
| ... | @@ -34,10 +37,10 @@ | ... | @@ -34,10 +37,10 @@ |
| 34 | <h1>{{ $chapter->name }}</h1> | 37 | <h1>{{ $chapter->name }}</h1> |
| 35 | <p class="text-muted">{{ $chapter->description }}</p> | 38 | <p class="text-muted">{{ $chapter->description }}</p> |
| 36 | 39 | ||
| 37 | - @if(count($chapter->pages) > 0) | 40 | + @if(count($pages) > 0) |
| 38 | <div class="page-list"> | 41 | <div class="page-list"> |
| 39 | <hr> | 42 | <hr> |
| 40 | - @foreach($chapter->pages as $page) | 43 | + @foreach($pages as $page) |
| 41 | @include('pages/list-item', ['page' => $page]) | 44 | @include('pages/list-item', ['page' => $page]) |
| 42 | <hr> | 45 | <hr> |
| 43 | @endforeach | 46 | @endforeach |
| ... | @@ -60,6 +63,29 @@ | ... | @@ -60,6 +63,29 @@ |
| 60 | </p> | 63 | </p> |
| 61 | </div> | 64 | </div> |
| 62 | <div class="col-md-3 col-md-offset-1"> | 65 | <div class="col-md-3 col-md-offset-1"> |
| 66 | + <div class="margin-top large"></div> | ||
| 67 | + @if($book->restricted || $chapter->restricted) | ||
| 68 | + <div class="text-muted"> | ||
| 69 | + | ||
| 70 | + @if($book->restricted) | ||
| 71 | + @if(userCan('restrictions-manage', $book)) | ||
| 72 | + <a href="{{ $book->getUrl() }}/restrict"><i class="zmdi zmdi-lock-outline"></i>Book Restricted</a> | ||
| 73 | + @else | ||
| 74 | + <i class="zmdi zmdi-lock-outline"></i>Book Restricted | ||
| 75 | + @endif | ||
| 76 | + <br> | ||
| 77 | + @endif | ||
| 78 | + | ||
| 79 | + @if($chapter->restricted) | ||
| 80 | + @if(userCan('restrictions-manage', $chapter)) | ||
| 81 | + <a href="{{ $chapter->getUrl() }}/restrict"><i class="zmdi zmdi-lock-outline"></i>Chapter Restricted</a> | ||
| 82 | + @else | ||
| 83 | + <i class="zmdi zmdi-lock-outline"></i>Chapter Restricted | ||
| 84 | + @endif | ||
| 85 | + @endif | ||
| 86 | + </div> | ||
| 87 | + @endif | ||
| 88 | + | ||
| 63 | @include('pages/sidebar-tree-list', ['book' => $book, 'sidebarTree' => $sidebarTree]) | 89 | @include('pages/sidebar-tree-list', ['book' => $book, 'sidebarTree' => $sidebarTree]) |
| 64 | </div> | 90 | </div> |
| 65 | </div> | 91 | </div> | ... | ... |
| ... | @@ -4,7 +4,7 @@ | ... | @@ -4,7 +4,7 @@ |
| 4 | 4 | ||
| 5 | 5 | ||
| 6 | <div class="container"> | 6 | <div class="container"> |
| 7 | - <h1 class="text-muted">Page Not Found</h1> | 7 | + <h1 class="text-muted">{{ $message or 'Page Not Found' }}</h1> |
| 8 | <p>Sorry, The page you were looking for could not be found.</p> | 8 | <p>Sorry, The page you were looking for could not be found.</p> |
| 9 | <a href="/" class="button">Return To Home</a> | 9 | <a href="/" class="button">Return To Home</a> |
| 10 | </div> | 10 | </div> | ... | ... |
resources/views/form/checkbox.blade.php
0 → 100644
| 1 | + | ||
| 2 | +<label> | ||
| 3 | + <input value="true" id="{{$name}}" type="checkbox" name="{{$name}}" | ||
| 4 | + @if($errors->has($name)) class="neg" @endif | ||
| 5 | + @if(old($name) || (!old() && isset($model) && $model->$name)) checked="checked" @endif | ||
| 6 | + > | ||
| 7 | + {{ $label }} | ||
| 8 | +</label> | ||
| 9 | + | ||
| 10 | +@if($errors->has($name)) | ||
| 11 | + <div class="text-neg text-small">{{ $errors->first($name) }}</div> | ||
| 12 | +@endif | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| 1 | + | ||
| 2 | +<label> | ||
| 3 | + <input value="true" id="{{$name}}[{{$role->id}}][{{$action}}]" type="checkbox" name="{{$name}}[{{$role->id}}][{{$action}}]" | ||
| 4 | + @if(old($name .'.'.$role->id.'.'.$action) || (!old() && isset($model) && $model->hasRestriction($role->id, $action))) checked="checked" @endif | ||
| 5 | + > | ||
| 6 | + {{ $label }} | ||
| 7 | +</label> | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| 1 | +<form action="{{ $model->getUrl() }}/restrict" method="POST"> | ||
| 2 | + {!! csrf_field() !!} | ||
| 3 | + <input type="hidden" name="_method" value="PUT"> | ||
| 4 | + | ||
| 5 | + <div class="form-group"> | ||
| 6 | + @include('form/checkbox', ['name' => 'restricted', 'label' => 'Restrict this ' . $model->getClassName()]) | ||
| 7 | + </div> | ||
| 8 | + | ||
| 9 | + <table class="table"> | ||
| 10 | + <tr> | ||
| 11 | + <th>Role</th> | ||
| 12 | + <th @if($model->isA('page')) colspan="3" @else colspan="4" @endif>Actions</th> | ||
| 13 | + </tr> | ||
| 14 | + @foreach($roles as $role) | ||
| 15 | + <tr> | ||
| 16 | + <td>{{ $role->display_name }}</td> | ||
| 17 | + <td>@include('form/restriction-checkbox', ['name'=>'restrictions', 'label' => 'View', 'action' => 'view'])</td> | ||
| 18 | + @if(!$model->isA('page')) | ||
| 19 | + <td>@include('form/restriction-checkbox', ['name'=>'restrictions', 'label' => 'Create', 'action' => 'create'])</td> | ||
| 20 | + @endif | ||
| 21 | + <td>@include('form/restriction-checkbox', ['name'=>'restrictions', 'label' => 'Update', 'action' => 'update'])</td> | ||
| 22 | + <td>@include('form/restriction-checkbox', ['name'=>'restrictions', 'label' => 'Delete', 'action' => 'delete'])</td> | ||
| 23 | + </tr> | ||
| 24 | + @endforeach | ||
| 25 | + </table> | ||
| 26 | + | ||
| 27 | + <a href="{{ $model->getUrl() }}" class="button muted">Cancel</a> | ||
| 28 | + <button type="submit" class="button pos">Save Restrictions</button> | ||
| 29 | +</form> | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| 1 | + | ||
| 2 | +@foreach($roles as $role) | ||
| 3 | + <label> | ||
| 4 | + <input value="{{ $role->id }}" id="{{$name}}-{{$role->name}}" type="checkbox" name="{{$name}}[{{$role->name}}]" | ||
| 5 | + @if($errors->has($name)) class="neg" @endif | ||
| 6 | + @if(old($name . '.' . $role->name) || (!old('name') && isset($model) && $model->hasRole($role->name))) checked="checked" @endif | ||
| 7 | + > | ||
| 8 | + {{ $role->display_name }} | ||
| 9 | + </label> | ||
| 10 | +@endforeach | ||
| 11 | + | ||
| 12 | +@if($errors->has($name)) | ||
| 13 | + <div class="text-neg text-small">{{ $errors->first($name) }}</div> | ||
| 14 | +@endif | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
resources/views/pages/restrictions.blade.php
0 → 100644
| 1 | +@extends('base') | ||
| 2 | + | ||
| 3 | +@section('content') | ||
| 4 | + | ||
| 5 | + <div class="faded-small toolbar"> | ||
| 6 | + <div class="container"> | ||
| 7 | + <div class="row"> | ||
| 8 | + <div class="col-sm-12 faded"> | ||
| 9 | + <div class="breadcrumbs"> | ||
| 10 | + <a href="{{$page->book->getUrl()}}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $page->book->getShortName() }}</a> | ||
| 11 | + @if($page->hasChapter()) | ||
| 12 | + <span class="sep">»</span> | ||
| 13 | + <a href="{{ $page->chapter->getUrl() }}" class="text-chapter text-button"> | ||
| 14 | + <i class="zmdi zmdi-collection-bookmark"></i> | ||
| 15 | + {{$page->chapter->getShortName()}} | ||
| 16 | + </a> | ||
| 17 | + @endif | ||
| 18 | + <span class="sep">»</span> | ||
| 19 | + <a href="{{$page->getUrl()}}" class="text-book text-button"><i class="zmdi zmdi-file"></i>{{ $page->getShortName() }}</a> | ||
| 20 | + </div> | ||
| 21 | + </div> | ||
| 22 | + </div> | ||
| 23 | + </div> | ||
| 24 | + </div> | ||
| 25 | + | ||
| 26 | + <div class="container" ng-non-bindable> | ||
| 27 | + <h1>Page Restrictions</h1> | ||
| 28 | + @include('form/restriction-form', ['model' => $page]) | ||
| 29 | + </div> | ||
| 30 | + | ||
| 31 | +@stop |
| ... | @@ -22,17 +22,20 @@ | ... | @@ -22,17 +22,20 @@ |
| 22 | <span dropdown class="dropdown-container"> | 22 | <span dropdown class="dropdown-container"> |
| 23 | <div dropdown-toggle class="text-button text-primary"><i class="zmdi zmdi-open-in-new"></i>Export</div> | 23 | <div dropdown-toggle class="text-button text-primary"><i class="zmdi zmdi-open-in-new"></i>Export</div> |
| 24 | <ul class="wide"> | 24 | <ul class="wide"> |
| 25 | - <li><a href="{{$page->getUrl() . '/export/html'}}" target="_blank">Contained Web File <span class="text-muted float right">.html</span></a></li> | 25 | + <li><a href="{{$page->getUrl()}}/export/html" target="_blank">Contained Web File <span class="text-muted float right">.html</span></a></li> |
| 26 | - <li><a href="{{$page->getUrl() . '/export/pdf'}}" target="_blank">PDF File <span class="text-muted float right">.pdf</span></a></li> | 26 | + <li><a href="{{$page->getUrl()}}/export/pdf" target="_blank">PDF File <span class="text-muted float right">.pdf</span></a></li> |
| 27 | - <li><a href="{{$page->getUrl() . '/export/plaintext'}}" target="_blank">Plain Text File <span class="text-muted float right">.txt</span></a></li> | 27 | + <li><a href="{{$page->getUrl()}}/export/plaintext" target="_blank">Plain Text File <span class="text-muted float right">.txt</span></a></li> |
| 28 | </ul> | 28 | </ul> |
| 29 | </span> | 29 | </span> |
| 30 | - @if($currentUser->can('page-update')) | 30 | + @if(userCan('page-update', $page)) |
| 31 | - <a href="{{$page->getUrl() . '/revisions'}}" class="text-primary text-button"><i class="zmdi zmdi-replay"></i>Revisions</a> | 31 | + <a href="{{$page->getUrl()}}/revisions" class="text-primary text-button"><i class="zmdi zmdi-replay"></i>Revisions</a> |
| 32 | - <a href="{{$page->getUrl() . '/edit'}}" class="text-primary text-button" ><i class="zmdi zmdi-edit"></i>Edit</a> | 32 | + <a href="{{$page->getUrl()}}/edit" class="text-primary text-button" ><i class="zmdi zmdi-edit"></i>Edit</a> |
| 33 | @endif | 33 | @endif |
| 34 | - @if($currentUser->can('page-delete')) | 34 | + @if(userCan('restrictions-manage', $page)) |
| 35 | - <a href="{{$page->getUrl() . '/delete'}}" class="text-neg text-button"><i class="zmdi zmdi-delete"></i>Delete</a> | 35 | + <a href="{{$page->getUrl()}}/restrict" class="text-primary text-button"><i class="zmdi zmdi-lock-outline"></i>Restrict</a> |
| 36 | + @endif | ||
| 37 | + @if(userCan('page-delete', $page)) | ||
| 38 | + <a href="{{$page->getUrl()}}/delete" class="text-neg text-button"><i class="zmdi zmdi-delete"></i>Delete</a> | ||
| 36 | @endif | 39 | @endif |
| 37 | </div> | 40 | </div> |
| 38 | </div> | 41 | </div> |
| ... | @@ -67,7 +70,38 @@ | ... | @@ -67,7 +70,38 @@ |
| 67 | </div> | 70 | </div> |
| 68 | </div> | 71 | </div> |
| 69 | <div class="col-md-3 print-hidden"> | 72 | <div class="col-md-3 print-hidden"> |
| 73 | + <div class="margin-top large"></div> | ||
| 74 | + @if($book->restricted || ($page->chapter && $page->chapter->restricted) || $page->restricted) | ||
| 75 | + <div class="text-muted"> | ||
| 76 | + | ||
| 77 | + @if($book->restricted) | ||
| 78 | + @if(userCan('restrictions-manage', $book)) | ||
| 79 | + <a href="{{ $book->getUrl() }}/restrict"><i class="zmdi zmdi-lock-outline"></i>Book restricted</a> | ||
| 80 | + @else | ||
| 81 | + <i class="zmdi zmdi-lock-outline"></i>Book restricted | ||
| 82 | + @endif | ||
| 83 | + <br> | ||
| 84 | + @endif | ||
| 70 | 85 | ||
| 86 | + @if($page->chapter && $page->chapter->restricted) | ||
| 87 | + @if(userCan('restrictions-manage', $page->chapter)) | ||
| 88 | + <a href="{{ $page->chapter->getUrl() }}/restrict"><i class="zmdi zmdi-lock-outline"></i>Chapter restricted</a> | ||
| 89 | + @else | ||
| 90 | + <i class="zmdi zmdi-lock-outline"></i>Chapter restricted | ||
| 91 | + @endif | ||
| 92 | + <br> | ||
| 93 | + @endif | ||
| 94 | + | ||
| 95 | + @if($page->restricted) | ||
| 96 | + @if(userCan('restrictions-manage', $page)) | ||
| 97 | + <a href="{{ $page->getUrl() }}/restrict"><i class="zmdi zmdi-lock-outline"></i>Page restricted</a> | ||
| 98 | + @else | ||
| 99 | + <i class="zmdi zmdi-lock-outline"></i>Page restricted | ||
| 100 | + @endif | ||
| 101 | + <br> | ||
| 102 | + @endif | ||
| 103 | + </div> | ||
| 104 | + @endif | ||
| 71 | @include('pages/sidebar-tree-list', ['book' => $book, 'sidebarTree' => $sidebarTree]) | 105 | @include('pages/sidebar-tree-list', ['book' => $book, 'sidebarTree' => $sidebarTree]) |
| 72 | 106 | ||
| 73 | </div> | 107 | </div> | ... | ... |
| ... | @@ -16,8 +16,8 @@ | ... | @@ -16,8 +16,8 @@ |
| 16 | 16 | ||
| 17 | {{ $activity->getText() }} | 17 | {{ $activity->getText() }} |
| 18 | 18 | ||
| 19 | - @if($activity->entity()) | 19 | + @if($activity->entity) |
| 20 | - <a href="{{ $activity->entity()->getUrl() }}">{{ $activity->entity()->name }}</a> | 20 | + <a href="{{ $activity->entity->getUrl() }}">{{ $activity->entity->name }}</a> |
| 21 | @endif | 21 | @endif |
| 22 | 22 | ||
| 23 | @if($activity->extra) "{{$activity->extra}}" @endif | 23 | @if($activity->extra) "{{$activity->extra}}" @endif | ... | ... |
| ... | @@ -54,7 +54,7 @@ | ... | @@ -54,7 +54,7 @@ |
| 54 | <select id="setting-registration-role" name="setting-registration-role" @if($errors->has('setting-registration-role')) class="neg" @endif> | 54 | <select id="setting-registration-role" name="setting-registration-role" @if($errors->has('setting-registration-role')) class="neg" @endif> |
| 55 | @foreach(\BookStack\Role::all() as $role) | 55 | @foreach(\BookStack\Role::all() as $role) |
| 56 | <option value="{{$role->id}}" | 56 | <option value="{{$role->id}}" |
| 57 | - @if(\Setting::get('registration-role', \BookStack\Role::getDefault()->id) == $role->id) selected @endif | 57 | + @if(\Setting::get('registration-role', \BookStack\Role::first()->id) == $role->id) selected @endif |
| 58 | > | 58 | > |
| 59 | {{ $role->display_name }} | 59 | {{ $role->display_name }} |
| 60 | </option> | 60 | </option> | ... | ... |
| ... | @@ -5,6 +5,7 @@ | ... | @@ -5,6 +5,7 @@ |
| 5 | <div class="col-md-12 setting-nav"> | 5 | <div class="col-md-12 setting-nav"> |
| 6 | <a href="/settings" @if($selected == 'settings') class="selected text-button" @endif><i class="zmdi zmdi-settings"></i>Settings</a> | 6 | <a href="/settings" @if($selected == 'settings') class="selected text-button" @endif><i class="zmdi zmdi-settings"></i>Settings</a> |
| 7 | <a href="/settings/users" @if($selected == 'users') class="selected text-button" @endif><i class="zmdi zmdi-accounts"></i>Users</a> | 7 | <a href="/settings/users" @if($selected == 'users') class="selected text-button" @endif><i class="zmdi zmdi-accounts"></i>Users</a> |
| 8 | + <a href="/settings/roles" @if($selected == 'roles') class="selected text-button" @endif><i class="zmdi zmdi-lock-open"></i>Roles</a> | ||
| 8 | </div> | 9 | </div> |
| 9 | </div> | 10 | </div> |
| 10 | </div> | 11 | </div> | ... | ... |
| 1 | +<input type="checkbox" name="permissions[{{ $permission }}]" | ||
| 2 | + @if(old('permissions.'.$permission, false)|| (!old('display_name', false) && (isset($role) && $role->hasPermission($permission)))) checked="checked" @endif | ||
| 3 | + value="true"> | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| 1 | +@extends('base') | ||
| 2 | + | ||
| 3 | +@section('content') | ||
| 4 | + | ||
| 5 | + @include('settings/navbar', ['selected' => 'roles']) | ||
| 6 | + | ||
| 7 | + <div class="container"> | ||
| 8 | + <h1>Create New Role</h1> | ||
| 9 | + | ||
| 10 | + <form action="/settings/roles/new" method="POST"> | ||
| 11 | + @include('settings/roles/form') | ||
| 12 | + </form> | ||
| 13 | + </div> | ||
| 14 | + | ||
| 15 | +@stop |
| 1 | +@extends('base') | ||
| 2 | + | ||
| 3 | +@section('content') | ||
| 4 | + | ||
| 5 | + @include('settings/navbar', ['selected' => 'roles']) | ||
| 6 | + | ||
| 7 | + <div class="container small" ng-non-bindable> | ||
| 8 | + <h1>Delete Role</h1> | ||
| 9 | + <p>This will delete the role with the name '{{$role->display_name}}'.</p> | ||
| 10 | + | ||
| 11 | + <form action="/settings/roles/delete/{{$role->id}}" method="POST"> | ||
| 12 | + {!! csrf_field() !!} | ||
| 13 | + <input type="hidden" name="_method" value="DELETE"> | ||
| 14 | + | ||
| 15 | + @if($role->users->count() > 0) | ||
| 16 | + <div class="form-group"> | ||
| 17 | + <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> | ||
| 18 | + @include('form/role-select', ['options' => $roles, 'name' => 'migration_role_id']) | ||
| 19 | + </div> | ||
| 20 | + @endif | ||
| 21 | + | ||
| 22 | + <p class="text-neg">Are you sure you want to delete this role?</p> | ||
| 23 | + <a href="/settings/roles/{{ $role->id }}" class="button">Cancel</a> | ||
| 24 | + <button type="submit" class="button neg">Confirm</button> | ||
| 25 | + </form> | ||
| 26 | + </div> | ||
| 27 | + | ||
| 28 | +@stop |
| 1 | +@extends('base') | ||
| 2 | + | ||
| 3 | +@section('content') | ||
| 4 | + | ||
| 5 | + @include('settings/navbar', ['selected' => 'roles']) | ||
| 6 | + | ||
| 7 | + <div class="container"> | ||
| 8 | + <div class="row"> | ||
| 9 | + <div class="col-sm-6"> | ||
| 10 | + <h1>Edit Role <small> {{ $role->display_name }}</small></h1> | ||
| 11 | + </div> | ||
| 12 | + <div class="col-sm-6"> | ||
| 13 | + <p></p> | ||
| 14 | + <a href="/settings/roles/delete/{{ $role->id }}" class="button neg float right">Delete Role</a> | ||
| 15 | + </div> | ||
| 16 | + </div> | ||
| 17 | + | ||
| 18 | + <form action="/settings/roles/{{ $role->id }}" method="POST"> | ||
| 19 | + <input type="hidden" name="_method" value="PUT"> | ||
| 20 | + @include('settings/roles/form', ['model' => $role]) | ||
| 21 | + </form> | ||
| 22 | + </div> | ||
| 23 | + | ||
| 24 | +@stop |
| 1 | +{!! csrf_field() !!} | ||
| 2 | + | ||
| 3 | +<div class="row"> | ||
| 4 | + | ||
| 5 | + <div class="col-md-6"> | ||
| 6 | + <h3>Role Details</h3> | ||
| 7 | + <div class="form-group"> | ||
| 8 | + <label for="name">Role Name</label> | ||
| 9 | + @include('form/text', ['name' => 'display_name']) | ||
| 10 | + </div> | ||
| 11 | + <div class="form-group"> | ||
| 12 | + <label for="name">Short Role Description</label> | ||
| 13 | + @include('form/text', ['name' => 'description']) | ||
| 14 | + </div> | ||
| 15 | + <h3>System Permissions</h3> | ||
| 16 | + <div class="row"> | ||
| 17 | + <div class="col-md-6"> | ||
| 18 | + <label> @include('settings/roles/checkbox', ['permission' => 'users-manage']) Manage users</label> | ||
| 19 | + </div> | ||
| 20 | + <div class="col-md-6"> | ||
| 21 | + <label>@include('settings/roles/checkbox', ['permission' => 'user-roles-manage']) Manage user roles</label> | ||
| 22 | + </div> | ||
| 23 | + </div> | ||
| 24 | + <hr class="even"> | ||
| 25 | + <div class="row"> | ||
| 26 | + <div class="col-md-6"> | ||
| 27 | + <label>@include('settings/roles/checkbox', ['permission' => 'restrictions-manage-all']) Manage all restrictions</label> | ||
| 28 | + </div> | ||
| 29 | + <div class="col-md-6"> | ||
| 30 | + <label>@include('settings/roles/checkbox', ['permission' => 'restrictions-manage-own']) Manage restrictions on own content</label> | ||
| 31 | + </div> | ||
| 32 | + </div> | ||
| 33 | + <hr class="even"> | ||
| 34 | + <div class="form-group"> | ||
| 35 | + <label>@include('settings/roles/checkbox', ['permission' => 'settings-manage']) Manage app settings</label> | ||
| 36 | + </div> | ||
| 37 | + <hr class="even"> | ||
| 38 | + | ||
| 39 | + </div> | ||
| 40 | + | ||
| 41 | + <div class="col-md-6"> | ||
| 42 | + | ||
| 43 | + <h3>Asset Permissions</h3> | ||
| 44 | + <p> | ||
| 45 | + These permissions control default access to the assets within the system. <br> | ||
| 46 | + Restrictions on Books, Chapters and Pages will override these permissions. | ||
| 47 | + </p> | ||
| 48 | + <table class="table"> | ||
| 49 | + <tr> | ||
| 50 | + <th></th> | ||
| 51 | + <th>Create</th> | ||
| 52 | + <th>Edit</th> | ||
| 53 | + <th>Delete</th> | ||
| 54 | + </tr> | ||
| 55 | + <tr> | ||
| 56 | + <td>Books</td> | ||
| 57 | + <td> | ||
| 58 | + <label>@include('settings/roles/checkbox', ['permission' => 'book-create-all']) All</label> | ||
| 59 | + </td> | ||
| 60 | + <td> | ||
| 61 | + <label>@include('settings/roles/checkbox', ['permission' => 'book-update-own']) Own</label> | ||
| 62 | + <label>@include('settings/roles/checkbox', ['permission' => 'book-update-all']) All</label> | ||
| 63 | + </td> | ||
| 64 | + <td> | ||
| 65 | + <label>@include('settings/roles/checkbox', ['permission' => 'book-delete-own']) Own</label> | ||
| 66 | + <label>@include('settings/roles/checkbox', ['permission' => 'book-delete-all']) All</label> | ||
| 67 | + </td> | ||
| 68 | + </tr> | ||
| 69 | + <tr> | ||
| 70 | + <td>Chapters</td> | ||
| 71 | + <td> | ||
| 72 | + <label>@include('settings/roles/checkbox', ['permission' => 'chapter-create-own']) Own</label> | ||
| 73 | + <label>@include('settings/roles/checkbox', ['permission' => 'chapter-create-all']) All</label> | ||
| 74 | + </td> | ||
| 75 | + <td> | ||
| 76 | + <label>@include('settings/roles/checkbox', ['permission' => 'chapter-update-own']) Own</label> | ||
| 77 | + <label>@include('settings/roles/checkbox', ['permission' => 'chapter-update-all']) All</label> | ||
| 78 | + </td> | ||
| 79 | + <td> | ||
| 80 | + <label>@include('settings/roles/checkbox', ['permission' => 'chapter-delete-own']) Own</label> | ||
| 81 | + <label>@include('settings/roles/checkbox', ['permission' => 'chapter-delete-all']) All</label> | ||
| 82 | + </td> | ||
| 83 | + </tr> | ||
| 84 | + <tr> | ||
| 85 | + <td>Pages</td> | ||
| 86 | + <td> | ||
| 87 | + <label>@include('settings/roles/checkbox', ['permission' => 'page-create-own']) Own</label> | ||
| 88 | + <label>@include('settings/roles/checkbox', ['permission' => 'page-create-all']) All</label> | ||
| 89 | + </td> | ||
| 90 | + <td> | ||
| 91 | + <label>@include('settings/roles/checkbox', ['permission' => 'page-update-own']) Own</label> | ||
| 92 | + <label>@include('settings/roles/checkbox', ['permission' => 'page-update-all']) All</label> | ||
| 93 | + </td> | ||
| 94 | + <td> | ||
| 95 | + <label>@include('settings/roles/checkbox', ['permission' => 'page-delete-own']) Own</label> | ||
| 96 | + <label>@include('settings/roles/checkbox', ['permission' => 'page-delete-all']) All</label> | ||
| 97 | + </td> | ||
| 98 | + </tr> | ||
| 99 | + <tr> | ||
| 100 | + <td>Images</td> | ||
| 101 | + <td>@include('settings/roles/checkbox', ['permission' => 'image-create-all'])</td> | ||
| 102 | + <td> | ||
| 103 | + <label>@include('settings/roles/checkbox', ['permission' => 'image-update-own']) Own</label> | ||
| 104 | + <label>@include('settings/roles/checkbox', ['permission' => 'image-update-all']) All</label> | ||
| 105 | + </td> | ||
| 106 | + <td> | ||
| 107 | + <label>@include('settings/roles/checkbox', ['permission' => 'image-delete-own']) Own</label> | ||
| 108 | + <label>@include('settings/roles/checkbox', ['permission' => 'image-delete-all']) All</label> | ||
| 109 | + </td> | ||
| 110 | + </tr> | ||
| 111 | + </table> | ||
| 112 | + </div> | ||
| 113 | + | ||
| 114 | +</div> | ||
| 115 | + | ||
| 116 | +<a href="/settings/roles" class="button muted">Cancel</a> | ||
| 117 | +<button type="submit" class="button pos">Save Role</button> | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| 1 | +@extends('base') | ||
| 2 | + | ||
| 3 | +@section('content') | ||
| 4 | + | ||
| 5 | + @include('settings/navbar', ['selected' => 'roles']) | ||
| 6 | + | ||
| 7 | + <div class="container small"> | ||
| 8 | + | ||
| 9 | + <h1>User Roles</h1> | ||
| 10 | + | ||
| 11 | + <p> | ||
| 12 | + <a href="/settings/roles/new" class="text-pos"><i class="zmdi zmdi-lock-open"></i>Add new role</a> | ||
| 13 | + </p> | ||
| 14 | + | ||
| 15 | + <table class="table"> | ||
| 16 | + <tr> | ||
| 17 | + <th>Role Name</th> | ||
| 18 | + <th></th> | ||
| 19 | + <th class="text-right">Users</th> | ||
| 20 | + </tr> | ||
| 21 | + @foreach($roles as $role) | ||
| 22 | + <tr> | ||
| 23 | + <td><a href="/settings/roles/{{ $role->id }}">{{ $role->display_name }}</a></td> | ||
| 24 | + <td>{{ $role->description }}</td> | ||
| 25 | + <td class="text-right">{{ $role->users->count() }}</td> | ||
| 26 | + </tr> | ||
| 27 | + @endforeach | ||
| 28 | + </table> | ||
| 29 | + </div> | ||
| 30 | + | ||
| 31 | +@stop |
| ... | @@ -3,21 +3,21 @@ | ... | @@ -3,21 +3,21 @@ |
| 3 | @include('form.text', ['name' => 'name']) | 3 | @include('form.text', ['name' => 'name']) |
| 4 | </div> | 4 | </div> |
| 5 | 5 | ||
| 6 | -@if($currentUser->can('user-update')) | 6 | +@if(userCan('users-manage')) |
| 7 | <div class="form-group"> | 7 | <div class="form-group"> |
| 8 | <label for="email">Email</label> | 8 | <label for="email">Email</label> |
| 9 | @include('form.text', ['name' => 'email']) | 9 | @include('form.text', ['name' => 'email']) |
| 10 | </div> | 10 | </div> |
| 11 | @endif | 11 | @endif |
| 12 | 12 | ||
| 13 | -@if($currentUser->can('user-update')) | 13 | +@if(userCan('users-manage')) |
| 14 | <div class="form-group"> | 14 | <div class="form-group"> |
| 15 | <label for="role">User Role</label> | 15 | <label for="role">User Role</label> |
| 16 | - @include('form.role-select', ['name' => 'role', 'options' => \BookStack\Role::all(), 'displayKey' => 'display_name']) | 16 | + @include('form/role-checkboxes', ['name' => 'roles', 'roles' => \BookStack\Role::all()]) |
| 17 | </div> | 17 | </div> |
| 18 | @endif | 18 | @endif |
| 19 | 19 | ||
| 20 | -@if($currentUser->can('user-update')) | 20 | +@if(userCan('users-manage')) |
| 21 | <div class="form-group"> | 21 | <div class="form-group"> |
| 22 | <label for="external_auth_id">External Authentication ID</label> | 22 | <label for="external_auth_id">External Authentication ID</label> |
| 23 | @include('form.text', ['name' => 'external_auth_id']) | 23 | @include('form.text', ['name' => 'external_auth_id']) | ... | ... |
| ... | @@ -8,10 +8,10 @@ | ... | @@ -8,10 +8,10 @@ |
| 8 | @include('form.text', ['name' => 'email']) | 8 | @include('form.text', ['name' => 'email']) |
| 9 | </div> | 9 | </div> |
| 10 | 10 | ||
| 11 | -@if($currentUser->can('user-update')) | 11 | +@if(userCan('users-manage')) |
| 12 | <div class="form-group"> | 12 | <div class="form-group"> |
| 13 | <label for="role">User Role</label> | 13 | <label for="role">User Role</label> |
| 14 | - @include('form.role-select', ['name' => 'role', 'options' => \BookStack\Role::all(), 'displayKey' => 'display_name']) | 14 | + @include('form/role-checkboxes', ['name' => 'roles', 'roles' => \BookStack\Role::all()]) |
| 15 | </div> | 15 | </div> |
| 16 | @endif | 16 | @endif |
| 17 | 17 | ... | ... |
| ... | @@ -8,7 +8,7 @@ | ... | @@ -8,7 +8,7 @@ |
| 8 | 8 | ||
| 9 | <div class="container small" ng-non-bindable> | 9 | <div class="container small" ng-non-bindable> |
| 10 | <h1>Users</h1> | 10 | <h1>Users</h1> |
| 11 | - @if($currentUser->can('user-create')) | 11 | + @if(userCan('users-manage')) |
| 12 | <p> | 12 | <p> |
| 13 | <a href="/settings/users/create" class="text-pos"><i class="zmdi zmdi-account-add"></i>Add new user</a> | 13 | <a href="/settings/users/create" class="text-pos"><i class="zmdi zmdi-account-add"></i>Add new user</a> |
| 14 | </p> | 14 | </p> |
| ... | @@ -18,30 +18,32 @@ | ... | @@ -18,30 +18,32 @@ |
| 18 | <th></th> | 18 | <th></th> |
| 19 | <th>Name</th> | 19 | <th>Name</th> |
| 20 | <th>Email</th> | 20 | <th>Email</th> |
| 21 | - <th>User Type</th> | 21 | + <th>User Roles</th> |
| 22 | </tr> | 22 | </tr> |
| 23 | @foreach($users as $user) | 23 | @foreach($users as $user) |
| 24 | <tr> | 24 | <tr> |
| 25 | <td style="line-height: 0;"><img class="avatar med" src="{{$user->getAvatar(40)}}" alt="{{$user->name}}"></td> | 25 | <td style="line-height: 0;"><img class="avatar med" src="{{$user->getAvatar(40)}}" alt="{{$user->name}}"></td> |
| 26 | <td> | 26 | <td> |
| 27 | - @if($currentUser->can('user-update') || $currentUser->id == $user->id) | 27 | + @if(userCan('users-manage') || $currentUser->id == $user->id) |
| 28 | <a href="/settings/users/{{$user->id}}"> | 28 | <a href="/settings/users/{{$user->id}}"> |
| 29 | @endif | 29 | @endif |
| 30 | {{ $user->name }} | 30 | {{ $user->name }} |
| 31 | - @if($currentUser->can('user-update') || $currentUser->id == $user->id) | 31 | + @if(userCan('users-manage') || $currentUser->id == $user->id) |
| 32 | </a> | 32 | </a> |
| 33 | @endif | 33 | @endif |
| 34 | </td> | 34 | </td> |
| 35 | <td> | 35 | <td> |
| 36 | - @if($currentUser->can('user-update') || $currentUser->id == $user->id) | 36 | + @if(userCan('users-manage') || $currentUser->id == $user->id) |
| 37 | <a href="/settings/users/{{$user->id}}"> | 37 | <a href="/settings/users/{{$user->id}}"> |
| 38 | @endif | 38 | @endif |
| 39 | {{ $user->email }} | 39 | {{ $user->email }} |
| 40 | - @if($currentUser->can('user-update') || $currentUser->id == $user->id) | 40 | + @if(userCan('users-manage') || $currentUser->id == $user->id) |
| 41 | </a> | 41 | </a> |
| 42 | @endif | 42 | @endif |
| 43 | </td> | 43 | </td> |
| 44 | - <td>{{ $user->role->display_name }}</td> | 44 | + <td> |
| 45 | + <small> {{ $user->roles->implode('display_name', ', ') }}</small> | ||
| 46 | + </td> | ||
| 45 | </tr> | 47 | </tr> |
| 46 | @endforeach | 48 | @endforeach |
| 47 | </table> | 49 | </table> | ... | ... |
| ... | @@ -133,12 +133,12 @@ class AuthTest extends TestCase | ... | @@ -133,12 +133,12 @@ class AuthTest extends TestCase |
| 133 | ->click('Add new user') | 133 | ->click('Add new user') |
| 134 | ->type($user->name, '#name') | 134 | ->type($user->name, '#name') |
| 135 | ->type($user->email, '#email') | 135 | ->type($user->email, '#email') |
| 136 | - ->select(2, '#role') | 136 | + ->check('roles[admin]') |
| 137 | ->type($user->password, '#password') | 137 | ->type($user->password, '#password') |
| 138 | ->type($user->password, '#password-confirm') | 138 | ->type($user->password, '#password-confirm') |
| 139 | ->press('Save') | 139 | ->press('Save') |
| 140 | - ->seeInDatabase('users', $user->toArray()) | ||
| 141 | ->seePageIs('/settings/users') | 140 | ->seePageIs('/settings/users') |
| 141 | + ->seeInDatabase('users', $user->toArray()) | ||
| 142 | ->see($user->name); | 142 | ->see($user->name); |
| 143 | } | 143 | } |
| 144 | 144 | ... | ... |
tests/RestrictionsTest.php
0 → 100644
This diff is collapsed.
Click to expand it.
tests/RolesTest.php
0 → 100644
This diff is collapsed.
Click to expand it.
| 1 | <?php | 1 | <?php |
| 2 | 2 | ||
| 3 | use Illuminate\Foundation\Testing\DatabaseTransactions; | 3 | use Illuminate\Foundation\Testing\DatabaseTransactions; |
| 4 | +use Symfony\Component\DomCrawler\Crawler; | ||
| 4 | 5 | ||
| 5 | class TestCase extends Illuminate\Foundation\Testing\TestCase | 6 | class TestCase extends Illuminate\Foundation\Testing\TestCase |
| 6 | { | 7 | { |
| ... | @@ -32,7 +33,8 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase | ... | @@ -32,7 +33,8 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase |
| 32 | public function asAdmin() | 33 | public function asAdmin() |
| 33 | { | 34 | { |
| 34 | if($this->admin === null) { | 35 | if($this->admin === null) { |
| 35 | - $this->admin = \BookStack\User::find(1); | 36 | + $adminRole = \BookStack\Role::getRole('admin'); |
| 37 | + $this->admin = $adminRole->users->first(); | ||
| 36 | } | 38 | } |
| 37 | return $this->actingAs($this->admin); | 39 | return $this->actingAs($this->admin); |
| 38 | } | 40 | } |
| ... | @@ -78,8 +80,19 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase | ... | @@ -78,8 +80,19 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase |
| 78 | protected function getNewUser($attributes = []) | 80 | protected function getNewUser($attributes = []) |
| 79 | { | 81 | { |
| 80 | $user = factory(\BookStack\User::class)->create($attributes); | 82 | $user = factory(\BookStack\User::class)->create($attributes); |
| 81 | - $userRepo = app('BookStack\Repos\UserRepo'); | 83 | + $role = \BookStack\Role::getRole('editor'); |
| 82 | - $userRepo->attachDefaultRole($user); | 84 | + $user->attachRole($role);; |
| 85 | + return $user; | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | + /** | ||
| 89 | + * Quick way to create a new user without any permissions | ||
| 90 | + * @param array $attributes | ||
| 91 | + * @return mixed | ||
| 92 | + */ | ||
| 93 | + protected function getNewBlankUser($attributes = []) | ||
| 94 | + { | ||
| 95 | + $user = factory(\BookStack\User::class)->create($attributes); | ||
| 83 | return $user; | 96 | return $user; |
| 84 | } | 97 | } |
| 85 | 98 | ||
| ... | @@ -111,6 +124,40 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase | ... | @@ -111,6 +124,40 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase |
| 111 | } | 124 | } |
| 112 | 125 | ||
| 113 | /** | 126 | /** |
| 127 | + * Assert that the current page matches a given URI. | ||
| 128 | + * | ||
| 129 | + * @param string $uri | ||
| 130 | + * @return $this | ||
| 131 | + */ | ||
| 132 | + protected function seePageUrlIs($uri) | ||
| 133 | + { | ||
| 134 | + $this->assertEquals( | ||
| 135 | + $uri, $this->currentUri, "Did not land on expected page [{$uri}].\n" | ||
| 136 | + ); | ||
| 137 | + | ||
| 138 | + return $this; | ||
| 139 | + } | ||
| 140 | + | ||
| 141 | + /** | ||
| 142 | + * Do a forced visit that does not error out on exception. | ||
| 143 | + * @param string $uri | ||
| 144 | + * @param array $parameters | ||
| 145 | + * @param array $cookies | ||
| 146 | + * @param array $files | ||
| 147 | + * @return $this | ||
| 148 | + */ | ||
| 149 | + protected function forceVisit($uri, $parameters = [], $cookies = [], $files = []) | ||
| 150 | + { | ||
| 151 | + $method = 'GET'; | ||
| 152 | + $uri = $this->prepareUrlForRequest($uri); | ||
| 153 | + $this->call($method, $uri, $parameters, $cookies, $files); | ||
| 154 | + $this->clearInputs()->followRedirects(); | ||
| 155 | + $this->currentUri = $this->app->make('request')->fullUrl(); | ||
| 156 | + $this->crawler = new Crawler($this->response->getContent(), $uri); | ||
| 157 | + return $this; | ||
| 158 | + } | ||
| 159 | + | ||
| 160 | + /** | ||
| 114 | * Click the text within the selected element. | 161 | * Click the text within the selected element. |
| 115 | * @param $parentElement | 162 | * @param $parentElement |
| 116 | * @param $linkText | 163 | * @param $linkText | ... | ... |
-
Please register or sign in to post a comment