]> BookStack Code Mirror - bookstack/blob - tests/Entity/CopyTest.php
Copying: Fixed issue with non-page links to page permalinks
[bookstack] / tests / Entity / CopyTest.php
1 <?php
2
3 namespace Tests\Entity;
4
5 use BookStack\Entities\Models\Book;
6 use BookStack\Entities\Models\BookChild;
7 use BookStack\Entities\Models\Bookshelf;
8 use BookStack\Entities\Models\Chapter;
9 use BookStack\Entities\Models\Page;
10 use BookStack\Entities\Repos\BookRepo;
11 use Tests\TestCase;
12
13 class CopyTest extends TestCase
14 {
15     public function test_book_show_view_has_copy_button()
16     {
17         $book = $this->entities->book();
18         $resp = $this->asEditor()->get($book->getUrl());
19
20         $this->withHtml($resp)->assertElementContains("a[href=\"{$book->getUrl('/copy')}\"]", 'Copy');
21     }
22
23     public function test_book_copy_view()
24     {
25         $book = $this->entities->book();
26         $resp = $this->asEditor()->get($book->getUrl('/copy'));
27
28         $resp->assertOk();
29         $resp->assertSee('Copy Book');
30         $this->withHtml($resp)->assertElementExists("input[name=\"name\"][value=\"{$book->name}\"]");
31     }
32
33     public function test_book_copy()
34     {
35         /** @var Book $book */
36         $book = Book::query()->whereHas('chapters')->whereHas('pages')->first();
37         $resp = $this->asEditor()->post($book->getUrl('/copy'), ['name' => 'My copy book']);
38
39         /** @var Book $copy */
40         $copy = Book::query()->where('name', '=', 'My copy book')->first();
41
42         $resp->assertRedirect($copy->getUrl());
43         $this->assertEquals($book->getDirectVisibleChildren()->count(), $copy->getDirectVisibleChildren()->count());
44
45         $this->get($copy->getUrl())->assertSee($book->description_html, false);
46     }
47
48     public function test_book_copy_does_not_copy_non_visible_content()
49     {
50         /** @var Book $book */
51         $book = Book::query()->whereHas('chapters')->whereHas('pages')->first();
52
53         // Hide child content
54         /** @var BookChild $page */
55         foreach ($book->getDirectVisibleChildren() as $child) {
56             $this->permissions->setEntityPermissions($child, [], []);
57         }
58
59         $this->asEditor()->post($book->getUrl('/copy'), ['name' => 'My copy book']);
60         /** @var Book $copy */
61         $copy = Book::query()->where('name', '=', 'My copy book')->first();
62
63         $this->assertEquals(0, $copy->getDirectVisibleChildren()->count());
64     }
65
66     public function test_book_copy_does_not_copy_pages_or_chapters_if_user_cant_create()
67     {
68         /** @var Book $book */
69         $book = Book::query()->whereHas('chapters')->whereHas('directPages')->whereHas('chapters')->first();
70         $viewer = $this->users->viewer();
71         $this->permissions->grantUserRolePermissions($viewer, ['book-create-all']);
72
73         $this->actingAs($viewer)->post($book->getUrl('/copy'), ['name' => 'My copy book']);
74         /** @var Book $copy */
75         $copy = Book::query()->where('name', '=', 'My copy book')->first();
76
77         $this->assertEquals(0, $copy->pages()->count());
78         $this->assertEquals(0, $copy->chapters()->count());
79     }
80
81     public function test_book_copy_clones_cover_image_if_existing()
82     {
83         $book = $this->entities->book();
84         $bookRepo = $this->app->make(BookRepo::class);
85         $coverImageFile = $this->files->uploadedImage('cover.png');
86         $bookRepo->updateCoverImage($book, $coverImageFile);
87
88         $this->asEditor()->post($book->getUrl('/copy'), ['name' => 'My copy book'])->assertRedirect();
89         /** @var Book $copy */
90         $copy = Book::query()->where('name', '=', 'My copy book')->first();
91
92         $this->assertNotNull($copy->coverInfo()->getImage());
93         $this->assertNotEquals($book->coverInfo()->getImage()->id, $copy->coverInfo()->getImage()->id);
94     }
95
96     public function test_book_copy_adds_book_to_shelves_if_edit_permissions_allows()
97     {
98         /** @var Bookshelf $shelfA */
99         /** @var Bookshelf $shelfB */
100         [$shelfA, $shelfB] = Bookshelf::query()->take(2)->get();
101         $book = $this->entities->book();
102
103         $shelfA->appendBook($book);
104         $shelfB->appendBook($book);
105
106         $viewer = $this->users->viewer();
107         $this->permissions->grantUserRolePermissions($viewer, ['book-update-all', 'book-create-all', 'bookshelf-update-all']);
108         $this->permissions->setEntityPermissions($shelfB);
109
110
111         $this->asEditor()->post($book->getUrl('/copy'), ['name' => 'My copy book']);
112         /** @var Book $copy */
113         $copy = Book::query()->where('name', '=', 'My copy book')->first();
114
115         $this->assertTrue($copy->shelves()->where('id', '=', $shelfA->id)->exists());
116         $this->assertFalse($copy->shelves()->where('id', '=', $shelfB->id)->exists());
117     }
118
119     public function test_chapter_show_view_has_copy_button()
120     {
121         $chapter = $this->entities->chapter();
122
123         $resp = $this->asEditor()->get($chapter->getUrl());
124         $this->withHtml($resp)->assertElementContains("a[href$=\"{$chapter->getUrl('/copy')}\"]", 'Copy');
125     }
126
127     public function test_chapter_copy_view()
128     {
129         $chapter = $this->entities->chapter();
130
131         $resp = $this->asEditor()->get($chapter->getUrl('/copy'));
132         $resp->assertOk();
133         $resp->assertSee('Copy Chapter');
134         $this->withHtml($resp)->assertElementExists("input[name=\"name\"][value=\"{$chapter->name}\"]");
135         $this->withHtml($resp)->assertElementExists('input[name="entity_selection"]');
136     }
137
138     public function test_chapter_copy()
139     {
140         /** @var Chapter $chapter */
141         $chapter = Chapter::query()->whereHas('pages')->first();
142         /** @var Book $otherBook */
143         $otherBook = Book::query()->where('id', '!=', $chapter->book_id)->first();
144
145         $resp = $this->asEditor()->post($chapter->getUrl('/copy'), [
146             'name'             => 'My copied chapter',
147             'entity_selection' => 'book:' . $otherBook->id,
148         ]);
149
150         /** @var Chapter $newChapter */
151         $newChapter = Chapter::query()->where('name', '=', 'My copied chapter')->first();
152
153         $resp->assertRedirect($newChapter->getUrl());
154         $this->assertEquals($otherBook->id, $newChapter->book_id);
155         $this->assertEquals($chapter->pages->count(), $newChapter->pages->count());
156     }
157
158     public function test_chapter_copy_does_not_copy_non_visible_pages()
159     {
160         $chapter = $this->entities->chapterHasPages();
161
162         // Hide pages to all non-admin roles
163         /** @var Page $page */
164         foreach ($chapter->pages as $page) {
165             $this->permissions->setEntityPermissions($page, [], []);
166         }
167
168         $this->asEditor()->post($chapter->getUrl('/copy'), [
169             'name' => 'My copied chapter',
170         ]);
171
172         /** @var Chapter $newChapter */
173         $newChapter = Chapter::query()->where('name', '=', 'My copied chapter')->first();
174         $this->assertEquals(0, $newChapter->pages()->count());
175     }
176
177     public function test_chapter_copy_does_not_copy_pages_if_user_cant_page_create()
178     {
179         $chapter = $this->entities->chapterHasPages();
180         $viewer = $this->users->viewer();
181         $this->permissions->grantUserRolePermissions($viewer, ['chapter-create-all']);
182
183         // Lacking permission results in no copied pages
184         $this->actingAs($viewer)->post($chapter->getUrl('/copy'), [
185             'name' => 'My copied chapter',
186         ]);
187
188         /** @var Chapter $newChapter */
189         $newChapter = Chapter::query()->where('name', '=', 'My copied chapter')->first();
190         $this->assertEquals(0, $newChapter->pages()->count());
191
192         $this->permissions->grantUserRolePermissions($viewer, ['page-create-all']);
193
194         // Having permission rules in copied pages
195         $this->actingAs($viewer)->post($chapter->getUrl('/copy'), [
196             'name' => 'My copied again chapter',
197         ]);
198
199         /** @var Chapter $newChapter2 */
200         $newChapter2 = Chapter::query()->where('name', '=', 'My copied again chapter')->first();
201         $this->assertEquals($chapter->pages()->count(), $newChapter2->pages()->count());
202     }
203
204     public function test_book_copy_updates_internal_references()
205     {
206         $book = $this->entities->bookHasChaptersAndPages();
207         /** @var Chapter $chapter */
208         $chapter = $book->chapters()->first();
209         /** @var Page $page */
210         $page = $chapter->pages()->first();
211         $this->asEditor();
212         $this->entities->updatePage($page, [
213             'name' => 'reference test page',
214             'html' => '<p>This is a test <a href="' . $book->getUrl() . '">book link</a></p>',
215         ]);
216
217         // Quick pre-update to get stable slug
218         $this->put($book->getUrl(), ['name' => 'Internal ref test']);
219         $book->refresh();
220         $page->refresh();
221
222         $html = '<p>This is a test <a href="' . $page->getUrl() . '">page link</a></p>';
223         $this->put($book->getUrl(), ['name' => 'Internal ref test', 'description_html' => $html]);
224
225         $this->post($book->getUrl('/copy'), ['name' => 'My copied book']);
226
227         $newBook = Book::query()->where('name', '=', 'My copied book')->first();
228         $newPage = $newBook->pages()->where('name', '=', 'reference test page')->first();
229
230         $this->assertStringContainsString($newBook->getUrl(), $newPage->html);
231         $this->assertStringContainsString($newPage->getUrl(), $newBook->description_html);
232
233         $this->assertStringNotContainsString($book->getUrl(), $newPage->html);
234         $this->assertStringNotContainsString($page->getUrl(), $newBook->description_html);
235     }
236
237     public function test_chapter_copy_updates_internal_references()
238     {
239         $chapter = $this->entities->chapterHasPages();
240         /** @var Page $page */
241         $page = $chapter->pages()->first();
242         $this->asEditor();
243         $this->entities->updatePage($page, [
244             'name' => 'reference test page',
245             'html' => '<p>This is a test <a href="' . $chapter->getUrl() . '">chapter link</a></p>',
246         ]);
247
248         // Quick pre-update to get stable slug
249         $this->put($chapter->getUrl(), ['name' => 'Internal ref test']);
250         $chapter->refresh();
251         $page->refresh();
252
253         $html = '<p>This is a test <a href="' . $page->getUrl() . '">page link</a></p>';
254         $this->put($chapter->getUrl(), ['name' => 'Internal ref test', 'description_html' => $html]);
255
256         $this->post($chapter->getUrl('/copy'), ['name' => 'My copied chapter']);
257
258         $newChapter = Chapter::query()->where('name', '=', 'My copied chapter')->first();
259         $newPage = $newChapter->pages()->where('name', '=', 'reference test page')->first();
260
261         $this->assertStringContainsString($newChapter->getUrl() . '"', $newPage->html);
262         $this->assertStringContainsString($newPage->getUrl() . '"', $newChapter->description_html);
263
264         $this->assertStringNotContainsString($chapter->getUrl() . '"', $newPage->html);
265         $this->assertStringNotContainsString($page->getUrl() . '"', $newChapter->description_html);
266     }
267
268     public function test_chapter_copy_updates_internal_permalink_references_in_its_description()
269     {
270         $chapter = $this->entities->chapterHasPages();
271         /** @var Page $page */
272         $page = $chapter->pages()->first();
273
274         $this->asEditor()->put($chapter->getUrl(), [
275             'name' => 'Internal ref test',
276             'description_html' => '<p>This is a test <a href="' . $page->getPermalink() . '">page link</a></p>',
277         ]);
278         $chapter->refresh();
279
280         $this->post($chapter->getUrl('/copy'), ['name' => 'My copied chapter']);
281         $newChapter = Chapter::query()->where('name', '=', 'My copied chapter')->first();
282
283         $this->assertStringContainsString('/link/', $newChapter->description_html);
284         $this->assertStringNotContainsString($page->getPermalink() . '"', $newChapter->description_html);
285     }
286
287     public function test_page_copy_updates_internal_self_references()
288     {
289         $page = $this->entities->page();
290         $this->asEditor();
291
292         // Initial update to get stable slug
293         $this->entities->updatePage($page, ['name' => 'reference test page']);
294
295         $page->refresh();
296         $this->entities->updatePage($page, [
297             'name' => 'reference test page',
298             'html' => '<p>This is a test <a href="' . $page->getUrl() . '">page link</a></p>',
299         ]);
300
301         $this->post($page->getUrl('/copy'), ['name' => 'My copied page']);
302         $newPage = Page::query()->where('name', '=', 'My copied page')->first();
303         $this->assertNotNull($newPage);
304
305         $this->assertStringContainsString($newPage->getUrl(), $newPage->html);
306         $this->assertStringNotContainsString($page->getUrl(), $newPage->html);
307     }
308
309     public function test_page_copy()
310     {
311         $page = $this->entities->page();
312         $page->html = '<p>This is some test content</p>';
313         $page->save();
314
315         $currentBook = $page->book;
316         $newBook = Book::where('id', '!=', $currentBook->id)->first();
317
318         $resp = $this->asEditor()->get($page->getUrl('/copy'));
319         $resp->assertSee('Copy Page');
320
321         $movePageResp = $this->post($page->getUrl('/copy'), [
322             'entity_selection' => 'book:' . $newBook->id,
323             'name'             => 'My copied test page',
324         ]);
325         $pageCopy = Page::where('name', '=', 'My copied test page')->first();
326
327         $movePageResp->assertRedirect($pageCopy->getUrl());
328         $this->assertTrue($pageCopy->book->id == $newBook->id, 'Page was copied to correct book');
329         $this->assertStringContainsString('This is some test content', $pageCopy->html);
330     }
331
332     public function test_page_copy_with_markdown_has_both_html_and_markdown()
333     {
334         $page = $this->entities->page();
335         $page->html = '<h1>This is some test content</h1>';
336         $page->markdown = '# This is some test content';
337         $page->save();
338         $newBook = Book::where('id', '!=', $page->book->id)->first();
339
340         $this->asEditor()->post($page->getUrl('/copy'), [
341             'entity_selection' => 'book:' . $newBook->id,
342             'name'             => 'My copied test page',
343         ]);
344         $pageCopy = Page::where('name', '=', 'My copied test page')->first();
345
346         $this->assertStringContainsString('This is some test content', $pageCopy->html);
347         $this->assertEquals('# This is some test content', $pageCopy->markdown);
348     }
349
350     public function test_page_copy_with_no_destination()
351     {
352         $page = $this->entities->page();
353         $currentBook = $page->book;
354
355         $resp = $this->asEditor()->get($page->getUrl('/copy'));
356         $resp->assertSee('Copy Page');
357
358         $movePageResp = $this->post($page->getUrl('/copy'), [
359             'name' => 'My copied test page',
360         ]);
361
362         $pageCopy = Page::where('name', '=', 'My copied test page')->first();
363
364         $movePageResp->assertRedirect($pageCopy->getUrl());
365         $this->assertTrue($pageCopy->book->id == $currentBook->id, 'Page was copied to correct book');
366         $this->assertTrue($pageCopy->id !== $page->id, 'Page copy is not the same instance');
367     }
368
369     public function test_page_can_be_copied_without_edit_permission()
370     {
371         $page = $this->entities->page();
372         $currentBook = $page->book;
373         $newBook = Book::where('id', '!=', $currentBook->id)->first();
374         $viewer = $this->users->viewer();
375
376         $resp = $this->actingAs($viewer)->get($page->getUrl());
377         $resp->assertDontSee($page->getUrl('/copy'));
378
379         $newBook->owned_by = $viewer->id;
380         $newBook->save();
381         $this->permissions->grantUserRolePermissions($viewer, ['page-create-own']);
382         $this->permissions->regenerateForEntity($newBook);
383
384         $resp = $this->actingAs($viewer)->get($page->getUrl());
385         $resp->assertSee($page->getUrl('/copy'));
386
387         $movePageResp = $this->post($page->getUrl('/copy'), [
388             'entity_selection' => 'book:' . $newBook->id,
389             'name'             => 'My copied test page',
390         ]);
391         $movePageResp->assertRedirect();
392
393         $this->assertDatabaseHasEntityData('page', [
394             'name'       => 'My copied test page',
395             'created_by' => $viewer->id,
396             'book_id'    => $newBook->id,
397         ]);
398     }
399 }