From 65796cfc7b7a04f0e61b2ce682c6cfce9d421b23 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 1 Jan 2017 21:21:11 +0000 Subject: [PATCH] Rewrote book children query --- app/Http/Controllers/BookController.php | 1 + app/Repos/BookRepo.php | 61 ++++++++++--------------- app/Repos/EntityRepo.php | 2 +- app/Services/ActivityService.php | 2 +- app/Services/PermissionService.php | 59 +++++++++++++++++++++++- app/Services/ViewService.php | 2 +- app/User.php | 10 ++++ 7 files changed, 94 insertions(+), 43 deletions(-) diff --git a/app/Http/Controllers/BookController.php b/app/Http/Controllers/BookController.php index 0b4749a48..b6856f273 100644 --- a/app/Http/Controllers/BookController.php +++ b/app/Http/Controllers/BookController.php @@ -22,6 +22,7 @@ class BookController extends Controller /** * BookController constructor. + * @param EntityRepo $entityRepo * @param BookRepo $bookRepo * @param PageRepo $pageRepo * @param ChapterRepo $chapterRepo diff --git a/app/Repos/BookRepo.php b/app/Repos/BookRepo.php index 3043d2916..f5d19bc37 100644 --- a/app/Repos/BookRepo.php +++ b/app/Repos/BookRepo.php @@ -93,47 +93,32 @@ class BookRepo extends EntityRepo */ public function getChildren(Book $book, $filterDrafts = false) { - $pageQuery = $book->pages()->where('chapter_id', '=', 0); - $pageQuery = $this->permissionService->enforcePageRestrictions($pageQuery, 'view'); + $q = $this->permissionService->bookChildrenQuery($book->id, $filterDrafts); + $entities = []; + $parents = []; + $tree = []; - if ($filterDrafts) { - $pageQuery = $pageQuery->where('draft', '=', false); - } - - $pages = $pageQuery->get(); - - $chapterQuery = $book->chapters()->with(['pages' => function ($query) use ($filterDrafts) { - $this->permissionService->enforcePageRestrictions($query, 'view'); - if ($filterDrafts) $query->where('draft', '=', false); - }]); - $chapterQuery = $this->permissionService->enforceChapterRestrictions($chapterQuery, 'view'); - $chapters = $chapterQuery->get(); - $children = $pages->values(); - foreach ($chapters as $chapter) { - $children->push($chapter); - } - $bookSlug = $book->slug; - - $children->each(function ($child) use ($bookSlug) { - $child->setAttribute('bookSlug', $bookSlug); - if ($child->isA('chapter')) { - $child->pages->each(function ($page) use ($bookSlug) { - $page->setAttribute('bookSlug', $bookSlug); - }); - $child->pages = $child->pages->sortBy(function ($child, $key) { - $score = $child->priority; - if ($child->draft) $score -= 100; - return $score; - }); + foreach ($q as $index => $rawEntity) { + if ($rawEntity->entity_type === 'Bookstack\\Page') { + $entities[$index] = $this->page->newFromBuilder($rawEntity); + } else if ($rawEntity->entity_type === 'Bookstack\\Chapter') { + $entities[$index] = $this->chapter->newFromBuilder($rawEntity); + $key = $entities[$index]->entity_type . ':' . $entities[$index]->id; + $parents[$key] = $entities[$index]; + $parents[$key]->setAttribute('pages', collect()); } - }); + if ($entities[$index]->chapter_id === 0) $tree[] = $entities[$index]; + $entities[$index]->book = $book; + } - // Sort items with drafts first then by priority. - return $children->sortBy(function ($child, $key) { - $score = $child->priority; - if ($child->isA('page') && $child->draft) $score -= 100; - return $score; - }); + foreach ($entities as $entity) { + if ($entity->chapter_id === 0) continue; + $parentKey = 'Bookstack\\Chapter:' . $entity->chapter_id; + $chapter = $parents[$parentKey]; + $chapter->pages->push($entity); + } + + return collect($tree); } } \ No newline at end of file diff --git a/app/Repos/EntityRepo.php b/app/Repos/EntityRepo.php index 40fdea54e..dd3016c6c 100644 --- a/app/Repos/EntityRepo.php +++ b/app/Repos/EntityRepo.php @@ -7,7 +7,6 @@ use BookStack\Exceptions\NotFoundException; use BookStack\Page; use BookStack\Services\PermissionService; use BookStack\Services\ViewService; -use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Collection; class EntityRepo @@ -127,6 +126,7 @@ class EntityRepo public function getBySlug($type, $slug, $bookSlug = false) { $q = $this->entityQuery($type)->where('slug', '=', $slug); + if (strtolower($type) === 'chapter' || strtolower($type) === 'page') { $q = $q->where('book_id', '=', function($query) use ($bookSlug) { $query->select('id') diff --git a/app/Services/ActivityService.php b/app/Services/ActivityService.php index e41036238..2368ba10a 100644 --- a/app/Services/ActivityService.php +++ b/app/Services/ActivityService.php @@ -114,7 +114,7 @@ class ActivityService $activity = $this->permissionService ->filterRestrictedEntityRelations($query, 'activities', 'entity_id', 'entity_type') - ->orderBy('created_at', 'desc')->skip($count * $page)->take($count)->get(); + ->orderBy('created_at', 'desc')->with(['entity', 'user.avatar'])->skip($count * $page)->take($count)->get(); return $this->filterSimilar($activity); } diff --git a/app/Services/PermissionService.php b/app/Services/PermissionService.php index d5044c1bb..0db2d1b5e 100644 --- a/app/Services/PermissionService.php +++ b/app/Services/PermissionService.php @@ -8,6 +8,7 @@ use BookStack\Ownable; use BookStack\Page; use BookStack\Role; use BookStack\User; +use Illuminate\Database\Connection; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Collection; @@ -23,6 +24,8 @@ class PermissionService public $chapter; public $page; + protected $db; + protected $jointPermission; protected $role; @@ -31,18 +34,21 @@ class PermissionService /** * PermissionService constructor. * @param JointPermission $jointPermission + * @param Connection $db * @param Book $book * @param Chapter $chapter * @param Page $page * @param Role $role */ - public function __construct(JointPermission $jointPermission, Book $book, Chapter $chapter, Page $page, Role $role) + public function __construct(JointPermission $jointPermission, Connection $db, Book $book, Chapter $chapter, Page $page, Role $role) { + $this->db = $db; $this->jointPermission = $jointPermission; $this->role = $role; $this->book = $book; $this->chapter = $chapter; $this->page = $page; + // TODO - Update so admin still goes through filters } /** @@ -302,6 +308,10 @@ class PermissionService $explodedAction = explode('-', $action); $restrictionAction = end($explodedAction); + if ($role->system_name === 'admin') { + return $this->createJointPermissionDataArray($entity, $role, $action, true, true); + } + if ($entity->isA('book')) { if (!$entity->restricted) { @@ -461,6 +471,51 @@ class PermissionService return $q; } + public function bookChildrenQuery($book_id, $filterDrafts = false) { + + // Draft setup + $params = [ + 'userId' => $this->currentUser()->id, + 'bookIdPage' => $book_id, + 'bookIdChapter' => $book_id + ]; + if (!$filterDrafts) { + $params['userIdDrafts'] = $this->currentUser()->id; + } + // Role setup + $userRoles = $this->getRoles(); + $roleBindings = []; + $roleValues = []; + foreach ($userRoles as $index => $roleId) { + $roleBindings[':role'.$index] = $roleId; + $roleValues['role'.$index] = $roleId; + } + // TODO - Clean this up, Maybe extract into a nice class for doing these kind of manual things + // Something which will handle the above role crap in a nice clean way + $roleBindingString = implode(',', array_keys($roleBindings)); + $query = "SELECT * from ( +(SELECT 'Bookstack\\\Page' as entity_type, id, slug, name, text, '' as description, book_id, priority, chapter_id, draft FROM {$this->page->getTable()} + where book_id = :bookIdPage AND ". ($filterDrafts ? '(draft = 0)' : '(draft = 0 OR (draft = 1 AND created_by = :userIdDrafts))') .") +UNION +(SELECT 'Bookstack\\\Chapter' as entity_type, id, slug, name, '' as text, description, book_id, priority, 0 as chapter_id, 0 as draft FROM {$this->chapter->getTable()} WHERE book_id = :bookIdChapter) +) as U WHERE ( + SELECT COUNT(*) FROM {$this->jointPermission->getTable()} jp + WHERE + jp.entity_id=U.id AND + jp.entity_type=U.entity_type AND + jp.action = 'view' AND + jp.role_id IN ({$roleBindingString}) AND + ( + jp.has_permission = 1 OR + (jp.has_permission_own = 1 AND jp.created_by = :userId) + ) +) > 0 +ORDER BY draft desc, priority asc"; + + $this->clean(); + return $this->db->select($query, array_replace($roleValues, $params)); + } + /** * Add restrictions for a page query * @param $query @@ -608,7 +663,7 @@ class PermissionService private function isAdmin() { if ($this->isAdminUser === null) { - $this->isAdminUser = ($this->currentUser()->id !== null) ? $this->currentUser()->hasRole('admin') : false; + $this->isAdminUser = ($this->currentUser()->id !== null) ? $this->currentUser()->hasSystemRole('admin') : false; } return $this->isAdminUser; diff --git a/app/Services/ViewService.php b/app/Services/ViewService.php index 73b353d8f..3285745ce 100644 --- a/app/Services/ViewService.php +++ b/app/Services/ViewService.php @@ -37,7 +37,7 @@ class ViewService // Otherwise create new view count $entity->views()->save($this->view->create([ - 'user_id' => user()->id, + 'user_id' => $user->id, 'views' => 1 ])); diff --git a/app/User.php b/app/User.php index 09b189cbb..b5bb221e8 100644 --- a/app/User.php +++ b/app/User.php @@ -74,6 +74,16 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon return $this->roles->pluck('name')->contains($role); } + /** + * Check if the user has a role. + * @param $role + * @return mixed + */ + public function hasSystemRole($role) + { + return $this->roles->pluck('system_name')->contains('admin'); + } + /** * Get all permissions belonging to a the current user. * @param bool $cache