]> BookStack Code Mirror - bookstack/blob - app/Auth/Permissions/PermissionService.php
Merge branch 'Copy-For-View-Only' of git://github.com/mark-james/BookStack into mark...
[bookstack] / app / Auth / Permissions / PermissionService.php
1 <?php namespace BookStack\Auth\Permissions;
2
3 use BookStack\Auth\Permissions;
4 use BookStack\Auth\Role;
5 use BookStack\Entities\Book;
6 use BookStack\Entities\Bookshelf;
7 use BookStack\Entities\Chapter;
8 use BookStack\Entities\Entity;
9 use BookStack\Entities\EntityProvider;
10 use BookStack\Entities\Page;
11 use BookStack\Ownable;
12 use Illuminate\Database\Connection;
13 use Illuminate\Database\Eloquent\Builder;
14 use Illuminate\Database\Query\Builder as QueryBuilder;
15 use Illuminate\Support\Collection;
16
17 class PermissionService
18 {
19
20     protected $currentAction;
21     protected $isAdminUser;
22     protected $userRoles = false;
23     protected $currentUserModel = false;
24
25     /**
26      * @var Connection
27      */
28     protected $db;
29
30     /**
31      * @var JointPermission
32      */
33     protected $jointPermission;
34
35     /**
36      * @var Role
37      */
38     protected $role;
39
40     /**
41      * @var EntityPermission
42      */
43     protected $entityPermission;
44
45     /**
46      * @var EntityProvider
47      */
48     protected $entityProvider;
49
50     protected $entityCache;
51
52     /**
53      * PermissionService constructor.
54      * @param JointPermission $jointPermission
55      * @param EntityPermission $entityPermission
56      * @param Role $role
57      * @param Connection $db
58      * @param EntityProvider $entityProvider
59      */
60     public function __construct(
61         JointPermission $jointPermission,
62         Permissions\EntityPermission $entityPermission,
63         Role $role,
64         Connection $db,
65         EntityProvider $entityProvider
66     ) {
67         $this->db = $db;
68         $this->jointPermission = $jointPermission;
69         $this->entityPermission = $entityPermission;
70         $this->role = $role;
71         $this->entityProvider = $entityProvider;
72     }
73
74     /**
75      * Set the database connection
76      * @param Connection $connection
77      */
78     public function setConnection(Connection $connection)
79     {
80         $this->db = $connection;
81     }
82
83     /**
84      * Prepare the local entity cache and ensure it's empty
85      * @param \BookStack\Entities\Entity[] $entities
86      */
87     protected function readyEntityCache($entities = [])
88     {
89         $this->entityCache = [];
90
91         foreach ($entities as $entity) {
92             $type = $entity->getType();
93             if (!isset($this->entityCache[$type])) {
94                 $this->entityCache[$type] = collect();
95             }
96             $this->entityCache[$type]->put($entity->id, $entity);
97         }
98     }
99
100     /**
101      * Get a book via ID, Checks local cache
102      * @param $bookId
103      * @return Book
104      */
105     protected function getBook($bookId)
106     {
107         if (isset($this->entityCache['book']) && $this->entityCache['book']->has($bookId)) {
108             return $this->entityCache['book']->get($bookId);
109         }
110
111         $book = $this->entityProvider->book->find($bookId);
112         if ($book === null) {
113             $book = false;
114         }
115
116         return $book;
117     }
118
119     /**
120      * Get a chapter via ID, Checks local cache
121      * @param $chapterId
122      * @return \BookStack\Entities\Book
123      */
124     protected function getChapter($chapterId)
125     {
126         if (isset($this->entityCache['chapter']) && $this->entityCache['chapter']->has($chapterId)) {
127             return $this->entityCache['chapter']->get($chapterId);
128         }
129
130         $chapter = $this->entityProvider->chapter->find($chapterId);
131         if ($chapter === null) {
132             $chapter = false;
133         }
134
135         return $chapter;
136     }
137
138     /**
139      * Get the roles for the current user;
140      * @return array|bool
141      */
142     protected function getRoles()
143     {
144         if ($this->userRoles !== false) {
145             return $this->userRoles;
146         }
147
148         $roles = [];
149
150         if (auth()->guest()) {
151             $roles[] = $this->role->getSystemRole('public')->id;
152             return $roles;
153         }
154
155
156         foreach ($this->currentUser()->roles as $role) {
157             $roles[] = $role->id;
158         }
159         return $roles;
160     }
161
162     /**
163      * Re-generate all entity permission from scratch.
164      */
165     public function buildJointPermissions()
166     {
167         $this->jointPermission->truncate();
168         $this->readyEntityCache();
169
170         // Get all roles (Should be the most limited dimension)
171         $roles = $this->role->with('permissions')->get()->all();
172
173         // Chunk through all books
174         $this->bookFetchQuery()->chunk(5, function ($books) use ($roles) {
175             $this->buildJointPermissionsForBooks($books, $roles);
176         });
177
178         // Chunk through all bookshelves
179         $this->entityProvider->bookshelf->newQuery()->select(['id', 'restricted', 'created_by'])
180             ->chunk(50, function ($shelves) use ($roles) {
181                 $this->buildJointPermissionsForShelves($shelves, $roles);
182             });
183     }
184
185     /**
186      * Get a query for fetching a book with it's children.
187      * @return QueryBuilder
188      */
189     protected function bookFetchQuery()
190     {
191         return $this->entityProvider->book->newQuery()
192             ->select(['id', 'restricted', 'created_by'])->with(['chapters' => function ($query) {
193                 $query->select(['id', 'restricted', 'created_by', 'book_id']);
194             }, 'pages'  => function ($query) {
195                 $query->select(['id', 'restricted', 'created_by', 'book_id', 'chapter_id']);
196             }]);
197     }
198
199     /**
200      * @param Collection $shelves
201      * @param array $roles
202      * @param bool $deleteOld
203      * @throws \Throwable
204      */
205     protected function buildJointPermissionsForShelves($shelves, $roles, $deleteOld = false)
206     {
207         if ($deleteOld) {
208             $this->deleteManyJointPermissionsForEntities($shelves->all());
209         }
210         $this->createManyJointPermissions($shelves, $roles);
211     }
212
213     /**
214      * Build joint permissions for an array of books
215      * @param Collection $books
216      * @param array $roles
217      * @param bool $deleteOld
218      * @throws \Throwable
219      */
220     protected function buildJointPermissionsForBooks($books, $roles, $deleteOld = false)
221     {
222         $entities = clone $books;
223
224         /** @var Book $book */
225         foreach ($books->all() as $book) {
226             foreach ($book->getRelation('chapters') as $chapter) {
227                 $entities->push($chapter);
228             }
229             foreach ($book->getRelation('pages') as $page) {
230                 $entities->push($page);
231             }
232         }
233
234         if ($deleteOld) {
235             $this->deleteManyJointPermissionsForEntities($entities->all());
236         }
237         $this->createManyJointPermissions($entities, $roles);
238     }
239
240     /**
241      * Rebuild the entity jointPermissions for a particular entity.
242      * @param \BookStack\Entities\Entity $entity
243      * @throws \Throwable
244      */
245     public function buildJointPermissionsForEntity(Entity $entity)
246     {
247         $entities = [$entity];
248         if ($entity->isA('book')) {
249             $books = $this->bookFetchQuery()->where('id', '=', $entity->id)->get();
250             $this->buildJointPermissionsForBooks($books, $this->role->newQuery()->get(), true);
251             return;
252         }
253
254         if ($entity->book) {
255             $entities[] = $entity->book;
256         }
257
258         if ($entity->isA('page') && $entity->chapter_id) {
259             $entities[] = $entity->chapter;
260         }
261
262         if ($entity->isA('chapter')) {
263             foreach ($entity->pages as $page) {
264                 $entities[] = $page;
265             }
266         }
267
268         $this->buildJointPermissionsForEntities(collect($entities));
269     }
270
271     /**
272      * Rebuild the entity jointPermissions for a collection of entities.
273      * @param Collection $entities
274      * @throws \Throwable
275      */
276     public function buildJointPermissionsForEntities(Collection $entities)
277     {
278         $roles = $this->role->newQuery()->get();
279         $this->deleteManyJointPermissionsForEntities($entities->all());
280         $this->createManyJointPermissions($entities, $roles);
281     }
282
283     /**
284      * Build the entity jointPermissions for a particular role.
285      * @param Role $role
286      */
287     public function buildJointPermissionForRole(Role $role)
288     {
289         $roles = [$role];
290         $this->deleteManyJointPermissionsForRoles($roles);
291
292         // Chunk through all books
293         $this->bookFetchQuery()->chunk(20, function ($books) use ($roles) {
294             $this->buildJointPermissionsForBooks($books, $roles);
295         });
296
297         // Chunk through all bookshelves
298         $this->entityProvider->bookshelf->newQuery()->select(['id', 'restricted', 'created_by'])
299             ->chunk(50, function ($shelves) use ($roles) {
300                 $this->buildJointPermissionsForShelves($shelves, $roles);
301             });
302     }
303
304     /**
305      * Delete the entity jointPermissions attached to a particular role.
306      * @param Role $role
307      */
308     public function deleteJointPermissionsForRole(Role $role)
309     {
310         $this->deleteManyJointPermissionsForRoles([$role]);
311     }
312
313     /**
314      * Delete all of the entity jointPermissions for a list of entities.
315      * @param Role[] $roles
316      */
317     protected function deleteManyJointPermissionsForRoles($roles)
318     {
319         $roleIds = array_map(function ($role) {
320             return $role->id;
321         }, $roles);
322         $this->jointPermission->newQuery()->whereIn('role_id', $roleIds)->delete();
323     }
324
325     /**
326      * Delete the entity jointPermissions for a particular entity.
327      * @param Entity $entity
328      * @throws \Throwable
329      */
330     public function deleteJointPermissionsForEntity(Entity $entity)
331     {
332         $this->deleteManyJointPermissionsForEntities([$entity]);
333     }
334
335     /**
336      * Delete all of the entity jointPermissions for a list of entities.
337      * @param \BookStack\Entities\Entity[] $entities
338      * @throws \Throwable
339      */
340     protected function deleteManyJointPermissionsForEntities($entities)
341     {
342         if (count($entities) === 0) {
343             return;
344         }
345
346         $this->db->transaction(function () use ($entities) {
347
348             foreach (array_chunk($entities, 1000) as $entityChunk) {
349                 $query = $this->db->table('joint_permissions');
350                 foreach ($entityChunk as $entity) {
351                     $query->orWhere(function (QueryBuilder $query) use ($entity) {
352                         $query->where('entity_id', '=', $entity->id)
353                             ->where('entity_type', '=', $entity->getMorphClass());
354                     });
355                 }
356                 $query->delete();
357             }
358         });
359     }
360
361     /**
362      * Create & Save entity jointPermissions for many entities and jointPermissions.
363      * @param Collection $entities
364      * @param array $roles
365      * @throws \Throwable
366      */
367     protected function createManyJointPermissions($entities, $roles)
368     {
369         $this->readyEntityCache($entities);
370         $jointPermissions = [];
371
372         // Fetch Entity Permissions and create a mapping of entity restricted statuses
373         $entityRestrictedMap = [];
374         $permissionFetch = $this->entityPermission->newQuery();
375         foreach ($entities as $entity) {
376             $entityRestrictedMap[$entity->getMorphClass() . ':' . $entity->id] = boolval($entity->getRawAttribute('restricted'));
377             $permissionFetch->orWhere(function ($query) use ($entity) {
378                 $query->where('restrictable_id', '=', $entity->id)->where('restrictable_type', '=', $entity->getMorphClass());
379             });
380         }
381         $permissions = $permissionFetch->get();
382
383         // Create a mapping of explicit entity permissions
384         $permissionMap = [];
385         foreach ($permissions as $permission) {
386             $key = $permission->restrictable_type . ':' . $permission->restrictable_id . ':' . $permission->role_id . ':' . $permission->action;
387             $isRestricted = $entityRestrictedMap[$permission->restrictable_type . ':' . $permission->restrictable_id];
388             $permissionMap[$key] = $isRestricted;
389         }
390
391         // Create a mapping of role permissions
392         $rolePermissionMap = [];
393         foreach ($roles as $role) {
394             foreach ($role->permissions as $permission) {
395                 $rolePermissionMap[$role->getRawAttribute('id') . ':' . $permission->getRawAttribute('name')] = true;
396             }
397         }
398
399         // Create Joint Permission Data
400         foreach ($entities as $entity) {
401             foreach ($roles as $role) {
402                 foreach ($this->getActions($entity) as $action) {
403                     $jointPermissions[] = $this->createJointPermissionData($entity, $role, $action, $permissionMap, $rolePermissionMap);
404                 }
405             }
406         }
407
408         $this->db->transaction(function () use ($jointPermissions) {
409             foreach (array_chunk($jointPermissions, 1000) as $jointPermissionChunk) {
410                 $this->db->table('joint_permissions')->insert($jointPermissionChunk);
411             }
412         });
413     }
414
415
416     /**
417      * Get the actions related to an entity.
418      * @param \BookStack\Entities\Entity $entity
419      * @return array
420      */
421     protected function getActions(Entity $entity)
422     {
423         $baseActions = ['view', 'update', 'delete'];
424         if ($entity->isA('chapter') || $entity->isA('book')) {
425             $baseActions[] = 'page-create';
426         }
427         if ($entity->isA('book')) {
428             $baseActions[] = 'chapter-create';
429         }
430         return $baseActions;
431     }
432
433     /**
434      * Create entity permission data for an entity and role
435      * for a particular action.
436      * @param Entity $entity
437      * @param Role $role
438      * @param string $action
439      * @param array $permissionMap
440      * @param array $rolePermissionMap
441      * @return array
442      */
443     protected function createJointPermissionData(Entity $entity, Role $role, $action, $permissionMap, $rolePermissionMap)
444     {
445         $permissionPrefix = (strpos($action, '-') === false ? ($entity->getType() . '-') : '') . $action;
446         $roleHasPermission = isset($rolePermissionMap[$role->getRawAttribute('id') . ':' . $permissionPrefix . '-all']);
447         $roleHasPermissionOwn = isset($rolePermissionMap[$role->getRawAttribute('id') . ':' . $permissionPrefix . '-own']);
448         $explodedAction = explode('-', $action);
449         $restrictionAction = end($explodedAction);
450
451         if ($role->system_name === 'admin') {
452             return $this->createJointPermissionDataArray($entity, $role, $action, true, true);
453         }
454
455         if ($entity->restricted) {
456             $hasAccess = $this->mapHasActiveRestriction($permissionMap, $entity, $role, $restrictionAction);
457             return $this->createJointPermissionDataArray($entity, $role, $action, $hasAccess, $hasAccess);
458         }
459
460         if ($entity->isA('book') || $entity->isA('bookshelf')) {
461             return $this->createJointPermissionDataArray($entity, $role, $action, $roleHasPermission, $roleHasPermissionOwn);
462         }
463
464         // For chapters and pages, Check if explicit permissions are set on the Book.
465         $book = $this->getBook($entity->book_id);
466         $hasExplicitAccessToParents = $this->mapHasActiveRestriction($permissionMap, $book, $role, $restrictionAction);
467         $hasPermissiveAccessToParents = !$book->restricted;
468
469         // For pages with a chapter, Check if explicit permissions are set on the Chapter
470         if ($entity->isA('page') && $entity->chapter_id !== 0 && $entity->chapter_id !== '0') {
471             $chapter = $this->getChapter($entity->chapter_id);
472             $hasPermissiveAccessToParents = $hasPermissiveAccessToParents && !$chapter->restricted;
473             if ($chapter->restricted) {
474                 $hasExplicitAccessToParents = $this->mapHasActiveRestriction($permissionMap, $chapter, $role, $restrictionAction);
475             }
476         }
477
478         return $this->createJointPermissionDataArray(
479             $entity,
480             $role,
481             $action,
482             ($hasExplicitAccessToParents || ($roleHasPermission && $hasPermissiveAccessToParents)),
483             ($hasExplicitAccessToParents || ($roleHasPermissionOwn && $hasPermissiveAccessToParents))
484         );
485     }
486
487     /**
488      * Check for an active restriction in an entity map.
489      * @param $entityMap
490      * @param Entity $entity
491      * @param Role $role
492      * @param $action
493      * @return bool
494      */
495     protected function mapHasActiveRestriction($entityMap, Entity $entity, Role $role, $action)
496     {
497         $key = $entity->getMorphClass() . ':' . $entity->getRawAttribute('id') . ':' . $role->getRawAttribute('id') . ':' . $action;
498         return isset($entityMap[$key]) ? $entityMap[$key] : false;
499     }
500
501     /**
502      * Create an array of data with the information of an entity jointPermissions.
503      * Used to build data for bulk insertion.
504      * @param \BookStack\Entities\Entity $entity
505      * @param Role $role
506      * @param $action
507      * @param $permissionAll
508      * @param $permissionOwn
509      * @return array
510      */
511     protected function createJointPermissionDataArray(Entity $entity, Role $role, $action, $permissionAll, $permissionOwn)
512     {
513         return [
514             'role_id'            => $role->getRawAttribute('id'),
515             'entity_id'          => $entity->getRawAttribute('id'),
516             'entity_type'        => $entity->getMorphClass(),
517             'action'             => $action,
518             'has_permission'     => $permissionAll,
519             'has_permission_own' => $permissionOwn,
520             'created_by'         => $entity->getRawAttribute('created_by')
521         ];
522     }
523
524     /**
525      * Checks if an entity has a restriction set upon it.
526      * @param Ownable $ownable
527      * @param $permission
528      * @return bool
529      */
530     public function checkOwnableUserAccess(Ownable $ownable, $permission)
531     {
532         $explodedPermission = explode('-', $permission);
533
534         $baseQuery = $ownable->where('id', '=', $ownable->id);
535         $action = end($explodedPermission);
536         $this->currentAction = $action;
537
538         $nonJointPermissions = ['restrictions', 'image', 'attachment', 'comment'];
539
540         // Handle non entity specific jointPermissions
541         if (in_array($explodedPermission[0], $nonJointPermissions)) {
542             $allPermission = $this->currentUser() && $this->currentUser()->can($permission . '-all');
543             $ownPermission = $this->currentUser() && $this->currentUser()->can($permission . '-own');
544             $this->currentAction = 'view';
545             $isOwner = $this->currentUser() && $this->currentUser()->id === $ownable->created_by;
546             return ($allPermission || ($isOwner && $ownPermission));
547         }
548
549         // Handle abnormal create jointPermissions
550         if ($action === 'create') {
551             $this->currentAction = $permission;
552         }
553
554         $q = $this->entityRestrictionQuery($baseQuery)->count() > 0;
555         $this->clean();
556         return $q;
557     }
558
559     /**
560      * Checks if a user has a book or chapter available to create a page
561      * @param Ownable $ownable
562      * @param $permission
563      * @return bool
564      */
565     public function checkAvailableCreatePageAccess()
566     {
567         $userRoleIds = $this->currentUser()->roles()->pluck('id')->toArray();
568         $userId = $this->currentUser()->id;
569
570
571         $canCreatePage = $this->db->table('joint_permissions')
572             ->where('action', '=', 'page-create')
573             ->whereIn('role_id', $userRoleIds)
574             ->where(function ($query) use ($userId) {
575                 $query->where('has_permission', '=', 1)
576                 ->orWhere(function ($query2) use ($userId) {
577                     $query2->where('has_permission_own', '=', 1)
578                     ->where('created_by', '=', $userId);
579                 });       
580             })
581             ->get()->count() > 0;
582
583         return $canCreatePage;
584     }
585
586     /**
587      * Check if an entity has restrictions set on itself or its
588      * parent tree.
589      * @param \BookStack\Entities\Entity $entity
590      * @param $action
591      * @return bool|mixed
592      */
593     public function checkIfRestrictionsSet(Entity $entity, $action)
594     {
595         $this->currentAction = $action;
596         if ($entity->isA('page')) {
597             return $entity->restricted || ($entity->chapter && $entity->chapter->restricted) || $entity->book->restricted;
598         } elseif ($entity->isA('chapter')) {
599             return $entity->restricted || $entity->book->restricted;
600         } elseif ($entity->isA('book')) {
601             return $entity->restricted;
602         }
603     }
604
605     /**
606      * The general query filter to remove all entities
607      * that the current user does not have access to.
608      * @param $query
609      * @return mixed
610      */
611     protected function entityRestrictionQuery($query)
612     {
613         $q = $query->where(function ($parentQuery) {
614             $parentQuery->whereHas('jointPermissions', function ($permissionQuery) {
615                 $permissionQuery->whereIn('role_id', $this->getRoles())
616                     ->where('action', '=', $this->currentAction)
617                     ->where(function ($query) {
618                         $query->where('has_permission', '=', true)
619                             ->orWhere(function ($query) {
620                                 $query->where('has_permission_own', '=', true)
621                                     ->where('created_by', '=', $this->currentUser()->id);
622                             });
623                     });
624             });
625         });
626         $this->clean();
627         return $q;
628     }
629
630     /**
631      * Get the children of a book in an efficient single query, Filtered by the permission system.
632      * @param integer $book_id
633      * @param bool $filterDrafts
634      * @param bool $fetchPageContent
635      * @return QueryBuilder
636      */
637     public function bookChildrenQuery($book_id, $filterDrafts = false, $fetchPageContent = false)
638     {
639         $entities = $this->entityProvider;
640         $pageSelect = $this->db->table('pages')->selectRaw($entities->page->entityRawQuery($fetchPageContent))
641             ->where('book_id', '=', $book_id)->where(function ($query) use ($filterDrafts) {
642                 $query->where('draft', '=', 0);
643                 if (!$filterDrafts) {
644                     $query->orWhere(function ($query) {
645                         $query->where('draft', '=', 1)->where('created_by', '=', $this->currentUser()->id);
646                     });
647                 }
648             });
649         $chapterSelect = $this->db->table('chapters')->selectRaw($entities->chapter->entityRawQuery())->where('book_id', '=', $book_id);
650         $query = $this->db->query()->select('*')->from($this->db->raw("({$pageSelect->toSql()} UNION {$chapterSelect->toSql()}) AS U"))
651             ->mergeBindings($pageSelect)->mergeBindings($chapterSelect);
652
653         // Add joint permission filter
654         $whereQuery = $this->db->table('joint_permissions as jp')->selectRaw('COUNT(*)')
655             ->whereRaw('jp.entity_id=U.id')->whereRaw('jp.entity_type=U.entity_type')
656             ->where('jp.action', '=', 'view')->whereIn('jp.role_id', $this->getRoles())
657             ->where(function ($query) {
658                 $query->where('jp.has_permission', '=', 1)->orWhere(function ($query) {
659                     $query->where('jp.has_permission_own', '=', 1)->where('jp.created_by', '=', $this->currentUser()->id);
660                 });
661             });
662         $query->whereRaw("({$whereQuery->toSql()}) > 0")->mergeBindings($whereQuery);
663
664         $query->orderBy('draft', 'desc')->orderBy('priority', 'asc');
665         $this->clean();
666         return  $query;
667     }
668
669     /**
670      * Add restrictions for a generic entity
671      * @param string $entityType
672      * @param Builder|\BookStack\Entities\Entity $query
673      * @param string $action
674      * @return Builder
675      */
676     public function enforceEntityRestrictions($entityType, $query, $action = 'view')
677     {
678         if (strtolower($entityType) === 'page') {
679             // Prevent drafts being visible to others.
680             $query = $query->where(function ($query) {
681                 $query->where('draft', '=', false);
682                 if ($this->currentUser()) {
683                     $query->orWhere(function ($query) {
684                         $query->where('draft', '=', true)->where('created_by', '=', $this->currentUser()->id);
685                     });
686                 }
687             });
688         }
689
690         $this->currentAction = $action;
691         return $this->entityRestrictionQuery($query);
692     }
693
694     /**
695      * Filter items that have entities set as a polymorphic relation.
696      * @param $query
697      * @param string $tableName
698      * @param string $entityIdColumn
699      * @param string $entityTypeColumn
700      * @param string $action
701      * @return mixed
702      */
703     public function filterRestrictedEntityRelations($query, $tableName, $entityIdColumn, $entityTypeColumn, $action = 'view')
704     {
705
706         $this->currentAction = $action;
707         $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn, 'entityTypeColumn' => $entityTypeColumn];
708
709         $q = $query->where(function ($query) use ($tableDetails) {
710             $query->whereExists(function ($permissionQuery) use (&$tableDetails) {
711                 $permissionQuery->select('id')->from('joint_permissions')
712                     ->whereRaw('joint_permissions.entity_id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
713                     ->whereRaw('joint_permissions.entity_type=' . $tableDetails['tableName'] . '.' . $tableDetails['entityTypeColumn'])
714                     ->where('action', '=', $this->currentAction)
715                     ->whereIn('role_id', $this->getRoles())
716                     ->where(function ($query) {
717                         $query->where('has_permission', '=', true)->orWhere(function ($query) {
718                             $query->where('has_permission_own', '=', true)
719                                 ->where('created_by', '=', $this->currentUser()->id);
720                         });
721                     });
722             });
723         });
724         $this->clean();
725         return $q;
726     }
727
728     /**
729      * Filters pages that are a direct relation to another item.
730      * @param $query
731      * @param $tableName
732      * @param $entityIdColumn
733      * @return mixed
734      */
735     public function filterRelatedPages($query, $tableName, $entityIdColumn)
736     {
737         $this->currentAction = 'view';
738         $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn];
739
740         $pageMorphClass = $this->entityProvider->page->getMorphClass();
741         $q = $query->where(function ($query) use ($tableDetails, $pageMorphClass) {
742             $query->where(function ($query) use (&$tableDetails, $pageMorphClass) {
743                 $query->whereExists(function ($permissionQuery) use (&$tableDetails, $pageMorphClass) {
744                     $permissionQuery->select('id')->from('joint_permissions')
745                         ->whereRaw('joint_permissions.entity_id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
746                         ->where('entity_type', '=', $pageMorphClass)
747                         ->where('action', '=', $this->currentAction)
748                         ->whereIn('role_id', $this->getRoles())
749                         ->where(function ($query) {
750                             $query->where('has_permission', '=', true)->orWhere(function ($query) {
751                                 $query->where('has_permission_own', '=', true)
752                                     ->where('created_by', '=', $this->currentUser()->id);
753                             });
754                         });
755                 });
756             })->orWhere($tableDetails['entityIdColumn'], '=', 0);
757         });
758         $this->clean();
759         return $q;
760     }
761
762     /**
763      * Get the current user
764      * @return \BookStack\Auth\User
765      */
766     private function currentUser()
767     {
768         if ($this->currentUserModel === false) {
769             $this->currentUserModel = user();
770         }
771
772         return $this->currentUserModel;
773     }
774
775     /**
776      * Clean the cached user elements.
777      */
778     private function clean()
779     {
780         $this->currentUserModel = false;
781         $this->userRoles = false;
782         $this->isAdminUser = null;
783     }
784 }