diff options
| author | Ron Lucke <lucke@elan-ev.de> | 2024-11-04 11:39:35 +0000 |
|---|---|---|
| committer | Ron Lucke <lucke@elan-ev.de> | 2024-11-04 11:39:35 +0000 |
| commit | c7c10f40175d34d9c76fa90d328ff0cdc2c7dfd2 (patch) | |
| tree | b444b0206bd43cb87612f90f0f646eccff301348 /lib | |
| parent | e8ce2c6e2bb858f9af664dbc5ac37edb958b8850 (diff) | |
Courseware: Rechte und Sichtbarkeit funktionieren nach unterschiedlichen Kriterien
Closes #3442
Merge request studip/studip!2635
Diffstat (limited to 'lib')
13 files changed, 530 insertions, 227 deletions
diff --git a/lib/classes/JsonApi/Routes/Courseware/CoursesUnitsIndex.php b/lib/classes/JsonApi/Routes/Courseware/CoursesUnitsIndex.php index b09d0c8..9a3425f 100644 --- a/lib/classes/JsonApi/Routes/Courseware/CoursesUnitsIndex.php +++ b/lib/classes/JsonApi/Routes/Courseware/CoursesUnitsIndex.php @@ -39,9 +39,15 @@ class CoursesUnitsIndex extends JsonApiController } $resources = Unit::findCoursesUnits($course); - $total = count($resources); + $readable_resources = []; + foreach ($resources as $resource) { + if ($resource->canRead($user)) { + $readable_resources[] = $resource; + } + } + $total = count($readable_resources); [$offset, $limit] = $this->getOffsetAndLimit(); - return $this->getPaginatedContentResponse(array_slice($resources, $offset, $limit), $total); + return $this->getPaginatedContentResponse(array_slice($readable_resources, $offset, $limit), $total); } } diff --git a/lib/classes/JsonApi/Routes/Courseware/StructuralElementsCreate.php b/lib/classes/JsonApi/Routes/Courseware/StructuralElementsCreate.php index c038c45..99481f5 100644 --- a/lib/classes/JsonApi/Routes/Courseware/StructuralElementsCreate.php +++ b/lib/classes/JsonApi/Routes/Courseware/StructuralElementsCreate.php @@ -81,8 +81,18 @@ class StructuralElementsCreate extends JsonApiController 'title' => self::arrayGet($json, 'data.attributes.title', ''), 'purpose' => self::arrayGet($json, 'data.attributes.purpose', $parent->purpose), 'payload' => self::arrayGet($json, 'data.attributes.payload', ''), - 'read_approval' => $parent->read_approval, - 'write_approval' => $parent->write_approval, + 'permission_type'=> $parent->permission_type, + 'visible' => $parent->visible, + 'visible_all' => $parent->visible_all, + 'visible_start_date' => $parent->visible_start_date, + 'visible_end_date' => $parent->visible_end_date, + 'writable' => $parent->writable, + 'writable_all' => $parent->writable_all, + 'writable_start_date' => $parent->writable_start_date, + 'writable_end_date' => $parent->writable_end_date, + 'visible_approval' => $parent->visible_approval, + 'writable_approval' => $parent->writable_approval, + 'content_approval' => $parent->content_approval, 'position' => $parent->countChildren(), 'commentable' => 0 ]); diff --git a/lib/classes/JsonApi/Routes/Courseware/StructuralElementsReleasedIndex.php b/lib/classes/JsonApi/Routes/Courseware/StructuralElementsReleasedIndex.php index b4a8e1c..699623b 100644 --- a/lib/classes/JsonApi/Routes/Courseware/StructuralElementsReleasedIndex.php +++ b/lib/classes/JsonApi/Routes/Courseware/StructuralElementsReleasedIndex.php @@ -49,7 +49,7 @@ class StructuralElementsReleasedIndex extends JsonApiController ); foreach ($contents as $content) { - if ((count($content->read_approval) && count($content->read_approval['users']) > 0) || (count($content->write_approval) && count($content->write_approval['users']) > 0)) { + if (count($content->content_approval) && count($content->content_approval['users']) > 0) { $resources[] = $content; } } diff --git a/lib/classes/JsonApi/Routes/Courseware/StructuralElementsSharedIndex.php b/lib/classes/JsonApi/Routes/Courseware/StructuralElementsSharedIndex.php index 1582fbf..9ebf923 100644 --- a/lib/classes/JsonApi/Routes/Courseware/StructuralElementsSharedIndex.php +++ b/lib/classes/JsonApi/Routes/Courseware/StructuralElementsSharedIndex.php @@ -49,19 +49,13 @@ class StructuralElementsSharedIndex extends JsonApiController ); foreach ($contents as $content) { - if (!count($content->read_approval) || !count($content->write_approval)) { + if (count($content->content_approval) === 0) { continue; } $add_content = false; - foreach ($content->read_approval['users'] as $listedUserPerm) { - if ($listedUserPerm['id'] == $user->id && $listedUserPerm['read']) { - $add_content = true; - } - } - - foreach ($content->write_approval['users'] as $listedUserPerm) { + foreach ($content->content_approval['users'] as $listedUserPerm) { if ($listedUserPerm['id'] == $user->id && $listedUserPerm['read']) { $add_content = true; } diff --git a/lib/classes/JsonApi/Routes/Courseware/StructuralElementsUpdate.php b/lib/classes/JsonApi/Routes/Courseware/StructuralElementsUpdate.php index 455aacc..3b3deb0 100644 --- a/lib/classes/JsonApi/Routes/Courseware/StructuralElementsUpdate.php +++ b/lib/classes/JsonApi/Routes/Courseware/StructuralElementsUpdate.php @@ -6,6 +6,7 @@ use Courseware\StructuralElement; use JsonApi\Errors\AuthorizationFailedException; 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\FileRef as FileRefSchema; @@ -19,6 +20,7 @@ use Psr\Http\Message\ServerRequestInterface as Request; class StructuralElementsUpdate extends JsonApiController { use EditBlockAwareTrait; + use TimestampTrait; use ValidationTrait; /** @@ -26,7 +28,8 @@ class StructuralElementsUpdate extends JsonApiController */ public function __invoke(Request $request, Response $response, $args) { - if (!($resource = StructuralElement::find($args['id']))) { + $resource = StructuralElement::find($args['id']); + if (!$resource) { throw new RecordNotFoundException(); } $json = $this->validate($request, $resource); @@ -105,7 +108,7 @@ class StructuralElementsUpdate extends JsonApiController } $parentId = self::arrayGet($json, 'data.relationships.parent.data.id'); - return \Courseware\StructuralElement::find($parentId); + return StructuralElement::find($parentId); } private function updateStructuralElement(\User $user, StructuralElement $resource, array $json): StructuralElement @@ -118,11 +121,13 @@ class StructuralElementsUpdate extends JsonApiController 'position', 'public', 'purpose', - 'read-approval', - 'release-date', 'title', - 'withdraw-date', - 'write-approval', + 'permission-type', + 'visible', + 'writable', + 'visible-approval', + 'writable-approval', + 'content-approval', ]; foreach ($attributes as $jsonKey) { @@ -131,13 +136,43 @@ class StructuralElementsUpdate extends JsonApiController $resource->$sormKey = $val; } } - - if (isset($json['data']['attributes']['release-date'])) { - $resource->release_date = $json['data']['attributes']['release-date']; + if (self::arrayHas($json, 'data.attributes.visible-all')) { + $resource->visible_all = self::arrayGet($json, 'data.attributes.visible-all'); } - - if (isset($json['data']['attributes']['withdraw-date'])) { - $resource->withdraw_date = $json['data']['attributes']['withdraw-date']; + if (self::arrayHas($json, 'data.attributes.writable-all')) { + $resource->writable_all = self::arrayGet($json, 'data.attributes.writable-all'); + } + if (self::arrayHas($json, 'data.attributes.visible-start-date')) { + $visibleStartDate = self::arrayGet($json, 'data.attributes.visible-start-date'); + if ($visibleStartDate) { + $visibleStartDate = self::fromISO8601($visibleStartDate); + $visibleStartDate = $visibleStartDate->getTimestamp(); + } + $resource->visible_start_date = $visibleStartDate; + } + if (self::arrayHas($json, 'data.attributes.visible-end-date')) { + $visibleEndDate = self::arrayGet($json, 'data.attributes.visible-end-date'); + if ($visibleEndDate) { + $visibleEndDate = self::fromISO8601($visibleEndDate); + $visibleEndDate = $visibleEndDate->getTimestamp(); + } + $resource->visible_end_date = $visibleEndDate; + } + if (self::arrayHas($json, 'data.attributes.writable-start-date')) { + $writableStartDate = self::arrayGet($json, 'data.attributes.writable-start-date'); + if ($writableStartDate) { + $writableStartDate = self::fromISO8601($writableStartDate); + $writableStartDate = $writableStartDate->getTimestamp(); + } + $resource->writable_start_date = $writableStartDate; + } + if (self::arrayHas($json, 'data.attributes.writable-end-date')) { + $writableEndDate = self::arrayGet($json, 'data.attributes.writable-end-date'); + if ($writableEndDate) { + $writableEndDate = self::fromISO8601($writableEndDate); + $writableEndDate = $writableEndDate->getTimestamp(); + } + $resource->writable_end_date = $writableEndDate; } if (isset($json['data']['attributes']['commentable'])) { diff --git a/lib/classes/JsonApi/Routes/Courseware/UnitsCopy.php b/lib/classes/JsonApi/Routes/Courseware/UnitsCopy.php index 61ffa95..62a1d0a 100644 --- a/lib/classes/JsonApi/Routes/Courseware/UnitsCopy.php +++ b/lib/classes/JsonApi/Routes/Courseware/UnitsCopy.php @@ -31,6 +31,7 @@ class UnitsCopy extends NonJsonApiController $rangeId = $data['rangeId']; $rangeType = $data['rangeType']; $modified = $data['modified']; + $duplicate = $data['duplicate']; try { $range = \RangeFactory::createRange($rangeType, $rangeId); @@ -42,7 +43,7 @@ class UnitsCopy extends NonJsonApiController throw new AuthorizationFailedException(); } - $newUnit = $sourceUnit->copy($user, $rangeId, $rangeType, $modified); + $newUnit = $sourceUnit->copy($user, $rangeId, $rangeType, $modified, $duplicate); $response = $response->withHeader('Content-Type', 'application/json'); $response->getBody()->write((string) json_encode($newUnit)); diff --git a/lib/classes/JsonApi/Routes/Courseware/UnitsCreate.php b/lib/classes/JsonApi/Routes/Courseware/UnitsCreate.php index 9dfd2e6..8f3e217 100644 --- a/lib/classes/JsonApi/Routes/Courseware/UnitsCreate.php +++ b/lib/classes/JsonApi/Routes/Courseware/UnitsCreate.php @@ -97,7 +97,10 @@ class UnitsCreate extends JsonApiController 'purpose' => self::arrayGet($json, 'data.attributes.purpose', 'content'), 'payload' => self::arrayGet($json, 'data.attributes.payload', ''), 'position' => 0, - 'commentable' => 0 + 'commentable' => 0, + 'permission_type' => self::arrayGet($json, 'data.attributes.permission-type', 'all'), + 'visible' => self::arrayGet($json, 'data.attributes.visible', 'always'), + 'writable' => self::arrayGet($json, 'data.attributes.writable', 'never'), ]); \Courseware\Container::create([ @@ -114,7 +117,7 @@ class UnitsCreate extends JsonApiController ]), ]); - $unit = \Courseware\Unit::create([ + $unit = Unit::create([ 'range_id' => $range->getRangeId(), 'range_type' => $range->getRangeType(), 'structural_element_id' => $struct->id, @@ -122,8 +125,9 @@ class UnitsCreate extends JsonApiController 'position' => Unit::getNewPosition($range->getRangeId()), 'creator_id' => $user->id, 'public' => self::arrayGet($json, 'data.attributes.public', '0'), - 'release_date' => self::arrayGet($json, 'data.attributes.release-date'), - 'withdraw_date' => self::arrayGet($json, 'data.attributes.withdraw-date'), + 'permission_type' => self::arrayGet($json, 'data.attributes.permission-type', 'all'), + 'visible' => self::arrayGet($json, 'data.attributes.visible', 'always'), + 'writable' => self::arrayGet($json, 'data.attributes.writable', 'never'), ]); $instance = new \Courseware\Instance($struct); diff --git a/lib/classes/JsonApi/Routes/Courseware/UnitsUpdate.php b/lib/classes/JsonApi/Routes/Courseware/UnitsUpdate.php index 446d61e..4c48086 100644 --- a/lib/classes/JsonApi/Routes/Courseware/UnitsUpdate.php +++ b/lib/classes/JsonApi/Routes/Courseware/UnitsUpdate.php @@ -57,37 +57,92 @@ class UnitsUpdate extends JsonApiController return 'Document must have an `id`.'; } - if (self::arrayHas($json, 'data.attributes.release-date')) { - $releaseDate = self::arrayGet($json, 'data.attributes.release-date'); - if (!self::isValidTimestamp($releaseDate)) { - return '`release-date` is not an ISO 8601 timestamp.'; + if (self::arrayHas($json, 'data.attributes.visible-start-date')) { + $visibleStartDate = self::arrayGet($json, 'data.attributes.visible-start-date'); + if ($visibleStartDate && !self::isValidTimestamp($visibleStartDate)) { + return '`visible-start-date` is not an ISO 8601 timestamp.'; } } - if (self::arrayHas($json, 'data.attributes.withdraw-date')) { - $withdrawDate = self::arrayGet($json, 'data.attributes.withdraw-date'); - if (!self::isValidTimestamp($withdrawDate)) { - return '`withdraw-date` is not an ISO 8601 timestamp.'; + if (self::arrayHas($json, 'data.attributes.visible-end-date')) { + $visibleEndDate = self::arrayGet($json, 'data.attributes.visible-end-date'); + if ($visibleEndDate && !self::isValidTimestamp($visibleEndDate)) { + return '`visible-start-date` is not an ISO 8601 timestamp.'; + } + } + + if (self::arrayHas($json, 'data.attributes.writable-start-date')) { + $writableStartDate = self::arrayGet($json, 'data.attributes.writable-start-date'); + if ($writableStartDate && !self::isValidTimestamp($writableStartDate)) { + return '`writable-start-date` is not an ISO 8601 timestamp.'; + } + } + + if (self::arrayHas($json, 'data.attributes.writable-end-date')) { + $writableEndDate = self::arrayGet($json, 'data.attributes.writable-end-date'); + if ($writableEndDate && !self::isValidTimestamp($writableEndDate)) { + return '`writable-end-date` is not an ISO 8601 timestamp.'; } } } private function updateUnit(\User $user, Unit $resource, array $json): Unit { - if (self::arrayHas($json, 'data.attributes.public')) { - $resource->public = self::arrayGet($json, 'data.attributes.public'); - } + $attributes = [ + 'position', + 'public', + 'permission-scope', + 'permission-type', + 'visible', + 'visible-approval', + 'writable', + 'writable-approval', + ]; - if (self::arrayHas($json, 'data.attributes.release-date')) { - $releaseDate = self::arrayGet($json, 'data.attributes.release-date', ''); - $releaseDate = self::fromISO8601($releaseDate); - $resource->release_date = $releaseDate->getTimestamp(); + foreach ($attributes as $jsonKey) { + $sormKey = strtr($jsonKey, '-', '_'); + $val = self::arrayGet($json, 'data.attributes.' . $jsonKey, ''); + if ($val) { + $resource->$sormKey = $val; + } } - - if (self::arrayHas($json, 'data.attributes.withdraw-date')) { - $withdrawDate = self::arrayGet($json, 'data.attributes.withdraw-date', ''); - $withdrawDate = self::fromISO8601($withdrawDate); - $resource->withdraw_date = $withdrawDate->getTimestamp(); + if (self::arrayHas($json, 'data.attributes.visible-all')) { + $resource->visible_all = self::arrayGet($json, 'data.attributes.visible-all'); + } + if (self::arrayHas($json, 'data.attributes.writable-all')) { + $resource->writable_all = self::arrayGet($json, 'data.attributes.writable-all'); + } + if (self::arrayHas($json, 'data.attributes.visible-start-date')) { + $visibleStartDate = self::arrayGet($json, 'data.attributes.visible-start-date'); + if ($visibleStartDate) { + $visibleStartDate = self::fromISO8601($visibleStartDate); + $visibleStartDate = $visibleStartDate->getTimestamp(); + } + $resource->visible_start_date = $visibleStartDate; + } + if (self::arrayHas($json, 'data.attributes.visible-end-date')) { + $visibleEndDate = self::arrayGet($json, 'data.attributes.visible-end-date'); + if ($visibleEndDate) { + $visibleEndDate = self::fromISO8601($visibleEndDate); + $visibleEndDate = $visibleEndDate->getTimestamp(); + } + $resource->visible_end_date = $visibleEndDate; + } + if (self::arrayHas($json, 'data.attributes.writable-start-date')) { + $writableStartDate = self::arrayGet($json, 'data.attributes.writable-start-date'); + if ($writableStartDate) { + $writableStartDate = self::fromISO8601($writableStartDate); + $writableStartDate = $writableStartDate->getTimestamp(); + } + $resource->writable_start_date = $writableStartDate; + } + if (self::arrayHas($json, 'data.attributes.writable-end-date')) { + $writableEndDate = self::arrayGet($json, 'data.attributes.writable-end-date'); + if ($writableEndDate) { + $writableEndDate = self::fromISO8601($writableEndDate); + $writableEndDate = $writableEndDate->getTimestamp(); + } + $resource->writable_end_date = $writableEndDate; } $resource->store(); diff --git a/lib/classes/JsonApi/Routes/SemestersIndex.php b/lib/classes/JsonApi/Routes/SemestersIndex.php index ffa1d4c..24883c1 100644 --- a/lib/classes/JsonApi/Routes/SemestersIndex.php +++ b/lib/classes/JsonApi/Routes/SemestersIndex.php @@ -4,6 +4,7 @@ namespace JsonApi\Routes; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ResponseInterface as Response; +use JsonApi\Errors\RecordNotFoundException; use JsonApi\JsonApiController; /** @@ -13,10 +14,30 @@ class SemestersIndex extends JsonApiController { protected $allowedPagingParameters = ['offset', 'limit']; + protected $allowedFilteringParameters = ['current', 'timestamp']; + public function __invoke(Request $request, Response $response, $args) { list($offset, $limit) = $this->getOffsetAndLimit(); - $semesters = \Semester::getAll(); + + $filtering = $this->getQueryParameters()->getFilteringParameters(); + + if (empty($filtering)) { + $semesters = \Semester::getAll(); + } else { + if (array_key_exists('current', $filtering)) { + $semester = \Semester::findCurrent(); + } + if (isset($filtering['timestamp'])) { + $semester = \Semester::findByTimestamp($filtering['timestamp']); + } + + if (!$semester) { + throw new RecordNotFoundException('Could not find semester.'); + } else { + $semesters = [$semester]; + } + } return $this->getPaginatedContentResponse( array_slice($semesters, $offset, $limit), diff --git a/lib/classes/JsonApi/Schemas/Courseware/StructuralElement.php b/lib/classes/JsonApi/Schemas/Courseware/StructuralElement.php index e6ccafa..f1a5841 100644 --- a/lib/classes/JsonApi/Schemas/Courseware/StructuralElement.php +++ b/lib/classes/JsonApi/Schemas/Courseware/StructuralElement.php @@ -47,10 +47,18 @@ class StructuralElement extends SchemaProvider 'purpose' => (string) $resource['purpose'], 'payload' => $resource['payload']->getIterator(), 'public' => (int) $resource['public'], - 'release-date' => $resource['release_date'] ? date('Y-m-d', (int) $resource['release_date']) : null, - 'withdraw-date' => $resource['withdraw_date'] ? date('Y-m-d', (int) $resource['withdraw_date']) : null, - 'read-approval' => $resource['read_approval']->getIterator(), - 'write-approval' => $resource['write_approval']->getIterator(), + '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), diff --git a/lib/classes/JsonApi/Schemas/Courseware/Unit.php b/lib/classes/JsonApi/Schemas/Courseware/Unit.php index 6152e94..a311ac8 100644 --- a/lib/classes/JsonApi/Schemas/Courseware/Unit.php +++ b/lib/classes/JsonApi/Schemas/Courseware/Unit.php @@ -28,15 +28,30 @@ class Unit extends SchemaProvider */ public function getAttributes($resource, ContextInterface $context): iterable { + $user = $this->currentUser; + return [ 'content-type' => (string) $resource['content_type'], 'position' => (int) $resource['position'], 'public' => (int) $resource['public'], - 'release-date' => $resource['release_date'] ? date('c', $resource['release_date']) : null, - 'withdraw-date' => $resource['withdraw_date'] ? date('c', $resource['withdraw_date']) : null, + 'permission-scope' => (string) $resource['permission_scope'], + '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']), 'config' => json_decode($resource['config']), - 'mkdate' => date('c', $resource['mkdate']), - 'chdate' => date('c', $resource['chdate']), + 'can-read' => $resource->canRead($user), + 'can-edit' => $resource->canEdit($user), + 'can-edit-content' => $resource->canEditContent($user), + 'mkdate' => date('c', $resource['mkdate']), + 'chdate' => date('c', $resource['chdate']), ]; } diff --git a/lib/models/Courseware/StructuralElement.php b/lib/models/Courseware/StructuralElement.php index 8e06a39..6f87e70 100644 --- a/lib/models/Courseware/StructuralElement.php +++ b/lib/models/Courseware/StructuralElement.php @@ -32,10 +32,18 @@ use User; * @property \JSONArrayObject $payload database column * @property int $public database column * @property int $commentable database column - * @property int $release_date database column - * @property int $withdraw_date database column - * @property \JSONArrayObject $read_approval database column - * @property \JSONArrayObject $write_approval database column + * @property string $permission_type database column + * @property string $visible database column + * @property bool $visible_all database column + * @property int $visible_start_date database column + * @property int $visible_end_date database column + * @property string $writable database column + * @property bool $writable_all database column + * @property int|null $writable_start_date database column + * @property int|null $writable_end_date database column + * @property \JSONArrayObject $visible_approval database column + * @property \JSONArrayObject $writable_approval database column + * @property \JSONArrayObject $content_approval database column * @property \JSONArrayObject $copy_approval database column * @property \JSONArrayObject $external_relations database column * @property int $mkdate database column @@ -60,8 +68,9 @@ class StructuralElement extends \SimpleORMap implements \PrivacyObject, \Feedbac $config['db_table'] = 'cw_structural_elements'; $config['serialized_fields']['payload'] = JSONArrayObject::class; - $config['serialized_fields']['read_approval'] = JSONArrayObject::class; - $config['serialized_fields']['write_approval'] = JSONArrayObject::class; + $config['serialized_fields']['visible_approval'] = JSONArrayObject::class; + $config['serialized_fields']['writable_approval'] = JSONArrayObject::class; + $config['serialized_fields']['content_approval'] = JSONArrayObject::class; $config['serialized_fields']['copy_approval'] = JSONArrayObject::class; $config['serialized_fields']['external_relations'] = JSONArrayObject::class; @@ -276,37 +285,42 @@ class StructuralElement extends \SimpleORMap implements \PrivacyObject, \Feedbac if ($this->range_id === $user->id) { return true; } - - return $this->hasWriteApproval($user); + + return $this->hasWriteContentApproval($user); case 'course': - $hasEditingPermission = $this->hasEditingPermission($user); - if ($this->isTask()) { - $task = $this->task; - if (!$task) { - $task = $this->findParentTask(); + $unit = $this->findUnit(); + if ($unit->permission_scope === 'unit') { + return $unit->canEditContent($user); + } else { + $hasEditingPermission = $this->hasEditingPermission($user, $unit); + if ($this->isTask()) { + $task = $this->task; if (!$task) { + $task = $this->findParentTask(); + if (!$task) { + return false; + } + } + + if ($hasEditingPermission) { return false; } - } - if ($hasEditingPermission) { - return false; - } + if ($task->isSubmitted()) { + return false; + } - if ($task->isSubmitted()) { - return false; + return $task->userIsASolver($user); } - return $task->userIsASolver($user); - } + if ($hasEditingPermission) { + return true; + } - if ($hasEditingPermission) { - return true; + return $this->hasWriteApproval($user); } - return $this->hasWriteApproval($user); - default: throw new \InvalidArgumentException('Unknown range type.'); } @@ -337,22 +351,23 @@ class StructuralElement extends \SimpleORMap implements \PrivacyObject, \Feedbac return true; } - return $this->hasReadApproval($user); + return $this->hasReadContentApproval($user); case 'course': - if (!$GLOBALS['perm']->have_studip_perm('user', $this->range_id, $user->id)) { - return false; - } + $unit = $this->findUnit(); + if ($unit->permission_scope === 'unit') { + return $unit->canRead($user); + } else { + if (!$GLOBALS['perm']->have_studip_perm('user', $this->range_id, $user->id)) { + return false; + } - if ($this->canEdit($user)) { - return true; - } + if ($this->canEdit($user)) { + return true; + } - if (!$this->releasedForReaders($this)) { - return false; + return $this->hasReadApproval($user); } - return $this->hasReadApproval($user); - default: throw new \InvalidArgumentException('Unknown range type.'); } @@ -371,7 +386,7 @@ class StructuralElement extends \SimpleORMap implements \PrivacyObject, \Feedbac return true; } - return $this->hasReadApproval($user); + return $this->hasReadContentApproval($user); case 'course': if (!$GLOBALS['perm']->have_studip_perm('user', $this->range_id, $user->id)) { @@ -411,10 +426,6 @@ class StructuralElement extends \SimpleORMap implements \PrivacyObject, \Feedbac return true; } - if (!$this->releasedForReaders($this)) { - return false; - } - return $this->hasReadApproval($user) && $this->canReadSequential($user); default: @@ -425,9 +436,11 @@ class StructuralElement extends \SimpleORMap implements \PrivacyObject, \Feedbac /** * @param \User|\Seminar_User $user */ - public function hasEditingPermission($user): bool + public function hasEditingPermission(User $user, Unit $unit = null): bool { - $unit = $this->findUnit(); + if (!isset($unit)) { + $unit = $this->findUnit(); + } return $GLOBALS['perm']->have_perm('root', $user->id) || $GLOBALS['perm']->have_studip_perm( $unit->config['editing_permission'] ?? 'tutor', @@ -438,34 +451,21 @@ class StructuralElement extends \SimpleORMap implements \PrivacyObject, \Feedbac private function hasReadApproval($user): bool { - // this property is shared between all range types. - if ($this->read_approval['all']) { - return true; - } - - // now we also check against the perms for contents. - if ($this->range_type === 'user') { - return $this->hasUserReadApproval($user); - } else { - if (!count($this->read_approval)) { + if ($this->permission_type === 'all' || $this->visible_all) { + if ($this->isVisible()) { return true; } - - // find user in users - $users = $this->read_approval['users']; - foreach ($users as $approvedUserId) { - if ($approvedUserId === $user->id) { - return true; - } - } - - // find user in groups - $groups = $this->read_approval['groups']; + return false; + } + if ($this->permission_type === 'users') { + return in_array($user->id, json_decode($this->visible_approval)) && $this->isVisible(); + } + if ($this->permission_type === 'groups') { + $groups = json_decode($this->visible_approval); foreach ($groups as $groupId) { - /** @var ?\Statusgruppen $group */ $group = \Statusgruppen::find($groupId); if ($group && $group->isMember($user->id)) { - return true; + return $this->isVisible(); } } } @@ -473,28 +473,38 @@ class StructuralElement extends \SimpleORMap implements \PrivacyObject, \Feedbac return false; } - private function hasUserReadApproval($user): bool + private function isVisible(): bool + { + if ($this->visible === 'always') { + return true; + } + if ($this->visible === 'never') { + return false; + } + + return + (empty($this->visible_start_date) || $this->visible_start_date < strtotime('today')) + && (empty($this->visible_end_date) || $this->visible_end_date >= strtotime('today')); + } + + private function hasReadContentApproval($user): bool { - if (!count($this->read_approval)) { + if (count($this->content_approval) === 0) { if ($this->isRootNode()) { return false; } - return $this->parent->hasUserReadApproval($user); + return $this->parent->hasReadContentApproval($user); } // find user in users - $users = $this->read_approval['users']; + $users = $this->content_approval['users']; foreach ($users as $listedUserPerm) { - // now for contents, there is an expiry date defined. if (!empty($listedUserPerm['expiry']) && strtotime($listedUserPerm['expiry']) < strtotime('today')) { if ($this->isRootNode()) { return false; } - return $this->parent->hasUserReadApproval($user); + return $this->parent->hasReadContentApproval($user); } - // In order to have a record of the users in the perms list of contents, - // we keep a full perm record in read_approval column, and set read property to true or false, - // this won't apply to write_approval column. if ($listedUserPerm['id'] == $user->id && $listedUserPerm['read'] == true) { return true; } @@ -505,33 +515,22 @@ class StructuralElement extends \SimpleORMap implements \PrivacyObject, \Feedbac private function hasWriteApproval($user): bool { - // this property is shared between all range types. - if ($this->write_approval['all']) { - return true; - } - - // now we also check against the perms for contents. - if ($this->range_type === 'user') { - return $this->hasUserWriteApproval($user); - } else { - if (!count($this->write_approval)) { - return false; - } - - if ($this->write_approval['all']) { - return true; - } - - // find user in users - $users = $this->write_approval['users']->getArrayCopy(); - if (in_array($user->id, $users)) { + if ($this->permission_type === 'all' || $this->writable_all) { + if ($this->isWritable()) { return true; } + return false; + } + if ($this->permission_type === 'users') { + return in_array($user->id, json_decode($this->writable_approval)) && $this->isWritable(); + } - // find user in groups - foreach (\Statusgruppen::findMany($this->write_approval['groups']->getArrayCopy()) as $group) { - if ($group->isMember($user->id)) { - return true; + if ($this->permission_type === 'groups') { + $groups = json_decode($this->writable_approval); + foreach ($groups as $groupId) { + $group = \Statusgruppen::find($groupId); + if ($group && $group->isMember($user->id)) { + return $this->isWritable(); } } } @@ -539,26 +538,39 @@ class StructuralElement extends \SimpleORMap implements \PrivacyObject, \Feedbac return false; } - private function hasUserWriteApproval($user): bool + private function isWritable(): bool { - if (!count($this->write_approval)) { + if ($this->writable === 'always') { + return true; + } + if ($this->writable === 'never') { + return false; + } + + return + (empty($this->writable_start_date) || $this->writable_start_date < strtotime('today')) + && (empty($this->writable_end_date) || $this->writable_end_date >= strtotime('today')); + } + + private function hasWriteContentApproval($user): bool + { + if (!count($this->content_approval)) { if ($this->isRootNode()) { return false; } - return $this->parent->hasUserWriteApproval($user); + return $this->parent->hasWriteContentApproval($user); } // find user in users - $users = $this->write_approval['users']; + $users = $this->content_approval['users']; foreach ($users as $listedUserPerm) { - // now for contents, there is an expiry date defined. if (!empty($listedUserPerm['expiry']) && strtotime($listedUserPerm['expiry']) < strtotime('today')) { if ($this->isRootNode()) { return false; } - return $this->parent->hasUserWriteApproval($user); + return $this->parent->hasWriteContentApproval($user); } - if ($listedUserPerm['id'] == $user->id) { + if ($listedUserPerm['id'] == $user->id && $listedUserPerm['write']) { return true; } } @@ -566,7 +578,7 @@ class StructuralElement extends \SimpleORMap implements \PrivacyObject, \Feedbac if ($this->isRootNode()) { return false; } - return $this->parent->hasUserWriteApproval($user); + return $this->parent->hasWriteContentApproval($user); } /** @@ -587,30 +599,6 @@ class StructuralElement extends \SimpleORMap implements \PrivacyObject, \Feedbac } /** - * @return bool true if the user may read this instance in time interval - * - * @SuppressWarnings(PHPMD.Superglobals) - */ - private function releasedForReaders(StructuralElement $element): bool - { - $released = false; - if (!$element->release_date || $element->release_date <= time()) { - $released = true; - } - - if ($element->withdraw_date && $element->withdraw_date <= time()) { - $released = false; - } - - $parent_released = true; - if (!$element->isRootNode()) { - $parent_released = $this->releasedForReaders($element->parent); - } - - return $released && $parent_released; - } - - /** * @param mixed $user the user to validate * * @return bool true if the user has achieved previous elements @@ -839,13 +827,18 @@ SQL; /** * Copies this instance into another course oder users contents. * - * @param User $user this user will be the owner of the copy - * @param Range $parent the target where to copy this instance + * @param User $user this user will be the owner of the copy + * @param \Range $parent the target where to copy this instance * * @return StructuralElement the copy of this instance */ - public function copyToRange(User $user, string $rangeId, string $rangeType, string $purpose = ''): StructuralElement - { + public function copyToRange( + User $user, + string $rangeId, + string $rangeType, + string $purpose = '', + bool $duplicate = false + ): StructuralElement { $element = self::build([ 'parent_id' => null, 'range_id' => $rangeId, @@ -857,7 +850,19 @@ SQL; 'purpose' => $purpose ?: $this->purpose, 'position' => 0, 'payload' => $this->payload, - 'commentable' => 0 + 'commentable' => $duplicate ? $this->commentable : 0, + 'permission_type' => $duplicate ? $this->permission_type : 'all', + 'visible' => $duplicate ? $this->visible : 'always', + 'visible_all' => $duplicate ? $this->visible_all : 0, + 'visible_start_date' => $duplicate ? $this->visible_start_date : null, + 'visible_end_date' => $duplicate ? $this->visible_end_date : null, + 'visible_approval' => $duplicate ? $this->visible_approval : '', + 'writable' => $duplicate ? $this->writable : 'never', + 'writable_all' => $duplicate ? $this->writable_all : 0, + 'writable_start_date' => $duplicate ? $this->writable_start_date : null, + 'writable_end_date' => $duplicate ? $this->writable_end_date : null, + 'writable_approval' => $duplicate ? $this->writable_approval : '', + 'content_approval' => $duplicate ? $this->content_approval : '', ]); $element->store(); @@ -869,7 +874,7 @@ SQL; $this->copyContainers($user, $element); - $this->copyChildren($user, $element, $purpose); + $this->copyChildren($user, $element, $purpose, '', $duplicate); return $element; } @@ -877,15 +882,20 @@ SQL; /** * Copies this instance as a child into another structural element. * - * @param User $user this user will be the owner of the copy + * @param User $user this user will be the owner of the copy * @param StructuralElement $parent the target where to copy this instance * @param string $purpose the purpose of copying this instance * @param string $recursiveId the optional mapping id for copying child structural elements upon recursive call to this function * * @return StructuralElement the copy of this instance */ - public function copy(User $user, StructuralElement $parent, string $purpose = '', string $recursiveId = ''): StructuralElement - { + public function copy( + User $user, + StructuralElement $parent, + string $purpose = '', + string $recursiveId = '', + bool $duplicate = false + ): StructuralElement { $ancestorIds = array_column($parent->findAncestors(), 'id'); $ancestorIds[] = $parent->id; if (in_array($this->id, $ancestorIds)) { @@ -908,9 +918,19 @@ SQL; 'payload' => $this->payload, 'image_id' => $image_id, 'image_type' => $this->image_type, - 'read_approval' => $parent->read_approval, - 'write_approval' => $parent->write_approval, - 'commentable' => 0 + 'permission_type' => $duplicate ? $this->permission_type : 'all', + 'visible' => $duplicate ? $this->visible : 'always', + 'visible_all' => $duplicate ? $this->visible_all : 0, + 'visible_start_date' => $duplicate ? $this->visible_start_date : null, + 'visible_end_date' => $duplicate ? $this->visible_end_date : null, + 'visible_approval' => $duplicate ? $this->visible_approval : '', + 'writable' => $duplicate ? $this->writable : 'never', + 'writable_all' => $duplicate ? $this->writable_all : 0, + 'writable_start_date' => $duplicate ? $this->writable_start_date : null, + 'writable_end_date' => $duplicate ? $this->writable_end_date : null, + 'writable_approval' => $duplicate ? $this->writable_approval : '', + 'content_approval' => $duplicate ? $this->content_approval : '', + 'commentable' => $duplicate ? $this->commentable : 0, ]); $element->store(); @@ -1027,10 +1047,15 @@ SQL; return [$containerMap, $blockMap]; } - private function copyChildren(User $user, StructuralElement $newElement, string $purpose = '', string $recursiveId = ''): void - { + private function copyChildren( + User $user, + StructuralElement $newElement, + string $purpose = '', + string $recursiveId = '', + bool $duplicate = false + ): void { foreach ($this->children as $child) { - $child->copy($user, $newElement, $purpose, $recursiveId); + $child->copy($user, $newElement, $purpose, $recursiveId, $duplicate); } } @@ -1049,8 +1074,16 @@ SQL; 'purpose' => $this->purpose, 'position' => $parent->countChildren(), 'payload' => $this->payload, - 'read_approval' => $parent->read_approval, - 'write_approval' => $parent->write_approval, + 'permission_type'=> $parent->permission_type, + 'visible' => $parent->visible, + 'visible_start_date' => $parent->visible_start_date, + 'visible_end_date' => $parent->visible_end_date, + 'writable' => $parent->writable, + 'writable_start_date' => $parent->writable_start_date, + 'writable_end_date' => $parent->writable_end_date, + 'visible_approval' => $parent->visible_approval, + 'writable_approval' => $parent->writable_approval, + 'content_approval' => $parent->content_approval, 'commentable' => 0 ]); diff --git a/lib/models/Courseware/Unit.php b/lib/models/Courseware/Unit.php index 2a38a29..86013e3 100644 --- a/lib/models/Courseware/Unit.php +++ b/lib/models/Courseware/Unit.php @@ -20,8 +20,18 @@ use User; * @property string $content_type database column * @property int $public database column * @property string|null $creator_id database column - * @property int|null $release_date database column - * @property int|null $withdraw_date database column + * @property string $permission_scope database column + * @property string $permission_type database column + * @property string $visible database column + * @property bool $visible_all database column + * @property int|null $visible_start_date database column + * @property int|null $visible_end_date database column + * @property string $writable database column + * @property bool $writable_all database column + * @property int|null $writable_start_date database column + * @property int|null $writable_end_date database column + * @property \JSONArrayObject $writable_approval database column + * @property \JSONArrayObject $visible_approval database column * @property \JSONArrayObject $config database column * @property int $mkdate database column * @property int $chdate database column @@ -38,6 +48,8 @@ class Unit extends \SimpleORMap implements \PrivacyObject, \FeedbackRange $config['db_table'] = 'cw_units'; $config['serialized_fields']['config'] = JSONArrayObject::class; + $config['serialized_fields']['visible_approval'] = JSONArrayObject::class; + $config['serialized_fields']['writable_approval'] = JSONArrayObject::class; $config['has_one']['structural_element'] = [ 'class_name' => StructuralElement::class, @@ -45,7 +57,7 @@ class Unit extends \SimpleORMap implements \PrivacyObject, \FeedbackRange 'on_delete' => 'delete', ]; $config['belongs_to']['course'] = [ - 'class_name' => \Course::class, + 'class_name' => \Course::class, 'foreign_key' => 'range_id', 'assoc_foreign_key' => 'seminar_id', ]; @@ -75,26 +87,116 @@ class Unit extends \SimpleORMap implements \PrivacyObject, \FeedbackRange return self::findBySQL('range_id = ? AND range_type = ?', [$course->id, 'course']); } - public static function findUsersUnits(\User $user): array + public static function findUsersUnits(User $user): array { return self::findBySQL('range_id = ? AND range_type = ?', [$user->id, 'user']); } - public function canRead(\User $user): bool + public function canRead(User $user): bool { - return $this->structural_element->canRead($user); + if ($this->canEdit($user) || $this->canEditContent($user)) { + return true; + } + if ($this->permission_scope === 'unit') { + if ($this->permission_type === 'all' || $this->visible_all) { + if ($this->isVisible()) { + return true; + } + return false; + } + if ($this->permission_type === 'users') { + return in_array($user->id, json_decode($this->visible_approval)) && $this->isVisible(); + } + if ($this->permission_type === 'groups') { + $groups = json_decode($this->visible_approval); + foreach ($groups as $groupId) { + $group = \Statusgruppen::find($groupId); + if ($group && $group->isMember($user->id)) { + return $this->isVisible(); + } + } + } + } + + return true; + } + + private function isVisible(): bool + { + if ($this->visible === 'always') { + return true; + } + if ($this->visible === 'never') { + return false; + } + + return + (empty($this->visible_start_date) || $this->visible_start_date < strtotime('today')) + && (empty($this->visible_end_date) || $this->visible_end_date >= strtotime('today')); + } + + public function canEdit(User $user): bool + { + if ($user->id === $this->range_id) { + return true; + } + + return $GLOBALS['perm']->have_perm('root', $user->id) || $GLOBALS['perm']->have_studip_perm('tutor', $this->range_id, $user->id); } - public function canEdit(\User $user): bool + public function canEditContent(User $user): bool { - return $this->structural_element->canEdit($user);; + if ($this->canEdit($user)) { + return true; + } + if ($this->permission_scope === 'unit') { + if ($this->permission_type === 'all' || $this->writable_all) { + if ($this->isWritable()) { + return true; + } + return false; + } + if ($this->permission_type === 'users') { + return in_array($user->id, json_decode($this->writable_approval)) && $this->isWritable(); + } + if ($this->permission_type === 'groups') { + $groups = json_decode($this->writable_approval); + foreach ($groups as $groupId) { + $group = \Statusgruppen::find($groupId); + if ($group && $group->isMember($user->id)) { + return $this->isWritable(); + } + } + } + } + + return false; } - public function copy(\User $user, string $rangeId, string $rangeType, array $modified = null): Unit + private function isWritable(): bool { + if ($this->writable === 'always') { + return true; + } + if ($this->writable === 'never') { + return false; + } + + return + (empty($this->writable_start_date) || $this->writable_start_date < strtotime('today')) + && (empty($this->writable_end_date) || $this->writable_end_date >= strtotime('today')); + } + + public function copy( + User $user, + string $rangeId, + string $rangeType, + array $modified = null, + bool $duplicate = false + ): Unit { $sourceUnitElement = $this->structural_element; - $newElement = $sourceUnitElement->copyToRange($user, $rangeId, $rangeType); + $newElement = $sourceUnitElement->copyToRange($user, $rangeId, $rangeType, '', $duplicate); if ($modified !== null) { $newElement->title = $modified['title'] ?? $newElement->title; @@ -108,10 +210,21 @@ class Unit extends \SimpleORMap implements \PrivacyObject, \FeedbackRange 'range_type' => $rangeType, 'structural_element_id' => $newElement->id, 'content_type' => 'courseware', + 'position' => $this->getNewPosition($rangeId), 'creator_id' => $user->id, 'public' => '', - 'release_date' => null, - 'withdraw_date' => null, + 'permission_scope' => $duplicate ? $this->permission_scope : 'unit', + 'permission_type' => $duplicate ? $this->permission_type : 'all', + 'visible' => $duplicate ? $this->visible : 'always', + 'visible_all' => $duplicate ? $this->visible_all : 0, + 'visible_start_date' => $duplicate ? $this->visible_start_date : null, + 'visible_end_date' => $duplicate ? $this->visible_end_date : null, + 'visible_approval' => $duplicate ? $this->visible_approval : '', + 'writable' => $duplicate ? $this->writable : 'never', + 'writable_all' => $duplicate ? $this->writable_all : 0, + 'writable_start_date' => $duplicate ? $this->writable_start_date : null, + 'writable_end_date' => $duplicate ? $this->writable_end_date : null, + 'writable_approval' => $duplicate ? $this->writable_approval : '', ]); $newUnit->store(); @@ -133,7 +246,7 @@ class Unit extends \SimpleORMap implements \PrivacyObject, \FeedbackRange if ($units) { $storage->addTabularData(_('Courseware Lernmaterialien'), 'cw_units', $units); } - + } public static function getNewPosition($range_id): int @@ -148,16 +261,18 @@ class Unit extends \SimpleORMap implements \PrivacyObject, \FeedbackRange } $db = \DBManager::get(); - $stmt = $db->prepare(sprintf( - 'UPDATE + $stmt = $db->prepare( + sprintf( + 'UPDATE %s SET position = position - 1 WHERE range_id = :range_id AND position > :position', - 'cw_units' - )); + 'cw_units' + ) + ); $stmt->bindValue(':range_id', $this->range_id); $stmt->bindValue(':position', $this->position); $stmt->execute(); @@ -173,7 +288,8 @@ class Unit extends \SimpleORMap implements \PrivacyObject, \FeedbackRange position = FIND_IN_SET(id, ?) - 1 WHERE range_id = ?', - 'cw_units'); + 'cw_units' + ); $args = array(join(',', $positions), $range->id); $stmt = $db->prepare($query); $stmt->execute($args); @@ -226,7 +342,7 @@ class Unit extends \SimpleORMap implements \PrivacyObject, \FeedbackRange public function getRangeUrl(): string { if ($this->structural_element->range_type === 'user') { - return 'contents/courseware/'; + return 'contents/courseware/'; } return 'course/courseware/' . '?cid=' . $this->range_id; @@ -234,7 +350,7 @@ class Unit extends \SimpleORMap implements \PrivacyObject, \FeedbackRange public function isRangeAccessible(string $user_id = null): bool { - $user = \User::find($user_id); + $user = \User::find($user_id); if ($user) { return $this->canRead($user); } @@ -249,4 +365,9 @@ class Unit extends \SimpleORMap implements \PrivacyObject, \FeedbackRange [$this->id, self::class] ); } + + public function getRange() + { + return $this->range_type::find($this->range_id); + } } |
