]> BookStack Code Mirror - bookstack/blob - tests/Entity/PageTest.php
Pages: Redirect user to view if they can't edit
[bookstack] / tests / Entity / PageTest.php
1 <?php
2
3 namespace Tests\Entity;
4
5 use BookStack\Entities\Models\Book;
6 use BookStack\Entities\Models\Page;
7 use Carbon\Carbon;
8 use Tests\TestCase;
9
10 class PageTest extends TestCase
11 {
12     public function test_create()
13     {
14         $chapter = $this->entities->chapter();
15         $page = Page::factory()->make([
16             'name' => 'My First Page',
17         ]);
18
19         $resp = $this->asEditor()->get($chapter->getUrl());
20         $this->withHtml($resp)->assertElementContains('a[href="' . $chapter->getUrl('/create-page') . '"]', 'New Page');
21
22         $resp = $this->get($chapter->getUrl('/create-page'));
23         /** @var Page $draftPage */
24         $draftPage = Page::query()
25             ->where('draft', '=', true)
26             ->orderBy('created_at', 'desc')
27             ->first();
28         $resp->assertRedirect($draftPage->getUrl());
29
30         $resp = $this->get($draftPage->getUrl());
31         $this->withHtml($resp)->assertElementContains('form[action="' . $draftPage->getUrl() . '"][method="POST"]', 'Save Page');
32
33         $resp = $this->post($draftPage->getUrl(), $draftPage->only('name', 'html'));
34         $draftPage->refresh();
35         $resp->assertRedirect($draftPage->getUrl());
36     }
37
38     public function test_page_view_when_creator_is_deleted_but_owner_exists()
39     {
40         $page = $this->entities->page();
41         $user = $this->users->viewer();
42         $owner = $this->users->editor();
43         $page->created_by = $user->id;
44         $page->owned_by = $owner->id;
45         $page->save();
46         $user->delete();
47
48         $resp = $this->asAdmin()->get($page->getUrl());
49         $resp->assertStatus(200);
50         $resp->assertSeeText('Owned by ' . $owner->name);
51     }
52
53     public function test_page_show_includes_pointer_section_select_mode_button()
54     {
55         $page = $this->entities->page();
56         $resp = $this->asEditor()->get($page->getUrl());
57         $this->withHtml($resp)->assertElementContains('.content-wrap button.screen-reader-only', 'Enter section select mode');
58     }
59
60     public function test_page_creation_with_markdown_content()
61     {
62         $this->setSettings(['app-editor' => 'markdown']);
63         $book = $this->entities->book();
64
65         $this->asEditor()->get($book->getUrl('/create-page'));
66         $draft = Page::query()->where('book_id', '=', $book->id)
67             ->where('draft', '=', true)->first();
68
69         $details = [
70             'markdown' => '# a title',
71             'html'     => '<h1>a title</h1>',
72             'name'     => 'my page',
73         ];
74         $resp = $this->post($book->getUrl("/draft/{$draft->id}"), $details);
75         $resp->assertRedirect();
76
77         $this->assertDatabaseHas('pages', [
78             'markdown' => $details['markdown'],
79             'name'     => $details['name'],
80             'id'       => $draft->id,
81             'draft'    => false,
82         ]);
83
84         $draft->refresh();
85         $resp = $this->get($draft->getUrl('/edit'));
86         $resp->assertSee('# a title');
87     }
88
89     public function test_page_creation_allows_summary_to_be_set()
90     {
91         $book = $this->entities->book();
92
93         $this->asEditor()->get($book->getUrl('/create-page'));
94         $draft = Page::query()->where('book_id', '=', $book->id)
95             ->where('draft', '=', true)->first();
96
97         $details = [
98             'html'    => '<h1>a title</h1>',
99             'name'    => 'My page with summary',
100             'summary' => 'Here is my changelog message for a new page!',
101         ];
102         $resp = $this->post($book->getUrl("/draft/{$draft->id}"), $details);
103         $resp->assertRedirect();
104
105         $this->assertDatabaseHas('page_revisions', [
106             'page_id' => $draft->id,
107             'summary' => 'Here is my changelog message for a new page!',
108         ]);
109
110         $draft->refresh();
111         $resp = $this->get($draft->getUrl('/revisions'));
112         $resp->assertSee('Here is my changelog message for a new page!');
113     }
114
115     public function test_page_delete()
116     {
117         $page = $this->entities->page();
118         $this->assertNull($page->deleted_at);
119
120         $deleteViewReq = $this->asEditor()->get($page->getUrl('/delete'));
121         $deleteViewReq->assertSeeText('Are you sure you want to delete this page?');
122
123         $deleteReq = $this->delete($page->getUrl());
124         $deleteReq->assertRedirect($page->getParent()->getUrl());
125         $this->assertActivityExists('page_delete', $page);
126
127         $page->refresh();
128         $this->assertNotNull($page->deleted_at);
129         $this->assertTrue($page->deletions()->count() === 1);
130
131         $redirectReq = $this->get($deleteReq->baseResponse->headers->get('location'));
132         $this->assertNotificationContains($redirectReq, 'Page Successfully Deleted');
133     }
134
135     public function test_page_full_delete_removes_all_revisions()
136     {
137         $page = $this->entities->page();
138         $page->revisions()->create([
139             'html' => '<p>ducks</p>',
140             'name' => 'my page revision',
141             'type' => 'draft',
142         ]);
143         $page->revisions()->create([
144             'html' => '<p>ducks</p>',
145             'name' => 'my page revision',
146             'type' => 'revision',
147         ]);
148
149         $this->assertDatabaseHas('page_revisions', [
150             'page_id' => $page->id,
151         ]);
152
153         $this->asEditor()->delete($page->getUrl());
154         $this->asAdmin()->post('/settings/recycle-bin/empty');
155
156         $this->assertDatabaseMissing('page_revisions', [
157             'page_id' => $page->id,
158         ]);
159     }
160
161     public function test_page_copy()
162     {
163         $page = $this->entities->page();
164         $page->html = '<p>This is some test content</p>';
165         $page->save();
166
167         $currentBook = $page->book;
168         $newBook = Book::where('id', '!=', $currentBook->id)->first();
169
170         $resp = $this->asEditor()->get($page->getUrl('/copy'));
171         $resp->assertSee('Copy Page');
172
173         $movePageResp = $this->post($page->getUrl('/copy'), [
174             'entity_selection' => 'book:' . $newBook->id,
175             'name'             => 'My copied test page',
176         ]);
177         $pageCopy = Page::where('name', '=', 'My copied test page')->first();
178
179         $movePageResp->assertRedirect($pageCopy->getUrl());
180         $this->assertTrue($pageCopy->book->id == $newBook->id, 'Page was copied to correct book');
181         $this->assertStringContainsString('This is some test content', $pageCopy->html);
182     }
183
184     public function test_page_copy_with_markdown_has_both_html_and_markdown()
185     {
186         $page = $this->entities->page();
187         $page->html = '<h1>This is some test content</h1>';
188         $page->markdown = '# This is some test content';
189         $page->save();
190         $newBook = Book::where('id', '!=', $page->book->id)->first();
191
192         $this->asEditor()->post($page->getUrl('/copy'), [
193             'entity_selection' => 'book:' . $newBook->id,
194             'name'             => 'My copied test page',
195         ]);
196         $pageCopy = Page::where('name', '=', 'My copied test page')->first();
197
198         $this->assertStringContainsString('This is some test content', $pageCopy->html);
199         $this->assertEquals('# This is some test content', $pageCopy->markdown);
200     }
201
202     public function test_page_copy_with_no_destination()
203     {
204         $page = $this->entities->page();
205         $currentBook = $page->book;
206
207         $resp = $this->asEditor()->get($page->getUrl('/copy'));
208         $resp->assertSee('Copy Page');
209
210         $movePageResp = $this->post($page->getUrl('/copy'), [
211             'name' => 'My copied test page',
212         ]);
213
214         $pageCopy = Page::where('name', '=', 'My copied test page')->first();
215
216         $movePageResp->assertRedirect($pageCopy->getUrl());
217         $this->assertTrue($pageCopy->book->id == $currentBook->id, 'Page was copied to correct book');
218         $this->assertTrue($pageCopy->id !== $page->id, 'Page copy is not the same instance');
219     }
220
221     public function test_page_can_be_copied_without_edit_permission()
222     {
223         $page = $this->entities->page();
224         $currentBook = $page->book;
225         $newBook = Book::where('id', '!=', $currentBook->id)->first();
226         $viewer = $this->users->viewer();
227
228         $resp = $this->actingAs($viewer)->get($page->getUrl());
229         $resp->assertDontSee($page->getUrl('/copy'));
230
231         $newBook->owned_by = $viewer->id;
232         $newBook->save();
233         $this->permissions->grantUserRolePermissions($viewer, ['page-create-own']);
234         $this->permissions->regenerateForEntity($newBook);
235
236         $resp = $this->actingAs($viewer)->get($page->getUrl());
237         $resp->assertSee($page->getUrl('/copy'));
238
239         $movePageResp = $this->post($page->getUrl('/copy'), [
240             'entity_selection' => 'book:' . $newBook->id,
241             'name'             => 'My copied test page',
242         ]);
243         $movePageResp->assertRedirect();
244
245         $this->assertDatabaseHas('pages', [
246             'name'       => 'My copied test page',
247             'created_by' => $viewer->id,
248             'book_id'    => $newBook->id,
249         ]);
250     }
251
252     public function test_old_page_slugs_redirect_to_new_pages()
253     {
254         $page = $this->entities->page();
255
256         // Need to save twice since revisions are not generated in seeder.
257         $this->asAdmin()->put($page->getUrl(), [
258             'name' => 'super test',
259             'html' => '<p></p>',
260         ]);
261
262         $page->refresh();
263         $pageUrl = $page->getUrl();
264
265         $this->put($pageUrl, [
266             'name' => 'super test page',
267             'html' => '<p></p>',
268         ]);
269
270         $this->get($pageUrl)
271             ->assertRedirect("/books/{$page->book->slug}/page/super-test-page");
272     }
273
274     public function test_page_within_chapter_deletion_returns_to_chapter()
275     {
276         $chapter = $this->entities->chapter();
277         $page = $chapter->pages()->first();
278
279         $this->asEditor()->delete($page->getUrl())
280             ->assertRedirect($chapter->getUrl());
281     }
282
283     public function test_recently_updated_pages_view()
284     {
285         $user = $this->users->editor();
286         $content = $this->entities->createChainBelongingToUser($user);
287
288         $resp = $this->asAdmin()->get('/pages/recently-updated');
289         $this->withHtml($resp)->assertElementContains('.entity-list .page:nth-child(1)', $content['page']->name);
290     }
291
292     public function test_recently_updated_pages_view_shows_updated_by_details()
293     {
294         $user = $this->users->editor();
295         $page = $this->entities->page();
296
297         $this->actingAs($user)->put($page->getUrl(), [
298             'name' => 'Updated title',
299             'html' => '<p>Updated content</p>',
300         ]);
301
302         $resp = $this->asAdmin()->get('/pages/recently-updated');
303         $this->withHtml($resp)->assertElementContains('.entity-list .page:nth-child(1) small', 'by ' . $user->name);
304     }
305
306     public function test_recently_updated_pages_view_shows_parent_chain()
307     {
308         $user = $this->users->editor();
309         $page = $this->entities->pageWithinChapter();
310
311         $this->actingAs($user)->put($page->getUrl(), [
312             'name' => 'Updated title',
313             'html' => '<p>Updated content</p>',
314         ]);
315
316         $resp = $this->asAdmin()->get('/pages/recently-updated');
317         $this->withHtml($resp)->assertElementContains('.entity-list .page:nth-child(1)', $page->chapter->getShortName(42));
318         $this->withHtml($resp)->assertElementContains('.entity-list .page:nth-child(1)', $page->book->getShortName(42));
319     }
320
321     public function test_recently_updated_pages_view_does_not_show_parent_if_not_visible()
322     {
323         $user = $this->users->editor();
324         $page = $this->entities->pageWithinChapter();
325
326         $this->actingAs($user)->put($page->getUrl(), [
327             'name' => 'Updated title',
328             'html' => '<p>Updated content</p>',
329         ]);
330
331         $this->permissions->setEntityPermissions($page->book);
332         $this->permissions->setEntityPermissions($page, ['view'], [$user->roles->first()]);
333
334         $resp = $this->get('/pages/recently-updated');
335         $resp->assertDontSee($page->book->getShortName(42));
336         $resp->assertDontSee($page->chapter->getShortName(42));
337         $this->withHtml($resp)->assertElementContains('.entity-list .page:nth-child(1)', 'Updated title');
338     }
339
340     public function test_recently_updated_pages_on_home()
341     {
342         /** @var Page $page */
343         $page = Page::query()->orderBy('updated_at', 'asc')->first();
344         Page::query()->where('id', '!=', $page->id)->update([
345             'updated_at' => Carbon::now()->subSecond(1),
346         ]);
347
348         $resp = $this->asAdmin()->get('/');
349         $this->withHtml($resp)->assertElementNotContains('#recently-updated-pages', $page->name);
350
351         $this->put($page->getUrl(), [
352             'name' => $page->name,
353             'html' => $page->html,
354         ]);
355
356         $resp = $this->get('/');
357         $this->withHtml($resp)->assertElementContains('#recently-updated-pages', $page->name);
358     }
359
360     public function test_page_edit_without_update_permissions_but_with_view_redirects_to_page()
361     {
362         $page = $this->entities->page();
363
364         $resp = $this->asViewer()->get($page->getUrl('/edit'));
365         $resp->assertRedirect($page->getUrl());
366
367         $resp->assertSessionHas('error', 'You do not have permission to access the requested page.');
368     }
369 }