aboutsummaryrefslogtreecommitdiff
path: root/lib/models/Courseware/StructuralElement.php
diff options
context:
space:
mode:
authorRon Lucke <lucke@elan-ev.de>2024-11-04 11:39:35 +0000
committerRon Lucke <lucke@elan-ev.de>2024-11-04 11:39:35 +0000
commitc7c10f40175d34d9c76fa90d328ff0cdc2c7dfd2 (patch)
treeb444b0206bd43cb87612f90f0f646eccff301348 /lib/models/Courseware/StructuralElement.php
parente8ce2c6e2bb858f9af664dbc5ac37edb958b8850 (diff)
Courseware: Rechte und Sichtbarkeit funktionieren nach unterschiedlichen Kriterien
Closes #3442 Merge request studip/studip!2635
Diffstat (limited to 'lib/models/Courseware/StructuralElement.php')
-rw-r--r--lib/models/Courseware/StructuralElement.php329
1 files changed, 181 insertions, 148 deletions
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
]);