diff --git a/app/Console/Commands/RegenerateCommentContentCommand.php b/app/Console/Commands/RegenerateCommentContentCommand.php deleted file mode 100644 index f7ec42620..000000000 --- a/app/Console/Commands/RegenerateCommentContentCommand.php +++ /dev/null @@ -1,49 +0,0 @@ -<?php - -namespace BookStack\Console\Commands; - -use BookStack\Activity\CommentRepo; -use BookStack\Activity\Models\Comment; -use Illuminate\Console\Command; -use Illuminate\Support\Facades\DB; - -class RegenerateCommentContentCommand extends Command -{ - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'bookstack:regenerate-comment-content - {--database= : The database connection to use}'; - - /** - * The console command description. - * - * @var string - */ - protected $description = 'Regenerate the stored HTML of all comments'; - - /** - * Execute the console command. - */ - public function handle(CommentRepo $commentRepo): int - { - $connection = DB::getDefaultConnection(); - if ($this->option('database') !== null) { - DB::setDefaultConnection($this->option('database')); - } - - Comment::query()->chunk(100, function ($comments) use ($commentRepo) { - foreach ($comments as $comment) { - $comment->html = $commentRepo->commentToHtml($comment->text); - $comment->save(); - } - }); - - DB::setDefaultConnection($connection); - $this->comment('Comment HTML content has been regenerated'); - - return 0; - } -} diff --git a/database/factories/Activity/Models/CommentFactory.php b/database/factories/Activity/Models/CommentFactory.php index 4b76cd11d..35d6db9bd 100644 --- a/database/factories/Activity/Models/CommentFactory.php +++ b/database/factories/Activity/Models/CommentFactory.php @@ -27,6 +27,7 @@ class CommentFactory extends Factory 'html' => $html, 'text' => $text, 'parent_id' => null, + 'local_id' => 1, ]; } } diff --git a/resources/views/comments/comments.blade.php b/resources/views/comments/comments.blade.php index 2c314864b..37329c42c 100644 --- a/resources/views/comments/comments.blade.php +++ b/resources/views/comments/comments.blade.php @@ -40,6 +40,9 @@ <script src="{{ versioned_asset('libs/tinymce/tinymce.min.js') }}" nonce="{{ $cspNonce }}"></script> @include('form.editor-translations') @endpush + @push('post-app-html') + @include('entities.selector-popup') + @endpush @endif </section> \ No newline at end of file diff --git a/resources/views/layouts/base.blade.php b/resources/views/layouts/base.blade.php index 43cca6b14..4d4d07dc2 100644 --- a/resources/views/layouts/base.blade.php +++ b/resources/views/layouts/base.blade.php @@ -68,7 +68,7 @@ </div> @yield('bottom') - + @stack('post-app-html') @if($cspNonce ?? false) <script src="{{ versioned_asset('dist/app.js') }}" nonce="{{ $cspNonce }}"></script> diff --git a/tests/Activity/WatchTest.php b/tests/Activity/WatchTest.php index 38935bbf5..605b60fd4 100644 --- a/tests/Activity/WatchTest.php +++ b/tests/Activity/WatchTest.php @@ -196,7 +196,7 @@ class WatchTest extends TestCase $notifications = Notification::fake(); $this->asAdmin()->post("/comment/{$entities['page']->id}", [ - 'text' => 'My new comment' + 'html' => '<p>My new comment</p>' ]); $notifications->assertSentTo($editor, CommentCreationNotification::class); } @@ -217,12 +217,12 @@ class WatchTest extends TestCase $notifications = Notification::fake(); $this->actingAs($editor)->post("/comment/{$entities['page']->id}", [ - 'text' => 'My new comment' + 'html' => '<p>My new comment</p>' ]); $comment = $entities['page']->comments()->orderBy('id', 'desc')->first(); $this->asAdmin()->post("/comment/{$entities['page']->id}", [ - 'text' => 'My new comment response', + 'html' => '<p>My new comment response</p>', 'parent_id' => $comment->local_id, ]); $notifications->assertSentTo($editor, CommentCreationNotification::class); @@ -257,7 +257,7 @@ class WatchTest extends TestCase // Comment post $this->actingAs($admin)->post("/comment/{$entities['page']->id}", [ - 'text' => 'My new comment response', + 'html' => '<p>My new comment response</p>', ]); $notifications->assertSentTo($editor, function (CommentCreationNotification $notification) use ($editor, $admin, $entities) { @@ -376,7 +376,7 @@ class WatchTest extends TestCase $this->permissions->disableEntityInheritedPermissions($page); $this->asAdmin()->post("/comment/{$page->id}", [ - 'text' => 'My new comment response', + 'html' => '<p>My new comment response</p>', ])->assertOk(); $notifications->assertNothingSentTo($editor); diff --git a/tests/Commands/RegenerateCommentContentCommandTest.php b/tests/Commands/RegenerateCommentContentCommandTest.php deleted file mode 100644 index 4940d66c3..000000000 --- a/tests/Commands/RegenerateCommentContentCommandTest.php +++ /dev/null @@ -1,31 +0,0 @@ -<?php - -namespace Tests\Commands; - -use BookStack\Activity\Models\Comment; -use Tests\TestCase; - -class RegenerateCommentContentCommandTest extends TestCase -{ - public function test_regenerate_comment_content_command() - { - Comment::query()->forceCreate([ - 'html' => 'some_old_content', - 'text' => 'some_fresh_content', - ]); - - $this->assertDatabaseHas('comments', [ - 'html' => 'some_old_content', - ]); - - $exitCode = \Artisan::call('bookstack:regenerate-comment-content'); - $this->assertTrue($exitCode === 0, 'Command executed successfully'); - - $this->assertDatabaseMissing('comments', [ - 'html' => 'some_old_content', - ]); - $this->assertDatabaseHas('comments', [ - 'html' => "<p>some_fresh_content</p>\n", - ]); - } -} diff --git a/tests/Entity/CommentTest.php b/tests/Entity/CommentTest.php index 23fc68197..c080359bc 100644 --- a/tests/Entity/CommentTest.php +++ b/tests/Entity/CommentTest.php @@ -27,7 +27,7 @@ class CommentTest extends TestCase 'local_id' => 1, 'entity_id' => $page->id, 'entity_type' => Page::newModelInstance()->getMorphClass(), - 'text' => $comment->text, + 'text' => null, 'parent_id' => 2, ]); @@ -43,17 +43,17 @@ class CommentTest extends TestCase $this->postJson("/comment/$page->id", $comment->getAttributes()); $comment = $page->comments()->first(); - $newText = 'updated text content'; + $newHtml = '<p>updated text content</p>'; $resp = $this->putJson("/comment/$comment->id", [ - 'text' => $newText, + 'html' => $newHtml, ]); $resp->assertStatus(200); - $resp->assertSee($newText); - $resp->assertDontSee($comment->text); + $resp->assertSee($newHtml, false); + $resp->assertDontSee($comment->html, false); $this->assertDatabaseHas('comments', [ - 'text' => $newText, + 'html' => $newHtml, 'entity_id' => $page->id, ]); @@ -80,46 +80,28 @@ class CommentTest extends TestCase $this->assertActivityExists(ActivityType::COMMENT_DELETE); } - public function test_comments_converts_markdown_input_to_html() - { - $page = $this->entities->page(); - $this->asAdmin()->postJson("/comment/$page->id", [ - 'text' => '# My Title', - ]); - - $this->assertDatabaseHas('comments', [ - 'entity_id' => $page->id, - 'entity_type' => $page->getMorphClass(), - 'text' => '# My Title', - 'html' => "<h1>My Title</h1>\n", - ]); - - $pageView = $this->get($page->getUrl()); - $pageView->assertSee('<h1>My Title</h1>', false); - } - - public function test_html_cannot_be_injected_via_comment_content() + public function test_scripts_cannot_be_injected_via_comment_html() { $this->asAdmin(); $page = $this->entities->page(); - $script = '<script>const a = "script";</script>\n\n# sometextinthecomment'; + $script = '<script>const a = "script";</script><p onclick="1">My lovely comment</p>'; $this->postJson("/comment/$page->id", [ - 'text' => $script, + 'html' => $script, ]); $pageView = $this->get($page->getUrl()); $pageView->assertDontSee($script, false); - $pageView->assertSee('sometextinthecomment'); + $pageView->assertSee('<p>My lovely comment</p>', false); $comment = $page->comments()->first(); $this->putJson("/comment/$comment->id", [ - 'text' => $script . 'updated', + 'html' => $script . '<p>updated</p>', ]); $pageView = $this->get($page->getUrl()); $pageView->assertDontSee($script, false); - $pageView->assertSee('sometextinthecommentupdated'); + $pageView->assertSee('<p>My lovely comment</p><p>updated</p>'); } public function test_reply_comments_are_nested() @@ -127,15 +109,17 @@ class CommentTest extends TestCase $this->asAdmin(); $page = $this->entities->page(); - $this->postJson("/comment/$page->id", ['text' => 'My new comment']); - $this->postJson("/comment/$page->id", ['text' => 'My new comment']); + $this->postJson("/comment/$page->id", ['html' => '<p>My new comment</p>']); + $this->postJson("/comment/$page->id", ['html' => '<p>My new comment</p>']); $respHtml = $this->withHtml($this->get($page->getUrl())); $respHtml->assertElementCount('.comment-branch', 3); $respHtml->assertElementNotExists('.comment-branch .comment-branch'); $comment = $page->comments()->first(); - $resp = $this->postJson("/comment/$page->id", ['text' => 'My nested comment', 'parent_id' => $comment->local_id]); + $resp = $this->postJson("/comment/$page->id", [ + 'html' => '<p>My nested comment</p>', 'parent_id' => $comment->local_id + ]); $resp->assertStatus(200); $respHtml = $this->withHtml($this->get($page->getUrl())); @@ -147,7 +131,7 @@ class CommentTest extends TestCase { $page = $this->entities->page(); - $this->asAdmin()->postJson("/comment/$page->id", ['text' => 'My great comment to see in the editor']); + $this->asAdmin()->postJson("/comment/$page->id", ['html' => '<p>My great comment to see in the editor</p>']); $respHtml = $this->withHtml($this->get($page->getUrl('/edit'))); $respHtml->assertElementContains('.comment-box .content', 'My great comment to see in the editor'); @@ -164,4 +148,34 @@ class CommentTest extends TestCase $pageResp = $this->asAdmin()->get($page->getUrl()); $pageResp->assertSee('Wolfeschlegels…'); } + + public function test_comment_editor_js_loaded_with_create_or_edit_permissions() + { + $editor = $this->users->editor(); + $page = $this->entities->page(); + + $resp = $this->actingAs($editor)->get($page->getUrl()); + $resp->assertSee('tinymce.min.js?', false); + $resp->assertSee('window.editor_translations', false); + $resp->assertSee('component="entity-selector"', false); + + $this->permissions->removeUserRolePermissions($editor, ['comment-create-all']); + $this->permissions->grantUserRolePermissions($editor, ['comment-update-own']); + + $resp = $this->actingAs($editor)->get($page->getUrl()); + $resp->assertDontSee('tinymce.min.js?', false); + $resp->assertDontSee('window.editor_translations', false); + $resp->assertDontSee('component="entity-selector"', false); + + Comment::factory()->create([ + 'created_by' => $editor->id, + 'entity_type' => 'page', + 'entity_id' => $page->id, + ]); + + $resp = $this->actingAs($editor)->get($page->getUrl()); + $resp->assertSee('tinymce.min.js?', false); + $resp->assertSee('window.editor_translations', false); + $resp->assertSee('component="entity-selector"', false); + } }