Added restriction tests and fixed any bugs in the process
Also updated many styles within areas affected by the new permission and roles system.
Showing
26 changed files
with
267 additions
and
26 deletions
| ... | @@ -56,7 +56,8 @@ class Handler extends ExceptionHandler | ... | @@ -56,7 +56,8 @@ class Handler extends ExceptionHandler |
| 56 | // Which will include the basic message to point the user roughly to the cause. | 56 | // Which will include the basic message to point the user roughly to the cause. |
| 57 | if (($e instanceof PrettyException || $e->getPrevious() instanceof PrettyException) && !config('app.debug')) { | 57 | if (($e instanceof PrettyException || $e->getPrevious() instanceof PrettyException) && !config('app.debug')) { |
| 58 | $message = ($e instanceof PrettyException) ? $e->getMessage() : $e->getPrevious()->getMessage(); | 58 | $message = ($e instanceof PrettyException) ? $e->getMessage() : $e->getPrevious()->getMessage(); |
| 59 | - return response()->view('errors/500', ['message' => $message], 500); | 59 | + $code = ($e->getCode() === 0) ? 500 : $e->getCode(); |
| 60 | + return response()->view('errors/' . $code, ['message' => $message], $code); | ||
| 60 | } | 61 | } |
| 61 | 62 | ||
| 62 | return parent::render($request, $e); | 63 | return parent::render($request, $e); | ... | ... |
app/Exceptions/NotFoundException.php
0 → 100644
| 1 | +<?php namespace BookStack\Exceptions; | ||
| 2 | + | ||
| 3 | + | ||
| 4 | +class NotFoundException extends PrettyException { | ||
| 5 | + | ||
| 6 | + /** | ||
| 7 | + * NotFoundException constructor. | ||
| 8 | + * @param string $message | ||
| 9 | + */ | ||
| 10 | + public function __construct($message = 'Item not found') | ||
| 11 | + { | ||
| 12 | + parent::__construct($message, 404); | ||
| 13 | + } | ||
| 14 | +} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| ... | @@ -80,7 +80,14 @@ class ChapterController extends Controller | ... | @@ -80,7 +80,14 @@ class ChapterController extends Controller |
| 80 | $sidebarTree = $this->bookRepo->getChildren($book); | 80 | $sidebarTree = $this->bookRepo->getChildren($book); |
| 81 | Views::add($chapter); | 81 | Views::add($chapter); |
| 82 | $this->setPageTitle($chapter->getShortName()); | 82 | $this->setPageTitle($chapter->getShortName()); |
| 83 | - 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 | + ]); | ||
| 84 | } | 91 | } |
| 85 | 92 | ||
| 86 | /** | 93 | /** | ... | ... |
| 1 | <?php namespace BookStack\Http\Controllers; | 1 | <?php namespace BookStack\Http\Controllers; |
| 2 | 2 | ||
| 3 | use Activity; | 3 | use Activity; |
| 4 | +use BookStack\Exceptions\NotFoundException; | ||
| 4 | use BookStack\Repos\UserRepo; | 5 | use BookStack\Repos\UserRepo; |
| 5 | use BookStack\Services\ExportService; | 6 | use BookStack\Services\ExportService; |
| 6 | use Illuminate\Http\Request; | 7 | use Illuminate\Http\Request; |
| ... | @@ -94,7 +95,7 @@ class PageController extends Controller | ... | @@ -94,7 +95,7 @@ class PageController extends Controller |
| 94 | 95 | ||
| 95 | try { | 96 | try { |
| 96 | $page = $this->pageRepo->getBySlug($pageSlug, $book->id); | 97 | $page = $this->pageRepo->getBySlug($pageSlug, $book->id); |
| 97 | - } catch (NotFoundHttpException $e) { | 98 | + } catch (NotFoundException $e) { |
| 98 | $page = $this->pageRepo->findPageUsingOldSlug($pageSlug, $bookSlug); | 99 | $page = $this->pageRepo->findPageUsingOldSlug($pageSlug, $bookSlug); |
| 99 | if ($page === null) abort(404); | 100 | if ($page === null) abort(404); |
| 100 | return redirect($page->getUrl()); | 101 | return redirect($page->getUrl()); | ... | ... |
| 1 | <?php namespace BookStack\Repos; | 1 | <?php namespace BookStack\Repos; |
| 2 | 2 | ||
| 3 | use Activity; | 3 | use Activity; |
| 4 | +use BookStack\Exceptions\NotFoundException; | ||
| 4 | use BookStack\Services\RestrictionService; | 5 | use BookStack\Services\RestrictionService; |
| 5 | use Illuminate\Support\Str; | 6 | use Illuminate\Support\Str; |
| 6 | use BookStack\Book; | 7 | use BookStack\Book; |
| ... | @@ -111,11 +112,12 @@ class BookRepo | ... | @@ -111,11 +112,12 @@ class BookRepo |
| 111 | * Get a book by slug | 112 | * Get a book by slug |
| 112 | * @param $slug | 113 | * @param $slug |
| 113 | * @return mixed | 114 | * @return mixed |
| 115 | + * @throws NotFoundException | ||
| 114 | */ | 116 | */ |
| 115 | public function getBySlug($slug) | 117 | public function getBySlug($slug) |
| 116 | { | 118 | { |
| 117 | $book = $this->bookQuery()->where('slug', '=', $slug)->first(); | 119 | $book = $this->bookQuery()->where('slug', '=', $slug)->first(); |
| 118 | - if ($book === null) abort(404); | 120 | + if ($book === null) throw new NotFoundException('Book not found'); |
| 119 | return $book; | 121 | return $book; |
| 120 | } | 122 | } |
| 121 | 123 | ||
| ... | @@ -153,6 +155,7 @@ class BookRepo | ... | @@ -153,6 +155,7 @@ class BookRepo |
| 153 | $this->chapterRepo->destroy($chapter); | 155 | $this->chapterRepo->destroy($chapter); |
| 154 | } | 156 | } |
| 155 | $book->views()->delete(); | 157 | $book->views()->delete(); |
| 158 | + $book->restrictions()->delete(); | ||
| 156 | $book->delete(); | 159 | $book->delete(); |
| 157 | } | 160 | } |
| 158 | 161 | ||
| ... | @@ -210,11 +213,13 @@ class BookRepo | ... | @@ -210,11 +213,13 @@ class BookRepo |
| 210 | public function getChildren(Book $book) | 213 | public function getChildren(Book $book) |
| 211 | { | 214 | { |
| 212 | $pageQuery = $book->pages()->where('chapter_id', '=', 0); | 215 | $pageQuery = $book->pages()->where('chapter_id', '=', 0); |
| 213 | - $this->restrictionService->enforcePageRestrictions($pageQuery, 'view'); | 216 | + $pageQuery = $this->restrictionService->enforcePageRestrictions($pageQuery, 'view'); |
| 214 | $pages = $pageQuery->get(); | 217 | $pages = $pageQuery->get(); |
| 215 | 218 | ||
| 216 | - $chapterQuery = $book->chapters()->with('pages'); | 219 | + $chapterQuery = $book->chapters()->with(['pages' => function($query) { |
| 217 | - $this->restrictionService->enforceChapterRestrictions($chapterQuery, 'view'); | 220 | + $this->restrictionService->enforcePageRestrictions($query, 'view'); |
| 221 | + }]); | ||
| 222 | + $chapterQuery = $this->restrictionService->enforceChapterRestrictions($chapterQuery, 'view'); | ||
| 218 | $chapters = $chapterQuery->get(); | 223 | $chapters = $chapterQuery->get(); |
| 219 | $children = $pages->merge($chapters); | 224 | $children = $pages->merge($chapters); |
| 220 | $bookSlug = $book->slug; | 225 | $bookSlug = $book->slug; | ... | ... |
| ... | @@ -2,6 +2,7 @@ | ... | @@ -2,6 +2,7 @@ |
| 2 | 2 | ||
| 3 | 3 | ||
| 4 | use Activity; | 4 | use Activity; |
| 5 | +use BookStack\Exceptions\NotFoundException; | ||
| 5 | use BookStack\Services\RestrictionService; | 6 | use BookStack\Services\RestrictionService; |
| 6 | use Illuminate\Support\Str; | 7 | use Illuminate\Support\Str; |
| 7 | use BookStack\Chapter; | 8 | use BookStack\Chapter; |
| ... | @@ -66,15 +67,25 @@ class ChapterRepo | ... | @@ -66,15 +67,25 @@ class ChapterRepo |
| 66 | * @param $slug | 67 | * @param $slug |
| 67 | * @param $bookId | 68 | * @param $bookId |
| 68 | * @return mixed | 69 | * @return mixed |
| 70 | + * @throws NotFoundException | ||
| 69 | */ | 71 | */ |
| 70 | public function getBySlug($slug, $bookId) | 72 | public function getBySlug($slug, $bookId) |
| 71 | { | 73 | { |
| 72 | $chapter = $this->chapterQuery()->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first(); | 74 | $chapter = $this->chapterQuery()->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first(); |
| 73 | - if ($chapter === null) abort(404); | 75 | + if ($chapter === null) throw new NotFoundException('Chapter not found'); |
| 74 | return $chapter; | 76 | return $chapter; |
| 75 | } | 77 | } |
| 76 | 78 | ||
| 77 | /** | 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 | + /** | ||
| 78 | * Create a new chapter from request input. | 89 | * Create a new chapter from request input. |
| 79 | * @param $input | 90 | * @param $input |
| 80 | * @return $this | 91 | * @return $this |
| ... | @@ -98,6 +109,7 @@ class ChapterRepo | ... | @@ -98,6 +109,7 @@ class ChapterRepo |
| 98 | } | 109 | } |
| 99 | Activity::removeEntity($chapter); | 110 | Activity::removeEntity($chapter); |
| 100 | $chapter->views()->delete(); | 111 | $chapter->views()->delete(); |
| 112 | + $chapter->restrictions()->delete(); | ||
| 101 | $chapter->delete(); | 113 | $chapter->delete(); |
| 102 | } | 114 | } |
| 103 | 115 | ... | ... |
| ... | @@ -4,6 +4,7 @@ | ... | @@ -4,6 +4,7 @@ |
| 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; | ||
| 7 | use BookStack\Services\RestrictionService; | 8 | use BookStack\Services\RestrictionService; |
| 8 | use Illuminate\Http\Request; | 9 | use Illuminate\Http\Request; |
| 9 | use Illuminate\Support\Facades\Auth; | 10 | use Illuminate\Support\Facades\Auth; |
| ... | @@ -56,11 +57,12 @@ class PageRepo | ... | @@ -56,11 +57,12 @@ class PageRepo |
| 56 | * @param $slug | 57 | * @param $slug |
| 57 | * @param $bookId | 58 | * @param $bookId |
| 58 | * @return mixed | 59 | * @return mixed |
| 60 | + * @throws NotFoundException | ||
| 59 | */ | 61 | */ |
| 60 | public function getBySlug($slug, $bookId) | 62 | public function getBySlug($slug, $bookId) |
| 61 | { | 63 | { |
| 62 | $page = $this->pageQuery()->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first(); | 64 | $page = $this->pageQuery()->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first(); |
| 63 | - if ($page === null) throw new NotFoundHttpException('Page not found'); | 65 | + if ($page === null) throw new NotFoundException('Page not found'); |
| 64 | return $page; | 66 | return $page; |
| 65 | } | 67 | } |
| 66 | 68 | ||
| ... | @@ -373,6 +375,7 @@ class PageRepo | ... | @@ -373,6 +375,7 @@ class PageRepo |
| 373 | Activity::removeEntity($page); | 375 | Activity::removeEntity($page); |
| 374 | $page->views()->delete(); | 376 | $page->views()->delete(); |
| 375 | $page->revisions()->delete(); | 377 | $page->revisions()->delete(); |
| 378 | + $page->restrictions()->delete(); | ||
| 376 | $page->delete(); | 379 | $page->delete(); |
| 377 | } | 380 | } |
| 378 | 381 | ... | ... |
| ... | @@ -15,10 +15,16 @@ class RestrictionService | ... | @@ -15,10 +15,16 @@ class RestrictionService |
| 15 | public function __construct() | 15 | public function __construct() |
| 16 | { | 16 | { |
| 17 | $user = auth()->user(); | 17 | $user = auth()->user(); |
| 18 | - $this->userRoles = $user ? auth()->user()->roles->pluck('id') : false; | 18 | + $this->userRoles = $user ? auth()->user()->roles->pluck('id') : []; |
| 19 | $this->isAdmin = $user ? auth()->user()->hasRole('admin') : false; | 19 | $this->isAdmin = $user ? auth()->user()->hasRole('admin') : false; |
| 20 | } | 20 | } |
| 21 | 21 | ||
| 22 | + /** | ||
| 23 | + * Checks if an entity has a restriction set upon it. | ||
| 24 | + * @param Entity $entity | ||
| 25 | + * @param $action | ||
| 26 | + * @return bool | ||
| 27 | + */ | ||
| 22 | public function checkIfEntityRestricted(Entity $entity, $action) | 28 | public function checkIfEntityRestricted(Entity $entity, $action) |
| 23 | { | 29 | { |
| 24 | if ($this->isAdmin) return true; | 30 | if ($this->isAdmin) return true; |
| ... | @@ -93,12 +99,28 @@ class RestrictionService | ... | @@ -93,12 +99,28 @@ class RestrictionService |
| 93 | }); | 99 | }); |
| 94 | }); | 100 | }); |
| 95 | }) | 101 | }) |
| 102 | + // Page unrestricted, Has an unrestricted chapter & book has accepted restrictions | ||
| 103 | + ->orWhere(function ($query) { | ||
| 104 | + $query->where('restricted', '=', false) | ||
| 105 | + ->whereExists(function ($query) { | ||
| 106 | + $query->select('*')->from('chapters') | ||
| 107 | + ->whereRaw('chapters.id=pages.chapter_id')->where('restricted', '=', false); | ||
| 108 | + }) | ||
| 109 | + ->whereExists(function ($query) { | ||
| 110 | + $query->select('*')->from('books') | ||
| 111 | + ->whereRaw('books.id=pages.book_id') | ||
| 112 | + ->whereExists(function ($query) { | ||
| 113 | + $this->checkRestrictionsQuery($query, 'books', 'Book'); | ||
| 114 | + }); | ||
| 115 | + }); | ||
| 116 | + }) | ||
| 96 | // Page unrestricted, Has a chapter with accepted permissions | 117 | // Page unrestricted, Has a chapter with accepted permissions |
| 97 | ->orWhere(function ($query) { | 118 | ->orWhere(function ($query) { |
| 98 | $query->where('restricted', '=', false) | 119 | $query->where('restricted', '=', false) |
| 99 | ->whereExists(function ($query) { | 120 | ->whereExists(function ($query) { |
| 100 | $query->select('*')->from('chapters') | 121 | $query->select('*')->from('chapters') |
| 101 | ->whereRaw('chapters.id=pages.chapter_id') | 122 | ->whereRaw('chapters.id=pages.chapter_id') |
| 123 | + ->where('restricted', '=', true) | ||
| 102 | ->whereExists(function ($query) { | 124 | ->whereExists(function ($query) { |
| 103 | $this->checkRestrictionsQuery($query, 'chapters', 'Chapter'); | 125 | $this->checkRestrictionsQuery($query, 'chapters', 'Chapter'); |
| 104 | }); | 126 | }); |
| ... | @@ -183,10 +205,12 @@ class RestrictionService | ... | @@ -183,10 +205,12 @@ class RestrictionService |
| 183 | return $query->where(function ($parentWhereQuery) { | 205 | return $query->where(function ($parentWhereQuery) { |
| 184 | $parentWhereQuery | 206 | $parentWhereQuery |
| 185 | ->where('restricted', '=', false) | 207 | ->where('restricted', '=', false) |
| 186 | - ->orWhereExists(function ($query) { | 208 | + ->orWhere(function ($query) { |
| 209 | + $query->where('restricted', '=', true)->whereExists(function ($query) { | ||
| 187 | $this->checkRestrictionsQuery($query, 'books', 'Book'); | 210 | $this->checkRestrictionsQuery($query, 'books', 'Book'); |
| 188 | }); | 211 | }); |
| 189 | }); | 212 | }); |
| 213 | + }); | ||
| 190 | } | 214 | } |
| 191 | 215 | ||
| 192 | /** | 216 | /** | ... | ... |
| 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 | * |
| ... | @@ -39,6 +39,7 @@ if (! function_exists('versioned_asset')) { | ... | @@ -39,6 +39,7 @@ if (! function_exists('versioned_asset')) { |
| 39 | */ | 39 | */ |
| 40 | function userCan($permission, \BookStack\Ownable $ownable = null) | 40 | function userCan($permission, \BookStack\Ownable $ownable = null) |
| 41 | { | 41 | { |
| 42 | + if (!auth()->check()) return false; | ||
| 42 | if ($ownable === null) { | 43 | if ($ownable === null) { |
| 43 | return auth()->user() && auth()->user()->can($permission); | 44 | return auth()->user() && auth()->user()->can($permission); |
| 44 | } | 45 | } |
| ... | @@ -47,9 +48,9 @@ function userCan($permission, \BookStack\Ownable $ownable = null) | ... | @@ -47,9 +48,9 @@ function userCan($permission, \BookStack\Ownable $ownable = null) |
| 47 | $permissionBaseName = strtolower($permission) . '-'; | 48 | $permissionBaseName = strtolower($permission) . '-'; |
| 48 | $hasPermission = false; | 49 | $hasPermission = false; |
| 49 | if (auth()->user()->can($permissionBaseName . 'all')) $hasPermission = true; | 50 | if (auth()->user()->can($permissionBaseName . 'all')) $hasPermission = true; |
| 50 | - if (auth()->user()->can($permissionBaseName . 'own') && $ownable->createdBy->id === auth()->user()->id) $hasPermission = true; | 51 | + if (auth()->user()->can($permissionBaseName . 'own') && $ownable->createdBy && $ownable->createdBy->id === auth()->user()->id) $hasPermission = true; |
| 51 | 52 | ||
| 52 | - if(!$ownable instanceof \BookStack\Entity) return $hasPermission; | 53 | + if (!$ownable instanceof \BookStack\Entity) return $hasPermission; |
| 53 | 54 | ||
| 54 | // Check restrictions on the entitiy | 55 | // Check restrictions on the entitiy |
| 55 | $restrictionService = app('BookStack\Services\RestrictionService'); | 56 | $restrictionService = app('BookStack\Services\RestrictionService'); | ... | ... |
| ... | @@ -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"/> | ... | ... |
| ... | @@ -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; | ... | ... |
| ... | @@ -30,8 +30,10 @@ | ... | @@ -30,8 +30,10 @@ |
| 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 | + @if(userCan('books-create-all')) | ||
| 33 | <a href="/books/create" class="text-pos"><i class="zmdi zmdi-edit"></i>Create one now</a> | 34 | <a href="/books/create" class="text-pos"><i class="zmdi zmdi-edit"></i>Create one now</a> |
| 34 | @endif | 35 | @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"> |
| 37 | <div id="recents"> | 39 | <div id="recents"> | ... | ... |
| ... | @@ -2,6 +2,19 @@ | ... | @@ -2,6 +2,19 @@ |
| 2 | 2 | ||
| 3 | @section('content') | 3 | @section('content') |
| 4 | 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 | + | ||
| 5 | <div class="container" ng-non-bindable> | 18 | <div class="container" ng-non-bindable> |
| 6 | <h1>Book Restrictions</h1> | 19 | <h1>Book Restrictions</h1> |
| 7 | @include('form/restriction-form', ['model' => $book]) | 20 | @include('form/restriction-form', ['model' => $book]) | ... | ... |
| ... | @@ -2,7 +2,7 @@ | ... | @@ -2,7 +2,7 @@ |
| 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"> |
| ... | @@ -15,13 +15,22 @@ | ... | @@ -15,13 +15,22 @@ |
| 15 | @endif | 15 | @endif |
| 16 | @if(userCan('book-update', $book)) | 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> | 18 | + @endif |
| 19 | + @if(userCan('book-update', $book) || userCan('restrictions-manage', $book) || userCan('book-delete', $book)) | ||
| 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> | ||
| 19 | @endif | 25 | @endif |
| 20 | @if(userCan('restrictions-manage', $book)) | 26 | @if(userCan('restrictions-manage', $book)) |
| 21 | - <a href="{{$book->getUrl()}}/restrict" class="text-primary text-button"><i class="zmdi zmdi-lock-outline"></i>Restrict</a> | 27 | + <li><a href="{{$book->getUrl()}}/restrict" class="text-primary"><i class="zmdi zmdi-lock-outline"></i>Restrict</a></li> |
| 22 | @endif | 28 | @endif |
| 23 | @if(userCan('book-delete', $book)) | 29 | @if(userCan('book-delete', $book)) |
| 24 | - <a href="{{ $book->getUrl() }}/delete" class="text-neg text-button"><i class="zmdi zmdi-delete"></i>Delete</a> | 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> | ||
| 25 | @endif | 34 | @endif |
| 26 | </div> | 35 | </div> |
| 27 | </div> | 36 | </div> |
| ... | @@ -78,6 +87,15 @@ | ... | @@ -78,6 +87,15 @@ |
| 78 | 87 | ||
| 79 | <div class="col-md-4 col-md-offset-1"> | 88 | <div class="col-md-4 col-md-offset-1"> |
| 80 | <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 | ||
| 81 | <div class="search-box"> | 99 | <div class="search-box"> |
| 82 | <form ng-submit="searchBook($event)"> | 100 | <form ng-submit="searchBook($event)"> |
| 83 | <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"> | ... | ... |
| ... | @@ -2,6 +2,20 @@ | ... | @@ -2,6 +2,20 @@ |
| 2 | 2 | ||
| 3 | @section('content') | 3 | @section('content') |
| 4 | 4 | ||
| 5 | + <div class="faded-small toolbar"> | ||
| 6 | + <div class="container"> | ||
| 7 | + <div class="row"> | ||
| 8 | + <div class="col-sm-12 faded"> | ||
| 9 | + <div class="breadcrumbs"> | ||
| 10 | + <a href="{{$chapter->book->getUrl()}}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $chapter->book->getShortName() }}</a> | ||
| 11 | + <span class="sep">»</span> | ||
| 12 | + <a href="{{ $chapter->getUrl() }}" class="text-chapter text-button"><i class="zmdi zmdi-collection-bookmark"></i>{{$chapter->getShortName()}}</a> | ||
| 13 | + </div> | ||
| 14 | + </div> | ||
| 15 | + </div> | ||
| 16 | + </div> | ||
| 17 | + </div> | ||
| 18 | + | ||
| 5 | <div class="container" ng-non-bindable> | 19 | <div class="container" ng-non-bindable> |
| 6 | <h1>Chapter Restrictions</h1> | 20 | <h1>Chapter Restrictions</h1> |
| 7 | @include('form/restriction-form', ['model' => $chapter]) | 21 | @include('form/restriction-form', ['model' => $chapter]) | ... | ... |
| ... | @@ -37,10 +37,10 @@ | ... | @@ -37,10 +37,10 @@ |
| 37 | <h1>{{ $chapter->name }}</h1> | 37 | <h1>{{ $chapter->name }}</h1> |
| 38 | <p class="text-muted">{{ $chapter->description }}</p> | 38 | <p class="text-muted">{{ $chapter->description }}</p> |
| 39 | 39 | ||
| 40 | - @if(count($chapter->pages) > 0) | 40 | + @if(count($pages) > 0) |
| 41 | <div class="page-list"> | 41 | <div class="page-list"> |
| 42 | <hr> | 42 | <hr> |
| 43 | - @foreach($chapter->pages as $page) | 43 | + @foreach($pages as $page) |
| 44 | @include('pages/list-item', ['page' => $page]) | 44 | @include('pages/list-item', ['page' => $page]) |
| 45 | <hr> | 45 | <hr> |
| 46 | @endforeach | 46 | @endforeach |
| ... | @@ -63,6 +63,29 @@ | ... | @@ -63,6 +63,29 @@ |
| 63 | </p> | 63 | </p> |
| 64 | </div> | 64 | </div> |
| 65 | <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 | + | ||
| 66 | @include('pages/sidebar-tree-list', ['book' => $book, 'sidebarTree' => $sidebarTree]) | 89 | @include('pages/sidebar-tree-list', ['book' => $book, 'sidebarTree' => $sidebarTree]) |
| 67 | </div> | 90 | </div> |
| 68 | </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> | ... | ... |
| ... | @@ -3,7 +3,7 @@ | ... | @@ -3,7 +3,7 @@ |
| 3 | <input type="hidden" name="_method" value="PUT"> | 3 | <input type="hidden" name="_method" value="PUT"> |
| 4 | 4 | ||
| 5 | <div class="form-group"> | 5 | <div class="form-group"> |
| 6 | - @include('form/checkbox', ['name' => 'restricted', 'label' => 'Restrict this page?']) | 6 | + @include('form/checkbox', ['name' => 'restricted', 'label' => 'Restrict this ' . $model->getClassName()]) |
| 7 | </div> | 7 | </div> |
| 8 | 8 | ||
| 9 | <table class="table"> | 9 | <table class="table"> |
| ... | @@ -24,5 +24,6 @@ | ... | @@ -24,5 +24,6 @@ |
| 24 | @endforeach | 24 | @endforeach |
| 25 | </table> | 25 | </table> |
| 26 | 26 | ||
| 27 | + <a href="{{ $model->getUrl() }}" class="button muted">Cancel</a> | ||
| 27 | <button type="submit" class="button pos">Save Restrictions</button> | 28 | <button type="submit" class="button pos">Save Restrictions</button> |
| 28 | </form> | 29 | </form> |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| ... | @@ -2,6 +2,27 @@ | ... | @@ -2,6 +2,27 @@ |
| 2 | 2 | ||
| 3 | @section('content') | 3 | @section('content') |
| 4 | 4 | ||
| 5 | + <div class="faded-small toolbar"> | ||
| 6 | + <div class="container"> | ||
| 7 | + <div class="row"> | ||
| 8 | + <div class="col-sm-12 faded"> | ||
| 9 | + <div class="breadcrumbs"> | ||
| 10 | + <a href="{{$page->book->getUrl()}}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $page->book->getShortName() }}</a> | ||
| 11 | + @if($page->hasChapter()) | ||
| 12 | + <span class="sep">»</span> | ||
| 13 | + <a href="{{ $page->chapter->getUrl() }}" class="text-chapter text-button"> | ||
| 14 | + <i class="zmdi zmdi-collection-bookmark"></i> | ||
| 15 | + {{$page->chapter->getShortName()}} | ||
| 16 | + </a> | ||
| 17 | + @endif | ||
| 18 | + <span class="sep">»</span> | ||
| 19 | + <a href="{{$page->getUrl()}}" class="text-book text-button"><i class="zmdi zmdi-file"></i>{{ $page->getShortName() }}</a> | ||
| 20 | + </div> | ||
| 21 | + </div> | ||
| 22 | + </div> | ||
| 23 | + </div> | ||
| 24 | + </div> | ||
| 25 | + | ||
| 5 | <div class="container" ng-non-bindable> | 26 | <div class="container" ng-non-bindable> |
| 6 | <h1>Page Restrictions</h1> | 27 | <h1>Page Restrictions</h1> |
| 7 | @include('form/restriction-form', ['model' => $page]) | 28 | @include('form/restriction-form', ['model' => $page]) | ... | ... |
| ... | @@ -70,7 +70,38 @@ | ... | @@ -70,7 +70,38 @@ |
| 70 | </div> | 70 | </div> |
| 71 | </div> | 71 | </div> |
| 72 | <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"> | ||
| 73 | 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 | ||
| 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 | ||
| 74 | @include('pages/sidebar-tree-list', ['book' => $book, 'sidebarTree' => $sidebarTree]) | 105 | @include('pages/sidebar-tree-list', ['book' => $book, 'sidebarTree' => $sidebarTree]) |
| 75 | 106 | ||
| 76 | </div> | 107 | </div> | ... | ... |
| ... | @@ -3,6 +3,7 @@ | ... | @@ -3,6 +3,7 @@ |
| 3 | <div class="row"> | 3 | <div class="row"> |
| 4 | 4 | ||
| 5 | <div class="col-md-6"> | 5 | <div class="col-md-6"> |
| 6 | + <h3>Role Details</h3> | ||
| 6 | <div class="form-group"> | 7 | <div class="form-group"> |
| 7 | <label for="name">Role Name</label> | 8 | <label for="name">Role Name</label> |
| 8 | @include('form/text', ['name' => 'display_name']) | 9 | @include('form/text', ['name' => 'display_name']) |
| ... | @@ -11,7 +12,7 @@ | ... | @@ -11,7 +12,7 @@ |
| 11 | <label for="name">Short Role Description</label> | 12 | <label for="name">Short Role Description</label> |
| 12 | @include('form/text', ['name' => 'description']) | 13 | @include('form/text', ['name' => 'description']) |
| 13 | </div> | 14 | </div> |
| 14 | - <hr class="even"> | 15 | + <h3>System Permissions</h3> |
| 15 | <div class="row"> | 16 | <div class="row"> |
| 16 | <div class="col-md-6"> | 17 | <div class="col-md-6"> |
| 17 | <label> @include('settings/roles/checkbox', ['permission' => 'users-manage']) Manage users</label> | 18 | <label> @include('settings/roles/checkbox', ['permission' => 'users-manage']) Manage users</label> |
| ... | @@ -33,10 +34,17 @@ | ... | @@ -33,10 +34,17 @@ |
| 33 | <div class="form-group"> | 34 | <div class="form-group"> |
| 34 | <label>@include('settings/roles/checkbox', ['permission' => 'settings-manage']) Manage app settings</label> | 35 | <label>@include('settings/roles/checkbox', ['permission' => 'settings-manage']) Manage app settings</label> |
| 35 | </div> | 36 | </div> |
| 37 | + <hr class="even"> | ||
| 36 | 38 | ||
| 37 | </div> | 39 | </div> |
| 38 | 40 | ||
| 39 | <div class="col-md-6"> | 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> | ||
| 40 | <table class="table"> | 48 | <table class="table"> |
| 41 | <tr> | 49 | <tr> |
| 42 | <th></th> | 50 | <th></th> |
| ... | @@ -104,4 +112,6 @@ | ... | @@ -104,4 +112,6 @@ |
| 104 | </div> | 112 | </div> |
| 105 | 113 | ||
| 106 | </div> | 114 | </div> |
| 115 | + | ||
| 116 | +<a href="/settings/roles" class="button muted">Cancel</a> | ||
| 107 | <button type="submit" class="button pos">Save Role</button> | 117 | <button type="submit" class="button pos">Save Role</button> |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
tests/RestrictionsTest.php
0 → 100644
This diff is collapsed.
Click to expand it.
| 1 | <?php | 1 | <?php |
| 2 | 2 | ||
| 3 | use Illuminate\Foundation\Testing\DatabaseTransactions; | 3 | use Illuminate\Foundation\Testing\DatabaseTransactions; |
| 4 | +use Symfony\Component\DomCrawler\Crawler; | ||
| 4 | 5 | ||
| 5 | class TestCase extends Illuminate\Foundation\Testing\TestCase | 6 | class TestCase extends Illuminate\Foundation\Testing\TestCase |
| 6 | { | 7 | { |
| ... | @@ -123,6 +124,40 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase | ... | @@ -123,6 +124,40 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase |
| 123 | } | 124 | } |
| 124 | 125 | ||
| 125 | /** | 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 | + /** | ||
| 126 | * Click the text within the selected element. | 161 | * Click the text within the selected element. |
| 127 | * @param $parentElement | 162 | * @param $parentElement |
| 128 | * @param $linkText | 163 | * @param $linkText | ... | ... |
-
Please register or sign in to post a comment