Dan Brown

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);
......
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
1 +<?php namespace BookStack\Exceptions;
2 +
3 +
4 +use Exception;
5 +
6 +class PermissionsException extends Exception {}
...\ No newline at end of file ...\ No newline at end of file
...@@ -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
......
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
......
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 }
......
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
......
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
......
...@@ -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
......
...@@ -87,6 +87,9 @@ header { ...@@ -87,6 +87,9 @@ header {
87 padding-top: $-s; 87 padding-top: $-s;
88 } 88 }
89 } 89 }
90 + .dropdown-container {
91 + font-size: 0.9em;
92 + }
90 } 93 }
91 94
92 form.search-box { 95 form.search-box {
......
...@@ -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">
......
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">&raquo;</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>
......
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
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">&raquo;</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">&raquo;</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
......
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
......