diff --git a/app/V1Module/presenters/ReferenceExerciseSolutionsPresenter.php b/app/V1Module/presenters/ReferenceExerciseSolutionsPresenter.php index 0f3ccd6a..4fc1c94e 100644 --- a/app/V1Module/presenters/ReferenceExerciseSolutionsPresenter.php +++ b/app/V1Module/presenters/ReferenceExerciseSolutionsPresenter.php @@ -209,6 +209,33 @@ public function actionDetail(string $solutionId) $this->sendSuccessResponse($this->referenceSolutionViewFactory->getReferenceSolution($solution)); } + public function checkUpdate(string $solutionId) + { + $solution = $this->referenceSolutions->findOrThrow($solutionId); + if (!$this->referenceSolutionAcl->canUpdate($solution)) { + throw new ForbiddenRequestException("You cannot update the ref. solution"); + } + } + + /** + * Update details about the ref. solution (note, etc...) + * @POST + * @Param(type="post", name="note", validation="string:0..65535", + * description="A description by the author of the solution") + * @param string $solutionId Identifier of the solution + * @throws NotFoundException + * @throws InternalServerException + */ + public function actionUpdate(string $solutionId) + { + $req = $this->getRequest(); + $solution = $this->referenceSolutions->findOrThrow($solutionId); + $solution->setDescription($req->getPost("note")); + + $this->referenceSolutions->flush(); + $this->sendSuccessResponse($this->referenceSolutionViewFactory->getReferenceSolution($solution)); + } + public function checkDeleteReferenceSolution(string $solutionId) { $solution = $this->referenceSolutions->findOrThrow($solutionId); diff --git a/app/V1Module/router/RouterFactory.php b/app/V1Module/router/RouterFactory.php index ad169ea2..836e8254 100644 --- a/app/V1Module/router/RouterFactory.php +++ b/app/V1Module/router/RouterFactory.php @@ -336,6 +336,7 @@ private static function createReferenceSolutionsRoutes(string $prefix): RouteLis ); $router[] = new GetRoute("$prefix/", "ReferenceExerciseSolutions:detail"); + $router[] = new PostRoute("$prefix/", "ReferenceExerciseSolutions:update"); $router[] = new DeleteRoute("$prefix/", "ReferenceExerciseSolutions:deleteReferenceSolution"); $router[] = new PostRoute("$prefix//resubmit", "ReferenceExerciseSolutions:resubmit"); $router[] = new GetRoute("$prefix//submissions", "ReferenceExerciseSolutions:submissions"); diff --git a/app/V1Module/security/ACL/IReferenceExerciseSolutionPermissions.php b/app/V1Module/security/ACL/IReferenceExerciseSolutionPermissions.php index 14c3da5c..85db10ce 100644 --- a/app/V1Module/security/ACL/IReferenceExerciseSolutionPermissions.php +++ b/app/V1Module/security/ACL/IReferenceExerciseSolutionPermissions.php @@ -8,6 +8,8 @@ interface IReferenceExerciseSolutionPermissions { public function canViewDetail(ReferenceExerciseSolution $referenceExerciseSolution): bool; + public function canUpdate(ReferenceExerciseSolution $referenceExerciseSolution): bool; + public function canDelete(ReferenceExerciseSolution $referenceExerciseSolution): bool; public function canEvaluate(ReferenceExerciseSolution $referenceExerciseSolution): bool; diff --git a/app/config/permissions.neon b/app/config/permissions.neon index a5380a58..ff836165 100644 --- a/app/config/permissions.neon +++ b/app/config/permissions.neon @@ -882,6 +882,7 @@ permissions: resource: referenceExerciseSolution actions: - viewDetail + - update - promote - allow: true @@ -918,6 +919,20 @@ permissions: - referenceExerciseSolution.isExerciseSuperGroupAdmin - referenceExerciseSolution.isExerciseSubGroupNonStudentMember + - allow: true + role: supervisor-student + resource: referenceExerciseSolution + actions: + - update + conditions: + - or: + - referenceExerciseSolution.isAuthor + - and: + - referenceExerciseSolution.isPublic + - or: + - referenceExerciseSolution.isExerciseAuthorOrAdmin + - referenceExerciseSolution.isExerciseSuperGroupAdmin + - allow: true role: supervisor-student resource: referenceExerciseSolution diff --git a/app/model/entity/ReferenceExerciseSolution.php b/app/model/entity/ReferenceExerciseSolution.php index 63057739..5f9c306c 100644 --- a/app/model/entity/ReferenceExerciseSolution.php +++ b/app/model/entity/ReferenceExerciseSolution.php @@ -101,6 +101,11 @@ public function getDescription(): string return $this->description; } + public function setDescription(string $desccription): void + { + $this->description = $desccription; + } + public function getSolution(): Solution { return $this->solution; diff --git a/tests/Presenters/ReferenceExerciseSolutionsPresenter.phpt b/tests/Presenters/ReferenceExerciseSolutionsPresenter.phpt index 4c40f055..f2db5b24 100644 --- a/tests/Presenters/ReferenceExerciseSolutionsPresenter.phpt +++ b/tests/Presenters/ReferenceExerciseSolutionsPresenter.phpt @@ -714,6 +714,54 @@ class TestReferenceExerciseSolutionsPresenter extends Tester\TestCase Assert::equal(ReferenceExerciseSolution::VISIBILITY_PROMOTED, $payload['visibility']); Assert::equal(ReferenceExerciseSolution::VISIBILITY_PROMOTED, $solution->getVisibility()); } + + public function testUpdateDesription() + { + PresenterTestHelper::login($this->container, PresenterTestHelper::ANOTHER_SUPERVISOR_LOGIN); + $solution = current(array_filter($this->referenceSolutions->findAll(), function ($rs) { + // get solutions of logged-in user + return $rs->getSolution()->getAuthor()->getEmail() === PresenterTestHelper::ANOTHER_SUPERVISOR_LOGIN; + })); + Assert::truthy($solution); + + $newNote = 'new-description'; + + $payload = PresenterTestHelper::performPresenterRequest( + $this->presenter, + 'V1:ReferenceExerciseSolutions', + 'POST', + [ 'action' => 'update', 'solutionId' => $solution->getId() ], + [ 'note' => $newNote ] + ); + + Assert::equal($newNote, $payload['description']); + $this->referenceSolutions->refresh($solution); + Assert::equal($newNote, $solution->getDescription()); + } + + public function testUpdateDesriptionUnauthorized() + { + PresenterTestHelper::login($this->container, PresenterTestHelper::ANOTHER_SUPERVISOR_LOGIN); + $solution = current(array_filter($this->referenceSolutions->findAll(), function ($rs) { + // get solutions the do not belong to the logged-in user + return $rs->getSolution()->getAuthor()->getEmail() !== PresenterTestHelper::ANOTHER_SUPERVISOR_LOGIN; + })); + Assert::truthy($solution); + + // another supervisor is not the author, nor admin of the exercise/group... + Assert::exception( + function () use ($solution) { + $payload = PresenterTestHelper::performPresenterRequest( + $this->presenter, + 'V1:ReferenceExerciseSolutions', + 'POST', + [ 'action' => 'update', 'solutionId' => $solution->getId() ], + [ 'note' => 'new-description' ] + ); + }, + ForbiddenRequestException::class + ); + } } $testCase = new TestReferenceExerciseSolutionsPresenter();