]> BookStack Code Mirror - bookstack/blob - tests/Helpers/EntityProvider.php
Aligned logic to entity_permission role_id usage change
[bookstack] / tests / Helpers / EntityProvider.php
1 <?php
2
3 namespace Tests\Helpers;
4
5 use BookStack\Auth\Permissions\EntityPermission;
6 use BookStack\Auth\Role;
7 use BookStack\Auth\User;
8 use BookStack\Entities\Models\Book;
9 use BookStack\Entities\Models\Bookshelf;
10 use BookStack\Entities\Models\Chapter;
11 use BookStack\Entities\Models\Entity;
12 use BookStack\Entities\Models\Page;
13 use BookStack\Entities\Repos\BookRepo;
14 use BookStack\Entities\Repos\BookshelfRepo;
15 use BookStack\Entities\Repos\ChapterRepo;
16 use BookStack\Entities\Repos\PageRepo;
17 use Illuminate\Database\Eloquent\Builder;
18
19 /**
20  * Class to provider and action entity models for common test case
21  * operations. Tracks handled models and only returns fresh models.
22  * Does not dedupe against nested/child/parent models.
23  */
24 class EntityProvider
25 {
26     /**
27      * @var array<string, int[]>
28      */
29     protected array $fetchCache = [
30         'book' => [],
31         'page' => [],
32         'bookshelf' => [],
33         'chapter' => [],
34     ];
35
36     /**
37      * Get an un-fetched page from the system.
38      */
39     public function page(callable $queryFilter = null): Page
40     {
41         /** @var Page $page */
42         $page = Page::query()->when($queryFilter, $queryFilter)->whereNotIn('id', $this->fetchCache['page'])->first();
43         $this->addToCache($page);
44         return $page;
45     }
46
47     public function pageWithinChapter(): Page
48     {
49         return $this->page(fn(Builder $query) => $query->whereHas('chapter')->with('chapter'));
50     }
51
52     public function pageNotWithinChapter(): Page
53     {
54         return $this->page(fn(Builder $query) => $query->where('chapter_id', '=', 0));
55     }
56
57     /**
58      * Get an un-fetched chapter from the system.
59      */
60     public function chapter(callable $queryFilter = null): Chapter
61     {
62         /** @var Chapter $chapter */
63         $chapter = Chapter::query()->when($queryFilter, $queryFilter)->whereNotIn('id', $this->fetchCache['chapter'])->first();
64         $this->addToCache($chapter);
65         return $chapter;
66     }
67
68     public function chapterHasPages(): Chapter
69     {
70         return $this->chapter(fn(Builder $query) => $query->whereHas('pages'));
71     }
72
73     /**
74      * Get an un-fetched book from the system.
75      */
76     public function book(callable $queryFilter = null): Book
77     {
78         /** @var Book $book */
79         $book = Book::query()->when($queryFilter, $queryFilter)->whereNotIn('id', $this->fetchCache['book'])->first();
80         $this->addToCache($book);
81         return $book;
82     }
83
84     /**
85      * Get a book that has chapters and pages assigned.
86      */
87     public function bookHasChaptersAndPages(): Book
88     {
89         return $this->book(function (Builder $query) {
90             $query->has('chapters')->has('pages')->with(['chapters', 'pages']);
91         });
92     }
93
94     /**
95      * Get an un-fetched shelf from the system.
96      */
97     public function shelf(callable $queryFilter = null): Bookshelf
98     {
99         /** @var Bookshelf $shelf */
100         $shelf = Bookshelf::query()->when($queryFilter, $queryFilter)->whereNotIn('id', $this->fetchCache['bookshelf'])->first();
101         $this->addToCache($shelf);
102         return $shelf;
103     }
104
105     /**
106      * Get all entity types from the system.
107      * @return array{page: Page, chapter: Chapter, book: Book, bookshelf: Bookshelf}
108      */
109     public function all(): array
110     {
111         return [
112             'page'      => $this->page(),
113             'chapter'   => $this->chapter(),
114             'book'      => $this->book(),
115             'bookshelf' => $this->shelf(),
116         ];
117     }
118
119     public function updatePage(Page $page, array $data): Page
120     {
121         $this->addToCache($page);
122         return app()->make(PageRepo::class)->update($page, $data);
123     }
124
125     /**
126      * Create a book to page chain of entities that belong to a specific user.
127      * @return array{book: Book, chapter: Chapter, page: Page}
128      */
129     public function createChainBelongingToUser(User $creatorUser, ?User $updaterUser = null): array
130     {
131         if (empty($updaterUser)) {
132             $updaterUser = $creatorUser;
133         }
134
135         $userAttrs = ['created_by' => $creatorUser->id, 'owned_by' => $creatorUser->id, 'updated_by' => $updaterUser->id];
136         /** @var Book $book */
137         $book = Book::factory()->create($userAttrs);
138         $chapter = Chapter::factory()->create(array_merge(['book_id' => $book->id], $userAttrs));
139         $page = Page::factory()->create(array_merge(['book_id' => $book->id, 'chapter_id' => $chapter->id], $userAttrs));
140
141         $book->rebuildPermissions();
142         $this->addToCache([$page, $chapter, $book]);
143
144         return compact('book', 'chapter', 'page');
145     }
146
147     /**
148      * Create and return a new bookshelf.
149      */
150     public function newShelf(array $input = ['name' => 'test shelf', 'description' => 'My new test shelf']): Bookshelf
151     {
152         $shelf = app(BookshelfRepo::class)->create($input, []);
153         $this->addToCache($shelf);
154         return $shelf;
155     }
156
157     /**
158      * Create and return a new book.
159      */
160     public function newBook(array $input = ['name' => 'test book', 'description' => 'My new test book']): Book
161     {
162         $book = app(BookRepo::class)->create($input);
163         $this->addToCache($book);
164         return $book;
165     }
166
167     /**
168      * Create and return a new test chapter.
169      */
170     public function newChapter(array $input, Book $book): Chapter
171     {
172         $chapter = app(ChapterRepo::class)->create($input, $book);
173         $this->addToCache($chapter);
174         return $chapter;
175     }
176
177     /**
178      * Create and return a new test page.
179      */
180     public function newPage(array $input = ['name' => 'test page', 'html' => 'My new test page']): Page
181     {
182         $book = $this->book();
183         $pageRepo = app(PageRepo::class);
184         $draftPage = $pageRepo->getNewDraftPage($book);
185         $this->addToCache($draftPage);
186         return $pageRepo->publishDraft($draftPage, $input);
187     }
188
189     /**
190      * Regenerate the permission for an entity.
191      * Centralised to manage clearing of cached elements between requests.
192      */
193     public function regenPermissions(Entity $entity): void
194     {
195         $entity->rebuildPermissions();
196         $entity->load('jointPermissions');
197     }
198
199     /**
200      * Set the given entity as having restricted permissions, and apply the given
201      * permissions for the given roles.
202      * @param string[] $actions
203      * @param Role[] $roles
204      */
205     public function setPermissions(Entity $entity, array $actions = [], array $roles = []): void
206     {
207         $entity->permissions()->delete();
208
209         $permissions = [
210             // Set default permissions to not allow actions so that only the provided role permissions are at play.
211             ['role_id' => null, 'view' => false, 'create' => false, 'update' => false, 'delete' => false],
212         ];
213
214         foreach ($roles as $role) {
215             $permission = ['role_id' => $role->id];
216             foreach (EntityPermission::PERMISSIONS as $possibleAction) {
217                 $permission[$possibleAction] = in_array($possibleAction, $actions);
218             }
219             $permissions[] = $permission;
220         }
221
222         $entity->permissions()->createMany($permissions);
223         $entity->load('permissions');
224         $this->regenPermissions($entity);
225     }
226
227     /**
228      * @param Entity|Entity[] $entities
229      */
230     protected function addToCache($entities): void
231     {
232         if (!is_array($entities)) {
233             $entities = [$entities];
234         }
235
236         foreach ($entities as $entity) {
237             $this->fetchCache[$entity->getType()][] = $entity->id;
238         }
239     }
240 }