diff --git a/Modules/IndividualAssessment/classes/Members/class.ilIndividualAssessmentMembersStorageDB.php b/Modules/IndividualAssessment/classes/Members/class.ilIndividualAssessmentMembersStorageDB.php
index f668fcf96255..4298415b57ed 100644
--- a/Modules/IndividualAssessment/classes/Members/class.ilIndividualAssessmentMembersStorageDB.php
+++ b/Modules/IndividualAssessment/classes/Members/class.ilIndividualAssessmentMembersStorageDB.php
@@ -31,7 +31,8 @@ class ilIndividualAssessmentMembersStorageDB implements ilIndividualAssessmentMe
public function __construct(
protected ilDBInterface $db,
protected IRSS $irss,
- protected ilIndividualAssessmentGradingStakeholder $stakeholder
+ protected ilIndividualAssessmentGradingStakeholder $stakeholder,
+ protected SpecifiedFormStorage $specified_form_storage
) {
}
@@ -71,7 +72,7 @@ public function loadMembersAsSingleObjects(
}
$res = $this->db->query($sql);
while ($rec = $this->db->fetchAssoc($res)) {
- $usr = new ilObjUser((int)$rec["usr_id"]);
+ $usr = new ilObjUser((int) $rec["usr_id"]);
$members[] = $this->createAssessmentMember($obj, $usr, $rec);
}
return $members;
@@ -114,10 +115,14 @@ protected function createAssessmentMember(
if (!is_null($examiner_id)) {
$examiner_id = (int) $examiner_id;
}
+
+ $custom_fields = $this->specified_form_storage->getSpecifiedFormFields($obj->getId(), $usr->getId());
+
return new ilIndividualAssessmentMember(
$obj,
$usr,
- $this->createGrading($record, $usr->getFullname()),
+ $this->createGrading($record, $usr->getFullname())
+ ->withCustomFields($custom_fields),
(int) $record[ilIndividualAssessmentMembers::FIELD_NOTIFICATION_TS],
$examiner_id,
$changer_id,
@@ -133,6 +138,7 @@ protected function createGrading(array $record, string $user_fullname): ilIndivi
$event_time = new DateTimeImmutable();
$event_time = $event_time->setTimestamp((int) $event_time_db);
}
+
return new ilIndividualAssessmentUserGrading(
$user_fullname,
(string) $record[ilIndividualAssessmentMembers::FIELD_RECORD],
@@ -179,6 +185,7 @@ public function updateMember(ilIndividualAssessmentMember $member): void
];
$this->db->update(self::MEMBERS_TABLE, $values, $where);
+ $this->specified_form_storage->storeSpecifiedUserValues(...$member->getGrading()->getCustomFields());
}
protected function getActualDateTime(): string
@@ -191,10 +198,12 @@ protected function getActualDateTime(): string
*/
public function deleteMembers(ilObjIndividualAssessment $obj): void
{
- foreach($this->loadMembers($obj) as $member) {
- if($identifier = $member[ilIndividualAssessmentMembers::FIELD_FILE_NAME]) {
+ foreach ($this->loadMembers($obj) as $member) {
+ if ($identifier = $member[ilIndividualAssessmentMembers::FIELD_FILE_NAME]) {
$resource_id = $this->irss->manage()->find($identifier);
- $this->irss->manage()->remove($resource_id, $this->stakeholder);
+ if ($resource_id) {
+ $this->irss->manage()->remove($resource_id, $this->stakeholder);
+ }
}
}
@@ -202,6 +211,17 @@ public function deleteMembers(ilObjIndividualAssessment $obj): void
$this->db->manipulate($sql);
}
+ public function deleteCustomFieldsForObj(ilObjIndividualAssessment $obj): void
+ {
+ $this->specified_form_storage->deleteAllUserValuesAndFields(
+ $this->irss,
+ $this->stakeholder,
+ $obj->getId()
+ );
+ }
+
+
+
protected function loadMemberQuery(): string
{
return "SELECT "
@@ -352,8 +372,7 @@ public function insertMembersRecord(ilObjIndividualAssessment $iass, array $reco
*/
public function removeMembersRecord(ilObjIndividualAssessment $iass, array $record): void
{
-
- if(array_key_exists(ilIndividualAssessmentMembers::FIELD_FILE_NAME, $record)
+ if (array_key_exists(ilIndividualAssessmentMembers::FIELD_FILE_NAME, $record)
&& $identifier = $record[ilIndividualAssessmentMembers::FIELD_FILE_NAME]) {
$resource_id = $this->irss->manage()->find($identifier);
$this->irss->manage()->remove($resource_id, $this->stakeholder);
@@ -366,6 +385,12 @@ public function removeMembersRecord(ilObjIndividualAssessment $iass, array $reco
;
$this->db->manipulate($sql);
+ $this->specified_form_storage->deleteSpecifiedUserValues(
+ $this->irss,
+ $this->stakeholder,
+ $iass->getId(),
+ $record[ilIndividualAssessmentMembers::FIELD_USR_ID]
+ );
}
/**
diff --git a/Modules/IndividualAssessment/classes/class.ilIndividualAssessmentDIC.php b/Modules/IndividualAssessment/classes/class.ilIndividualAssessmentDIC.php
index 54f05a00d9c8..6f896cb531f4 100644
--- a/Modules/IndividualAssessment/classes/class.ilIndividualAssessmentDIC.php
+++ b/Modules/IndividualAssessment/classes/class.ilIndividualAssessmentDIC.php
@@ -102,10 +102,19 @@ public function getObjectDIC(
$dic->http()->wrapper()->query(),
$c['helper.dateformat'],
$dic['resource_storage'],
- $stakeholder = $c['irss.stakeholder']
+ $stakeholder = $c['irss.stakeholder'],
+ $c['iafp.fieldbuilder']
);
};
+ $container['iafp.fieldbuilder'] = static fn(): ILIAS\IndividualAssessmentFormPool\FieldBuilder =>
+ new ILIAS\IndividualAssessmentFormPool\FieldBuilder(
+ $dic['ui.factory']->input()->field(),
+ $dic['refinery'],
+ $dic['lng'],
+ new \ilUIDemoFileUploadHandlerGUI(),
+ new \ilUIMarkdownPreviewGUI()
+ );
$container['ilIndividualAssessmentCommonSettingsGUI'] = function ($c) use ($object, $dic) {
return new ilIndividualAssessmentCommonSettingsGUI(
$object,
@@ -126,8 +135,13 @@ public function getObjectDIC(
new ilIndividualAssessmentMembersStorageDB(
$dic['ilDB'],
$dic['resource_storage'],
- $stakeholder = $c['irss.stakeholder']
+ $stakeholder = $c['irss.stakeholder'],
+ $c['iass.member.custom_storage'],
);
+
+ $container['iass.member.custom_storage'] = static fn($c): SpecifiedFormStorage =>
+ new SpecifiedFormStorageDB($dic['ilDB']);
+
$container['iass.accesshandler'] = static fn($c): ilIndividualAssessmentAccessHandler =>
new ilIndividualAssessmentAccessHandler(
$object,
diff --git a/Modules/IndividualAssessment/classes/class.ilIndividualAssessmentMemberGUI.php b/Modules/IndividualAssessment/classes/class.ilIndividualAssessmentMemberGUI.php
index b43cfd895d94..d0d1c7d6202f 100644
--- a/Modules/IndividualAssessment/classes/class.ilIndividualAssessmentMemberGUI.php
+++ b/Modules/IndividualAssessment/classes/class.ilIndividualAssessmentMemberGUI.php
@@ -33,6 +33,7 @@
use ILIAS\Data;
use ILIAS\Refinery;
use ILIAS\ResourceStorage\Services as IRSS;
+use ILIAS\IndividualAssessmentFormPool\FieldBuilder;
class ilIndividualAssessmentMemberGUI extends AbstractCtrlAwareUploadHandler
{
@@ -69,6 +70,7 @@ public function __construct(
protected ilIndividualAssessmentDateFormatter $date_formatter,
protected IRSS $irss,
protected ilIndividualAssessmentGradingStakeholder $stakeholder,
+ protected FieldBuilder $field_builder,
) {
parent::__construct();
}
@@ -177,7 +179,7 @@ protected function downloadFile(): void
{
$identifier = $this->getMember()->getGrading()->getFile();
$resource_id = $this->irss->manage()->find($identifier);
- if($resource_id) {
+ if ($resource_id) {
$this->irss->consume()->download($resource_id)->run();
}
}
@@ -220,6 +222,7 @@ protected function buildForm(
$this->refinery_factory,
$this,
$this->user->getDateFormat(),
+ $this->field_builder,
$this->getPossibleLPStates(),
$may_be_edited,
$this->getObject()->getSettings()->isEventTimePlaceRequired(),
@@ -353,7 +356,7 @@ protected function getUploadResult(): HandlerResult
protected function getRemoveResult(string $identifier): HandlerResult
{
$resource_id = $this->irss->manage()->find($identifier);
- if($resource_id) {
+ if ($resource_id) {
$this->irss->manage()->remove($resource_id, $this->stakeholder);
$status = HandlerResult::STATUS_OK;
$message = $this->lng->txt('iass_file_deleted');
@@ -368,7 +371,7 @@ protected function getRemoveResult(string $identifier): HandlerResult
public function getInfoResult(string $identifier): ?FileInfoResult
{
$resource_id = $this->irss->manage()->find($identifier);
- if(! $resource_id) {
+ if (! $resource_id) {
return null;
}
$resource = $this->irss->manage()->getResource($resource_id);
@@ -388,7 +391,7 @@ public function getInfoForExistingFiles(array $file_ids): array
$file_ids = array_filter($file_ids, fn($id) => $id !== "");
return array_map(function ($id) {
$resource_id = $this->irss->manage()->find($identifier);
- if(! $resource_id) {
+ if (! $resource_id) {
return null;
}
$resource = $this->irss->manage()->getResource($resource_id);
diff --git a/Modules/IndividualAssessment/classes/class.ilObjIndividualAssessment.php b/Modules/IndividualAssessment/classes/class.ilObjIndividualAssessment.php
index 621898c5cd03..0e945aa42c2f 100755
--- a/Modules/IndividualAssessment/classes/class.ilObjIndividualAssessment.php
+++ b/Modules/IndividualAssessment/classes/class.ilObjIndividualAssessment.php
@@ -169,6 +169,7 @@ public function delete(): bool
$this->deleteMetaData();
$this->settings_storage->deleteSettings($this);
$this->members_storage->deleteMembers($this);
+ $this->members_storage->deleteCustomFieldsForObj($this);
return parent::delete();
}
@@ -245,6 +246,7 @@ public function cloneObject(int $target_id, int $copy_id = 0, bool $omit_tree =
$new_obj->settings_storage->updateSettings($new_settings);
$new_obj->settings_storage->updateInfoSettings($new_info_settings);
+ (new IAFPCollector())->cloneFields($this->getId(), $new_obj->getId());
return $new_obj;
}
diff --git a/Modules/IndividualAssessment/classes/class.ilObjIndividualAssessmentGUI.php b/Modules/IndividualAssessment/classes/class.ilObjIndividualAssessmentGUI.php
index ff3030a6012e..868ecd4a3aef 100644
--- a/Modules/IndividualAssessment/classes/class.ilObjIndividualAssessmentGUI.php
+++ b/Modules/IndividualAssessment/classes/class.ilObjIndividualAssessmentGUI.php
@@ -157,7 +157,7 @@ public function executeCommand(): void
$cmd = 'members';
}
}
- if($cmd === 'edit' && $this->object->accessHandler()->simulateMember()) {
+ if ($cmd === 'edit' && $this->object->accessHandler()->simulateMember()) {
$cmd = 'view';
}
@@ -237,7 +237,7 @@ protected function downloadFileObject(): void
) {
$identifier = $member->getGrading()->getFile();
$resource_id = $this->irss->manage()->find($identifier);
- if($resource_id) {
+ if ($resource_id) {
$this->irss->consume()->download($resource_id)->run();
}
}
@@ -421,8 +421,20 @@ protected function getEntryForStatus(int $status): string
}
}
+ protected function addDidacticTemplateOptions(array &$a_options): void
+ {
+ $collector = \ilObjIndividualAssessmentFormPool::getRepository();
+ $a_options = $collector->getFormsSelection();
+ }
+
protected function afterSave(ilObject $new_object): void
{
+ if ($form_id = $this->getDidacticTemplateVar("iass")) {
+ $collector = \ilObjIndividualAssessmentFormPool::getRepository();
+ $collector->copyFieldsToIASS($form_id, $new_object->getId());
+ }
+
+
$this->tpl->setOnScreenMessage("success", $this->txt("iass_added"), true);
$this->ctrl->setParameter($this, "ref_id", $new_object->getRefId());
$this->ctrl->redirectToUrl($this->ctrl->getLinkTargetByClass(
@@ -449,4 +461,5 @@ protected function txt(string $code): string
{
return $this->lng->txt($code);
}
+
}
diff --git a/Modules/IndividualAssessment/classes/ilIndividualAssessmentUserGrading.php b/Modules/IndividualAssessment/classes/ilIndividualAssessmentUserGrading.php
index 671341e3b117..257cb1557eb2 100644
--- a/Modules/IndividualAssessment/classes/ilIndividualAssessmentUserGrading.php
+++ b/Modules/IndividualAssessment/classes/ilIndividualAssessmentUserGrading.php
@@ -22,6 +22,7 @@
use ILIAS\Refinery\Factory as Refinery;
use ILIAS\Data\Factory as DataFactory;
use ILIAS\FileUpload\Handler\AbstractCtrlAwareUploadHandler;
+use ILIAS\IndividualAssessmentFormPool\FieldBuilder;
class ilIndividualAssessmentUserGrading
{
@@ -35,6 +36,7 @@ class ilIndividualAssessmentUserGrading
protected ?DateTimeImmutable $event_time;
protected bool $notify;
protected bool $finalized;
+ protected array $custom_fields = [];
public function __construct(
string $name,
@@ -129,6 +131,18 @@ public function withFile(?string $file): ilIndividualAssessmentUserGrading
return $clone;
}
+ public function getCustomFields(): array
+ {
+ return $this->custom_fields;
+ }
+
+ public function withCustomFields(array $custom_fields): self
+ {
+ $clone = clone $this;
+ $clone->custom_fields = $custom_fields;
+ return $clone;
+ }
+
public function toFormInput(
Field\Factory $input,
DataFactory $data_factory,
@@ -136,12 +150,14 @@ public function toFormInput(
Refinery $refinery,
AbstractCtrlAwareUploadHandler $file_handler,
\ILIAS\Data\DateFormat\DateFormat $date_format,
+ FieldBuilder $field_builder,
array $grading_options,
bool $may_be_edited = true,
bool $place_required = false,
bool $file_required = false,
bool $amend = false
): \ILIAS\UI\Component\Input\Container\Form\FormInput {
+
$name = $input
->text($lng->txt('name'), '')
->withDisabled(true)
@@ -206,15 +222,23 @@ public function toFormInput(
->withDisabled(!$may_be_edited)
;
+
+ $custom = [];
+ $custom_fields = $this->custom_fields;
+ foreach ($custom_fields as $cf) {
+ $custom[$cf->getFieldId()] = $cf->toFormInput($input, $refinery, $file_handler, $field_builder);
+ }
+
$fields = [
'name' => $name,
'record' => $record,
'internal_note' => $internal_note,
'file' => $file,
'file_visible' => $file_visible,
- 'learning_progress' => $learning_progress,
'place' => $place,
'event_time' => $event_time,
+ 'custom' => $input->group($custom),
+ 'learning_progress' => $learning_progress,
'notify' => $notify
];
@@ -232,7 +256,7 @@ public function toFormInput(
$fields,
$lng->txt('iass_edit_record')
)->withAdditionalTransformation(
- $refinery->custom()->transformation(function ($values) use ($amend) {
+ $refinery->custom()->transformation(function ($values) use ($amend, $custom_fields) {
$finalized = $this->isFinalized();
if (!$amend) {
$finalized = $values['finalized'];
@@ -246,7 +270,12 @@ public function toFormInput(
$file = $values['file'][0];
}
- return new ilIndividualAssessmentUserGrading(
+ $updated_custom = [];
+ foreach ($custom_fields as $cf) {
+ $updated_custom[] = $cf->withValue($values['custom'][$cf->getFieldId()]);
+ }
+
+ return (new ilIndividualAssessmentUserGrading(
$values['name'],
$values['record'],
$values['internal_note'],
@@ -257,7 +286,8 @@ public function toFormInput(
$values['event_time'],
$values['notify'],
$finalized
- );
+ ))
+ ->withCustomFields($updated_custom);
})
);
}
diff --git a/Modules/IndividualAssessment/test/Members/ilIndividualAssessmentMembersStorageDBTest.php b/Modules/IndividualAssessment/test/Members/ilIndividualAssessmentMembersStorageDBTest.php
index 607af7cd33eb..8c69ced614a6 100644
--- a/Modules/IndividualAssessment/test/Members/ilIndividualAssessmentMembersStorageDBTest.php
+++ b/Modules/IndividualAssessment/test/Members/ilIndividualAssessmentMembersStorageDBTest.php
@@ -61,7 +61,8 @@ public function getWrapperObj(ilDBInterface $db): ilIndividualAssessmentMembersS
return new ilIndividualAssessmentMembersStorageDBWrapper(
$db,
$irss,
- $stakeholder
+ $stakeholder,
+ $this->createMock(SpecifiedFormStorage::class)
);
}
@@ -72,7 +73,8 @@ public function testCreateObject(): void
$obj = new ilIndividualAssessmentMembersStorageDB(
$db,
$irss,
- new ilIndividualAssessmentGradingStakeholder()
+ new ilIndividualAssessmentGradingStakeholder(),
+ $this->createMock(SpecifiedFormStorage::class)
);
$this->assertInstanceOf(ilIndividualAssessmentMembersStorageDB::class, $obj);
}
@@ -129,7 +131,8 @@ public function test_loadMembers(): void
$obj = new ilIndividualAssessmentMembersStorageDB(
$db,
$this->createMock(IRSS::class),
- new ilIndividualAssessmentGradingStakeholder()
+ new ilIndividualAssessmentGradingStakeholder(),
+ $this->createMock(SpecifiedFormStorage::class)
);
$result = $obj->loadMembers($iass);
@@ -193,7 +196,12 @@ public function test_loadMembersAsSingleObjects(): void
$irss = $this->createMock(IRSS::class);
$stakeholder = new ilIndividualAssessmentGradingStakeholder();
- $obj = new ilIndividualAssessmentMembersStorageDB($db, $irss, $stakeholder);
+ $obj = new ilIndividualAssessmentMembersStorageDB(
+ $db,
+ $irss,
+ $stakeholder,
+ $this->createMock(SpecifiedFormStorage::class)
+ );
$result = $obj->loadMembersAsSingleObjects($iass);
$this->assertIsArray($result);
@@ -265,7 +273,12 @@ public function test_loadMember_exception(): void
$irss = $this->createMock(IRSS::class);
$stakeholder = new ilIndividualAssessmentGradingStakeholder();
- $obj = new ilIndividualAssessmentMembersStorageDB($db, $irss, $stakeholder);
+ $obj = new ilIndividualAssessmentMembersStorageDB(
+ $db,
+ $irss,
+ $stakeholder,
+ $this->createMock(SpecifiedFormStorage::class)
+ );
$this->expectException(ilIndividualAssessmentException::class);
$this->expectExceptionMessage("invalid usr-obj combination");
@@ -337,7 +350,12 @@ public function test_loadMember(): void
$irss = $this->createMock(IRSS::class);
$stakeholder = new ilIndividualAssessmentGradingStakeholder();
- $obj = new ilIndividualAssessmentMembersStorageDB($db, $irss, $stakeholder);
+ $obj = new ilIndividualAssessmentMembersStorageDB(
+ $db,
+ $irss,
+ $stakeholder,
+ $this->createMock(SpecifiedFormStorage::class)
+ );
$this->expectException(ilIndividualAssessmentException::class);
$this->expectExceptionMessage("invalid usr-obj combination");
@@ -529,7 +547,12 @@ public function test_deleteMembers(): void
$irss = $this->createMock(IRSS::class);
$stakeholder = new ilIndividualAssessmentGradingStakeholder();
- $obj = new ilIndividualAssessmentMembersStorageDB($db, $irss, $stakeholder);
+ $obj = new ilIndividualAssessmentMembersStorageDB(
+ $db,
+ $irss,
+ $stakeholder,
+ $this->createMock(SpecifiedFormStorage::class)
+ );
$obj->deleteMembers($iass);
}
@@ -595,7 +618,7 @@ public function test_removeMembersRecord(): void
{
$iass = $this->createMock(ilObjIndividualAssessment::class);
$iass
- ->expects($this->once())
+ ->expects($this->exactly(2))
->method("getId")
->willReturn(11)
;
@@ -623,7 +646,12 @@ public function test_removeMembersRecord(): void
$irss = $this->createMock(IRSS::class);
$stakeholder = new ilIndividualAssessmentGradingStakeholder();
- $obj = new ilIndividualAssessmentMembersStorageDB($db, $irss, $stakeholder);
+ $obj = new ilIndividualAssessmentMembersStorageDB(
+ $db,
+ $irss,
+ $stakeholder,
+ $this->createMock(SpecifiedFormStorage::class)
+ );
$obj->removeMembersRecord($iass, $record);
}
diff --git a/Modules/IndividualAssessment/test/ilIndividualAssessmentUserGradingTest.php b/Modules/IndividualAssessment/test/ilIndividualAssessmentUserGradingTest.php
index a091075a9ae0..fb28a095cc04 100644
--- a/Modules/IndividualAssessment/test/ilIndividualAssessmentUserGradingTest.php
+++ b/Modules/IndividualAssessment/test/ilIndividualAssessmentUserGradingTest.php
@@ -1,7 +1,5 @@
getFieldBuilder();
+
$input = $grading->toFormInput(
$f,
$df,
@@ -165,6 +172,7 @@ public function testToFormInput(): void
$refinery,
$file_handler,
$df->dateFormat()->standard(),
+ $field_builder,
[
ilIndividualAssessmentMembers::LP_IN_PROGRESS,
ilIndividualAssessmentMembers::LP_FAILED,
diff --git a/Modules/IndividualAssessmentFormPool/classes/Forms/Field.php b/Modules/IndividualAssessmentFormPool/classes/Forms/Field.php
new file mode 100644
index 000000000000..2a53f433059c
--- /dev/null
+++ b/Modules/IndividualAssessmentFormPool/classes/Forms/Field.php
@@ -0,0 +1,150 @@
+field_id;
+ }
+
+ public function withFieldId(int $field_id): self
+ {
+ $clone = clone $this;
+ $clone->field_id = $field_id;
+ return $clone;
+ }
+
+ public function asNew(): self
+ {
+ $clone = clone $this;
+ $clone->field_id = -1;
+ return $clone;
+ }
+
+ public function getObjId(): int
+ {
+ return $this->iafp_obj_id;
+ }
+
+ public function withObjId(int $iafp_obj_id): self
+ {
+ $clone = clone $this;
+ $clone->iafp_obj_id = $iafp_obj_id;
+ return $clone;
+ }
+
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+
+
+ public function hasNotes(): bool
+ {
+ return $this->with_notes;
+ }
+
+ public function isAvailableForExaminers(): bool
+ {
+ return $this->available_for_examiners;
+ }
+
+ public function getConfig(): FieldConfig
+ {
+ return $this->config;
+ }
+
+ public function toFormInput(
+ FieldFactory $factory,
+ \ilLanguage $lng,
+ Refinery $refinery,
+ int $iafp_obj_id,
+ FieldBuilder $field_builder
+ ): FormInput {
+
+ $config = $this->getConfig();
+
+ $inputs = [];
+ $inputs['id'] = $factory->hidden()
+ ->withAdditionalTransformation($refinery->kindlyTo()->int())
+ ->withValue($this->getFieldId());
+
+ $inputs['type'] = $factory->select(
+ $lng->txt('field_type'),
+ [$config->getType()->value => $config->getType()->name],
+ $lng->txt('field_type_byline')
+ )
+ ->withValue($config->getType()->value)
+ ->withRequired(true);
+
+ $inputs['name'] = $factory->text($lng->txt('field_name'), $lng->txt('field_name_byline'))
+ ->withRequired(true)
+ ->withValue($this->getName());
+
+ $inputs['type_config'] = $config->toFormInput(
+ $factory,
+ $lng,
+ $refinery,
+ $field_builder,
+ ($this->getFieldId() !== -1)
+ );
+
+ $inputs['with_notes'] = $factory->checkbox($lng->txt('field_with_notes'), $lng->txt('field_with_notes_byline'))
+ ->withValue($this->hasNotes());
+ $inputs['available_for_examiners'] = $factory->checkbox($lng->txt('field_available_for_examiners'), $lng->txt('field_available_for_examiners_byline'))
+ ->withValue($this->isAvailableForExaminers());
+
+ return $factory->section(
+ $inputs,
+ $this->getFieldId() === -1 ? $lng->txt('field_section_create') : $lng->txt('field_section_edit'),
+ $lng->txt('field_section_byline')
+ )
+ ->withAdditionalTransformation(
+ $refinery->custom()->transformation(
+ fn($v) => new self(
+ $v['type_config'],
+ $v['id'],
+ $iafp_obj_id,
+ $v['name'],
+ $v['with_notes'],
+ $v['available_for_examiners'],
+ )
+ )
+ );
+ }
+}
diff --git a/Modules/IndividualAssessmentFormPool/classes/Forms/FieldBuilder.php b/Modules/IndividualAssessmentFormPool/classes/Forms/FieldBuilder.php
new file mode 100644
index 000000000000..02eec7c3d6f4
--- /dev/null
+++ b/Modules/IndividualAssessmentFormPool/classes/Forms/FieldBuilder.php
@@ -0,0 +1,101 @@
+getLabel();
+ $description = $config->getDescription();
+
+ $value = $value ?? $config->getDefaultValue();
+
+ $factory = $this->factory;
+ switch ($config->getType()) {
+ case FieldType::MARKDOWN:
+ $md_renderer = $md_renderer ?? $this->default_mdrenderer;
+ return $factory->markdown($md_renderer, $label, $description)
+ ->withValue((string) $value)
+ ->withMaxLimit(512);
+
+ case FieldType::FILE:
+ $value = ($value === null || $value === '') ?
+ [] : [$value];
+
+ $upload_handler = $upload_handler ?? $this->default_uploadhandler;
+ return $factory->file($upload_handler, $label, $description)
+ ->withValue($value);
+
+ case FieldType::DATETIME:
+ $value = ($value === null || $value === '') ?
+ null : \DateTimeImmutable::createFromFormat('U', $value);
+
+ return $factory->datetime($label, $description)
+ ->withTimezone('UTC')
+ ->withAdditionalTransformation(
+ $this->refinery->custom()->transformation(
+ fn($v) => (string) $v->format('U')
+ )
+ )
+ ->withValue($value);
+
+ case FieldType::SINGLESELECT:
+ $options = $config->getOptions() ?? [];
+ $options = array_combine($options, $options);
+ $value = in_array($value, $options) ? $value : null; //better: constraint
+ return $factory->select($label, $options, $description)
+ ->withValue($value);
+
+ case FieldType::TAG:
+ $options = $config->getOptions() ?? [];
+ $value = ($value === null || $value === '') ?
+ null : explode(\SpecifiedFormStorageDB::VALUE_DELIMITER, $value);
+ return $factory->tag($label, $options, $description)
+ ->withUserCreatedTagsAllowed(false)
+ ->withValue($value);
+
+ case FieldType::RATING:
+ $options = [1,2,3,4,5];
+ return $factory->select($label, $options, $description)
+ ;//->withValue((string) $config->getDefaultValue());
+ }
+ }
+}
diff --git a/Modules/IndividualAssessmentFormPool/classes/Forms/FieldConfig.php b/Modules/IndividualAssessmentFormPool/classes/Forms/FieldConfig.php
new file mode 100644
index 000000000000..0aa8844a1177
--- /dev/null
+++ b/Modules/IndividualAssessmentFormPool/classes/Forms/FieldConfig.php
@@ -0,0 +1,141 @@
+type;
+ }
+
+ public function getLabel(): string
+ {
+ return $this->label;
+ }
+
+ public function getDescription(): string
+ {
+ return $this->description;
+ }
+
+ public function getOptions(): mixed
+ {
+ return $this->options;
+ }
+
+ public function getDefaultValue(): mixed
+ {
+ return $this->default_value;
+ }
+
+ public function toFormInput(
+ FieldFactory $factory,
+ \ilLanguage $lng,
+ Refinery $refinery,
+ FieldBuilder $field_builder,
+ bool $fixed
+ ): FormInput {
+ $type_options = $fixed ?
+ [$this->type->value => $this->type->name] : FieldType::toArray();
+
+ $inputs = [];
+ $inputs = array_filter([
+ 'label' => $factory->text($lng->txt('field_label'), $lng->txt('field_label_byline'))
+ ->withValue($this->getLabel()),
+ 'description' => $factory->textarea($lng->txt('field_description'), $lng->txt('field_description_byline'))
+ ->withValue($this->getDescription()),
+ 'opts' => $this->getFormInputOptions($factory, $lng, $refinery),
+ 'default' => $this->getFormInputDefault($field_builder, $lng)
+ ]);
+
+ return $factory->group($inputs)->withAdditionalTransformation(
+ $refinery->custom()->transformation(
+ function ($v) {
+ $opts = array_key_exists('opts', $v) ? $v['opts'] : null;
+ $default = array_key_exists('default', $v) ? $v['default'] : null;
+ return new self(
+ $this->getType(),
+ $v['label'],
+ $v['description'],
+ $default,
+ $opts,
+ );
+ }
+ )
+ );
+
+ }
+
+ private function getFormInputOptions(
+ FieldFactory $factory,
+ \ilLanguage $lng,
+ Refinery $refinery,
+ ): ?FormInput {
+ switch ($this->type) {
+ case FieldType::SINGLESELECT:
+ case FieldType::TAG:
+ return $factory->group([ //group is needed due to exisiting transforms on tag-input
+ $factory->tag($lng->txt('field_options'), [], $lng->txt('field_options_byline'))
+ ->withUserCreatedTagsAllowed(true)
+ ->withRequired(true)
+ ->withValue($this->getOptions())
+ ])
+ ->withAdditionalTransformation(
+ $refinery->custom()->transformation(
+ fn($v) => array_shift($v)
+ )
+ );
+ break;
+ default:
+ return null;
+ }
+ }
+
+ private function getFormInputDefault(
+ FieldBuilder $field_builder,
+ \ilLanguage $lng
+ ): ?FormInput {
+
+ switch ($this->type) {
+ case FieldType::FILE:
+ return null;
+ default:
+ return $field_builder->build($this)
+ ->withLabel($lng->txt('field_default_value'))
+ ->withByLine($lng->txt('field_default_value_byline'));
+ }
+ }
+
+}
diff --git a/Modules/IndividualAssessmentFormPool/classes/Forms/FieldType.php b/Modules/IndividualAssessmentFormPool/classes/Forms/FieldType.php
new file mode 100644
index 000000000000..da96abfef132
--- /dev/null
+++ b/Modules/IndividualAssessmentFormPool/classes/Forms/FieldType.php
@@ -0,0 +1,39 @@
+ui_factory->symbol()->icon()->custom('templates/default/images/standard/icon_checked.svg', '', 'small');
+ $icon_false = $this->ui_factory->symbol()->icon()->custom('templates/default/images/standard/icon_unchecked.svg', '', 'small');
+ return [
+ 'type' => $this->ui_factory->table()->column()->text($this->lng->txt('type')),
+ 'name' => $this->ui_factory->table()->column()->text($this->lng->txt('name')),
+ 'label' => $this->ui_factory->table()->column()->text($this->lng->txt('label')),
+ 'description' => $this->ui_factory->table()->column()->text($this->lng->txt('description'))
+ ->withIsSortable(false),
+ 'default_value' => $this->ui_factory->table()->column()->text($this->lng->txt('default_value')),
+ 'with_notes' => $this->ui_factory->table()->column()->boolean($this->lng->txt('with_notes'), $icon_true, $icon_false),
+ 'available_for_examiners' => $this->ui_factory->table()->column()->boolean($this->lng->txt('available_for_examiners'), $icon_true, $icon_false),
+ ];
+ }
+
+ public function getRows(
+ DataRowBuilder $row_builder,
+ array $visible_column_ids,
+ Range $range,
+ Order $order,
+ ?array $filter_data,
+ ?array $additional_parameters
+ ): \Generator {
+ $fields = $this->forms_repo->getFieldsForObjId($this->obj_id, $range, $order);
+ $used_in_forms = $this->forms_repo->getMappedFieldIds();
+ foreach ($fields as $field) {
+ $row_id = (string) $field->getFieldId();
+ $config = $field->getConfig();
+ $record = [
+ 'type' => $config->getType()->name,
+ 'name' => $field->getName(),
+ 'label' => $config->getLabel(),
+ 'description' => $config->getDescription(),
+ 'default_value' => (string) $config->getDefaultValue(),
+ 'with_notes' => $field->hasNotes(),
+ 'available_for_examiners' => $field->isAvailableForExaminers(),
+ ];
+ yield $row_builder->buildDataRow($row_id, $record)
+ ->withDisabledAction(
+ 'delete',
+ in_array($field->getFieldId(), $used_in_forms)
+ );
+ }
+ }
+
+ public function getTotalRowCount(
+ ?array $filter_data,
+ ?array $additional_parameters
+ ): ?int {
+ return $this->forms_repo->getFieldsCountForObjId($this->obj_id);
+ }
+}
diff --git a/Modules/IndividualAssessmentFormPool/classes/Forms/Form.php b/Modules/IndividualAssessmentFormPool/classes/Forms/Form.php
new file mode 100644
index 000000000000..94077e0f8fcd
--- /dev/null
+++ b/Modules/IndividualAssessmentFormPool/classes/Forms/Form.php
@@ -0,0 +1,157 @@
+form_id;
+ }
+
+ public function asNew(): self
+ {
+ $clone = clone $this;
+ $clone->form_id = -1;
+ return $clone;
+ }
+
+ public function getObjId(): int
+ {
+ return $this->iafp_obj_id;
+ }
+
+ public function withObjId(int $iafp_obj_id): self
+ {
+ $clone = clone $this;
+ $clone->iafp_obj_id = $iafp_obj_id;
+ return $clone;
+ }
+
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ public function withName(string $name): self
+ {
+ $clone = clone $this;
+ $clone->name = $name;
+ return $clone;
+ }
+
+ public function getDescription(): string
+ {
+ return $this->description;
+ }
+
+ public function withDescription(string $description): self
+ {
+ $clone = clone $this;
+ $clone->description = $description;
+ return $clone;
+ }
+
+ public function getFields(): array
+ {
+ return $this->fields;
+ }
+
+ public function withFields(Field ...$fields): self
+ {
+ $clone = clone $this;
+ $clone->fields = $fields;
+ return $clone;
+ }
+
+ public function toFormInput(
+ FieldFactory $factory,
+ Renderer $renderer,
+ \ilLanguage $lng,
+ Refinery $refinery,
+ int $iafp_obj_id,
+ FormsStorageDB $forms_repo,
+ FieldBuilder $field_builder
+ ): FormInput {
+
+ $options = [];
+ foreach ($this->getFields() as $field) {
+ $options[$field->getFieldId()] = $renderer->render(
+ $field_builder->build($field->getConfig())->withDisabled(true)
+ );
+ }
+
+ return $factory->section(
+ [
+ 'id' => $factory->hidden()
+ ->withValue($this->getFormId())
+ ->withAdditionalTransformation($refinery->kindlyTo()->int()),
+ 'name' => $factory->text($lng->txt('form_name'), $lng->txt('form_name_byline'))
+ ->withRequired(true)
+ ->withValue($this->getName()),
+ 'desc' => $factory->textarea($lng->txt('form_description'), $lng->txt('form_description_byline'))
+ ->withValue($this->getDescription()),
+ 'fields' => $factory->multiselect($lng->txt('fields'), $options, $lng->txt('fields_byline'))
+ ->withValue(array_keys($options))
+ ->withAdditionalTransformation(
+ $refinery->custom()->transformation(
+ fn($v) => array_map(
+ fn($fid) => $forms_repo->getFieldById((int) $fid),
+ $v
+ )
+ )
+ )
+ ],
+ $this->getFormId() === -1 ? $lng->txt('form_section_create') : $lng->txt('form_section_edit'),
+ $lng->txt('form_section_byline')
+ )
+ ->withAdditionalTransformation(
+ $refinery->custom()->transformation(
+ fn($v) => new self(
+ $v['id'],
+ $iafp_obj_id,
+ $v['name'],
+ $v['desc'],
+ $v['fields'] ?? []
+ )
+ )
+ );
+
+ }
+
+}
diff --git a/Modules/IndividualAssessmentFormPool/classes/Forms/FormsDataRetrieval.php b/Modules/IndividualAssessmentFormPool/classes/Forms/FormsDataRetrieval.php
new file mode 100644
index 000000000000..f5db95064d3f
--- /dev/null
+++ b/Modules/IndividualAssessmentFormPool/classes/Forms/FormsDataRetrieval.php
@@ -0,0 +1,80 @@
+ $this->ui_factory->table()->column()->text($this->lng->txt('name')),
+ 'description' => $this->ui_factory->table()->column()->text($this->lng->txt('description'))
+ ->withIsSortable(false),
+ 'fields_count' => $this->ui_factory->table()->column()->number($this->lng->txt('fields_count'))
+ ->withIsSortable(false),
+ ];
+ }
+
+ public function getRows(
+ DataRowBuilder $row_builder,
+ array $visible_column_ids,
+ Range $range,
+ Order $order,
+ ?array $filter_data,
+ ?array $additional_parameters
+ ): \Generator {
+ $forms = $this->forms_repo->getFormsForObjId($this->obj_id, $range, $order);
+ foreach ($forms as $form) {
+ $row_id = (string) $form->getFormId();
+ $record = [
+ 'name' => $form->getName(),
+ 'description' => $form->getDescription(),
+ 'fields_count' => count($form->getFields()),
+ ];
+ yield $row_builder->buildDataRow($row_id, $record);
+ }
+ }
+
+ public function getTotalRowCount(
+ ?array $filter_data,
+ ?array $additional_parameters
+ ): ?int {
+ return $this->forms_repo->getFormsCountForObjId($this->obj_id);
+ }
+}
diff --git a/Modules/IndividualAssessmentFormPool/classes/Forms/FormsStorage.php b/Modules/IndividualAssessmentFormPool/classes/Forms/FormsStorage.php
new file mode 100644
index 000000000000..2bfd5305cdc0
--- /dev/null
+++ b/Modules/IndividualAssessmentFormPool/classes/Forms/FormsStorage.php
@@ -0,0 +1,61 @@
+ FieldType::SINGLESELECT,
+ 'iafp_cfg_tag' => FieldType::TAG,
+ ];
+
+ private const CONFIG_TABLES_COLLECTOR = [
+ FieldType::SINGLESELECT->name => ['iafp_cfg_singleselect', 'iass_cfg_singleselect'],
+ FieldType::TAG->name => ['iafp_cfg_tag', 'iass_cfg_tag'],
+ ];
+
+ public function __construct(
+ private readonly \ilDBInterface $db,
+ private readonly \ilAccess $access
+ ) {
+ }
+
+ public function getFormsForObjId(
+ int $iafp_obj_id,
+ ?Range $range = null,
+ ?Order $order = null
+ ): \Generator {
+
+ $query_order = '';
+ $query_range = '';
+ if ($order !== null) {
+ $query_order = $order->join('ORDER BY', fn(...$o) => implode(' ', $o));
+ }
+ if ($range !== null) {
+ $query_range = sprintf('LIMIT %2$s OFFSET %1$s', ...$range->unpack());
+ }
+
+ $query = 'SELECT obj_id, form_id, name, description' . PHP_EOL
+ . 'FROM iafp_forms' . PHP_EOL
+ . 'WHERE obj_id = ' . $this->db->quote($iafp_obj_id, 'integer') . PHP_EOL
+ . $query_order . PHP_EOL
+ . $query_range;
+
+ $res = $this->db->query($query);
+ while ($row = $this->db->fetchAssoc($res)) {
+ $form_id = $row['form_id'];
+ $fields = $this->getFieldsForFormId($form_id);
+ yield new Form(
+ $form_id,
+ $iafp_obj_id,
+ $row['name'],
+ $row['description'],
+ $fields
+ );
+ }
+ }
+
+ public function getFormById(int $form_id): ?Form
+ {
+ $query = 'SELECT obj_id, form_id, name, description' . PHP_EOL
+ . 'FROM iafp_forms' . PHP_EOL
+ . 'WHERE form_id = ' . $this->db->quote($form_id, 'integer');
+
+ $res = $this->db->query($query);
+ if ($this->db->numRows($res) === 0) {
+ return null;
+ }
+ $row = $this->db->fetchAssoc($res);
+ $form_id = $row['form_id'];
+ $fields = $this->getFieldsForFormId($form_id);
+ return new Form(
+ $form_id,
+ $row['obj_id'],
+ $row['name'],
+ $row['description'],
+ $fields
+ );
+ }
+
+
+ private function getSQLPartsFieldConfig(): array
+ {
+ $opts = [];
+ $joins = [];
+ foreach (self::CONFIG_TABLES as $table => $type) {
+ $opts[] = 'CONCAT(' . $table . '.value)';
+ $joins[] = 'LEFT JOIN ' . $table . ' ON iafp_fields.field_id = ' . $table . '.field_id AND type = ' . $type->value ;
+ }
+ return [$opts, $joins];
+ }
+
+ private function getFieldFromRow(array $row): Field
+ {
+ $field_id = $row['field_id'];
+ $type = FieldType::from($row['type']);
+ $options = ($row['options'] !== null) ? explode(',', $row['options']) : null;
+ $default_value = $row['default_value'];
+ //$config = $this->field_config_factory->buildFieldConfig($type, $options, $default_value);
+
+ $config = new FieldConfig(
+ $type,
+ $row['label'],
+ $row['description'],
+ $row['default_value'],
+ $options,
+ );
+
+ return new Field(
+ $config,
+ $field_id,
+ $row['obj_id'],
+ $row['name'],
+ (bool) $row['with_notes'],
+ (bool) $row['available_for_examiners'],
+ );
+ }
+
+ public function createField(FieldType $type, int $iafp_obj_id): Field
+ {
+ $config = new FieldConfig($type);
+ $field = new Field(
+ $config,
+ -1,
+ $iafp_obj_id,
+ $name = '',
+ $with_notes = false,
+ $available_for_examiners = true
+ );
+ return $this->storeField($field);
+ }
+
+ public function getFieldsForFormId(int $form_id): array
+ {
+ list($opts, $joins) = $this->getSQLPartsFieldConfig();
+ $query = 'SELECT iafp_fields.field_id, obj_id, type, name, label, description, default_value, with_notes, available_for_examiners' . PHP_EOL
+ . ', GROUP_CONCAT(COALESCE(' . implode(',', $opts) . ')) AS options' . PHP_EOL
+ . 'FROM iafp_fields' . PHP_EOL
+ . 'INNER JOIN iafp_fieldmap ON iafp_fields.field_id = iafp_fieldmap.field_id '
+ . 'AND iafp_fieldmap.form_id = ' . $this->db->quote($form_id, 'integer') . PHP_EOL
+ . implode(' ', $joins) . PHP_EOL
+ . 'GROUP BY iafp_fields.field_id '
+ . 'ORDER BY iafp_fieldmap.position ASC';
+
+ $ret = [];
+ $res = $this->db->query($query);
+ while ($row = $this->db->fetchAssoc($res)) {
+ $ret[] = $this->getFieldFromRow($row);
+ }
+
+ return $ret;
+ }
+
+ public function getFieldsForObjId(
+ int $iafp_obj_id,
+ ?Range $range = null,
+ ?Order $order = null
+ ): \Generator {
+ $query_order = '';
+ $query_range = '';
+ if ($order !== null) {
+ $query_order = $order->join('ORDER BY', fn(...$o) => implode(' ', $o));
+ }
+ if ($range !== null) {
+ $query_range = sprintf('LIMIT %2$s OFFSET %1$s', ...$range->unpack());
+ }
+
+ list($opts, $joins) = $this->getSQLPartsFieldConfig();
+ $query = 'SELECT iafp_fields.field_id, obj_id, type, name, label, description, default_value, with_notes, available_for_examiners' . PHP_EOL
+ . ', GROUP_CONCAT(COALESCE(' . implode(',', $opts) . ')) AS options' . PHP_EOL
+ . 'FROM iafp_fields' . PHP_EOL
+ . implode(' ', $joins) . PHP_EOL
+ . 'WHERE obj_id = ' . $this->db->quote($iafp_obj_id, 'integer') . PHP_EOL
+ . 'GROUP BY iafp_fields.field_id' . PHP_EOL
+ . $query_order . PHP_EOL
+ . $query_range;
+
+ $res = $this->db->query($query);
+ while ($row = $this->db->fetchAssoc($res)) {
+ yield $this->getFieldFromRow($row);
+ }
+ }
+
+ public function getFieldById(int $field_id): ?Field
+ {
+ list($opts, $joins) = $this->getSQLPartsFieldConfig();
+ $query = 'SELECT iafp_fields.field_id, obj_id, type, name, label, description, default_value, with_notes, available_for_examiners' . PHP_EOL
+ . ', GROUP_CONCAT(COALESCE(' . implode(',', $opts) . ')) AS options' . PHP_EOL
+ . 'FROM iafp_fields' . PHP_EOL
+ . implode(' ', $joins) . PHP_EOL
+ . 'WHERE iafp_fields.field_id = ' . $this->db->quote($field_id, 'integer');
+
+
+ $res = $this->db->query($query);
+ if ($this->db->numRows($res) === 0) {
+ return null;
+ }
+ return $this->getFieldFromRow($this->db->fetchAssoc($res));
+ }
+
+
+ public function getFormsCountForObjId(int $iafp_obj_id): int
+ {
+ $query = 'SELECT count(*) as cnt' . PHP_EOL
+ . 'FROM iafp_forms' . PHP_EOL
+ . 'WHERE obj_id = ' . $this->db->quote($iafp_obj_id, 'integer');
+ $res = $this->db->query($query);
+ return (int) $this->db->fetchAssoc($res)['cnt'];
+ }
+
+ public function getAllFormIdsForObjId(int $iafp_obj_id): array
+ {
+ $query = 'SELECT form_id FROM iafp_forms' . PHP_EOL
+ . 'WHERE obj_id = ' . $this->db->quote($iafp_obj_id, 'integer');
+ $res = $this->db->query($query);
+ return array_map(
+ fn($row) => $row['form_id'],
+ $this->db->fetchAll($this->db->query($query))
+ );
+ }
+
+
+ public function getNewForm(int $iafp_obj_id): Form
+ {
+ return new Form(
+ -1,
+ $iafp_obj_id,
+ $name = '',
+ $description = '',
+ $fields = []
+ );
+ }
+
+ public function storeForm(Form $form): int
+ {
+ $id = $form->getFormId();
+ if ($form->getFormId() === -1) {
+ $id = $this->db->nextId('iafp_forms');
+ }
+
+ $query = 'REPLACE INTO iafp_forms (obj_id, form_id, name, description) VALUES (' . PHP_EOL
+ . $this->db->quote($form->getObjId(), 'integer') . ','
+ . $this->db->quote($id, 'integer') . ','
+ . $this->db->quote($form->getName(), 'text') . ','
+ . $this->db->quote($form->getDescription(), 'text') . PHP_EOL
+ . ')';
+
+ $this->db->manipulate($query);
+
+ $query = 'DELETE FROM iafp_fieldmap WHERE ' . PHP_EOL
+ . 'form_id = ' . $this->db->quote($form->getFormId(), 'integer');
+ $this->db->manipulate($query);
+
+ $position = 0;
+ foreach ($form->getFields() as $field) {
+ $position += 10;
+ $query = 'INSERT INTO iafp_fieldmap (form_id, field_id, position) VALUES (' . PHP_EOL
+ . $this->db->quote($id, 'integer') . ','
+ . $this->db->quote($field->getFieldId(), 'integer') . ','
+ . $this->db->quote($position, 'integer')
+ . ')';
+
+ $this->db->manipulate($query);
+ }
+
+ return $id;
+ }
+
+ public function deleteFormsByIds(array $form_ids): void
+ {
+ $ids = $this->db->in('form_id', $form_ids, false, 'integer');
+ $query = 'DELETE FROM iafp_forms WHERE ' . $ids;
+ $this->db->manipulate($query);
+ $query = 'DELETE FROM iafp_fieldmap WHERE ' . $ids;
+ $this->db->manipulate($query);
+ }
+
+
+ public function getFieldsCountForObjId(int $iafp_obj_id): int
+ {
+ $query = 'SELECT count(*) as cnt' . PHP_EOL
+ . 'FROM iafp_fields' . PHP_EOL
+ . 'WHERE obj_id = ' . $this->db->quote($iafp_obj_id, 'integer');
+ $res = $this->db->query($query);
+ return (int) $this->db->fetchAssoc($res)['cnt'];
+ }
+
+ public function storeField(Field $field): Field
+ {
+ if ($field->getFieldId() === -1) {
+ $field = $field->withFieldId($this->db->nextId('iafp_fields'));
+ }
+ $id = $field->getFieldId();
+ $config = $field->getConfig();
+
+ $default_value = $config->getDefaultValue();
+ if (is_array($default_value)) {
+ $default_value = implode(\SpecifiedFormStorageDB::VALUE_DELIMITER, $default_value);
+ }
+
+ $query = 'REPLACE INTO iafp_fields (obj_id, field_id, type, name, label, description, default_value, with_notes, available_for_examiners) VALUES (' . PHP_EOL
+ . $this->db->quote($field->getObjId(), 'integer') . ','
+ . $this->db->quote($id, 'integer') . ','
+ . $this->db->quote($field->getConfig()->getType()->value, 'integer') . ','
+ . $this->db->quote($field->getName(), 'text') . ','
+ . $this->db->quote($config->getLabel(), 'text') . ','
+ . $this->db->quote($config->getDescription(), 'text') . ','
+ . $this->db->quote($default_value, 'text') . ','
+ . $this->db->quote($field->hasNotes(), 'integer') . ','
+ . $this->db->quote($field->isAvailableForExaminers(), 'integer') . PHP_EOL
+ . ')';
+ $this->db->manipulate($query);
+ if ($config->getOptions() !== null) {
+ $this->storeFieldConfig($id, $config);
+ }
+ return $field;
+ }
+
+ public function getAllFieldIdsForObjId(int $iafp_obj_id): array
+ {
+ $query = 'SELECT field_id FROM iafp_fields' . PHP_EOL
+ . 'WHERE obj_id = ' . $this->db->quote($iafp_obj_id, 'integer');
+ $res = $this->db->query($query);
+ return array_map(
+ fn($row) => $row['field_id'],
+ $this->db->fetchAll($this->db->query($query))
+ );
+ }
+
+ public function deleteFieldsByIds(array $field_ids): void
+ {
+ $query = 'DELETE FROM iafp_fields WHERE '
+ . $this->db->in('field_id', $field_ids, false, 'integer');
+ $this->db->manipulate($query);
+
+ $query = 'DELETE FROM iafp_fieldmap WHERE '
+ . $this->db->in('field_id', $field_ids, false, 'integer');
+ $this->db->manipulate($query);
+
+ foreach (array_keys(self::CONFIG_TABLES) as $cfg_table) {
+ $query = 'DELETE FROM ' . $cfg_table . ' WHERE '
+ . $this->db->in('field_id', $field_ids, false, 'integer');
+ $this->db->manipulate($query);
+ }
+ }
+
+ public function getMappedFieldIds(): array
+ {
+ $query = 'SELECT DISTINCT field_id FROM iafp_fieldmap';
+ $res = $this->db->query($query);
+ return array_map(
+ fn($row) => $row['field_id'],
+ $this->db->fetchAll($this->db->query($query))
+ );
+ }
+
+ private function storeFieldConfig(int $field_id, FieldConfig $config): void
+ {
+ $table = array_filter(
+ self::CONFIG_TABLES,
+ fn($v) => $v === $config->getType(),
+ );
+
+ if ($table === []) {
+ var_dump($config->getOptions());
+ die();
+ }
+
+ $table = array_key_first($table);
+
+ $delete = 'DELETE FROM ' . $table . ' WHERE field_id = ' . $this->db->quote($field_id, 'integer');
+ $this->db->manipulate($delete);
+
+ if ($config->getOptions() !== []) {
+ $options = [];
+ foreach ($config->getOptions() as $opt) {
+ $options[] = '('
+ . implode(',', [$this->db->quote($field_id, 'integer'), $this->db->quote($opt, 'text')])
+ . ')' . PHP_EOL;
+ }
+ $insert = 'INSERT INTO ' . $table . ' (field_id, value) VALUES' . PHP_EOL
+ . implode(', ', $options);
+
+ $this->db->manipulate($insert);
+ }
+ }
+
+
+ // --- collector ---
+
+ public function getFormsSelection(): array
+ {
+ $options = [-1 => ['std.', 'no form pool']]; //add default option ('standard'), form only shows for entries > 1
+ foreach ($this->getFormPools() as $pool_info) {
+ list($ref_id, $obj_id, $title) = $pool_info;
+ $forms = $this->getFormsForObjId($obj_id);
+ foreach ($forms as $form) {
+ $options['iass_' . $form->getFormId()] = [$form->getName(), $form->getDescription()];
+ }
+ }
+ return $options;
+ }
+
+ public function copyFieldsToIASS(int $iafp_form_id, int $iass_obj_id): void
+ {
+ $fields = $this->getFieldsForFormId($iafp_form_id);
+ foreach ($fields as $field) {
+ $config = $field->getConfig();
+ $query = 'REPLACE INTO iass_formfields (obj_id, field_id, type, name, label, description, default_value, with_notes, available_for_examiners) VALUES (' . PHP_EOL
+ . $this->db->quote($iass_obj_id, 'integer') . ','
+ . $this->db->quote($field->getFieldId(), 'integer') . ','
+ . $this->db->quote($config->getType()->value, 'integer') . ','
+ . $this->db->quote($field->getName(), 'text') . ','
+ . $this->db->quote($config->getLabel(), 'text') . ','
+ . $this->db->quote($config->getDescription(), 'text') . ','
+ . $this->db->quote($config->getDefaultValue(), 'text') . ','
+ . $this->db->quote($field->hasNotes(), 'integer') . ','
+ . $this->db->quote($field->isAvailableForExaminers(), 'integer') . PHP_EOL
+ . ')';
+ $this->db->manipulate($query);
+
+ if (array_key_exists($config->getType()->name, self::CONFIG_TABLES_COLLECTOR)) {
+ list($source_table, $target_table) = self::CONFIG_TABLES_COLLECTOR[$field->getConfig()->getType()->name];
+
+ $fields = $this->getFieldsFromTable($source_table);
+ $query = 'REPLACE INTO ' . $target_table . '(obj_id, ' . $fields . ')' . PHP_EOL
+ . 'SELECT ' . $this->db->quote($iass_obj_id, 'integer') . ',' . $fields . PHP_EOL
+ . 'FROM ' . $source_table . PHP_EOL
+ . 'WHERE field_id = ' . $field->getFieldId();
+
+ $this->db->manipulate($query);
+ }
+ }
+ }
+
+ protected function getFieldsFromTable(string $table_name): string
+ {
+ $query = 'SELECT GROUP_CONCAT(COLUMN_NAME) as fields FROM INFORMATION_SCHEMA.COLUMNS' . PHP_EOL
+ . 'WHERE COLUMN_NAME <> "obj_id" AND TABLE_NAME = ' . $this->db->quote($table_name, 'text');
+ $res = $this->db->query($query);
+ return $this->db->fetchAssoc($res)['fields'];
+ }
+
+ public function cloneFields(int $source_iass_obj_id, int $target_iass_obj_id): void
+ {
+ $tables = [
+ 'iass_formfields',
+ 'iass_cfg_singleselect',
+ 'iass_cfg_tag',
+ ];
+ foreach ($tables as $table) {
+ $fields = $this->getFieldsFromTable($table);
+ $query = 'INSERT INTO ' . $table . ' (obj_id,' . $fields . ')' . PHP_EOL
+ . 'SELECT '
+ . $this->db->quote($target_iass_obj_id, 'integer') . ',' . $fields . PHP_EOL
+ . 'FROM ' . $table . ' WHERE obj_id = ' . $this->db->quote($source_iass_obj_id, 'integer');
+ $this->db->manipulate($query);
+ }
+ }
+
+ private function getFormPools(): array
+ {
+ $query = 'SELECT ref.obj_id, ref.ref_id, od.title FROM object_data od' . PHP_EOL
+ . 'JOIN object_reference ref ON od.obj_id = ref.obj_id' . PHP_EOL
+ . 'WHERE ref.deleted IS NULL AND od.type = "iafp"';
+
+ $ret = [];
+ $res = $this->db->query($query);
+ while ($row = $this->db->fetchAssoc($res)) {
+ $ref_id = (int) $row['ref_id'];
+ $obj_id = (int) $row['obj_id'];
+ if ($this->access->checkAccess('read', '', $ref_id)) {
+ $ret[] = [
+ $ref_id,
+ $obj_id,
+ $row['title']
+ ];
+ };
+ }
+ return $ret;
+ }
+}
diff --git a/Modules/IndividualAssessmentFormPool/classes/IndAssFacade/IAFPCollector.php b/Modules/IndividualAssessmentFormPool/classes/IndAssFacade/IAFPCollector.php
new file mode 100644
index 000000000000..5042d780adf4
--- /dev/null
+++ b/Modules/IndividualAssessmentFormPool/classes/IndAssFacade/IAFPCollector.php
@@ -0,0 +1,42 @@
+getFormId()] = [$form->getName(), $form->getDescription()];
+ */
+ public function getFormsSelection(): array;
+
+ /**
+ * copy fields and their config to the given IASS object
+ */
+ public function copyFieldsToIASS(int $iafp_form_id, int $iass_obj_id): void;
+
+ /**
+ * clone fields (on copy of object)
+ */
+ public function cloneFields(int $source_iass_obj_id, int $target_iass_obj_id): void;
+
+}
diff --git a/Modules/IndividualAssessmentFormPool/classes/IndAssFacade/IASSCustomField.php b/Modules/IndividualAssessmentFormPool/classes/IndAssFacade/IASSCustomField.php
new file mode 100644
index 000000000000..85ed386f7b12
--- /dev/null
+++ b/Modules/IndividualAssessmentFormPool/classes/IndAssFacade/IASSCustomField.php
@@ -0,0 +1,99 @@
+config;
+ }
+
+ public function getFieldId(): int
+ {
+ return $this->field_id;
+ }
+
+ public function getIds(): array
+ {
+ return [
+ $this->obj_id,
+ $this->field_id,
+ $this->usr_id
+ ];
+ }
+
+ public function hasNotes(): bool
+ {
+ return $this->has_note;
+ }
+
+ public function withValue(null|string|array $value): self
+ {
+ $clone = clone $this;
+ $clone->value = $value;
+ return $clone;
+ }
+
+ public function getValue(): null|string|array
+ {
+ return $this->value;
+ }
+
+ public function toFormInput(
+ FieldFactory $factory,
+ Refinery $refinery,
+ UploadHandler $upload_handler, //USE IT
+ FieldBuilder $field_builder
+ ): FormInput {
+ $ui_field = $field_builder->build(
+ $this->getConfig(),
+ $this->getValue(),
+ $upload_handler
+ );
+
+ if ($this->hasNotes()) {
+ $note = $factory->textarea('', '')
+ ->withValue($this->note)
+ ->withMaxLimit(512);
+ $ui_field = $factory->group([$ui_field, $note]);
+ }
+ return $ui_field;
+ }
+}
diff --git a/Modules/IndividualAssessmentFormPool/classes/IndAssFacade/SpecifiedFormStorage.php b/Modules/IndividualAssessmentFormPool/classes/IndAssFacade/SpecifiedFormStorage.php
new file mode 100644
index 000000000000..54e0cade3f8e
--- /dev/null
+++ b/Modules/IndividualAssessmentFormPool/classes/IndAssFacade/SpecifiedFormStorage.php
@@ -0,0 +1,61 @@
+value . PHP_EOL
+ . 'LEFT JOIN iass_cfg_tag cfg_t ON ff.field_id = cfg_t.field_id AND type = ' . FieldType::TAG->value . PHP_EOL
+
+ . 'LEFT JOIN iass_cust_values val ON ff.obj_id = val.obj_id' . PHP_EOL
+ . 'AND ff.field_id = val.field_id' . PHP_EOL
+ . 'AND val.usr_id = ' . $this->db->quote($member_usr_id, 'integer') . PHP_EOL
+
+ . 'LEFT JOIN iass_cust_notes notes ON ff.obj_id = notes.obj_id' . PHP_EOL
+ . 'AND ff.field_id = notes.field_id' . PHP_EOL
+ . 'AND notes.usr_id = ' . $this->db->quote($member_usr_id, 'integer') . PHP_EOL
+
+ . 'WHERE ff.obj_id = ' . $this->db->quote($obj_id, 'integer') . PHP_EOL
+ . 'GROUP BY field_id';
+
+ $ret = [];
+ $res = $this->db->query($query);
+ while ($row = $this->db->fetchAssoc($res)) {
+ $options = [];
+ if ($row['options'] !== null) {
+ $options = explode(',', $row['options']);
+ }
+
+ $config = new FieldConfig(
+ FieldType::from($row['type']),
+ $row['label'],
+ $row['description'],
+ $row['default_value'],
+ $options
+ );
+ $ret[] = new IASSCustomField(
+ $config,
+ $obj_id,
+ $row['field_id'],
+ $member_usr_id,
+ (bool) $row['with_notes'],
+ (bool) $row['available_for_examiners'],
+ (string) $row['value'],
+ (string) $row['note'],
+ );
+ }
+ return $ret;
+ }
+
+ public function storeSpecifiedUserValues(IASSCustomField ...$fields): void
+ {
+ foreach ($fields as $field) {
+ list($obj_id, $field_id, $usr_id) = $field->getIds();
+ $value = $field->getValue();
+
+ if ($field->hasNotes()) {
+ list($value, $note) = $value;
+ $query = 'REPLACE INTO iass_cust_notes (obj_id, field_id, usr_id, value)' . PHP_EOL
+ . 'VALUES (' . PHP_EOL
+ . $obj_id . ','
+ . $field_id . ','
+ . $usr_id . ','
+ . $this->db->quote((string) $note, 'text') . PHP_EOL
+ . ')';
+ $this->db->manipulate($query);
+ }
+
+ if (is_array($value)) {
+ $value = implode(self::VALUE_DELIMITER, $value);
+ }
+ $query = 'REPLACE INTO iass_cust_values (obj_id, field_id, usr_id, value)' . PHP_EOL
+ . 'VALUES (' . PHP_EOL
+ . $obj_id . ','
+ . $field_id . ','
+ . $usr_id . ','
+ . $this->db->quote($value, 'text') . PHP_EOL
+ . ')';
+ $this->db->manipulate($query);
+ }
+ }
+
+ public function deleteSpecifiedUserValues(
+ IRSS $irss,
+ ilIndividualAssessmentGradingStakeholder $stakeholder,
+ int $iass_obj_id,
+ int $member_usr_id,
+ ): void {
+ $this->deleteFileResources(
+ $irss,
+ $stakeholder,
+ ...$this->getResourceIdentifiers($iass_obj_id, $member_usr_id)
+ );
+
+ $tables = [
+ 'iass_cust_values',
+ 'iass_cust_notes'
+ ];
+ foreach ($tables as $table) {
+ $query = 'DELETE FROM ' . $table . PHP_EOL
+ . 'WHERE obj_id = ' . $this->db->quote($iass_obj_id, 'integer') . PHP_EOL
+ . 'AND usr_id = ' . $this->db->quote($member_usr_id, 'integer');
+ $this->db->manipulate($query);
+ }
+ }
+
+ public function deleteAllUserValuesAndFields(
+ IRSS $irss,
+ ilIndividualAssessmentGradingStakeholder $stakeholder,
+ int $iass_obj_id
+ ): void {
+ $this->deleteFileResources(
+ $irss,
+ $stakeholder,
+ ...$this->getResourceIdentifiers($iass_obj_id)
+ );
+
+ $tables = [
+ 'iass_cfg_singleselect',
+ 'iass_cfg_tag',
+ 'iass_cust_values',
+ 'iass_cust_notes',
+ 'iass_formfields',
+ ];
+ foreach ($tables as $table) {
+ $query = 'DELETE FROM ' . $table . PHP_EOL
+ . 'WHERE obj_id = ' . $this->db->quote($iass_obj_id, 'integer');
+ $this->db->manipulate($query);
+ }
+ }
+
+ protected function deleteFileResources(
+ IRSS $irss,
+ ilIndividualAssessmentGradingStakeholder $stakeholder,
+ string ...$ids
+ ): void {
+ foreach ($ids as $identifier) {
+ $resource_id = $irss->manage()->find($identifier);
+ if ($resource_id !== null) {
+ $irss->manage()->remove($resource_id, $stakeholder);
+ }
+ }
+ }
+
+ protected function getResourceIdentifiers(int $obj_id, int ...$usr_ids): array
+ {
+ $query = 'SELECT value FROM iass_cust_values ival' . PHP_EOL
+ . 'JOIN iass_formfields ifield' . PHP_EOL
+ . 'ON ival.obj_id = ifield.obj_id' . PHP_EOL
+ . 'AND ival.field_id = ifield.field_id' . PHP_EOL
+ . 'WHERE ival.obj_id = ' . $this->db->quote($obj_id, 'integer') . PHP_EOL
+ . 'AND ifield.type = ' . FieldType::FILE->value;
+
+ if ($usr_ids !== []) {
+ $query .= ' AND ' . $this->db->in('ival.usr_id', $usr_ids, false, 'integer');
+ }
+
+ $ret = [];
+ $res = $this->db->query($query);
+ while ($row = $this->db->fetchAssoc($res)) {
+ $ret[] = $row['value'];
+ }
+ return $ret;
+ }
+
+}
diff --git a/Modules/IndividualAssessmentFormPool/classes/Setup/class.IAFPTablesDBUpdateSteps.php b/Modules/IndividualAssessmentFormPool/classes/Setup/class.IAFPTablesDBUpdateSteps.php
new file mode 100644
index 000000000000..268df140c326
--- /dev/null
+++ b/Modules/IndividualAssessmentFormPool/classes/Setup/class.IAFPTablesDBUpdateSteps.php
@@ -0,0 +1,369 @@
+db = $db;
+ }
+
+ public function step_1(): void
+ {
+ $this->db->createTable(
+ 'iafp_forms',
+ [
+ 'obj_id' => [
+ 'type' => 'integer',
+ 'length' => 8,
+ 'notnull' => true
+ ],
+ 'form_id' => [
+ 'type' => 'integer',
+ 'length' => 8,
+ 'notnull' => true
+ ],
+ 'name' => [
+ 'type' => 'text',
+ 'length' => 128,
+ 'notnull' => true
+ ],
+ 'description' => [
+ 'type' => 'text',
+ 'length' => 255,
+ 'notnull' => true,
+ 'default' => '',
+ ],
+ ]
+ );
+
+ $this->db->addPrimaryKey('iafp_forms', ['obj_id', 'form_id']);
+ $this->db->addIndex('iafp_forms', ['obj_id'], 'oid');
+ $this->db->addIndex('iafp_forms', ['form_id'], 'fid');
+
+ $this->db->createSequence('iafp_forms');
+ }
+
+ public function step_2(): void
+ {
+ $this->db->createTable(
+ 'iafp_fields',
+ [
+ 'obj_id' => [
+ 'type' => 'integer',
+ 'length' => 8,
+ 'notnull' => true
+ ],
+ 'field_id' => [
+ 'type' => 'integer',
+ 'length' => 8,
+ 'notnull' => true
+ ],
+ 'type' => [
+ 'type' => 'integer',
+ 'length' => 1,
+ 'notnull' => true
+ ],
+ 'name' => [
+ 'type' => 'text',
+ 'length' => 128,
+ 'notnull' => true
+ ],
+ 'label' => [
+ 'type' => 'text',
+ 'length' => 128,
+ 'notnull' => true
+ ],
+ 'description' => [
+ 'type' => 'text',
+ 'length' => 255,
+ 'notnull' => true,
+ 'default' => '',
+ ],
+ 'default_value' => [
+ 'type' => 'text',
+ 'length' => 512,
+ 'notnull' => false,
+ ],
+ 'with_notes' => [
+ 'type' => 'integer',
+ 'length' => 1,
+ 'notnull' => true,
+ 'default' => 0,
+ ],
+ 'available_for_examiners' => [
+ 'type' => 'integer',
+ 'length' => 1,
+ 'notnull' => true,
+ 'default' => 1,
+ ],
+ ]
+ );
+
+ $this->db->addPrimaryKey('iafp_fields', ['obj_id', 'field_id']);
+ $this->db->addIndex('iafp_fields', ['obj_id'], 'oid');
+ $this->db->addIndex('iafp_fields', ['field_id'], 'fid');
+
+ $this->db->createSequence('iafp_fields');
+ }
+
+ public function step_3(): void
+ {
+ $this->db->createTable(
+ 'iafp_fieldmap',
+ [
+ 'form_id' => [
+ 'type' => 'integer',
+ 'length' => 8,
+ 'notnull' => true
+ ],
+ 'field_id' => [
+ 'type' => 'integer',
+ 'length' => 8,
+ 'notnull' => true
+ ],
+ 'position' => [
+ 'type' => 'integer',
+ 'length' => 4,
+ 'notnull' => true
+ ],
+ ]
+ );
+
+ $this->db->addPrimaryKey('iafp_fieldmap', ['form_id', 'field_id']);
+ $this->db->addIndex('iafp_fieldmap', ['form_id'], 'fo');
+ $this->db->addIndex('iafp_fieldmap', ['field_id'], 'fi');
+ }
+
+ public function step_4(): void
+ {
+ $this->db->createTable(
+ 'iafp_cfg_singleselect',
+ [
+ 'field_id' => [
+ 'type' => 'integer',
+ 'length' => 8,
+ 'notnull' => true
+ ],
+ 'value' => [
+ 'type' => 'text',
+ 'length' => 128,
+ 'notnull' => true
+ ],
+ ]
+ );
+ }
+
+ public function step_5(): void
+ {
+ $this->db->createTable(
+ 'iafp_cfg_tag',
+ [
+ 'field_id' => [
+ 'type' => 'integer',
+ 'length' => 8,
+ 'notnull' => true
+ ],
+ 'value' => [
+ 'type' => 'text',
+ 'length' => 128,
+ 'notnull' => true
+ ]
+ ]
+ );
+ }
+
+ public function step_6(): void
+ {
+ $this->db->createTable(
+ 'iass_formfields',
+ [
+ 'obj_id' => [
+ 'type' => 'integer',
+ 'length' => 8,
+ 'notnull' => true
+ ],
+ 'field_id' => [
+ 'type' => 'integer',
+ 'length' => 8,
+ 'notnull' => true
+ ],
+ 'type' => [
+ 'type' => 'integer',
+ 'length' => 1,
+ 'notnull' => true
+ ],
+ 'name' => [
+ 'type' => 'text',
+ 'length' => 128,
+ 'notnull' => true
+ ],
+ 'label' => [
+ 'type' => 'text',
+ 'length' => 128,
+ 'notnull' => true
+ ],
+ 'description' => [
+ 'type' => 'text',
+ 'length' => 255,
+ 'notnull' => true,
+ 'default' => '',
+ ],
+ 'default_value' => [
+ 'type' => 'text',
+ 'length' => 255,
+ 'notnull' => false,
+ ],
+ 'with_notes' => [
+ 'type' => 'integer',
+ 'length' => 1,
+ 'notnull' => true,
+ 'default' => 0,
+ ],
+ 'available_for_examiners' => [
+ 'type' => 'integer',
+ 'length' => 1,
+ 'notnull' => true,
+ 'default' => 1,
+ ],
+ ]
+ );
+ $this->db->addPrimaryKey('iass_formfields', ['obj_id', 'field_id']);
+ $this->db->addIndex('iass_formfields', ['obj_id'], 'oid');
+ $this->db->addIndex('iass_formfields', ['field_id'], 'fid');
+ }
+
+ public function step_7(): void
+ {
+ $this->db->createTable(
+ 'iass_cfg_singleselect',
+ [
+ 'obj_id' => [
+ 'type' => 'integer',
+ 'length' => 8,
+ 'notnull' => true
+ ],
+ 'field_id' => [
+ 'type' => 'integer',
+ 'length' => 8,
+ 'notnull' => true
+ ],
+ 'value' => [
+ 'type' => 'text',
+ 'length' => 128,
+ 'notnull' => true
+ ],
+ ]
+ );
+ }
+
+ public function step_8(): void
+ {
+ $this->db->createTable(
+ 'iass_cfg_tag',
+ [
+ 'obj_id' => [
+ 'type' => 'integer',
+ 'length' => 8,
+ 'notnull' => true
+ ],
+ 'field_id' => [
+ 'type' => 'integer',
+ 'length' => 8,
+ 'notnull' => true
+ ],
+ 'value' => [
+ 'type' => 'text',
+ 'length' => 128,
+ 'notnull' => true
+ ]
+ ]
+ );
+ }
+
+ public function step_9(): void
+ {
+ $this->db->createTable(
+ 'iass_cust_values',
+ [
+ 'obj_id' => [
+ 'type' => 'integer',
+ 'length' => 8,
+ 'notnull' => true
+ ],
+ 'field_id' => [
+ 'type' => 'integer',
+ 'length' => 8,
+ 'notnull' => true
+ ],
+ 'usr_id' => [
+ 'type' => 'integer',
+ 'length' => 8,
+ 'notnull' => true
+ ],
+ 'value' => [
+ 'type' => 'text',
+ 'length' => 512,
+ 'notnull' => false
+ ]
+ ]
+ );
+ $this->db->addPrimaryKey('iass_cust_values', ['obj_id', 'field_id', 'usr_id']);
+ $this->db->addIndex('iass_cust_values', ['obj_id'], 'oid');
+ $this->db->addIndex('iass_cust_values', ['field_id'], 'fid');
+ $this->db->addIndex('iass_cust_values', ['usr_id'], 'uid');
+ }
+
+ public function step_10(): void
+ {
+ $this->db->createTable(
+ 'iass_cust_notes',
+ [
+ 'obj_id' => [
+ 'type' => 'integer',
+ 'length' => 8,
+ 'notnull' => true
+ ],
+ 'field_id' => [
+ 'type' => 'integer',
+ 'length' => 8,
+ 'notnull' => true
+ ],
+ 'usr_id' => [
+ 'type' => 'integer',
+ 'length' => 8,
+ 'notnull' => true
+ ],
+ 'value' => [
+ 'type' => 'text',
+ 'length' => 512,
+ 'notnull' => true
+ ]
+ ]
+ );
+ $this->db->addPrimaryKey('iass_cust_notes', ['obj_id', 'field_id', 'usr_id']);
+ $this->db->addIndex('iass_cust_notes', ['obj_id'], 'oid');
+ $this->db->addIndex('iass_cust_notes', ['field_id'], 'fid');
+ $this->db->addIndex('iass_cust_notes', ['usr_id'], 'uid');
+ }
+
+}
diff --git a/Modules/IndividualAssessmentFormPool/classes/Setup/class.ilIndividualAssessmentFormPoolSetupAgent.php b/Modules/IndividualAssessmentFormPool/classes/Setup/class.ilIndividualAssessmentFormPoolSetupAgent.php
new file mode 100644
index 000000000000..fff291e45462
--- /dev/null
+++ b/Modules/IndividualAssessmentFormPool/classes/Setup/class.ilIndividualAssessmentFormPoolSetupAgent.php
@@ -0,0 +1,123 @@
+getObjectives()
+ );
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getUpdateObjective(Setup\Config $config = null): Setup\Objective
+ {
+ return new Setup\ObjectiveCollection(
+ 'Type is registered and database is updated for Module/IndividualAssessmentFormPool',
+ false,
+ ...$this->getObjectives()
+ );
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getBuildArtifactObjective(): Setup\Objective
+ {
+ return new Setup\Objective\NullObjective();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getStatusObjective(Setup\Metrics\Storage $storage): Setup\Objective
+ {
+ return new Setup\ObjectiveCollection(
+ 'Component IndividualAssessmentFormPool',
+ true,
+ new ilObjectNewTypeAddedObjective(
+ self::TYPE,
+ self::TYPE_TITLE,
+ ),
+ new ilDatabaseUpdateStepsMetricsCollectedObjective($storage, new IAFPTablesDBUpdateSteps()),
+ //new ilDatabaseUpdateStepsMetricsCollectedObjective($storage, new ilLearningSequenceRegisterNotificationType())
+ );
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getMigrations(): array
+ {
+ return [];
+ }
+
+ private function getObjectives(): array
+ {
+ return [
+ new ilObjectNewTypeAddedObjective(
+ self::TYPE,
+ self::TYPE_TITLE,
+ ),
+ new ilDatabaseUpdateStepsExecutedObjective(
+ new IAFPTablesDBUpdateSteps()
+ ),
+ new ilAccessRbacStandardOperationsAddedObjective(self::TYPE),
+
+ /*
+ new ilDatabaseUpdateStepsExecutedObjective(
+ new xxxxTableDBUpdateSteps()
+ )
+ */
+ ];
+ }
+
+}
diff --git a/Modules/IndividualAssessmentFormPool/classes/class.IAFPAccessHandler.php b/Modules/IndividualAssessmentFormPool/classes/class.IAFPAccessHandler.php
new file mode 100644
index 000000000000..ee5e99148cf3
--- /dev/null
+++ b/Modules/IndividualAssessmentFormPool/classes/class.IAFPAccessHandler.php
@@ -0,0 +1,74 @@
+isSystemAdmin() ||
+ $this->access->checkAccess('visible', '', $this->iafp_ref_id);
+ }
+
+ public function mayRead(): bool
+ {
+ return $this->isSystemAdmin() ||
+ $this->access->checkAccess('read', '', $this->iafp_ref_id);
+
+ }
+ public function mayEdit(): bool
+ {
+ return $this->isSystemAdmin() ||
+ $this->access->checkAccess('write', '', $this->iafp_ref_id);
+ }
+
+ protected function isSystemAdmin(): bool
+ {
+ return $this->review->isAssigned($this->current_usr_id, SYSTEM_ROLE_ID);
+ }
+
+}
diff --git a/Modules/IndividualAssessmentFormPool/classes/class.IAFPFieldsGUI.php b/Modules/IndividualAssessmentFormPool/classes/class.IAFPFieldsGUI.php
new file mode 100644
index 000000000000..9be8df394a29
--- /dev/null
+++ b/Modules/IndividualAssessmentFormPool/classes/class.IAFPFieldsGUI.php
@@ -0,0 +1,317 @@
+url_builder,
+ $this->action_token,
+ $this->rowid_token
+ ) = $url_builder->acquireParameters($namespace, 'act', 'fid');
+ }
+
+ public function executeCommand(): void
+ {
+ $next_class = $this->ctrl->getNextClass($this);
+ $cmd = $this->ctrl->getCmd() ?? self::CMD_VIEW;
+
+ //$this->addToNavigationHistory();
+ //$this->prepareOutput();
+
+ switch ($next_class) {
+ default:
+
+ if ($this->query->has($this->action_token->getName())) {
+ $cmd = $this->query->retrieve(
+ $this->action_token->getName(),
+ $this->refinery->to()->string()
+ );
+ $ids = $this->query->retrieve(
+ $this->rowid_token->getName(),
+ $this->refinery->byTrying([
+ $this->refinery->to()->listOf($this->refinery->kindlyTo()->int()),
+ $this->refinery->custom()->transformation($this->getAllIds()),
+ $this->refinery->always([])
+ ])
+ );
+ }
+
+ switch ($cmd) {
+ case self::CMD_LIST:
+ $list = $this->listFields();
+ //if ($list !== '') {
+ $this->tpl->setContent($list);
+ break;
+ //}
+ //$this->ctrl->redirect($this, self:CMD_EDIT);
+ case self::CMD_CREATE:
+ $type = $this->getFieldCreationModal()->withRequest($this->request)->getData();
+ $field_id = $this->forms_repo->createField($type, $this->iafp_obj_id)->getFieldId();
+ $url = $this->getUrlString(self::CMD_EDIT, $field_id);
+ $this->ctrl->redirectToURL($url);
+ break;
+
+ case self::CMD_EDIT:
+ $field_id = array_shift($ids);
+ $frm = $this->getEditForm($this->forms_repo->getFieldById($field_id));
+ $this->tpl->setContent($this->ui_renderer->render($frm));
+ break;
+
+ case self::CMD_SAVE:
+ $field_id = array_shift($ids);
+ $field = $this->forms_repo->getFieldById($field_id);
+ $ui_form = $this->getEditForm($field);
+ $this->tpl->setContent($this->ui_renderer->render($this->save($ui_form)));
+ break;
+
+ case self::CMD_DELETE:
+ echo $this->ui_renderer->renderAsync(
+ $this->getDeleteConfirmation($ids)
+ );
+ exit();
+ case self::CMD_DELETE_CONFIRMED:
+ $used_ids = $this->forms_repo->getMappedFieldIds();
+ $ids = array_filter($ids, static fn(int $id): bool => !in_array($id, $used_ids));
+ $this->forms_repo->deleteFieldsByIds($ids);
+ $this->ctrl->redirect($this, self::CMD_LIST);
+
+ // no break
+ default:
+ throw new \Exception('no such command: ' . $cmd);
+ }
+ }
+ //$this->addHeaderAction();
+ }
+
+ protected function getTableActions(): array
+ {
+ $f = $this->ui_factory->table()->action();
+ return [
+ 'edit' => $f->single(
+ $this->txt('edit'),
+ $this->url_builder->withParameter($this->action_token, self::CMD_EDIT),
+ $this->rowid_token
+ ),
+ 'delete' => $f->standard(
+ $this->txt('delete'),
+ $this->url_builder->withParameter($this->action_token, 'delete'),
+ $this->rowid_token
+ )->withAsync(),
+ ];
+ }
+
+ protected function getUrlString(string $cmd, int|array $row_ids): string
+ {
+ $row_ids = is_array($row_ids) ? $row_ids : [$row_ids];
+ return $this->url_builder
+ ->withParameter($this->action_token, $cmd)
+ ->withParameter($this->rowid_token, $row_ids)
+ ->buildURI()
+ ->__toString();
+ }
+
+
+ protected function getFieldCreationModal(): RoundTrip
+ {
+ return $this->ui_factory->modal()->roundtrip(
+ $this->txt('new_field'),
+ null,
+ [
+ $this->ui_factory->input()->field()->select(
+ $this->lng->txt('field_type'),
+ FieldType::toArray(),
+ $this->lng->txt('field_type_byline')
+ )
+ ],
+ $this->ctrl->getLinkTarget($this, self::CMD_CREATE)
+ )->withAdditionalTransformation(
+ $this->refinery->custom()->transformation(
+ fn($v) => FieldType::from((int) array_shift($v))
+ )
+ );
+ }
+
+ protected function listFields(): string
+ {
+ $modal = $this->getFieldCreationModal();
+ $new_entry = $this->ui_factory->button()->primary(
+ $this->txt('new_field'),
+ //$this->ctrl->getLinkTarget($this, self::CMD_CREATE)
+ $modal->getShowSignal()
+ );
+
+ $table = $this->ui_factory->table()
+ ->data(
+ $this->txt('iafp_fields'),
+ $this->data_retrieval->getColumns(),
+ $this->data_retrieval
+ )
+ ->withId('iafp_fields_table_' . $this->iafp_obj_id)
+ ->withActions($this->getTableActions())
+ ->withRequest($this->request);
+
+ return $this->ui_renderer->render([
+ $modal,
+ $new_entry,
+ $table
+ ]);
+ }
+
+ protected function getEditForm(Field $field): UIForm
+ {
+ return $this->ui_factory->input()->container()->form()->standard(
+ $this->getUrlString(self::CMD_SAVE, $field->getFieldId()),
+ [
+ $field->toFormInput(
+ $this->ui_factory->input()->field(),
+ $this->lng,
+ $this->refinery,
+ $this->iafp_obj_id,
+ $this->field_builder
+ )
+ ]
+ )
+ ->withAdditionalTransformation(
+ $this->refinery->custom()->transformation(
+ fn($v) => array_shift($v)
+ )
+ );
+ }
+
+ protected function save(UIForm $ui_form): UIForm
+ {
+ $ui_form = $ui_form->withRequest($this->request);
+ $field = $ui_form->getData();
+ if ($field === null) {
+ return $ui_form;
+ }
+
+ $field_id = $this->forms_repo->storeField($field)->getFieldId();
+ $this->tpl->setOnScreenMessage('success', $this->lng->txt('object_saved'), true);
+
+ $url = $this->getUrlString(self::CMD_EDIT, $field_id);
+ $this->ctrl->redirectToURL($url);
+ }
+
+
+ protected function getDeleteConfirmation(array $ids): MessageBox
+ {
+ $msg = '';
+
+ $used_ids = $this->forms_repo->getMappedFieldIds();
+ $delete_ids = array_filter(
+ $ids,
+ static fn(int $id): bool => !in_array($id, $used_ids)
+ );
+ $nodelete_ids = array_diff($ids, $delete_ids);
+ if ($nodelete_ids !== []) {
+ $msg .= 'Cannot delete these (used):'
+ . $this->ui_renderer->render(
+ $this->ui_factory->listing()->unordered(
+ array_map('strval', $nodelete_ids)
+ )
+ );
+ }
+
+ $msg .= '
Delete?'
+ . $this->ui_renderer->render(
+ $this->ui_factory->listing()->unordered(
+ array_map('strval', $delete_ids)
+ )
+ );
+
+ $buttons = [
+ $this->ui_factory->button()->standard(
+ $this->lng->txt('confirm_delete'),
+ $this->getUrlString(self::CMD_DELETE_CONFIRMED, $ids)
+ ),
+ $this->ui_factory->button()->standard(
+ $this->lng->txt('confirm_cancel'),
+ $this->ctrl->getLinkTarget($this, self::CMD_LIST)
+ ),
+ ];
+ return $this->ui_factory->messageBox()->confirmation($msg)
+ ->withButtons($buttons);
+ }
+
+ protected function getAllIds(): Closure
+ {
+ $repo = $this->forms_repo;
+ $obj_id = $this->iafp_obj_id;
+ return function ($v) use ($repo, $obj_id): array {
+ if (array_shift($v) === 'ALL_OBJECTS') {
+ return $repo->getAllFieldIdsForObjId($obj_id);
+ }
+ throw new \InvalidArgumentException('no ids');
+ };
+ }
+
+ protected function txt(string $code): string
+ {
+ return $this->lng->txt($code);
+ }
+}
diff --git a/Modules/IndividualAssessmentFormPool/classes/class.IAFPFormsGUI.php b/Modules/IndividualAssessmentFormPool/classes/class.IAFPFormsGUI.php
new file mode 100644
index 000000000000..05d9490760bc
--- /dev/null
+++ b/Modules/IndividualAssessmentFormPool/classes/class.IAFPFormsGUI.php
@@ -0,0 +1,377 @@
+url_builder,
+ $this->action_token,
+ $this->rowid_token
+ ) = $url_builder->acquireParameters($namespace, 'act', 'fid');
+ }
+
+ public function executeCommand(): void
+ {
+ $next_class = $this->ctrl->getNextClass($this);
+ $cmd = $this->ctrl->getCmd() ?? self::CMD_VIEW;
+
+ //$this->addToNavigationHistory();
+ //$this->prepareOutput();
+
+ switch ($next_class) {
+ /*
+ case 'ilinfoscreengui':
+ $this->tabs_gui->activateTab(self::TAB_INFO);
+ $this->ctrl->forwardCommand(new ilInfoScreenGUI($this));
+ break;
+ */
+ default:
+
+ if ($this->query->has($this->action_token->getName())) {
+ $cmd = $this->query->retrieve(
+ $this->action_token->getName(),
+ $this->refinery->to()->string()
+ );
+ $ids = $this->query->retrieve(
+ $this->rowid_token->getName(),
+ $this->refinery->byTrying([
+ $this->refinery->to()->listOf($this->refinery->kindlyTo()->int()),
+ $this->refinery->custom()->transformation($this->getAllIds()),
+ $this->refinery->always([])
+ ])
+ );
+ }
+
+ switch ($cmd) {
+ case self::CMD_LIST:
+ //access $this->object->accessHandler()->simulateMember()
+ $list = $this->listForms();
+ if ($list !== '') {
+ $this->tpl->setContent($list);
+ break;
+ }
+ //$this->ctrl->redirect($this, self:CMD_EDIT);
+ // no break
+ case self::CMD_CREATE:
+ $frm = $this->getEditForm($this->forms_repo->getNewForm($this->iafp_obj_id));
+ $this->tpl->setContent($this->ui_renderer->render($frm));
+ break;
+
+ case self::CMD_EDIT:
+ //access $this->object->accessHandler()->simulateMember()
+ $form_id = array_shift($ids);
+ $ui_form = $this->getEditForm($this->forms_repo->getFormById($form_id));
+
+ $out = $this->getFieldSelection($form_id);
+ $out[] = $ui_form;
+
+ $this->tpl->setContent($this->ui_renderer->render($out));
+ break;
+
+ case self::CMD_SAVE:
+ $form_id = array_shift($ids);
+ $form = $form_id === -1 ?
+ $this->forms_repo->getNewForm($this->iafp_obj_id) : $this->forms_repo->getFormById($form_id);
+
+ //access $this->object->accessHandler()->simulateMember()
+ $ui_form = $this->getEditForm($form);
+ $this->tpl->setContent($this->ui_renderer->render($this->save($ui_form)));
+ break;
+
+ case self::CMD_ADDFIELDS:
+ $form_id = array_shift($ids);
+ $this->addFields($form_id);
+ $url = $this->getUrlString(self::CMD_EDIT, $form_id);
+ $this->ctrl->redirectToURL($url);
+
+ // no break
+ case self::CMD_PREVIEW:
+ $form_id = array_shift($ids);
+ $form = $this->forms_repo->getFormById($form_id);
+ /*
+ $fields = array_map(
+ fn($f) => $f->build($this->ui_factory->input()->field()),
+ $form->getFields()
+ );
+ */
+ $fields = array_map(
+ fn($f) => $this->field_builder->build($f),
+ $form->getFields()
+ );
+
+
+ echo $this->ui_renderer->render(
+ $this->ui_factory->input()->field()->section(
+ $fields,
+ $this->lng->txt('preview')
+ )->withDisabled(true)
+ );
+ exit();
+
+ case self::CMD_DELETE:
+ echo $this->ui_renderer->renderAsync(
+ $this->getDeleteConfirmation($ids)
+ );
+ exit();
+ case self::CMD_DELETE_CONFIRMED:
+ $this->forms_repo->deleteFormsByIds($ids);
+ $this->ctrl->redirect($this, self::CMD_LIST);
+
+ // no break
+ default:
+ throw new \Exception('no such command: ' . $cmd);
+ }
+ }
+ //$this->addHeaderAction();
+ }
+
+ protected function getTableActions(): array
+ {
+ $f = $this->ui_factory->table()->action();
+ return [
+ 'edit' => $f->single(
+ $this->txt('edit'),
+ $this->url_builder->withParameter($this->action_token, self::CMD_EDIT),
+ $this->rowid_token
+ ),
+ 'preview' => $f->single(
+ $this->txt('preview'),
+ $this->url_builder->withParameter($this->action_token, self::CMD_PREVIEW),
+ $this->rowid_token
+ )->withAsync(),
+ 'delete' => $f->standard(
+ $this->txt('delete'),
+ $this->url_builder->withParameter($this->action_token, 'delete'),
+ $this->rowid_token
+ )->withAsync(),
+ ];
+ }
+
+ protected function getUrlString(string $cmd, int|array $row_ids): string
+ {
+ $row_ids = is_array($row_ids) ? $row_ids : [$row_ids];
+ return $this->url_builder
+ ->withParameter($this->action_token, $cmd)
+ ->withParameter($this->rowid_token, $row_ids)
+ ->buildURI()
+ ->__toString();
+ }
+
+ protected function listForms(): string
+ {
+ $new_entry = $this->ui_factory->button()->primary(
+ $this->txt('new_form'),
+ $this->ctrl->getLinkTarget($this, self::CMD_CREATE)
+ );
+
+ $table = $this->ui_factory->table()
+ ->data(
+ $this->txt('iafp_forms'),
+ $this->data_retrieval->getColumns(),
+ $this->data_retrieval
+ )
+ ->withId('iafp_forms_table_' . $this->iafp_obj_id)
+ ->withActions($this->getTableActions())
+ ->withRequest($this->request);
+
+ return $this->ui_renderer->render([
+ $new_entry,
+ $table
+ ]);
+ }
+
+ protected function getEditForm(Form $form): UIForm
+ {
+ return $this->ui_factory->input()->container()->form()->standard(
+ $this->getUrlString(self::CMD_SAVE, $form->getFormId()),
+ [
+ $form->toFormInput(
+ $this->ui_factory->input()->field(),
+ $this->ui_renderer,
+ $this->lng,
+ $this->refinery,
+ $this->iafp_obj_id,
+ $this->forms_repo,
+ $this->field_builder
+ )
+ ]
+ )
+ ->withAdditionalTransformation(
+ $this->refinery->custom()->transformation(
+ fn($v) => array_shift($v)
+ )
+ );
+ }
+
+ protected function getFieldSelection(int $form_id): array
+ {
+ $options = [];
+ $available_fields = $this->forms_repo->getFieldsForObjId($this->iafp_obj_id);
+ foreach ($available_fields as $field) {
+ $options[$field->getFieldId()] = $field->getName();
+ }
+
+ $modal = $this->ui_factory->modal()->roundtrip(
+ $this->lng->txt('add_fields'),
+ null,
+ [
+ $this->ui_factory->input()->field()->multiselect(
+ $this->lng->txt('pick_fields'),
+ $options
+ )
+ ],
+ $this->getUrlString(self::CMD_ADDFIELDS, $form_id)
+ )
+ ->withAdditionalTransformation(
+ $this->refinery->custom()->transformation(
+ fn($v) => array_shift($v)
+ )
+ );
+
+ $button = $this->ui_factory->button()->primary(
+ $this->lng->txt('add_fields'),
+ $modal->getShowSignal()
+ );
+ return [$button, $modal];
+ }
+
+
+ protected function addFields(int $form_id): void
+ {
+ list($button, $modal) = $this->getFieldSelection($form_id);
+ $data = $modal->withRequest($this->request)->getData();
+ if ($data !== null) {
+ $form = $this->forms_repo->getFormById($form_id);
+ $fields = $form->getFields();
+ $field_ids = array_map(fn($f) => $f->getFieldId(), $fields);
+ foreach ($data as $field_id) {
+ if (! in_array((int) $field_id, $field_ids)) {
+ $fields[] = $this->forms_repo->getFieldById((int) $field_id);
+ }
+ }
+ $this->forms_repo->storeForm($form->withFields(...$fields));
+ $this->tpl->setOnScreenMessage('success', $this->lng->txt('fields_added'), true);
+ }
+ }
+
+ protected function save(UIForm $ui_form): UIForm
+ {
+ $ui_form = $ui_form->withRequest($this->request);
+ $form = $ui_form->getData();
+ if ($form === null) {
+ return $ui_form;
+ }
+ $form_id = $this->forms_repo->storeForm($form);
+ $this->tpl->setOnScreenMessage('success', $this->lng->txt('object_saved'), true);
+
+ $url = $this->getUrlString(self::CMD_EDIT, $form_id);
+ $this->ctrl->redirectToURL($url);
+ }
+
+ protected function getDeleteConfirmation(array $ids): MessageBox
+ {
+ $msg = 'Delete?'
+ . $this->ui_renderer->render(
+ $this->ui_factory->listing()->unordered(
+ array_map('strval', $ids)
+ )
+ );
+
+ $buttons = [
+ $this->ui_factory->button()->standard(
+ $this->lng->txt('confirm_delete'),
+ $this->getUrlString(self::CMD_DELETE_CONFIRMED, $ids)
+ ),
+ $this->ui_factory->button()->standard(
+ $this->lng->txt('confirm_cancel'),
+ $this->ctrl->getLinkTarget($this, self::CMD_LIST)
+ ),
+ ];
+ return $this->ui_factory->messageBox()->confirmation($msg)
+ ->withButtons($buttons);
+ }
+
+ protected function getAllIds(): Closure
+ {
+ $repo = $this->forms_repo;
+ $obj_id = $this->iafp_obj_id;
+ return function ($v) use ($repo, $obj_id): array {
+ if (array_shift($v) === 'ALL_OBJECTS') {
+ return $repo->getAllFormIdsForObjId($obj_id);
+ }
+ throw new \InvalidArgumentException('no ids');
+ };
+ }
+
+ protected function txt(string $code): string
+ {
+ return $this->lng->txt($code);
+ }
+}
diff --git a/Modules/IndividualAssessmentFormPool/classes/class.ilIndividualAssessmentFormPoolDIC.php b/Modules/IndividualAssessmentFormPool/classes/class.ilIndividualAssessmentFormPoolDIC.php
new file mode 100644
index 000000000000..c16009259bda
--- /dev/null
+++ b/Modules/IndividualAssessmentFormPool/classes/class.ilIndividualAssessmentFormPoolDIC.php
@@ -0,0 +1,131 @@
+
+ new URLBuilder(
+ (new DataFactory())->uri($DIC['http']->request()->getUri()->__toString())
+ );
+
+ $container['fieldbuilder'] = static fn(): FieldBuilder =>
+ new FieldBuilder(
+ $DIC['ui.factory']->input()->field(),
+ $DIC['refinery'],
+ $DIC['lng'],
+ new \ilUIDemoFileUploadHandlerGUI(),
+ new \ilUIMarkdownPreviewGUI()
+ );
+
+ $container['repo.forms'] = static fn(): FormsStorageDB =>
+ new FormsStorageDB(
+ $DIC['ilDB'],
+ $DIC['ilAccess']
+ );
+ return $container;
+ }
+
+ public function getObjectDIC(
+ ilObjIndividualAssessmentFormPool $object,
+ ArrayAccess $DIC
+ ): Container {
+ $container = new Container();
+ $general_dic = self::getGeneralDIC($DIC);
+
+ $container['repo.forms'] = static fn(): FormsStorageDB => $general_dic['repo.forms'];
+ $container['urlbuilder'] = static fn($c): URLBuilder => $general_dic['urlbuilder'];
+ $container['fieldbuilder'] = static fn($c): FieldBuilder => $general_dic['fieldbuilder'];
+
+ $container['gui.forms'] = static fn($c): IAFPFormsGUI =>
+ new IAFPFormsGUI(
+ $c['access'],
+ $DIC['tpl'],
+ $DIC['ilCtrl'],
+ $DIC['ui.factory'],
+ $DIC['ui.renderer'],
+ $DIC['refinery'],
+ $DIC['http']->request(),
+ $DIC['http']->wrapper()->query(),
+ $DIC['lng'],
+ $c['repo.forms'],
+ $c['dataretrieval.forms'],
+ $c['urlbuilder'],
+ $c['fieldbuilder'],
+ $object->getId(),
+ );
+
+ $container['gui.fields'] = static fn($c): IAFPFieldsGUI =>
+ new IAFPFieldsGUI(
+ $c['access'],
+ $DIC['tpl'],
+ $DIC['ilCtrl'],
+ $DIC['ui.factory'],
+ $DIC['ui.renderer'],
+ $DIC['refinery'],
+ $DIC['http']->request(),
+ $DIC['http']->wrapper()->query(),
+ $DIC['lng'],
+ $c['repo.forms'],
+ $c['dataretrieval.fields'],
+ $c['urlbuilder'],
+ $c['fieldbuilder'],
+ $object->getId(),
+ );
+
+ $container['access'] = static fn(): IAFPAccessHandler =>
+ new IAFPAccessHandler(
+ $DIC['ilAccess'],
+ $DIC['rbacreview'],
+ $DIC['ilUser']->getId(),
+ $object->getRefId()
+ );
+
+ $container['dataretrieval.forms'] = static fn($c): FormsDataRetrieval =>
+ new FormsDataRetrieval(
+ $c['repo.forms'],
+ $DIC['ui.factory'],
+ $DIC['lng'],
+ $object->getId(),
+ );
+ $container['dataretrieval.fields'] = static fn($c): FieldsDataRetrieval =>
+ new FieldsDataRetrieval(
+ $c['repo.forms'],
+ $DIC['ui.factory'],
+ $DIC['lng'],
+ $object->getId(),
+ );
+
+ return $container;
+ }
+}
diff --git a/Modules/IndividualAssessmentFormPool/classes/class.ilObjIndividualAssessmentFormPool.php b/Modules/IndividualAssessmentFormPool/classes/class.ilObjIndividualAssessmentFormPool.php
new file mode 100644
index 000000000000..622d90d24580
--- /dev/null
+++ b/Modules/IndividualAssessmentFormPool/classes/class.ilObjIndividualAssessmentFormPool.php
@@ -0,0 +1,149 @@
+type = self::OBJ_TYPE;
+ parent::__construct($id, $call_by_reference);
+
+ global $DIC;
+ $this->dic = $this->getObjectDIC($this, $DIC);
+ $this->repo = $this->dic['repo.forms'];
+ $this->access = $this->dic['access'];
+
+ }
+
+ public static function getRepository(): FormsStorageDB
+ {
+ global $DIC;
+ return self::getGeneralDIC($DIC)['repo.forms'];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function create(): int
+ {
+ $id = parent::create();
+ return $id;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function read(): void
+ {
+ parent::read();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function delete(): bool
+ {
+ $form_ids = $this->repo->getAllFormIdsForObjId($this->getId());
+ $fields = $this->repo->getFieldsForObjId($this->getId());
+ $field_ids = array_map(
+ static fn(Field $field): int => $field->getFieldId(),
+ iterator_to_array($fields)
+ );
+ $this->repo->deleteFormsByIds($form_ids);
+ $this->repo->deleteFieldsByIds($field_ids);
+ return parent::delete();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function update(): bool
+ {
+ parent::update();
+ return true;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function initDefaultRoles(): void
+ {
+ //$this->access_handler->initDefaultRolesForObject($this);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function cloneObject(int $target_id, int $copy_id = 0, bool $omit_tree = false): ?ilObject
+ {
+ $new_obj = parent::cloneObject($target_id, $copy_id, $omit_tree);
+ $forms = $this->repo->getFormsForObjId($this->getId());
+ $fields = $this->repo->getFieldsForObjId($this->getId());
+
+ $field_mapping = [];
+ foreach ($fields as $field) {
+ $nu_field = $this->repo->storeField(
+ $field
+ ->asNew()
+ ->withObjId($new_obj->getId())
+ );
+ $field_mapping[$field->getFieldId()] = $nu_field->getFieldId();
+ }
+
+
+ $nu_forms = [];
+ foreach ($forms as $form) {
+ $fields = array_map(
+ static fn(Field $field): Field => $field
+ ->withFieldId($field_mapping[$field->getFieldId()])
+ ->withObjId($new_obj->getId()),
+ $form->getFields()
+ );
+
+ $this->repo->storeForm(
+ $form
+ ->asNew()
+ ->withObjId($new_obj->getId())
+ ->withFields(...$fields)
+ );
+ }
+
+ return $new_obj;
+ }
+
+
+ public function getDic(): Pimple\Container
+ {
+ return $this->dic;
+ }
+
+}
diff --git a/Modules/IndividualAssessmentFormPool/classes/class.ilObjIndividualAssessmentFormPoolAccess.php b/Modules/IndividualAssessmentFormPool/classes/class.ilObjIndividualAssessmentFormPoolAccess.php
new file mode 100644
index 000000000000..9aa565decbb4
--- /dev/null
+++ b/Modules/IndividualAssessmentFormPool/classes/class.ilObjIndividualAssessmentFormPoolAccess.php
@@ -0,0 +1,34 @@
+ "read", "cmd" => "", "lang_var" => "show", "default" => true],
+ ["permission" => "write", "cmd" => "edit", "lang_var" => "settings", "default" => false]
+ ];
+ }
+}
diff --git a/Modules/IndividualAssessmentFormPool/classes/class.ilObjIndividualAssessmentFormPoolGUI.php b/Modules/IndividualAssessmentFormPool/classes/class.ilObjIndividualAssessmentFormPoolGUI.php
new file mode 100644
index 000000000000..7ab5a1519961
--- /dev/null
+++ b/Modules/IndividualAssessmentFormPool/classes/class.ilObjIndividualAssessmentFormPoolGUI.php
@@ -0,0 +1,343 @@
+type = 'iafp';
+
+ global $DIC;
+ $this->navigation_history = $DIC['ilNavigationHistory'];
+ $this->tpl = $DIC['tpl'];
+ $this->ctrl = $DIC['ilCtrl'];
+ $this->usr = $DIC['ilUser'];
+ $this->error_object = $DIC['ilErr'];
+ $this->lng = $DIC['lng'];
+ $this->lng->loadLanguageModule('iafp');
+ $this->tpl->loadStandardTemplate();
+ $this->refinery = $DIC->refinery();
+ $this->request_wrapper = $DIC->http()->wrapper()->query();
+
+ parent::__construct($data, $id, $call_by_reference, $prepare_output);
+ }
+
+ public function executeCommand(): void
+ {
+ $next_class = $this->ctrl->getNextClass($this);
+ $cmd = $this->ctrl->getCmd(self::CMD_VIEW);
+
+ $this->addToNavigationHistory();
+ $this->prepareOutput();
+
+ switch ($next_class) {
+ case 'ilinfoscreengui':
+ $this->tabs_gui->activateTab(self::TAB_INFO);
+ $this->ctrl->forwardCommand(new ilInfoScreenGUI($this));
+ break;
+ case "ilexportgui":
+ $this->tabs_gui->activateTab(self::TAB_EXPORT);
+ $exp_gui = new ilExportGUI($this); // $this is the ilObj...GUI class of the resource
+ $exp_gui->addFormat("xml");
+ $this->ctrl->forwardCommand($exp_gui);
+ break;
+ case 'ilpermissiongui':
+ $this->tabs_gui->activateTab(self::TAB_PERMISSION);
+ $this->ctrl->forwardCommand(new ilPermissionGUI($this));
+ break;
+ case "ilcommonactiondispatchergui":
+ $gui = ilCommonActionDispatcherGUI::getInstanceFromAjaxCall();
+ $this->ctrl->forwardCommand($gui);
+ break;
+ case 'ilobjectcopygui':
+ $cp = new ilObjectCopyGUI($this);
+ $this->ctrl->forwardCommand(new ilObjectCopyGUI($this));
+ break;
+
+ case 'iafpformsgui':
+ $this->tabs_gui->activateTab(self::TAB_FORMS);
+ $gui = $this->object->getDic()['gui.forms'];
+ $this->ctrl->forwardCommand($gui);
+ break;
+ case 'iafpfieldsgui':
+ $this->tabs_gui->activateTab(self::TAB_FIELDS);
+ $gui = $this->object->getDic()['gui.fields'];
+ $this->ctrl->forwardCommand($gui);
+ break;
+
+ default:
+ switch ($cmd) {
+ case 'create':
+ parent::createObject();
+ break;
+ case 'cancel':
+ parent::cancelObject();
+ break;
+ case self::CMD_VIEW:
+ case self::CMD_INFO:
+ $this->checkPermission('visible');
+ $this->ctrl->redirectByClass('ilinfoscreengui', 'showSummary');
+ break;
+ case self::CMD_EDIT:
+ $this->checkPermission('write');
+ $this->tabs_gui->activateTab(self::TAB_SETTINGS);
+ $this->edit();
+ break;
+ case self::CMD_SAVE:
+ if ($this->getCreationMode()) {
+ parent::saveObject();
+ $this->ctrl->redirectToURL(
+ $this->getLinkTarget(self::CMD_EDIT)
+ );
+ }
+ $this->checkPermission('write');
+ $this->tabs_gui->activateTab(self::TAB_SETTINGS);
+ $this->save();
+ break;
+
+ default:
+ throw new \Exception('no such command: ' . $cmd);
+ }
+ }
+ $this->addHeaderAction();
+ }
+
+ public function edit(): void
+ {
+ $form = $this->initPropertiesForm();
+ $this->tpl->setContent($this->ui_renderer->render($form));
+ }
+
+ public function save(): void
+ {
+ $form = $this->initPropertiesForm()->withRequest($this->request);
+ $data = $form->getData();
+
+ if ($data === null) {
+ $this->tpl->setOnScreenMessage('failure', $this->lng->txt("form_input_not_valid"), true);
+ } else {
+ list($title_and_desc, $online) = $data;
+ $this->object->getObjectProperties()->storePropertyTitleAndDescription($title_and_desc);
+ $this->object->getObjectProperties()->storePropertyIsOnline($online);
+
+ $this->tpl->setOnScreenMessage('success', $this->lng->txt("msg_obj_modified"), true);
+ }
+ $this->tpl->setContent($this->ui_renderer->render($form));
+ }
+
+ protected function initPropertiesForm(): Form
+ {
+ $shift = $this->refinery->custom()->transformation(
+ fn($v) => array_shift($v)
+ );
+
+ $title_and_description = $this->object->getObjectProperties()->getPropertyTitleAndDescription()->toForm(
+ $this->lng,
+ $this->ui_factory->input()->field(),
+ $this->refinery
+ );
+
+ $online = $this->object->getObjectProperties()->getPropertyIsOnline()->toForm(
+ $this->lng,
+ $this->ui_factory->input()->field(),
+ $this->refinery
+ );
+
+ $settings = $this->ui_factory->input()->field()->section(
+ [$title_and_description],
+ $this->lng->txt('iafp_settings')
+ )->withAdditionalTransformation(
+ $shift
+ );
+
+ $availability = $this->ui_factory->input()->field()->section(
+ [$online],
+ $this->lng->txt('iafp_settings_availability')
+ )->withAdditionalTransformation(
+ $shift
+ );
+
+ return $this->ui_factory->input()->container()->form()->standard(
+ $this->ctrl->getLinkTargetByClass(self::class, self::CMD_SAVE),
+ [$settings, $availability]
+ );
+ }
+
+ protected function getTabs(): void
+ {
+ $this->tabs_gui->addTab(
+ self::TAB_INFO,
+ $this->txt('info_short'),
+ $this->ctrl->getLinkTargetByClass('ilinfoscreengui', 'showSummary'),
+ );
+
+ if ($this->object->getDic()['access']->mayEdit()) {
+ $this->tabs_gui->addTab(
+ self::TAB_SETTINGS,
+ $this->txt('settings'),
+ $this->ctrl->getLinkTarget($this, self::CMD_EDIT)
+ );
+ $this->tabs_gui->addTab(
+ self::TAB_FORMS,
+ $this->txt(self::TAB_FORMS),
+ $this->ctrl->getLinkTargetByClass('iafpformsgui', IAFPFormsGUI::CMD_LIST)
+ );
+ $this->tabs_gui->addTab(
+ self::TAB_FIELDS,
+ $this->txt(self::TAB_FIELDS),
+ $this->ctrl->getLinkTargetByClass('iafpfieldsgui', IAFPFieldsGUI::CMD_LIST)
+ );
+ }
+
+ /*
+ $this->tabs_gui->addTab(
+ self::TAB_EXPORT,
+ $this->txt('export'),
+ $this->ctrl->getLinkTargetByClass('ilexportgui', ''),
+ );
+ */
+ $this->tabs_gui->addTab(
+ self::TAB_PERMISSION,
+ $this->txt('perm_settings'),
+ $this->ctrl->getLinkTargetByClass('ilpermissiongui', 'perm'),
+ );
+ }
+
+ public function handleAccessViolation(): void
+ {
+ $this->error_object->raiseError($this->txt("msg_no_perm_read"), $this->error_object->WARNING);
+ }
+
+ public static function _goto(string $a_target, string $a_add = ''): void
+ {
+ global $DIC;
+ $a_target = (int) $a_target;
+ if ($DIC['ilAccess']->checkAccess('write', '', $a_target)) {
+ ilObjectGUI::_gotoRepositoryNode($a_target, 'edit');
+ }
+ if ($DIC['ilAccess']->checkAccess('read', '', $a_target)) {
+ ilObjectGUI::_gotoRepositoryNode($a_target);
+ }
+ }
+
+ protected function addLocatorItems(): void
+ {
+ if (is_object($this->object)) {
+ $this->locator->addItem(
+ $this->object->getTitle(),
+ $this->ctrl->getLinkTarget($this, "view"),
+ "",
+ $this->object->getRefId()
+ );
+ }
+ }
+
+ public function viewObject(): void
+ {
+ $this->tabs_gui->activateTab(self::TAB_INFO);
+ $this->ctrl->setCmd('showSummary');
+ $this->ctrl->setCmdClass('ilinfoscreengui');
+ $info = $this->buildInfoScreen();
+ $this->ctrl->forwardCommand($info);
+ $this->recordIndividualAssessmentRead();
+ }
+
+ public function membersObject(): void
+ {
+ $this->tabs_gui->activateTab(self::TAB_MEMBERS);
+ $gui = $this->object->getMembersGUI();
+ $this->ctrl->forwardCommand($gui);
+ }
+
+ protected function getLinkTarget(string $cmd): string
+ {
+ if ($cmd == 'settings') {
+ return $this->ctrl->getLinkTargetByClass('ilindividualassessmentsettingsgui', 'edit');
+ }
+ if ($cmd == 'info') {
+ return $this->ctrl->getLinkTarget($this, 'view');
+ }
+ if ($cmd == 'members') {
+ return $this->ctrl->getLinkTargetByClass('ilindividualassessmentmembersgui', 'view');
+ }
+ return $this->ctrl->getLinkTarget($this, $cmd);
+ }
+
+ public function editObject(): void
+ {
+ $link = $this->getLinkTarget('settings');
+ $this->ctrl->redirectToURL($link);
+ }
+
+ public function getBaseEditForm(): ilPropertyFormGUI
+ {
+ return $this->initEditForm();
+ }
+
+ protected function afterSave(ilObject $new_object): void
+ {
+ $this->tpl->setOnScreenMessage("success", $this->txt("iafp_added"), true);
+ $this->ctrl->setParameter($this, "ref_id", $new_object->getRefId());
+ }
+
+ private function addToNavigationHistory(): void
+ {
+ if (!$this->getCreationMode()) {
+ $ref_id = $this->request_wrapper->retrieve("ref_id", $this->refinery->kindlyTo()->int());
+ $link = ilLink::_getLink($ref_id, ilObjIndividualAssessmentFormPool::OBJ_TYPE);
+ $this->navigation_history->addItem($ref_id, $link, ilObjIndividualAssessmentFormPool::OBJ_TYPE);
+ }
+ }
+
+ protected function txt(string $code): string
+ {
+ return $this->lng->txt($code);
+ }
+}
diff --git a/Modules/IndividualAssessmentFormPool/classes/class.ilObjIndividualAssessmentFormPoolListGUI.php b/Modules/IndividualAssessmentFormPool/classes/class.ilObjIndividualAssessmentFormPoolListGUI.php
new file mode 100644
index 000000000000..0cd1041a8520
--- /dev/null
+++ b/Modules/IndividualAssessmentFormPool/classes/class.ilObjIndividualAssessmentFormPoolListGUI.php
@@ -0,0 +1,67 @@
+static_link_enabled = true;
+ $this->delete_enabled = true;
+ $this->cut_enabled = true;
+ $this->copy_enabled = true;
+ $this->subscribe_enabled = true;
+ $this->link_enabled = true;
+ $this->info_screen_enabled = true;
+ $this->type = "iass";
+ $this->gui_class_name = "ilObjIndividualAssessmentFormPoolGUI";
+
+
+ // $this->substitutions = ilAdvancedMDSubstitution::_getInstanceByObjectType($this->type);
+ // $this->enableSubstitutions($this->substitutions->isActive());
+
+ // general commands array
+ $this->commands = ilObjIndividualAssessmentFormPoolAccess::_getCommands();
+ }
+
+ /**
+ * Get command target frame
+ *
+ * @param string $cmd command
+ *
+ * @return string command target frame
+ */
+ public function getCommandFrame(string $cmd): string
+ {
+ return ilFrameTargetInfo::_getFrame("MainContent");
+ }
+
+ public function getCommandLink(string $cmd): string
+ {
+ switch ($cmd) {
+ case 'infoScreen':
+ $return = $this->ctrl->getLinkTargetByClass($this->gui_class_name, "view");
+ break;
+ default:
+ $return = parent::getCommandLink($cmd);
+ }
+
+ return $return;
+ }
+}
diff --git a/Modules/IndividualAssessmentFormPool/maintenance.json b/Modules/IndividualAssessmentFormPool/maintenance.json
new file mode 100644
index 000000000000..f5d64fc18270
--- /dev/null
+++ b/Modules/IndividualAssessmentFormPool/maintenance.json
@@ -0,0 +1,14 @@
+{
+ "maintenance_model": "Classic",
+ "first_maintainer": "rklees(34047)",
+ "second_maintainer": "",
+ "implicit_maintainers": [],
+ "coordinator": [
+ ""
+ ],
+ "tester": "kunkel(115)",
+ "testcase_writer": "",
+ "path": "Modules/IndividualAssessment",
+ "belong_to_component": "IndividualAssessment",
+ "used_in_components": []
+}
\ No newline at end of file
diff --git a/Modules/IndividualAssessmentFormPool/module.xml b/Modules/IndividualAssessmentFormPool/module.xml
new file mode 100644
index 000000000000..55cc2bd7bc1f
--- /dev/null
+++ b/Modules/IndividualAssessmentFormPool/module.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Modules/IndividualAssessmentFormPool/test/FieldBuilderMockFactory.php b/Modules/IndividualAssessmentFormPool/test/FieldBuilderMockFactory.php
new file mode 100644
index 000000000000..01b9a95793f8
--- /dev/null
+++ b/Modules/IndividualAssessmentFormPool/test/FieldBuilderMockFactory.php
@@ -0,0 +1,73 @@
+createMock(UploadLimitResolver::class),
+ new \IncrementalSignalGenerator(),
+ new \ILIAS\Data\Factory(),
+ $this->getRefinery(),
+ $this->getLanguage()
+ );
+ }
+
+ protected function getRefinery(): Refinery
+ {
+ return new Refinery(
+ new \ILIAS\Data\Factory(),
+ $this->getLanguage()
+ );
+ }
+
+ protected function getLanguage(): \ilLanguage
+ {
+ return new class () extends \ilLanguage {
+ public function __construct()
+ {
+ }
+
+ public function loadLanguageModule(string $a_module): void
+ {
+ }
+ };
+ }
+
+ protected function getFieldBuilder(): FieldBuilder
+ {
+ return new FieldBuilder(
+ $this->getFieldFactory(),
+ $this->getRefinery(),
+ $this->getLanguage(),
+ $this->createMock(UploadHandler::class),
+ $this->createMock(\ilUIMarkdownPreviewGUI::class),
+ );
+ }
+}
diff --git a/Modules/IndividualAssessmentFormPool/test/FieldBuilderTest.php b/Modules/IndividualAssessmentFormPool/test/FieldBuilderTest.php
new file mode 100644
index 000000000000..9d96756d68c6
--- /dev/null
+++ b/Modules/IndividualAssessmentFormPool/test/FieldBuilderTest.php
@@ -0,0 +1,92 @@
+builder = $this->getFieldBuilder();
+ }
+
+ public function fieldBuilderData(): array
+ {
+ return [
+ [new FieldConfig(FieldType::MARKDOWN),
+ 'some value',
+ Field\Markdown::class,
+ 'some value'
+ ],
+ [new FieldConfig(FieldType::FILE),
+ 'some_id',
+ Field\File::class,
+ ['some_id' => 'some_id']
+ ],
+ [new FieldConfig(FieldType::DATETIME),
+ '1742784914',
+ Field\DateTime::class,
+ '2025-03-24 02:55'
+ ],
+ [new FieldConfig(FieldType::SINGLESELECT, '', '', null, ['AA', 'BB', 'CC']),
+ 'BB',
+ Field\Select::class,
+ 'BB'
+ ],
+ [new FieldConfig(FieldType::TAG, '', '', null, ['a', 'b', 'c']),
+ implode(\SpecifiedFormStorageDB::VALUE_DELIMITER, ['c' , 'a']),
+ Field\Tag::class,
+ ['c' , 'a']
+ ],
+ [new FieldConfig(FieldType::MARKDOWN),null,Field\Markdown::class,''],
+ [new FieldConfig(FieldType::FILE),null,Field\File::class,[]],
+ [new FieldConfig(FieldType::DATETIME),null,Field\DateTime::class,''],
+ [new FieldConfig(FieldType::SINGLESELECT),null,Field\Select::class,''],
+ [new FieldConfig(FieldType::TAG),null,Field\Tag::class,null],
+
+ ];
+ }
+
+ /**
+ * @dataProvider fieldBuilderData
+ */
+ public function testBuildField(
+ FieldConfig $cfg,
+ mixed $value,
+ string $expected_class,
+ mixed $expected_value
+ ): void {
+ $f = $this->builder->build($cfg, $value);
+ $this->assertInstanceOf($expected_class, $f);
+ $this->assertEquals($expected_value, $f->getValue());
+ }
+
+}
diff --git a/Modules/IndividualAssessmentFormPool/test/IAFPFormsStorageTest.php b/Modules/IndividualAssessmentFormPool/test/IAFPFormsStorageTest.php
new file mode 100644
index 000000000000..242ba0b02230
--- /dev/null
+++ b/Modules/IndividualAssessmentFormPool/test/IAFPFormsStorageTest.php
@@ -0,0 +1,71 @@
+createMock(\ilDBInterface::class);
+ $ildb->method('nextId')
+ ->willReturn(-2);
+ $this->db = new FormsStorageDB(
+ $ildb,
+ $this->createMock(\ilAccess::class)
+ );
+ }
+
+ public function testIAFPGetNewForm(): void
+ {
+ $form = $this->db->getNewForm(777);
+ $this->assertInstanceOf(Form::class, $form);
+ $this->assertEquals(777, $form->getObjId());
+ $this->assertEquals(-1, $form->getFormId());
+ }
+
+ public function getFieldTypes(): array
+ {
+ return array_map(
+ fn($t) => [$t],
+ FieldType::cases()
+ );
+ }
+
+ /**
+ * @dataProvider getFieldTypes
+ */
+ public function testIAFPCreateField(FieldType $type): void
+ {
+ $field = $this->db->createField($type, 777);
+ $this->assertInstanceOf(FieldConfig::class, $field->getConfig());
+ $this->assertEquals(-2, $field->getFieldId()); //next id
+ $this->assertEquals(777, $field->getObjId());
+ $this->assertEquals($type, $field->getConfig()->getType());
+ }
+
+}
diff --git a/Modules/IndividualAssessmentFormPool/test/ilModulesIndividualAssessmentFormPoolSuite.php b/Modules/IndividualAssessmentFormPool/test/ilModulesIndividualAssessmentFormPoolSuite.php
new file mode 100644
index 000000000000..970a7803c8a4
--- /dev/null
+++ b/Modules/IndividualAssessmentFormPool/test/ilModulesIndividualAssessmentFormPoolSuite.php
@@ -0,0 +1,36 @@
+addTestSuite('FieldBuilderTest');
+ $suite->addTestSuite('IAFPFormsStorageTest');
+ return $suite;
+ }
+}
diff --git a/Services/Repository/Administration/class.ilObjRepositorySettings.php b/Services/Repository/Administration/class.ilObjRepositorySettings.php
index 2cf866f6bec5..9efdb4e85327 100644
--- a/Services/Repository/Administration/class.ilObjRepositorySettings.php
+++ b/Services/Repository/Administration/class.ilObjRepositorySettings.php
@@ -250,7 +250,7 @@ public static function getDefaultNewItemGrouping(): array
"breaker1" => null,
"content" => ["file", "webr", "feed", "copa", "wiki", "blog", "lm", "htlm", "sahs", 'cmix', 'lti', "lso", "glo", "dcl", "bibl", "mcst", "mep"],
"breaker2" => null,
- "assessment" => ["exc", "tst", "qpl", "iass"],
+ "assessment" => ["exc", "tst", "qpl", "iass", 'iafp'],
"feedback" => ["poll", "svy", "spl"],
"templates" => ["prtt"]
];
diff --git a/Services/Repository/classes/class.ilRepositoryGUI.php b/Services/Repository/classes/class.ilRepositoryGUI.php
index 52c96ffc960b..2d65a88ffd1a 100755
--- a/Services/Repository/classes/class.ilRepositoryGUI.php
+++ b/Services/Repository/classes/class.ilRepositoryGUI.php
@@ -41,6 +41,7 @@
* @ilCtrl_Calls ilRepositoryGUI: ilObjLTIConsumerGUI
* @ilCtrl_Calls ilRepositoryGUI: ilObjCmiXapiGUI
* @ilCtrl_Calls ilRepositoryGUI: ilPermissionGUI
+ * @ilCtrl_Calls ilRepositoryGUI: ilObjIndividualAssessmentFormPoolGUI
*
*/
class ilRepositoryGUI implements ilCtrlBaseClassInterface
diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang
index 629f6f4da4ba..7378b9444af5 100644
--- a/lang/ilias_de.lang
+++ b/lang/ilias_de.lang
@@ -4301,6 +4301,7 @@ common#:#http_not_possible#:#Dieser Server unterstützt keine HTTP-Anfragen.
common#:#http_path#:#HTTP-Pfad
common#:#https_not_possible#:#Dieser Server unterstützt keine HTTPS-Verbindungen.
common#:#i2passwd#:#ILIAS_2-Passwort
+common#:#iafp_new#:#Add Individual Assessment Form Pool
common#:#iass_import#:#Individuelle Bewertung importieren
common#:#iass_new#:#Neue Individuelle Bewertung anlegen
common#:#icon#:#Symbol
@@ -4930,6 +4931,7 @@ common#:#obj_hlps#:#Online-Hilfe
common#:#obj_hlps_desc#:#Einstellungen der Online-Hilfe
common#:#obj_htlm#:#Lernmodul HTML
common#:#obj_htlm_duplicate#:#HTML Lernmodul kopieren
+common#:#obj_iafp#:#Individual Assessment Form Pool
common#:#obj_iass#:#Individuelle Bewertung
common#:#obj_iass_duplicate#:#Individuelle Bewertung kopieren
common#:#obj_iass_select#:#-- Wählen Sie eine Individuelle Bewertung aus --
@@ -5138,6 +5140,7 @@ common#:#objs_glo#:#Glossare
common#:#objs_grp#:#Gruppen
common#:#objs_grpr#:#Gruppenlinks
common#:#objs_htlm#:#HTML-Lernmodule
+common#:#objs_iafp#:#Individual Assessment Form Pool
common#:#objs_iass#:#Individuelle Bewertungen
common#:#objs_itgr#:#Objekteblöcke
common#:#objs_lm#:#ILIAS-Lernmodule
@@ -10394,6 +10397,52 @@ help#:#help_tooltips_and_help#:#Tooltips und Handlungsanweisungen
help#:#help_tooltips_only#:#Nur Tooltips
help#:#help_topcis#:#Themen
help#:#help_tt_text#:#Text
+iafp#:#_settings_saved#:#Settings saved.
+iafp#:#add_fields#:#Add Fields to the Form
+iafp#:#available_for_examiners#:#available for examiners
+iafp#:#confirm_cancel#:#Cancel
+iafp#:#confirm_delete#:#Delete
+iafp#:#default_value#:#Default Value
+iafp#:#field_available_for_examiners#:#Available for Examiners
+iafp#:#field_available_for_examiners_byline#:#should the field be available for examiners?
+iafp#:#field_default_value#:#Default Value
+iafp#:#field_default_value_byline#:#default/initial value
+iafp#:#field_description#:#Description
+iafp#:#field_description_byline#:#description of the field
+iafp#:#field_label#:#Label
+iafp#:#field_label_byline#:#label for the field
+iafp#:#field_name_byline#:#name for the field
+iafp#:#field_options#:#Options
+iafp#:#field_options_byline#:#add options the user may choose from
+iafp#:#field_section_byline#:#configure the field
+iafp#:#field_section_create#:#Edit new Field
+iafp#:#field_section_edit#:#Edit Field
+iafp#:#field_type_byline#:#type of the field
+iafp#:#field_with_notes#:#With Notes
+iafp#:#field_with_notes_byline#:#add a textarea for notes?
+iafp#:#fields#:#Fields in this Form
+iafp#:#fields_added#:#Fields added.
+iafp#:#fields_byline#:#Unselect and save to remove a field
+iafp#:#fields_count#:#amount of fields
+iafp#:#form_description#:#Description
+iafp#:#form_description_byline#:#description of the form
+iafp#:#form_name#:#Name
+iafp#:#form_name_byline#:#name of the form
+iafp#:#form_section_byline#:#configure the form
+iafp#:#form_section_create#:#Edit new Form
+iafp#:#form_section_edit#:#Edit Form
+iafp#:#iafp_fields#:#Fields
+iafp#:#iafp_forms#:#Forms
+iafp#:#iafp_settings#:#Settings
+iafp#:#iafp_settings_availability#:#Availability
+iafp#:#label#:#label
+iafp#:#new_field#:#Add a new Field
+iafp#:#new_form#:#Add a new Form
+iafp#:#object_saved#:#Saved.
+iafp#:#pick_fields#:#Select Fields
+iafp#:#tab_fields#:#Fields
+iafp#:#tab_forms#:#Forms
+iafp#:#with_notes#:#Notes
iass#:#download_assessment_paper#:#Prüfungsblatt herunterladen
iass#:#general#:#Allgemein
iass#:#grading#:#Bewertung
@@ -14059,6 +14108,12 @@ rbac#:#htlm_read#:#HTML-Lernmodul lesen
rbac#:#htlm_read_learning_progress#:#Benutzer kann Lernfortschritt anderer Benutzer einsehen
rbac#:#htlm_visible#:#HTML-Lernmodul ist sichtbar
rbac#:#htlm_write#:#HTML-Lernmodul bearbeiten
+rbac#:#iafp_copy#:#Copy an Individual Assessment Form Pool
+rbac#:#iafp_delete#:#Delete an Individual Assessment Form Pool
+rbac#:#iafp_edit_permission#:#Edit permissions of an Individual Assessment Form Pool
+rbac#:#iafp_read#:#Read content of an Individual Assessment Form Pool
+rbac#:#iafp_visible#:#Individual Assessment Form Pool is visible
+rbac#:#iafp_write#:#Edit an Individual Assessment Form Pool
rbac#:#iass_amend_grading#:#Finalisierte Bewertung der Individuellen Bewertung ändern
rbac#:#iass_copy#:#Individuelle Bewertung kopieren
rbac#:#iass_delete#:#Individuelle Bewertung löschen
diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang
index 334c82a11491..618a038fca73 100644
--- a/lang/ilias_en.lang
+++ b/lang/ilias_en.lang
@@ -4301,6 +4301,7 @@ common#:#http_not_possible#:#This server is not supporting http requests.
common#:#http_path#:#HTTP Path
common#:#https_not_possible#:#This server is not supporting HTTPS connections.
common#:#i2passwd#:#ILIAS 2 password
+common#:#iafp_new#:#Add Individual Assessment Form Pool
common#:#iass_import#:#Import Individual Assessment
common#:#iass_new#:#Create Individual Assessment
common#:#icon#:#Icon
@@ -4930,6 +4931,7 @@ common#:#obj_hlps#:#Help System
common#:#obj_hlps_desc#:#Settings for the online help
common#:#obj_htlm#:#Learning Module HTML
common#:#obj_htlm_duplicate#:#Copy HTML Learning Module
+common#:#obj_iafp#:#Individual Assessment Form Pool
common#:#obj_iass#:#Individual Assessment
common#:#obj_iass_duplicate#:#Copy Individual Assessment
common#:#obj_iass_select#:#-- Please select one individual assessment --
@@ -5138,6 +5140,7 @@ common#:#objs_glo#:#Glossaries
common#:#objs_grp#:#Groups
common#:#objs_grpr#:#Group Links
common#:#objs_htlm#:#HTML Learning Modules
+common#:#objs_iafp#:#Individual Assessment Form Pool
common#:#objs_iass#:#Individual Assessments
common#:#objs_itgr#:#Item Groups
common#:#objs_lm#:#ILIAS Learning Modules
@@ -10394,6 +10397,52 @@ help#:#help_tooltips_and_help#:#Tooltips and Workflow Help
help#:#help_tooltips_only#:#Tooltips Only
help#:#help_topcis#:#Topics
help#:#help_tt_text#:#Text
+iafp#:#_settings_saved#:#Settings saved.
+iafp#:#add_fields#:#Add Fields to the Form
+iafp#:#available_for_examiners#:#available for examiners
+iafp#:#confirm_cancel#:#Cancel
+iafp#:#confirm_delete#:#Delete
+iafp#:#default_value#:#Default Value
+iafp#:#field_available_for_examiners#:#Available for Examiners
+iafp#:#field_available_for_examiners_byline#:#should the field be available for examiners?
+iafp#:#field_default_value#:#Default Value
+iafp#:#field_default_value_byline#:#default/initial value
+iafp#:#field_description#:#Description
+iafp#:#field_description_byline#:#description of the field
+iafp#:#field_label#:#Label
+iafp#:#field_label_byline#:#label for the field
+iafp#:#field_name_byline#:#name for the field
+iafp#:#field_options#:#Options
+iafp#:#field_options_byline#:#add options the user may choose from
+iafp#:#field_section_byline#:#configure the field
+iafp#:#field_section_create#:#Edit new Field
+iafp#:#field_section_edit#:#Edit Field
+iafp#:#field_type_byline#:#type of the field
+iafp#:#field_with_notes#:#With Notes
+iafp#:#field_with_notes_byline#:#add a textarea for notes?
+iafp#:#fields#:#Fields in this Form
+iafp#:#fields_added#:#Fields added.
+iafp#:#fields_byline#:#Unselect and save to remove a field
+iafp#:#fields_count#:#amount of fields
+iafp#:#form_description#:#Description
+iafp#:#form_description_byline#:#description of the form
+iafp#:#form_name#:#Name
+iafp#:#form_name_byline#:#name of the form
+iafp#:#form_section_byline#:#configure the form
+iafp#:#form_section_create#:#Edit new Form
+iafp#:#form_section_edit#:#Edit Form
+iafp#:#iafp_fields#:#Fields
+iafp#:#iafp_forms#:#Forms
+iafp#:#iafp_settings#:#Settings
+iafp#:#iafp_settings_availability#:#Availability
+iafp#:#label#:#label
+iafp#:#new_field#:#Add a new Field
+iafp#:#new_form#:#Add a new Form
+iafp#:#object_saved#:#Saved.
+iafp#:#pick_fields#:#Select Fields
+iafp#:#tab_fields#:#Fields
+iafp#:#tab_forms#:#Forms
+iafp#:#with_notes#:#Notes
iass#:#download_assessment_paper#:#Download Record file
iass#:#general#:#General Information
iass#:#grading#:#Grading
@@ -14059,6 +14108,12 @@ rbac#:#htlm_read#:#User can read HTML learning module
rbac#:#htlm_read_learning_progress#:#User can view learning progress of other users
rbac#:#htlm_visible#:#HTML learning module is visible
rbac#:#htlm_write#:#User can edit content and settings of HTML learning module
+rbac#:#iafp_copy#:#Copy an Individual Assessment Form Pool
+rbac#:#iafp_delete#:#Delete an Individual Assessment Form Pool
+rbac#:#iafp_edit_permission#:#Edit permissions of an Individual Assessment Form Pool
+rbac#:#iafp_read#:#Read content of an Individual Assessment Form Pool
+rbac#:#iafp_visible#:#Individual Assessment Form Pool is visible
+rbac#:#iafp_write#:#Edit an Individual Assessment Form Pool
rbac#:#iass_amend_grading#:#Amend gradings in an Individual Assessment
rbac#:#iass_copy#:#Copy an Individual Assessment
rbac#:#iass_delete#:#Delete an Individual Assessment
diff --git a/templates/default/images/standard/icon_iafp.svg b/templates/default/images/standard/icon_iafp.svg
new file mode 100644
index 000000000000..90fdfcc67519
--- /dev/null
+++ b/templates/default/images/standard/icon_iafp.svg
@@ -0,0 +1,27 @@
+
+
+