From be777142fc65233ea41b41743532d11dc27edb1e Mon Sep 17 00:00:00 2001 From: rufa9486 Date: Sun, 15 Mar 2026 10:43:33 +0100 Subject: update courseware container to new sorm crud --- .../Courseware/StructuralElementAuthority.php | 55 +++ .../Routes/Courseware/CoursewareContainers.php | 2 +- .../Courseware/CoursewareStructuralElements.php | 75 ++++ .../Rel/BookmarkedStructuralElements.php | 2 +- .../Courseware/Rel/StructuralElementsChildren.php | 6 +- .../Rel/StructuralElementsContainers.php | 4 +- .../Rel/StructuralElementsEditBlocker.php | 2 +- .../Rel/UsersBookmarkedStructuralElements.php | 2 +- .../Courseware/StructuralElementCommentsCreate.php | 2 +- .../Courseware/StructuralElementCommentsUpdate.php | 2 +- .../Courseware/StructuralElementFeedbackCreate.php | 2 +- .../Courseware/StructuralElementFeedbackUpdate.php | 2 +- .../Routes/Courseware/StructuralElementsCreate.php | 2 +- .../Routes/Courseware/StructuralElementsUpdate.php | 2 +- .../JsonApi/Routes/Courseware/TaskGroupsCreate.php | 2 +- .../JsonApi/Routes/Courseware/TemplatesCreate.php | 2 +- lib/classes/JsonApi/SchemaMap.php | 2 +- .../Schemas/Courseware/StructuralElement.php | 454 --------------------- .../Schemas/Courseware/StructuralElementSchema.php | 447 ++++++++++++++++++++ package-lock.json | 8 +- tests/jsonapi/StructuralElementsShowTest.php | 2 +- 21 files changed, 601 insertions(+), 476 deletions(-) create mode 100644 lib/classes/JsonApi/Authorities/Courseware/StructuralElementAuthority.php create mode 100644 lib/classes/JsonApi/Routes/Courseware/CoursewareStructuralElements.php delete mode 100644 lib/classes/JsonApi/Schemas/Courseware/StructuralElement.php create mode 100644 lib/classes/JsonApi/Schemas/Courseware/StructuralElementSchema.php diff --git a/lib/classes/JsonApi/Authorities/Courseware/StructuralElementAuthority.php b/lib/classes/JsonApi/Authorities/Courseware/StructuralElementAuthority.php new file mode 100644 index 0000000..943ced2 --- /dev/null +++ b/lib/classes/JsonApi/Authorities/Courseware/StructuralElementAuthority.php @@ -0,0 +1,55 @@ +canEdit($user); + } + + /** + * @param User|null $user + * @param StructuralElement $sorm + * + * @return bool + */ + public function mayAccess(?User $user, SORM $sorm): bool + { + return $sorm->canRead($user); + } + + /** + * @param User|null $user + * @param StructuralElement $sorm + * + * @return bool + */ + public function mayEdit(?User $user, SORM $sorm): bool + { + return $sorm->canEdit($user); + } + + /** + * @param User|null $user + * @param StructuralElement $sorm + * + * @return bool + */ + public function mayDelete(?User $user, SORM $sorm): bool + { + return $sorm->canEdit($user); + } +} diff --git a/lib/classes/JsonApi/Routes/Courseware/CoursewareContainers.php b/lib/classes/JsonApi/Routes/Courseware/CoursewareContainers.php index 1e12f70..9dc57a2 100644 --- a/lib/classes/JsonApi/Routes/Courseware/CoursewareContainers.php +++ b/lib/classes/JsonApi/Routes/Courseware/CoursewareContainers.php @@ -8,7 +8,7 @@ use Courseware\ContainerTypes\ContainerType; use Courseware\StructuralElement; use JsonApi\Errors\UnprocessableEntityException; use JsonApi\Schemas\Courseware\ContainerSchema; -use JsonApi\Schemas\Courseware\StructuralElement as StructuralElementSchema; +use JsonApi\Schemas\Courseware\StructuralElementSchema as StructuralElementSchema; use JsonApi\SORM; use JsonApi\SormCRUDController; use Psr\Http\Message\ResponseInterface as Response; diff --git a/lib/classes/JsonApi/Routes/Courseware/CoursewareStructuralElements.php b/lib/classes/JsonApi/Routes/Courseware/CoursewareStructuralElements.php new file mode 100644 index 0000000..ef55d1c --- /dev/null +++ b/lib/classes/JsonApi/Routes/Courseware/CoursewareStructuralElements.php @@ -0,0 +1,75 @@ +getUser($request); + $data = [ + 'position' => (int) $this->getAttribute('position', $current ? $current->position : 0), + 'title' => $this->getAttribute('title', $current ? $current->title :''), + 'purpose' => $this->getAttribute('purpose', $current ? $current->purpose : ''), + 'payload' => $this->getAttribute('payload', $current ? $current->payload->getIterator() : []), + 'public' => (int) $this->getAttribute('public', $current ? $current->public : 0), + 'permission-type' => $this->getAttribute('permission-type', $current ? $current->permission_type : ''), + 'visible' => $this->getAttribute('visible', $current ? $current->visible : ''), + 'visible_all' => (bool) $this->getAttribute('visible-all', $current ? $current->visible_all : ''), + 'visible-start-date' => $this->getDateAttribute('visible-start-date'), + 'visible-end-date' => $this->getDateAttribute('visible-end-date'), + 'writable' => (string) $this->getAttribute('writable', $current ? $current->writable : ''), + 'writable_all' => (bool) $this->getAttribute('writable-all', $current ? $current->writable_all : ''), + 'writable_start_date' => $this->getDateAttribute('writable-start-date'), + 'writable_end_date' => $this->getDateAttribute('writable-end-date'), + 'visible_approval' => json_encode($this->getAttribute('visible-approval', $current ? $current->visible_approval : '')), + 'writable_approval' => json_encode($this->getAttribute('writable-approval', $current ? $current->writable_end_date : '')), + 'content_approval' => $this->getAttribute('content-approval', $current ? $current->content_approval->getIterator() : []), + 'copy_approval' => $this->getAttribute('copy-approval', $current ? $current->copy_approval->getIterator() : []), + 'can_edit' => $current ? $current->canEdit($user) : false, + 'can_visit' => $current ? $current->canVisit($user) : false, + 'is_link' => (int) $this->getAttribute('is-link', $current ? $current->is_link : 0), + 'commentable' => (bool) $this->getAttribute('commentable', $current ? $current->commentable : false), + 'target_id' => (int) $this->getAttribute('target-id', $current ? $current->target_id : ''), + 'external_relations' => $this->getAttribute('external-relations', $current ? $current->external_relations->getIterator() : []), + 'mkdate' => $this->getDateAttribute('mkdate'), + 'chdate' => $this->getDateAttribute('chdate'), + ]; + } +} diff --git a/lib/classes/JsonApi/Routes/Courseware/Rel/BookmarkedStructuralElements.php b/lib/classes/JsonApi/Routes/Courseware/Rel/BookmarkedStructuralElements.php index 1e68624..ef28079 100644 --- a/lib/classes/JsonApi/Routes/Courseware/Rel/BookmarkedStructuralElements.php +++ b/lib/classes/JsonApi/Routes/Courseware/Rel/BookmarkedStructuralElements.php @@ -112,7 +112,7 @@ class BookmarkedStructuralElements extends RelationshipsController } foreach ($data as $item) { - if (\JsonApi\Schemas\Courseware\StructuralElement::TYPE !== self::arrayGet($item, 'type')) { + if (\JsonApi\Schemas\Courseware\StructuralElementSchema::TYPE !== self::arrayGet($item, 'type')) { return 'Wrong `type` in document´s `data`.'; } diff --git a/lib/classes/JsonApi/Routes/Courseware/Rel/StructuralElementsChildren.php b/lib/classes/JsonApi/Routes/Courseware/Rel/StructuralElementsChildren.php index 3ff573a..bf5b227 100644 --- a/lib/classes/JsonApi/Routes/Courseware/Rel/StructuralElementsChildren.php +++ b/lib/classes/JsonApi/Routes/Courseware/Rel/StructuralElementsChildren.php @@ -62,7 +62,7 @@ class StructuralElementsChildren extends RelationshipsController */ protected function getRelationshipSelfLink($resource, $schema, $userData) { - return $schema->getRelationshipSelfLink($resource, \JsonApi\Schemas\Courseware\StructuralElement::REL_CHILDREN); + return $schema->getRelationshipSelfLink($resource, \JsonApi\Schemas\Courseware\StructuralElementSchema::REL_CHILDREN); } /** @@ -70,7 +70,7 @@ class StructuralElementsChildren extends RelationshipsController */ protected function getRelationshipRelatedLink($resource, $schema, $userData) { - return $schema->getRelationshipRelatedLink($resource, \JsonApi\Schemas\Courseware\StructuralElement::REL_CHILDREN); + return $schema->getRelationshipRelatedLink($resource, \JsonApi\Schemas\Courseware\StructuralElementSchema::REL_CHILDREN); } protected function validateResourceDocument($json, $data) @@ -86,7 +86,7 @@ class StructuralElementsChildren extends RelationshipsController } foreach ($data as $item) { - if (self::arrayGet($item, 'type') !== \JsonApi\Schemas\Courseware\StructuralElement::TYPE) { + if (self::arrayGet($item, 'type') !== \JsonApi\Schemas\Courseware\StructuralElementSchema::TYPE) { return 'Wrong `type` in document´s `data`.'; } diff --git a/lib/classes/JsonApi/Routes/Courseware/Rel/StructuralElementsContainers.php b/lib/classes/JsonApi/Routes/Courseware/Rel/StructuralElementsContainers.php index 3ede3e5..44ee3fe 100644 --- a/lib/classes/JsonApi/Routes/Courseware/Rel/StructuralElementsContainers.php +++ b/lib/classes/JsonApi/Routes/Courseware/Rel/StructuralElementsContainers.php @@ -63,7 +63,7 @@ class StructuralElementsContainers extends RelationshipsController */ protected function getRelationshipSelfLink($resource, $schema, $userData) { - return $schema->getRelationshipSelfLink($resource, \JsonApi\Schemas\Courseware\StructuralElement::REL_CONTAINERS); + return $schema->getRelationshipSelfLink($resource, \JsonApi\Schemas\Courseware\StructuralElementSchema::REL_CONTAINERS); } /** @@ -71,7 +71,7 @@ class StructuralElementsContainers extends RelationshipsController */ protected function getRelationshipRelatedLink($resource, $schema, $userData) { - return $schema->getRelationshipRelatedLink($resource, \JsonApi\Schemas\Courseware\StructuralElement::REL_CONTAINERS); + return $schema->getRelationshipRelatedLink($resource, \JsonApi\Schemas\Courseware\StructuralElementSchema::REL_CONTAINERS); } protected function validateResourceDocument($json, $data) diff --git a/lib/classes/JsonApi/Routes/Courseware/Rel/StructuralElementsEditBlocker.php b/lib/classes/JsonApi/Routes/Courseware/Rel/StructuralElementsEditBlocker.php index 3c2780f..b376707 100644 --- a/lib/classes/JsonApi/Routes/Courseware/Rel/StructuralElementsEditBlocker.php +++ b/lib/classes/JsonApi/Routes/Courseware/Rel/StructuralElementsEditBlocker.php @@ -73,7 +73,7 @@ class StructuralElementsEditBlocker extends RelationshipsController */ protected function getRelationshipSelfLink($resource, $schema, $userData) { - return $schema->getRelationshipSelfLink($resource, \JsonApi\Schemas\Courseware\StructuralElement::REL_EDITBLOCKER); + return $schema->getRelationshipSelfLink($resource, \JsonApi\Schemas\Courseware\StructuralElementSchema::REL_EDITBLOCKER); } /** diff --git a/lib/classes/JsonApi/Routes/Courseware/Rel/UsersBookmarkedStructuralElements.php b/lib/classes/JsonApi/Routes/Courseware/Rel/UsersBookmarkedStructuralElements.php index cfe487d..1f9f990 100644 --- a/lib/classes/JsonApi/Routes/Courseware/Rel/UsersBookmarkedStructuralElements.php +++ b/lib/classes/JsonApi/Routes/Courseware/Rel/UsersBookmarkedStructuralElements.php @@ -113,7 +113,7 @@ class UsersBookmarkedStructuralElements extends RelationshipsController } foreach ($data as $item) { - if (\JsonApi\Schemas\Courseware\StructuralElement::TYPE !== self::arrayGet($item, 'type')) { + if (\JsonApi\Schemas\Courseware\StructuralElementSchema::TYPE !== self::arrayGet($item, 'type')) { return 'Wrong `type` in document´s `data`.'; } diff --git a/lib/classes/JsonApi/Routes/Courseware/StructuralElementCommentsCreate.php b/lib/classes/JsonApi/Routes/Courseware/StructuralElementCommentsCreate.php index 9dfa77b..9ab427e 100644 --- a/lib/classes/JsonApi/Routes/Courseware/StructuralElementCommentsCreate.php +++ b/lib/classes/JsonApi/Routes/Courseware/StructuralElementCommentsCreate.php @@ -5,7 +5,7 @@ namespace JsonApi\Routes\Courseware; use JsonApi\Errors\AuthorizationFailedException; use JsonApi\JsonApiController; use JsonApi\Routes\ValidationTrait; -use JsonApi\Schemas\Courseware\StructuralElement as StructuralElementSchema; +use JsonApi\Schemas\Courseware\StructuralElementSchema as StructuralElementSchema; use JsonApi\Schemas\Courseware\StructuralElementComment as StructuralElementCommentSchema; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; diff --git a/lib/classes/JsonApi/Routes/Courseware/StructuralElementCommentsUpdate.php b/lib/classes/JsonApi/Routes/Courseware/StructuralElementCommentsUpdate.php index 5728847..c2e7c6a 100644 --- a/lib/classes/JsonApi/Routes/Courseware/StructuralElementCommentsUpdate.php +++ b/lib/classes/JsonApi/Routes/Courseware/StructuralElementCommentsUpdate.php @@ -7,7 +7,7 @@ use JsonApi\Errors\AuthorizationFailedException; use JsonApi\Errors\RecordNotFoundException; use JsonApi\JsonApiController; use JsonApi\Routes\ValidationTrait; -use JsonApi\Schemas\Courseware\StructuralElement as StructuralElementSchema; +use JsonApi\Schemas\Courseware\StructuralElementSchema as StructuralElementSchema; use JsonApi\Schemas\Courseware\StructuralElementComment as StructuralElementCommentSchema; use JsonApi\Schemas\User as UserSchema; use Psr\Http\Message\ResponseInterface as Response; diff --git a/lib/classes/JsonApi/Routes/Courseware/StructuralElementFeedbackCreate.php b/lib/classes/JsonApi/Routes/Courseware/StructuralElementFeedbackCreate.php index bcf5425..a3b8d71 100644 --- a/lib/classes/JsonApi/Routes/Courseware/StructuralElementFeedbackCreate.php +++ b/lib/classes/JsonApi/Routes/Courseware/StructuralElementFeedbackCreate.php @@ -6,7 +6,7 @@ use Courseware\Container; use JsonApi\Errors\AuthorizationFailedException; use JsonApi\JsonApiController; use JsonApi\Routes\ValidationTrait; -use JsonApi\Schemas\Courseware\StructuralElement as StructuralElementSchema; +use JsonApi\Schemas\Courseware\StructuralElementSchema as StructuralElementSchema; use JsonApi\Schemas\Courseware\StructuralElementFeedback as StructuralElementFeedbackSchema; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; diff --git a/lib/classes/JsonApi/Routes/Courseware/StructuralElementFeedbackUpdate.php b/lib/classes/JsonApi/Routes/Courseware/StructuralElementFeedbackUpdate.php index 5d4b0af..6e3eedc 100644 --- a/lib/classes/JsonApi/Routes/Courseware/StructuralElementFeedbackUpdate.php +++ b/lib/classes/JsonApi/Routes/Courseware/StructuralElementFeedbackUpdate.php @@ -7,7 +7,7 @@ use JsonApi\Errors\AuthorizationFailedException; use JsonApi\Errors\RecordNotFoundException; use JsonApi\JsonApiController; use JsonApi\Routes\ValidationTrait; -use JsonApi\Schemas\Courseware\StructuralElement as StructuralElementSchema; +use JsonApi\Schemas\Courseware\StructuralElementSchema as StructuralElementSchema; use JsonApi\Schemas\Courseware\StructuralElementFeedback as StructuralElementFeedbackSchema; use JsonApi\Schemas\User as UserSchema; use Psr\Http\Message\ResponseInterface as Response; diff --git a/lib/classes/JsonApi/Routes/Courseware/StructuralElementsCreate.php b/lib/classes/JsonApi/Routes/Courseware/StructuralElementsCreate.php index 082821b..30002c4 100644 --- a/lib/classes/JsonApi/Routes/Courseware/StructuralElementsCreate.php +++ b/lib/classes/JsonApi/Routes/Courseware/StructuralElementsCreate.php @@ -5,7 +5,7 @@ namespace JsonApi\Routes\Courseware; use JsonApi\Errors\AuthorizationFailedException; use JsonApi\JsonApiController; use JsonApi\Routes\ValidationTrait; -use JsonApi\Schemas\Courseware\StructuralElement as StructuralElementSchema; +use JsonApi\Schemas\Courseware\StructuralElementSchema as StructuralElementSchema; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Studip\Activity\Activity; diff --git a/lib/classes/JsonApi/Routes/Courseware/StructuralElementsUpdate.php b/lib/classes/JsonApi/Routes/Courseware/StructuralElementsUpdate.php index ae58bca..820fda7 100644 --- a/lib/classes/JsonApi/Routes/Courseware/StructuralElementsUpdate.php +++ b/lib/classes/JsonApi/Routes/Courseware/StructuralElementsUpdate.php @@ -8,7 +8,7 @@ use JsonApi\Errors\RecordNotFoundException; use JsonApi\JsonApiController; use JsonApi\Routes\TimestampTrait; use JsonApi\Routes\ValidationTrait; -use JsonApi\Schemas\Courseware\StructuralElement as StructuralElementSchema; +use JsonApi\Schemas\Courseware\StructuralElementSchema as StructuralElementSchema; use JsonApi\Schemas\FileRef as FileRefSchema; use JsonApi\Schemas\StockImage as StockImageSchema; use Psr\Http\Message\ResponseInterface as Response; diff --git a/lib/classes/JsonApi/Routes/Courseware/TaskGroupsCreate.php b/lib/classes/JsonApi/Routes/Courseware/TaskGroupsCreate.php index f7357a4..3d0f481 100644 --- a/lib/classes/JsonApi/Routes/Courseware/TaskGroupsCreate.php +++ b/lib/classes/JsonApi/Routes/Courseware/TaskGroupsCreate.php @@ -9,7 +9,7 @@ use JsonApi\Errors\AuthorizationFailedException; use JsonApi\JsonApiController; use JsonApi\Routes\TimestampTrait; use JsonApi\Routes\ValidationTrait; -use JsonApi\Schemas\Courseware\StructuralElement as StructuralElementSchema; +use JsonApi\Schemas\Courseware\StructuralElementSchema as StructuralElementSchema; use JsonApi\Schemas\Courseware\TaskGroup as TaskGroupSchema; use JsonApi\Schemas\StatusGroup as StatusGroupSchema; use JsonApi\Schemas\User as UserSchema; diff --git a/lib/classes/JsonApi/Routes/Courseware/TemplatesCreate.php b/lib/classes/JsonApi/Routes/Courseware/TemplatesCreate.php index f23468c..58a1994 100644 --- a/lib/classes/JsonApi/Routes/Courseware/TemplatesCreate.php +++ b/lib/classes/JsonApi/Routes/Courseware/TemplatesCreate.php @@ -8,7 +8,7 @@ use JsonApi\Errors\UnprocessableEntityException; use JsonApi\JsonApiController; use JsonApi\Routes\ValidationTrait; use JsonApi\Schemas\Courseware\Task as TaskSchema; -use JsonApi\Schemas\Courseware\StructuralElement as StructuralElementSchema; +use JsonApi\Schemas\Courseware\StructuralElementSchema as StructuralElementSchema; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; diff --git a/lib/classes/JsonApi/SchemaMap.php b/lib/classes/JsonApi/SchemaMap.php index 3d7dae4..1db5759 100644 --- a/lib/classes/JsonApi/SchemaMap.php +++ b/lib/classes/JsonApi/SchemaMap.php @@ -96,7 +96,7 @@ class SchemaMap \Courseware\PeerReview::class => Schemas\Courseware\PeerReview::class, \Courseware\PeerReviewProcess::class => Schemas\Courseware\PeerReviewProcess::class, \Courseware\PublicLink::class => Schemas\Courseware\PublicLink::class, - \Courseware\StructuralElement::class => Schemas\Courseware\StructuralElement::class, + \Courseware\StructuralElement::class => Schemas\Courseware\StructuralElementSchema::class, \Courseware\StructuralElementComment::class => Schemas\Courseware\StructuralElementComment::class, \Courseware\StructuralElementFeedback::class => Schemas\Courseware\StructuralElementFeedback::class, \Courseware\Task::class => Schemas\Courseware\Task::class, diff --git a/lib/classes/JsonApi/Schemas/Courseware/StructuralElement.php b/lib/classes/JsonApi/Schemas/Courseware/StructuralElement.php deleted file mode 100644 index c00d863..0000000 --- a/lib/classes/JsonApi/Schemas/Courseware/StructuralElement.php +++ /dev/null @@ -1,454 +0,0 @@ -id; - } - - /** - * {@inheritdoc} - */ - public function getAttributes($resource, ContextInterface $context): iterable - { - $user = $this->currentUser; - - return [ - 'position' => (int) $resource['position'], - 'title' => (string) $resource['title'], - 'purpose' => (string) $resource['purpose'], - 'payload' => $resource['payload']->getIterator(), - 'public' => (int) $resource['public'], - 'permission-type' => (string) $resource['permission_type'], - 'visible' => (string) $resource['visible'], - 'visible-all' => (bool) $resource['visible_all'], - 'visible-start-date' => $resource['visible_start_date'] ? date('c', $resource['visible_start_date']) : null, - 'visible-end-date' => $resource['visible_end_date'] ? date('c', $resource['visible_end_date']) : null, - 'writable' => (string) $resource['writable'], - 'writable-all' => (bool) $resource['writable_all'], - 'writable-start-date' => $resource['writable_start_date'] ? date('c', $resource['writable_start_date']) : null, - 'writable-end-date' => $resource['writable_end_date'] ? date('c', $resource['writable_end_date']) : null, - 'visible-approval' => json_decode($resource['visible_approval']), - 'writable-approval' => json_decode($resource['writable_approval']), - 'content-approval' => $resource['content_approval']->getIterator(), - 'copy-approval' => $resource['copy_approval']->getIterator(), - 'can-edit' => $resource->canEdit($user), - 'can-visit' => $resource->canVisit($user), - 'is-link' => (int) $resource['is_link'], - 'commentable' => (bool) $resource['commentable'], - 'target-id' => (int) $resource['target_id'], - 'external-relations' => $resource['external_relations']->getIterator(), - 'mkdate' => date('c', $resource['mkdate']), - 'chdate' => date('c', $resource['chdate']), - ]; - } - - /** - * {@inheritdoc} - * - * @param StructuralElement $resource - * @param ContextInterface $context - */ - public function getRelationships($resource, ContextInterface $context): iterable - { - $relationships = []; - - $relationships = $this->addChildrenRelationship( - $relationships, - $resource, - $this->shouldInclude($context, self::REL_CHILDREN) - ); - - $relationships = $this->addContainersRelationship( - $relationships, - $resource, - $this->shouldInclude($context, self::REL_CONTAINERS) - ); - - $relationships = $this->addRangeRelationship($relationships, $resource, $context); - - $relationships = $this->addOwnerRelationship( - $relationships, - $resource, - $this->shouldInclude($context, self::REL_OWNER) - ); - - $relationships = $this->addEditorRelationship( - $relationships, - $resource, - $this->shouldInclude($context, self::REL_EDITOR) - ); - - $relationships = $this->addEditBlockerRelationship( - $relationships, - $resource, - $this->shouldInclude($context, self::REL_EDITBLOCKER) - ); - - $relationships = $this->addParentRelationship( - $relationships, - $resource, - $this->shouldInclude($context, self::REL_PARENT) - ); - - $relationships = $this->addAncestorsRelationship( - $relationships, - $resource, - $this->shouldInclude($context, self::REL_ANCESTORS) - ); - - $relationships = $this->addDescendantsRelationship( - $relationships, - $resource, - $this->shouldInclude($context, self::REL_DESCENDANTS) - ); - - $relationships = $this->addImageRelationship( - $relationships, - $resource, - $this->shouldInclude($context, self::REL_IMAGE) - ); - - $relationships = $this->addTaskRelationship( - $relationships, - $resource, - $this->shouldInclude($context, self::REL_TASK) - ); - - $relationships = $this->addUnitRelationship( - $relationships, - $resource, - $this->shouldInclude($context, self::REL_UNIT) - ); - - $relationships = $this->addFeedbackElementRelationship( - $relationships, - $resource, - $this->shouldInclude($context, self::REL_FEEDBACKELEMENT) - ); - - return $relationships; - } - - private function addAncestorsRelationship(array $relationships, $resource, $includeData) - { - $relation = [ - self::RELATIONSHIP_LINKS => [ - Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_ANCESTORS), - ], - ]; - - if ($includeData) { - $related = $resource->findAncestors(); - $relation[self::RELATIONSHIP_DATA] = $related; - } - - $relationships[self::REL_ANCESTORS] = $relation; - - return $relationships; - } - - private function addChildrenRelationship(array $relationships, $resource, bool $includeData): array - { - $relation = [ - self::RELATIONSHIP_LINKS => [ - Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_CHILDREN), - ], - ]; - - if ($includeData) { - $user = $this->currentUser; - $relation[self::RELATIONSHIP_DATA] = $resource->children->filter(function ($child) use ($user) { - return $child->canRead($user); - }); - } - - $relationships[self::REL_CHILDREN] = $relation; - - return $relationships; - } - - private function addContainersRelationship(array $relationships, $resource, bool $includeData): array - { - $relation = [ - self::RELATIONSHIP_LINKS => [ - Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_CONTAINERS), - ], - ]; - - if ($includeData) { - $relation[self::RELATIONSHIP_DATA] = $resource->containers; - } else { - $relation[self::RELATIONSHIP_DATA] = function () use ($resource) { - $sql = 'SELECT id FROM cw_containers WHERE structural_element_id = ?'; - $containers = \DBManager::get()->fetchAll($sql, [$resource->id], function ($container) { - return new Identifier($container['id'], \JsonApi\Schemas\Courseware\ContainerSchema::TYPE); - }); - - return $containers; - }; - } - $relationships[self::REL_CONTAINERS] = $relation; - - return $relationships; - } - - private function addDescendantsRelationship(array $relationships, $resource, $includeData) - { - $relation = [ - self::RELATIONSHIP_LINKS => [ - Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_DESCENDANTS), - ], - ]; - - if ($includeData) { - $user = $this->currentUser; - $related = $resource->findDescendants($user); - $relation[self::RELATIONSHIP_DATA] = $related; - } - - $relationships[self::REL_DESCENDANTS] = $relation; - - return $relationships; - } - - private function addImageRelationship(array $relationships, $resource, $includeData) - { - $image = $resource->image; - $relation = [ - self::RELATIONSHIP_DATA => $image ?: null, - ]; - - if ($image) { - $relation[self::RELATIONSHIP_META] = [ - 'download-url' => $resource->getImageUrl(), - ]; - } - - $relationships[self::REL_IMAGE] = $relation; - - return $relationships; - } - - private function addEditBlockerRelationship(array $relationships, $resource, bool $includeData): array - { - $relation = [ - self::RELATIONSHIP_LINKS_SELF => true, - ]; - if ($resource['edit_blocker_id']) { - $relation[self::RELATIONSHIP_LINKS] = [ - Link::RELATED => $this->createLinkToUser($resource['edit_blocker_id']), - ]; - $relation[self::RELATIONSHIP_DATA] = $includeData - ? $resource->edit_blocker - : new Identifier($resource['edit_blocker_id'], \JsonApi\Schemas\User::TYPE); - } else { - $relation[self::RELATIONSHIP_DATA] = null; - } - $relationships[self::REL_EDITBLOCKER] = $relation; - - return $relationships; - } - - private function addEditorRelationship(array $relationships, $resource, bool $includeData): array - { - $relation = []; - if ($resource['editor_id']) { - $relation[self::RELATIONSHIP_LINKS] = [ - Link::RELATED => $this->createLinkToUser($resource['editor_id']), - ]; - $relation[self::RELATIONSHIP_DATA] = $includeData - ? $resource->editor - : new Identifier($resource['editor_id'], \JsonApi\Schemas\User::TYPE); - } else { - $relation[self::RELATIONSHIP_DATA] = null; - } - $relationships[self::REL_EDITOR] = $relation; - - return $relationships; - } - - private function addOwnerRelationship(array $relationships, $resource, bool $includeData): array - { - $relation = []; - if ($resource['owner_id']) { - $relation[self::RELATIONSHIP_LINKS] = [ - Link::RELATED => $this->createLinkToUser($resource['owner_id']), - ]; - $relation[self::RELATIONSHIP_DATA] = $includeData - ? $resource->owner - : new Identifier($resource['owner_id'], \JsonApi\Schemas\User::TYPE); - } else { - $relation[self::RELATIONSHIP_DATA] = null; - } - $relationships[self::REL_OWNER] = $relation; - - return $relationships; - } - - private function addParentRelationship(array $relationships, $resource, bool $includeData): array - { - $relation = []; - - if ($resource['parent_id']) { - $relation[self::RELATIONSHIP_LINKS] = [ - Link::RELATED => $this->createLinkToStructuralElement($resource['parent_id']), - ]; - $relation[self::RELATIONSHIP_DATA] = $includeData - ? $resource->parent - : new Identifier($resource['parent_id'], self::TYPE); - } else { - $relation[self::RELATIONSHIP_DATA] = null; - } - $relationships[self::REL_PARENT] = $relation; - - return $relationships; - } - - private function addRangeRelationship(array $relationships, $resource, $context): array - { - if ($resource['range_type'] === 'course') { - $includeData = $this->shouldInclude($context, self::REL_COURSE); - $relationships[self::REL_COURSE] = [ - self::RELATIONSHIP_LINKS => [ - Link::RELATED => $this->createLinkToCourse($resource['range_id']), - ], - self::RELATIONSHIP_DATA => $includeData - ? $resource->course - : new Identifier($resource['range_id'], \JsonApi\Schemas\Course::TYPE), - ]; - } elseif ($resource['range_type'] === 'user') { - $includeData = $this->shouldInclude($context, self::REL_USER); - $relationships[self::REL_USER] = [ - self::RELATIONSHIP_LINKS => [ - Link::RELATED => $this->createLinkToUser($resource['range_id']), - ], - self::RELATIONSHIP_DATA => $includeData - ? $resource->user - : new Identifier($resource['range_id'], \JsonApi\Schemas\User::TYPE), - ]; - } - - return $relationships; - } - - private function addTaskRelationship( - array $relationships, - $resource, - bool $shouldInclude - ): array { - $relationships[self::REL_TASK] = $resource->task - ? [ - self::RELATIONSHIP_LINKS => [ - Link::RELATED => $this->createLinkToResource($resource->task), - ], - self::RELATIONSHIP_DATA => $resource->task, - ] - : [ - self::RELATIONSHIP_DATA => null, - ]; - - return $relationships; - } - - private function addUnitRelationship(array $relationships, $resource, $includeData): array - { - $relation = [ - self::RELATIONSHIP_LINKS => [ - Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_UNIT), - ], - ]; - - $related = $resource->findUnit(); - $relation[self::RELATIONSHIP_DATA] = $related; - - $relationships[self::REL_UNIT] = $relation; - - return $relationships; - } - - private function addFeedbackElementRelationship(array $relationships, $resource, $includeData): array - { - $relation = [ - self::RELATIONSHIP_LINKS => [ - Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_FEEDBACKELEMENT), - ], - ]; - - $feedback = $resource->getFeedbackElement(); - $relation[self::RELATIONSHIP_DATA] = $feedback; - $relationships[self::REL_FEEDBACKELEMENT] = $relation; - - - return $relationships; - } - - private static $memo = []; - - private function createLinkToCourse($rangeId) - { - if (isset(self::$memo['course' . $rangeId])) { - return self::$memo['course' . $rangeId]; - } - - $course = \Course::build(['id' => $rangeId], false); - $link = $this->createLinkToResource($course); - self::$memo['course' . $rangeId] = $link; - - return $link; - } - - private function createLinkToStructuralElement($structuralElementId) - { - if (isset(self::$memo['structuralelement' . $structuralElementId])) { - return self::$memo['structuralelement' . $structuralElementId]; - } - - $structuralElement = \Courseware\StructuralElement::build(['id' => $structuralElementId], false); - $link = $this->createLinkToResource($structuralElement); - self::$memo['structuralelement' . $structuralElementId] = $link; - - return $link; - } - - private function createLinkToUser($rangeId) - { - if (isset(self::$memo['user' . $rangeId])) { - return self::$memo['user' . $rangeId]; - } - - $course = \User::build(['id' => $rangeId], false); - $link = $this->createLinkToResource($course); - self::$memo['user' . $rangeId] = $link; - - return $link; - } -} diff --git a/lib/classes/JsonApi/Schemas/Courseware/StructuralElementSchema.php b/lib/classes/JsonApi/Schemas/Courseware/StructuralElementSchema.php new file mode 100644 index 0000000..5d0caa7 --- /dev/null +++ b/lib/classes/JsonApi/Schemas/Courseware/StructuralElementSchema.php @@ -0,0 +1,447 @@ +currentUser; + + return [ + 'position' => (int) $resource['position'], + 'title' => (string) $resource['title'], + 'purpose' => (string) $resource['purpose'], + 'payload' => $resource['payload']->getIterator(), + 'public' => (int) $resource['public'], + 'permission-type' => (string) $resource['permission_type'], + 'visible' => (string) $resource['visible'], + 'visible-all' => (bool) $resource['visible_all'], + 'visible-start-date' => $resource['visible_start_date'] ? date('c', $resource['visible_start_date']) : null, + 'visible-end-date' => $resource['visible_end_date'] ? date('c', $resource['visible_end_date']) : null, + 'writable' => (string) $resource['writable'], + 'writable-all' => (bool) $resource['writable_all'], + 'writable-start-date' => $resource['writable_start_date'] ? date('c', $resource['writable_start_date']) : null, + 'writable-end-date' => $resource['writable_end_date'] ? date('c', $resource['writable_end_date']) : null, + 'visible-approval' => json_decode($resource['visible_approval']), + 'writable-approval' => json_decode($resource['writable_approval']), + 'content-approval' => $resource['content_approval']->getIterator(), + 'copy-approval' => $resource['copy_approval']->getIterator(), + 'can-edit' => $resource->canEdit($user), + 'can-visit' => $resource->canVisit($user), + 'is-link' => (int) $resource['is_link'], + 'commentable' => (bool) $resource['commentable'], + 'target-id' => (int) $resource['target_id'], + 'external-relations' => $resource['external_relations']->getIterator(), + 'mkdate' => date('c', $resource['mkdate']), + 'chdate' => date('c', $resource['chdate']), + ]; + } + + /** + * {@inheritdoc} + * + * @param StructuralElementSchema $resource + * @param ContextInterface $context + */ + public function getRelationships($resource, ContextInterface $context): iterable + { + $relationships = []; + + $relationships = $this->addChildrenRelationship( + $relationships, + $resource, + $this->shouldInclude($context, self::REL_CHILDREN) + ); + + $relationships = $this->addContainersRelationship( + $relationships, + $resource, + $this->shouldInclude($context, self::REL_CONTAINERS) + ); + + $relationships = $this->addRangeRelationship($relationships, $resource, $context); + + $relationships = $this->addOwnerRelationship( + $relationships, + $resource, + $this->shouldInclude($context, self::REL_OWNER) + ); + + $relationships = $this->addEditorRelationship( + $relationships, + $resource, + $this->shouldInclude($context, self::REL_EDITOR) + ); + + $relationships = $this->addEditBlockerRelationship( + $relationships, + $resource, + $this->shouldInclude($context, self::REL_EDITBLOCKER) + ); + + $relationships = $this->addParentRelationship( + $relationships, + $resource, + $this->shouldInclude($context, self::REL_PARENT) + ); + + $relationships = $this->addAncestorsRelationship( + $relationships, + $resource, + $this->shouldInclude($context, self::REL_ANCESTORS) + ); + + $relationships = $this->addDescendantsRelationship( + $relationships, + $resource, + $this->shouldInclude($context, self::REL_DESCENDANTS) + ); + + $relationships = $this->addImageRelationship( + $relationships, + $resource, + $this->shouldInclude($context, self::REL_IMAGE) + ); + + $relationships = $this->addTaskRelationship( + $relationships, + $resource, + $this->shouldInclude($context, self::REL_TASK) + ); + + $relationships = $this->addUnitRelationship( + $relationships, + $resource, + $this->shouldInclude($context, self::REL_UNIT) + ); + + $relationships = $this->addFeedbackElementRelationship( + $relationships, + $resource, + $this->shouldInclude($context, self::REL_FEEDBACKELEMENT) + ); + + return $relationships; + } + + private function addAncestorsRelationship(array $relationships, $resource, $includeData) + { + $relation = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_ANCESTORS), + ], + ]; + + if ($includeData) { + $related = $resource->findAncestors(); + $relation[self::RELATIONSHIP_DATA] = $related; + } + + $relationships[self::REL_ANCESTORS] = $relation; + + return $relationships; + } + + private function addChildrenRelationship(array $relationships, $resource, bool $includeData): array + { + $relation = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_CHILDREN), + ], + ]; + + if ($includeData) { + $user = $this->currentUser; + $relation[self::RELATIONSHIP_DATA] = $resource->children->filter(function ($child) use ($user) { + return $child->canRead($user); + }); + } + + $relationships[self::REL_CHILDREN] = $relation; + + return $relationships; + } + + private function addContainersRelationship(array $relationships, $resource, bool $includeData): array + { + $relation = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_CONTAINERS), + ], + ]; + + if ($includeData) { + $relation[self::RELATIONSHIP_DATA] = $resource->containers; + } else { + $relation[self::RELATIONSHIP_DATA] = function () use ($resource) { + $sql = 'SELECT id FROM cw_containers WHERE structural_element_id = ?'; + $containers = \DBManager::get()->fetchAll($sql, [$resource->id], function ($container) { + return new Identifier($container['id'], \JsonApi\Schemas\Courseware\ContainerSchema::TYPE); + }); + + return $containers; + }; + } + $relationships[self::REL_CONTAINERS] = $relation; + + return $relationships; + } + + private function addDescendantsRelationship(array $relationships, $resource, $includeData) + { + $relation = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_DESCENDANTS), + ], + ]; + + if ($includeData) { + $user = $this->currentUser; + $related = $resource->findDescendants($user); + $relation[self::RELATIONSHIP_DATA] = $related; + } + + $relationships[self::REL_DESCENDANTS] = $relation; + + return $relationships; + } + + private function addImageRelationship(array $relationships, $resource, $includeData) + { + $image = $resource->image; + $relation = [ + self::RELATIONSHIP_DATA => $image ?: null, + ]; + + if ($image) { + $relation[self::RELATIONSHIP_META] = [ + 'download-url' => $resource->getImageUrl(), + ]; + } + + $relationships[self::REL_IMAGE] = $relation; + + return $relationships; + } + + private function addEditBlockerRelationship(array $relationships, $resource, bool $includeData): array + { + $relation = [ + self::RELATIONSHIP_LINKS_SELF => true, + ]; + if ($resource['edit_blocker_id']) { + $relation[self::RELATIONSHIP_LINKS] = [ + Link::RELATED => $this->createLinkToUser($resource['edit_blocker_id']), + ]; + $relation[self::RELATIONSHIP_DATA] = $includeData + ? $resource->edit_blocker + : new Identifier($resource['edit_blocker_id'], \JsonApi\Schemas\User::TYPE); + } else { + $relation[self::RELATIONSHIP_DATA] = null; + } + $relationships[self::REL_EDITBLOCKER] = $relation; + + return $relationships; + } + + private function addEditorRelationship(array $relationships, $resource, bool $includeData): array + { + $relation = []; + if ($resource['editor_id']) { + $relation[self::RELATIONSHIP_LINKS] = [ + Link::RELATED => $this->createLinkToUser($resource['editor_id']), + ]; + $relation[self::RELATIONSHIP_DATA] = $includeData + ? $resource->editor + : new Identifier($resource['editor_id'], \JsonApi\Schemas\User::TYPE); + } else { + $relation[self::RELATIONSHIP_DATA] = null; + } + $relationships[self::REL_EDITOR] = $relation; + + return $relationships; + } + + private function addOwnerRelationship(array $relationships, $resource, bool $includeData): array + { + $relation = []; + if ($resource['owner_id']) { + $relation[self::RELATIONSHIP_LINKS] = [ + Link::RELATED => $this->createLinkToUser($resource['owner_id']), + ]; + $relation[self::RELATIONSHIP_DATA] = $includeData + ? $resource->owner + : new Identifier($resource['owner_id'], \JsonApi\Schemas\User::TYPE); + } else { + $relation[self::RELATIONSHIP_DATA] = null; + } + $relationships[self::REL_OWNER] = $relation; + + return $relationships; + } + + private function addParentRelationship(array $relationships, $resource, bool $includeData): array + { + $relation = []; + + if ($resource['parent_id']) { + $relation[self::RELATIONSHIP_LINKS] = [ + Link::RELATED => $this->createLinkToStructuralElement($resource['parent_id']), + ]; + $relation[self::RELATIONSHIP_DATA] = $includeData + ? $resource->parent + : new Identifier($resource['parent_id'], self::TYPE); + } else { + $relation[self::RELATIONSHIP_DATA] = null; + } + $relationships[self::REL_PARENT] = $relation; + + return $relationships; + } + + private function addRangeRelationship(array $relationships, $resource, $context): array + { + if ($resource['range_type'] === 'course') { + $includeData = $this->shouldInclude($context, self::REL_COURSE); + $relationships[self::REL_COURSE] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->createLinkToCourse($resource['range_id']), + ], + self::RELATIONSHIP_DATA => $includeData + ? $resource->course + : new Identifier($resource['range_id'], \JsonApi\Schemas\Course::TYPE), + ]; + } elseif ($resource['range_type'] === 'user') { + $includeData = $this->shouldInclude($context, self::REL_USER); + $relationships[self::REL_USER] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->createLinkToUser($resource['range_id']), + ], + self::RELATIONSHIP_DATA => $includeData + ? $resource->user + : new Identifier($resource['range_id'], \JsonApi\Schemas\User::TYPE), + ]; + } + + return $relationships; + } + + private function addTaskRelationship( + array $relationships, + $resource, + bool $shouldInclude + ): array { + $relationships[self::REL_TASK] = $resource->task + ? [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->createLinkToResource($resource->task), + ], + self::RELATIONSHIP_DATA => $resource->task, + ] + : [ + self::RELATIONSHIP_DATA => null, + ]; + + return $relationships; + } + + private function addUnitRelationship(array $relationships, $resource, $includeData): array + { + $relation = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_UNIT), + ], + ]; + + $related = $resource->findUnit(); + $relation[self::RELATIONSHIP_DATA] = $related; + + $relationships[self::REL_UNIT] = $relation; + + return $relationships; + } + + private function addFeedbackElementRelationship(array $relationships, $resource, $includeData): array + { + $relation = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_FEEDBACKELEMENT), + ], + ]; + + $feedback = $resource->getFeedbackElement(); + $relation[self::RELATIONSHIP_DATA] = $feedback; + $relationships[self::REL_FEEDBACKELEMENT] = $relation; + + + return $relationships; + } + + private static $memo = []; + + private function createLinkToCourse($rangeId) + { + if (isset(self::$memo['course' . $rangeId])) { + return self::$memo['course' . $rangeId]; + } + + $course = \Course::build(['id' => $rangeId], false); + $link = $this->createLinkToResource($course); + self::$memo['course' . $rangeId] = $link; + + return $link; + } + + private function createLinkToStructuralElement($structuralElementId) + { + if (isset(self::$memo['structuralelement' . $structuralElementId])) { + return self::$memo['structuralelement' . $structuralElementId]; + } + + $structuralElement = \Courseware\StructuralElement::build(['id' => $structuralElementId], false); + $link = $this->createLinkToResource($structuralElement); + self::$memo['structuralelement' . $structuralElementId] = $link; + + return $link; + } + + private function createLinkToUser($rangeId) + { + if (isset(self::$memo['user' . $rangeId])) { + return self::$memo['user' . $rangeId]; + } + + $course = \User::build(['id' => $rangeId], false); + $link = $this->createLinkToResource($course); + self::$memo['user' . $rangeId] = $link; + + return $link; + } +} diff --git a/package-lock.json b/package-lock.json index 09d74d5..9aa3e34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,9 +12,7 @@ "@fullcalendar/resource": "^6.1.19", "@fullcalendar/vue3": "^6.1.19", "@vojtechlanka/vue-tags-input": "^3.1.1", - "jsonapi-serializer": "^3.6.9", - "qrcode.vue": "^3.6.0", - "sqids": "^0.3.0" + "jsonapi-serializer": "^3.6.9" }, "devDependencies": { "@axe-core/playwright": "^4.6.1", @@ -95,12 +93,14 @@ "postcss": "^8.4.49", "postcss-loader": "^8.1.1", "postcss-scss": "^4.0.4", + "qrcode.vue": "^3.6.0", "raw-loader": "^4.0.2", "sanitize-html": "^2.7.0", "sass": "^1.29.0", "sass-loader": "^16.0.4", "select2": "4.0.13", "sprintf-js": "^1.0.3", + "sqids": "^0.3.0", "stream-browserify": "^3.0.0", "style-loader": "^4.0.0", "stylelint": "^15.11.0", @@ -11601,6 +11601,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/qrcode.vue/-/qrcode.vue-3.6.0.tgz", "integrity": "sha512-vQcl2fyHYHMjDO1GguCldJxepq2izQjBkDEEu9NENgfVKP6mv/e2SU62WbqYHGwTgWXLhxZ1NCD1dAZKHQq1fg==", + "dev": true, "license": "MIT", "peerDependencies": { "vue": "^3.0.0" @@ -12535,6 +12536,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/sqids/-/sqids-0.3.0.tgz", "integrity": "sha512-lOQK1ucVg+W6n3FhRwwSeUijxe93b51Bfz5PMRMihVf1iVkl82ePQG7V5vwrhzB11v0NtsR25PSZRGiSomJaJw==", + "dev": true, "license": "MIT" }, "node_modules/stack-utils": { diff --git a/tests/jsonapi/StructuralElementsShowTest.php b/tests/jsonapi/StructuralElementsShowTest.php index c42e88f..83a21cc 100644 --- a/tests/jsonapi/StructuralElementsShowTest.php +++ b/tests/jsonapi/StructuralElementsShowTest.php @@ -3,7 +3,7 @@ use Courseware\Instance; use Courseware\StructuralElement; use JsonApi\Routes\Courseware\StructuralElementsShow; -use JsonApi\Schemas\Courseware\StructuralElement as Schema; +use JsonApi\Schemas\Courseware\StructuralElementSchema as Schema; use WoohooLabs\Yang\JsonApi\Response\JsonApiResponse; class StructuralElementsShowTest extends \Codeception\Test\Unit -- cgit v1.0