diff --git a/config/routes.php b/config/routes.php
index 2f903922..91fb30ab 100644
--- a/config/routes.php
+++ b/config/routes.php
@@ -39,6 +39,7 @@ $route->get('/news', 'NewsController@index');
$route->get('/meetings', 'NewsController@meetings');
$route->get('/news/{id:\d+}', 'NewsController@show');
$route->post('/news/{id:\d+}', 'NewsController@comment');
+$route->post('/news/comment/{id:\d+}', 'NewsController@deleteComment');
// FAQ
$route->get('/faq', 'FaqController@index');
diff --git a/resources/lang/de_DE/additional.po b/resources/lang/de_DE/additional.po
index 875c7f0b..c2507d80 100644
--- a/resources/lang/de_DE/additional.po
+++ b/resources/lang/de_DE/additional.po
@@ -74,6 +74,9 @@ msgstr "Die Minuten nach dem Talk müssen eine Zahl sein."
msgid "news.comment.success"
msgstr "Kommentar gespeichert."
+msgid "news.comment-delete.success"
+msgstr "Kommentar erfolgreich gelöscht."
+
msgid "news.edit.success"
msgstr "News erfolgreich aktualisiert."
diff --git a/resources/lang/en_US/additional.po b/resources/lang/en_US/additional.po
index db0e33ea..0a828419 100644
--- a/resources/lang/en_US/additional.po
+++ b/resources/lang/en_US/additional.po
@@ -72,6 +72,9 @@ msgstr "The minutes after the talk have to be an integer."
msgid "news.comment.success"
msgstr "Comment saved."
+msgid "news.comment-delete.success"
+msgstr "Comment successfully deleted."
+
msgid "news.edit.success"
msgstr "News successfully updated."
diff --git a/resources/views/pages/news/news.twig b/resources/views/pages/news/news.twig
index 93d21628..8bc3225d 100644
--- a/resources/views/pages/news/news.twig
+++ b/resources/views/pages/news/news.twig
@@ -22,6 +22,17 @@
{{ comment.created_at.format(__('Y-m-d H:i')) }}
{{ m.user(comment.user) }}
+
+ {% if comment.user.id == user.id or has_permission_to('admin_news') or has_permission_to('comment.delete') %}
+
+
+
+ {% endif %}
{% endfor %}
diff --git a/src/Controllers/NewsController.php b/src/Controllers/NewsController.php
index 2d9b540d..c42a16e1 100644
--- a/src/Controllers/NewsController.php
+++ b/src/Controllers/NewsController.php
@@ -4,6 +4,7 @@ namespace Engelsystem\Controllers;
use Engelsystem\Config\Config;
use Engelsystem\Helpers\Authenticator;
+use Engelsystem\Http\Exceptions\HttpForbidden;
use Engelsystem\Http\Redirector;
use Engelsystem\Http\Request;
use Engelsystem\Http\Response;
@@ -18,6 +19,9 @@ class NewsController extends BaseController
/** @var Authenticator */
protected $auth;
+ /** @var NewsComment */
+ protected $comment;
+
/** @var Config */
protected $config;
@@ -39,13 +43,15 @@ class NewsController extends BaseController
/** @var array */
protected $permissions = [
'news',
- 'meetings' => 'user_meetings',
- 'comment' => 'news_comments',
+ 'meetings' => 'user_meetings',
+ 'comment' => 'news_comments',
+ 'deleteComment' => 'news_comments',
];
/**
* @param Authenticator $auth
* @param Config $config
+ * @param NewsComment $comment
* @param LoggerInterface $log
* @param News $news
* @param Redirector $redirector
@@ -54,6 +60,7 @@ class NewsController extends BaseController
*/
public function __construct(
Authenticator $auth,
+ NewsComment $comment,
Config $config,
LoggerInterface $log,
News $news,
@@ -62,6 +69,7 @@ class NewsController extends BaseController
Request $request
) {
$this->auth = $auth;
+ $this->comment = $comment;
$this->config = $config;
$this->log = $log;
$this->news = $news;
@@ -132,6 +140,41 @@ class NewsController extends BaseController
return $this->redirect->back();
}
+ /**
+ * @param Request $request
+ *
+ * @return Response
+ */
+ public function deleteComment(Request $request): Response
+ {
+ $id = $request->getAttribute('id');
+ $this->validate(
+ $request,
+ [
+ 'delete' => 'checked',
+ ]
+ );
+
+ $comment = $this->comment->findOrFail($id);
+ if (
+ $comment->user->id != $this->auth->user()->id
+ && !$this->auth->can('admin_news')
+ && !$this->auth->can('comment.delete')
+ ) {
+ throw new HttpForbidden();
+ }
+
+ $comment->delete();
+
+ $this->log->info(
+ 'Deleted comment "{comment}" of news "{news}"',
+ ['comment' => $comment->text, 'news' => $comment->news->title]
+ );
+ $this->addNotification('news.comment-delete.success');
+
+ return $this->redirect->to('/news/' . $comment->news->id);
+ }
+
/**
* @param bool $onlyMeetings
* @return Response
diff --git a/tests/Unit/Controllers/ControllerTest.php b/tests/Unit/Controllers/ControllerTest.php
index 1d2f35eb..a0b76339 100644
--- a/tests/Unit/Controllers/ControllerTest.php
+++ b/tests/Unit/Controllers/ControllerTest.php
@@ -72,5 +72,6 @@ abstract class ControllerTest extends TestCase
$this->config = new Config();
$this->app->instance('config', $this->config);
+ $this->app->instance(Config::class, $this->config);
}
}
diff --git a/tests/Unit/Controllers/NewsControllerTest.php b/tests/Unit/Controllers/NewsControllerTest.php
index df0c977a..1e2a26dd 100644
--- a/tests/Unit/Controllers/NewsControllerTest.php
+++ b/tests/Unit/Controllers/NewsControllerTest.php
@@ -2,30 +2,20 @@
namespace Engelsystem\Test\Unit\Controllers;
-use Engelsystem\Config\Config;
use Engelsystem\Controllers\NewsController;
use Engelsystem\Helpers\Authenticator;
+use Engelsystem\Http\Exceptions\HttpForbidden;
use Engelsystem\Http\Exceptions\ValidationException;
-use Engelsystem\Http\Request;
-use Engelsystem\Http\Response;
-use Engelsystem\Http\UrlGenerator;
-use Engelsystem\Http\UrlGeneratorInterface;
use Engelsystem\Http\Validation\Validator;
use Engelsystem\Models\News;
use Engelsystem\Models\NewsComment;
use Engelsystem\Models\User\User;
use Engelsystem\Test\Unit\HasDatabase;
-use Engelsystem\Test\Unit\TestCase;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Collection;
use PHPUnit\Framework\MockObject\MockObject;
-use Psr\Http\Message\ServerRequestInterface;
-use Psr\Log\LoggerInterface;
-use Psr\Log\Test\TestLogger;
-use Symfony\Component\HttpFoundation\Session\Session;
-use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
-class NewsControllerTest extends TestCase
+class NewsControllerTest extends ControllerTest
{
use HasDatabase;
@@ -75,15 +65,6 @@ class NewsControllerTest extends TestCase
],
];
- /** @var TestLogger */
- protected $log;
-
- /** @var Response|MockObject */
- protected $response;
-
- /** @var Request */
- protected $request;
-
/**
* @covers \Engelsystem\Controllers\NewsController::__construct
* @covers \Engelsystem\Controllers\NewsController::index
@@ -225,51 +206,108 @@ class NewsControllerTest extends TestCase
$this->log->hasInfoThatContains('Created news comment');
/** @var NewsComment $comment */
- $comment = NewsComment::whereNewsId(1)->first();
+ $comment = NewsComment::whereNewsId(1)->get()[2];
$this->assertEquals('Foo bar!', $comment->text);
}
/**
- * Setup environment
+ * @covers \Engelsystem\Controllers\NewsController::deleteComment
*/
- public function setUp(): void
+ public function testDeleteCommentInvalidRequest()
{
- parent::setUp();
- $this->initDatabase();
+ /** @var NewsController $controller */
+ $controller = $this->app->get(NewsController::class);
+ $controller->setValidator($this->app->get(Validator::class));
- $this->request = new Request();
- $this->app->instance('request', $this->request);
- $this->app->instance(Request::class, $this->request);
- $this->app->instance(ServerRequestInterface::class, $this->request);
+ $this->expectException(ValidationException::class);
+ $controller->deleteComment($this->request);
+ }
- $this->response = $this->createMock(Response::class);
- $this->app->instance(Response::class, $this->response);
+ /**
+ * @covers \Engelsystem\Controllers\NewsController::deleteComment
+ */
+ public function testDeleteCommentNotFound()
+ {
+ $this->request = $this->request->withAttribute('id', 42)->withParsedBody(['delete' => '1']);
- $this->app->instance(Config::class, new Config(['display_news' => 2]));
+ /** @var NewsController $controller */
+ $controller = $this->app->get(NewsController::class);
+ $controller->setValidator($this->app->get(Validator::class));
- $this->log = new TestLogger();
- $this->app->instance(LoggerInterface::class, $this->log);
+ $this->expectException(ModelNotFoundException::class);
+ $controller->deleteComment($this->request);
+ }
- $this->app->instance('session', new Session(new MockArraySessionStorage()));
+ /**
+ * @covers \Engelsystem\Controllers\NewsController::deleteComment
+ */
+ public function testDeleteCommentNotAllowed()
+ {
+ $this->request = $this->request->withAttribute('id', 2)->withParsedBody(['delete' => '1']);
- $this->auth = $this->createMock(Authenticator::class);
- $this->app->instance(Authenticator::class, $this->auth);
+ $this->addUser(1);
+ $this->addUser(2);
- $this->app->bind(UrlGeneratorInterface::class, UrlGenerator::class);
+ /** @var NewsController $controller */
+ $controller = $this->app->get(NewsController::class);
+ $controller->setValidator($this->app->get(Validator::class));
- $this->app->instance('config', new Config());
+ $this->expectException(HttpForbidden::class);
+ $controller->deleteComment($this->request);
+ }
+
+ /**
+ * @covers \Engelsystem\Controllers\NewsController::deleteComment
+ */
+ public function testDeleteComment()
+ {
+ $this->request = $this->request->withAttribute('id', 1)->withParsedBody(['delete' => '1']);
+ $this->setExpects($this->response, 'redirectTo', ['http://localhost/news/1'], $this->response);
+
+ $this->addUser(1);
+
+ /** @var NewsController $controller */
+ $controller = $this->app->get(NewsController::class);
+ $controller->setValidator($this->app->get(Validator::class));
+
+ $controller->deleteComment($this->request);
+
+ $this->assertCount(1, NewsComment::all());
+ $this->assertTrue($this->log->hasInfoThatContains('Deleted comment'));
+ $this->assertHasNotification('news.comment-delete.success');
+ }
+
+ /**
+ * Setup environment
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ $this->config->set(['display_news' => 2]);
+
+ $this->auth = $this->createMock(Authenticator::class);
+ $this->app->instance(Authenticator::class, $this->auth);
foreach ($this->data as $news) {
(new News($news))->save();
}
+
+ foreach ([1, 2] as $i) {
+ NewsComment::create([
+ 'news_id' => 1,
+ 'text' => 'test comment ' . $i,
+ 'user_id' => $i,
+ ]);
+ }
}
/**
* Creates a new user
*/
- protected function addUser()
+ protected function addUser(int $id = 42)
{
- $user = User::factory()->create(['id' => 42]);
+ $user = User::factory()->create(['id' => $id]);
$this->auth->expects($this->any())
->method('user')