diff --git a/lib/classes/FeedbackRange.interface.php b/lib/classes/FeedbackRange.interface.php index f5eaaeff743f622619105796e93d2ad858eadb61..863c197ba64f4424e8be500144c7ac74948b851c 100644 --- a/lib/classes/FeedbackRange.interface.php +++ b/lib/classes/FeedbackRange.interface.php @@ -11,6 +11,13 @@ interface FeedbackRange { + /** + * Returns the ID of this range. + * + * @return string|integer The ID of the range. + */ + public function getId(); + /** * Returns a human-friendly representation of the FeedbackRange object instance's name. * diff --git a/lib/classes/JsonApi/RouteMap.php b/lib/classes/JsonApi/RouteMap.php index 2eb33a815e2869bf776046c189f7cc93f0b47e91..0177db0cf373262c4ca7547fd73fdb165f27bd78 100644 --- a/lib/classes/JsonApi/RouteMap.php +++ b/lib/classes/JsonApi/RouteMap.php @@ -239,12 +239,20 @@ class RouteMap private function addAuthenticatedFeedbackRoutes(RouteCollectorProxy $group): void { $group->get('/feedback-elements/{id}', Routes\Feedback\FeedbackElementsShow::class); - $group->get('/feedback-elements/{id}/entries', Routes\Feedback\FeedbackEntriesIndex::class); $group->get('/courses/{id}/feedback-elements', Routes\Feedback\FeedbackElementsByCourseIndex::class); $group->get('/file-refs/{id}/feedback-elements', Routes\Feedback\FeedbackElementsByFileRefIndex::class); $group->get('/folders/{id}/feedback-elements', Routes\Feedback\FeedbackElementsByFolderIndex::class); + $group->post('/feedback-elements', Routes\Feedback\FeedbackElementsCreate::class); + $group->patch('/feedback-elements/{id}', Routes\Feedback\FeedbackElementsUpdate::class); + $group->delete('/feedback-elements/{id}', Routes\Feedback\FeedbackElementsDelete::class); + + $group->get('/feedback-elements/{id}/entries', Routes\Feedback\FeedbackEntriesIndex::class); + $group->post('/feedback-entries', Routes\Feedback\FeedbackEntriesCreate::class); + $group->get('/feedback-entries/{id}', Routes\Feedback\FeedbackEntriesShow::class); + $group->patch('/feedback-entries/{id}', Routes\Feedback\FeedbackEntriesUpdate::class); + $group->delete('/feedback-entries/{id}', Routes\Feedback\FeedbackEntriesDelete::class); } private function addAuthenticatedInstitutesRoutes(RouteCollectorProxy $group): void @@ -299,7 +307,7 @@ class RouteMap $group->get('/tree-node/{id}', Routes\Tree\TreeShow::class); $group->get('/tree-node/{id}/children', Routes\Tree\ChildrenOfTreeNode::class); - $group->get('/tree-node/{id}/courseinfo', Routes\Tree\CourseInfoOfTreeNode::class); + $group->get('/tree-node/{id}/courseinfo', Routes\Tree\CourseinfoOfTreeNode::class); $group->get('/tree-node/{id}/courses', Routes\Tree\CoursesOfTreeNode::class); $group->get('/tree-node/course/pathinfo/{classname}/{id}', Routes\Tree\PathinfoOfTreeNodeCourse::class); $group->get('/tree-node/course/details/{id}', Routes\Tree\DetailsOfTreeNodeCourse::class); diff --git a/lib/classes/JsonApi/Routes/Feedback/Authority.php b/lib/classes/JsonApi/Routes/Feedback/Authority.php index 44397817fa063b867b91ae19a4657dce56b3a28b..683f1ae96d653659b6a2e623f83b0def30bf4432 100644 --- a/lib/classes/JsonApi/Routes/Feedback/Authority.php +++ b/lib/classes/JsonApi/Routes/Feedback/Authority.php @@ -2,45 +2,109 @@ namespace JsonApi\Routes\Feedback; +use Feedback; +use FeedbackElement; +use FeedbackEntry; +use FeedbackRange; +use SimpleORMap; use User; +/** + * @SuppressWarnings(PHPMD.StaticAccess) + * @SuppressWarnings(PHPMD.TooManyPublicMethods) + */ class Authority { - public static function canShowFeedbackElement(User $user, \FeedbackElement $resource) + public static function canShowFeedbackElement(User $user, FeedbackElement $resource): bool { - return \Feedback::hasRangeAccess($resource->range_id, $resource->range_type, $user->id); + return Feedback::hasRangeAccess($resource->range_id, $resource->range_type, $user->getId()); } - public static function canIndexFeedbackEntries(User $user, \FeedbackElement $resource) + public static function canIndexFeedbackEntries(User $user, FeedbackElement $resource): bool { return self::canShowFeedbackElement($user, $resource); } - public static function canSeeResultsOfFeedbackElement(User $user, \FeedbackElement $resource) + public static function canSeeResultsOfFeedbackElement(User $user, FeedbackElement $resource): bool { return self::canIndexFeedbackEntries($user, $resource) && - ($resource['results_visible'] || \Feedback::hasAdminPerm($resource['course_id'], $user->id)); + ($resource['results_visible'] || \Feedback::hasAdminPerm($resource['course_id'], $user->getId())); } - public static function canIndexFeedbackElementsOfCourse(User $user, \Course $course) + public static function canIndexFeedbackElementsOfCourse(User $user, \Course $course): bool { - return \Feedback::hasRangeAccess($course->id, \Course::class, $user->id); + return \Feedback::hasRangeAccess($course->getId(), \Course::class, $user->getId()); } - public static function canIndexFeedbackElementsOfFileRef(User $user, \FileRef $fileRef) + public static function canIndexFeedbackElementsOfFileRef(User $user, \FileRef $fileRef): bool { - return \Feedback::hasRangeAccess($fileRef->id, \FileRef::class, $user->id); + return \Feedback::hasRangeAccess($fileRef->getId(), \FileRef::class, $user->getId()); } - public static function canIndexFeedbackElementsOfFolder(User $user, \Folder $folder) + public static function canIndexFeedbackElementsOfFolder(User $user, \Folder $folder): bool { - return \Feedback::hasRangeAccess($folder->id, \Folder::class, $user->id); + return \Feedback::hasRangeAccess($folder->getId(), \Folder::class, $user->getId()); } - public static function canShowFeedbackEntry(User $user, \FeedbackEntry $resource) + public static function canShowFeedbackEntry(User $user, \FeedbackEntry $resource): bool { $feedbackElement = $resource->feedback; return self::canShowFeedbackElement($user, $feedbackElement); } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public static function canCreateFeedbackEntry(User $user, FeedbackElement $element): bool + { + if (!$element->isFeedbackable()) { + return false; + } + + // TODO: Wann darf ich Feedback Entries schreiben + return true; + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public static function canUpdateFeedbackEntry(User $user, FeedbackEntry $entry): bool + { + if (!$entry->isEditable()) { + return false; + } + + // TODO: Wann darf ich Feedback Entries bearbeiten + return true; + } + + public static function canDeleteFeedbackEntry(User $user, FeedbackEntry $entry): bool + { + return self::canUpdateFeedbackEntry($user, $entry); + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public static function canCreateFeedbackElement(User $user, FeedbackRange $range): bool + { + // TODO: Wann darf ich Feedback Elemente anhängen + // bisher https://gitlab.studip.de/studip/studip/-/blob/main/lib/classes/Feedback.class.php#L76 + return true; + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public static function canUpdateFeedbackElement(User $user, FeedbackElement $element): bool + { + // TODO: Wann darf ich Feedback Elemente ändern? + return true; + } + + public static function canDeleteFeedbackElement(User $user, FeedbackElement $element): bool + { + return self::canUpdateFeedbackElement($user, $element); + } } diff --git a/lib/classes/JsonApi/Routes/Feedback/FeedbackElementsCreate.php b/lib/classes/JsonApi/Routes/Feedback/FeedbackElementsCreate.php new file mode 100644 index 0000000000000000000000000000000000000000..85dd4bea810cbc852090b0d826ac791f4099a17c --- /dev/null +++ b/lib/classes/JsonApi/Routes/Feedback/FeedbackElementsCreate.php @@ -0,0 +1,116 @@ +<?php + +namespace JsonApi\Routes\Feedback; + +use FeedbackElement; +use FeedbackRange; +use User; +use JsonApi\Errors\AuthorizationFailedException; +use JsonApi\JsonApiController; +use JsonApi\Routes\ValidationTrait; +use JsonApi\Schemas\FeedbackElement as FeedbackElementSchema; +use Psr\Http\Message\ResponseInterface as Response; +use Psr\Http\Message\ServerRequestInterface as Request; + +/** + * Create a FeedbackElement. + * + * @SuppressWarnings(PHPMD.StaticAccess) + */ +class FeedbackElementsCreate extends JsonApiController +{ + use RangeTypeAware; + use ValidationTrait; + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @param array $args + * + * @return Response + */ + public function __invoke(Request $request, Response $response, $args) + { + $this->preparePossibleRangeTypes(); + + $json = $this->validate($request); + $range = $this->getRangeFromJson($json); + $user = $this->getUser($request); + + if (!Authority::canCreateFeedbackElement($user, $range)) { + throw new AuthorizationFailedException(); + } + + $feedbackElement = $this->create($user, $json); + + return $this->getCreatedResponse($feedbackElement); + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameters) + * + * @param array $json + * @param mixed $data + * + * @return string|void + */ + protected function validateResourceDocument($json, $data) + { + if (!self::arrayHas($json, 'data')) { + return 'Missing `data` member at document´s top level.'; + } + if (FeedbackElementSchema::TYPE !== self::arrayGet($json, 'data.type')) { + return 'Invalid `type` of document´s `data`.'; + } + if (self::arrayHas($json, 'data.id')) { + return 'New document must not have an `id`.'; + } + + $required = ['question', 'description', 'mode', 'results-visible', 'is-commentable']; + foreach ($required as $attribute) { + if (!self::arrayHas($json, 'data.attributes.' . $attribute)) { + return 'Missing `' . $attribute . '` attribute.'; + } + } + + if (!self::arrayHas($json, 'data.relationships.range')) { + return 'Missing `range` relationship.'; + } + if (!$this->getRangeFromJson($json)) { + return 'Invalid `range` relationship.'; + } + } + + private function getRangeFromJson(array $json): ?FeedbackRange + { + $rangeType = self::arrayGet($json, 'data.relationships.range.data.type'); + $rangeId = self::arrayGet($json, 'data.relationships.range.data.id'); + + if (!isset($this->possibleRangeTypes[$rangeType])) { + return null; + } + $rangeClass = $this->possibleRangeTypes[$rangeType]; + + return $rangeClass::find($rangeId); + } + + private function create(User $user, array $json): FeedbackElement + { + $range = $this->getRangeFromJson($json); + $feedback = \FeedbackElement::build([ + 'range_id' => $range->getId(), + 'range_type' => get_class($range), + 'user_id' => $user->getId(), + 'question' => self::arrayGet($json, 'data.attributes.question'), + 'description' => self::arrayGet($json, 'data.attributes.description'), + 'mode' => self::arrayGet($json, 'data.attributes.mode'), + 'results_visible' => (int) self::arrayGet($json, 'data.attributes.results-visible'), + 'commentable' => (int) self::arrayGet($json, 'data.attributes.is-commentable'), + // TODO: + 'course_id' => $range->getRangeCourseId(), + ]); + $feedback->store(); + + return $feedback; + } +} diff --git a/lib/classes/JsonApi/Routes/Feedback/FeedbackElementsDelete.php b/lib/classes/JsonApi/Routes/Feedback/FeedbackElementsDelete.php new file mode 100644 index 0000000000000000000000000000000000000000..874a172e7082cdaa337888e57126288409720081 --- /dev/null +++ b/lib/classes/JsonApi/Routes/Feedback/FeedbackElementsDelete.php @@ -0,0 +1,39 @@ +<?php + +namespace JsonApi\Routes\Feedback; + +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; +use JsonApi\Errors\AuthorizationFailedException; +use JsonApi\Errors\RecordNotFoundException; +use JsonApi\JsonApiController; + +/** + * Deletes a feedback element. + * + * @SuppressWarnings(PHPMD.StaticAccess) + */ +class FeedbackElementsDelete extends JsonApiController +{ + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @param array $args + * + * @return Response + */ + public function __invoke(Request $request, Response $response, $args) + { + $resource = \FeedbackElement::find($args['id']); + if (!$resource) { + throw new RecordNotFoundException(); + } + + if (!Authority::canDeleteFeedbackElement($this->getUser($request), $resource)) { + throw new AuthorizationFailedException(); + } + $resource->delete(); + + return $this->getCodeResponse(204); + } +} diff --git a/lib/classes/JsonApi/Routes/Feedback/FeedbackElementsShow.php b/lib/classes/JsonApi/Routes/Feedback/FeedbackElementsShow.php index 5ecc5932c83f5548d14aabea816251d06e253831..849e58a7ea9f2beed3627b81d3a7e240ed241345 100644 --- a/lib/classes/JsonApi/Routes/Feedback/FeedbackElementsShow.php +++ b/lib/classes/JsonApi/Routes/Feedback/FeedbackElementsShow.php @@ -7,20 +7,33 @@ use Psr\Http\Message\ResponseInterface as Response; use JsonApi\Errors\AuthorizationFailedException; use JsonApi\Errors\RecordNotFoundException; use JsonApi\JsonApiController; +use JsonApi\Schemas\FeedbackElement as FeedbackElementSchema; /** * Displays a certain feedback element. + * + * @SuppressWarnings(PHPMD.StaticAccess) */ class FeedbackElementsShow extends JsonApiController { - protected $allowedIncludePaths = ['author', 'course', 'entries', 'range']; + protected $allowedIncludePaths = [ + FeedbackElementSchema::REL_AUTHOR, + FeedbackElementSchema::REL_COURSE, + FeedbackElementSchema::REL_ENTRIES, + FeedbackElementSchema::REL_RANGE, + ]; /** * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @param array $args + * + * @return Response */ public function __invoke(Request $request, Response $response, $args) { - if (!$resource = \FeedbackElement::find($args['id'])) { + $resource = \FeedbackElement::find($args['id']); + if (!$resource) { throw new RecordNotFoundException(); } diff --git a/lib/classes/JsonApi/Routes/Feedback/FeedbackElementsUpdate.php b/lib/classes/JsonApi/Routes/Feedback/FeedbackElementsUpdate.php new file mode 100644 index 0000000000000000000000000000000000000000..4a9460b63920b0e2cb3d922fbc3b03d1e1a36875 --- /dev/null +++ b/lib/classes/JsonApi/Routes/Feedback/FeedbackElementsUpdate.php @@ -0,0 +1,100 @@ +<?php + +namespace JsonApi\Routes\Feedback; + +use FeedbackElement; +use FeedbackRange; +use User; +use JsonApi\Errors\AuthorizationFailedException; +use JsonApi\Errors\RecordNotFoundException; +use JsonApi\JsonApiController; +use JsonApi\Routes\ValidationTrait; +use JsonApi\Schemas\FeedbackElement as FeedbackElementSchema; +use Psr\Http\Message\ResponseInterface as Response; +use Psr\Http\Message\ServerRequestInterface as Request; + +/** + * Update a FeedbackElement. + * + * @SuppressWarnings(PHPMD.StaticAccess) + */ +class FeedbackElementsUpdate extends JsonApiController +{ + use RangeTypeAware; + use ValidationTrait; + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @param array $args + * + * @return Response + */ + public function __invoke(Request $request, Response $response, $args) + { + $this->preparePossibleRangeTypes(); + $resource = \FeedbackElement::find($args['id']); + if (!$resource) { + throw new RecordNotFoundException(); + } + + $json = $this->validate($request); + $user = $this->getUser($request); + + if (!Authority::canUpdateFeedbackElement($user, $resource)) { + throw new AuthorizationFailedException(); + } + + $feedbackElement = $this->update($resource, $json); + + return $this->getContentResponse($feedbackElement); + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameters) + * + * @param array $json + * @param mixed $data + * + * @return string|void + */ + protected function validateResourceDocument($json, $data) + { + if (!self::arrayHas($json, 'data')) { + return 'Missing `data` member at document´s top level.'; + } + if (FeedbackElementSchema::TYPE !== self::arrayGet($json, 'data.type')) { + return 'Invalid `type` of document´s `data`.'; + } + if (!self::arrayHas($json, 'data.id')) { + return 'An existing document must have an `id`.'; + } + + $required = ['question', 'description', 'mode', 'results-visible', 'is-commentable']; + foreach ($required as $attribute) { + if (!self::arrayHas($json, 'data.attributes.' . $attribute)) { + return 'Missing `' . $attribute . '` attribute.'; + } + } + } + + private function update(FeedbackElement $feedbackElement, array $json): FeedbackElement + { + $strAttrs = ['question', 'description', 'mode']; + foreach ($strAttrs as $attribute) { + if (self::arrayHas($json, 'data.attributes.' . $attribute)) { + $feedbackElement[$attribute] = self::arrayGet($json, 'data.attributes.' . $attribute); + } + } + + $boolAttrs = ['results-visible', 'is-commentable']; + foreach ($boolAttrs as $attribute) { + if (self::arrayHas($json, 'data.attributes.' . $attribute)) { + $feedbackElement[$attribute] = self::arrayGet($json, 'data.attributes.' . $attribute) ? 1 : 0; + } + } + $feedbackElement->store(); + + return $feedbackElement; + } +} diff --git a/lib/classes/JsonApi/Routes/Feedback/FeedbackEntriesCreate.php b/lib/classes/JsonApi/Routes/Feedback/FeedbackEntriesCreate.php new file mode 100644 index 0000000000000000000000000000000000000000..0337ce4e2e1e018c7d028cde314b79b4f7c15fcb --- /dev/null +++ b/lib/classes/JsonApi/Routes/Feedback/FeedbackEntriesCreate.php @@ -0,0 +1,112 @@ +<?php + +namespace JsonApi\Routes\Feedback; + +use FeedbackElement; +use FeedbackEntry; +use InvalidArgumentException; +use User; +use JsonApi\Errors\AuthorizationFailedException; +use JsonApi\JsonApiController; +use JsonApi\Routes\ValidationTrait; +use JsonApi\Schemas\FeedbackElement as FeedbackElementSchema; +use JsonApi\Schemas\FeedbackEntry as FeedbackEntrySchema; +use Psr\Http\Message\ResponseInterface as Response; +use Psr\Http\Message\ServerRequestInterface as Request; + +/** + * Create a FeedbackEntry. + * + * @SuppressWarnings(PHPMD.StaticAccess) + */ +class FeedbackEntriesCreate extends JsonApiController +{ + use RatingHelper; + use ValidationTrait; + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @param array $args + * + * @return Response + */ + public function __invoke(Request $request, Response $response, $args) + { + $json = $this->validate($request); + $element = $this->getElementFromJson($json); + $user = $this->getUser($request); + + if (!Authority::canCreateFeedbackEntry($user, $element)) { + throw new AuthorizationFailedException(); + } + + $feedbackEntry = $this->create($user, $json); + + return $this->getCreatedResponse($feedbackEntry); + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameters) + * + * @param array $json + * @param mixed $data + * + * @return string|void + */ + protected function validateResourceDocument($json, $data) + { + if (!self::arrayHas($json, 'data')) { + return 'Missing `data` member at document´s top level.'; + } + if (FeedbackEntrySchema::TYPE !== self::arrayGet($json, 'data.type')) { + return 'Invalid `type` of document´s `data`.'; + } + if (self::arrayHas($json, 'data.id')) { + return 'New document must not have an `id`.'; + } + + if (!self::arrayHas($json, 'data.relationships.feedback-element')) { + return 'Missing `feedback-element` relationship.'; + } + if (!$this->getElementFromJson($json)) { + return 'Invalid `feedback-element` relationship.'; + } + + $required = ['rating']; + foreach ($required as $attribute) { + if (!self::arrayHas($json, 'data.attributes.' . $attribute)) { + return 'Missing `' . $attribute . '` attribute.'; + } + } + } + + private function getElementFromJson(array $json): ?FeedbackElement + { + $relationship = FeedbackEntrySchema::REL_FEEDBACK; + if (!$this->validateResourceObject($json, 'data.relationships.' . $relationship, FeedbackElementSchema::TYPE)) { + return null; + } + $resourceId = self::arrayGet($json, 'data.relationships.' . $relationship . '.data.id'); + + return FeedbackElement::find($resourceId); + } + + private function create(User $user, array $json): FeedbackEntry + { + $element = $this->getElementFromJson($json); + $entry = \FeedbackEntry::build([ + 'feedback_id' => $element->getId(), + 'user_id' => $user->getId(), + 'rating' => $this->getRating($element, (int) self::arrayGet($json, 'data.attributes.rating')), + ]); + + if ($element['commentable']) { + $entry['comment'] = self::arrayGet($json, 'data.attributes.comment', ''); + } + + $entry->store(); + + return $entry; + } +} diff --git a/lib/classes/JsonApi/Routes/Feedback/FeedbackEntriesDelete.php b/lib/classes/JsonApi/Routes/Feedback/FeedbackEntriesDelete.php new file mode 100644 index 0000000000000000000000000000000000000000..7afcdb12ea143ee9316375bab7bb5f2c93f6f182 --- /dev/null +++ b/lib/classes/JsonApi/Routes/Feedback/FeedbackEntriesDelete.php @@ -0,0 +1,40 @@ +<?php + +namespace JsonApi\Routes\Feedback; + +use FeedbackEntry; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; +use JsonApi\Errors\AuthorizationFailedException; +use JsonApi\Errors\RecordNotFoundException; +use JsonApi\JsonApiController; + +/** + * Deletes a feedback entry. + * + * @SuppressWarnings(PHPMD.StaticAccess) + */ +class FeedbackEntriesDelete extends JsonApiController +{ + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @param array $args + * + * @return Response + */ + public function __invoke(Request $request, Response $response, $args) + { + $resource = FeedbackEntry::find($args['id']); + if (!$resource) { + throw new RecordNotFoundException(); + } + + if (!Authority::canDeleteFeedbackEntry($this->getUser($request), $resource)) { + throw new AuthorizationFailedException(); + } + $resource->delete(); + + return $this->getCodeResponse(204); + } +} diff --git a/lib/classes/JsonApi/Routes/Feedback/FeedbackEntriesShow.php b/lib/classes/JsonApi/Routes/Feedback/FeedbackEntriesShow.php index b591db3e1351e5ea1cf20660d078909686a7e6e4..4a85c69cdbe1a53635f439fb38e787c349ab69d7 100644 --- a/lib/classes/JsonApi/Routes/Feedback/FeedbackEntriesShow.php +++ b/lib/classes/JsonApi/Routes/Feedback/FeedbackEntriesShow.php @@ -7,19 +7,27 @@ use Psr\Http\Message\ResponseInterface as Response; use JsonApi\Errors\AuthorizationFailedException; use JsonApi\Errors\RecordNotFoundException; use JsonApi\JsonApiController; +use JsonApi\Schemas\FeedbackEntry as FeedbackEntrySchema; /** * Displays a certain feedback entry. */ class FeedbackEntriesShow extends JsonApiController { - protected $allowedIncludePaths = ['author', 'feedback-element']; + protected $allowedIncludePaths = [FeedbackEntrySchema::REL_AUTHOR, FeedbackEntrySchema::REL_FEEDBACK]; + /** * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @SuppressWarnings(PHPMD.StaticAccess) + * + * @param array $args + * + * @return Response */ public function __invoke(Request $request, Response $response, $args) { - if (!$resource = \FeedbackEntry::find($args['id'])) { + $resource = \FeedbackEntry::find($args['id']); + if (!$resource) { throw new RecordNotFoundException(); } diff --git a/lib/classes/JsonApi/Routes/Feedback/FeedbackEntriesUpdate.php b/lib/classes/JsonApi/Routes/Feedback/FeedbackEntriesUpdate.php new file mode 100644 index 0000000000000000000000000000000000000000..4fc408635a444a0631c57d60321f19692d2a19c6 --- /dev/null +++ b/lib/classes/JsonApi/Routes/Feedback/FeedbackEntriesUpdate.php @@ -0,0 +1,94 @@ +<?php + +namespace JsonApi\Routes\Feedback; + +use FeedbackElement; +use FeedbackEntry; +use User; +use JsonApi\Errors\AuthorizationFailedException; +use JsonApi\Errors\RecordNotFoundException; +use JsonApi\JsonApiController; +use JsonApi\Routes\ValidationTrait; +use JsonApi\Schemas\FeedbackElement as FeedbackElementSchema; +use JsonApi\Schemas\FeedbackEntry as FeedbackEntrySchema; +use Psr\Http\Message\ResponseInterface as Response; +use Psr\Http\Message\ServerRequestInterface as Request; + +/** + * Update a FeedbackEntry. + * + * @SuppressWarnings(PHPMD.StaticAccess) + */ +class FeedbackElementsUpdate extends JsonApiController +{ + use RatingHelper; + use ValidationTrait; + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @param array $args + * + * @return Response + */ + public function __invoke(Request $request, Response $response, $args) + { + $resource = \FeedbackEntry::find($args['id']); + if (!$resource) { + throw new RecordNotFoundException(); + } + + $json = $this->validate($request); + $user = $this->getUser($request); + + if (!Authority::canUpdateFeedbackEntry($user, $resource)) { + throw new AuthorizationFailedException(); + } + + $feedbackEntry = $this->update($resource, $json); + + return $this->getContentResponse($feedbackEntry); + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameters) + * + * @param array $json + * @param mixed $data + * + * @return string|void + */ + protected function validateResourceDocument($json, $data) + { + if (!self::arrayHas($json, 'data')) { + return 'Missing `data` member at document´s top level.'; + } + if (FeedbackEntrySchema::TYPE !== self::arrayGet($json, 'data.type')) { + return 'Invalid `type` of document´s `data`.'; + } + if (!self::arrayHas($json, 'data.id')) { + return 'An existing document must have an `id`.'; + } + + $required = ['rating']; + foreach ($required as $attribute) { + if (!self::arrayHas($json, 'data.attributes.' . $attribute)) { + return 'Missing `' . $attribute . '` attribute.'; + } + } + } + + private function update(FeedbackEntry $feedbackEntry, array $json): FeedbackEntry + { + $feedbackEntry->rating = $this->getRating( + $feedbackEntry->feedback, + (int) self::arrayGet($json, 'data.attributes.rating') + ); + if ($feedbackEntry->feedback->commentable && self::arrayHas($json, 'data.attributes.comment')) { + $feedbackEntry->comment = self::arrayGet($json, 'data.attributes.comment'); + } + $feedbackEntry->store(); + + return $feedbackEntry; + } +} diff --git a/lib/classes/JsonApi/Routes/Feedback/RangeTypeAware.php b/lib/classes/JsonApi/Routes/Feedback/RangeTypeAware.php new file mode 100644 index 0000000000000000000000000000000000000000..88fd1b164b6d7f32d15b2b0ba83f62ea01050cfe --- /dev/null +++ b/lib/classes/JsonApi/Routes/Feedback/RangeTypeAware.php @@ -0,0 +1,20 @@ +<?php + +namespace JsonApi\Routes\Feedback; + +use FeedbackRange; +use SimpleORMap; + +trait RangeTypeAware +{ + protected $possibleRangeTypes = null; + + protected function preparePossibleRangeTypes(): void + { + foreach (app('json-api-integration-schemas') as $class => $schema) { + if (is_subclass_of($class, FeedbackRange::class) && is_subclass_of($class, SimpleORMap::class)) { + $this->possibleRangeTypes[$schema::TYPE] = $class; + } + } + } +} diff --git a/lib/classes/JsonApi/Routes/Feedback/RatingHelper.php b/lib/classes/JsonApi/Routes/Feedback/RatingHelper.php new file mode 100644 index 0000000000000000000000000000000000000000..552d9fd7ecc108145d461ca56dcb270caa1d2ea8 --- /dev/null +++ b/lib/classes/JsonApi/Routes/Feedback/RatingHelper.php @@ -0,0 +1,31 @@ +<?php + +namespace JsonApi\Routes\Feedback; + +use FeedbackElement; + +trait RatingHelper +{ + // TODO: Hier passt einiges nicht + private function getRating(FeedbackElement $element, int $rating): int + { + $mode = $element['mode']; + if ($mode === 0) { + return 0; + } + + if ($rating === 0) { + return 1; + } + + if ($mode === 1) { + return min(5, $rating); + } + + if ($mode === 2) { + return min(10, $rating); + } + + throw new InvalidArgumentException("Invalid mode {$mode}"); + } +} diff --git a/lib/classes/JsonApi/Schemas/FeedbackElement.php b/lib/classes/JsonApi/Schemas/FeedbackElement.php index 143bb0c1cf47776d38da16e3d6a4a0f26ebf28e3..41dc37075f1ba5c3983bcb635b0bdc81e083351e 100644 --- a/lib/classes/JsonApi/Schemas/FeedbackElement.php +++ b/lib/classes/JsonApi/Schemas/FeedbackElement.php @@ -2,24 +2,28 @@ namespace JsonApi\Schemas; +use JsonApi\Errors\InternalServerError; use Neomerx\JsonApi\Contracts\Schema\ContextInterface; use Neomerx\JsonApi\Schema\Link; class FeedbackElement extends SchemaProvider { - const TYPE = 'feedback-elements'; - const REL_AUTHOR = 'author'; - const REL_COURSE = 'course'; - const REL_ENTRIES = 'entries'; - const REL_RANGE = 'range'; + public const TYPE = 'feedback-elements'; + public const REL_AUTHOR = 'author'; + public const REL_COURSE = 'course'; + public const REL_ENTRIES = 'entries'; + public const REL_RANGE = 'range'; public function getId($resource): ?string { - return (int) $resource->id; + return (string) $resource->id; } + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ public function getAttributes($resource, ContextInterface $context): iterable { $attributes = [ @@ -76,7 +80,7 @@ class FeedbackElement extends SchemaProvider return $relationships; } - private function getAuthorRelationship(array $relationships, \FeedbackElement $resource, $includeData): array + private function getAuthorRelationship(array $relationships, \FeedbackElement $resource, bool $includeData): array { $userId = $resource['user_id']; $related = $includeData ? \User::find($userId) : \User::build(['id' => $userId], false); @@ -90,7 +94,7 @@ class FeedbackElement extends SchemaProvider return $relationships; } - private function getCourseRelationship(array $relationships, \FeedbackElement $resource, $includeData): array + private function getCourseRelationship(array $relationships, \FeedbackElement $resource, bool $includeData): array { if ($courseId = $resource['course_id']) { $related = $includeData ? \Course::find($courseId) : \Course::build(['id' => $courseId], false); @@ -119,23 +123,17 @@ class FeedbackElement extends SchemaProvider private function getRangeRelationship(array $relationships, \FeedbackElement $resource, bool $includeData): array { - $rangeType = $resource['range_type']; - $link = null; - + $range = $resource->getRange(); try { - $link = $this->createLinkToResource($rangeType); - if ( - is_subclass_of($rangeType, \FeedbackRange::class) && - is_subclass_of($rangeType, \SimpleORMap::class) - ) { - if ($range = $rangeType::find($resource['range_id'])) { - $relationships[self::REL_RANGE] = [ - self::RELATIONSHIP_LINKS => [Link::RELATED => $link], - self::RELATIONSHIP_DATA => $range - ]; - } - } + $link = $this->createLinkToResource($range); + $relationships[self::REL_RANGE] = [ + self::RELATIONSHIP_LINKS => [Link::RELATED => $link], + self::RELATIONSHIP_DATA => $range + ]; } catch (\InvalidArgumentException $e) { + // don't show this relation + } catch (InternalServerError $ise) { + // don't show this relation } return $relationships;