From c7c10f40175d34d9c76fa90d328ff0cdc2c7dfd2 Mon Sep 17 00:00:00 2001 From: Ron Lucke Date: Mon, 4 Nov 2024 11:39:35 +0000 Subject: Courseware: Rechte und Sichtbarkeit funktionieren nach unterschiedlichen Kriterien Closes #3442 Merge request studip/studip!2635 --- db/migrations/6.0.27_add_unit_permissions.php | 93 ++ .../6.0.28_add_structural_element_permissions.php | 100 ++ ....0.29_update_structural_element_permissions.php | 75 ++ .../Routes/Courseware/CoursesUnitsIndex.php | 10 +- .../Routes/Courseware/StructuralElementsCreate.php | 14 +- .../Courseware/StructuralElementsReleasedIndex.php | 2 +- .../Courseware/StructuralElementsSharedIndex.php | 10 +- .../Routes/Courseware/StructuralElementsUpdate.php | 59 +- .../JsonApi/Routes/Courseware/UnitsCopy.php | 3 +- .../JsonApi/Routes/Courseware/UnitsCreate.php | 12 +- .../JsonApi/Routes/Courseware/UnitsUpdate.php | 95 +- lib/classes/JsonApi/Routes/SemestersIndex.php | 23 +- .../Schemas/Courseware/StructuralElement.php | 16 +- lib/classes/JsonApi/Schemas/Courseware/Unit.php | 23 +- lib/models/Courseware/StructuralElement.php | 329 +++--- lib/models/Courseware/Unit.php | 161 ++- resources/assets/stylesheets/scss/courseware.scss | 2 + .../courseware/containers/default-container.scss | 64 -- .../scss/courseware/layouts/permissions.scss | 32 + .../scss/courseware/layouts/radioset.scss | 88 ++ .../stylesheets/scss/courseware/layouts/tile.scss | 37 +- resources/assets/stylesheets/scss/dialog.scss | 2 +- .../courseware/CoursewareContentPermissions.vue | 113 +-- .../courseware/CoursewareContentShared.vue | 10 +- .../containers/CoursewareDefaultContainer.vue | 6 +- .../courseware/layouts/CoursewareTile.vue | 13 +- .../structural-element/CoursewareRootContent.vue | 9 +- .../CoursewareStructuralElement.vue | 1043 ++++++-------------- ...rsewareStructuralElementDialogExportChooser.vue | 4 +- .../CoursewareStructuralElementDialogExportOer.vue | 110 +++ .../CoursewareStructuralElementDialogExportPdf.vue | 6 +- .../CoursewareStructuralElementDialogInfo.vue | 72 ++ ...CoursewareStructuralElementDialogOerSuggest.vue | 72 ++ ...oursewareStructuralElementDialogPermissions.vue | 619 ++++++++++++ ...CoursewareStructuralElementDialogPublicLink.vue | 84 ++ .../CoursewareStructuralElementDialogSettings.vue | 342 +++++++ .../structural-element/CoursewareTree.vue | 7 +- .../structural-element/CoursewareTreeItem.vue | 163 ++- .../courseware/unit/CoursewareShelfDialogCopy.vue | 3 +- .../courseware/unit/CoursewareUnitItem.vue | 338 ++++++- .../CoursewareUnitItemDialogPermissionScope.vue | 44 + .../unit/CoursewareUnitItemDialogPermissions.vue | 585 +++++++++++ .../courseware/unit/CoursewareUnitItems.vue | 4 +- resources/vue/courseware-index-app.js | 2 +- resources/vue/courseware-shelf-app.js | 1 + .../store/courseware/courseware-shelf.module.js | 4 +- .../vue/store/courseware/courseware.module.js | 12 +- 47 files changed, 3700 insertions(+), 1216 deletions(-) create mode 100644 db/migrations/6.0.27_add_unit_permissions.php create mode 100644 db/migrations/6.0.28_add_structural_element_permissions.php create mode 100644 db/migrations/6.0.29_update_structural_element_permissions.php create mode 100644 resources/assets/stylesheets/scss/courseware/layouts/permissions.scss create mode 100644 resources/assets/stylesheets/scss/courseware/layouts/radioset.scss create mode 100644 resources/vue/components/courseware/structural-element/CoursewareStructuralElementDialogExportOer.vue create mode 100644 resources/vue/components/courseware/structural-element/CoursewareStructuralElementDialogInfo.vue create mode 100644 resources/vue/components/courseware/structural-element/CoursewareStructuralElementDialogOerSuggest.vue create mode 100644 resources/vue/components/courseware/structural-element/CoursewareStructuralElementDialogPermissions.vue create mode 100644 resources/vue/components/courseware/structural-element/CoursewareStructuralElementDialogPublicLink.vue create mode 100644 resources/vue/components/courseware/structural-element/CoursewareStructuralElementDialogSettings.vue create mode 100644 resources/vue/components/courseware/unit/CoursewareUnitItemDialogPermissionScope.vue create mode 100644 resources/vue/components/courseware/unit/CoursewareUnitItemDialogPermissions.vue diff --git a/db/migrations/6.0.27_add_unit_permissions.php b/db/migrations/6.0.27_add_unit_permissions.php new file mode 100644 index 0000000..c275a58 --- /dev/null +++ b/db/migrations/6.0.27_add_unit_permissions.php @@ -0,0 +1,93 @@ +exec("ALTER TABLE `cw_units` + ADD `permission_scope` ENUM('unit', 'structural_element') COLLATE latin1_bin NOT NULL DEFAULT 'unit' + AFTER `creator_id` + "); + \DBManager::get()->exec("ALTER TABLE `cw_units` + ADD `permission_type` ENUM('all', 'users', 'groups') COLLATE latin1_bin NOT NULL DEFAULT 'all' + AFTER `permission_scope` + "); + + \DBManager::get()->exec("ALTER TABLE `cw_units` + ADD `visible` ENUM('always', 'never', 'period') COLLATE latin1_bin NOT NULL DEFAULT 'always' + AFTER `permission_type` + "); + + \DBManager::get()->exec("ALTER TABLE `cw_units` + ADD `visible_all` TINYINT NOT NULL DEFAULT 0 + AFTER `visible` + "); + + \DBManager::get()->exec("ALTER TABLE `cw_units` + ADD `writable` ENUM('always', 'never', 'period') COLLATE latin1_bin NOT NULL DEFAULT 'never' + AFTER `withdraw_date` + "); + + \DBManager::get()->exec("ALTER TABLE `cw_units` + ADD `writable_all` TINYINT NOT NULL DEFAULT 0 + AFTER `writable` + "); + + \DBManager::get()->exec("ALTER TABLE `cw_units` + ADD `writable_start_date` INT UNSIGNED DEFAULT NULL + AFTER `writable_all` + "); + + \DBManager::get()->exec("ALTER TABLE `cw_units` + ADD `writable_end_date` INT UNSIGNED DEFAULT NULL + AFTER `writable_start_date` + "); + + \DBManager::get()->exec("ALTER TABLE `cw_units` + ADD `visible_approval` TEXT NOT NULL + AFTER `writable_end_date` + "); + + \DBManager::get()->exec("ALTER TABLE `cw_units` + ADD `writable_approval` TEXT NOT NULL + AFTER `visible_approval` + "); + + \DBManager::get()->exec("ALTER TABLE `cw_units` + CHANGE `release_date` `visible_start_date` INT UNSIGNED DEFAULT NULL + "); + + \DBManager::get()->exec("ALTER TABLE `cw_units` + CHANGE `withdraw_date` `visible_end_date` INT UNSIGNED DEFAULT NULL + "); + + } + + public function down() + { + \DBManager::get()->exec("ALTER TABLE `cw_units` DROP `permission_scope`"); + \DBManager::get()->exec("ALTER TABLE `cw_units` DROP `permission_type`"); + \DBManager::get()->exec("ALTER TABLE `cw_units` DROP `visible`"); + \DBManager::get()->exec("ALTER TABLE `cw_units` DROP `visible_all`"); + \DBManager::get()->exec("ALTER TABLE `cw_units` DROP `writable`"); + \DBManager::get()->exec("ALTER TABLE `cw_units` DROP `writable_all`"); + \DBManager::get()->exec("ALTER TABLE `cw_units` DROP `writable_start_date`"); + \DBManager::get()->exec("ALTER TABLE `cw_units` DROP `writable_end_date`"); + \DBManager::get()->exec("ALTER TABLE `cw_units` DROP `visible_approval`"); + \DBManager::get()->exec("ALTER TABLE `cw_units` DROP `writable_approval`"); + + + \DBManager::get()->exec("ALTER TABLE `cw_units` + CHANGE `visible_start_date` `release_date` INT UNSIGNED DEFAULT NULL + "); + + \DBManager::get()->exec("ALTER TABLE `cw_units` + CHANGE `visible_end_date` `withdraw_date` INT UNSIGNED DEFAULT NULL + "); + } +} \ No newline at end of file diff --git a/db/migrations/6.0.28_add_structural_element_permissions.php b/db/migrations/6.0.28_add_structural_element_permissions.php new file mode 100644 index 0000000..8c5b5dd --- /dev/null +++ b/db/migrations/6.0.28_add_structural_element_permissions.php @@ -0,0 +1,100 @@ +exec("ALTER TABLE `cw_structural_elements` + ADD `permission_type` ENUM('all', 'users', 'groups') COLLATE latin1_bin NOT NULL DEFAULT 'all' + AFTER `commentable` + "); + \DBManager::get()->exec("ALTER TABLE `cw_structural_elements` + ADD `visible` ENUM('always', 'never', 'period') COLLATE latin1_bin NOT NULL DEFAULT 'always' + AFTER `permission_type` + "); + \DBManager::get()->exec("ALTER TABLE `cw_structural_elements` + ADD `visible_all` TINYINT NOT NULL DEFAULT 0 + AFTER `visible` + "); + + \DBManager::get()->exec("ALTER TABLE `cw_structural_elements` + ADD `writable` ENUM('always', 'never', 'period') COLLATE latin1_bin NOT NULL DEFAULT 'never' + AFTER `withdraw_date` + "); + + \DBManager::get()->exec("ALTER TABLE `cw_structural_elements` + ADD `writable_all` TINYINT NOT NULL DEFAULT 0 + AFTER `writable` + "); + + \DBManager::get()->exec("ALTER TABLE `cw_structural_elements` + ADD `writable_start_date` INT UNSIGNED NULL DEFAULT NULL + AFTER `writable_all` + "); + + \DBManager::get()->exec("ALTER TABLE `cw_structural_elements` + ADD `writable_end_date` INT UNSIGNED NULL DEFAULT NULL + AFTER `writable_start_date` + "); + + \DBManager::get()->exec("ALTER TABLE `cw_structural_elements` + ADD `content_approval` TEXT NOT NULL + AFTER `write_approval` + "); + + // change cols + \DBManager::get()->exec("ALTER TABLE `cw_structural_elements` + CHANGE `release_date` `visible_start_date` INT UNSIGNED NULL DEFAULT NULL + "); + + \DBManager::get()->exec("ALTER TABLE `cw_structural_elements` + CHANGE `withdraw_date` `visible_end_date` INT UNSIGNED NULL DEFAULT NULL + "); + + \DBManager::get()->exec("UPDATE `cw_structural_elements` SET `visible_start_date` = NULL WHERE `visible_start_date` = 0 "); + + \DBManager::get()->exec("UPDATE `cw_structural_elements` SET `visible_end_date` = NULL WHERE `visible_end_date` = 0 "); + + \DBManager::get()->exec("ALTER TABLE `cw_structural_elements` + CHANGE `read_approval` `visible_approval` TEXT NOT NULL + "); + + \DBManager::get()->exec("ALTER TABLE `cw_structural_elements` + CHANGE `write_approval` `writable_approval` TEXT NOT NULL + "); + } + + public function down() + { + \DBManager::get()->exec("ALTER TABLE `cw_structural_elements` DROP `visible`"); + \DBManager::get()->exec("ALTER TABLE `cw_structural_elements` DROP `visible_all`"); + \DBManager::get()->exec("ALTER TABLE `cw_structural_elements` DROP `writable`"); + \DBManager::get()->exec("ALTER TABLE `cw_structural_elements` DROP `writable_all`"); + \DBManager::get()->exec("ALTER TABLE `cw_structural_elements` DROP `writable_start_date`"); + \DBManager::get()->exec("ALTER TABLE `cw_structural_elements` DROP `writable_end_date`"); + + \DBManager::get()->exec("ALTER TABLE `cw_structural_elements` + CHANGE `visible_start_date` `release_date` INT UNSIGNED DEFAULT NULL + "); + + \DBManager::get()->exec("ALTER TABLE `cw_structural_elements` + CHANGE `visible_end_date` `withdraw_date` INT UNSIGNED DEFAULT NULL + "); + + \DBManager::get()->exec("ALTER TABLE `cw_structural_elements` + CHANGE `visible_approval` `read_approval` TEXT NOT NULL + "); + + \DBManager::get()->exec("ALTER TABLE `cw_structural_elements` + CHANGE `writable_approval` `write_approval` TEXT NOT NULL + "); + + } + +} \ No newline at end of file diff --git a/db/migrations/6.0.29_update_structural_element_permissions.php b/db/migrations/6.0.29_update_structural_element_permissions.php new file mode 100644 index 0000000..5de8630 --- /dev/null +++ b/db/migrations/6.0.29_update_structural_element_permissions.php @@ -0,0 +1,75 @@ +prepare($query); + $rows = $rows_statement->execute(); + + $query = "UPDATE `cw_structural_elements` + SET + `permission_type` = :permission_type, + `visible` = :visible, + `writable` = :writable + `visible_approval` = :visible_approval, + `writable_approval` = :writable_approval, + WHERE `id` = :id"; + $statement = DBManager::get()->prepare($query); + + foreach ($rows as $row) { + $read_approval = json_decode($row['visible_approval'], true) ?: []; + $write_approval = json_decode($row['writable_approval'], true) ?: []; + $permission_type = $row['permission_type']; + $visible = $row['visible']; + $writable = $row['writable']; + $visible_approval = []; + $writable_approval = []; + if (!$read_approval['all'] && $write_approval['all']) { + $writable = 'always'; + } + + if (count($read_approval['groups']) || count($write_approval['groups'])) { + $permission_type = 'groups'; + $writable = 'always'; + $visible_approval = $read_approval['groups']; + $writable_approval = $write_approval['groups']; + } + if (count($read_approval['users']) || count($write_approval['users'])) { + $permission_type = 'users'; + $writable = 'always'; + $visible_approval = $read_approval['users']; + $writable_approval = $write_approval['users']; + } + + $statement->bindValue(':permission_type', $permission_type); + $statement->bindValue(':visible', $visible); + $statement->bindValue(':writable', $writable); + $statement->bindValue(':visible_approval', json_encode($visible_approval)); + $statement->bindValue(':writable_approval', json_encode($writable_approval)); + $statement->execute(); + } + + $query = "SELECT * FROM `cw_structural_elements` WHERE `visible_start_date` IS NOT NULL OR `visible_end_date` IS NOT NULL"; + $rows_statement = DBManager::get()->prepare($query); + $rows = $rows_statement->execute(); + + $query = "UPDATE `cw_structural_elements` + SET + `visible` = :visible, + WHERE `id` = :id"; + $statement = DBManager::get()->prepare($query); + + foreach ($rows as $row) { + $visible = 'period'; + + $statement->bindValue(':visible', $visible); + $statement->execute(); + } + } +} \ No newline at end of file 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); + } } diff --git a/resources/assets/stylesheets/scss/courseware.scss b/resources/assets/stylesheets/scss/courseware.scss index c555b6d..8c9d87f 100644 --- a/resources/assets/stylesheets/scss/courseware.scss +++ b/resources/assets/stylesheets/scss/courseware.scss @@ -26,7 +26,9 @@ @import './courseware/layouts/companion.scss'; @import './courseware/layouts/import-zip.scss'; @import './courseware/layouts/input-file.scss'; +@import './courseware/layouts/permissions.scss'; @import './courseware/layouts/progress.scss'; +@import './courseware/layouts/radioset.scss'; @import './courseware/layouts/ribbon.scss'; @import './courseware/layouts/tabs.scss'; @import './courseware/layouts/talk-bubble.scss'; diff --git a/resources/assets/stylesheets/scss/courseware/containers/default-container.scss b/resources/assets/stylesheets/scss/courseware/containers/default-container.scss index baed503..948339c 100644 --- a/resources/assets/stylesheets/scss/courseware/containers/default-container.scss +++ b/resources/assets/stylesheets/scss/courseware/containers/default-container.scss @@ -103,67 +103,3 @@ form.cw-container-dialog-edit-form { max-width: 200px; } } - -.cw-radioset { - display: flex; - flex-direction: row; - justify-content: center; - margin-bottom: 1em; - .cw-radioset-box { - width: 128px; - height: 128px; - text-align: center; - margin-right: 16px; - border: solid thin var(--content-color-40); - &.selected { - border-color: var(--base-color); - background-color: var(--content-color-20); - } - &:last-child { - margin-right: 0; - } - label { - height: 100%; - width: 100%; - margin: 0; - cursor: pointer; - .label-icon { - background-position: center 8px; - background-repeat: no-repeat; - height: 64px; - padding: 8px; - &.accordion { - @include background-icon(block-accordion, clickable, 64); - } - &.list { - @include background-icon(view-list, clickable, 64); - } - &.tabs { - @include background-icon(block-tabs, clickable, 64); - } - &.full { - @include background-icon(column-full, clickable, 64); - } - &.half { - @include background-icon(column-half, clickable, 64); - } - &.half-center { - @include background-icon(column-half-centered, clickable, 64); - } - } - - } - input[type=radio] { - position: absolute; - opacity: 0; - width: 0; - - &:focus + label { - outline-color: Highlight; - outline-color: -webkit-focus-ring-color; - outline-style: auto; - outline-width: 1px; - } - } - } -} diff --git a/resources/assets/stylesheets/scss/courseware/layouts/permissions.scss b/resources/assets/stylesheets/scss/courseware/layouts/permissions.scss new file mode 100644 index 0000000..fa84a1f --- /dev/null +++ b/resources/assets/stylesheets/scss/courseware/layouts/permissions.scss @@ -0,0 +1,32 @@ +.cw-permissions-form-wrapper { + display: flex; + max-width: 840px; + flex-direction: row; + gap: 1em; + + .cw-form-selects { + flex-grow: 1; + + .cw-form-selects-row { + display: flex; + flex-direction: row; + justify-content: space-between; + gap: 1em; + + label { + flex-grow: 1; + text-indent: 0; + + select { + min-width: 9em; + } + } + } + } + + .permission-table { + img { + padding: 0 4px; + } + } +} \ No newline at end of file diff --git a/resources/assets/stylesheets/scss/courseware/layouts/radioset.scss b/resources/assets/stylesheets/scss/courseware/layouts/radioset.scss new file mode 100644 index 0000000..a65a189 --- /dev/null +++ b/resources/assets/stylesheets/scss/courseware/layouts/radioset.scss @@ -0,0 +1,88 @@ +$radio-icons: ( + accordion: block-accordion, + list: view-list, + tabs: block-tabs, + full: column-full, + half: column-half, + half-center: column-half-centered, + all: group3, + users: person, + groups: group2 +); + +.cw-radioset { + display: flex; + flex-direction: row; + justify-content: center; + margin-bottom: 1em; + + .cw-radioset-box { + width: 128px; + height: 128px; + text-align: center; + margin-right: 8px; + border: solid thin var(--content-color-40); + label { + height: 100%; + width: 100%; + margin: 0; + cursor: pointer; + @include background-icon(radiobutton-unchecked, clickable); + background-position: center 104px; + background-repeat: no-repeat; + + .label-text { + height: 48px; + max-width: 80%; + overflow: hidden; + display: grid; + align-items: center; + margin: 0 auto; + } + + .label-icon { + background-position: center 8px; + background-repeat: no-repeat; + height: 48px; + padding: 4px; + + @each $type, $icon in $radio-icons { + &.#{$type} { + @include background-icon(#{$icon}, clickable, 48); + } + } + } + + } + input[type=radio] { + position: absolute; + opacity: 0; + width: 0; + + &:focus + label { + outline-color: Highlight; + outline-color: -webkit-focus-ring-color; + outline-style: auto; + outline-width: 1px; + } + } + + &.selected { + border-color: var(--base-color); + background-color: var(--content-color-20); + label { + @include background-icon(check-circle, clickable); + .label-icon { + @each $type, $icon in $radio-icons { + &.#{$type} { + @include background-icon(#{$icon}, info, 48); + } + } + } + } + } + &:last-child { + margin-right: 0; + } + } +} \ No newline at end of file diff --git a/resources/assets/stylesheets/scss/courseware/layouts/tile.scss b/resources/assets/stylesheets/scss/courseware/layouts/tile.scss index a563244..7b56857 100644 --- a/resources/assets/stylesheets/scss/courseware/layouts/tile.scss +++ b/resources/assets/stylesheets/scss/courseware/layouts/tile.scss @@ -55,6 +55,19 @@ text-align: right; } + .overlay-text-bottom { + padding: 6px 7px; + margin: 146px -33px 4px 4px; + background-color: rgba(255, 255, 255, 0.8); + width: fit-content; + max-width: 100%; + height: 1.25em; + overflow: hidden; + text-overflow: ellipsis; + float: right; + text-align: right; + } + .overlay-action-menu { padding: 0; margin: 0.25em; @@ -89,12 +102,8 @@ background-repeat: no-repeat; background-position: 0 0; - @each $type, $icon in $element-icons { - &.description-icon-#{$type} { - width: 212px; - padding-left: 28px; - @include background-icon(#{$icon}, info_alt, 22); - } + .title-icon { + vertical-align: bottom; } } @@ -126,10 +135,10 @@ .description-text-wrapper { overflow: hidden; - height: 10em; + height: 7em; margin-top: 4px; display: -webkit-box; - -webkit-line-clamp: 7; + -webkit-line-clamp: 5; -webkit-box-orient: vertical; p { text-align: left; @@ -137,16 +146,16 @@ } footer { - width: 242px; - margin-top: 8px; + display: table-cell; + height: 5em; color: var(--white); - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - display: flex; - align-items: center; - justify-content: space-between; + vertical-align: bottom; + p { + margin: 0; + } img { vertical-align: text-bottom; } diff --git a/resources/assets/stylesheets/scss/dialog.scss b/resources/assets/stylesheets/scss/dialog.scss index f45dc9b..bd8ad03 100644 --- a/resources/assets/stylesheets/scss/dialog.scss +++ b/resources/assets/stylesheets/scss/dialog.scss @@ -320,7 +320,7 @@ v u e d i a l o g display: flex; justify-content: center; align-items: center; - z-index: 3001; + z-index: 1001; } .studip-dialog-body { position: absolute; diff --git a/resources/vue/components/courseware/CoursewareContentPermissions.vue b/resources/vue/components/courseware/CoursewareContentPermissions.vue index 60f0964..841fc8f 100644 --- a/resources/vue/components/courseware/CoursewareContentPermissions.vue +++ b/resources/vue/components/courseware/CoursewareContentPermissions.vue @@ -8,7 +8,7 @@ @@ -19,17 +19,17 @@ - - - - - + + + + + @@ -69,7 +69,7 @@ :min="minDate" :id="`${user_perm.id}_expiry`" v-model="userPermsList[index]['expiry']" - @change="refreshReadWriteApproval" + @change="refreshContentApproval" /> @@ -149,10 +149,7 @@ export default { showAddMultiPersonDialog: null, userPermsList: [], selectedUsers:[], - userPermsReadAll: false, - userPermsWriteAll: false, - userPermsReadUsers: [], - userPermsWriteUsers: [], + contentApprovalUsers: [], message: false, showDeleteDialog: false, deleteUserPermIndex: -1 @@ -160,16 +157,6 @@ export default { }, mounted() { - if (this.element.attributes['read-approval'].all !== undefined) { - this.userPermsReadAll = this.element.attributes['read-approval'].all; - } else { - this.userPermsReadAll = false; - } - if (this.element.attributes['write-approval'].all !== undefined) { - this.userPermsWriteAll = this.element.attributes['write-approval'].all; - } else { - this.userPermsWriteAll = false; - } this.initUserPermsList(); }, @@ -182,19 +169,9 @@ export default { return this.userPermsList.length === 0; }, - readApproval() { - return { - all: this.userPermsReadAll, - users: this.userPermsReadUsers, - groups: [] - }; - }, - - writeApproval() { + contentApproval() { return { - all: this.userPermsWriteAll, - users: this.userPermsWriteUsers, - groups: [] + users: this.contentApprovalUsers, }; }, @@ -231,22 +208,17 @@ export default { async initUserPermsList() { - if (this.element.attributes['read-approval'].users !== undefined) { - this.userPermsReadUsers = this.element.attributes['read-approval'].users; - } - - if (this.element.attributes['write-approval'].users !== undefined) { - this.userPermsWriteUsers = this.element.attributes['write-approval'].users; + if (this.element.attributes['content-approval'].users !== undefined) { + this.contentApprovalUsers = this.element.attributes['content-approval'].users; } /* eslint-disable no-await-in-loop */ - for (const user_perm_obj of this.userPermsReadUsers) { + for (const user_perm_obj of this.contentApprovalUsers) { let userObj = await this.getUser(user_perm_obj.id); - let writePerm = this.userPermsWriteUsers.some(user_write_perm => user_write_perm.id === user_perm_obj.id) ? true : false; this.userPermsList.push({ 'id' : user_perm_obj.id, 'read': user_perm_obj.read, - 'write': writePerm, + 'write': user_perm_obj.write, 'expiry': user_perm_obj.expiry ? new Date(user_perm_obj.expiry).toISOString().split('T')[0] : '', 'formatted-name': userObj.attributes['formatted-name'], 'username': userObj.attributes['username'], @@ -272,7 +244,7 @@ export default { 'username': selected_user.username, }; this.userPermsList.push(newUserPerm); - this.refreshReadWriteApproval(); + this.refreshContentApproval(); } else { duplicatedUsers.push(selected_user); } @@ -305,7 +277,7 @@ export default { performDeleteUserPerm() { if (this.deleteUserPermIndex !== -1) { this.userPermsList.splice(this.deleteUserPermIndex, 1); - this.refreshReadWriteApproval(); + this.refreshContentApproval(); } this.clearDeleteUserPerm(); }, @@ -328,7 +300,7 @@ export default { this.userPermsList[index]['read'] = read; this.userPermsList[index]['write'] = write; - this.refreshReadWriteApproval(); + this.refreshContentApproval(); }, setAllPerms(permtype) { @@ -344,54 +316,21 @@ export default { return true; }); - this.refreshReadWriteApproval(); + this.refreshContentApproval(); }, - refreshReadWriteApproval() { - this.refreshReadApproval(); - this.refreshWriteApproval(); - }, - - refreshReadApproval() { - this.userPermsReadUsers = []; + refreshContentApproval() { + this.contentApprovalUsers = []; for (const user_perm_obj of this.userPermsList) { let readRight = user_perm_obj.write ? true : user_perm_obj.read; - this.userPermsReadUsers.push({ + this.contentApprovalUsers.push({ 'id': user_perm_obj.id, 'read': readRight, 'write': user_perm_obj.write, 'expiry': user_perm_obj.expiry ? new Date(user_perm_obj.expiry).toISOString() : '' }); } - this.$emit('updateReadApproval', this.readApproval); - }, - - refreshWriteApproval() { - this.userPermsWriteUsers = []; - for (const user_perm_obj of this.userPermsList) { - if (user_perm_obj.write) { - this.userPermsWriteUsers.push({ - 'id': user_perm_obj.id, - 'expiry': user_perm_obj.expiry ? new Date(user_perm_obj.expiry).toISOString() : '' - }); - } - } - this.$emit('updateWriteApproval', this.writeApproval); - } - }, - - watch: { - userPermsReadAll(newVal, oldVal) { - this.$emit('updateReadApproval', this.readApproval); - if (newVal === true) { - this.userPermsWriteAll = false; - } - }, - userPermsWriteAll(newVal, oldVal) { - this.$emit('updateWriteApproval', this.writeApproval); - if (newVal === true) { - this.userPermsReadAll = false; - } + this.$emit('updateContentApproval', this.contentApproval); }, }, }; diff --git a/resources/vue/components/courseware/CoursewareContentShared.vue b/resources/vue/components/courseware/CoursewareContentShared.vue index 8eb9bf6..0bdc4b5 100644 --- a/resources/vue/components/courseware/CoursewareContentShared.vue +++ b/resources/vue/components/courseware/CoursewareContentShared.vue @@ -65,8 +65,7 @@ @@ -123,11 +122,8 @@ export default { getElementUrl(element) { return STUDIP.URLHelper.base_url + 'dispatch.php/contents/courseware/courseware#/structural_element/' + element.id; }, - updateReadApproval(approval) { - this.selectedElement.attributes['read-approval'] = approval; - }, - updateWriteApproval(approval) { - this.selectedElement.attributes['write-approval'] = approval; + updateContentApproval(approval) { + this.selectedElement.attributes['content-approval'] = approval; }, displayEditReleases(element) { this.selectedElement = element; diff --git a/resources/vue/components/courseware/containers/CoursewareDefaultContainer.vue b/resources/vue/components/courseware/containers/CoursewareDefaultContainer.vue index 427a930..7f4a882 100644 --- a/resources/vue/components/courseware/containers/CoursewareDefaultContainer.vue +++ b/resources/vue/components/courseware/containers/CoursewareDefaultContainer.vue @@ -65,7 +65,7 @@ @close="closeChange" @confirm="storeChange" height="520" - width="480" + width="440" >
-

{{ structuralElement.attributes.title }}

+

{{ structuralElement.attributes?.title ?? '-' }}

- {{ structuralElement.attributes.payload.description }} + {{ structuralElement.attributes?.payload?.description ?? '-' }}

@@ -134,6 +134,7 @@ export default { const color = this.mixinColors.find((c) => { return c.class === elementColor; }); + return color.hex; }, bgColor() { diff --git a/resources/vue/components/courseware/structural-element/CoursewareStructuralElement.vue b/resources/vue/components/courseware/structural-element/CoursewareStructuralElement.vue index d423eac..2b7cdbc 100644 --- a/resources/vue/components/courseware/structural-element/CoursewareStructuralElement.vue +++ b/resources/vue/components/courseware/structural-element/CoursewareStructuralElement.vue @@ -7,16 +7,28 @@ class="cw-structural-element" >
- +
- Personen + {{ $gettext('Personen') }}
NameLeserechteLese- und SchreibrechteAblaufdatumAktion{{ $gettext('Name') }}{{ $gettext('Leserechte') }}{{ $gettext('Lese- und Schreibrechte') }}{{ $gettext('Ablaufdatum') }}{{ $gettext('Aktion') }}
- Es wurden noch keine Freigaben erteilt + {{ $gettext('Es wurden noch keine Freigaben erteilt') }}
@@ -87,7 +87,7 @@