diff --git a/.babelrc.json b/.babelrc.json deleted file mode 100755 index bc92ecff3da0..000000000000 --- a/.babelrc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "presets": [ - [ - "@babel/preset-env", - ] - ] -} diff --git a/.eslintrc.json b/.eslintrc.json index 1a92f678f798..04a297f376ad 100755 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -9,14 +9,7 @@ // globals from the browser environment. "document": "readonly", "window": "readonly", - "il": "writable", - // globals from the mocha testing framework. - "beforeEach": "readonly", - "afterEach": "readonly", - "describe": "readonly", - "before": "readonly", - "after": "readonly", - "it": "readonly" + "il": "writable" }, // minified and bundled scripts are exempt from the // code-style. diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 0a103e4a9f57..ba623ab3da0c 100755 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -12,7 +12,7 @@ jobs: fail-fast: false matrix: php: [8.3, 8.4] - nodejs: [ 20.x ] + nodejs: [ 23.x ] steps: - name: Checkout code uses: actions/checkout@v2 @@ -44,7 +44,7 @@ jobs: GHRUN: "yes" - name: JS Unit Test - run: exit 0 # npm test + run: npm test env: GHRUN: "yes" diff --git a/.gitignore b/.gitignore index aa881d21c966..bd4249d785e2 100755 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,4 @@ phpstan-baseline.neon # File Delivery override.php components/ILIAS/FileHandlingDemo +public/data/ diff --git a/.mocharc.json b/.mocharc.json deleted file mode 100755 index 41fb1c30763e..000000000000 --- a/.mocharc.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "require": [ - "@babel/register" - ], - "spec": [ - "components/ILIAS/**/tests/**/*.js" - ] -} diff --git a/Customizing/README.md b/Customizing/README.md deleted file mode 100755 index 315562dee557..000000000000 --- a/Customizing/README.md +++ /dev/null @@ -1,58 +0,0 @@ -# Customizing - - - -1. [Introduction](#introduction) -1. [System Language Changes](#system-language-changes) -1. [Skins and Styles](#skins-and-styles) -1. [Plugins](#plugins) - - - - -## Introduction - -This directory holds all customized files for this ILIAS installation. - -On the top level two directories may be created: `/Customizing/global` for -global changes and `/Customizing/clients` for changes that should be applied to -clients. - -The clients directory holds a subdirectory for each client: - -``` -/Customizing/clients/ -``` - -At the time being, only user agreements can be offered for clients! Customized -skins and languages are only supported globally. - - -## System Language Changes - -You may change terms used in the user interface of ILIAS. To do this, use the -same format as is used in the language files in directory `/lang`. Store the -values to be overwritten in files ending with `.lang.local` and put them into -the `/global/lang` directory. Client specific changes are not supported yet. - -``` -/global/lang/ilias_.lang.local -``` - -Example: - -``` -/global/lang/ilias_en.lang.local -``` - - -## Skins and Styles - -You find all information about how to create your own skin in the [Custom -Styles](/templates/Readme.md#custom-styles) documentation. - - -## Plugins - -Plugins are installed under `/global/plugins`. Each plugin should come with its -own documentation stating the exact target directory. diff --git a/Customizing/global/plugins/.gitkeep b/Customizing/global/plugins/.gitkeep deleted file mode 100755 index e69de29bb2d1..000000000000 diff --git a/cli/entry_point.php b/cli/entry_point.php new file mode 100644 index 000000000000..b38d55b4e5c2 --- /dev/null +++ b/cli/entry_point.php @@ -0,0 +1,39 @@ + */ -class ilAccessRBACSetupAgent extends Setup\Agent\NullAgent +class ilAccessRBACSetupAgent extends NullAgent { /** * @inheritdoc */ - public function getUpdateObjective(?Setup\Config $config = null): Setup\Objective + public function getUpdateObjective(Config $config = null): Objective { - return new ilDatabaseUpdateStepsExecutedObjective(new ilAccessRBACDeleteDbkSteps()); + return new ilDatabaseUpdateStepsExecutedObjective(new AccessControl10DBUpdateSteps()); } /** * @inheritdoc */ - public function getStatusObjective(Setup\Metrics\Storage $storage): Setup\Objective + public function getStatusObjective(Storage $storage): Objective { - return new ilDatabaseUpdateStepsMetricsCollectedObjective($storage, new ilAccessRBACDeleteDbkSteps()); + return new ilDatabaseUpdateStepsMetricsCollectedObjective( + $storage, + new AccessControl10DBUpdateSteps() + ); } } diff --git a/components/ILIAS/AccessControl/classes/class.ilObjRoleFolderGUI.php b/components/ILIAS/AccessControl/classes/class.ilObjRoleFolderGUI.php index 25612cf18a61..27c6eb357907 100755 --- a/components/ILIAS/AccessControl/classes/class.ilObjRoleFolderGUI.php +++ b/components/ILIAS/AccessControl/classes/class.ilObjRoleFolderGUI.php @@ -18,7 +18,6 @@ declare(strict_types=1); -use ILIAS\HTTP\GlobalHttpState; use ILIAS\Refinery\Factory; use ILIAS\UI\Factory as UIFactory; @@ -38,8 +37,6 @@ class ilObjRoleFolderGUI extends ilObjectGUI private ilLogger $logger; protected ilRbacAdmin $rbacadmin; - - protected GlobalHttpState $http; protected Factory $refinery; protected UIFactory $ui_factory; @@ -53,7 +50,6 @@ public function __construct($a_data, int $a_id, bool $a_call_by_reference) $this->logger = $DIC->logger()->ac(); $this->rbacadmin = $DIC['rbacadmin']; - $this->http = $DIC->http(); $this->refinery = $DIC->refinery(); $this->ui_factory = $DIC['ui.factory']; @@ -98,7 +94,7 @@ protected function initCopySourceFromGET(): int } /** - * @return int[] + * @return array */ protected function initRolesFromPOST(): array { @@ -128,6 +124,31 @@ public function returnObject(): void $this->viewObject(); } + protected function buildTargetNamesString(): string + { + $targets = $this->initRolesFromPOST(); + + if ($targets === []) { + $this->tpl->setOnScreenMessage('failure', $this->lng->txt('rbac_copy_no_targets'), true); + $this->ctrl->redirect($this, 'roleSearchList'); + } + + if (count($targets) > 3) { + return sprintf($this->lng->txt('rbac_copy_multi_targets'), '' . ilObject::_lookupTitle($targets[0]), ilObject::_lookupTitle($targets[1]) . '', '' . (string) count($targets) - 2, ''); + } + + if (count($targets) > 1) { + $target_names = '' . ilObject::_lookupTitle(array_shift($targets)); + foreach ($targets as $target) { + $target_names .= ', ' . ilObject::_lookupTitle($target); + } + return $target_names . ''; + } + + // we have one single target + return '' . ilObject::_lookupTitle($targets[0]) . ''; + } + public function viewObject(): void { $this->tabs_gui->activateTab('view'); @@ -191,7 +212,13 @@ protected function roleSearchObject(): void } $this->ctrl->setParameter($this, 'csource', $this->initCopySourceFromGET()); - $this->tpl->setOnScreenMessage('info', $this->lng->txt('rbac_choose_copy_targets')); + $this->tpl->setOnScreenMessage( + 'info', + sprintf( + $this->lng->txt('rbac_choose_copy_targets'), + '' . ilObject::_lookupTitle($this->initCopySourceFromGET()) . '' + ) + ); $form = $this->initRoleSearchForm(); $this->tpl->setContent($form->getHTML()); @@ -250,7 +277,13 @@ protected function roleSearchListObject(): void $this->ctrl->setParameter($this, 'csource', $this->initCopySourceFromGET()); if (strlen(ilSession::get('rolf_search_query'))) { - $this->tpl->setOnScreenMessage('info', $this->lng->txt('rbac_select_copy_targets')); + $this->tpl->setOnScreenMessage( + 'info', + sprintf( + $this->lng->txt('rbac_select_copy_targets'), + '' . ilObject::_lookupTitle($this->initCopySourceFromGET()) . '' + ) + ); $table = new ilRoleTableGUI($this, 'roleSearchList'); $table->setType(ilRoleTableGUI::TYPE_SEARCH); $table->setRoleTitleFilter(ilSession::get('rolf_search_query')); @@ -291,6 +324,16 @@ protected function initCopyBehaviourForm(): ilPropertyFormGUI // not only for role templates; add/remove permissions is also applicable for roles $full_featured = true; + $this->tpl->setOnScreenMessage( + 'info', + sprintf( + $this->lng->txt('rbac_copy_behaviour_info'), + '' . ilObject::_lookupTitle($this->initCopySourceFromGET()) . '', + $this->buildTargetNamesString() + ), + true + ); + $form = new ilPropertyFormGUI(); $form->setTitle($this->lng->txt('rbac_copy_behaviour')); $form->setFormAction($this->ctrl->getFormAction($this, 'chooseCopyBehaviour')); @@ -404,62 +447,67 @@ protected function adjustRoleObject(): void $source = $this->initCopySourceFromGET(); $form = $this->initCopyBehaviourForm(); - if ($form->checkInput()) { - $adjustment_type = $form->getInput('type'); - foreach ((array) $roles as $role_id) { - if ($role_id !== $source) { - $start_obj = $this->rbac_review->getRoleFolderOfRole($role_id); - $this->logger->debug('Start object: ' . $start_obj); - - switch ($adjustment_type) { - case self::COPY_ADD_PERMISSIONS: - $change_existing = (bool) $form->getInput('add_ce_type'); - $this->doAddRolePermissions( - $source, - $role_id + if (!$form->checkInput()) { + $form->setValuesByPost(); + $this->chooseCopyBehaviourObject($form); + return; + } + + $adjustment_type = $form->getInput('type'); + foreach ((array) $roles as $role_id) { + if ($role_id !== $source) { + $start_obj = $this->rbac_review->getRoleFolderOfRole($role_id); + $this->logger->debug('Start object: ' . $start_obj); + + switch ($adjustment_type) { + case self::COPY_ADD_PERMISSIONS: + $change_existing = (bool) $form->getInput('add_ce_type'); + $this->doAddRolePermissions( + $source, + $role_id + ); + if ($change_existing) { + $this->doChangeExistingObjects( + $start_obj, + $role_id, + \ilObjRole::MODE_ADD_OPERATIONS, + $source ); - if ($change_existing) { - $this->doChangeExistingObjects( - $start_obj, - $role_id, - \ilObjRole::MODE_ADD_OPERATIONS, - $source - ); - } - break; - case self::COPY_CLONE_PERMISSIONS: - $change_existing = (bool) $form->getInput('clone_ce_type'); - $this->doCopyRole( - $source, - $role_id + } + break; + case self::COPY_CLONE_PERMISSIONS: + $change_existing = (bool) $form->getInput('clone_ce_type'); + $this->doCopyRole( + $source, + $role_id + ); + if ($change_existing) { + $this->doChangeExistingObjects( + $start_obj, + $role_id, + \ilObjRole::MODE_READ_OPERATIONS, + $source ); - if ($change_existing) { - $this->doChangeExistingObjects( - $start_obj, - $role_id, - \ilObjRole::MODE_READ_OPERATIONS, - $source - ); - } - break; - case self::COPY_REMOVE_PERMISSIONS: - $change_existing = (bool) $form->getInput('remove_ce_type'); - $this->doRemoveRolePermissions( - $source, - $role_id + } + break; + case self::COPY_REMOVE_PERMISSIONS: + $change_existing = (bool) $form->getInput('remove_ce_type'); + $this->doRemoveRolePermissions( + $source, + $role_id + ); + if ($change_existing) { + $this->doChangeExistingObjects( + $start_obj, + $role_id, + \ilObjRole::MODE_REMOVE_OPERATIONS, + $source ); - if ($change_existing) { - $this->doChangeExistingObjects( - $start_obj, - $role_id, - \ilObjRole::MODE_REMOVE_OPERATIONS, - $source - ); - } - break; - } + } + break; } } + $this->tpl->setOnScreenMessage('success', $this->lng->txt('rbac_copy_finished'), true); $this->ctrl->redirect($this, 'view'); } diff --git a/components/ILIAS/AccessControl/classes/class.ilObjRoleGUI.php b/components/ILIAS/AccessControl/classes/class.ilObjRoleGUI.php index bf0ff4adf603..c4c2561c693e 100755 --- a/components/ILIAS/AccessControl/classes/class.ilObjRoleGUI.php +++ b/components/ILIAS/AccessControl/classes/class.ilObjRoleGUI.php @@ -18,7 +18,6 @@ declare(strict_types=1); -use ILIAS\HTTP\GlobalHttpState; use ILIAS\Refinery\Factory; use ILIAS\UI\Factory as UIFactory; use ILIAS\UI\Renderer; @@ -49,7 +48,6 @@ class ilObjRoleGUI extends ilObjectGUI protected int $role_id = 0; protected ilHelpGUI $help; private ilLogger $logger; - private GlobalHttpState $http; protected Factory $refinery; protected UIFactory $ui_factory; protected Renderer $ui_renderer; @@ -67,7 +65,6 @@ public function __construct( $this->logger = $DIC->logger()->ac(); $this->role_id = $a_id; - $this->http = $DIC['http']; $this->refinery = $DIC['refinery']; $this->ui_factory = $DIC['ui.factory']; $this->ui_renderer = $DIC['ui.renderer']; diff --git a/components/ILIAS/AccessControl/classes/class.ilObjRoleTemplateGUI.php b/components/ILIAS/AccessControl/classes/class.ilObjRoleTemplateGUI.php index d6469607fdcc..dd48cae2389c 100755 --- a/components/ILIAS/AccessControl/classes/class.ilObjRoleTemplateGUI.php +++ b/components/ILIAS/AccessControl/classes/class.ilObjRoleTemplateGUI.php @@ -38,24 +38,15 @@ class ilObjRoleTemplateGUI extends ilObjectGUI private int $rolf_ref_id; - protected ilRbacAdmin $rbac_admin; - - private GlobalHttpState $http; - protected Factory $refinery; - public function __construct($a_data, int $a_id, bool $a_call_by_reference) { global $DIC; - $this->rbac_admin = $DIC->rbac()->admin(); - $this->type = "rolt"; parent::__construct($a_data, $a_id, $a_call_by_reference, false); $this->lng->loadLanguageModule('rbac'); $this->rolf_ref_id = &$this->ref_id; $this->ctrl->saveParameter($this, "obj_id"); - $this->http = $DIC->http(); - $this->refinery = $DIC->refinery(); } public function executeCommand(): void diff --git a/components/ILIAS/AccessControl/classes/class.ilPermissionGUI.php b/components/ILIAS/AccessControl/classes/class.ilPermissionGUI.php index e7224518c9a7..1bd0e72952b6 100755 --- a/components/ILIAS/AccessControl/classes/class.ilPermissionGUI.php +++ b/components/ILIAS/AccessControl/classes/class.ilPermissionGUI.php @@ -1,4 +1,5 @@ getPositionRepo()->getArray(null, 'id'); $ref_id = $this->getCurrentObject()->getRefId(); - // handle local sets - $local_post = $this->http->wrapper()->post()->has('local') + $positions_with_local_perms_from_post = $this->http->wrapper()->post()->has('local') ? $this->http->wrapper()->post()->retrieve( 'local', $this->refinery->kindlyTo()->dictOf($this->refinery->kindlyTo()->int()) @@ -862,7 +862,7 @@ public function savePositionsPermissions(): void : []; foreach ($positions as $position_id) { - if (isset($local_post[$position_id])) { + if (isset($positions_with_local_perms_from_post[$position_id])) { $this->getPermissionRepo()->get($ref_id, $position_id); } else { $this->getPermissionRepo()->delete($ref_id, $position_id); @@ -880,22 +880,32 @@ public function savePositionsPermissions(): void ) : []; - if ($position_perm_post) { // TODO: saving an empty (enabled) set is not working, as the POST variable is empty for that set - foreach ($position_perm_post as $position_id => $ops) { - if (!isset($local_post[$position_id])) { - continue; - } - $ilOrgUnitPermission = $this->getPermissionRepo()->getLocalorDefault($ref_id, $position_id); - if (!$ilOrgUnitPermission->isTemplate()) { - $new_ops = []; - foreach ($ops as $op_id => $op) { - $new_ops[] = $this->getOperationRepo()->getById($op_id); - } - $ilOrgUnitPermission = $ilOrgUnitPermission->withOperations($new_ops); - $ilOrgUnitPermission = $this->getPermissionRepo()->store($ilOrgUnitPermission); + foreach ($position_perm_post as $position_id => $ops) { + if (!isset($positions_with_local_perms_from_post[$position_id])) { + continue; + } + $org_unit_permissions = $this->getPermissionRepo()->getLocalorDefault($ref_id, $position_id); + if (!$org_unit_permissions->isTemplate()) { + $new_ops = []; + foreach ($ops as $op_id => $op) { + $new_ops[] = $this->getOperationRepo()->getById($op_id); } + $org_unit_permissions = $this->getPermissionRepo()->store( + $org_unit_permissions->withOperations($new_ops) + ); } } + + foreach (array_keys($positions_with_local_perms_from_post) as $position_id_from_post) { + if (array_key_exists($position_id_from_post, $position_perm_post)) { + continue; + } + $org_unit_permissions = $this->getPermissionRepo()->find($ref_id, $position_id_from_post); + if ($org_unit_permissions !== null && !$org_unit_permissions->isTemplate()) { + $this->getPermissionRepo()->store($org_unit_permissions->withOperations([])); + } + } + $this->tpl->setOnScreenMessage('success', $this->lng->txt('settings_saved'), true); $this->ctrl->redirect($this, self::CMD_PERM_POSITIONS); } diff --git a/components/ILIAS/AccessControl/classes/class.ilRoleAutoComplete.php b/components/ILIAS/AccessControl/classes/class.ilRoleAutoComplete.php index dfeee6336f60..1907f8ba9bfd 100755 --- a/components/ILIAS/AccessControl/classes/class.ilRoleAutoComplete.php +++ b/components/ILIAS/AccessControl/classes/class.ilRoleAutoComplete.php @@ -17,6 +17,7 @@ *********************************************************************/ declare(strict_types=1); + /** * Auto completion class for user lists * @author Stefan Meyer @@ -36,8 +37,9 @@ public static function getList(string $a_str): string "JOIN rbac_fa fa ON o1.obj_id = rol_id " . // "JOIN tree t1 ON fa.parent = t1.child " . // "JOIN object_reference obr ON ref_id = t1.parent " . - "JOIN object_reference obr ON ref_id = fa.parent " . + "JOIN object_reference obr ON ref_id = fa.parent AND obr.deleted IS NULL " . "JOIN object_data o2 ON obr.obj_id = o2.obj_id " . + "INNER JOIN tree t ON t.child = obr.ref_id AND t.tree = 1 " . "WHERE o1.type = 'role' " . "AND assign = 'y' " . "AND (" . $ilDB->like('o1.title', 'text', '%' . $a_str . '%') . "OR " . diff --git a/components/ILIAS/AccessControl/classes/class.ilRoleTableGUI.php b/components/ILIAS/AccessControl/classes/class.ilRoleTableGUI.php index 98478ce19af7..68f2475b4896 100755 --- a/components/ILIAS/AccessControl/classes/class.ilRoleTableGUI.php +++ b/components/ILIAS/AccessControl/classes/class.ilRoleTableGUI.php @@ -131,7 +131,7 @@ protected function fillRow(array $a_set): void if ($this->getType() == self::TYPE_VIEW and $a_set['obj_id'] != SYSTEM_ROLE_ID) { if ($this->system->checkAccess('write', $this->role_folder_id)) { // Copy role - $this->tpl->setVariable('COPY_TEXT', $this->lng->txt('rbac_role_rights_copy')); + $this->tpl->setVariable('COPY_TEXT', $this->lng->txt('rbac_copy_role_copy')); $this->ctrl->setParameter($this->getParentObject(), "csource", $a_set["obj_id"]); $link = $this->ctrl->getLinkTarget($this->getParentObject(), 'roleSearch'); $this->tpl->setVariable( @@ -176,7 +176,7 @@ public function init(): void $this->addColumn($this->lng->txt('search_title_description'), 'title', '30%'); $this->addColumn($this->lng->txt('type'), 'rtype', '20%'); $this->addColumn($this->lng->txt('context'), '', '50%'); - $this->setTitle($this->lng->txt('rbac_role_rights_copy')); + $this->setTitle($this->lng->txt('rbac_select_roles')); $this->addMultiCommand('chooseCopyBehaviour', $this->lng->txt('btn_next')); $this->addCommandButton('roleSearch', $this->lng->txt('btn_previous')); break; @@ -189,7 +189,9 @@ public function init(): void $this->path_gui = new ilPathGUI(); $this->getPathGUI()->enableTextOnly(false); $this->getPathGUI()->enableHideLeaf(false); - $this->initFilter(); + if ($this->type === self::TYPE_VIEW) { + $this->initFilter(); + } } public function getType(): int diff --git a/components/ILIAS/AccessControl/src/Log/Table.php b/components/ILIAS/AccessControl/src/Log/Table.php index 3ada3b10bd29..c88dc1dc63da 100644 --- a/components/ILIAS/AccessControl/src/Log/Table.php +++ b/components/ILIAS/AccessControl/src/Log/Table.php @@ -93,6 +93,7 @@ private function getTable(): DataTable $cf = $this->ui_factory->table()->column(); return $this->ui_factory->table()->data( + $this, $this->lng->txt('rbac_log'), [ self::COLUMN_DATE => $cf->date( @@ -105,7 +106,6 @@ private function getTable(): DataTable self::COLUMN_CHANGES => $cf->text($this->lng->txt('rbac_changes')) ->withIsSortable(false) ], - $this )->withRequest($this->request); } diff --git a/components/ILIAS/Authentication/classes/Setup/class.ilAuthenticationDatabaseUpdateSteps8.php b/components/ILIAS/AccessControl/src/Setup/AccessControl10DBUpdateSteps.php old mode 100755 new mode 100644 similarity index 59% rename from components/ILIAS/Authentication/classes/Setup/class.ilAuthenticationDatabaseUpdateSteps8.php rename to components/ILIAS/AccessControl/src/Setup/AccessControl10DBUpdateSteps.php index a084a27b9480..12d1eeda28b7 --- a/components/ILIAS/Authentication/classes/Setup/class.ilAuthenticationDatabaseUpdateSteps8.php +++ b/components/ILIAS/AccessControl/src/Setup/AccessControl10DBUpdateSteps.php @@ -18,24 +18,21 @@ declare(strict_types=1); -class ilAuthenticationDatabaseUpdateSteps8 implements ilDatabaseUpdateSteps +namespace ILIAS\AccessControl\Setup; + +class AccessControl10DBUpdateSteps implements \ilDatabaseUpdateSteps { - protected ilDBInterface $db; + protected \ilDBInterface $db; - public function prepare(ilDBInterface $db): void + public function prepare(\ilDBInterface $db): void { $this->db = $db; } public function step_1(): void { - if ($this->db->tableExists('usr_session_stats_raw') && - $this->db->tableColumnExists('usr_session_stats_raw', 'start_time')) { - $this->db->addIndex( - 'usr_session_stats_raw', - ['start_time'], - 'i1' - ); + if (!$this->db->indexExistsByFields('rbac_templates', ['type', 'ops_id'])) { + $this->db->addIndex('rbac_templates', ['type', 'ops_id'], 'toi'); } } } diff --git a/components/ILIAS/Accessibility/classes/class.ilObjAccessibilitySettingsGUI.php b/components/ILIAS/Accessibility/classes/class.ilObjAccessibilitySettingsGUI.php index cf3c8006fce4..e7ac4f32ef71 100755 --- a/components/ILIAS/Accessibility/classes/class.ilObjAccessibilitySettingsGUI.php +++ b/components/ILIAS/Accessibility/classes/class.ilObjAccessibilitySettingsGUI.php @@ -115,6 +115,8 @@ public function executeCommand(): void protected function getSettingsForm(): ilPropertyFormGUI { + $writable = $this->rbacsystem->checkAccess('write', $this->object->getRefId()); + $this->form = new ilPropertyFormGUI(); $this->form->setTitle($this->lng->txt('settings')); @@ -122,12 +124,14 @@ protected function getSettingsForm(): ilPropertyFormGUI $cb->setValue(1); $cb->setChecked(ilObjAccessibilitySettings::getControlConceptStatus()); $cb->setInfo($this->lng->txt('adm_acc_ctrl_cpt_desc')); + $cb->setDisabled(!$writable); $this->form->addItem($cb); $ti = new ilTextInputGUI($this->lng->txt("adm_accessibility_contacts"), "accessibility_support_contacts"); $ti->setMaxLength(500); $ti->setValue(ilAccessibilitySupportContacts::getList()); $ti->setInfo($this->lng->txt("adm_accessibility_contacts_info")); + $ti->setDisabled(!$writable); $this->form->addItem($ti); ilAdministrationSettingsFormHandler::addFieldsToForm( @@ -136,8 +140,10 @@ protected function getSettingsForm(): ilPropertyFormGUI $this ); - $this->form->addCommandButton("saveAccessibilitySettings", $this->lng->txt("save")); - $this->form->setFormAction($this->ctrl->getFormAction($this)); + if ($writable) { + $this->form->addCommandButton("saveAccessibilitySettings", $this->lng->txt("save")); + $this->form->setFormAction($this->ctrl->getFormAction($this)); + } return $this->form; } diff --git a/components/ILIAS/ActiveRecord/Connector/Concat/class.arConcat.php b/components/ILIAS/ActiveRecord/Connector/Concat/class.arConcat.php index 6c6ed348b7c3..ea9f3a249116 100755 --- a/components/ILIAS/ActiveRecord/Connector/Concat/class.arConcat.php +++ b/components/ILIAS/ActiveRecord/Connector/Concat/class.arConcat.php @@ -26,9 +26,11 @@ class arConcat extends arStatement protected string $as = ''; protected array $fields = []; - public function asSQLStatement(ActiveRecord $activeRecord): string + public function asSQLStatement(ActiveRecord $activeRecord, ilDBInterface $db): string { - return ' CONCAT(' . implode(', ', $this->getFields()) . ') AS ' . $this->getAs(); + $fields = $this->wrapFields($this->getFields(), $db); + + return ' CONCAT(' . implode(', ', $fields) . ') AS ' . $this->getAs(); } public function getAs(): string diff --git a/components/ILIAS/ActiveRecord/Connector/Concat/class.arConcatCollection.php b/components/ILIAS/ActiveRecord/Connector/Concat/class.arConcatCollection.php index d02c489918ff..7cad1d16bf7f 100755 --- a/components/ILIAS/ActiveRecord/Connector/Concat/class.arConcatCollection.php +++ b/components/ILIAS/ActiveRecord/Connector/Concat/class.arConcatCollection.php @@ -23,14 +23,14 @@ */ class arConcatCollection extends arStatementCollection { - public function asSQLStatement(): string + public function asSQLStatement(ilDBInterface $db): string { $return = ''; if ($this->hasStatements()) { $return = ', '; $concats = $this->getConcats(); foreach ($concats as $concat) { - $return .= $concat->asSQLStatement($this->getAr()); + $return .= $concat->asSQLStatement($this->getAr(), $db); if ($concat !== end($concats)) { $return .= ', '; } diff --git a/components/ILIAS/ActiveRecord/Connector/Having/class.arHaving.php b/components/ILIAS/ActiveRecord/Connector/Having/class.arHaving.php index 4fc3800629c3..03d29026b692 100755 --- a/components/ILIAS/ActiveRecord/Connector/Having/class.arHaving.php +++ b/components/ILIAS/ActiveRecord/Connector/Having/class.arHaving.php @@ -34,16 +34,17 @@ class arHaving extends arStatement protected string $glue = 'AND'; /** + * @param ilDBInterface $db * @description Build WHERE Statement * @throws arException */ - public function asSQLStatement(ActiveRecord $activeRecord): string + public function asSQLStatement(ActiveRecord $activeRecord, ilDBInterface $db): string { $statement = ''; if ($this->getTableName() !== '' && $this->getTableName() !== '0') { $statement .= $this->getTableName() . '.'; } - $statement .= $this->getFieldname() . ' ' . $this->getOperator() . ' "' . $this->getValue() . '"'; + $statement .= $db->quoteIdentifier($this->getFieldname()) . ' ' . $this->getOperator() . ' ' . $db->quote($this->getValue()) . ''; $this->setStatement($statement); return $this->getStatement(); diff --git a/components/ILIAS/ActiveRecord/Connector/Having/class.arHavingCollection.php b/components/ILIAS/ActiveRecord/Connector/Having/class.arHavingCollection.php index fcf34fda22da..f55b0fa80a55 100755 --- a/components/ILIAS/ActiveRecord/Connector/Having/class.arHavingCollection.php +++ b/components/ILIAS/ActiveRecord/Connector/Having/class.arHavingCollection.php @@ -23,7 +23,7 @@ */ class arHavingCollection extends arStatementCollection { - public function asSQLStatement(): string + public function asSQLStatement(ilDBInterface $db): string { $return = ''; if ($this->hasStatements()) { @@ -31,7 +31,7 @@ public function asSQLStatement(): string $havings = $this->getHavings(); $last = end($havings); foreach ($havings as $having) { - $return .= $having->asSQLStatement($this->getAr()); + $return .= $having->asSQLStatement($this->getAr(), $db); if ($having !== $last) { $return .= ' ' . $having->getGlue() . ' '; } diff --git a/components/ILIAS/ActiveRecord/Connector/Join/class.arJoin.php b/components/ILIAS/ActiveRecord/Connector/Join/class.arJoin.php index 8135ca38770d..5c0582395b7e 100755 --- a/components/ILIAS/ActiveRecord/Connector/Join/class.arJoin.php +++ b/components/ILIAS/ActiveRecord/Connector/Join/class.arJoin.php @@ -18,6 +18,7 @@ /** * Class arJoin + * * @author Fabian Schmid * @version 2.0.7 */ @@ -38,23 +39,31 @@ class arJoin extends arStatement protected bool $both_external = false; protected bool $is_mapped = false; - protected function asStatementText(ActiveRecord $activeRecord, string $as = ' AS '): string + protected function asStatementText(ActiveRecord $activeRecord, ilDBInterface $db, string $as = ' AS '): string { $return = ' ' . $this->getType() . ' '; $return .= ' JOIN ' . $this->getTableName() . $as . $this->getTableNameAs(); if ($this->getBothExternal()) { - $return .= ' ON ' . $this->getOnFirstField() . ' ' . $this->getOperator() . ' '; + $return .= ' ON ' . $this->wrapField($this->getOnFirstField(), $db) . ' ' . $this->getOperator() . ' '; } else { - $return .= ' ON ' . $activeRecord->getConnectorContainerName() . '.' . $this->getOnFirstField( - ) . ' ' . $this->getOperator() . ' '; + $return .= ' ON ' + . $this->wrapField( + $activeRecord->getConnectorContainerName() . '.' . $this->getOnFirstField(), + $db + ) + . ' ' . $this->getOperator() . ' '; } - return $return . ($this->getTableNameAs() . '.' . $this->getOnSecondField()); + return $return . $this->wrapField( + $this->getTableNameAs() . '.' . + $this->getOnSecondField(), + $db + ); } - public function asSQLStatement(ActiveRecord $activeRecord): string + public function asSQLStatement(ActiveRecord $activeRecord, ilDBInterface $db): string { - return $this->asStatementText($activeRecord, self::AS_TEXT); + return $this->asStatementText($activeRecord, $db, self::AS_TEXT); } public function setLeft(): void diff --git a/components/ILIAS/ActiveRecord/Connector/Join/class.arJoinCollection.php b/components/ILIAS/ActiveRecord/Connector/Join/class.arJoinCollection.php index a971f47987a6..0165d5afe2e5 100755 --- a/components/ILIAS/ActiveRecord/Connector/Join/class.arJoinCollection.php +++ b/components/ILIAS/ActiveRecord/Connector/Join/class.arJoinCollection.php @@ -50,12 +50,12 @@ public function add(arStatement $arStatement): void parent::add($arStatement); } - public function asSQLStatement(): string + public function asSQLStatement(ilDBInterface $db): string { $return = ''; if ($this->hasStatements()) { foreach ($this->getJoins() as $arJoin) { - $return .= $arJoin->asSQLStatement($this->getAr()); + $return .= $arJoin->asSQLStatement($this->getAr(), $db); } } diff --git a/components/ILIAS/ActiveRecord/Connector/Limit/class.arLimit.php b/components/ILIAS/ActiveRecord/Connector/Limit/class.arLimit.php index 9840bfbc6b9c..20fd53225ab3 100755 --- a/components/ILIAS/ActiveRecord/Connector/Limit/class.arLimit.php +++ b/components/ILIAS/ActiveRecord/Connector/Limit/class.arLimit.php @@ -26,7 +26,7 @@ class arLimit extends arStatement protected int $start = 0; protected int $end = 0; - public function asSQLStatement(ActiveRecord $activeRecord): string + public function asSQLStatement(ActiveRecord $activeRecord, ilDBInterface $db): string { return ' LIMIT ' . $this->getStart() . ', ' . $this->getEnd(); } diff --git a/components/ILIAS/ActiveRecord/Connector/Limit/class.arLimitCollection.php b/components/ILIAS/ActiveRecord/Connector/Limit/class.arLimitCollection.php index 72d0ef4f3715..f55807e3b15a 100755 --- a/components/ILIAS/ActiveRecord/Connector/Limit/class.arLimitCollection.php +++ b/components/ILIAS/ActiveRecord/Connector/Limit/class.arLimitCollection.php @@ -23,7 +23,7 @@ */ class arLimitCollection extends arStatementCollection { - public function asSQLStatement(): string + public function asSQLStatement(ilDBInterface $db): string { if ($this->hasStatements()) { /** @@ -32,7 +32,7 @@ public function asSQLStatement(): string $statements = $this->getStatements(); $last = end($statements); - return $last->asSQLStatement($this->getAr()); + return $last->asSQLStatement($this->getAr(), $db); } return ''; } diff --git a/components/ILIAS/ActiveRecord/Connector/Order/class.arOrder.php b/components/ILIAS/ActiveRecord/Connector/Order/class.arOrder.php index b4f460562f08..0111ed762001 100755 --- a/components/ILIAS/ActiveRecord/Connector/Order/class.arOrder.php +++ b/components/ILIAS/ActiveRecord/Connector/Order/class.arOrder.php @@ -26,9 +26,9 @@ class arOrder extends arStatement protected string $fieldname = ''; protected string $direction = 'ASC'; - public function asSQLStatement(ActiveRecord $activeRecord): string + public function asSQLStatement(ActiveRecord $activeRecord, ilDBInterface $db): string { - return ' ' . $this->getFieldname() . ' ' . strtoupper($this->getDirection()); + return ' ' . $this->wrapField($this->getFieldname(), $db) . ' ' . $this->getDirection(); } public function setDirection(string $direction): void diff --git a/components/ILIAS/ActiveRecord/Connector/Order/class.arOrderCollection.php b/components/ILIAS/ActiveRecord/Connector/Order/class.arOrderCollection.php index 3fbef2d18dc1..756791a71f0a 100755 --- a/components/ILIAS/ActiveRecord/Connector/Order/class.arOrderCollection.php +++ b/components/ILIAS/ActiveRecord/Connector/Order/class.arOrderCollection.php @@ -23,14 +23,14 @@ */ class arOrderCollection extends arStatementCollection { - public function asSQLStatement(): string + public function asSQLStatement(ilDBInterface $db): string { $return = ''; if ($this->hasStatements()) { $return .= ' ORDER BY '; $orders = $this->getOrders(); foreach ($orders as $order) { - $return .= $order->asSQLStatement($this->getAr()); + $return .= $order->asSQLStatement($this->getAr(), $db); if ($order !== end($orders)) { $return .= ', '; } diff --git a/components/ILIAS/ActiveRecord/Connector/Select/class.arSelect.php b/components/ILIAS/ActiveRecord/Connector/Select/class.arSelect.php index fe12bd2e512c..454bb3c1e0b2 100755 --- a/components/ILIAS/ActiveRecord/Connector/Select/class.arSelect.php +++ b/components/ILIAS/ActiveRecord/Connector/Select/class.arSelect.php @@ -27,13 +27,25 @@ class arSelect extends arStatement protected string $as = ''; protected string $field_name = ''; - public function asSQLStatement(ActiveRecord $activeRecord): string + /** + * @param bool $disabled_escaping In some special cases, one need to disable the escaping of the field name. + * In this cases, the consumer is responsible to avoid sqm injection. + */ + public function __construct(private bool $disabled_escaping = false) + { + } + + public function asSQLStatement(ActiveRecord $activeRecord, ilDBInterface $db): string { $return = ''; if ($this->getTableName() !== '' && $this->getTableName() !== '0') { $return .= $this->getTableName() . '.'; } - $return .= $this->getFieldName(); + if ($this->disabled_escaping) { + $return .= $this->getFieldName(); + } else { + $return .= $this->wrapField($this->getFieldName(), $db); + } if ($this->getAs() && $this->getFieldName() !== '*') { $return .= ' AS ' . $this->getAs(); } diff --git a/components/ILIAS/ActiveRecord/Connector/Select/class.arSelectCollection.php b/components/ILIAS/ActiveRecord/Connector/Select/class.arSelectCollection.php index ede3a8f78377..941d5b03aad8 100755 --- a/components/ILIAS/ActiveRecord/Connector/Select/class.arSelectCollection.php +++ b/components/ILIAS/ActiveRecord/Connector/Select/class.arSelectCollection.php @@ -23,12 +23,12 @@ */ class arSelectCollection extends arStatementCollection { - public function asSQLStatement(): string + public function asSQLStatement(ilDBInterface $db): string { $return = 'SELECT '; if ($this->hasStatements()) { $activeRecord = $this->getAr(); - $selectSQLs = array_map(fn($select): string => $select->asSQLStatement($activeRecord), $this->getSelects()); + $selectSQLs = array_map(fn($select) => $select->asSQLStatement($activeRecord, $db), $this->getSelects()); $return .= implode(', ', $selectSQLs); } diff --git a/components/ILIAS/ActiveRecord/Connector/Statement/class.arStatement.php b/components/ILIAS/ActiveRecord/Connector/Statement/class.arStatement.php index 132668d53be8..70012a8e8f71 100755 --- a/components/ILIAS/ActiveRecord/Connector/Statement/class.arStatement.php +++ b/components/ILIAS/ActiveRecord/Connector/Statement/class.arStatement.php @@ -18,6 +18,7 @@ /** * Class arStatement + * * @author Fabian Schmid * @version 2.0.7 */ @@ -25,7 +26,7 @@ abstract class arStatement { protected string $table_name_as = ''; - abstract public function asSQLStatement(ActiveRecord $activeRecord): string; + abstract public function asSQLStatement(ActiveRecord $activeRecord, ilDBInterface $db): string; public function getTableNameAs(): string { @@ -36,4 +37,33 @@ public function setTableNameAs(string $table_name_as): void { $this->table_name_as = $table_name_as; } + + protected function wrapFields(array $fields, ilDBInterface $db): array + { + $wrapped_fields = []; + + foreach ($fields as $field) { + if (empty($field)) { + continue; + } + $wrapped_fields[] = $this->wrapField($field, $db); + } + + return $wrapped_fields; + } + + protected function wrapField(string $field, ilDBInterface $db): string + { + $slitted = explode('.', $field); + + if (count($slitted) === 1 && $slitted[0] === '*') { + return $field; + } + + if (count($slitted) === 2) { + return $db->quoteIdentifier($slitted[0]) . '.' . $db->quoteIdentifier($slitted[1]); + } + + return $db->quoteIdentifier($field); + } } diff --git a/components/ILIAS/ActiveRecord/Connector/Statement/class.arStatementCollection.php b/components/ILIAS/ActiveRecord/Connector/Statement/class.arStatementCollection.php index 28cb4f4cf6a5..1c75ae301104 100755 --- a/components/ILIAS/ActiveRecord/Connector/Statement/class.arStatementCollection.php +++ b/components/ILIAS/ActiveRecord/Connector/Statement/class.arStatementCollection.php @@ -55,7 +55,7 @@ public static function getInstance(ActiveRecord $activeRecord): arStatementColle return $arWhereCollection; } - abstract public function asSQLStatement(): string; + abstract public function asSQLStatement(ilDBInterface $db): string; public function setAr(ActiveRecord $activeRecord): void { diff --git a/components/ILIAS/ActiveRecord/Connector/Where/class.arWhere.php b/components/ILIAS/ActiveRecord/Connector/Where/class.arWhere.php index 88b8dc01970f..99289a3933fa 100755 --- a/components/ILIAS/ActiveRecord/Connector/Where/class.arWhere.php +++ b/components/ILIAS/ActiveRecord/Connector/Where/class.arWhere.php @@ -36,10 +36,11 @@ class arWhere extends arStatement protected string $link = 'AND'; /** + * @param ilDBInterface $db * @description Build WHERE Statement * @throws arException */ - public function asSQLStatement(ActiveRecord $activeRecord): string + public function asSQLStatement(ActiveRecord $activeRecord, ilDBInterface $db): string { $type = null; if ($this->getType() === self::TYPE_REGULAR) { diff --git a/components/ILIAS/ActiveRecord/Connector/Where/class.arWhereCollection.php b/components/ILIAS/ActiveRecord/Connector/Where/class.arWhereCollection.php index da193fa6d0e2..a47dbc078980 100755 --- a/components/ILIAS/ActiveRecord/Connector/Where/class.arWhereCollection.php +++ b/components/ILIAS/ActiveRecord/Connector/Where/class.arWhereCollection.php @@ -23,7 +23,7 @@ */ class arWhereCollection extends arStatementCollection { - public function asSQLStatement(): string + public function asSQLStatement(ilDBInterface $db): string { $return = ''; if ($this->hasStatements()) { @@ -31,7 +31,7 @@ public function asSQLStatement(): string $wheres = $this->getWheres(); $last = end($wheres); foreach ($wheres as $where) { - $return .= $where->asSQLStatement($this->getAr()); + $return .= $where->asSQLStatement($this->getAr(), $db); if ($where !== $last) { $return .= ' ' . $where->getLink() . ' '; } diff --git a/components/ILIAS/ActiveRecord/Connector/class.arConnectorDB.php b/components/ILIAS/ActiveRecord/Connector/class.arConnectorDB.php index 725ae92d0700..d0502b99019d 100755 --- a/components/ILIAS/ActiveRecord/Connector/class.arConnectorDB.php +++ b/components/ILIAS/ActiveRecord/Connector/class.arConnectorDB.php @@ -268,23 +268,21 @@ public function affectedRows(ActiveRecordList $activeRecordList): int */ protected function buildQuery(ActiveRecordList $activeRecordList): string { - $method = 'asSQLStatement'; - // SELECTS - $q = $activeRecordList->getArSelectCollection()->{$method}(); + $q = $activeRecordList->getArSelectCollection()->asSQLStatement($this->db); // Concats - $q .= $activeRecordList->getArConcatCollection()->{$method}(); + $q .= $activeRecordList->getArConcatCollection()->asSQLStatement($this->db); $q .= ' FROM ' . $activeRecordList->getAR()->getConnectorContainerName(); // JOINS - $q .= $activeRecordList->getArJoinCollection()->{$method}(); + $q .= $activeRecordList->getArJoinCollection()->asSQLStatement($this->db); // WHERE - $q .= $activeRecordList->getArWhereCollection()->{$method}(); + $q .= $activeRecordList->getArWhereCollection()->asSQLStatement($this->db); // HAVING - $q .= $activeRecordList->getArHavingCollection()->{$method}(); + $q .= $activeRecordList->getArHavingCollection()->asSQLStatement($this->db); // ORDER - $q .= $activeRecordList->getArOrderCollection()->{$method}(); + $q .= $activeRecordList->getArOrderCollection()->asSQLStatement($this->db); // LIMIT - $q .= $activeRecordList->getArLimitCollection()->{$method}(); + $q .= $activeRecordList->getArLimitCollection()->asSQLStatement($this->db); $activeRecordList->setLastQuery($q); diff --git a/components/ILIAS/ActiveRecord/Fields/class.arFieldList.php b/components/ILIAS/ActiveRecord/Fields/class.arFieldList.php index 5adbb2aa996b..9c4e3265533a 100755 --- a/components/ILIAS/ActiveRecord/Fields/class.arFieldList.php +++ b/components/ILIAS/ActiveRecord/Fields/class.arFieldList.php @@ -165,25 +165,25 @@ public function getPrimaryFieldType(): string return $this->getPrimaryField()->getFieldType(); } - protected function initRawFields(ActiveRecord $activeRecord): void + protected function initRawFields(ActiveRecord $ar): void { - $regex = "/[ ]*\\* @(" . implode('|', self::$prefixes) . ")_([a-zA-Z0-9_]*)[ ]*([a-zA-Z0-9_]*)/u"; - $reflectionClass = new ReflectionClass($activeRecord); + $regex = "/[\t ]*\\* @(" . implode('|', self::$prefixes) . ")_([a-zA-Z0-9_]+)[\t ]+([a-zA-Z0-9_]+)/u"; + $reflection = new ReflectionClass($ar); $raw_fields = []; - foreach ($reflectionClass->getProperties() as $reflectionProperty) { - if (in_array($reflectionProperty->getName(), self::$protected_names)) { + foreach ($reflection->getProperties() as $property) { + if (in_array($property->getName(), self::$protected_names)) { continue; } $properties_array = []; $has_property = false; - foreach (explode("\n", $reflectionProperty->getDocComment()) as $line) { + foreach (explode("\n", $property->getDocComment()) as $line) { if (preg_match($regex, $line, $matches)) { $has_property = true; $properties_array[$matches[2]] = $matches[3]; } } if ($has_property) { - $raw_fields[$reflectionProperty->getName()] = $properties_array; + $raw_fields[$property->getName()] = $properties_array; } } diff --git a/components/ILIAS/ActiveRecord/README.md b/components/ILIAS/ActiveRecord/README.md index 427845039fdb..c3d58162f930 100755 --- a/components/ILIAS/ActiveRecord/README.md +++ b/components/ILIAS/ActiveRecord/README.md @@ -262,7 +262,13 @@ proper persistent layer access. | con_length | Length of the field in the persistent layer | determines from the fieldtype. See 'Databse Access and Database Schema' for further information. | All this information is parsed from the PHPDoc once per ActiveRecord-Class and request and are cached for all other -instances of this type. So there should not be a remarkable performance-drop. This is an Example for a primary key $id: +instances of this type. So there should not be a remarkable performance-drop. +In order to work properly +- there may be any number and combination of blanks and tabs before the '*' (including zero characters) +- the '*' must be followed by exactky one space character +- there may be any number or combination of blanks and tabs between field name and value, but at least one separator character is required + +This is an Example for a primary key $id: ```php /** diff --git a/components/ILIAS/ActiveRecord/class.ActiveRecord.php b/components/ILIAS/ActiveRecord/class.ActiveRecord.php index a1d07cf0f0a2..76d297ddd184 100755 --- a/components/ILIAS/ActiveRecord/class.ActiveRecord.php +++ b/components/ILIAS/ActiveRecord/class.ActiveRecord.php @@ -17,12 +17,11 @@ *********************************************************************/ /** - * Class ActiveRecord - * @author Fabian Schmid - * @author Oskar Truffer - * @experimental - * @description - * @version 2.0.7 + * @depracated This service is now deprecated and will be removed with ILIAS 12 or 13 (depending on how quickly users + * can replace their implementations). The ActiveRecord was a simple way of handling database operations and object + * mapping in its day. However, the pattern is not very lightweight and has a very large overhead. It also leads to + * many database queries and high memory consumption. For several releases now, the “Repository Pattern” has been + * propagated as a better alternative. */ #[\AllowDynamicProperties] abstract class ActiveRecord diff --git a/components/ILIAS/Administration/classes/class.ilAdministrationGUI.php b/components/ILIAS/Administration/classes/class.ilAdministrationGUI.php index 410ddbc60ad3..09d7e0ef3b4a 100755 --- a/components/ILIAS/Administration/classes/class.ilAdministrationGUI.php +++ b/components/ILIAS/Administration/classes/class.ilAdministrationGUI.php @@ -149,6 +149,7 @@ public function executeCommand(): void // check creation mode // determined by "new_type" parameter + // e.g. creation of a new role, user org unit, talk template $new_type = $this->request->getNewType(); if ($new_type) { $this->creation_mode = true; @@ -158,8 +159,6 @@ public function executeCommand(): void $obj_type = $new_type; $class_name = $this->objDefinition->getClassName($obj_type); $next_class = strtolower("ilObj" . $class_name . "GUI"); - // @todo: removed deprecated ilCtrl methods, this needs inspection by a maintainer. - // $this->ctrl->setCmdClass($next_class); } // set next_class directly for page translations @@ -177,9 +176,6 @@ public function executeCommand(): void $obj_type = ilObject::_lookupType($this->cur_ref_id, true); $class_name = $this->objDefinition->getClassName($obj_type); $next_class = strtolower("ilObj" . $class_name . "GUI"); - // @todo: removed deprecated ilCtrl methods, this needs inspection by a maintainer. - // $this->ctrl->setCmdClass($next_class); - // $this->ctrl->setCmd("view"); } $cmd = $this->ctrl->getCmd("forward"); diff --git a/components/ILIAS/Administration/maintenance.json b/components/ILIAS/Administration/maintenance.json index f8fd26fdcde9..9a3dd2bb4afd 100755 --- a/components/ILIAS/Administration/maintenance.json +++ b/components/ILIAS/Administration/maintenance.json @@ -1,7 +1,7 @@ { "maintenance_model": "Classic", - "first_maintainer": "akill(149)", - "second_maintainer": "smeyer(191)", + "first_maintainer": "fneumann(1560)", + "second_maintainer": "", "implicit_maintainers": [], "coordinator": [ "" diff --git a/components/ILIAS/AdministrativeNotification/classes/DataRetrieval.php b/components/ILIAS/AdministrativeNotification/classes/DataRetrieval.php index eeaa9f61ac99..32f1f9208064 100755 --- a/components/ILIAS/AdministrativeNotification/classes/DataRetrieval.php +++ b/components/ILIAS/AdministrativeNotification/classes/DataRetrieval.php @@ -18,6 +18,8 @@ namespace ILIAS\AdministrativeNotification; +use ilLanguage; +use Generator; use DateTimeImmutable; use ilADNNotification; use ilDatePresentation; @@ -31,10 +33,11 @@ */ class DataRetrieval implements I\DataRetrieval { - private \ilLanguage $lng; + private ilLanguage $lng; - public function __construct() - { + public function __construct( + protected bool $has_write_access, + ) { global $DIC; $this->lng = $DIC['lng']; } @@ -46,12 +49,16 @@ public function getRows( Order $order, ?array $filter_data, ?array $additional_parameters - ): \Generator { + ): Generator { $records = $this->getRecords($order); - foreach ($records as $idx => $record) { + foreach ($records as $record) { $row_id = (string) $record['id']; - yield $row_builder->buildDataRow($row_id, $record); + yield $row_builder->buildDataRow($row_id, $record) + ->withDisabledAction(Table::ACTION_DUPLICATE, !$this->has_write_access) + ->withDisabledAction(Table::ACTION_EDIT, !$this->has_write_access) + ->withDisabledAction(Table::ACTION_DELETE, !$this->has_write_access) + ->withDisabledAction(Table::ACTION_RESET, !$this->has_write_access); } } diff --git a/components/ILIAS/AdministrativeNotification/classes/Table.php b/components/ILIAS/AdministrativeNotification/classes/Table.php index a37b71505374..337c96f26779 100755 --- a/components/ILIAS/AdministrativeNotification/classes/Table.php +++ b/components/ILIAS/AdministrativeNotification/classes/Table.php @@ -20,6 +20,11 @@ use ILIAS\UI\Factory; use ILIAS\UI\Renderer; +use ilCtrlInterface; +use ilLanguage; +use ilADNNotificationGUI; +use ilObjAdministrativeNotificationAccess; +use ilBiblFieldFilterGUI; use ilADNAbstractGUI; use ILIAS\UI\URLBuilder; use ILIAS\Data\URI; @@ -30,17 +35,21 @@ */ class Table { + public const ACTION_EDIT = 'edit'; + public const ACTION_DELETE = 'delete'; + public const ACTION_RESET = 'reset'; + public const ACTION_DUPLICATE = 'duplicate'; private Factory $ui_factory; private Renderer $ui_renderer; - private \ilCtrlInterface $ctrl; - private \ilLanguage $lng; + private ilCtrlInterface $ctrl; + private ilLanguage $lng; private URLBuilder $url_builder; private URLBuilderToken $id_token; protected array $components = []; public function __construct( - private \ilADNNotificationGUI $calling_gui, + private ilADNNotificationGUI $calling_gui, ) { global $DIC; $this->ui_factory = $DIC['ui.factory']; @@ -51,7 +60,9 @@ public function __construct( $this->url_builder = $this->initURIBuilder(); $columns = $this->initColumns(); $actions = $this->initActions(); - $data_retrieval = new DataRetrieval(); + $data_retrieval = new DataRetrieval( + (new ilObjAdministrativeNotificationAccess())->hasUserPermissionTo('write') + ); $this->components[] = $this->ui_factory->table()->data( $this->lng->txt('notifications'), @@ -65,7 +76,7 @@ public function __construct( private function initURIBuilder(): URLBuilder { $url_builder = new URLBuilder( - $this->getURI(\ilBiblFieldFilterGUI::CMD_STANDARD) + $this->getURI(ilBiblFieldFilterGUI::CMD_STANDARD) ); // these are the query parameters this instance is controlling @@ -94,30 +105,28 @@ protected function initColumns(): array protected function initActions(): array { - $actions = [ - 'edit' => $this->ui_factory->table()->action()->single( + return [ + self::ACTION_EDIT => $this->ui_factory->table()->action()->single( $this->lng->txt("btn_edit"), - $this->url_builder->withURI($this->getURI(\ilADNNotificationGUI::CMD_EDIT)), + $this->url_builder->withURI($this->getURI(ilADNNotificationGUI::CMD_EDIT)), $this->id_token ), - 'delete' => $this->ui_factory->table()->action()->standard( + self::ACTION_DELETE => $this->ui_factory->table()->action()->standard( $this->lng->txt("btn_delete"), - $this->url_builder->withURI($this->getURI(\ilADNNotificationGUI::CMD_CONFIRM_DELETE)), + $this->url_builder->withURI($this->getURI(ilADNNotificationGUI::CMD_CONFIRM_DELETE)), $this->id_token )->withAsync(true), - 'reset' => $this->ui_factory->table()->action()->standard( + self::ACTION_RESET => $this->ui_factory->table()->action()->standard( $this->lng->txt("btn_reset"), - $this->url_builder->withURI($this->getURI(\ilADNNotificationGUI::CMD_RESET)), + $this->url_builder->withURI($this->getURI(ilADNNotificationGUI::CMD_RESET)), $this->id_token ), - 'duplicate' => $this->ui_factory->table()->action()->single( + self::ACTION_DUPLICATE => $this->ui_factory->table()->action()->single( $this->lng->txt("btn_duplicate"), - $this->url_builder->withURI($this->getURI(\ilADNNotificationGUI::CMD_DUPLICATE)), + $this->url_builder->withURI($this->getURI(ilADNNotificationGUI::CMD_DUPLICATE)), $this->id_token ), ]; - - return $actions; } /** diff --git a/components/ILIAS/AdvancedMetaData/classes/Record/class.ilAdvancedMDRecordGUI.php b/components/ILIAS/AdvancedMetaData/classes/Record/class.ilAdvancedMDRecordGUI.php index 7339db61158b..e9fdf9a2ed08 100755 --- a/components/ILIAS/AdvancedMetaData/classes/Record/class.ilAdvancedMDRecordGUI.php +++ b/components/ILIAS/AdvancedMetaData/classes/Record/class.ilAdvancedMDRecordGUI.php @@ -341,7 +341,7 @@ public function writeEditForm(?int $a_obj_id = null, ?int $a_sub_id = null): boo */ private function parseSearch(): void { - // this is NOT used for the global search, see ilLuceneAdvancedSearchFields::getFormElement() + // this is NOT used for the global search // (so searchable flag is NOT relevant) // // current usage: wiki page element "[amd] page list" diff --git a/components/ILIAS/AdvancedMetaData/classes/Record/class.ilAdvancedMDRecordParser.php b/components/ILIAS/AdvancedMetaData/classes/Record/class.ilAdvancedMDRecordParser.php index afd47ccdbdaf..37cccc642a70 100755 --- a/components/ILIAS/AdvancedMetaData/classes/Record/class.ilAdvancedMDRecordParser.php +++ b/components/ILIAS/AdvancedMetaData/classes/Record/class.ilAdvancedMDRecordParser.php @@ -88,9 +88,8 @@ public function startParsing(): void */ public function setHandlers($a_xml_parser): void { - xml_set_object($a_xml_parser, $this); - xml_set_element_handler($a_xml_parser, 'handlerBeginTag', 'handlerEndTag'); - xml_set_character_data_handler($a_xml_parser, 'handlerCharacterData'); + xml_set_element_handler($a_xml_parser, $this->handlerBeginTag(...), $this->handlerEndTag(...)); + xml_set_character_data_handler($a_xml_parser, $this->handlerCharacterData(...)); } /** diff --git a/components/ILIAS/AdvancedMetaData/classes/Types/class.ilAdvancedMDFieldDefinitionSelect.php b/components/ILIAS/AdvancedMetaData/classes/Types/class.ilAdvancedMDFieldDefinitionSelect.php index 19b588bdb3bc..3a2eaff7cffb 100755 --- a/components/ILIAS/AdvancedMetaData/classes/Types/class.ilAdvancedMDFieldDefinitionSelect.php +++ b/components/ILIAS/AdvancedMetaData/classes/Types/class.ilAdvancedMDFieldDefinitionSelect.php @@ -714,7 +714,10 @@ protected function updateOptions(): void protected function saveOptions(): void { $this->db_gateway->create($this->getFieldId(), $this->options()); - $this->options = $this->db_gateway->readByID($this->getFieldId()); + $options = $this->db_gateway->readByID($this->getFieldId()); + if ($options) { + $this->options = $this->db_gateway->readByID($this->getFieldId()); + } } public function update(): void @@ -840,7 +843,23 @@ public function importXMLProperty(string $a_key, string $a_value): void public function getValueForXML(ilADT $element): string { - return $element->getSelection(); + $record = ilAdvancedMDRecord::_getInstanceByRecordId($this->getRecordID()); + if (!$record->getParentObject()) { + return (string) $element->getSelection(); + } + /** + * Options of imported local fields don't keep their ID, + * but get assigned a new ID based on order. To conserve + * assigments, the same logic has to be applied here. + */ + $index = 1; + foreach ($this->options()->getOptions() as $option) { + if ($option->optionID() === (int) $element->getSelection()) { + return (string) $index; + } + $index++; + } + return ''; } public function importValueFromXML(string $a_cdata): void @@ -866,7 +885,7 @@ protected function translateLegacyImportValueFromXML(string $value): string $default_language = ilAdvancedMDRecord::_getInstanceByRecordId($this->getRecordId())->getDefaultLanguage(); foreach ($this->options()->getOptions() as $option) { - if ($value = $option->getTranslationInLanguage($default_language)) { + if ($value === $option->getTranslationInLanguage($default_language)) { return (string) $option->optionID(); } } diff --git a/components/ILIAS/AdvancedMetaData/classes/Types/class.ilAdvancedMDFieldDefinitionSelectMulti.php b/components/ILIAS/AdvancedMetaData/classes/Types/class.ilAdvancedMDFieldDefinitionSelectMulti.php index 2ad89faec3da..e229cd4e49e0 100755 --- a/components/ILIAS/AdvancedMetaData/classes/Types/class.ilAdvancedMDFieldDefinitionSelectMulti.php +++ b/components/ILIAS/AdvancedMetaData/classes/Types/class.ilAdvancedMDFieldDefinitionSelectMulti.php @@ -52,16 +52,38 @@ public function importCustomDefinitionFormPostValues(ilPropertyFormGUI $a_form, } public function getValueForXML(ilADT $element): string + { + $record = ilAdvancedMDRecord::_getInstanceByRecordId($this->getRecordID()); + if (!$record->getParentObject()) { + return $this->implodeValuesForXML((array) $element->getSelections()); + } + /** + * Options of imported local fields don't keep their ID, + * but get assigned a new ID based on order. To conserve + * assigments, the same logic has to be applied here. + */ + $index = 1; + $selections = []; + foreach ($this->options()->getOptions() as $option) { + if (in_array($option->optionID(), (array) $element->getSelections())) { + $selections[] = $index; + } + $index++; + } + return $this->implodeValuesForXML($selections); + } + + protected function implodeValuesForXML(array $values): string { return self::XML_SEPARATOR . - implode(self::XML_SEPARATOR, (array) $element->getSelections()) . + implode(self::XML_SEPARATOR, $values) . self::XML_SEPARATOR; } public function importValueFromXML(string $a_cdata): void { $values = []; - foreach (explode(self::XML_SEPARATOR, $a_cdata) as $value) { + foreach (explode(self::XML_SEPARATOR, trim($a_cdata, self::XML_SEPARATOR)) as $value) { $value = $this->translateLegacyImportValueFromXML($value); $values[] = $value; } diff --git a/components/ILIAS/AdvancedMetaData/classes/class.ilAdvancedMDFieldDefinition.php b/components/ILIAS/AdvancedMetaData/classes/class.ilAdvancedMDFieldDefinition.php index a06c262ffda5..ebe99b9c33e8 100755 --- a/components/ILIAS/AdvancedMetaData/classes/class.ilAdvancedMDFieldDefinition.php +++ b/components/ILIAS/AdvancedMetaData/classes/class.ilAdvancedMDFieldDefinition.php @@ -1001,8 +1001,20 @@ public function prepareElementForSearch(ilADTSearchBridge $a_bridge): void */ public function _clone(int $a_new_record_id): self { + $empty_generic_data = new GenericDataImplementation( + $this->generic_data->type(), + 0, + '', + '', + '', + 0, + false, + false, + [] + ); + $class = get_class($this); - $obj = new $class(); + $obj = new $class($empty_generic_data); $obj->setRecordId($a_new_record_id); $obj->setTitle($this->getTitle()); $obj->setDescription($this->getDescription()); diff --git a/components/ILIAS/AdvancedMetaData/classes/class.ilAdvancedMDParser.php b/components/ILIAS/AdvancedMetaData/classes/class.ilAdvancedMDParser.php index 7d52e9a0f7ae..71d022d9d8b2 100755 --- a/components/ILIAS/AdvancedMetaData/classes/class.ilAdvancedMDParser.php +++ b/components/ILIAS/AdvancedMetaData/classes/class.ilAdvancedMDParser.php @@ -17,10 +17,10 @@ *********************************************************************/ declare(strict_types=1); + /** * Adv MD XML Parser * @author Jörg Lützenkirchen - * @extends ilMDSaxParser */ class ilAdvancedMDParser extends ilSaxParser implements ilSaxSubsetParser { diff --git a/components/ILIAS/App/tests/RootFolderTest.php b/components/ILIAS/App/tests/RootFolderTest.php index ce20132bc087..cd5201237cb2 100755 --- a/components/ILIAS/App/tests/RootFolderTest.php +++ b/components/ILIAS/App/tests/RootFolderTest.php @@ -22,18 +22,12 @@ use PHPUnit\Framework\TestCase; -/** - * Class RootFolderTest - * @author Michael Jansen - */ final class RootFolderTest extends TestCase { - private const ALLOWED_ROOT_FOLDER_FILES = [ - '.babelrc.json', + private const array ALLOWED_ROOT_FOLDER_FILES = [ '.eslintrc.json', '.gitignore', '.htaccess', - '.mocharc.json', '.phpunit.result.cache', 'captainhook.local.json', 'phpstan.local.neon', @@ -57,14 +51,13 @@ final class RootFolderTest extends TestCase '.project' ]; - private const ALLOWED_ROOT_FOLDER_DIRS = [ + private const array ALLOWED_ROOT_FOLDER_DIRS = [ '.git', '.github', '.idea', 'artifacts', 'cli', 'components', - 'Customizing', 'docs', 'extern', 'lang', @@ -121,7 +114,7 @@ static function (\DirectoryIterator $file): bool { $this->assertEmpty( $unexpected_files, - sprintf( + \sprintf( 'The following files are not expected in the ILIAS root folder: %s', implode(', ', $unexpected_files) ) @@ -147,7 +140,7 @@ static function (\DirectoryIterator $file): bool { $this->assertEmpty( $unexpected_directories, - sprintf( + \sprintf( 'The following directories are not expected in the ILIAS root folder: %s', implode(', ', $unexpected_directories) ) diff --git a/components/ILIAS/AuthApache/classes/class.ilAuthProviderApache.php b/components/ILIAS/AuthApache/classes/class.ilAuthProviderApache.php index 773358483a76..ec90eb2ae657 100755 --- a/components/ILIAS/AuthApache/classes/class.ilAuthProviderApache.php +++ b/components/ILIAS/AuthApache/classes/class.ilAuthProviderApache.php @@ -18,23 +18,18 @@ declare(strict_types=1); -/** - * Apache auth provider - * @author Stefan Meyer - * @author Michael Jansen - */ final class ilAuthProviderApache extends ilAuthProvider implements ilAuthProviderAccountMigrationInterface { - public const APACHE_AUTH_TYPE_DIRECT_MAPPING = 1; - public const APACHE_AUTH_TYPE_EXTENDED_MAPPING = 2; - public const APACHE_AUTH_TYPE_BY_FUNCTION = 3; + public const int APACHE_AUTH_TYPE_DIRECT_MAPPING = 1; + public const int APACHE_AUTH_TYPE_EXTENDED_MAPPING = 2; + public const int APACHE_AUTH_TYPE_BY_FUNCTION = 3; - private const ENV_APACHE_AUTH_INDICATOR_NAME = 'apache_auth_indicator_name'; + private const string ENV_APACHE_AUTH_INDICATOR_NAME = 'apache_auth_indicator_name'; - private const ERR_WRONG_LOGIN = 'err_wrong_login'; + private const string ERR_WRONG_LOGIN = 'err_wrong_login'; - private const APACHE_ENABLE_LDAP = 'apache_enable_ldap'; - private const APACHE_LDAP_SID = 'apache_ldap_sid'; + private const string APACHE_ENABLE_LDAP = 'apache_enable_ldap'; + private const string APACHE_LDAP_SID = 'apache_ldap_sid'; private readonly ilSetting $settings; private string $migration_account = ''; @@ -65,7 +60,7 @@ public function doAuthentication(ilAuthStatus $status): bool $validIndicatorValues = array_filter(array_map( 'trim', - str_getcsv($this->settings->get('apache_auth_indicator_value', '')) + str_getcsv($this->settings->get('apache_auth_indicator_value', ''), ',', '"', '\\') )); //TODO PHP8-REVIEW: $DIC->http()->request()->getServerParams()['apache_auth_indicator_name'] if ( diff --git a/components/ILIAS/AuthApache/classes/class.ilWhiteListUrlValidator.php b/components/ILIAS/AuthApache/classes/class.ilWhiteListUrlValidator.php index f85669be815d..fc5031a136c6 100755 --- a/components/ILIAS/AuthApache/classes/class.ilWhiteListUrlValidator.php +++ b/components/ILIAS/AuthApache/classes/class.ilWhiteListUrlValidator.php @@ -18,18 +18,13 @@ declare(strict_types=1); -/** - * Class ilWhiteListUrlValidator - * @author Michael Jansen - */ -final class ilWhiteListUrlValidator +final readonly class ilWhiteListUrlValidator { - /** @var string[] */ + /** @var list */ private array $whitelist; /** - * ilWhiteListUrlValidator constructor. - * @param string[] $whitelist + * @param list $whitelist */ public function __construct(private string $url, array $whitelist) { diff --git a/components/ILIAS/AuthApache/tests/ilWhiteListUrlValidatorTest.php b/components/ILIAS/AuthApache/tests/ilWhiteListUrlValidatorTest.php index 1698cb9e4b84..b71e8aa26268 100755 --- a/components/ILIAS/AuthApache/tests/ilWhiteListUrlValidatorTest.php +++ b/components/ILIAS/AuthApache/tests/ilWhiteListUrlValidatorTest.php @@ -20,10 +20,6 @@ use PHPUnit\Framework\TestCase; -/** - * Class ilWhiteListUrlValidatorTest - * @author Michael Jansen - */ final class ilWhiteListUrlValidatorTest extends TestCase { public static function domainProvider(): array @@ -64,9 +60,7 @@ public static function domainProvider(): array ]; } - /** - * @dataProvider domainProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('domainProvider')] public function testValidator(string $domain, array $whitelist, bool $result): void { $this->assertSame((new ilWhiteListUrlValidator($domain, $whitelist))->isValid(), $result); diff --git a/components/ILIAS/AuthShibboleth/classes/Config/class.ilShibbolethSettingsForm.php b/components/ILIAS/AuthShibboleth/classes/Config/class.ilShibbolethSettingsForm.php index ef415050b213..861697dcb181 100755 --- a/components/ILIAS/AuthShibboleth/classes/Config/class.ilShibbolethSettingsForm.php +++ b/components/ILIAS/AuthShibboleth/classes/Config/class.ilShibbolethSettingsForm.php @@ -162,7 +162,7 @@ public function initForm(): void $instructions = $field->textarea($this->txt('auth_login_instructions')) ->withValue($this->settings->get('login_instructions', '')) ->withAdditionalTransformation($custom_trafo(function ($v): void { - $this->settings->set('login_instructions', (string) $v); + $this->settings->set('login_instructions', htmlspecialchars_decode($v)); })); $data_manipulation = $field->text($this->txt('shib_data_conv')) @@ -219,7 +219,8 @@ public function initForm(): void public function setValuesByPost(): void { - $this->form = $this->form->withRequest($this->request); + $request = $this->request->withParsedBody(array_map('htmlspecialchars', $this->request->getParsedBody())); + $this->form = $this->form->withRequest($request); } protected function fillObject(): bool diff --git a/components/ILIAS/AuthShibboleth/classes/User/class.shibUser.php b/components/ILIAS/AuthShibboleth/classes/User/class.shibUser.php index f768713ee5b4..3386e17908a8 100755 --- a/components/ILIAS/AuthShibboleth/classes/User/class.shibUser.php +++ b/components/ILIAS/AuthShibboleth/classes/User/class.shibUser.php @@ -1,4 +1,5 @@ setGender($this->shibServerData->getGender()); } if ($shibConfig->getUpdateTitle()) { - $this->setTitle($this->shibServerData->getTitle()); + $this->setUTitle($this->shibServerData->getTitle()); } if ($shibConfig->getUpdateInstitution()) { $this->setInstitution($this->shibServerData->getInstitution()); @@ -112,7 +115,7 @@ public function createFields(): void $this->setPasswd(md5((string) end($array)), ilObjUser::PASSWD_CRYPTED); $this->setGender($this->shibServerData->getGender()); $this->setExternalAccount($this->shibServerData->getLogin()); - $this->setTitle($this->shibServerData->getTitle()); + $this->setUTitle($this->shibServerData->getTitle()); $this->setInstitution($this->shibServerData->getInstitution()); $this->setDepartment($this->shibServerData->getDepartment()); $this->setStreet($this->shibServerData->getStreet()); @@ -182,13 +185,18 @@ public function isNew(): bool protected function cleanName(string $name): string { - return strtolower( - strtr( - $name, - 'ŠŒŽšœžŸ¥µÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿ', - 'SOZsozYYuAAAAAAACEEEEIIIIDNOOOOOOUUUUYsaaaaaaaceeeeiiiionoooooouuuuyy' - ) - ); + $umlaut_map = [ + 'ä' => 'ae', 'ö' => 'oe', 'ü' => 'ue', + 'Ä' => 'Ae', 'Ö' => 'Oe', 'Ü' => 'Ue', + 'ß' => 'ss' + ]; + $name = strtr($name, $umlaut_map); + + $form_d = new UTFNormal(); + $name = $form_d->formD()->transform($name); + $name = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', (string) $name); + + return strtolower((string) preg_replace('/[^a-zA-Z0-9\s]/', '', $name)); } private function loginExists(string $login, int $usr_id): bool diff --git a/components/ILIAS/AuthShibboleth/classes/class.ilAuthShibbolethSettingsGUI.php b/components/ILIAS/AuthShibboleth/classes/class.ilAuthShibbolethSettingsGUI.php index fe2dc4cde05b..0a657e843ee8 100755 --- a/components/ILIAS/AuthShibboleth/classes/class.ilAuthShibbolethSettingsGUI.php +++ b/components/ILIAS/AuthShibboleth/classes/class.ilAuthShibbolethSettingsGUI.php @@ -209,7 +209,7 @@ protected function deleteRules(): bool } $rule_ids = $this->wrapper->post()->retrieve( 'rule_ids', - $this->refinery->to()->listOf($this->refinery->to()->int()) + $this->refinery->to()->listOf($this->refinery->kindlyTo()->int()) ); foreach ($rule_ids as $rule_id) { $rule = new ilShibbolethRoleAssignmentRule($rule_id); diff --git a/components/ILIAS/AuthShibboleth/resources/shib_login.php b/components/ILIAS/AuthShibboleth/resources/shib_login.php index b6107aea74a3..d37144b4336b 100644 --- a/components/ILIAS/AuthShibboleth/resources/shib_login.php +++ b/components/ILIAS/AuthShibboleth/resources/shib_login.php @@ -1,4 +1,5 @@ http()->request()->getServerParams(); @@ -36,6 +40,14 @@ $DIC->ui()->mainTemplate()->setContent($DIC->ui()->renderer()->render($message_box)); $DIC->ui()->mainTemplate()->printToStdout(); } else { - // authentication is done here -> - $DIC->ctrl()->redirectByClass(ilStartUpGUI::class, 'doShibbolethAuthentication'); + // authentication is done here + $login = new LoginPerformer( + ilLoggerFactory::getLogger('init'), + $DIC->ctrl(), + $DIC->ui()->mainTemplate(), + $DIC->language(), + $DIC['ilAuthSession'] + ); + + $login->doShibbolethAuthentication(); } diff --git a/components/ILIAS/AuthShibboleth/resources/shib_logout.php b/components/ILIAS/AuthShibboleth/resources/shib_logout.php index 4d721747838a..d7a46c37f3cc 100644 --- a/components/ILIAS/AuthShibboleth/resources/shib_logout.php +++ b/components/ILIAS/AuthShibboleth/resources/shib_logout.php @@ -1,4 +1,5 @@ http()->wrapper()->query(); if ( @@ -24,7 +26,7 @@ && $q->has('action') && $q->retrieve('action', $DIC->refinery()->to()->string()) === 'logout' ) { - ilInitialisation::initILIAS(); + entry_point("ILIAS Legacy Initialisation Adapter"); // Logout out user from application // Destroy application session/cookie etc $GLOBALS['DIC']['ilAuthSession']->logout(); @@ -45,7 +47,7 @@ ilContext::init(ilContext::CONTEXT_SOAP); // Load ILIAS libraries and initialise ILIAS in non-web context - ilInitialisation::initILIAS(); + entry_point("ILIAS Legacy Initialisation Adapter"); // Set SOAP header $server = new SoapServer('https://' . $_SERVER['HTTP_HOST'] . $_SERVER['SCRIPT_NAME'] . '/LogoutNotification.wsdl'); diff --git a/components/ILIAS/AuthShibboleth/src/LoginPerformer.php b/components/ILIAS/AuthShibboleth/src/LoginPerformer.php new file mode 100755 index 000000000000..417f2473f451 --- /dev/null +++ b/components/ILIAS/AuthShibboleth/src/LoginPerformer.php @@ -0,0 +1,90 @@ + + */ +class LoginPerformer +{ + public function __construct( + private ilLogger $logger, + private ilCtrlInterface $ctrl, + private ilGlobalTemplateInterface $template, + private ilLanguage $lng, + private ilAuthSession $auth_ession, + ) { + } + + public function doShibbolethAuthentication(): void + { + $this->logger->debug('Trying shibboleth authentication'); + + $credentials = new ilAuthFrontendCredentialsShibboleth(); + $credentials->initFromRequest(); + + $provider_factory = new ilAuthProviderFactory(); + $provider = $provider_factory->getProviderByAuthMode($credentials, ilAuthUtils::AUTH_SHIBBOLETH); + + $status = ilAuthStatus::getInstance(); + + $frontend_factory = new ilAuthFrontendFactory(); + $frontend_factory->setContext(ilAuthFrontendFactory::CONTEXT_STANDARD_FORM); + $frontend = $frontend_factory->getFrontend( + $this->auth_ession, + $status, + $credentials, + [$provider] + ) ?? throw new ilException('No frontend found'); + $frontend->authenticate(); + + switch ($status->getStatus()) { + case ilAuthStatus::STATUS_AUTHENTICATED: + $this->logger->debug('Authentication successful; Redirecting to starting page.'); + ilInitialisation::redirectToStartingPage(); + + // no break + case ilAuthStatus::STATUS_ACCOUNT_MIGRATION_REQUIRED: + $this->ctrl->redirect($this, 'showAccountMigration'); + + // no break + case ilAuthStatus::STATUS_AUTHENTICATION_FAILED: + $this->template->setOnScreenMessage('failure', $status->getTranslatedReason(), true); + $this->ctrl->redirect($this, 'showLoginPage'); + } + + $this->template->setOnScreenMessage('failure', $this->lng->txt('err_wrong_login'), true); + $this->ctrl->setTargetScript('ilias.php'); + $this->ctrl->redirectByClass(ilStartUpGUI::class, 'showLoginPage'); + } +} diff --git a/components/ILIAS/Authentication/Authentication.php b/components/ILIAS/Authentication/Authentication.php index 8390519017fc..414e42e6eb48 100644 --- a/components/ILIAS/Authentication/Authentication.php +++ b/components/ILIAS/Authentication/Authentication.php @@ -64,8 +64,8 @@ public function offsetUnset(mixed $offset): void ); $contribute[Component\Resource\PublicAsset::class] = fn() => - new Component\Resource\Endpoint($this, "sessioncheck.php"); + new Component\Resource\Endpoint($this, 'sessioncheck.php'); $contribute[Component\Resource\PublicAsset::class] = fn() => - new Component\Resource\ComponentJS($this, "session_reminder.js"); + new Component\Resource\ComponentJS($this, 'js/dist/SessionReminder.min.js'); } } diff --git a/components/ILIAS/Authentication/classes/Cookie/class.ilCookie.php b/components/ILIAS/Authentication/classes/Cookie/class.ilCookie.php deleted file mode 100755 index f9d6a165002b..000000000000 --- a/components/ILIAS/Authentication/classes/Cookie/class.ilCookie.php +++ /dev/null @@ -1,118 +0,0 @@ - - * - */ -class ilCookie -{ - private string $name; - private string $value = ''; - private int $expire = 0; - private string $path = ''; - private string $domain = ''; - private bool $secure = false; - private bool $http_only = false; - - public function __construct(string $a_name) - { - $this->name = $a_name; - } - - public function setName(string $a_name): void - { - $this->name = $a_name; - } - - /** - * Get name - */ - public function getName(): string - { - return $this->name; - } - - /** - * Currently no restriction on cookie length. - * RFC 2965 suggests a minimum of 4096 bytes - */ - public function setValue(string $a_value): void - { - $this->value = $a_value; - } - - public function getValue(): string - { - return $this->value; - } - - public function setExpire(int $a_expire): void - { - $this->expire = $a_expire; - } - - public function getExpire(): int - { - return $this->expire; - } - - public function setPath(string $a_path): void - { - $this->path = $a_path; - } - - public function getPath(): string - { - return $this->path; - } - - public function setDomain(string $a_domain): void - { - $this->domain = $a_domain; - } - - public function getDomain(): string - { - return $this->domain; - } - - public function setSecure(bool $a_status): void - { - $this->secure = $a_status; - } - - public function isSecure(): bool - { - return $this->secure; - } - - public function setHttpOnly(bool $a_http_only): void - { - $this->http_only = $a_http_only; - } - - public function isHttpOnly(): bool - { - return $this->http_only; - } -} diff --git a/components/ILIAS/Authentication/classes/Cron/class.ilAuthDestroyExpiredSessionsCron.php b/components/ILIAS/Authentication/classes/Cron/class.ilAuthDestroyExpiredSessionsCron.php index 1f2dda127417..20e7b429c4d0 100755 --- a/components/ILIAS/Authentication/classes/Cron/class.ilAuthDestroyExpiredSessionsCron.php +++ b/components/ILIAS/Authentication/classes/Cron/class.ilAuthDestroyExpiredSessionsCron.php @@ -50,7 +50,7 @@ public function getDescription(): string public function hasAutoActivation(): bool { - return true; + return false; } public function hasFlexibleSchedule(): bool @@ -58,9 +58,17 @@ public function hasFlexibleSchedule(): bool return true; } + public function getValidScheduleTypes(): array + { + return [ + \ILIAS\Cron\Job\Schedule\JobScheduleType::IN_MINUTES, + \ILIAS\Cron\Job\Schedule\JobScheduleType::IN_HOURS + ]; + } + public function getDefaultScheduleType(): \ILIAS\Cron\Job\Schedule\JobScheduleType { - return \ILIAS\Cron\Job\Schedule\JobScheduleType::IN_HOURS; + return \ILIAS\Cron\Job\Schedule\JobScheduleType::IN_MINUTES; } public function getDefaultScheduleValue(): ?int diff --git a/components/ILIAS/Authentication/classes/External/UserAttributeMapping/class.ilExternalAuthUserAttributeMapping.php b/components/ILIAS/Authentication/classes/External/UserAttributeMapping/class.ilExternalAuthUserAttributeMapping.php index 575ec60acd1e..e14550c8e81f 100755 --- a/components/ILIAS/Authentication/classes/External/UserAttributeMapping/class.ilExternalAuthUserAttributeMapping.php +++ b/components/ILIAS/Authentication/classes/External/UserAttributeMapping/class.ilExternalAuthUserAttributeMapping.php @@ -18,16 +18,12 @@ declare(strict_types=1); -/** - * Class ilExternalAuthUserAttributeMapping - * @author Michael Jansen - */ class ilExternalAuthUserAttributeMapping implements ArrayAccess, Countable, Iterator { protected ilDBInterface $db; protected string $authMode; protected int $authSourceId; - /** @var ilExternalAuthUserAttributeMappingRule[] */ + /** @var list */ protected array $mapping = []; public function __construct(string $authMode, int $authSourceId = 0) @@ -78,7 +74,7 @@ public function offsetGet($offset): ?ilExternalAuthUserAttributeMappingRule public function offsetSet($offset, $value): void { - if (is_null($offset)) { + if ($offset === null) { $this->mapping[] = $value; } else { $this->mapping[$offset] = $value; diff --git a/components/ILIAS/Authentication/classes/External/UserAttributeMapping/class.ilExternalAuthUserAttributeMappingRule.php b/components/ILIAS/Authentication/classes/External/UserAttributeMapping/class.ilExternalAuthUserAttributeMappingRule.php index 4fb6482ef75f..53e20f46e338 100755 --- a/components/ILIAS/Authentication/classes/External/UserAttributeMapping/class.ilExternalAuthUserAttributeMappingRule.php +++ b/components/ILIAS/Authentication/classes/External/UserAttributeMapping/class.ilExternalAuthUserAttributeMappingRule.php @@ -18,10 +18,6 @@ declare(strict_types=1); -/** - * Class ilExternalAuthUserAttributeMappingRule - * @author Michael Jansen - */ class ilExternalAuthUserAttributeMappingRule { protected string $attribute = ''; diff --git a/components/ILIAS/Authentication/classes/External/UserAttributeMapping/class.ilExternalAuthUserCreationAttributeMappingFilter.php b/components/ILIAS/Authentication/classes/External/UserAttributeMapping/class.ilExternalAuthUserCreationAttributeMappingFilter.php index c08917e1e936..6432a9de590c 100755 --- a/components/ILIAS/Authentication/classes/External/UserAttributeMapping/class.ilExternalAuthUserCreationAttributeMappingFilter.php +++ b/components/ILIAS/Authentication/classes/External/UserAttributeMapping/class.ilExternalAuthUserCreationAttributeMappingFilter.php @@ -18,10 +18,6 @@ declare(strict_types=1); -/** - * Class ilExternalAuthUserCreationAttributeMappingFilter - * @author Michael Jansen - */ class ilExternalAuthUserCreationAttributeMappingFilter extends FilterIterator { public function accept(): bool diff --git a/components/ILIAS/Authentication/classes/External/UserAttributeMapping/class.ilExternalAuthUserUpdateAttributeMappingFilter.php b/components/ILIAS/Authentication/classes/External/UserAttributeMapping/class.ilExternalAuthUserUpdateAttributeMappingFilter.php index 4bb4a005bb2c..4591f9033e77 100755 --- a/components/ILIAS/Authentication/classes/External/UserAttributeMapping/class.ilExternalAuthUserUpdateAttributeMappingFilter.php +++ b/components/ILIAS/Authentication/classes/External/UserAttributeMapping/class.ilExternalAuthUserUpdateAttributeMappingFilter.php @@ -18,10 +18,6 @@ declare(strict_types=1); -/** - * Class ilExternalAuthUserUpdateAttributeMappingFilter - * @author Michael Jansen - */ class ilExternalAuthUserUpdateAttributeMappingFilter extends FilterIterator { public function accept(): bool diff --git a/components/ILIAS/Authentication/classes/Form/ApacheAuthSettingsForm.php b/components/ILIAS/Authentication/classes/Form/ApacheAuthSettingsForm.php new file mode 100644 index 000000000000..3d9c338a9a23 --- /dev/null +++ b/components/ILIAS/Authentication/classes/Form/ApacheAuthSettingsForm.php @@ -0,0 +1,210 @@ + $values + */ + public function __construct( + private int $ref_id, + private object $parentObject, + private string $show_command, + private string $save_command, + private array $values = [], + ?UIFactory $ui_factory = null, + ?ilLanguage $lng = null, + ?ilRbacSystem $rbac_system = null, + ?ilRbacReview $rbac_review = null, + ?ilCtrlInterface $ctrl = null + ) { + global $DIC; + + $this->ui_factory = $ui_factory ?? $DIC->ui()->factory(); + $this->lng = $lng ?? $DIC->language(); + $this->rbac_system = $rbac_system ?? $DIC->rbac()->system(); + $this->rbac_review = $rbac_review ?? $DIC->rbac()->review(); + $this->ctrl = $ctrl ?? $DIC->ctrl(); + + $this->ui_field = $this->ui_factory->input()->field(); + + $this->lng->loadLanguageModule('auth'); + } + + public function buildForm(): StandardForm + { + $access = $this->rbac_system->checkAccess('write', $this->ref_id); + + $form = $this->ui_factory->input()->container()->form()->standard( + $this->ctrl->getFormAction($this->parentObject, $access ? $this->save_command : $this->show_command), + [ + 'apache_enable_auth' => $this->buildEnableAuthInput(), + 'apache_enable_local' => $this->ui_field->checkbox($this->lng->txt('apache_enable_local')) + ->withValue((bool) ($this->values['apache_enable_local'] ?? true)), + 'apache_enable_ldap' => $this->buildLdapEnableInput(), + 'apache_auth_indicator_name' => $this->ui_field->text($this->lng->txt('apache_auth_indicator_name')) + ->withRequired(true) + ->withValue($this->values['apache_auth_indicator_name'] ?? ''), + 'apache_auth_indicator_value' => $this->ui_field->text($this->lng->txt('apache_auth_indicator_value')) + ->withRequired(true) + ->withValue($this->values['apache_auth_indicator_value'] ?? ''), + 'apache_auth_enable_override_login_page' => $this->buildAuthEnableOverrideLoginPageInput(), + 'apache_auth_authenticate_on_login_page' => $this->ui_field->checkbox($this->lng->txt('apache_auth_authenticate_on_login_page')) + ->withValue((bool) ($this->values['apache_auth_authenticate_on_login_page'] ?? true)), + 'apache_auth_username_config' => $this->ui_field->section([ + 'apache_auth_username_config_type' => $this->buildAuthUsernameConfigTypeInput() + ], $this->lng->txt('apache_auth_username_config')), + 'apache_auth_security' => $this->ui_field->section([ + 'apache_auth_domains' => $this->ui_field->textarea( + $this->lng->txt('apache_auth_domains'), + $this->lng->txt('apache_auth_domains_description') + )->withValue($this->values['apache_auth_domains'] ?? '') + ], $this->lng->txt('apache_auth_security')) + ] + ); + + if (!$access) { + $form = $form->withSubmitLabel($this->lng->txt('refresh')); + } + + return $form; + } + + private function buildEnableAuthInput(): OptionalGroup + { + $roleOptions = []; + foreach ($this->rbac_review->getGlobalRolesArray() as $role) { + $roleOptions[$role['obj_id']] = ilObject::_lookupTitle($role['obj_id']); + } + + $apache_default_role = $this->ui_field->select( + $this->lng->txt('apache_default_role'), + $roleOptions + ); + + $apache_local_autocreate = $this->ui_field->optionalGroup([ + 'apache_default_role' => $apache_default_role + ], $this->lng->txt('apache_autocreate')); + + return $this->ui_field->optionalGroup([ + 'apache_local_autocreate' => $apache_local_autocreate + ], $this->lng->txt('apache_enable_auth')) + ->withValue( + $this->checkGroupEnabled('apache_enable_auth') + ? [ + 'apache_local_autocreate' => $this->checkGroupEnabled('apache_local_autocreate') + ? [ + 'apache_default_role' => $this->values['apache_default_role'] ?? 4 + ] + : null + ] + : null + ); + } + + private function buildLdapEnableInput(): Checkbox|OptionalGroup + { + $servers = ilLDAPServer::getServerIds(); + + if ($servers !== []) { + $options[0] = $this->lng->txt('select_one'); + foreach ($servers as $server_id) { + $ldap_server = new ilLDAPServer($server_id); + $options[$server_id] = $ldap_server->getName(); + } + + $apache_enable_ldap = $this->ui_field->optionalGroup([ + 'apache_ldap_sid' => $this->ui_field->select($this->lng->txt('auth_ldap_server_ds'), $options) + ->withRequired(true) + ], $this->lng->txt('apache_enable_ldap'), $this->lng->txt('apache_ldap_hint_ldap_must_be_configured')) + ->withValue( + $this->checkGroupEnabled('apache_enable_ldap') + ? [ + 'apache_ldap_sid' => $this->values['apache_ldap_sid'] ?? ilLDAPServer::getDataSource(ilAuthUtils::AUTH_APACHE) + ] + : null + ); + } else { + $apache_enable_ldap = $this->ui_field->checkbox( + $this->lng->txt('apache_enable_ldap'), + $this->lng->txt('apache_ldap_hint_ldap_must_be_configured') + )->withValue((bool) ($this->values['apache_enable_ldap'] ?? true)); + } + + return $apache_enable_ldap; + } + + private function buildAuthEnableOverrideLoginPageInput(): OptionalGroup + { + return $this->ui_field->optionalGroup([ + 'apache_auth_target_override_login_page' => $this->ui_field->text($this->lng->txt('apache_auth_target_override_login')) + ->withRequired(true) + ], $this->lng->txt('apache_auth_enable_override_login')) + ->withValue( + $this->checkGroupEnabled('apache_auth_enable_override_login_page') + ? [ + 'apache_auth_target_override_login_page' => $this->values['apache_auth_target_override_login_page'] ?? '' + ] + : null + ); + } + + private function buildAuthUsernameConfigTypeInput(): SwitchableGroup + { + return $this->ui_field->switchableGroup([ + '1' => $this->ui_field->group([ + 'apache_auth_username_direct_mapping_fieldname' => $this->ui_field->text( + $this->lng->txt('apache_auth_username_direct_mapping_fieldname') + )->withValue($this->values['apache_auth_username_direct_mapping_fieldname'] ?? '') + ], $this->lng->txt('apache_auth_username_direct_mapping')), + '2' => $this->ui_field->group([], $this->lng->txt('apache_auth_username_extended_mapping'))->withDisabled(true), + '3' => $this->ui_field->group([], $this->lng->txt('apache_auth_username_by_function')), + ], $this->lng->txt('apache_auth_username_config_type')) + ->withValue($this->values['apache_auth_username_config_type'] ?? '1'); + } + + private function checkGroupEnabled(string $post_var): bool + { + return isset($this->values[$post_var]) && $this->values[$post_var]; + } +} diff --git a/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontend.php b/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontend.php index 65886b6ea7b3..f35393d9bfdf 100755 --- a/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontend.php +++ b/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontend.php @@ -18,17 +18,11 @@ declare(strict_types=1); -/** - * - * @author Stefan Meyer - * - */ -//TODO check why authfront does not include frontendInterface -class ilAuthFrontend +class ilAuthFrontend implements ilAuthFrontendInterface { - public const MIG_EXTERNAL_ACCOUNT = 'mig_ext_account'; - public const MIG_TRIGGER_AUTHMODE = 'mig_trigger_auth_mode'; - public const MIG_DESIRED_AUTHMODE = 'mig_desired_auth_mode'; + public const string MIG_EXTERNAL_ACCOUNT = 'mig_ext_account'; + public const string MIG_TRIGGER_AUTHMODE = 'mig_trigger_auth_mode'; + public const string MIG_DESIRED_AUTHMODE = 'mig_desired_auth_mode'; private ilLogger $logger; private ilSetting $settings; @@ -36,20 +30,15 @@ class ilAuthFrontend private ilAuthCredentials $credentials; private ilAuthStatus $status; - /** @var ilAuthProvider[] */ + /** @var list */ private array $providers; private ilAuthSession $auth_session; private ilAppEventHandler $ilAppEventHandler; private ilUserProfile $user_profile; - private bool $authenticated = false; - /** - * @param ilAuthSession $session - * @param ilAuthStatus $status - * @param ilAuthCredentials $credentials - * @param ilAuthProvider[] $providers + * @param list $providers */ public function __construct(ilAuthSession $session, ilAuthStatus $status, ilAuthCredentials $credentials, array $providers) { @@ -67,42 +56,29 @@ public function __construct(ilAuthSession $session, ilAuthStatus $status, ilAuth $this->user_profile = new ilUserProfile(); } - /** - * Get auth session - */ public function getAuthSession(): ilAuthSession { return $this->auth_session; } - /** - * Get auth credentials - */ public function getCredentials(): ilAuthCredentials { return $this->credentials; } /** - * Get providers - * @return ilAuthProviderInterface[] $provider + * @return list */ public function getProviders(): array { return $this->providers; } - /** - * @return \ilAuthStatus - */ public function getStatus(): ilAuthStatus { return $this->status; } - /** - * Reset status - */ public function resetStatus(): void { $this->getStatus()->setStatus(ilAuthStatus::STATUS_UNDEFINED); @@ -110,10 +86,6 @@ public function resetStatus(): void $this->getStatus()->setAuthenticatedUserId(ANONYMOUS_USER_ID); } - /** - * Migrate Account to existing user account - * @throws \InvalidArgumentException if current auth provider does not support account migration - */ public function migrateAccount(ilAuthSession $session): bool { if (!$session->isAuthenticated()) { @@ -148,9 +120,6 @@ public function migrateAccount(ilAuthSession $session): bool return $this->handleAuthenticationFail(); } - /** - * Create new user account - */ public function migrateAccountNew(): bool { foreach ($this->providers as $provider) { @@ -169,10 +138,6 @@ public function migrateAccountNew(): bool } - - /** - * Try to authenticate user - */ public function authenticate(): bool { foreach ($this->getProviders() as $provider) { @@ -189,7 +154,7 @@ public function authenticate(): bool return $this->handleAuthenticationSuccess($provider); case ilAuthStatus::STATUS_ACCOUNT_MIGRATION_REQUIRED: - $this->logger->notice("Account migration required."); + $this->logger->notice('Account migration required.'); if ($provider instanceof ilAuthProviderAccountMigrationInterface) { return $this->handleAccountMigration($provider); } @@ -205,10 +170,6 @@ public function authenticate(): bool return $this->handleAuthenticationFail(); } - /** - * Handle account migration - * @param ilAuthProvider $provider - */ protected function handleAccountMigration(ilAuthProviderAccountMigrationInterface $provider): bool { $this->logger->debug('Trigger auth mode: ' . $provider->getTriggerAuthMode()); @@ -227,13 +188,11 @@ protected function handleAccountMigration(ilAuthProviderAccountMigrationInterfac return true; } - /** - * Handle successful authentication - */ protected function handleAuthenticationSuccess(ilAuthProviderInterface $provider): bool { $user = ilObjectFactory::getInstanceByObjId($this->getStatus()->getAuthenticatedUserId(), false); + $this->getStatus()->setReason('auth_err_invalid_user_account'); // reset expired status $this->getAuthSession()->setExpired(false); @@ -241,7 +200,6 @@ protected function handleAuthenticationSuccess(ilAuthProviderInterface $provider $this->logger->error('Cannot instantiate user account with id: ' . $this->getStatus()->getAuthenticatedUserId()); $this->getStatus()->setStatus(ilAuthStatus::STATUS_AUTHENTICATION_FAILED); $this->getStatus()->setAuthenticatedUserId(ANONYMOUS_USER_ID); - $this->getStatus()->setReason('auth_err_invalid_user_account'); return false; } @@ -249,7 +207,6 @@ protected function handleAuthenticationSuccess(ilAuthProviderInterface $provider $this->logger->info('Authentication failed for inactive user with id and too may login attempts: ' . $this->getStatus()->getAuthenticatedUserId()); $this->getStatus()->setStatus(ilAuthStatus::STATUS_AUTHENTICATION_FAILED); $this->getStatus()->setAuthenticatedUserId(ANONYMOUS_USER_ID); - $this->getStatus()->setReason('auth_err_login_attempts_deactivation'); return false; } @@ -257,7 +214,6 @@ protected function handleAuthenticationSuccess(ilAuthProviderInterface $provider $this->logger->info('Authentication failed for inactive user with id: ' . $this->getStatus()->getAuthenticatedUserId()); $this->getStatus()->setStatus(ilAuthStatus::STATUS_AUTHENTICATION_FAILED); $this->getStatus()->setAuthenticatedUserId(ANONYMOUS_USER_ID); - $this->getStatus()->setReason('err_inactive'); return false; } @@ -273,7 +229,6 @@ protected function handleAuthenticationSuccess(ilAuthProviderInterface $provider $this->getStatus()->setStatus(ilAuthStatus::STATUS_AUTHENTICATION_FAILED); $this->getStatus()->setAuthenticatedUserId(ANONYMOUS_USER_ID); } - $this->getStatus()->setReason('time_limit_reached'); return false; } @@ -282,13 +237,6 @@ protected function handleAuthenticationSuccess(ilAuthProviderInterface $provider $this->logger->info('Authentication failed (wrong ip) for user with id: ' . $this->getStatus()->getAuthenticatedUserId()); $this->getStatus()->setStatus(ilAuthStatus::STATUS_AUTHENTICATION_FAILED); $this->getStatus()->setAuthenticatedUserId(ANONYMOUS_USER_ID); - - $this->getStatus()->setTranslatedReason( - sprintf( - $this->lng->txt('wrong_ip_detected'), - $_SERVER['REMOTE_ADDR'] - ) - ); return false; } @@ -298,7 +246,6 @@ protected function handleAuthenticationSuccess(ilAuthProviderInterface $provider $this->logger->info('Authentication failed: simultaneous logins forbidden for user: ' . $this->getStatus()->getAuthenticatedUserId()); $this->getStatus()->setStatus(ilAuthStatus::STATUS_AUTHENTICATION_FAILED); $this->getStatus()->setAuthenticatedUserId(ANONYMOUS_USER_ID); - $this->getStatus()->setReason('simultaneous_login_detected'); return false; } @@ -348,7 +295,7 @@ protected function handleAuthenticationSuccess(ilAuthProviderInterface $provider // --- anonymous/registered user - if (PHP_SAPI !== "cli") { + if (PHP_SAPI !== 'cli') { $this->logger->info( 'logged in as ' . $user->getLogin() . ', remote:' . $_SERVER['REMOTE_ADDR'] . ':' . $_SERVER['REMOTE_PORT'] . @@ -364,16 +311,15 @@ protected function handleAuthenticationSuccess(ilAuthProviderInterface $provider $this->ilAppEventHandler->raise( 'components/ILIAS/Authentication', 'afterLogin', - array( - 'username' => $user->getLogin()) + [ + 'username' => $user->getLogin() + ] ); + $this->getStatus()->setReason(''); return true; } - /** - * Check activation - */ protected function checkActivation(ilObjUser $user): bool { return $user->getActive(); @@ -402,36 +348,27 @@ protected function checkExceededLoginAttempts(ilObjUser $user): bool return $numLoginAttempts < $maxLoginAttempts; } - /** - * Check time limit - */ protected function checkTimeLimit(ilObjUser $user): bool { return $user->checkTimeLimit(); } - /** - * Check ip - */ protected function checkIp(ilObjUser $user): bool { $clientip = $user->getClientIP(); - if (trim($clientip) !== "") { - $clientip = preg_replace("/[^0-9.?*,:]+/", "", $clientip); - $clientip = str_replace([".", "?", "*", ","], ["\\.", "[0-9]", "[0-9]*", "|"], $clientip); + if (trim($clientip) !== '') { + $clientip = preg_replace('/[^0-9.?*,:]+/', '', $clientip); + $clientip = str_replace(['.', '?', '*', ','], ["\\.", '[0-9]', '[0-9]*', '|'], $clientip); ilLoggerFactory::getLogger('auth')->debug('Check ip ' . $clientip . ' against ' . $_SERVER['REMOTE_ADDR']); - if (!preg_match("/^" . $clientip . "$/", $_SERVER["REMOTE_ADDR"])) { + if (!preg_match('/^' . $clientip . '$/', $_SERVER['REMOTE_ADDR'])) { return false; } } return true; } - /** - * Check simultaneous logins - */ protected function checkSimultaneousLogins(ilObjUser $user): bool { $this->logger->debug('Setting prevent simultaneous session is: ' . $this->settings->get('ps_prevent_simultaneous_logins')); diff --git a/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontendCredentials.php b/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontendCredentials.php index e319dff0bec9..447545e736be 100755 --- a/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontendCredentials.php +++ b/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontendCredentials.php @@ -18,13 +18,9 @@ declare(strict_types=1); -/** - * @author Stefan Meyer - */ class ilAuthFrontendCredentials implements ilAuthCredentials { - private ilLogger $logger; - + protected ilLogger $logger; private string $username = ''; private string $password = ''; private string $auth_mode = ''; @@ -35,50 +31,32 @@ public function __construct() $this->logger = $DIC->logger()->auth(); } - /** - * Set username - */ public function setUsername(string $a_name): void { $this->logger->debug('Username: "' . $a_name . '"'); $this->username = trim($a_name); } - /** - * Get username - */ public function getUsername(): string { return $this->username; } - /** - * Set password - */ public function setPassword(string $a_password): void { $this->password = $a_password; } - /** - * Get password - */ public function getPassword(): string { return $this->password; } - /** - * Set auth mode - */ public function setAuthMode(string $a_auth_mode): void { $this->auth_mode = $a_auth_mode; } - /** - * Get auth mode - */ public function getAuthMode(): string { return $this->auth_mode; diff --git a/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontendCredentialsApache.php b/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontendCredentialsApache.php index a4f2b5caa5f5..339838f00c09 100755 --- a/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontendCredentialsApache.php +++ b/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontendCredentialsApache.php @@ -20,24 +20,14 @@ use Psr\Http\Message\ServerRequestInterface; -/** - * Description of class class - * - * @author Stefan Meyer - * @author Michael Jansen - * - */ class ilAuthFrontendCredentialsApache extends ilAuthFrontendCredentials { private ServerRequestInterface $httpRequest; private ilCtrlInterface $ctrl; private ilSetting $settings; - private ilLogger $logger; public function __construct(ServerRequestInterface $httpRequest, ilCtrlInterface $ctrl) { - global $DIC; - $this->logger = $DIC->logger()->auth(); $this->httpRequest = $httpRequest; $this->ctrl = $ctrl; $this->settings = new ilSetting('apache_auth'); @@ -51,11 +41,11 @@ public function __construct(ServerRequestInterface $httpRequest, ilCtrlInterface public function tryAuthenticationOnLoginPage(): void { $cmd = (string) ($this->httpRequest->getQueryParams()['cmd'] ?? ''); - if ('' === $cmd) { + if ($cmd === '') { $cmd = (string) ($this->httpRequest->getParsedBody()['cmd'] ?? ''); } - if ('force_login' === $cmd) { + if ($cmd === 'force_login') { return; } @@ -76,11 +66,11 @@ public function tryAuthenticationOnLoginPage(): void } $path = (string) ($this->httpRequest->getServerParams()['REQUEST_URI'] ?? ''); - if (strpos($path, '/') === 0) { + if (str_starts_with($path, '/')) { $path = substr($path, 1); } - if (strpos($path, 'http') !== 0) { + if (!str_starts_with($path, 'http')) { $parts = parse_url(ILIAS_HTTP_PATH); $path = $parts['scheme'] . '://' . $parts['host'] . '/' . $path; } diff --git a/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontendCredentialsHTTP.php b/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontendCredentialsHTTP.php index 0ccdcc2bcbcc..669d47f4c86a 100755 --- a/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontendCredentialsHTTP.php +++ b/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontendCredentialsHTTP.php @@ -18,17 +18,8 @@ declare(strict_types=1); -/** - * HTTP auth credentials - * - * @author Stefan Meyer - * - */ class ilAuthFrontendCredentialsHTTP extends ilAuthFrontendCredentials { - /** - * Init credentials from request - */ public function initFromRequest(): void { $this->setUsername($_SERVER['PHP_AUTH_USER']); diff --git a/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontendCredentialsSoap.php b/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontendCredentialsSoap.php index f63d3292f187..7dba3e0168f8 100755 --- a/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontendCredentialsSoap.php +++ b/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontendCredentialsSoap.php @@ -23,21 +23,16 @@ class ilAuthFrontendCredentialsSoap extends ilAuthFrontendCredentials { private ServerRequestInterface $httpRequest; - private ilCtrlInterface $ctrl; - private ilSetting $settings; - private ilAuthSession $authSession; - private \ilGlobalTemplateInterface $main_tpl; - private ilLogger $logger; + private ilGlobalTemplateInterface $main_tpl; public function __construct(ServerRequestInterface $httpRequest, ilCtrlInterface $ctrl, ilSetting $settings) { global $DIC; $this->main_tpl = $DIC->ui()->mainTemplate(); $this->authSession = $DIC['ilAuthSession']; - $this->logger = $DIC->logger()->auth(); $this->httpRequest = $httpRequest; $this->ctrl = $ctrl; $this->settings = $settings; @@ -49,15 +44,6 @@ public function __construct(ServerRequestInterface $httpRequest, ilCtrlInterface */ public function tryAuthenticationOnLoginPage(): void { - $cmd = ''; - if (isset($this->httpRequest->getQueryParams()['cmd']) && is_string($this->httpRequest->getQueryParams()['cmd'])) { - $cmd = $this->httpRequest->getQueryParams()['cmd']; - } - if ('' === $cmd && - isset($this->httpRequest->getParsedBody()['cmd']) && is_string($this->httpRequest->getParsedBody()['cmd'])) { - $cmd = $this->httpRequest->getParsedBody()['cmd']; - } - $passedSso = ''; if (isset($this->httpRequest->getQueryParams()['passed_sso']) && is_string($this->httpRequest->getQueryParams()['passed_sso'])) { $passedSso = $this->httpRequest->getQueryParams()['passed_sso']; @@ -67,7 +53,7 @@ public function tryAuthenticationOnLoginPage(): void return; } - if (!(bool) $this->settings->get('soap_auth_active', "")) { + if (!$this->settings->get('soap_auth_active', '')) { return; } @@ -89,7 +75,6 @@ public function tryAuthenticationOnLoginPage(): void $this, [$provider] ); - $frontend->authenticate(); switch ($status->getStatus()) { diff --git a/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontendFactory.php b/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontendFactory.php index 5af093a7f2cc..38589e43880b 100755 --- a/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontendFactory.php +++ b/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontendFactory.php @@ -18,61 +18,47 @@ declare(strict_types=1); -/** - * Factory for auth frontend classes. - * - * @author Stefan Meyer - * - */ class ilAuthFrontendFactory { - private const CONTEXT_UNDEFINED = 0; - - // authentication with id and password. Used for standard form based authentication - // soap auth (login) but not for (CLI (cron)?) and HTTP basic authentication - public const CONTEXT_STANDARD_FORM = 2; - - // CLI context for cron - public const CONTEXT_CLI = 3; - - // Rest soap context - public const CONTEXT_WS = 4; - - // http auth - public const CONTEXT_HTTP = 5; - + private const int CONTEXT_UNDEFINED = 0; + /** + * Authentication with id and password. Used for standard form based authentication, + * SOAP auth (login), but not for (CLI (cron)) and HTTP basic authentication + */ + public const int CONTEXT_STANDARD_FORM = 2; + public const int CONTEXT_CLI = 3; + /** @var int Rest soap context */ + public const int CONTEXT_WS = 4; + public const int CONTEXT_HTTP = 5; private int $context = self::CONTEXT_UNDEFINED; private ilLogger $logger; - - /** - * Constructor - */ public function __construct() { global $DIC; $this->logger = $DIC->logger()->auth(); } - /** - * Set context for following authentication requests - */ public function setContext(int $a_context): void { $this->context = $a_context; } - /** - * Get context - */ public function getContext(): int { return $this->context; } - public function getFrontend(ilAuthSession $session, ilAuthStatus $status, ilAuthCredentials $credentials, array $providers): ?ilAuthFrontendInterface - { + /** + * @param list $providers + */ + public function getFrontend( + ilAuthSession $session, + ilAuthStatus $status, + ilAuthCredentials $credentials, + array $providers + ): ?ilAuthFrontendInterface { switch ($this->getContext()) { case self::CONTEXT_CLI: $this->logger->debug('Init auth frontend with standard auth context'); @@ -118,6 +104,7 @@ public function getFrontend(ilAuthSession $session, ilAuthStatus $status, ilAuth $this->logger->error('Trying to init auth with empty context'); break; } + return null; } } diff --git a/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontendHTTP.php b/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontendHTTP.php index 23fbbba91f49..c70ebecf1987 100755 --- a/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontendHTTP.php +++ b/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontendHTTP.php @@ -18,15 +18,19 @@ declare(strict_types=1); -/** - * @author Stefan Meyer - */ class ilAuthFrontendHTTP extends ilAuthFrontend implements ilAuthFrontendInterface { private ilLogger $logger; - public function __construct(ilAuthSession $session, ilAuthStatus $status, ilAuthCredentials $credentials, array $providers) - { + /** + * @param list $providers + */ + public function __construct( + ilAuthSession $session, + ilAuthStatus $status, + ilAuthCredentials $credentials, + array $providers + ) { parent::__construct($session, $status, $credentials, $providers); global $DIC; @@ -50,7 +54,7 @@ public function authenticate(): bool return $this->handleAuthenticationSuccess($provider); case ilAuthStatus::STATUS_ACCOUNT_MIGRATION_REQUIRED: - $this->logger->notice("Account migration required."); + $this->logger->notice('Account migration required.'); break; case ilAuthStatus::STATUS_AUTHENTICATION_FAILED: @@ -62,14 +66,11 @@ public function authenticate(): bool return $this->handleAuthenticationFail(); } - /** - * Draw basic auth - */ protected function handleAuthenticationFail(): bool { parent::handleAuthenticationFail(); - header("WWW-Authenticate: Basic realm=\"" . CLIENT_ID . "\""); + header('WWW-Authenticate: Basic realm="' . CLIENT_ID . '"'); header('HTTP/1.0 401 Unauthorized'); return false; } diff --git a/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontendWS.php b/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontendWS.php index 033c7ab0609d..6e2edefaf7a1 100755 --- a/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontendWS.php +++ b/components/ILIAS/Authentication/classes/Frontend/class.ilAuthFrontendWS.php @@ -18,10 +18,6 @@ declare(strict_types=1); -/** - * @author Stefan Meyer - * - */ class ilAuthFrontendWS extends ilAuthFrontend implements ilAuthFrontendInterface { } diff --git a/components/ILIAS/Authentication/classes/Frontend/class.ilAuthStandardFormFrontend.php b/components/ILIAS/Authentication/classes/Frontend/class.ilAuthStandardFormFrontend.php index 535c894e2a20..28a1758abb21 100755 --- a/components/ILIAS/Authentication/classes/Frontend/class.ilAuthStandardFormFrontend.php +++ b/components/ILIAS/Authentication/classes/Frontend/class.ilAuthStandardFormFrontend.php @@ -18,12 +18,6 @@ declare(strict_types=1); -/** - * Auth class for form based authentication - * - * @author Stefan Meyer - * - */ class ilAuthStandardFormFrontend extends ilAuthFrontend implements ilAuthFrontendInterface { } diff --git a/components/ILIAS/Authentication/classes/Logout/ConfigurableLogoutTarget.php b/components/ILIAS/Authentication/classes/Logout/ConfigurableLogoutTarget.php index d2ac1b9a2e22..5e12f56eb762 100644 --- a/components/ILIAS/Authentication/classes/Logout/ConfigurableLogoutTarget.php +++ b/components/ILIAS/Authentication/classes/Logout/ConfigurableLogoutTarget.php @@ -31,8 +31,8 @@ class ConfigurableLogoutTarget implements LogoutTarget { - public const INTERNAL_RESSOURCE = 'internal_ressource'; - public const EXTERNAL_RESSOURCE = 'external_ressource'; + public const string INTERNAL_RESSOURCE = 'internal_ressource'; + public const string EXTERNAL_RESSOURCE = 'external_ressource'; public function __construct( private readonly ilCtrlInterface $ctrl, diff --git a/components/ILIAS/Authentication/classes/Pages/AuthPageLanguagesOverviewTable.php b/components/ILIAS/Authentication/classes/Pages/AuthPageLanguagesOverviewTable.php index 16acae458179..2c2987fe31f2 100644 --- a/components/ILIAS/Authentication/classes/Pages/AuthPageLanguagesOverviewTable.php +++ b/components/ILIAS/Authentication/classes/Pages/AuthPageLanguagesOverviewTable.php @@ -30,9 +30,9 @@ class AuthPageLanguagesOverviewTable implements UI\Component\Table\DataRetrieval { - public const ACTIVATE = 'activate'; - public const DEACTIVATE = 'deactivate'; - public const EDIT = 'edit'; + public const string ACTIVATE = 'activate'; + public const string DEACTIVATE = 'deactivate'; + public const string EDIT = 'edit'; private ServerRequestInterface $request; private Data\Factory $data_factory; @@ -60,7 +60,7 @@ public function getComponent(): UI\Component\Table\Data return $this->ui_factory ->table() - ->data($this->lng->txt($this->context->pageLanguageIdentifier(true)), $columns, $this) + ->data($this, $this->lng->txt($this->context->pageLanguageIdentifier(true)), $columns) ->withId(self::class . '_' . $this->context->value) ->withOrder(new \ILIAS\Data\Order('language', \ILIAS\Data\Order::ASC)) ->withActions($actions) diff --git a/components/ILIAS/Authentication/classes/Password/class.LocalUserPasswordEncoderFactory.php b/components/ILIAS/Authentication/classes/Password/class.LocalUserPasswordEncoderFactory.php index bbd682839533..b6697a30563b 100644 --- a/components/ILIAS/Authentication/classes/Password/class.LocalUserPasswordEncoderFactory.php +++ b/components/ILIAS/Authentication/classes/Password/class.LocalUserPasswordEncoderFactory.php @@ -113,10 +113,12 @@ public function setSupportedEncoders(array $supported_encoders): void $this->supported_encoders = []; foreach ($supported_encoders as $encoder) { if (!($encoder instanceof ilPasswordEncoder) || !$encoder->isSupportedByRuntime()) { - throw new ilUserException(sprintf( - 'One of the passed encoders is not valid: %s.', - print_r($encoder, true) - )); + throw new ilUserException( + \sprintf( + 'One of the passed encoders is not valid: %s.', + print_r($encoder, true) + ) + ); } $this->supported_encoders[$encoder->getName()] = $encoder; } diff --git a/components/ILIAS/Authentication/classes/Password/class.LocalUserPasswordManager.php b/components/ILIAS/Authentication/classes/Password/class.LocalUserPasswordManager.php index c05070affcbb..cb3c50c04bcb 100644 --- a/components/ILIAS/Authentication/classes/Password/class.LocalUserPasswordManager.php +++ b/components/ILIAS/Authentication/classes/Password/class.LocalUserPasswordManager.php @@ -32,7 +32,7 @@ class LocalUserPasswordManager { - private const MIN_SALT_SIZE = 16; + private const int MIN_SALT_SIZE = 16; private static ?self $instance = null; private ?LocalUserPasswordEncoderFactory $encoderFactory = null; @@ -72,17 +72,21 @@ public function __construct(array $config = []) } if (!$this->getEncoderName()) { - throw new ilUserException(sprintf( - '"password_encoder" must be set in %s.', - print_r($config, true) - )); + throw new ilUserException( + \sprintf( + '"password_encoder" must be set in %s.', + print_r($config, true) + ) + ); } if (!$this->getEncoderFactory() instanceof LocalUserPasswordEncoderFactory) { - throw new ilUserException(sprintf( - '"encoder_factory" must be instance of LocalUserPasswordEncoderFactory and set in %s.', - print_r($config, true) - )); + throw new ilUserException( + \sprintf( + '"encoder_factory" must be instance of LocalUserPasswordEncoderFactory and set in %s.', + print_r($config, true) + ) + ); } } @@ -180,7 +184,7 @@ public function encodePassword(ilObjUser $user, string $raw): void public function isEncodingTypeSupported(string $name): bool { - return in_array($name, $this->getEncoderFactory()->getSupportedEncoderNames()); + return \in_array($name, $this->getEncoderFactory()->getSupportedEncoderNames()); } public function verifyPassword(ilObjUser $user, string $raw): bool diff --git a/components/ILIAS/Authentication/classes/Password/class.ilLocalUserPasswordSettingsGUI.php b/components/ILIAS/Authentication/classes/Password/class.ilLocalUserPasswordSettingsGUI.php index 7cf0e3ccc730..704b60a28c14 100644 --- a/components/ILIAS/Authentication/classes/Password/class.ilLocalUserPasswordSettingsGUI.php +++ b/components/ILIAS/Authentication/classes/Password/class.ilLocalUserPasswordSettingsGUI.php @@ -30,10 +30,10 @@ class ilLocalUserPasswordSettingsGUI { - private const NEW_PASSWORD = 'new_password'; - private const CURRENT_PASSWORD = 'current_password'; - public const CMD_SHOW_PASSWORD = 'showPassword'; - public const CMD_SAVE_PASSWORD = 'savePassword'; + private const string NEW_PASSWORD = 'new_password'; + private const string CURRENT_PASSWORD = 'current_password'; + public const string CMD_SHOW_PASSWORD = 'showPassword'; + public const string CMD_SAVE_PASSWORD = 'savePassword'; private readonly ServerRequestInterface $request; private readonly ilErrorHandling $error; private readonly Refinery $refinery; @@ -193,14 +193,6 @@ function (Closure $cls, Password $value): string { break; case ilAuthUtils::AUTH_SHIBBOLETH: - case ilAuthUtils::AUTH_CAS: - if (ilDAVActivationChecker::_isActive()) { - $title = $this->lng->txt('chg_ilias_and_webfolder_password'); - } else { - $title = $this->lng->txt('chg_ilias_password'); - } - - break; default: $title = $this->lng->txt('chg_ilias_password'); @@ -235,7 +227,6 @@ public function savePassword(): void $errors = [self::CURRENT_PASSWORD => [], self::NEW_PASSWORD => []]; if (!$form->getError()) { - $data = $form->getData(); $error = false; if ($cp && $cp->getError()) { $error = true; diff --git a/components/ILIAS/Authentication/classes/Provider/class.ilAuthProvider.php b/components/ILIAS/Authentication/classes/Provider/class.ilAuthProvider.php index 603b5df17fc4..dbc107d318b4 100755 --- a/components/ILIAS/Authentication/classes/Provider/class.ilAuthProvider.php +++ b/components/ILIAS/Authentication/classes/Provider/class.ilAuthProvider.php @@ -18,29 +18,11 @@ declare(strict_types=1); -/** - * Base class for authentication providers (ldap, apache, ...) - * - * @author Stefan Meyer - * - */ abstract class ilAuthProvider implements ilAuthProviderInterface { - private const STATUS_UNDEFINED = 0; - private const STATUS_AUTHENTICATION_SUCCESS = 1; - private const STATUS_AUTHENTICATION_FAILED = 2; - private const STATUS_MIGRATION = 3; - - private ilLogger $logger; - private ilAuthCredentials $credentials; - private int $status = self::STATUS_UNDEFINED; - private int $user_id = 0; - /** - * Constructor - */ public function __construct(ilAuthCredentials $credentials) { global $DIC; @@ -48,26 +30,16 @@ public function __construct(ilAuthCredentials $credentials) $this->credentials = $credentials; } - /** - * Get logger - * @return \ilLogger $logger - */ public function getLogger(): ilLogger { return $this->logger; } - /** - * @return \ilAuthCredentials $credentials - */ public function getCredentials(): ilAuthCredentials { return $this->credentials; } - /** - * Handle failed authentication - */ protected function handleAuthenticationFail(ilAuthStatus $status, string $a_reason): bool { $status->setStatus(ilAuthStatus::STATUS_AUTHENTICATION_FAILED); diff --git a/components/ILIAS/Authentication/classes/Provider/class.ilAuthProviderCliFactory.php b/components/ILIAS/Authentication/classes/Provider/class.ilAuthProviderCliFactory.php index 4e257e2442fc..00f73a1368b5 100755 --- a/components/ILIAS/Authentication/classes/Provider/class.ilAuthProviderCliFactory.php +++ b/components/ILIAS/Authentication/classes/Provider/class.ilAuthProviderCliFactory.php @@ -38,7 +38,7 @@ public function getProviderByAuthMode(ilAuthCredentials $credentials, $a_authmod return $provider->withoutPasswordVerification(); default: - throw new CronException("The cron CLI script supports local authentication only."); + throw new CronException('The cron CLI script supports local authentication only.'); } } } diff --git a/components/ILIAS/Authentication/classes/Provider/class.ilAuthProviderFactory.php b/components/ILIAS/Authentication/classes/Provider/class.ilAuthProviderFactory.php index c037e5286c15..8eeba171a0b8 100755 --- a/components/ILIAS/Authentication/classes/Provider/class.ilAuthProviderFactory.php +++ b/components/ILIAS/Authentication/classes/Provider/class.ilAuthProviderFactory.php @@ -45,12 +45,13 @@ public function getProviders(ilAuthCredentials $credentials): array $sequence = $auth_determination->getAuthModeSequence($credentials->getUsername()); $providers = []; - foreach ($sequence as $position => $authmode) { + foreach ($sequence as $authmode) { $provider = $this->getProviderByAuthMode($credentials, (string) $authmode); if ($provider instanceof ilAuthProviderInterface) { $providers[] = $provider; } } + return $providers; } @@ -77,10 +78,6 @@ public function getProviderByAuthMode(ilAuthCredentials $credentials, $a_authmod $this->logger->debug('Using apache authentication.'); return new ilAuthProviderApache($credentials); - case ilAuthUtils::AUTH_CAS: - $this->logger->debug('Using CAS authentication'); - return new ilAuthProviderCAS($credentials); - case ilAuthUtils::AUTH_SHIBBOLETH: $this->logger->debug('Using shibboleth authentication.'); return new ilAuthProviderShibboleth($credentials); diff --git a/components/ILIAS/Authentication/classes/Setup/AbandonCASAuthModeUpdateObjective.php b/components/ILIAS/Authentication/classes/Setup/AbandonCASAuthModeUpdateObjective.php new file mode 100644 index 000000000000..5ddb2f3c6a33 --- /dev/null +++ b/components/ILIAS/Authentication/classes/Setup/AbandonCASAuthModeUpdateObjective.php @@ -0,0 +1,73 @@ +db = $db; + } + + public function step_1(): void + { + $default_auth_mode_result = $this->db->query( + 'SELECT value FROM settings WHERE module = ' . $this->db->quote('common', ilDBConstants::T_TEXT) + . ' AND keyword = ' . $this->db->quote('auth_mode', ilDBConstants::T_TEXT) + ); + + $default_auth_mode = (int) ($this->db->fetchAssoc($default_auth_mode_result)['value'] ?? ilAuthUtils::AUTH_LOCAL); + + $this->db->manipulateF( + 'UPDATE ' . self::TABLE_NAME . ' SET auth_mode = %s WHERE auth_mode = %s', + [ilDBConstants::T_TEXT, ilDBConstants::T_TEXT], + [$default_auth_mode === ilAuthUtils::AUTH_LOCAL ? 'default' : 'local', 'cas'] + ); + } + + public function step_2(): void + { + $settings = [ + 'cas_server', + 'cas_port', + 'cas_uri', + 'cas_login_instructions', + 'cas_active', + 'cas_create_users', + 'cas_allow_local', + 'cas_user_default_role', + ]; + + $this->db->manipulate( + 'DELETE FROM settings WHERE module = ' . $this->db->quote('common', ilDBConstants::T_TEXT) . ' AND ' + . $this->db->in('keyword', $settings, false, ilDBConstants::T_TEXT), + ); + } +} diff --git a/components/ILIAS/Authentication/classes/Setup/AbandonLoadDependantSessionDatabaseUpdateObjective.php b/components/ILIAS/Authentication/classes/Setup/AbandonLoadDependantSessionDatabaseUpdateObjective.php deleted file mode 100644 index 68bee1e819df..000000000000 --- a/components/ILIAS/Authentication/classes/Setup/AbandonLoadDependantSessionDatabaseUpdateObjective.php +++ /dev/null @@ -1,70 +0,0 @@ -db = $db; - } - - public function step_1(): void - { - $this->db->manipulate( - 'DELETE FROM settings WHERE ' . $this->db->in( - 'keyword', - [ - 'session_handling_type', - 'session_max_count', - 'session_min_idle', - 'session_max_idle', - 'session_max_idle_after_first_request' - ] - ) - ); - - if ($this->db->tableExists('usr_session_log')) { - $this->db->dropTable('usr_session_log', false); - } - - if ($this->db->tableColumnExists('usr_session_stats', 'max_sessions')) { - $this->db->dropTableColumn('usr_session_stats', 'max_sessions'); - } - - if ($this->db->tableColumnExists('usr_session_stats', 'closed_limit')) { - $this->db->dropTableColumn('usr_session_stats', 'closed_limit'); - } - - if ($this->db->tableColumnExists('usr_session_stats', 'closed_idle')) { - $this->db->dropTableColumn('usr_session_stats', 'closed_idle'); - } - - if ($this->db->tableColumnExists('usr_session_stats', 'closed_idle_first')) { - $this->db->dropTableColumn('usr_session_stats', 'closed_idle_first'); - } - } -} diff --git a/components/ILIAS/Authentication/classes/Setup/class.ilAuthenticationSetupAgent.php b/components/ILIAS/Authentication/classes/Setup/class.ilAuthenticationSetupAgent.php index aa163d0cf354..80c2934823a0 100755 --- a/components/ILIAS/Authentication/classes/Setup/class.ilAuthenticationSetupAgent.php +++ b/components/ILIAS/Authentication/classes/Setup/class.ilAuthenticationSetupAgent.php @@ -18,6 +18,7 @@ declare(strict_types=1); +use ILIAS\Authentication\Setup\AbandonCASAuthModeUpdateObjective; use ILIAS\Setup; use ILIAS\Refinery; @@ -25,7 +26,7 @@ class ilAuthenticationSetupAgent implements Setup\Agent { use Setup\Agent\HasNoNamedObjective; - protected const DEFAULT_SESSION_EXPIRE_IN_SECONDS = 1_800; + protected const int DEFAULT_SESSION_EXPIRE_IN_SECONDS = 1_800; public function __construct(protected Refinery\Factory $refinery) { @@ -38,8 +39,8 @@ public function hasConfig(): bool public function getArrayToConfigTransformation(): Refinery\Transformation { - return $this->refinery->custom()->transformation(function ($data): \ilAuthenticationSetupConfig { - return new ilAuthenticationSetupConfig($data["session_max_idle"] ?? self::DEFAULT_SESSION_EXPIRE_IN_SECONDS); + return $this->refinery->custom()->transformation(function ($data): ilAuthenticationSetupConfig { + return new ilAuthenticationSetupConfig($data['session_max_idle'] ?? self::DEFAULT_SESSION_EXPIRE_IN_SECONDS); }); } @@ -60,13 +61,10 @@ public function getUpdateObjective(?Setup\Config $config = null): Setup\Objectiv return new Setup\ObjectiveCollection( 'Authentication', true, + new ilSessionMaxIdleIsSetObjective($config), new ilDatabaseUpdateStepsExecutedObjective( - new ilAuthenticationDatabaseUpdateSteps8() + new AbandonCASAuthModeUpdateObjective() ), - new ilDatabaseUpdateStepsExecutedObjective( - new AbandonAuthRichTextEditorDatabaseUpdateSteps() - ), - new ilSessionMaxIdleIsSetObjective($config) ); } @@ -74,10 +72,7 @@ public function getUpdateObjective(?Setup\Config $config = null): Setup\Objectiv 'Authentication', true, new ilDatabaseUpdateStepsExecutedObjective( - new ilAuthenticationDatabaseUpdateSteps8() - ), - new ilDatabaseUpdateStepsExecutedObjective( - new AbandonAuthRichTextEditorDatabaseUpdateSteps() + new AbandonCASAuthModeUpdateObjective() ), ); } @@ -94,11 +89,7 @@ public function getStatusObjective(Setup\Metrics\Storage $storage): Setup\Object true, new ilDatabaseUpdateStepsMetricsCollectedObjective( $storage, - new ilAuthenticationDatabaseUpdateSteps8() - ), - new ilDatabaseUpdateStepsMetricsCollectedObjective( - $storage, - new AbandonAuthRichTextEditorDatabaseUpdateSteps() + new AbandonCASAuthModeUpdateObjective() ), ); } diff --git a/components/ILIAS/Authentication/classes/Setup/class.ilSessionMaxIdleIsSetObjective.php b/components/ILIAS/Authentication/classes/Setup/class.ilSessionMaxIdleIsSetObjective.php index 80e74f48cd57..9457b936d42f 100644 --- a/components/ILIAS/Authentication/classes/Setup/class.ilSessionMaxIdleIsSetObjective.php +++ b/components/ILIAS/Authentication/classes/Setup/class.ilSessionMaxIdleIsSetObjective.php @@ -23,7 +23,7 @@ class ilSessionMaxIdleIsSetObjective implements Setup\Objective { public function __construct( - protected ilAuthenticationSetupConfig $config + protected Setup\Config $config ) { } @@ -71,14 +71,16 @@ public function achieve(Setup\Environment $environment): Setup\Environment $url = $ini->readVariable('server', 'http_path'); $filename = uniqid((string) mt_rand(), true) . '.php'; $url .= '/' . $filename; - $key = bin2hex(random_bytes(32)); - $this->generateServerInfoFile($key, $filename); + $token = bin2hex(random_bytes(32)); + $this->generateServerInfoFile($filename, $token); try { + $curl = null; if (ilCurlConnection::_isCurlExtensionLoaded()) { - $result = $this->getPHPIniValuesByCurl($settings, $key, $url); + $curl = $this->getCurlConnection($settings, $url, $token); + $result = $curl->exec(); } else { - $result = $this->getPHPIniValuesByFileGetContents($key, $url); + $result = $this->getPHPIniValuesByFileGetContents($url, $token); } } catch (Throwable $e) { $io->inform( @@ -92,6 +94,7 @@ public function achieve(Setup\Environment $environment): Setup\Environment return $environment; } finally { + $curl?->close(); unlink("public/$filename"); } @@ -136,10 +139,44 @@ public function achieve(Setup\Environment $environment): Setup\Environment public function isApplicable(Setup\Environment $environment): bool { + $factory = $environment->getResource(Setup\Environment::RESOURCE_SETTINGS_FACTORY); + /** @var ilSetting $settings */ + $settings = $factory->settingsFor('common'); + /** @var ilIniFile $ini */ + $ini = $environment->getResource(Setup\Environment::RESOURCE_ILIAS_INI); + /** @var Setup\CLI\IOWrapper $io */ + $io = $environment->getResource(Setup\Environment::RESOURCE_ADMIN_INTERACTION); + + $url = $ini->readVariable('server', 'http_path'); + + if (ilCurlConnection::_isCurlExtensionLoaded()) { + try { + $curl = null; + $curl = $this->getCurlConnection($settings, $url); + $curl->exec(); + $result = $curl->getInfo(CURLINFO_HTTP_CODE); + if ($result !== 200) { + throw new Exception(); + } + } catch (Exception) { + $this->infoNoConnection($io); + return false; + } finally { + $curl?->close(); + } + } else { + try { + $this->getPHPIniValuesByFileGetContents($url); + } catch (Exception) { + $this->infoNoConnection($io); + return false; + } + } + return true; } - private function generateServerInfoFile(string $key, string $filename): void + private function generateServerInfoFile(string $filename, string $token): void { $content = <<init(); $curl->setOpt(CURLOPT_SSL_VERIFYPEER, 0); $curl->setOpt(CURLOPT_SSL_VERIFYHOST, 0); $curl->setOpt(CURLOPT_RETURNTRANSFER, 1); - $curl->setOpt(CURLOPT_FOLLOWLOCATION, 1); $curl->setOpt(CURLOPT_MAXREDIRS, 1); - $result = $curl->exec(); - $curl->close(); + return $curl; + } - return $result; + /** + * @throws ErrorException + */ + private function getPHPIniValuesByFileGetContents(string $url, ?string $token = null): string + { + set_error_handler(static function (int $severity, string $message, string $file, int $line): never { + throw new ErrorException($message, $severity, $severity, $file, $line); + }); + + if ($token !== null) { + $url .= '?token=' . $token; + } + + try { + return file_get_contents($url); + } catch (ErrorException $e) { + restore_error_handler(); + throw $e; + } } - private function getPHPIniValuesByFileGetContents(string $key, string $url): string + private function infoNoConnection(Setup\CLI\IOWrapper $io): void { - return file_get_contents("$url?token=$key"); + $message = + "ilSessionMaxIdleIsSetObjective:\n" . + "Cannot establish proper connection to webserver.\n" . + "In the event of an installation the value for session expire\n" . + "will be the default value.\n" . + "In the event of an update, the current value for session expire\n" . + 'is retained.' + ; + + $io->inform($message); } } diff --git a/components/ILIAS/Authentication/classes/StaticUrlHandler.php b/components/ILIAS/Authentication/classes/StaticUrlHandler.php index 1848c6c81fde..6668c3842854 100644 --- a/components/ILIAS/Authentication/classes/StaticUrlHandler.php +++ b/components/ILIAS/Authentication/classes/StaticUrlHandler.php @@ -26,19 +26,17 @@ use ILIAS\StaticURL\Context; use ILIAS\StaticURL\Response\Factory; use ILIAS\StaticURL\Response\Response; -use ilCtrlInterface; use ilLanguage; class StaticUrlHandler extends BaseHandler implements Handler { - private readonly ilCtrlInterface $ctrl; private readonly ilLanguage $language; public function __construct() { global $DIC; - $this->ctrl = $DIC->ctrl(); $this->language = $DIC->language(); + parent::__construct(); } public function getNamespace(): string diff --git a/components/ILIAS/Authentication/classes/class.ilAuthFactory.php b/components/ILIAS/Authentication/classes/class.ilAuthFactory.php index 7fc5b4e5873e..b520c565542d 100755 --- a/components/ILIAS/Authentication/classes/class.ilAuthFactory.php +++ b/components/ILIAS/Authentication/classes/class.ilAuthFactory.php @@ -18,72 +18,47 @@ declare(strict_types=1); -/** - * Authentication frontend factory - * - * @author Stefan Meyer - */ class ilAuthFactory { /** * Web based authentication */ - public const CONTEXT_WEB = 1; - + public const int CONTEXT_WEB = 1; /** * HTTP Auth used for WebDAV and CalDAV * If a special handling for WebDAV or CalDAV is required * overwrite ilAuthHTTP with ilAuthCalDAV and create new * constants. */ - public const CONTEXT_HTTP = 2; - - + public const int CONTEXT_HTTP = 2; /** * SOAP based authentication */ - public const CONTEXT_SOAP = 3; - - public const CONTEXT_CAS = 5; - + public const int CONTEXT_SOAP = 3; + public const int CONTEXT_CAS = 5; /** * Maybe not required. HTTP based authentication for calendar access */ - public const CONTEXT_CALENDAR = 6; - - + public const int CONTEXT_CALENDAR = 6; /** * Calendar authentication with auth token */ - public const CONTEXT_CALENDAR_TOKEN = 7; - - + public const int CONTEXT_CALENDAR_TOKEN = 7; /** * Calendar authentication with auth token */ - public const CONTEXT_ECS = 8; - - - + public const int CONTEXT_ECS = 8; /** * Apache based authentication */ - public const CONTEXT_APACHE = 10; - + public const int CONTEXT_APACHE = 10; private static int $context = self::CONTEXT_WEB; - /** - * - * @return int current context - */ public static function getContext(): int { return self::$context; } - /** - * set context - */ public static function setContext(int $a_context): void { self::$context = $a_context; diff --git a/components/ILIAS/Authentication/classes/class.ilAuthModeDetermination.php b/components/ILIAS/Authentication/classes/class.ilAuthModeDetermination.php index 5cd7f9d36343..188faaacf727 100755 --- a/components/ILIAS/Authentication/classes/class.ilAuthModeDetermination.php +++ b/components/ILIAS/Authentication/classes/class.ilAuthModeDetermination.php @@ -18,31 +18,20 @@ declare(strict_types=1); -/** -* @author Stefan Meyer -*/ class ilAuthModeDetermination { - public const TYPE_MANUAL = 0; - public const TYPE_AUTOMATIC = 1; + public const int TYPE_MANUAL = 0; + public const int TYPE_AUTOMATIC = 1; private static ?ilAuthModeDetermination $instance = null; private ilLogger $logger; - private ilSetting $settings; private ilSetting $commonSettings; - private int $kind = self::TYPE_MANUAL; + /** @var list */ private array $position = []; - - /** - * Constructor (Singleton) - * - * @access private - * - */ private function __construct() { global $DIC; @@ -51,31 +40,22 @@ private function __construct() $this->commonSettings = $DIC->settings(); - $this->settings = new ilSetting("auth_mode_determination"); + $this->settings = new ilSetting('auth_mode_determination'); $this->read(); } - /** - * Get instance - */ public static function _getInstance(): ilAuthModeDetermination { - if (self::$instance) { - return self::$instance; - } - return self::$instance = new ilAuthModeDetermination(); + return self::$instance ??= new ilAuthModeDetermination(); } - /** - * is manual selection - */ public function isManualSelection(): bool { return $this->kind === self::TYPE_MANUAL; } /** - * get kind + * @return int */ public function getKind(): int { @@ -83,27 +63,27 @@ public function getKind(): int } /** - * set kind of determination - * - * @param int TYPE_MANUAL or TYPE_DETERMINATION - * + * @param int $a_kind */ public function setKind(int $a_kind): void { - // TODO check value range + if (!in_array($a_kind, [self::TYPE_MANUAL, self::TYPE_AUTOMATIC], true)) { + throw new InvalidArgumentException('Invalid kind given'); + } + $this->kind = $a_kind; } /** - * get auth mode sequence + * @return list */ public function getAuthModeSequence(string $a_username = ''): array { if ($a_username === '') { - return $this->position ?: array(); + return $this->position; } - $sorted = array(); + $sorted = []; foreach ($this->position as $auth_key) { $sid = ilLDAPServer::getServerIdByAuthMode((string) $auth_key); if ($sid) { @@ -113,11 +93,26 @@ public function getAuthModeSequence(string $a_username = ''): array //#17731 $pattern = str_replace('*', '.*?', $server->getUsernameFilter()); - if (preg_match('/^' . $pattern . '$/', $a_username)) { - $this->logger->debug('Filter matches for ' . $a_username); - array_unshift($sorted, $auth_key); - continue; + foreach (ilAuthUtils::REGEX_DELIMITERS as $delimiter) { + $this->logger->debug('Trying pattern to match username:' . $pattern . ' => ' . $a_username); + set_error_handler(static function (int $severity, string $message, string $file, int $line): never { + throw new ErrorException($message, $severity, $severity, $file, $line); + }); + + try { + if (preg_match($delimiter . '^' . $pattern . '$' . $delimiter . 'i', $a_username) === 1) { + $this->logger->debug('Filter matches for ' . $a_username); + array_unshift($sorted, $auth_key); + continue 2; + } + break; + } catch (Exception $ex) { + $this->logger->warning('Error occurred in preg_match Ex.: ' . $ex->getMessage()); + } finally { + restore_error_handler(); + } } + $this->logger->debug('Filter matches not for ' . $a_username . ' <-> ' . $server->getUsernameFilter()); } } @@ -127,18 +122,13 @@ public function getAuthModeSequence(string $a_username = ''): array return $sorted; } - /** - * get number of auth modes - */ public function getCountActiveAuthModes(): int { return count($this->position); } /** - * set auth mode sequence - * - * @param array position => AUTH_MODE + * @param list $a_pos position => AUTH_MODE * */ public function setAuthModeSequence(array $a_pos): void @@ -146,9 +136,6 @@ public function setAuthModeSequence(array $a_pos): void $this->position = $a_pos; } - /** - * Save settings - */ public function save(): void { $this->settings->deleteAll(); @@ -162,16 +149,12 @@ public function save(): void } - /** - * Read settings - */ private function read(): void { $this->kind = (int) $this->settings->get('kind', (string) self::TYPE_MANUAL); - $soap_active = (bool) $this->commonSettings->get('soap_auth_active', ""); + $soap_active = (bool) $this->commonSettings->get('soap_auth_active', ''); - // apache settings $apache_settings = new ilSetting('apache_auth'); $apache_active = $apache_settings->get('apache_enable_auth'); @@ -182,18 +165,20 @@ private function read(): void if ($auth_mode === null) { break; } + if ($auth_mode) { switch ((int) $auth_mode) { case ilAuthUtils::AUTH_LOCAL: $this->position[] = (int) $auth_mode; break; + case ilAuthUtils::AUTH_LDAP: $auth_id = ilLDAPServer::getServerIdByAuthMode($auth_mode); if ($auth_id === null) { break; } - $server = ilLDAPServer::getInstanceByServerId($auth_id); + $server = ilLDAPServer::getInstanceByServerId($auth_id); if ($server->isActive()) { $this->position[] = $auth_mode; } @@ -214,7 +199,7 @@ private function read(): void default: foreach (ilAuthUtils::getAuthPlugins() as $pl) { if ($pl->isAuthActive((int) $auth_mode)) { - $this->position[] = $auth_mode; + $this->position[] = (int) $auth_mode; } } break; @@ -230,7 +215,7 @@ private function read(): void foreach (ilLDAPServer::_getActiveServerList() as $sid) { $server = ilLDAPServer::getInstanceByServerId($sid); if ($server->isActive() && !in_array(ilAuthUtils::AUTH_LDAP . '_' . $sid, $this->position, true)) { - $this->position[] = (ilAuthUtils::AUTH_LDAP . '_' . $sid); + $this->position[] = ilAuthUtils::AUTH_LDAP . '_' . $sid; } } // end-patch ldap_multiple diff --git a/components/ILIAS/Authentication/classes/class.ilAuthPageEditorGUI.php b/components/ILIAS/Authentication/classes/class.ilAuthPageEditorGUI.php index c2f0e17e17a9..6b0ecb049705 100644 --- a/components/ILIAS/Authentication/classes/class.ilAuthPageEditorGUI.php +++ b/components/ILIAS/Authentication/classes/class.ilAuthPageEditorGUI.php @@ -28,9 +28,9 @@ */ class ilAuthPageEditorGUI { - final public const DEFAULT_COMMAND = 'showPageEditorLanguages'; - final public const LANGUAGE_TABLE_ACTIONS_COMMAND = 'handlePageActions'; - final public const CONTEXT_HTTP_PARAM = 'auth_ipe_context'; + final public const string DEFAULT_COMMAND = 'showPageEditorLanguages'; + final public const string LANGUAGE_TABLE_ACTIONS_COMMAND = 'handlePageActions'; + final public const string CONTEXT_HTTP_PARAM = 'auth_ipe_context'; private ilCtrlInterface $ctrl; private ilLanguage $lng; diff --git a/components/ILIAS/Authentication/classes/class.ilAuthPlugin.php b/components/ILIAS/Authentication/classes/class.ilAuthPlugin.php index 105c7de10737..6c5f8c9a2a9d 100755 --- a/components/ILIAS/Authentication/classes/class.ilAuthPlugin.php +++ b/components/ILIAS/Authentication/classes/class.ilAuthPlugin.php @@ -17,17 +17,10 @@ *********************************************************************/ declare(strict_types=1); -/** - * Authentication plugin - * - * @author Stefan Meyer - */ abstract class ilAuthPlugin extends ilPlugin implements ilAuthDefinition { /** * Does your AuthProvider needs "ext_account"? return true, false otherwise. - * - * @param int $a_auth_id */ abstract public function isExternalAccountNameRequired(int $a_auth_id): bool; @@ -61,7 +54,7 @@ abstract public function isAuthActive(int $a_auth_id): bool; /** * - * @return array IDs of your Auth-Modes and Sub-Modes. + * @return list IDs of your Auth-Modes and Sub-Modes. */ abstract public function getAuthIds(): array; } diff --git a/components/ILIAS/Authentication/classes/class.ilAuthSession.php b/components/ILIAS/Authentication/classes/class.ilAuthSession.php index 9da2b11d9611..d04208edb55c 100755 --- a/components/ILIAS/Authentication/classes/class.ilAuthSession.php +++ b/components/ILIAS/Authentication/classes/class.ilAuthSession.php @@ -18,14 +18,11 @@ declare(strict_types=1); -/** - * @author Stefan Meyer - */ class ilAuthSession { - private const SESSION_AUTH_AUTHENTICATED = '_authsession_authenticated'; - private const SESSION_AUTH_USER_ID = '_authsession_user_id'; - private const SESSION_AUTH_EXPIRED = '_authsession_expired'; + private const string SESSION_AUTH_AUTHENTICATED = '_authsession_authenticated'; + private const string SESSION_AUTH_USER_ID = '_authsession_user_id'; + private const string SESSION_AUTH_EXPIRED = '_authsession_expired'; private static ?ilAuthSession $instance = null; @@ -36,17 +33,12 @@ class ilAuthSession private bool $expired = false; private bool $authenticated = false; - private function __construct(\ilLogger $logger) + private function __construct(ilLogger $logger) { $this->logger = $logger; } - /** - * Get instance - * @param \ilLogger - * @return ilAuthSession - */ - public static function getInstance(\ilLogger $logger): ilAuthSession + public static function getInstance(ilLogger $logger): ilAuthSession { if (self::$instance) { return self::$instance; @@ -54,9 +46,6 @@ public static function getInstance(\ilLogger $logger): ilAuthSession return self::$instance = new self($logger); } - /** - * @return ilLogger - */ protected function getLogger(): ilLogger { return $this->logger; @@ -132,7 +121,7 @@ public function logout(): void */ public function isAuthenticated(): bool { - return $this->authenticated || $this->user_id === (int) ANONYMOUS_USER_ID; + return $this->authenticated || $this->user_id === ANONYMOUS_USER_ID; } /** @@ -155,7 +144,7 @@ public function setAuthenticated(bool $a_status, int $a_user_id): void */ public function isExpired(): bool { - return $this->expired && $this->user_id !== (int) ANONYMOUS_USER_ID; + return $this->expired && $this->user_id !== ANONYMOUS_USER_ID; } /** diff --git a/components/ILIAS/Authentication/classes/class.ilAuthStatus.php b/components/ILIAS/Authentication/classes/class.ilAuthStatus.php index 2a177deda009..ad750718b6ab 100755 --- a/components/ILIAS/Authentication/classes/class.ilAuthStatus.php +++ b/components/ILIAS/Authentication/classes/class.ilAuthStatus.php @@ -18,23 +18,17 @@ declare(strict_types=1); -/** - * Auth status implementation - * - * @author Stefan Meyer - * - */ class ilAuthStatus { private static ?ilAuthStatus $instance = null; private ilLanguage $lng; - public const STATUS_UNDEFINED = 1; - public const STATUS_AUTHENTICATED = 2; - public const STATUS_AUTHENTICATION_FAILED = 3; - public const STATUS_ACCOUNT_MIGRATION_REQUIRED = 4; - public const STATUS_CODE_ACTIVATION_REQUIRED = 5; + public const int STATUS_UNDEFINED = 1; + public const int STATUS_AUTHENTICATED = 2; + public const int STATUS_AUTHENTICATION_FAILED = 3; + public const int STATUS_ACCOUNT_MIGRATION_REQUIRED = 4; + public const int STATUS_CODE_ACTIVATION_REQUIRED = 5; private int $status = self::STATUS_UNDEFINED; private string $reason = ''; diff --git a/components/ILIAS/Authentication/classes/class.ilAuthUtils.php b/components/ILIAS/Authentication/classes/class.ilAuthUtils.php index 5db7325b0b9c..70b33f3ae9a9 100755 --- a/components/ILIAS/Authentication/classes/class.ilAuthUtils.php +++ b/components/ILIAS/Authentication/classes/class.ilAuthUtils.php @@ -18,77 +18,57 @@ declare(strict_types=1); -/** -* static utility functions used to manage authentication modes -* -* @author Sascha Hofmann -*/ class ilAuthUtils { - public const LOCAL_PWV_FULL = 1; - public const LOCAL_PWV_NO = 2; - public const LOCAL_PWV_USER = 3; - - - public const AUTH_LOCAL = 1; - public const AUTH_LDAP = 2; - public const AUTH_SCRIPT = 4; - public const AUTH_SHIBBOLETH = 5; - public const AUTH_CAS = 6; - public const AUTH_SOAP = 7; - public const AUTH_HTTP = 8; // Used for WebDAV - public const AUTH_ECS = 9; - - public const AUTH_APACHE = 11; - public const AUTH_SAML = 12; - - public const AUTH_OPENID_CONNECT = 15; - + public const int LOCAL_PWV_FULL = 1; + public const int LOCAL_PWV_NO = 2; + public const int LOCAL_PWV_USER = 3; + + public const int AUTH_LOCAL = 1; + public const int AUTH_LDAP = 2; + public const int AUTH_SCRIPT = 4; + public const int AUTH_SHIBBOLETH = 5; + public const int AUTH_SOAP = 7; + public const int AUTH_HTTP = 8; // Used for WebDAV + public const int AUTH_ECS = 9; + public const int AUTH_APACHE = 11; + public const int AUTH_SAML = 12; + public const int AUTH_OPENID_CONNECT = 15; //TODO this is not used anywhere, can it be removed - private const AUTH_INACTIVE = 18; - + private const int AUTH_INACTIVE = 18; //TODO this is not used anywhere, can it be removed - private const AUTH_MULTIPLE = 20; - + private const int AUTH_MULTIPLE = 20; //TODO this is not used anywhere, can it be removed - private const AUTH_SESSION = 21; - - public const AUTH_PROVIDER_LTI = 22; + private const int AUTH_SESSION = 21; + public const int AUTH_PROVIDER_LTI = 22; //TODO this is not used anywhere, can it be removed - private const AUTH_SOAP_NO_ILIAS_USER = -100; + private const int AUTH_SOAP_NO_ILIAS_USER = -100; //TODO this is not used anywhere, can it be removed - private const AUTH_LDAP_NO_ILIAS_USER = -200; - + private const int AUTH_LDAP_NO_ILIAS_USER = -200; // apache auhtentication failed... // maybe no (valid) certificate or // username could not be extracted //TODO this is not used anywhere, can it be removed - private const AUTH_APACHE_FAILED = -500; - + private const int AUTH_APACHE_FAILED = -500; //TODO this is not used anywhere, can it be removed - private const AUTH_SAML_FAILED = -501; - + private const int AUTH_SAML_FAILED = -501; //TODO this is not used anywhere, can it be removed - private const AUTH_MODE_INACTIVE = -1000; - + private const int AUTH_MODE_INACTIVE = -1000; // an external user cannot be found in ilias, but his email address // matches one or more ILIAS users //TODO this is not used anywhere, can it be removed? - private const AUTH_SOAP_NO_ILIAS_USER_BUT_EMAIL = -101; - //TODO this is not used anywhere, can it be removed? - private const AUTH_CAS_NO_ILIAS_USER = -90; - + private const int AUTH_SOAP_NO_ILIAS_USER_BUT_EMAIL = -101; // ilUser validation (no login) //TODO All these are is not used anywhere, can it be removed? - private const AUTH_USER_WRONG_IP = -600; - private const AUTH_USER_INACTIVE = -601; - private const AUTH_USER_TIME_LIMIT_EXCEEDED = -602; - private const AUTH_USER_SIMULTANEOUS_LOGIN = -603; + private const int AUTH_USER_WRONG_IP = -600; + private const int AUTH_USER_INACTIVE = -601; + private const int AUTH_USER_TIME_LIMIT_EXCEEDED = -602; + private const int AUTH_USER_SIMULTANEOUS_LOGIN = -603; + + /** @var list */ + public const array REGEX_DELIMITERS = ['/', '~', '@', ';', '%', '`', '#']; - /** - * Check if authentication is should be forced. - */ public static function isAuthenticationForced(): bool { //TODO rework forced authentication concept @@ -120,7 +100,6 @@ public static function handleForcedAuthentication(): void $credentials, $providers ); - $frontend->authenticate(); switch ($status->getStatus()) { @@ -143,32 +122,32 @@ public static function _getAuthMode(?string $a_auth_mode) $ilSetting = $DIC['ilSetting']; - if (null === $a_auth_mode) { - return $ilSetting->get("auth_mode"); + if ($a_auth_mode === null) { + return $ilSetting->get('auth_mode'); } - if (strpos($a_auth_mode, '_') !== false) { + if (str_contains($a_auth_mode, '_')) { $auth_arr = explode('_', $a_auth_mode); $auth_switch = $auth_arr[0]; } else { $auth_switch = $a_auth_mode; } switch ($auth_switch) { - case "local": + case 'local': return self::AUTH_LOCAL; break; - case "ldap": + case 'ldap': return ilLDAPServer::getKeyByAuthMode($a_auth_mode); case 'lti': return ilAuthProviderLTI::getKeyByAuthMode($a_auth_mode); - case "script": + case 'script': return self::AUTH_SCRIPT; break; - case "shibboleth": + case 'shibboleth': return self::AUTH_SHIBBOLETH; break; @@ -179,11 +158,7 @@ public static function _getAuthMode(?string $a_auth_mode) case 'saml': return ilSamlIdp::getKeyByAuthMode($a_auth_mode); - case "cas": - return self::AUTH_CAS; - break; - - case "soap": + case 'soap': return self::AUTH_SOAP; break; @@ -194,7 +169,7 @@ public static function _getAuthMode(?string $a_auth_mode) return self::AUTH_APACHE; default: - return $ilSetting->get("auth_mode"); + return $ilSetting->get('auth_mode'); break; } } @@ -206,7 +181,7 @@ public static function _getAuthModeName($a_auth_key): string { switch ((int) $a_auth_key) { case self::AUTH_LOCAL: - return "local"; + return 'local'; break; case self::AUTH_LDAP: @@ -217,23 +192,19 @@ public static function _getAuthModeName($a_auth_key): string case self::AUTH_PROVIDER_LTI: return ilAuthProviderLTI::getAuthModeByKey($a_auth_key); - case self::AUTH_CAS: - return "cas"; - break; - case self::AUTH_SCRIPT: - return "script"; + return 'script'; break; case self::AUTH_SHIBBOLETH: - return "shibboleth"; + return 'shibboleth'; break; case self::AUTH_SAML: return ilSamlIdp::getAuthModeByKey($a_auth_key); case self::AUTH_SOAP: - return "soap"; + return 'soap'; break; case self::AUTH_ECS: @@ -247,7 +218,7 @@ public static function _getAuthModeName($a_auth_key): string break; default: - return "default"; + return 'default'; break; } } @@ -262,7 +233,7 @@ public static function _getActiveAuthModes(): array $ilSetting = $DIC['ilSetting']; $modes = [ - 'default' => $ilSetting->get("auth_mode"), + 'default' => $ilSetting->get('auth_mode'), 'local' => self::AUTH_LOCAL ]; @@ -278,19 +249,16 @@ public static function _getActiveAuthModes(): array $modes['oidc'] = self::AUTH_OPENID_CONNECT; } - if ($ilSetting->get("shib_active")) { + if ($ilSetting->get('shib_active')) { $modes['shibboleth'] = self::AUTH_SHIBBOLETH; } - if ($ilSetting->get("script_active")) { + if ($ilSetting->get('script_active')) { $modes['script'] = self::AUTH_SCRIPT; } - if ($ilSetting->get("cas_active")) { - $modes['cas'] = self::AUTH_CAS; - } - if ($ilSetting->get("soap_auth_active")) { + if ($ilSetting->get('soap_auth_active')) { $modes['soap'] = self::AUTH_SOAP; } - if ($ilSetting->get("apache_active")) { + if ($ilSetting->get('apache_active')) { $modes['apache'] = self::AUTH_APACHE; } @@ -320,19 +288,18 @@ public static function _getActiveAuthModes(): array */ public static function _getAllAuthModes(): array { - $modes = array( + $modes = [ self::AUTH_LOCAL, self::AUTH_LDAP, self::AUTH_SHIBBOLETH, self::AUTH_SAML, - self::AUTH_CAS, self::AUTH_SOAP, self::AUTH_ECS, self::AUTH_PROVIDER_LTI, self::AUTH_OPENID_CONNECT, self::AUTH_APACHE - ); - $ret = array(); + ]; + $ret = []; foreach ($modes as $mode) { if ($mode === self::AUTH_PROVIDER_LTI) { foreach (ilAuthProviderLTI::getAuthModes() as $sid) { @@ -378,8 +345,10 @@ public static function _generateLogin(string $a_login): string $postfix = 0; $c_login = $a_login; while (!$found) { - $r = $ilDB->query("SELECT login FROM usr_data WHERE login = " . - $ilDB->quote($c_login)); + $r = $ilDB->query( + 'SELECT login FROM usr_data WHERE login = ' . + $ilDB->quote($c_login) + ); if ($r->numRows() > 0) { $postfix++; $c_login = $a_login . $postfix; @@ -415,12 +384,10 @@ public static function _hasMultipleAuthenticationMethods(): bool } // end-patch auth_plugin - return false; } /** - * @param ilLanguage $lng * @return array */ public static function _getMultipleAuthModeOptions(ilLanguage $lng): array @@ -478,7 +445,7 @@ public static function _getMultipleAuthModeOptions(ilLanguage $lng): array /** * Check if an external account name is required. - * That's the case if LDAP, CAS or SOAP is active + * That's the case if LDAP or SOAP is active */ public static function _isExternalAccountEnabled(): bool { @@ -486,13 +453,10 @@ public static function _isExternalAccountEnabled(): bool $ilSetting = $DIC['ilSetting']; - if ($ilSetting->get("cas_active")) { - return true; - } - if ($ilSetting->get("soap_auth_active")) { + if ($ilSetting->get('soap_auth_active')) { return true; } - if ($ilSetting->get("shib_active")) { + if ($ilSetting->get('shib_active')) { return true; } if (count(ilLDAPServer::_getActiveServerList())) { @@ -526,7 +490,7 @@ public static function _isExternalAccountEnabled(): bool /** * Allow password modification - * @param int|string auth_mode + * @param int|string $a_auth_mode */ public static function _allowPasswordModificationByAuthMode($a_auth_mode): bool { @@ -557,12 +521,9 @@ public static function _needsExternalAccountByAuthMode($a_auth_mode): bool } } - /** - * @return bool - */ public static function isPasswordModificationHidden(): bool { - /** @var $ilSetting \ilSetting */ + /** @var $ilSetting ilSetting */ global $DIC; $ilSetting = $DIC['ilSetting']; @@ -573,7 +534,6 @@ public static function isPasswordModificationHidden(): bool /** * Check if local password validation is enabled for a specific auth_mode * @param int|string $a_authmode - * @return bool */ public static function isLocalPasswordEnabledForAuthMode($a_authmode): bool { @@ -600,11 +560,9 @@ public static function isLocalPasswordEnabledForAuthMode($a_authmode): bool return $idp->isActive() && $idp->allowLocalAuthentication(); case self::AUTH_SHIBBOLETH: - return (bool) $ilSetting->get("shib_auth_allow_local", '0'); + return (bool) $ilSetting->get('shib_auth_allow_local', '0'); case self::AUTH_SOAP: - return (bool) $ilSetting->get("soap_auth_allow_local", '0'); - case self::AUTH_CAS: - return (bool) $ilSetting->get("cas_allow_local", '0'); + return (bool) $ilSetting->get('soap_auth_allow_local', '0'); } return false; } @@ -613,7 +571,6 @@ public static function isLocalPasswordEnabledForAuthMode($a_authmode): bool /** * Check if password modification is enabled * @param int|string $a_authmode - * @return bool */ public static function isPasswordModificationEnabled($a_authmode): bool { @@ -640,12 +597,10 @@ public static function supportsLocalPasswordValidation($a_authmode): int case self::AUTH_OPENID_CONNECT: case self::AUTH_SAML: case self::AUTH_SOAP: - case self::AUTH_CAS: if (!self::isPasswordModificationEnabled($a_authmode)) { return self::LOCAL_PWV_NO; } return self::LOCAL_PWV_USER; - case self::AUTH_PROVIDER_LTI: case self::AUTH_ECS: case self::AUTH_SCRIPT: @@ -656,9 +611,9 @@ public static function supportsLocalPasswordValidation($a_authmode): int } /** - * Get active enabled auth plugins + * @return Iterator */ - public static function getAuthPlugins(): \Iterator + public static function getAuthPlugins(): Iterator { return $GLOBALS['DIC']['component.factory']->getActivePluginsInSlot('authhk'); } diff --git a/components/ILIAS/Authentication/classes/class.ilLoginPage.php b/components/ILIAS/Authentication/classes/class.ilLoginPage.php index a1bd9eaf96d2..cf415fdddadd 100755 --- a/components/ILIAS/Authentication/classes/class.ilLoginPage.php +++ b/components/ILIAS/Authentication/classes/class.ilLoginPage.php @@ -20,7 +20,7 @@ class ilLoginPage extends ilPageObject { - final public const PAGE_TYPE = 'auth'; + final public const string PAGE_TYPE = 'auth'; public function getParentType(): string { diff --git a/components/ILIAS/Authentication/classes/class.ilLogoutPage.php b/components/ILIAS/Authentication/classes/class.ilLogoutPage.php index cab914b5f0f4..cd8afa404710 100644 --- a/components/ILIAS/Authentication/classes/class.ilLogoutPage.php +++ b/components/ILIAS/Authentication/classes/class.ilLogoutPage.php @@ -20,7 +20,7 @@ class ilLogoutPage extends ilPageObject { - final public const PAGE_TYPE = 'aout'; + final public const string PAGE_TYPE = 'aout'; public function getParentType(): string { diff --git a/components/ILIAS/Authentication/classes/class.ilObjAuthSettings.php b/components/ILIAS/Authentication/classes/class.ilObjAuthSettings.php index c552fd4aeab8..9f1134bf7b64 100755 --- a/components/ILIAS/Authentication/classes/class.ilObjAuthSettings.php +++ b/components/ILIAS/Authentication/classes/class.ilObjAuthSettings.php @@ -18,44 +18,24 @@ declare(strict_types=1); -/** -* @author Sascha Hofmann -*/ class ilObjAuthSettings extends ilObject { - /** - * @param integer reference_id or object_id - * @param boolean treat the id as reference_id (true) or object_id (false) - */ public function __construct(int $a_id = 0, bool $a_call_by_reference = true) { - $this->type = "auth"; + $this->type = 'auth'; parent::__construct($a_id, $a_call_by_reference); } - public function checkAuthLDAP(): bool - { - $settings = $this->ilias->getAllSettings(); - - if (!$settings["ldap_server"] || !$settings["ldap_basedn"] || !$settings["ldap_port"]) { - return false; - } - - $this->ilias->setSetting('ldap_active', "1"); - - return true; - } - public function checkAuthSHIB(): bool { $settings = $this->ilias->getAllSettings(); - if (!$settings["shib_hos_type"] || !isset($settings["shib_user_default_role"]) || !$settings["shib_login"] - || !$settings["shib_firstname"] || !$settings["shib_lastname"]) { + if (!$settings['shib_hos_type'] || !isset($settings['shib_user_default_role']) || !$settings['shib_login'] + || !$settings['shib_firstname'] || !$settings['shib_lastname']) { return false; } - $this->ilias->setSetting('shibboleth_active', "1"); + $this->ilias->setSetting('shibboleth_active', '1'); return true; } @@ -64,11 +44,11 @@ public function checkAuthScript(): bool { $settings = $this->ilias->getAllSettings(); - if (!$settings["auth_script_name"]) { + if (!$settings['auth_script_name']) { return false; } - $this->ilias->setSetting('script_active', "1"); + $this->ilias->setSetting('script_active', '1'); return true; } diff --git a/components/ILIAS/Authentication/classes/class.ilObjAuthSettingsAccess.php b/components/ILIAS/Authentication/classes/class.ilObjAuthSettingsAccess.php index c5a6236d738e..c6399aa7595c 100755 --- a/components/ILIAS/Authentication/classes/class.ilObjAuthSettingsAccess.php +++ b/components/ILIAS/Authentication/classes/class.ilObjAuthSettingsAccess.php @@ -18,9 +18,6 @@ declare(strict_types=1); -/** - * @author Alex Killing - */ class ilObjAuthSettingsAccess extends ilObjectAccess { } diff --git a/components/ILIAS/Authentication/classes/class.ilObjAuthSettingsGUI.php b/components/ILIAS/Authentication/classes/class.ilObjAuthSettingsGUI.php index 3cbc602c492b..53331a109e9a 100755 --- a/components/ILIAS/Authentication/classes/class.ilObjAuthSettingsGUI.php +++ b/components/ILIAS/Authentication/classes/class.ilObjAuthSettingsGUI.php @@ -18,8 +18,11 @@ declare(strict_types=1); +use ILIAS\Authentication\Form\ApacheAuthSettingsForm; use ILIAS\Style\Content\GUIService; use ILIAS\components\Authentication\Pages\AuthPageEditorContext; +use ILIAS\UI\Component\Input\Container\Form\Standard as StandardForm; +use ILIAS\UICore\GlobalTemplate; /** * @ilCtrl_Calls ilObjAuthSettingsGUI: ilPermissionGUI, ilRegistrationSettingsGUI, ilLDAPSettingsGUI @@ -29,11 +32,12 @@ */ class ilObjAuthSettingsGUI extends ilObjectGUI { - private const PROP_AUTH_MODE_KIND = 'kind'; - private const PROP_AUTH_MODE_SEQUENCE = 'sequence'; + private const string CMD_SHOW_APACHE_SETTINGS = 'apacheAuthSettings'; + private const string CMD_SAVE_APACHE_SETTINGS = 'saveApacheSettings'; + private const string PROP_AUTH_MODE_KIND = 'kind'; + private const string PROP_AUTH_MODE_SEQUENCE = 'sequence'; private ilLogger $logger; - private ILIAS\HTTP\GlobalHttpState $http; private GUIService $content_style_gui; @@ -45,8 +49,6 @@ public function __construct($a_data, int $a_id, bool $a_call_by_reference, bool global $DIC; $this->logger = $DIC->logger()->auth(); - $this->http = $DIC->http(); - $this->lng->loadLanguageModule('registration'); $this->lng->loadLanguageModule('auth'); $this->lng->loadLanguageModule('content'); @@ -83,8 +85,6 @@ private function authSettingsObject( $generalSettingsTpl->setVariable('TXT_LDAP', $this->lng->txt('auth_ldap')); $generalSettingsTpl->setVariable('TXT_SHIB', $this->lng->txt('auth_shib')); - $generalSettingsTpl->setVariable('TXT_CAS', $this->lng->txt('auth_cas')); - $generalSettingsTpl->setVariable('TXT_SCRIPT', $this->lng->txt('auth_script')); $generalSettingsTpl->setVariable('TXT_APACHE', $this->lng->txt('auth_apache')); @@ -96,7 +96,6 @@ private function authSettingsObject( ilAuthUtils::AUTH_LDAP, ilAuthUtils::AUTH_SHIBBOLETH, ilAuthUtils::AUTH_SAML, - ilAuthUtils::AUTH_CAS, ilAuthUtils::AUTH_APACHE, ilAuthUtils::AUTH_OPENID_CONNECT ]; @@ -181,7 +180,12 @@ private function authSettingsObject( $page_content = [ $this->ui_factory->panel()->standard( $this->lng->txt('auth_select'), - $this->ui_factory->legacy()->content($generalSettingsTpl->get()), + $this->ui_factory->legacy()->content(implode('', [ + $this->ui_renderer->render($this->ui_factory->messageBox()->info( + $this->lng->txt('auth_mode_default_change_info') + )), + $generalSettingsTpl->get() + ])), ) ]; @@ -210,7 +214,7 @@ private function buildRegistrationRoleMappingForm(): ILIAS\UI\Component\Input\Co $fields = []; $reg_roles = ilObjRole::_lookupRegisterAllowed(); - $excluded_auth_names = ['default', 'cas', 'saml', 'shibboleth', 'ldap', 'apache', 'ecs', 'openid']; + $excluded_auth_names = ['default', 'saml', 'shibboleth', 'ldap', 'apache', 'ecs', 'openid']; // do not list auth modes with external login screen // even not default, because it can easily be set to // a non-working auth mode @@ -727,7 +731,7 @@ public function testSoapAuthConnectionObject(): void if ($this->request->getMethod() === 'POST') { $test_form = $test_form->withRequest($this->request); $result = $test_form->getData(); - if (!is_null($result)) { + if ($result !== null) { $panel_content[] = $this->ui_factory->legacy()->content( ilSOAPAuth::testConnection($result['ext_uid'], $result['soap_pw'], $result['new_user']) ); @@ -749,7 +753,7 @@ public function saveSOAPObject(): void if ($this->request->getMethod() === 'POST') { $soap_form = $soap_form->withRequest($this->request); $result = $soap_form->getData(); - if (!is_null($result)) { + if ($result !== null) { $this->settings->set('soap_auth_active', (string) $result['active']); $this->settings->set('soap_auth_server', $result['server']); $this->settings->set('soap_auth_port', (string) $result['port']); @@ -891,13 +895,6 @@ public function executeCommand(): void $this->ctrl->forwardCommand($shib_settings_gui); break; - case 'ilcassettingsgui': - $this->tabs_gui->setTabActive('auth_cas'); - - $cas_settings = new ilCASSettingsGUI($this->object->getRefId()); - $this->ctrl->forwardCommand($cas_settings); - break; - case strtolower(ilAuthPageEditorGUI::class): $this->setSubTabs('authSettings'); $this->tabs_gui->setTabActive('authentication_settings'); @@ -978,11 +975,6 @@ protected function getTabs(): void $this->ctrl->getLinkTargetByClass('ilauthshibbolethsettingsgui', 'settings') ); - $this->tabs_gui->addTarget( - 'auth_cas', - $this->ctrl->getLinkTargetByClass('ilcassettingsgui', 'settings') - ); - $this->tabs_gui->addTarget( 'auth_soap', $this->ctrl->getLinkTarget($this, 'editSOAP'), @@ -993,7 +985,7 @@ protected function getTabs(): void $this->tabs_gui->addTarget( 'apache_auth_settings', - $this->ctrl->getLinkTarget($this, 'apacheAuthSettings'), + $this->ctrl->getLinkTarget($this, self::CMD_SHOW_APACHE_SETTINGS), '', '', '' @@ -1069,13 +1061,11 @@ public function setSubTabs(string $a_tab): void } } - public function apacheAuthSettingsObject(?ilPropertyFormGUI $form = null): void + public function apacheAuthSettingsObject(?StandardForm $form = null): void { $this->tabs_gui->setTabActive('apache_auth_settings'); - if ($form === null) { - $form = $this->getApacheAuthSettingsForm(); - + if (!$form) { $settings = new ilSetting('apache_auth'); $settingsMap = $settings->getAll(); @@ -1084,17 +1074,35 @@ public function apacheAuthSettingsObject(?ilPropertyFormGUI $form = null): void $settingsMap['apache_auth_domains'] = file_get_contents($path); } - $form->setValuesByArray($settingsMap); + $form = (new ApacheAuthSettingsForm( + $this->ref_id, + $this, + self::CMD_SHOW_APACHE_SETTINGS, + self::CMD_SAVE_APACHE_SETTINGS, + $settingsMap + ))->buildForm(); + } - $this->tpl->setVariable('ADM_CONTENT', $form->getHtml()); + + $this->tpl->setContent($this->ui_renderer->render([ + $this->ui_factory->item()->standard($this->lng->txt('apache_settings')), + $form + ])); } public function saveApacheSettingsObject(): void { - $form = $this->getApacheAuthSettingsForm(); - $form->setValuesByPost(); - if ($form->checkInput()) { + $form = (new ApacheAuthSettingsForm( + $this->ref_id, + $this, + self::CMD_SHOW_APACHE_SETTINGS, + self::CMD_SAVE_APACHE_SETTINGS + ))->buildForm()->withRequest($this->http->request()); + if (!$form->getError()) { + $data = $form->getData(); + $settings = new ilSetting('apache_auth'); + $fields = [ 'apache_auth_indicator_name', 'apache_auth_indicator_value', @@ -1112,158 +1120,49 @@ public function saveApacheSettingsObject(): void ]; foreach ($fields as $field) { - $settings->set($field, (string) $form->getInput($field)); + $value = match ($field) { + 'apache_enable_auth', + 'apache_auth_enable_override_login_page', + 'apache_auth_username_config', + 'apache_auth_security', + 'apache_enable_ldap' => (bool) ($data[$field] ?? false), + 'apache_auth_username_config_type' => $data['apache_auth_username_config'][$field][0] ?? 1, + 'apache_auth_target_override_login_page' => $data['apache_auth_enable_override_login_page'][$field] ?? '', + 'apache_auth_username_direct_mapping_fieldname' => $data['apache_auth_username_config']['apache_auth_username_config_type'][1][$field] ?? '', + 'apache_auth_domains' => $data['apache_auth_security'][$field] ?? '', + 'apache_local_autocreate' => (bool) ($data['apache_enable_auth'][$field] ?? false), + 'apache_default_role' => $data['apache_enable_auth']['apache_local_autocreate'][$field] ?? 4, + 'apache_ldap_sid' => $data['apache_enable_ldap'][$field] ?? '', + default => $data[$field], + }; + + $settings->set( + $field, + ilUtil::stripSlashes(trim((string) ($value === false ? '0' : $value))) + ); } - if ($form->getInput('apache_enable_auth')) { + if ($data[$field] ?? false) { $this->ilias->setSetting('apache_active', '1'); } else { $this->ilias->setSetting('apache_active', '0'); - global $DIC; - - $ilSetting = $DIC['ilSetting']; - if ((int) $ilSetting->get('auth_mode', '0') === ilAuthUtils::AUTH_APACHE) { - $ilSetting->set('auth_mode', (string) ilAuthUtils::AUTH_LOCAL); + if ($this->ilias->getSetting('auth_mode', '0') === ilAuthUtils::AUTH_APACHE) { + $this->ilias->setSetting('auth_mode', (string) ilAuthUtils::AUTH_LOCAL); } } - $allowedDomains = $this->validateApacheAuthAllowedDomains((string) $form->getInput('apache_auth_domains')); - file_put_contents(ILIAS_DATA_DIR . '/' . CLIENT_ID . '/apache_auth_allowed_domains.txt', $allowedDomains); - - $this->tpl->setOnScreenMessage('success', $this->lng->txt('apache_settings_changed_success'), true); - $this->ctrl->redirect($this, 'apacheAuthSettings'); - } else { - $this->apacheAuthSettingsObject($form); - } - } - - public function getApacheAuthSettingsForm(): ilPropertyFormGUI - { - $form = new ilPropertyFormGUI(); - $form->setFormAction($this->ctrl->getFormAction($this)); - $form->setTitle($this->lng->txt('apache_settings')); - - $chb_enabled = new ilCheckboxInputGUI($this->lng->txt('apache_enable_auth'), 'apache_enable_auth'); - $chb_enabled->setValue('1'); - $form->addItem($chb_enabled); - - $chb_local_create_account = new ilCheckboxInputGUI( - $this->lng->txt('apache_autocreate'), - 'apache_local_autocreate' - ); - $chb_local_create_account->setValue('1'); - $chb_enabled->addSubitem($chb_local_create_account); - - $roles = $this->rbac_review->getGlobalRolesArray(); - $select = new ilSelectInputGUI($this->lng->txt('apache_default_role'), 'apache_default_role'); - $roleOptions = []; - foreach ($roles as $role) { - $roleOptions[$role['obj_id']] = ilObject::_lookupTitle($role['obj_id']); - } - $select->setOptions($roleOptions); - $select->setValue(4); - - $chb_local_create_account->addSubitem($select); - - $chb_local = new ilCheckboxInputGUI($this->lng->txt('apache_enable_local'), 'apache_enable_local'); - $chb_local->setValue('1'); - $form->addItem($chb_local); - - $chb_ldap = new ilCheckboxInputGUI($this->lng->txt('apache_enable_ldap'), 'apache_enable_ldap'); - $chb_local->setValue('1'); - - $chb_ldap->setInfo($this->lng->txt('apache_ldap_hint_ldap_must_be_configured')); - - $this->lng->loadLanguageModule('auth'); - - $servers = ilLDAPServer::getServerIds(); - if (count($servers)) { - $ldap_server_select = new ilSelectInputGUI($this->lng->txt('auth_ldap_server_ds'), 'apache_ldap_sid'); - $options[0] = $this->lng->txt('select_one'); - foreach ($servers as $server_id) { - $ldap_server = new ilLDAPServer($server_id); - $options[$server_id] = $ldap_server->getName(); - } - $ldap_server_select->setOptions($options); - $ldap_server_select->setRequired(true); - - $ds = ilLDAPServer::getDataSource(ilAuthUtils::AUTH_APACHE); - $ldap_server_select->setValue($ds); - - $chb_ldap->addSubItem($ldap_server_select); - } - $form->addItem($chb_ldap); - - $txt = new ilTextInputGUI($this->lng->txt('apache_auth_indicator_name'), 'apache_auth_indicator_name'); - $txt->setRequired(true); - $form->addItem($txt); - - $txt = new ilTextInputGUI($this->lng->txt('apache_auth_indicator_value'), 'apache_auth_indicator_value'); - $txt->setRequired(true); - $form->addItem($txt); - - $chb = new ilCheckboxInputGUI( - $this->lng->txt('apache_auth_enable_override_login'), - 'apache_auth_enable_override_login_page' - ); - $chb->setValue('1'); - $form->addItem($chb); - - $txt = new ilTextInputGUI( - $this->lng->txt('apache_auth_target_override_login'), - 'apache_auth_target_override_login_page' - ); - $txt->setRequired(true); - $chb->addSubItem($txt); - - $chb = new ilCheckboxInputGUI( - $this->lng->txt('apache_auth_authenticate_on_login_page'), - 'apache_auth_authenticate_on_login_page' - ); - $chb->setValue('1'); - $form->addItem($chb); - - $sec = new ilFormSectionHeaderGUI(); - $sec->setTitle($this->lng->txt('apache_auth_username_config')); - $form->addItem($sec); - - $rag = new ilRadioGroupInputGUI( - $this->lng->txt('apache_auth_username_config_type'), - 'apache_auth_username_config_type' - ); - $form->addItem($rag); - - $rao = new ilRadioOption($this->lng->txt('apache_auth_username_direct_mapping'), '1'); - $rag->addOption($rao); - - $txt = new ilTextInputGUI( - $this->lng->txt('apache_auth_username_direct_mapping_fieldname'), - 'apache_auth_username_direct_mapping_fieldname' - ); - $rao->addSubItem($txt); - - $rao = new ilRadioOption($this->lng->txt('apache_auth_username_extended_mapping'), '2'); - $rao->setDisabled(true); - $rag->addOption($rao); - - $rao = new ilRadioOption($this->lng->txt('apache_auth_username_by_function'), '3'); - $rag->addOption($rao); + $allowed_domains = $this->validateApacheAuthAllowedDomains($data['apache_auth_security']['apache_auth_domains'] ?? ''); + file_put_contents(ILIAS_DATA_DIR . '/' . CLIENT_ID . '/apache_auth_allowed_domains.txt', $allowed_domains); - $sec = new ilFormSectionHeaderGUI(); - $sec->setTitle($this->lng->txt('apache_auth_security')); - $form->addItem($sec); - - $txt = new ilTextAreaInputGUI($this->lng->txt('apache_auth_domains'), 'apache_auth_domains'); - $txt->setInfo($this->lng->txt('apache_auth_domains_description')); - - $form->addItem($txt); - - if ($this->access->checkAccess('write', '', $this->ref_id)) { - $form->addCommandButton('saveApacheSettings', $this->lng->txt('save')); + $this->tpl->setOnScreenMessage( + $this->tpl::MESSAGE_TYPE_SUCCESS, + $this->lng->txt('apache_settings_changed_success'), + true + ); + $this->ctrl->redirect($this, self::CMD_SHOW_APACHE_SETTINGS); } - $form->addCommandButton('cancel', $this->lng->txt('cancel')); - return $form; + $this->ctrl->redirect($this, self::CMD_SHOW_APACHE_SETTINGS); } private function validateApacheAuthAllowedDomains(string $text): string diff --git a/components/ILIAS/Authentication/classes/class.ilSession.php b/components/ILIAS/Authentication/classes/class.ilSession.php index 336ad306085c..944c5a29d504 100755 --- a/components/ILIAS/Authentication/classes/class.ilSession.php +++ b/components/ILIAS/Authentication/classes/class.ilSession.php @@ -18,26 +18,21 @@ declare(strict_types=1); -/** -* @author Alex Killing -* -* @externalTableAccess ilObjUser on usr_session -*/ class ilSession { /** * Constant for reason of session destroy * - * @var integer + * @var int */ - public const SESSION_CLOSE_USER = 1; // manual logout - public const SESSION_CLOSE_EXPIRE = 2; // has expired - public const SESSION_CLOSE_LOGIN = 6; // anonymous => login - public const SESSION_CLOSE_PUBLIC = 7; // => anonymous - public const SESSION_CLOSE_TIME = 8; // account time limit reached - public const SESSION_CLOSE_IP = 9; // wrong ip - public const SESSION_CLOSE_SIMUL = 10; // simultaneous login - public const SESSION_CLOSE_INACTIVE = 11; // inactive account + public const int SESSION_CLOSE_USER = 1; // manual logout + public const int SESSION_CLOSE_EXPIRE = 2; // has expired + public const int SESSION_CLOSE_LOGIN = 6; // anonymous => login + public const int SESSION_CLOSE_PUBLIC = 7; // => anonymous + public const int SESSION_CLOSE_TIME = 8; // account time limit reached + public const int SESSION_CLOSE_IP = 9; // wrong ip + public const int SESSION_CLOSE_SIMUL = 10; // simultaneous login + public const int SESSION_CLOSE_INACTIVE = 11; // inactive account private static ?int $closing_context = null; @@ -62,8 +57,8 @@ public static function _getData(string $a_session_id): string $ilDB = $DIC['ilDB']; - $q = "SELECT data FROM usr_session WHERE session_id = " . - $ilDB->quote($a_session_id, "text"); + $q = 'SELECT data FROM usr_session WHERE session_id = ' . + $ilDB->quote($a_session_id, 'text'); $set = $ilDB->query($q); $rec = $ilDB->fetchAssoc($set); if (!is_array($rec)) { @@ -71,7 +66,7 @@ public static function _getData(string $a_session_id): string } // fix for php #70520 - return (string) $rec["data"]; + return (string) $rec['data']; } /** @@ -207,14 +202,6 @@ public static function _writeData(string $a_session_id, string $a_data): bool return true; } - - - /** - * Check whether session exists - * - * @param string session id - * @return boolean true, if session id exists - */ public static function _exists(string $a_session_id): bool { if (!$a_session_id) { @@ -224,7 +211,7 @@ public static function _exists(string $a_session_id): bool $ilDB = $DIC['ilDB']; - $q = "SELECT 1 FROM usr_session WHERE session_id = " . $ilDB->quote($a_session_id, "text"); + $q = 'SELECT 1 FROM usr_session WHERE session_id = ' . $ilDB->quote($a_session_id, 'text'); $set = $ilDB->query($q); return $ilDB->numRows($set) > 0; @@ -249,16 +236,16 @@ public static function _destroy($a_session_id, ?int $a_closing_context = null, $ ilSessionStatistics::closeRawEntry($a_session_id, $a_closing_context, $a_expired_at); - if (!is_array($a_session_id)) { - $q = "DELETE FROM usr_session WHERE session_id = " . - $ilDB->quote($a_session_id, "text"); - } else { + if (is_array($a_session_id)) { // array: id => timestamp - so we get rid of timestamps if ($a_expired_at) { $a_session_id = array_keys($a_session_id); } - $q = "DELETE FROM usr_session WHERE " . - $ilDB->in("session_id", $a_session_id, false, "text"); + $q = 'DELETE FROM usr_session WHERE ' . + $ilDB->in('session_id', $a_session_id, false, 'text'); + } else { + $q = 'DELETE FROM usr_session WHERE session_id = ' . + $ilDB->quote($a_session_id, 'text'); } ilSessionIStorage::destroySession($a_session_id); @@ -276,7 +263,7 @@ public static function _destroy($a_session_id, ?int $a_closing_context = null, $ $cookieJar = $DIC->http()->cookieJar()->without(session_name()); $cookieJar->renderIntoResponseHeader($DIC->http()->response()); } - } catch (\Throwable $e) { + } catch (Throwable) { // ignore // this is needed for "header already" sent errors when the random cleanup of expired sessions is triggered } @@ -296,8 +283,8 @@ public static function _destroyByUserId(int $a_user_id): bool $ilDB = $DIC['ilDB']; - $q = "DELETE FROM usr_session WHERE user_id = " . - $ilDB->quote($a_user_id, "integer"); + $q = 'DELETE FROM usr_session WHERE user_id = ' . + $ilDB->quote($a_user_id, 'integer'); $ilDB->manipulate($q); return true; @@ -342,13 +329,13 @@ public static function _duplicate(string $a_session_id): string $new_session = $a_session_id; do { $new_session = md5($new_session); - $q = "SELECT * FROM usr_session WHERE " . - "session_id = " . $ilDB->quote($new_session, "text"); + $q = 'SELECT * FROM usr_session WHERE ' . + 'session_id = ' . $ilDB->quote($new_session, 'text'); $res = $ilDB->query($q); } while ($ilDB->fetchAssoc($res)); - $query = "SELECT * FROM usr_session " . - "WHERE session_id = " . $ilDB->quote($a_session_id, "text"); + $query = 'SELECT * FROM usr_session ' . + 'WHERE session_id = ' . $ilDB->quote($a_session_id, 'text'); $res = $ilDB->query($query); if ($row = $ilDB->fetchObject($res)) { @@ -356,7 +343,7 @@ public static function _duplicate(string $a_session_id): string return $new_session; } //TODO check if throwing an excpetion might be a better choice - return ""; + return ''; } /** @@ -408,9 +395,6 @@ public static function has($a_var): bool return isset($_SESSION[$a_var]); } - /** - * @param string $a_var - */ public static function clear(string $a_var): void { if (isset($_SESSION[$a_var])) { @@ -439,19 +423,11 @@ public static function getClosingContext(): int return self::$closing_context; } - - - /** - * @return boolean - */ public static function isWebAccessWithoutSessionEnabled(): bool { return self::$enable_web_access_without_session; } - /** - * @param boolean $enable_web_access_without_session - */ public static function enableWebAccessWithoutSession(bool $enable_web_access_without_session): void { self::$enable_web_access_without_session = $enable_web_access_without_session; diff --git a/components/ILIAS/Authentication/classes/class.ilSessionControl.php b/components/ILIAS/Authentication/classes/class.ilSessionControl.php index facf545809d3..616fab8e00c9 100755 --- a/components/ILIAS/Authentication/classes/class.ilSessionControl.php +++ b/components/ILIAS/Authentication/classes/class.ilSessionControl.php @@ -18,72 +18,52 @@ declare(strict_types=1); -/** - * @author Bjoern Heyser - */ class ilSessionControl { /** * default value for settings that have not * been defined in setup or administration yet */ - public const DEFAULT_MIN_IDLE = 15; - public const DEFAULT_ALLOW_CLIENT_MAINTENANCE = 1; + public const int DEFAULT_MIN_IDLE = 15; + public const int DEFAULT_ALLOW_CLIENT_MAINTENANCE = 1; /** - * all fieldnames that are saved in settings table - * - * @var array $setting_fields + * @var list $setting_fields */ - private static array $setting_fields = array( + private static array $setting_fields = [ 'session_allow_client_maintenance', - ); + ]; /** * session types from which one is * assigned to each session */ - private const SESSION_TYPE_UNKNOWN = 0; - private const SESSION_TYPE_SYSTEM = 1; - private const SESSION_TYPE_ADMIN = 2; - private const SESSION_TYPE_USER = 3; - private const SESSION_TYPE_ANONYM = 4; + private const int SESSION_TYPE_UNKNOWN = 0; + private const int SESSION_TYPE_SYSTEM = 1; + private const int SESSION_TYPE_ADMIN = 2; + private const int SESSION_TYPE_USER = 3; + private const int SESSION_TYPE_ANONYM = 4; - private const SESSION_TYPE_KEY = "SessionType"; + private const string SESSION_TYPE_KEY = 'SessionType'; /** * all session types that will be involved when count of sessions * will be determined or when idleing sessions will be destroyed * * @var array $session_types_not_controlled */ - public static array $session_types_controlled = array( + public static array $session_types_controlled = [ self::SESSION_TYPE_USER, self::SESSION_TYPE_ANONYM - ); + ]; - /** - * all session types that will be ignored when count of sessions - * will be determined or when idleing sessions will be destroyed - * - * @var array $session_types_not_controlled - */ - private static array $session_types_not_controlled = array( + private static array $session_types_not_controlled = [ self::SESSION_TYPE_UNKNOWN, self::SESSION_TYPE_SYSTEM, self::SESSION_TYPE_ADMIN - ); + ]; - /** - * when current session is allowed to be created it marks it with - * type regarding to the sessions user context. - * when session is not allowed to be created it will be destroyed. - */ public static function handleLoginEvent(string $a_login, ilAuthSession $auth_session): bool { - global $DIC; - - $ilSetting = $DIC['ilSetting']; - $user_id = ilObjUser::_lookupId($a_login); // we need the session type for the session statistics @@ -107,7 +87,7 @@ public static function handleLoginEvent(string $a_login, ilAuthSession $auth_ses } ilSession::set(self::SESSION_TYPE_KEY, $type); - self::debug(__METHOD__ . " --> update sessions type to (" . $type . ")"); + self::debug(__METHOD__ . ' --> update sessions type to (' . $type . ')'); return true; } @@ -120,7 +100,7 @@ public static function handleLogoutEvent(): void } /** - * returns number of valid sessions relating to given session types + * @param list $a_types */ public static function getExistingSessionCount(array $a_types): int { @@ -130,34 +110,28 @@ public static function getExistingSessionCount(array $a_types): int $ts = time(); - $query = "SELECT count(session_id) AS num_sessions FROM usr_session " . - "WHERE expires > %s " . - "AND " . $ilDB->in('type', $a_types, false, 'integer'); + $query = 'SELECT count(session_id) AS num_sessions FROM usr_session ' . + 'WHERE expires > %s ' . + 'AND ' . $ilDB->in('type', $a_types, false, 'integer'); - $res = $ilDB->queryF($query, array('integer'), array($ts)); + $res = $ilDB->queryF($query, ['integer'], [$ts]); return (int) $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)->num_sessions; } - /** - * checks if session exists for given id - * and if it is still valid - * - * @return boolean session_valid - */ private static function isValidSession(string $a_sid): bool { global $DIC; $ilDB = $DIC['ilDB']; - $query = "SELECT session_id, expires FROM usr_session " . - "WHERE session_id = %s"; + $query = 'SELECT session_id, expires FROM usr_session ' . + 'WHERE session_id = %s'; - $res = $ilDB->queryF($query, array('text'), array($a_sid)); + $res = $ilDB->queryF($query, ['text'], [$a_sid]); $ts = time(); - $sessions = array(); + $sessions = []; while ($row = $ilDB->fetchAssoc($res)) { if ($row['expires'] > $ts) { @@ -183,22 +157,12 @@ private static function isValidSession(string $a_sid): bool return false; } - /** - * removes a session cookie, so it is not sent by browser anymore - */ private static function removeSessionCookie(): void { ilUtil::setCookie(session_name(), 'deleted', true, true); self::debug('Session cookie has been removed'); } - /** - * checks wether a given user login relates to an user - * with administrative permissions - * - * @global ilRbacSystem $rbacsystem - * @return boolean access - */ private static function checkAdministrationPermission(int $a_user_id): bool { if (!$a_user_id) { @@ -218,11 +182,6 @@ private static function checkAdministrationPermission(int $a_user_id): bool return $access; } - /** - * logs the given debug message in \ilLogger - * - * @param string $a_debug_log_message - */ private static function debug(string $a_debug_log_message): void { global $DIC; @@ -233,9 +192,7 @@ private static function debug(string $a_debug_log_message): void } /** - * returns the array of setting fields - * - * @return array setting_fields + * @return list */ public static function getSettingFields(): array { diff --git a/components/ILIAS/Authentication/classes/class.ilSessionDBHandler.php b/components/ILIAS/Authentication/classes/class.ilSessionDBHandler.php index 709e1388fe53..9ed00a589eae 100755 --- a/components/ILIAS/Authentication/classes/class.ilSessionDBHandler.php +++ b/components/ILIAS/Authentication/classes/class.ilSessionDBHandler.php @@ -18,9 +18,6 @@ declare(strict_types=1); -/** - * Database Session Handling - */ class ilSessionDBHandler implements SessionHandlerInterface { /** diff --git a/components/ILIAS/Authentication/classes/class.ilSessionIStorage.php b/components/ILIAS/Authentication/classes/class.ilSessionIStorage.php index 48bf849a7b8d..bdc032d65575 100755 --- a/components/ILIAS/Authentication/classes/class.ilSessionIStorage.php +++ b/components/ILIAS/Authentication/classes/class.ilSessionIStorage.php @@ -35,25 +35,17 @@ * reads all key/value pairs at the beginning of a request and writes all of * them at the end. Similar issues can appear if a page initiates additional * requests by (i)frames. - * - * @author Alex Killing */ class ilSessionIStorage { - private string $session_id = ""; + private string $session_id; private string $component_id; private static array $values = []; - /** - * Constructor - * - * @param string $a_component_id component id (e.g. "crs", "lm", ...) - * @param string $a_sess_id session id - */ - public function __construct(string $a_component_id, string $a_sess_id = "") + public function __construct(string $a_component_id, string $a_sess_id = '') { $this->component_id = $a_component_id; - if ($a_sess_id !== "") { + if ($a_sess_id !== '') { $this->session_id = $a_sess_id; } else { $this->session_id = session_id(); @@ -67,11 +59,6 @@ private function initComponentCacheIfNotExists(): void } } - /** - * Set a value - * - * @param string $a_val value - */ public function set(string $a_key, string $a_val): void { global $DIC; @@ -82,20 +69,16 @@ public function set(string $a_key, string $a_val): void self::$values[$this->component_id][$a_key] = $a_val; $ilDB->replace( - "usr_sess_istorage", - array( - "session_id" => array("text", $this->session_id), - "component_id" => array("text", $this->component_id), - "vkey" => array("text", $a_key) - ), - array("value" => array("text", $a_val)) + 'usr_sess_istorage', + [ + 'session_id' => ['text', $this->session_id], + 'component_id' => ['text', $this->component_id], + 'vkey' => ['text', $a_key] + ], + ['value' => ['text', $a_val]] ); } - /** - * @param string $a_key - * @return string - */ public function get(string $a_key): string { global $DIC; @@ -107,10 +90,10 @@ public function get(string $a_key): string } $set = $ilDB->query( - "SELECT value FROM usr_sess_istorage " . - " WHERE session_id = " . $ilDB->quote($this->session_id, "text") . - " AND component_id = " . $ilDB->quote($this->component_id, "text") . - " AND vkey = " . $ilDB->quote($a_key, "text") + 'SELECT value FROM usr_sess_istorage ' . + ' WHERE session_id = ' . $ilDB->quote($this->session_id, 'text') . + ' AND component_id = ' . $ilDB->quote($this->component_id, 'text') . + ' AND vkey = ' . $ilDB->quote($a_key, 'text') ); $rec = $ilDB->fetchAssoc($set); $value = (string) ($rec['value'] ?? ''); @@ -124,18 +107,18 @@ public function get(string $a_key): string /** * Destroy session(s). This is called by ilSession->destroy - * @param $a_session_id string|array ids of sessions to be deleted + * @param string|list $a_session_id Ids of sessions to be deleted */ public static function destroySession($a_session_id): void { global $DIC; - if (!is_array($a_session_id)) { - $q = "DELETE FROM usr_sess_istorage WHERE session_id = " . - $DIC->database()->quote($a_session_id, "text"); + if (is_array($a_session_id)) { + $q = 'DELETE FROM usr_sess_istorage WHERE ' . + $DIC->database()->in('session_id', $a_session_id, false, 'text'); } else { - $q = "DELETE FROM usr_sess_istorage WHERE " . - $DIC->database()->in("session_id", $a_session_id, false, "text"); + $q = 'DELETE FROM usr_sess_istorage WHERE session_id = ' . + $DIC->database()->quote($a_session_id, 'text'); } $DIC->database()->manipulate($q); diff --git a/components/ILIAS/Authentication/classes/class.ilSessionReminder.php b/components/ILIAS/Authentication/classes/class.ilSessionReminder.php index 78edf6cc64bc..e9d5c6d6b1bb 100755 --- a/components/ILIAS/Authentication/classes/class.ilSessionReminder.php +++ b/components/ILIAS/Authentication/classes/class.ilSessionReminder.php @@ -23,12 +23,10 @@ class ilSessionReminder { - public const LEAD_TIME_DISABLED = 0; - public const MIN_LEAD_TIME = 1; - public const SUGGESTED_LEAD_TIME = 5; - private ClockInterface $clock; - private ilObjUser $user; - private ilSetting $settings; + public const int LEAD_TIME_DISABLED = 0; + public const int MIN_LEAD_TIME = 1; + public const int SUGGESTED_LEAD_TIME = 5; + private int $lead_time = self::SUGGESTED_LEAD_TIME; private int $expiration_time = 0; private int $current_time = 0; @@ -36,14 +34,10 @@ class ilSessionReminder private int $seconds_until_reminder = 0; public function __construct( - ilObjUser $user, - ClockInterface $clock, - ilSetting $settings + private ilObjUser $user, + private readonly ClockInterface $clock, + private readonly ilSetting $settings ) { - $this->user = $user; - $this->clock = $clock; - $this->settings = $settings; - $this->init(); } @@ -79,10 +73,8 @@ private function buildValidLeadTime(int $lead_time): int $min_value = self::MIN_LEAD_TIME; $max_value = $this->getMaxPossibleLeadTime(); - if ( - $lead_time !== self::LEAD_TIME_DISABLED && - ($lead_time < $min_value || $lead_time > $max_value) - ) { + if ($lead_time !== self::LEAD_TIME_DISABLED && + ($lead_time < $min_value || $lead_time > $max_value)) { $lead_time = self::SUGGESTED_LEAD_TIME; } diff --git a/components/ILIAS/Authentication/classes/class.ilSessionReminderCheck.php b/components/ILIAS/Authentication/classes/class.ilSessionReminderCheck.php index 6782d488ba2b..79a13fbe8486 100755 --- a/components/ILIAS/Authentication/classes/class.ilSessionReminderCheck.php +++ b/components/ILIAS/Authentication/classes/class.ilSessionReminderCheck.php @@ -25,59 +25,55 @@ use ILIAS\HTTP\GlobalHttpState; use ILIAS\Refinery\Factory as Refinery; -class ilSessionReminderCheck +readonly class ilSessionReminderCheck { - private GlobalHttpState $http; - private Refinery $refinery; - private ilLanguage $lng; - private ilDBInterface $db; - private ilIniFile $clientIni; - private ilLogger $logger; - private ClockInterface $clock; - public function __construct( - GlobalHttpState $http, - Refinery $refinery, - ilLanguage $lng, - ilDBInterface $db, - ilIniFile $clientIni, - ilLogger $logger, - ClockInterface $utcClock, + private GlobalHttpState $http, + private Refinery $refinery, + private ilLanguage $lng, + private ilDBInterface $db, + private ilIniFile $client_ini, + private ilLogger $logger, + private ClockInterface $clock, + private ilSetting $settings ) { - $this->http = $http; - $this->refinery = $refinery; - $this->lng = $lng; - $this->db = $db; - $this->clientIni = $clientIni; - $this->logger = $logger; - $this->clock = $utcClock; } public function handle(): ResponseInterface { - $sessionIdHash = ilUtil::stripSlashes( + $hash = ilUtil::stripSlashes( $this->http->wrapper()->post()->retrieve( 'hash', $this->refinery->kindlyTo()->string() ) ); - $this->logger->debug('Session reminder call for session id hash: ' . $sessionIdHash); + $this->logger->debug('Session reminder call for session id hash: ' . $hash); // disable session writing and extension of expiration time ilSession::enableWebAccessWithoutSession(true); $response = ['remind' => false]; + $concat = $this->db->concat( + [ + ['usess.session_id'], + ['usess.user_id'], + ['od.create_date'] + ] + ); $res = $this->db->queryF( - 'SELECT expires, user_id, data FROM usr_session WHERE MD5(session_id) = %s', - ['text'], - [$sessionIdHash] + 'SELECT usess.expires, usess.user_id, usess.data ' . + 'FROM usr_session usess ' . + 'INNER JOIN object_data od ON od.obj_id = usess.user_id ' . + "WHERE SHA2($concat, 256) = %s", + [ilDBConstants::T_TEXT], + [$hash] ); $num = $this->db->numRows($res); - if (0 === $num) { + if ($num === 0) { $response['message'] = 'ILIAS could not determine the session data.'; return $this->toJsonResponse($response); } @@ -93,11 +89,12 @@ public function handle(): ResponseInterface return $this->toJsonResponse($response); } - $expiration_time = (int) $data['expires']; - if (null === $expiration_time) { + $expiration_time = $data['expires']; + if ($expiration_time === null) { $response['message'] = 'ILIAS could not determine the expiration time from the session data.'; return $this->toJsonResponse($response); } + $expiration_time = (int) $data['expires']; if ($this->isSessionAlreadyExpired($expiration_time)) { $response['message'] = 'The session is already expired. The client should have received a remind command before.'; @@ -111,7 +108,11 @@ public function handle(): ResponseInterface return $this->toJsonResponse($response); } - $session_reminder = ilSessionReminder::byLoggedInUser(); + $session_reminder = new ilSessionReminder( + $ilUser, + $this->clock, + $this->settings + ); $reminder_time = $expiration_time - ($session_reminder->getEffectiveLeadTime() * 60); if ($reminder_time > $this->clock->now()->getTimestamp()) { // session will expire in minutes @@ -133,7 +134,7 @@ public function handle(): ResponseInterface } $response = [ - 'extend_url' => './ilias.php?baseClass=ilDashboardGUI', + 'extend_url' => './ilias.php?baseClass=' . ilDashboardGUI::class, 'txt' => str_replace( "\\n", '%0A', @@ -141,7 +142,7 @@ public function handle(): ResponseInterface $this->lng->txt('session_reminder_alert'), ilDatePresentation::secondsToString($expiration_time - $this->clock->now()->getTimestamp()), $formatted_expiration_time, - $this->clientIni->readVariable('client', 'name') . ' | ' . ilUtil::_getHttpPath() + $this->client_ini->readVariable('client', 'name') . ' | ' . ilUtil::_getHttpPath() ) ), 'remind' => true diff --git a/components/ILIAS/Authentication/classes/class.ilSessionReminderGUI.php b/components/ILIAS/Authentication/classes/class.ilSessionReminderGUI.php index a274bb858483..37c9c3fe16ba 100755 --- a/components/ILIAS/Authentication/classes/class.ilSessionReminderGUI.php +++ b/components/ILIAS/Authentication/classes/class.ilSessionReminderGUI.php @@ -18,49 +18,46 @@ declare(strict_types=1); -class ilSessionReminderGUI +readonly class ilSessionReminderGUI { - private ilSessionReminder $sessionReminder; - private ilGlobalTemplateInterface $page; - private ilLanguage $lng; - public function __construct( - ilSessionReminder $sessionReminder, - ilGlobalTemplateInterface $page, - ilLanguage $language + private ilSessionReminder $session_reminder, + private ilGlobalTemplateInterface $page, + private ilLanguage $lng, + private ilLoggerFactory $logger_factory ) { - $this->sessionReminder = $sessionReminder; - $this->page = $page; - $this->lng = $language; } public function populatePage(): void { - if (!$this->sessionReminder->isActive()) { + if (!$this->session_reminder->isActive()) { return; } - iljQueryUtil::initjQuery($this->page); - - $this->page->addJavaScript('assets/js/session_reminder.js'); + $this->page->addJavaScript('assets/js/SessionReminder.min.js'); $url = './sessioncheck.php?client_id=' . CLIENT_ID . '&lang=' . $this->lng->getLangKey(); - $devMode = defined('DEVMODE') && DEVMODE ? 1 : 0; - $clientId = defined('CLIENT_ID') ? CLIENT_ID : ''; - $sessionId = session_id(); - $sessionHash = md5($sessionId); + $client_id = defined('CLIENT_ID') ? CLIENT_ID : ''; + $hash = hash( + 'sha256', + implode('', [ + session_id(), + $this->session_reminder->getUser()->getId(), + $this->session_reminder->getUser()->getCreateDate() + ]) + ); + $log_level = $this->logger_factory->getSettings()->getLevelByComponent('auth'); $javascript = <<page->addOnLoadCode($javascript); } diff --git a/components/ILIAS/Authentication/classes/class.ilSessionStatistics.php b/components/ILIAS/Authentication/classes/class.ilSessionStatistics.php index 383bf42a5bfc..8ed679c53efe 100755 --- a/components/ILIAS/Authentication/classes/class.ilSessionStatistics.php +++ b/components/ILIAS/Authentication/classes/class.ilSessionStatistics.php @@ -18,12 +18,9 @@ declare(strict_types=1); -/** -* @author Jörg Lützenkirchen -*/ class ilSessionStatistics { - private const SLOT_SIZE = 15; + private const int SLOT_SIZE = 15; /** * Is session statistics active at all? @@ -34,7 +31,7 @@ public static function isActive(): bool $ilSetting = $DIC['ilSetting']; - return (bool) $ilSetting->get('session_statistics', "1"); + return (bool) $ilSetting->get('session_statistics', '1'); } /** @@ -54,15 +51,15 @@ public static function createRawEntry(string $a_session_id, int $a_session_type, // in use there will be a id-collision for the raw-entry $ilDB->replace( - "usr_session_stats_raw", - array( - "session_id" => array("text", $a_session_id) - ), - array( - "type" => array("integer", $a_session_type), - "start_time" => array("integer", $a_timestamp), - "user_id" => array("integer", $a_user_id) - ) + 'usr_session_stats_raw', + [ + 'session_id' => ['text', $a_session_id] + ], + [ + 'type' => ['integer', $a_session_type], + 'start_time' => ['integer', $a_timestamp], + 'user_id' => ['integer', $a_user_id] + ] ); } @@ -90,36 +87,36 @@ public static function closeRawEntry($a_session_id, ?int $a_context = null, $a_e } else { $end_time = time(); } - $sql = "UPDATE usr_session_stats_raw" . - " SET end_time = " . $ilDB->quote($end_time, "integer"); + $sql = 'UPDATE usr_session_stats_raw' . + ' SET end_time = ' . $ilDB->quote($end_time, 'integer'); if ($a_context) { - $sql .= ",end_context = " . $ilDB->quote($a_context, "integer"); + $sql .= ',end_context = ' . $ilDB->quote($a_context, 'integer'); } - $sql .= " WHERE session_id = " . $ilDB->quote($a_session_id, "text") . - " AND end_time IS NULL"; + $sql .= ' WHERE session_id = ' . $ilDB->quote($a_session_id, 'text') . + ' AND end_time IS NULL'; $ilDB->manipulate($sql); } // batch closing elseif (!$a_expired_at) { - $sql = "UPDATE usr_session_stats_raw" . - " SET end_time = " . $ilDB->quote(time(), "integer"); + $sql = 'UPDATE usr_session_stats_raw' . + ' SET end_time = ' . $ilDB->quote(time(), 'integer'); if ($a_context) { - $sql .= ",end_context = " . $ilDB->quote($a_context, "integer"); + $sql .= ',end_context = ' . $ilDB->quote($a_context, 'integer'); } - $sql .= " WHERE " . $ilDB->in("session_id", $a_session_id, false, "text") . - " AND end_time IS NULL"; + $sql .= ' WHERE ' . $ilDB->in('session_id', $a_session_id, false, 'text') . + ' AND end_time IS NULL'; $ilDB->manipulate($sql); } // batch with individual timestamps else { foreach ($a_session_id as $id => $ts) { - $sql = "UPDATE usr_session_stats_raw" . - " SET end_time = " . $ilDB->quote($ts, "integer"); + $sql = 'UPDATE usr_session_stats_raw' . + ' SET end_time = ' . $ilDB->quote($ts, 'integer'); if ($a_context) { - $sql .= ",end_context = " . $ilDB->quote($a_context, "integer"); + $sql .= ',end_context = ' . $ilDB->quote($a_context, 'integer'); } - $sql .= " WHERE session_id = " . $ilDB->quote($id, "text") . - " AND end_time IS NULL"; + $sql .= ' WHERE session_id = ' . $ilDB->quote($id, 'text') . + ' AND end_time IS NULL'; $ilDB->manipulate($sql); } } @@ -137,23 +134,23 @@ protected static function getCurrentSlot(int $a_now): ?array $ilDB = $DIC['ilDB']; // get latest slot in db - $sql = "SELECT MAX(slot_end) previous_slot_end" . - " FROM usr_session_stats"; + $sql = 'SELECT MAX(slot_end) previous_slot_end' . + ' FROM usr_session_stats'; $res = $ilDB->query($sql); $row = $ilDB->fetchAssoc($res); - $previous_slot_end = $row["previous_slot_end"]; + $previous_slot_end = $row['previous_slot_end']; // no previous slot? calculate last complete slot // should we use minimum session raw date instead? (problem: table lock) if (!$previous_slot_end) { - $slot = (int) (floor(date("i") / self::SLOT_SIZE)); + $slot = (int) (floor(date('i') / self::SLOT_SIZE)); // last slot of previous hour if (!$slot) { - $current_slot_begin = mktime((int) date("H", $a_now) - 1, 60 - self::SLOT_SIZE, 0); + $current_slot_begin = mktime((int) date('H', $a_now) - 1, 60 - self::SLOT_SIZE, 0); } // "normalize" to slot else { - $current_slot_begin = mktime((int) date("H", $a_now), ($slot - 1) * self::SLOT_SIZE, 0); + $current_slot_begin = mktime((int) date('H', $a_now), ($slot - 1) * self::SLOT_SIZE, 0); } } else { $current_slot_begin = $previous_slot_end + 1; @@ -163,30 +160,24 @@ protected static function getCurrentSlot(int $a_now): ?array // no complete slot: nothing to do yet if ($current_slot_end < $a_now) { - return array($current_slot_begin, $current_slot_end); + return [$current_slot_begin, $current_slot_end]; } return null; } - /** - * Count number of active sessions at given time - * - * @param integer $a_time - * @return integer - */ protected static function getNumberOfActiveRawSessions(int $a_time): int { global $DIC; $ilDB = $DIC['ilDB']; - $sql = "SELECT COUNT(*) counter FROM usr_session_stats_raw" . - " WHERE (end_time IS NULL OR end_time >= " . $ilDB->quote($a_time, "integer") . ")" . - " AND start_time <= " . $ilDB->quote($a_time, "integer") . - " AND " . $ilDB->in("type", ilSessionControl::$session_types_controlled, false, "integer"); + $sql = 'SELECT COUNT(*) counter FROM usr_session_stats_raw' . + ' WHERE (end_time IS NULL OR end_time >= ' . $ilDB->quote($a_time, 'integer') . ')' . + ' AND start_time <= ' . $ilDB->quote($a_time, 'integer') . + ' AND ' . $ilDB->in('type', ilSessionControl::$session_types_controlled, false, 'integer'); $res = $ilDB->query($sql); $row = $ilDB->fetchAssoc($res); - return (int) $row["counter"]; + return (int) $row['counter']; } /** @@ -198,13 +189,13 @@ protected static function getRawData(int $a_begin, int $a_end): array $ilDB = $DIC['ilDB']; - $sql = "SELECT start_time,end_time,end_context FROM usr_session_stats_raw" . - " WHERE start_time <= " . $ilDB->quote($a_end, "integer") . - " AND (end_time IS NULL OR end_time >= " . $ilDB->quote($a_begin, "integer") . ")" . - " AND " . $ilDB->in("type", ilSessionControl::$session_types_controlled, false, "integer") . - " ORDER BY start_time"; + $sql = 'SELECT start_time,end_time,end_context FROM usr_session_stats_raw' . + ' WHERE start_time <= ' . $ilDB->quote($a_end, 'integer') . + ' AND (end_time IS NULL OR end_time >= ' . $ilDB->quote($a_begin, 'integer') . ')' . + ' AND ' . $ilDB->in('type', ilSessionControl::$session_types_controlled, false, 'integer') . + ' ORDER BY start_time'; $res = $ilDB->query($sql); - $all = array(); + $all = []; while ($row = $ilDB->fetchAssoc($res)) { $all[] = $row; } @@ -223,7 +214,7 @@ protected static function createNewAggregationSlot(int $a_now): ?array $ilDB = $DIC['ilDB']; $ilAtomQuery = $ilDB->buildAtomQuery(); - $ilAtomQuery->addTableLock("usr_session_stats"); + $ilAtomQuery->addTableLock('usr_session_stats'); $ilAtomQuery->addQueryCallable(function (ilDBInterface $ilDB) use ($a_now, &$slot) { // if we had to wait for the lock, no current slot should be returned here @@ -234,11 +225,11 @@ protected static function createNewAggregationSlot(int $a_now): ?array } // save slot to mark as taken - $fields = array( - "slot_begin" => array("integer", $slot[0]), - "slot_end" => array("integer", $slot[1]), - ); - $ilDB->insert("usr_session_stats", $fields); + $fields = [ + 'slot_begin' => ['integer', $slot[0]], + 'slot_end' => ['integer', $slot[1]], + ]; + $ilDB->insert('usr_session_stats', $fields); }); $ilAtomQuery->run(); @@ -248,8 +239,6 @@ protected static function createNewAggregationSlot(int $a_now): ?array /** * Aggregate raw session data (older than given time) - * - * @param integer $a_now */ public static function aggretateRaw(int $a_now): void { @@ -276,15 +265,16 @@ public static function aggregateRawHelper(int $a_begin, int $a_end): void global $DIC; $ilDB = $DIC['ilDB']; - $ilSetting = $DIC['ilSetting']; // "relevant" closing types - $separate_closed = array(ilSession::SESSION_CLOSE_USER, + $separate_closed = [ + ilSession::SESSION_CLOSE_USER, ilSession::SESSION_CLOSE_EXPIRE, - ilSession::SESSION_CLOSE_LOGIN); + ilSession::SESSION_CLOSE_LOGIN + ]; // gather/process data (build event timeline) - $closed_counter = $events = array(); + $closed_counter = $events = []; $opened_counter = 0; foreach (self::getRawData($a_begin, $a_end) as $item) { // open/close counters are _not_ time related @@ -295,22 +285,22 @@ public static function aggregateRawHelper(int $a_begin, int $a_end): void // "closed_other" would have been a good idea... // session opened - if ($item["start_time"] >= $a_begin) { + if ($item['start_time'] >= $a_begin) { $opened_counter++; - $events[$item["start_time"]][] = 1; + $events[$item['start_time']][] = 1; } // session closed - if ($item["end_time"] && $item["end_time"] <= $a_end) { - if (in_array($item["end_context"], $separate_closed, true)) { - if (!isset($closed_counter[$item["end_context"]])) { - $closed_counter[$item["end_context"]] = 0; + if ($item['end_time'] && $item['end_time'] <= $a_end) { + if (in_array($item['end_context'], $separate_closed, true)) { + if (!isset($closed_counter[$item['end_context']])) { + $closed_counter[$item['end_context']] = 0; } - $closed_counter[$item["end_context"]]++; + $closed_counter[$item['end_context']]++; } else { $closed_counter[0] = ($closed_counter[0] ?? 0) + 1; } - $events[$item["end_time"]][] = -1; + $events[$item['end_time']][] = -1; } } @@ -366,31 +356,31 @@ public static function aggregateRawHelper(int $a_begin, int $a_end): void unset($events); // save aggregated data - $fields = array( - "active_min" => array("integer", $active_min), - "active_max" => array("integer", $active_max), - "active_avg" => array("integer", $active_avg), - "active_end" => array("integer", $active_end), - "opened" => array("integer", $opened_counter), - "closed_manual" => array("integer", (int) ($closed_counter[ilSession::SESSION_CLOSE_USER] ?? 0)), - "closed_expire" => array("integer", (int) ($closed_counter[ilSession::SESSION_CLOSE_EXPIRE] ?? 0)), - "closed_login" => array("integer", (int) ($closed_counter[ilSession::SESSION_CLOSE_LOGIN] ?? 0)), - "closed_misc" => array("integer", (int) ($closed_counter[0] ?? 0)), - ); + $fields = [ + 'active_min' => ['integer', $active_min], + 'active_max' => ['integer', $active_max], + 'active_avg' => ['integer', $active_avg], + 'active_end' => ['integer', $active_end], + 'opened' => ['integer', $opened_counter], + 'closed_manual' => ['integer', (int) ($closed_counter[ilSession::SESSION_CLOSE_USER] ?? 0)], + 'closed_expire' => ['integer', (int) ($closed_counter[ilSession::SESSION_CLOSE_EXPIRE] ?? 0)], + 'closed_login' => ['integer', (int) ($closed_counter[ilSession::SESSION_CLOSE_LOGIN] ?? 0)], + 'closed_misc' => ['integer', (int) ($closed_counter[0] ?? 0)], + ]; $ilDB->update( - "usr_session_stats", + 'usr_session_stats', $fields, - array("slot_begin" => array("integer", $a_begin), - "slot_end" => array("integer", $a_end)) + [ + 'slot_begin' => ['integer', $a_begin], + 'slot_end' => ['integer', $a_end] + ] ); } /** * Remove already aggregated raw data - * - * @param integer $a_now */ - protected static function deleteAggregatedRaw($a_now): void + protected static function deleteAggregatedRaw(int $a_now): void { global $DIC; @@ -399,8 +389,10 @@ protected static function deleteAggregatedRaw($a_now): void // we are rather defensive here - 7 days BEFORE current aggregation $cut = $a_now - (60 * 60 * 24 * 7); - $ilDB->manipulate("DELETE FROM usr_session_stats_raw" . - " WHERE start_time <= " . $ilDB->quote($cut, "integer")); + $ilDB->manipulate( + 'DELETE FROM usr_session_stats_raw' . + ' WHERE start_time <= ' . $ilDB->quote($cut, 'integer') + ); } /** @@ -412,12 +404,12 @@ public static function getNumberOfSessionsByType(int $a_from, int $a_to): array $ilDB = $DIC['ilDB']; - $sql = "SELECT SUM(opened) opened, SUM(closed_manual) closed_manual," . - " SUM(closed_expire) closed_expire," . - " SUM(closed_login) closed_login, SUM(closed_misc) closed_misc" . - " FROM usr_session_stats" . - " WHERE slot_end > " . $ilDB->quote($a_from, "integer") . - " AND slot_begin < " . $ilDB->quote($a_to, "integer"); + $sql = 'SELECT SUM(opened) opened, SUM(closed_manual) closed_manual,' . + ' SUM(closed_expire) closed_expire,' . + ' SUM(closed_login) closed_login, SUM(closed_misc) closed_misc' . + ' FROM usr_session_stats' . + ' WHERE slot_end > ' . $ilDB->quote($a_from, 'integer') . + ' AND slot_begin < ' . $ilDB->quote($a_to, 'integer'); $res = $ilDB->query($sql); return $ilDB->fetchAssoc($res); } @@ -432,13 +424,13 @@ public static function getActiveSessions(int $a_from, int $a_to): array /** @var ilDBInterface $ilDB */ $ilDB = $DIC['ilDB']; - $sql = "SELECT slot_begin, slot_end, active_min, active_max, active_avg" . - " FROM usr_session_stats" . - " WHERE slot_end > " . $ilDB->quote($a_from, "integer") . - " AND slot_begin < " . $ilDB->quote($a_to, "integer") . - " ORDER BY slot_begin"; + $sql = 'SELECT slot_begin, slot_end, active_min, active_max, active_avg' . + ' FROM usr_session_stats' . + ' WHERE slot_end > ' . $ilDB->quote($a_from, 'integer') . + ' AND slot_begin < ' . $ilDB->quote($a_to, 'integer') . + ' ORDER BY slot_begin'; $res = $ilDB->query($sql); - $all = array(); + $all = []; while ($row = $ilDB->fetchAssoc($res)) { $entry = []; foreach ($row as $key => $value) { @@ -458,11 +450,11 @@ public static function getLastAggregation(): ?int $ilDB = $DIC['ilDB']; - $sql = "SELECT max(slot_end) latest FROM usr_session_stats"; + $sql = 'SELECT max(slot_end) latest FROM usr_session_stats'; $res = $ilDB->query($sql); $row = $ilDB->fetchAssoc($res); - if ($row["latest"]) { - return (int) $row["latest"]; + if ($row['latest']) { + return (int) $row['latest']; } //TODO check if return null as timestamp causes issues return null; diff --git a/components/ILIAS/Authentication/classes/class.ilSessionStatisticsGUI.php b/components/ILIAS/Authentication/classes/class.ilSessionStatisticsGUI.php index 2e6b72158fbf..f376c171789f 100755 --- a/components/ILIAS/Authentication/classes/class.ilSessionStatisticsGUI.php +++ b/components/ILIAS/Authentication/classes/class.ilSessionStatisticsGUI.php @@ -18,31 +18,28 @@ declare(strict_types=1); -/** - * @author Jörg Lützenkirchen - */ class ilSessionStatisticsGUI { - private const MODE_TODAY = 1; - private const MODE_LAST_DAY = 2; - private const MODE_LAST_WEEK = 3; - private const MODE_LAST_MONTH = 4; - private const MODE_DAY = 5; - private const MODE_WEEK = 6; - private const MODE_MONTH = 7; - private const MODE_YEAR = 8; - - private const SCALE_DAY = 1; - private const SCALE_WEEK = 2; - private const SCALE_MONTH = 3; - private const SCALE_YEAR = 4; - private const SCALE_PERIODIC_WEEK = 5; - - private const REQUEST_SMD = "smd"; - private const REQUEST_SMM = "smm"; - private const REQUEST_SST = "sst"; - private const REQUEST_STO = "sto"; - private const REQUEST_REF = "ref_id"; + private const int MODE_TODAY = 1; + private const int MODE_LAST_DAY = 2; + private const int MODE_LAST_WEEK = 3; + private const int MODE_LAST_MONTH = 4; + private const int MODE_DAY = 5; + private const int MODE_WEEK = 6; + private const int MODE_MONTH = 7; + private const int MODE_YEAR = 8; + + private const int SCALE_DAY = 1; + private const int SCALE_WEEK = 2; + private const int SCALE_MONTH = 3; + private const int SCALE_YEAR = 4; + private const int SCALE_PERIODIC_WEEK = 5; + + private const string REQUEST_SMD = 'smd'; + private const string REQUEST_SMM = 'smm'; + private const string REQUEST_SST = 'sst'; + private const string REQUEST_STO = 'sto'; + private const string REQUEST_REF = 'ref_id'; private ilCtrl $ilCtrl; private ilTabsGUI $ilTabs; @@ -78,7 +75,7 @@ public function __construct() $http = $DIC->http(); $kindlyTo = $DIC->refinery()->kindlyTo(); - if ($http->request()->getMethod() === "POST") { + if ($http->request()->getMethod() === 'POST') { if ($http->wrapper()->post()->has(self::REQUEST_SMD)) { $this->smd = $http->wrapper()->post()->retrieve(self::REQUEST_SMD, $kindlyTo->int()); } @@ -116,7 +113,7 @@ public function executeCommand(): bool switch ($this->ilCtrl->getNextClass()) { default: - $cmd = $this->ilCtrl->getCmd("current"); + $cmd = $this->ilCtrl->getCmd('current'); $this->$cmd(); } @@ -126,30 +123,30 @@ public function executeCommand(): bool protected function setSubTabs(): void { $this->ilTabs->addSubTab( - "current", - $this->lng->txt("trac_current_system_load"), - $this->ilCtrl->getLinkTarget($this, "current") + 'current', + $this->lng->txt('trac_current_system_load'), + $this->ilCtrl->getLinkTarget($this, 'current') ); $this->ilTabs->addSubTab( - "short", - $this->lng->txt("trac_short_system_load"), - $this->ilCtrl->getLinkTarget($this, "short") + 'short', + $this->lng->txt('trac_short_system_load'), + $this->ilCtrl->getLinkTarget($this, 'short') ); $this->ilTabs->addSubTab( - "long", - $this->lng->txt("trac_long_system_load"), - $this->ilCtrl->getLinkTarget($this, "long") + 'long', + $this->lng->txt('trac_long_system_load'), + $this->ilCtrl->getLinkTarget($this, 'long') ); $this->ilTabs->addSubTab( - "periodic", - $this->lng->txt("trac_periodic_system_load"), - $this->ilCtrl->getLinkTarget($this, "periodic") + 'periodic', + $this->lng->txt('trac_periodic_system_load'), + $this->ilCtrl->getLinkTarget($this, 'periodic') ); } protected function current(bool $a_export = false): void { - $this->ilTabs->activateSubTab("current"); + $this->ilTabs->activateSubTab('current'); // current mode if (!$this->smd) { @@ -160,7 +157,7 @@ protected function current(bool $a_export = false): void // current measure if (!$this->smm) { - $measure = "avg"; + $measure = 'avg'; } else { $measure = $this->smm; } @@ -168,8 +165,8 @@ protected function current(bool $a_export = false): void switch ($mode) { default: case self::MODE_TODAY: - $time_from = strtotime("today"); - $time_to = strtotime("tomorrow") - 1; + $time_from = strtotime('today'); + $time_to = strtotime('tomorrow') - 1; $scale = self::SCALE_DAY; break; @@ -192,39 +189,41 @@ protected function current(bool $a_export = false): void break; } - $mode_options = array( - self::MODE_TODAY => $this->lng->txt("trac_session_statistics_mode_today"), - self::MODE_LAST_DAY => $this->lng->txt("trac_session_statistics_mode_last_day"), - self::MODE_LAST_WEEK => $this->lng->txt("trac_session_statistics_mode_last_week"), - self::MODE_LAST_MONTH => $this->lng->txt("trac_session_statistics_mode_last_month")); + $mode_options = [ + self::MODE_TODAY => $this->lng->txt('trac_session_statistics_mode_today'), + self::MODE_LAST_DAY => $this->lng->txt('trac_session_statistics_mode_last_day'), + self::MODE_LAST_WEEK => $this->lng->txt('trac_session_statistics_mode_last_week'), + self::MODE_LAST_MONTH => $this->lng->txt('trac_session_statistics_mode_last_month') + ]; - $title = $this->lng->txt("trac_current_system_load") . " - " . $mode_options[$mode]; + $title = $this->lng->txt('trac_current_system_load') . ' - ' . $mode_options[$mode]; $data = $this->buildData($time_from, $time_to, $title); if (!$a_export) { // toolbar - $this->toolbar->setFormAction($this->ilCtrl->getFormAction($this, "current")); + $this->toolbar->setFormAction($this->ilCtrl->getFormAction($this, 'current')); - $mode_selector = new ilSelectInputGUI(" " . $this->lng->txt("trac_scale"), "smd"); + $mode_selector = new ilSelectInputGUI(' ' . $this->lng->txt('trac_scale'), 'smd'); $mode_selector->setOptions($mode_options); $mode_selector->setValue($mode); $this->toolbar->addInputItem($mode_selector, true); - $measure_options = array( - "avg" => $this->lng->txt("trac_session_active_avg"), - "min" => $this->lng->txt("trac_session_active_min"), - "max" => $this->lng->txt("trac_session_active_max")); + $measure_options = [ + 'avg' => $this->lng->txt('trac_session_active_avg'), + 'min' => $this->lng->txt('trac_session_active_min'), + 'max' => $this->lng->txt('trac_session_active_max') + ]; - $measure_selector = new ilSelectInputGUI(" " . $this->lng->txt("trac_measure"), "smm"); + $measure_selector = new ilSelectInputGUI(' ' . $this->lng->txt('trac_measure'), 'smm'); $measure_selector->setOptions($measure_options); $measure_selector->setValue($measure); $this->toolbar->addInputItem($measure_selector, true); - $this->toolbar->addFormButton($this->lng->txt("ok"), "current"); + $this->toolbar->addFormButton($this->lng->txt('ok'), 'current'); - if (count($data["active"])) { + if (count($data['active'])) { $this->toolbar->addSeparator(); - $this->toolbar->addFormButton($this->lng->txt("export"), "currentExport"); + $this->toolbar->addFormButton($this->lng->txt('export'), 'currentExport'); } $this->tpl->setContent($this->render($data, $scale, $measure)); @@ -257,7 +256,7 @@ protected function importDate(string $a_incoming, ?int $a_default = null) protected function short(bool $a_export = false): void { - $this->ilTabs->activateSubTab("short"); + $this->ilTabs->activateSubTab('short'); //TODO validate input // current start @@ -272,7 +271,7 @@ protected function short(bool $a_export = false): void // current measure if (!$this->smm) { - $measure = "avg"; + $measure = 'avg'; } else { $measure = $this->smm; } @@ -290,42 +289,43 @@ protected function short(bool $a_export = false): void break; } - $mode_options = array( - self::MODE_DAY => $this->lng->txt("trac_session_statistics_mode_day"), - self::MODE_WEEK => $this->lng->txt("trac_session_statistics_mode_week") - ); + $mode_options = [ + self::MODE_DAY => $this->lng->txt('trac_session_statistics_mode_day'), + self::MODE_WEEK => $this->lng->txt('trac_session_statistics_mode_week') + ]; - $title = $this->lng->txt("trac_short_system_load") . " - " . $mode_options[$mode]; + $title = $this->lng->txt('trac_short_system_load') . ' - ' . $mode_options[$mode]; $data = $this->buildData($time_from, $time_to, $title); if (!$a_export) { // toolbar - $this->toolbar->setFormAction($this->ilCtrl->getFormAction($this, "short")); + $this->toolbar->setFormAction($this->ilCtrl->getFormAction($this, 'short')); - $start_selector = new ilDateTimeInputGUI($this->lng->txt("trac_end_at"), "sst"); + $start_selector = new ilDateTimeInputGUI($this->lng->txt('trac_end_at'), 'sst'); $start_selector->setDate(new ilDate($time_to, IL_CAL_UNIX)); $this->toolbar->addInputItem($start_selector, true); - $mode_selector = new ilSelectInputGUI(" " . $this->lng->txt("trac_scale"), "smd"); + $mode_selector = new ilSelectInputGUI(' ' . $this->lng->txt('trac_scale'), 'smd'); $mode_selector->setOptions($mode_options); $mode_selector->setValue($mode); $this->toolbar->addInputItem($mode_selector, true); - $measure_options = array( - "avg" => $this->lng->txt("trac_session_active_avg"), - "min" => $this->lng->txt("trac_session_active_min"), - "max" => $this->lng->txt("trac_session_active_max")); + $measure_options = [ + 'avg' => $this->lng->txt('trac_session_active_avg'), + 'min' => $this->lng->txt('trac_session_active_min'), + 'max' => $this->lng->txt('trac_session_active_max') + ]; - $measure_selector = new ilSelectInputGUI(" " . $this->lng->txt("trac_measure"), "smm"); + $measure_selector = new ilSelectInputGUI(' ' . $this->lng->txt('trac_measure'), 'smm'); $measure_selector->setOptions($measure_options); $measure_selector->setValue($measure); $this->toolbar->addInputItem($measure_selector, true); - $this->toolbar->addFormButton($this->lng->txt("ok"), "short"); + $this->toolbar->addFormButton($this->lng->txt('ok'), 'short'); - if (count($data["active"])) { + if (count($data['active'])) { $this->toolbar->addSeparator(); - $this->toolbar->addFormButton($this->lng->txt("export"), "shortExport"); + $this->toolbar->addFormButton($this->lng->txt('export'), 'shortExport'); } $this->tpl->setContent($this->render($data, $scale, $measure)); @@ -341,7 +341,7 @@ protected function shortExport(): void protected function long($a_export = false): void { - $this->ilTabs->activateSubTab("long"); + $this->ilTabs->activateSubTab('long'); // current start //TODO validate input @@ -372,33 +372,33 @@ protected function long($a_export = false): void break; } - $mode_options = array( - self::MODE_WEEK => $this->lng->txt("trac_session_statistics_mode_week"), - self::MODE_MONTH => $this->lng->txt("trac_session_statistics_mode_month"), - self::MODE_YEAR => $this->lng->txt("trac_session_statistics_mode_year") - ); + $mode_options = [ + self::MODE_WEEK => $this->lng->txt('trac_session_statistics_mode_week'), + self::MODE_MONTH => $this->lng->txt('trac_session_statistics_mode_month'), + self::MODE_YEAR => $this->lng->txt('trac_session_statistics_mode_year') + ]; - $title = $this->lng->txt("trac_long_system_load") . " - " . $mode_options[$mode]; + $title = $this->lng->txt('trac_long_system_load') . ' - ' . $mode_options[$mode]; $data = $this->buildData($time_from, $time_to, $title); if (!$a_export) { // toolbar - $this->toolbar->setFormAction($this->ilCtrl->getFormAction($this, "long")); + $this->toolbar->setFormAction($this->ilCtrl->getFormAction($this, 'long')); - $start_selector = new ilDateTimeInputGUI($this->lng->txt("trac_end_at"), "sst"); + $start_selector = new ilDateTimeInputGUI($this->lng->txt('trac_end_at'), 'sst'); $start_selector->setDate(new ilDate($time_to, IL_CAL_UNIX)); $this->toolbar->addInputItem($start_selector, true); - $mode_selector = new ilSelectInputGUI(" " . $this->lng->txt("trac_scale"), "smd"); + $mode_selector = new ilSelectInputGUI(' ' . $this->lng->txt('trac_scale'), 'smd'); $mode_selector->setOptions($mode_options); $mode_selector->setValue($mode); $this->toolbar->addInputItem($mode_selector, true); - $this->toolbar->addFormButton($this->lng->txt("ok"), "long"); + $this->toolbar->addFormButton($this->lng->txt('ok'), 'long'); - if (count($data["active"])) { + if (count($data['active'])) { $this->toolbar->addSeparator(); - $this->toolbar->addFormButton($this->lng->txt("export"), "longExport"); + $this->toolbar->addFormButton($this->lng->txt('export'), 'longExport'); } $this->tpl->setContent($this->render($data, $scale)); @@ -414,14 +414,14 @@ protected function longExport(): void protected function periodic($a_export = false): void { - $this->ilTabs->activateSubTab("periodic"); + $this->ilTabs->activateSubTab('periodic'); //TODO validate input // current start $time_to = $this->importDate((string) $this->sst); // current end - $time_from = $this->importDate((string) $this->sto, strtotime("-7 days")); + $time_from = $this->importDate((string) $this->sto, strtotime('-7 days')); // mixed up dates? if ($time_to < $time_from) { @@ -430,26 +430,26 @@ protected function periodic($a_export = false): void $time_from = $tmp; } - $title = $this->lng->txt("trac_periodic_system_load"); + $title = $this->lng->txt('trac_periodic_system_load'); $data = $this->buildData($time_from, $time_to, $title); if (!$a_export) { // toolbar - $this->toolbar->setFormAction($this->ilCtrl->getFormAction($this, "periodic")); + $this->toolbar->setFormAction($this->ilCtrl->getFormAction($this, 'periodic')); - $end_selector = new ilDateTimeInputGUI($this->lng->txt("trac_begin_at"), "sto"); + $end_selector = new ilDateTimeInputGUI($this->lng->txt('trac_begin_at'), 'sto'); $end_selector->setDate(new ilDate($time_from, IL_CAL_UNIX)); $this->toolbar->addInputItem($end_selector, true); - $start_selector = new ilDateTimeInputGUI($this->lng->txt("trac_end_at"), "sst"); + $start_selector = new ilDateTimeInputGUI($this->lng->txt('trac_end_at'), 'sst'); $start_selector->setDate(new ilDate($time_to, IL_CAL_UNIX)); $this->toolbar->addInputItem($start_selector, true); - $this->toolbar->addFormButton($this->lng->txt("ok"), "periodic"); + $this->toolbar->addFormButton($this->lng->txt('ok'), 'periodic'); - if (count($data["active"])) { + if (count($data['active'])) { $this->toolbar->addSeparator(); - $this->toolbar->addFormButton($this->lng->txt("export"), "periodicExport"); + $this->toolbar->addFormButton($this->lng->txt('export'), 'periodicExport'); } $this->tpl->setContent($this->render($data, self::SCALE_PERIODIC_WEEK)); @@ -474,19 +474,19 @@ protected function renderCurrentBasics(): string // build left column - $left = new ilTemplate("tpl.session_statistics_left.html", true, true, "components/ILIAS/Authentication"); + $left = new ilTemplate('tpl.session_statistics_left.html', true, true, 'components/ILIAS/Authentication'); - $left->setVariable("CAPTION_CURRENT", $this->lng->txt("users_online")); - $left->setVariable("VALUE_CURRENT", $active); + $left->setVariable('CAPTION_CURRENT', $this->lng->txt('users_online')); + $left->setVariable('VALUE_CURRENT', $active); - $left->setVariable("CAPTION_LAST_AGGR", $this->lng->txt("trac_last_aggregation")); - $left->setVariable("VALUE_LAST_AGGR", ilDatePresentation::formatDate($last_aggr)); + $left->setVariable('CAPTION_LAST_AGGR', $this->lng->txt('trac_last_aggregation')); + $left->setVariable('VALUE_LAST_AGGR', ilDatePresentation::formatDate($last_aggr)); // sync button - if ($this->access->checkAccess("write", "", $this->ref_id)) { - $left->setVariable("URL_SYNC", $this->ilCtrl->getFormAction($this, "adminSync")); - $left->setVariable("CMD_SYNC", "adminSync"); - $left->setVariable("TXT_SYNC", $this->lng->txt("trac_sync_session_stats")); + if ($this->access->checkAccess('write', '', $this->ref_id)) { + $left->setVariable('URL_SYNC', $this->ilCtrl->getFormAction($this, 'adminSync')); + $left->setVariable('CMD_SYNC', 'adminSync'); + $left->setVariable('TXT_SYNC', $this->lng->txt('trac_sync_session_stats')); } return $left->get(); @@ -497,64 +497,64 @@ protected function buildData(int $a_time_from, int $a_time_to, string $a_title): // basic data - time related $counters = ilSessionStatistics::getNumberOfSessionsByType($a_time_from, $a_time_to); - $opened = (int) $counters["opened"]; - unset($counters["opened"]); + $opened = (int) $counters['opened']; + unset($counters['opened']); // build center column - $data = array(); + $data = []; ilDatePresentation::setUseRelativeDates(false); - $data["title"] = $a_title . " (" . + $data['title'] = $a_title . ' (' . ilDatePresentation::formatPeriod( new ilDateTime($a_time_from, IL_CAL_UNIX), new ilDateTime($a_time_to, IL_CAL_UNIX) - ) . ")"; + ) . ')'; - $data["opened"] = array($this->lng->txt("trac_sessions_opened"), $opened); - $data["closed"] = array($this->lng->txt("trac_sessions_closed"), array_sum($counters)); + $data['opened'] = [$this->lng->txt('trac_sessions_opened'), $opened]; + $data['closed'] = [$this->lng->txt('trac_sessions_closed'), array_sum($counters)]; foreach ($counters as $type => $counter) { - $data["closed_details"][] = array($this->lng->txt("trac_" . $type), (int) $counter); + $data['closed_details'][] = [$this->lng->txt('trac_' . $type), (int) $counter]; } - $data["active"] = ilSessionStatistics::getActiveSessions($a_time_from, $a_time_to); - $this->logger->debug("Data to plot: " . var_export($data, true)); + $data['active'] = ilSessionStatistics::getActiveSessions($a_time_from, $a_time_to); + $this->logger->debug('Data to plot: ' . var_export($data, true)); return $data; } protected function render(array $a_data, int $a_scale, ?string $a_measure = null): string { - $center = new ilTemplate("tpl.session_statistics_center.html", true, true, "components/ILIAS/Authentication"); + $center = new ilTemplate('tpl.session_statistics_center.html', true, true, 'components/ILIAS/Authentication'); foreach ($a_data as $idx => $item) { switch ($idx) { - case "active": - case "title": + case 'active': + case 'title': // nothing to do break; - case "closed_details": - $center->setCurrentBlock("closed_details"); + case 'closed_details': + $center->setCurrentBlock('closed_details'); foreach ($item as $detail) { - $center->setVariable("CAPTION_CLOSED_DETAILS", $detail[0]); - $center->setVariable("VALUE_CLOSED_DETAILS", $detail[1]); + $center->setVariable('CAPTION_CLOSED_DETAILS', $detail[0]); + $center->setVariable('VALUE_CLOSED_DETAILS', $detail[1]); $center->parseCurrentBlock(); } break; default: $tpl_var = strtoupper($idx); - $center->setVariable("CAPTION_" . $tpl_var, $item[0]); - $center->setVariable("VALUE_" . $tpl_var, $item[1]); + $center->setVariable('CAPTION_' . $tpl_var, $item[0]); + $center->setVariable('VALUE_' . $tpl_var, $item[1]); break; } } - if ($a_data["active"]) { - $center->setVariable("CHART", $this->getChart($a_data["active"], $a_data["title"], $a_scale, $a_measure)); + if ($a_data['active']) { + $center->setVariable('CHART', $this->getChart($a_data['active'], $a_data['title'], $a_scale, $a_measure)); } else { - $this->tpl->setOnScreenMessage('info', $this->lng->txt("trac_session_statistics_no_data")); + $this->tpl->setOnScreenMessage('info', $this->lng->txt('trac_session_statistics_no_data')); } return $center->get(); @@ -569,28 +569,30 @@ protected function getChart( int $a_scale = self::SCALE_DAY, ?string $a_measure = null ): string { - $chart = ilChart::getInstanceByType(ilChart::TYPE_GRID, "objstacc"); - $chart->setSize("700", "500"); + $chart = ilChart::getInstanceByType(ilChart::TYPE_GRID, 'objstacc'); + $chart->setSize('700', '500'); $chart->setYAxisToInteger(true); $legend = new ilChartLegend(); $chart->setLegend($legend); if (!$a_measure) { - $measures = ["min", "avg", "max"]; + $measures = ['min', 'avg', 'max']; } else { $measures = [$a_measure]; } - $colors_map = array("min" => "#00cc00", - "avg" => "#0000cc", - "max" => "#cc00cc"); + $colors_map = [ + 'min' => '#00cc00', + 'avg' => '#0000cc', + 'max' => '#cc00cc' + ]; - $colors = $act_line = array(); + $colors = $act_line = []; foreach ($measures as $measure) { $act_line[$measure] = $chart->getDataInstance(ilChartGrid::DATA_LINES); $act_line[$measure]->setLineSteps(true); - $act_line[$measure]->setLabel($this->lng->txt("trac_session_active_" . $measure)); + $act_line[$measure]->setLabel($this->lng->txt('trac_session_active_' . $measure)); $colors[] = $colors_map[$measure]; } @@ -599,26 +601,26 @@ protected function getChart( $chart_data = $this->adaptDataToScale($a_scale, $a_data); $scale = ceil(count($chart_data) / 5); - $labels = array(); + $labels = []; foreach ($chart_data as $idx => $item) { - $date = $item["slot_begin"]; + $date = $item['slot_begin']; if ($a_scale === self::SCALE_PERIODIC_WEEK || !($idx % ceil($scale))) { switch ($a_scale) { case self::SCALE_DAY: - $labels[$date] = date("H:i", $date); + $labels[$date] = date('H:i', $date); break; case self::SCALE_WEEK: - $labels[$date] = date("d.m. H", $date) . "h"; + $labels[$date] = date('d.m. H', $date) . 'h'; break; case self::SCALE_MONTH: - $labels[$date] = date("d.m.", $date); + $labels[$date] = date('d.m.', $date); break; case self::SCALE_YEAR: - $labels[$date] = date("Y-m", $date); + $labels[$date] = date('Y-m', $date); break; case self::SCALE_PERIODIC_WEEK: @@ -645,7 +647,7 @@ protected function getChart( } foreach ($measures as $measure) { - $value = (int) $item["active_" . $measure]; + $value = (int) $item['active_' . $measure]; $act_line[$measure]->addPoint($date, $value); } } @@ -674,47 +676,47 @@ protected function adaptDataToScale(int $a_scale, array $a_data): array $tmp = []; foreach ($a_data as $item) { - $date_parts = getdate($item["slot_begin"]); + $date_parts = getdate($item['slot_begin']); // aggregate slots for scale switch ($a_scale) { default: case self::SCALE_MONTH: // aggregate to hours => 720 values - $slot = mktime($date_parts["hours"], 0, 0, $date_parts["mon"], $date_parts["mday"], $date_parts["year"]); + $slot = mktime($date_parts['hours'], 0, 0, $date_parts['mon'], $date_parts['mday'], $date_parts['year']); break; case self::SCALE_YEAR: // aggregate to days => 365 values - $slot = mktime(0, 0, 1, $date_parts["mon"], $date_parts["mday"], $date_parts["year"]); + $slot = mktime(0, 0, 1, $date_parts['mon'], $date_parts['mday'], $date_parts['year']); break; case self::SCALE_PERIODIC_WEEK: // aggregate to weekdays => 672 values - $day = $date_parts["wday"]; + $day = $date_parts['wday']; if (!$day) { $day = 7; } - $slot = $day . date("His", $item["slot_begin"]); + $slot = $day . date('His', $item['slot_begin']); break; } // process minx/max, prepare avg foreach ($item as $id => $value) { switch (substr((string) $id, -3)) { - case "min": + case 'min': if (!isset($tmp[$slot][$id]) || $value < $tmp[$slot][$id]) { $tmp[$slot][$id] = $value; } break; - case "max": + case 'max': if (!isset($tmp[$slot][$id]) || $value > $tmp[$slot][$id]) { $tmp[$slot][$id] = $value; } break; - case "avg": + case 'avg': $tmp[$slot][$id][] = $value; break; } @@ -722,8 +724,8 @@ protected function adaptDataToScale(int $a_scale, array $a_data): array } foreach ($tmp as $slot => $attr) { - $tmp[$slot]["active_avg"] = (int) round(array_sum($attr["active_avg"]) / count($attr["active_avg"])); - $tmp[$slot]["slot_begin"] = $slot; + $tmp[$slot]['active_avg'] = (int) round(array_sum($attr['active_avg']) / count($attr['active_avg'])); + $tmp[$slot]['slot_begin'] = $slot; } ksort($tmp); return array_values($tmp); @@ -736,38 +738,38 @@ protected function adminSync(): void ilSession::_destroyExpiredSessions(); ilSessionStatistics::aggretateRaw($now); - $this->tpl->setOnScreenMessage('success', $this->lng->txt("trac_sync_session_stats_success"), true); + $this->tpl->setOnScreenMessage('success', $this->lng->txt('trac_sync_session_stats_success'), true); $this->ilCtrl->redirect($this); } - protected function exportCSV(array $a_data, $a_scale): void + protected function exportCSV(array $a_data, $a_scale): never { ilDatePresentation::setUseRelativeDates(false); $csv = new ilCSVWriter(); - $csv->setSeparator(";"); + $csv->setSeparator(';'); $now = time(); // meta - $meta = array( - $this->lng->txt("trac_name_of_installation") => $this->clientIniFile->readVariable('client', 'name'), - $this->lng->txt("trac_report_date") => ilDatePresentation::formatDate(new ilDateTime($now, IL_CAL_UNIX)), - $this->lng->txt("trac_report_owner") => $this->user->getFullName(), - ); + $meta = [ + $this->lng->txt('trac_name_of_installation') => $this->clientIniFile->readVariable('client', 'name'), + $this->lng->txt('trac_report_date') => ilDatePresentation::formatDate(new ilDateTime($now, IL_CAL_UNIX)), + $this->lng->txt('trac_report_owner') => $this->user->getFullName(), + ]; foreach ($a_data as $idx => $item) { switch ($idx) { - case "title": - $meta[$this->lng->txt("title")] = $item; + case 'title': + $meta[$this->lng->txt('title')] = $item; break; - case "active": + case 'active': // nothing to do break; - case "closed_details": + case 'closed_details': foreach ($item as $detail) { - $meta[$a_data["closed"][0] . " - " . $detail[0]] = $detail[1]; + $meta[$a_data['closed'][0] . ' - ' . $detail[0]] = $detail[1]; } break; @@ -784,16 +786,16 @@ protected function exportCSV(array $a_data, $a_scale): void $csv->addRow(); // aggregate data - $aggr_data = $this->adaptDataToScale($a_scale, $a_data["active"]); + $aggr_data = $this->adaptDataToScale($a_scale, $a_data['active']); // header $first = $aggr_data; $first = array_keys(array_shift($first)); foreach ($first as $column) { // split weekday and time slot again - if ($a_scale === self::SCALE_PERIODIC_WEEK && $column === "slot_begin") { - $csv->addColumn("weekday"); - $csv->addColumn("time"); + if ($a_scale === self::SCALE_PERIODIC_WEEK && $column === 'slot_begin') { + $csv->addColumn('weekday'); + $csv->addColumn('time'); } else { $csv->addColumn(strip_tags((string) $column)); } @@ -807,18 +809,18 @@ protected function exportCSV(array $a_data, $a_scale): void $value = implode(', ', $value); } switch ($column) { - case "slot_begin": + case 'slot_begin': // split weekday and time slot again if ($a_scale === self::SCALE_PERIODIC_WEEK) { $csv->addColumn(ilCalendarUtil::_numericDayToString((int) substr((string) $value, 0, 1))); - $value = substr((string) $value, 1, 2) . ":" . substr((string) $value, 3, 2); + $value = substr((string) $value, 1, 2) . ':' . substr((string) $value, 3, 2); break; } // fallthrough // no break - case "slot_end": - $value = date("d.m.Y H:i", $value); + case 'slot_end': + $value = date('d.m.Y H:i', $value); break; } $csv->addColumn(strip_tags((string) $value)); @@ -827,12 +829,12 @@ protected function exportCSV(array $a_data, $a_scale): void } // send - $filename = "session_statistics_" . date("Ymd", $now) . ".csv"; - header("Content-type: text/comma-separated-values"); - header("Content-Disposition: attachment; filename=\"" . $filename . "\""); - header("Expires: 0"); - header("Cache-Control: must-revalidate, post-check=0,pre-check=0"); - header("Pragma: public"); + $filename = 'session_statistics_' . date('Ymd', $now) . '.csv'; + header('Content-type: text/comma-separated-values'); + header('Content-Disposition: attachment; filename="' . $filename . '"'); + header('Expires: 0'); + header('Cache-Control: must-revalidate, post-check=0,pre-check=0'); + header('Pragma: public'); echo $csv->getCSVString(); exit(); } diff --git a/components/ILIAS/Authentication/interfaces/interface.ilAuthCredentials.php b/components/ILIAS/Authentication/interfaces/interface.ilAuthCredentials.php index 879876c81f6c..407af87049a6 100755 --- a/components/ILIAS/Authentication/interfaces/interface.ilAuthCredentials.php +++ b/components/ILIAS/Authentication/interfaces/interface.ilAuthCredentials.php @@ -18,43 +18,17 @@ declare(strict_types=1); -/** - * Interface of auth credentials - * - * @author Stefan Meyer - * - */ interface ilAuthCredentials { - /** - * Set username - */ public function setUsername(string $a_name): void; - /** - * Get username - */ public function getUsername(): string; - /** - * Set password - */ public function setPassword(string $a_password): void; - /** - * Get password - */ public function getPassword(): string; - /** - * Set auth mode. - * Used - for instance - for manual selection on login screen. - * @param string $a_auth_mode - */ public function setAuthMode(string $a_auth_mode): void; - /** - * Get auth mode - */ public function getAuthMode(): string; } diff --git a/components/ILIAS/Authentication/interfaces/interface.ilAuthDefinition.php b/components/ILIAS/Authentication/interfaces/interface.ilAuthDefinition.php index 37ca7ad1d169..c775b66b0490 100755 --- a/components/ILIAS/Authentication/interfaces/interface.ilAuthDefinition.php +++ b/components/ILIAS/Authentication/interfaces/interface.ilAuthDefinition.php @@ -18,9 +18,6 @@ declare(strict_types=1); -/** - * @author Stefan Meyer - */ interface ilAuthDefinition { /** @@ -34,7 +31,7 @@ public function getProvider(ilAuthCredentials $credentials, string $a_auth_id): * For plugins the auth must be greater than 1000 and unique * * @see constants like in ilAuthUtils::AUTH_LDAP - * @return int[] + * @return list */ public function getAuthIds(): array; diff --git a/components/ILIAS/Authentication/interfaces/interface.ilAuthFrontendInterface.php b/components/ILIAS/Authentication/interfaces/interface.ilAuthFrontendInterface.php index 4b2b65d6773a..3172a5093d03 100755 --- a/components/ILIAS/Authentication/interfaces/interface.ilAuthFrontendInterface.php +++ b/components/ILIAS/Authentication/interfaces/interface.ilAuthFrontendInterface.php @@ -18,16 +18,7 @@ declare(strict_types=1); -/** - * Interface for auth methods (web form, http, ...) - * - * @author Stefan Meyer - * - */ interface ilAuthFrontendInterface { - /** - * Try authentication. - */ public function authenticate(): bool; } diff --git a/components/ILIAS/Authentication/interfaces/interface.ilAuthProviderAccountMigrationInterface.php b/components/ILIAS/Authentication/interfaces/interface.ilAuthProviderAccountMigrationInterface.php index 545ecaf38f2e..3a5248aad4d9 100755 --- a/components/ILIAS/Authentication/interfaces/interface.ilAuthProviderAccountMigrationInterface.php +++ b/components/ILIAS/Authentication/interfaces/interface.ilAuthProviderAccountMigrationInterface.php @@ -18,11 +18,6 @@ declare(strict_types=1); -/** - * - * @author Stefan Meyer - * - */ interface ilAuthProviderAccountMigrationInterface { /** @@ -46,8 +41,6 @@ public function getUserAuthModeName(): string; */ public function getExternalAccountName(): string; - - /** * Create new account */ diff --git a/components/ILIAS/Authentication/interfaces/interface.ilAuthProviderInterface.php b/components/ILIAS/Authentication/interfaces/interface.ilAuthProviderInterface.php index d1fdbe3cb364..91b53443f9e4 100755 --- a/components/ILIAS/Authentication/interfaces/interface.ilAuthProviderInterface.php +++ b/components/ILIAS/Authentication/interfaces/interface.ilAuthProviderInterface.php @@ -18,18 +18,7 @@ declare(strict_types=1); -/** - * Standard interface for auth provider implementations - * - * @author Stefan Meyer - * - */ interface ilAuthProviderInterface { - /** - * Do authentication - * @param \ilAuthStatus $status Authentication status - * @return bool - */ - public function doAuthentication(\ilAuthStatus $status): bool; + public function doAuthentication(ilAuthStatus $status): bool; } diff --git a/components/ILIAS/Authentication/js/account_migration.js b/components/ILIAS/Authentication/js/account_migration.js deleted file mode 100755 index d92acd37def6..000000000000 --- a/components/ILIAS/Authentication/js/account_migration.js +++ /dev/null @@ -1,29 +0,0 @@ -function ilToggleVisibleElements() -{ - var migration = document.getElementById('acc_migrate'); - var userName = document.getElementById('acc_user_name'); - var userPass = document.getElementById('acc_user_pass'); - - if(migration && !migration.checked) - { - userName.disabled = true; - userPass.disabled = true; - } - else - { - userName.disabled = false; - userPass.disabled = false; - } -} - -function ilCheckMigration() -{ - var migration = document.getElementById('acc_migrate'); - - if(migration) - { - migration.checked = true; - } -} - - \ No newline at end of file diff --git a/components/ILIAS/Authentication/resources/js/dist/SessionReminder.min.js b/components/ILIAS/Authentication/resources/js/dist/SessionReminder.min.js new file mode 100644 index 000000000000..1cf72f07e9f0 --- /dev/null +++ b/components/ILIAS/Authentication/resources/js/dist/SessionReminder.min.js @@ -0,0 +1,15 @@ +/** + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + */ +!function(e){"use strict";class t{#e;#t;#s;#i;#r;#n;constructor(e,t,s,i,r){this.#e=e,this.#i=s,this.#s=t,this.#n=i,this.#r=r;const n=this.#s.getItem(this.#r);this.#t=n?{...i,...n}:i,t.notifyOnUpdate(this.#r,(e=>{null!==e&&(this.#t={...this.#t,...e},this.#i.info(`State updated: ${JSON.stringify(this.#t)}`))}))}get(){return{...this.#t}}update(e){this.#t={...this.#t,...e},this.#s.setItem(this.#r,this.#t),this.#i.info(`State updated (incl. storage): ${JSON.stringify(this.#t)}`)}}class s{#e;#s;#i;#o;#a;#l={};constructor(e,t,s,i,r){this.#e=e,this.#s=t,this.#i=s,this.#o=i,this.#a=r,this.#e.addEventListener("storage",(e=>{if(e.key&&e.key.startsWith(`${this.#o}_${this.#a}_`)){const t=e.key.replace(`${this.#o}_${this.#a}_`,"");if(Object.prototype.hasOwnProperty.call(this.#l,t)){const s=JSON.parse(e.newValue),i=s?s.value:null;this.#i.info(`Storage event: Item ${e.key} changed to: ${e.newValue}`),this.#l[t].forEach((e=>{e(i)}))}else this.#i.info(`Storage event: Could not find subscriber for item: ${t}`)}})),this.gc()}notifyOnUpdate(e,t){Object.prototype.hasOwnProperty.call(this.#l,e)||(this.#l[e]=[]),this.#l[e].push(t)}setItem(e,t){const s={lastChange:(new Date).getTime(),value:t};this.#s.setItem(`${this.#o}_${this.#a}_${e}`,JSON.stringify(s))}getItem(e,t={}){const s=this.#s.getItem(`${this.#o}_${this.#a}_${e}`);if(null===s)return t;return JSON.parse(s).value||t}gc(){this.#e.setInterval((()=>{for(const[e,t]of Object.entries(this.#s))if(-1!==e.indexOf(this.#o)&&Object.prototype.hasOwnProperty.call(this.#s,e)){let s=t;"string"==typeof s&&(s=JSON.parse(s)),s.lastChange<(new Date).getTime()-864e5&&(this.#s.removeItem(e),this.#i.debug(`Garbage collected: ${e}`))}}),6e4)}}class i{#e;#c;#h;#g=null;#d;constructor(e,t,s){this.#e=e,this.#c=t,this.#h=s,this.#d=!1,this.#u()}#u(){this.#g=this.#e.setInterval((()=>this.#f()),this.#h)}stop(){this.#g&&(this.#e.clearInterval(this.#g),this.#g=null)}#f(){if(!this.#d)try{this.#d=!0,this.#c()}finally{this.#d=!1}}}class r{#b;#t;#i;#p;#m;#e;constructor(e,t,s,i,r){this.#b=e,this.#t=t,this.#i=s,this.#m=i,this.#e=r}run(){this.#e.addEventListener("beforeunload",(()=>{this.#t.update({status:"unlocked"}),this.#i.info("Unlocked session reminder on browser's unload event")})),this.#i.info("Session reminder started"),this.#t.get().hash!==this.#b.hash&&(this.#t.update({activation:"enabled",status:"unlocked",hash:this.#b.hash}),this.#i.info("Session cookie changed after new login or session reminder initially started for current session: Released lock and enabled reminder.")),this.#p=new this.#m(this.#e,this.#c.bind(this),1e3*this.#b.frequency),this.#i.info("Started periodical executer")}#c(){const e=this.#t.get();if("disabled"===e.activation||"locked"===e.status)return void this.#i.info("Session reminder disabled or locked for current user session");this.#t.update({status:"locked"}),this.#i.info("Session reminder locked"),this.#p.stop(),this.#i.info("Stopped periodical executer");const t=new FormData;t.append("hash",this.#b.hash),fetch(this.#b.url,{method:"POST",body:t}).then((e=>e.json())).then((e=>{try{if("object"!=typeof e||null===e||Array.isArray(e)||void 0===JSON.stringify(e))throw new Error("The response body seems not to be valid JSON")}catch(e){throw new Error("Invalid response format",{cause:e})}if(e.message&&"string"==typeof e.message&&this.#i.info(e.message),!e.remind)return void this.#i.info("Reminder of session expiration not necessary: Session reminder unlocked");if(!this.#e.confirm(e.txt))return this.#t.update({activation:"disabled",status:"unlocked"}),void this.#i.info("User disabled reminder for current session: Session reminder disabled but unlocked");fetch(e.extend_url,{method:"GET"}).then((()=>(this.#i.info("Session extended successfully"),new Promise((e=>{e(!0)}))))).catch((e=>{this.#i.error("Fetch error occurred:",e)}))})).catch((e=>{this.#i.error("Fetch error occurred:",e)})).finally((()=>{this.#t.update({status:"unlocked"}),this.#i.info("Unlocked session reminder"),this.#p=new this.#m(this.#e,this.#c.bind(this),1e3*this.#b.frequency),this.#i.info("Restarted periodical executer")}))}}class n{static defineLogLevel(e,t){return{value:e,name:t}}static TRACE=n.defineLogLevel(50,"TRACE");static DEBUG=n.defineLogLevel(100,"DEBUG");static INFO=n.defineLogLevel(200,"INFO");static NOTICE=n.defineLogLevel(250,"NOTICE");static WARNING=n.defineLogLevel(300,"WARN");static ERROR=n.defineLogLevel(400,"ERROR");static CRITICAL=n.defineLogLevel(500,"CRITICAL");static ALERT=n.defineLogLevel(550,"ALERT");static EMERGENCY=n.defineLogLevel(600,"EMERGENCY");static OFF=n.defineLogLevel(1e3,"OFF");static LEVELS=[n.TRACE,n.DEBUG,n.INFO,n.NOTICE,n.WARNING,n.ERROR,n.CRITICAL,n.ALERT,n.EMERGENCY,n.OFF];#w;#E;constructor(e,t=n.DEBUG){this.#w=e,this.setLevel(t)}setLevel(e){e&&"value"in e&&(this.#E=e)}getLevel(){return this.#E}enabledFor(e){return e.value>=this.#E.value}logMessage(e,t){if(!this.enabledFor(e))return;let s=t,[i,...r]=s;"string"==typeof i&&(i=`SessionReminder | ${i}`,s=[i,...r]),e.name.toLowerCase()in this.#w?this.#w[e.name.toLowerCase()](...s):this.#w.error(...s)}trace(...e){this.logMessage(n.TRACE,e)}debug(...e){this.logMessage(n.DEBUG,e)}info(...e){this.logMessage(n.INFO,e)}notice(...e){this.logMessage(n.NOTICE,e)}warn(...e){this.logMessage(n.WARNING,e)}error(...e){this.logMessage(n.ERROR,e)}critical(...e){this.logMessage(n.CRITICAL,e)}alert(...e){this.logMessage(n.ALERT,e)}emergency(...e){this.logMessage(n.EMERGENCY,e)}log(...e){this.info(...e)}static levelForNumericValue(e){if(Number.isNaN(e))return n.DEBUG;for(const t of n.LEVELS)if(Number.parseInt(e,10)===t.value)return t;return n.DEBUG}}e.SessionReminder=e.SessionReminder||(()=>{let e=null;return{init(o,a,l){if(e)return e.logger?.warning("SessionReminder init() called again; already running."),e;const c={url:"",clientId:"",hash:"",frequency:60,logLevel:n.INFO.value},h=Object.fromEntries(Object.entries({...c,...o}).filter((([e])=>e in c))),g=new n(l,n.levelForNumericValue(h.logLevel));"localStorage"in a||g.warn("No 'localStorage' support.");const d="localStorage"in a?a.localStorage:(()=>{const e={};return{removeItem:t=>{delete e[t]},getItem:t=>e[t]??null,setItem:(t,s)=>{e[t]=s}}})(),u=new s(a,d,g,"il_sr",h.clientId),f=new t(a,u,g,{activation:"disabled",status:"unlocked",hash:""},"state");return e=new r(h,f,g,i,a),e},run(){if(!e)throw new Error("SessionReminder not initialized. Call init() first.");e.run(),this.run=()=>{e.logger?.warning("SessionReminder run() called again; already running.")}}}})()}(il); diff --git a/components/ILIAS/Authentication/resources/js/rollup.config.js b/components/ILIAS/Authentication/resources/js/rollup.config.js new file mode 100644 index 000000000000..556110e75d63 --- /dev/null +++ b/components/ILIAS/Authentication/resources/js/rollup.config.js @@ -0,0 +1,45 @@ +/** + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + * + ******************************************************************** */ + +import terser from '@rollup/plugin-terser'; +import copyright from '../../../../../scripts/Copyright-Checker/copyright.js'; +import preserveCopyright from '../../../../../scripts/Copyright-Checker/preserveCopyright.js'; + +export default [ + { + input: './src/index.js', + output: { + file: './dist/SessionReminder.min.js', + format: 'iife', + banner: copyright, + plugins: [ + terser({ + format: { + comments: preserveCopyright, + }, + }), + ], + globals: { + il: 'il', + window: 'window', + console: 'console', + localStorage: 'localStorage', + confirm: 'confirm', + }, + }, + external: ['il', 'window', 'console', 'localStorage', 'confirm'], + }, +]; diff --git a/components/ILIAS/Authentication/resources/js/src/Logger.js b/components/ILIAS/Authentication/resources/js/src/Logger.js new file mode 100644 index 000000000000..d006b02dc935 --- /dev/null +++ b/components/ILIAS/Authentication/resources/js/src/Logger.js @@ -0,0 +1,194 @@ +/** + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + * + ******************************************************************** */ + +/** + * @typedef {Object} LogLevel + * @property {number} value + * @property {string} name + */ + +export default class Logger { + /** + * @param {number} value - The code of the log level + * @param {string} name - The readable log level name + * @returns {LogLevel} + */ + static defineLogLevel(value, name) { + return { value, name }; + } + + static TRACE = Logger.defineLogLevel(50, 'TRACE'); + + static DEBUG = Logger.defineLogLevel(100, 'DEBUG'); + + static INFO = Logger.defineLogLevel(200, 'INFO'); + + static NOTICE = Logger.defineLogLevel(250, 'NOTICE'); + + static WARNING = Logger.defineLogLevel(300, 'WARN'); + + static ERROR = Logger.defineLogLevel(400, 'ERROR'); + + static CRITICAL = Logger.defineLogLevel(500, 'CRITICAL'); + + static ALERT = Logger.defineLogLevel(550, 'ALERT'); + + static EMERGENCY = Logger.defineLogLevel(600, 'EMERGENCY'); + + static OFF = Logger.defineLogLevel(1000, 'OFF'); + + static LEVELS = [ + Logger.TRACE, Logger.DEBUG, Logger.INFO, Logger.NOTICE, + Logger.WARNING, Logger.ERROR, Logger.CRITICAL, Logger.ALERT, + Logger.EMERGENCY, Logger.OFF, + ]; + + /** + * @type {Console} + */ + #consoleObj; + + /** + * @type {LogLevel} + */ + #level; + + /** + * @param {Console} consoleObj - The console object for logging. + * @param {LogLevel} level - The logging level. + */ + constructor(consoleObj, level = Logger.DEBUG) { + this.#consoleObj = consoleObj; + this.setLevel(level); + } + + /** + * @param {LogLevel} level + */ + setLevel(level) { + if (level && 'value' in level) { + this.#level = level; + } + } + + /** + * @returns {LogLevel} + */ + getLevel() { + return this.#level; + } + + /** + * @param {LogLevel} level + * @returns {boolean} + */ + enabledFor(level) { + return level.value >= this.#level.value; + } + + /** + * + * @param {LogLevel} level + * @param {...*} args + */ + logMessage(level, args) { + if (!this.enabledFor(level)) { + return; + } + + let argumentList = args; + + let [firstElement, ...rest] = argumentList; + if (typeof firstElement === 'string') { + firstElement = `SessionReminder | ${firstElement}`; + argumentList = [firstElement, ...rest]; + } + + if (level.name.toLowerCase() in this.#consoleObj) { + this.#consoleObj[level.name.toLowerCase()](...argumentList); + } else { + this.#consoleObj.error(...argumentList); + } + } + + /** + * @param {...*} args + */ + trace(...args) { this.logMessage(Logger.TRACE, args); } + + /** + * @param {...*} args + */ + debug(...args) { this.logMessage(Logger.DEBUG, args); } + + /** + * @param {...*} args + */ + info(...args) { this.logMessage(Logger.INFO, args); } + + /** + * @param {...*} args + */ + notice(...args) { this.logMessage(Logger.NOTICE, args); } + + /** + * @param {...*} args + */ + warn(...args) { this.logMessage(Logger.WARNING, args); } + + /** + * @param {...*} args + */ + error(...args) { this.logMessage(Logger.ERROR, args); } + + /** + * @param {...*} args + */ + critical(...args) { this.logMessage(Logger.CRITICAL, args); } + + /** + * @param {...*} args + */ + alert(...args) { this.logMessage(Logger.ALERT, args); } + + /** + * @param {...*} args + */ + emergency(...args) { this.logMessage(Logger.EMERGENCY, args); } + + /** + * @param {...*} args + */ + log(...args) { this.info(...args); } + + /** + * @param {number} numericLevel - The log level to be used (e.g. provided from backend config) + * @returns {LogLevel} + */ + static levelForNumericValue(numericLevel) { + if (Number.isNaN(numericLevel)) { + return Logger.DEBUG; + } + + for (const level of Logger.LEVELS) { + if (Number.parseInt(numericLevel, 10) === level.value) { + return level; + } + } + + return Logger.DEBUG; + } +} diff --git a/components/ILIAS/Authentication/resources/js/src/PeriodicalExecuter.js b/components/ILIAS/Authentication/resources/js/src/PeriodicalExecuter.js new file mode 100644 index 000000000000..5e263f1b1d7b --- /dev/null +++ b/components/ILIAS/Authentication/resources/js/src/PeriodicalExecuter.js @@ -0,0 +1,81 @@ +/** + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + * + ******************************************************************** */ + +/** + * @callback NoArgsCallback + * @returns {void} + */ + +export default class PeriodicalExecuter { + /** + * @type {Window} + */ + #windowObj; + + /** + * @type {NoArgsCallback} + */ + #callback; + + /** + * @type {number} + */ + #frequency; + + /** + * @type {number | null} + */ + #timer = null; + + /** + * @type {boolean} + */ + #currentlyExecuting; + + /** + * @param {Window} windowObj - The global window object. + * @param {NoArgsCallback} callback - The function to execute periodically. + * @param {number} frequency - The execution interval in milliseconds. + */ + constructor(windowObj, callback, frequency) { + this.#windowObj = windowObj; + this.#callback = callback; + this.#frequency = frequency; + this.#currentlyExecuting = false; + this.#registerCallback(); + } + + #registerCallback() { + this.#timer = this.#windowObj.setInterval(() => this.#onTimerEvent(), this.#frequency); + } + + stop() { + if (!this.#timer) return; + this.#windowObj.clearInterval(this.#timer); + this.#timer = null; + } + + #onTimerEvent() { + if (!this.#currentlyExecuting) { + try { + this.#currentlyExecuting = true; + this.#callback(); + } finally { + this.#currentlyExecuting = false; + } + } + } +} diff --git a/components/ILIAS/Authentication/resources/js/src/SessionReminder.js b/components/ILIAS/Authentication/resources/js/src/SessionReminder.js new file mode 100644 index 000000000000..c6e01dcd3cfa --- /dev/null +++ b/components/ILIAS/Authentication/resources/js/src/SessionReminder.js @@ -0,0 +1,168 @@ +/** + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + * + ******************************************************************** */ + +export default class SessionReminder { + /** + * @type {Object} + */ + #options; + + /** + * @type {State} + */ + #state; + + /** + * @type {Logger} + */ + #logger; + + /** + * @type {PeriodicalExecuter} + */ + #executer; + + /** + * @type {typeof PeriodicalExecuter} + */ + #periodicalExecuter; + + /** + * @type {Window} + */ + #windowObj; + + /** + * @param {Object} options - Configuration options. + * @param {State} state - The state. + * @param {Logger} logger - The logger. + * @param {typeof PeriodicalExecuter} PeriodicalExecuter - Class for periodic execution. + * @param {Window} windowObj - The global window object. + */ + constructor( + options, + state, + logger, + PeriodicalExecuter, + windowObj, + ) { + this.#options = options; + this.#state = state; + this.#logger = logger; + this.#periodicalExecuter = PeriodicalExecuter; + this.#windowObj = windowObj; + } + + run() { + this.#windowObj.addEventListener('beforeunload', () => { + this.#state.update({ status: 'unlocked' }); + this.#logger.info("Unlocked session reminder on browser's unload event"); + }); + + this.#logger.info('Session reminder started'); + + if (this.#state.get().hash !== this.#options.hash) { + this.#state.update({ activation: 'enabled', status: 'unlocked', hash: this.#options.hash }); + this.#logger.info( + 'Session cookie changed after new login or session reminder initially started ' + + 'for current session: Released lock and enabled reminder.', + ); + } + + this.#executer = new this.#periodicalExecuter( + this.#windowObj, + this.#callback.bind(this), + this.#options.frequency * 1000, + ); + this.#logger.info('Started periodical executer'); + } + + #callback() { + const state = this.#state.get(); + if (state.activation === 'disabled' || state.status === 'locked') { + this.#logger.info('Session reminder disabled or locked for current user session'); + return; + } + + this.#state.update({ status: 'locked' }); + this.#logger.info('Session reminder locked'); + this.#executer.stop(); + this.#logger.info('Stopped periodical executer'); + + const formData = new FormData(); + formData.append('hash', this.#options.hash); + + fetch(this.#options.url, { + method: 'POST', + body: formData, + }) + .then((response) => response.json()) + .then((data) => { + try { + if (typeof data !== 'object' + || data === null + || Array.isArray(data) + || JSON.stringify(data) === undefined) { + throw new Error('The response body seems not to be valid JSON'); + } + } catch (e) { + throw new Error('Invalid response format', { cause: e }); + } + + if (data.message && typeof data.message === 'string') { + this.#logger.info(data.message); + } + + if (!data.remind) { + this.#logger.info('Reminder of session expiration not necessary: Session reminder unlocked'); + return; + } + + const extend = this.#windowObj.confirm(data.txt); + if (!extend) { + this.#state.update({ activation: 'disabled', status: 'unlocked' }); + this.#logger.info('User disabled reminder for current session: Session reminder disabled but unlocked'); + return; + } + + fetch(data.extend_url, { method: 'GET' }) + .then(() => { + this.#logger.info('Session extended successfully'); + return new Promise( + (resolve) => { + resolve(true); + }, + ); + }) + .catch((error) => { + this.#logger.error('Fetch error occurred:', error); + }); + }) + .catch((error) => { + this.#logger.error('Fetch error occurred:', error); + }) + .finally(() => { + this.#state.update({ status: 'unlocked' }); + this.#logger.info('Unlocked session reminder'); + this.#executer = new this.#periodicalExecuter( + this.#windowObj, + this.#callback.bind(this), + this.#options.frequency * 1000, + ); + this.#logger.info('Restarted periodical executer'); + }); + } +} diff --git a/components/ILIAS/Authentication/resources/js/src/State.js b/components/ILIAS/Authentication/resources/js/src/State.js new file mode 100644 index 000000000000..574a5730781d --- /dev/null +++ b/components/ILIAS/Authentication/resources/js/src/State.js @@ -0,0 +1,108 @@ +/** + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + * + ******************************************************************** */ + +/** + * @typedef {Object} SessionState + * @property {'disabled'|'enabled'} activation + * @property {'locked'|'unlocked'} status + * @property {string} hash + */ + +/** + * @typedef {Object} PartialSessionState + * @property {'disabled'|'enabled'} [activation] + * @property {'locked'|'unlocked'} [status] + * @property {string} [hash] + */ + +export default class State { + /** + * @type {Window} + */ + #windowObj; + + /** + * @type {SessionState} + */ + #state; + + /** + * @type {Storage} + */ + #storage; + + /** + * @type {Logger} + */ + #logger; + + /** + * @type {string} + */ + #key; + + /** + * @type {SessionState + */ + #initialState; + + /** + * + * @param {Window} windowObj - The global window object. + * @param {Storage} storage - The storrage mechanism (e.g., localStorage). + * @param {Logger} logger - The logger. + * @param {SessionState} initialState - The initial state to be stored. + * @param {string} key - The key under which the state is stored. + */ + constructor(windowObj, storage, logger, initialState, key) { + this.#windowObj = windowObj; + this.#logger = logger; + this.#storage = storage; + this.#initialState = initialState; + this.#key = key; + + const saved = this.#storage.getItem(this.#key); + this.#state = saved ? { ...initialState, ...saved } : initialState; + + storage.notifyOnUpdate( + this.#key, + (updates) => { + if (updates !== null) { + this.#state = { ...this.#state, ...updates }; + this.#logger.info(`State updated: ${JSON.stringify(this.#state)}`); + } + }, + ); + } + + /** + * Returns the current state. + * @returns {SessionState} + */ + get() { + return { ...this.#state }; + } + + /** + * + * @param {PartialSessionState} updates + */ + update(updates) { + this.#state = { ...this.#state, ...updates }; + this.#storage.setItem(this.#key, this.#state); + this.#logger.info(`State updated (incl. storage): ${JSON.stringify(this.#state)}`); + } +} diff --git a/components/ILIAS/Authentication/resources/js/src/Storage.js b/components/ILIAS/Authentication/resources/js/src/Storage.js new file mode 100644 index 000000000000..876af6652a13 --- /dev/null +++ b/components/ILIAS/Authentication/resources/js/src/Storage.js @@ -0,0 +1,163 @@ +/** + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + * + ******************************************************************** */ + +/** + * @typedef {Object} LogLevel + * @property {number} value + * @property {string} name + */ + +/** + * @callback GenericCallback + * @param {*} value + * @returns {void} + */ + +/** + * @typedef {Object.} Subscribers + */ + +export default class Storage { + /** + * @type {Window} + */ + #windowObj; + + /** + * @type {Storage} + */ + #storage; + + /** + * @type {Logger} + */ + #logger; + + /** + * @type {string} + */ + #namespace; + + /** + * @type {string} + */ + #prefix; + + /** + * @type {Subscribers} + */ + #subscribers = {}; + + /** + * @param {Window} windowObj - The global window object. + * @param {Storage} storage - The storage mechanism (e.g., localStorage). + * @param {Logger} logger - The logger. + * @param {string} namespace - The namespace for keys. + * @param {string} prefix - The prefix for keys. + */ + constructor(windowObj, storage, logger, namespace, prefix) { + this.#windowObj = windowObj; + this.#storage = storage; + this.#logger = logger; + this.#namespace = namespace; + this.#prefix = prefix; + + this.#windowObj.addEventListener('storage', (event) => { + if (event.key && event.key.startsWith(`${this.#namespace}_${this.#prefix}_`)) { + const key = event.key.replace(`${this.#namespace}_${this.#prefix}_`, ''); + + if (Object.prototype.hasOwnProperty.call(this.#subscribers, key)) { + const item = JSON.parse(event.newValue); + const value = item ? item.value : null; + + this.#logger.info(`Storage event: Item ${event.key} changed to: ${event.newValue}`); + + this.#subscribers[key].forEach((callback) => { + callback(value); + }); + } else { + this.#logger.info(`Storage event: Could not find subscriber for item: ${key}`); + } + } + }); + + this.gc(); + } + + /** + * @param {string} key - The key to listen for a change. + * @param {GenericCallback} callback - A callback function to be executed when the value changes. + */ + notifyOnUpdate(key, callback) { + if (!Object.prototype.hasOwnProperty.call(this.#subscribers, key)) { + this.#subscribers[key] = []; + } + + this.#subscribers[key].push(callback); + } + + /** + * + * @param {string} key - The keyword to store the value under. + * @param {object} value - The value to store. + */ + + setItem(key, value) { + const item = { + lastChange: (new Date()).getTime(), + value, + }; + + this.#storage.setItem(`${this.#namespace}_${this.#prefix}_${key}`, JSON.stringify(item)); + } + + /** + * Retrieves the stored value. + * @param {string} key - The keyword to store the value under. + * @param {object} defaultValue - A default valur. + * @returns {object} The parsed state object. + */ + getItem(key, defaultValue = {}) { + const item = this.#storage.getItem(`${this.#namespace}_${this.#prefix}_${key}`); + if (item === null) { + return defaultValue; + } + + const parseItem = JSON.parse(item); + + return parseItem.value || defaultValue; + } + + gc() { + this.#windowObj.setInterval(() => { + for (const [key, value] of Object.entries(this.#storage)) { + if (key.indexOf(this.#namespace) !== -1 + && Object.prototype.hasOwnProperty.call(this.#storage, key) + ) { + let item = value; + if (typeof item === 'string') { + item = JSON.parse(item); + } + + if (item.lastChange < (new Date()).getTime() - (24 * 60 * 60 * 1000)) { + this.#storage.removeItem(key); + this.#logger.debug(`Garbage collected: ${key}`); + } + } + } + }, (60 * 1000)); + } +} diff --git a/components/ILIAS/Authentication/resources/js/src/index.js b/components/ILIAS/Authentication/resources/js/src/index.js new file mode 100644 index 000000000000..e1ce27863331 --- /dev/null +++ b/components/ILIAS/Authentication/resources/js/src/index.js @@ -0,0 +1,108 @@ +/** + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + * + ******************************************************************** */ + +import il from 'il'; +import State from './State.js'; +import Storage from './Storage.js'; +import PeriodicalExecuter from './PeriodicalExecuter.js'; +import SessionReminder from './SessionReminder.js'; +import Logger from './Logger.js'; + +il.SessionReminder = il.SessionReminder || (() => { + let instance = null; + + return { + init(serverOptions, windowObj, consoleObj) { + if (instance) { + instance.logger?.warning('SessionReminder init() called again; already running.'); + return instance; + } + + const defaultOptions = { + url: '', + clientId: '', + hash: '', + frequency: 60, + logLevel: Logger.INFO.value, + }; + + const options = Object.fromEntries( + Object.entries({ ...defaultOptions, ...serverOptions }) + .filter(([key]) => key in defaultOptions), + ); + + const logger = new Logger( + consoleObj, + Logger.levelForNumericValue(options.logLevel), + ); + + if (!('localStorage' in windowObj)) { + logger.warn("No 'localStorage' support."); + } + + const ls = 'localStorage' in windowObj ? windowObj.localStorage : (() => { + const items = {}; + return { + removeItem: (key) => { delete items[key]; }, + getItem: (key) => items[key] ?? null, + setItem: (key, value) => { items[key] = value; }, + }; + })(); + + const storage = new Storage( + windowObj, + ls, + logger, + 'il_sr', + options.clientId, + ); + + const state = new State( + windowObj, + storage, + logger, + { + activation: 'disabled', + status: 'unlocked', + hash: '', + }, + 'state', + ); + + instance = new SessionReminder( + options, + state, + logger, + PeriodicalExecuter, + windowObj, + ); + + return instance; + }, + + run() { + if (!instance) { + throw new Error('SessionReminder not initialized. Call init() first.'); + } + + instance.run(); + + this.run = () => { + instance.logger?.warning('SessionReminder run() called again; already running.'); + }; + }, + }; +})(); diff --git a/components/ILIAS/Authentication/resources/session_reminder.js b/components/ILIAS/Authentication/resources/session_reminder.js deleted file mode 100644 index 1a7090119690..000000000000 --- a/components/ILIAS/Authentication/resources/session_reminder.js +++ /dev/null @@ -1,341 +0,0 @@ -(function ($) { - "use strict"; - - class Cookies { - /** - * - * @type {{secure: bool, SameSite: string, expires: number|Date, path: string, domain: string}} - */ - static #defaultParameters = { - secure: window.location.protocol === "https:", - SameSite: "Lax" - }; - - /** - * - * @param {string} value - * @returns {string} - */ - static #readValue(value) { - if (value[0] === '"') { - value = value.slice(1, -1) - } - - return value.replace(/(%[\dA-F]{2})+/gi, decodeURIComponent); - }; - - /** - * - * @param {string} value - * @returns {string} - */ - static #writeValue(value) { - return encodeURIComponent(value).replace( - /%(2[346BF]|3[AC-F]|40|5[BDE]|60|7[BCD])/g, - decodeURIComponent - ); - }; - - /** - * - * @param {string} name - * @returns {string} - */ - static #encodeName(name) { - name = encodeURIComponent(name) - .replace(/%(2[346B]|5E|60|7C)/g, decodeURIComponent) - .replace(/[()]/g, escape)//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/escape - - return name; - }; - - /** - * - * @param {{secure: bool, SameSite: string, expires: number|Date, path: string, domain: string}} attributes - * @returns {string} - */ - static #attributesToString(attributes) { - let text = ""; - - for (const attributeName in attributes) { - if (!attributes.hasOwnProperty(attributeName) || !attributes[attributeName]) { - continue; - } - - if (attributeName === "domain") { - text += "; domain=" + attributes[attributeName].split(";")[0]; - } - - if (attributeName === "path") { - text += "; path=" + attributes[attributeName].split(";")[0]; - } - - if (attributeName === "expires" && attributes[attributeName] instanceof Date) { - text += "; expires=" + attributes[attributeName].toUTCString(); - } - - if (attributeName === "SameSite") { - text += "; SameSite=" + attributes[attributeName].split(";")[0]; - } - - if (attributeName === "secure" && attributes[attributeName] === true) { - text += "; secure"; - } - } - - return text; - }; - - /** - * - * @param {string} name - * @param {string} value - * @param {{secure: bool, SameSite: string, expires: number|Date, path: string, domain: string}} attributes - */ - static set(name, value, attributes) { - attributes = Object.assign({}, Cookies.#defaultParameters, attributes); - - if (typeof attributes.expires === "number") { - attributes.expires = new Date(Date.now() + attributes.expires * 864e5) - } - - document.cookie = name + "=" + Cookies.#writeValue(value) + Cookies.#attributesToString( - attributes); - } - - /** - * - * @param {string} name - * @returns {string|undefined} - */ - static get(name) { - const cookies = document.cookie ? document.cookie.split("; ") : []; - let cookieJar = {}; - - for (let i = 0; i < cookies.length; i++) { - const parts = cookies[i].split("="), - cookie = parts.slice(1).join("="); - - try { - cookieJar[decodeURIComponent(parts[0])] = Cookies.#readValue( - cookie, - decodeURIComponent(parts[0]) - ); - } - catch (e) { - } - } - - return cookieJar[name]; - } - - /** - * - * @param {string} name - */ - remove(name) { - Cookies.set( - name, - '', - { expires: -1 } - ); - } - } - - class PeriodicalExecuter { - #callback; - #frequency; - #timer; - #currentlyExecuting; - - /** - * @param {function} callback - * @param {number} frequency - */ - constructor(callback, frequency) { - this.#callback = callback; - this.#frequency = frequency; - this.#currentlyExecuting = false; - this.#registerCallback(); - } - - #registerCallback() { - this.#timer = setInterval(() => { - this.#onTimerEvent() - }, this.#frequency); - } - - #execute() { - this.#callback(this); - } - - stop() { - if (!this.#timer) return; - clearInterval(this.#timer); - this.#timer = null; - } - - #onTimerEvent() { - if (!this.#currentlyExecuting) { - try { - this.#currentlyExecuting = true; - this.#execute(); - } finally { - this.#currentlyExecuting = false; - } - } - } - } - - $.fn.ilSessionReminder = function (method) { - let session_reminder_executer = null, - session_reminder_locked = false; - - const internals = { - log: function (message) { - if (this.properties.debug) { - console.log(message); - } - }, - properties: {} - }; - - const ilSessionReminderCallback = function () { - const properties = internals.properties, - cookie_prefix = "il_sr_" + properties.client_id + "_"; - - if (Cookies.get(cookie_prefix + "activation") == "disabled" || - Cookies.get(cookie_prefix + "status") == "locked") { - internals.log("Session reminder disabled or locked for current user session"); - return; - } - - Cookies.set(cookie_prefix + "status", "locked"); - session_reminder_locked = true; - internals.log("Session reminder locked"); - $.ajax({ - url: properties.url, - dataType: 'json', - type: 'POST', - data: { - hash: properties.hash - }, - success: function (response) { - if (response.message && typeof response.message == "string") { - internals.log(response.message); - } - - if (response.remind) { - session_reminder_executer.stop(); - - const extend = confirm(unescape(response.txt)); - - if (extend == true) { - $.ajax({ - url: response.extend_url, - type: 'GET', - success: function () { - session_reminder_executer = new PeriodicalExecuter( - ilSessionReminderCallback, - properties.frequency * 1000 - ); - Cookies.set(cookie_prefix + "status", "unlocked"); - session_reminder_locked = false; - internals.log("User extends session: Session reminder unlocked"); - } - }).fail(function () { - session_reminder_executer = new PeriodicalExecuter( - ilSessionReminderCallback, - properties.frequency * 1000 - ); - Cookies.set(cookie_prefix + "status", "unlocked"); - session_reminder_locked = false; - internals.log("XHR Failure: Session reminder unlocked"); - }); - } else { - Cookies.set(cookie_prefix + "activation", "disabled"); - Cookies.set(cookie_prefix + "status", "unlocked"); - session_reminder_locked = false; - internals.log( - "User disabled reminder for current session: Session reminder disabled but unlocked"); - session_reminder_executer = new PeriodicalExecuter( - ilSessionReminderCallback, - properties.frequency * 1000 - ); - } - } else { - Cookies.set(cookie_prefix + "status", "unlocked"); - session_reminder_locked = false; - internals.log("Reminder of session expiration not necessary: Session reminder unlocked"); - } - } - }).fail(function () { - Cookies.set(cookie_prefix + "status", "unlocked"); - session_reminder_locked = false; - internals.log("XHR Failure: Session reminder unlocked"); - }); - }; - - const methods = { - init: function (params) { - return this.each(function () { - const $this = $(this); - - if ($this.data('sessionreminder')) { - return; - } - - const data = { - properties: $.extend( - true, {}, - { - url: "", - client_id: "", - hash: "", - frequency: 60, - debug: 0 - }, - params - ) - }; - - $this.data("sessionreminder", data); - internals.properties = data.properties; - - const properties = internals.properties, - cookie_prefix = "il_sr_" + properties.client_id + "_"; - - $(window).on('beforeunload', function () { - if (session_reminder_locked) { - Cookies.set(cookie_prefix + "status", "unlocked"); - internals.log("Unlocked session reminder on unload event"); - } - }); - - internals.log("Session reminder started"); - if (Cookies.get(cookie_prefix + "session_id_hash") !== properties.hash) { - Cookies.set(cookie_prefix + "activation", "enabled"); - Cookies.set(cookie_prefix + "status", "unlocked"); - Cookies.set(cookie_prefix + "session_id_hash", properties.hash); - internals.log( - "Session cookie changed after new login or session reminder initially started " + - "for current session: Release lock and enabled reminder" - ); - } - - session_reminder_executer = new PeriodicalExecuter( - ilSessionReminderCallback, properties.frequency * 1000 - ); - }); - } - }; - - if (methods[method]) { - return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); - } else if (typeof method === "object" || !method) { - return methods.init.apply(this, arguments); - } else { - $.error("Method " + method + " does not exist on jQuery.ilSessionReminder"); - } - }; -})(jQuery); diff --git a/components/ILIAS/Authentication/resources/sessioncheck.php b/components/ILIAS/Authentication/resources/sessioncheck.php index 154fe92cf67a..a494647d7f73 100644 --- a/components/ILIAS/Authentication/resources/sessioncheck.php +++ b/components/ILIAS/Authentication/resources/sessioncheck.php @@ -41,6 +41,7 @@ $DIC['ilClientIniFile'], $DIC->logger()->auth(), (new DataFactory())->clock()->utc(), + $DIC->settings() ) )->handle() ); diff --git a/components/ILIAS/Authentication/tests/LocalUserPasswordTest.php b/components/ILIAS/Authentication/tests/LocalUserPasswordTest.php index 510d63767444..03d178a7ad95 100644 --- a/components/ILIAS/Authentication/tests/LocalUserPasswordTest.php +++ b/components/ILIAS/Authentication/tests/LocalUserPasswordTest.php @@ -101,16 +101,23 @@ public function testExceptionIsRaisedIfPasswordManagerIsCreatedWithoutValidFacto */ public function testInstanceCanBeCreated(): void { + $call_count = 0; $factory_mock = $this->getMockBuilder(LocalUserPasswordEncoderFactory::class)->disableOriginalConstructor()->getMock(); - $factory_mock->expects($this->exactly(2))->method('getSupportedEncoderNames')->will($this->onConsecutiveCalls( - [ - 'mockencoder', - 'second_mockencoder' - ], - [ - 'mockencoder' - ] - )); + $factory_mock + ->expects($this->exactly(2)) + ->method('getSupportedEncoderNames') + ->willReturnCallback(function () use (&$call_count) { + $call_count++; + if ($call_count === 1) { + return [ + 'mockencoder', + 'second_mockencoder' + ]; + } + return [ + 'mockencoder' + ]; + }); $password_manager = new LocalUserPasswordManager([ 'password_encoder' => 'md5', diff --git a/components/ILIAS/Awareness/classes/Provider/AwarenessToastProvider.php b/components/ILIAS/Awareness/classes/Provider/AwarenessToastProvider.php index 7c6c91c4485c..96b6d3aae46c 100755 --- a/components/ILIAS/Awareness/classes/Provider/AwarenessToastProvider.php +++ b/components/ILIAS/Awareness/classes/Provider/AwarenessToastProvider.php @@ -85,9 +85,7 @@ public function getToasts(): array $this->if->identifier(self::PROVIDER_KEY . '_' . $this->dic->user()->getId()), $this->dic->language()->txt('awareness_now_online') ) - ->withIcon($this->dic->ui()->factory()->symbol()->icon()->standard(Standard::USR, '')) - ->withVanishTime((int) $setting->get('osd_vanish', (string) Toast::DEFAULT_VANISH_TIME)) - ->withDelayTime((int) $setting->get('osd_delay', (string) Toast::DEFAULT_DELAY_TIME)); + ->withIcon($this->dic->ui()->factory()->symbol()->icon()->standard(Standard::USR, '')); $links = []; foreach ($new_users as $user) { $uname = "[" . $user['login'] . "]"; diff --git a/components/ILIAS/BackgroundTasks/src/Implementation/Bucket/BasicBucket.php b/components/ILIAS/BackgroundTasks/src/Implementation/Bucket/BasicBucket.php index 07352a4c671b..7fcdc9e285a9 100755 --- a/components/ILIAS/BackgroundTasks/src/Implementation/Bucket/BasicBucket.php +++ b/components/ILIAS/BackgroundTasks/src/Implementation/Bucket/BasicBucket.php @@ -207,7 +207,7 @@ public function getDescription(): string public function setDescription(string $description): void { - $this->description = $description; + $this->description = mb_substr($description, 0, 255); } /** diff --git a/components/ILIAS/BackgroundTasks/src/Implementation/Persistence/BasicPersistence.php b/components/ILIAS/BackgroundTasks/src/Implementation/Persistence/BasicPersistence.php index 001788e94a50..cce22d3d63bd 100755 --- a/components/ILIAS/BackgroundTasks/src/Implementation/Persistence/BasicPersistence.php +++ b/components/ILIAS/BackgroundTasks/src/Implementation/Persistence/BasicPersistence.php @@ -57,11 +57,59 @@ public function __construct(protected \ilDBInterface $db) protected function gc(): void { - $this->db->manipulateF( - "DELETE FROM il_bt_bucket WHERE user_id = %s AND (state = %s OR state = %s)", - ['integer', 'integer', 'integer'], - [defined('ANONYMOUS_USER_ID') ? \ANONYMOUS_USER_ID : 13, State::FINISHED, State::USER_INTERACTION] - ); + $atom = $this->db->buildAtomQuery(); + + $atom->addTableLock('il_bt_bucket'); + $atom->addTableLock('il_bt_task'); + $atom->addTableLock('il_bt_value'); + $atom->addTableLock('il_bt_value_to_task'); + $atom->addQueryCallable(function (\ilDBInterface $db): void { + $this->db->manipulateF( + "DELETE FROM il_bt_bucket WHERE user_id = %s AND (state = %s OR state = %s) AND last_heartbeat < %s AND last_heartbeat > 0", + ['integer', 'integer', 'integer', 'integer'], + [ + defined('ANONYMOUS_USER_ID') ? \ANONYMOUS_USER_ID : 13, + State::FINISHED, + State::USER_INTERACTION, + time() - 1 * 60 * 24 * 30 + ] + ); + + // remove old finished buckets + $this->db->manipulateF( + "DELETE FROM il_bt_bucket WHERE state = %s AND last_heartbeat < %s AND last_heartbeat > 0", + ['integer', 'integer'], + [State::FINISHED, time() - 60 * 60 * 24 * 30] // older than 30 days + ); + + // remove old buckets with other states + $this->db->manipulateF( + "DELETE FROM il_bt_bucket WHERE state != %s AND last_heartbeat < %s AND last_heartbeat > 0", + ['integer', 'integer'], + [State::FINISHED, time() - 60 * 60 * 24 * 180] // older than 180 days + ); + + // remove tasks without a bucket + $this->db->manipulate( + "DELETE il_bt_task FROM il_bt_task LEFT JOIN il_bt_bucket ON il_bt_bucket.id = il_bt_task.bucket_id WHERE il_bt_bucket.id IS NULL;" + ); + + // remove value to bucket links without a bucket + $this->db->manipulate( + "DELETE il_bt_value_to_task FROM il_bt_value_to_task LEFT JOIN il_bt_bucket ON il_bt_bucket.id = il_bt_value_to_task.bucket_id WHERE il_bt_bucket.id IS NULL;" + ); + + // remove value to bucket links without a task + $this->db->manipulate( + "DELETE il_bt_value_to_task FROM il_bt_value_to_task LEFT JOIN il_bt_task ON il_bt_task.id = il_bt_value_to_task.task_id WHERE il_bt_task.id IS NULL;" + ); + + // remove values without a task + $this->db->manipulate( + "DELETE il_bt_value FROM il_bt_value LEFT JOIN il_bt_value_to_task ON il_bt_value_to_task.value_id = il_bt_value.id WHERE il_bt_value_to_task.id IS NULL;" + ); + }); + $atom->run(); } public function setConnector(\arConnector $c): void @@ -108,7 +156,11 @@ public function updateBucket(Bucket $bucket): void public function getBucketIdsOfUser(int $user_id, string $order_by = "id", string $order_direction = "ASC"): array { // Garbage Collection - $this->gc(); + $random = new \Random\Randomizer(); + + if($random->getInt(1, 100) === 1) { + $this->gc(); + } return BucketContainer::where(['user_id' => $user_id]) ->orderBy($order_by, $order_direction) diff --git a/components/ILIAS/BackgroundTasks_/classes/Setup/class.ilBackgroundTasksConfigStoredObjective.php b/components/ILIAS/BackgroundTasks_/classes/Setup/class.ilBackgroundTasksConfigStoredObjective.php index 48bdb9f33e6a..6f1fb1a3e3ee 100755 --- a/components/ILIAS/BackgroundTasks_/classes/Setup/class.ilBackgroundTasksConfigStoredObjective.php +++ b/components/ILIAS/BackgroundTasks_/classes/Setup/class.ilBackgroundTasksConfigStoredObjective.php @@ -59,6 +59,12 @@ public function achieve(Environment $environment): Environment $ini->addGroup("background_tasks"); } + if ($this->config->getType() === \ilBackgroundTasksSetupConfig::TYPE_ASYNCHRONOUS) { + $io = $environment->getResource(Setup\Environment::RESOURCE_ADMIN_INTERACTION); + /** @var Setup\CLI\IOWrapper $io */ + $io->inform('Asynchronous background tasks need SOAP administration to be enabled. Make sure to enable it in your configuarion.'); + } + $ini->setVariable("background_tasks", "concurrency", $this->config->getType()); $ini->setVariable("background_tasks", "number_of_concurrent_tasks", $this->config->getMaxCurrentTasks()); diff --git a/components/ILIAS/BackgroundTasks_/classes/class.ilBTPopOverGUI.php b/components/ILIAS/BackgroundTasks_/classes/class.ilBTPopOverGUI.php index 9f57763d2e63..8577b6f034b3 100755 --- a/components/ILIAS/BackgroundTasks_/classes/class.ilBTPopOverGUI.php +++ b/components/ILIAS/BackgroundTasks_/classes/class.ilBTPopOverGUI.php @@ -27,6 +27,7 @@ use ILIAS\UI\Component\Button\Button; use ILIAS\UI\Component\Button\Shy; use ILIAS\UI\Component\Legacy\Content; +use ILIAS\BackgroundTasks\Task\Job; /** * Class ilBTPopOverGUI @@ -127,7 +128,7 @@ public function getItemForObserver(Bucket $observer): Notification $item = $item->withAdditionalOnLoadCode(fn($id): string => "var notification_item = il.UI.item.notification.getNotificationItemObject($('#$id')); il.BGTask.refreshItem(notification_item,'$url');"); - $expected = $current_task->getExpectedTimeOfTaskInSeconds(); + $expected = $current_task instanceof Job ? $current_task->getExpectedTimeOfTaskInSeconds() : 0; $possibly_failed = ($observer->getLastHeartbeat() < (time() - $expected)); if ($possibly_failed) { $item = $item->withDescription($this->txt('task_might_be_failed')); diff --git a/components/ILIAS/Badge/classes/class.ilBadge.php b/components/ILIAS/Badge/classes/class.ilBadge.php index 93f94a6af297..7ae5afe3942a 100755 --- a/components/ILIAS/Badge/classes/class.ilBadge.php +++ b/components/ILIAS/Badge/classes/class.ilBadge.php @@ -23,6 +23,7 @@ class ilBadge { + private ilLogger $log; protected ilDBInterface $db; protected int $id = 0; protected int $parent_id = 0; @@ -50,6 +51,7 @@ public function __construct( $this->db = $container->database(); $this->resource_storage = $container->resourceStorage(); + $this->log = $container->logger()->root(); if ($a_id) { $this->read($a_id); } @@ -129,13 +131,7 @@ public function clone(int $target_parent_obj_id): void $this->setImageRid($new_collection_id); $this->create(); } else { - $img = $this->getImagePath(); - $this->setId(0); - $this->create(); - if ($img) { - // see uploadImage() - copy($img, $this->getImagePath()); - } + $this->log->warning('Please run the "Migration of files of badges to the resource storage service" job, before working with badges.'); } } } @@ -159,15 +155,12 @@ public function copy( if ($this->getId()) { $this->setId(0); + $old_rid = $this->getImageRid(); $this->create(); - if ($this->getImageRid()) { + if ($old_rid !== null) { + $new_rid = $this->resource_storage->manage()->clone(new ResourceIdentification($old_rid)); + $this->setImageRid($new_rid); $this->update(); - } else { - $img = $this->getImagePath(); - if ($img) { - // see uploadImage() - copy($img, $this->getImagePath()); - } } } } @@ -501,14 +494,11 @@ public function delete(): void return; } - if (file_exists($this->getImagePath())) { - unlink($this->getImagePath()); - } else { - if ($this->getImageRid() !== null) { - try { - $this->resource_storage->manage()->remove(new ResourceIdentification($this->getImageRid()), new ilBadgeFileStakeholder()); - } catch (Exception $e) { - } + if ($this->getImageRid()) { + try { + $this->resource_storage->manage()->remove(new ResourceIdentification($this->getImageRid()), new ilBadgeFileStakeholder()); + } catch (Exception $e) { + $this->log->warning(sprintf('There was an exception, while deleting the badge with id %s. Exception: %s', $this->getId(), $e->getMessage())); } } diff --git a/components/ILIAS/Badge/classes/class.ilBadgeImage.php b/components/ILIAS/Badge/classes/class.ilBadgeImage.php index 0d314bf273a7..8b8f6c5b076d 100644 --- a/components/ILIAS/Badge/classes/class.ilBadgeImage.php +++ b/components/ILIAS/Badge/classes/class.ilBadgeImage.php @@ -27,8 +27,8 @@ use ILIAS\FileUpload\Exception\IllegalStateException; use ilGlobalTemplateInterface; use ilWACSignedPath; -use ilFSStorageBadgeImageTemplate; -use ILIAS\Badge; +use ILIAS\ResourceStorage\Identification\ResourceIdentification; +use Exception; class ilBadgeImage { @@ -73,7 +73,7 @@ public function getImageFromResourceId( $image_src = $urls[$size]; } } - } elseif ($badge instanceof ilBadge) { + } elseif ($badge->getImage()) { $image_src = ilWACSignedPath::signFile($badge->getImagePath()); } @@ -95,4 +95,12 @@ public function processImageUpload(ilBadge $badge): void $this->main_template->setOnScreenMessage('failure', $e->getMessage(), true); } } + + /** + * @throws Exception + */ + public function cloneBadgeImageByRid(ResourceIdentification $identification): string + { + return $this->resource_storage->manage()->clone($identification)->serialize(); + } } diff --git a/components/ILIAS/Badge/classes/class.ilBadgeImageTemplate.php b/components/ILIAS/Badge/classes/class.ilBadgeImageTemplate.php index 22fdcadfb634..187981d50843 100755 --- a/components/ILIAS/Badge/classes/class.ilBadgeImageTemplate.php +++ b/components/ILIAS/Badge/classes/class.ilBadgeImageTemplate.php @@ -20,6 +20,7 @@ use ILIAS\ResourceStorage\Services; use ILIAS\FileUpload\FileUpload; use ILIAS\Badge\ilBadgeImage; +use ILIAS\ResourceStorage\Identification\ResourceIdentification; class ilBadgeImageTemplate { @@ -178,6 +179,7 @@ public function processImageUpload(ilBadgeImageTemplate $badge): void $array_result = $this->upload_service->getResults(); $array_result = array_pop($array_result); if ($array_result->getName() !== '') { + $this->resource_storage->manage()->remove(new ResourceIdentification($badge->getImageRid()), new ilBadgeFileStakeholder()); $stakeholder = new ilBadgeFileStakeholder(); $identification = $this->resource_storage->manage()->upload($array_result, $stakeholder); $this->resource_storage->flavours()->ensure($identification, new \ilBadgePictureDefinition()); diff --git a/components/ILIAS/Badge/classes/class.ilBadgeImageTemplateTableGUI.php b/components/ILIAS/Badge/classes/class.ilBadgeImageTemplateTableGUI.php index 016d92da7835..35643e65df3d 100755 --- a/components/ILIAS/Badge/classes/class.ilBadgeImageTemplateTableGUI.php +++ b/components/ILIAS/Badge/classes/class.ilBadgeImageTemplateTableGUI.php @@ -214,7 +214,7 @@ public function renderTable(): void $table = $this->factory ->table() - ->data($this->lng->txt('badge_image_templates'), $this->getColumns(), $this) + ->data($this, $this->lng->txt('badge_image_templates'), $this->getColumns()) ->withId(self::class) ->withOrder(new Order('title', Order::ASC)) ->withActions($this->getActions($url_builder, $action_parameter_token, $row_id_token)) diff --git a/components/ILIAS/Badge/classes/class.ilBadgeManagementGUI.php b/components/ILIAS/Badge/classes/class.ilBadgeManagementGUI.php index 16a4775f8166..4987d9222ea9 100755 --- a/components/ILIAS/Badge/classes/class.ilBadgeManagementGUI.php +++ b/components/ILIAS/Badge/classes/class.ilBadgeManagementGUI.php @@ -23,6 +23,9 @@ use ILIAS\Badge\ilBadgeTableGUI; use ILIAS\Badge\ilBadgeUserTableGUI; use ILIAS\Refinery\Factory; +use ILIAS\ResourceStorage\Identification\ResourceIdentification; +use ILIAS\ResourceStorage\Collection\ResourceCollection; +use ILIAS\Setup\ArrayEnvironment; /** * @ilCtrl_Calls ilBadgeManagementGUI: ilPropertyFormGUI @@ -484,10 +487,7 @@ protected function saveBadge(): void $this->badge_image_service->processImageUpload($badge); } else { $tmpl = new ilBadgeImageTemplate($form->getInput('tmpl')); - if ($tmpl->getImageRid() !== null) { - $badge->setImageRid($tmpl->getImageRid()); - $badge->update(); - } + $this->cloneBadgeTemplate($badge, new ResourceIdentification($tmpl->getImageRid())); } $this->tpl->setOnScreenMessage('success', $lng->txt('settings_saved'), true); @@ -585,26 +585,24 @@ protected function updateBadge(): void $badge->setDescription($form->getInput('desc')); $badge->setCriteria($form->getInput('crit')); $badge->setValid($form->getInput('valid')); + $image = $form->getInput('img'); if (isset($image['name']) && $image['name'] !== '') { + $this->removeResourceStorageImage($badge); $this->badge_image_service->processImageUpload($badge); } if ($custom) { $badge->setConfiguration($custom->getConfigFromForm($form)); } - $badge->update(); - $tmpl_id = $form->getInput('tmpl'); if ($tmpl_id !== '') { + $this->removeResourceStorageImage($badge); $tmpl = new ilBadgeImageTemplate($tmpl_id); - if ($tmpl->getImageRid() !== '') { - $badge->setImageRid($tmpl->getImageRid()); - $this->badge_image_service->processImageUpload($badge); - $badge->update(); - } + $this->cloneBadgeTemplate($badge, new ResourceIdentification($tmpl->getImageRid())); } + $badge->update(); $this->tpl->setOnScreenMessage('success', $lng->txt('settings_saved'), true); $ilCtrl->redirect($this, 'listBadges'); } @@ -991,4 +989,26 @@ protected function deassignBadge(): void $this->tpl->setOnScreenMessage('success', $lng->txt('settings_saved'), true); $ilCtrl->redirect($this, 'listUsers'); } + + /** + * @throws Exception + */ + protected function cloneBadgeTemplate(ilBadge $badge, ?ResourceIdentification $rid): void + { + if ($rid !== null) { + $new_rid = $this->badge_image_service->cloneBadgeImageByRid($rid); + $badge->setImageRid($new_rid); + $badge->update(); + } + } + + protected function removeResourceStorageImage(ilBadge $badge): void + { + if ($badge->getImageRid() !== '') { + $this->resource_storage->manage()->remove( + new ResourceIdentification($badge->getImageRid()), + new ilBadgeFileStakeholder() + ); + } + } } diff --git a/components/ILIAS/Badge/classes/class.ilBadgePersonalTableGUI.php b/components/ILIAS/Badge/classes/class.ilBadgePersonalTableGUI.php index fc5572365b10..f6658be632b6 100755 --- a/components/ILIAS/Badge/classes/class.ilBadgePersonalTableGUI.php +++ b/components/ILIAS/Badge/classes/class.ilBadgePersonalTableGUI.php @@ -268,11 +268,6 @@ protected function getActions( public function renderTable(): void { $df = new \ILIAS\Data\Factory(); - if ((int) $this->user->getTimeFormat() === ilCalendarSettings::TIME_FORMAT_12) { - $date_format = $df->dateFormat()->withTime12($this->user->getDateFormat()); - } else { - $date_format = $df->dateFormat()->withTime24($this->user->getDateFormat()); - } $table_uri = $df->uri($this->request->getUri()->__toString()); $url_builder = new URLBuilder($table_uri); @@ -287,9 +282,9 @@ public function renderTable(): void $table = $this->factory ->table() ->data( + $this, $this->lng->txt('badge_personal_badges'), - $this->getColumns($date_format), - $this + $this->getColumns($this->user->getDateTimeFormat()), ) ->withId(self::class) ->withOrder(new Order('title', Order::ASC)) diff --git a/components/ILIAS/Badge/classes/class.ilBadgeTableGUI.php b/components/ILIAS/Badge/classes/class.ilBadgeTableGUI.php index 3c9b2877efa0..74c314f9aa18 100755 --- a/components/ILIAS/Badge/classes/class.ilBadgeTableGUI.php +++ b/components/ILIAS/Badge/classes/class.ilBadgeTableGUI.php @@ -50,7 +50,8 @@ class ilBadgeTableGUI implements DataRetrieval private readonly string $parent_type; private readonly ilLanguage $lng; private readonly ilGlobalTemplateInterface $tpl; - private ilBadgeImage $badge_image_service; + private readonly \ILIAS\ResourceStorage\Services $irss; + private readonly ilBadgeImage $badge_image_service; /** * @return null|listrefinery = $DIC->refinery(); $this->request = $DIC->http()->request(); $this->http = $DIC->http(); + $this->irss = $DIC->resourceStorage(); $this->parent_id = $parent_obj_id; $this->parent_type = $parent_obj_type; $this->badge_image_service = new ilBadgeImage( - $DIC->resourceStorage(), + $this->irss, $DIC->upload(), $DIC->ui()->mainTemplate() ); @@ -93,8 +95,6 @@ public function __construct(int $parent_obj_id, string $parent_obj_type, protect * active: bool, * type: string, * manual: bool, - * image: string, - * title: string, * title_sortable: string * }> */ @@ -105,43 +105,7 @@ private function getRecords(): array } $rows = []; - $modal_container = new ModalBuilder(); - foreach (ilBadge::getInstancesByParentId($this->parent_id) as $badge) { - $images = [ - 'rendered' => null, - 'large' => null, - ]; - $image_src = $this->badge_image_service->getImageFromBadge($badge); - if ($image_src !== '') { - $images['rendered'] = $this->renderer->render( - $this->factory->image()->responsive( - $image_src, - $badge->getTitle() - ) - ); - - $image_src_large = $this->badge_image_service->getImageFromBadge( - $badge, - ilBadgeImage::IMAGE_SIZE_XL - ); - if ($image_src_large !== '') { - $images['large'] = $this->factory->image()->responsive( - $image_src_large, - $badge->getTitle() - ); - } - } - - $modal = $modal_container->constructModal( - $images['large'], - $badge->getTitle(), - [ - 'description' => $badge->getDescription(), - 'badge_criteria' => $badge->getCriteria(), - ] - ); - $rows[] = [ 'id' => $badge->getId(), 'badge' => $badge, @@ -150,14 +114,6 @@ private function getRecords(): array ? ilBadge::getExtendedTypeCaption($badge->getTypeInstance()) : $badge->getTypeInstance()->getCaption(), 'manual' => !$badge->getTypeInstance() instanceof ilBadgeAuto, - 'image' => $images['rendered'] ? ($modal_container->renderShyButton( - $images['rendered'], - $modal - ) . ' ') : '', - 'title' => implode('', [ - $modal_container->renderShyButton($badge->getTitle(), $modal), - $modal_container->renderModal($modal) - ]), 'title_sortable' => $badge->getTitle() ]; } @@ -167,6 +123,77 @@ private function getRecords(): array return $rows; } + /** + * @param array{ + * id: int, + * badge: ilBadge, + * active: bool, + * type: string, + * manual: bool, + * title_sortable: string + * } $record + * @return array{ + * id: int, + * badge: ilBadge, + * active: bool, + * type: string, + * manual: bool, + * title_sortable: string, + * title: string, + * image: string + * } + */ + private function enrichRecord(ModalBuilder $modal_builder, array $record): array + { + $badge = $record['badge']; + + $images = [ + 'rendered' => null, + 'large' => null, + ]; + + $image_src = $this->badge_image_service->getImageFromBadge($badge); + if ($image_src !== '') { + $images['rendered'] = $this->renderer->render( + $this->factory->image()->responsive( + $image_src, + $badge->getTitle() + ) + ); + + $image_src_large = $this->badge_image_service->getImageFromBadge( + $badge, + ilBadgeImage::IMAGE_SIZE_XL + ); + if ($image_src_large !== '') { + $images['large'] = $this->factory->image()->responsive( + $image_src_large, + $badge->getTitle() + ); + } + } + + $modal = $modal_builder->constructModal( + $images['large'], + $badge->getTitle(), + [ + 'description' => $badge->getDescription(), + 'badge_criteria' => $badge->getCriteria(), + ] + ); + + $record['image'] = $images['rendered'] + ? $modal_builder->renderShyButton($images['rendered'], $modal) . ' ' + : ''; + $record['title'] = implode('', [ + $modal_builder->renderShyButton($badge->getTitle(), $modal), + $modal_builder->renderModal($modal) + ]); + + return $record; + } + + public function getRows( DataRowBuilder $row_builder, array $visible_column_ids, @@ -211,7 +238,19 @@ public function getRows( $records = \array_slice($records, $range->getStart(), $range->getLength()); } + $identifications = []; foreach ($records as $record) { + if ($record['badge']->getImageRid() !== null && $record['badge']->getImageRid() !== '') { + $identifications[] = $record['badge']->getImageRid(); + } + } + + $this->irss->preload($identifications); + + $modal_container = new ModalBuilder(); + foreach ($records as $record) { + $record = $this->enrichRecord($modal_container, $record); + yield $row_builder ->buildDataRow((string) $record['id'], $record) ->withDisabledAction( @@ -305,7 +344,7 @@ public function renderTable(): void $table = $this->factory ->table() - ->data($this->lng->txt('obj_bdga'), $this->getColumns(), $this) + ->data($this, $this->lng->txt('obj_bdga'), $this->getColumns()) ->withId(self::class . '_' . $this->parent_id) ->withOrder(new Order('title', Order::ASC)) ->withActions($this->getActions($url_builder, $action_parameter_token, $row_id_token)) diff --git a/components/ILIAS/Badge/classes/class.ilBadgeTypesTableGUI.php b/components/ILIAS/Badge/classes/class.ilBadgeTypesTableGUI.php index 7549862eb307..7852f19de631 100755 --- a/components/ILIAS/Badge/classes/class.ilBadgeTypesTableGUI.php +++ b/components/ILIAS/Badge/classes/class.ilBadgeTypesTableGUI.php @@ -228,7 +228,7 @@ public function renderTable(): void $table = $this->factory ->table() - ->data($this->lng->txt('badge_types'), $this->getColumns(), $this) + ->data($this, $this->lng->txt('badge_types'), $this->getColumns()) ->withId(self::class) ->withOrder(new Order('name', Order::ASC)) ->withActions($this->getActions($url_builder, $action_parameter_token, $row_id_token)) diff --git a/components/ILIAS/Badge/classes/class.ilBadgeUserTableGUI.php b/components/ILIAS/Badge/classes/class.ilBadgeUserTableGUI.php index 3b891c4fd9fb..f2382ea9a1c2 100755 --- a/components/ILIAS/Badge/classes/class.ilBadgeUserTableGUI.php +++ b/components/ILIAS/Badge/classes/class.ilBadgeUserTableGUI.php @@ -343,11 +343,7 @@ private function getActions( public function renderTable(): void { $df = new \ILIAS\Data\Factory(); - if ((int) $this->user->getTimeFormat() === ilCalendarSettings::TIME_FORMAT_12) { - $this->date_format = $df->dateFormat()->withTime12($this->user->getDateFormat()); - } else { - $this->date_format = $df->dateFormat()->withTime24($this->user->getDateFormat()); - } + $this->date_format = $this->user->getDateTimeFormat(); $table_uri = $df->uri($this->request->getUri()->__toString()); $url_builder = new URLBuilder($table_uri); @@ -384,7 +380,7 @@ public function renderTable(): void $table = $this->factory ->table() - ->data($title, $this->getColumns(), $this) + ->data($this, $title, $this->getColumns()) ->withId(self::class . '_' . $this->parent_ref_id) ->withOrder(new Order('name', Order::ASC)) ->withActions($this->getActions($url_builder, $action_parameter_token, $row_id_token)) diff --git a/components/ILIAS/Badge/classes/class.ilObjBadgeAdministrationGUI.php b/components/ILIAS/Badge/classes/class.ilObjBadgeAdministrationGUI.php index 70764d190ec3..05127dac2f05 100755 --- a/components/ILIAS/Badge/classes/class.ilObjBadgeAdministrationGUI.php +++ b/components/ILIAS/Badge/classes/class.ilObjBadgeAdministrationGUI.php @@ -33,7 +33,6 @@ class ilObjBadgeAdministrationGUI extends ilObjectGUI private ilRbacSystem $rbacsystem; private ilBadgeGUIRequest $badge_request; private ilTabsGUI $tabs; - private Services $http; public function __construct( $a_data, @@ -52,7 +51,6 @@ public function __construct( $this->tpl = $DIC['tpl']; $this->tabs = $DIC->tabs(); $this->type = 'bdga'; - $this->http = $DIC->http(); parent::__construct($a_data, $a_id, $a_call_by_reference, $a_prepare_output); $this->badge_request = new ilBadgeGUIRequest( diff --git a/components/ILIAS/Badge/classes/class.ilObjectBadgeTableGUI.php b/components/ILIAS/Badge/classes/class.ilObjectBadgeTableGUI.php index d61eefbd274f..722146cc8556 100755 --- a/components/ILIAS/Badge/classes/class.ilObjectBadgeTableGUI.php +++ b/components/ILIAS/Badge/classes/class.ilObjectBadgeTableGUI.php @@ -48,6 +48,8 @@ class ilObjectBadgeTableGUI implements DataRetrieval { + private const RECORD_RAW = '__raw__'; + private readonly Factory $factory; private readonly Renderer $renderer; private readonly \ILIAS\Refinery\Factory $refinery; @@ -57,6 +59,7 @@ class ilObjectBadgeTableGUI implements DataRetrieval private readonly ilGlobalTemplateInterface $tpl; private readonly ilObjBadgeAdministrationGUI $parent_obj; private readonly ilAccessHandler $access; + private readonly \ILIAS\ResourceStorage\Services $irss; private readonly ilBadgeImage $badge_image_service; /** * @var null|list */ private ?array $cached_records = null; + /** @var array */ + private array $has_access_by_parent_cache = []; + /** @var array */ + private array $first_ref_id_for_parent_cache = []; public function __construct( ilObjBadgeAdministrationGUI $parentObj, @@ -87,8 +94,9 @@ public function __construct( $this->http = $DIC->http(); $this->access = $DIC->access(); $this->parent_obj = $parentObj; + $this->irss = $DIC->resourceStorage(); $this->badge_image_service = new ilBadgeImage( - $DIC->resourceStorage(), + $this->irss, $DIC->upload(), $DIC->ui()->mainTemplate() ); @@ -138,8 +146,22 @@ public function getRows( $records = \array_slice($records, $range->getStart(), $range->getLength()); } + $identifications = []; foreach ($records as $record) { - yield $row_builder->buildDataRow((string) $record['id'], $record); + if (isset($record[self::RECORD_RAW]['image_rid']) && $record[self::RECORD_RAW]['image_rid'] !== '') { + $identifications[] = $record[self::RECORD_RAW]['image_rid']; + } + } + + $this->irss->preload($identifications); + + $modal_container = new ModalBuilder(); + $container_deleted_title_part = '' . $this->lng->txt('deleted') . ''; + foreach ($records as $record) { + yield $row_builder->buildDataRow( + (string) $record['id'], + $this->enrichRecord($modal_container, $container_deleted_title_part, $record) + ); } } @@ -151,7 +173,30 @@ public function getTotalRowCount( } /** - * @return listsetId($badge_item['id']); + $badge->setImageRid($badge_item['image_rid']); + $badge->setImage($badge_item['image']); + + $images = [ + 'rendered' => null, + 'large' => null, + ]; + $image_src = $this->badge_image_service->getImageFromResourceId($badge); + if ($image_src !== '') { + $images['rendered'] = $this->renderer->render( + $this->factory->image()->responsive( + $image_src, + $badge_item['title'] + ) + ); + + $image_src_large = $this->badge_image_service->getImageFromResourceId( + $badge, + ilBadgeImage::IMAGE_SIZE_XL + ); + if ($image_src_large !== '') { + $images['large'] = $this->factory->image()->responsive( + $image_src_large, + $badge_item['title'] + ); + } + } + + $container_title_parts = [ + 'icon' => $this->renderer->render( + $this->factory->symbol()->icon()->custom( + ilObject::_getIcon($badge_item['parent_id'], 'big', $badge_item['parent_type'] ?? ''), + $this->lng->txt('obj_' . ($badge_item['parent_type'] ?? '')) + ) + ), + 'title' => $badge_item['parent_title'] ?? '', + ]; + + $sortable_container_title_parts = [ + 'title' => $badge_item['parent_title'] ?? '' + ]; + if ($badge_item['deleted']) { + $container_title_parts['suffix'] = $container_deleted_title_part; + $sortable_container_title_parts['suffix'] = $container_deleted_title_part; + } else { + if (isset($this->has_access_by_parent_cache[$badge_item['parent_id']])) { + $has_access = $this->has_access_by_parent_cache[$badge_item['parent_id']] ?? false; + $ref_id = $this->first_ref_id_for_parent_cache[$badge_item['parent_id']] ?? null; + } else { + $ref_ids = ilObject::_getAllReferences($badge_item['parent_id']); + $ref_id = array_shift($ref_ids); + $this->first_ref_id_for_parent_cache[$badge_item['parent_id']] = $ref_id; + $has_access = $ref_id && $this->access->checkAccess('read', '', $ref_id); + $this->has_access_by_parent_cache[$badge_item['parent_id']] = $has_access; + } + + if ($has_access) { + $container_title_parts['title'] = $this->renderer->render( + new Standard( + $container_title_parts['title'], + (string) new URI( + ilLink::_getLink( + $ref_id, + $badge_item['parent_type'] ?? '' + ) + ) + ) + ); + } else { + $container_title_parts['suffix'] = $container_deleted_title_part; + $sortable_container_title_parts['suffix'] = $container_deleted_title_part; + } + } + + $modal = $modal_builder->constructModal( + $images['large'], + $badge_item['title'], + [ + 'active' => $badge_item['active'] ? $this->lng->txt('yes') : $this->lng->txt('no'), + 'type' => $record['type'], + 'container' => implode(' ', \array_slice($container_title_parts, 1, null, true)), + ] + ); + + return [ + 'id' => $badge_item['id'], + 'active' => (bool) $badge_item['active'], + 'type' => $record['type'], + 'image' => $images['rendered'] ? ($modal_builder->renderShyButton( + $images['rendered'], + $modal + ) . ' ') : '', + 'title' => implode('', [ + $modal_builder->renderShyButton($badge_item['title'], $modal), + $modal_builder->renderModal($modal) + ]), + 'title_sortable' => $badge_item['title'], + 'container' => implode(' ', $container_title_parts), + 'container_sortable' => implode(' ', $sortable_container_title_parts), + ]; + } + + /** + * @return list */ private function getRecords(): array @@ -168,9 +349,6 @@ private function getRecords(): array return $this->cached_records; } - $container_deleted_title_part = '' . $this->lng->txt('deleted') . ''; - $modal_container = new ModalBuilder(); - // A filter is not implemented, yet $filter = [ 'type' => '', @@ -179,103 +357,23 @@ private function getRecords(): array ]; $types = ilBadgeHandler::getInstance()->getAvailableTypes(false); - $rows = []; - foreach (ilBadge::getObjectInstances($filter) as $badge_item) { - $type_caption = ilBadge::getExtendedTypeCaption($types[$badge_item['type_id']]); - - $badge = new ilBadge(0); - $badge->setId($badge_item['id']); - $badge->setImageRid($badge_item['image_rid']); - $badge->setImage($badge_item['image']); - - $images = [ - 'rendered' => null, - 'large' => null, - ]; - $image_src = $this->badge_image_service->getImageFromResourceId($badge); - if ($image_src !== '') { - $images['rendered'] = $this->renderer->render( - $this->factory->image()->responsive( - $image_src, - $badge_item['title'] - ) - ); - - $image_src_large = $this->badge_image_service->getImageFromResourceId( - $badge, - ilBadgeImage::IMAGE_SIZE_XL - ); - if ($image_src_large !== '') { - $images['large'] = $this->factory->image()->responsive( - $image_src_large, - $badge_item['title'] - ); - } - } - - $sortable_container_title_parts = [ - 'title' => $badge_item['parent_title'] ?? '' - ]; - $container_title_parts = [ - 'icon' => $this->renderer->render( - $this->factory->symbol()->icon()->custom( - ilObject::_getIcon($badge_item['parent_id'], 'big', $badge_item['parent_type'] ?? ''), - $this->lng->txt('obj_' . ($badge_item['parent_type'] ?? '')) - ) - ), - 'title' => $sortable_container_title_parts['title'], - ]; - - if ($badge_item['deleted']) { - $container_title_parts['suffix'] = $container_deleted_title_part; - $sortable_container_title_parts['suffix'] = $container_deleted_title_part; - } else { - $ref_ids = ilObject::_getAllReferences($badge_item['parent_id']); - $ref_id = array_shift($ref_ids); - if ($ref_id && $this->access->checkAccess('read', '', $ref_id)) { - $container_title_parts['title'] = $this->renderer->render( - new Standard( - $container_title_parts['title'], - (string) new URI(ilLink::_getLink($ref_id)) - ) - ); - } else { - $container_title_parts['suffix'] = $container_deleted_title_part; - $sortable_container_title_parts['suffix'] = $container_deleted_title_part; - } - } + $raw_records = ilBadge::getObjectInstances($filter); - $modal = $modal_container->constructModal( - $images['large'], - $badge_item['title'], - [ - 'active' => $badge_item['active'] ? $this->lng->txt('yes') : $this->lng->txt('no'), - 'type' => $type_caption, - 'container' => implode(' ', \array_slice($container_title_parts, 1, null, true)), - ] - ); - - $rows[] = [ + $sortable_rows = array_map(function (array $badge_item) use ($types) { + return [ 'id' => $badge_item['id'], 'active' => (bool) $badge_item['active'], - 'type' => $type_caption, - 'image' => $images['rendered'] ? ($modal_container->renderShyButton( - $images['rendered'], - $modal - ) . ' ') : '', - 'title' => implode('', [ - $modal_container->renderShyButton($badge_item['title'], $modal), - $modal_container->renderModal($modal) - ]), + 'type' => ilBadge::getExtendedTypeCaption($types[$badge_item['type_id']]), 'title_sortable' => $badge_item['title'], - 'container' => implode(' ', $container_title_parts), - 'container_sortable' => implode(' ', $sortable_container_title_parts), + 'container_sortable' => ($badge_item['parent_title'] ?? '') . + ($badge_item['deleted'] ? ' ' . $this->lng->txt('deleted') : ''), + self::RECORD_RAW => $badge_item ]; - } + }, $raw_records); - $this->cached_records = $rows; + $this->cached_records = $sortable_rows; - return $rows; + return $this->cached_records; } /** @@ -287,7 +385,7 @@ public function getColumns(): array 'image' => $this->factory->table()->column()->text($this->lng->txt('image'))->withIsSortable(false), 'title' => $this->factory->table()->column()->text($this->lng->txt('title')), 'type' => $this->factory->table()->column()->text($this->lng->txt('type')), - 'container' => $this->factory->table()->column()->text($this->lng->txt('container')), + 'container' => $this->factory->table()->column()->text($this->lng->txt('object')), 'active' => $this->factory->table()->column()->boolean( $this->lng->txt('active'), $this->lng->txt('yes'), @@ -350,7 +448,7 @@ public function renderTable(): void $table = $this->factory ->table() - ->data($this->lng->txt('badge_object_badges'), $this->getColumns(), $this) + ->data($this, $this->lng->txt('badge_object_badges'), $this->getColumns()) ->withId(self::class) ->withOrder(new Order('title', Order::ASC)) ->withActions($this->getActions($url_builder, $action_parameter_token, $row_id_token)) diff --git a/components/ILIAS/Badge/tests/BadgeManagementSessionRepositoryTest.php b/components/ILIAS/Badge/tests/BadgeManagementSessionRepositoryTest.php index ad7810aa745a..725ffb02a05b 100755 --- a/components/ILIAS/Badge/tests/BadgeManagementSessionRepositoryTest.php +++ b/components/ILIAS/Badge/tests/BadgeManagementSessionRepositoryTest.php @@ -18,9 +18,6 @@ use PHPUnit\Framework\TestCase; -/** - * @author Alexander Killing - */ class BadgeManagementSessionRepositoryTest extends TestCase { protected ilBadgeManagementSessionRepository $repo; diff --git a/components/ILIAS/Badge/tests/ModalTest.php b/components/ILIAS/Badge/tests/ModalTest.php index 67b24b9fb271..8514f5a0672c 100755 --- a/components/ILIAS/Badge/tests/ModalTest.php +++ b/components/ILIAS/Badge/tests/ModalTest.php @@ -34,7 +34,6 @@ use ILIAS\UI\Component\Item\Standard as Item; use ILIAS\UI\Component\Listing\Factory as Listing; use ILIAS\UI\Factory as UI; -use ILIAS\UI\Renderer; use PHPUnit\Framework\TestCase; use ilBadge; use ilLanguage; diff --git a/components/ILIAS/Badge/tests/PresentationHeaderTest.php b/components/ILIAS/Badge/tests/PresentationHeaderTest.php index 0ef4e69b8762..5d1feb5b02e0 100755 --- a/components/ILIAS/Badge/tests/PresentationHeaderTest.php +++ b/components/ILIAS/Badge/tests/PresentationHeaderTest.php @@ -31,6 +31,7 @@ use ilCtrl; use ILIAS\UI\Component\Component; use ilLanguage; +use PHPUnit\Framework\Attributes\DataProvider; class PresentationHeaderTest extends TestCase { @@ -41,16 +42,14 @@ public function testConstruct(): void $this->assertInstanceOf(PresentationHeader::class, $head); } - /** - * @dataProvider showProvider - */ + #[DataProvider('showProvider')] public function testShow(bool $additional = false): void { $mode = $this->getMockBuilder(Mode::class)->disableOriginalConstructor()->getMock(); - $mode->expects(self::once())->method('withActive')->with('tile_view')->willReturn($mode); + $mode->expects($this->once())->method('withActive')->with('tile_view')->willReturn($mode); $view_control = $this->getMockBuilder(ViewControl::class)->disableOriginalConstructor()->getMock(); - $view_control->expects(self::once())->method('mode')->with([ + $view_control->expects($this->once())->method('mode')->with([ 'tile_view' => 'list URL', 'table_view' => 'manage URL', ])->willReturn($mode); @@ -65,7 +64,7 @@ public function testShow(bool $additional = false): void $toolbar = $this->getMockBuilder(ilToolbarGUI::class)->disableOriginalConstructor()->getMock(); $toolbar - ->expects(self::exactly($additional + 1)) + ->expects($this->exactly($additional + 1)) ->method('addStickyItem') ->willReturnCallback( function ($component) use (&$consecutive_expected) { @@ -76,7 +75,7 @@ function ($component) use (&$consecutive_expected) { $factory = $this->getMockBuilder(UI::class)->disableOriginalConstructor()->getMock(); - $factory->expects(self::once())->method('viewControl')->willReturn($view_control); + $factory->expects($this->once())->method('viewControl')->willReturn($view_control); $ui = $this->getMockBuilder(UIServices::class)->disableOriginalConstructor()->getMock(); $ui->method('factory')->willReturn($factory); @@ -87,7 +86,7 @@ function ($component) use (&$consecutive_expected) { ]; $ctrl = $this->getMockBuilder(ilCtrl::class)->disableOriginalConstructor()->getMock(); $ctrl - ->expects(self::exactly(2)) + ->expects($this->exactly(2)) ->method('getLinkTargetByClass') ->willReturnCallback( function ($class, $cmd) use (&$consecutive) { @@ -102,7 +101,7 @@ function ($class, $cmd) use (&$consecutive) { $language->method('txt')->willReturnCallback(static fn(string $name): string => $name); $container = $this->getMockBuilder(Container::class)->disableOriginalConstructor()->getMock(); - $container->expects(self::once())->method('toolbar')->willReturn($toolbar); + $container->expects($this->once())->method('toolbar')->willReturn($toolbar); $container->method('ui')->willReturn($ui); $container->method('ctrl')->willReturn($ctrl); $container->method('language')->willReturn($language); diff --git a/components/ILIAS/Badge/tests/SortingTest.php b/components/ILIAS/Badge/tests/SortingTest.php index c60035a1fb2d..2d110550e053 100755 --- a/components/ILIAS/Badge/tests/SortingTest.php +++ b/components/ILIAS/Badge/tests/SortingTest.php @@ -24,6 +24,8 @@ use ilBadge; use ilBadgeAssignment; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Depends; class SortingTest extends TestCase { @@ -33,10 +35,8 @@ public function testConstruct(): void $this->assertInstanceOf(Sorting::class, $sort); } - /** - * @depends testConstruct - * @dataProvider sortProvider - */ + #[DataProvider('sortProvider')] + #[Depends('testConstruct')] public function testSorting(array $input, string $key, string $label, string $what, string $method, array $equal, array $less, array $greater): void { $sort = new Sorting(...$input); @@ -47,9 +47,7 @@ public function testSorting(array $input, string $key, string $label, string $wh $this->assertEquals(1, $this->sign($sort->compare($this->pair($what, $method, $greater[0]), $this->pair($what, $method, $greater[1])))); } - /** - * @depends testConstruct - */ + #[Depends('testConstruct')] public function testOptions(): void { $this->assertEquals([ @@ -65,8 +63,8 @@ public static function sortProvider(): array return [ 'Default sort is title_asc' => [[], 'title_asc', 'sort_by_title_asc', 'badge', 'getTitle', ['A', 'a'], ['f', 'G'], ['d', 'c']], 'Descending title' => [['title_desc'], 'title_desc', 'sort_by_title_desc', 'badge', 'getTitle', ['A', 'a'], ['d', 'c'], ['f', 'G']], - 'Ascending date' => [['date_asc'], 'date_asc', 'sort_by_date_asc', 'assignment', 'getTimestamp', ['7', '7'], [8, 30], [20, 6]], - 'Ascending date' => [['date_desc'], 'date_desc', 'sort_by_date_desc', 'assignment', 'getTimestamp', [7, 7], [20, 6], [8, 30]], + 'Ascending date' => [['date_asc'], 'date_asc', 'sort_by_date_asc', 'assignment', 'getTimestamp', [7, 7], [8, 30], [20, 6]], + 'Descending date' => [['date_desc'], 'date_desc', 'sort_by_date_desc', 'assignment', 'getTimestamp', [7, 7], [20, 6], [8, 30]], 'Random input results in title_asc' => [['Lorem ipsum'], 'title_asc', 'sort_by_title_asc', 'badge', 'getTitle', ['A', 'a'], ['f', 'G'], ['d', 'c']] ]; } diff --git a/components/ILIAS/Badge/tests/TileTest.php b/components/ILIAS/Badge/tests/TileTest.php index 6b15e53cab0b..36f1e8e4a6c6 100755 --- a/components/ILIAS/Badge/tests/TileTest.php +++ b/components/ILIAS/Badge/tests/TileTest.php @@ -20,34 +20,21 @@ namespace ILIAS\Badge\test; -use Exception; use ILIAS\DI\Container; use ILIAS\DI\UIServices; use ILIAS\Badge\BadgeParent; use ILIAS\Badge\Modal; use ILIAS\Badge\ModalContent; use ILIAS\Badge\Tile; -use ILIAS\UI\Component\Button\Factory as Button; -use ILIAS\UI\Component\Button\Standard as StandardButton; use ILIAS\UI\Component\Button\Button as ButtonComponent; -use ILIAS\UI\Component\Card\Factory as Card; -use ILIAS\UI\Component\Card\Standard as StandardCard; -use ILIAS\UI\Component\Component; -use ILIAS\UI\Component\Image\Factory as Image; use ILIAS\UI\Component\Image\Image as ImageComponent; -use ILIAS\UI\Component\Modal\Factory as UIModal; use ILIAS\UI\Component\Modal\Modal as ModalComponent; -use ILIAS\UI\Component\Modal\Lightbox; -use ILIAS\UI\Component\Modal\LightboxCardPage; -use ILIAS\UI\Factory as UI; use PHPUnit\Framework\TestCase; use ilBadge; -use ilBadgeAssignment; use ilCtrl; use ilLanguage; -use ILIAS\ResourceStorage\Identification\ResourceIdentification; use ILIAS\ResourceStorage\Services; -use ILIAS\Badge\ilBadgeImage; +use PHPUnit\Framework\Attributes\DataProvider; class TileTest extends TestCase { @@ -62,9 +49,7 @@ public function testConstruct(): void $this->assertInstanceOf(Tile::class, new Tile($container, $parent, $modal, $sign_file, $format_date)); } - /** - * @dataProvider provideAsVariants - */ + #[DataProvider('provideAsVariants')] public function testAs(string $method, array $expected_components): void { $signed_file = '/some-signed-file'; @@ -88,7 +73,7 @@ public function testAs(string $method, array $expected_components): void $container->method('language')->willReturn($language); $container->method('resourceStorage')->willReturn($resource_storage); $format_date = function (int $x): void { - throw new Exception('Should not be called.'); + throw new \RuntimeException('Should not be called.'); }; $sign_file = function (string $path) use ($signed_file, $badge_image_path): string { return $signed_file; @@ -103,7 +88,7 @@ public function testAs(string $method, array $expected_components): void $components = $tile->$method($modal_content); - $this->assertSame(count($expected_components), count($components)); + $this->assertCount(\count($expected_components), $components); array_map($this->assertInstanceOf(...), $expected_components, $components); } diff --git a/components/ILIAS/Bibliographic/classes/Admin/Library/class.ilBiblLibraryTableGUI.php b/components/ILIAS/Bibliographic/classes/Admin/Library/class.ilBiblLibraryTableGUI.php index f0a497640b38..4ddaf55fb638 100755 --- a/components/ILIAS/Bibliographic/classes/Admin/Library/class.ilBiblLibraryTableGUI.php +++ b/components/ILIAS/Bibliographic/classes/Admin/Library/class.ilBiblLibraryTableGUI.php @@ -65,9 +65,9 @@ public function getRenderedTable(): string private function buildTable(): DataTable { return $this->ui_factory->table()->data( + $this, $this->lng->txt('bibl_settings_libraries'), $this->getColumns(), - $this )->withActions( $this->getActions() )->withRange( diff --git a/components/ILIAS/Bibliographic/classes/Attribute/class.ilBiblAttributeFactory.php b/components/ILIAS/Bibliographic/classes/Attribute/class.ilBiblAttributeFactory.php index 3c99168793f8..db9be31cc018 100755 --- a/components/ILIAS/Bibliographic/classes/Attribute/class.ilBiblAttributeFactory.php +++ b/components/ILIAS/Bibliographic/classes/Attribute/class.ilBiblAttributeFactory.php @@ -1,4 +1,5 @@ $entry->getId()])->get(); } - /** - * @inheritDoc - */ + public function sortAttributesArray(array $attributes): array + { + $type_id = $this->field_factory->getType()->getId(); + uksort($attributes, function ($key_a, $key_b) use ($type_id): int { + $field_a = $this->field_factory->findOrCreateFieldByTypeAndIdentifier($type_id, $key_a); + $field_b = $this->field_factory->findOrCreateFieldByTypeAndIdentifier($type_id, $key_b); + + return $field_a->getPosition() - $field_b->getPosition(); + }); + + return $attributes; + } public function sortAttributes(array $attributes): array { $sorted = []; diff --git a/components/ILIAS/Bibliographic/classes/Entry/class.ilBiblEntryTableGUI.php b/components/ILIAS/Bibliographic/classes/Entry/class.ilBiblEntryTableGUI.php index 16c855f16293..a30d7b07a3b7 100755 --- a/components/ILIAS/Bibliographic/classes/Entry/class.ilBiblEntryTableGUI.php +++ b/components/ILIAS/Bibliographic/classes/Entry/class.ilBiblEntryTableGUI.php @@ -36,7 +36,6 @@ class ilBiblEntryTableGUI public const SORTATION_BY_YEAR_ASC = 5; public const SORTATION_BY_YEAR_DESC = 6; - private readonly HttpServices $http; private readonly ilLanguage $lng; private readonly UIFactory $ui_factory; @@ -52,8 +51,11 @@ class ilBiblEntryTableGUI /** @var ilBiblFieldFilterInterface[] */ protected array $filter_objects = []; - public function __construct(protected ilObjBibliographicGUI $a_parent_obj, protected ilBiblFactoryFacade $facade, protected UIServices $ui) - { + public function __construct( + protected ilObjBibliographicGUI $a_parent_obj, + protected ilBiblFactoryFacade $facade, + protected UIServices $ui + ) { global $DIC; $this->ctrl = $DIC->ctrl(); $this->http = $DIC->http(); @@ -85,7 +87,10 @@ protected function buildFilter(): ?StandardFilter return null; } - $available_field_ids_for_object = array_map(static fn(ilBiblField $field): ?int => $field->getId(), $this->facade->fieldFactory()->getAvailableFieldsForObjId($this->facade->iliasObjId())); + $available_field_ids_for_object = array_map( + static fn(ilBiblField $field): ?int => $field->getId(), + $this->facade->fieldFactory()->getAvailableFieldsForObjId($this->facade->iliasObjId()) + ); $filter_inputs = []; $filter_active_states = []; @@ -118,7 +123,10 @@ protected function buildFilter(): ?StandardFilter private function renderLibraryButtons(int $entry_id): array { - $entry = $this->facade->entryFactory()->findByIdAndTypeString($entry_id, $this->facade->type()->getStringRepresentation()); + $entry = $this->facade->entryFactory()->findByIdAndTypeString( + $entry_id, + $this->facade->type()->getStringRepresentation() + ); $settings = $this->facade->libraryFactory()->getAll(); if ($settings === []) { @@ -146,16 +154,22 @@ protected function buildTable(): PresentationTable foreach (array_keys($this->getSortationsMapping()) as $sort_id) { $sortations[$sort_id] = $this->lng->txt('sorting_' . $sort_id); } - $view_controls[] = $this->ui_factory->viewControl()->sortation($sortations) - ->withTargetURL( - $this->ctrl->getLinkTargetByClass(ilObjBibliographicGUI::class, ilObjBibliographicGUI::CMD_SHOW_CONTENT), - self::P_SORTATION - )->withLabel($this->lng->txt('sorting_' . $this->determineSortation())); + if ($sortations !== []) { + $view_controls[] = $this->ui_factory->viewControl()->sortation($sortations, array_key_first($sortations)) + ->withTargetURL( + $this->ctrl->getLinkTargetByClass( + ilObjBibliographicGUI::class, + ilObjBibliographicGUI::CMD_SHOW_CONTENT + ), + self::P_SORTATION + ); + } + $view_controls[] = $this->ui_factory->viewControl()->pagination() - ->withTargetURL($this->http->request()->getRequestTarget(), self::P_PAGE) - ->withTotalEntries(count($records)) - ->withPageSize($this->entries_per_page) - ->withCurrentPage($this->current_page); + ->withTargetURL($this->http->request()->getRequestTarget(), self::P_PAGE) + ->withTotalEntries(count($records)) + ->withPageSize($this->entries_per_page) + ->withCurrentPage($this->current_page); return $this->ui_factory->table()->presentation( "", @@ -176,10 +190,22 @@ function ( $entry_id = $record['entry_id']; unset($record['author'], $record['title'], $record['AU'], $record['TI'], $record['entry_id']); + $translated_record = $this->getRecordWithTranslatedKeys($record); + $this->ctrl->setParameterByClass(ilObjBibliographicGUI::class, 'entry_id', $entry_id); + return $row ->withHeadline($title) + ->withAction( + $this->ui_factory->button()->shy( + $this->lng->txt('detail_view'), + $this->ctrl->getLinkTargetByClass( + ilObjBibliographicGUI::class, + 'showDetails' + ) + ) + ) ->withSubheadline($author) ->withImportantFields([$year]) ->withFurtherFields($this->renderLibraryButtons($entry_id)) @@ -188,7 +214,6 @@ function ( )->withData($records_current_page); } - protected function getData(): array { $query = new ilBiblTableQueryInfo(); @@ -228,7 +253,10 @@ protected function getData(): array foreach ($entries as $entry) { /** @var $bibl_entry ilBiblEntry */ - $bibl_entry = $this->facade->entryFactory()->findByIdAndTypeString($entry['entry_id'], $entry['entry_type']); + $bibl_entry = $this->facade->entryFactory()->findByIdAndTypeString( + $entry['entry_id'], + $entry['entry_type'] + ); $entry_attributes = $this->facade->attributeFactory()->getAttributesForEntry($bibl_entry); $sorted_attributes = $this->facade->attributeFactory()->sortAttributes($entry_attributes); $entry_data = []; @@ -236,15 +264,9 @@ protected function getData(): array foreach ($sorted_attributes as $sorted_attribute) { $entry_data[$sorted_attribute->getName()] = $sorted_attribute->getValue(); } - if (!array_key_exists('author', $entry_data)) { - $entry_data['author'] = ''; - } - if (!array_key_exists('title', $entry_data)) { - $entry_data['title'] = ''; - } - if (!array_key_exists('year', $entry_data)) { - $entry_data['year'] = ''; - } + $entry_data['author'] ??= $entry_data['AU'] ?? $entry_data['A1'] ?? $entry_data['A2'] ?? ''; + $entry_data['title'] ??= $entry_data['T1'] ?? $entry_data['T2'] ?? ''; + $entry_data['year'] ??= $entry_data['PY'] ?? $entry_data['Y1'] ?? ''; $bibl_data[] = $entry_data; } @@ -263,7 +285,6 @@ protected function getSortedRecords(array $records): array return $records; } - protected function getRecordsOfCurrentPage(array $records): array { $offset = array_search($this->current_page * $this->entries_per_page, array_keys($records), true); @@ -271,9 +292,10 @@ protected function getRecordsOfCurrentPage(array $records): array return array_slice($records, $offset, $length); } - protected function getRecordWithTranslatedKeys(array $record): array { + $record = $this->facade->attributeFactory()->sortAttributesArray($record); + $translated_record = []; foreach ($record as $key => $value) { /** @var ilBiblField $field */ @@ -287,7 +309,6 @@ protected function getRecordWithTranslatedKeys(array $record): array return $translated_record; } - private function determinePage(): int { return $this->http->wrapper()->query()->has(self::P_PAGE) diff --git a/components/ILIAS/Bibliographic/classes/Field/DataRetrieval.php b/components/ILIAS/Bibliographic/classes/Field/DataRetrieval.php index c235c65de029..ccb5afe7a8e9 100755 --- a/components/ILIAS/Bibliographic/classes/Field/DataRetrieval.php +++ b/components/ILIAS/Bibliographic/classes/Field/DataRetrieval.php @@ -19,7 +19,6 @@ namespace ILIAS\Bibliographic\Field; use ILIAS\Data\Order; -use ILIAS\Data\Range; use ILIAS\UI\Component\Table as I; use ILIAS\UI\Component\Table\OrderingRowBuilder; @@ -27,12 +26,13 @@ * Class DataRetrieval * */ -class DataRetrieval implements I\OrderingBinding +class DataRetrieval implements I\OrderingRetrieval { private \ilLanguage $lng; public function __construct( - protected \ilBiblAdminFactoryFacadeInterface $facade + protected \ilBiblAdminFactoryFacadeInterface $facade, + private bool $has_write_access ) { global $DIC; $this->lng = $DIC['lng']; @@ -43,30 +43,15 @@ public function getRows( array $visible_column_ids ): \Generator { $records = $this->getRecords(new Order('position', 'ASC')); - foreach ($records as $idx => $record) { + foreach ($records as $record) { $row_id = (string) $record['id']; $field = $this->facade->fieldFactory()->findById($record['id']); $record['data_type'] = $this->facade->translationFactory()->translate($field); - $record['is_standard_field'] = $field->isStandardField() ? $this->lng->txt('standard') : $this->lng->txt('custom'); - yield $row_builder->buildOrderingRow($row_id, $record); - } - } - - public function getRows22( - I\DataRowBuilder $row_builder, - array $visible_column_ids, - Range $range, - Order $order, - ?array $filter_data, - ?array $additional_parameters - ): \Generator { - $records = $this->getRecords($order); - foreach ($records as $idx => $record) { - $row_id = (string) $record['id']; - $field = $this->facade->fieldFactory()->findById($record['id']); - $record['data_type'] = $this->facade->translationFactory()->translate($field); - $record['is_standard_field'] = $field->isStandardField() ? $this->lng->txt('standard') : $this->lng->txt('custom'); - yield $row_builder->buildDataRow($row_id, $record); + $record['is_standard_field'] = $field->isStandardField() ? $this->lng->txt('standard') : $this->lng->txt( + 'custom' + ); + yield $row_builder->buildOrderingRow($row_id, $record) + ->withDisabledAction('translate', !$this->has_write_access); } } diff --git a/components/ILIAS/Bibliographic/classes/Field/Table.php b/components/ILIAS/Bibliographic/classes/Field/Table.php index dcf032074136..f31218ae0a6a 100755 --- a/components/ILIAS/Bibliographic/classes/Field/Table.php +++ b/components/ILIAS/Bibliographic/classes/Field/Table.php @@ -49,7 +49,7 @@ class Table */ public function __construct( private \ilBiblAdminFieldGUI $calling_gui, - private \ilBiblAdminFactoryFacadeInterface $facade + \ilBiblAdminFactoryFacadeInterface $facade ) { global $DIC; $this->ui_factory = $DIC['ui.factory']; @@ -61,13 +61,14 @@ public function __construct( $columns = $this->initColumns(); $actions = $this->initActions(); $data_retrieval = new DataRetrieval( - $facade + $facade, + $calling_gui->checkPermissionBoolAndReturn('write') ); $this->components[] = $this->table = $this->ui_factory->table()->ordering( + $data_retrieval, $this->lng->txt('filter'), $columns, - $data_retrieval, new URI( ILIAS_HTTP_PATH . "/" . $this->ctrl->getLinkTarget($this->calling_gui, \ilBiblAdminFieldGUI::CMD_SAVE_ORDERING) ) diff --git a/components/ILIAS/Bibliographic/classes/Field/class.ilBiblAdminFieldGUI.php b/components/ILIAS/Bibliographic/classes/Field/class.ilBiblAdminFieldGUI.php index 3d0cc4788942..733b79234d4d 100755 --- a/components/ILIAS/Bibliographic/classes/Field/class.ilBiblAdminFieldGUI.php +++ b/components/ILIAS/Bibliographic/classes/Field/class.ilBiblAdminFieldGUI.php @@ -42,7 +42,7 @@ abstract class ilBiblAdminFieldGUI private ilCtrl $ctrl; private ilTabsGUI $tabs; private ilLanguage $lng; - private ilAccessHandler $access; + protected ilAccessHandler $access; protected Table $table; private \ilGlobalTemplateInterface $main_tpl; @@ -53,15 +53,15 @@ public function __construct(protected \ilBiblAdminFactoryFacadeInterface $facade { global $DIC; $this->main_tpl = $DIC->ui()->mainTemplate(); - $this->table = new Table( - $this, - $this->facade - ); $this->http = $DIC['http']; $this->ctrl = $DIC['ilCtrl']; $this->tabs = $DIC['ilTabs']; $this->lng = $DIC['lng']; - $this->access = $DIC['ilAccess']; + $this->access = $DIC->access(); + $this->table = new Table( + $this, + $this->facade + ); } public function executeCommand(): void @@ -191,7 +191,6 @@ protected function setSubTabs(): void protected function save(): void { $this->saveOrdering(); - ; } protected function applyFilter(): void @@ -218,6 +217,6 @@ public function checkPermissionAndFail(string $permission): void public function checkPermissionBoolAndReturn(string $permission): bool { - return (bool) $this->access->checkAccess($permission, '', $this->http->request()->getQueryParams()['ref_id']); + return $this->access->checkAccess($permission, '', $this->http->request()->getQueryParams()['ref_id']); } } diff --git a/components/ILIAS/Bibliographic/classes/FieldFilter/Table.php b/components/ILIAS/Bibliographic/classes/FieldFilter/Table.php index 63ec7618d90b..80a0e845b463 100755 --- a/components/ILIAS/Bibliographic/classes/FieldFilter/Table.php +++ b/components/ILIAS/Bibliographic/classes/FieldFilter/Table.php @@ -56,9 +56,9 @@ public function __construct( ); $this->components[] = $this->ui_factory->table()->data( + $data_retrieval, $this->lng->txt('filter'), $columns, - $data_retrieval )->withActions($actions)->withRequest( $DIC->http()->request() ); diff --git a/components/ILIAS/Bibliographic/classes/Translation/class.ilBiblTranslationFactory.php b/components/ILIAS/Bibliographic/classes/Translation/class.ilBiblTranslationFactory.php index 16d0b6330601..aff7f5adc3ca 100755 --- a/components/ILIAS/Bibliographic/classes/Translation/class.ilBiblTranslationFactory.php +++ b/components/ILIAS/Bibliographic/classes/Translation/class.ilBiblTranslationFactory.php @@ -144,9 +144,9 @@ public function findArCreateInstanceForFieldAndlanguage( ): \ilBiblTranslationInterface { $inst = $this->getCollectionOfTranslationsForField($field) ->where(["language_key" => $language_key,]) - ->get(); + ->first(); - if ($inst === []) { + if ($inst === null) { $inst = new ilBiblTranslation(); $inst->setFieldId($field->getId()); $inst->setLanguageKey($language_key); diff --git a/components/ILIAS/Bibliographic/classes/class.ilBibliographicDataSet.php b/components/ILIAS/Bibliographic/classes/class.ilBibliographicDataSet.php index 290ea92b1bec..0136a9d62c87 100755 --- a/components/ILIAS/Bibliographic/classes/class.ilBibliographicDataSet.php +++ b/components/ILIAS/Bibliographic/classes/class.ilBibliographicDataSet.php @@ -1,4 +1,5 @@ create(); } $new_obj->getObjectProperties()->storePropertyIsOnline( - new ilObjectPropertyIsOnline(false) + new Online(false) ); $this->import_bib_object = $new_obj; $a_mapping->addMapping('components/ILIAS/Bibliographic', 'bibl', $a_rec['id'], $new_obj->getId()); diff --git a/components/ILIAS/Bibliographic/classes/class.ilObjBibliographic.php b/components/ILIAS/Bibliographic/classes/class.ilObjBibliographic.php index 4318f2abe96b..8c5d82998986 100755 --- a/components/ILIAS/Bibliographic/classes/class.ilObjBibliographic.php +++ b/components/ILIAS/Bibliographic/classes/class.ilObjBibliographic.php @@ -22,6 +22,7 @@ use ILIAS\ResourceStorage\Services; use ILIAS\FileUpload\FileUpload; use ILIAS\ResourceStorage\Identification\ResourceIdentification; +use ILIAS\ILIASObject\Properties\CoreProperties\Online; /** * Class ilObjBibliographic @@ -137,7 +138,7 @@ protected function doCreate(bool $clone_mode = false): void ] ); $this->parseFileToDatabase(); - $this->getObjectProperties()->storePropertyIsOnline(new ilObjectPropertyIsOnline(false)); + $this->getObjectProperties()->storePropertyIsOnline(new Online(false)); } protected function doRead(): void @@ -315,7 +316,7 @@ protected function doCloneObject(ilObject2 $new_obj, int $a_target_id, ?int $a_c $cp_options = ilCopyWizardOptions::_getInstance($a_copy_id); if (!$cp_options->isRootNode($this->getRefId())) { - $new_obj->getObjectProperties()->storePropertyIsOnline(new ilObjectPropertyIsOnline(false)); + $new_obj->getObjectProperties()->storePropertyIsOnline(new Online(false)); } $new_obj->cloneStructure($this->getId()); diff --git a/components/ILIAS/Bibliographic/classes/class.ilObjBibliographicGUI.php b/components/ILIAS/Bibliographic/classes/class.ilObjBibliographicGUI.php index 59675fa80d1c..abc8fe4a05a7 100755 --- a/components/ILIAS/Bibliographic/classes/class.ilObjBibliographicGUI.php +++ b/components/ILIAS/Bibliographic/classes/class.ilObjBibliographicGUI.php @@ -78,7 +78,6 @@ class ilObjBibliographicGUI extends ilObject2GUI implements ilDesktopItemHandlin protected ilHelpGUI $help; protected Services $storage; protected \ilObjBibliographicStakeholder $stakeholder; - protected \ILIAS\HTTP\Services $http; protected Factory $ui_factory; protected \ILIAS\Refinery\Factory $refinery; protected ?string $cmd = self::CMD_SHOW_CONTENT; @@ -90,7 +89,6 @@ public function __construct(int $a_id = 0, int $a_id_type = self::REPOSITORY_NOD $this->help = $DIC['ilHelp']; $this->storage = $DIC['resource_storage']; $this->stakeholder = new ilObjBibliographicStakeholder(); - $this->http = $DIC->http(); $this->ui_factory = $DIC->ui()->factory(); $this->refinery = $DIC->refinery(); @@ -127,7 +125,7 @@ public function getType(): string /** * executeCommand */ - #[\Override] + #[Override] public function executeCommand(): void { global $DIC; @@ -177,7 +175,7 @@ public function executeCommand(): void $this->prepareOutput(); $this->dic()->tabs()->setTabActive(self::TAB_EXPORT); $exp_gui = new ilExportGUI($this); - $exp_gui->addFormat(); + $exp_gui->addFormat("xml"); $this->ctrl->forwardCommand($exp_gui); break; case strtolower(ilBiblFieldFilterGUI::class): @@ -220,10 +218,7 @@ public function executeCommand(): void */ public function infoScreen(): void { - // @todo: removed deprecated ilCtrl methods, this needs inspection by a maintainer. - // $this->ctrl->setCmd("showSummary"); - // $this->ctrl->setCmdClass(ilInfoScreenGUI::class); - $this->infoScreenForward(); + $this->ctrl->redirectByClass(ilInfoScreenGUI::class, "showSummary"); } /** @@ -301,7 +296,7 @@ public static function _goto(string $a_target): void /** * @return mixed[] */ - #[\Override] + #[Override] protected function initCreateForm(string $new_type): ilPropertyFormGUI { $form = new ilPropertyFormGUI(); @@ -334,7 +329,7 @@ protected function initCreateForm(string $new_type): ilPropertyFormGUI return $form; } - #[\Override] + #[Override] public function save(): void { $form = $this->initCreateForm($this->getType()); @@ -346,7 +341,7 @@ public function save(): void } } - #[\Override] + #[Override] public function saveObject(): void { // create permission is already checked in createObject. This check here is done to prevent hacking attempts @@ -381,7 +376,7 @@ public function saveObject(): void $this->tpl->setContent($form->getHTML()); } - #[\Override] + #[Override] public function updateObject(): void { $form = $this->getSettingsForm(); @@ -411,7 +406,7 @@ public function updateObject(): void } } - #[\Override] + #[Override] protected function afterSave(ilObject $a_new_object): void { $this->addNews($a_new_object->getId(), 'created'); @@ -425,7 +420,7 @@ protected function settings(): void private function replaceBibliograficFileInit(): void { - if (!$DIC->access()->checkAccess('write', "", $this->object->getRefId())) { + if (!$this->access->checkAccess('write', "", $this->object->getRefId())) { $this->ctrl->redirect($this, self::CMD_SHOW_CONTENT); return; } @@ -490,6 +485,10 @@ protected function getReplaceBibliographicFileForm(): Standard $rid = $bibl_obj->getResourceId() ? $bibl_obj->getResourceId()->serialize() : ""; $bibl_upload_handler = new ilObjBibliographicUploadHandlerGUI($rid); + $max_filesize_bytes = $this->upload_limit->getPhpUploadLimitInBytes(); + $max_filesize_mb = round($max_filesize_bytes / 1024 / 1024, 1); + $info_file_limitations = $this->lng->txt('file_notice') . " " . number_format($max_filesize_mb, 1) . " MB
" + . $this->lng->txt('file_allowed_suffixes') . " .bib, .bibtex, .ris"; $section_replace_bibliographic_file = $this->ui_factory ->input() ->field() @@ -500,8 +499,10 @@ protected function getReplaceBibliographicFileForm(): Standard ->field() ->file( $bibl_upload_handler, - $this->lng->txt('bibliography_file') + $this->lng->txt('bibliography_file'), + $info_file_limitations ) + ->withMaxFileSize($max_filesize_bytes) ->withRequired(true) ->withAdditionalTransformation( $this->getValidBiblFileSuffixConstraint() @@ -543,7 +544,7 @@ function ($bibl_file_input): bool { * create tabs (repository/workspace switch) * this had to be moved here because of the context-specific permission tab */ - #[\Override] + #[Override] public function setTabs(): void { global $DIC; @@ -623,7 +624,7 @@ protected function initSubTabs(): void * edit object * @access public */ - #[\Override] + #[Override] public function editObject(): void { if (!$this->checkPermissionBool("write")) { @@ -694,34 +695,32 @@ public function render(): void */ public function showContent(): void { - global $DIC; - // if user has read permission and object is online OR user has write permissions - $read_access = $DIC->access()->checkAccess('read', "", $this->object->getRefId()); + $read_access = $this->access->checkAccess('read', "", $this->object->getRefId()); $online = $this->object->getObjectProperties()->getPropertyIsOnline()->getIsOnline(); - $write_access = $DIC->access()->checkAccess('write', "", $this->object->getRefId()); + $write_access = $this->access->checkAccess('write', "", $this->object->getRefId()); if (($read_access && $online) || $write_access) { - $DIC->tabs()->activateTab(self::TAB_CONTENT); + $this->tabs_gui->activateTab(self::TAB_CONTENT); $btn_download_original_file = $this->ui()->factory()->button()->primary( $this->lng->txt('download_original_file'), - $this->ctrl()->getLinkTargetByClass(self::class, self::CMD_SEND_FILE) + $this->ctrl->getLinkTargetByClass(self::class, self::CMD_SEND_FILE) ); $this->toolbar->addComponent($btn_download_original_file); if ($write_access) { $btn_overwrite_bibliographic_file = $this->ui()->factory()->button()->standard( $this->lng->txt('replace_bibliography_file'), - $this->ctrl()->getLinkTargetByClass(self::class, self::CMD_OVERWRITE_BIBLIOGRAPHIC_FILE) + $this->ctrl->getLinkTargetByClass(self::class, self::CMD_OVERWRITE_BIBLIOGRAPHIC_FILE) ); $this->toolbar->addComponent($btn_overwrite_bibliographic_file); } $table_gui = new ilBiblEntryTableGUI($this, $this->facade, $this->ui()); - $DIC->ui()->mainTemplate()->setContent($table_gui->getRenderedTableAndExistingFilters()); + $this->tpl->setContent($table_gui->getRenderedTableAndExistingFilters()); //Permanent Link - $DIC->ui()->mainTemplate()->setPermanentLink("bibl", $this->object->getRefId()); + $this->tpl->setPermanentLink("bibl", $this->object->getRefId()); } else { $object_title = ilObject::_lookupTitle(ilObject::_lookupObjId($this->ref_id)); $this->tpl->setOnScreenMessage( @@ -809,7 +808,7 @@ public function showDetails(): void } } - #[\Override] + #[Override] public function view(): void { $this->showContent(); @@ -878,7 +877,7 @@ public function removeFromDesk(): void $this->removeFromDeskObject(); } - #[\Override] + #[Override] protected function afterImport(ilObject $a_new_object): void { /** diff --git a/components/ILIAS/Bibliographic/classes/trait.ilBibliographicSecureString.php b/components/ILIAS/Bibliographic/classes/trait.ilBibliographicSecureString.php index 60c9d3ae6428..0025fce93906 100755 --- a/components/ILIAS/Bibliographic/classes/trait.ilBibliographicSecureString.php +++ b/components/ILIAS/Bibliographic/classes/trait.ilBibliographicSecureString.php @@ -1,4 +1,5 @@ rbac_review = $rbac_review; + $this->local_roles = $local_roles; + } + + public function getData( + array $fields, + ?Range $range = null, + ?Order $order = null, + array $filter = [], + array $parameters = [] + ): \Generator { + $data = $this->collectData(); + + // Apply ordering if specified + $data = $this->applyOrder($data, $order); + + // Apply range (pagination) if specified + $data = $this->applyRange($data, $range); + + foreach ($data as $row) { + yield $row; + } + } + + protected function applyOrder(array $data, ?Order $order = null): array + { + if ($order !== null) { + $order_field = array_keys($order->get())[0]; + $order_direction = $order->get()[$order_field]; + + array_multisort( + array_column($data, $order_field), + $order_direction === 'ASC' ? SORT_ASC : SORT_DESC, + $this->isFieldNumeric($order_field) ? SORT_NUMERIC : SORT_STRING, + $data + ); + } + return $data; + } + + protected function applyRange(array $data, ?Range $range = null): array + { + if ($range !== null) { + $offset = $range->getStart(); + $limit = $range->getLength(); + $data = array_slice($data, $offset, $limit); + } + return $data; + } + + public function count( + array $filter, + array $parameters + ): int { + return count($this->collectData()); + } + + protected function collectData(): array + { + $user_map = $assigned = array(); + foreach ($this->local_roles as $id => $title) { + $local = $this->rbac_review->assignedUsers($id); + $assigned = array_merge($assigned, $local); + foreach ($local as $user_id) { + $user_map[$user_id][] = $title; + } + } + + $data = array(); + foreach (array_unique($assigned) as $id) { + $data[] = array( + "id" => $id, + "name" => \ilUserUtil::getNamePresentation($id, false, false, "", true), + "role" => $user_map[$id] + ); + } + + return $data; + } + + public function isFieldNumeric(string $field): bool + { + return $field === "id"; + } + +} diff --git a/components/ILIAS/Blog/Contributor/ContributorTableBuilder.php b/components/ILIAS/Blog/Contributor/ContributorTableBuilder.php new file mode 100644 index 000000000000..d2de6ea2dba4 --- /dev/null +++ b/components/ILIAS/Blog/Contributor/ContributorTableBuilder.php @@ -0,0 +1,98 @@ +local_roles = $roles; + parent::__construct($parent_gui, $parent_cmd); + } + + protected function getId(): string + { + return "contributor"; + } + + protected function getTitle(): string + { + if ($this->contributor_ids) { + return $this->domain->lng()->txt("blog_contributor_container_add"); + } else { + return $this->domain->lng()->txt("blog_contributors"); + } + } + + protected function getRetrieval(): RetrievalInterface + { + return new ContributorRetrieval( + $this->domain->rbac()->review(), + $this->local_roles + ); + } + + protected function transformRow(array $data_row): array + { + return [ + "id" => $data_row["id"], + "name" => $data_row["name"], + "role" => implode(", ", $data_row["role"]) + ]; + } + + protected function build(TableAdapterGUI $table): TableAdapterGUI + { + $lng = $this->domain->lng(); + + $table = $table + ->textColumn("name", $lng->txt("name"), true) + ->textColumn("role", $lng->txt("obj_role")); + + if ($this->contributor_ids) { + $table = $table->multiAction( + "addContributorContainerAction", + $lng->txt("add") + ); + } else { + $table = $table->multiAction( + "confirmRemoveContributor", + $lng->txt("remove") + ); + } + + return $table; + } +} diff --git a/components/ILIAS/Blog/Contributor/Service/class.GUIService.php b/components/ILIAS/Blog/Contributor/Service/class.GUIService.php index b1e5e35a2b60..ef8de2f00f64 100755 --- a/components/ILIAS/Blog/Contributor/Service/class.GUIService.php +++ b/components/ILIAS/Blog/Contributor/Service/class.GUIService.php @@ -43,16 +43,18 @@ public function __construct( $this->gui = $gui; } - /*public function administration() : Administration\GUIService - { - return new Administration\GUIService( + + public function contributorTableBuilder( + array $roles, + object $parent_gui, + string $parent_cmd + ): ContributorTableBuilder { + return new ContributorTableBuilder( $this->domain_service, - $this + $this->gui, + $roles, + $parent_gui, + $parent_cmd ); - }*/ - - public function ilContributorTableGUI(\ilObjBlogGUI $parent_gui, string $cmd, array $roles): \ilContributorTableGUI - { - return new \ilContributorTableGUI($this->domain_service->rbac()->review(), $parent_gui, $cmd, $roles); } } diff --git a/components/ILIAS/Blog/Contributor/class.ilContributorTableGUI.php b/components/ILIAS/Blog/Contributor/class.ilContributorTableGUI.php deleted file mode 100755 index 70de9a0bd877..000000000000 --- a/components/ILIAS/Blog/Contributor/class.ilContributorTableGUI.php +++ /dev/null @@ -1,93 +0,0 @@ - - */ -class ilContributorTableGUI extends ilTable2GUI -{ - protected ilRbacReview $rbacreview; - protected array $local_roles = []; - protected array $contributor_ids = []; - - public function __construct( - ilRbacReview $rbacreview, - object $a_parent_obj, - string $a_parent_cmd, - array $a_roles - ) { - $this->local_roles = $a_roles; - $this->rbacreview = $rbacreview; - - parent::__construct($a_parent_obj, $a_parent_cmd); - - $this->addColumn("", "", "1"); - $this->addColumn($this->lng->txt("name"), "name"); - $this->addColumn($this->lng->txt("obj_role"), "role"); - - $this->setDefaultOrderField("name"); - - $this->setRowTemplate("tpl.contributor_row.html", "components/ILIAS/Blog"); - $this->setFormAction($this->ctrl->getFormAction($a_parent_obj, $a_parent_cmd)); - - $this->setSelectAllCheckbox("id"); // #16472 - - if ($this->contributor_ids) { - $this->setTitle($this->lng->txt("blog_contributor_container_add")); - $this->addMultiCommand("addContributorContainerAction", $this->lng->txt("add")); - } else { - $this->setTitle($this->lng->txt("blog_contributors")); - $this->addMultiCommand("confirmRemoveContributor", $this->lng->txt("remove")); - } - - $this->getItems(); - } - - protected function getItems(): void - { - $rbacreview = $this->rbacreview; - - $user_map = $assigned = array(); - foreach ($this->local_roles as $id => $title) { - $local = $rbacreview->assignedUsers($id); - $assigned = array_merge($assigned, $local); - foreach ($local as $user_id) { - $user_map[$user_id][] = $title; - } - } - - $data = array(); - foreach (array_unique($assigned) as $id) { - $data[] = array("id" => $id, - "name" => ilUserUtil::getNamePresentation($id, false, false, "", true), - "role" => $user_map[$id]); - } - - $this->setData($data); - } - - protected function fillRow(array $a_set): void - { - $this->tpl->setVariable("VAL_ID", $a_set["id"]); - $this->tpl->setVariable("TXT_NAME", $a_set["name"]); - $this->tpl->setVariable("TXT_ROLES", implode(", ", $a_set["role"])); - } -} diff --git a/components/ILIAS/Blog/Export/BlogHtmlExport.php b/components/ILIAS/Blog/Export/BlogHtmlExport.php index 29c890d8b8e5..3b18da4a8960 100755 --- a/components/ILIAS/Blog/Export/BlogHtmlExport.php +++ b/components/ILIAS/Blog/Export/BlogHtmlExport.php @@ -59,7 +59,7 @@ public function __construct( $this->collector->init(); $this->blog = $blog; - $this->export_dir = $exp_dir; + //$this->export_dir = $exp_dir; $this->sub_dir = $sub_dir; $this->target_dir = $exp_dir . "/" . $sub_dir; @@ -113,11 +113,15 @@ protected function initDirectories(): void * @throws \ILIAS\UI\NotImplementedException * @throws \ilTemplateException */ - public function exportHTML(): string + public function exportHTML(): void { $this->initDirectories(); - $this->export_util->exportSystemStyle(); + $this->export_util->exportSystemStyle( + [ + "icon_blog.svg" + ] + ); $this->export_util->exportCOPageFiles( $this->content_style_domain->getEffectiveStyleId(), @@ -138,8 +142,6 @@ public function exportHTML(): string */ $this->export_util->exportResourceFiles(); $this->co_page_html_export->exportPageElements(); - - return $this->zipPackage(); } protected function exportUserImages(): void @@ -150,21 +152,6 @@ protected function exportUserImages(): void } } - public function zipPackage(): string - { - // zip it all - $date = time(); - $type = ($this->include_comments) - ? "html_comments" - : "html"; - $zip_file = \ilExport::_getExportDirectory($this->blog->getId(), $type, "blog") . - "/" . $date . "__" . IL_INST_ID . "__" . - $this->blog->getType() . "_" . $this->blog->getId() . ".zip"; - ilFileUtils::zip($this->target_dir, $zip_file); - ilFileUtils::delDir($this->target_dir); - return $zip_file; - } - /** * Export all pages (note: this one is called from the portfolio html export!) * @throws \ILIAS\UI\NotImplementedException @@ -299,7 +286,7 @@ public function exportHTMLPagesPrint(): void $print_view = $this->blog_gui->getPrintView(); $print_view->setOffline(true); $html = $print_view->renderPrintView(); - file_put_contents($this->target_dir . "/index.html", $html); + $this->collector->addString($html, "index.html"); } @@ -413,7 +400,6 @@ protected function writeExportFile( $content = $a_tpl->printToString(); // open file - // file_put_contents($file, $content); $this->collector->addString($content, $a_file); return $file; diff --git a/components/ILIAS/Blog/Navigation/ToolbarNavigationRenderer.php b/components/ILIAS/Blog/Navigation/ToolbarNavigationRenderer.php index 5ad3b85dfa4f..9b2705a2e2bc 100755 --- a/components/ILIAS/Blog/Navigation/ToolbarNavigationRenderer.php +++ b/components/ILIAS/Blog/Navigation/ToolbarNavigationRenderer.php @@ -22,7 +22,7 @@ use ILIAS\Blog\InternalDomainService; use ILIAS\Blog\InternalGUIService; -use ILIAS\Blog\Access\BlogAccess; +use ILIAS\Blog\Permission\PermissionManager; class ToolbarNavigationRenderer { @@ -33,7 +33,7 @@ class ToolbarNavigationRenderer protected \ILIAS\Blog\Presentation\Util $util; protected $current_month; protected \ilCtrl $ctrl; - protected BlogAccess $blog_access; + protected PermissionManager $blog_access; protected InternalDomainService $domain; public function __construct( @@ -46,7 +46,7 @@ public function __construct( } public function renderToolbarNavigation( - BlogAccess $blog_acces, + PermissionManager $blog_acces, array $a_items, int $blog_page, bool $single_posting, diff --git a/components/ILIAS/Blog/News/class.NewsManager.php b/components/ILIAS/Blog/News/class.NewsManager.php new file mode 100644 index 000000000000..b6b863237105 --- /dev/null +++ b/components/ILIAS/Blog/News/class.NewsManager.php @@ -0,0 +1,122 @@ +domain->lng(); + $ilUser = $this->domain->user(); + + if (!$page->getActive()) { + return; + } + + $news_item = null; + + if ($update) { + $news_id = \ilNewsItem::getLastNewsIdForContext( + $page->getBlogId(), + "blog", + $page->getId(), + $page->getParentType(), + true + ); + if ($news_id > 0) { + $news_item = new \ilNewsItem($news_id); + } + } + + if (!$news_item) { + $news_set = new \ilSetting("news"); + $default_visibility = $news_set->get("default_visibility", "users"); + + $news_item = new \ilNewsItem(); + $news_item->setContext( + $page->getBlogId(), + "blog", + $page->getId(), + $page->getParentType() + ); + $news_item->setPriority(NEWS_NOTICE); + $news_item->setVisibility($default_visibility); + } + + $news_item->setUserId($ilUser->getId()); + + $news_item->setTitle($page->getTitle()); + + $contentKey = $update + ? "blog_news_posting_updated" + : "blog_news_posting_published"; + $content = sprintf( + $lng->txt($contentKey), + \ilUserUtil::getNamePresentation($ilUser->getId()) + ); + + $contributors = []; + foreach (\ilBlogPosting::getPageContributors($page->getParentType(), $page->getId()) as $user) { + $contributors[] = $user["user_id"]; + } + if (count($contributors) > 1 || !in_array($page->getAuthor(), $contributors, true)) { + $authors = [\ilUserUtil::getNamePresentation($page->getAuthor())]; + foreach ($contributors as $user_id) { + if ($user_id !== $page->getAuthor()) { + $authors[] = \ilUserUtil::getNamePresentation($user_id); + } + } + $content .= "\n" . sprintf( + $lng->txt("blog_news_posting_authors"), + implode(", ", $authors) + ); + } + + $news_item->setContentTextIsLangVar(false); + $news_item->setContent($content); + + $snippet = \ilBlogPostingGUI::getSnippet($page->getId()); + $news_item->setContentLong($snippet); + + if (!$news_item->getId()) { + $news_item->create(); + } else { + $news_item->update(true); + } + } +} diff --git a/components/ILIAS/Blog/Notification/NotificationManager.php b/components/ILIAS/Blog/Notification/NotificationManager.php new file mode 100644 index 000000000000..351a0edbfec9 --- /dev/null +++ b/components/ILIAS/Blog/Notification/NotificationManager.php @@ -0,0 +1,156 @@ +current_user_id = $this->domain->user()->getId(); + } + + public function sendNotification( + string $action, + bool $in_wsp, + int $blog_node_id, + int $posting_id, + ?string $comment = null + ): void { + $blog_obj_id = $this->getBlogObjectId($in_wsp, $blog_node_id); + if (!$blog_obj_id) { + return; + } + + $posting = new \ilBlogPosting($posting_id); + + $ignore_threshold = ($action === "comment"); + $admin_only = false; + + if (!$posting->isApproved()) { + $blog_settings = $this->domain->blogSettings()->getByObjId($blog_obj_id); + if ($blog_settings?->getApproval()) { + switch ($action) { + case "update": + return; + + case "new": + $admin_only = true; + $ignore_threshold = true; + $action = "approve"; + break; + } + } + } + + if (!$in_wsp && in_array($action, array("update", "new"))) { + $this->domain->news()->handle($posting, ($action === "update")); + } + + $users = \ilNotification::getNotificationsForObject( + \ilNotification::TYPE_BLOG, + $blog_obj_id, + $posting_id, + $ignore_threshold + ); + if (!count($users)) { + return; + } + + $this->sendSystemNotification( + $in_wsp, + $blog_node_id, + $action, + $posting, + $comment, + $users, + $admin_only + ); + + if (count($users)) { + \ilNotification::updateNotificationTime( + \ilNotification::TYPE_BLOG, + $blog_obj_id, + $users, + $posting_id + ); + } + } + + protected function getBlogObjectId(bool $in_wsp, int $blog_node_id): ?int + { + if ($in_wsp) { + $tree = new \ilWorkspaceTree($this->current_user_id); + return $tree->lookupObjectId($blog_node_id); + } else { + return \ilObject::_lookupObjId($blog_node_id); + } + } + + protected function sendSystemNotification( + bool $in_wsp, + int $blog_node_id, + string $action, + \ilBlogPosting $posting, + ?string $comment, + array $users, + bool $admin_only + ): void { + $ntf = new \ilSystemNotification($in_wsp); + $ntf->setLangModules(array("blog")); + $ntf->setRefId($blog_node_id); + $ntf->setChangedByUserId($this->current_user_id); + $ntf->setSubjectLangId('blog_change_notification_subject'); + $ntf->setIntroductionLangId('blog_change_notification_body_' . $action); + $ntf->addAdditionalInfo('blog_posting', $posting->getTitle()); + + if ($comment) { + $ntf->addAdditionalInfo('comment', $comment, true); + } + + $ntf->setGotoLangId('blog_change_notification_link'); + $ntf->setReasonLangId('blog_change_notification_reason'); + + $abstract = $posting->getNotificationAbstract(); + if ($abstract) { + $ntf->addAdditionalInfo('content', $abstract, true); + } + + $notified = $ntf->sendMailAndReturnRecipients( + $users, + "_" . $posting->getId(), + ($admin_only ? "write" : "read") + ); + + if (count($notified)) { + \ilNotification::updateNotificationTime( + \ilNotification::TYPE_BLOG, + $posting->getBlogId(), + $notified, + $posting->getId() + ); + } + } +} diff --git a/components/ILIAS/Blog/PermanentLink/StaticUrlHandler.php b/components/ILIAS/Blog/PermanentLink/StaticUrlHandler.php index 00169a957194..20d97ab4bcbf 100644 --- a/components/ILIAS/Blog/PermanentLink/StaticUrlHandler.php +++ b/components/ILIAS/Blog/PermanentLink/StaticUrlHandler.php @@ -40,7 +40,8 @@ public function handle(Request $request, Context $context, Factory $response_fac $ctrl = $DIC->ctrl(); $access = $DIC->access(); - $uri = ""; + $uri = null; + $blog_domain = $DIC->blog()->internal()->domain(); $id = $request->getReferenceId()?->toInt() ?? 0; $additional_params = $request->getAdditionalParameters() ?? []; @@ -115,6 +116,13 @@ public function handle(Request $request, Context $context, Factory $response_fac ], "infoScreen"); } } + if (is_null($uri)) { + if ($blog_domain->user()->isAnonymous() || $blog_domain->user()->getId() == 0) { + return $response_factory->loginFirst(); + } else { + return $response_factory->cannot(); + } + } return $response_factory->can($uri); } diff --git a/components/ILIAS/Blog/Access/BlogAccess.php b/components/ILIAS/Blog/Permission/PermissionManager.php similarity index 89% rename from components/ILIAS/Blog/Access/BlogAccess.php rename to components/ILIAS/Blog/Permission/PermissionManager.php index 00cd9baa5675..c0ab7581e9ee 100755 --- a/components/ILIAS/Blog/Access/BlogAccess.php +++ b/components/ILIAS/Blog/Permission/PermissionManager.php @@ -18,9 +18,9 @@ declare(strict_types=1); -namespace ILIAS\Blog\Access; +namespace ILIAS\Blog\Permission; -class BlogAccess +class PermissionManager { protected int $owner; protected int $id_type; @@ -96,4 +96,13 @@ protected function checkPermissionBool(string $perm): bool return false; } + /** + * Check if user has admin access (approve, may edit & deactivate all postings) + */ + public function canManage(): bool + { + return ($this->checkPermissionBool("redact") || + $this->checkPermissionBool("write")); + } + } diff --git a/components/ILIAS/Blog/Posting/class.Posting.php b/components/ILIAS/Blog/Posting/class.Posting.php new file mode 100644 index 000000000000..bb1bddd51f05 --- /dev/null +++ b/components/ILIAS/Blog/Posting/class.Posting.php @@ -0,0 +1,72 @@ +id; + } + + public function getBlogId(): int + { + return $this->blog_id; + } + + public function getTitle(): string + { + return $this->title; + } + + public function getCreated(): ilDateTime + { + return $this->created; + } + + public function getAuthor(): int + { + return $this->author; + } + + public function isApproved(): bool + { + return $this->approved; + } + + public function getLastWithdrawn(): ?ilDateTime + { + return $this->last_withdrawn; + } +} diff --git a/components/ILIAS/Blog/Posting/class.PostingDBRepository.php b/components/ILIAS/Blog/Posting/class.PostingDBRepository.php new file mode 100644 index 000000000000..fb3447e9bc70 --- /dev/null +++ b/components/ILIAS/Blog/Posting/class.PostingDBRepository.php @@ -0,0 +1,167 @@ +data->posting( + (int) $rec['id'], + (int) $rec['blog_id'], + (string) $rec['title'], + new ilDateTime($rec['created'], IL_CAL_DATETIME), + (int) $rec['author'], + (bool) $rec['approved'], + $rec['last_withdrawn'] !== null + ? new ilDateTime($rec['last_withdrawn'], IL_CAL_DATETIME) + : null + ); + } + + public function create(Posting $posting): int + { + $id = $this->db->nextId('il_blog_posting'); + $this->db->insert('il_blog_posting', [ + 'id' => ['integer', $id], + 'blog_id' => ['integer', $posting->getBlogId()], + 'title' => ['text', $posting->getTitle()], + 'created' => ['timestamp', $posting->getCreated()->get(IL_CAL_DATETIME)], + 'author' => ['integer', $posting->getAuthor()], + 'approved' => ['integer', $posting->isApproved()], + 'last_withdrawn' => ['timestamp', $posting->getLastWithdrawn()?->get(IL_CAL_DATETIME)], + ]); + return $id; + } + + public function update(Posting $posting): void + { + $this->db->update('il_blog_posting', [ + 'title' => ['text', $posting->getTitle()], + 'created' => ['timestamp', $posting->getCreated()->get(IL_CAL_DATETIME)], + 'approved' => ['integer', $posting->isApproved()], + 'last_withdrawn' => ['timestamp', $posting->getLastWithdrawn()?->get(IL_CAL_DATETIME)], + ], [ + 'id' => ['integer', $posting->getId()], + ]); + } + + public function getById(int $id): ?Posting + { + $set = $this->db->queryF( + 'SELECT * FROM il_blog_posting WHERE id = %s', + ['integer'], + [$id] + ); + if ($rec = $this->db->fetchAssoc($set)) { + return $this->getPostingFromRecord($rec); + } + return null; + } + + public function delete(int $id): void + { + $this->db->manipulateF( + 'DELETE FROM il_blog_posting WHERE id = %s', + ['integer'], + [$id] + ); + } + + public function deleteAllBlogPostings(int $blog_id): void + { + $this->db->manipulateF( + 'DELETE FROM il_blog_posting WHERE blog_id = %s', + ['integer'], + [$blog_id] + ); + } + + public function lookupBlogId(int $posting_id): ?int + { + $set = $this->db->queryF( + 'SELECT blog_id FROM il_blog_posting WHERE id = %s', + ['integer'], + [$posting_id] + ); + if ($rec = $this->db->fetchAssoc($set)) { + return (int) $rec['blog_id']; + } + return null; + } + + public function getAllByBlog(int $blog_id, int $limit = 1000, int $offset = 0): array + { + if ($limit) { + $this->db->setLimit($limit, $offset); + } + $set = $this->db->queryF( + 'SELECT * FROM il_blog_posting WHERE blog_id = %s ORDER BY created DESC', + ['integer'], + [$blog_id] + ); + $posts = []; + while ($rec = $this->db->fetchAssoc($set)) { + $posts[] = $this->getPostingFromRecord($rec); + } + return $posts; + } + + public function exists(int $blog_id, int $posting_id): bool + { + $set = $this->db->queryF( + 'SELECT id FROM il_blog_posting WHERE blog_id = %s AND id = %s', + ['integer', 'integer'], + [$blog_id, $posting_id] + ); + return $this->db->numRows($set) > 0; + } + + public function getLastPost(int $blog_id): int + { + $all = $this->getAllByBlog($blog_id, 1, 0); + return $all ? $all[0]->getId() : 0; + } + + public function searchBlogsByAuthor(int $user_id): array + { + $ids = []; + $set = $this->db->queryF( + 'SELECT DISTINCT(blog_id) FROM il_blog_posting WHERE author = %s', + ['integer'], + [$user_id] + ); + while ($row = $this->db->fetchAssoc($set)) { + $ids[] = (int) $row['blog_id']; + } + return $ids; + } +} diff --git a/components/ILIAS/Blog/Posting/class.PostingManager.php b/components/ILIAS/Blog/Posting/class.PostingManager.php new file mode 100644 index 000000000000..ada9ec9faddf --- /dev/null +++ b/components/ILIAS/Blog/Posting/class.PostingManager.php @@ -0,0 +1,89 @@ +repo->posting()->create($posting); + } + + public function update(Posting $posting): void + { + $this->repo->posting()->update($posting); + } + + public function delete(Posting $posting): void + { + $this->repo->posting()->delete($posting->getId()); + } + + public function getById(int $id): ?Posting + { + return $this->repo->posting()->getById($id); + } + + /** + * @return Posting[] + */ + public function getAllByBlog(int $blog_id, int $limit = 1000, int $offset = 0): array + { + return $this->repo->posting()->getAllByBlog($blog_id, $limit, $offset); + } + + public function exists(int $blog_id, int $posting_id): bool + { + return $this->repo->posting()->exists($blog_id, $posting_id); + } + + public function lookupBlogId(int $posting_id): ?int + { + return $this->repo->posting()->lookupBlogId($posting_id); + } + + public function deleteAllByBlog(int $blog_id): void + { + $this->repo->posting()->deleteAllBlogPostings($blog_id); + } + + public function getLastPost(int $blog_id): int + { + return $this->repo->posting()->getLastPost($blog_id); + } + + public function searchBlogsByAuthor(int $user_id): array + { + return $this->repo->posting()->searchBlogsByAuthor($user_id); + } +} diff --git a/components/ILIAS/Blog/Posting/class.ilBlogPosting.php b/components/ILIAS/Blog/Posting/class.ilBlogPosting.php index c196ac99d664..85a5927d5092 100755 --- a/components/ILIAS/Blog/Posting/class.ilBlogPosting.php +++ b/components/ILIAS/Blog/Posting/class.ilBlogPosting.php @@ -18,6 +18,9 @@ declare(strict_types=1); +use ILIAS\Blog\InternalDataService; +use ILIAS\Blog\Posting\PostingManager; + /** * Class ilBlogPosting * @@ -32,6 +35,15 @@ class ilBlogPosting extends ilPageObject protected int $author = 0; protected bool $approved = false; protected ?ilDateTime $withdrawn = null; + protected InternalDataService $internal_data; + protected PostingManager $posting_manager; + + public function afterConstructor(): void + { + global $DIC; + $this->internal_data = $DIC->blog()->internal()->data(); + $this->posting_manager = $DIC->blog()->internal()->domain()->posting(); + } public function getParentType(): string { @@ -111,33 +123,21 @@ public function getWithdrawn(): ?ilDateTime public function create( bool $a_import = false ): void { - $ilDB = $this->db; - - $id = $ilDB->nextId("il_blog_posting"); + $data = $this->internal_data; + $post = $data->posting( + 0, + $this->getBlogId(), + $this->getTitle(), + new ilDateTime(ilUtil::now(), IL_CAL_DATETIME), + $this->getAuthor(), + $this->isApproved(), + $this->getWithdrawn() + ); + $id = $this->posting_manager->create($post); $this->setId($id); - if (!$a_import) { - $created = ilUtil::now(); - } else { - $created = $this->getCreated()->get(IL_CAL_DATETIME); - } - - // we are using a separate creation date to enable sorting without JOINs - $withdrawn = $this->getWithdrawn()?->get(IL_CAL_DATETIME); - $query = "INSERT INTO il_blog_posting (id, title, blog_id, created, author, approved, last_withdrawn)" . - " VALUES (" . - $ilDB->quote($this->getId(), "integer") . "," . - $ilDB->quote($this->getTitle(), "text") . "," . - $ilDB->quote($this->getBlogId(), "integer") . "," . - $ilDB->quote($created, "timestamp") . "," . - $ilDB->quote($this->getAuthor(), "integer") . "," . - $ilDB->quote($this->isApproved(), "integer") . "," . // #16526 - import - $ilDB->quote($withdrawn, "timestamp") . ")"; - $ilDB->manipulate($query); - if (!$a_import) { parent::create($a_import); - // $this->saveInternalLinks($this->getXMLContent()); } } @@ -147,22 +147,27 @@ public function update( bool $a_notify = true, string $a_notify_action = "update" ) { - $ilDB = $this->db; - - // blog_id, author and created cannot be changed - $withdrawn = $this->getWithdrawn()?->get(IL_CAL_DATETIME); - $query = "UPDATE il_blog_posting SET" . - " title = " . $ilDB->quote($this->getTitle(), "text") . - ",created = " . $ilDB->quote($this->getCreated()->get(IL_CAL_DATETIME), "timestamp") . - ",approved =" . $ilDB->quote($this->isApproved(), "integer") . - ",last_withdrawn =" . $ilDB->quote($withdrawn, "timestamp") . - " WHERE id = " . $ilDB->quote($this->getId(), "integer"); - $ilDB->manipulate($query); + $data = $this->internal_data; + $post = $data->posting( + $this->getId(), + $this->getBlogId(), + $this->getTitle(), + $this->getCreated(), + $this->getAuthor(), + $this->isApproved(), + $this->getWithdrawn() + ); + $this->posting_manager->update($post); $ret = parent::update($a_validate, $a_no_history); if ($a_notify && $this->getActive()) { - ilObjBlog::sendNotification($a_notify_action, $this->blog_node_is_wsp, $this->blog_node_id, $this->getId()); + ilObjBlog::sendNotification( + $a_notify_action, + $this->blog_node_is_wsp, + $this->blog_node_id, + $this->getId() + ); } return $ret; @@ -217,9 +222,7 @@ public function delete(): void $this->getParentType() ); - $query = "DELETE FROM il_blog_posting" . - " WHERE id = " . $ilDB->quote($this->getId(), "integer"); - $ilDB->manipulate($query); + $this->posting_manager->delete($this->getId()); parent::delete(); } @@ -242,7 +245,6 @@ public function unpublish(): void ); } - /** * Delete all postings for blog */ @@ -251,17 +253,11 @@ public static function deleteAllBlogPostings( ): void { global $DIC; - $ilDB = $DIC->database(); $lom_services = $DIC->learningObjectMetadata(); - - $query = "SELECT * FROM il_blog_posting" . - " WHERE blog_id = " . $ilDB->quote($a_blog_id, "integer"); - $set = $ilDB->query($query); - while ($rec = $ilDB->fetchAssoc($set)) { - // delete lom - $lom_services->deleteAll($a_blog_id, $rec["id"], "blp"); - - $post = new ilBlogPosting($rec["id"]); + $mgr = $DIC->blog()->internal()->domain()->posting(); + foreach ($mgr->getAllByBlog($a_blog_id, 0) as $posting) { + $lom_services->deleteAll($a_blog_id, $posting->getId(), "blp"); + $post = new ilBlogPosting($posting->getId()); $post->delete(); } } @@ -270,16 +266,7 @@ public static function lookupBlogId( int $a_posting_id ): ?int { global $DIC; - - $ilDB = $DIC->database(); - - $query = "SELECT blog_id FROM il_blog_posting" . - " WHERE id = " . $ilDB->quote($a_posting_id, "integer"); - $set = $ilDB->query($query); - if ($rec = $ilDB->fetchAssoc($set)) { - return (int) $rec["blog_id"]; - } - return null; + return $DIC->blog()->internal()->domain()->posting()->lookupBlogId($a_posting_id); } /** @@ -292,37 +279,31 @@ public static function getAllPostings( ): array { global $DIC; - $ilDB = $DIC->database(); - $pages = parent::getAllPages("blp", $a_blog_id); - - if ($a_limit) { - $ilDB->setLimit($a_limit, $a_offset); - } - - $query = "SELECT * FROM il_blog_posting" . - " WHERE blog_id = " . $ilDB->quote($a_blog_id, "integer") . - " ORDER BY created DESC"; - $set = $ilDB->query($query); - $post = array(); - while ($rec = $ilDB->fetchAssoc($set)) { - if (isset($pages[$rec["id"]])) { - $post[$rec["id"]] = $pages[$rec["id"]]; - $post[$rec["id"]]["title"] = $rec["title"]; - $post[$rec["id"]]["created"] = new ilDateTime($rec["created"], IL_CAL_DATETIME); - $post[$rec["id"]]["author"] = $rec["author"]; - $post[$rec["id"]]["approved"] = (bool) $rec["approved"]; - $post[$rec["id"]]["last_withdrawn"] = new ilDateTime($rec["last_withdrawn"], IL_CAL_DATETIME); - - foreach (self::getPageContributors("blp", $rec["id"]) as $editor) { - if ($editor["user_id"] != $rec["author"]) { - $post[$rec["id"]]["editors"][] = $editor["user_id"]; + $posts = []; + foreach ($DIC->blog()->internal()->domain()->posting()->getAllByBlog( + $a_blog_id, + $a_limit, + $a_offset + ) as $posting) { + $id = $posting->getId(); + if (isset($pages[$id])) { + $posts[$id] = $pages[$id]; + $posts[$id]["title"] = $posting->getTitle(); + $posts[$id]["created"] = $posting->getCreated(); + $posts[$id]["author"] = $posting->getAuthor(); + $posts[$id]["approved"] = $posting->isApproved(); + $posts[$id]["last_withdrawn"] = $posting->getLastWithdrawn(); + + foreach (self::getPageContributors("blp", $id) as $editor) { + if ($editor["user_id"] != $posting->getAuthor()) { + $posts[$id]["editors"][] = $editor["user_id"]; } } } } - return $post; + return $posts; } /** @@ -333,17 +314,10 @@ public static function exists( int $a_posting_id ): bool { global $DIC; - - $ilDB = $DIC->database(); - - $query = "SELECT id FROM il_blog_posting" . - " WHERE blog_id = " . $ilDB->quote($a_blog_id, "integer") . - " AND id = " . $ilDB->quote($a_posting_id, "integer"); - $set = $ilDB->query($query); - if ($rec = $ilDB->fetchAssoc($set)) { - return true; - } - return false; + return $DIC->blog()->internal()->domain()->posting()->exists( + $a_blog_id, + $a_posting_id + ); } /** @@ -352,12 +326,8 @@ public static function exists( public static function getLastPost( int $a_blog_id ): int { - $data = self::getAllPostings($a_blog_id, 1); - if ($data) { - $keys = array_keys($data); - return end($keys); - } - return 0; + global $DIC; + return $DIC->blog()->internal()->domain()->posting()->getLastPost($a_blog_id); } /** @@ -371,28 +341,6 @@ public function setBlogNodeId( $this->blog_node_is_wsp = $a_is_in_workspace; } - /** - * Get all blogs where user has postings - */ - public static function searchBlogsByAuthor( - int $a_user_id - ): array { - global $DIC; - - $ilDB = $DIC->database(); - - $ids = array(); - - $sql = "SELECT DISTINCT(blog_id)" . - " FROM il_blog_posting" . - " WHERE author = " . $ilDB->quote($a_user_id); - $set = $ilDB->query($sql); - while ($row = $ilDB->fetchAssoc($set)) { - $ids[] = (int) $row["blog_id"]; - } - return $ids; - } - public function getNotificationAbstract(): string { $snippet = ilBlogPostingGUI::getSnippet($this->getId(), true); @@ -403,7 +351,6 @@ public function getNotificationAbstract(): string return trim(strip_tags($snippet)); } - // keywords public function updateKeywords( array $keywords @@ -433,119 +380,4 @@ public static function getKeywords( return $result; } - - /** - * Handle news item - */ - public function handleNews( - bool $a_update = false - ): void { - $lng = $this->lng; - $ilUser = $this->user; - - // see ilWikiPage::updateNews() - - if (!$this->getActive()) { - return; - } - - $news_item = null; - - // try to re-use existing news item - if ($a_update) { - // get last news item of the day (if existing) - $news_id = ilNewsItem::getLastNewsIdForContext( - $this->getBlogId(), - "blog", - $this->getId(), - $this->getParentType(), - true - ); - if ($news_id > 0) { - $news_item = new ilNewsItem($news_id); - } - } - - // create new news item - if (!$news_item) { - $news_set = new ilSetting("news"); - $default_visibility = $news_set->get("default_visibility", "users"); - - $news_item = new ilNewsItem(); - $news_item->setContext( - $this->getBlogId(), - "blog", - $this->getId(), - $this->getParentType() - ); - $news_item->setPriority(NEWS_NOTICE); - $news_item->setVisibility($default_visibility); - } - - // news author - $news_item->setUserId($ilUser->getId()); - - - // news title/content - - $news_item->setTitle($this->getTitle()); - - $content = $a_update - ? "blog_news_posting_updated" - : "blog_news_posting_published"; - - // news "author" - $content = sprintf($lng->txt($content), ilUserUtil::getNamePresentation($ilUser->getId())); - - // posting author[s] - $contributors = array(); - foreach (self::getPageContributors($this->getParentType(), $this->getId()) as $user) { - $contributors[] = $user["user_id"]; - } - if (count($contributors) > 1 || !in_array($this->getAuthor(), $contributors)) { - // original author should come first? - $authors = array(ilUserUtil::getNamePresentation($this->getAuthor())); - foreach ($contributors as $user_id) { - if ($user_id != $this->getAuthor()) { - $authors[] = ilUserUtil::getNamePresentation($user_id); - } - } - $content .= "\n" . sprintf($lng->txt("blog_news_posting_authors"), implode(", ", $authors)); - } - - $news_item->setContentTextIsLangVar(false); - $news_item->setContent($content); - - $snippet = ilBlogPostingGUI::getSnippet($this->getId()); - $news_item->setContentLong($snippet); - - if (!$news_item->getId()) { - $news_item->create(); - } else { - $news_item->update(true); - } - } - - /** - * Lookup posting property - */ - protected static function lookup( - string $a_field, - int $a_posting_id - ): ?string { - global $DIC; - - $db = $DIC->database(); - - $set = $db->query("SELECT $a_field FROM il_blog_posting " . - " WHERE id = " . $db->quote($a_posting_id, "integer")); - $rec = $db->fetchAssoc($set); - - return $rec[$a_field] ?? null; - } - - public static function lookupTitle(int $a_posting_id): string - { - return (string) self::lookup("title", $a_posting_id); - } } diff --git a/components/ILIAS/Blog/Posting/class.ilBlogPostingGUI.php b/components/ILIAS/Blog/Posting/class.ilBlogPostingGUI.php index ca9a7cbebda7..c1d815b05a08 100755 --- a/components/ILIAS/Blog/Posting/class.ilBlogPostingGUI.php +++ b/components/ILIAS/Blog/Posting/class.ilBlogPostingGUI.php @@ -21,6 +21,10 @@ use ILIAS\Blog\StandardGUIRequest; use ILIAS\Repository\Profile\ProfileGUI; use ILIAS\MetaData\Services\ServicesInterface as LOMServices; +use ILIAS\Blog\Posting\PostingManager; +use ILIAS\Blog\InternalDomainService; +use ILIAS\Blog\InternalGUIService; +use ILIAS\Blog\ReadingTime\ReadingTimeManager; /** * Class ilBlogPosting GUI class @@ -30,16 +34,18 @@ */ class ilBlogPostingGUI extends ilPageObjectGUI { - protected \ILIAS\Blog\InternalGUIService $blog_gui; + protected InternalDomainService $domain; + protected InternalGUIService $blog_gui; protected ProfileGUI $profile_gui; protected \ILIAS\Notes\Service $notes; - protected \ILIAS\Blog\ReadingTime\ReadingTimeManager $reading_time_manager; + protected ReadingTimeManager $reading_time_manager; protected StandardGUIRequest $blog_request; protected ilTabsGUI $tabs; protected ilLocatorGUI $locator; protected ilSetting $settings; protected LOMServices $lom_services; protected int $node_id; + protected PostingManager $posting_manager; protected ?object $access_handler = null; protected bool $enable_public_notes = false; protected bool $may_contribute = false; @@ -59,16 +65,17 @@ public function __construct( ) { global $DIC; - $this->tabs = $DIC->tabs(); - $this->locator = $DIC["ilLocator"]; - $this->settings = $DIC->settings(); - $this->user = $DIC->user(); - $tpl = $DIC["tpl"]; - $lng = $DIC->language(); - $this->blog_request = $DIC->blog() - ->internal() - ->gui() - ->standardRequest(); + $service = $DIC->blog()->internal(); + $this->domain = $service->domain(); + $this->blog_gui = $DIC->blog()->internal()->gui(); + + $this->tabs = $this->blog_gui->tabs(); + $this->locator = $this->blog_gui->locator(); + $this->settings = $this->domain->settings(); + $this->user = $this->domain->user(); + $tpl = $this->blog_gui->ui()->mainTemplate(); + $lng = $this->domain->lng(); + $this->blog_request = $this->blog_gui->standardRequest(); $this->lom_services = $DIC->learningObjectMetadata(); $lng->loadLanguageModule("blog"); @@ -96,15 +103,6 @@ public function __construct( ); $tpl->parseCurrentBlock(); - // #17814 - /* - $tpl->setCurrentBlock("ContentStyle"); - $tpl->setVariable( - "LOCATION_CONTENT_STYLESHEET", - ilObjStyleSheet::getContentStylePath($a_style_sheet_id) - ); - $tpl->parseCurrentBlock();*/ - // needed for editor $this->setStyleId($a_style_sheet_id); @@ -112,10 +110,10 @@ public function __construct( $this->fetchall = $this->blog_request->getFetchAll(); $this->term = $this->blog_request->getTerm(); - $this->reading_time_manager = new \ILIAS\Blog\ReadingTime\ReadingTimeManager(); + $this->reading_time_manager = new ReadingTimeManager(); $this->notes = $DIC->notes(); $this->profile_gui = $DIC->blog()->internal()->gui()->profile(); - $this->blog_gui = $DIC->blog()->internal()->gui(); + $this->posting_manager = $DIC->blog()->internal()->domain()->posting(); } public function executeCommand(): string @@ -461,8 +459,7 @@ public function updateTitle(): void $page = $this->getPageObject(); $page->setTitle($form->getInput("title")); $page->update(); - - $page->handleNews(true); + $this->domain->news()->handle($page, true); $this->tpl->setOnScreenMessage('success', $lng->txt("settings_saved"), true); //$ilCtrl->redirect($this, "preview"); @@ -666,9 +663,13 @@ protected function initKeywordsForm(): \ILIAS\UI\Component\Input\Container\Form\ // other keywords in blog $other = array(); - foreach (array_keys(ilBlogPosting::getAllPostings($this->getBlogPosting()->getBlogId())) as $posting_id) { - if ($posting_id != $this->getBlogPosting()->getId()) { - $other = array_merge($other, ilBlogPosting::getKeywords($this->getBlogPosting()->getBlogId(), $posting_id)); + foreach ($this->posting_manager->getAllByBlog($this->getBlogPosting()->getBlogId(), 0) as $posting) { + $pid = $posting->getId(); + if ($pid !== $this->getBlogPosting()->getId()) { + $other = array_merge( + $other, + ilBlogPosting::getKeywords($this->getBlogPosting()->getBlogId(), $pid) + ); } } // #17414 diff --git a/components/ILIAS/Blog/PrintView/class.BlogPrintViewProviderGUI.php b/components/ILIAS/Blog/PrintView/class.BlogPrintViewProviderGUI.php index b09eb8d68dfa..c65bca857c1e 100755 --- a/components/ILIAS/Blog/PrintView/class.BlogPrintViewProviderGUI.php +++ b/components/ILIAS/Blog/PrintView/class.BlogPrintViewProviderGUI.php @@ -122,7 +122,7 @@ public function getSelectionForm(): ?ilPropertyFormGUI foreach ($postings as $p) { $nl->addListNode( - $p["id"], + (string) $p["id"], $p["title"], "0", false, diff --git a/components/ILIAS/Blog/Service/class.InternalDataService.php b/components/ILIAS/Blog/Service/class.InternalDataService.php index 2ef953f6037c..24b30e21f0c9 100755 --- a/components/ILIAS/Blog/Service/class.InternalDataService.php +++ b/components/ILIAS/Blog/Service/class.InternalDataService.php @@ -20,6 +20,9 @@ namespace ILIAS\Blog; +use ilDateTime; +use ILIAS\Blog\Posting\Posting; + /** * @author Alexander Killing */ @@ -73,4 +76,24 @@ public function settings( $nav_order ); } + + public function posting( + int $id, + int $blog_id, + string $title, + ilDateTime $created, + int $author, + bool $approved, + ?ilDateTime $last_withdrawn + ): Posting { + return new Posting( + $id, + $blog_id, + $title, + $created, + $author, + $approved, + $last_withdrawn + ); + } } diff --git a/components/ILIAS/Blog/Service/class.InternalDomainService.php b/components/ILIAS/Blog/Service/class.InternalDomainService.php index b9a79a9f5760..034167485eb9 100755 --- a/components/ILIAS/Blog/Service/class.InternalDomainService.php +++ b/components/ILIAS/Blog/Service/class.InternalDomainService.php @@ -23,10 +23,13 @@ use ILIAS\DI\Container; use ILIAS\Repository\GlobalDICDomainServices; use ILIAS\Blog\Exercise\BlogExercise; -use ILIAS\Blog\Access\BlogAccess; +use ILIAS\Blog\Permission\PermissionManager; use ILIAS\Blog\ReadingTime\ReadingTimeManager; use ILIAS\Blog\Settings\SettingsManager; +use ILIAS\Blog\Posting\PostingManager; use ILIAS\Notes; +use ILIAS\Blog\News\NewsManager; +use ILIAS\Blog\Notification\NotificationManager; /** * @author Alexander Killing @@ -56,14 +59,14 @@ public function exercise(int $a_node_id): BlogExercise ); } - public function blogAccess( + public function perm( $access_handler, ?int $node_id, int $id_type, int $user_id, int $owner - ): BlogAccess { - return new BlogAccess( + ): PermissionManager { + return new PermissionManager( $access_handler, $node_id, $id_type, @@ -92,4 +95,29 @@ public function blogSettings(): SettingsManager ); } + public function posting(): PostingManager + { + return self::$instance["posting"] ??= new PostingManager( + $this->data, + $this->repo, + $this + ); + } + + public function news(): NewsManager + { + return self::$instance["news"] ??= new NewsManager( + $this->data, + $this->repo, + $this + ); + } + + public function notification(): NotificationManager + { + return self::$instance["notification"] ??= new NotificationManager( + $this + ); + } + } diff --git a/components/ILIAS/Blog/Service/class.InternalGUIService.php b/components/ILIAS/Blog/Service/class.InternalGUIService.php index 9c2c2c6985ae..ed88602e9a9e 100755 --- a/components/ILIAS/Blog/Service/class.InternalGUIService.php +++ b/components/ILIAS/Blog/Service/class.InternalGUIService.php @@ -29,17 +29,13 @@ class InternalGUIService { use GlobalDICGUIServices; - protected InternalDataService $data_service; - protected InternalDomainService $domain_service; protected static array $instance = []; public function __construct( Container $DIC, - InternalDataService $data_service, - InternalDomainService $domain_service + protected InternalDataService $data_service, + protected InternalDomainService $domain_service ) { - $this->data_service = $data_service; - $this->domain_service = $domain_service; $this->initGUIServices($DIC); } diff --git a/components/ILIAS/Blog/Service/class.InternalRepoService.php b/components/ILIAS/Blog/Service/class.InternalRepoService.php index 00b0b704557f..7d03347b0b89 100755 --- a/components/ILIAS/Blog/Service/class.InternalRepoService.php +++ b/components/ILIAS/Blog/Service/class.InternalRepoService.php @@ -21,6 +21,7 @@ namespace ILIAS\Blog; use ILIAS\Blog\Settings\SettingsDBRepository; +use ILIAS\Blog\Posting\PostingDBRepository; class InternalRepoService { @@ -39,4 +40,12 @@ public function settings(): SettingsDBRepository $this->data ); } + + public function posting(): PostingDBRepository + { + return self::$instance["posting"] ??= new PostingDBRepository( + $this->db, + $this->data + ); + } } diff --git a/components/ILIAS/Blog/Settings/class.BlockSettingsGUI.php b/components/ILIAS/Blog/Settings/class.BlockSettingsGUI.php index 07dbf2de4f6c..309b4fc16c0b 100644 --- a/components/ILIAS/Blog/Settings/class.BlockSettingsGUI.php +++ b/components/ILIAS/Blog/Settings/class.BlockSettingsGUI.php @@ -25,7 +25,7 @@ use ILIAS\Repository\Form\FormAdapterGUI; use ILIAS\Blog\InternalDataService; use ILIAS\UI\URLBuilder; -use ILIAS\UI\Component\Table\OrderingBinding; +use ILIAS\UI\Component\Table\OrderingRetrieval; use ILIAS\UI\Component\Table\OrderingRowBuilder; use ILIAS\Data\URI; use ILIAS\UI\Component\Table\Ordering; @@ -85,7 +85,7 @@ protected function getTable(): Ordering $settings, $this->obj_id, $this->in_repository - ) implements OrderingBinding { + ) implements OrderingRetrieval { protected array $records; public function __construct( @@ -115,7 +115,7 @@ public function getRows( $target = $ctrl->getLinkTargetByClass([self::class], "saveOrder"); $target = (new URI(ILIAS_HTTP_PATH . "/" . $target)); - $table = $f->table()->ordering($lng->txt("blog_nav_sortorder"), $columns, $data_retrieval, $target) + $table = $f->table()->ordering($data_retrieval, $target, $lng->txt("blog_nav_sortorder"), $columns) ->withActions($actions) ->withRequest($request); diff --git a/components/ILIAS/Blog/Tasks/classes/class.ilBlogDraftsDerivedTaskProvider.php b/components/ILIAS/Blog/Tasks/classes/class.ilBlogDraftsDerivedTaskProvider.php index 6351c80c0f4e..82c19daf2bc2 100755 --- a/components/ILIAS/Blog/Tasks/classes/class.ilBlogDraftsDerivedTaskProvider.php +++ b/components/ILIAS/Blog/Tasks/classes/class.ilBlogDraftsDerivedTaskProvider.php @@ -53,20 +53,23 @@ public function getTasks(int $user_id): array { $tasks = []; - $blogs = ilBlogPosting::searchBlogsByAuthor($user_id); + global $DIC; + $mgr = $DIC->blog()->internal()->domain()->posting(); + $blogs = $mgr->searchBlogsByAuthor($user_id); foreach ($blogs as $blog_id) { - $posts = ilBlogPosting::getAllPostings($blog_id); - foreach ($posts as $post_id => $post) { - if ((int) $post['author'] !== $user_id) { + $posts = $mgr->getAllByBlog($blog_id); + foreach ($posts as $post) { + $post_id = $post->getId(); + if ($post->getAuthor() !== $user_id) { continue; } - $active = ilBlogPosting::_lookupActive($post_id, "blp"); - $withdrawn = $post['last_withdrawn']->get(IL_CAL_DATETIME); + $active = ilBlogPosting::_lookupActive($post->getId(), "blp"); + $withdrawn = $post->getLastWithdrawn()?->get(IL_CAL_DATETIME); if (!$active && $withdrawn === null) { $refId = $this->getFirstRefIdWithPermission('read', $blog_id, $user_id); $wspId = 0; - $url = $this->gui->permanentLink($refId)->getPermanentLink($post_id, true); + $url = $this->gui->permanentLink($refId)->getPermanentLink($post->getId(), true); if ($refId === 0) { $wspId = $this->getWspId($blog_id, $user_id); @@ -78,7 +81,7 @@ public function getTasks(int $user_id): array $title = sprintf( $this->lng->txt('blog_task_publishing_draft_title'), - $post['title'] + $post->getTitle(), ); $task = $this->taskService->derived()->factory()->task( diff --git a/components/ILIAS/Blog/classes/class.ilObjBlog.php b/components/ILIAS/Blog/classes/class.ilObjBlog.php index 066d19338584..b7765b17b55d 100755 --- a/components/ILIAS/Blog/classes/class.ilObjBlog.php +++ b/components/ILIAS/Blog/classes/class.ilObjBlog.php @@ -44,13 +44,15 @@ public function __construct( global $DIC; $this->notes_service = $DIC->notes(); + $this->settings_manager = $DIC->blog()->internal()->domain()->blogSettings(); + parent::__construct($a_id, $a_reference); $this->rbac_review = $DIC->rbac()->review(); $this->content_style_domain = $DIC ->contentStyle() ->domain(); - $this->settings_manager = $DIC->blog()->internal()->domain()->blogSettings(); + if ($this->getId() > 0) { $this->blog_settings = $this->settings_manager->getByObjId($this->getId()); } @@ -161,92 +163,13 @@ public static function sendNotification( ): void { global $DIC; - $ilUser = $DIC->user(); - - // get blog object id (repository or workspace) - if ($a_in_wsp) { - $tree = new ilWorkspaceTree($ilUser->getId()); // owner of tree is irrelevant - $blog_obj_id = $tree->lookupObjectId($a_blog_node_id); - $access_handler = new ilWorkspaceAccessHandler($tree); - } else { - $blog_obj_id = ilObject::_lookupObjId($a_blog_node_id); - $access_handler = null; - } - if (!$blog_obj_id) { - return; - } - - $posting = new ilBlogPosting($a_posting_id); - - // #11138 - $ignore_threshold = ($a_action === "comment"); - - $admin_only = false; - - // approval handling - if (!$posting->isApproved()) { - $blog_settings = $DIC->blog()->internal()->domain()->blogSettings()->getByObjId($blog_obj_id); - if ($blog_settings?->getApproval()) { - switch ($a_action) { - case "update": - // un-approved posting was updated - no notifications - return; - - case "new": - // un-approved posting was activated - admin-only notification - $admin_only = true; - $ignore_threshold = true; - $a_action = "approve"; - break; - } - } - } - - // create/update news item (only in repository) - if (!$a_in_wsp && - in_array($a_action, array("update", "new"))) { - $posting->handleNews(($a_action === "update")); - } - - // recipients - $users = ilNotification::getNotificationsForObject( - ilNotification::TYPE_BLOG, - $blog_obj_id, + $DIC->blog()->internal()->domain()->notification()->sendNotification( + $a_action, + $a_in_wsp, + $a_blog_node_id, $a_posting_id, - $ignore_threshold + $a_comment ); - if (!count($users)) { - return; - } - - $ntf = new ilSystemNotification($a_in_wsp); - $ntf->setLangModules(array("blog")); - $ntf->setRefId($a_blog_node_id); - $ntf->setChangedByUserId($ilUser->getId()); - $ntf->setSubjectLangId('blog_change_notification_subject'); - $ntf->setIntroductionLangId('blog_change_notification_body_' . $a_action); - $ntf->addAdditionalInfo('blog_posting', $posting->getTitle()); - if ($a_comment) { - $ntf->addAdditionalInfo('comment', $a_comment, true); - } - $ntf->setGotoLangId('blog_change_notification_link'); - $ntf->setReasonLangId('blog_change_notification_reason'); - - $abstract = $posting->getNotificationAbstract(); - if ($abstract) { - $ntf->addAdditionalInfo('content', $abstract, true); - } - - $notified = $ntf->sendMailAndReturnRecipients( - $users, - "_" . $a_posting_id, - ($admin_only ? "write" : "read") - ); - - // #14387 - if (count($notified)) { - ilNotification::updateNotificationTime(ilNotification::TYPE_BLOG, $blog_obj_id, $notified, $a_posting_id); - } } /** diff --git a/components/ILIAS/Blog/classes/class.ilObjBlogGUI.php b/components/ILIAS/Blog/classes/class.ilObjBlogGUI.php index 944e86c5c513..56bbad0d9fa4 100755 --- a/components/ILIAS/Blog/classes/class.ilObjBlogGUI.php +++ b/components/ILIAS/Blog/classes/class.ilObjBlogGUI.php @@ -22,6 +22,13 @@ use ILIAS\Blog\StandardGUIRequest; use ILIAS\Blog\Settings\SettingsGUI; use ILIAS\Blog\Export\BlogHtmlExport; +use ILIAS\Blog\Permission\PermissionManager; +use ILIAS\Blog\InternalDomainService; +use ILIAS\Blog\InternalGUIService; +use ILIAS\Repository\Profile\ProfileAdapter; +use ILIAS\Repository\Profile\ProfileGUI; +use ILIAS\Blog\Settings\Settings; +use ILIAS\Blog\ReadingTime\ReadingTimeManager; /** * @ilCtrl_Calls ilObjBlogGUI: ilBlogPostingGUI, ilWorkspaceAccessGUI @@ -34,16 +41,16 @@ */ class ilObjBlogGUI extends ilObject2GUI implements ilDesktopItemHandling { - protected ?\ILIAS\Blog\Settings\Settings $blog_settings = null; - protected \ILIAS\Repository\Profile\ProfileGUI $profile_gui; - protected \ILIAS\Repository\Profile\ProfileAdapter $profile; - protected \ILIAS\Blog\Access\BlogAccess $blog_access; - protected \ILIAS\Blog\InternalDomainService $domain; - protected \ILIAS\Blog\InternalGUIService $gui; + protected ?Settings $blog_settings = null; + protected ProfileGUI $profile_gui; + protected ProfileAdapter $profile; + protected PermissionManager $perm; + protected InternalDomainService $domain; + protected InternalGUIService $gui; protected string $rendered_content = ""; protected \ILIAS\Notes\Service $notes; protected \ILIAS\Blog\ReadingTime\BlogSettingsGUI $reading_time_gui; - protected \ILIAS\Blog\ReadingTime\ReadingTimeManager $reading_time_manager; + protected ReadingTimeManager $reading_time_manager; protected StandardGUIRequest $blog_request; protected ilHelpGUI $help; @@ -66,7 +73,6 @@ class ilObjBlogGUI extends ilObject2GUI implements ilDesktopItemHandling protected string $new_type = ""; protected bool $disable_notes = false; protected ContextServices $tool_context; - protected \ILIAS\HTTP\Services $http; protected \ILIAS\DI\UIServices $ui; protected \ILIAS\Style\Content\GUIService $content_style_gui; protected \ILIAS\Style\Content\Object\ObjectFacade $content_style_domain; @@ -103,7 +109,6 @@ public function __construct( $this->toolbar = $gui->toolbar(); $this->ui = $gui->ui(); $this->locator = $gui->locator(); - $this->http = $gui->http(); $this->nav_history = $DIC["ilNavigationHistory"]; $this->ctrl = $gui->ctrl(); @@ -166,7 +171,7 @@ public function __construct( $this->reading_time_manager = $domain->readingTime(); $this->notes = $DIC->notes(); $owner = $this->object?->getOwner() ?? 0; - $this->blog_access = $domain->blogAccess( + $this->perm = $domain->perm( $this->getAccessHandler(), $this->node_id, $this->id_type, @@ -309,7 +314,7 @@ protected function setTabs(): void } } - if ($this->blog_access->mayContribute()) { + if ($this->perm->mayContribute()) { $this->tabs_gui->addNonTabbedLink( "preview", $lng->txt("blog_preview"), @@ -366,7 +371,7 @@ public function executeCommand(): void $this->blpg, $this->old_nr, ($this->object->getNotesStatus() && !$this->disable_notes), - $this->blog_access->mayEditPosting($this->blpg), + $this->perm->mayEditPosting($this->blpg), $style_sheet_id ); @@ -409,7 +414,7 @@ public function executeCommand(): void if ($ret != "") { // $is_owner = $this->object->getOwner() == $ilUser->getId(); - $is_owner = $this->blog_access->mayContribute(); + $is_owner = $this->perm->mayContribute(); $is_active = $bpost_gui->getBlogPosting()->getActive(); // do not show inactive postings @@ -738,7 +743,7 @@ public function render(): void $ilTabs->activateTab("content"); // toolbar - if ($this->blog_access->mayContribute()) { + if ($this->perm->mayContribute()) { $ilToolbar->setFormAction($ilCtrl->getFormAction($this, "createPosting")); $title = new ilTextInputGUI($lng->txt("title"), "title"); @@ -784,7 +789,7 @@ public function render(): void } // $is_owner = ($this->object->getOwner() == $ilUser->getId()); - $is_owner = $this->blog_access->mayContribute(); + $is_owner = $this->perm->mayContribute(); $list_items = $this->getListItems($is_owner); @@ -910,7 +915,7 @@ public function renderFullScreen( $back = "ilias.php?baseClass=ilDashboardGUI&cmd=jumpToWorkspace&wsp_id=" . $this->node_id; } // from editor (#10073) - elseif ($this->blog_access->mayContribute()) { + elseif ($this->perm->mayContribute()) { $this->ctrl->setParameter($this, "prvm", ""); if ($this->blpg === 0) { $back = $this->ctrl->getLinkTarget($this, ""); @@ -931,7 +936,7 @@ public function renderFullScreen( $back = "ilias.php?baseClass=ilDashboardGUI&cmd=jumpToWorkspace&dsh=" . $owner; } // contributor - elseif ($this->blog_access->mayContribute()) { + elseif ($this->perm->mayContribute()) { $back = $this->ctrl->getLinkTarget($this, ""); $back_caption = $this->lng->txt("blog_back_to_blog_owner"); } @@ -981,7 +986,7 @@ public function renderFullscreenHeader( } $ppic = ""; - if ($this->blog_settings?->getProfilePicture()) { + if ($this->blog_settings?->getProfilePicture() && !$a_export) { // repository (multi-user) if ($this->id_type === self::REPOSITORY_NODE_ID) { // #15030 @@ -1002,6 +1007,8 @@ public function renderFullscreenHeader( $ppic = basename($ppic); } } + } else { + $ppic = ilUtil::getImagePath("standard/icon_blog.svg"); } $a_tpl->resetHeaderBlock(false); $a_tpl->setTitleIcon($ppic); @@ -1058,7 +1065,7 @@ public function renderList( $wtpl = new ilTemplate("tpl.blog_list.html", true, true, "components/ILIAS/Blog"); - $is_admin = $this->isAdmin(); + $is_admin = $this->perm->canManage(); $last_month = null; $is_empty = true; @@ -1105,7 +1112,7 @@ public function renderList( $more_link = $preview; // actions - $posting_edit = $this->blog_access->mayEditPosting($item["id"], $item["author"]); + $posting_edit = $this->perm->mayEditPosting($item["id"], $item["author"]); if (($posting_edit || $is_admin) && !$a_link_template && $a_cmd === "preview") { $actions = []; @@ -1211,11 +1218,12 @@ public function renderList( } // permanent link - if ($a_cmd !== "preview") { + if ($this->node_id !== null && + $a_cmd !== "preview") { if ($this->id_type === self::WORKSPACE_NODE_ID) { - $goto = $this->gui->permanentLink(0, $this->node_id)->getPermanentLink((int) $item["id"]); + $goto = $this->gui->permanentLink(0, (int) $this->node_id)->getPermanentLink((int) $item["id"]); } else { - $goto = $this->gui->permanentLink($this->node_id)->getPermanentLink((int) $item["id"]); + $goto = $this->gui->permanentLink((int) $this->node_id)->getPermanentLink((int) $item["id"]); } $wtpl->setCurrentBlock("permalink"); $wtpl->setVariable("URL_PERMALINK", $goto); @@ -1679,7 +1687,7 @@ public function renderToolbarNavigation( ): void { $nav_renderer = $this->gui->navigation()->toolbarNavigationRenderer(); $nav_renderer->renderToolbarNavigation( - $this->blog_access, + $this->perm, $a_items, $this->blpg, $single_posting, @@ -1729,7 +1737,7 @@ public function renderNavigation( if ($this->blog_settings->getKeywords()) { // keywords $may_edit_keywords = ($blpg > 0 && - $this->blog_access->mayEditPosting($blpg) && + $this->perm->mayEditPosting($blpg) && $a_list_cmd !== "preview" && $a_list_cmd !== "gethtml" && !$a_link_template); @@ -1865,15 +1873,15 @@ public function buildExportFile( } // create export file - ilExport::_createExportDirectory($this->object->getId(), $type, "blog"); - $exp_dir = ilExport::_getExportDirectory($this->object->getId(), $type, "blog"); + //ilExport::_createExportDirectory($this->object->getId(), $type, "blog"); + //$exp_dir = ilExport::_getExportDirectory($this->object->getId(), $type, "blog"); $subdir = $this->object->getType() . "_" . $this->object->getId(); if ($print_version) { $subdir .= "print"; } - $blog_export = new BlogHtmlExport($this, $exp_dir, $subdir); + $blog_export = new BlogHtmlExport($this, "", $subdir); $blog_export->setPrintVersion($print_version); $blog_export->includeComments($a_include_comments); $blog_export->exportHTML(); @@ -1971,7 +1979,7 @@ protected function initHeaderAction( } // #11758 - if ($this->blog_access->mayContribute()) { + if ($this->perm->mayContribute()) { $ilCtrl->setParameter($this, "prvm", ""); $ilCtrl->setParameter($this, "bmn", ""); @@ -1981,7 +1989,7 @@ protected function initHeaderAction( $ilCtrl->setParameter($this, "bmn", $this->month); $lg->addCustomCommand($link, "blog_edit"); // #11868 - if ($sub_id && $this->blog_access->mayEditPosting($sub_id)) { + if ($sub_id && $this->perm->mayEditPosting($sub_id)) { $link = $ilCtrl->getLinkTargetByClass("ilblogpostinggui", "edit"); $lg->addCustomCommand($link, "blog_edit_posting"); } @@ -2070,15 +2078,6 @@ public function filterItemsByKeyword( return $res; } - /** - * Check if user has admin access (approve, may edit & deactivate all postings) - */ - protected function isAdmin(): bool - { - return ($this->checkPermissionBool("redact") || - $this->checkPermissionBool("write")); - } - protected function addLocatorItems(): void { $ilLocator = $this->locator; @@ -2090,7 +2089,7 @@ protected function addLocatorItems(): void public function approve(): void { - if ($this->isAdmin() && $this->apid > 0) { + if ($this->perm->canManage() && $this->apid > 0) { $post = new ilBlogPosting($this->apid); $post->setApproved(true); $post->setBlogNodeId($this->node_id, ($this->id_type == self::WORKSPACE_NODE_ID)); @@ -2142,13 +2141,17 @@ public function contributors(): void $this->tpl->setOnScreenMessage('info', sprintf($lng->txt("blog_contribute_other_roles"), implode(", ", $other_roles))); } - $tbl = $this->gui->contributor()->ilContributorTableGUI( + $table = $this->gui->contributor()->contributorTableBuilder( + $this->object->getAllLocalRoles($this->node_id), $this, - "contributors", - $this->object->getAllLocalRoles($this->node_id) - ); + "contributors" + )->getTable(); - $tpl->setContent($tbl->getHTML()); + if ($table->handleCommand()) { + return; + } + + $tpl->setContent($table->render()); } /** @@ -2228,12 +2231,13 @@ public function addContributor( } /** - * Used in ilContributorTableGUI + * Used in ContributorTableBuilder */ - public function confirmRemoveContributor(): void + public function confirmRemoveContributor(array $ids = []): void { - $ids = $this->blog_request->getIds(); - + if (empty($ids)) { + $ids = $this->blog_request->getIds(); + } if (count($ids) === 0) { $this->tpl->setOnScreenMessage('failure', $this->lng->txt("select_one"), true); $this->ctrl->redirect($this, "contributors"); @@ -2287,6 +2291,20 @@ public function removeContributor(): void $this->ctrl->redirect($this, "contributors"); } + /** + * Used in ContributorTableBuilder + */ + public function addContributorContainerAction(array $ids = []): void + { + if (empty($ids)) { + $ids = $this->blog_request->getIds(); + } + + // This would typically add contributors from a container + // For now, redirecting back to contributors as this seems to be a placeholder action + $this->ctrl->redirect($this, "contributors"); + } + public function deactivateAdmin(): void { if ($this->checkPermissionBool("write") && $this->apid > 0) { diff --git a/components/ILIAS/BookingManager/Access/AccessManager.php b/components/ILIAS/BookingManager/Access/AccessManager.php index f0918dce33f3..fb068a76e151 100644 --- a/components/ILIAS/BookingManager/Access/AccessManager.php +++ b/components/ILIAS/BookingManager/Access/AccessManager.php @@ -168,4 +168,20 @@ public function canManageMembersOfParent(int $ref_id): bool } return false; } + + public function validateBookingObjId(int $book_obj_id, int $pool_id): void + { + if ($book_obj_id > 0 && \ilBookingObject::lookupPoolId($book_obj_id) !== $pool_id) { + throw new \ilPermissionException("Booking object pool id does not match pool id."); + } + } + + public function validateScheduleId(int $schedule_id, int $pool_id): void + { + $sm = $this->domain->schedules($pool_id); + if (!$sm->hasScheduleId($schedule_id)) { + throw new \ilPermissionException("Schedule id does not match pool id."); + } + } + } diff --git a/components/ILIAS/BookingManager/Objects/class.ObjectsDBRepository.php b/components/ILIAS/BookingManager/Objects/class.ObjectsDBRepository.php index 478531787dde..9a4a5151618f 100755 --- a/components/ILIAS/BookingManager/Objects/class.ObjectsDBRepository.php +++ b/components/ILIAS/BookingManager/Objects/class.ObjectsDBRepository.php @@ -117,10 +117,10 @@ public function deleteObjectInfo(int $booking_object_id): bool } } - public function getObjectInfoPath(int $booking_object_id): void + public function getObjectInfoPath(int $booking_object_id): string { $rid = $this->getObjectInfoRidForBookingObjectId($booking_object_id); - $this->wrapper->getResourcePath($rid); + return $this->wrapper->getResourcePath($rid); } public function deliverObjectInfo(int $booking_object_id): void @@ -195,10 +195,10 @@ public function deliverBookingInfo(int $booking_object_id): void $this->wrapper->deliverFile($rid); } - public function getBookingInfoPath(int $booking_object_id): void + public function getBookingInfoPath(int $booking_object_id): string { $rid = $this->getBookingInfoRidForBookingObjectId($booking_object_id); - $this->wrapper->getResourcePath($rid); + return $this->wrapper->getResourcePath($rid); } public function getBookingInfoFilename(int $booking_object_id): string diff --git a/components/ILIAS/BookingManager/Objects/class.ObjectsManager.php b/components/ILIAS/BookingManager/Objects/class.ObjectsManager.php index 1334e383de89..d66dd86da2c3 100755 --- a/components/ILIAS/BookingManager/Objects/class.ObjectsManager.php +++ b/components/ILIAS/BookingManager/Objects/class.ObjectsManager.php @@ -136,6 +136,11 @@ public function importBookingInfoFromLegacyUpload(int $booking_obj_id, array $fi ); } + public function hasObjectInfo(int $booking_obj_id): bool + { + return $this->object_repo->hasObjectInfo($booking_obj_id); + } + public function deliverObjectInfo(int $booking_obj_id): void { if ($this->object_repo->hasObjectInfo($booking_obj_id)) { @@ -143,6 +148,11 @@ public function deliverObjectInfo(int $booking_obj_id): void } } + public function hasBookingInfo(int $booking_obj_id): bool + { + return $this->object_repo->hasBookingInfo($booking_obj_id); + } + public function deliverBookingInfo(int $booking_obj_id): void { if ($this->object_repo->hasBookingInfo($booking_obj_id)) { @@ -167,7 +177,7 @@ public function deleteObjectInfo(int $booking_obj_id): void } } - public function deleteBookingInfo(int $booking_obj_id): string + public function deleteBookingInfo(int $booking_obj_id): void { if ($this->object_repo->hasBookingInfo($booking_obj_id)) { $this->object_repo->deleteBookingInfo($booking_obj_id); @@ -184,12 +194,12 @@ public function cloneTo( } } - public function getObjectInfoPath(int $booking_object_id): int + public function getObjectInfoPath(int $booking_object_id): string { return $this->object_repo->getObjectInfoPath($booking_object_id); } - public function getBookingInfoPath(int $booking_object_id): int + public function getBookingInfoPath(int $booking_object_id): string { return $this->object_repo->getBookingInfoPath($booking_object_id); } diff --git a/components/ILIAS/BookingManager/Objects/class.ilBookBulkCreationGUI.php b/components/ILIAS/BookingManager/Objects/class.ilBookBulkCreationGUI.php index 0ef6b0c125f6..b2179bb24740 100755 --- a/components/ILIAS/BookingManager/Objects/class.ilBookBulkCreationGUI.php +++ b/components/ILIAS/BookingManager/Objects/class.ilBookBulkCreationGUI.php @@ -160,7 +160,7 @@ protected function renderConfirmation(string $data, int $schedule_id = 0): strin "#" )->withAdditionalOnLoadCode(static function (string $id) { return << { book_bulk_button.closest(".c-modal").querySelector(".modal-body").querySelector("form").submit(); }); diff --git a/components/ILIAS/BookingManager/Objects/class.ilBookingBulkCreationTableGUI.php b/components/ILIAS/BookingManager/Objects/class.ilBookingBulkCreationTableGUI.php index d818df39daec..d2a84a038e7b 100755 --- a/components/ILIAS/BookingManager/Objects/class.ilBookingBulkCreationTableGUI.php +++ b/components/ILIAS/BookingManager/Objects/class.ilBookingBulkCreationTableGUI.php @@ -38,6 +38,7 @@ public function __construct( ->internal() ->domain() ->objects($pool_id); + $html_util = $DIC->bookingManager()->internal()->gui()->html(); $ctrl = $DIC->ctrl(); $lng = $DIC->language(); @@ -55,7 +56,10 @@ public function __construct( "tpl.bulk_creation_row.html", "components/ILIAS/BookingManager/Objects" ); - $this->addHiddenInput("data", $raw_data); + $this->addHiddenInput( + "data", + $html_util->escape($raw_data) + ); } protected function fillRow(array $a_set): void diff --git a/components/ILIAS/BookingManager/Objects/class.ilBookingObjectGUI.php b/components/ILIAS/BookingManager/Objects/class.ilBookingObjectGUI.php index 3e2e893fc35f..0b858bfc4d0f 100755 --- a/components/ILIAS/BookingManager/Objects/class.ilBookingObjectGUI.php +++ b/components/ILIAS/BookingManager/Objects/class.ilBookingObjectGUI.php @@ -109,6 +109,11 @@ public function __construct( $this->rsv_ids = array_map('intval', $this->book_request->getReservationIdsFromString()); $this->objects_manager = $DIC->bookingManager()->internal()->domain()->objects($this->pool->getId()); + + $this->access->validateBookingObjId( + $this->object_id, + (int) $this->pool_gui->getObject()?->getId() + ); } public function activateManagement(bool $a_val): void diff --git a/components/ILIAS/BookingManager/Schedule/class.ScheduleManager.php b/components/ILIAS/BookingManager/Schedule/class.ScheduleManager.php index f986bf7af9f6..17b92ae1567d 100755 --- a/components/ILIAS/BookingManager/Schedule/class.ScheduleManager.php +++ b/components/ILIAS/BookingManager/Schedule/class.ScheduleManager.php @@ -64,4 +64,11 @@ public function getScheduleData(): array { return $this->schedule_repo->getScheduleData($this->pool_id); } + + public function hasScheduleId(int $schedule_id): bool + { + $pool_id = $this->schedule_repo->getPoolIdForSchedule($schedule_id); + return ($pool_id === $this->pool_id); + } + } diff --git a/components/ILIAS/BookingManager/Schedule/class.SchedulesDBRepository.php b/components/ILIAS/BookingManager/Schedule/class.SchedulesDBRepository.php index aa310ffb2a22..415e43d49576 100755 --- a/components/ILIAS/BookingManager/Schedule/class.SchedulesDBRepository.php +++ b/components/ILIAS/BookingManager/Schedule/class.SchedulesDBRepository.php @@ -100,4 +100,15 @@ public function getScheduleData(int $pool_id): array return $schedules; } + public function getPoolIdForSchedule(int $schedule_id): int + { + $set = $this->db->query("SELECT pool_id " . + " FROM booking_schedule" . + " WHERE booking_schedule_id = " . $this->db->quote($schedule_id, 'integer')); + if ($rec = $this->db->fetchAssoc($set)) { + return (int) $rec['pool_id']; + } + return 0; + } + } diff --git a/components/ILIAS/BookingManager/Schedule/class.ilBookingScheduleGUI.php b/components/ILIAS/BookingManager/Schedule/class.ilBookingScheduleGUI.php index 4dab27040902..e3aeae2b2e87 100755 --- a/components/ILIAS/BookingManager/Schedule/class.ilBookingScheduleGUI.php +++ b/components/ILIAS/BookingManager/Schedule/class.ilBookingScheduleGUI.php @@ -53,6 +53,13 @@ public function __construct( ->gui() ->standardRequest(); $this->schedule_id = $this->book_request->getScheduleId(); + + if ($this->schedule_id > 0) { + $this->access->validateScheduleId( + $this->schedule_id, + ilObject::_lookupObjId($this->ref_id) + ); + } } public function executeCommand(): void diff --git a/components/ILIAS/CAS/classes/class.ilAuthProviderCAS.php b/components/ILIAS/CAS/classes/class.ilAuthProviderCAS.php deleted file mode 100755 index 503480752f0d..000000000000 --- a/components/ILIAS/CAS/classes/class.ilAuthProviderCAS.php +++ /dev/null @@ -1,143 +0,0 @@ - - */ -class ilAuthProviderCAS extends ilAuthProvider -{ - private ilCASSettings $settings; - - public function __construct(ilAuthCredentials $credentials) - { - parent::__construct($credentials); - $this->settings = ilCASSettings::getInstance(); - } - - protected function getSettings(): ilCASSettings - { - return $this->settings; - } - - public function doAuthentication(ilAuthStatus $status): bool - { - $this->getLogger()->debug('Starting cas authentication attempt... '); - - try { - // Uncomment the following line to get trace-level loggin by CAS - //phpCAS::setLogger($this->getLogger()); - // Caution: If you set this to "true", there might be output - // and the redirect won't work and you get an ILIAS Whoopsy - // Though, you may need to for debugging other issues. - phpCAS::setVerbose(false); - $this->getLogger()->debug('Create client... '); - phpCAS::client( - CAS_VERSION_2_0, - $this->getSettings()->getServer(), - $this->getSettings()->getPort(), - $this->getSettings()->getUri(), - ilUtil::_getHttpPath() - ); - - phpCAS::setNoCasServerValidation(); - $this->getLogger()->debug('Fore CAS auth... '); - phpCAS::forceAuthentication(); - $this->getLogger()->debug('Fore CAS auth done.'); - } catch (Exception $e) { - $this->getLogger()->error('Cas authentication failed with message: ' . $e->getMessage()); - $this->handleAuthenticationFail($status, 'err_wrong_login'); - return false; - } - - if (phpCAS::getUser() === '') { - $this->getLogger()->debug('CAS user is empty.'); - return $this->handleAuthenticationFail($status, 'err_wrong_login'); - } - $this->getCredentials()->setUsername(phpCAS::getUser()); - $this->getLogger()->debug('user name set to CAS user.'); - - // check and handle ldap data sources - if (ilLDAPServer::isDataSourceActive(ilAuthUtils::AUTH_CAS)) { - return $this->handleLDAPDataSource($status); - } - - // Check account available - $local_user = ilObjUser::_checkExternalAuthAccount("cas", $this->getCredentials()->getUsername()); - if ($local_user !== '' && $local_user !== null) { - $this->getLogger()->debug('CAS authentication successful.'); - $status->setStatus(ilAuthStatus::STATUS_AUTHENTICATED); - $status->setAuthenticatedUserId(ilObjUser::_lookupId($local_user)); - return true; - } - - if (!$this->getSettings()->isUserCreationEnabled()) { - $this->getLogger()->debug('User creation disabled. No valid local account found'); - $this->handleAuthenticationFail($status, 'err_auth_cas_no_ilias_user'); - return false; - } - - $importer = new ilCASAttributeToUser($this->getSettings()); - $new_name = $importer->create($this->getCredentials()->getUsername()); - - if ($new_name === '') { - $this->getLogger()->debug('User creation failed.'); - $this->handleAuthenticationFail($status, 'err_auth_cas_no_ilias_user'); - return false; - } - - $status->setStatus(ilAuthStatus::STATUS_AUTHENTICATED); - $status->setAuthenticatedUserId(ilObjUser::_lookupId($new_name)); - return true; - } - - protected function handleLDAPDataSource(ilAuthStatus $status): bool - { - $server = ilLDAPServer::getInstanceByServerId( - ilLDAPServer::getDataSource(ilAuthUtils::AUTH_CAS) - ); - - $this->getLogger()->debug('Using ldap data source for user: ' . $this->getCredentials()->getUsername()); - - $sync = new ilLDAPUserSynchronisation('cas', $server->getServerId()); - $sync->setExternalAccount($this->getCredentials()->getUsername()); - $sync->setUserData(array()); - $sync->forceCreation(true); - - try { - $internal_account = $sync->sync(); - } catch (UnexpectedValueException $e) { - $this->getLogger()->warning('Authentication failed with message: ' . $e->getMessage()); - $this->handleAuthenticationFail($status, 'err_wrong_login'); - return false; - } catch (ilLDAPSynchronisationFailedException $e) { - $this->handleAuthenticationFail($status, 'err_auth_ldap_failed'); - return false; - } catch (ilLDAPSynchronisationForbiddenException|ilLDAPAccountMigrationRequiredException $e) { - // No syncronisation allowed => create Error - $this->getLogger()->warning('User creation disabled. No valid local account found'); - $this->handleAuthenticationFail($status, 'err_auth_cas_no_ilias_user'); - return false; - } - $status->setStatus(ilAuthStatus::STATUS_AUTHENTICATED); - $status->setAuthenticatedUserId(ilObjUser::_lookupId($internal_account)); - return true; - } -} diff --git a/components/ILIAS/CAS/classes/class.ilCASAttributeToUser.php b/components/ILIAS/CAS/classes/class.ilCASAttributeToUser.php deleted file mode 100755 index c163d8838042..000000000000 --- a/components/ILIAS/CAS/classes/class.ilCASAttributeToUser.php +++ /dev/null @@ -1,87 +0,0 @@ - - */ -class ilCASAttributeToUser -{ - private ilLogger $logger; - private ilXmlWriter $writer; - private ilCASSettings $settings; - - public function __construct(\ilCASSettings $settings) - { - global $DIC; - - $this->logger = $DIC->logger()->auth(); - - $this->writer = new ilXmlWriter(); - - $this->settings = $settings; - } - - public function create(string $a_username): string - { - $this->writer->xmlStartTag('Users'); - - $this->writer->xmlStartTag('User', array('Action' => 'Insert')); - $new_name = ilAuthUtils::_generateLogin($a_username); - $this->writer->xmlElement('Login', array(), $new_name); - - // Assign to role only for new users - $this->writer->xmlElement( - 'Role', - array( - 'Id' => $this->settings->getDefaultRole(), - 'Type' => 'Global', - 'Action' => 'Assign'), - '' - ); - - $this->writer->xmlElement('Active', array(), "true"); - $this->writer->xmlElement('TimeLimitOwner', array(), 7); - $this->writer->xmlElement('TimeLimitUnlimited', array(), 1); - $this->writer->xmlElement('TimeLimitFrom', array(), time()); - $this->writer->xmlElement('TimeLimitUntil', array(), time()); - $this->writer->xmlElement('AuthMode', array('type' => 'cas'), 'cas'); - $this->writer->xmlElement('ExternalAccount', array(), $a_username); - - $this->writer->xmlEndTag('User'); - $this->writer->xmlEndTag('Users'); - - $this->logger->info('CAS: Startet creation of user: ' . $new_name); - - $importParser = new ilUserImportParser(); - $importParser->setXMLContent($this->writer->xmlDumpMem(false)); - $importParser->setRoleAssignment( - array( - $this->settings->getDefaultRole() => $this->settings->getDefaultRole() - ) - ); - //TODO check if there is a constant - $importParser->setFolderId(7); - $importParser->startParsing(); - - return $new_name; - } -} diff --git a/components/ILIAS/CAS/classes/class.ilCASSettings.php b/components/ILIAS/CAS/classes/class.ilCASSettings.php deleted file mode 100755 index af1c673fa760..000000000000 --- a/components/ILIAS/CAS/classes/class.ilCASSettings.php +++ /dev/null @@ -1,173 +0,0 @@ - - */ -class ilCASSettings -{ - public const SYNC_DISABLED = 0; - public const SYNC_CAS = 1; - public const SYNC_LDAP = 2; - - private static ?ilCASSettings $instance = null; - - private ilSetting $storage; - private string $server = ''; - private int $port = 0; - private string $uri = ''; - private string $login_instructions = ''; - private bool $active = false; - private bool $create_users = false; - private bool $allow_local = false; - private int $user_default_role = 0; - private int $default_role = 0; - - /** - * Singleton constructor - */ - protected function __construct() - { - global $DIC; - - $this->storage = $DIC->settings(); - $this->read(); - } - - /** - * Get singleton instance - */ - public static function getInstance(): ilCASSettings - { - if (self::$instance) { - return self::$instance; - } - return self::$instance = new ilCASSettings(); - } - - public function setServer(string $a_server): void - { - $this->server = $a_server; - } - - public function getServer(): string - { - return $this->server; - } - - public function setPort(int $a_port): void - { - $this->port = $a_port; - } - - public function getPort(): int - { - return $this->port; - } - - public function setUri(string $a_uri): void - { - $this->uri = $a_uri; - } - - public function getUri(): string - { - return $this->uri; - } - - public function setLoginInstruction(string $a_inst): void - { - $this->login_instructions = $a_inst; - } - - public function getLoginInstruction(): string - { - return $this->login_instructions; - } - - public function setActive($a_active): void - { - $this->active = $a_active; - } - - public function isActive(): bool - { - return $this->active; - } - - public function enableUserCreation($a_uc): void - { - $this->create_users = $a_uc; - } - - public function isUserCreationEnabled(): bool - { - return $this->create_users; - } - - public function enableLocalAuthentication($a_local): void - { - $this->allow_local = $a_local; - } - - public function isLocalAuthenticationEnabled(): bool - { - return $this->allow_local; - } - - public function setDefaultRole($a_role): void - { - $this->default_role = $a_role; - } - - public function getDefaultRole(): int - { - return $this->default_role; - } - - public function save(): void - { - $this->getStorage()->set('cas_server', $this->getServer()); - $this->getStorage()->set('cas_port', (string) $this->getPort()); - $this->getStorage()->set('cas_uri', $this->getUri()); - $this->getStorage()->set('cas_login_instructions', $this->getLoginInstruction()); - $this->getStorage()->set('cas_active', (string) $this->isActive()); - $this->getStorage()->set('cas_create_users', (string) $this->isUserCreationEnabled()); - $this->getStorage()->set('cas_allow_local', (string) $this->isLocalAuthenticationEnabled()); - $this->getStorage()->set('cas_user_default_role', (string) $this->getDefaultRole()); - } - - private function read(): void - { - $this->setServer($this->getStorage()->get('cas_server', $this->server)); - $this->setPort((int) $this->getStorage()->get('cas_port', (string) $this->port)); - $this->setUri($this->getStorage()->get('cas_uri', $this->uri)); - $this->setActive((bool) $this->getStorage()->get('cas_active', (string) $this->active)); - $this->setDefaultRole((int) $this->getStorage()->get('cas_user_default_role', (string) $this->user_default_role)); - $this->setLoginInstruction($this->getStorage()->get('cas_login_instructions', $this->login_instructions)); - $this->enableLocalAuthentication((bool) $this->getStorage()->get('cas_allow_local', (string) $this->allow_local)); - $this->enableUserCreation((bool) $this->getStorage()->get('cas_create_users', (string) $this->create_users)); - } - - private function getStorage(): ilSetting - { - return $this->storage; - } -} diff --git a/components/ILIAS/CAS/classes/class.ilCASSettingsGUI.php b/components/ILIAS/CAS/classes/class.ilCASSettingsGUI.php deleted file mode 100755 index c857ef4c337a..000000000000 --- a/components/ILIAS/CAS/classes/class.ilCASSettingsGUI.php +++ /dev/null @@ -1,286 +0,0 @@ - - */ -class ilCASSettingsGUI -{ - public const SYNC_DISABLED = 0; - public const SYNC_CAS = 1; - public const SYNC_LDAP = 2; - - private ilCASSettings $settings; - - private int $ref_id; - - private \ilGlobalTemplateInterface $tpl; - private ilCtrl $ctrl; - private ilLanguage $lng; - private ilRbacSystem $rbacSystem; - private ilRbacReview $rbacReview; - private ilErrorHandling $ilErr; - - public function __construct(int $a_auth_ref_id) - { - global $DIC; - $this->tpl = $DIC->ui()->mainTemplate(); - - $this->ctrl = $DIC->ctrl(); - $this->rbacSystem = $DIC->rbac()->system(); - $this->rbacReview = $DIC->rbac()->review(); - $this->ilErr = $DIC['ilErr']; - $this->lng = $DIC->language(); - $this->lng->loadLanguageModule('registration'); - $this->lng->loadLanguageModule('auth'); - - $this->renderer = $DIC->ui()->renderer(); - $this->factory = $DIC->ui()->factory(); - - $this->ref_id = $a_auth_ref_id; - - $this->settings = ilCASSettings::getInstance(); - } - - protected function getSettings(): ilCASSettings - { - return $this->settings; - } - - public function executeCommand(): bool - { - $next_class = $this->ctrl->getNextClass($this); - $cmd = $this->ctrl->getCmd("settings"); - - if (!$this->rbacSystem->checkAccess("visible,read", $this->ref_id)) { - $this->ilErr->raiseError($this->lng->txt('msg_no_perm_read'), $this->ilErr->WARNING); - } - - switch ($next_class) { - default: - if (!$cmd) { - $cmd = "settings"; - } - $this->$cmd(); - break; - } - return true; - } - - protected function initFormSettings(): ilPropertyFormGUI - { - $this->lng->loadLanguageModule('auth'); - - $form = new ilPropertyFormGUI(); - $form->setFormAction($this->ctrl->getFormAction($this)); - - $form->setTitle($this->lng->txt('auth_cas_auth')); - - $drop_in_replacements_url = 'https://github.com/ILIAS-eLearning/ILIAS/tree/trunk/components/ILIAS/HTTP#dropinreplacements'; - $drop_in_replacements_link = $this->factory->link()->standard( - $this->lng->txt("auth_cas_auth_desc"), - $drop_in_replacements_url - ); - $form->setDescription($this->renderer->render( - $drop_in_replacements_link - )); - - // Form checkbox - $check = new ilCheckboxInputGUI($this->lng->txt("active"), 'active'); - $check->setChecked($this->getSettings()->isActive()); - $check->setValue("1"); - $form->addItem($check); - - $text = new ilTextInputGUI($this->lng->txt('server'), 'server'); - $text->setValue($this->getSettings()->getServer()); - $text->setRequired(true); - $text->setInfo($this->lng->txt('auth_cas_server_desc')); - $text->setSize(64); - $text->setMaxLength(255); - $form->addItem($text); - - $port = new ilNumberInputGUI($this->lng->txt("port"), 'port'); - $port->setValue((string) $this->getSettings()->getPort()); - $port->setRequired(true); - $port->setMinValue(0); - $port->setMaxValue(65535); - $port->setSize(5); - $port->setMaxLength(5); - $port->setInfo($this->lng->txt('auth_cas_port_desc')); - $form->addItem($port); - - $text = new ilTextInputGUI($this->lng->txt('uri'), 'uri'); - $text->setValue($this->getSettings()->getUri()); - $text->setRequired(true); - $text->setInfo($this->lng->txt('auth_cas_uri_desc')); - $text->setSize(64); - $text->setMaxLength(255); - $form->addItem($text); - - // User synchronization - // 0: Disabled - // 1: CAS - // 2: LDAP - $sync = new ilRadioGroupInputGUI($this->lng->txt('auth_sync'), 'sync'); - $sync->setRequired(true); - $form->addItem($sync); - - // Disabled - $dis = new ilRadioOption( - $this->lng->txt('disabled'), - (string) self::SYNC_DISABLED, - '' - ); - $sync->addOption($dis); - - // CAS - $rad = new ilRadioOption( - $this->lng->txt('auth_sync_cas'), - (string) self::SYNC_CAS, - '' - ); - $rad->setInfo($this->lng->txt('auth_sync_cas_info')); - $sync->addOption($rad); - - $select = new ilSelectInputGUI($this->lng->txt('auth_user_default_role'), 'role'); - $select->setOptions($this->prepareRoleSelection()); - $select->setValue($this->getSettings()->getDefaultRole()); - $rad->addSubItem($select); - - - // LDAP - $server_ids = ilLDAPServer::getAvailableDataSources(ilAuthUtils::AUTH_CAS); - - if (count($server_ids)) { - $ldap = new ilRadioOption( - $this->lng->txt('auth_css_ldap'), - (string) ilCASSettings::SYNC_LDAP, - '' - ); - $ldap->setInfo($this->lng->txt('auth_cas_ldap_info')); - $sync->addOption($ldap); - - $ldap_server_select = new ilSelectInputGUI($this->lng->txt('auth_ldap_server_ds'), 'ldap_sid'); - $options[0] = $this->lng->txt('select_one'); - foreach ($server_ids as $ldap_sid) { - $ldap_server = new ilLDAPServer($ldap_sid); - $options[$ldap_sid] = $ldap_server->getName(); - } - $ldap_server_select->setOptions($options); - $ldap_server_select->setRequired(true); - $ds = ilLDAPServer::getDataSource(ilAuthUtils::AUTH_CAS); - $ldap_server_select->setValue($ds); - - $ldap->addSubItem($ldap_server_select); - } - - if (ilLDAPServer::isDataSourceActive(ilAuthUtils::AUTH_CAS)) { - $sync->setValue((string) ilCASSettings::SYNC_LDAP); - } else { - $sync->setValue( - $this->getSettings()->isUserCreationEnabled() ? - (string) ilCASSettings::SYNC_CAS : - (string) ilCASSettings::SYNC_DISABLED - ); - } - - $instruction = new ilTextAreaInputGUI($this->lng->txt('auth_login_instructions'), 'instruction'); - $instruction->setCols(80); - $instruction->setRows(6); - $instruction->setValue($this->getSettings()->getLoginInstruction()); - $form->addItem($instruction); - - $create = new ilCheckboxInputGUI($this->lng->txt('auth_allow_local'), 'local'); - $create->setInfo($this->lng->txt('auth_cas_allow_local_desc')); - $create->setChecked($this->getSettings()->isLocalAuthenticationEnabled()); - $create->setValue("1"); - $form->addItem($create); - - if ($this->rbacSystem->checkAccess('write', $this->ref_id)) { - $form->addCommandButton('save', $this->lng->txt('save')); - } - - return $form; - } - - public function settings(): void - { - $form = $this->initFormSettings(); - $this->tpl->setContent($form->getHTML()); - } - - public function save(): void - { - $form = $this->initFormSettings(); - if ($form->checkInput()) { - $this->getSettings()->setActive((bool) $form->getInput('active')); - $this->getSettings()->setServer($form->getInput('server')); - $this->getSettings()->setPort((int) $form->getInput('port')); - $this->getSettings()->setUri($form->getInput('uri')); - $this->getSettings()->setDefaultRole((int) $form->getInput('role')); - $this->getSettings()->enableLocalAuthentication((bool) $form->getInput('local')); - $this->getSettings()->setLoginInstruction($form->getInput('instruction')); - $this->getSettings()->enableUserCreation((int) $form->getInput('sync') === ilCASSettings::SYNC_CAS); - $this->getSettings()->save(); - - switch ((int) $form->getInput('sync')) { - case ilCASSettings::SYNC_CAS: - case ilCASSettings::SYNC_DISABLED: - ilLDAPServer::disableDataSourceForAuthMode(ilAuthUtils::AUTH_CAS); - break; - - case ilCASSettings::SYNC_LDAP: - if (!(int) $form->getInput('ldap_sid')) { - $this->tpl->setOnScreenMessage('failure', $this->lng->txt('err_check_input')); - $this->settings(); - //TODO do we need return false? - return; - } - - ilLDAPServer::toggleDataSource((int) $form->getInput('ldap_sid'), ilAuthUtils::AUTH_CAS, 1); - break; - } - - $this->tpl->setOnScreenMessage('success', $this->lng->txt('settings_saved'), true); - $this->ctrl->redirect($this, 'settings'); - } - - $form->setValuesByPost(); - $this->tpl->setOnScreenMessage('failure', $this->lng->txt('err_ceck_input')); - $this->tpl->setContent($form->getHTML()); - } - - private function prepareRoleSelection(): array - { - $global_roles = ilUtil::_sortIds( - $this->rbacReview->getGlobalRoles(), - 'object_data', - 'title', - 'obj_id' - ); - - $select[0] = $this->lng->txt('links_select_one'); - foreach ($global_roles as $role_id) { - $select[$role_id] = ilObject::_lookupTitle((int) $role_id); - } - - return $select; - } -} diff --git a/components/ILIAS/CAS/maintenance.json b/components/ILIAS/CAS/maintenance.json deleted file mode 100755 index 951f1291af95..000000000000 --- a/components/ILIAS/CAS/maintenance.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "maintenance_model": "Classic", - "first_maintainer": "PerPascalSeeland(31492)", - "second_maintainer": "", - "implicit_maintainers": [], - "coordinator": [ - "" - ], - "tester": "vimotion(25105)", - "testcase_writer": "FH Aachen", - "path": "Services/CAS", - "belong_to_component": "Login, Auth & Registration", - "used_in_components": [] -} \ No newline at end of file diff --git a/components/ILIAS/CAS/tests/ilCASSettingsTest.php b/components/ILIAS/CAS/tests/ilCASSettingsTest.php deleted file mode 100755 index 322a305b56da..000000000000 --- a/components/ILIAS/CAS/tests/ilCASSettingsTest.php +++ /dev/null @@ -1,80 +0,0 @@ -dic = new Container(); - $GLOBALS['DIC'] = $this->dic; - $this->setGlobalVariable( - 'ilSetting', - $this->getMockBuilder(ilSetting::class)->disableOriginalConstructor()->getMock() - ); - parent::setUp(); - } - - /** - * @param string $name - * @param mixed $value - */ - protected function setGlobalVariable(string $name, $value): void - { - global $DIC; - - $GLOBALS[$name] = $value; - - unset($DIC[$name]); - $DIC[$name] = static function ($c) use ($name) { - return $GLOBALS[$name]; - }; - } - - public function testBasicSessionBehaviour(): void - { - global $DIC; - - //setup some method calls - /** @var $setting MockObject */ - $setting = $DIC['ilSetting']; - $consecutive_returns = [ - 'cas_server' => 'casserver', - 'cas_port' => '1', - 'cas_uri' => 'cas', - 'cas_active' => 'true', - 'cas_user_default_role' => '0', - 'cas_login_instructions' => 'casInstruction', - 'cas_allow_local' => 'false', - 'cas_create_users' => 'true', - ]; - $setting->method("get") - ->willReturnCallback(fn($k) => $consecutive_returns[$k]); - - $casSettings = ilCASSettings::getInstance(); - $this->assertEquals("casserver", $casSettings->getServer()); - $this->assertTrue($casSettings->isActive()); - } -} diff --git a/components/ILIAS/COPage/Editor/js/src/client/client.js b/components/ILIAS/COPage/Editor/js/src/client/client.js index 61d0f772e572..01f50cc931b7 100755 --- a/components/ILIAS/COPage/Editor/js/src/client/client.js +++ b/components/ILIAS/COPage/Editor/js/src/client/client.js @@ -12,7 +12,7 @@ * https://www.ilias.de * https://github.com/ILIAS-eLearning * - *********************************************************************/ + ******************************************************************** */ import ResponseFactory from './response/response-factory.js'; import FetchWrapper from './fetch-wrapper.js'; @@ -21,31 +21,30 @@ import FormCommandAction from './actions/form-command-action.js'; import CommandQueue from './actions/command-queue.js'; export default class Client { - /** * @type {boolean} */ - //debug = true; + // debug = true; /** * @type {string} */ - //query_endpoint; + // query_endpoint; /** * @type {string} */ - //command_endpoint; + // command_endpoint; /** * @type {ResponseFactory} */ - //response_factory; + // response_factory; /** * @type {string} */ - //form_action; + // form_action; /** * Constructor @@ -55,7 +54,7 @@ export default class Client { * @param {ResponseFactory} response_factory */ constructor(query_endpoint, command_endpoint, form_action, response_factory) { - this.debug = true; + this.debug = false; this.query_endpoint = query_endpoint; this.command_endpoint = command_endpoint; this.form_action = form_action; @@ -83,37 +82,33 @@ export default class Client { * @returns {Promise} */ sendQuery(query_action) { - this.log("client.sendQuery"); + this.log('client.sendQuery'); this.log(query_action); const errorHandler = (err) => { this.errorHandler(err); - } + }; return new Promise((resolve, reject) => { let params = { action_id: query_action.getId(), component: query_action.getComponent(), - action: query_action.getType() + action: query_action.getType(), }; params = Object.assign(params, query_action.getParams()); FetchWrapper.getJson(this.query_endpoint, params) - .then(response => { - this.log("client.sendQuery, response:"); - this.log(response); + .then((response) => { + this.log('client.sendQuery, response:'); + this.log(response); - if (!response.ok) { - const statusText = response.statusText; - response.text().then(text => - errorHandler(statusText + " " + text) - ).catch(errorHandler); - } else { + if (!response.ok) { + const { statusText } = response; + response.text().then((text) => errorHandler(`${statusText} ${text}`)).catch(errorHandler); + } else { // note that fetch.json() returns yet another promise - response.json().then(json => - resolve(this.response_factory.response(query_action, json)) - ).catch(errorHandler); - } - }).catch(errorHandler); + response.json().then((json) => resolve(this.response_factory.response(query_action, json))).catch(errorHandler); + } + }).catch(errorHandler); }); } @@ -132,15 +127,9 @@ export default class Client { sendCommand(command_action) { if (command_action.getQueueable()) { const t = this; - console.log("### Put command in queue:"); - console.log(command_action); - return this.queue.push(() => {return t._sendCommand(command_action);}); - } else { - console.log("### Sending command directly:"); - console.trace(); - console.log(command_action); - return this._sendCommand(command_action); + return this.queue.push(() => t._sendCommand(command_action)); } + return this._sendCommand(command_action); } /** @@ -149,80 +138,66 @@ export default class Client { * @returns {Promise} */ _sendCommand(command_action) { - - this.log("...sending Command " + command_action.getId()); + this.log(`...sending Command ${command_action.getId()}`); const errorHandler = (err) => { this.errorHandler(err); - } + }; // POST FORM if (command_action instanceof FormCommandAction) { - return new Promise((resolve, reject) => { - const formData = command_action.getParams(); - formData.append("action_id", command_action.getId()); - formData.append("component", command_action.getComponent()); - formData.append("action", command_action.getType()); + formData.append('action_id', command_action.getId()); + formData.append('component', command_action.getComponent()); + formData.append('action', command_action.getType()); - FetchWrapper.postForm(this.command_endpoint, formData).then(response => { - this.log("client.sendCommand, response:"); + FetchWrapper.postForm(this.command_endpoint, formData).then((response) => { + this.log('client.sendCommand, response:'); this.log(response); let getAsJSON = false; - const contentType = response.headers.get("content-type"); - if (response.ok && contentType && contentType.indexOf("application/json") !== -1) { + const contentType = response.headers.get('content-type'); + if (response.ok && contentType && contentType.indexOf('application/json') !== -1) { getAsJSON = true; } if (!getAsJSON) { - const statusText = response.statusText; - response.text().then(text => - errorHandler(statusText + " " + text) - ).catch(errorHandler); + const { statusText } = response; + response.text().then((text) => errorHandler(`${statusText} ${text}`)).catch(errorHandler); } else { // note that fetch.json() returns yet another promise - response.json().then(json => - resolve(this.response_factory.response(command_action, json)) - ).catch(errorHandler); + response.json().then((json) => resolve(this.response_factory.response(command_action, json))).catch(errorHandler); } - this.log("...left in Queue: " + this.queue.count()); + this.log(`...left in Queue: ${this.queue.count()}`); }).catch(errorHandler); }); + } // POST JSON - } else { // POST JSON - - return new Promise((resolve, reject) => { - - FetchWrapper.postJson(this.command_endpoint, { - action_id: command_action.getId(), - component: command_action.getComponent(), - action: command_action.getType(), - data: command_action.getParams() - }).then(response => { - this.log("client.sendCommand, response:"); - this.log(response); + return new Promise((resolve, reject) => { + FetchWrapper.postJson(this.command_endpoint, { + action_id: command_action.getId(), + component: command_action.getComponent(), + action: command_action.getType(), + data: command_action.getParams(), + }).then((response) => { + this.log('client.sendCommand, response:'); + this.log(response); - let getAsJSON = false; - const contentType = response.headers.get("content-type"); - if (response.ok && contentType && contentType.indexOf("application/json") !== -1) { - getAsJSON = true; - } - if (!getAsJSON) { - const statusText = response.statusText; - response.text().then(text => - errorHandler(statusText + " " + text) - ).catch(errorHandler); - } else { - // note that fetch.json() returns yet another promise - response.json().then(json => - resolve(this.response_factory.response(command_action, json)) - ).catch(errorHandler); - } - this.log("...left in Queue: " + this.queue.count()); - }).catch(errorHandler); - }); - } + let getAsJSON = false; + const contentType = response.headers.get('content-type'); + if (response.ok && contentType && contentType.indexOf('application/json') !== -1) { + getAsJSON = true; + } + if (!getAsJSON) { + const { statusText } = response; + response.text().then((text) => errorHandler(`${statusText} ${text}`)).catch(errorHandler); + } else { + // note that fetch.json() returns yet another promise + response.json().then((json) => resolve(this.response_factory.response(command_action, json))).catch(errorHandler); + } + this.log(`...left in Queue: ${this.queue.count()}`); + }).catch(errorHandler); + }); } /** @@ -230,17 +205,14 @@ export default class Client { * @param {CommandAction} command_action */ sendForm(command_action) { - const data = command_action.getParams(); - if (data['cmd']) { - data["cmd[" + data['cmd'] + "]"] = "-"; + if (data.cmd) { + data[`cmd[${data.cmd}]`] = '-'; } - this.log("client.sendForm " + this.form_action); + this.log(`client.sendForm ${this.form_action}`); this.log(data); FormWrapper.postForm(this.form_action, data); } - - -} \ No newline at end of file +} diff --git a/components/ILIAS/COPage/ID/class.ContentIdGenerator.php b/components/ILIAS/COPage/ID/class.ContentIdGenerator.php index 7567dee58c34..67a4d056e45e 100755 --- a/components/ILIAS/COPage/ID/class.ContentIdGenerator.php +++ b/components/ILIAS/COPage/ID/class.ContentIdGenerator.php @@ -31,7 +31,7 @@ public function __construct() public function generate(): string { - $random = new \ilRandom(); - return md5((string) ($random->int(1, 9999999) + str_replace(" ", "", (string) microtime()))); + $random = new \Random\Randomizer(); + return md5((string) ($random->getInt(1, 9999999) + str_replace(" ", "", (string) microtime()))); } } diff --git a/components/ILIAS/COPage/IntLink/class.ilInternalLinkGUI.php b/components/ILIAS/COPage/IntLink/class.ilInternalLinkGUI.php index 719238cb583d..189daba369ad 100755 --- a/components/ILIAS/COPage/IntLink/class.ilInternalLinkGUI.php +++ b/components/ILIAS/COPage/IntLink/class.ilInternalLinkGUI.php @@ -24,6 +24,7 @@ */ class ilInternalLinkGUI { + protected \ILIAS\MediaObjects\Thumbs\ThumbsGUI $thumbs_gui; protected ?ilObjFile $uploaded_file = null; protected int $parent_fold_id; protected string $default_parent_obj_type; @@ -65,9 +66,11 @@ public function __construct( $DIC->http(), $DIC->refinery() ); + $this->thumbs_gui = $DIC->mediaObjects()->internal()->gui()->thumbs(); $this->lng->loadLanguageModule("link"); $this->lng->loadLanguageModule("content"); + $this->lng->loadLanguageModule("copg"); $this->ctrl->saveParameter($this, array("linkmode", "link_par_ref_id", "link_par_obj_id", "link_par_fold_id", "link_type")); @@ -325,7 +328,7 @@ public function showLinkHelp(): void $tpl->setCurrentBlock("chapter_list"); $tpl->setVariable("TXT_CONTENT_OBJECT", $this->lng->txt("obj_lm")); $tpl->setVariable("TXT_CONT_TITLE", $cont_obj->getTitle()); - $tpl->setVariable("THEAD", $this->lng->txt("pages")); + $tpl->setVariable("THEAD", $this->lng->txt("copg_pages")); $tpl->setCurrentBlock("change_cont_obj"); @@ -620,7 +623,7 @@ public function showLinkHelp(): void $tpl->setCurrentBlock("chapter_list"); $tpl->setVariable("TXT_CONTENT_OBJECT", $this->lng->txt("obj_" . ilObject::_lookupType($prtf_id))); $tpl->setVariable("TXT_CONT_TITLE", ilObject::_lookupTitle($prtf_id)); - $tpl->setVariable("THEAD", $this->lng->txt("pages")); + $tpl->setVariable("THEAD", $this->lng->txt("copg_pages")); foreach ($ppages as $ppage) { $this->renderLink( @@ -744,10 +747,6 @@ public function outputThumbnail( string $a_mode = "" ): void { // output thumbnail - $mob = new ilObjMediaObject($a_id); - $med = $mob->getMediaItem("Standard"); - $target = $med->getThumbnailTarget("small"); - $suff = ""; if ($this->getSetLinkTargetScript() !== "") { $tpl->setCurrentBlock("thumbnail_link"); $suff = "_link"; @@ -756,13 +755,12 @@ public function outputThumbnail( $suff = "_js"; } - if ($target !== "") { - $tpl->setCurrentBlock("thumb" . $suff); - $tpl->setVariable("SRC_THUMB", $target); - $tpl->parseCurrentBlock(); - } else { - $tpl->setVariable("NO_THUMB", " "); - } + $tpl->setCurrentBlock("thumb" . $suff); + $tpl->setVariable( + "THUMB", + $this->thumbs_gui->getThumbHtml($a_id) + ); + $tpl->parseCurrentBlock(); if ($this->getSetLinkTargetScript() !== "") { $tpl->setCurrentBlock("thumbnail_link"); diff --git a/components/ILIAS/COPage/IntLink/templates/default/tpl.link_help_asynch.html b/components/ILIAS/COPage/IntLink/templates/default/tpl.link_help_asynch.html index d00553da882e..44b4f980f2da 100755 --- a/components/ILIAS/COPage/IntLink/templates/default/tpl.link_help_asynch.html +++ b/components/ILIAS/COPage/IntLink/templates/default/tpl.link_help_asynch.html @@ -35,7 +35,7 @@ - {NO_THUMB} + {NO_THUMB}{THUMB} {TXT_CHAPTER} @@ -49,7 +49,7 @@ - {NO_THUMB} + {NO_THUMB}{THUMB} diff --git a/components/ILIAS/COPage/Layout/Administration/class.ilPageLayoutAdministrationGUI.php b/components/ILIAS/COPage/Layout/Administration/class.ilPageLayoutAdministrationGUI.php index 83e516736e25..e94106fcc886 100755 --- a/components/ILIAS/COPage/Layout/Administration/class.ilPageLayoutAdministrationGUI.php +++ b/components/ILIAS/COPage/Layout/Administration/class.ilPageLayoutAdministrationGUI.php @@ -50,6 +50,7 @@ public function __construct() $this->lng = $DIC->language(); $this->tpl = $DIC["tpl"]; $this->tabs = $DIC->tabs(); + $this->lng->loadLanguageModule("style"); $this->settings = new ilContentStyleSettings(); $this->admin_request = $DIC diff --git a/components/ILIAS/COPage/Link/LinkManager.php b/components/ILIAS/COPage/Link/LinkManager.php index 9025690f1aac..065b1f9eb973 100755 --- a/components/ILIAS/COPage/Link/LinkManager.php +++ b/components/ILIAS/COPage/Link/LinkManager.php @@ -379,7 +379,6 @@ public function handleRepositoryLinksOnCopy( $source_node = $node; $new_node = $source_node->cloneNode(true); //$new_node->parentNode->removeChild($new_node); - $childs = $new_node->child_nodes(); foreach ($new_node->childNodes as $child) { //$this->log->debug("... move node $j " . $child->node_name() . " before " . $source_node->node_name()); $source_node->parentNode->insertBefore($child, $source_node); diff --git a/components/ILIAS/COPage/PC/Blog/class.ilPCBlogGUI.php b/components/ILIAS/COPage/PC/Blog/class.ilPCBlogGUI.php index 32471dd8f7a2..3f2e99eb4a87 100755 --- a/components/ILIAS/COPage/PC/Blog/class.ilPCBlogGUI.php +++ b/components/ILIAS/COPage/PC/Blog/class.ilPCBlogGUI.php @@ -16,6 +16,8 @@ * *********************************************************************/ +use ILIAS\Blog\Posting\PostingManager; + /** * Class ilPCBlogGUI * Handles user commands on blog data @@ -24,6 +26,7 @@ */ class ilPCBlogGUI extends ilPageContentGUI { + protected PostingManager $posting_manger; protected int $requested_blog; protected int $requested_blog_id; protected ilObjUser $user; @@ -45,6 +48,7 @@ public function __construct( // ... not sure why different ids are used for this... $this->requested_blog_id = $this->request->getInt("blog_id"); $this->requested_blog = $this->request->getInt("blog"); + $this->posting_manger = $DIC->blog()->internal()->domain()->posting(); } /** @@ -105,7 +109,8 @@ protected function initForm(bool $a_insert = false): ilPropertyFormGUI } $options = array(); - $blogs_ids = ilBlogPosting::searchBlogsByAuthor($ilUser->getId()); + $blogs_ids = $this->posting_manger->searchBlogsByAuthor($ilUser->getId()); + if ($blogs_ids) { foreach ($blogs_ids as $blog_id) { $options[$blog_id] = ilObject::_lookupTitle($blog_id); diff --git a/components/ILIAS/COPage/PC/FileList/class.ilPCFileListTableGUI.php b/components/ILIAS/COPage/PC/FileList/class.ilPCFileListTableGUI.php index 293174ca9ff3..ceddff44aeec 100755 --- a/components/ILIAS/COPage/PC/FileList/class.ilPCFileListTableGUI.php +++ b/components/ILIAS/COPage/PC/FileList/class.ilPCFileListTableGUI.php @@ -94,6 +94,7 @@ protected function fillRow(array $a_set): void $this->pos += 10; $this->tpl->setVariable("POS", $this->pos); $this->tpl->setVariable("FID", $a_set["hier_id"] . ":" . $a_set["pc_id"]); - $this->tpl->setVariable("TXT_FILE", ilObject::_lookupTitle($a_set["id"])); + $file = new ilObjFile((int) $a_set["id"], false); + $this->tpl->setVariable("TXT_FILE", $file->getFileName()); } } diff --git a/components/ILIAS/COPage/PC/InteractiveImage/InteractiveImageQueryActionHandler.php b/components/ILIAS/COPage/PC/InteractiveImage/InteractiveImageQueryActionHandler.php index 33c07412e04c..074c3b92ab95 100755 --- a/components/ILIAS/COPage/PC/InteractiveImage/InteractiveImageQueryActionHandler.php +++ b/components/ILIAS/COPage/PC/InteractiveImage/InteractiveImageQueryActionHandler.php @@ -108,7 +108,6 @@ protected function init(): Server\Response return new Server\Response($o); } - /** * Get interactive image model */ @@ -240,8 +239,8 @@ public function getMainSlate(): string return $tpl->get(); } - public function getBackgroundImage( - ): string { + public function getBackgroundImage(): string + { if ($this->pc_id !== "") { /** @var \ilPCInteractiveImage $pc */ @@ -287,23 +286,38 @@ protected function getTriggerViewControls(): string protected function getTriggerPropertiesFormAdapter(): \ILIAS\Repository\Form\FormAdapterGUI { + $lng = $this->lng; return $this->gui->form(null, "#") - ->text( - "title", - $this->lng->txt("title") - ) - ->select( - "shape", - $this->lng->txt("cont_shape"), - [ - "Rect" => $this->lng->txt("cont_Rect"), - "Circle" => $this->lng->txt("cont_Circle"), - "Poly" => $this->lng->txt("cont_Poly"), - "Marker" => $this->lng->txt("cont_marker") - ], - "", - "Rect" - )->required(); + ->text( + "title", + $this->lng->txt("title") + ) + ->select( + "shape", + $this->lng->txt("cont_shape"), + [ + "Rect" => $this->lng->txt("cont_Rect"), + "Circle" => $this->lng->txt("cont_Circle"), + "Poly" => $this->lng->txt("cont_Poly"), + "Marker" => $this->lng->txt("cont_marker") + ], + "", + "Rect" + )->required() + ->select( + "hl_mode", + $this->lng->txt("cont_highlight_mode"), + \ilMapArea::getAllHighlightModes(), + "", + "" + )->required() + ->select( + "hl_class", + $this->lng->txt("cont_highlight_class"), + \ilMapArea::getAllHighlightClasses(), + "", + \ilMapArea::HLCL_ACCENTED + )->required(); } protected function getMessageArea(): string @@ -324,7 +338,10 @@ protected function getTriggerProperties(): string $content .= $this->getMessageArea(); $content .= $this->ui_wrapper->getRenderedAdapterForm( $this->getTriggerPropertiesFormAdapter(), - [["InteractiveImage", "trigger.properties.save", $this->lng->txt("save")]], + [ + ["InteractiveImage", "trigger.properties.save", $this->lng->txt("save")], + ["InteractiveImage", "trigger.delete", $this->lng->txt("delete")], + ], "copg-iim-trigger-prop-form" ); @@ -376,10 +393,10 @@ protected function getTriggerPopupFormAdapter(): \ILIAS\Repository\Form\FormAdap "size", $this->lng->txt("cont_iim_size"), [ - "sm" => $this->lng->txt("cont_iim_sm"), - "md" => $this->lng->txt("cont_iim_md"), - "lg" => $this->lng->txt("cont_iim_lg") - ], + "sm" => $this->lng->txt("cont_iim_sm"), + "md" => $this->lng->txt("cont_iim_md"), + "lg" => $this->lng->txt("cont_iim_lg") + ], "", "md" )->required(); @@ -443,12 +460,14 @@ protected function getOverlayOverview(): string null, "InteractiveImage" )); - $content .= $this->section($this->ui_wrapper->getRenderedListingPanelTemplate($this->lng->txt("cont_iim_overview"), true)); + $content .= $this->section($this->ui_wrapper->getRenderedListingPanelTemplate( + $this->lng->txt("cont_iim_overview"), + true + )); return $content; } - protected function getBackgroundProperties(): string { $this->ctrl->setParameterByClass( @@ -461,7 +480,10 @@ protected function getBackgroundProperties(): string $content .= $this->getHeading($this->lng->txt("cont_iim_background_image"), true); $content .= $this->getMessageArea(); $content .= $this->ui_wrapper->getRenderedAdapterForm( - $this->getPCInteractiveImageGUI()->getBackgroundPropertiesFormAdapter([get_class($this->page_gui), \ilPageEditorGUI::class, \ilPCInteractiveImageGUI::class]), + $this->getPCInteractiveImageGUI()->getBackgroundPropertiesFormAdapter([get_class($this->page_gui), + \ilPageEditorGUI::class, + \ilPCInteractiveImageGUI::class + ]), [["InteractiveImage", "component.save", $this->lng->txt("save")]] ); @@ -494,7 +516,10 @@ protected function getPCInteractiveImageGUI(): \ilPCInteractiveImageGUI protected function getOverlayUploadFormAdapter(): \ILIAS\Repository\Form\FormAdapterGUI { return $this->getPCInteractiveImageGUI() - ->getOverlayUploadFormAdapter([get_class($this->page_gui), \ilPageEditorGUI::class, \ilPCInteractiveImageGUI::class]); + ->getOverlayUploadFormAdapter([get_class($this->page_gui), + \ilPageEditorGUI::class, + \ilPCInteractiveImageGUI::class + ]); } protected function getOverlayUpload(): string diff --git a/components/ILIAS/COPage/PC/InteractiveImage/class.InteractiveImageCommandActionHandler.php b/components/ILIAS/COPage/PC/InteractiveImage/class.InteractiveImageCommandActionHandler.php index 0d593d5a0af0..d2ca89857c08 100755 --- a/components/ILIAS/COPage/PC/InteractiveImage/class.InteractiveImageCommandActionHandler.php +++ b/components/ILIAS/COPage/PC/InteractiveImage/class.InteractiveImageCommandActionHandler.php @@ -73,6 +73,9 @@ public function handle(array $query, array $body): Server\Response case "delete.popup": return $this->deletePopup($query['pc_id'], $body); + case "delete.trigger": + return $this->deleteTrigger($query['pc_id'], $body); + case "save.settings": return $this->saveSettings($query['pc_id'], $body); @@ -125,7 +128,7 @@ protected function saveTriggerProperties(string $pc_id, array $body): Server\Res $page = $this->page_gui->getPageObject(); /** @var \ilPCInteractiveImage $pc */ $pc = $this->page_gui->getPageObject()->getContentObjectForPcId($pc_id); - $pc->setTriggerProperties((string) $body["data"]["trigger_nr"], $body["data"]["title"], $body["data"]["shape_type"], $body["data"]["coords"]); + $pc->setTriggerProperties((string) $body["data"]["trigger_nr"], $body["data"]["title"], $body["data"]["shape_type"], $body["data"]["coords"], $body["data"]["hl_mode"], $body["data"]["hl_class"]); $updated = $page->update(); return $this->getStandardResponse($updated, $pc); @@ -238,6 +241,16 @@ protected function deletePopup(string $pc_id, array $body): Server\Response return $this->getStandardResponse($updated, $pc); } + protected function deleteTrigger(string $pc_id, array $body): Server\Response + { + $page = $this->page_gui->getPageObject(); + $pc = $this->getPCInteractiveImage($pc_id); + $std_alias_item = $pc->getStandardAliasItem(); + $pc->deleteTrigger($std_alias_item, (int) $body["data"]["nr"]); + $updated = $page->update(); + return $this->getStandardResponse($updated, $pc); + } + protected function saveSettings(string $pc_id, array $body): Server\Response { $page = $this->page_gui->getPageObject(); diff --git a/components/ILIAS/COPage/PC/InteractiveImage/class.ilPCIIMOverlaysTableGUI.php b/components/ILIAS/COPage/PC/InteractiveImage/class.ilPCIIMOverlaysTableGUI.php deleted file mode 100755 index fbefaacceda5..000000000000 --- a/components/ILIAS/COPage/PC/InteractiveImage/class.ilPCIIMOverlaysTableGUI.php +++ /dev/null @@ -1,89 +0,0 @@ - - */ -class ilPCIIMOverlaysTableGUI extends ilTable2GUI -{ - protected IIMManager $iim_manager; - protected ilObjMediaObject $mob; - protected ilAccessHandler $access; - - public function __construct( - object $a_parent_obj, - string $a_parent_cmd, - $a_mob - ) { - global $DIC; - - $this->ctrl = $DIC->ctrl(); - $this->lng = $DIC->language(); - $this->access = $DIC->access(); - $ilCtrl = $DIC->ctrl(); - $lng = $DIC->language(); - $this->iim_manager = $DIC->copage()->internal()->domain()->pc()->interactiveImage(); - - parent::__construct($a_parent_obj, $a_parent_cmd); - $this->mob = $a_mob; - $this->setData($this->getOverlays()); - $this->setTitle($lng->txt("cont_overlay_images")); - - $this->addColumn("", "", "1", true); - $this->addColumn($this->lng->txt("thumbnail"), "", "20px"); - $this->addColumn($this->lng->txt("filename")); - - $this->setFormAction($ilCtrl->getFormAction($a_parent_obj)); - $this->setRowTemplate("tpl.iim_overlays_row.html", "components/ILIAS/COPage"); - - $this->addMultiCommand("confirmDeleteOverlays", $lng->txt("delete")); - } - - public function getOverlays(): array - { - $ov = array(); - $files = $this->mob->getFilesOfDirectory("/overlays"); - foreach ($files as $f) { - $ov[] = array("filename" => $f); - } - return $ov; - } - - protected function fillRow(array $a_set): void - { - $this->tpl->setVariable("FILENAME", $a_set["filename"]); - $piname = pathinfo($a_set["filename"]); - $th_path = $this->iim_manager->getOverlayThumbnailPath( - $this->mob, - basename($a_set["filename"], "." . $piname['extension']) . ".png" - ) ; - if (!is_file($th_path)) { - $this->mob->makeThumbnail( - "overlays/" . $a_set["filename"], - basename($a_set["filename"], "." . $piname['extension']) . ".png" - ); - } - if (is_file($th_path)) { - $this->tpl->setVariable("THUMB", ilUtil::img($th_path)); - } - } -} diff --git a/components/ILIAS/COPage/PC/InteractiveImage/class.ilPCIIMPopupTableGUI.php b/components/ILIAS/COPage/PC/InteractiveImage/class.ilPCIIMPopupTableGUI.php deleted file mode 100755 index 8b14953fbf25..000000000000 --- a/components/ILIAS/COPage/PC/InteractiveImage/class.ilPCIIMPopupTableGUI.php +++ /dev/null @@ -1,65 +0,0 @@ - - */ -class ilPCIIMPopupTableGUI extends ilTable2GUI -{ - protected ilPCInteractiveImage $content_obj; - - public function __construct( - object $a_parent_obj, - string $a_parent_cmd, - ilPCInteractiveImage $a_content_obj - ) { - global $DIC; - - $this->ctrl = $DIC->ctrl(); - $this->lng = $DIC->language(); - $ilCtrl = $DIC->ctrl(); - $lng = $DIC->language(); - - parent::__construct($a_parent_obj, $a_parent_cmd); - - $this->addColumn("", "", "1"); - $this->addColumn($lng->txt("title"), "", "100%"); - $this->setFormAction($ilCtrl->getFormAction($a_parent_obj)); - $this->setRowTemplate( - "tpl.iim_popup_content_row.html", - "components/ILIAS/COPage" - ); - - $this->content_obj = $a_content_obj; - $this->setData($this->content_obj->getPopups()); - $this->setLimit(0); - - $this->addMultiCommand("confirmPopupDeletion", $lng->txt("delete")); - $this->addCommandButton("savePopups", $lng->txt("cont_save_all_titles")); - - $this->setTitle($lng->txt("cont_content_popups")); - } - - protected function fillRow(array $a_set): void - { - $this->tpl->setVariable("TID", $a_set["hier_id"] . ":" . $a_set["pc_id"]); - $this->tpl->setVariable("TITLE", ilLegacyFormElementsUtil::prepareFormOutput($a_set["title"])); - } -} diff --git a/components/ILIAS/COPage/PC/InteractiveImage/class.ilPCInteractiveImage.php b/components/ILIAS/COPage/PC/InteractiveImage/class.ilPCInteractiveImage.php index 6373e2c389af..2c8c4b996556 100755 --- a/components/ILIAS/COPage/PC/InteractiveImage/class.ilPCInteractiveImage.php +++ b/components/ILIAS/COPage/PC/InteractiveImage/class.ilPCInteractiveImage.php @@ -25,6 +25,7 @@ class ilPCInteractiveImage extends ilPageContent { public const AREA = "Area"; public const MARKER = "Marker"; + protected \ILIAS\MediaObjects\Thumbs\ThumbsManager $thumbs; protected \ILIAS\COPage\Xsl\XslManager $xsl; protected DOMNode $mal_node; protected DOMNode $med_alias_node; @@ -47,6 +48,7 @@ public function init(): void $this->htmlTransform = $DIC->copage()->internal()->domain()->htmlTransformUtil(); $this->ui = $DIC->copage()->internal()->gui()->ui(); $this->xsl = $DIC->copage()->internal()->domain()->xsl(); + $this->thumbs = $DIC->mediaObjects()->internal()->domain()->thumbs(); } public function readMediaObject(int $a_mob_id = 0): void @@ -112,7 +114,7 @@ public function getStandardAliasItem(): ilMediaAliasItem public function getBaseThumbnailTarget(): string { - return $this->getMediaObject()->getMediaItem("Standard")->getThumbnailTarget(); + return $this->thumbs->getThumbSrc($this->getMediaObject()->getId()); } public function createAlias( @@ -282,8 +284,10 @@ protected function setMapAreaProperties( string $a_shape_type, string $a_coords, string $a_title, - string $a_id - ) { + string $a_id, + string $hl_mode = "", + string $hl_class = "" + ): void { $link = array( "LinkType" => IL_EXT_LINK, "Href" => ilUtil::stripSlashes("#") @@ -295,7 +299,9 @@ protected function setMapAreaProperties( $a_coords, ilUtil::stripSlashes($a_title), $link, - $a_id + $a_id, + $hl_mode, + $hl_class ); } @@ -306,7 +312,9 @@ public function addTriggerArea( ilMediaAliasItem $a_alias_item, string $a_shape_type, string $a_coords, - string $a_title + string $a_title, + string $hl_mode = "", + string $hl_class = "" ): void { $max = 0; $triggers = $this->getTriggers(); @@ -319,7 +327,9 @@ public function addTriggerArea( $a_shape_type, $a_coords, ilUtil::stripSlashes($a_title), - (string) ($max + 1) + (string) ($max + 1), + $hl_mode, + $hl_class ); $attributes = array("Type" => self::AREA, @@ -346,8 +356,10 @@ public function addTriggerArea( /** * Add a new trigger marker */ - public function addTriggerMarker(): void - { + public function addTriggerMarker( + string $title = "", + string $coords = "" + ): void { $lng = $this->lng; $max = 0; @@ -356,13 +368,24 @@ public function addTriggerMarker(): void $max = max($max, (int) $t["Nr"]); } + if ($title === "") { + $title = $lng->txt("cont_new_marker"); + } + $markerx = "0"; + $markery = "0"; + if ($coords !== "") { + $coord_parts = explode(",", $coords); + $markerx = ($coord_parts[0] ?? "0"); + $markery = ($coord_parts[1] ?? "0"); + } + $attributes = array("Type" => self::MARKER, - "Title" => $lng->txt("cont_new_marker"), + "Title" => $title, "Nr" => $max + 1, "OverlayX" => "0", "OverlayY" => "0", - "MarkerX" => "0", - "MarkerY" => "0", + "MarkerX" => $markerx, + "MarkerY" => $markery, "PopupNr" => "", "PopupX" => "0", "PopupY" => "0", @@ -656,28 +679,34 @@ protected function getTriggerNode(string $nr) return null; } - public function setTriggerProperties(string $nr, string $title, string $shape_type, string $coords): void + public function setTriggerProperties(string $nr, string $title, string $shape_type, string $coords, string $hl_mode = "", string $hl_class = ""): void { $tr_node = $this->getTriggerNode($nr); if ($shape_type === "Marker") { - // set marker properties - $tr_node->setAttribute("Type", "Marker"); - $tr_node->setAttribute( - "Title", - $title - ); - $coord_parts = explode(",", $coords); - $tr_node->setAttribute("MarkerX", ($coord_parts[0] ?? "0")); - $tr_node->setAttribute("MarkerY", ($coord_parts[1] ?? "0")); - - // remove area - $path = "//PageContent[@HierId = '" . $this->hier_id . "']/InteractiveImage/MediaAliasItem/MapArea[@Id='" . $nr . "']"; - $nodes = $this->dom_util->path($this->dom_doc, $path); - if (count($nodes) > 0) { - $child = $nodes->item(0); - $child->parentNode->removeChild($child); + if (!$tr_node) { + $this->addTriggerMarker( + ilUtil::stripSlashes($title), + $coords + ); + } else { + // set marker properties + $tr_node->setAttribute("Type", "Marker"); + $tr_node->setAttribute( + "Title", + $title + ); + $coord_parts = explode(",", $coords); + $tr_node->setAttribute("MarkerX", ($coord_parts[0] ?? "0")); + $tr_node->setAttribute("MarkerY", ($coord_parts[1] ?? "0")); + + // remove area + $path = "//PageContent[@HierId = '" . $this->hier_id . "']/InteractiveImage/MediaAliasItem/MapArea[@Id='" . $nr . "']"; + $res = $this->dom_util->path($this->dom_doc, $path); + if ($child = $res->item(0)) { + $child->parentNode->removeChild($child); + } } return; } @@ -686,20 +715,27 @@ public function setTriggerProperties(string $nr, string $title, string $shape_ty $tr_node->setAttribute("Type", "Area"); $tr_node->removeAttribute("MarkerX"); $tr_node->removeAttribute("MarkerY"); - + $tr_node->setAttribute( + "Title", + ilUtil::stripSlashes($title), + ); $this->setMapAreaProperties( $this->getStandardAliasItem(), $shape_type, $coords, ilUtil::stripSlashes($title), - $nr + $nr, + $hl_mode, + $hl_class ); } else { $this->addTriggerArea( $this->getStandardAliasItem(), $shape_type, $coords, - $title + $title, + $hl_mode, + $hl_class ); } } diff --git a/components/ILIAS/COPage/PC/InteractiveImage/class.ilPCInteractiveImageGUI.php b/components/ILIAS/COPage/PC/InteractiveImage/class.ilPCInteractiveImageGUI.php index 12a14e4ab519..950adf0fa142 100755 --- a/components/ILIAS/COPage/PC/InteractiveImage/class.ilPCInteractiveImageGUI.php +++ b/components/ILIAS/COPage/PC/InteractiveImage/class.ilPCInteractiveImageGUI.php @@ -342,256 +342,6 @@ public function rightFloatAlign(): void $this->updateAndReturn(); } - //// - //// Overlay Images - //// - - public function listOverlayImages(): void - { - $tpl = $this->tpl; - $ilToolbar = $this->toolbar; - $ilCtrl = $this->ctrl; - $ilTabs = $this->tabs; - $lng = $this->lng; - - $this->tpl->setOnScreenMessage('info', $lng->txt("cont_iim_overlay_info")); - - $ilTabs->setTabActive("list_overlays"); - - $ilToolbar->addButton( - $lng->txt("cont_add_images"), - $ilCtrl->getLinkTarget($this, "addOverlayImages") - ); - - $tab = new ilPCIIMOverlaysTableGUI( - $this, - "listOverlayImages", - $this->content_obj->getMediaObject() - ); - $tpl->setContent($tab->getHTML()); - } - - public function addOverlayImages(?ilPropertyFormGUI $a_form = null): void - { - $tpl = $this->tpl; - - if ($a_form) { - $form = $a_form; - } else { - $form = $this->initAddOverlaysForm(); - } - - $tpl->setContent($form->getHTML()); - } - - public function initAddOverlaysForm(): ilPropertyFormGUI - { - $lng = $this->lng; - $ilCtrl = $this->ctrl; - $ilTabs = $this->tabs; - - $ilTabs->setTabActive("list_overlays"); - - $form = new ilPropertyFormGUI(); - $form->setTitle($lng->txt("cont_add_images")); - $form->setFormAction($ilCtrl->getFormAction($this)); - - // file input - $fi = new ilFileWizardInputGUI($lng->txt("file"), "ovfile"); - $fi->setSuffixes(array("gif", "jpeg", "jpg", "png")); - $fi->setFilenames(array(0 => '')); - $fi->setRequired(true); - $form->addItem($fi); - - $form->addCommandButton("uploadOverlayImages", $lng->txt("upload")); - $form->addCommandButton("listOverlayImages", $lng->txt("cancel")); - - return $form; - } - - public function uploadOverlayImages(): void - { - $lng = $this->lng; - $ilCtrl = $this->ctrl; - - $form = $this->initAddOverlaysForm(); - if ($form->checkInput()) { - if (is_array($_FILES["ovfile"]["name"])) { - foreach ($_FILES["ovfile"]["name"] as $k => $v) { - $name = $_FILES["ovfile"]["name"][$k]; - $tmp_name = $_FILES["ovfile"]["tmp_name"][$k]; - - $this->content_obj->getMediaObject()->uploadAdditionalFile( - $name, - $tmp_name, - "overlays" - ); - $piname = pathinfo($name); - $this->content_obj->getMediaObject()->makeThumbnail( - "overlays/" . $name, - basename($name, "." . $piname['extension']) . ".png" - ); - } - } - $this->tpl->setOnScreenMessage('success', $lng->txt("msg_obj_modified")); - $ilCtrl->redirect($this, "listOverlayImages"); - } else { - $form->setValuesByPost(); - $this->addOverlayImages($form); - } - } - - public function confirmDeleteOverlays(): void - { - $ilCtrl = $this->ctrl; - $tpl = $this->tpl; - $lng = $this->lng; - $ilTabs = $this->tabs; - - $ilTabs->setTabActive("list_overlays"); - - $files = $this->request->getStringArray("file"); - if (count($files) == 0) { - $this->tpl->setOnScreenMessage('failure', $lng->txt("no_checkbox"), true); - $ilCtrl->redirect($this, "listOverlayImages"); - } else { - $cgui = new ilConfirmationGUI(); - $cgui->setFormAction($ilCtrl->getFormAction($this)); - $cgui->setHeaderText($lng->txt("cont_really_delete_overlays")); - $cgui->setCancel($lng->txt("cancel"), "listOverlayImages"); - $cgui->setConfirm($lng->txt("delete"), "deleteOverlays"); - - foreach ($files as $i => $d) { - $cgui->addItem("file[]", $i, $i); - } - - $tpl->setContent($cgui->getHTML()); - } - } - - public function deleteOverlays(): void - { - $ilCtrl = $this->ctrl; - $lng = $this->lng; - - $files = $this->request->getStringArray("file"); - if (count($files) > 0) { - foreach ($files as $f) { - $f = str_replace("..", "", ilUtil::stripSlashes($f)); - $this->content_obj->getMediaObject() - ->removeAdditionalFile("overlays/" . $f); - } - - $this->tpl->setOnScreenMessage('success', $lng->txt("cont_overlays_have_been_deleted"), true); - } - $ilCtrl->redirect($this, "listOverlayImages"); - } - - - //// - //// Content Popups - //// - - public function listContentPopups(): void - { - $tpl = $this->tpl; - $ilToolbar = $this->toolbar; - $ilCtrl = $this->ctrl; - $ilTabs = $this->tabs; - $lng = $this->lng; - - $this->tpl->setOnScreenMessage('info', $lng->txt("cont_iim_content_popups_info")); - - $ilTabs->setTabActive("content_popups"); - - $ilToolbar->addButton( - $lng->txt("cont_add_popup"), - $ilCtrl->getLinkTarget($this, "addPopup") - ); - - /** @var ilPCInteractiveImage $iim */ - $iim = $this->content_obj; - $tab = new ilPCIIMPopupTableGUI( - $this, - "listContentPopups", - $iim - ); - $tpl->setContent($tab->getHTML()); - } - - public function addPopup(): void - { - $ilCtrl = $this->ctrl; - $lng = $this->lng; - - $this->content_obj->addContentPopup(); - $this->pg_obj->update(); - $this->tpl->setOnScreenMessage('success', $lng->txt("msg_obj_modified"), true); - $ilCtrl->redirect($this, "listContentPopups"); - } - - public function savePopups(): void - { - $ilCtrl = $this->ctrl; - $lng = $this->lng; - - $titles = $this->request->getStringArray("title"); - if (count($titles) > 0) { - $this->content_obj->savePopups($titles); - $this->pg_obj->update(); - $this->tpl->setOnScreenMessage('success', $lng->txt("msg_obj_modified"), true); - } - $ilCtrl->redirect($this, "listContentPopups"); - } - - public function confirmPopupDeletion(): void - { - $ilCtrl = $this->ctrl; - $tpl = $this->tpl; - $lng = $this->lng; - $ilTabs = $this->tabs; - - $ilTabs->setTabActive("content_popups"); - - $tids = $this->request->getStringArray("tid"); - $titles = $this->request->getStringArray("title"); - - if (count($tids) == 0) { - $this->tpl->setOnScreenMessage('failure', $lng->txt("no_checkbox"), true); - $ilCtrl->redirect($this, "listContentPopups"); - } else { - $cgui = new ilConfirmationGUI(); - $cgui->setFormAction($ilCtrl->getFormAction($this)); - $cgui->setHeaderText($lng->txt("cont_really_delete_popups")); - $cgui->setCancel($lng->txt("cancel"), "listContentPopups"); - $cgui->setConfirm($lng->txt("delete"), "deletePopups"); - - foreach ($tids as $i => $d) { - $cgui->addItem("tid[]", $i, $titles[$i]); - } - - $tpl->setContent($cgui->getHTML()); - } - } - - public function deletePopups(): void - { - $lng = $this->lng; - $ilCtrl = $this->ctrl; - - $tids = $this->request->getStringArray("tid"); - - if (count($tids) > 0) { - foreach ($tids as $id) { - $id = explode(":", $id); - $this->content_obj->deletePopup($id[0], $id[1]); - } - $this->pg_obj->update(); - $this->tpl->setOnScreenMessage('success', $lng->txt("cont_popups_have_been_deleted"), true); - } - $ilCtrl->redirect($this, "listContentPopups"); - } - public function getImportFormAdapter(): \ILIAS\Repository\Form\FormAdapterGUI { $this->ctrl->setParameter($this, "cname", "InteractiveImage"); diff --git a/components/ILIAS/COPage/PC/InteractiveImage/js/editor/src/actions/iim-action-types.js b/components/ILIAS/COPage/PC/InteractiveImage/js/editor/src/actions/iim-action-types.js index e3a0d6e3596b..50eaebc9766f 100755 --- a/components/ILIAS/COPage/PC/InteractiveImage/js/editor/src/actions/iim-action-types.js +++ b/components/ILIAS/COPage/PC/InteractiveImage/js/editor/src/actions/iim-action-types.js @@ -12,46 +12,48 @@ * https://www.ilias.de * https://github.com/ILIAS-eLearning * - *********************************************************************/ + ******************************************************************** */ const ACTIONS = { // query actions (being sent to the server to "ask for stuff") - Q_INIT: "init", + Q_INIT: 'init', // command actions (being sent to the server to "change things") - C_SAVE_TRIGGER_PROPERTIES: "save.trigger.properties", - C_SAVE_TRIGGER_OVERLAY: "save.trigger.overlay", - C_SAVE_TRIGGER_POPUP: "save.trigger.popup", - C_UPLOAD_OVERLAY: "upload.overlay", - C_DELETE_OVERLAY: "delete.overlay", - C_SAVE_POPUP: "save.popup", - C_DELETE_POPUP: "delete.popup", - C_SAVE_SETTINGS: "save.settings", + C_SAVE_TRIGGER_PROPERTIES: 'save.trigger.properties', + C_SAVE_TRIGGER_OVERLAY: 'save.trigger.overlay', + C_SAVE_TRIGGER_POPUP: 'save.trigger.popup', + C_DELETE_TRIGGER: 'delete.trigger', + C_UPLOAD_OVERLAY: 'upload.overlay', + C_DELETE_OVERLAY: 'delete.overlay', + C_SAVE_POPUP: 'save.popup', + C_DELETE_POPUP: 'delete.popup', + C_SAVE_SETTINGS: 'save.settings', // editor actions (things happening in the editor client side) - E_ADD_TRIGGER: "add.trigger", - E_EDIT_TRIGGER: "edit.trigger", - E_TRIGGER_PROPERTIES: "trigger.properties", - E_TRIGGER_PROPERTIES_SAVE: "trigger.properties.save", - E_TRIGGER_SHAPE_CHANGE: "trigger.shape.change", - E_TRIGGER_OVERLAY_CHANGE: "trigger.overlay.change", - E_TRIGGER_OVERLAY: "trigger.overlay", - E_TRIGGER_OVERLAY_ADD: "trigger.add.overlay", - E_TRIGGER_OVERLAY_SAVE: "trigger.overlay.save", - E_TRIGGER_POPUP_ADD: "trigger.add.popup", - E_TRIGGER_POPUP_SAVE: "trigger.save.popup", - E_OVERLAY_UPLOAD: "overlay.upload", - E_OVERLAY_DELETE: "overlay.delete", - E_POPUP_DELETE: "popup.delete", - E_POPUP_SAVE: "popup.save", - E_POPUP_RENAME: "popup.rename", - E_TRIGGER_POPUP: "trigger.popup", - E_TRIGGER_BACK: "trigger.back", - E_SWITCH_SETTINGS: "switch.settings", - E_SWITCH_OVERLAYS: "switch.overlays", - E_SWITCH_POPUPS: "switch.popups", - E_SAVE_SETTINGS: "component.save", - E_COMPONENT_BACK: "component.back", // component sends back (to main page) request + E_ADD_TRIGGER: 'add.trigger', + E_EDIT_TRIGGER: 'edit.trigger', + E_TRIGGER_PROPERTIES: 'trigger.properties', + E_TRIGGER_PROPERTIES_SAVE: 'trigger.properties.save', + E_TRIGGER_DELETE: 'trigger.delete', + E_TRIGGER_SHAPE_CHANGE: 'trigger.shape.change', + E_TRIGGER_OVERLAY_CHANGE: 'trigger.overlay.change', + E_TRIGGER_OVERLAY: 'trigger.overlay', + E_TRIGGER_OVERLAY_ADD: 'trigger.add.overlay', + E_TRIGGER_OVERLAY_SAVE: 'trigger.overlay.save', + E_TRIGGER_POPUP_ADD: 'trigger.add.popup', + E_TRIGGER_POPUP_SAVE: 'trigger.save.popup', + E_OVERLAY_UPLOAD: 'overlay.upload', + E_OVERLAY_DELETE: 'overlay.delete', + E_POPUP_DELETE: 'popup.delete', + E_POPUP_SAVE: 'popup.save', + E_POPUP_RENAME: 'popup.rename', + E_TRIGGER_POPUP: 'trigger.popup', + E_TRIGGER_BACK: 'trigger.back', + E_SWITCH_SETTINGS: 'switch.settings', + E_SWITCH_OVERLAYS: 'switch.overlays', + E_SWITCH_POPUPS: 'switch.popups', + E_SAVE_SETTINGS: 'component.save', + E_COMPONENT_BACK: 'component.back', // component sends back (to main page) request }; -export default ACTIONS; \ No newline at end of file +export default ACTIONS; diff --git a/components/ILIAS/COPage/PC/InteractiveImage/js/editor/src/actions/iim-command-action-factory.js b/components/ILIAS/COPage/PC/InteractiveImage/js/editor/src/actions/iim-command-action-factory.js index 7796478df366..a72775795646 100755 --- a/components/ILIAS/COPage/PC/InteractiveImage/js/editor/src/actions/iim-command-action-factory.js +++ b/components/ILIAS/COPage/PC/InteractiveImage/js/editor/src/actions/iim-command-action-factory.js @@ -12,26 +12,24 @@ * https://www.ilias.de * https://github.com/ILIAS-eLearning * - *********************************************************************/ + ******************************************************************** */ -import ACTIONS from "./iim-action-types.js"; +import ACTIONS from './iim-action-types.js'; /** * COPage command actions being sent to the server */ export default class IIMCommandActionFactory { - /** * @type {ClientActionFactory} */ - //clientActionFactory; - + // clientActionFactory; /** * @param {ClientActionFactory} clientActionFactory */ constructor(clientActionFactory) { - this.COMPONENT = "InteractiveImage"; + this.COMPONENT = 'InteractiveImage'; this.clientActionFactory = clientActionFactory; } @@ -41,12 +39,20 @@ export default class IIMCommandActionFactory { * @param redirect * @return {CommandAction} */ - saveTriggerProperties(triggerNr, title, shapeType, coords) { + saveTriggerProperties(triggerNr, title, shapeType, coords, hl_mode, hl_class) { return this.clientActionFactory.command(this.COMPONENT, ACTIONS.C_SAVE_TRIGGER_PROPERTIES, { trigger_nr: triggerNr, - title: title, + title, shape_type: shapeType, - coords: coords + coords, + hl_mode, + hl_class, + }); + } + + deleteTrigger(nr) { + return this.clientActionFactory.command(this.COMPONENT, ACTIONS.C_DELETE_TRIGGER, { + nr, }); } @@ -59,8 +65,8 @@ export default class IIMCommandActionFactory { saveTriggerOverlay(triggerNr, overlay, coords) { return this.clientActionFactory.command(this.COMPONENT, ACTIONS.C_SAVE_TRIGGER_OVERLAY, { trigger_nr: triggerNr, - overlay: overlay, - coords: coords + overlay, + coords, }); } @@ -73,9 +79,9 @@ export default class IIMCommandActionFactory { saveTriggerPopup(triggerNr, popup, position, size) { return this.clientActionFactory.command(this.COMPONENT, ACTIONS.C_SAVE_TRIGGER_POPUP, { trigger_nr: triggerNr, - popup: popup, - position: position, - size: size + popup, + position, + size, }); } @@ -89,7 +95,7 @@ export default class IIMCommandActionFactory { deleteOverlay(overlay) { return this.clientActionFactory.command(this.COMPONENT, ACTIONS.C_DELETE_OVERLAY, { - overlay: overlay + overlay, }); } @@ -98,7 +104,7 @@ export default class IIMCommandActionFactory { * @return {CommandAction} */ savePopup(data) { - console.log("---"); + console.log('---'); console.log(data); return this.clientActionFactory.formCommand(this.COMPONENT, ACTIONS.C_SAVE_POPUP, data); @@ -106,7 +112,7 @@ export default class IIMCommandActionFactory { deletePopup(nr) { return this.clientActionFactory.command(this.COMPONENT, ACTIONS.C_DELETE_POPUP, { - nr: nr + nr, }); } @@ -117,5 +123,4 @@ export default class IIMCommandActionFactory { saveSettings(data) { return this.clientActionFactory.formCommand(this.COMPONENT, ACTIONS.C_SAVE_SETTINGS, data); } - -} \ No newline at end of file +} diff --git a/components/ILIAS/COPage/PC/InteractiveImage/js/editor/src/actions/iim-editor-action-factory.js b/components/ILIAS/COPage/PC/InteractiveImage/js/editor/src/actions/iim-editor-action-factory.js index ec376265de6b..3259957a1c4e 100755 --- a/components/ILIAS/COPage/PC/InteractiveImage/js/editor/src/actions/iim-editor-action-factory.js +++ b/components/ILIAS/COPage/PC/InteractiveImage/js/editor/src/actions/iim-editor-action-factory.js @@ -12,27 +12,26 @@ * https://www.ilias.de * https://github.com/ILIAS-eLearning * - *********************************************************************/ + ******************************************************************** */ -import ACTIONS from "./iim-action-types.js"; +import ACTIONS from './iim-action-types.js'; /** * COPage action factory * */ export default class IIMEditorActionFactory { - /** * @type {EditorActionFactory} */ - //editorActionFactory; + // editorActionFactory; /** * * @param {EditorActionFactory} editorActionFactory */ constructor(editorActionFactory) { - this.COMPONENT = "InteractiveImage"; + this.COMPONENT = 'InteractiveImage'; this.editorActionFactory = editorActionFactory; } @@ -49,7 +48,7 @@ export default class IIMEditorActionFactory { editTrigger(nr) { return this.editorActionFactory.action(this.COMPONENT, ACTIONS.E_EDIT_TRIGGER, { - triggerNr : nr + triggerNr: nr, }); } @@ -109,13 +108,25 @@ export default class IIMEditorActionFactory { nr, title, shapeType, - coords + coords, + hl_mode, + hl_class, ) { return this.editorActionFactory.action(this.COMPONENT, ACTIONS.E_TRIGGER_PROPERTIES_SAVE, { - nr: nr, - title: title, - shapeType: shapeType, - coords: coords + nr, + title, + shapeType, + coords, + hl_mode, + hl_class, + }); + } + + deleteTrigger( + nr, + ) { + return this.editorActionFactory.action(this.COMPONENT, ACTIONS.E_TRIGGER_DELETE, { + nr, }); } @@ -123,18 +134,17 @@ export default class IIMEditorActionFactory { * @returns {EditorAction} */ changeTriggerShape( - shape + shape, ) { return this.editorActionFactory.action(this.COMPONENT, ACTIONS.E_TRIGGER_SHAPE_CHANGE, { - shape: shape + shape, }); } /** * @returns {EditorAction} */ - addTriggerOverlay( - ) { + addTriggerOverlay() { return this.editorActionFactory.action(this.COMPONENT, ACTIONS.E_TRIGGER_OVERLAY_ADD, { }); } @@ -145,12 +155,12 @@ export default class IIMEditorActionFactory { saveTriggerOverlay( nr, overlay, - coords + coords, ) { return this.editorActionFactory.action(this.COMPONENT, ACTIONS.E_TRIGGER_OVERLAY_SAVE, { - nr: nr, - overlay: overlay, - coords: coords + nr, + overlay, + coords, }); } @@ -161,13 +171,13 @@ export default class IIMEditorActionFactory { nr, popup, position, - size + size, ) { return this.editorActionFactory.action(this.COMPONENT, ACTIONS.E_TRIGGER_POPUP_SAVE, { - nr: nr, - popup: popup, - position: position, - size: size + nr, + popup, + position, + size, }); } @@ -175,57 +185,55 @@ export default class IIMEditorActionFactory { * @returns {EditorAction} */ changeTriggerOverlay( - overlay + overlay, ) { return this.editorActionFactory.action(this.COMPONENT, ACTIONS.E_TRIGGER_OVERLAY_CHANGE, { - overlay: overlay + overlay, }); } uploadOverlay(data) { return this.editorActionFactory.action(this.COMPONENT, ACTIONS.E_OVERLAY_UPLOAD, { - data: data + data, }); } deleteOverlay(overlay) { return this.editorActionFactory.action(this.COMPONENT, ACTIONS.E_OVERLAY_DELETE, { - overlay: overlay + overlay, }); } renamePopup(nr) { return this.editorActionFactory.action(this.COMPONENT, ACTIONS.E_POPUP_RENAME, { - nr: nr + nr, }); } deletePopup(nr) { return this.editorActionFactory.action(this.COMPONENT, ACTIONS.E_POPUP_DELETE, { - nr: nr + nr, }); } /** * @returns {EditorAction} */ - addTriggerPopup( - ) { + addTriggerPopup() { return this.editorActionFactory.action(this.COMPONENT, ACTIONS.E_TRIGGER_POPUP_ADD, { }); } savePopup(data, nr) { return this.editorActionFactory.action(this.COMPONENT, ACTIONS.E_POPUP_SAVE, { - data: data, - nr: nr + data, + nr, }); } saveSettings(form) { return this.editorActionFactory.action(this.COMPONENT, ACTIONS.E_SAVE_SETTINGS, { - form: form + form, }); } - -} \ No newline at end of file +} diff --git a/components/ILIAS/COPage/PC/InteractiveImage/js/editor/src/model/model.js b/components/ILIAS/COPage/PC/InteractiveImage/js/editor/src/model/model.js index 03b52a73e0ac..b9417819ae21 100755 --- a/components/ILIAS/COPage/PC/InteractiveImage/js/editor/src/model/model.js +++ b/components/ILIAS/COPage/PC/InteractiveImage/js/editor/src/model/model.js @@ -12,37 +12,35 @@ * https://www.ilias.de * https://github.com/ILIAS-eLearning * - *********************************************************************/ + ******************************************************************** */ -import AreaFactory from "../area/area-factory.js"; -import TriggerFactory from "../trigger/trigger-factory.js"; +import AreaFactory from '../area/area-factory.js'; +import TriggerFactory from '../trigger/trigger-factory.js'; import OverlayFactory from '../overlay/overlay-factory.js'; import MarkerFactory from '../marker/marker-factory.js'; - /** * Interactive Image Model */ export default class Model { - constructor() { - this.debug = true; - - this.STATE_OVERVIEW = "overview"; // overview - this.STATE_TRIGGER_PROPERTIES = "trigger_prop"; // trigger properties - this.STATE_TRIGGER_OVERLAY = "trigger_overlay"; // trigger overlay - this.STATE_TRIGGER_POPUP = "trigger_popup"; // trigger popup - this.STATE_SETTINGS = "settings"; // settings - this.STATE_OVERLAYS = "overlays"; // settings - this.STATE_POPUPS = "popups"; // settings - this.ACTION_STATE_ADD = "add"; // add - this.ACTION_STATE_EDIT = "edit"; // edit + this.debug = false; + + this.STATE_OVERVIEW = 'overview'; // overview + this.STATE_TRIGGER_PROPERTIES = 'trigger_prop'; // trigger properties + this.STATE_TRIGGER_OVERLAY = 'trigger_overlay'; // trigger overlay + this.STATE_TRIGGER_POPUP = 'trigger_popup'; // trigger popup + this.STATE_SETTINGS = 'settings'; // settings + this.STATE_OVERLAYS = 'overlays'; // settings + this.STATE_POPUPS = 'popups'; // settings + this.ACTION_STATE_ADD = 'add'; // add + this.ACTION_STATE_EDIT = 'edit'; // edit this.model = { state: this.STATE_OVERVIEW, areaNr: 0, iim: null, - currentTrigger: null + currentTrigger: null, }; this.states = [ this.STATE_OVERVIEW, @@ -51,11 +49,11 @@ export default class Model { this.STATE_TRIGGER_POPUP, this.STATE_SETTINGS, this.STATE_OVERLAYS, - this.STATE_POPUPS + this.STATE_POPUPS, ]; this.actionStates = [ this.ACTION_STATE_ADD, - this.ACTION_STATE_EDIT + this.ACTION_STATE_EDIT, ]; this.areaFactory = new AreaFactory(); this.triggerFactory = new TriggerFactory(); @@ -81,7 +79,7 @@ export default class Model { */ setState(state) { if (this.states.includes(state)) { - this.log("model.setState " + state); + this.log(`model.setState ${state}`); this.model.state = state; } } @@ -120,50 +118,48 @@ export default class Model { addStandardTrigger() { const area = this.areaFactory.area( - "Rect", - "10,10,50,50" + 'Rect', + '10,10,50,50', ); this.model.currentTrigger = this.triggerFactory.trigger( this.getNextTriggerNr(), null, null, - "", - "", - "", - "", - area + '', + '', + '', + '', + area, ); - this.log("addStandardTrigger"); + this.log('addStandardTrigger'); } changeTriggerShape(shape) { let area; let marker; - console.log("MODEL: CHANGE SHAPE"); - console.log(shape); switch (shape) { - case "Rect": + case 'Rect': area = this.areaFactory.area( - "Rect", - "10,10,50,50" + 'Rect', + '10,10,50,50', ); this.model.currentTrigger.setArea(area); break; - case "Circle": + case 'Circle': area = this.areaFactory.area( - "Circle", - "100,100,50" + 'Circle', + '100,100,50', ); this.model.currentTrigger.setArea(area); break; - case "Poly": + case 'Poly': area = this.areaFactory.area( - "Poly", - "" + 'Poly', + '', ); this.model.currentTrigger.setArea(area); break; - case "Marker": + case 'Marker': marker = this.markerFactory.marker(0, 0, this.model.currentTrigger.getNr()); this.model.currentTrigger.setMarker(marker); break; @@ -190,7 +186,7 @@ export default class Model { setTriggerByNr(triggerNr) { this.model.currentTrigger = this.triggerFactory.fullTriggerFromModel( triggerNr, - this.model.iim + this.model.iim, ); } @@ -225,5 +221,4 @@ export default class Model { }); return title; } - -} \ No newline at end of file +} diff --git a/components/ILIAS/COPage/PC/InteractiveImage/js/editor/src/shape-edit/shape-editor.js b/components/ILIAS/COPage/PC/InteractiveImage/js/editor/src/shape-edit/shape-editor.js index 6c59ea6c00c5..7c02092d7a4d 100755 --- a/components/ILIAS/COPage/PC/InteractiveImage/js/editor/src/shape-edit/shape-editor.js +++ b/components/ILIAS/COPage/PC/InteractiveImage/js/editor/src/shape-edit/shape-editor.js @@ -1,4 +1,3 @@ - /** * This file is part of ILIAS, a powerful learning management system * published by ILIAS open source e-Learning e.V. @@ -13,204 +12,197 @@ * https://www.ilias.de * https://github.com/ILIAS-eLearning * - *********************************************************************/ + ******************************************************************** */ -import ShapeFactory from "./shape-factory.js"; -import Poly from "./poly.js"; -import IimCommonUtil from "../../../common/src/util.js"; +import ShapeFactory from './shape-factory.js'; +import Poly from './poly.js'; +import IimCommonUtil from '../../../common/src/util.js'; /** * Circle */ export default class ShapeEditor { - - /** + /** * @param Handle center * @param Handle point */ - constructor(mobElement) { - this.mobElement = mobElement; - this.shapes = []; - this.currentShape = null; - this.overlays = []; - this.currentOverlay = null; - this.markers = []; - this.currentMarker = null; - this.factory = new ShapeFactory(); - this.initEvents(); - this.allowAdd = false - this.iimCommonUtil = new IimCommonUtil(); - } - - setAllowAdd(allow) { - this.allowAdd = allow; - } - - initEvents() { - const t = this; - const f = this.factory; - const mob = this.mobElement; - mob.addEventListener("click", (e) => { - if (t.currentShape === null || !t.allowAdd) { - return; - } - const cs = t.shapes[t.currentShape]; - if (cs instanceof Poly) { - e = e || window.event; - e.preventDefault(); - let rect = mob.getBoundingClientRect(); - let x = Math.round(e.clientX - rect.left); - let y = Math.round(e.clientY - rect.top); - cs.addHandle(f.handle(x, y)); - t.repaint(); - } - }); - } - - factory() { - return this.factory; - } - - removeAllShapes() { - this.shapes = []; - this.currentShape = null; - } - addShape(shape, asCurrent = false) { - if (!shape) { - return; - shape = this.factory.rect(10,10,50,50); - } - this.shapes.push(shape); - if (asCurrent) { - this.currentShape = this.shapes.length - 1; - } - } - - addMarker(marker, asCurrent = false) { - if (!marker) { - return; - } - this.markers.push(marker); - if (asCurrent) { - this.currentMarker = this.markers.length - 1; - } - } - - removeAllMarkers() { - this.markers = []; - this.currentMarker = null; - } - - removeAllOverlays() { - this.overlays = []; - this.currentOverlay = null; - } - - addOverlay(overlay, asCurrent = false) { - this.overlays.push(overlay); - if (asCurrent) { - this.currentOverlay = this.overlays.length - 1; - } - } - - removeAllChilds(node) { - while (node.firstChild) { - node.removeChild(node.lastChild); - } - } - - removeAllChildsOfName(node, name) { - node.querySelectorAll(name).forEach(n => n.remove()); - } - - removeAllChildsBySelector(node, selector) { - node.querySelectorAll(selector).forEach(n => n.remove()); - } - - getSvg() { - return this.iimCommonUtil.getOverlaySvg(this.mobElement); - } - - addClickLayer() { - let click = document.getElementById("il-copg-iim-click"); - if (!click) { - const img = this.mobElement.querySelector("img"); - const click = img.cloneNode(true); - click.id = "il-copg-iim-click"; - click.style.position = "absolute"; - click.style.left = "0px"; - click.style.top = "0px"; - click.style.width = "100%"; - click.style.height = "100%"; - click.style.opacity = "1e-10"; - this.mobElement.appendChild(click); - - const map = document.createElement("map"); - map.name = "il-copg-iim-map"; - map.id = "il-copg-iim-map"; - this.mobElement.appendChild(map); - let cnt = 0; - this.shapes.forEach((shape) => { - shape.addToMap(cnt++, map); - }); - click.useMap = "#il-copg-iim-map"; - }; - return click; - } - - removeAllHandles() { - this.removeAllChildsOfName(this.mobElement, "a[data-copg-iim-type='handle']"); - } - - removeAllOverlayImages() { - this.removeAllChildsBySelector(this.mobElement, "img[data-copg-iim-type='overlay']"); - } - - removeAllMarkerLinks() { - this.removeAllChildsBySelector(this.mobElement, "a[data-copg-iim-type='marker']"); - } - - repaint() { - console.log("REPAINT"); - this.repaintSvg(); - this.removeAllHandles(); - this.removeAllOverlayImages(); - this.removeAllMarkerLinks(); - if (this.currentShape !== null) { - console.log("1"); - const cs = this.shapes[this.currentShape]; - cs.getHandles().forEach((h) => { - h.addHandleToMobElement(this.mobElement, !this.allowAdd); - h.setOnDrag(() => { - this.repaintSvg(); - }); - }); - } else { - console.log("2"); - if (this.currentMarker !== null) { - console.log("3"); - console.log("ADDING MARKER"); - const m = this.markers[this.currentMarker]; - m.addMarkerToMobElement(this.mobElement, true); - } else { - this.markers.forEach((m) => { - m.addMarkerToMobElement(this.mobElement, false); - }); - } - } - if (this.currentOverlay !== null) { - const ov = this.overlays[this.currentOverlay]; - ov.addOverlayToMobElement(this.mobElement, true); - } - } - - repaintSvg() { - const svg = this.getSvg(); - this.removeAllChilds(svg); - let cnt = 0; - this.shapes.forEach((shape) => { - shape.addToSvg(svg); + constructor(mobElement) { + this.mobElement = mobElement; + this.shapes = []; + this.currentShape = null; + this.overlays = []; + this.currentOverlay = null; + this.markers = []; + this.currentMarker = null; + this.factory = new ShapeFactory(); + this.initEvents(); + this.allowAdd = false; + this.iimCommonUtil = new IimCommonUtil(); + } + + setAllowAdd(allow) { + this.allowAdd = allow; + } + + initEvents() { + const t = this; + const f = this.factory; + const mob = this.mobElement; + mob.addEventListener('click', (e) => { + if (t.currentShape === null || !t.allowAdd) { + return; + } + const cs = t.shapes[t.currentShape]; + if (cs instanceof Poly) { + e = e || window.event; + e.preventDefault(); + const rect = mob.getBoundingClientRect(); + const x = Math.round(e.clientX - rect.left); + const y = Math.round(e.clientY - rect.top); + cs.addHandle(f.handle(x, y)); + t.repaint(); + } + }); + } + + factory() { + return this.factory; + } + + removeAllShapes() { + this.shapes = []; + this.currentShape = null; + } + + addShape(shape, asCurrent = false) { + if (!shape) { + return; + shape = this.factory.rect(10, 10, 50, 50); + } + this.shapes.push(shape); + if (asCurrent) { + this.currentShape = this.shapes.length - 1; + } + } + + addMarker(marker, asCurrent = false) { + if (!marker) { + return; + } + this.markers.push(marker); + if (asCurrent) { + this.currentMarker = this.markers.length - 1; + } + } + + removeAllMarkers() { + this.markers = []; + this.currentMarker = null; + } + + removeAllOverlays() { + this.overlays = []; + this.currentOverlay = null; + } + + addOverlay(overlay, asCurrent = false) { + this.overlays.push(overlay); + if (asCurrent) { + this.currentOverlay = this.overlays.length - 1; + } + } + + removeAllChilds(node) { + while (node.firstChild) { + node.removeChild(node.lastChild); + } + } + + removeAllChildsOfName(node, name) { + node.querySelectorAll(name).forEach((n) => n.remove()); + } + + removeAllChildsBySelector(node, selector) { + node.querySelectorAll(selector).forEach((n) => n.remove()); + } + + getSvg() { + return this.iimCommonUtil.getOverlaySvg(this.mobElement); + } + + addClickLayer() { + const click = document.getElementById('il-copg-iim-click'); + if (!click) { + const img = this.mobElement.querySelector('img'); + const click = img.cloneNode(true); + click.id = 'il-copg-iim-click'; + click.style.position = 'absolute'; + click.style.left = '0px'; + click.style.top = '0px'; + click.style.width = '100%'; + click.style.height = '100%'; + click.style.opacity = '1e-10'; + this.mobElement.appendChild(click); + + const map = document.createElement('map'); + map.name = 'il-copg-iim-map'; + map.id = 'il-copg-iim-map'; + this.mobElement.appendChild(map); + let cnt = 0; + this.shapes.forEach((shape) => { + shape.addToMap(cnt++, map); + }); + click.useMap = '#il-copg-iim-map'; + } + return click; + } + + removeAllHandles() { + this.removeAllChildsOfName(this.mobElement, "a[data-copg-iim-type='handle']"); + } + + removeAllOverlayImages() { + this.removeAllChildsBySelector(this.mobElement, "img[data-copg-iim-type='overlay']"); + } + + removeAllMarkerLinks() { + this.removeAllChildsBySelector(this.mobElement, "a[data-copg-iim-type='marker']"); + } + + repaint() { + this.repaintSvg(); + this.removeAllHandles(); + this.removeAllOverlayImages(); + this.removeAllMarkerLinks(); + if (this.currentShape !== null) { + const cs = this.shapes[this.currentShape]; + cs.getHandles().forEach((h) => { + h.addHandleToMobElement(this.mobElement, !this.allowAdd); + h.setOnDrag(() => { + this.repaintSvg(); }); - } - + }); + } else if (this.currentMarker !== null) { + const m = this.markers[this.currentMarker]; + m.addMarkerToMobElement(this.mobElement, true); + } else { + this.markers.forEach((m) => { + m.addMarkerToMobElement(this.mobElement, false); + }); + } + if (this.currentOverlay !== null) { + const ov = this.overlays[this.currentOverlay]; + ov.addOverlayToMobElement(this.mobElement, true); + } + } + + repaintSvg() { + const svg = this.getSvg(); + this.removeAllChilds(svg); + const cnt = 0; + this.shapes.forEach((shape) => { + const shapeEl = shape.addToSvg(svg); + shapeEl.classList.add('copg-iim-hl-mode-Edit'); + }); + } } diff --git a/components/ILIAS/COPage/PC/InteractiveImage/js/editor/src/ui/iim-ui-action-handler.js b/components/ILIAS/COPage/PC/InteractiveImage/js/editor/src/ui/iim-ui-action-handler.js index 06bea127ea04..33be37d3cf4f 100755 --- a/components/ILIAS/COPage/PC/InteractiveImage/js/editor/src/ui/iim-ui-action-handler.js +++ b/components/ILIAS/COPage/PC/InteractiveImage/js/editor/src/ui/iim-ui-action-handler.js @@ -12,381 +12,408 @@ * https://www.ilias.de * https://github.com/ILIAS-eLearning * - *********************************************************************/ + ******************************************************************** */ -import ACTIONS from "../actions/iim-action-types.js"; -import ActionFactory from "../actions/iim-action-factory.js" -import UI from "./iim-ui.js"; -import Util from "../../../../../../Editor/js/src/ui/util.js"; +import ACTIONS from '../actions/iim-action-types.js'; +import ActionFactory from '../actions/iim-action-factory.js'; +import UI from './iim-ui.js'; +import Util from '../../../../../../Editor/js/src/ui/util.js'; /** * Interactive image UI action handler */ export default class IIMUIActionHandler { - - /** + /** * @type {ActionFactory} */ - //actionFactory; + // actionFactory; - /** + /** * @type {Dispatcher} */ - //dispatcher; + // dispatcher; - /** + /** * @type {Client} */ - //client; + // client; - /** + /** * @param {ActionFactory} actionFactory * @param {Client} client */ - constructor(actionFactory, client) { - this.actionFactory = actionFactory; - this.client = client; - this.ui = null; - this.dispatcher = null; - this.util = new Util(); - } - - /** + constructor(actionFactory, client) { + this.actionFactory = actionFactory; + this.client = client; + this.ui = null; + this.dispatcher = null; + this.util = new Util(); + } + + /** * @param {UI} ui */ - setUI(ui) { - this.ui = ui; - } + setUI(ui) { + this.ui = ui; + } - /** + /** * @param {Dispatcher} dispatcher */ - setDispatcher(dispatcher) { - this.dispatcher = dispatcher; - } + setDispatcher(dispatcher) { + this.dispatcher = dispatcher; + } - /** + /** * @param {EditorAction} action * @param {IIMModel} model */ - handle(action, model) { - const dispatcher = this.dispatcher; - const actionFactory = this.actionFactory; - const client = this.client; - let form_sent = false; - - const params = action.getParams(); - - // page actions - if (action.getComponent() === "InteractiveImage") { - switch (action.getType()) { - - case ACTIONS.E_ADD_TRIGGER: - this.ui.editTrigger(model.getCurrentTrigger().nr); - break; - - case ACTIONS.E_EDIT_TRIGGER: - this.ui.editTrigger(params.triggerNr); - break; - - case ACTIONS.E_TRIGGER_SHAPE_CHANGE: - this.ui.repaintTrigger(); - break; - - case ACTIONS.E_TRIGGER_PROPERTIES: - this.ui.showTriggerProperties(); - break; - - case ACTIONS.E_TRIGGER_OVERLAY: - this.ui.showTriggerOverlay(); - break; - - case ACTIONS.E_TRIGGER_OVERLAY_CHANGE: - //this.ui.showTriggerOverlay(); - break; - - case ACTIONS.E_TRIGGER_POPUP: - this.ui.showTriggerPopup(); - break; - - case ACTIONS.E_TRIGGER_BACK: - this.ui.showMainScreen(); - break; - - case ACTIONS.E_SWITCH_SETTINGS: - this.ui.showSettings(); - break; - - case ACTIONS.E_SWITCH_OVERLAYS: - this.ui.showOverlays(); - break; - - case ACTIONS.E_SWITCH_POPUPS: - this.ui.showPopups(); - break; - - case ACTIONS.E_TRIGGER_PROPERTIES_SAVE: - this.sendSaveTriggerPropertiesCommand( - params, - model - ); - break; - - case ACTIONS.E_TRIGGER_OVERLAY_ADD: - this.ui.showOverlayModal(); - break; - - case ACTIONS.E_TRIGGER_OVERLAY_SAVE: - this.sendSaveTriggerOverlayCommand( - params, - model - ); - break; - - case ACTIONS.E_TRIGGER_POPUP_ADD: - this.ui.showPopupModal(); - break; - - case ACTIONS.E_TRIGGER_POPUP_SAVE: - this.sendSaveTriggerPopupCommand( - params, - model - ); - break; - - case ACTIONS.E_POPUP_SAVE: - this.sendPopupSave(params, model); - break; - - case ACTIONS.E_POPUP_RENAME: - this.ui.showPopupModal(params, model); - break; - - case ACTIONS.E_POPUP_DELETE: - this.sendDeletePopup(params, model); - break; - - case ACTIONS.E_OVERLAY_UPLOAD: - this.sendUploadOverlay(params, model); - break; - - case ACTIONS.E_OVERLAY_DELETE: - this.sendDeleteOverlay(params, model); - break; - - case ACTIONS.E_SAVE_SETTINGS: - this.sendSaveSettings(params, model); - break; - - case ACTIONS.E_COMPONENT_BACK: - this.ui.redirectToPage(); - break; - - } - } - } - - sendSaveTriggerPropertiesCommand(params, model) { - let update_action; - const af = this.actionFactory; - const dispatch = this.dispatcher; - - this.ui.deactivateSlateButtons(); - this.ui.setLoader(); - - update_action = af.interactiveImage().command().saveTriggerProperties( - params.nr, - params.title, - params.shapeType, - params.coords - ); - - this.client.sendCommand(update_action).then(result => { - if (this.handleStandardResponse(result, model)) { - this.ui.activateSlateButtons(); - this.ui.refreshTriggerViewControl(); - this.ui.setMessage('commonSuccessMessage'); - } - }); - } - - sendSaveTriggerOverlayCommand(params, model) { - let update_action; - const af = this.actionFactory; - const dispatch = this.dispatcher; - - this.ui.deactivateSlateButtons(); - this.ui.setLoader(); - - update_action = af.interactiveImage().command().saveTriggerOverlay( - params.nr, - params.overlay, - params.coords - ); - - this.client.sendCommand(update_action).then(result => { - if (this.handleStandardResponse(result, model)) { - this.ui.activateSlateButtons(); - this.ui.refreshTriggerViewControl(); - this.ui.setMessage('commonSuccessMessage'); - this.ui.updateOverlayPresentationAfterSaving(); - } - }); - } - - sendSaveTriggerPopupCommand(params, model) { - let update_action; - const af = this.actionFactory; - const dispatch = this.dispatcher; - - this.ui.deactivateSlateButtons(); - this.ui.setLoader(); - - update_action = af.interactiveImage().command().saveTriggerPopup( - params.nr, - params.popup, - params.position, - params.size - ); - - this.client.sendCommand(update_action).then(result => { - if (this.handleStandardResponse(result, model)) { - this.ui.activateSlateButtons(); - this.ui.refreshTriggerViewControl(); - this.ui.setMessage('commonSuccessMessage'); - this.ui.updatePopupPresentationAfterSaving(); - } - }); - } - - handleStandardResponse(result, model) - { - const pl = result.getPayload(); - - if(pl.error === false) - { - model.initModel(pl.model); - model.updateCurrentTriggerFromModel(); - this.ui.uiModel.backgroundImage = pl.backgroundImage; - return true; - } - return false; - } - - sendUploadOverlay(params, model) { - let upload_action; - const af = this.actionFactory; - const dispatch = this.dispatcher; - const util = this.util; - - const old_overlays = []; - model.getOverlays().forEach((ov) => { - old_overlays.push(ov.name) - }); - - this.util.sendFiles(params.data.form).then(() => { - const data = new FormData(params.data.form); - upload_action = af.interactiveImage().command().uploadOverlay( - data - ); - util.hideModal(this.ui.uiModel.modal); - this.ui.deactivateSlateButtons(); - this.ui.setLoader(); - this.client.sendCommand(upload_action).then(result => { - if (this.handleStandardResponse(result, model)) { - this.ui.activateSlateButtons(); - if (model.getCurrentTrigger() == null) { - this.ui.showOverlays(); - this.ui.setMessage('commonSuccessMessage'); - } else { - this.ui.refreshTriggerViewControl(); - this.ui.showTriggerOverlay(); - this.ui.setMessage('commonSuccessMessage'); - model.getOverlays().forEach((ov) => { - if (!old_overlays.includes(ov.name)) { - dispatch.dispatch(af.interactiveImage() - .editor() - .changeTriggerOverlay( - ov.name - )); - } - }); - } - } - }); - }); + handle(action, model) { + const { dispatcher } = this; + const { actionFactory } = this; + const { client } = this; + const form_sent = false; + + const params = action.getParams(); + + // page actions + if (action.getComponent() === 'InteractiveImage') { + switch (action.getType()) { + case ACTIONS.E_ADD_TRIGGER: + this.ui.addTrigger(); + break; + + case ACTIONS.E_EDIT_TRIGGER: + this.ui.editTrigger(params.triggerNr); + break; + + case ACTIONS.E_TRIGGER_SHAPE_CHANGE: + this.ui.repaintTrigger(); + break; + + case ACTIONS.E_TRIGGER_PROPERTIES: + this.ui.showTriggerProperties(); + break; + + case ACTIONS.E_TRIGGER_OVERLAY: + this.ui.showTriggerOverlay(); + break; + + case ACTIONS.E_TRIGGER_OVERLAY_CHANGE: + // this.ui.showTriggerOverlay(); + break; + + case ACTIONS.E_TRIGGER_POPUP: + this.ui.showTriggerPopup(); + break; + + case ACTIONS.E_TRIGGER_BACK: + this.ui.showMainScreen(); + break; + + case ACTIONS.E_SWITCH_SETTINGS: + this.ui.showSettings(); + break; + + case ACTIONS.E_SWITCH_OVERLAYS: + this.ui.showOverlays(); + break; + + case ACTIONS.E_SWITCH_POPUPS: + this.ui.showPopups(); + break; + + case ACTIONS.E_TRIGGER_PROPERTIES_SAVE: + this.sendSaveTriggerPropertiesCommand( + params, + model, + ); + break; + + case ACTIONS.E_TRIGGER_DELETE: + this.sendTriggerDeleteCommand( + params, + model, + ); + break; + + case ACTIONS.E_TRIGGER_OVERLAY_ADD: + this.ui.showOverlayModal(); + break; + + case ACTIONS.E_TRIGGER_OVERLAY_SAVE: + this.sendSaveTriggerOverlayCommand( + params, + model, + ); + break; + + case ACTIONS.E_TRIGGER_POPUP_ADD: + this.ui.showPopupModal(); + break; + + case ACTIONS.E_TRIGGER_POPUP_SAVE: + this.sendSaveTriggerPopupCommand( + params, + model, + ); + break; + + case ACTIONS.E_POPUP_SAVE: + this.sendPopupSave(params, model); + break; + + case ACTIONS.E_POPUP_RENAME: + this.ui.showPopupModal(params, model); + break; + + case ACTIONS.E_POPUP_DELETE: + this.sendDeletePopup(params, model); + break; + + case ACTIONS.E_OVERLAY_UPLOAD: + this.sendUploadOverlay(params, model); + break; + + case ACTIONS.E_OVERLAY_DELETE: + this.sendDeleteOverlay(params, model); + break; + + case ACTIONS.E_SAVE_SETTINGS: + this.sendSaveSettings(params, model); + break; + + case ACTIONS.E_COMPONENT_BACK: + this.ui.redirectToPage(); + break; + } } - - sendDeleteOverlay(params, model) { - const af = this.actionFactory; - const dispatch = this.dispatcher; - const delete_action = af.interactiveImage().command().deleteOverlay( - params.overlay - ); - this.client.sendCommand(delete_action).then(result => { - this.handleStandardResponse(result, model); - dispatch.dispatch(af.interactiveImage().editor().switchOverlays()); - }); - } - - sendPopupSave(params, model) { - const af = this.actionFactory; - const dispatch = this.dispatcher; - const util = this.util; - const data = new FormData(params.data.form); - data.append('nr', params.nr); - const save_action = af.interactiveImage().command().savePopup( - data - ); - this.client.sendCommand(save_action).then(result => { - this.handleStandardResponse(result, model); - util.hideModal(this.ui.uiModel.modal); - if (model.getCurrentTrigger() == null) { - this.ui.showPopups(); - this.ui.setMessage('commonSuccessMessage'); - } else { - this.ui.refreshTriggerViewControl(); - this.ui.showTriggerPopup(); - this.ui.setMessage('commonSuccessMessage'); - } - }); + } + + sendSaveTriggerPropertiesCommand(params, model) { + let update_action; + const af = this.actionFactory; + const dispatch = this.dispatcher; + + this.ui.deactivateSlateButtons(); + this.ui.setLoader(); + + update_action = af.interactiveImage().command().saveTriggerProperties( + params.nr, + params.title, + params.shapeType, + params.coords, + params.hl_mode, + params.hl_class, + ); + + this.client.sendCommand(update_action).then((result) => { + if (this.handleStandardResponse(result, model)) { + this.ui.activateSlateButtons(); + this.ui.refreshTriggerViewControl(); + this.ui.showTriggerDeleteButton(); + this.ui.setMessage('commonSuccessMessage'); + this.ui.editTrigger(params.nr); // this will also update the area from the model + } + }); + } + + sendTriggerDeleteCommand(params, model) { + let update_action; + const af = this.actionFactory; + const dispatch = this.dispatcher; + + this.ui.deactivateSlateButtons(); + this.ui.setLoader(); + + update_action = af.interactiveImage().command().deleteTrigger( + params.nr, + ); + + this.client.sendCommand(update_action).then((result) => { + if (this.handleStandardResponse(result, model)) { + this.ui.activateSlateButtons(); + this.ui.refreshTriggerViewControl(); + this.ui.showMainScreen(); + this.ui.setMessage('commonSuccessMessage'); + } + }); + } + + sendSaveTriggerOverlayCommand(params, model) { + let update_action; + const af = this.actionFactory; + const dispatch = this.dispatcher; + + this.ui.deactivateSlateButtons(); + this.ui.setLoader(); + + update_action = af.interactiveImage().command().saveTriggerOverlay( + params.nr, + params.overlay, + params.coords, + ); + + this.client.sendCommand(update_action).then((result) => { + if (this.handleStandardResponse(result, model)) { + this.ui.activateSlateButtons(); + this.ui.refreshTriggerViewControl(); + this.ui.setMessage('commonSuccessMessage'); + this.ui.updateOverlayPresentationAfterSaving(); + } + }); + } + + sendSaveTriggerPopupCommand(params, model) { + let update_action; + const af = this.actionFactory; + const dispatch = this.dispatcher; + + this.ui.deactivateSlateButtons(); + this.ui.setLoader(); + + update_action = af.interactiveImage().command().saveTriggerPopup( + params.nr, + params.popup, + params.position, + params.size, + ); + + this.client.sendCommand(update_action).then((result) => { + if (this.handleStandardResponse(result, model)) { + this.ui.activateSlateButtons(); + this.ui.refreshTriggerViewControl(); + this.ui.setMessage('commonSuccessMessage'); + this.ui.updatePopupPresentationAfterSaving(); + } + }); + } + + handleStandardResponse(result, model) { + const pl = result.getPayload(); + + if (pl.error === false) { + model.initModel(pl.model); + model.updateCurrentTriggerFromModel(); + this.ui.uiModel.backgroundImage = pl.backgroundImage; + return true; } - - sendDeletePopup(params, model) { - const af = this.actionFactory; - const dispatch = this.dispatcher; - const delete_action = af.interactiveImage().command().deletePopup( - params.nr - ); - this.client.sendCommand(delete_action).then(result => { - this.handleStandardResponse(result, model); - this.ui.showPopups(); + return false; + } + + sendUploadOverlay(params, model) { + let upload_action; + const af = this.actionFactory; + const dispatch = this.dispatcher; + const { util } = this; + + const old_overlays = []; + model.getOverlays().forEach((ov) => { + old_overlays.push(ov.name); + }); + + this.util.sendFiles(params.data.form).then(() => { + const data = new FormData(params.data.form); + upload_action = af.interactiveImage().command().uploadOverlay( + data, + ); + util.hideModal(this.ui.uiModel.modal); + this.ui.deactivateSlateButtons(); + this.ui.setLoader(); + this.client.sendCommand(upload_action).then((result) => { + if (this.handleStandardResponse(result, model)) { + this.ui.activateSlateButtons(); + if (model.getCurrentTrigger() == null) { + this.ui.showOverlays(); this.ui.setMessage('commonSuccessMessage'); - }); - } - - sendSaveSettings(params, model) { - let save_action; - const af = this.actionFactory; - const dispatch = this.dispatcher; - const util = this.util; - - this.util.sendFiles(params.form).then(() => { - const data = new FormData(params.form); - save_action = af.interactiveImage().command().saveSettings( - data - ); - this.ui.deactivateSlateButtons(); - this.ui.setLoader(); - this.client.sendCommand(save_action).then(result => { - if (this.handleStandardResponse(result, model)) { - this.ui.activateSlateButtons(); - this.ui.showSettings(); - this.ui.setMessage('commonSuccessMessage'); - this.ui.refreshMainScreen(); - } + } else { + this.ui.refreshTriggerViewControl(); + this.ui.showTriggerOverlay(); + this.ui.setMessage('commonSuccessMessage'); + model.getOverlays().forEach((ov) => { + if (!old_overlays.includes(ov.name)) { + dispatch.dispatch(af.interactiveImage() + .editor() + .changeTriggerOverlay( + ov.name, + )); + } }); - }); - } - -} \ No newline at end of file + } + } + }); + }); + } + + sendDeleteOverlay(params, model) { + const af = this.actionFactory; + const dispatch = this.dispatcher; + const delete_action = af.interactiveImage().command().deleteOverlay( + params.overlay, + ); + this.client.sendCommand(delete_action).then((result) => { + this.handleStandardResponse(result, model); + dispatch.dispatch(af.interactiveImage().editor().switchOverlays()); + }); + } + + sendPopupSave(params, model) { + const af = this.actionFactory; + const dispatch = this.dispatcher; + const { util } = this; + const data = new FormData(params.data.form); + data.append('nr', params.nr); + const save_action = af.interactiveImage().command().savePopup( + data, + ); + this.client.sendCommand(save_action).then((result) => { + this.handleStandardResponse(result, model); + util.hideModal(this.ui.uiModel.modal); + if (model.getCurrentTrigger() == null) { + this.ui.showPopups(); + this.ui.setMessage('commonSuccessMessage'); + } else { + this.ui.refreshTriggerViewControl(); + this.ui.showTriggerPopup(); + this.ui.setMessage('commonSuccessMessage'); + } + }); + } + + sendDeletePopup(params, model) { + const af = this.actionFactory; + const dispatch = this.dispatcher; + const delete_action = af.interactiveImage().command().deletePopup( + params.nr, + ); + this.client.sendCommand(delete_action).then((result) => { + this.handleStandardResponse(result, model); + this.ui.showPopups(); + this.ui.setMessage('commonSuccessMessage'); + }); + } + + sendSaveSettings(params, model) { + let save_action; + const af = this.actionFactory; + const dispatch = this.dispatcher; + const { util } = this; + + this.util.sendFiles(params.form).then(() => { + const data = new FormData(params.form); + save_action = af.interactiveImage().command().saveSettings( + data, + ); + this.ui.deactivateSlateButtons(); + this.ui.setLoader(); + this.client.sendCommand(save_action).then((result) => { + if (this.handleStandardResponse(result, model)) { + this.ui.activateSlateButtons(); + this.ui.showSettings(); + this.ui.setMessage('commonSuccessMessage'); + this.ui.refreshMainScreen(); + } + }); + }); + } +} diff --git a/components/ILIAS/COPage/PC/InteractiveImage/js/editor/src/ui/iim-ui.js b/components/ILIAS/COPage/PC/InteractiveImage/js/editor/src/ui/iim-ui.js index 15955e660f0e..9e44fb935603 100755 --- a/components/ILIAS/COPage/PC/InteractiveImage/js/editor/src/ui/iim-ui.js +++ b/components/ILIAS/COPage/PC/InteractiveImage/js/editor/src/ui/iim-ui.js @@ -67,6 +67,7 @@ export default class UI { /** * @type {pageModifier} */ + // pageModifier; /** @@ -78,7 +79,7 @@ export default class UI { * @param {IIMUIModifier} uiModifier */ constructor(client, dispatcher, actionFactory, iimModel, uiModel, toolSlate, uiModifier) { - this.debug = true; + this.debug = false; this.client = client; this.dispatcher = dispatcher; this.actionFactory = actionFactory; @@ -172,7 +173,6 @@ export default class UI { initShapeEditor() { const el = document.getElementById('il-copg-iim-main'); const mob = el.querySelector('.ilc_Mob'); - console.log('initShapeEditor'); if (mob) { this.shapeEditor = new ShapeEditor(mob); const ed = this.shapeEditor; @@ -230,14 +230,14 @@ export default class UI { addTrigger() { const trigger = this.iimModel.getCurrentTrigger(); - this.showTriggerProperties(); + this.showTriggerProperties(true); this.shapeEditor.addShape(trigger.getShape()); this.shapeEditor.repaint(); } editTrigger(nr) { const trigger = this.iimModel.getCurrentTrigger(); - this.showTriggerProperties(); + this.showTriggerProperties(false); this.setEditorAddMode(); this.shapeEditor.removeAllShapes(); this.shapeEditor.addShape(trigger.getShape(), true); @@ -267,7 +267,29 @@ export default class UI { return `form/input_${nr}`; } - showTriggerProperties() { + showTriggerDeleteButton() { + document.querySelectorAll("form [data-copg-ed-type='form-button']").forEach((button) => { + const act = button.dataset.copgEdAction; + if (act === ACTIONS.E_TRIGGER_DELETE) { + button.style.display = ''; + } + }); + } + + updateTriggerProperties() { + const shapeSelect = document.querySelector(`#copg-iim-trigger-prop-form [name="${this.formInput(1)}"]`); + const modeSelect = document.querySelector(`#copg-iim-trigger-prop-form [name="${this.formInput(2)}"]`).closest('.c-input'); + const classSelect = document.querySelector(`#copg-iim-trigger-prop-form [name="${this.formInput(3)}"]`).closest('.c-input'); + if (shapeSelect.value === 'Marker') { + modeSelect.style.display = 'none'; + classSelect.style.display = 'none'; + } else { + modeSelect.style.display = ''; + classSelect.style.display = ''; + } + } + + showTriggerProperties(add) { const dispatch = this.dispatcher; const action = this.actionFactory; const tr = this.iimModel.getCurrentTrigger(); @@ -275,12 +297,20 @@ export default class UI { this.setInputValueByName('#copg-iim-trigger-prop-form', this.formInput(0), tr.title); if (tr.getShape()) { this.setInputValueByName('#copg-iim-trigger-prop-form', this.formInput(1), tr.area.shapeType); + this.setInputValueByName('#copg-iim-trigger-prop-form', this.formInput(2), tr.area.hMode); + this.setInputValueByName('#copg-iim-trigger-prop-form', this.formInput(3), tr.area.hClass); } else { this.setInputValueByName('#copg-iim-trigger-prop-form', this.formInput(1), 'Marker'); } this.initTriggerViewControl(); this.initBackButton(); model = this.iimModel; + const shapeSelect = document.querySelector(`#copg-iim-trigger-prop-form [name="${this.formInput(1)}"]`); + shapeSelect.addEventListener('change', (event) => { + this.updateTriggerProperties(); + }); + this.updateTriggerProperties(); + document.querySelectorAll("form [data-copg-ed-type='form-button']").forEach((button) => { const act = button.dataset.copgEdAction; button.addEventListener('click', (event) => { @@ -298,10 +328,21 @@ export default class UI { this.getInputValueByName(this.formInput(0)), this.getInputValueByName(this.formInput(1)), coords, + this.getInputValueByName(this.formInput(2)), + this.getInputValueByName(this.formInput(3)), + )); + break; + case ACTIONS.E_TRIGGER_DELETE: + event.preventDefault(); + dispatch.dispatch(action.interactiveImage().editor().deleteTrigger( + model.getCurrentTrigger().nr, )); break; } }); + if (add && act === ACTIONS.E_TRIGGER_DELETE) { + button.style.display = 'none'; + } }); document.querySelectorAll(`form [name='${this.formInput(1)}']`).forEach((select) => { select.addEventListener('change', (event) => { @@ -326,6 +367,14 @@ export default class UI { setInputValueByName(sel, name, value) { const path = `${sel} input[name='${name}'],select[name='${name}']`; const el = document.querySelector(path); + if (el && el.options) { + const options = Array.from(el.options); + options.forEach((option) => { + if (option.hidden) { + el.removeChild(option); + } + }); + } if (el) { el.value = value; } @@ -391,16 +440,18 @@ export default class UI { this.shapeEditor.removeAllMarkers(); this.removeDummyPopup(); if (showOverlay && overlay) { - this.setInputValueByName('#copg-iim-trigger-overlay-form', this.formInput(0), overlay.getSrc()); + this.setInputValueByName( + '#copg-iim-trigger-overlay-form', + this.formInput(0), + overlay.getSrc(), + ); this.shapeEditor.addOverlay(overlay, true); } this.shapeEditor.removeAllShapes(); if (trigger.getShape()) { this.shapeEditor.addShape(trigger.getShape(), edit); } - console.log('A'); if (trigger.getMarker()) { - console.log('B'); this.shapeEditor.addMarker(trigger.getMarker(), edit); } this.shapeEditor.repaint(); @@ -454,7 +505,11 @@ export default class UI { if (size == '') { size = 'md'; } - this.setInputValueByName('#copg-iim-trigger-overlay-form', this.formInput(0), tr.getPopupNr()); + this.setInputValueByName( + '#copg-iim-trigger-overlay-form', + this.formInput(0), + tr.getPopupNr(), + ); this.setInputValueByName('#copg-iim-trigger-overlay-form', this.formInput(1), size); } this.showCurrentShape(); @@ -476,10 +531,17 @@ export default class UI { dummy.setAttribute('data-copg-iim-type', 'dummmy-popup'); let ln = 160; switch (size) { - case 'sm': ln = 40; break; - case 'lg': ln = 360; break; + case 'sm': + ln = 40; + break; + case 'lg': + ln = 360; + break; } - dummy.innerHTML = this.uiModel.popupDummy.replace('###content###', `${this.uiModel.lore.substr(0, ln)}...`); + dummy.innerHTML = this.uiModel.popupDummy.replace( + '###content###', + `${this.uiModel.lore.substr(0, ln)}...`, + ); mainEl.appendChild(dummy); const popEl = mainEl.querySelector("[data-copg-cont-type='iim-popup']"); popEl.classList.remove('copg-iim-popup-md'); @@ -580,9 +642,15 @@ export default class UI { refreshTriggerViewControl() { const model = this.iimModel; - const prop = document.querySelector("[data-copg-ed-type='view-control'][data-copg-ed-action='trigger.properties']"); - const ov = document.querySelector("[data-copg-ed-type='view-control'][data-copg-ed-action='trigger.overlay']"); - const pop = document.querySelector("[data-copg-ed-type='view-control'][data-copg-ed-action='trigger.popup']"); + const prop = document.querySelector( + "[data-copg-ed-type='view-control'][data-copg-ed-action='trigger.properties']", + ); + const ov = document.querySelector( + "[data-copg-ed-type='view-control'][data-copg-ed-action='trigger.overlay']", + ); + const pop = document.querySelector( + "[data-copg-ed-type='view-control'][data-copg-ed-action='trigger.popup']", + ); prop.classList.remove('engaged'); ov.classList.remove('engaged'); pop.classList.remove('engaged'); @@ -680,7 +748,10 @@ export default class UI { } fillItemList(items) { - let newNode; let newLiNode; let liTempl; let + let newNode; + let newLiNode; + let liTempl; + let liParent; const dispatch = this.dispatcher; const templEl = document.querySelector('#copg-editor-slate-content .il-std-item-container'); diff --git a/components/ILIAS/COPage/PC/InteractiveImage/js/presentation/src/presentation.js b/components/ILIAS/COPage/PC/InteractiveImage/js/presentation/src/presentation.js index b42f211a6453..6726180c9176 100755 --- a/components/ILIAS/COPage/PC/InteractiveImage/js/presentation/src/presentation.js +++ b/components/ILIAS/COPage/PC/InteractiveImage/js/presentation/src/presentation.js @@ -12,137 +12,168 @@ * https://www.ilias.de * https://github.com/ILIAS-eLearning * - *********************************************************************/ + ******************************************************************** */ -import Util from "../../common/src/util.js"; -import Area from "../../editor/src/area/area.js"; -import AreaFactory from "../../editor/src/area/area-factory.js"; +import Util from '../../common/src/util.js'; +import Area from '../../editor/src/area/area.js'; +import AreaFactory from '../../editor/src/area/area-factory.js'; const presentation = (function () { - function mouseover(triggerId) { - const overlay = document.getElementById("iim_ov_" + triggerId); + const overlay = document.getElementById(`iim_ov_${triggerId}`); if (overlay) { // of not positioned under ilc_Mob, do it - if (!overlay.parentNode.classList.contains("ilc_Mob")) { - const img = overlay.parentNode.querySelector(".ilc_Mob > img"); + if (!overlay.parentNode.classList.contains('ilc_Mob')) { + const img = overlay.parentNode.querySelector('.ilc_Mob > img'); overlay.style.position = 'absolute'; img.after(overlay); // get the position from the trigger data element - const triggerDataEl = document.querySelector("[data-copg-iim-data-type='trigger'][data-copg-iim-tr-id='" + triggerId + "']"); - overlay.style.left = triggerDataEl.getAttribute('data-copg-iim-ovx') + "px"; - overlay.style.top = triggerDataEl.getAttribute('data-copg-iim-ovy') + "px"; + const triggerDataEl = document.querySelector(`[data-copg-iim-data-type='trigger'][data-copg-iim-tr-id='${triggerId}']`); + overlay.style.left = `${triggerDataEl.getAttribute('data-copg-iim-ovx')}px`; + overlay.style.top = `${triggerDataEl.getAttribute('data-copg-iim-ovy')}px`; } overlay.style.display = ''; } } function mouseout(triggerId) { - console.log("out " + triggerId); - const overlay = document.getElementById("iim_ov_" + triggerId); + const overlay = document.getElementById(`iim_ov_${triggerId}`); if (overlay) { overlay.style.display = 'none'; } } + function hideAllPopups() { + document.querySelectorAll("[data-copg-cont-type='iim-popup']").forEach((p) => { + p.style.display = 'none'; + }); + } + + function positionMarker(mobElement, markerEl) { + mobElement.appendChild(markerEl); + const markX = markerEl.getAttribute('data-copg-iim-markx'); + const markY = markerEl.getAttribute('data-copg-iim-marky'); + markerEl.style.display = 'block'; + markerEl.style.position = 'absolute'; + markerEl.style.left = `${markX}px`; + markerEl.style.top = `${markY}px`; + } + function init(node) { let iimId; let mobEl; let areaEl; let svg; - //let popupEl; + // let popupEl; let popupNr; - const util = new Util; + const util = new Util(); const areaFactory = new AreaFactory(); let topContainer; + // listener to hide all popups, if clicking outside of a popup + document.addEventListener('click', (e) => { + const isPopup = e.target.closest('[data-copg-cont-type="iim-popup"]'); + if (!isPopup) { + hideAllPopups(); + } + }); + // find all triggers within the node node.querySelectorAll("[data-copg-iim-data-type='trigger']").forEach((tr) => { - let areaDataEl, areaId, triggerNr, markerEl, clickEl; - let triggerType, triggerId, size; + let areaDataEl; let areaId; let triggerNr; let markerEl; let + clickEl; + let triggerType; let triggerId; let + size; + let hlMode; + let hlClass; topContainer = tr.closest('.ilc_page_cont_PageContainer'); if (!topContainer) { topContainer = tr.closest('#il_center_col'); } // get map area of trigger - triggerNr = tr.getAttribute("data-copg-iim-nr"); - iimId = tr.getAttribute("data-copg-iim-id"); // image id - triggerId = tr.getAttribute("data-copg-iim-tr-id"); - popupNr = tr.getAttribute("data-copg-iim-popup-nr"); - size = tr.getAttribute("data-copg-iim-popup-size"); - triggerType = tr.getAttribute("data-copg-iim-type"); - mobEl = tr.parentNode.querySelector(".ilc_Mob"); + triggerNr = tr.getAttribute('data-copg-iim-nr'); + iimId = tr.getAttribute('data-copg-iim-id'); // image id + triggerId = tr.getAttribute('data-copg-iim-tr-id'); + popupNr = tr.getAttribute('data-copg-iim-popup-nr'); + size = tr.getAttribute('data-copg-iim-popup-size'); + triggerType = tr.getAttribute('data-copg-iim-type'); + mobEl = tr.parentNode.querySelector('.ilc_Mob'); areaEl = null; markerEl = null; clickEl = null; - if (triggerType == "Area") { - areaDataEl = document.querySelector("[data-copg-iim-data-type='area'][data-copg-iim-id='" + - iimId + "'][data-copg-iim-tr-nr='" + triggerNr + "']"); - areaId = areaDataEl.getAttribute("data-copg-iim-area-id"); + svg = util.getOverlaySvg(mobEl); // this will add the svg to the mob + if (triggerType == 'Area') { + areaDataEl = document.querySelector(`[data-copg-iim-data-type='area'][data-copg-iim-id='${ + iimId}'][data-copg-iim-tr-nr='${triggerNr}']`); + areaId = areaDataEl.getAttribute('data-copg-iim-area-id'); + hlMode = areaDataEl.getAttribute('data-copg-iim-hl-mode'); + hlClass = areaDataEl.getAttribute('data-copg-iim-hl-class'); areaEl = document.getElementById(areaId); } else { - markerEl = document.querySelector("[data-copg-iim-data-type='marker'][data-copg-iim-id='" + - iimId + "'][data-copg-iim-tr-nr='" + triggerNr + "']"); + markerEl = document.querySelector(`[data-copg-iim-data-type='marker'][data-copg-iim-id='${ + iimId}'][data-copg-iim-tr-nr='${triggerNr}']`); + positionMarker(mobEl, markerEl); clickEl = markerEl; } - svg = util.getOverlaySvg(mobEl); let popupEl = null; if (popupNr) { - popupEl = mobEl.parentNode.parentNode.parentNode.querySelector("[data-copg-cont-type='iim-popup'][data-copg-popup-nr='" + popupNr + "']"); - popupEl.id = "iim_popup_parent_" + iimId + "_" + popupNr; + popupEl = mobEl.parentNode.parentNode.parentNode.querySelector(`[data-copg-cont-type='iim-popup'][data-copg-popup-nr='${popupNr}']`); + popupEl.id = `iim_popup_parent_${iimId}_${popupNr}`; } if (areaEl) { const area = areaFactory.area( - areaEl.getAttribute("shape"), - areaEl.getAttribute("coords") + areaEl.getAttribute('shape'), + areaEl.getAttribute('coords'), + hlClass, + hlMode, ); const shape = area.getShape(); const shapeEl = shape.addToSvg(svg); + shapeEl.classList.add(`copg-iim-hl-mode-${hlMode}`); + shapeEl.classList.add(`copg-iim-hl-class-${hlClass}`); clickEl = shapeEl; } if (popupEl && clickEl) { - util.attachPopupToShape(topContainer, mobEl, popupEl, clickEl); - clickEl.addEventListener("click", () => { + clickEl.addEventListener('click', (event) => { + event.stopPropagation(); + event.preventDefault(); util.lastClicked(popupEl, clickEl); - if (popupEl.style.display === "none") { - document.querySelectorAll("[data-copg-cont-type='iim-popup']").forEach((p) => { - p.style.display = "none"; - }); - if (size == "") { - size = "md"; + if (popupEl.style.display === 'none') { + hideAllPopups(); + if (size == '') { + size = 'md'; } popupEl.classList.remove('copg-iim-popup-md'); popupEl.classList.remove('copg-iim-popup-lg'); popupEl.classList.remove('copg-iim-popup-sm'); - popupEl.classList.add('copg-iim-popup-' + size); - popupEl.style.display = ""; + popupEl.classList.add(`copg-iim-popup-${size}`); + popupEl.style.display = ''; util.refreshPopupPosition(topContainer, mobEl, popupEl, clickEl); window.dispatchEvent(new Event('resize')); } else { - popupEl.style.display = "none"; + popupEl.style.display = 'none'; } }); } if (clickEl) { - clickEl.addEventListener("mouseover", () => { + clickEl.addEventListener('mouseover', () => { mouseover(triggerId); }); - clickEl.addEventListener("mouseout", () => { + clickEl.addEventListener('mouseout', () => { mouseout(triggerId); }); } }); - } return { - init + init, }; -})(); -window.addEventListener('load', function () { +}()); +window.addEventListener('load', () => { presentation.init(document); -}, false); \ No newline at end of file +}, false); diff --git a/components/ILIAS/COPage/PC/Login/class.ilPCLoginPageElement.php b/components/ILIAS/COPage/PC/Login/class.ilPCLoginPageElement.php index 8c4b0e43cc2d..0dcb391024d4 100755 --- a/components/ILIAS/COPage/PC/Login/class.ilPCLoginPageElement.php +++ b/components/ILIAS/COPage/PC/Login/class.ilPCLoginPageElement.php @@ -26,7 +26,6 @@ class ilPCLoginPageElement extends ilPageContent { private static array $types = array( 'login-form' => 'login_form', - 'cas-login-form' => 'cas_login_form', 'shibboleth-login-form' => 'shib_login_form', 'openid-connect-login' => 'openid_connect_login', 'registration-link' => 'registration_link', diff --git a/components/ILIAS/COPage/PC/Login/class.ilPCLoginPageElementGUI.php b/components/ILIAS/COPage/PC/Login/class.ilPCLoginPageElementGUI.php index da4eb3fab24c..5ed23ccd436c 100755 --- a/components/ILIAS/COPage/PC/Login/class.ilPCLoginPageElementGUI.php +++ b/components/ILIAS/COPage/PC/Login/class.ilPCLoginPageElementGUI.php @@ -146,6 +146,11 @@ public function create(): void } } + public function update_login_page_element(): void + { + $this->update(); + } + /** * Update Login page element */ diff --git a/components/ILIAS/COPage/PC/Map/class.ilPCMapGUI.php b/components/ILIAS/COPage/PC/Map/class.ilPCMapGUI.php index 941a19569c7b..22ce6967fab8 100755 --- a/components/ILIAS/COPage/PC/Map/class.ilPCMapGUI.php +++ b/components/ILIAS/COPage/PC/Map/class.ilPCMapGUI.php @@ -157,7 +157,7 @@ public function initForm(string $a_mode): void $this->form->addCommandButton("create", $lng->txt("save")); $this->form->addCommandButton("cancelCreate", $lng->txt("cancel")); } else { - $this->form->addCommandButton("update_map", $lng->txt("save")); + $this->form->addCommandButton("update", $lng->txt("save")); $this->form->addCommandButton("cancelUpdate", $lng->txt("cancel")); } } diff --git a/components/ILIAS/COPage/PC/MediaObject/class.ilPCMediaObject.php b/components/ILIAS/COPage/PC/MediaObject/class.ilPCMediaObject.php index 9c97b6e4e6e5..cb95d540c45f 100755 --- a/components/ILIAS/COPage/PC/MediaObject/class.ilPCMediaObject.php +++ b/components/ILIAS/COPage/PC/MediaObject/class.ilPCMediaObject.php @@ -501,9 +501,7 @@ public function getJavascriptFiles( public function getCssFiles( string $a_mode ): array { - $js_files = ilPlayerUtil::getCssFilePaths(); - - return $js_files; + return []; } public function getStandardMediaAliasItem(): ilMediaAliasItem diff --git a/components/ILIAS/COPage/PC/MediaObject/class.ilPCMediaObjectQuickEdit.php b/components/ILIAS/COPage/PC/MediaObject/class.ilPCMediaObjectQuickEdit.php index 6451e2f02662..4f42baa6869c 100755 --- a/components/ILIAS/COPage/PC/MediaObject/class.ilPCMediaObjectQuickEdit.php +++ b/components/ILIAS/COPage/PC/MediaObject/class.ilPCMediaObjectQuickEdit.php @@ -175,7 +175,11 @@ public function setCaption(string $caption): void $std_alias = $this->pcmedia->getStandardMediaAliasItem(); $std_item = $this->mob->getMediaItem("Standard"); if ($this->pcmedia->checkInstanceEditing()) { - $std_alias->setCaption($caption); + if ($caption !== $std_item->getCaption()) { // in quick edit we derive if the value is equal to the item caption + $std_alias->setCaption($caption); + } else { + $std_alias->deriveCaption(); + } } else { $std_alias->deriveCaption(); $std_item->setCaption($caption); @@ -206,7 +210,11 @@ public function setTextRepresentation(string $alt_text): void $std_alias = $this->pcmedia->getStandardMediaAliasItem(); $std_item = $this->mob->getMediaItem("Standard"); if ($this->pcmedia->checkInstanceEditing()) { - $std_alias->setTextRepresentation($alt_text); + if ($alt_text !== $std_item->getTextRepresentation()) { // in quick edit we derive if the value is equal to the item caption + $std_alias->setTextRepresentation($alt_text); + } else { + $std_alias->deriveTextRepresentation(); + } } else { $std_alias->deriveTextRepresentation(); $std_item->setTextRepresentation($alt_text); diff --git a/components/ILIAS/COPage/PC/Paragraph/class.ilPCParagraph.php b/components/ILIAS/COPage/PC/Paragraph/class.ilPCParagraph.php index 59ce4bf08484..4cfa4ea3fe85 100755 --- a/components/ILIAS/COPage/PC/Paragraph/class.ilPCParagraph.php +++ b/components/ILIAS/COPage/PC/Paragraph/class.ilPCParagraph.php @@ -1372,8 +1372,11 @@ public function saveJS( } else { $this->inserted_pc_id = $pc_id[1]; }*/ - - $par->setLanguage($ilUser->getLanguage()); + $lang = (string) $ilUser->getLanguage(); + if ($lang === "") { + $lang = "de"; + } + $par->setLanguage($lang); $par->setCharacteristic($t["class"]); $t2 = $par->input2xml($t["text"], true, false); diff --git a/components/ILIAS/COPage/PC/Question/class.ilPCQuestion.php b/components/ILIAS/COPage/PC/Question/class.ilPCQuestion.php index 2be925eb4c66..afdb55f03749 100755 --- a/components/ILIAS/COPage/PC/Question/class.ilPCQuestion.php +++ b/components/ILIAS/COPage/PC/Question/class.ilPCQuestion.php @@ -473,7 +473,8 @@ public static function handleCopiedContent( $path = "//Question"; $nodes = $dom_util->path($a_domdoc, $path); foreach ($nodes as $node) { - $node->parentNode->removeChild($node); + $parent = $node->parentNode; + $parent->parentNode->removeChild($parent); } } } diff --git a/components/ILIAS/COPage/PC/Section/class.ilPCSectionGUI.php b/components/ILIAS/COPage/PC/Section/class.ilPCSectionGUI.php index c893c14b27e3..b9bbac57f849 100755 --- a/components/ILIAS/COPage/PC/Section/class.ilPCSectionGUI.php +++ b/components/ILIAS/COPage/PC/Section/class.ilPCSectionGUI.php @@ -138,7 +138,7 @@ public static function _getStandardCharacteristics(): array "AdvancedKnowledge" => $lng->txt("cont_AdvancedKnowledge")); } - public static function _getCharacteristics(string $a_style_id): array + public static function _getCharacteristics(int $a_style_id): array { global $DIC; diff --git a/components/ILIAS/COPage/PC/Verification/class.ilPCVerificationGUI.php b/components/ILIAS/COPage/PC/Verification/class.ilPCVerificationGUI.php index 27c7bf289ccb..d1d36cfaf5e5 100755 --- a/components/ILIAS/COPage/PC/Verification/class.ilPCVerificationGUI.php +++ b/components/ILIAS/COPage/PC/Verification/class.ilPCVerificationGUI.php @@ -191,6 +191,11 @@ protected function initForm(bool $a_insert = false): ilPropertyFormGUI return $form; } + public function create_verification(): void + { + $this->create(); + } + public function create(): void { $form = $this->initForm(true); diff --git a/components/ILIAS/COPage/Page/class.PageContentManager.php b/components/ILIAS/COPage/Page/class.PageContentManager.php index b39d5d4bc91f..7f3502035392 100755 --- a/components/ILIAS/COPage/Page/class.PageContentManager.php +++ b/components/ILIAS/COPage/Page/class.PageContentManager.php @@ -678,7 +678,7 @@ public function moveContentAfter( $clone_node = $source_node?->cloneNode(true); // delete source node - $this->deleteContent($page, $a_source, $a_spcid, false); + $this->deleteContent($page, $a_source, $a_spcid, true); // insert cloned node at target if ($content) { diff --git a/components/ILIAS/COPage/Page/class.PageQueryActionHandler.php b/components/ILIAS/COPage/Page/class.PageQueryActionHandler.php index 058208cc0e52..54f4f98d7ef7 100755 --- a/components/ILIAS/COPage/Page/class.PageQueryActionHandler.php +++ b/components/ILIAS/COPage/Page/class.PageQueryActionHandler.php @@ -23,6 +23,7 @@ use ParagraphStyleSelector; use SectionStyleSelector; use MediaObjectStyleSelector; +use ILIAS\ILIASObject\Properties\Translations\CachedRepository as TranslationsRepository; /** * @author Alexander Killing @@ -38,6 +39,7 @@ class PageQueryActionHandler implements Server\QueryActionHandler protected \ilObjUser $user; protected Server\UIWrapper $ui_wrapper; protected \ilCtrl $ctrl; + protected \ilDBInterface $db; protected \ilComponentFactory $component_factory; public function __construct(\ilPageObjectGUI $page_gui, string $pc_id = "") @@ -49,6 +51,7 @@ public function __construct(\ilPageObjectGUI $page_gui, string $pc_id = "") $this->page_gui = $page_gui; $this->user = $DIC->user(); $this->ctrl = $DIC->ctrl(); + $this->db = $DIC->database(); $this->component_factory = $DIC["component.factory"]; $this->gui = $DIC->copage()->internal()->gui(); $this->pc_id = $pc_id; @@ -415,13 +418,15 @@ public function getMultiLangActions(): array // general multi lang support and single page mode? if ($config->getMultiLangSupport()) { - $ot = \ilObjectTranslation::getInstance($page->getParentId()); + $ot = (new TranslationsRepository( + $this->db + ))->getFor($page->getParentId()); - if ($ot->getContentActivated()) { + if ($ot->getContentTranslationActivated()) { $lng->loadLanguageModule("meta"); if ($page->getLanguage() != "-") { - $l = $ot->getMasterLanguage(); + $l = $ot->getBaseLanguage(); $items[] = $ui->factory()->link()->standard( $lng->txt("cont_edit_language_version") . ": " . $lng->txt("meta_l_" . $l), @@ -431,7 +436,7 @@ public function getMultiLangActions(): array foreach ($ot->getLanguages() as $al => $lang) { if ($page->getLanguage() != $al && - $al != $ot->getMasterLanguage()) { + $al != $ot->getBaseLanguage()) { $ctrl->setParameter($this->page_gui, "totransl", $al); $items[] = $ui->factory()->link()->standard( $lng->txt("cont_edit_language_version") . ": " . @@ -458,9 +463,11 @@ public function getMultiLangInfo(): string // general multi lang support and single page mode? if ($config->getMultiLangSupport()) { - $ot = \ilObjectTranslation::getInstance($page->getParentId()); + $ot = (new TranslationsRepository( + $this->db + ))->getFor($page->getParentId()); - if ($ot->getContentActivated()) { + if ($ot->getContentTranslationActivated()) { $lng->loadLanguageModule("meta"); $ml_gui = new \ilPageMultiLangGUI( diff --git a/components/ILIAS/COPage/classes/Setup/class.Agent.php b/components/ILIAS/COPage/classes/Setup/class.Agent.php index ef7c2d199681..380b7a4fe477 100755 --- a/components/ILIAS/COPage/classes/Setup/class.Agent.php +++ b/components/ILIAS/COPage/classes/Setup/class.Agent.php @@ -29,7 +29,13 @@ class Agent extends Setup\Agent\NullAgent { public function getUpdateObjective(?Setup\Config $config = null): Setup\Objective { - return new \ilDatabaseUpdateStepsExecutedObjective(new ilCOPageDBUpdateSteps()); + return new Setup\ObjectiveCollection( + 'COPage Update', + true, + new \ilDatabaseUpdateStepsExecutedObjective(new ilCOPageDBUpdateSteps()), + new \ilDatabaseUpdateStepsExecutedObjective(new ilCOPageHotfix9DBUpdateSteps()) + ); + } public function getStatusObjective(Metrics\Storage $storage): Objective diff --git a/components/ILIAS/COPage/classes/Setup/class.ilCOPageHotfix9DBUpdateSteps.php b/components/ILIAS/COPage/classes/Setup/class.ilCOPageHotfix9DBUpdateSteps.php new file mode 100644 index 000000000000..065e42d65a8c --- /dev/null +++ b/components/ILIAS/COPage/classes/Setup/class.ilCOPageHotfix9DBUpdateSteps.php @@ -0,0 +1,44 @@ +db = $db; + } + + public function step_1(): void + { + $this->db->modifyTableColumn( + 'copg_section_timings', + 'unix_ts', + [ + 'type' => 'integer', + 'length' => 8, + 'notnull' => true, + 'default' => 0 + ] + ); + } + +} diff --git a/components/ILIAS/COPage/classes/class.ilCOPageDataSet.php b/components/ILIAS/COPage/classes/class.ilCOPageDataSet.php index 64ee794f2ff1..5dc69bdb1515 100755 --- a/components/ILIAS/COPage/classes/class.ilCOPageDataSet.php +++ b/components/ILIAS/COPage/classes/class.ilCOPageDataSet.php @@ -109,7 +109,6 @@ public function importRecord(string $a_entity, array $a_types, array $a_rec, ilI $pt = new ilPageLayout(); $pt->setTitle($a_rec["Title"]); $pt->setDescription($a_rec["Description"]); - $pt->setSpecialPage($a_rec["SpecialPage"]); $pt->update(); $this->current_obj = $pt; diff --git a/components/ILIAS/COPage/classes/class.ilCOPageHTMLExport.php b/components/ILIAS/COPage/classes/class.ilCOPageHTMLExport.php index a60482cbe24e..edb10674d02f 100755 --- a/components/ILIAS/COPage/classes/class.ilCOPageHTMLExport.php +++ b/components/ILIAS/COPage/classes/class.ilCOPageHTMLExport.php @@ -28,8 +28,6 @@ class ilCOPageHTMLExport protected \ILIAS\MediaObjects\MediaObjectManager $media_manager; protected \ILIAS\Style\Content\InternalDomainService $content_style; protected \ILIAS\COPage\Xsl\XslManager $xsl; - protected string $mp3_dir = ""; - protected string $flv_dir = ""; protected string $css_dir = ""; protected string $js_yahoo_dir = ""; protected string $js_dir = ""; @@ -82,8 +80,6 @@ public function __construct( $this->services_dir = $a_exp_dir . "/components/ILIAS"; $this->media_service_dir = $this->services_dir . "/MediaObjects"; - $this->flv_dir = $a_exp_dir . "/" . ilPlayerUtil::getMediaPlayerDirectory(); - $this->mp3_dir = $this->media_service_dir . "/flash_mp3_player"; $this->js_dir = $a_exp_dir . '/js'; $this->js_yahoo_dir = $a_exp_dir . '/js/yahoo'; @@ -112,8 +108,6 @@ public function createDirectories(): void ilFileUtils::makeDir($this->content_style_img_dir); ilFileUtils::makeDir($this->services_dir); ilFileUtils::makeDir($this->media_service_dir); - ilFileUtils::makeDirParents($this->flv_dir); - ilFileUtils::makeDirParents($this->mp3_dir); ilFileUtils::makeDirParents($this->js_dir); ilFileUtils::makeDirParents($this->js_yahoo_dir); @@ -200,8 +194,6 @@ public function exportSupportScripts(): void foreach ($collector->getCssFiles() as $css) { $this->exportResourceFile($this->exp_dir, $css); } - // mediaelement.js - // ilPlayerUtil::copyPlayerFilesToTargetDirectory($this->flv_dir); } protected function exportResourceFile( diff --git a/components/ILIAS/COPage/classes/class.ilMediaAliasItem.php b/components/ILIAS/COPage/classes/class.ilMediaAliasItem.php index e24910640991..74961e6f612f 100755 --- a/components/ILIAS/COPage/classes/class.ilMediaAliasItem.php +++ b/components/ILIAS/COPage/classes/class.ilMediaAliasItem.php @@ -536,9 +536,9 @@ public function setAreaTitle( $this->getPcId() ); if (is_object($ma_nodes[$a_nr - 1])) { - $childs = $ma_nodes[$a_nr - 1]->child_nodes(); + $childs = $ma_nodes[$a_nr - 1]->childNodes; if (is_object($childs[0]) && - ($childs[0]->node_name() == "IntLink" || $childs[0]->node_name() == "ExtLink")) { + ($childs[0]->nodeName == "IntLink" || $childs[0]->nodeName == "ExtLink")) { $childs[0]->set_content($a_title); } } @@ -660,10 +660,13 @@ public function addMapArea( string $a_coords, string $a_title, array $a_link, - string $a_id = "" + string $a_id = "", + string $hl_mode = "", + string $hl_class = "", ): void { $attributes = array("Shape" => $a_shape_type, - "Coords" => $a_coords, "Id" => $a_id); + "Coords" => $a_coords, "Id" => $a_id, + "HighlightMode" => $hl_mode, "HighlightClass" => $hl_class); $ma_node = $this->dom_util->addElementToList( $this->getItemNode(), diff --git a/components/ILIAS/COPage/classes/class.ilPageComponentPluginGUI.php b/components/ILIAS/COPage/classes/class.ilPageComponentPluginGUI.php index 6bfbbeb2dc21..d2450248bc73 100755 --- a/components/ILIAS/COPage/classes/class.ilPageComponentPluginGUI.php +++ b/components/ILIAS/COPage/classes/class.ilPageComponentPluginGUI.php @@ -87,6 +87,11 @@ abstract public function getElementHTML( string $plugin_version ): string; + public function create_plug(): void + { + $this->create(); + } + public function createElement(array $a_properties): bool { return $this->getPCGUI()->createElement($a_properties); diff --git a/components/ILIAS/COPage/classes/class.ilPageContent.php b/components/ILIAS/COPage/classes/class.ilPageContent.php index 6c844dd63252..3d6bda8088fc 100755 --- a/components/ILIAS/COPage/classes/class.ilPageContent.php +++ b/components/ILIAS/COPage/classes/class.ilPageContent.php @@ -36,11 +36,11 @@ abstract class ilPageContent public ?DOMNode $dom_node = null; public string $page_lang = ""; // needed for post processing (e.g. content includes) - protected string $file_download_link; + protected string $file_download_link = ''; // needed for post processing (e.g. content includes) - protected string $fullscreen_link; + protected string $fullscreen_link = ''; // needed for post processing (e.g. content includes) - protected string $sourcecode_download_script; + protected string $sourcecode_download_script = ''; protected ilLogger $log; protected string $profile_back_url = ""; diff --git a/components/ILIAS/COPage/classes/class.ilPageEditorGUI.php b/components/ILIAS/COPage/classes/class.ilPageEditorGUI.php index c6a20ba891a8..51078c717213 100755 --- a/components/ILIAS/COPage/classes/class.ilPageEditorGUI.php +++ b/components/ILIAS/COPage/classes/class.ilPageEditorGUI.php @@ -230,7 +230,6 @@ public function executeCommand(): string $hier_id = $hid[$this->requested_pl_pc_id]; } $this->log->debug("step PH: next class: " . $next_class); - if (!is_null($com) && ($com[0] == "insert" || $com[0] == "create")) { // Step CM (creation mode handling) $cmd = $com[0]; @@ -300,9 +299,17 @@ public function executeCommand(): string $this->ctrl->setParameter($this, "hier_id", $hier_id); $this->ctrl->setParameter($this, "pc_id", $pc_id); + if ($next_class == "") { $pc_def = $this->pc_definition->getPCDefinitionByType($ctype); if (is_array($pc_def)) { + if ($ctype === "plug") { + $this->ctrl->setParameterByClass( + $pc_def["pc_gui_class"], + "pluginName", + $this->request->getString("pluginName") + ); + } $this->ctrl->redirectByClass($pc_def["pc_gui_class"], $this->ctrl->getCmd()); } $next_class = $this->ctrl->getNextClass($this); diff --git a/components/ILIAS/COPage/classes/class.ilPageMultiLangGUI.php b/components/ILIAS/COPage/classes/class.ilPageMultiLangGUI.php index a00af2480bc9..d739a4484af7 100755 --- a/components/ILIAS/COPage/classes/class.ilPageMultiLangGUI.php +++ b/components/ILIAS/COPage/classes/class.ilPageMultiLangGUI.php @@ -16,6 +16,9 @@ * *********************************************************************/ +use ILIAS\ILIASObject\Properties\Translations\Translations; +use ILIAS\ILIASObject\Properties\Translations\CachedRepository as TranslationsRepository; + /** * Page multilinguality GUI class. * This could be generalized as an object service in the future. @@ -24,7 +27,7 @@ */ class ilPageMultiLangGUI { - protected ilObjectTranslation $ot; + protected Translations $ot; protected \ilCtrl $ctrl; protected ilLanguage $lng; protected bool $single_page_mode = false; @@ -48,7 +51,7 @@ public function __construct( //$this->ml = new ilPageMultiLang($a_parent_type, $a_parent_id); // object translation - $this->ot = ilObjectTranslation::getInstance($a_parent_id); + $this->ot = (new TranslationsRepository($DIC->database()))->getFor($a_parent_id); } /** @@ -85,10 +88,10 @@ public function getMultiLangInfo( $lng->loadLanguageModule("meta"); $tpl = new ilTemplate("tpl.page_multi_lang_info.html", true, true, "components/ILIAS/COPage"); - $tpl->setVariable("TXT_MASTER_LANG", $lng->txt("obj_master_lang")); - $tpl->setVariable("VAL_ML", $lng->txt("meta_l_" . $this->ot->getMasterLanguage())); + $tpl->setVariable("TXT_MASTER_LANG", $lng->txt("obj_base_lang")); + $tpl->setVariable("VAL_ML", $lng->txt("meta_l_" . $this->ot->getBaseLanguage())); $cl = ($a_page_lang == "-") - ? $this->ot->getMasterLanguage() + ? $this->ot->getBaseLanguage() : $a_page_lang; $tpl->setVariable("TXT_CURRENT_LANG", $lng->txt("cont_current_lang")); $tpl->setVariable("VAL_CL", $lng->txt("meta_l_" . $cl)); diff --git a/components/ILIAS/COPage/classes/class.ilPageObjectGUI.php b/components/ILIAS/COPage/classes/class.ilPageObjectGUI.php index 31a447865107..82a6386fcabc 100755 --- a/components/ILIAS/COPage/classes/class.ilPageObjectGUI.php +++ b/components/ILIAS/COPage/classes/class.ilPageObjectGUI.php @@ -1725,7 +1725,7 @@ public static function getTinyMenu( $ctrl = $DIC->ctrl(); $ui = $DIC->ui(); - $ui->renderer()->renderAsync($ui->factory()->legacy("")); + $ui->renderer()->renderAsync($ui->factory()->legacy()->content("")); $style_service = $DIC->contentStyle()->internal(); $style_access_manager = $style_service->domain()->access( diff --git a/components/ILIAS/COPage/css/content_base.css b/components/ILIAS/COPage/css/content_base.css index 1d3a4dd340b2..c8fa36a0ff83 100755 --- a/components/ILIAS/COPage/css/content_base.css +++ b/components/ILIAS/COPage/css/content_base.css @@ -18,6 +18,13 @@ background-color: transparent; } +#tinytarget_div .tox-tinymce { + border-radius: 0; +} +#tinytarget_div .tox.tox-edit-focus .tox-edit-area::before { + opacity: 0; +} + div.ilc_QuestionPlaceHolder { padding: 10px 10px 10px 10px; @@ -73,14 +80,32 @@ div.ilc_VerificationPlaceHolder /* no custom image yet */ } .copg-iim-area-shape-sel { - stroke: #B54F00; stroke-width: 1; fill: transparent; + cursor: pointer; } +.copg-iim-area-shape-sel.copg-iim-hl-mode-Edit { + stroke: #B54F00; +} +.copg-iim-area-shape-sel.copg-iim-hl-mode-Always, +.copg-iim-area-shape-sel.copg-iim-hl-mode-Hover:hover { + stroke: #B54F00; +} +.copg-iim-area-shape-sel.copg-iim-hl-mode-Always.copg-iim-hl-class-Dark, +.copg-iim-area-shape-sel.copg-iim-hl-mode-Hover.copg-iim-hl-class-Dark:hover +{ + stroke: #181818; +} +.copg-iim-area-shape-sel.copg-iim-hl-mode-Always.copg-iim-hl-class-Light, +.copg-iim-area-shape-sel.copg-iim-hl-mode-Hover.copg-iim-hl-class-Light:hover{ + stroke: #ffffff; +} .copg-iim-area-shape-sel:hover { - stroke: #0078D7; stroke-width: 2; +} +.copg-iim-area-shape-sel.copg-iim-hl-mode-Edit:hover { + stroke: #0078D7; fill-opacity: 40%; fill: white; } @@ -89,6 +114,7 @@ div.ilc_VerificationPlaceHolder /* no custom image yet */ position:absolute; width: auto; box-shadow: 2px 2px 4px rgba(0, 0, 0, .1); + z-index: 1; } button.copg-add.dropdown-toggle.btn:focus-visible { @@ -187,4 +213,4 @@ button.copg-add.dropdown-toggle.btn:focus-visible { } .copg-src-form.c-form [data-il-ui-component=textarea-field-input] .c-input__field textarea { height: 50vh; -} \ No newline at end of file +} diff --git a/components/ILIAS/COPage/css/print_content.css b/components/ILIAS/COPage/css/print_content.css index 21a6e03a33c2..a1829cf77f41 100755 --- a/components/ILIAS/COPage/css/print_content.css +++ b/components/ILIAS/COPage/css/print_content.css @@ -43,7 +43,7 @@ table.ilc_Page { width:100%; } -div.ilc_PrintChapterTitle +.ilc_PrintChapterTitle { margin-top: 5px; margin-bottom: 20px; @@ -54,7 +54,7 @@ div.ilc_PrintChapterTitle border-color: #000000; } -div.ilc_PrintPageTitle +h1.ilc_PrintPageTitle { margin-top: 3px; margin-bottom: 15px; @@ -176,4 +176,4 @@ div.ilc_Question font-size: 14px; font-weight: bold; padding-bottom: 0.5em; -} \ No newline at end of file +} diff --git a/components/ILIAS/COPage/xsl/page.xsl b/components/ILIAS/COPage/xsl/page.xsl index e39dd337c4a6..f4bd89c8a423 100755 --- a/components/ILIAS/COPage/xsl/page.xsl +++ b/components/ILIAS/COPage/xsl/page.xsl @@ -160,7 +160,7 @@ - + @@ -266,6 +266,8 @@ area + + marea__ _ @@ -400,7 +402,7 @@ n default n - +
@@ -431,7 +433,7 @@
- + + {FILE_NAME} diff --git a/components/ILIAS/MediaPool/Clipboard/class.ilClipboardTableGUI.php b/components/ILIAS/MediaPool/Clipboard/class.ilClipboardTableGUI.php index 983a5db58be0..409ce883a368 100755 --- a/components/ILIAS/MediaPool/Clipboard/class.ilClipboardTableGUI.php +++ b/components/ILIAS/MediaPool/Clipboard/class.ilClipboardTableGUI.php @@ -22,6 +22,7 @@ */ class ilClipboardTableGUI extends ilTable2GUI { + protected \ILIAS\MediaObjects\Thumbs\ThumbsGUI $thumbs_gui; protected ilAccessHandler $access; protected ilObjUser $user; @@ -37,6 +38,7 @@ public function __construct( $this->user = $DIC->user(); $ilCtrl = $DIC->ctrl(); $lng = $DIC->language(); + $this->thumbs_gui = $DIC->mediaObjects()->internal()->gui()->thumbs(); parent::__construct($a_parent_obj, $a_parent_cmd); $lng->loadLanguageModule("mep"); @@ -86,30 +88,10 @@ protected function fillRow(array $a_set): void $mob = null; if ($a_set["type"] === "mob") { - // output thumbnail - $mob = new ilObjMediaObject($a_set["id"]); - $med = $mob->getMediaItem("Standard"); - $target = $med->getThumbnailTarget(); - if ($target !== "") { - $this->tpl->setCurrentBlock("thumbnail"); - $this->tpl->setVariable("IMG_THUMB", $target); - $this->tpl->parseCurrentBlock(); - } - if (ilUtil::deducibleSize($med->getFormat()) && - $med->getLocationType() === "Reference") { - $size = getimagesize($med->getLocation()); - if ($size[0] > 0 && $size[1] > 0) { - $wr = $size[0] / 80; - $hr = $size[1] / 80; - $r = max($wr, $hr); - $w = (int) ($size[0] / $r); - $h = (int) ($size[1] / $r); - $this->tpl->setVariable( - "IMG", - ilUtil::img($med->getLocation(), "", $w, $h) - ); - } - } + $this->tpl->setVariable( + "IMG", + $this->thumbs_gui->getThumbHtml((int) $a_set["id"]) + ); } elseif ($a_set["type"] === "incl") { $this->tpl->setCurrentBlock("thumbnail"); $this->tpl->setVariable( diff --git a/components/ILIAS/MediaPool/classes/class.ilMediaPoolExportOptionXMLMaster.php b/components/ILIAS/MediaPool/classes/class.ilMediaPoolExportOptionXMLMaster.php index 44fffbc81380..ee87b96bbb73 100644 --- a/components/ILIAS/MediaPool/classes/class.ilMediaPoolExportOptionXMLMaster.php +++ b/components/ILIAS/MediaPool/classes/class.ilMediaPoolExportOptionXMLMaster.php @@ -25,15 +25,18 @@ use ILIAS\Export\ExportHandler\I\Consumer\File\Identifier\CollectionInterface as ilExportHandlerConsumerFileIdentifierCollectionInterface; use ILIAS\Export\ExportHandler\I\Consumer\File\Identifier\HandlerInterface as ilExportHandlerConsumerFileIdentifierInterface; use ILIAS\Export\ExportHandler\I\Info\File\CollectionInterface as ilExportHandlerFileInfoCollectionInterface; +use ILIAS\ILIASObject\Properties\Translations\CachedRepository as TranslationsRepository; use ILIAS\DI\Container; class ilMediaPoolExportOptionXMLMaster extends ilBasicLegacyExportOption { protected ilLanguage $lng; + protected ilDBInterface $db; public function init(Container $DIC): void { $this->lng = $DIC->language(); + $this->db = $DIC->database(); parent::init($DIC); } @@ -150,8 +153,8 @@ public function getFiles( public function isObjectSupported( ObjectId $object_id ): bool { - $ot = ilObjectTranslation::getInstance($object_id->toInt()); - return $ot->getContentActivated(); + $ot = (new TranslationsRepository($this->db))->getFor($object_id->toInt()); + return $ot->getContentTranslationActivated(); } public function onExportOptionSelected( diff --git a/components/ILIAS/MediaPool/classes/class.ilMediaPoolExportOptionXMLMasterNoMedia.php b/components/ILIAS/MediaPool/classes/class.ilMediaPoolExportOptionXMLMasterNoMedia.php index 01c9d35e6037..22c260f0421c 100644 --- a/components/ILIAS/MediaPool/classes/class.ilMediaPoolExportOptionXMLMasterNoMedia.php +++ b/components/ILIAS/MediaPool/classes/class.ilMediaPoolExportOptionXMLMasterNoMedia.php @@ -25,15 +25,18 @@ use ILIAS\Export\ExportHandler\I\Consumer\File\Identifier\CollectionInterface as ilExportHandlerConsumerFileIdentifierCollectionInterface; use ILIAS\Export\ExportHandler\I\Consumer\File\Identifier\HandlerInterface as ilExportHandlerConsumerFileIdentifierInterface; use ILIAS\Export\ExportHandler\I\Info\File\CollectionInterface as ilExportHandlerFileInfoCollectionInterface; +use ILIAS\ILIASObject\Properties\Translations\CachedRepository as TranslationsRepository; use ILIAS\DI\Container; class ilMediaPoolExportOptionXMLMasterNoMedia extends ilBasicLegacyExportOption { protected ilLanguage $lng; + protected ilDBInterface $db; public function init(Container $DIC): void { $this->lng = $DIC->language(); + $this->db = $DIC->database(); parent::init($DIC); } @@ -135,8 +138,8 @@ public function getFiles( public function isObjectSupported(ObjectId $object_id): bool { - $ot = ilObjectTranslation::getInstance($object_id->toInt()); - return $ot->getContentActivated(); + $ot = (new TranslationsRepository($this->db))->getFor($object_id->toInt()); + return $ot->getContentTranslationActivated(); } public function onExportOptionSelected(ilExportHandlerConsumerContextInterface $context): void diff --git a/components/ILIAS/MediaPool/classes/class.ilMediaPoolImportGUI.php b/components/ILIAS/MediaPool/classes/class.ilMediaPoolImportGUI.php index d1a2a493ed16..9200fcf3d103 100755 --- a/components/ILIAS/MediaPool/classes/class.ilMediaPoolImportGUI.php +++ b/components/ILIAS/MediaPool/classes/class.ilMediaPoolImportGUI.php @@ -16,6 +16,8 @@ * *********************************************************************/ +use ILIAS\ILIASObject\Properties\Translations\Translations; + /** * Import related features for media pools (currently used for translation imports) * @@ -28,6 +30,7 @@ class ilMediaPoolImportGUI protected ilCtrl $ctrl; protected ilLanguage $lng; protected ilGlobalTemplateInterface $tpl; + protected Translations $ot; public function __construct(ilObjMediaPool $a_mep) { @@ -41,6 +44,7 @@ public function __construct(ilObjMediaPool $a_mep) ->internal() ->gui() ->standardRequest(); + $this->ot = $a_mep->getObjectProperties()->getPropertyTranslations(); } public function executeCommand(): void @@ -80,10 +84,10 @@ public function initTranslationImportForm(): ilPropertyFormGUI $fi->setSize(30); $form->addItem($fi); - $ot = ilObjectTranslation::getInstance($this->mep->getId()); + $ot = $this->ot; $options = []; foreach ($ot->getLanguages() as $l) { - if ($l->getLanguageCode() != $ot->getMasterLanguage()) { + if ($l->getLanguageCode() != $ot->getBaseLanguage()) { $options[$l->getLanguageCode()] = $lng->txt("meta_l_" . $l->getLanguageCode()); } } @@ -109,8 +113,8 @@ public function importTranslation(): void $conf = $imp->getConfig("components/ILIAS/MediaPool"); $target_lang = $this->request->getImportLang(); - $ot = ilObjectTranslation::getInstance($this->mep->getId()); - if ($target_lang === $ot->getMasterLanguage()) { + $ot = $this->ot; + if ($target_lang === $ot->getBaseLanguage()) { $this->tpl->setOnScreenMessage('failure', $lng->txt("mep_transl_master_language_not_allowed"), true); $ilCtrl->redirect($this, "showTranslationImportForm"); } diff --git a/components/ILIAS/MediaPool/classes/class.ilMediaPoolTableGUI.php b/components/ILIAS/MediaPool/classes/class.ilMediaPoolTableGUI.php index 0c24be6cd8ad..b7f8129bff1f 100755 --- a/components/ILIAS/MediaPool/classes/class.ilMediaPoolTableGUI.php +++ b/components/ILIAS/MediaPool/classes/class.ilMediaPoolTableGUI.php @@ -29,6 +29,7 @@ class ilMediaPoolTableGUI extends ilTable2GUI public const IL_MEP_EDIT = "edit"; public const IL_MEP_SELECT_CONTENT = "selectc"; public const IL_MEP_SELECT_SINGLE = "selectsingle"; + protected \ILIAS\MediaObjects\Thumbs\ThumbsGUI $thumbs_gui; protected \ILIAS\DI\UIServices $ui; protected string $mode = ""; @@ -203,6 +204,7 @@ public function __construct( $ilAccess->checkAccess("write", "", $this->media_pool->getRefId())) { $this->setSelectAllCheckbox("id"); } + $this->thumbs_gui = $DIC->mediaObjects()->internal()->gui()->thumbs(); } protected function showAdvMetadata(): bool @@ -512,45 +514,14 @@ protected function fillRow(array $a_set): void // output thumbnail (or mob icon) if (ilObject::_lookupType($a_set["foreign_id"]) === "mob") { - $mob = new ilObjMediaObject($a_set["foreign_id"]); - $med = $mob->getMediaItem("Standard"); - $target = ""; - - // thumbnail picture - if ($med) { - $target = $med->getThumbnailTarget(); - } - - // video preview - if ($target === "") { - $target = $mob->getVideoPreviewPic(); - } - - if ($target !== "") { - $this->tpl->setVariable("IMG", ilUtil::img($target)); - } else { - $this->tpl->setVariable( - "IMG", - ilUtil::img(ilUtil::getImagePath("standard/icon_" . $a_set["type"] . ".svg")) - ); - } - if ($med && ilUtil::deducibleSize($med->getFormat()) && - $med->getLocationType() === "Reference") { - $size = getimagesize($med->getLocation()); - if ($size[0] > 0 && $size[1] > 0) { - $wr = $size[0] / 80; - $hr = $size[1] / 80; - $r = max($wr, $hr); - $w = (int) ($size[0] / $r); - $h = (int) ($size[1] / $r); - $this->tpl->setVariable( - "IMG", - ilUtil::img($med->getLocation(), "", $w, $h) - ); - } - } + $mob_id = (int) $a_set["foreign_id"]; + $this->tpl->setVariable( + "IMG", + $this->thumbs_gui->getThumbHtml($mob_id) + ); // output media info + $mob = new ilObjMediaObject($mob_id); $this->tpl->setVariable( "MEDIA_INFO", ilObjMediaObjectGUI::_getMediaInfoHTML($mob) diff --git a/components/ILIAS/MediaPool/classes/class.ilObjMediaPool.php b/components/ILIAS/MediaPool/classes/class.ilObjMediaPool.php index 95cadcc1b9e5..f646f65a7184 100755 --- a/components/ILIAS/MediaPool/classes/class.ilObjMediaPool.php +++ b/components/ILIAS/MediaPool/classes/class.ilObjMediaPool.php @@ -439,7 +439,6 @@ public function cloneObject(int $target_id, int $copy_id = 0, bool $omit_tree = /** @var ilObjMediaPool $new_obj */ $new_obj = parent::cloneObject($target_id, $copy_id, $omit_tree); - $new_obj->setTitle($this->getTitle()); $new_obj->setDescription($this->getDescription()); $new_obj->setDefaultWidth($this->getDefaultWidth()); $new_obj->setDefaultHeight($this->getDefaultHeight()); diff --git a/components/ILIAS/MediaPool/classes/class.ilObjMediaPoolGUI.php b/components/ILIAS/MediaPool/classes/class.ilObjMediaPoolGUI.php index 119b992338e6..9939f041ead9 100755 --- a/components/ILIAS/MediaPool/classes/class.ilObjMediaPoolGUI.php +++ b/components/ILIAS/MediaPool/classes/class.ilObjMediaPoolGUI.php @@ -25,6 +25,7 @@ use ILIAS\MediaPool\InternalGUIService; use ILIAS\FileUpload\Handler\HandlerResult; use ILIAS\MediaPool\Settings\SettingsGUI; +use ILIAS\ILIASObject\Properties\Translations\TranslationGUI; /** * User Interface class for media pool objects @@ -33,7 +34,7 @@ * * @ilCtrl_Calls ilObjMediaPoolGUI: ilObjMediaObjectGUI, ilObjFolderGUI, ilEditClipboardGUI, ilPermissionGUI * @ilCtrl_Calls ilObjMediaPoolGUI: ilInfoScreenGUI, ilMediaPoolPageGUI, ilExportGUI - * @ilCtrl_Calls ilObjMediaPoolGUI: ilCommonActionDispatcherGUI, ilObjectCopyGUI, ilObjectTranslationGUI, ilMediaPoolImportGUI + * @ilCtrl_Calls ilObjMediaPoolGUI: ilCommonActionDispatcherGUI, ilObjectCopyGUI, ILIAS\ILIASObject\Properties\Translations\TranslationGUI, ilMediaPoolImportGUI * @ilCtrl_Calls ilObjMediaPoolGUI: ilObjectMetaDataGUI * @ilCtrl_Calls ilObjMediaPoolGUI: ilMobMultiSrtUploadGUI, ilObjectMetaDataGUI, ilRepoStandardUploadHandlerGUI, ilMediaCreationGUI * @ilCtrl_Calls ilObjMediaPoolGUI: ILIAS\MediaPool\Settings\SettingsGUI @@ -53,6 +54,7 @@ class ilObjMediaPoolGUI extends ilObject2GUI protected ilGlobalTemplateInterface $main_tpl; protected FileUpload $upload; protected ilLogger $mep_log; + protected ilDBInterface $db; public bool $output_prepared; public function __construct( @@ -80,6 +82,7 @@ public function __construct( $this->mep_log = ilLoggerFactory::getLogger("mep"); + $this->db = $DIC->database(); $this->mode = ($this->mep_request->getMode() !== "") ? $this->mep_request->getMode() @@ -392,14 +395,26 @@ public function executeCommand(): void $this->ctrl->forwardCommand($gui); break; - case 'ilobjecttranslationgui': + case strtolower(TranslationGUI::class): $this->prepareOutput(); $this->addHeaderAction(); //$this->setTabs("settings"); $ilTabs->activateTab("settings"); $this->setSettingsSubTabs("obj_multilinguality"); - $transgui = new ilObjectTranslationGUI($this); - $transgui->setTitleDescrOnlyMode(false); + $transgui = new TranslationGUI( + $this->getObject(), + $this->lng, + $this->access, + $this->user, + $this->ctrl, + $this->tpl, + $this->ui_factory, + $this->ui_renderer, + $this->http, + $this->refinery, + $this->toolbar + ); + $transgui->forceContentTranslation(); $this->ctrl->forwardCommand($transgui); $this->tpl->printToStdout(); break; @@ -1230,7 +1245,7 @@ public function setSettingsSubTabs( if ($mset->get("mep_activate_pages")) { $ilTabs->addSubTabTarget( "obj_multilinguality", - $this->ctrl->getLinkTargetByClass("ilobjecttranslationgui", "") + $this->ctrl->getLinkTargetByClass(TranslationGUI::class, "") ); } } @@ -1414,9 +1429,9 @@ public static function getPreviewModalHTML( public function export(): void { - $ot = ilObjectTranslation::getInstance($this->object->getId()); + $ot = $this->object->getObjectProperties()->getPropertyTranslations(); $opt = ""; - if ($ot->getContentActivated()) { + if ($ot->getContentTranslationActivated()) { $format = explode("_", $this->mep_request->getExportFormat()); $opt = ilUtil::stripSlashes($format[1]); } @@ -1470,6 +1485,15 @@ protected function handleUploadResult( $mob->setDescription(""); $mob->create(); + $media_item = $mob->addMediaItemFromUpload( + "Standard", + $result, + $this->mep_request->getUploadHash() + ); + + $mob->update(); + + /* $mob->createDirectory(); $media_item = new ilMediaItem(); $mob->addMediaItem($media_item); @@ -1485,7 +1509,13 @@ protected function handleUploadResult( Location::WEB, $file_name, true - ); + );*/ + + // duration + $med_item = $mob->getMediaItem("Standard"); + $med_item->determineDuration(); + $med_item->update(); + $mep_item = new ilMediaPoolItem(); $mep_item->setTitle($title); @@ -1498,6 +1528,7 @@ protected function handleUploadResult( $tree->insertNode($mep_item->getId(), $parent); // get mime type + /* $format = ilObjMediaObject::getMimeType($file); $location = $file_name; @@ -1506,17 +1537,19 @@ protected function handleUploadResult( $media_item->setLocation($location); $media_item->setLocationType("LocalFile"); $media_item->setUploadHash($this->mep_request->getUploadHash()); - $mob->update(); + $mob->update();*/ $item_ids[] = $mob->getId(); + /* $mob = new ilObjMediaObject($mob->getId()); - $mob->generatePreviewPic(320, 240); + $mob->generatePreviewPic(320, 240);*/ // duration + /* $med_item = $mob->getMediaItem("Standard"); $med_item->determineDuration(); - $med_item->update(); + $med_item->update();*/ return new BasicHandlerResult( "mep_id", diff --git a/components/ILIAS/MediaPool/classes/class.ilObjMediaPoolSubItemListGUI.php b/components/ILIAS/MediaPool/classes/class.ilObjMediaPoolSubItemListGUI.php index 7249475d06d8..f3fa2f3437d7 100755 --- a/components/ILIAS/MediaPool/classes/class.ilObjMediaPoolSubItemListGUI.php +++ b/components/ILIAS/MediaPool/classes/class.ilObjMediaPoolSubItemListGUI.php @@ -89,41 +89,17 @@ public function getHTML(): string protected function parseImage(int $a_sub_id): bool { + global $DIC; + $thumbs_gui = $DIC->mediaObjects()->internal()->gui()->thumbs(); + $sub_id = ilMediaPoolItem::lookupForeignId($a_sub_id); // output thumbnail (or mob icon) if (ilObject::_lookupType($sub_id) === "mob") { $mob = new ilObjMediaObject($sub_id); - $med = $mob->getMediaItem("Standard"); - $target = $med->getThumbnailTarget(); - - if ($target != "") { - // begin-patch mime_filter - $this->tpl->setVariable( - 'LINKED_LINK', - ilLink::_getLink( - $this->getRefId(), - 'mep', - array('action' => 'showMedia', 'mob_id' => $sub_id,'mepitem_id' => $a_sub_id) - ) - ); - $this->tpl->setVariable('LINKED_TARGET', '_blank'); - $this->tpl->setVariable("LINKED_IMAGE", ilUtil::img($target)); - // end-patch mime_filter - } else { - $this->tpl->setVariable("SUB_ITEM_IMAGE", ilUtil::img(ilUtil::getImagePath("standard/icon_" . "mob" . ".gif"))); - } - if (ilUtil::deducibleSize($med->getFormat()) && $med->getLocationType() === "Reference") { - $size = getimagesize($med->getLocation()); - if ($size[0] > 0 && $size[1] > 0) { - $wr = $size[0] / 80; - $hr = $size[1] / 80; - $r = max($wr, $hr); - $w = (int) ($size[0] / $r); - $h = (int) ($size[1] / $r); - $this->tpl->setVariable("SUB_ITEM_IMAGE", ilUtil::img($med->getLocation(), "", $w, $h)); - return true; - } - } + $this->tpl->setVariable( + "SUB_ITEM_IMAGE", + $thumbs_gui->getThumbHtml($sub_id) + ); } return false; } diff --git a/components/ILIAS/MediaPool/templates/default/tpl.mep_list_row.html b/components/ILIAS/MediaPool/templates/default/tpl.mep_list_row.html index 92aea5341969..399b75069190 100755 --- a/components/ILIAS/MediaPool/templates/default/tpl.mep_list_row.html +++ b/components/ILIAS/MediaPool/templates/default/tpl.mep_list_row.html @@ -7,7 +7,7 @@ - + {IMG} diff --git a/components/ILIAS/Membership/classes/class.ilAttendanceList.php b/components/ILIAS/Membership/classes/class.ilAttendanceList.php index d7561eb44014..d8ea508e411c 100755 --- a/components/ILIAS/Membership/classes/class.ilAttendanceList.php +++ b/components/ILIAS/Membership/classes/class.ilAttendanceList.php @@ -607,7 +607,7 @@ public function getHTML(): string if (!$this->has_local_role) { $valid_user_ids = array_merge($valid_user_ids, $members); } else { - $valid_user_ids = array_merge($valid_user_ids, (array) $members[$role_id]); + $valid_user_ids = array_merge($valid_user_ids, (array) ($members[$role_id] ?? [])); } break; } diff --git a/components/ILIAS/Membership/classes/class.ilMembershipGUI.php b/components/ILIAS/Membership/classes/class.ilMembershipGUI.php index 427b08936956..4903bef475bc 100755 --- a/components/ILIAS/Membership/classes/class.ilMembershipGUI.php +++ b/components/ILIAS/Membership/classes/class.ilMembershipGUI.php @@ -1,4 +1,5 @@ tpl->setOnScreenMessage('success', $this->lng->txt("crs_users_added"), true); } else { - $this->tpl->setOnScreenMessage('failure', $this->lng->txt("crs_users_already_assigned"), true); + $this->tpl->setOnScreenMessage('info', $this->lng->txt("crs_users_already_assigned"), true); } $this->ctrl->redirect($this, 'participants'); } diff --git a/components/ILIAS/Membership/classes/class.ilParticipants.php b/components/ILIAS/Membership/classes/class.ilParticipants.php index d71983fd240a..ed38e4fb6513 100755 --- a/components/ILIAS/Membership/classes/class.ilParticipants.php +++ b/components/ILIAS/Membership/classes/class.ilParticipants.php @@ -108,8 +108,8 @@ public static function getInstance(int $a_ref_id): ilParticipants case 'sess': return ilSessionParticipants::getInstance($a_ref_id); default: - $logger()->mem()->logStack(); - $logger()->mem()->warning('Invalid ref_id -> obj_id given: ' . $a_ref_id . ' -> ' . $obj_id); + $logger->logStack(); + $logger->warning('Invalid ref_id -> obj_id given: ' . $a_ref_id . ' -> ' . $obj_id); throw new InvalidArgumentException('Invalid obj_id given.'); } } @@ -689,7 +689,7 @@ public function checkLastAdmin(array $a_usr_ids): bool public function isBlocked(int $a_usr_id): bool { if (isset($this->participants_status[$a_usr_id])) { - return (bool) $this->participants_status[$a_usr_id]['blocked']; + return (bool) ($this->participants_status[$a_usr_id]['blocked'] ?? false); } return false; } @@ -700,7 +700,7 @@ public function isBlocked(int $a_usr_id): bool public function hasPassed(int $a_usr_id): bool { if (isset($this->participants_status[$a_usr_id])) { - return (bool) $this->participants_status[$a_usr_id]['passed']; + return (bool) ($this->participants_status[$a_usr_id]['passed'] ?? false); } return false; } @@ -886,7 +886,7 @@ public function addRecommendation(int $a_usr_id): void public function isNotificationEnabled(int $a_usr_id): bool { if (isset($this->participants_status[$a_usr_id])) { - return (bool) $this->participants_status[$a_usr_id]['notification']; + return (bool) ($this->participants_status[$a_usr_id]['notification'] ?? false); } return false; } @@ -894,7 +894,7 @@ public function isNotificationEnabled(int $a_usr_id): bool public function isContact(int $a_usr_id): bool { if (isset($this->participants_status[$a_usr_id])) { - return (bool) $this->participants_status[$a_usr_id]['contact']; + return (bool) ($this->participants_status[$a_usr_id]['contact'] ?? false); } return false; } diff --git a/components/ILIAS/MetaData/ROADMAP.md b/components/ILIAS/MetaData/ROADMAP.md index c78b79ef117f..b56168873cb8 100755 --- a/components/ILIAS/MetaData/ROADMAP.md +++ b/components/ILIAS/MetaData/ROADMAP.md @@ -67,15 +67,6 @@ The `Derivator` in the API could be expanded to contain methods like LOM set before it is persisted. The repository would need to take into account more types of markers/scaffolds in `transferMD`. -### Abandon the old backend - -All ILIAS components using MD should at some point only use the -new classes as the new MD editor does. - -Only a few usages of the deprecated classes remain, most of these -related to export/import. Those and all deprecated classes will be -removed with ILIAS 10. - ### Customizable LOM Digest Customizing of LOM Digest could be made possible for plugins, in diff --git a/components/ILIAS/MetaData/classes/Editor/Digest/ContentAssembler.php b/components/ILIAS/MetaData/classes/Editor/Digest/ContentAssembler.php index 85932094193b..a3723a8a2ad5 100755 --- a/components/ILIAS/MetaData/classes/Editor/Digest/ContentAssembler.php +++ b/components/ILIAS/MetaData/classes/Editor/Digest/ContentAssembler.php @@ -38,18 +38,18 @@ class ContentAssembler { // post variables - public const KEYWORDS = 'keywords'; - public const GENERAL = 'general'; - public const AUTHORS = 'authors'; - public const RIGHTS = 'rights'; - public const TYPICAL_LEARNING_TIME = 'tlt'; - public const FIRST_AUTHOR = 'first_author'; - public const SECOND_AUTHOR = 'second_author'; - public const THIRD_AUTHOR = 'third_author'; - - public const CUSTOM_CP = 'custom_cp'; - public const CUSTOM_CP_DESCRIPTION = 'custom_cp_description'; - public const OER_BLOCKED = 'oer_blocked_'; + public const string KEYWORDS = 'keywords'; + public const string GENERAL = 'general'; + public const string AUTHORS = 'authors'; + public const string RIGHTS = 'rights'; + public const string TYPICAL_LEARNING_TIME = 'tlt'; + public const string FIRST_AUTHOR = 'first_author'; + public const string SECOND_AUTHOR = 'second_author'; + public const string THIRD_AUTHOR = 'third_author'; + + public const string CUSTOM_CP = 'custom_cp'; + public const string CUSTOM_CP_DESCRIPTION = 'custom_cp_description'; + public const string OER_BLOCKED = 'oer_blocked_'; protected PathFactory $path_factory; protected NavigatorFactoryInterface $navigator_factory; diff --git a/components/ILIAS/MetaData/classes/Editor/Digest/ManipulatorAdapter.php b/components/ILIAS/MetaData/classes/Editor/Digest/ManipulatorAdapter.php index f4d4e756d185..5bcca30dd53b 100755 --- a/components/ILIAS/MetaData/classes/Editor/Digest/ManipulatorAdapter.php +++ b/components/ILIAS/MetaData/classes/Editor/Digest/ManipulatorAdapter.php @@ -25,7 +25,6 @@ use ILIAS\MetaData\Paths\FactoryInterface as PathFactory; use ILIAS\MetaData\Paths\Navigator\NavigatorFactoryInterface; use ILIAS\MetaData\Editor\Manipulator\ManipulatorInterface; -use ILIAS\MetaData\Vocabularies\Standard\Dictionary\LOMDictionaryInitiator; use ILIAS\MetaData\Vocabularies\Factory\Factory; use ILIAS\MetaData\Vocabularies\Factory\FactoryInterface; diff --git a/components/ILIAS/MetaData/classes/Editor/Full/FullEditor.php b/components/ILIAS/MetaData/classes/Editor/Full/FullEditor.php index 57f66bce30a9..cd903e38f1e8 100755 --- a/components/ILIAS/MetaData/classes/Editor/Full/FullEditor.php +++ b/components/ILIAS/MetaData/classes/Editor/Full/FullEditor.php @@ -37,10 +37,10 @@ class FullEditor { - public const TABLE = 'table'; - public const PANEL = 'panel'; - public const ROOT = 'root'; - public const FORM = 'form'; + public const string TABLE = 'table'; + public const string PANEL = 'panel'; + public const string ROOT = 'root'; + public const string FORM = 'form'; protected EditorDictionaryInterface $editor_dictionary; protected NavigatorFactoryInterface $navigator_factory; diff --git a/components/ILIAS/MetaData/classes/Editor/Full/Services/Actions/ModalFactory.php b/components/ILIAS/MetaData/classes/Editor/Full/Services/Actions/ModalFactory.php index 575cee49abf6..5777efde2b61 100755 --- a/components/ILIAS/MetaData/classes/Editor/Full/Services/Actions/ModalFactory.php +++ b/components/ILIAS/MetaData/classes/Editor/Full/Services/Actions/ModalFactory.php @@ -38,7 +38,7 @@ class ModalFactory { - public const MAX_LENGTH = 128; + public const int MAX_LENGTH = 128; protected LinkProvider $link_provider; protected UIFactory $factory; @@ -118,7 +118,7 @@ public function update( $to_be_updated, false ); - $modal = $this->getRoundtripModal( + $modal = $this->getRoundtripModal( $to_be_updated, $form, Command::UPDATE_FULL, diff --git a/components/ILIAS/MetaData/classes/Editor/Full/TableContent.php b/components/ILIAS/MetaData/classes/Editor/Full/TableContent.php index 8911aaf0a9a9..a7646785d973 100755 --- a/components/ILIAS/MetaData/classes/Editor/Full/TableContent.php +++ b/components/ILIAS/MetaData/classes/Editor/Full/TableContent.php @@ -51,7 +51,7 @@ public function content( $request, ...$elements ); - $builder = $this->services->tableFactory()->table(); + $builder = $this->services->tableFactory()->table(); $delete_buttons = []; $update_buttons = []; foreach ($elements as $element) { diff --git a/components/ILIAS/MetaData/classes/Editor/Presenter/Data.php b/components/ILIAS/MetaData/classes/Editor/Presenter/Data.php index 88b42d06d9eb..924d3f03ae5f 100755 --- a/components/ILIAS/MetaData/classes/Editor/Presenter/Data.php +++ b/components/ILIAS/MetaData/classes/Editor/Presenter/Data.php @@ -27,6 +27,7 @@ use ILIAS\MetaData\Vocabularies\Slots\Identifier as SlotIdentifier; use ILIAS\MetaData\Vocabularies\Dispatch\Presentation\PresentationInterface as VocabulariesPresentation; use ILIAS\MetaData\Vocabularies\Dispatch\Presentation\LabelledValueInterface; +use ILIAS\MetaData\Vocabularies\Dispatch\Presentation\LabelledValue; class Data implements DataInterface { @@ -46,35 +47,49 @@ public function __construct( public function dataValue(ElementsDataInterface $data): string { - return $this->data_presentation->dataValue($data); + return $this->utilities->sanitizeForHTML( + $this->data_presentation->dataValue($data) + ); } /** - * @return string[] with values as keys + * @return LabelledValueInterface[] */ public function vocabularyValues(SlotIdentifier $slot, string ...$values): \Generator { - yield from $this->vocabularies_presentation->presentableLabels( + $labels = $this->vocabularies_presentation->presentableLabels( $this->utilities, $slot, true, ...$values ); + foreach ($labels as $label) { + yield new LabelledValue( + $label->value(), + $this->utilities->sanitizeForHTML($label->label()) + ); + } } public function language(string $language): string { - return $this->data_presentation->language($language); + return $this->utilities->sanitizeForHTML( + $this->data_presentation->language($language) + ); } public function datetime(string $datetime): string { - return $this->data_presentation->datetime($datetime); + return $this->utilities->sanitizeForHTML( + $this->data_presentation->datetime($datetime) + ); } public function duration(string $duration): string { - return $this->data_presentation->duration($duration); + return $this->utilities->sanitizeForHTML( + $this->data_presentation->duration($duration) + ); } /** diff --git a/components/ILIAS/MetaData/classes/Editor/Presenter/Utilities.php b/components/ILIAS/MetaData/classes/Editor/Presenter/Utilities.php index 60dd279c4765..3b1f3d652105 100755 --- a/components/ILIAS/MetaData/classes/Editor/Presenter/Utilities.php +++ b/components/ILIAS/MetaData/classes/Editor/Presenter/Utilities.php @@ -58,4 +58,9 @@ public function txtFill(string $key, string ...$values): string { return $this->utilities->txtFill($key, ...$values); } + + public function sanitizeForHTML(string $string): string + { + return $this->utilities->sanitizeForHTML($string); + } } diff --git a/components/ILIAS/MetaData/classes/Editor/Presenter/UtilitiesInterface.php b/components/ILIAS/MetaData/classes/Editor/Presenter/UtilitiesInterface.php index 2839ad600ea8..eac7e7aad11c 100755 --- a/components/ILIAS/MetaData/classes/Editor/Presenter/UtilitiesInterface.php +++ b/components/ILIAS/MetaData/classes/Editor/Presenter/UtilitiesInterface.php @@ -34,4 +34,6 @@ public function getUserDateFormat(): DateFormat; public function txt(string $key): string; public function txtFill(string $key, string ...$values): string; + + public function sanitizeForHTML(string $string): string; } diff --git a/components/ILIAS/MetaData/classes/Editor/Tree/MDEditorToolProvider.php b/components/ILIAS/MetaData/classes/Editor/Tree/MDEditorToolProvider.php index 29118eba92bd..6cb920e8b8ac 100755 --- a/components/ILIAS/MetaData/classes/Editor/Tree/MDEditorToolProvider.php +++ b/components/ILIAS/MetaData/classes/Editor/Tree/MDEditorToolProvider.php @@ -97,7 +97,7 @@ protected function buildTreeAsTool(SetInterface $set, PathInterface $path): Tool $lng->txt('meta_lom_short') ) ) - ->withContent($this->services->dic()->ui()->factory()->legacy( + ->withContent($this->services->dic()->ui()->factory()->legacy()->content( $this->services->dic()->ui()->renderer()->render($this->getUITree( $set, $path diff --git a/components/ILIAS/MetaData/classes/Editor/class.ilMDEditorGUI.php b/components/ILIAS/MetaData/classes/Editor/class.ilMDEditorGUI.php index c69e0bdbea3a..a46eef92bb2b 100755 --- a/components/ILIAS/MetaData/classes/Editor/class.ilMDEditorGUI.php +++ b/components/ILIAS/MetaData/classes/Editor/class.ilMDEditorGUI.php @@ -44,8 +44,8 @@ */ class ilMDEditorGUI { - public const SET_FOR_TREE = 'md_set_for_tree'; - public const PATH_FOR_TREE = 'md_path_for_tree'; + public const string SET_FOR_TREE = 'md_set_for_tree'; + public const string PATH_FOR_TREE = 'md_path_for_tree'; protected FullEditorInitiator $full_editor_initiator; protected DigestInitiator $digest_initiator; @@ -112,14 +112,14 @@ public function executeCommand(): void public function debug(): bool { - $button = $this->renderButtonToFullEditor(); - $xml = $this->xml_writer->write($this->repository->getMD($this->obj_id, $this->sub_id, $this->type)); $dom = new DOMDocument('1.0'); $dom->formatOutput = true; $dom->preserveWhiteSpace = false; $dom->loadXML($xml->asXML()); - $this->tpl->setContent($button . '
' . htmlentities($dom->saveXML()) . '
'); + + $this->addButtonToFullEditor(); + $this->tpl->setContent('
' . htmlentities($dom->saveXML()) . '
'); return true; } @@ -195,10 +195,8 @@ protected function renderDigest( break; } } - $this->tpl->setContent( - $this->renderButtonToFullEditor() . - $this->ui_renderer->render($template_content) - ); + $this->addButtonToFullEditor(); + $this->tpl->setContent($this->ui_renderer->render($template_content)); } protected function fullEditorCreate(): void @@ -386,29 +384,20 @@ protected function setTabsForFullEditor(): void ); } - protected function renderButtonToFullEditor(): string + protected function addButtonToFullEditor(): void { - $bulky = $this->ui_factory->button()->bulky( - $this->ui_factory->symbol()->icon()->standard( - 'mds', - $this->presenter->utilities()->txt('meta_button_to_full_editor_label'), - 'medium' - ), + $editor = $this->ui_factory->button()->standard( $this->presenter->utilities()->txt('meta_button_to_full_editor_label'), $this->ctrl->getLinkTarget($this, 'fullEditor') ); + $this->toolbar->addComponent($editor); if (DEVMODE) { - $debug = $this->ui_factory->button()->bulky( - $this->ui_factory->symbol()->icon()->standard( - 'adm', - 'Debug' - ), + $debug = $this->ui_factory->button()->standard( 'Debug', $this->ctrl->getLinkTarget($this, 'debug') ); + $this->toolbar->addComponent($debug); } - return $this->ui_renderer->render($bulky) . - (isset($debug) ? '

' . $this->ui_renderer->render($debug) : ''); } protected function checkAccess(): void diff --git a/components/ILIAS/MetaData/classes/Elements/RessourceID/RessourceID.php b/components/ILIAS/MetaData/classes/Elements/RessourceID/RessourceID.php index 47fd71b7988e..509fc726c629 100755 --- a/components/ILIAS/MetaData/classes/Elements/RessourceID/RessourceID.php +++ b/components/ILIAS/MetaData/classes/Elements/RessourceID/RessourceID.php @@ -32,7 +32,7 @@ public function __construct( string $type ) { $this->obj_id = $obj_id; - $this->sub_id =$sub_id; + $this->sub_id = $sub_id; $this->type = $type; } diff --git a/components/ILIAS/MetaData/classes/Manipulator/Manipulator.php b/components/ILIAS/MetaData/classes/Manipulator/Manipulator.php index 9bb894f17be9..23cedbd2e047 100755 --- a/components/ILIAS/MetaData/classes/Manipulator/Manipulator.php +++ b/components/ILIAS/MetaData/classes/Manipulator/Manipulator.php @@ -164,7 +164,6 @@ protected function getElementsToUpdate( $root ); } - continue; } } @@ -244,7 +243,6 @@ protected function getElementsToCreate( $root ); } - continue; } } diff --git a/components/ILIAS/MetaData/classes/Paths/Navigator/BaseNavigator.php b/components/ILIAS/MetaData/classes/Paths/Navigator/BaseNavigator.php index 1496ef897d1c..00c310a66a2f 100755 --- a/components/ILIAS/MetaData/classes/Paths/Navigator/BaseNavigator.php +++ b/components/ILIAS/MetaData/classes/Paths/Navigator/BaseNavigator.php @@ -99,7 +99,7 @@ public function nextStep(): ?BaseNavigatorInterface public function previousStep(): ?BaseNavigatorInterface { - if(empty($this->previous_steps)) { + if (empty($this->previous_steps)) { return null; } $clone = clone $this; diff --git a/components/ILIAS/MetaData/classes/Presentation/NullUtilities.php b/components/ILIAS/MetaData/classes/Presentation/NullUtilities.php index 2d3c4ae4ad4b..101fb247cef1 100755 --- a/components/ILIAS/MetaData/classes/Presentation/NullUtilities.php +++ b/components/ILIAS/MetaData/classes/Presentation/NullUtilities.php @@ -38,4 +38,9 @@ public function txtFill(string $key, string ...$values): string { return ''; } + + public function sanitizeForHTML(string $string): string + { + return ''; + } } diff --git a/components/ILIAS/MetaData/classes/Presentation/Services/Services.php b/components/ILIAS/MetaData/classes/Presentation/Services/Services.php index b194248e6542..fc1fba670d16 100755 --- a/components/ILIAS/MetaData/classes/Presentation/Services/Services.php +++ b/components/ILIAS/MetaData/classes/Presentation/Services/Services.php @@ -57,7 +57,8 @@ public function utilities(): UtilitiesInterface } return $this->utilities = new Utilities( $this->dic->language(), - $this->dic->user() + $this->dic->user(), + $this->dic->refinery() ); } diff --git a/components/ILIAS/MetaData/classes/Presentation/Utilities.php b/components/ILIAS/MetaData/classes/Presentation/Utilities.php index e164073f7d38..9372d8d7ca3e 100755 --- a/components/ILIAS/MetaData/classes/Presentation/Utilities.php +++ b/components/ILIAS/MetaData/classes/Presentation/Utilities.php @@ -21,19 +21,23 @@ namespace ILIAS\MetaData\Presentation; use ILIAS\Data\DateFormat\DateFormat; +use ILIAS\Refinery\Factory as Refinery; class Utilities implements UtilitiesInterface { protected \ilLanguage $lng; protected \ilObjUser $user; + protected Refinery $refinery; public function __construct( \ilLanguage $lng, \ilObjUser $user, + Refinery $refinery ) { $this->lng = $lng; $this->lng->loadLanguageModule('meta'); $this->user = $user; + $this->refinery = $refinery; } public function getUserDateFormat(): DateFormat @@ -53,4 +57,9 @@ public function txtFill(string $key, string ...$values): string } return $key . ' ' . implode(', ', $values); } + + public function sanitizeForHTML(string $string): string + { + return $this->refinery->encode()->htmlSpecialCharsAsEntities()->transform($string); + } } diff --git a/components/ILIAS/MetaData/classes/Presentation/UtilitiesInterface.php b/components/ILIAS/MetaData/classes/Presentation/UtilitiesInterface.php index df5f8840458d..a71ccab27184 100755 --- a/components/ILIAS/MetaData/classes/Presentation/UtilitiesInterface.php +++ b/components/ILIAS/MetaData/classes/Presentation/UtilitiesInterface.php @@ -29,4 +29,6 @@ public function getUserDateFormat(): DateFormat; public function txt(string $key): string; public function txtFill(string $key, string ...$values): string; + + public function sanitizeForHTML(string $string): string; } diff --git a/components/ILIAS/MetaData/classes/Repository/Dictionary/LOMDictionaryInitiator.php b/components/ILIAS/MetaData/classes/Repository/Dictionary/LOMDictionaryInitiator.php index 25fe914780c9..2ea0e04a5bcf 100755 --- a/components/ILIAS/MetaData/classes/Repository/Dictionary/LOMDictionaryInitiator.php +++ b/components/ILIAS/MetaData/classes/Repository/Dictionary/LOMDictionaryInitiator.php @@ -28,7 +28,7 @@ class LOMDictionaryInitiator extends BaseDictionaryInitiator { - public const TABLES = [ + public const array TABLES = [ 'annotation' => 'il_meta_annotation', 'classification' => 'il_meta_classification', 'contribute' => 'il_meta_contribute', @@ -59,7 +59,7 @@ class LOMDictionaryInitiator extends BaseDictionaryInitiator 'context' => 'il_meta_context' ]; - public const ID_NAME = [ + public const array ID_NAME = [ 'annotation' => 'meta_annotation_id', 'classification' => 'meta_classification_id', 'contribute' => 'meta_contribute_id', diff --git a/components/ILIAS/MetaData/classes/Settings/Copyright/Usage/class.ilMDCopyrightUsageGUI.php b/components/ILIAS/MetaData/classes/Settings/Copyright/Usage/class.ilMDCopyrightUsageGUI.php index 8a47dacd2028..625817377142 100755 --- a/components/ILIAS/MetaData/classes/Settings/Copyright/Usage/class.ilMDCopyrightUsageGUI.php +++ b/components/ILIAS/MetaData/classes/Settings/Copyright/Usage/class.ilMDCopyrightUsageGUI.php @@ -29,7 +29,7 @@ */ class ilMDCopyrightUsageGUI { - public const DEFAULT_CMD = 'showUsageTable'; + public const string DEFAULT_CMD = 'showUsageTable'; protected EntryInterface $entry; diff --git a/components/ILIAS/MetaData/classes/Settings/Copyright/class.ilMDCopyrightSelectionEntry.php b/components/ILIAS/MetaData/classes/Settings/Copyright/class.ilMDCopyrightSelectionEntry.php deleted file mode 100755 index a254890f4337..000000000000 --- a/components/ILIAS/MetaData/classes/Settings/Copyright/class.ilMDCopyrightSelectionEntry.php +++ /dev/null @@ -1,359 +0,0 @@ - - */ -class ilMDCopyrightSelectionEntry -{ - protected ilLogger $logger; - protected ilDBInterface $db; - protected RendererInterface $renderer; - protected RepositoryInterface $repository; - protected UIRenderer $ui_renderer; - - private int $entry_id; - private string $title = ''; - private string $description = ''; - private string $copyright = ''; - private int $usage = 0; - - protected bool $outdated = false; - - protected int $order_position = 0; - - public function __construct(int $a_entry_id) - { - global $DIC; - - $this->renderer = new Renderer( - $DIC->ui()->factory(), - $DIC->resourceStorage() - ); - $this->repository = new DatabaseRepository(new Wrapper($DIC->database())); - $this->ui_renderer = $DIC->ui()->renderer(); - $this->logger = $DIC->logger()->meta(); - $this->db = $DIC->database(); - $this->entry_id = $a_entry_id; - $this->read(); - } - - /** - * @return ilMDCopyrightSelectionEntry[] - */ - public static function _getEntries(): array - { - global $DIC; - - $ilDB = $DIC->database(); - - $query = "SELECT entry_id FROM il_md_cpr_selections ORDER BY is_default DESC, position ASC"; - $res = $ilDB->query($query); - - $entries = []; - while ($row = $ilDB->fetchObject($res)) { - $entries[] = new ilMDCopyrightSelectionEntry((int) $row->entry_id); - } - return $entries; - } - - public static function lookupCopyyrightTitle(string $a_cp_string): string - { - global $DIC; - - $ilDB = $DIC->database(); - - if (!$entry_id = self::_extractEntryId($a_cp_string)) { - return $a_cp_string; - } - - $query = "SELECT title FROM il_md_cpr_selections " . - "WHERE entry_id = " . $ilDB->quote($entry_id, ilDBConstants::T_INTEGER) . " "; - $res = $ilDB->query($query); - $row = $ilDB->fetchObject($res); - return $row->title ?? ''; - } - - public static function _lookupCopyright(string $a_cp_string): string - { - global $DIC; - - $renderer = new Renderer( - $DIC->ui()->factory(), - $DIC->resourceStorage() - ); - $repository = new DatabaseRepository(new Wrapper($DIC->database())); - $ui_renderer = $DIC->ui()->renderer(); - - if (!$entry_id = self::_extractEntryId($a_cp_string)) { - return $a_cp_string; - } - - $entry = $repository->getEntry($entry_id); - $components = $renderer->toUIComponents($entry->copyrightData()); - - return $ui_renderer->render($components); - } - - public static function _lookupCopyrightForExport(string $a_cp_string): string - { - global $DIC; - - $repository = new DatabaseRepository(new Wrapper($DIC->database())); - - if (!$entry_id = self::_extractEntryId($a_cp_string)) { - return $a_cp_string; - } - - $data = $repository->getEntry($entry_id)->copyrightData(); - - return (string) ($data->link() ?? $data->fullName()); - } - - public static function lookupCopyrightFromImport(string $copyright_text): int - { - global $DIC; - - $repository = new DatabaseRepository(new Wrapper($DIC->database())); - - // url should be made to match regardless of scheme - $normalized_copyright = str_replace('https://', 'http://', $copyright_text); - - $matches_by_name = null; - foreach ($repository->getAllEntries() as $entry) { - $entry_link = (string) $entry->copyrightData()->link(); - $normalized_link = str_replace('https://', 'http://', $entry_link); - if ($normalized_link !== '' && str_contains($normalized_copyright, $normalized_link)) { - return $entry->id(); - } - - if ( - is_null($matches_by_name) && - trim($copyright_text) === trim($entry->copyrightData()->fullName()) - ) { - $matches_by_name = $entry->id(); - } - } - - if (!is_null($matches_by_name)) { - return $matches_by_name; - } - return 0; - } - - public static function _extractEntryId(string $a_cp_string): int - { - if (!preg_match('/il_copyright_entry__([0-9]+)__([0-9]+)/', $a_cp_string, $matches)) { - return 0; - } - if ($matches[1] != IL_INST_ID) { - return 0; - } - return (int) ($matches[2] ?? 0); - } - - public static function isEntry($a_cp_string): bool - { - if (!preg_match('/il_copyright_entry__([0-9]+)__([0-9]+)/', $a_cp_string)) { - return false; - } - return true; - } - - public function getUsage(): int - { - return $this->usage; - } - - public function getEntryId(): int - { - return $this->entry_id; - } - - /** - * Get if the entry is default - * No setter for this. - */ - public function getIsDefault(): bool - { - $query = "SELECT is_default FROM il_md_cpr_selections " . - "WHERE entry_id = " . $this->db->quote($this->entry_id, 'integer'); - - $res = $this->db->query($query); - $row = $this->db->fetchAssoc($res); - - return (bool) ($row['is_default'] ?? false); - } - - public function setOutdated(bool $a_value): void - { - $this->outdated = $a_value; - } - - public function getOutdated(): bool - { - return $this->outdated; - } - - public static function getDefault(): int - { - global $DIC; - - $db = $DIC->database(); - - $query = "SELECT entry_id FROM il_md_cpr_selections " . - "WHERE is_default = " . $db->quote(1, 'integer'); - - $res = $db->query($query); - $row = $db->fetchAssoc($res); - - return (int) $row['entry_id']; - } - - public function setTitle(string $a_title): void - { - $this->title = $a_title; - } - - public function getTitle(): string - { - return $this->title; - } - - public function setDescription(string $a_desc): void - { - $this->description = $a_desc; - } - - public function getDescription(): string - { - return $this->description; - } - - public function setCopyright(string $a_copyright): void - { - $this->copyright = $a_copyright; - } - - public function getCopyright(): string - { - return $this->copyright; - } - - public function setOrderPosition(int $a_position): void - { - $this->order_position = $a_position; - } - - public function getOrderPosition(): int - { - return $this->order_position; - } - - protected function getNextOrderPosition(): int - { - $query = "SELECT count(entry_id) total FROM il_md_cpr_selections"; - $res = $this->db->query($query); - $row = $this->db->fetchAssoc($res); - - return $row['total'] + 1; - } - - public function add(): bool - { - $next_id = $this->db->nextId('il_md_cpr_selections'); - - $this->db->insert('il_md_cpr_selections', array( - 'entry_id' => array('integer', $next_id), - 'title' => array('text', $this->getTitle()), - 'description' => array('clob', $this->getDescription()), - //'copyright' => array('clob', $this->getCopyright()), - 'outdated' => array('integer', $this->getOutdated()), - 'position' => array('integer', $this->getNextOrderPosition()) - )); - $this->entry_id = $next_id; - return true; - } - - public function update(): bool - { - $this->db->update('il_md_cpr_selections', array( - 'title' => array('text', $this->getTitle()), - 'description' => array('clob', $this->getDescription()), - //'copyright' => array('clob', $this->getCopyright()), - 'outdated' => array('integer', $this->getOutdated()), - 'position' => array('integer', $this->getOrderPosition()) - ), array( - 'entry_id' => array('integer', $this->getEntryId()) - )); - return true; - } - - public function delete(): void - { - /*$query = "DELETE FROM il_md_cpr_selections " . - "WHERE entry_id = " . $this->db->quote($this->getEntryId(), 'integer') . " "; - $res = $this->db->manipulate($query);*/ - } - - public function validate(): bool - { - return $this->getTitle() !== ''; - } - - private function read(): void - { - $entry = $this->repository->getEntry($this->entry_id); - - $rendered_cp = $this->ui_renderer->render( - $this->renderer->toUIComponents($entry->copyrightData()) - ); - - $this->setTitle($entry->title()); - $this->setDescription($entry->description()); - $this->setCopyright($rendered_cp); - $this->setOutdated($entry->isOutdated()); - $this->setOrderPosition($entry->position()); - - $query = "SELECT count(meta_rights_id) used FROM il_meta_rights " . - "WHERE description = " . $this->db->quote( - 'il_copyright_entry__' . IL_INST_ID . '__' . $this->getEntryId(), - 'text' - ); - - $res = $this->db->query($query); - $row = $this->db->fetchObject($res); - $this->usage = (int) $row->used; - } - - public static function createIdentifier(int $a_entry_id): string - { - return 'il_copyright_entry__' . IL_INST_ID . '__' . $a_entry_id; - } -} diff --git a/components/ILIAS/MetaData/classes/Settings/OER/class.ilMDOERSettingsGUI.php b/components/ILIAS/MetaData/classes/Settings/OER/class.ilMDOERSettingsGUI.php index d3c9343451af..fa6e0f53d53e 100644 --- a/components/ILIAS/MetaData/classes/Settings/OER/class.ilMDOERSettingsGUI.php +++ b/components/ILIAS/MetaData/classes/Settings/OER/class.ilMDOERSettingsGUI.php @@ -100,24 +100,35 @@ protected function initSettingsForm(): ilPropertyFormGUI { $form = new ilPropertyFormGUI(); $form->setFormAction($this->ctrl->getFormAction($this)); - $form->setTitle($this->lng->txt('md_copyright_settings')); if ($this->access_service->hasCurrentUserWriteAccess()) { $form->addCommandButton('saveOERSettings', $this->lng->txt('save')); } + $header = new ilFormSectionHeaderGUI(); + $header->setTitle($this->lng->txt('md_settings_licence')); + $form->addItem($header); + $check = new ilCheckboxInputGUI($this->lng->txt('md_copyright_enabled'), 'active'); $check->setChecked($this->MDSettings()->isCopyrightSelectionActive()); $check->setValue('1'); $check->setInfo($this->lng->txt('md_copyright_enable_info')); $form->addItem($check); + $header = new ilFormSectionHeaderGUI(); + $header->setTitle($this->lng->txt('md_settings_harvester')); + $form->addItem($header); + ilAdministrationSettingsFormHandler::addFieldsToForm( $this->getAdministrationFormId(), $form, $this->parent_obj_gui ); + $header = new ilFormSectionHeaderGUI(); + $header->setTitle($this->lng->txt('md_settings_publishing')); + $form->addItem($header); + $oai_check = new ilCheckboxInputGUI($this->lng->txt('md_oai_pmh_enabled'), 'oai_active'); $oai_check->setChecked($this->MDSettings()->isOAIPMHActive()); $oai_check->setValue('1'); diff --git a/components/ILIAS/MetaData/classes/Settings/Vocabularies/Presentation.php b/components/ILIAS/MetaData/classes/Settings/Vocabularies/Presentation.php index ef11f3f0b3dc..6cd741bf7215 100755 --- a/components/ILIAS/MetaData/classes/Settings/Vocabularies/Presentation.php +++ b/components/ILIAS/MetaData/classes/Settings/Vocabularies/Presentation.php @@ -156,7 +156,7 @@ public function makeValuesPresentable( $vocabulary->type() === VocabType::STANDARD || $vocabulary->type() === VocabType::COPYRIGHT ) { - $presentable_values[] = $labelled_value->label(); + $presentable_values[] = $this->presentation_utils->sanitizeForHTML($labelled_value->label()); continue; } @@ -164,7 +164,7 @@ public function makeValuesPresentable( if ($labelled_value->label() !== '') { $presentable_value = $labelled_value->label() . ' (' . $presentable_value . ')'; } - $presentable_values[] = $presentable_value; + $presentable_values[] = $this->presentation_utils->sanitizeForHTML($presentable_value); } return $presentable_values; diff --git a/components/ILIAS/MetaData/classes/Settings/Vocabularies/class.ilMDVocabulariesGUI.php b/components/ILIAS/MetaData/classes/Settings/Vocabularies/class.ilMDVocabulariesGUI.php index b5f7bc76e31a..99d2679e2d3a 100644 --- a/components/ILIAS/MetaData/classes/Settings/Vocabularies/class.ilMDVocabulariesGUI.php +++ b/components/ILIAS/MetaData/classes/Settings/Vocabularies/class.ilMDVocabulariesGUI.php @@ -36,6 +36,7 @@ use ILIAS\Refinery\Factory as Refinery; use ILIAS\MetaData\Settings\Vocabularies\DataRetrieval; use JetBrains\PhpStorm\NoReturn; +use ILIAS\UICore\GlobalTemplate; /** * @ilCtrl_Calls ilMDVocabulariesGUI: ilMDVocabularyUploadHandlerGUI @@ -276,7 +277,7 @@ protected function deleteVocabulary(): void ); } $this->tpl->setOnScreenMessage( - ilGlobalTemplateInterface::MESSAGE_TYPE_SUCCESS, + GlobalTemplate::MESSAGE_TYPE_SUCCESS, $this->lng->txt('md_vocab_deletion_successful'), true ); @@ -289,7 +290,7 @@ protected function activateVocabulary(string $vocab_id): void $this->vocab_manager->getVocabulary($vocab_id) ); $this->tpl->setOnScreenMessage( - ilGlobalTemplateInterface::MESSAGE_TYPE_SUCCESS, + GlobalTemplate::MESSAGE_TYPE_SUCCESS, $this->lng->txt('md_vocab_update_successful'), true ); @@ -302,7 +303,7 @@ protected function deactivateVocabulary(string $vocab_id): void $this->vocab_manager->getVocabulary($vocab_id) ); $this->tpl->setOnScreenMessage( - ilGlobalTemplateInterface::MESSAGE_TYPE_SUCCESS, + GlobalTemplate::MESSAGE_TYPE_SUCCESS, $this->lng->txt('md_vocab_update_successful'), true ); @@ -315,7 +316,7 @@ protected function allowCustomInputForVocabulary(string $vocab_id): void $this->vocab_manager->getVocabulary($vocab_id) ); $this->tpl->setOnScreenMessage( - ilGlobalTemplateInterface::MESSAGE_TYPE_SUCCESS, + GlobalTemplate::MESSAGE_TYPE_SUCCESS, $this->lng->txt('md_vocab_update_successful'), true ); @@ -328,7 +329,7 @@ protected function disallowCustomInputForVocabulary(string $vocab_id): void $this->vocab_manager->getVocabulary($vocab_id) ); $this->tpl->setOnScreenMessage( - ilGlobalTemplateInterface::MESSAGE_TYPE_SUCCESS, + GlobalTemplate::MESSAGE_TYPE_SUCCESS, $this->lng->txt('md_vocab_update_successful'), true ); @@ -404,13 +405,13 @@ protected function getTable(): DataTable )->withAsync(true); return $this->ui_factory->table()->data( - $this->lng->txt('md_vocab_table_title'), - $columns, new DataRetrieval( $this->vocab_manager, $this->presentation, $this->ui_factory - ) + ), + $this->lng->txt('md_vocab_table_title'), + $columns, )->withActions($actions)->withRequest($this->http->request()); } diff --git a/components/ILIAS/MetaData/classes/Settings/class.ilMDSettingsControllerGUI.php b/components/ILIAS/MetaData/classes/Settings/class.ilMDSettingsControllerGUI.php index 78b2a53e6130..0d1f5a94e923 100644 --- a/components/ILIAS/MetaData/classes/Settings/class.ilMDSettingsControllerGUI.php +++ b/components/ILIAS/MetaData/classes/Settings/class.ilMDSettingsControllerGUI.php @@ -104,7 +104,7 @@ protected function setTabs(): void $this->tabs_gui->addSubTab( self::OER_SETTINGS_TAB, - $this->lng->txt('settings'), + $this->lng->txt('md_settings'), $this->ctrl->getLinkTargetByClass( ilMDOERSettingsGUI::class, 'showOERSettings' diff --git a/components/ILIAS/MetaData/classes/Setup/DeleteLOMForObjectTypeMigration.php b/components/ILIAS/MetaData/classes/Setup/DeleteLOMForObjectTypeMigration.php new file mode 100755 index 000000000000..af7908b86aab --- /dev/null +++ b/components/ILIAS/MetaData/classes/Setup/DeleteLOMForObjectTypeMigration.php @@ -0,0 +1,136 @@ +db = $environment->getResource(Environment::RESOURCE_DATABASE); + + $io = $environment->getResource(Environment::RESOURCE_ADMIN_INTERACTION); + if ($io instanceof IOWrapper) { + $this->io = $io; + } + } + + final public function step(Environment $environment): void + { + $selects = []; + foreach (LOMDictionaryInitiator::TABLES as $table) { + $selects[] = 'SELECT rbac_id, obj_id FROM ' . $this->db->quoteIdentifier($table) . + ' WHERE obj_type = ' . $this->quotedObjectType(); + } + if (empty($selects)) { + return; + } + $query = 'SELECT rbac_id, obj_id FROM (' . implode(' UNION ', $selects) . + ') AS t ORDER BY t.rbac_id, t.obj_id ASC LIMIT 1'; + $res = $this->db->query($query); + if (!($row = $this->db->fetchAssoc($res))) { + $this->logInfo('No LOM found for ' . $this->objectType()); + return; + } + $rbac_id = $row['rbac_id']; + $obj_id = $row['obj_id']; + + $this->logInfo('Deleting LOM for rbac_id = ' . $rbac_id . ' and obj_id = ' . $obj_id); + + foreach (LOMDictionaryInitiator::TABLES as $table) { + $query = 'DELETE FROM ' . $this->db->quoteIdentifier($table) . + ' WHERE obj_type = ' . $this->quotedObjectType() . + ' AND rbac_id = ' . $this->db->quote($rbac_id, \ilDBConstants::T_INTEGER) . + ' AND obj_id = ' . $this->db->quote($obj_id, \ilDBConstants::T_INTEGER); + $this->db->manipulate($query); + } + $this->logSuccess('Done!'); + } + + final public function getRemainingAmountOfSteps(): int + { + $selects = []; + foreach (LOMDictionaryInitiator::TABLES as $table) { + $selects[] = 'SELECT rbac_id, obj_id FROM ' . $this->db->quoteIdentifier($table) . + ' WHERE obj_type = ' . $this->quotedObjectType(); + } + if (empty($selects)) { + return 0; + } + $query = 'SELECT COUNT(*) AS count FROM (' . implode(' UNION ', $selects) . ') AS t'; + $res = $this->db->query($query); + if ($row = $this->db->fetchAssoc($res)) { + return (int) $row['count']; + } + return 0; + } + + private function quotedObjectType(): string + { + return $this->db->quote($this->objectType(), \ilDBConstants::T_TEXT); + } + + protected function logInfo(string $str): void + { + if (!isset($this->io) || !$this->io->isVerbose()) { + return; + } + $this->io->inform($str); + } + + protected function logSuccess(string $str): void + { + if (!isset($this->io) || !$this->io->isVerbose()) { + return; + } + $this->io->success($str); + } +} diff --git a/components/ILIAS/MetaData/classes/Setup/InitLOMForObjectTypeMigration.php b/components/ILIAS/MetaData/classes/Setup/InitLOMForObjectTypeMigration.php index bad8ab85e9ac..72fca70481e9 100755 --- a/components/ILIAS/MetaData/classes/Setup/InitLOMForObjectTypeMigration.php +++ b/components/ILIAS/MetaData/classes/Setup/InitLOMForObjectTypeMigration.php @@ -78,7 +78,7 @@ final public function step(Environment $environment): void object_description.description AS long_description FROM object_data LEFT JOIN object_description ON object_data.obj_id = object_description.obj_id - LEFT JOIN il_meta_general ON il_meta_general.rbac_id = object_data.obj_id + LEFT JOIN il_meta_general ON il_meta_general.rbac_id = object_data.obj_id AND il_meta_general.obj_type = object_data.type WHERE object_data.type = " . $this->quotedObjectType() . " " . "AND il_meta_general.rbac_id IS NULL AND NOT COALESCE(object_data.title, '') = '' @@ -147,7 +147,7 @@ final public function getRemainingAmountOfSteps(): int { $res = $this->db->query( $query = "SELECT count(*) AS count FROM object_data LEFT JOIN il_meta_general - ON il_meta_general.rbac_id = object_data.obj_id + ON il_meta_general.rbac_id = object_data.obj_id AND il_meta_general.obj_type = object_data.type WHERE object_data.type = " . $this->quotedObjectType() . " " . "AND il_meta_general.rbac_id IS NULL AND NOT COALESCE(object_data.title, '') = ''" diff --git a/components/ILIAS/MetaData/classes/Vocabularies/Controlled/Repository.php b/components/ILIAS/MetaData/classes/Vocabularies/Controlled/Repository.php index 6e637dbae079..3c1351d253cb 100644 --- a/components/ILIAS/MetaData/classes/Vocabularies/Controlled/Repository.php +++ b/components/ILIAS/MetaData/classes/Vocabularies/Controlled/Repository.php @@ -181,10 +181,17 @@ public function getLabelsForValues( $this->db->in('value', ...$values) ); + $labels_by_value = []; foreach ($result as $row) { + $labels_by_value[(string) $row['value']] = (string) $row['label']; + } + foreach ($values as $value) { + if (!array_key_exists($value, $labels_by_value)) { + continue; + } yield new LabelledValue( - (string) $row['value'], - (string) $row['label'] + $value, + $labels_by_value[$value] ); } } @@ -276,7 +283,7 @@ protected function readVocabularyValues(string $vocab_id): \Generator { $result = $this->db->query( 'SELECT value FROM il_md_vocab_contr_vals WHERE vocab_id = ' . - $this->db->quoteAsInteger($vocab_id) + $this->db->quoteAsInteger($vocab_id) . " ORDER BY COALESCE(NULLIF(label,''), value) ASC" ); foreach ($result as $row) { yield (string) $row['value']; diff --git a/components/ILIAS/MetaData/classes/Vocabularies/Factory/FactoryInterface.php b/components/ILIAS/MetaData/classes/Vocabularies/Factory/FactoryInterface.php index 2d0ebb3fa0f1..e6c245bc9a88 100755 --- a/components/ILIAS/MetaData/classes/Vocabularies/Factory/FactoryInterface.php +++ b/components/ILIAS/MetaData/classes/Vocabularies/Factory/FactoryInterface.php @@ -25,8 +25,8 @@ interface FactoryInterface { - public const STANDARD_SOURCE = 'LOMv1.0'; - public const COPYRIGHT_SOURCE = 'ILIAS'; + public const string STANDARD_SOURCE = 'LOMv1.0'; + public const string COPYRIGHT_SOURCE = 'ILIAS'; public function standard(SlotIdentifier $slot, string ...$values): BuilderInterface; diff --git a/components/ILIAS/MetaData/classes/XML/Writer/SimpleDC/SimpleDC.php b/components/ILIAS/MetaData/classes/XML/Writer/SimpleDC/SimpleDC.php index 9d9cfaeae332..37f6756ed667 100644 --- a/components/ILIAS/MetaData/classes/XML/Writer/SimpleDC/SimpleDC.php +++ b/components/ILIAS/MetaData/classes/XML/Writer/SimpleDC/SimpleDC.php @@ -125,6 +125,14 @@ protected function addCreatorsPublishersAndContributorsToXML(\SimpleXMLElement $ ->withNextStep('entity') ->get(); + $path_from_entity_to_role = $this->path_factory + ->custom() + ->withRelative(true) + ->withNextStepToSuperElement() + ->withNextStep('role') + ->withNextStep('value') + ->get(); + $creators = []; $creator_navigator = $this->navigator_factory->navigator($creator_path, $set->getRoot()); foreach ($creator_navigator->elementsAtFinalStep() as $creator) { @@ -156,10 +164,17 @@ protected function addCreatorsPublishersAndContributorsToXML(\SimpleXMLElement $ continue; } + $role = $this->navigator_factory + ->navigator($path_from_entity_to_role, $any_contributor) + ->lastElementAtFinalStep()?->getData()?->value() ?? ''; + $contributor = $any_contributor->getData()->value(); + if ($role !== '') { + $contributor .= ' (' . $role . ')'; + } $this->addNamespacedChildToXML( $xml, 'contributor', - $any_contributor->getData()->value() + $contributor ); } } @@ -436,6 +451,8 @@ protected function addNamespacedChildToXML( if ($value === '') { return null; } - return $xml->addChild($name, $value, "http://purl.org/dc/elements/1.1/"); + $child_xml = $xml->addChild($name, null, "http://purl.org/dc/elements/1.1/"); + $child_xml[0] = $value; + return $child_xml; } } diff --git a/components/ILIAS/MetaData/classes/XML/Writer/Standard/Standard.php b/components/ILIAS/MetaData/classes/XML/Writer/Standard/Standard.php index e3bb43cca2f8..e071d7cd720d 100644 --- a/components/ILIAS/MetaData/classes/XML/Writer/Standard/Standard.php +++ b/components/ILIAS/MetaData/classes/XML/Writer/Standard/Standard.php @@ -115,7 +115,8 @@ protected function addSubElementsToXML( continue; } - $child_xml = $xml->addChild($sub_name, $sub_value); + $child_xml = $xml->addChild($sub_name); + $child_xml[0] = $sub_value; $this->addSubElementsToXML($sub_element, $sub_tag, $child_xml, $depth + 1); } } @@ -141,10 +142,8 @@ protected function addLangStringToXML( $this->getTagForElement($string_element) ); } - $string_xml = $xml->addChild( - 'string', - $string_value - ); + $string_xml = $xml->addChild('string'); + $xml->string = $string_value; if (is_null($language_element)) { return; diff --git a/components/ILIAS/MetaData/classes/class.ilCronOerHarvester.php b/components/ILIAS/MetaData/classes/class.ilCronOerHarvester.php index 35c09d6069cd..d3a247bce8b6 100755 --- a/components/ILIAS/MetaData/classes/class.ilCronOerHarvester.php +++ b/components/ILIAS/MetaData/classes/class.ilCronOerHarvester.php @@ -94,6 +94,10 @@ public function hasCustomSettings(): bool public function addCustomSettingsToForm(ilPropertyFormGUI $a_form): void { // target selection + $header = new ilFormSectionHeaderGUI(); + $header->setTitle($this->lng->txt('meta_oer_categories')); + $a_form->addItem($header); + $target = new ilRepositorySelector2InputGUI( $this->lng->txt('meta_oer_target'), 'target', @@ -136,6 +140,10 @@ public function addCustomSettingsToForm(ilPropertyFormGUI $a_form): void $a_form->addItem($ex_target); // copyright selection + $header = new ilFormSectionHeaderGUI(); + $header->setTitle($this->lng->txt('meta_oer_harvested_licences')); + $a_form->addItem($header); + $checkbox_group = new ilCheckboxGroupInputGUI( $this->lng->txt('meta_oer_copyright_selection'), 'copyright' @@ -157,6 +165,10 @@ public function addCustomSettingsToForm(ilPropertyFormGUI $a_form): void $a_form->addItem($checkbox_group); // object type selection + $header = new ilFormSectionHeaderGUI(); + $header->setTitle($this->lng->txt('meta_oer_harvested_types')); + $a_form->addItem($header); + $checkbox_group = new ilCheckboxGroupInputGUI( $this->lng->txt('meta_oer_object_type_selection'), 'object_type' diff --git a/components/ILIAS/MetaData/classes/class.ilMD.php b/components/ILIAS/MetaData/classes/class.ilMD.php deleted file mode 100755 index 215578d8fe74..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMD.php +++ /dev/null @@ -1,339 +0,0 @@ -getRBACId(), $this->getObjId())) { - $gen = new ilMDGeneral(); - $gen->setMetaId($id); - return $gen; - } - return null; - } - - public function addGeneral(): ?ilMDGeneral - { - $gen = new ilMDGeneral($this->getRBACId(), $this->getObjId(), $this->getObjType()); - - return $gen; - } - - public function getLifecycle(): ?ilMDLifecycle - { - if ($id = ilMDLifecycle::_getId($this->getRBACId(), $this->getObjId())) { - $lif = new ilMDLifecycle(); - $lif->setMetaId($id); - - return $lif; - } - return null; - } - - public function addLifecycle(): ilMDLifecycle - { - $lif = new ilMDLifecycle($this->getRBACId(), $this->getObjId(), $this->getObjType()); - - return $lif; - } - - public function getMetaMetadata(): ?ilMDMetaMetadata - { - if ($id = ilMDMetaMetadata::_getId($this->getRBACId(), $this->getObjId())) { - $met = new ilMDMetaMetadata(); - $met->setMetaId($id); - - return $met; - } - return null; - } - - public function addMetaMetadata(): ilMDMetaMetadata - { - $met = new ilMDMetaMetadata($this->getRBACId(), $this->getObjId(), $this->getObjType()); - - return $met; - } - - public function getTechnical(): ?ilMDTechnical - { - if ($id = ilMDTechnical::_getId($this->getRBACId(), $this->getObjId())) { - $tec = new ilMDTechnical(); - $tec->setMetaId($id); - - return $tec; - } - return null; - } - - public function addTechnical(): ilMDTechnical - { - $tec = new ilMDTechnical($this->getRBACId(), $this->getObjId(), $this->getObjType()); - - return $tec; - } - - public function getEducational(): ?ilMDEducational - { - if ($id = ilMDEducational::_getId($this->getRBACId(), $this->getObjId())) { - $edu = new ilMDEducational(); - $edu->setMetaId($id); - - return $edu; - } - return null; - } - - public function addEducational(): ilMDEducational - { - $edu = new ilMDEducational($this->getRBACId(), $this->getObjId(), $this->getObjType()); - - return $edu; - } - - public function getRights(): ?ilMDRights - { - if ($id = ilMDRights::_getId($this->getRBACId(), $this->getObjId())) { - $rig = new ilMDRights(); - $rig->setMetaId($id); - - return $rig; - } - return null; - } - - public function addRights(): ilMDRights - { - $rig = new ilMDRights($this->getRBACId(), $this->getObjId(), $this->getObjType()); - - return $rig; - } - - /** - * @return int[] - */ - public function getRelationIds(): array - { - return ilMDRelation::_getIds($this->getRBACId(), $this->getObjId()); - } - - public function getRelation(int $a_relation_id): ?ilMDRelation - { - if (!$a_relation_id) { - return null; - } - - $rel = new ilMDRelation(); - $rel->setMetaId($a_relation_id); - - return $rel; - } - - public function addRelation(): ilMDRelation - { - $rel = new ilMDRelation($this->getRBACId(), $this->getObjId(), $this->getObjType()); - - return $rel; - } - - /** - * @return int[] - */ - public function getAnnotationIds(): array - { - return ilMDAnnotation::_getIds($this->getRBACId(), $this->getObjId()); - } - - public function getAnnotation(int $a_annotation_id): ?ilMDAnnotation - { - if (!$a_annotation_id) { - return null; - } - - $ann = new ilMDAnnotation(); - $ann->setMetaId($a_annotation_id); - - return $ann; - } - - public function addAnnotation(): ilMDAnnotation - { - $ann = new ilMDAnnotation($this->getRBACId(), $this->getObjId(), $this->getObjType()); - - return $ann; - } - - /** - * @return int[] - */ - public function getClassificationIds(): array - { - return ilMDClassification::_getIds($this->getRBACId(), $this->getObjId()); - } - - public function getClassification(int $a_classification_id): ?ilMDClassification - { - if (!$a_classification_id) { - return null; - } - - $cla = new ilMDClassification(); - $cla->setMetaId($a_classification_id); - - return $cla; - } - - public function addClassification(): ilMDClassification - { - $cla = new ilMDClassification($this->getRBACId(), $this->getObjId(), $this->getObjType()); - - return $cla; - } - - public function toXML(ilXmlWriter $writer): void - { - $writer->xmlStartTag('MetaData'); - - // General - if (is_object($gen = $this->getGeneral())) { - $gen->setExportMode($this->getExportMode()); - $gen->toXML($writer); - } else { - // Defaults - - $gen = new ilMDGeneral( - $this->getRBACId(), - $this->getObjId(), - $this->getObjType() - ); // added type, alex, 31 Oct 2007 - $gen->setExportMode($this->getExportMode()); - $gen->toXML($writer); - } - - // Lifecycle - if (is_object($lif = $this->getLifecycle())) { - $lif->toXML($writer); - } - - // Meta-Metadata - if (is_object($met = $this->getMetaMetadata())) { - $met->toXML($writer); - } - - // Technical - if (is_object($tec = $this->getTechnical())) { - $tec->toXML($writer); - } - - // Educational - if (is_object($edu = $this->getEducational())) { - $edu->toXML($writer); - } - - // Rights - if (is_object($rig = $this->getRights())) { - $rig->toXML($writer); - } - - // Relations - foreach ($this->getRelationIds() as $id) { - $rel = $this->getRelation($id); - $rel->toXML($writer); - } - - // Annotations - foreach ($this->getAnnotationIds() as $id) { - $ann = $this->getAnnotation($id); - $ann->toXML($writer); - } - - // Classification - foreach ($this->getClassificationIds() as $id) { - $cla = $this->getClassification($id); - $cla->toXML($writer); - } - - $writer->xmlEndTag('MetaData'); - } - - public function cloneMD(int $a_rbac_id, int $a_obj_id, string $a_obj_type): ilMD - { - // this method makes an xml export of the original meta data set - // and uses this xml string to clone the object - $md2xml = new ilMD2XML($this->getRBACId(), $this->getObjId(), $this->getObjType()); - $md2xml->startExport(); - - $mdxmlcopier = new ilMDXMLCopier($md2xml->getXML(), $a_rbac_id, $a_obj_id, $a_obj_type); - $mdxmlcopier->startParsing(); - - return $mdxmlcopier->getMDObject(); - } - - public function deleteAll(): bool - { - $tables = [ - 'il_meta_annotation', - 'il_meta_classification', - 'il_meta_contribute', - 'il_meta_description', - 'il_meta_educational', - 'il_meta_entity', - 'il_meta_format', - 'il_meta_general', - 'il_meta_identifier', - 'il_meta_identifier_', - 'il_meta_keyword', - 'il_meta_language', - 'il_meta_lifecycle', - 'il_meta_location', - 'il_meta_meta_data', - 'il_meta_relation', - 'il_meta_requirement', - 'il_meta_rights', - 'il_meta_taxon', - 'il_meta_taxon_path', - 'il_meta_technical', - 'il_meta_tar' - ]; - - foreach ($tables as $table) { - $query = "DELETE FROM " . $table . " " . - "WHERE rbac_id = " . $this->db->quote($this->getRBACId(), ilDBConstants::T_INTEGER) . " " . - "AND obj_id = " . $this->db->quote($this->getObjId(), ilDBConstants::T_INTEGER); - - $this->db->query($query); - } - - return true; - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMD2XML.php b/components/ILIAS/MetaData/classes/class.ilMD2XML.php deleted file mode 100755 index 869118252a14..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMD2XML.php +++ /dev/null @@ -1,64 +0,0 @@ -md_obj = new ilMD($a_rbac_id, $a_obj_id, $a_type); - parent::__construct(); - } - - public function setExportMode(bool $a_export_mode = true): void - { - $this->export_mode = $a_export_mode; - } - - public function getExportMode(): bool - { - return $this->export_mode; - } - - public function startExport(): void - { - // Starts the xml export and calls all element classes - $this->md_obj->setExportMode($this->getExportMode()); - $this->md_obj->toXML($this); - } - - public function getXML(): string - { - return $this->xmlDumpMem(false); - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDAnnotation.php b/components/ILIAS/MetaData/classes/class.ilMDAnnotation.php deleted file mode 100755 index 359a3cc1f27d..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDAnnotation.php +++ /dev/null @@ -1,189 +0,0 @@ -entity = $a_entity; - } - - public function getEntity(): string - { - return $this->entity; - } - - public function setDate(string $a_date): void - { - $this->date = $a_date; - } - - public function getDate(): string - { - return $this->date; - } - - public function setDescription(string $a_desc): void - { - $this->description = $a_desc; - } - - public function getDescription(): string - { - return $this->description; - } - - public function setDescriptionLanguage(ilMDLanguageItem $lng_obj): void - { - $this->description_language = $lng_obj; - } - - public function getDescriptionLanguage(): ilMDLanguageItem - { - return $this->description_language; - } - - public function getDescriptionLanguageCode(): string - { - if (is_object($this->description_language)) { - return $this->description_language->getLanguageCode(); - } - return ''; - } - - public function save(): int - { - $fields = $this->__getFields(); - $fields['meta_annotation_id'] = array('integer', $next_id = $this->db->nextId('il_meta_annotation')); - - if ($this->db->insert('il_meta_annotation', $fields)) { - $this->setMetaId($next_id); - return $this->getMetaId(); - } - return 0; - } - - public function update(): bool - { - return $this->getMetaId() && $this->db->update( - 'il_meta_annotation', - $this->__getFields(), - array("meta_annotation_id" => array('integer', $this->getMetaId())) - ); - } - - public function delete(): bool - { - if ($this->getMetaId()) { - $query = "DELETE FROM il_meta_annotation " . - "WHERE meta_annotation_id = " . $this->db->quote($this->getMetaId(), 'integer'); - $res = $this->db->manipulate($query); - - return true; - } - return false; - } - - /** - * @return array> - */ - public function __getFields(): array - { - return array( - 'rbac_id' => array('integer', $this->getRBACId()), - 'obj_id' => array('integer', $this->getObjId()), - 'obj_type' => array('text', $this->getObjType()), - 'entity' => array('clob', $this->getEntity()), - 'a_date' => array('clob', $this->getDate()), - 'description' => array('clob', $this->getDescription()), - 'description_language' => array('text', $this->getDescriptionLanguageCode()) - ); - } - - public function read(): bool - { - if ($this->getMetaId()) { - $query = "SELECT * FROM il_meta_annotation " . - "WHERE meta_annotation_id = " . $this->db->quote($this->getMetaId(), 'integer'); - - $res = $this->db->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $this->setRBACId((int) $row->rbac_id); - $this->setObjId((int) $row->obj_id); - $this->setObjType($row->obj_type); - $this->setEntity($row->entity ?? ''); - $this->setDate($row->a_date ?? ''); - $this->setDescription($row->description ?? ''); - $this->description_language = new ilMDLanguageItem($row->description_language ?? ''); - } - } - return true; - } - - public function toXML(ilXmlWriter $writer): void - { - $writer->xmlStartTag('Annotation'); - $writer->xmlElement('Entity', null, $this->getEntity()); - $writer->xmlElement('Date', null, $this->getDate()); - $writer->xmlElement( - 'Description', - array( - 'Language' => $this->getDescriptionLanguageCode() ?: 'en' - ), - $this->getDescription() - ); - $writer->xmlEndTag('Annotation'); - } - - // STATIC - - /** - * @return int[] - */ - public static function _getIds(int $a_rbac_id, int $a_obj_id): array - { - global $DIC; - - $ilDB = $DIC['ilDB']; - - $query = "SELECT meta_annotation_id FROM il_meta_annotation " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . " " . - "AND obj_id = " . $ilDB->quote($a_obj_id, 'integer'); - - $res = $ilDB->query($query); - $ids = []; - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $ids[] = (int) $row->meta_annotation_id; - } - return $ids; - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDBase.php b/components/ILIAS/MetaData/classes/class.ilMDBase.php deleted file mode 100755 index ac5bdb6fdf65..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDBase.php +++ /dev/null @@ -1,194 +0,0 @@ -__ - */ - private bool $export_mode = false; - - protected ilLogger $log; - protected ilDBInterface $db; - - /** - * constructor - * - * @param int $a_rbac_id object id (NOT ref_id!) of rbac object (e.g for page objects - * the obj_id of the content object; for media objects this - * is set to 0, because their object id are not assigned to ref ids) - * @param int $a_obj_id object id (e.g for structure objects the obj_id of the structure object) - * @param string $a_type type of the object (e.g st,pg,crs ...) - */ - public function __construct( - int $a_rbac_id = 0, - int $a_obj_id = 0, - string $a_type = '' - ) { - global $DIC; - - $this->db = $DIC->database(); - - if ($a_obj_id === 0) { - $a_obj_id = $a_rbac_id; - } - - $this->log = ilLoggerFactory::getLogger("meta"); - - $this->rbac_id = $a_rbac_id; - $this->obj_id = $a_obj_id; - $this->obj_type = $a_type; - } - - abstract public function read(): bool; - - // SET/GET - public function setRBACId(int $a_id): void - { - $this->rbac_id = $a_id; - } - - public function getRBACId(): int - { - return $this->rbac_id; - } - - public function setObjId(int $a_id): void - { - $this->obj_id = $a_id; - } - - public function getObjId(): int - { - return $this->obj_id; - } - - public function setObjType(string $a_type): void - { - $this->obj_type = $a_type; - } - - public function getObjType(): string - { - return $this->obj_type; - } - - public function setMetaId(int $a_meta_id, bool $a_read_data = true): void - { - $this->meta_id = $a_meta_id; - - if ($a_read_data) { - $this->read(); - } - } - - public function getMetaId(): ?int - { - return $this->meta_id; - } - - public function setParentType(string $a_parent_type): void - { - $this->parent_type = $a_parent_type; - } - - public function getParentType(): string - { - return $this->parent_type; - } - - public function setParentId(int $a_id): void - { - $this->parent_id = $a_id; - } - - public function getParentId(): int - { - return $this->parent_id; - } - - public function setExportMode(bool $a_export_mode = true): void - { - $this->export_mode = $a_export_mode; - } - - public function getExportMode(): bool - { - return $this->export_mode; - } - - public function validate(): bool - { - return false; - } - - public function update(): bool - { - return false; - } - - public function save(): int - { - return 0; - } - - public function delete(): bool - { - return false; - } - - public function toXML(ilXmlWriter $writer): void - { - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDClassification.php b/components/ILIAS/MetaData/classes/class.ilMDClassification.php deleted file mode 100755 index e63d493bf57b..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDClassification.php +++ /dev/null @@ -1,306 +0,0 @@ - 'Discipline', - 'idea' => 'Idea', - 'prerequisite' => 'Prerequisite', - 'educational objective' => 'EducationalObjective', - 'accessibility restrictions' => 'AccessibilityRestrictions', - 'educational level' => 'EducationalLevel', - 'skill level' => 'SkillLevel', - 'security level' => 'SecurityLevel', - 'competency' => 'Competency' - ]; - - private string $purpose = ''; - private string $description = ''; - private ?ilMDLanguageItem $description_language = null; - - // METHODS OF CLIENT OBJECTS (TaxonPath, Keyword) - - /** - * @return int[] - */ - public function getTaxonPathIds(): array - { - return ilMDTaxonPath::_getIds($this->getRBACId(), $this->getObjId(), $this->getMetaId(), 'meta_classification'); - } - - public function getTaxonPath(int $a_taxon_path_id): ?ilMDTaxonPath - { - if (!$a_taxon_path_id) { - return null; - } - $tax = new ilMDTaxonPath(); - $tax->setMetaId($a_taxon_path_id); - - return $tax; - } - - public function addTaxonPath(): ilMDTaxonPath - { - $tax = new ilMDTaxonPath($this->getRBACId(), $this->getObjId(), $this->getObjType()); - $tax->setParentId($this->getMetaId()); - $tax->setParentType('meta_classification'); - - return $tax; - } - - public function getKeywordIds(): ?array - { - return ilMDKeyword::_getIds($this->getRBACId(), $this->getObjId(), $this->getMetaId(), 'meta_classification'); - } - - public function getKeyword(int $a_keyword_id): ?ilMDKeyword - { - if (!$a_keyword_id) { - return null; - } - $key = new ilMDKeyword(); - $key->setMetaId($a_keyword_id); - - return $key; - } - - public function addKeyword(): ilMDKeyword - { - $key = new ilMDKeyword($this->getRBACId(), $this->getObjId(), $this->getObjType()); - $key->setParentId($this->getMetaId()); - $key->setParentType('meta_classification'); - - return $key; - } - - // SET/GET - public function setPurpose(string $a_purpose): bool - { - switch ($a_purpose) { - case 'Discipline': - case 'Idea': - case 'Prerequisite': - case 'EducationalObjective': - case 'AccessibilityRestrictions': - case 'EducationalLevel': - case 'SkillLevel': - case 'SecurityLevel': - case 'Competency': - $this->purpose = $a_purpose; - return true; - - default: - return false; - } - } - - public function getPurpose(): string - { - return $this->purpose; - } - - public function setDescription(string $a_description): void - { - $this->description = $a_description; - } - - public function getDescription(): string - { - return $this->description; - } - - public function setDescriptionLanguage(ilMDLanguageItem $lng_obj): void - { - $this->description_language = $lng_obj; - } - - public function getDescriptionLanguage(): ?ilMDLanguageItem - { - return is_object($this->description_language) ? $this->description_language : null; - } - - public function getDescriptionLanguageCode(): string - { - return is_object($this->description_language) ? $this->description_language->getLanguageCode() : ''; - } - - public function save(): int - { - $fields = $this->__getFields(); - $fields['meta_classification_id'] = array('integer', $next_id = $this->db->nextId('il_meta_classification')); - - if ($this->db->insert('il_meta_classification', $fields)) { - $this->setMetaId($next_id); - return $this->getMetaId(); - } - return 0; - } - - public function update(): bool - { - return $this->getMetaId() && $this->db->update( - 'il_meta_classification', - $this->__getFields(), - ["meta_classification_id" => ['integer', $this->getMetaId()]] - ); - } - - public function delete(): bool - { - if ($this->getMetaId()) { - $query = "DELETE FROM il_meta_classification " . - "WHERE meta_classification_id = " . $this->db->quote($this->getMetaId(), 'integer'); - $res = $this->db->manipulate($query); - - foreach ($this->getTaxonPathIds() as $id) { - $tax = $this->getTaxonPath($id); - $tax->delete(); - } - foreach ($this->getKeywordIds() as $id) { - $key = $this->getKeyword($id); - $key->delete(); - } - - return true; - } - return false; - } - - /** - * @return array> - */ - public function __getFields(): array - { - /** - * Compatibility fix for legacy MD classes for new db tables - */ - $purpose = (string) array_search( - $this->getPurpose(), - self::PURPOSE_TRANSLATION - ); - - return array( - 'rbac_id' => array('integer', $this->getRBACId()), - 'obj_id' => array('integer', $this->getObjId()), - 'obj_type' => array('text', $this->getObjType()), - 'purpose' => array('text', $purpose), - 'description' => array('text', $this->getDescription()), - 'description_language' => array('text', $this->getDescriptionLanguageCode()) - ); - } - - public function read(): bool - { - if ($this->getMetaId()) { - $query = "SELECT * FROM il_meta_classification " . - "WHERE meta_classification_id = " . $this->db->quote($this->getMetaId(), 'integer'); - - $res = $this->db->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - /** - * Compatibility fix for legacy MD classes for new db tables - */ - if (key_exists($row->purpose ?? '', self::PURPOSE_TRANSLATION)) { - $row->purpose = self::PURPOSE_TRANSLATION[$row->purpose ?? '']; - } - - $this->setRBACId((int) $row->rbac_id); - $this->setObjId((int) $row->obj_id); - $this->setObjType($row->obj_type); - $this->setPurpose($row->purpose ?? ''); - $this->setDescription($row->description ?? ''); - $this->description_language = new ilMDLanguageItem($row->description_language ?? ''); - } - } - return true; - } - - public function toXML(ilXmlWriter $writer): void - { - $writer->xmlStartTag('Classification', array( - 'Purpose' => $this->getPurpose() ?: 'Idea' - )); - - // Taxon Path - $taxs = $this->getTaxonPathIds(); - foreach ($taxs as $id) { - $tax = $this->getTaxonPath($id); - $tax->toXML($writer); - } - if (!count($taxs)) { - $tax = new ilMDTaxonPath($this->getRBACId(), $this->getObjId()); - $tax->toXML($writer); - } - - // Description - $writer->xmlElement( - 'Description', - array( - 'Language' => $this->getDescriptionLanguageCode() ?: 'en' - ), - $this->getDescription() - ); - - // Keyword - $keys = $this->getKeywordIds(); - foreach ($keys as $id) { - $key = $this->getKeyword($id); - $key->toXML($writer); - } - if (!count($keys)) { - $key = new ilMDKeyword($this->getRBACId(), $this->getObjId()); - $key->toXML($writer); - } - $writer->xmlEndTag('Classification'); - } - - // STATIC - - /** - * @return int[] - */ - public static function _getIds(int $a_rbac_id, int $a_obj_id): array - { - global $DIC; - - $ilDB = $DIC['ilDB']; - - $query = "SELECT meta_classification_id FROM il_meta_classification " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . " " . - "AND obj_id = " . $ilDB->quote($a_obj_id, 'integer'); - - $res = $ilDB->query($query); - $ids = []; - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $ids[] = (int) $row->meta_classification_id; - } - return $ids; - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDContribute.php b/components/ILIAS/MetaData/classes/class.ilMDContribute.php deleted file mode 100755 index b2440f6b6c2d..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDContribute.php +++ /dev/null @@ -1,285 +0,0 @@ - - * @package ilias-core - * @version $Id$ - * @deprecated will be removed with ILIAS 11, please use the new API (see {@see ../docs/api.md}) - */ -class ilMDContribute extends ilMDBase -{ - /** - * Compatibility fix for legacy MD classes for new db tables - */ - private const ROLE_TRANSLATION = [ - 'author' => 'Author', - 'publisher' => 'Publisher', - 'unknown' => 'Unknown', - 'initiator' => 'Initiator', - 'terminator' => 'Terminator', - 'editor' => 'Editor', - 'graphical designer' => 'GraphicalDesigner', - 'technical implementer' => 'TechnicalImplementer', - 'content provider' => 'ContentProvider', - 'technical validator' => 'TechnicalValidator', - 'educational validator' => 'EducationalValidator', - 'script writer' => 'ScriptWriter', - 'instructional designer' => 'InstructionalDesigner', - 'subject matter expert' => 'SubjectMatterExpert', - 'creator' => 'Creator', - 'validator' => 'Validator' - ]; - - // Subelements - private string $date = ''; - private string $role = ''; - - /** - * @return int[] - */ - public function getEntityIds(): array - { - return ilMDEntity::_getIds($this->getRBACId(), $this->getObjId(), (int) $this->getMetaId(), 'meta_contribute'); - } - - public function getEntity(int $a_entity_id): ?ilMDEntity - { - if (!$a_entity_id) { - return null; - } - $ent = new ilMDEntity(); - $ent->setMetaId($a_entity_id); - - return $ent; - } - - public function addEntity(): ilMDEntity - { - $ent = new ilMDEntity($this->getRBACId(), $this->getObjId(), $this->getObjType()); - $ent->setParentId($this->getMetaId()); - $ent->setParentType('meta_contribute'); - - return $ent; - } - - // SET/GET - public function setRole(string $a_role): bool - { - switch ($a_role) { - case 'Author': - case 'Publisher': - case 'Unknown': - case 'Initiator': - case 'Terminator': - case 'Editor': - case 'GraphicalDesigner': - case 'TechnicalImplementer': - case 'ContentProvider': - case 'TechnicalValidator': - case 'EducationalValidator': - case 'ScriptWriter': - case 'InstructionalDesigner': - case 'SubjectMatterExpert': - case 'Creator': - case 'Validator': - case 'PointOfContact': - $this->role = $a_role; - return true; - - default: - return false; - } - } - - public function getRole(): string - { - return $this->role; - } - - public function setDate(string $a_date): void - { - $this->date = $a_date; - } - - public function getDate(): string - { - return $this->date; - } - - public function save(): int - { - $fields = $this->__getFields(); - $fields['meta_contribute_id'] = array('integer', $next_id = $this->db->nextId('il_meta_contribute')); - - if ($this->db->insert('il_meta_contribute', $fields)) { - $this->setMetaId($next_id); - return $this->getMetaId(); - } - return 0; - } - - public function update(): bool - { - return $this->getMetaId() && $this->db->update( - 'il_meta_contribute', - $this->__getFields(), - array("meta_contribute_id" => array('integer', $this->getMetaId())) - ); - } - - public function delete(): bool - { - if ($this->getMetaId()) { - $query = "DELETE FROM il_meta_contribute " . - "WHERE meta_contribute_id = " . $this->db->quote($this->getMetaId(), 'integer'); - $res = $this->db->manipulate($query); - - foreach ($this->getEntityIds() as $id) { - $ent = $this->getEntity($id); - $ent->delete(); - } - return true; - } - return false; - } - - /** - * @return array> - */ - public function __getFields(): array - { - /** - * Compatibility fix for legacy MD classes for new db tables - */ - $role = (string) array_search( - $this->getRole(), - self::ROLE_TRANSLATION - ); - - return array( - 'rbac_id' => array('integer', $this->getRBACId()), - 'obj_id' => array('integer', $this->getObjId()), - 'obj_type' => array('text', $this->getObjType()), - 'parent_type' => array('text', $this->getParentType()), - 'parent_id' => array('integer', $this->getParentId()), - 'role' => array('text', $role), - 'c_date' => array('text', $this->getDate()) - ); - } - - public function read(): bool - { - if ($this->getMetaId()) { - $query = "SELECT * FROM il_meta_contribute " . - "WHERE meta_contribute_id = " . $this->db->quote($this->getMetaId(), 'integer'); - - $res = $this->db->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - /** - * Compatibility fix for legacy MD classes for new db tables - */ - if (key_exists($row->role ?? '', self::ROLE_TRANSLATION)) { - $row->role = self::ROLE_TRANSLATION[$row->role ?? '']; - } - - $this->setRBACId((int) $row->rbac_id); - $this->setObjId((int) $row->obj_id); - $this->setObjType($row->obj_type ?? ''); - $this->setParentId((int) $row->parent_id); - $this->setParentType($row->parent_type ?? ''); - $this->setRole($row->role ?? ''); - $this->setDate($row->c_date ?? ''); - } - } - return true; - } - - public function toXML(ilXmlWriter $writer): void - { - $writer->xmlStartTag('Contribute', array( - 'Role' => $this->getRole() ?: 'Author' - )); - - // Entities - $entities = $this->getEntityIds(); - foreach ($entities as $id) { - $ent = $this->getEntity($id); - $ent->toXML($writer); - } - if (!count($entities)) { - $ent = new ilMDEntity($this->getRBACId(), $this->getObjId()); - $ent->toXML($writer); - } - - $writer->xmlElement('Date', null, $this->getDate()); - $writer->xmlEndTag('Contribute'); - } - - // STATIC - - /** - * @return int[] - */ - public static function _getIds(int $a_rbac_id, int $a_obj_id, int $a_parent_id, string $a_parent_type): array - { - global $DIC; - - $ilDB = $DIC['ilDB']; - - $query = "SELECT meta_contribute_id FROM il_meta_contribute " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . " " . - "AND obj_id = " . $ilDB->quote($a_obj_id, 'integer') . " " . - "AND parent_id = " . $ilDB->quote($a_parent_id, 'integer') . " " . - "AND parent_type = " . $ilDB->quote($a_parent_type, 'text'); - - $res = $ilDB->query($query); - $ids = []; - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $ids[] = (int) $row->meta_contribute_id; - } - return $ids; - } - - /** - * @return string[] - */ - public static function _lookupAuthors(int $a_rbac_id, int $a_obj_id, string $a_obj_type): array - { - global $DIC; - - $ilDB = $DIC['ilDB']; - - // Ask for 'author' later to use indexes - $authors = []; - $query = "SELECT entity,ent.parent_type,role FROM il_meta_entity ent " . - "JOIN il_meta_contribute con ON ent.parent_id = con.meta_contribute_id " . - "WHERE ent.rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . " " . - "AND ent.obj_id = " . $ilDB->quote($a_obj_id, 'integer') . " "; - $res = $ilDB->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - if ($row->role === 'Author' && $row->parent_type === 'meta_contribute') { - $authors[] = trim($row->entity); - } - } - return $authors; - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDCreator.php b/components/ILIAS/MetaData/classes/class.ilMDCreator.php deleted file mode 100755 index 11be25c48e8d..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDCreator.php +++ /dev/null @@ -1,227 +0,0 @@ - - * @version $Id$ - * @deprecated will be removed with ILIAS 11, please use the new API (see {@see ../docs/api.md}) - */ -class ilMDCreator -{ - protected ilMD $md_obj; - - /* - * rbac_id ref_id of rbac object (e.g for page objects the obj_id of the content object) - */ - private int $rbac_id; - - /* - * obj_id (e.g for structure objects the obj_id of the structure object) - */ - private int $obj_id; - - /* - * type of the object (e.g st,pg,crs ...) - */ - public string $obj_type; - - private string $structure = ''; - private string $catalog = ''; - private string $entry = ''; - private string $keyword = ''; - private string $title = ''; - private string $description = ''; - private string $title_lng = ''; - - public function __construct(int $a_rbac_id, int $a_obj_id, string $a_type) - { - if ($a_obj_id === 0) { - $a_obj_id = $a_rbac_id; - } - - $this->rbac_id = $a_rbac_id; - $this->obj_id = $a_obj_id; - $this->obj_type = $a_type; - - $this->md_obj = new ilMD($a_rbac_id, $a_obj_id, $a_type); - } - - // SET/GET - public function setTitle(string $a_title): void - { - $this->title = $a_title; - } - - public function getTitle(): string - { - return $this->title; - } - - public function setDescription(string $a_desc): void - { - $this->description = $a_desc; - } - - public function getDescription(): string - { - return $this->description; - } - - public function setTitleLanguage(string $a_lng): void - { - $this->title_lng = $a_lng; - } - - public function getTitleLanguage(): ilMDLanguageItem - { - return new ilMDLanguageItem($this->title_lng); - } - - public function setDescriptionLanguage(string $a_lng): void - { - $this->title_lng = $a_lng; - } - - public function getDescriptionLanguage(): ilMDLanguageItem - { - return new ilMDLanguageItem($this->title_lng); - } - - public function setLanguage(string $a_lng): void - { - $this->title_lng = $a_lng; - } - - public function getLanguage(): ilMDLanguageItem - { - return new ilMDLanguageItem($this->title_lng); - } - - public function setKeyword(string $a_key): void - { - $this->keyword = $a_key; - } - - public function getKeyword(): string - { - return $this->keyword; - } - - public function getRBACId(): int - { - return $this->rbac_id; - } - - public function getObjId(): int - { - return $this->obj_id; - } - - public function getObjType(): string - { - return $this->obj_type; - } - - public function setKeywordLanguage(string $a_lng): void - { - $this->title_lng = $a_lng; - } - - public function getKeywordLanguage(): ilMDLanguageItem - { - return new ilMDLanguageItem($this->title_lng); - } - - public function setCatalog(string $a_cat): void - { - $this->catalog = $a_cat; - } - - public function getCatalog(): string - { - return $this->catalog ?: 'ILIAS'; - } - - public function setEntry(string $a_entry): void - { - $this->entry = $a_entry; - } - - public function getEntry(): string - { - return $this->entry ?: 'il__' . $this->getObjType() . '_' . $this->getObjId(); - } - - public function setStructure(string $a_structure): void - { - $this->structure = $a_structure; - } - - public function getStructure(): string - { - return $this->structure ?: 'Hierarchical'; - } - - public function create(): void - { - $this->__createGeneral(); - } - - // PROTECTED - public function __createGeneral(): bool - { - $md_gen = $this->md_obj->addGeneral(); - - $md_gen->setStructure($this->getStructure()); - $md_gen->setTitle($this->getTitle()); - $md_gen->setTitleLanguage($this->getTitleLanguage()); - $md_gen->save(); - - $md_ide = $md_gen->addIdentifier(); - $md_ide->setCatalog($this->getCatalog()); - $md_ide->setEntry($this->getEntry()); - $md_ide->save(); - - $md_lng = $md_gen->addLanguage(); - $md_lng->setLanguage($this->getLanguage()); - $md_lng->save(); - - $md_des = $md_gen->addDescription(); - $md_des->setDescription($this->getDescription()); - $md_des->setDescriptionLanguage($this->getDescriptionLanguage()); - $md_des->save(); - - $md_key = $md_gen->addKeyword(); - $md_key->setKeyword($this->getKeyword()); - $md_key->setKeywordLanguage($this->getKeywordLanguage()); - $md_key->save(); - - return true; - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDDescription.php b/components/ILIAS/MetaData/classes/class.ilMDDescription.php deleted file mode 100755 index b22960a16190..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDDescription.php +++ /dev/null @@ -1,163 +0,0 @@ -description = $a_description; - } - - public function getDescription(): string - { - return $this->description; - } - - public function setDescriptionLanguage(ilMDLanguageItem $lng_obj): void - { - $this->description_language = $lng_obj; - } - - public function getDescriptionLanguage(): ?ilMDLanguageItem - { - return is_object($this->description_language) ? $this->description_language : null; - } - - public function getDescriptionLanguageCode(): string - { - return is_object($this->description_language) ? $this->description_language->getLanguageCode() : ''; - } - - public function save(): int - { - $fields = $this->__getFields(); - $fields['meta_description_id'] = array('integer', $next_id = $this->db->nextId('il_meta_description')); - - if ($this->db->insert('il_meta_description', $fields)) { - $this->setMetaId($next_id); - return $this->getMetaId(); - } - return 0; - } - - public function update(): bool - { - return $this->getMetaId() && $this->db->update( - 'il_meta_description', - $this->__getFields(), - array("meta_description_id" => array('integer', $this->getMetaId())) - ); - } - - public function delete(): bool - { - if ($this->getMetaId()) { - $query = "DELETE FROM il_meta_description " . - "WHERE meta_description_id = " . $this->db->quote($this->getMetaId(), 'integer'); - $res = $this->db->manipulate($query); - - return true; - } - return false; - } - - /** - * @return array> - */ - public function __getFields(): array - { - return array( - 'rbac_id' => array('integer', $this->getRBACId()), - 'obj_id' => array('integer', $this->getObjId()), - 'obj_type' => array('text', $this->getObjType()), - 'parent_type' => array('text', $this->getParentType()), - 'parent_id' => array('integer', $this->getParentId()), - 'description' => array('clob', $this->getDescription()), - 'description_language' => array('text', $this->getDescriptionLanguageCode()) - ); - } - - public function read(): bool - { - if ($this->getMetaId()) { - $query = "SELECT * FROM il_meta_description " . - "WHERE meta_description_id = " . $this->db->quote($this->getMetaId(), 'integer'); - - $res = $this->db->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $this->setRBACId((int) $row->rbac_id); - $this->setObjId((int) $row->obj_id); - $this->setObjType($row->obj_type ?? ''); - $this->setParentId((int) $row->parent_id); - $this->setParentType($row->parent_type ?? ''); - $this->setDescription($row->description ?? ''); - $this->setDescriptionLanguage(new ilMDLanguageItem($row->description_language ?? '')); - } - } - return true; - } - - public function toXML(ilXmlWriter $writer): void - { - $writer->xmlElement( - 'Description', - array( - 'Language' => $this->getDescriptionLanguageCode() ?: 'en' - ), - $this->getDescription() - ); - } - - // STATIC - - /** - * @return int[] - */ - public static function _getIds(int $a_rbac_id, int $a_obj_id, int $a_parent_id, string $a_parent_type): array - { - global $DIC; - - $ilDB = $DIC->database(); - - $query = "SELECT meta_description_id FROM il_meta_description " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, ilDBConstants::T_INTEGER) . " " . - "AND obj_id = " . $ilDB->quote($a_obj_id, ilDBConstants::T_INTEGER) . " " . - "AND parent_id = " . $ilDB->quote($a_parent_id, ilDBConstants::T_INTEGER) . " " . - "AND parent_type = " . $ilDB->quote($a_parent_type, ilDBConstants::T_INTEGER) . " " . - "ORDER BY meta_description_id"; - - $res = $ilDB->query($query); - $ids = []; - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $ids[] = (int) $row->meta_description_id; - } - return $ids; - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDEducational.php b/components/ILIAS/MetaData/classes/class.ilMDEducational.php deleted file mode 100755 index 57220e662335..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDEducational.php +++ /dev/null @@ -1,898 +0,0 @@ - 'Active', - 'expositive' => 'Expositive', - 'mixed' => 'Mixed' - ]; - - private const LEARNING_RESOURCE_TYPE_TRANSLATION = [ - 'exercise' => 'Exercise', - 'simulation' => 'Simulation', - 'questionnaire' => 'Questionnaire', - 'diagram' => 'Diagram', - 'figure' => 'Figure', - 'graph' => 'Graph', - 'index' => 'Index', - 'slide' => 'Slide', - 'table' => 'Table', - 'narrative text' => 'NarrativeText', - 'exam' => 'Exam', - 'experiment' => 'Experiment', - 'problem statement' => 'ProblemStatement', - 'self assessment' => 'SelfAssessment', - 'lecture' => 'Lecture' - ]; - - private const INTERACTIVITY_LEVEL_TRANSLATION = [ - 'very low' => 'VeryLow', - 'low' => 'Low', - 'medium' => 'Medium', - 'high' => 'High', - 'very high' => 'VeryHigh' - ]; - - private const SEMANTIC_DENSITY_TRANSLATION = [ - 'very low' => 'VeryLow', - 'low' => 'Low', - 'medium' => 'Medium', - 'high' => 'High', - 'very high' => 'VeryHigh' - ]; - - private const INTENDED_END_USER_ROLE_TRANSLATION = [ - 'teacher' => 'Teacher', - 'author' => 'Author', - 'learner' => 'Learner', - 'manager' => 'Manager' - ]; - - private const CONTEXT_TRANSLATION = [ - 'school' => 'School', - 'higher education' => 'HigherEducation', - 'training' => 'Training', - 'other' => 'Other' - ]; - - private const DIFFICULTY_TRANSLATION = [ - 'very easy' => 'VeryEasy', - 'easy' => 'Easy', - 'medium' => 'Medium', - 'difficult' => 'Difficult', - 'very difficult' => 'VeryDifficult' - ]; - - private string $interactivity_type = ''; - private string $learning_resource_type = ''; - private string $interactivity_level = ''; - private string $semantic_density = ''; - private string $intended_end_user_role = ''; - private string $context = ''; - private string $difficulty = ''; - private string $typical_learning_time = ''; - - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - private int $learning_resource_type_id = 0; - private int $intended_end_user_role_id = 0; - private int $context_id = 0; - - /** - * @return int[] - */ - public function getTypicalAgeRangeIds(): array - { - return ilMDTypicalAgeRange::_getIds( - $this->getRBACId(), - $this->getObjId(), - $this->getMetaId(), - 'meta_educational' - ); - } - - public function getTypicalAgeRange(int $a_typical_age_range_id): ?ilMDTypicalAgeRange - { - if (!$a_typical_age_range_id) { - return null; - } - $typ = new ilMDTypicalAgeRange(); - $typ->setMetaId($a_typical_age_range_id); - - return $typ; - } - - public function addTypicalAgeRange(): ilMDTypicalAgeRange - { - $typ = new ilMDTypicalAgeRange($this->getRBACId(), $this->getObjId(), $this->getObjType()); - $typ->setParentId($this->getMetaId()); - $typ->setParentType('meta_educational'); - - return $typ; - } - - /** - * @return int[] - */ - public function getDescriptionIds(): array - { - return ilMDDescription::_getIds($this->getRBACId(), $this->getObjId(), $this->getMetaId(), 'meta_educational'); - } - - public function getDescription(int $a_description_id): ?ilMDDescription - { - if (!$a_description_id) { - return null; - } - $des = new ilMDDescription(); - $des->setMetaId($a_description_id); - - return $des; - } - - public function addDescription(): ilMDDescription - { - $des = new ilMDDescription($this->getRBACId(), $this->getObjId(), $this->getObjType()); - $des->setParentId($this->getMetaId()); - $des->setParentType('meta_educational'); - - return $des; - } - - /** - * @return int[] - */ - public function getLanguageIds(): array - { - return ilMDLanguage::_getIds($this->getRBACId(), $this->getObjId(), $this->getMetaId(), 'meta_educational'); - } - - public function getLanguage(int $a_language_id): ?ilMDLanguage - { - if (!$a_language_id) { - return null; - } - $lan = new ilMDLanguage(); - $lan->setMetaId($a_language_id); - - return $lan; - } - - public function addLanguage(): ilMDLanguage - { - $lan = new ilMDLanguage($this->getRBACId(), $this->getObjId(), $this->getObjType()); - $lan->setParentId($this->getMetaId()); - $lan->setParentType('meta_educational'); - - return $lan; - } - - // SET/GET - public function setInteractivityType(string $a_iat): bool - { - switch ($a_iat) { - case 'Active': - case 'Expositive': - case 'Mixed': - $this->interactivity_type = $a_iat; - return true; - - default: - return false; - } - } - - public function getInteractivityType(): string - { - return $this->interactivity_type; - } - - public function setLearningResourceType(string $a_lrt): bool - { - switch ($a_lrt) { - case 'Exercise': - case 'Simulation': - case 'Questionnaire': - case 'Diagram': - case 'Figure': - case 'Graph': - case 'Index': - case 'Slide': - case 'Table': - case 'NarrativeText': - case 'Exam': - case 'Experiment': - case 'ProblemStatement': - case 'SelfAssessment': - case 'Lecture': - $this->learning_resource_type = $a_lrt; - return true; - - default: - return false; - } - } - - public function getLearningResourceType(): string - { - return $this->learning_resource_type; - } - - public function setInteractivityLevel(string $a_iat): bool - { - switch ($a_iat) { - case 'VeryLow': - case 'Low': - case 'Medium': - case 'High': - case 'VeryHigh': - $this->interactivity_level = $a_iat; - return true; - - default: - return false; - } - } - - public function getInteractivityLevel(): string - { - return $this->interactivity_level; - } - - public function setSemanticDensity(string $a_sd): bool - { - switch ($a_sd) { - case 'VeryLow': - case 'Low': - case 'Medium': - case 'High': - case 'VeryHigh': - $this->semantic_density = $a_sd; - return true; - - default: - return false; - } - } - - public function getSemanticDensity(): string - { - return $this->semantic_density; - } - - public function setIntendedEndUserRole(string $a_ieur): bool - { - switch ($a_ieur) { - case 'Teacher': - case 'Author': - case 'Learner': - case 'Manager': - $this->intended_end_user_role = $a_ieur; - return true; - - default: - return false; - } - } - - public function getIntendedEndUserRole(): string - { - return $this->intended_end_user_role; - } - - public function setContext(string $a_context): bool - { - switch ($a_context) { - case 'School': - case 'HigherEducation': - case 'Training': - case 'Other': - $this->context = $a_context; - return true; - - default: - return false; - } - } - - public function getContext(): string - { - return $this->context; - } - - public function setDifficulty(string $a_difficulty): bool - { - switch ($a_difficulty) { - case 'VeryEasy': - case 'Easy': - case 'Medium': - case 'Difficult': - case 'VeryDifficult': - $this->difficulty = $a_difficulty; - return true; - - default: - return false; - } - } - - public function getDifficulty(): string - { - return $this->difficulty; - } - - public function setPhysicalTypicalLearningTime( - int $months, - int $days, - int $hours, - int $minutes, - int $seconds - ): bool { - if (!$months && !$days && !$hours && !$minutes && !$seconds) { - $this->setTypicalLearningTime('PT00H00M'); - return true; - } - $tlt = 'P'; - if ($months) { - $tlt .= ($months . 'M'); - } - if ($days) { - $tlt .= ($days . 'D'); - } - if ($hours || $minutes || $seconds) { - $tlt .= 'T'; - } - if ($hours) { - $tlt .= ($hours . 'H'); - } - if ($minutes) { - $tlt .= ($minutes . 'M'); - } - if ($seconds) { - $tlt .= ($seconds . 'S'); - } - $this->setTypicalLearningTime($tlt); - return true; - } - - public function setTypicalLearningTime(string $a_tlt): void - { - $this->typical_learning_time = $a_tlt; - } - - public function getTypicalLearningTime(): string - { - return $this->typical_learning_time; - } - - public function getTypicalLearningTimeSeconds(): int - { - $time_arr = ilMDUtils::_LOMDurationToArray($this->getTypicalLearningTime()); - if ($time_arr === []) { - return 0; - } - return 60 * 60 * 24 * 30 * $time_arr[0] + 60 * 60 * 24 * $time_arr[1] + 60 * 60 * $time_arr[2] + 60 * $time_arr[3] + $time_arr[4]; - } - - public function save(): int - { - $fields = $this->__getFields(); - $fields['meta_educational_id'] = array('integer', $next_id = $this->db->nextId('il_meta_educational')); - - if ($this->db->insert('il_meta_educational', $fields)) { - $this->setMetaId($next_id); - $this->createOrUpdateLearningResourceType(); - $this->createOrUpdateIntendedEndUserRole(); - $this->createOrUpdateContext(); - return $this->getMetaId(); - } - return 0; - } - - public function update(): bool - { - if (!$this->getMetaId()) { - return false; - } - - $this->createOrUpdateLearningResourceType(); - $this->createOrUpdateIntendedEndUserRole(); - $this->createOrUpdateContext(); - - return (bool) $this->db->update( - 'il_meta_educational', - $this->__getFields(), - array("meta_educational_id" => array('integer', $this->getMetaId())) - ); - } - - public function delete(): bool - { - if ($this->getMetaId()) { - $query = "DELETE FROM il_meta_educational " . - "WHERE meta_educational_id = " . $this->db->quote($this->getMetaId(), ilDBConstants::T_INTEGER); - $res = $this->db->manipulate($query); - - $this->deleteAllLearningResourceTypes(); - $this->deleteAllIntendedEndUserRoles(); - $this->deleteAllContexts(); - - foreach ($this->getTypicalAgeRangeIds() as $id) { - $typ = $this->getTypicalAgeRange($id); - $typ->delete(); - } - foreach ($this->getDescriptionIds() as $id) { - $des = $this->getDescription($id); - $des->delete(); - } - foreach ($this->getLanguageIds() as $id) { - $lan = $this->getLanguage($id); - $lan->delete(); - } - - return true; - } - return false; - } - - /** - * @return array> - */ - public function __getFields(): array - { - /** - * Compatibility fix for legacy MD classes for new db tables - */ - $interactivity_type = (string) array_search( - $this->getInteractivityType(), - self::INTERACTIVITY_TYPE_TRANSLATION - ); - $interactivity_level = (string) array_search( - $this->getInteractivityLevel(), - self::INTERACTIVITY_LEVEL_TRANSLATION - ); - $semantic_density = (string) array_search( - $this->getSemanticDensity(), - self::SEMANTIC_DENSITY_TRANSLATION - ); - $difficulty = (string) array_search( - $this->getDifficulty(), - self::DIFFICULTY_TRANSLATION - ); - - return array( - 'rbac_id' => array('integer', $this->getRBACId()), - 'obj_id' => array('integer', $this->getObjId()), - 'obj_type' => array('text', $this->getObjType()), - 'interactivity_type' => array('text', $interactivity_type), - //'learning_resource_type' => array('text', $this->getLearningResourceType()), - 'interactivity_level' => array('text', $interactivity_level), - 'semantic_density' => array('text', $semantic_density), - //'intended_end_user_role' => array('text', $this->getIntendedEndUserRole()), - //'context' => array('text', $this->getContext()), - 'difficulty' => array('text', $difficulty), - 'typical_learning_time' => array('text', $this->getTypicalLearningTime()) - ); - } - - public function read(): bool - { - if ($this->getMetaId()) { - $query = "SELECT * FROM il_meta_educational " . - "WHERE meta_educational_id = " . $this->db->quote($this->getMetaId(), 'integer'); - - $res = $this->db->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - /** - * Compatibility fix for legacy MD classes for new db tables - */ - if (key_exists($row->interactivity_type ?? '', self::INTERACTIVITY_TYPE_TRANSLATION)) { - $row->interactivity_type = self::INTERACTIVITY_TYPE_TRANSLATION[$row->interactivity_type ?? '']; - } - if (key_exists($row->interactivity_level ?? '', self::INTERACTIVITY_LEVEL_TRANSLATION)) { - $row->interactivity_level = self::INTERACTIVITY_LEVEL_TRANSLATION[$row->interactivity_level ?? '']; - } - if (key_exists($row->semantic_density ?? '', self::SEMANTIC_DENSITY_TRANSLATION)) { - $row->semantic_density = self::SEMANTIC_DENSITY_TRANSLATION[$row->semantic_density ?? '']; - } - if (key_exists($row->difficulty ?? '', self::DIFFICULTY_TRANSLATION)) { - $row->difficulty = self::DIFFICULTY_TRANSLATION[$row->difficulty ?? '']; - } - - $this->setRBACId((int) $row->rbac_id); - $this->setObjId((int) $row->obj_id); - $this->setObjType($row->obj_type ?? ''); - $this->setInteractivityType($row->interactivity_type ?? ''); - //$this->setLearningResourceType($row->learning_resource_type ?? ''); - $this->setInteractivityLevel($row->interactivity_level ?? ''); - $this->setSemanticDensity($row->semantic_density ?? ''); - //$this->setIntendedEndUserRole($row->intended_end_user_role ?? ''); - //$this->setContext($row->context ?? ''); - $this->setDifficulty($row->difficulty ?? ''); - $this->setTypicalLearningTime($row->typical_learning_time ?? ''); - } - - $this->readFirstLearningResourceType(); - $this->readFirstIntendedEndUserRole(); - $this->readFirstContext(); - return true; - } - return false; - } - - public function toXML(ilXmlWriter $writer): void - { - $writer->xmlStartTag( - 'Educational', - array( - 'InteractivityType' => $this->getInteractivityType() ?: 'Active', - 'LearningResourceType' => $this->getLearningResourceType() ?: 'Exercise', - 'InteractivityLevel' => $this->getInteractivityLevel() ?: 'Medium', - 'SemanticDensity' => $this->getSemanticDensity() ?: 'Medium', - 'IntendedEndUserRole' => $this->getIntendedEndUserRole() ?: 'Learner', - 'Context' => $this->getContext() ?: 'Other', - 'Difficulty' => $this->getDifficulty() ?: 'Medium' - ) - ); - - // TypicalAgeRange - $typ_ages = $this->getTypicalAgeRangeIds(); - foreach ($typ_ages as $id) { - $key = $this->getTypicalAgeRange($id); - - // extra test due to bug 5316 (may be due to eLaix import) - if (is_object($key)) { - $key->toXML($writer); - } - } - if (!count($typ_ages)) { - $typ = new ilMDTypicalAgeRange($this->getRBACId(), $this->getObjId()); - $typ->toXML($writer); - } - - // TypicalLearningTime - $writer->xmlElement('TypicalLearningTime', null, $this->getTypicalLearningTime()); - - // Description - foreach ($this->getDescriptionIds() as $id) { - $key = $this->getDescription($id); - $key->toXML($writer); - } - // Language - foreach ($this->getLanguageIds() as $id) { - $lang = $this->getLanguage($id); - $lang->toXML($writer); - } - $writer->xmlEndTag('Educational'); - } - - // STATIC - public static function _getId(int $a_rbac_id, int $a_obj_id): int - { - global $DIC; - - $ilDB = $DIC->database(); - - $query = "SELECT meta_educational_id FROM il_meta_educational " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . " " . - "AND obj_id = " . $ilDB->quote($a_obj_id, 'integer'); - - $res = $ilDB->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - return (int) $row->meta_educational_id; - } - return 0; - } - - public static function _getTypicalLearningTimeSeconds(int $a_rbac_id, int $a_obj_id = 0): int - { - global $DIC; - - $ilDB = $DIC->database(); - - $a_obj_id = $a_obj_id ?: $a_rbac_id; - - $query = "SELECT typical_learning_time FROM il_meta_educational " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . " " . - "AND obj_id = " . $ilDB->quote($a_obj_id, 'integer'); - $res = $ilDB->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $time_arr = ilMDUtils::_LOMDurationToArray((string) $row->typical_learning_time); - if (!count($time_arr)) { - return 0; - } - return 60 * 60 * 24 * 30 * $time_arr[0] + - 60 * 60 * 24 * $time_arr[1] + - 60 * 60 * $time_arr[2] + - 60 * $time_arr[3] + - $time_arr[4]; - } - return 0; - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function createOrUpdateLearningResourceType(): void - { - $learning_resource_type = (string) array_search( - $this->getLearningResourceType(), - self::LEARNING_RESOURCE_TYPE_TRANSLATION - ); - - $this->learning_resource_type_id = $this->createOrUpdateInNewTable( - 'il_meta_lr_type', - 'meta_lr_type_id', - $this->getLearningResourceTypeId(), - 'learning_resource_type', - $learning_resource_type - ); - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function createOrUpdateIntendedEndUserRole(): void - { - $intended_end_user_role = (string) array_search( - $this->getIntendedEndUserRole(), - self::INTENDED_END_USER_ROLE_TRANSLATION - ); - - $this->intended_end_user_role_id = $this->createOrUpdateInNewTable( - 'il_meta_end_usr_role', - 'meta_end_usr_role_id', - $this->getIntendedEndUserRoleId(), - 'intended_end_user_role', - $intended_end_user_role - ); - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function createOrUpdateContext(): void - { - $context = (string) array_search( - $this->getContext(), - self::CONTEXT_TRANSLATION - ); - - $this->context_id = $this->createOrUpdateInNewTable( - 'il_meta_context', - 'meta_context_id', - $this->getContextId(), - 'context', - $context - ); - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function createOrUpdateInNewTable( - string $table, - string $id_field, - int $id, - string $data_field, - string $data_value - ): int { - if ($data_value === '') { - return 0; - } - - if (!$id) { - $this->db->insert( - $table, - [ - $id_field => ['integer', $next_id = $this->db->nextId($table)], - 'rbac_id' => ['integer', $this->getRBACId()], - 'obj_id' => ['integer', $this->getObjId()], - 'obj_type' => ['text', $this->getObjType()], - 'parent_type' => ['text', 'meta_educational'], - 'parent_id' => ['integer', $this->getMetaId()], - $data_field => ['text', $data_value] - ] - ); - return $next_id; - } - - $this->db->update( - $table, - [$data_field => ['text', $data_value]], - [$id_field => ['integer', $id]] - ); - return $id; - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function readFirstLearningResourceType(): void - { - $query = "SELECT * FROM il_meta_lr_type WHERE meta_lr_type_id = " . - $this->db->quote($this->getLearningResourceTypeId(), 'integer'); - - $res = $this->db->query($query); - if ($row = $this->db->fetchAssoc($res)) { - if (key_exists($row['learning_resource_type'], self::LEARNING_RESOURCE_TYPE_TRANSLATION)) { - $row['learning_resource_type'] = self::LEARNING_RESOURCE_TYPE_TRANSLATION[$row['learning_resource_type']]; - } - $this->setLearningResourceType((string) $row['learning_resource_type']); - } - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function readFirstIntendedEndUserRole(): void - { - $query = "SELECT * FROM il_meta_end_usr_role WHERE meta_end_usr_role_id = " . - $this->db->quote($this->getIntendedEndUserRoleId(), 'integer'); - - $res = $this->db->query($query); - if ($row = $this->db->fetchAssoc($res)) { - if (key_exists($row['intended_end_user_role'], self::INTENDED_END_USER_ROLE_TRANSLATION)) { - $row['intended_end_user_role'] = self::INTENDED_END_USER_ROLE_TRANSLATION[$row['intended_end_user_role']]; - } - $this->setIntendedEndUserRole((string) $row['intended_end_user_role']); - } - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function readFirstContext(): void - { - $query = "SELECT * FROM il_meta_context WHERE meta_context_id = " . - $this->db->quote($this->getContextId(), 'integer'); - - $res = $this->db->query($query); - if ($row = $this->db->fetchAssoc($res)) { - if (key_exists($row['context'], self::CONTEXT_TRANSLATION)) { - $row['context'] = self::CONTEXT_TRANSLATION[$row['context']]; - } - $this->setContext((string) $row['context']); - } - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function deleteAllLearningResourceTypes(): void - { - $query = "DELETE FROM il_meta_lr_type WHERE parent_type = 'meta_educational' - AND parent_id = " . $this->db->quote($this->getMetaId(), 'integer'); - $res = $this->db->manipulate($query); - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function deleteAllIntendedEndUserRoles(): void - { - $query = "DELETE FROM il_meta_end_usr_role WHERE parent_type = 'meta_educational' - AND parent_id = " . $this->db->quote($this->getMetaId(), 'integer'); - $res = $this->db->manipulate($query); - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function deleteAllContexts(): void - { - $query = "DELETE FROM il_meta_context WHERE parent_type = 'meta_educational' - AND parent_id = " . $this->db->quote($this->getMetaId(), 'integer'); - $res = $this->db->manipulate($query); - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function getLearningResourceTypeId(): int - { - return $this->learning_resource_type_id; - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function getIntendedEndUserRoleId(): int - { - return $this->intended_end_user_role_id; - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function getContextId(): int - { - return $this->context_id; - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function readLearningResourceTypeId(int $parent_id): void - { - $query = "SELECT meta_lr_type_id FROM il_meta_lr_type WHERE parent_type = 'meta_educational' - AND parent_id = " . $this->db->quote($parent_id, 'integer') . - " ORDER BY meta_lr_type_id"; - - $res = $this->db->query($query); - if ($row = $this->db->fetchAssoc($res)) { - $this->learning_resource_type_id = (int) $row['meta_lr_type_id']; - } - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function readIntendedEndUserRoleId(int $parent_id): void - { - $query = "SELECT meta_end_usr_role_id FROM il_meta_end_usr_role WHERE parent_type = 'meta_educational' - AND parent_id = " . $this->db->quote($parent_id, 'integer') . - " ORDER BY meta_end_usr_role_id"; - - $res = $this->db->query($query); - if ($row = $this->db->fetchAssoc($res)) { - $this->intended_end_user_role_id = (int) $row['meta_end_usr_role_id']; - } - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function readContextId(int $parent_id): void - { - $query = "SELECT meta_context_id FROM il_meta_context WHERE parent_type = 'meta_educational' - AND parent_id = " . $this->db->quote($parent_id, 'integer') . - " ORDER BY meta_context_id"; - - $res = $this->db->query($query); - if ($row = $this->db->fetchAssoc($res)) { - $this->context_id = (int) $row['meta_context_id']; - } - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - public function setMetaId(int $a_meta_id, bool $a_read_data = true): void - { - $this->readLearningResourceTypeId($a_meta_id); - $this->readIntendedEndUserRoleId($a_meta_id); - $this->readContextId($a_meta_id); - parent::setMetaId($a_meta_id, $a_read_data); - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDEntity.php b/components/ILIAS/MetaData/classes/class.ilMDEntity.php deleted file mode 100755 index aedb0f35e3c5..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDEntity.php +++ /dev/null @@ -1,142 +0,0 @@ - - * @package ilias-core - * @version $Id$ - * @deprecated will be removed with ILIAS 11, please use the new API (see {@see ../docs/api.md}) - */ -class ilMDEntity extends ilMDBase -{ - private string $entity = ''; - - // SET/GET - public function setEntity(string $a_entity): void - { - $this->entity = $a_entity; - } - - public function getEntity(): string - { - return $this->entity; - } - - public function save(): int - { - $fields = $this->__getFields(); - $fields['meta_entity_id'] = array('integer', $next_id = $this->db->nextId('il_meta_entity')); - - if ($this->db->insert('il_meta_entity', $fields)) { - $this->setMetaId($next_id); - return $this->getMetaId(); - } - return 0; - } - - public function update(): bool - { - return $this->getMetaId() && $this->db->update( - 'il_meta_entity', - $this->__getFields(), - array("meta_entity_id" => array('integer', $this->getMetaId())) - ); - } - - public function delete(): bool - { - if ($this->getMetaId()) { - $query = "DELETE FROM il_meta_entity " . - "WHERE meta_entity_id = " . $this->db->quote($this->getMetaId(), 'integer'); - $res = $this->db->manipulate($query); - - $this->db->query($query); - - return true; - } - return false; - } - - /** - * @return array> - */ - public function __getFields(): array - { - return array( - 'rbac_id' => array('integer', $this->getRBACId()), - 'obj_id' => array('integer', $this->getObjId()), - 'obj_type' => array('text', $this->getObjType()), - 'parent_type' => array('text', $this->getParentType()), - 'parent_id' => array('integer', $this->getParentId()), - 'entity' => array('text', $this->getEntity()) - ); - } - - public function read(): bool - { - if ($this->getMetaId()) { - $query = "SELECT * FROM il_meta_entity " . - "WHERE meta_entity_id = " . $this->db->quote($this->getMetaId(), 'integer'); - - $res = $this->db->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $this->setRBACId((int) $row->rbac_id); - $this->setObjId((int) $row->obj_id); - $this->setObjType($row->obj_type ?? ''); - $this->setParentId((int) $row->parent_id); - $this->setParentType($row->parent_type ?? ''); - $this->setEntity($row->entity ?? ''); - } - } - return true; - } - - public function toXML(ilXmlWriter $writer): void - { - $writer->xmlElement('Entity', null, $this->getEntity()); - } - - // STATIC - - /** - * @return int[] - */ - public static function _getIds(int $a_rbac_id, int $a_obj_id, int $a_parent_id, string $a_parent_type): array - { - global $DIC; - - $ilDB = $DIC->database(); - - $query = "SELECT meta_entity_id FROM il_meta_entity " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . " " . - "AND obj_id = " . $ilDB->quote($a_obj_id, 'integer') . " " . - "AND parent_id = " . $ilDB->quote($a_parent_id, 'integer') . " " . - "AND parent_type = " . $ilDB->quote($a_parent_type, 'text') . " " . - "ORDER BY meta_entity_id "; - - $res = $ilDB->query($query); - $ids = []; - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $ids[] = (int) $row->meta_entity_id; - } - return $ids; - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDFormat.php b/components/ILIAS/MetaData/classes/class.ilMDFormat.php deleted file mode 100755 index d4dbbe318872..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDFormat.php +++ /dev/null @@ -1,135 +0,0 @@ - - * @package ilias-core - * @version $Id$ - * @deprecated will be removed with ILIAS 11, please use the new API (see {@see ../docs/api.md}) - */ -class ilMDFormat extends ilMDBase -{ - private string $format = ''; - - // SET/_GET - public function setFormat(string $a_format): void - { - $this->format = $a_format; - } - - public function getFormat(): string - { - return $this->format; - } - - public function save(): int - { - $fields = $this->__getFields(); - $fields['meta_format_id'] = array('integer', $next_id = $this->db->nextId('il_meta_format')); - - if ($this->db->insert('il_meta_format', $fields)) { - $this->setMetaId($next_id); - return $this->getMetaId(); - } - return 0; - } - - public function update(): bool - { - return $this->getMetaId() && $this->db->update( - 'il_meta_format', - $this->__getFields(), - array("meta_format_id" => array('integer', $this->getMetaId())) - ); - } - - public function delete(): bool - { - if ($this->getMetaId()) { - $query = "DELETE FROM il_meta_format " . - "WHERE meta_format_id = " . $this->db->quote($this->getMetaId(), 'integer'); - $res = $this->db->manipulate($query); - - return true; - } - return false; - } - - /** - * @return array> - */ - public function __getFields(): array - { - return array( - 'rbac_id' => array('integer', $this->getRBACId()), - 'obj_id' => array('integer', $this->getObjId()), - 'obj_type' => array('text', $this->getObjType()), - 'format' => array('text', $this->getFormat()) - ); - } - - public function read(): bool - { - if ($this->getMetaId()) { - $query = "SELECT * FROM il_meta_format " . - "WHERE meta_format_id = " . $this->db->quote($this->getMetaId(), 'integer'); - - $res = $this->db->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $this->setRBACId((int) $row->rbac_id); - $this->setObjId((int) $row->obj_id); - $this->setObjType($row->obj_type); - $this->setFormat($row->format ?? ''); - } - } - return true; - } - - public function toXML(ilXmlWriter $writer): void - { - if ($this->getFormat()) { - $writer->xmlElement('Format', null, $this->getFormat()); - } - } - - // STATIC - - /** - * @return int[] - */ - public static function _getIds(int $a_rbac_id, int $a_obj_id): array - { - global $DIC; - - $ilDB = $DIC->database(); - - $query = "SELECT meta_format_id FROM il_meta_format " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . " " . - "AND obj_id = " . $ilDB->quote($a_obj_id, 'integer'); - - $res = $ilDB->query($query); - $ids = []; - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $ids[] = (int) $row->meta_format_id; - } - return $ids; - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDGeneral.php b/components/ILIAS/MetaData/classes/class.ilMDGeneral.php deleted file mode 100755 index bb74bfeaec70..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDGeneral.php +++ /dev/null @@ -1,571 +0,0 @@ - - * @package ilias-core - * @version $Id$ - * @deprecated will be removed with ILIAS 11, please use the new API (see {@see ../docs/api.md}) - */ -class ilMDGeneral extends ilMDBase -{ - /** - * Compatibility fix for legacy MD classes for new db tables - */ - private const STRUCTURE_TRANSLATION = [ - 'atomic' => 'Atomic', - 'collection' => 'Collection', - 'networked' => 'Networked', - 'hierarchical' => 'Hierarchical', - 'linear' => 'Linear' - ]; - - protected ?ilMDLanguageItem $coverage_language = null; - - private string $coverage = ''; - private string $structure = ''; - private string $title = ''; - private ?ilMDLanguageItem $title_language = null; - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - private int $coverage_id = 0; - - /** - * @return array - */ - public function getPossibleSubelements(): array - { - $subs['Keyword'] = 'meta_keyword'; - $subs['Language'] = 'meta_language'; - $subs['Identifier'] = 'meta_identifier'; - $subs['Description'] = 'meta_description'; - - return $subs; - } - - // Subelements (Identifier, Language, Description, Keyword) - - /** - * @return int[] - */ - public function getIdentifierIds(): array - { - return ilMDIdentifier::_getIds($this->getRBACId(), $this->getObjId(), (int) $this->getMetaId(), 'meta_general'); - } - - public function getIdentifier(int $a_identifier_id): ?ilMDIdentifier - { - if (!$a_identifier_id) { - return null; - } - $ide = new ilMDIdentifier(); - $ide->setMetaId($a_identifier_id); - - return $ide; - } - - public function addIdentifier(): ilMDIdentifier - { - $ide = new ilMDIdentifier($this->getRBACId(), $this->getObjId(), $this->getObjType()); - $ide->setParentId($this->getMetaId()); - $ide->setParentType('meta_general'); - - return $ide; - } - - /** - * @return int[] - */ - public function getLanguageIds(): array - { - return ilMDLanguage::_getIds($this->getRBACId(), $this->getObjId(), (int) $this->getMetaId(), 'meta_general'); - } - - public function getLanguage(int $a_language_id): ?ilMDLanguage - { - if (!$a_language_id) { - return null; - } - $lan = new ilMDLanguage(); - $lan->setMetaId($a_language_id); - - return $lan; - } - - public function addLanguage(): ilMDLanguage - { - $lan = new ilMDLanguage($this->getRBACId(), $this->getObjId(), $this->getObjType()); - $lan->setParentId($this->getMetaId()); - $lan->setParentType('meta_general'); - - return $lan; - } - - /** - * @return int[] - */ - public function getDescriptionIds(): array - { - return ilMDDescription::_getIds($this->getRBACId(), $this->getObjId(), (int) $this->getMetaId(), 'meta_general'); - } - - public function getDescription(int $a_description_id): ?ilMDDescription - { - if (!$a_description_id) { - return null; - } - $des = new ilMDDescription(); - $des->setMetaId($a_description_id); - - return $des; - } - - public function addDescription(): ilMDDescription - { - $des = new ilMDDescription($this->getRBACId(), $this->getObjId(), $this->getObjType()); - $des->setParentId($this->getMetaId()); - $des->setParentType('meta_general'); - - return $des; - } - - /** - * @return int[] - */ - public function getKeywordIds(): array - { - return ilMDKeyword::_getIds($this->getRBACId(), $this->getObjId(), (int) $this->getMetaId(), 'meta_general'); - } - - public function getKeyword(int $a_keyword_id): ?ilMDKeyword - { - if (!$a_keyword_id) { - return null; - } - $key = new ilMDKeyword(); - $key->setMetaId($a_keyword_id); - - return $key; - } - - public function addKeyword(): ilMDKeyword - { - $key = new ilMDKeyword($this->getRBACId(), $this->getObjId(), $this->getObjType()); - $key->setParentId($this->getMetaId()); - $key->setParentType('meta_general'); - - return $key; - } - - // SET/GET - public function setStructure(string $a_structure): bool - { - switch ($a_structure) { - case 'Atomic': - case 'Collection': - case 'Networked': - case 'Hierarchical': - case 'Linear': - $this->structure = $a_structure; - return true; - - default: - return false; - } - } - - public function getStructure(): string - { - return $this->structure; - } - - public function setTitle(string $a_title): void - { - $this->title = $a_title; - } - - public function getTitle(): string - { - return $this->title; - } - - public function setTitleLanguage(ilMDLanguageItem $lng_obj): void - { - $this->title_language = $lng_obj; - } - - public function getTitleLanguage(): ?ilMDLanguageItem - { - return is_object($this->title_language) ? $this->title_language : null; - } - - public function getTitleLanguageCode(): string - { - return is_object($this->title_language) ? $this->title_language->getLanguageCode() : ''; - } - - public function setCoverage(string $a_coverage): void - { - $this->coverage = $a_coverage; - } - - public function getCoverage(): string - { - return $this->coverage; - } - - public function setCoverageLanguage(ilMDLanguageItem $lng_obj): void - { - $this->coverage_language = $lng_obj; - } - - public function getCoverageLanguage(): ?ilMDLanguageItem - { - return is_object($this->coverage_language) ? $this->coverage_language : null; - } - - public function getCoverageLanguageCode(): string - { - return is_object($this->coverage_language) ? $this->coverage_language->getLanguageCode() : ''; - } - - public function save(): int - { - $fields = $this->__getFields(); - $fields['meta_general_id'] = array('integer', $next_id = $this->db->nextId('il_meta_general')); - - $this->log->debug("Insert General " . print_r($fields, true)); - $this->log->logStack(ilLogLevel::DEBUG); - //ilUtil::printBacktrace(10); - - if ($this->db->insert('il_meta_general', $fields)) { - $this->setMetaId($next_id); - $this->createOrUpdateCoverage(); - return $this->getMetaId(); - } - return 0; - } - - public function update(): bool - { - if (!$this->getMetaId()) { - return false; - } - - $this->createOrUpdateCoverage(); - - return (bool) $this->db->update( - 'il_meta_general', - $this->__getFields(), - array("meta_general_id" => array('integer', $this->getMetaId())) - ); - } - - public function delete(): bool - { - if (!$this->getMetaId()) { - return false; - } - // Identifier - foreach ($this->getIdentifierIds() as $id) { - $ide = $this->getIdentifier($id); - $ide->delete(); - } - - // Language - foreach ($this->getLanguageIds() as $id) { - $lan = $this->getLanguage($id); - $lan->delete(); - } - - // Description - foreach ($this->getDescriptionIds() as $id) { - $des = $this->getDescription($id); - $des->delete(); - } - - // Keyword - foreach ($this->getKeywordIds() as $id) { - $key = $this->getKeyword($id); - $key->delete(); - } - - if ($this->getMetaId()) { - $query = "DELETE FROM il_meta_general " . - "WHERE meta_general_id = " . $this->db->quote($this->getMetaId(), 'integer'); - $res = $this->db->manipulate($query); - - $this->deleteAllCoverages(); - return true; - } - - return false; - } - - /** - * @return array> - */ - public function __getFields(): array - { - /** - * Compatibility fix for legacy MD classes for new db tables - */ - $structure = (string) array_search( - $this->getStructure(), - self::STRUCTURE_TRANSLATION - ); - - return array( - 'rbac_id' => array('integer', $this->getRBACId()), - 'obj_id' => array('integer', $this->getObjId()), - 'obj_type' => array('text', $this->getObjType()), - 'general_structure' => array('text', $structure), - 'title' => array('text', $this->getTitle()), - 'title_language' => array('text', $this->getTitleLanguageCode()), - //'coverage' => array('text', $this->getCoverage()), - //'coverage_language' => array('text', $this->getCoverageLanguageCode()) - ); - } - - public function read(): bool - { - if ($this->getMetaId()) { - $query = "SELECT * FROM il_meta_general " . - "WHERE meta_general_id = " . $this->db->quote($this->getMetaId(), 'integer'); - - $res = $this->db->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - /** - * Compatibility fix for legacy MD classes for new db tables - */ - if (key_exists($row->general_structure ?? '', self::STRUCTURE_TRANSLATION)) { - $row->general_structure = self::STRUCTURE_TRANSLATION[$row->general_structure ?? '']; - } - - $this->setRBACId((int) $row->rbac_id); - $this->setObjId((int) $row->obj_id); - $this->setObjType((string) $row->obj_type); - $this->setStructure((string) $row->general_structure); - $this->setTitle((string) $row->title); - $this->setTitleLanguage(new ilMDLanguageItem($row->title_language ?? '')); - //$this->setCoverage((string) $row->coverage); - //$this->setCoverageLanguage(new ilMDLanguageItem($row->coverage_language ?? '')); - } - - $this->readFirstCoverage(); - } - return true; - } - - public function toXML(ilXmlWriter $writer): void - { - $writer->xmlStartTag('General', array( - 'Structure' => $this->getStructure() ?: 'Atomic' - )); - - // Identifier - $first = true; - $identifiers = $this->getIdentifierIds(); - foreach ($identifiers as $id) { - $ide = $this->getIdentifier($id); - $ide->setExportMode($this->getExportMode()); - $ide->toXML($writer); - $first = false; - } - if (!count($identifiers)) { - $ide = new ilMDIdentifier( - $this->getRBACId(), - $this->getObjId(), - $this->getObjType() - ); // added type, alex, 31 Oct 2007 - $ide->setExportMode(true); - $ide->toXML($writer); - } - - // Title - $writer->xmlElement( - 'Title', - array( - 'Language' => $this->getTitleLanguageCode() ?: 'en' - ), - $this->getTitle() - ); - - // Language - $languages = $this->getLanguageIds(); - foreach ($languages as $id) { - $lan = $this->getLanguage($id); - $lan->toXML($writer); - } - - // Description - $descriptions = $this->getDescriptionIds(); - foreach ($descriptions as $id) { - $des = $this->getDescription($id); - $des->toXML($writer); - } - if (!count($descriptions)) { - // Default - - $des = new ilMDDescription($this->getRBACId(), $this->getObjId()); - $des->toXML($writer); - } - - // Keyword - $keywords = $this->getKeywordIds(); - foreach ($keywords as $id) { - $key = $this->getKeyword($id); - $key->toXML($writer); - } - if (!count($keywords)) { - // Default - - $key = new ilMDKeyword($this->getRBACId(), $this->getObjId()); - $key->toXML($writer); - } - - // Copverage - if ($this->getCoverage() !== '') { - $writer->xmlElement( - 'Coverage', - array( - 'Language' => $this->getCoverageLanguageCode() ?: 'en' - ), - $this->getCoverage() - ); - } - $writer->xmlEndTag('General'); - } - - // STATIC - public static function _getId(int $a_rbac_id, int $a_obj_id): int - { - global $DIC; - - $ilDB = $DIC->database(); - - $query = "SELECT meta_general_id FROM il_meta_general " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . " " . - "AND obj_id = " . $ilDB->quote($a_obj_id, 'integer'); - - $res = $ilDB->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - return (int) $row->meta_general_id; - } - return 0; - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function createOrUpdateCoverage(): void - { - if ($this->getCoverage() === '' && $this->getCoverageLanguageCode() === '') { - return; - } - - if (!$this->getCoverageId()) { - $this->db->insert( - 'il_meta_coverage', - [ - 'meta_coverage_id' => ['integer', $next_id = $this->db->nextId('il_meta_coverage')], - 'rbac_id' => ['integer', $this->getRBACId()], - 'obj_id' => ['integer', $this->getObjId()], - 'obj_type' => ['text', $this->getObjType()], - 'parent_type' => ['text', 'meta_general'], - 'parent_id' => ['integer', $this->getMetaId()], - 'coverage' => ['text', $this->getCoverage()], - 'coverage_language' => ['text', $this->getCoverageLanguageCode()] - ] - ); - $this->coverage_id = $next_id; - return; - } - - $this->db->update( - 'il_meta_coverage', - [ - 'coverage' => ['text', $this->getCoverage()], - 'coverage_language' => ['text', $this->getCoverageLanguageCode()] - ], - ['meta_coverage_id' => ['integer', $this->getCoverageId()]] - ); - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function deleteAllCoverages(): void - { - $query = "DELETE FROM il_meta_coverage WHERE parent_type = 'meta_general' - AND parent_id = " . $this->db->quote($this->getMetaId(), 'integer'); - $res = $this->db->manipulate($query); - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function readFirstCoverage(): void - { - $query = "SELECT * FROM il_meta_coverage WHERE meta_coverage_id = " . - $this->db->quote($this->getCoverageId(), 'integer'); - - $res = $this->db->query($query); - if ($row = $this->db->fetchAssoc($res)) { - $this->setCoverage((string) $row['coverage']); - $this->setCoverageLanguage(new ilMDLanguageItem((string) $row['coverage_language'])); - } - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function getCoverageId(): int - { - return $this->coverage_id; - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function readCoverageId(int $parent_id): void - { - $query = "SELECT meta_coverage_id FROM il_meta_coverage WHERE parent_type = 'meta_general' - AND parent_id = " . $this->db->quote($parent_id, 'integer') . - " ORDER BY meta_coverage_id"; - - $res = $this->db->query($query); - if ($row = $this->db->fetchAssoc($res)) { - $this->coverage_id = (int) $row['meta_coverage_id']; - } - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - public function setMetaId(int $a_meta_id, bool $a_read_data = true): void - { - $this->readCoverageId($a_meta_id); - parent::setMetaId($a_meta_id, $a_read_data); - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDIdentifier.php b/components/ILIAS/MetaData/classes/class.ilMDIdentifier.php deleted file mode 100755 index 8f3ed533c6ec..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDIdentifier.php +++ /dev/null @@ -1,269 +0,0 @@ -catalog = $a_catalog; - } - - public function getCatalog(): string - { - return $this->catalog; - } - - public function setEntry(string $a_entry): void - { - $this->entry = $a_entry; - } - - public function getEntry(): string - { - return $this->entry; - } - - public function save(): int - { - $fields = $this->__getFields(); - $fields['meta_identifier_id'] = array('integer', $next_id = $this->db->nextId('il_meta_identifier')); - - if ($this->db->insert('il_meta_identifier', $fields)) { - $this->setMetaId($next_id); - return $this->getMetaId(); - } - return 0; - } - - public function update(): bool - { - return $this->getMetaId() && $this->db->update( - 'il_meta_identifier', - $this->__getFields(), - array("meta_identifier_id" => array('integer', $this->getMetaId())) - ); - } - - public function delete(): bool - { - if ($this->getMetaId()) { - $query = "DELETE FROM il_meta_identifier " . - "WHERE meta_identifier_id = " . $this->db->quote($this->getMetaId(), 'integer'); - $res = $this->db->manipulate($query); - return true; - } - return false; - } - - /** - * @return array> - */ - public function __getFields(): array - { - return array( - 'rbac_id' => array('integer', $this->getRBACId()), - 'obj_id' => array('integer', $this->getObjId()), - 'obj_type' => array('text', $this->getObjType()), - 'parent_type' => array('text', $this->getParentType()), - 'parent_id' => array('integer', $this->getParentId()), - 'catalog' => array('text', $this->getCatalog()), - 'entry' => array('text', $this->getEntry()) - ); - } - - public function read(): bool - { - if ($this->getMetaId()) { - $query = "SELECT * FROM il_meta_identifier " . - "WHERE meta_identifier_id = " . $this->db->quote($this->getMetaId(), 'integer'); - - $res = $this->db->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $this->setRBACId((int) $row->rbac_id); - $this->setObjId((int) $row->obj_id); - $this->setObjType($row->obj_type ?? ''); - $this->setParentId((int) $row->parent_id); - $this->setParentType($row->parent_type); - $this->setCatalog($row->catalog ?? ''); - $this->setEntry($row->entry ?? ''); - } - } - return true; - } - - public function toXML(ilXmlWriter $writer): void - { - $entry_default = ($this->getObjId() === 0) - ? "il_" . IL_INST_ID . "_" . $this->getObjType() . "_" . $this->getRBACId() - : "il_" . IL_INST_ID . "_" . $this->getObjType() . "_" . $this->getObjId(); - - $entry = $this->getEntry() ?: $entry_default; - $catalog = $this->getCatalog(); - - if ($this->getExportMode() && $this->getCatalog() !== "ILIAS_NID") { - $entry = $entry_default; - $catalog = "ILIAS"; - } - - if ($catalog !== '') { - $writer->xmlElement('Identifier', array( - 'Catalog' => $catalog, - 'Entry' => $entry - )); - } else { - $writer->xmlElement('Identifier', array('Entry' => $entry)); - } - } - - // STATIC - - /** - * @return int[] - */ - public static function _getIds(int $a_rbac_id, int $a_obj_id, int $a_parent_id, string $a_parent_type): array - { - global $DIC; - - $ilDB = $DIC->database(); - - $query = "SELECT meta_identifier_id FROM il_meta_identifier " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . " " . - "AND obj_id = " . $ilDB->quote($a_obj_id, 'integer') . " " . - "AND parent_id = " . $ilDB->quote($a_parent_id, 'integer') . " " . - "AND parent_type = " . $ilDB->quote($a_parent_type, 'text'); - - $res = $ilDB->query($query); - $ids = []; - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $ids[] = (int) $row->meta_identifier_id; - } - return $ids; - } - - /** - * @return array> - */ - public static function _getEntriesForObj(int $a_rbac_id, int $a_obj_id, string $a_obj_type): array - { - global $DIC; - - $ilDB = $DIC->database(); - - $query = "SELECT meta_identifier_id, catalog, entry FROM il_meta_identifier " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . " " . - "AND obj_id = " . $ilDB->quote($a_obj_id, 'integer') . " " . - "AND obj_type = " . $ilDB->quote($a_obj_type, 'text'); - - $res = $ilDB->query($query); - $entries = array(); - while ($r = $ilDB->fetchAssoc($res)) { - $entries[$r["meta_identifier_id"]] = - array( - "catalog" => $r["catalog"], - "entry" => $r["entry"] - ); - } - return $entries; - } - - /** - * @return array> - */ - public static function _getEntriesForRbacObj(int $a_rbac_id, string $a_obj_type = ""): array - { - global $DIC; - - $ilDB = $DIC->database(); - - $query = "SELECT meta_identifier_id, catalog, entry, obj_id FROM il_meta_identifier " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer'); - - if ($a_obj_type !== "") { - $query .= - " AND obj_type = " . $ilDB->quote($a_obj_type, 'text'); - } - - $res = $ilDB->query($query); - $entries = array(); - while ($r = $ilDB->fetchAssoc($res)) { - $entries[$r["meta_identifier_id"]] = - array( - "catalog" => $r["catalog"], - "entry" => $r["entry"], - "obj_id" => $r["obj_id"] - ); - } - return $entries; - } - - public static function existsIdInRbacObject( - int $a_rbac_id, - string $a_obj_type, - string $a_catalog, - string $a_entry - ): bool { - global $DIC; - - $ilDB = $DIC->database(); - - $query = "SELECT meta_identifier_id, obj_id FROM il_meta_identifier " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . - " AND obj_type = " . $ilDB->quote($a_obj_type, 'text') . - " AND catalog = " . $ilDB->quote($a_catalog, 'text') . - " AND entry = " . $ilDB->quote($a_entry, 'text'); - $s = $ilDB->query($query); - if ($r = $ilDB->fetchAssoc($s)) { - return true; - } - return false; - } - - /** - * @return array> - */ - public static function readIdData(int $a_rbac_id, string $a_obj_type, string $a_catalog, string $a_entry): array - { - global $DIC; - - $ilDB = $DIC->database(); - - $query = "SELECT * FROM il_meta_identifier " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . - " AND obj_type = " . $ilDB->quote($a_obj_type, 'text') . - " AND catalog = " . $ilDB->quote($a_catalog, 'text') . - " AND entry = " . $ilDB->quote($a_entry, 'text'); - $s = $ilDB->query($query); - $data = array(); - while ($r = $ilDB->fetchAssoc($s)) { - $data[] = $r; - } - return $data; - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDIdentifier_.php b/components/ILIAS/MetaData/classes/class.ilMDIdentifier_.php deleted file mode 100755 index ac0d4a5c79e0..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDIdentifier_.php +++ /dev/null @@ -1,153 +0,0 @@ -catalog = $a_catalog; - } - - public function getCatalog(): string - { - return $this->catalog; - } - - public function setEntry(string $a_entry): void - { - $this->entry = $a_entry; - } - - public function getEntry(): string - { - return $this->entry; - } - - public function save(): int - { - $fields = $this->__getFields(); - $fields['meta_identifier__id'] = array('integer', $next_id = $this->db->nextId('il_meta_identifier_')); - - if ($this->db->insert('il_meta_identifier_', $fields)) { - $this->setMetaId($next_id); - return $this->getMetaId(); - } - return 0; - } - - public function update(): bool - { - return $this->getMetaId() && $this->db->update( - 'il_meta_identifier_', - $this->__getFields(), - array("meta_identifier__id" => array('integer', $this->getMetaId())) - ); - } - - public function delete(): bool - { - if ($this->getMetaId()) { - $query = "DELETE FROM il_meta_identifier_ " . - "WHERE meta_identifier__id = " . $this->db->quote($this->getMetaId(), 'integer'); - $res = $this->db->manipulate($query); - return true; - } - return false; - } - - /** - * @return array> - */ - public function __getFields(): array - { - return array( - 'rbac_id' => array('integer', $this->getRBACId()), - 'obj_id' => array('integer', $this->getObjId()), - 'obj_type' => array('text', $this->getObjType()), - 'parent_type' => array('text', $this->getParentType()), - 'parent_id' => array('integer', $this->getParentId()), - 'catalog' => array('text', $this->getCatalog()), - 'entry' => array('text', $this->getEntry()) - ); - } - - public function read(): bool - { - if ($this->getMetaId()) { - $query = "SELECT * FROM il_meta_identifier_ " . - "WHERE meta_identifier__id = " . $this->db->quote($this->getMetaId(), 'integer'); - - $res = $this->db->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $this->setRBACId((int) $row->rbac_id); - $this->setObjId((int) $row->obj_id); - $this->setObjType($row->obj_type ?? ''); - $this->setParentId((int) $row->parent_id); - $this->setParentType($row->parent_type); - $this->setCatalog($row->catalog ?? ''); - $this->setEntry($row->entry ?? ''); - } - } - return true; - } - - public function toXML(ilXmlWriter $writer): void - { - $writer->xmlElement('Identifier_', array( - 'Catalog' => $this->getCatalog(), - 'Entry' => $this->getEntry() ?: "ID1" - )); - } - - // STATIC - - /** - * @return int[] - */ - public static function _getIds(int $a_rbac_id, int $a_obj_id, int $a_parent_id, string $a_parent_type): array - { - global $DIC; - - $ilDB = $DIC->database(); - - $query = "SELECT meta_identifier__id FROM il_meta_identifier_ " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . " " . - "AND obj_id = " . $ilDB->quote($a_obj_id, 'integer') . " " . - "AND parent_id = " . $ilDB->quote($a_parent_id, 'integer') . " " . - "AND parent_type = " . $ilDB->quote($a_parent_type, 'text'); - - $res = $ilDB->query($query); - $ids = []; - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $ids[] = (int) $row->meta_identifier__id; - } - return $ids; - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDKeyword.php b/components/ILIAS/MetaData/classes/class.ilMDKeyword.php deleted file mode 100755 index 2e4fe94d59b5..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDKeyword.php +++ /dev/null @@ -1,325 +0,0 @@ -keyword = $a_keyword; - } - - public function getKeyword(): string - { - return $this->keyword; - } - - public function setKeywordLanguage(ilMDLanguageItem $lng_obj): void - { - $this->keyword_language = $lng_obj; - } - - public function getKeywordLanguage(): ?ilMDLanguageItem - { - return is_object($this->keyword_language) ? $this->keyword_language : null; - } - - public function getKeywordLanguageCode(): string - { - return is_object($this->keyword_language) ? $this->keyword_language->getLanguageCode() : ''; - } - - public function save(): int - { - $fields = $this->__getFields(); - $fields['meta_keyword_id'] = array('integer', $next_id = $this->db->nextId('il_meta_keyword')); - - if ($this->db->insert('il_meta_keyword', $fields)) { - $this->setMetaId($next_id); - return $this->getMetaId(); - } - return 0; - } - - public function update(): bool - { - return $this->getMetaId() && $this->db->update( - 'il_meta_keyword', - $this->__getFields(), - array("meta_keyword_id" => array('integer', $this->getMetaId())) - ); - } - - public function delete(): bool - { - if ($this->getMetaId()) { - $query = "DELETE FROM il_meta_keyword " . - "WHERE meta_keyword_id = " . $this->db->quote($this->getMetaId(), 'integer'); - $res = $this->db->manipulate($query); - - return true; - } - return false; - } - - /** - * @return array> - */ - public function __getFields(): array - { - return array( - 'rbac_id' => array('integer', $this->getRBACId()), - 'obj_id' => array('integer', $this->getObjId()), - 'obj_type' => array('text', $this->getObjType()), - 'parent_type' => array('text', $this->getParentType()), - 'parent_id' => array('integer', $this->getParentId()), - 'keyword' => array('text', $this->getKeyword()), - 'keyword_language' => array('text', $this->getKeywordLanguageCode()) - ); - } - - public function read(): bool - { - if ($this->getMetaId()) { - $query = "SELECT * FROM il_meta_keyword " . - "WHERE meta_keyword_id = " . $this->db->quote($this->getMetaId(), 'integer'); - - $res = $this->db->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $this->setRBACId((int) $row->rbac_id); - $this->setObjId((int) $row->obj_id); - $this->setObjType($row->obj_type ?? ''); - $this->setParentId((int) $row->parent_id); - $this->setParentType($row->parent_type ?? ''); - $this->setKeyword($row->keyword ?? ''); - $this->setKeywordLanguage(new ilMDLanguageItem($row->keyword_language ?? '')); - } - } - return true; - } - - public function toXML(ilXmlWriter $writer): void - { - $writer->xmlElement( - 'Keyword', - array( - 'Language' => $this->getKeywordLanguageCode() ?: 'en' - ), - $this->getKeyword() - ); - } - - // STATIC - - /** - * @return int[] - */ - public static function _getIds(int $a_rbac_id, int $a_obj_id, int $a_parent_id, string $a_parent_type): array - { - global $DIC; - - $ilDB = $DIC->database(); - - $query = "SELECT meta_keyword_id FROM il_meta_keyword " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . " " . - "AND obj_id = " . $ilDB->quote($a_obj_id, 'integer') . " " . - "AND parent_id = " . $ilDB->quote($a_parent_id, 'integer') . " " . - "AND parent_type = " . $ilDB->quote($a_parent_type, 'text') . " " . - "ORDER BY meta_keyword_id "; - - $res = $ilDB->query($query); - $ids = []; - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $ids[] = (int) $row->meta_keyword_id; - } - return $ids; - } - - /** - * @return array - */ - public static function _getKeywordsByLanguage(int $a_rbac_id, int $a_obj_id, string $a_type): array - { - global $DIC; - - $ilDB = $DIC->database(); - $ilObjDataCache = $DIC['ilObjDataCache']; - - $query = "SELECT keyword,keyword_language " . - "FROM il_meta_keyword " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . " " . - "AND obj_id = " . $ilDB->quote($a_obj_id, 'integer') . " " . - "AND obj_type = " . $ilDB->quote($a_type, 'text') . " "; - $res = $ilDB->query($query); - $keywords = []; - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - if ($row->keyword) { - $keywords[$row->keyword_language][] = $row->keyword; - } - } - return $keywords; - } - - /** - * @return array - */ - public static function _getKeywordsByLanguageAsString(int $a_rbac_id, int $a_obj_id, string $a_type): array - { - $key_string = []; - foreach (self::_getKeywordsByLanguage($a_rbac_id, $a_obj_id, $a_type) as $lng_code => $keywords) { - $key_string[$lng_code] = implode(",", $keywords); - } - return $key_string; - } - - /** - * @return int[] - */ - public static function _searchKeywords(string $a_query, string $a_type, int $a_rbac_id = 0): array - { - global $DIC; - - $ilDB = $DIC->database(); - - $qs = 'AND '; - $counter = 0; - foreach ((array) explode(' ', $a_query) as $part) { - if ($counter++) { - $qs .= 'OR '; - } - $qs .= ($ilDB->like('keyword', 'text', $part) . ' '); - } - - if ($a_rbac_id) { - $query = "SELECT obj_id FROM il_meta_keyword " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . ' ' . - 'AND obj_type = ' . $ilDB->quote($a_type, 'text') . ' ' . - $qs; - } else { - $query = "SELECT obj_id FROM il_meta_keyword " . - 'WHERE obj_type = ' . $ilDB->quote($a_type, 'text') . ' ' . - $qs; - } - - $res = $ilDB->query($query); - $obj_ids = []; - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $obj_ids[] = $row->obj_id; - } - - return $obj_ids; - } - - /** - * @return string[] - */ - public static function _getMatchingKeywords(string $a_query, string $a_type, int $a_rbac_id = 0): array - { - global $DIC; - - $ilDB = $DIC->database(); - - $query = "SELECT DISTINCT keyword FROM il_meta_keyword " . - 'WHERE obj_type = ' . $ilDB->quote($a_type, 'text') . ' ' . - 'AND ' . $ilDB->like('keyword', 'text', '%' . trim($a_query) . '%') . ' '; - - if ($a_rbac_id) { - $query .= "AND rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . ' '; - } - - $res = $ilDB->query($query); - $kws = []; - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $kws[] = $row->keyword; - } - return $kws; - } - - public static function lookupKeywords(int $a_rbac_id, int $a_obj_id, bool $a_return_ids = false): array - { - global $DIC; - - $ilDB = $DIC->database(); - - $query = "SELECT * FROM il_meta_keyword " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . ' ' . - "AND obj_id = " . $ilDB->quote($a_obj_id, 'integer') . ' '; - $res = $ilDB->query($query); - $kws = []; - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - if (!$a_return_ids) { - if (is_string($row->keyword) && $row->keyword !== '') { - $kws[] = $row->keyword; - } - } else { - $kws[] = $row->meta_keyword_id; - } - } - return $kws; - } - - public static function updateKeywords(ilMDGeneral $a_md_section, array $a_keywords): void - { - // trim keywords - $new_keywords = array(); - foreach ($a_keywords as $lang => $keywords) { - foreach ((array) $keywords as $keyword) { - $keyword = trim($keyword); - if ($keyword !== "" && !(isset($new_keywords[$lang]) && in_array($keyword, $new_keywords[$lang], true))) { - $new_keywords[$lang][] = $keyword; - } - } - } - - // update existing author entries (delete if not entered) - foreach ($ids = $a_md_section->getKeywordIds() as $id) { - $md_key = $a_md_section->getKeyword($id); - $lang = $md_key->getKeywordLanguageCode(); - - // entered keyword already exists - if (is_array($new_keywords[$lang] ?? false) && in_array($md_key->getKeyword(), $new_keywords[$lang], true)) { - unset($new_keywords[$lang][array_search($md_key->getKeyword(), $new_keywords[$lang], true)]); - } else { // existing keyword has not been entered again -> delete - $md_key->delete(); - } - } - - // insert entered, but not existing keywords - foreach ($new_keywords as $lang => $key_arr) { - foreach ($key_arr as $keyword) { - if ($keyword !== "") { - $md_key = $a_md_section->addKeyword(); - $md_key->setKeyword(ilUtil::stripSlashes($keyword)); - $md_key->setKeywordLanguage(new ilMDLanguageItem($lang)); - $md_key->save(); - } - } - } - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDLanguage.php b/components/ILIAS/MetaData/classes/class.ilMDLanguage.php deleted file mode 100755 index 79fabe5434a0..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDLanguage.php +++ /dev/null @@ -1,169 +0,0 @@ -database(); - - $lang = ''; - $query = "SELECT language FROM il_meta_language " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . " " . - "AND obj_id = " . $ilDB->quote($a_obj_id, 'integer') . " " . - "AND obj_type = " . $ilDB->quote($a_obj_type, 'text') . " " . - "AND parent_type = 'meta_general' " . - "ORDER BY meta_language_id "; - $ilDB->setLimit(1, 0); - $res = $ilDB->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $lang = $row->language; - } - return $lang; - } - - // SET/GET - public function setLanguage(ilMDLanguageItem $lng_obj): void - { - $this->language = $lng_obj; - } - - public function getLanguage(): ?ilMDLanguageItem - { - return is_object($this->language) ? $this->language : null; - } - - public function getLanguageCode(): string - { - return is_object($this->language) ? $this->language->getLanguageCode() : ''; - } - - public function save(): int - { - $fields = $this->__getFields(); - $fields['meta_language_id'] = array('integer', $next_id = $this->db->nextId('il_meta_language')); - if ($this->db->insert('il_meta_language', $fields)) { - $this->setMetaId($next_id); - return $this->getMetaId(); - } - return 0; - } - - public function update(): bool - { - return $this->getMetaId() && $this->db->update( - 'il_meta_language', - $this->__getFields(), - array("meta_language_id" => array('integer', $this->getMetaId())) - ); - } - - public function delete(): bool - { - if ($this->getMetaId()) { - $query = "DELETE FROM il_meta_language " . - "WHERE meta_language_id = " . $this->db->quote($this->getMetaId(), 'integer'); - $res = $this->db->manipulate($query); - - return true; - } - return false; - } - - /** - * @return array> - */ - public function __getFields(): array - { - return array( - 'rbac_id' => array('integer', $this->getRBACId()), - 'obj_id' => array('integer', $this->getObjId()), - 'obj_type' => array('text', $this->getObjType()), - 'parent_type' => array('text', $this->getParentType()), - 'parent_id' => array('integer', $this->getParentId()), - 'language' => array('text', $this->getLanguageCode()) - ); - } - - public function read(): bool - { - if ($this->getMetaId()) { - $query = "SELECT * FROM il_meta_language " . - "WHERE meta_language_id = " . $this->db->quote($this->getMetaId(), 'integer'); - - $res = $this->db->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $this->setRBACId((int) $row->rbac_id); - $this->setObjId((int) $row->obj_id); - $this->setObjType($row->obj_type ?? ''); - $this->setParentId((int) $row->parent_id); - $this->setParentType($row->parent_type); - $this->setLanguage(new ilMDLanguageItem($row->language ?? '')); - } - } - return true; - } - - public function toXML(ilXmlWriter $writer): void - { - $writer->xmlElement( - 'Language', - array( - 'Language' => $this->getLanguageCode() ?: 'en' - ), - $this->getLanguage() - ); - } - - // STATIC - - /** - * @return int[] - */ - public static function _getIds(int $a_rbac_id, int $a_obj_id, int $a_parent_id, string $a_parent_type): array - { - global $DIC; - - $ilDB = $DIC->database(); - - $query = "SELECT meta_language_id FROM il_meta_language " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . " " . - "AND obj_id = " . $ilDB->quote($a_obj_id, 'integer') . " " . - "AND parent_id = " . $ilDB->quote($a_parent_id, 'integer') . " " . - "AND parent_type = " . $ilDB->quote($a_parent_type, 'text'); - - $res = $ilDB->query($query); - $ids = []; - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $ids[] = (int) $row->meta_language_id; - } - return $ids; - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDLanguageElement.php b/components/ILIAS/MetaData/classes/class.ilMDLanguageElement.php deleted file mode 100755 index 88dbdbbaac49..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDLanguageElement.php +++ /dev/null @@ -1,191 +0,0 @@ -language_code = $a_code; - - $this->possible_language_codes = array( - "aa", - "ab", - "af", - "am", - "ar", - "as", - "ay", - "az", - "ba", - "be", - "bg", - "bh", - "bi", - "bn", - "bo", - "br", - "ca", - "co", - "cs", - "cy", - "da", - "de", - "dz", - "el", - "en", - "eo", - "es", - "et", - "eu", - "fa", - "fi", - "fj", - "fo", - "fr", - "fy", - "ga", - "gd", - "gl", - "gn", - "gu", - "ha", - "he", - "hi", - "hr", - "hu", - "hy", - "ia", - "ie", - "ik", - "id", - "is", - "it", - "iu", - "ja", - "jv", - "ka", - "kk", - "kl", - "km", - "kn", - "ko", - "ks", - "ku", - "ky", - "la", - "ln", - "lo", - "lt", - "lv", - "mg", - "mi", - "mk", - "ml", - "mn", - "mo", - "mr", - "ms", - "mt", - "my", - "na", - "ne", - "nl", - "no", - "oc", - "om", - "or", - "pa", - "pl", - "ps", - "pt", - "qu", - "rm", - "rn", - "ro", - "ru", - "rw", - "sa", - "sd", - "sg", - "sh", - "si", - "sk", - "sl", - "sm", - "sn", - "so", - "sq", - "sr", - "ss", - "st", - "su", - "sv", - "sw", - "ta", - "te", - "tg", - "th", - "ti", - "tk", - "tl", - "tn", - "to", - "tr", - "ts", - "tt", - "tw", - "ug", - "uk", - "ur", - "uz", - "vi", - "vo", - "wo", - "xh", - "yi", - "yo", - "za", - "zh", - "zu" - ); - } - - public function getLanguageCode(): string - { - if (in_array($this->language_code, $this->possible_language_codes, true)) { - return $this->language_code; - } - return ''; - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDLanguageItem.php b/components/ILIAS/MetaData/classes/class.ilMDLanguageItem.php deleted file mode 100755 index 14badb0ed6b3..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDLanguageItem.php +++ /dev/null @@ -1,216 +0,0 @@ -language_code = $a_code; - } - - public function getLanguageCode(): string - { - $lang = self::_getPossibleLanguageCodes(); - if (in_array($this->language_code, $lang)) { - return $this->language_code; - } - return ''; - } - - /** - * @return string[] - */ - public static function _getPossibleLanguageCodes(): array - { - return array( - "aa", - "ab", - "af", - "am", - "ar", - "as", - "ay", - "az", - "ba", - "be", - "bg", - "bh", - "bi", - "bn", - "bo", - "br", - "ca", - "co", - "cs", - "cy", - "da", - "de", - "dz", - "el", - "en", - "eo", - "es", - "et", - "eu", - "fa", - "fi", - "fj", - "fo", - "fr", - "fy", - "ga", - "gd", - "gl", - "gn", - "gu", - "ha", - "he", - "hi", - "hr", - "hu", - "hy", - "ia", - "ie", - "ik", - "id", - "is", - "it", - "iu", - "ja", - "jv", - "ka", - "kk", - "kl", - "km", - "kn", - "ko", - "ks", - "ku", - "ky", - "la", - "ln", - "lo", - "lt", - "lv", - "mg", - "mi", - "mk", - "ml", - "mn", - "mo", - "mr", - "ms", - "mt", - "my", - "na", - "ne", - "nl", - "no", - "oc", - "om", - "or", - "pa", - "pl", - "ps", - "pt", - "qu", - "rm", - "rn", - "ro", - "ru", - "rw", - "sa", - "sd", - "sg", - "sh", - "si", - "sk", - "sl", - "sm", - "sn", - "so", - "sq", - "sr", - "ss", - "st", - "su", - "sv", - "sw", - "ta", - "te", - "tg", - "th", - "ti", - "tk", - "tl", - "tn", - "to", - "tr", - "ts", - "tt", - "tw", - "ug", - "uk", - "ur", - "uz", - "vi", - "vo", - "wo", - "xh", - "yi", - "yo", - "za", - "zh", - "zu" - ); - } - - /** - * @return array - */ - public static function _getLanguages(): array - { - global $DIC; - - $lng = $DIC->language(); - - $lng->loadLanguageModule("meta"); - - $langs = array(); - foreach (self::_getPossibleLanguageCodes() as $lngcode) { - $langs[$lngcode] = $lng->txt("meta_l_" . $lngcode); - } - asort($langs); - return $langs; - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDLifecycle.php b/components/ILIAS/MetaData/classes/class.ilMDLifecycle.php deleted file mode 100755 index d5a15b1b8c2e..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDLifecycle.php +++ /dev/null @@ -1,255 +0,0 @@ - - * @package ilias-core - * @version $Id$ - * @deprecated will be removed with ILIAS 11, please use the new API (see {@see ../docs/api.md}) - */ -class ilMDLifecycle extends ilMDBase -{ - /** - * Compatibility fix for legacy MD classes for new db tables - */ - private const STATUS_TRANSLATION = [ - 'draft' => 'Draft', - 'final' => 'Final', - 'revised' => 'Revised', - 'unavailable' => 'Unavailable' - ]; - - private ?ilMDLanguageItem $version_language = null; - private string $version = ""; - private string $status = ""; - - /** - * @return array - */ - public function getPossibleSubelements(): array - { - $subs['Contribute'] = 'meta_contribute'; - - return $subs; - } - - /** - * @return int[] - */ - public function getContributeIds(): array - { - return ilMDContribute::_getIds($this->getRBACId(), $this->getObjId(), $this->getMetaId(), 'meta_lifecycle'); - } - - public function getContribute(int $a_contribute_id): ?ilMDContribute - { - if (!$a_contribute_id) { - return null; - } - $con = new ilMDContribute(); - $con->setMetaId($a_contribute_id); - - return $con; - } - - public function addContribute(): ilMDContribute - { - $con = new ilMDContribute($this->getRBACId(), $this->getObjId(), $this->getObjType()); - $con->setParentId($this->getMetaId()); - $con->setParentType('meta_lifecycle'); - - return $con; - } - - // SET/GET - public function setStatus(string $a_status): void - { - switch ($a_status) { - case 'Draft': - case 'Final': - case 'Revised': - case 'Unavailable': - $this->status = $a_status; - break; - } - } - - public function getStatus(): string - { - return $this->status; - } - - public function setVersion(string $a_version): void - { - $this->version = $a_version; - } - - public function getVersion(): string - { - return $this->version; - } - - public function setVersionLanguage(ilMDLanguageItem $lng_obj): void - { - $this->version_language = $lng_obj; - } - - public function getVersionLanguage(): ilMDLanguageItem - { - return $this->version_language; - } - - public function getVersionLanguageCode(): string - { - return is_object($this->version_language) ? $this->version_language->getLanguageCode() : ''; - } - - public function save(): int - { - $fields = $this->__getFields(); - $fields['meta_lifecycle_id'] = array('integer', $next_id = $this->db->nextId('il_meta_lifecycle')); - - if ($this->db->insert('il_meta_lifecycle', $fields)) { - $this->setMetaId($next_id); - return $this->getMetaId(); - } - return 0; - } - - public function update(): bool - { - return $this->getMetaId() && $this->db->update( - 'il_meta_lifecycle', - $this->__getFields(), - array("meta_lifecycle_id" => array('integer', $this->getMetaId())) - ); - } - - public function delete(): bool - { - // Delete 'contribute' - foreach ($this->getContributeIds() as $id) { - $con = $this->getContribute($id); - $con->delete(); - } - - if ($this->getMetaId()) { - $query = "DELETE FROM il_meta_lifecycle " . - "WHERE meta_lifecycle_id = " . $this->db->quote($this->getMetaId(), 'integer'); - $res = $this->db->manipulate($query); - return true; - } - return false; - } - - /** - * @return array> - */ - public function __getFields(): array - { - /** - * Compatibility fix for legacy MD classes for new db tables - */ - $status = (string) array_search( - $this->getStatus(), - self::STATUS_TRANSLATION - ); - - return array( - 'rbac_id' => array('integer', $this->getRBACId()), - 'obj_id' => array('integer', $this->getObjId()), - 'obj_type' => array('text', $this->getObjType()), - 'lifecycle_status' => array('text', $status), - 'meta_version' => array('text', $this->getVersion()), - 'version_language' => array('text', $this->getVersionLanguageCode()) - ); - } - - public function read(): bool - { - if ($this->getMetaId()) { - $query = "SELECT * FROM il_meta_lifecycle " . - "WHERE meta_lifecycle_id = " . $this->db->quote($this->getMetaId(), 'integer'); - - $res = $this->db->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - /** - * Compatibility fix for legacy MD classes for new db tables - */ - if (key_exists($row->lifecycle_status ?? '', self::STATUS_TRANSLATION)) { - $row->lifecycle_status = self::STATUS_TRANSLATION[$row->lifecycle_status ?? '']; - } - - $this->setRBACId((int) $row->rbac_id); - $this->setObjId((int) $row->obj_id); - $this->setObjType($row->obj_type ?? ''); - $this->setStatus($row->lifecycle_status ?? ''); - $this->setVersion($row->meta_version ?? ''); - $this->setVersionLanguage(new ilMDLanguageItem($row->version_language ?? '')); - } - } - return true; - } - - public function toXML(ilXmlWriter $writer): void - { - $writer->xmlStartTag('Lifecycle', array( - 'Status' => $this->getStatus() ?: 'Draft' - )); - $writer->xmlElement( - 'Version', - array( - 'Language' => $this->getVersionLanguageCode() ?: 'en' - ), - $this->getVersion() - ); - - // contribute - $contributes = $this->getContributeIds(); - foreach ($contributes as $id) { - $con = $this->getContribute($id); - $con->toXML($writer); - } - if (!count($contributes)) { - $con = new ilMDContribute($this->getRBACId(), $this->getObjId()); - $con->toXML($writer); - } - $writer->xmlEndTag('Lifecycle'); - } - - // STATIC - public static function _getId(int $a_rbac_id, int $a_obj_id): int - { - global $DIC; - - $ilDB = $DIC->database(); - - $query = "SELECT meta_lifecycle_id FROM il_meta_lifecycle " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . " " . - "AND obj_id = " . $ilDB->quote($a_obj_id, 'integer'); - - $res = $ilDB->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - return (int) $row->meta_lifecycle_id; - } - return 0; - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDLocation.php b/components/ILIAS/MetaData/classes/class.ilMDLocation.php deleted file mode 100755 index 63260ad8516e..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDLocation.php +++ /dev/null @@ -1,157 +0,0 @@ -location = $a_location; - } - - public function getLocation(): string - { - return $this->location; - } - - public function setLocationType(string $a_location_type): void - { - $this->location_type = $a_location_type; - } - - public function getLocationType(): string - { - return $this->location_type; - } - - public function save(): int - { - $fields = $this->__getFields(); - $fields['meta_location_id'] = array('integer', $next_id = $this->db->nextId('il_meta_location')); - - if ($this->db->insert('il_meta_location', $fields)) { - $this->setMetaId($next_id); - return $this->getMetaId(); - } - return 0; - } - - public function update(): bool - { - return $this->getMetaId() && $this->db->update( - 'il_meta_location', - $this->__getFields(), - array("meta_location_id" => array('integer', $this->getMetaId())) - ); - } - - public function delete(): bool - { - if ($this->getMetaId()) { - $query = "DELETE FROM il_meta_location " . - "WHERE meta_location_id = " . $this->db->quote($this->getMetaId(), 'integer'); - $res = $this->db->manipulate($query); - - return true; - } - return false; - } - - /** - * @return array> - */ - public function __getFields(): array - { - return array( - 'rbac_id' => array('integer', $this->getRBACId()), - 'obj_id' => array('integer', $this->getObjId()), - 'obj_type' => array('text', $this->getObjType()), - 'parent_type' => array('text', $this->getParentType()), - 'parent_id' => array('integer', $this->getParentId()), - 'location' => array('text', $this->getLocation()), - 'location_type' => array('text', $this->getLocationType()) - ); - } - - public function read(): bool - { - if ($this->getMetaId()) { - $query = "SELECT * FROM il_meta_location " . - "WHERE meta_location_id = " . $this->db->quote($this->getMetaId(), 'integer'); - - $res = $this->db->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $this->setRBACId((int) $row->rbac_id); - $this->setObjId((int) $row->obj_id); - $this->setObjType($row->obj_type ?? ''); - $this->setParentId((int) $row->parent_id); - $this->setParentType($row->parent_type); - $this->setLocation($row->location ?? ''); - $this->setLocationType($row->location_type ?? ''); - } - } - return true; - } - - public function toXML(ilXmlWriter $writer): void - { - $writer->xmlElement( - 'Location', - array( - 'Type' => $this->getLocationType() ?: 'LocalFile' - ), - $this->getLocation() - ); - } - - // STATIC - - /** - * @return int[] - */ - public static function _getIds(int $a_rbac_id, int $a_obj_id, int $a_parent_id, string $a_parent_type): array - { - global $DIC; - - $ilDB = $DIC->database(); - - $query = "SELECT meta_location_id FROM il_meta_location " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . " " . - "AND obj_id = " . $ilDB->quote($a_obj_id, 'integer') . " " . - "AND parent_id = " . $ilDB->quote($a_parent_id, 'integer') . " " . - "AND parent_type = " . $ilDB->quote($a_parent_type, 'text'); - - $res = $ilDB->query($query); - $ids = []; - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $ids[] = (int) $row->meta_location_id; - } - return $ids; - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDMetaMetadata.php b/components/ILIAS/MetaData/classes/class.ilMDMetaMetadata.php deleted file mode 100755 index 247e579c3aa5..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDMetaMetadata.php +++ /dev/null @@ -1,358 +0,0 @@ - - */ - public function getPossibleSubelements(): array - { - $subs['Identifier'] = 'meta_identifier'; - $subs['Contribute'] = 'meta_contribute'; - - return $subs; - } - - // SUBELEMENTS - - /** - * @return int[] - */ - public function getIdentifierIds(): array - { - return ilMDIdentifier::_getIds($this->getRBACId(), $this->getObjId(), $this->getMetaId(), 'meta_meta_data'); - } - - public function getIdentifier(int $a_identifier_id): ?ilMDIdentifier - { - if (!$a_identifier_id) { - return null; - } - $ide = new ilMDIdentifier(); - $ide->setMetaId($a_identifier_id); - - return $ide; - } - - public function addIdentifier(): ilMDIdentifier - { - $ide = new ilMDIdentifier($this->getRBACId(), $this->getObjId(), $this->getObjType()); - $ide->setParentId($this->getMetaId()); - $ide->setParentType('meta_meta_data'); - - return $ide; - } - - /** - * @return int[] - */ - public function getContributeIds(): array - { - return ilMDContribute::_getIds($this->getRBACId(), $this->getObjId(), $this->getMetaId(), 'meta_meta_data'); - } - - public function getContribute(int $a_contribute_id): ?ilMDContribute - { - if (!$a_contribute_id) { - return null; - } - $con = new ilMDContribute(); - $con->setMetaId($a_contribute_id); - - return $con; - } - - public function addContribute(): ilMDContribute - { - $con = new ilMDContribute($this->getRBACId(), $this->getObjId(), $this->getObjType()); - $con->setParentId($this->getMetaId()); - $con->setParentType('meta_meta_data'); - - return $con; - } - - // SET/GET - //TODO: check fixed attribute - public function setMetaDataScheme(string $a_val): void - { - $this->meta_data_scheme = $a_val; - } - - public function getMetaDataScheme(): string - { - // Fixed attribute - return 'LOM v 1.0'; - } - - public function setLanguage(ilMDLanguageItem $lng_obj): void - { - $this->language = $lng_obj; - } - - public function getLanguage(): ?ilMDLanguageItem - { - return is_object($this->language) ? $this->language : null; - } - - public function getLanguageCode(): string - { - return is_object($this->language) ? $this->language->getLanguageCode() : ''; - } - - public function save(): int - { - $fields = $this->__getFields(); - $fields['meta_meta_data_id'] = array('integer', $next_id = $this->db->nextId('il_meta_meta_data')); - - if ($this->db->insert('il_meta_meta_data', $fields)) { - $this->setMetaId($next_id); - $this->createOrUpdateFirstSchema(); - return $this->getMetaId(); - } - return 0; - } - - public function update(): bool - { - if (!$this->getMetaId()) { - return false; - } - - $this->createOrUpdateFirstSchema(); - - return (bool) $this->db->update( - 'il_meta_meta_data', - $this->__getFields(), - array("meta_meta_data_id" => array('integer', $this->getMetaId())) - ); - } - - public function delete(): bool - { - if ($this->getMetaId()) { - $query = "DELETE FROM il_meta_meta_data " . - "WHERE meta_meta_data_id = " . $this->db->quote($this->getMetaId(), 'integer'); - $res = $this->db->manipulate($query); - - $this->deleteAllSchemas(); - - foreach ($this->getIdentifierIds() as $id) { - $ide = $this->getIdentifier($id); - $ide->delete(); - } - - foreach ($this->getContributeIds() as $id) { - $con = $this->getContribute($id); - $con->delete(); - } - return true; - } - - return false; - } - - /** - * @return array> - */ - public function __getFields(): array - { - return array( - 'rbac_id' => array('integer', $this->getRBACId()), - 'obj_id' => array('integer', $this->getObjId()), - 'obj_type' => array('text', $this->getObjType()), - //'meta_data_scheme' => array('text', $this->getMetaDataScheme()), - 'language' => array('text', $this->getLanguageCode()) - ); - } - - public function read(): bool - { - if ($this->getMetaId()) { - $query = "SELECT * FROM il_meta_meta_data " . - "WHERE meta_meta_data_id = " . $this->db->quote($this->getMetaId(), 'integer'); - - $res = $this->db->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $this->setRBACId((int) $row->rbac_id); - $this->setObjId((int) $row->obj_id); - $this->setObjType($row->obj_type); - //$this->setMetaDataScheme($row->meta_data_scheme ?? ''); - $this->setLanguage(new ilMDLanguageItem($row->language ?? '')); - } - - $this->readFirstSchema(); - - return true; - } - return false; - } - - public function toXML(ilXmlWriter $writer): void - { - $attr = null; - if ($this->getMetaDataScheme()) { - $attr['MetadataScheme'] = $this->getMetaDataScheme(); - } - if ($this->getLanguageCode()) { - $attr['Language'] = $this->getLanguageCode(); - } - $writer->xmlStartTag('Meta-Metadata', $attr); - - // ELEMENT IDENTIFIER - $identifiers = $this->getIdentifierIds(); - foreach ($identifiers as $id) { - $ide = $this->getIdentifier($id); - $ide->toXML($writer); - } - if (!count($identifiers)) { - $ide = new ilMDIdentifier($this->getRBACId(), $this->getObjId()); - $ide->toXML($writer); - } - - // ELEMETN Contribute - $contributes = $this->getContributeIds(); - foreach ($contributes as $id) { - $con = $this->getContribute($id); - $con->toXML($writer); - } - if (!count($contributes)) { - $con = new ilMDContribute($this->getRBACId(), $this->getObjId()); - $con->toXML($writer); - } - - $writer->xmlEndTag('Meta-Metadata'); - } - - // STATIC - public static function _getId(int $a_rbac_id, int $a_obj_id): int - { - global $DIC; - - $ilDB = $DIC->database(); - - $query = "SELECT meta_meta_data_id FROM il_meta_meta_data " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . " " . - "AND obj_id = " . $ilDB->quote($a_obj_id, 'integer'); - - $res = $ilDB->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - return (int) $row->meta_meta_data_id; - } - return 0; - } - - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function createOrUpdateFirstSchema(): void - { - if ($this->getMetaDataScheme() === '') { - return; - } - - if (!$this->getSchemaId()) { - $this->db->insert( - 'il_meta_meta_schema', - [ - 'meta_meta_schema_id' => ['integer', $next_id = $this->db->nextId('il_meta_meta_schema')], - 'rbac_id' => ['integer', $this->getRBACId()], - 'obj_id' => ['integer', $this->getObjId()], - 'obj_type' => ['text', $this->getObjType()], - 'parent_type' => ['text', 'meta_general'], - 'parent_id' => ['integer', $this->getMetaId()], - 'meta_data_schema' => ['text', 'LOMv1.0'], - ] - ); - $this->schema_id = $next_id; - } - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function deleteAllSchemas(): void - { - $query = "DELETE FROM il_meta_meta_schema WHERE parent_type = 'meta_meta_data' - AND parent_id = " . $this->db->quote($this->getMetaId(), 'integer'); - $res = $this->db->manipulate($query); - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function readFirstSchema(): void - { - $query = "SELECT * FROM il_meta_meta_schema WHERE meta_meta_schema_id = " . - $this->db->quote($this->getMetaId(), 'integer'); - - $res = $this->db->query($query); - if ($row = $this->db->fetchAssoc($res)) { - $this->setMetaDataScheme((string) $row['meta_data_schema']); - } - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function getSchemaId(): int - { - return $this->schema_id; - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function readSchemaId(int $parent_id): void - { - $query = "SELECT meta_meta_schema_id FROM il_meta_meta_schema WHERE parent_type = 'meta_meta_data' - AND parent_id = " . $this->db->quote($parent_id, 'integer') . - " ORDER BY meta_meta_schema_id"; - - $res = $this->db->query($query); - if ($row = $this->db->fetchAssoc($res)) { - $this->schema_id = (int) $row['meta_meta_schema_id']; - } - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - public function setMetaId(int $a_meta_id, bool $a_read_data = true): void - { - $this->readSchemaId($a_meta_id); - parent::setMetaId($a_meta_id, $a_read_data); - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDOrComposite.php b/components/ILIAS/MetaData/classes/class.ilMDOrComposite.php deleted file mode 100755 index 79464e860f70..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDOrComposite.php +++ /dev/null @@ -1,151 +0,0 @@ -or_composite_id = $a_or_composite_id; - } - - public function getOrCompositeId(): int - { - if (!$this->or_composite_id) { - $query = "SELECT MAX(or_composite_id) orc FROM il_meta_requirement " . - "WHERE rbac_id = " . $this->db->quote($this->getRBACId(), 'integer') . " " . - "AND obj_id = " . $this->db->quote($this->getObjId(), 'integer') . " "; - - $res = $this->db->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $this->or_composite_id = (int) $row->orc; - } - ++$this->or_composite_id; - } - return $this->or_composite_id; - } - - /** - * @return int[] - */ - public function getRequirementIds(): array - { - return ilMDRequirement::_getIds( - $this->getRBACId(), - $this->getObjId(), - $this->getParentId(), - 'meta_technical', - $this->getOrCompositeId() - ); - } - - public function getRequirement(int $a_requirement_id): ?ilMDRequirement - { - if (!$a_requirement_id) { - return null; - } - $req = new ilMDRequirement(); - $req->setMetaId($a_requirement_id); - - return $req; - } - - public function addRequirement(): ilMDRequirement - { - $req = new ilMDRequirement($this->getRBACId(), $this->getObjId(), $this->getObjType()); - $req->setParentId($this->getParentId()); - $req->setParentType('meta_technical'); - $req->setOrCompositeId($this->getOrCompositeId()); - - return $req; - } - - public function save(): int - { - echo 'Use ilMDOrcomposite::addRequirement()'; - return 0; - } - - public function delete(): bool - { - foreach ($this->getRequirementIds() as $id) { - $req = $this->getRequirement($id); - $req->delete(); - } - return true; - } - - public function toXML(ilXmlWriter $writer): void - { - // For all requirements - $writer->xmlStartTag('OrComposite'); - - $reqs = $this->getRequirementIds(); - foreach ($reqs as $id) { - $req = $this->getRequirement($id); - $req->toXML($writer); - } - if (!count($reqs)) { - $req = new ilMDRequirement($this->getRBACId(), $this->getObjId()); - $req->toXML($writer); - } - $writer->xmlEndTag('OrComposite'); - } - - // STATIC - - /** - * @return int[] - */ - public static function _getIds( - int $a_rbac_id, - int $a_obj_id, - int $a_parent_id, - string $a_parent_type, - int $a_or_composite_id = 0 - ): array { - global $DIC; - - $ilDB = $DIC->database(); - - $query = "SELECT DISTINCT(or_composite_id) or_composite_id FROM il_meta_requirement " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . " " . - "AND obj_id = " . $ilDB->quote($a_obj_id, 'integer') . " " . - "AND parent_id = " . $ilDB->quote($a_parent_id, 'integer') . " " . - "AND parent_type = " . $ilDB->quote($a_parent_type, 'text') . " " . - "AND or_composite_id > 0 "; - - $res = $ilDB->query($query); - $ids = []; - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $ids[] = (int) $row->or_composite_id; - } - return $ids; - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDRelation.php b/components/ILIAS/MetaData/classes/class.ilMDRelation.php deleted file mode 100755 index 716f3aeefcc1..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDRelation.php +++ /dev/null @@ -1,278 +0,0 @@ - 'IsPartOf', - 'haspart' => 'HasPart', - 'isversionof' => 'IsVersionOf', - 'hasversion' => 'HasVersion', - 'isformatof' => 'IsFormatOf', - 'hasformat' => 'HasFormat', - 'references' => 'References', - 'isreferencedby' => 'IsReferencedBy', - 'isbasedon' => 'IsBasedOn', - 'isbasisfor' => 'IsBasisFor', - 'requires' => 'Requires', - 'isrequiredby' => 'IsRequiredBy' - ]; - - private string $kind = ''; - - // METHODS OF CHILD OBJECTS (Taxon) - - /** - * @return int[] - */ - public function getIdentifier_Ids(): array - { - return ilMDIdentifier_::_getIds($this->getRBACId(), $this->getObjId(), $this->getMetaId(), 'meta_relation'); - } - - public function getIdentifier_(int $a_identifier__id): ?ilMDIdentifier_ - { - if (!$a_identifier__id) { - return null; - } - $ide = new ilMDIdentifier_(); - $ide->setMetaId($a_identifier__id); - - return $ide; - } - - public function addIdentifier_(): ilMDIdentifier_ - { - $ide = new ilMDIdentifier_($this->getRBACId(), $this->getObjId(), $this->getObjType()); - $ide->setParentId($this->getMetaId()); - $ide->setParentType('meta_relation'); - - return $ide; - } - - /** - * @return int[] - */ - public function getDescriptionIds(): array - { - return ilMDDescription::_getIds($this->getRBACId(), $this->getObjId(), $this->getMetaId(), 'meta_relation'); - } - - public function getDescription(int $a_description_id): ?ilMDDescription - { - if (!$a_description_id) { - return null; - } - $des = new ilMDDescription(); - $des->setMetaId($a_description_id); - - return $des; - } - - public function addDescription(): ilMDDescription - { - $des = new ilMDDescription($this->getRBACId(), $this->getObjId(), $this->getObjType()); - $des->setParentId($this->getMetaId()); - $des->setParentType('meta_relation'); - - return $des; - } - - // SET/GET - public function setKind(string $a_kind): bool - { - switch ($a_kind) { - case 'IsPartOf': - case 'HasPart': - case 'IsVersionOf': - case 'HasVersion': - case 'IsFormatOf': - case 'HasFormat': - case 'References': - case 'IsReferencedBy': - case 'IsBasedOn': - case 'IsBasisFor': - case 'Requires': - case 'IsRequiredBy': - $this->kind = $a_kind; - return true; - - default: - return false; - } - } - - public function getKind(): string - { - return $this->kind; - } - - public function save(): int - { - $fields = $this->__getFields(); - $fields['meta_relation_id'] = array('integer', $next_id = $this->db->nextId('il_meta_relation')); - - if ($this->db->insert('il_meta_relation', $fields)) { - $this->setMetaId($next_id); - return $this->getMetaId(); - } - return 0; - } - - public function update(): bool - { - return $this->getMetaId() && $this->db->update( - 'il_meta_relation', - $this->__getFields(), - array("meta_relation_id" => array('integer', $this->getMetaId())) - ); - } - - public function delete(): bool - { - if ($this->getMetaId()) { - $query = "DELETE FROM il_meta_relation " . - "WHERE meta_relation_id = " . $this->db->quote($this->getMetaId(), 'integer'); - $res = $this->db->manipulate($query); - - foreach ($this->getIdentifier_Ids() as $id) { - $ide = $this->getIdentifier_($id); - $ide->delete(); - } - foreach ($this->getDescriptionIds() as $id) { - $des = $this->getDescription($id); - $des->delete(); - } - - return true; - } - return false; - } - - /** - * @return array> - */ - public function __getFields(): array - { - /** - * Compatibility fix for legacy MD classes for new db tables - */ - $kind = (string) array_search( - $this->getKind(), - self::KIND_TRANSLATION - ); - - return array( - 'rbac_id' => array('integer', $this->getRBACId()), - 'obj_id' => array('integer', $this->getObjId()), - 'obj_type' => array('text', $this->getObjType()), - 'kind' => array('text', $kind) - ); - } - - public function read(): bool - { - if ($this->getMetaId()) { - $query = "SELECT * FROM il_meta_relation " . - "WHERE meta_relation_id = " . $this->db->quote($this->getMetaId(), 'integer'); - - $res = $this->db->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - /** - * Compatibility fix for legacy MD classes for new db tables - */ - if (key_exists($row->kind ?? '', self::KIND_TRANSLATION)) { - $row->kind = self::KIND_TRANSLATION[$row->kind ?? '']; - } - - $this->setRBACId((int) $row->rbac_id); - $this->setObjId((int) $row->obj_id); - $this->setObjType($row->obj_type); - $this->setKind($row->kind ?? ''); - } - } - return true; - } - - public function toXML(ilXmlWriter $writer): void - { - $writer->xmlStartTag('Relation', array( - 'Kind' => $this->getKind() ?: 'IsPartOf' - )); - $writer->xmlStartTag('Resource'); - - // Identifier_ - $ides = $this->getIdentifier_Ids(); - foreach ($ides as $id) { - $ide = $this->getIdentifier_($id); - $ide->toXML($writer); - } - if (!count($ides)) { - $ide = new ilMDIdentifier_($this->getRBACId(), $this->getObjId()); - $ide->toXML($writer); - } - - // Description - $dess = $this->getDescriptionIds(); - foreach ($dess as $id) { - $des = $this->getDescription($id); - $des->toXML($writer); - } - if (!count($dess)) { - $des = new ilMDDescription($this->getRBACId(), $this->getObjId()); - $des->toXML($writer); - } - - $writer->xmlEndTag('Resource'); - $writer->xmlEndTag('Relation'); - } - - // STATIC - - /** - * @return int[] - */ - public static function _getIds(int $a_rbac_id, int $a_obj_id): array - { - global $DIC; - - $ilDB = $DIC->database(); - - $query = "SELECT meta_relation_id FROM il_meta_relation " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . " " . - "AND obj_id = " . $ilDB->quote($a_obj_id, 'integer'); - - $res = $ilDB->query($query); - $ids = []; - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $ids[] = (int) $row->meta_relation_id; - } - return $ids; - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDRequirement.php b/components/ILIAS/MetaData/classes/class.ilMDRequirement.php deleted file mode 100755 index 8c6510ac9462..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDRequirement.php +++ /dev/null @@ -1,478 +0,0 @@ - 'PC-DOS', - 'ms-windows' => 'MS-Windows', - 'macos' => 'MacOS', - 'unix' => 'Unix', - 'multi-os' => 'Multi-OS', - 'none' => 'None' - ]; - - private const BROWSER_TRANSLATION = [ - 'any' => 'Any', - 'netscape communicator' => 'NetscapeCommunicator', - 'ms-internet explorer' => 'MS-InternetExplorer', - 'opera' => 'Opera', - 'amaya' => 'Amaya' - ]; - - private int $or_composite_id = 0; - private string $operating_system_name = ''; - private string $operating_system_minimum_version = ''; - private string $operating_system_maximum_version = ''; - private string $browser_name = ''; - private string $browser_minimum_version = ''; - private string $browser_maximum_version = ''; - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - private int $or_id_browser = 0; - private int $or_id_os = 0; - - // SET/GET - public function setOrCompositeId(int $a_or_composite_id): void - { - $this->or_composite_id = $a_or_composite_id; - } - - public function getOrCompositeId(): int - { - return $this->or_composite_id; - } - - public function setOperatingSystemName(string $a_val): bool - { - switch ($a_val) { - case 'PC-DOS': - case 'MS-Windows': - case 'MacOS': - case 'Unix': - case 'Multi-OS': - case 'None': - $this->operating_system_name = $a_val; - return true; - - default: - return false; - } - } - - public function getOperatingSystemName(): string - { - return $this->operating_system_name; - } - - public function setOperatingSystemMinimumVersion(string $a_val): void - { - $this->operating_system_minimum_version = $a_val; - } - - public function getOperatingSystemMinimumVersion(): string - { - return $this->operating_system_minimum_version; - } - - public function setOperatingSystemMaximumVersion(string $a_val): void - { - $this->operating_system_maximum_version = $a_val; - } - - public function getOperatingSystemMaximumVersion(): string - { - return $this->operating_system_maximum_version; - } - - public function setBrowserName(string $a_val): bool - { - switch ($a_val) { - case 'Any': - case 'NetscapeCommunicator': - case 'MS-InternetExplorer': - case 'Opera': - case 'Amaya': - case 'Mozilla': - $this->browser_name = $a_val; - return true; - - default: - return false; - } - } - - public function getBrowserName(): string - { - return $this->browser_name; - } - - public function setBrowserMinimumVersion(string $a_val): void - { - $this->browser_minimum_version = $a_val; - } - - public function getBrowserMinimumVersion(): string - { - return $this->browser_minimum_version; - } - - public function setBrowserMaximumVersion(string $a_val): void - { - $this->browser_maximum_version = $a_val; - } - - public function getBrowserMaximumVersion(): string - { - return $this->browser_maximum_version; - } - - public function save(): int - { - $fields = $this->__getFields(); - $fields['meta_requirement_id'] = array('integer', $next_id = $this->db->nextId('il_meta_requirement')); - - if ($this->db->insert('il_meta_requirement', $fields)) { - $this->setMetaId($next_id); - $this->createOrUpdateOrs(); - return $this->getMetaId(); - } - return 0; - } - - public function update(): bool - { - if (!$this->getMetaId()) { - return false; - } - - $this->createOrUpdateOrs(); - - return (bool) $this->db->update( - 'il_meta_requirement', - $this->__getFields(), - array("meta_requirement_id" => array('integer', $this->getMetaId())) - ); - } - - public function delete(): bool - { - if ($this->getMetaId()) { - $query = "DELETE FROM il_meta_requirement " . - "WHERE meta_requirement_id = " . $this->db->quote($this->getMetaId(), 'integer'); - $res = $this->db->manipulate($query); - - $this->deleteAllOrs(); - return true; - } - return false; - } - - /** - * @return array> - */ - public function __getFields(): array - { - return array( - 'rbac_id' => array('integer', $this->getRBACId()), - 'obj_id' => array('integer', $this->getObjId()), - 'obj_type' => array('text', $this->getObjType()), - 'parent_type' => array('text', $this->getParentType()), - 'parent_id' => array('integer', $this->getParentId()), - //'operating_system_name' => array('text', $this->getOperatingSystemName()), - //'os_min_version' => array('text', $this->getOperatingSystemMinimumVersion()), - //'os_max_version' => array('text', $this->getOperatingSystemMaximumVersion()), - //'browser_name' => array('text', $this->getBrowserName()), - //'browser_minimum_version' => array('text', $this->getBrowserMinimumVersion()), - //'browser_maximum_version' => array('text', $this->getBrowserMaximumVersion()), - 'or_composite_id' => array('integer', $this->getOrCompositeId()) - ); - } - - public function read(): bool - { - if ($this->getMetaId()) { - $query = "SELECT * FROM il_meta_requirement " . - "WHERE meta_requirement_id = " . $this->db->quote($this->getMetaId(), 'integer'); - - $res = $this->db->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $this->setRBACId((int) $row->rbac_id); - $this->setObjId((int) $row->obj_id); - $this->setObjType($row->obj_type ?? ''); - $this->setParentId((int) $row->parent_id); - $this->setParentType($row->parent_type); - //$this->setOperatingSystemName($row->operating_system_name ?? ''); - //$this->setOperatingSystemMinimumVersion($row->os_min_version ?? ''); - //$this->setOperatingSystemMaximumVersion($row->os_max_version ?? ''); - //$this->setBrowserName($row->browser_name ?? ''); - //$this->setBrowserMinimumVersion($row->browser_minimum_version ?? ''); - //$this->setBrowserMaximumVersion($row->browser_maximum_version ?? ''); - $this->setOrCompositeId((int) $row->or_composite_id); - } - - $this->readFirstOrs(); - } - return true; - } - - /* - * XML Export of all meta data - * @param object (xml writer) see class.ilMD2XML.php - * - */ - public function toXML(ilXmlWriter $writer): void - { - $writer->xmlStartTag('Requirement'); - $writer->xmlStartTag('Type'); - - if ($this->getOperatingSystemName() !== '') { - $writer->xmlElement('OperatingSystem', array( - 'Name' => $this->getOperatingSystemName() ?: 'None', - 'MinimumVersion' => $this->getOperatingSystemMinimumVersion(), - 'MaximumVersion' => $this->getOperatingSystemMaximumVersion() - )); - } - if ($this->getBrowserName() !== '') { - $writer->xmlElement('Browser', array( - 'Name' => $this->getBrowserName() ?: 'Any', - 'MinimumVersion' => $this->getBrowserMinimumVersion(), - 'MaximumVersion' => $this->getBrowserMaximumVersion() - )); - } - $writer->xmlEndTag('Type'); - $writer->xmlEndTag('Requirement'); - } - - // STATIC - - /** - * @return int[] - */ - public static function _getIds( - int $a_rbac_id, - int $a_obj_id, - int $a_parent_id, - string $a_parent_type, - int $a_or_composite_id = 0 - ): array { - global $DIC; - - $ilDB = $DIC->database(); - - $query = "SELECT meta_requirement_id FROM il_meta_requirement " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . " " . - "AND obj_id = " . $ilDB->quote($a_obj_id, 'integer') . " " . - "AND parent_id = " . $ilDB->quote($a_parent_id, 'integer') . " " . - "AND parent_type = " . $ilDB->quote($a_parent_type, 'text') . " " . - "AND or_composite_id = " . $ilDB->quote($a_or_composite_id, 'integer'); - - $res = $ilDB->query($query); - $ids = []; - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $ids[] = (int) $row->meta_requirement_id; - } - return $ids; - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function createOrUpdateOrs(): void - { - $os_name = (string) array_search( - $this->getOperatingSystemName(), - self::OS_TRANSLATION - ); - $browser_name = (string) array_search( - $this->getBrowserName(), - self::BROWSER_TRANSLATION - ); - - $this->or_id_os = $this->createOrUpdateOr( - $this->getOrIdOS(), - 'operating system', - $os_name, - $this->getOperatingSystemMinimumVersion(), - $this->getOperatingSystemMaximumVersion() - ); - - $this->or_id_browser = $this->createOrUpdateOr( - $this->getOrIdBrowser(), - 'browser', - $browser_name, - $this->getBrowserMinimumVersion(), - $this->getBrowserMaximumVersion() - ); - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function createOrUpdateOr( - int $id, - string $type, - string $name, - string $min_version, - string $max_version - ): int { - if ($name === '' && $min_version === '' && $max_version === '') { - return 0; - } - - if (!$id) { - $this->db->insert( - 'il_meta_or_composite', - [ - 'meta_or_composite_id' => ['integer', $next_id = $this->db->nextId('il_meta_or_composite')], - 'rbac_id' => ['integer', $this->getRBACId()], - 'obj_id' => ['integer', $this->getObjId()], - 'obj_type' => ['text', $this->getObjType()], - 'parent_type' => ['text', 'meta_requirement'], - 'parent_id' => ['integer', $this->getMetaId()], - 'type' => ['text', $type], - 'name' => ['text', $name], - 'min_version' => ['text', $min_version], - 'max_version' => ['text', $max_version] - ] - ); - return $next_id; - } - - $this->db->update( - 'il_meta_or_composite', - [ - 'type' => ['text', $type], - 'name' => ['text', $name], - 'min_version' => ['text', $min_version], - 'max_version' => ['text', $max_version] - ], - ['meta_or_composite_id' => ['integer', $id]] - ); - return $id; - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function deleteAllOrs(): void - { - $query = "DELETE FROM il_meta_or_composite WHERE parent_type = 'meta_requirement' - AND parent_id = " . $this->db->quote($this->getMetaId(), 'integer'); - $res = $this->db->manipulate($query); - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function readFirstOrs(): void - { - $query = "SELECT * FROM il_meta_or_composite WHERE meta_or_composite_id = " . - $this->db->quote($this->getOrIdOS(), 'integer') . - " OR meta_or_composite_id = " . $this->db->quote($this->getOrIdBrowser(), 'integer'); - - $res = $this->db->query($query); - while ($row = $this->db->fetchAssoc($res)) { - switch ($row['type']) { - case 'operating system': - if (key_exists($row['name'] ?? '', self::OS_TRANSLATION)) { - $row['name'] = self::OS_TRANSLATION[$row['name'] ?? '']; - } - $this->setOperatingSystemName($row['name'] ?? ''); - $this->setOperatingSystemMinimumVersion($row['min_version'] ?? ''); - $this->setOperatingSystemMaximumVersion($row['max_version'] ?? ''); - break; - - case 'browser': - if (key_exists($row['name'] ?? '', self::BROWSER_TRANSLATION)) { - $row['name'] = self::BROWSER_TRANSLATION[$row['name'] ?? '']; - } - $this->setBrowserName($row['name'] ?? ''); - $this->setBrowserMinimumVersion($row['min_version'] ?? ''); - $this->setBrowserMaximumVersion($row['max_version'] ?? ''); - break; - } - } - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function readOrIds(int $parent_id): void - { - $query = "SELECT meta_or_composite_id, type FROM il_meta_or_composite WHERE - parent_id = " . $this->db->quote($parent_id, 'integer') . - " ORDER BY meta_or_composite_id"; - - $res = $this->db->query($query); - $browser_id = 0; - $os_id = 0; - while ($row = $this->db->fetchAssoc($res)) { - if (!$browser_id && $row['type'] === 'browser') { - $browser_id = (int) $row['meta_or_composite_id']; - } - if (!$os_id && $row['type'] === 'operating system') { - $os_id = (int) $row['meta_or_composite_id']; - } - if ($browser_id && $os_id) { - break; - } - } - - $this->or_id_browser = $browser_id; - $this->or_id_os = $os_id; - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function getOrIdOS(): int - { - return $this->or_id_os ?? 0; - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - protected function getOrIdBrowser(): int - { - return $this->or_id_browser ?? 0; - } - - /** - * Compatibility fix for legacy MD classes for new db tables - */ - public function setMetaId(int $a_meta_id, bool $a_read_data = true): void - { - $this->readOrIds($a_meta_id); - parent::setMetaId($a_meta_id, $a_read_data); - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDRights.php b/components/ILIAS/MetaData/classes/class.ilMDRights.php deleted file mode 100755 index 92c894680e86..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDRights.php +++ /dev/null @@ -1,284 +0,0 @@ - 'Yes', - 'no' => 'No' - ]; - - private string $costs = ''; - private string $caor = ''; - private string $description = ''; - private ?ilMDLanguageItem $description_language = null; - - /** - * @param string[] $a_types - * @param string[] $a_copyright - * @return int[] - */ - public static function lookupRightsByTypeAndCopyright(array $a_types, array $a_copyright): array - { - global $DIC; - - $db = $DIC->database(); - - $query = 'SELECT rbac_id FROM il_meta_rights ' . - 'WHERE ' . $db->in('obj_type', $a_types, false, 'text') . ' ' . - 'AND ' . $db->in('description', $a_copyright, false, 'text'); - $res = $db->query($query); - - ilLoggerFactory::getLogger('meta')->info($query); - - $obj_ids = []; - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $obj_ids[] = (int) $row->rbac_id; - } - return $obj_ids; - } - - // SET/GET - public function setCosts(string $a_costs): bool - { - switch ($a_costs) { - case 'Yes': - case 'No': - $this->costs = $a_costs; - return true; - - default: - return false; - } - } - - public function getCosts(): string - { - return $this->costs; - } - - public function setCopyrightAndOtherRestrictions(string $a_caor): bool - { - switch ($a_caor) { - case 'Yes': - case 'No': - $this->caor = $a_caor; - return true; - - default: - return false; - } - } - - public function getCopyrightAndOtherRestrictions(): string - { - return $this->caor; - } - - public function setDescription(string $a_description): void - { - $this->description = $a_description; - } - - public function getDescription(): string - { - return $this->description; - } - - public function setDescriptionLanguage(ilMDLanguageItem $lng_obj): void - { - $this->description_language = $lng_obj; - } - - public function getDescriptionLanguage(): ?ilMDLanguageItem - { - return is_object($this->description_language) ? $this->description_language : null; - } - - public function getDescriptionLanguageCode(): string - { - return is_object($this->description_language) ? $this->description_language->getLanguageCode() : ''; - } - - public function save(): int - { - $fields = $this->__getFields(); - $fields['meta_rights_id'] = array('integer', $next_id = $this->db->nextId('il_meta_rights')); - - if ($this->db->insert('il_meta_rights', $fields)) { - $this->setMetaId($next_id); - return $this->getMetaId(); - } - return 0; - } - - public function update(): bool - { - return $this->getMetaId() && $this->db->update( - 'il_meta_rights', - $this->__getFields(), - array("meta_rights_id" => array('integer', $this->getMetaId())) - ); - } - - public function delete(): bool - { - if ($this->getMetaId()) { - $query = "DELETE FROM il_meta_rights " . - "WHERE meta_rights_id = " . $this->db->quote($this->getMetaId(), 'integer'); - - $this->db->query($query); - - return true; - } - return false; - } - - /** - * @return array> - */ - public function __getFields(): array - { - /** - * Compatibility fix for legacy MD classes for new db tables - */ - $costs = (string) array_search( - $this->getCosts(), - self::YES_NO_TRANSLATION - ); - $cpr_and_or = (string) array_search( - $this->getCopyrightAndOtherRestrictions(), - self::YES_NO_TRANSLATION - ); - - return array( - 'rbac_id' => array('integer', $this->getRBACId()), - 'obj_id' => array('integer', $this->getObjId()), - 'obj_type' => array('text', $this->getObjType()), - 'costs' => array('text', $costs), - 'cpr_and_or' => array('text', $cpr_and_or), - 'description' => array('text', $this->getDescription()), - 'description_language' => array('text', $this->getDescriptionLanguageCode()) - ); - } - - public function read(): bool - { - if ($this->getMetaId()) { - $query = "SELECT * FROM il_meta_rights " . - "WHERE meta_rights_id = " . $this->db->quote($this->getMetaId(), 'integer'); - - $res = $this->db->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - /** - * Compatibility fix for legacy MD classes for new db tables - */ - if (key_exists($row->costs ?? '', self::YES_NO_TRANSLATION)) { - $row->costs = self::YES_NO_TRANSLATION[$row->costs ?? '']; - } - if (key_exists($row->cpr_and_or ?? '', self::YES_NO_TRANSLATION)) { - $row->cpr_and_or = self::YES_NO_TRANSLATION[$row->cpr_and_or ?? '']; - } - - $this->setRBACId((int) $row->rbac_id); - $this->setObjId((int) $row->obj_id); - $this->setObjType($row->obj_type ?? ''); - $this->setDescription($row->description ?? ''); - $this->setDescriptionLanguage(new ilMDLanguageItem($row->description_language ?? '')); - $this->setCosts($row->costs ?? ''); - $this->setCopyrightAndOtherRestrictions($row->cpr_and_or ?? ''); - } - return true; - } - return false; - } - - public function toXML(ilXmlWriter $writer): void - { - $writer->xmlStartTag('Rights', array( - 'Cost' => $this->getCosts() ?: 'No', - 'CopyrightAndOtherRestrictions' => $this->getCopyrightAndOtherRestrictions() ?: 'No' - )); - - $writer->xmlElement( - 'Description', - [ - 'Language' => $this->getDescriptionLanguageCode() ?: 'en' - ], - ilMDCopyrightSelectionEntry::_lookupCopyrightForExport($this->getDescription()) - ); - $writer->xmlEndTag('Rights'); - } - - public function parseDescriptionFromImport(string $a_description): void - { - $entry_id = ilMDCopyrightSelectionEntry::lookupCopyrightFromImport($a_description); - if (!$entry_id) { - $this->setDescription($a_description); - } else { - $this->setDescription(ilMDCopyrightSelectionEntry::createIdentifier($entry_id)); - } - } - - public static function _lookupDescription(int $a_rbac_id, int $a_obj_id): string - { - global $DIC; - - $ilDB = $DIC->database(); - - $query = "SELECT description FROM il_meta_rights " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . " " . - "AND obj_id = " . $ilDB->quote($a_obj_id, 'integer') . " "; - $res = $ilDB->query($query); - $row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT); - - if (isset($row) && isset($row->description)) { - return $row->description; - } - return ''; - } - - // STATIC - public static function _getId(int $a_rbac_id, int $a_obj_id): int - { - global $DIC; - - $ilDB = $DIC->database(); - - $query = "SELECT meta_rights_id FROM il_meta_rights " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . " " . - "AND obj_id = " . $ilDB->quote($a_obj_id, 'integer'); - - $res = $ilDB->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - return (int) $row->meta_rights_id; - } - return 0; - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDSaxParser.php b/components/ILIAS/MetaData/classes/class.ilMDSaxParser.php deleted file mode 100755 index c65ae337208e..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDSaxParser.php +++ /dev/null @@ -1,703 +0,0 @@ - - * Inserts Meta data from XML into ILIAS db - * @extends ilSaxParser - * @package ilias-core - * @deprecated will be removed with ILIAS 11, LOM should only be exported as a tail dependency - */ -class ilMDSaxParser extends ilSaxParser -{ - protected bool $md_in_md = false; - protected string $md_chr_data = ''; - protected ?ilMDIdentifier $md_ide = null; - protected ?ilMDLanguage $md_lan = null; - protected ?ilMDDescription $md_des = null; - protected ?ilMDLifecycle $md_lif = null; - protected ?ilMDContribute $md_con = null; - protected ?ilMDEntity $md_ent = null; - protected ?ilMDMetaMetadata $md_met = null; - protected ?ilMDTechnical $md_tec = null; - protected ?ilMDFormat $md_for = null; - protected ?ilMDLocation $md_loc = null; - protected ?ilMDRequirement $md_req = null; - protected ?ilMDOrComposite $md_orc = null; - protected ?ilMDEducational $md_edu = null; - protected ?ilMDTypicalAgeRange $md_typ = null; - protected ?ilMDRights $md_rig = null; - protected ?ilMDRelation $md_rel = null; - protected ?ilMDIdentifier_ $md_ide_ = null; - protected ?ilMDAnnotation $md_ann = null; - protected ?ilMDClassification $md_cla = null; - protected ?ilMDTaxonPath $md_taxp = null; - protected ?ilMDTaxon $md_tax = null; - protected ?ilMDKeyword $md_key = null; - - /** - * Array of mixed ilMD objects - * @var array - */ - protected array $md_parent = array(); - - private bool $md_parsing_enabled; - - protected ?ilMD $md = null; - - protected ?ilMDGeneral $md_gen = null; - - protected ilLogger $meta_log; - - public function __construct(?string $a_xml_file = '') - { - global $DIC; - - $this->meta_log = $DIC->logger()->meta(); - - // Enable parsing. E.g qpl' s will set this value to false - $this->md_parsing_enabled = true; - - parent::__construct($a_xml_file); - } - - public function enableMDParsing(bool $a_status): void - { - $this->md_parsing_enabled = $a_status; - } - - public function getMDParsingStatus(): bool - { - return $this->md_parsing_enabled; - } - - public function setMDObject(ilMD $md): void - { - $this->md = $md; - } - - public function getMDObject(): ?ilMD - { - return is_object($this->md) ? $this->md : null; - } - - public function inMetaData(): bool - { - return $this->md_in_md; - } - - /** - * Set event handlers - * @param XMLParser|resource reference to the xml parser - */ - public function setHandlers($a_xml_parser): void - { - xml_set_object($a_xml_parser, $this); - xml_set_element_handler($a_xml_parser, 'handlerBeginTag', 'handlerEndTag'); - xml_set_character_data_handler($a_xml_parser, 'handlerCharacterData'); - } - - /** - * @param XMLParser|resource $a_xml_parser - */ - public function handlerBeginTag($a_xml_parser, string $a_name, array $a_attribs): void - { - $a_attribs = $this->trimAndStripAttribs($a_attribs); - if (!$this->getMDParsingStatus()) { - return; - } - - switch ($a_name) { - case 'MetaData': - $this->md_in_md = true; - $this->__pushParent($this->md); - break; - - case 'General': - $this->md_gen = $this->md->addGeneral(); - $this->md_gen->setStructure($a_attribs['Structure'] ?? ''); - $this->md_gen->save(); - $this->__pushParent($this->md_gen); - break; - - case 'Identifier': - $par = $this->__getParent(); - $this->md_ide = $par->addIdentifier(); - $this->md_ide->setCatalog($a_attribs['Catalog'] ?? ''); - $this->md_ide->setEntry($a_attribs['Entry'] ?? ''); - $this->md_ide->save(); - $this->__pushParent($this->md_ide); - break; - - case 'Title': - $par = $this->__getParent(); - $par->setTitleLanguage(new ilMDLanguageItem($a_attribs['Language'] ?? '')); - break; - - case 'Language': - $par = $this->__getParent(); - $this->md_lan = $par->addLanguage(); - $this->md_lan->setLanguage(new ilMDLanguageItem($a_attribs['Language'] ?? '')); - $this->md_lan->save(); - $this->__pushParent($this->md_lan); - break; - - case 'Description': - $par = $this->__getParent(); - - if (strtolower(get_class($par)) === 'ilmdrights' || - strtolower(get_class($par)) === 'ilmdannotation' || - strtolower(get_class($par)) === 'ilmdclassification') { - $par->setDescriptionLanguage(new ilMDLanguageItem($a_attribs['Language'] ?? '')); - break; - } else { - $this->md_des = $par->addDescription(); - $this->md_des->setDescriptionLanguage(new ilMDLanguageItem($a_attribs['Language'] ?? '')); - $this->md_des->save(); - $this->__pushParent($this->md_des); - break; - } - - // no break - case 'Keyword': - $par = $this->__getParent(); - if (!$par instanceof ilMD) { - $this->md_key = $par->addKeyword(); - $this->md_key->setKeywordLanguage(new ilMDLanguageItem($a_attribs['Language'] ?? '')); - $this->md_key->save(); - $this->__pushParent($this->md_key); - } - break; - - case 'Coverage': - $par = $this->__getParent(); - $par->setCoverageLanguage(new ilMDLanguageItem($a_attribs['Language'] ?? '')); - break; - - case 'Lifecycle': - $par = $this->__getParent(); - $this->md_lif = $par->addLifecycle(); - $this->md_lif->setStatus($a_attribs['Status'] ?? ''); - $this->md_lif->save(); - $this->__pushParent($this->md_lif); - break; - - case 'Version': - $par = $this->__getParent(); - $par->setVersionLanguage(new ilMDLanguageItem($a_attribs['Language'] ?? '')); - break; - - case 'Contribute': - $par = $this->__getParent(); - $this->md_con = $par->addContribute(); - $this->md_con->setRole($a_attribs['Role'] ?? ''); - $this->md_con->save(); - $this->__pushParent($this->md_con); - break; - - case 'Entity': - $par = $this->__getParent(); - - if (strtolower(get_class($par)) === 'ilmdcontribute') { - $this->md_ent = $par->addEntity(); - $this->md_ent->save(); - $this->__pushParent($this->md_ent); - break; - } else { - // single element in 'Annotation' - break; - } - // no break - case 'Date': - break; - - case 'Meta-Metadata': - $par = $this->__getParent(); - $this->md_met = $par->addMetaMetadata(); - $this->md_met->setMetaDataScheme($a_attribs['MetadataScheme'] ?? ''); - $this->md_met->setLanguage(new ilMDLanguageItem((string) ($a_attribs['Language'] ?? ""))); - $this->md_met->save(); - $this->__pushParent($this->md_met); - break; - - case 'Technical': - $par = $this->__getParent(); - $this->md_tec = $par->addTechnical(); - $this->md_tec->save(); - $this->__pushParent($this->md_tec); - break; - - case 'Format': - $par = $this->__getParent(); - $this->md_for = $par->addFormat(); - $this->md_for->save(); - $this->__pushParent($this->md_for); - break; - - case 'Size': - break; - - case 'Location': - $par = $this->__getParent(); - $this->md_loc = $par->addLocation(); - $this->md_loc->setLocationType($a_attribs['Type'] ?? ''); - $this->md_loc->save(); - $this->__pushParent($this->md_loc); - break; - - case 'Requirement': - $par = $this->__getParent(); - $this->md_req = $par->addRequirement(); - $this->md_req->save(); - $this->__pushParent($this->md_req); - break; - - case 'OrComposite': - $par = $this->__getParent(); - $this->md_orc = $par->addOrComposite(); - $this->__pushParent($this->md_orc); - break; - - case 'Type': - break; - - case 'OperatingSystem': - $par = $this->__getParent(); - $par->setOperatingSystemName($a_attribs['Name'] ?? ''); - $par->setOperatingSystemMinimumVersion($a_attribs['MinimumVersion'] ?? ''); - $par->setOperatingSystemMaximumVersion($a_attribs['MaximumVersion'] ?? ''); - break; - - case 'Browser': - $par = $this->__getParent(); - $par->setBrowserName($a_attribs['Name'] ?? ''); - $par->setBrowserMinimumVersion($a_attribs['MinimumVersion'] ?? ''); - $par->setBrowserMaximumVersion($a_attribs['MaximumVersion'] ?? ''); - break; - - case 'InstallationRemarks': - $par = $this->__getParent(); - $par->setInstallationRemarksLanguage(new ilMDLanguageItem($a_attribs['Language'] ?? '')); - break; - - case 'OtherPlatformRequirements': - $par = $this->__getParent(); - $par->setOtherPlatformRequirementsLanguage(new ilMDLanguageItem($a_attribs['Language'] ?? '')); - break; - - case 'Duration': - break; - - case 'Educational': - $par = $this->__getParent(); - $this->md_edu = $par->addEducational(); - $this->md_edu->setInteractivityType($a_attribs['InteractivityType'] ?? ''); - $this->md_edu->setLearningResourceType($a_attribs['LearningResourceType'] ?? ''); - $this->md_edu->setInteractivityLevel($a_attribs['InteractivityLevel'] ?? ''); - $this->md_edu->setSemanticDensity($a_attribs['SemanticDensity'] ?? ''); - $this->md_edu->setIntendedEndUserRole($a_attribs['IntendedEndUserRole'] ?? ''); - $this->md_edu->setContext($a_attribs['Context'] ?? ''); - $this->md_edu->setDifficulty($a_attribs['Difficulty'] ?? ''); - $this->md_edu->save(); - $this->__pushParent($this->md_edu); - break; - - case 'TypicalAgeRange': - $par = $this->__getParent(); - $this->md_typ = $par->addTypicalAgeRange(); - $this->md_typ->setTypicalAgeRangeLanguage(new ilMDLanguageItem($a_attribs['Language'] ?? '')); - $this->md_typ->save(); - $this->__pushParent($this->md_typ); - break; - - case 'TypicalLearningTime': - break; - - case 'Rights': - $par = $this->__getParent(); - $this->md_rig = $par->addRights(); - $this->md_rig->setCosts($a_attribs['Cost'] ?? ''); - $this->md_rig->setCopyrightAndOtherRestrictions($a_attribs['CopyrightAndOtherRestrictions'] ?? ''); - $this->md_rig->save(); - $this->__pushParent($this->md_rig); - break; - - case 'Relation': - $par = $this->__getParent(); - $this->md_rel = $par->addRelation(); - $this->md_rel->setKind($a_attribs['Kind'] ?? ''); - $this->md_rel->save(); - $this->__pushParent($this->md_rel); - break; - - case 'Resource': - break; - - case 'Identifier_': - $par = $this->__getParent(); - $this->md_ide_ = $par->addIdentifier_(); - $this->md_ide_->setCatalog($a_attribs['Catalog'] ?? ''); - $this->md_ide_->setEntry($a_attribs['Entry'] ?? ''); - $this->md_ide_->save(); - $this->__pushParent($this->md_ide_); - break; - - case 'Annotation': - $par = $this->__getParent(); - $this->md_ann = $par->addAnnotation(); - $this->md_ann->save(); - $this->__pushParent($this->md_ann); - break; - - case 'Classification': - $par = $this->__getParent(); - $this->md_cla = $par->addClassification(); - $this->md_cla->setPurpose($a_attribs['Purpose'] ?? ''); - $this->md_cla->save(); - $this->__pushParent($this->md_cla); - break; - - case 'TaxonPath': - $par = $this->__getParent(); - $this->md_taxp = $par->addTaxonPath(); - $this->md_taxp->save(); - $this->__pushParent($this->md_taxp); - break; - - case 'Source': - $par = $this->__getParent(); - $par->setSourceLanguage(new ilMDLanguageItem($a_attribs['Language'] ?? '')); - break; - - case 'Taxon': - $par = $this->__getParent(); - $this->md_tax = $par->addTaxon(); - $this->md_tax->setTaxonLanguage(new ilMDLanguageItem($a_attribs['Language'] ?? '')); - $this->md_tax->setTaxonId($a_attribs['Id'] ?? ''); - $this->md_tax->save(); - $this->__pushParent($this->md_tax); - break; - } - } - - /** - * @param resource $a_xml_parser - */ - public function handlerEndTag($a_xml_parser, string $a_name): void - { - if (!$this->getMDParsingStatus()) { - return; - } - - switch ($a_name) { - case 'MetaData': - $this->md_parent = array(); - $this->md_in_md = false; - break; - - case 'General': - $par = $this->__getParent(); - $par->update(); - $this->__popParent(); - break; - - case 'Identifier': - $par = $this->__getParent(); - $par->update(); - $this->__popParent(); - break; - - case 'Title': - $par = $this->__getParent(); - $par->setTitle($this->__getCharacterData()); - break; - - case 'Language': - $par = $this->__getParent(); - $par->update(); - $this->__popParent(); - break; - - case 'Description': - $par = $this->__getParent(); - if ($par instanceof ilMDRights) { - $par->parseDescriptionFromImport( - $this->__getCharacterData() - ); - } else { - $par->setDescription($this->__getCharacterData()); - } - $par->update(); - if ($par instanceof ilMDDescription) { - $this->__popParent(); - } - break; - - case 'Keyword': - $par = $this->__getParent(); - if (!$par instanceof ilMD) { - $par->setKeyword($this->__getCharacterData()); - $this->meta_log->debug("Keyword: " . $this->__getCharacterData()); - $par->update(); - $this->__popParent(); - } - break; - - case 'Coverage': - $par = $this->__getParent(); - $par->setCoverage($this->__getCharacterData()); - break; - - case 'Lifecycle': - $par = $this->__getParent(); - $par->update(); - $this->__popParent(); - break; - - case 'Version': - $par = $this->__getParent(); - $par->setVersion($this->__getCharacterData()); - break; - - case 'Contribute': - $par = $this->__getParent(); - $par->update(); - $this->__popParent(); - break; - - case 'Entity': - $par = $this->__getParent(); - - if (strtolower(get_class($par)) === 'ilmdentity') { - $par->setEntity($this->__getCharacterData()); - $par->update(); - $this->__popParent(); - } else { - // Single element in 'Annotation' - $par->setEntity($this->__getCharacterData()); - } - break; - - case 'Date': - $par = $this->__getParent(); - $par->setDate($this->__getCharacterData()); - break; - - case 'Meta-Metadata': - $par = $this->__getParent(); - $par->update(); - $this->__popParent(); - break; - - case 'Technical': - $par = $this->__getParent(); - $par->update(); - $this->__popParent(); - break; - - case 'Format': - $par = $this->__getParent(); - $par->setFormat($this->__getCharacterData()); - $par->update(); - $this->__popParent(); - break; - - case 'Size': - $par = $this->__getParent(); - $par->setSize($this->__getCharacterData()); - break; - - case 'Location': - $par = $this->__getParent(); - $par->setLocation($this->__getCharacterData()); - $par->update(); - $this->__popParent(); - break; - - case 'Requirement': - $par = $this->__getParent(); - $par->update(); - $this->__popParent(); - break; - - case 'OrComposite': - $this->__popParent(); - break; - - case 'Type': - break; - - case 'OperatingSystem': - break; - - case 'Browser': - break; - - case 'InstallationRemarks': - $par = $this->__getParent(); - $par->setInstallationRemarks($this->__getCharacterData()); - break; - - case 'OtherPlatformRequirements': - $par = $this->__getParent(); - $par->setOtherPlatformRequirements($this->__getCharacterData()); - break; - - case 'Duration': - $par = $this->__getParent(); - $par->setDuration($this->__getCharacterData()); - break; - - case 'Educational': - $par = $this->__getParent(); - $par->update(); - $this->__popParent(); - break; - - case 'TypicalAgeRange': - $par = $this->__getParent(); - $par->setTypicalAgeRange($this->__getCharacterData()); - $par->update(); - $this->__popParent(); - break; - - case 'TypicalLearningTime': - $par = $this->__getParent(); - $par->setTypicalLearningTime($this->__getCharacterData()); - break; - - case 'Rights': - $par = $this->__getParent(); - $par->update(); - $this->__popParent(); - break; - - case 'Relation': - $par = $this->__getParent(); - $par->update(); - $this->__popParent(); - break; - - case 'Resource': - break; - - case 'Identifier_': - $par = $this->__getParent(); - $par->update(); - $this->__popParent(); - break; - - case 'Annotation': - $par = $this->__getParent(); - $par->update(); - $this->__popParent(); - break; - - case 'Classification': - $par = $this->__getParent(); - $par->update(); - $this->__popParent(); - break; - - case 'TaxonPath': - $par = $this->__getParent(); - $par->update(); - $this->__popParent(); - break; - - case 'Taxon': - $par = $this->__getParent(); - $par->setTaxon($this->__getCharacterData()); - $par->update(); - $this->__popParent(); - break; - - case 'Source': - $par = $this->__getParent(); - $par->setSource($this->__getCharacterData()); - break; - } - $this->md_chr_data = ''; - } - - /** - * @param resource $a_xml_parser - */ - public function handlerCharacterData($a_xml_parser, string $a_data): void - { - if (!$this->getMDParsingStatus()) { - return; - } - - if ($a_data !== "\n" && $this->inMetaData()) { - // Replace multiple tabs with one space - $a_data = preg_replace("/\t+/", " ", $a_data); - - $this->md_chr_data .= $a_data; - } - } - - // PRIVATE - public function __getCharacterData(): string - { - return $this->trimAndStrip($this->md_chr_data); - } - - public function __pushParent(object $md_obj): void - { - $this->md_parent[] = &$md_obj; - $this->meta_log->debug('New parent stack (push)...'); - foreach ($this->md_parent as $class) { - $this->meta_log->debug(get_class($class)); - } - } - - public function __popParent(): void - { - $this->meta_log->debug('New parent stack (pop)....'); - $class = array_pop($this->md_parent); - foreach ((array) $this->md_parent as $class) { - $this->meta_log->debug(get_class($class)); - } - $this->meta_log->debug(is_object($class) ? get_class($class) : 'null'); - unset($class); - } - - public function __getParent(): object - { - return $this->md_parent[count($this->md_parent) - 1]; - } - - protected function trimAndStripAttribs(array $attribs): array - { - $ret = []; - foreach ($attribs as $k => $v) { - $ret[$k] = $this->trimAndStrip((string) $v); - } - return $ret; - } - - protected function trimAndStrip(string $input): string - { - return ilUtil::stripSlashes(trim($input)); - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDTaxon.php b/components/ILIAS/MetaData/classes/class.ilMDTaxon.php deleted file mode 100755 index 48b81b6033e1..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDTaxon.php +++ /dev/null @@ -1,178 +0,0 @@ -taxon = $a_taxon; - } - - public function getTaxon(): string - { - return $this->taxon; - } - - public function setTaxonLanguage(ilMDLanguageItem $lng_obj): void - { - $this->taxon_language = $lng_obj; - } - - public function getTaxonLanguage(): ?ilMDLanguageItem - { - return is_object($this->taxon_language) ? $this->taxon_language : null; - } - - public function getTaxonLanguageCode(): string - { - return is_object($this->taxon_language) ? $this->taxon_language->getLanguageCode() : ''; - } - - public function setTaxonId(string $a_taxon_id): void - { - $this->taxon_id = $a_taxon_id; - } - - public function getTaxonId(): string - { - return $this->taxon_id; - } - - public function save(): int - { - $fields = $this->__getFields(); - $fields['meta_taxon_id'] = array('integer', $next_id = $this->db->nextId('il_meta_taxon')); - - if ($this->db->insert('il_meta_taxon', $fields)) { - $this->setMetaId($next_id); - return $this->getMetaId(); - } - return 0; - } - - public function update(): bool - { - return $this->getMetaId() && $this->db->update( - 'il_meta_taxon', - $this->__getFields(), - array("meta_taxon_id" => array('integer', $this->getMetaId())) - ); - } - - public function delete(): bool - { - if ($this->getMetaId()) { - $query = "DELETE FROM il_meta_taxon " . - "WHERE meta_taxon_id = " . $this->db->quote($this->getMetaId(), 'integer'); - - $this->db->query($query); - - return true; - } - return false; - } - - /** - * @return array> - */ - public function __getFields(): array - { - return array( - 'rbac_id' => array('integer', $this->getRBACId()), - 'obj_id' => array('integer', $this->getObjId()), - 'obj_type' => array('text', $this->getObjType()), - 'parent_type' => array('text', $this->getParentType()), - 'parent_id' => array('integer', $this->getParentId()), - 'taxon' => array('text', $this->getTaxon()), - 'taxon_language' => array('text', $this->getTaxonLanguageCode()), - 'taxon_id' => array('text', $this->getTaxonId()) - ); - } - - public function read(): bool - { - if ($this->getMetaId()) { - $query = "SELECT * FROM il_meta_taxon " . - "WHERE meta_taxon_id = " . $this->db->quote($this->getMetaId(), 'integer'); - - $res = $this->db->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $this->setRBACId((int) $row->rbac_id); - $this->setObjId((int) $row->obj_id); - $this->setObjType($row->obj_type ?? ''); - $this->setParentId((int) $row->parent_id); - $this->setParentType($row->parent_type); - $this->setTaxon($row->taxon ?? ''); - $this->taxon_language = new ilMDLanguageItem($row->taxon_language ?? ''); - $this->setTaxonId($row->taxon_id ?? ''); - } - } - return true; - } - - public function toXML(ilXmlWriter $writer): void - { - $random = new \ilRandom(); - $writer->xmlElement( - 'Taxon', - array( - 'Language' => $this->getTaxonLanguageCode() ?: 'en', - 'Id' => $this->getTaxonId() ?: ("ID" . $random->int()) - ), - $this->getTaxon() - ); - } - - // STATIC - - /** - * @return int[] - */ - public static function _getIds(int $a_rbac_id, int $a_obj_id, int $a_parent_id, string $a_parent_type): array - { - global $DIC; - - $ilDB = $DIC->database(); - - $query = "SELECT meta_taxon_id FROM il_meta_taxon " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . " " . - "AND obj_id = " . $ilDB->quote($a_obj_id, 'integer') . " " . - "AND parent_id = " . $ilDB->quote($a_parent_id, 'integer') . " " . - "AND parent_type = " . $ilDB->quote($a_parent_type, 'text'); - - $res = $ilDB->query($query); - $ids = []; - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $ids[] = (int) $row->meta_taxon_id; - } - return $ids; - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDTaxonPath.php b/components/ILIAS/MetaData/classes/class.ilMDTaxonPath.php deleted file mode 100755 index 475274190a19..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDTaxonPath.php +++ /dev/null @@ -1,213 +0,0 @@ -getMetaId()) { - return []; - } - return ilMDTaxon::_getIds($this->getRBACId(), $this->getObjId(), $this->getMetaId(), 'meta_taxon_path'); - } - - public function getTaxon(int $a_taxon_id): ?ilMDTaxon - { - if (!$a_taxon_id) { - return null; - } - $tax = new ilMDTaxon(); - $tax->setMetaId($a_taxon_id); - - return $tax; - } - - public function addTaxon(): ilMDTaxon - { - $tax = new ilMDTaxon($this->getRBACId(), $this->getObjId(), $this->getObjType()); - $tax->setParentId($this->getMetaId()); - $tax->setParentType('meta_taxon_path'); - - return $tax; - } - - // SET/GET - public function setSource(string $a_source): void - { - $this->source = $a_source; - } - - public function getSource(): string - { - return $this->source; - } - - public function setSourceLanguage(ilMDLanguageItem $lng_obj): void - { - $this->source_language = $lng_obj; - } - - public function getSourceLanguage(): ?ilMDLanguageItem - { - return is_object($this->source_language) ? $this->source_language : null; - } - - public function getSourceLanguageCode(): string - { - return is_object($this->source_language) ? $this->source_language->getLanguageCode() : ''; - } - - public function save(): int - { - $fields = $this->__getFields(); - $fields['meta_taxon_path_id'] = array('integer', $next_id = $this->db->nextId('il_meta_taxon_path')); - - if ($this->db->insert('il_meta_taxon_path', $fields)) { - $this->setMetaId($next_id); - return $this->getMetaId(); - } - return 0; - } - - public function update(): bool - { - return $this->getMetaId() && $this->db->update( - 'il_meta_taxon_path', - $this->__getFields(), - array("meta_taxon_path_id" => array('integer', $this->getMetaId())) - ); - } - - public function delete(): bool - { - if ($this->getMetaId()) { - $query = "DELETE FROM il_meta_taxon_path " . - "WHERE meta_taxon_path_id = " . $this->db->quote($this->getMetaId(), 'integer'); - $res = $this->db->manipulate($query); - - foreach ($this->getTaxonIds() as $id) { - $tax = $this->getTaxon($id); - $tax->delete(); - } - - return true; - } - return false; - } - - /** - * @return array> - */ - public function __getFields(): array - { - return array( - 'rbac_id' => array('integer', $this->getRBACId()), - 'obj_id' => array('integer', $this->getObjId()), - 'obj_type' => array('text', $this->getObjType()), - 'parent_type' => array('text', $this->getParentType()), - 'parent_id' => array('integer', $this->getParentId()), - 'source' => array('text', $this->getSource()), - 'source_language' => array('text', $this->getSourceLanguageCode()) - ); - } - - public function read(): bool - { - if ($this->getMetaId()) { - $query = "SELECT * FROM il_meta_taxon_path " . - "WHERE meta_taxon_path_id = " . $this->db->quote($this->getMetaId(), 'integer'); - - $res = $this->db->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $this->setRBACId((int) $row->rbac_id); - $this->setObjId((int) $row->obj_id); - $this->setObjType($row->obj_type); - $this->setParentId((int) $row->parent_id); - $this->setParentType($row->parent_type); - $this->setSource($row->source ?? ''); - $this->source_language = new ilMDLanguageItem($row->source_language ?? ''); - } - } - return true; - } - - public function toXML(ilXmlWriter $writer): void - { - $writer->xmlStartTag('TaxonPath'); - - $writer->xmlElement( - 'Source', - array( - 'Language' => $this->getSourceLanguageCode() ?: 'en' - ), - $this->getSource() - ); - - // Taxon - $taxs = $this->getTaxonIds(); - foreach ($taxs as $id) { - $tax = $this->getTaxon($id); - $tax->toXML($writer); - } - if (!count($taxs)) { - $tax = new ilMDTaxon($this->getRBACId(), $this->getObjId()); - $tax->toXML($writer); - } - - $writer->xmlEndTag('TaxonPath'); - } - - /** - * @return int[] - */ - public static function _getIds(int $a_rbac_id, int $a_obj_id, int $a_parent_id, string $a_parent_type): array - { - global $DIC; - - $ilDB = $DIC->database(); - - $query = "SELECT meta_taxon_path_id FROM il_meta_taxon_path " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . " " . - "AND obj_id = " . $ilDB->quote($a_obj_id, 'integer') . " " . - "AND parent_id = " . $ilDB->quote($a_parent_id, 'integer') . " " . - "AND parent_type = " . $ilDB->quote($a_parent_type, 'text'); - - $res = $ilDB->query($query); - $ids = []; - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $ids[] = (int) $row->meta_taxon_path_id; - } - return $ids; - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDTechnical.php b/components/ILIAS/MetaData/classes/class.ilMDTechnical.php deleted file mode 100755 index d3fac550f1a4..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDTechnical.php +++ /dev/null @@ -1,410 +0,0 @@ - - */ - public function getPossibleSubelements(): array - { - $subs['Format'] = 'meta_format'; - $subs['Location'] = 'meta_location'; - if (!$this->getOrCompositeIds()) { - $subs['Requirement'] = 'meta_requirement'; - } - if (!$this->getRequirementIds()) { - $subs['OrComposite'] = 'meta_or_composite'; - } - - return $subs; - } - - // Methods for child objects (Format, Location, Requirement OrComposite) - - /** - * @return int[] - */ - public function getFormatIds(): array - { - return ilMDFormat::_getIds($this->getRBACId(), $this->getObjId()); - } - - public function getFormat(int $a_format_id): ?ilMDFormat - { - if (!$a_format_id) { - return null; - } - $for = new ilMDFormat($this->getRBACId(), $a_format_id); - $for->setMetaId($a_format_id); - - return $for; - } - - public function addFormat(): ilMDFormat - { - $for = new ilMDFormat($this->getRBACId(), $this->getObjId(), $this->getObjType()); - - return $for; - } - - /** - * @return int[] - */ - public function getLocationIds(): array - { - return ilMDLocation::_getIds($this->getRBACId(), $this->getObjId(), $this->getMetaId(), 'meta_technical'); - } - - public function getLocation(int $a_location_id): ?ilMDLocation - { - if (!$a_location_id) { - return null; - } - $loc = new ilMDLocation(); - $loc->setMetaId($a_location_id); - - return $loc; - } - - public function addLocation(): ilMDLocation - { - $loc = new ilMDLocation($this->getRBACId(), $this->getObjId(), $this->getObjType()); - $loc->setParentId($this->getMetaId()); - $loc->setParentType('meta_technical'); - - return $loc; - } - - /** - * @return int[] - */ - public function getRequirementIds(): array - { - return ilMDRequirement::_getIds($this->getRBACId(), $this->getObjId(), $this->getMetaId(), 'meta_technical'); - } - - public function getRequirement(int $a_requirement_id): ?ilMDRequirement - { - if (!$a_requirement_id) { - return null; - } - $rec = new ilMDRequirement(); - $rec->setMetaId($a_requirement_id); - - return $rec; - } - - public function addRequirement(): ilMDRequirement - { - $rec = new ilMDRequirement($this->getRBACId(), $this->getObjId(), $this->getObjType()); - $rec->setParentId($this->getMetaId()); - $rec->setParentType('meta_technical'); - - return $rec; - } - - /** - * @return int[] - */ - public function getOrCompositeIds(): array - { - return ilMDOrComposite::_getIds($this->getRBACId(), $this->getObjId(), $this->getMetaId(), 'meta_technical'); - } - - public function getOrComposite(int $a_or_composite_id): ?ilMDOrComposite - { - if (!$a_or_composite_id) { - return null; - } - $orc = new ilMDOrComposite($this->getRBACId(), $this->getObjId(), $this->getObjType()); - $orc->setOrCompositeId($a_or_composite_id); - $orc->setParentId($this->getMetaId()); - $orc->setParentType('meta_technical'); - - return $orc; - } - - public function addOrComposite(): ilMDOrComposite - { - $orc = new ilMDOrComposite($this->getRBACId(), $this->getObjId(), $this->getObjType()); - $orc->setParentId($this->getMetaId()); - $orc->setParentType('meta_technical'); - - return $orc; - } - - // SET/GET - public function setSize(string $a_size): void - { - $this->size = $a_size; - } - - public function getSize(): string - { - return $this->size; - } - - public function setInstallationRemarks(string $a_val): void - { - $this->installation_remarks = $a_val; - } - - public function getInstallationRemarks(): string - { - return $this->installation_remarks; - } - - public function setInstallationRemarksLanguage(ilMDLanguageItem $lng_obj): void - { - $this->installation_remarks_language = $lng_obj; - } - - public function getInstallationRemarksLanguage(): ?ilMDLanguageItem - { - return is_object($this->installation_remarks_language) ? $this->installation_remarks_language : null; - } - - public function getInstallationRemarksLanguageCode(): string - { - return is_object($this->installation_remarks_language) ? $this->installation_remarks_language->getLanguageCode() : ''; - } - - public function setOtherPlatformRequirements(string $a_val): void - { - $this->other_platform_requirements = $a_val; - } - - public function getOtherPlatformRequirements(): string - { - return $this->other_platform_requirements; - } - - public function setOtherPlatformRequirementsLanguage(ilMDLanguageItem $lng_obj): void - { - $this->other_platform_requirements_language = $lng_obj; - } - - public function getOtherPlatformRequirementsLanguage(): ?ilMDLanguageItem - { - return is_object($this->other_platform_requirements_language) ? $this->other_platform_requirements_language : null; - } - - public function getOtherPlatformRequirementsLanguageCode(): string - { - return is_object($this->other_platform_requirements_language) - ? $this->other_platform_requirements_language->getLanguageCode() - : ''; - } - - public function setDuration(string $a_val): void - { - $this->duration = $a_val; - } - - public function getDuration(): string - { - return $this->duration; - } - - public function save(): int - { - $fields = $this->__getFields(); - $fields['meta_technical_id'] = array('integer', $next_id = $this->db->nextId('il_meta_technical')); - - if ($this->db->insert('il_meta_technical', $fields)) { - $this->setMetaId($next_id); - return $this->getMetaId(); - } - return 0; - } - - public function update(): bool - { - return $this->getMetaId() && $this->db->update( - 'il_meta_technical', - $this->__getFields(), - array("meta_technical_id" => array('integer', $this->getMetaId())) - ); - } - - public function delete(): bool - { - if ($this->getMetaId()) { - $query = "DELETE FROM il_meta_technical " . - "WHERE meta_technical_id = " . $this->db->quote($this->getMetaId(), 'integer'); - $res = $this->db->manipulate($query); - - foreach ($this->getFormatIds() as $id) { - $for = $this->getFormat($id); - $for->delete(); - } - - foreach ($this->getLocationIds() as $id) { - $loc = $this->getLocation($id); - $loc->delete(); - } - foreach ($this->getRequirementIds() as $id) { - $req = $this->getRequirement($id); - $req->delete(); - } - foreach ($this->getOrCompositeIds() as $id) { - $orc = $this->getOrComposite($id); - $orc->delete(); - } - - return true; - } - return false; - } - - /** - * @return array> - */ - public function __getFields(): array - { - return array( - 'rbac_id' => array('integer', $this->getRBACId()), - 'obj_id' => array('integer', $this->getObjId()), - 'obj_type' => array('text', $this->getObjType()), - 't_size' => array('text', $this->getSize()), - 'ir' => array('text', $this->getInstallationRemarks()), - 'ir_language' => array('text', $this->getInstallationRemarksLanguageCode()), - 'opr' => array('text', $this->getOtherPlatformRequirements()), - 'opr_language' => array('text', $this->getOtherPlatformRequirementsLanguageCode()), - 'duration' => array('text', $this->getDuration()) - ); - } - - public function read(): bool - { - if ($this->getMetaId()) { - $query = "SELECT * FROM il_meta_technical " . - "WHERE meta_technical_id = " . $this->db->quote($this->getMetaId(), 'integer') . " "; - - $res = $this->db->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $this->setRBACId((int) $row->rbac_id); - $this->setObjId((int) $row->obj_id); - $this->setObjType($row->obj_type); - $this->setSize($row->t_size ?? ''); - $this->setInstallationRemarks($row->ir ?? ''); - $this->setInstallationRemarksLanguage(new ilMDLanguageItem($row->ir_language ?? '')); - $this->setOtherPlatformRequirements($row->opr ?? ''); - $this->setOtherPlatformRequirementsLanguage(new ilMDLanguageItem($row->opr_language ?? '')); - $this->setDuration($row->duration ?? ''); - } - return true; - } - return false; - } - - public function toXML(ilXmlWriter $writer): void - { - $writer->xmlStartTag('Technical'); - - // Format - foreach ($this->getFormatIds() as $id) { - $for = $this->getFormat($id); - $for->toXML($writer); - } - - // Size - if ($this->getSize() !== '') { - $writer->xmlElement('Size', null, $this->getSize()); - } - - // Location - foreach ($this->getLocationIds() as $id) { - $loc = $this->getLocation($id); - $loc->toXML($writer); - } - - // Requirement - foreach ($this->getRequirementIds() as $id) { - $req = $this->getRequirement($id); - $req->toXML($writer); - } - - // OrComposite - foreach ($this->getOrCompositeIds() as $id) { - $orc = $this->getOrComposite($id); - $orc->toXML($writer); - } - - // InstallationRemarks - if ($this->getInstallationRemarks() !== '') { - $writer->xmlElement( - 'InstallationRemarks', - array( - 'Language' => $this->getInstallationRemarksLanguageCode() ?: 'en' - ), - $this->getInstallationRemarks() - ); - } - - // OtherPlatformRequirements - if ($this->getOtherPlatformRequirements() !== '') { - $writer->xmlElement( - 'OtherPlatformRequirements', - array( - 'Language' => $this->getOtherPlatformRequirementsLanguageCode() ?: 'en' - ), - $this->getOtherPlatformRequirements() - ); - } - // Duration - if ($this->getDuration() !== '') { - $writer->xmlElement('Duration', null, $this->getDuration()); - } - - $writer->xmlEndTag('Technical'); - } - - public static function _getId(int $a_rbac_id, int $a_obj_id): int - { - global $DIC; - - $ilDB = $DIC->database(); - - $query = "SELECT meta_technical_id FROM il_meta_technical " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . " " . - "AND obj_id = " . $ilDB->quote($a_obj_id, 'integer'); - - $res = $ilDB->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - return (int) $row->meta_technical_id; - } - return 0; - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDTypicalAgeRange.php b/components/ILIAS/MetaData/classes/class.ilMDTypicalAgeRange.php deleted file mode 100755 index b83e8856ad9b..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDTypicalAgeRange.php +++ /dev/null @@ -1,217 +0,0 @@ -typical_age_range = $a_typical_age_range; - } - - public function getTypicalAgeRange(): string - { - return $this->typical_age_range; - } - - public function setTypicalAgeRangeLanguage(ilMDLanguageItem $lng_obj): void - { - $this->typical_age_range_language = $lng_obj; - } - - public function getTypicalAgeRangeLanguage(): ?ilMDLanguageItem - { - return is_object($this->typical_age_range_language) ? $this->typical_age_range_language : null; - } - - public function getTypicalAgeRangeLanguageCode(): string - { - return is_object($this->typical_age_range_language) ? $this->typical_age_range_language->getLanguageCode() : ''; - } - - public function setTypicalAgeRangeMinimum(string $a_min): void - { - $this->typical_age_range_minimum = $a_min; - } - - public function getTypicalAgeRangeMinimum(): string - { - return $this->typical_age_range_minimum; - } - - public function setTypicalAgeRangeMaximum(string $a_max): void - { - $this->typical_age_range_maximum = $a_max; - } - - public function getTypicalAgeRangeMaximum(): string - { - return $this->typical_age_range_maximum; - } - - public function save(): int - { - $fields = $this->__getFields(); - $fields['meta_tar_id'] = array('integer', $next_id = $this->db->nextId('il_meta_tar')); - - if ($this->db->insert('il_meta_tar', $fields)) { - $this->setMetaId($next_id); - return $this->getMetaId(); - } - return 0; - } - - public function update(): bool - { - $this->__parseTypicalAgeRange(); - - return $this->getMetaId() && $this->db->update( - 'il_meta_tar', - $this->__getFields(), - array("meta_tar_id" => array('integer', $this->getMetaId())) - ); - } - - public function delete(): bool - { - if ($this->getMetaId()) { - $query = "DELETE FROM il_meta_tar " . - "WHERE meta_tar_id = " . $this->db->quote($this->getMetaId(), 'integer'); - $res = $this->db->manipulate($query); - return true; - } - return false; - } - - /** - * @return array> - */ - public function __getFields(): array - { - return array( - 'rbac_id' => array('integer', $this->getRBACId()), - 'obj_id' => array('integer', $this->getObjId()), - 'obj_type' => array('text', $this->getObjType()), - 'parent_type' => array('text', $this->getParentType()), - 'parent_id' => array('integer', $this->getParentId()), - 'typical_age_range' => array('text', $this->getTypicalAgeRange()), - 'tar_language' => array('text', $this->getTypicalAgeRangeLanguageCode()), - 'tar_min' => array('text', $this->getTypicalAgeRangeMinimum()), - 'tar_max' => array('text', $this->getTypicalAgeRangeMaximum()) - ); - } - - public function read(): bool - { - if ($this->getMetaId()) { - $query = "SELECT * FROM il_meta_tar " . - "WHERE meta_tar_id = " . $this->db->quote($this->getMetaId(), 'integer'); - - $res = $this->db->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $this->setRBACId((int) $row->rbac_id); - $this->setObjId((int) $row->obj_id); - $this->setObjType($row->obj_type); - $this->setParentId((int) $row->parent_id); - $this->setParentType((string) $row->parent_type); - $this->setTypicalAgeRange((string) $row->typical_age_range); - $this->setTypicalAgeRangeLanguage(new ilMDLanguageItem($row->tar_language ?? '')); - $this->setTypicalAgeRangeMinimum((string) $row->tar_min); - $this->setTypicalAgeRangeMaximum((string) $row->tar_max); - } - } - return true; - } - - public function toXML(ilXmlWriter $writer): void - { - $writer->xmlElement( - 'TypicalAgeRange', - array( - 'Language' => $this->getTypicalAgeRangeLanguageCode() ?: 'en' - ), - $this->getTypicalAgeRange() - ); - } - - // STATIC - - /** - * @return int[] - */ - public static function _getIds(int $a_rbac_id, int $a_obj_id, int $a_parent_id, string $a_parent_type): array - { - global $DIC; - - $ilDB = $DIC->database(); - - $query = "SELECT meta_tar_id FROM il_meta_tar " . - "WHERE rbac_id = " . $ilDB->quote($a_rbac_id, 'integer') . " " . - "AND obj_id = " . $ilDB->quote($a_obj_id, 'integer') . " " . - "AND parent_id = " . $ilDB->quote($a_parent_id, 'integer') . " " . - "AND parent_type = " . $ilDB->quote($a_parent_type, 'text'); - - $res = $ilDB->query($query); - $ids = []; - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $ids[] = (int) $row->meta_tar_id; - } - - return $ids; - } - - // PRIVATE - public function __parseTypicalAgeRange(): bool - { - if (preg_match("/\s*(\d*)\s*(-?)\s*(\d*)/", $this->getTypicalAgeRange(), $matches)) { - if (!$matches[2] and !$matches[3]) { - $min = $max = $matches[1]; - } elseif ($matches[2] and !$matches[3]) { - $min = $matches[1]; - $max = 99; - } else { - $min = $matches[1]; - $max = $matches[3]; - } - $this->setTypicalAgeRangeMaximum((string) $max); - $this->setTypicalAgeRangeMinimum((string) $min); - - return true; - } - - if (!$this->getTypicalAgeRange()) { - $this->setTypicalAgeRangeMinimum('-1'); - $this->setTypicalAgeRangeMaximum('-1'); - } - - return true; - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDUtilSelect.php b/components/ILIAS/MetaData/classes/class.ilMDUtilSelect.php deleted file mode 100755 index 094ad5406424..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDUtilSelect.php +++ /dev/null @@ -1,829 +0,0 @@ - - * @package ilias-core - * @version $Id$ - * @deprecated will be removed with ILIAS 11, please use the new API (see {@see ../docs/api.md}) - */ -class ilMDUtilSelect -{ - /** - * Prepare a meta data language selector - * @param array $prepend array(value => 'string') of first item. E.g: array(0,'-Please select-') - * @return string|array Complete html select - */ - // BEGIN PATCH Lucene search - public static function _getLanguageSelect( - string $a_selected, - string $a_name, - array $prepend = array(), - bool $a_options_only = false - ) { // END PATCH Lucene Search - global $DIC; - - $lng = $DIC['lng']; - - foreach (ilMDLanguageItem::_getPossibleLanguageCodes() as $code) { - $tmp_options[$code] = $lng->txt('meta_l_' . $code); - } - asort($tmp_options, SORT_STRING); - - $options = []; - foreach ($prepend as $value => $translation) { - $options[$value] = $translation; - } - $options = array_merge($options, $tmp_options); - // BEGIN PATCH Lucene search - return $a_options_only ? $options : ilLegacyFormElementsUtil::formSelect( - $a_selected, - $a_name, - $options, - false, - true - ); - // END PATCH Lucene Search - } - - /** - * Prepare a meta general structure selector - * @param array $prepend array(value => 'string') of first item. E.g: array(0,'-Please select-') - * @return string|array Complete html select - */ - // BEGIN PATCH Lucene search - public static function _getStructureSelect( - string $a_selected, - string $a_name, - array $prepend = array(), - bool $a_options_only = false - ) { // END PATCH Lucene Search - global $DIC; - - $lng = $DIC['lng']; - - $items = array('Atomic', 'Collection', 'Networked', 'Hierarchical', 'Linear'); - - foreach ($prepend as $value => $translation) { - $options[$value] = $translation; - } - - foreach ($items as $item) { - $options[$item] = $lng->txt('meta_' . strtolower($item)); - } - // BEGIN PATCH Lucene search - return $a_options_only ? $options : ilLegacyFormElementsUtil::formSelect( - $a_selected, - $a_name, - $options, - false, - true - ); - // END PATCH Lucene Search - } - - /** - * Prepare a meta lifecycle status selector - * @param array $prepend array(value => 'string') of first item. E.g: array(0,'-Please select-') - * @return string|array Complete html select - */ - // BEGIN PATCH Lucene search - public static function _getStatusSelect( - string $a_selected, - string $a_name, - array $prepend = array(), - bool $a_options_only = false - ) { // END PATCH Lucene Search - global $DIC; - - $lng = $DIC['lng']; - - $items = array('Draft', 'Final', 'Revised', 'Unavailable'); - - foreach ($prepend as $value => $translation) { - $options[$value] = $translation; - } - - foreach ($items as $item) { - $options[$item] = $lng->txt('meta_' . strtolower($item)); - } - // BEGIN PATCH Lucene search - return $a_options_only ? $options : ilLegacyFormElementsUtil::formSelect( - $a_selected, - $a_name, - $options, - false, - true - ); - // END PATCH Lucene Search - } - - /** - * Prepare a meta lifecycle status selector - * @param array $prepend array(value => 'string') of first item. E.g: array(0,'-Please select-') - * @return string|array Complete html select - */ - // BEGIN PATCH Lucene search - public static function _getRoleSelect( - string $a_selected, - string $a_name, - array $prepend = array(), - bool $a_options_only = false - ) { // END PATCH Lucene Search - global $DIC; - - $lng = $DIC['lng']; - - $items = array( - 'Author', - 'Publisher', - 'Unknown', - 'Initiator', - 'Terminator', - 'Editor', - 'GraphicalDesigner', - 'TechnicalImplementer', - 'ContentProvider', - 'TechnicalValidator', - 'EducationalValidator', - 'ScriptWriter', - 'InstructionalDesigner', - 'SubjectMatterExpert', - 'Creator', - 'Validator', - 'PointOfContact' - ); - - foreach ($prepend as $value => $translation) { - $options[$value] = $translation; - } - - foreach ($items as $item) { - $options[$item] = $lng->txt('meta_' . strtolower($item)); - } - // BEGIN PATCH Lucene search - return $a_options_only ? $options : ilLegacyFormElementsUtil::formSelect( - $a_selected, - $a_name, - $options, - false, - true - ); - // END PATCH Lucene Search - } - - /** - * Prepare a meta technical os selector - * @param array $prepend array(value => 'string') of first item. E.g: array(0,'-Please select-') - * @return string|array Complete html select - */ - // BEGIN PATCH Lucene search - public static function _getOperatingSystemSelect( - string $a_selected, - string $a_name, - array $prepend = array(), - bool $a_options_only = false - ) { // END PATCH Lucene Search - global $DIC; - - $lng = $DIC['lng']; - - $items = array('PC-DOS', 'MS-Windows', 'MAC-OS', 'Unix', 'Multi-OS', 'None'); - - foreach ($prepend as $value => $translation) { - $options[$value] = $translation; - } - - foreach ($items as $item) { - $options[$item] = $item; - } - // BEGIN PATCH Lucene search - return $a_options_only ? $options : ilLegacyFormElementsUtil::formSelect( - $a_selected, - $a_name, - $options, - false, - true - ); - // END PATCH Lucene Search - } - - /** - * Prepare a meta technical browser selector - * @param array $prepend array(value => 'string') of first item. E.g: array(0,'-Please select-') - * @return string|array Complete html select - */ - // BEGIN PATCH Lucene search - public static function _getBrowserSelect( - string $a_selected, - string $a_name, - array $prepend = array(), - bool $a_options_only = false - ) { // END PATCH Lucene Search - global $DIC; - - $lng = $DIC['lng']; - - $items = array('Any', 'NetscapeCommunicator', 'MS-InternetExplorer', 'Opera', 'Amaya', 'Mozilla'); - - foreach ($prepend as $value => $translation) { - $options[$value] = $translation; - } - - foreach ($items as $item) { - $options[$item] = $item; - } - // BEGIN PATCH Lucene search - return $a_options_only ? $options : ilLegacyFormElementsUtil::formSelect( - $a_selected, - $a_name, - $options, - false, - true - ); - // END PATCH Lucene Search - } - /** - * Prepare a meta technical format selector - * All possible entries in meta_format are shown - * @param array $prepend array(value => 'string') of first item. E.g: array(0,'-Please select-') - * @return string|array Complete html select - */ - // BEGIN PATCH Lucene search - public static function _getFormatSelect( - string $a_selected, - string $a_name, - array $prepend = array(), - bool $a_options_only = false - ) { // END PATCH Lucene Search - global $DIC; - - $lng = $DIC['lng']; - $ilDB = $DIC['ilDB']; - - $options = []; - foreach ($prepend as $value => $translation) { - $options[$value] = $translation; - } - - $ilDB->setLimit(200, 0); - // In case an index is defined on field il_meta_format, this group by - // statement takes advantage of it to improve the performance of the query. - $query = "SELECT format AS forma from il_meta_format GROUP BY format"; - $res = $ilDB->query($query); - if (!$res->numRows()) { - return ''; - } - - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - if (is_string($row->forma) && $row->forma !== '') { - $options[$row->forma] = substr($row->forma, 0, 48); - } - } - - // BEGIN PATCH Lucene search - return $a_options_only ? $options : ilLegacyFormElementsUtil::formSelect( - $a_selected, - $a_name, - $options, - false, - true - ); - // END PATCH Lucene Search - } - - /** - * Prepare a meta technical duration selector - * All possible entries in meta_format are shown - * @param array $prepend array(value => 'string') of first item. E.g: array(0,'-Please select-') - */ - public static function _getDurationSelect(string $a_selected, string $a_name, array $prepend = array()): string - { - global $DIC; - - $lng = $DIC['lng']; - - foreach ($prepend as $value => $translation) { - $options[$value] = $translation; - } - - $items = array( - 15 => '15 ' . $lng->txt('minutes'), - 30 => '30 ' . $lng->txt('minutes'), - 45 => '45 ' . $lng->txt('minutes'), - 60 => '1 ' . $lng->txt('hour'), - 90 => '1 ' . $lng->txt('hour') . ' 30 ' . $lng->txt('minutes'), - 120 => '2 ' . $lng->txt('hours'), - 180 => '3 ' . $lng->txt('hours'), - 240 => '4 ' . $lng->txt('hours') - ); - - foreach ($items as $key => $item) { - $options[$key] = $item; - } - return ilLegacyFormElementsUtil::formSelect($a_selected, $a_name, $options, false, true); - } - - /** - * Prepare a meta educational interactivity type - * All possible entries in meta_format are shown - * @param array $prepend array(value => 'string') of first item. E.g: array(0,'-Please select-') - * @return string|array Complete html select - */ - // BEGIN PATCH Lucene search - public static function _getInteractivityTypeSelect( - string $a_selected, - string $a_name, - array $prepend = array(), - bool $a_options_only = false - ) { // END PATCH Lucene Search - global $DIC; - - $lng = $DIC['lng']; - - $items = array('Actice', 'Expositive', 'Mixed'); - - foreach ($prepend as $value => $translation) { - $options[$value] = $translation; - } - - foreach ($items as $item) { - $options[$item] = $item; - } - // BEGIN PATCH Lucene search - return $a_options_only ? $options : ilLegacyFormElementsUtil::formSelect( - $a_selected, - $a_name, - $options, - false, - true - ); - // END PATCH Lucene Search - } - - /** - * Prepare a meta educational learning resource type - * All possible entries in meta_format are shown - * @param array $prepend array(value => 'string') of first item. E.g: array(0,'-Please select-') - * @return string|array Complete html select - */ - // BEGIN PATCH Lucene search - public static function _getLearningResourceTypeSelect( - string $a_selected, - string $a_name, - array $prepend = array(), - bool $a_options_only = false - ) { // END PATCH Lucene Search - global $DIC; - - $lng = $DIC['lng']; - - $items = array( - 'Exercise', - 'Simulation', - 'Questionnaire', - 'Diagram', - 'Figure', - 'Graph', - 'Index', - 'Slide', - 'Table', - 'NarrativeText', - 'Exam', - 'Experiment', - 'ProblemStatement', - 'SelfAssessment', - 'Lecture' - ); - - foreach ($prepend as $value => $translation) { - $options[$value] = $translation; - } - - foreach ($items as $item) { - $options[$item] = $item; - } - // BEGIN PATCH Lucene search - return $a_options_only ? $options : ilLegacyFormElementsUtil::formSelect( - $a_selected, - $a_name, - $options, - false, - true - ); - // END PATCH Lucene Search - } - - /** - * Prepare a meta educational interactivity level - * All possible entries in meta_format are shown - * @param mixed $a_selected - * @param string $a_name - * @param array $prepend - * @param bool $a_options_only - * @return array|string - */ - public static function _getInteractivityLevelSelect( - $a_selected, - string $a_name, - array $prepend = array(), - bool $a_options_only = false - ) { - global $DIC; - - $lng = $DIC['lng']; - - $items = array(1 => 'VeryLow', 2 => 'Low', 3 => 'Medium', 4 => 'High', 5 => 'VeryHigh'); - - foreach ($prepend as $value => $translation) { - $options[$value] = $translation; - } - - foreach ($items as $key => $item) { - $options[$key] = $item; - } - // BEGIN PATCH Lucene search - return $a_options_only ? $options : ilLegacyFormElementsUtil::formSelect( - $a_selected, - $a_name, - $options, - false, - true - ); - // END PATCH Lucene Search - } - - /** - * Prepare a meta educational semantic density - * All possible entries in meta_format are shown - * @param mixed $a_selected - * @param string $a_name - * @param array $prepend - * @param bool $a_options_only - * @return array|string - */ - public static function _getSemanticDensitySelect( - $a_selected, - string $a_name, - array $prepend = array(), - bool $a_options_only = false - ) { // END PATCH Lucene Search - global $DIC; - - $lng = $DIC['lng']; - - $items = array(1 => 'VeryLow', 2 => 'Low', 3 => 'Medium', 4 => 'High', 5 => 'VeryHigh'); - - foreach ($prepend as $value => $translation) { - $options[$value] = $translation; - } - - foreach ($items as $key => $item) { - $options[$key] = $item; - } - // BEGIN PATCH Lucene search - return $a_options_only ? $options : ilLegacyFormElementsUtil::formSelect( - $a_selected, - $a_name, - $options, - false, - true - ); - // END PATCH Lucene Search - } - - /** - * Prepare a meta educational intended end user role - * All possible entries in meta_format are shown - * @param array $prepend array(value => 'string') of first item. E.g: array(0,'-Please select-') - * @return string|array Complete html select - */ - // BEGIN PATCH Lucene search - public static function _getIntendedEndUserRoleSelect( - string $a_selected, - string $a_name, - array $prepend = array(), - bool $a_options_only = false - ) { // END PATCH Lucene Search - global $DIC; - - $lng = $DIC['lng']; - - $items = array('Teacher', 'Author', 'Learner', 'Manager'); - - foreach ($prepend as $value => $translation) { - $options[$value] = $translation; - } - - foreach ($items as $item) { - $options[$item] = $item; - } - // BEGIN PATCH Lucene search - return $a_options_only ? $options : ilLegacyFormElementsUtil::formSelect( - $a_selected, - $a_name, - $options, - false, - true - ); - // END PATCH Lucene Search - } - - /** - * Prepare a meta context - * @param array $prepend array(value => 'string') of first item. E.g: array(0,'-Please select-') - * @return string|array Complete html select - */ - // BEGIN PATCH Lucene search - public static function _getContextSelect( - string $a_selected, - string $a_name, - array $prepend = array(), - bool $a_options_only = false - ) { // END PATCH Lucene Search - global $DIC; - - $lng = $DIC['lng']; - - $items = array('School', 'HigherEducation', 'Training', 'Other'); - - foreach ($prepend as $value => $translation) { - $options[$value] = $translation; - } - - foreach ($items as $item) { - $options[$item] = $item; - } - // BEGIN PATCH Lucene search - return $a_options_only ? $options : ilLegacyFormElementsUtil::formSelect( - $a_selected, - $a_name, - $options, - false, - true - ); - // END PATCH Lucene Search - } - - /** - * Prepare a meta location type - * @param array $prepend array(value => 'string') of first item. E.g: array(0,'-Please select-') - */ - public static function _getLocationTypeSelect(string $a_selected, string $a_name, array $prepend = array()): string - { - global $DIC; - - $lng = $DIC['lng']; - - $items = array('LocalFile', 'Reference'); - - foreach ($prepend as $value => $translation) { - $options[$value] = $translation; - } - - foreach ($items as $item) { - $options[$item] = $item; - } - return ilLegacyFormElementsUtil::formSelect($a_selected, $a_name, $options, false, true); - } - - /** - * Prepare a meta educational difficulty - * All possible entries in meta_format are shown - * @param mixed $a_selected - * @param string $a_name - * @param array $prepend - * @param bool $a_options_only - * @return array|string - */ - public static function _getDifficultySelect( - $a_selected, - string $a_name, - array $prepend = array(), - bool $a_options_only = false - ) { // END PATCH Lucene Search - global $DIC; - - $lng = $DIC['lng']; - - $items = array(1 => 'VeryEasy', 2 => 'Easy', 3 => 'Medium', 4 => 'Difficult', 5 => 'VeryDifficult'); - - foreach ($prepend as $value => $translation) { - $options[$value] = $translation; - } - - foreach ($items as $key => $item) { - $options[$key] = $item; - } - // BEGIN PATCH Lucene search - return $a_options_only ? $options : ilLegacyFormElementsUtil::formSelect( - $a_selected, - $a_name, - $options, - false, - true - ); - // END PATCH Lucene Search - } - - /** - * Prepare a meta educational typical age range - * All possible entries in meta_format are shown - * @param array $prepend array(value => 'string') of first item. E.g: array(0,'-Please select-') - */ - public static function _getTypicalAgeRangeSelect( - string $a_selected, - string $a_name, - array $prepend = array() - ): string { - global $DIC; - - $lng = $DIC['lng']; - - $options = []; - foreach ($prepend as $value => $translation) { - $options[$value] = $translation; - } - $items = []; - for ($i = 1; $i < 100; $i++) { - $items[$i] = $i; - } - foreach ($items as $key => $item) { - $options[$key] = $item; - } - return ilLegacyFormElementsUtil::formSelect($a_selected, $a_name, $options, false, true); - } - - /** - * Prepare a meta educational typical learning time - * All possible entries in meta_format are shown - * @param array $prepend array(value => 'string') of first item. E.g: array(0,'-Please select-') - */ - public static function _getTypicalLearningTimeSelect( - string $a_selected, - string $a_name, - array $prepend = array() - ): string { - global $DIC; - - $lng = $DIC['lng']; - - foreach ($prepend as $value => $translation) { - $options[$value] = $translation; - } - $items = array( - 15 => '15 ' . $lng->txt('minutes'), - 30 => '30 ' . $lng->txt('minutes'), - 45 => '45 ' . $lng->txt('minutes'), - 60 => '1 ' . $lng->txt('hour'), - 90 => '1 ' . $lng->txt('hour') . ' 30 ' . $lng->txt('minutes'), - 120 => '2 ' . $lng->txt('hours'), - 180 => '3 ' . $lng->txt('hours'), - 240 => '4 ' . $lng->txt('hours') - ); - - foreach ($items as $key => $item) { - $options[$key] = $item; - } - return ilLegacyFormElementsUtil::formSelect($a_selected, $a_name, $options, false, true); - } - - /** - * Prepare a meta rights costs - * All possible entries in meta_format are shown - * @param array $prepend array(value => 'string') of first item. E.g: array(0,'-Please select-') - * @return string|array Complete html select - */ - // BEGIN PATCH Lucene search - public static function _getCostsSelect( - string $a_selected, - string $a_name, - array $prepend = array(), - bool $a_options_only = false - ) { // END PATCH Lucene Search - global $DIC; - - $lng = $DIC['lng']; - - $items = array('Yes', 'No'); - - foreach ($prepend as $value => $translation) { - $options[$value] = $translation; - } - - foreach ($items as $item) { - $options[$item] = $item; - } - // BEGIN PATCH Lucene search - return $a_options_only ? $options : ilLegacyFormElementsUtil::formSelect( - $a_selected, - $a_name, - $options, - false, - true - ); - // END PATCH Lucene Search - } - - /** - * Prepare a meta rights copyright and other restrictions - * All possible entries in meta_format are shown - * @param array $prepend array(value => 'string') of first item. E.g: array(0,'-Please select-') - * @return string|array Complete html select - */ - // BEGIN PATCH Lucene search - public static function _getCopyrightAndOtherRestrictionsSelect( - string $a_selected, - string $a_name, - array $prepend = array(), - bool $a_options_only = false - ) { // END PATCH Lucene Search - global $DIC; - - $lng = $DIC['lng']; - - $items = array('Yes', 'No'); - - foreach ($prepend as $value => $translation) { - $options[$value] = $translation; - } - - foreach ($items as $item) { - $options[$item] = $item; - } - // BEGIN PATCH Lucene search - return $a_options_only ? $options : ilLegacyFormElementsUtil::formSelect( - $a_selected, - $a_name, - $options, - false, - true - ); - // END PATCH Lucene Search - } - - /** - * Prepare a meta rights copyright and other restrictions - * All possible entries in meta_format are shown - * @param array $prepend array(value => 'string') of first item. E.g: array(0,'-Please select-') - * @return string|array Complete html select - */ - // BEGIN PATCH Lucene search - public static function _getPurposeSelect( - string $a_selected, - string $a_name, - array $prepend = array(), - bool $a_options_only = false - ) { // END PATCH Lucene Search - global $DIC; - - $lng = $DIC['lng']; - - $items = array( - 'Discipline', - 'Idea', - 'Prerequisite', - 'EducationalObjective', - 'AccessibilityRestrictions', - 'EducationalLevel', - 'SkillLevel', - 'SecurityLevel', - 'Competency' - ); - - foreach ($prepend as $value => $translation) { - $options[$value] = $translation; - } - - foreach ($items as $item) { - $options[$item] = $item; - } - // BEGIN PATCH Lucene search - return $a_options_only ? $options : ilLegacyFormElementsUtil::formSelect( - $a_selected, - $a_name, - $options, - false, - true - ); - // END PATCH Lucene Search - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDUtils.php b/components/ILIAS/MetaData/classes/class.ilMDUtils.php deleted file mode 100755 index 059f4165ee1d..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDUtils.php +++ /dev/null @@ -1,103 +0,0 @@ - - * @package ilias-core - * @version $Id$ - * @deprecated will be removed with ILIAS 11, please use the new API (see {@see ../docs/api.md}) - */ -class ilMDUtils -{ - /** - * LOM datatype duration is a string like P2M4DT7H18M2S (2 months 4 days 7 hours 18 minutes 2 seconds) - * This function tries to parse a given string in an array of months, days, hours, minutes and seconds - * @return int[] e.g array(1,2,0,1,2) => 1 month,2 days, 0 hours, 1 minute, 2 seconds or empty array if not parsable - * @deprecated use DataHelper::durationToArray - */ - public static function _LOMDurationToArray(string $a_string): array - { - global $DIC; - - $data_helper = $DIC->learningObjectMetadata()->dataHelper(); - - $array = $data_helper->durationToArray($a_string); - // this function never returned the year, so we throw it away for backwards compatibility - array_shift($array); - return $array ?? []; - } - - public static function _fillHTMLMetaTags(int $a_rbac_id, int $a_obj_id, string $a_type): bool - { - global $DIC; - - // currently disabled due to mantis 0026864 - return true; - - $tpl = $DIC['tpl']; - $ilObjDataCache = $DIC['ilObjDataCache']; - - foreach (ilMDKeyword::_getKeywordsByLanguageAsString( - $a_rbac_id, - $a_obj_id, - $a_type - ) as $lng_code => $key_string) { - $tpl->setCurrentBlock('mh_meta_item'); - $tpl->setVariable('MH_META_NAME', 'keywords'); - $tpl->setVariable('MH_META_LANG', $lng_code); - $tpl->setVariable('MH_META_CONTENT', $key_string); - $tpl->parseCurrentBlock(); - } - - foreach (ilMDContribute::_lookupAuthors($a_rbac_id, $a_obj_id, $a_type) as $author) { - $tpl->setCurrentBlock('mh_meta_item'); - $tpl->setVariable('MH_META_NAME', 'author'); - $tpl->setVariable('MH_META_CONTENT', $author); - $tpl->parseCurrentBlock(); - } - return true; - } - - /** - * Returns an empty string if copyright selection is not active, - * regardless of input. - */ - public static function _parseCopyright(string $a_copyright): string - { - $settings = ilMDSettings::_getInstance(); - if (!$settings->isCopyrightSelectionActive()) { - return ilMDCopyrightSelectionEntry::isEntry($a_copyright) ? '' : $a_copyright; - } - - return ilMDCopyrightSelectionEntry::_lookupCopyright($a_copyright); - } - - /** - * Returns an empty string if copyright selection is not active. - */ - public static function _getDefaultCopyright(): string - { - $default_id = ilMDCopyrightSelectionEntry::getDefault(); - return self::_parseCopyright( - ilMDCopyrightSelectionEntry::createIdentifier($default_id) - ); - } -} diff --git a/components/ILIAS/MetaData/classes/class.ilMDXMLCopier.php b/components/ILIAS/MetaData/classes/class.ilMDXMLCopier.php deleted file mode 100755 index 13d1a6be425d..000000000000 --- a/components/ILIAS/MetaData/classes/class.ilMDXMLCopier.php +++ /dev/null @@ -1,130 +0,0 @@ - - * @version $Id$ - * @deprecated will be removed with ILIAS 11, LOM should only be exported as a tail dependency - */ -class ilMDXMLCopier extends ilMDSaxParser -{ - private array $filter = []; - protected bool $in_meta_data = false; - - public function __construct($content, $a_rbac_id, $a_obj_id, $a_obj_type) - { - $this->setMDObject(new ilMD($a_rbac_id, $a_obj_id, $a_obj_type)); - - parent::__construct(); - $this->setXMLContent($content); - - // set filter of tags which are handled in this class - $this->__setFilter(); - } - - public function startParsing(): void - { - // delete existing entries from creations process - $clone_md = $this->getMDObject(); - $clone_md->deleteAll(); - - // rewrite autogenerated entry - $identifier = new ilMDIdentifier( - $clone_md->getRBACId(), - $clone_md->getObjId(), - $clone_md->getObjType(), - ); - $identifier->setEntry('il__' . $clone_md->getObjType() . '_' . $clone_md->getObjId()); - $identifier->update(); - - parent::startParsing(); - } - - /** - * @param XMLParser|resource $a_xml_parser reference to the xml parser - */ - public function handlerBeginTag($a_xml_parser, string $a_name, array $a_attribs): void - { - if ($this->in_meta_data && !$this->__inFilter($a_name)) { - parent::handlerBeginTag($a_xml_parser, $a_name, $a_attribs); - return; - } - - switch ($a_name) { - case 'MetaData': - $this->in_meta_data = true; - parent::handlerBeginTag($a_xml_parser, $a_name, $a_attribs); - break; - - case 'Identifier': - $par = $this->__getParent(); - $this->md_ide = $par->addIdentifier(); - $this->md_ide->setCatalog($a_attribs['Catalog'] ?? ''); - $this->md_ide->setEntry('il__' . $this->md->getObjType() . '_' . $this->md->getObjId()); - $this->md_ide->save(); - $this->__pushParent($this->md_ide); - break; - } - } - - /** - * @param XMLParser|resource $a_xml_parser reference to the xml parser - */ - public function handlerEndTag($a_xml_parser, string $a_name): void - { - if ($this->in_meta_data && !$this->__inFilter($a_name)) { - parent::handlerEndTag($a_xml_parser, $a_name); - return; - } - switch ($a_name) { - case 'Identifier': - $par = $this->__getParent(); - $par->update(); - $this->__popParent(); - break; - - case 'MetaData': - $this->in_meta_data = false; - parent::handlerEndTag($a_xml_parser, $a_name); - break; - } - } - - /** - * @param XMLParser|resource $a_xml_parser reference to the xml parser - */ - public function handlerCharacterData($a_xml_parser, string $a_data): void - { - if ($this->in_meta_data) { - parent::handlerCharacterData($a_xml_parser, $a_data); - } - } - - public function __setFilter(): void - { - $this->filter[] = 'Identifier'; - } - - public function __inFilter(string $a_tag_name): bool - { - return in_array($a_tag_name, $this->filter, true); - } -} diff --git a/components/ILIAS/MetaData/docs/enabling_lom.md b/components/ILIAS/MetaData/docs/enabling_lom.md index 7d8937f316d9..84a71b03d7cc 100755 --- a/components/ILIAS/MetaData/docs/enabling_lom.md +++ b/components/ILIAS/MetaData/docs/enabling_lom.md @@ -122,8 +122,8 @@ In `ilObjMediaObjectGUI::executeCommand`: ### Create LOM Sets for Pre-Existing Objects To avoid inconsistencies and problems e.g. in export/import, LOM sets -also need to be created for already existing objects. For this, the -To this end, add a migration to your component extending +also need to be created for already existing objects. To this end, +add a migration to your component extending `ILIAS\MetaData\Setup\InitLOMForObjectTypeMigration`. ### Show LOM on Info Screen diff --git a/components/ILIAS/MetaData/docs/oaipmh.md b/components/ILIAS/MetaData/docs/oaipmh.md index cd40c6203561..2224a1a01447 100644 --- a/components/ILIAS/MetaData/docs/oaipmh.md +++ b/components/ILIAS/MetaData/docs/oaipmh.md @@ -191,12 +191,12 @@ are derived as follows: | **DC Element** | **From LOM Element(s)** | **No. of Occurences** | **Additional Information** | |----------------|-----------------------------------------------------------------------------------------------------------|-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | title | `general > title > string` | 1 | with `general > title > language` as `xml:lang` attribute | -| creator | `entity` of `lifecycle > contribute` where `role > value` is 'author' | any | order of authors will be respected | +| creator | `entity` of `lifecycle > contribute` where `role > value` is 'author' | any | order of authors is respected | | subject | `general > keyword > string`, and `taxonPath` of `classification` where `purpose > value` is 'discipline' | any | with corresponding `general > keyword > language` as `xml:lang` attribute, and each `taxonPath` represented by colon-separated `taxon > entry > strings` | | description | `general > description > string` | any | with corresponding `general > description > language` as `xml:lang` attribute | -| publisher | `entity` of `lifecycle > contribute` where `role > value` is 'publisher' | any | order of publishers will be respected | -| contributor | `entity` of `lifecycle > contribute` where `role > value` is not 'author' or 'publisher' | any | order of contributors will be respected | -| date | first `lifecycle > contribute > date` | 0 or 1 | will be in `YYYY-MM-DD` format | +| publisher | `entity` of `lifecycle > contribute` where `role > value` is 'publisher' | any | order of publishers is respected | +| contributor | `entity` of `lifecycle > contribute` where `role > value` is not 'author' or 'publisher' | any | order of contributors is respected, the role is appended in brackets | +| date | first `lifecycle > contribute > date` | 0 or 1 | in `YYYY-MM-DD` format | | type | `educational > learningResourceType > value` | any | | | format | `technical > format` | any | | | identifier | - | 1 or 2 | The first identifier always contains static link to the Object in the Category for published OER. If the Object has a 'Public Access' export file, a download link to the file is included in a second identifier. | diff --git a/components/ILIAS/MetaData/docs/vocabularies.md b/components/ILIAS/MetaData/docs/vocabularies.md index cb8d66647379..1998b80d1757 100644 --- a/components/ILIAS/MetaData/docs/vocabularies.md +++ b/components/ILIAS/MetaData/docs/vocabularies.md @@ -117,6 +117,8 @@ and can be omitted when the values are already human-readable. Before a vocabulary is created, a validation is performed to make sure that the vocabulary conforms to the restrictions laid out [below](#controlled-vocabularies). +Note that the order of values is not important, and will not be +respected after import. Some vocabularies only apply when a different LOM element than the one the vocabulary applies to carries a certain value. Such a relationship @@ -193,6 +195,10 @@ a vocabulary cannot contain no values, and multiple vocabularies of type contain the same value. Further, the source of these vocabularies cannot be `LOMv1.0`. +Note that values from controlled vocabularies are shown in the LOM editor +ordered alphabetically by their label (or the value itself if there is +no label). + #### Applicable Elements Controlled vocabularies can be applied to almost all elements of type @@ -383,4 +389,4 @@ isrequiredby discipline, idea, prerequisite, educational objective, accessibility restrictions, educational level, skill level, security level, competency -```` \ No newline at end of file +```` diff --git a/components/ILIAS/MetaData/tests/Copyright/RendererTest.php b/components/ILIAS/MetaData/tests/Copyright/RendererTest.php index b3b3273858c5..33f00cc209eb 100755 --- a/components/ILIAS/MetaData/tests/Copyright/RendererTest.php +++ b/components/ILIAS/MetaData/tests/Copyright/RendererTest.php @@ -43,6 +43,12 @@ protected function getMockRenderer( string $src_from_irss ): Renderer { return new class ($icon, $link, $legacy, $src_from_irss) extends Renderer { + protected ?string $icon_src = null; + protected ?string $icon_alt = null; + protected ?string $link_label = null; + protected ?string $link_action = null; + protected ?string $legacy_text = null; + public function __construct( protected Icon $icon, protected Link $link, @@ -58,22 +64,21 @@ protected function getFallBackSrc(): string protected function customIcon(string $src, string $alt): Icon { - /** @noinspection PhpUndefinedMethodInspection */ - $this->icon->checkParams($src, $alt); + $this->icon_src = $src; + $this->icon_alt = $alt; return $this->icon; } protected function standardLink(string $label, string $action): Link { - /** @noinspection PhpUndefinedMethodInspection */ - $this->link->checkParams($label, $action); + $this->link_label = $label; + $this->link_action = $action; return $this->link; } protected function textInLegacy(string $text): Content { - /** @noinspection PhpUndefinedMethodInspection */ - $this->legacy->checkParams($text); + $this->legacy_text = $text; return $this->legacy; } @@ -81,6 +86,17 @@ protected function getSourceFromIRSS(string $string_id): string { return $this->src_from_irss; } + + public function exposeData(): array + { + return [ + 'icon_src' => $this->icon_src, + 'icon_alt' => $this->icon_alt, + 'link_label' => $this->link_label, + 'link_action' => $this->link_action, + 'legacy_text' => $this->legacy_text + ]; + } }; } @@ -88,7 +104,6 @@ protected function getMockIcon(): MockObject|Icon { return $this->getMockBuilder(IIcon::class) ->disableOriginalConstructor() - ->addMethods(['checkParams']) ->getMock(); } @@ -97,7 +112,6 @@ protected function getMockLink(): MockObject|Link return $this->getMockBuilder(ILink::class) ->disableOriginalConstructor() ->onlyMethods(['withAdditionalRelationshipToReferencedResource']) - ->addMethods(['checkParams']) ->getMock(); } @@ -105,7 +119,6 @@ protected function getMockLegacy(): MockObject|Content { return $this->getMockBuilder(ILegacy::class) ->disableOriginalConstructor() - ->addMethods(['checkParams']) ->getMock(); } @@ -120,14 +133,7 @@ protected function getMockURI(string $link): URI public function testToUIComponentsWithLinkAndImage(): void { - $icon = $this->getMockIcon(); - $icon->expects($this->once()) - ->method('checkParams') - ->with('image link', 'alt text'); $link = $this->getMockLink(); - $link->expects($this->once()) - ->method('checkParams') - ->with('full name', 'link'); $link->expects($this->once()) ->method('withAdditionalRelationshipToReferencedResource') ->with(Relationship::LICENSE); @@ -135,7 +141,7 @@ public function testToUIComponentsWithLinkAndImage(): void $img_uri = $this->getMockURI('image link'); $renderer = $this->getMockRenderer( - $icon, + $this->getMockIcon(), $link, $this->getMockLegacy(), '' @@ -182,6 +188,16 @@ public function altText(): string $this->assertSame(2, count($result)); $this->assertInstanceOf(Icon::class, $result[0]); $this->assertInstanceOf(Link::class, $result[1]); + $this->assertSame( + [ + 'icon_src' => 'image link', + 'icon_alt' => 'alt text', + 'link_label' => 'full name', + 'link_action' => 'link', + 'legacy_text' => null + ], + $renderer->exposeData() + ); } public function testToUIComponentsEmpty(): void @@ -201,21 +217,12 @@ public function testToUIComponentsEmpty(): void public function testToUIComponentsWithoutLink(): void { - $legacy = $this->getMockLegacy(); - $legacy->expects($this->once()) - ->method('checkParams') - ->with('full name'); - - $icon = $this->getMockIcon(); - $icon->expects($this->once()) - ->method('checkParams') - ->with('image link', 'alt text'); $uri = $this->getMockURI('image link'); $renderer = $this->getMockRenderer( - $icon, + $this->getMockIcon(), $this->getMockLink(), - $legacy, + $this->getMockLegacy(), '' ); $data = new class ($uri) extends NullCopyrightData { @@ -253,14 +260,21 @@ public function altText(): string $this->assertSame(2, count($result)); $this->assertInstanceOf(Icon::class, $result[0]); $this->assertInstanceOf(Content::class, $result[1]); + $this->assertSame( + [ + 'icon_src' => 'image link', + 'icon_alt' => 'alt text', + 'link_label' => null, + 'link_action' => null, + 'legacy_text' => 'full name' + ], + $renderer->exposeData() + ); } public function testToUIComponentsWithLinkNoImage(): void { $link = $this->getMockLink(); - $link->expects($this->once()) - ->method('checkParams') - ->with('full name', 'link'); $link->expects($this->once()) ->method('withAdditionalRelationshipToReferencedResource') ->with(Relationship::LICENSE); @@ -291,14 +305,21 @@ public function link(): ?URI $result = $renderer->toUIComponents($data); $this->assertSame(1, count($result)); $this->assertInstanceOf(Link::class, $result[0]); + $this->assertSame( + [ + 'icon_src' => null, + 'icon_alt' => null, + 'link_label' => 'full name', + 'link_action' => 'link', + 'legacy_text' => null + ], + $renderer->exposeData() + ); } public function testToUIComponentsLinkWithoutFullName(): void { $link = $this->getMockLink(); - $link->expects($this->once()) - ->method('checkParams') - ->with('link', 'link'); $link->expects($this->once()) ->method('withAdditionalRelationshipToReferencedResource') ->with(Relationship::LICENSE); @@ -324,18 +345,24 @@ public function link(): ?URI $result = $renderer->toUIComponents($data); $this->assertSame(1, count($result)); $this->assertInstanceOf(Link::class, $result[0]); + $this->assertSame( + [ + 'icon_src' => null, + 'icon_alt' => null, + 'link_label' => 'link', + 'link_action' => 'link', + 'legacy_text' => null + ], + $renderer->exposeData() + ); } public function testToUIComponentsWithImageFromLink(): void { - $icon = $this->getMockIcon(); - $icon->expects($this->once()) - ->method('checkParams') - ->with('image link', 'alt text'); $uri = $this->getMockURI('image link'); $renderer = $this->getMockRenderer( - $icon, + $this->getMockIcon(), $this->getMockLink(), $this->getMockLegacy(), '' @@ -369,18 +396,24 @@ public function altText(): string $result = $renderer->toUIComponents($data); $this->assertSame(1, count($result)); $this->assertInstanceOf(Icon::class, $result[0]); + $this->assertSame( + [ + 'icon_src' => 'image link', + 'icon_alt' => 'alt text', + 'link_label' => null, + 'link_action' => null, + 'legacy_text' => null + ], + $renderer->exposeData() + ); } public function testToUIComponentsWithImageFromIRSS(): void { - $icon = $this->getMockIcon(); - $icon->expects($this->once()) - ->method('checkParams') - ->with('image link', 'alt text'); $uri = $this->getMockURI('image link'); $renderer = $this->getMockRenderer( - $icon, + $this->getMockIcon(), $this->getMockLink(), $this->getMockLegacy(), 'image link' @@ -409,17 +442,22 @@ public function altText(): string $result = $renderer->toUIComponents($data); $this->assertSame(1, count($result)); $this->assertInstanceOf(Icon::class, $result[0]); + $this->assertSame( + [ + 'icon_src' => 'image link', + 'icon_alt' => 'alt text', + 'link_label' => null, + 'link_action' => null, + 'legacy_text' => null + ], + $renderer->exposeData() + ); } public function testToUIComponentsWithFallbackImage(): void { - $icon = $this->getMockIcon(); - $icon->expects($this->once()) - ->method('checkParams') - ->with('fallback src'); - $renderer = $this->getMockRenderer( - $icon, + $this->getMockIcon(), $this->getMockLink(), $this->getMockLegacy(), '' @@ -434,6 +472,16 @@ public function fallBackToDefaultImage(): bool $result = $renderer->toUIComponents($data); $this->assertSame(1, count($result)); $this->assertInstanceOf(Icon::class, $result[0]); + $this->assertSame( + [ + 'icon_src' => 'fallback src', + 'icon_alt' => '', + 'link_label' => null, + 'link_action' => null, + 'legacy_text' => null + ], + $renderer->exposeData() + ); } public function testCopyrightAsStringHasFullName(): void diff --git a/components/ILIAS/MetaData/tests/Manipulator/ManipulatorTest.php b/components/ILIAS/MetaData/tests/Manipulator/ManipulatorTest.php index e56df0629198..2e13dc00a61c 100755 --- a/components/ILIAS/MetaData/tests/Manipulator/ManipulatorTest.php +++ b/components/ILIAS/MetaData/tests/Manipulator/ManipulatorTest.php @@ -191,7 +191,6 @@ protected function filterElements(): void } } $this->my_elements = $elements; - continue; } } } diff --git a/components/ILIAS/MetaData/tests/Presentation/ElementsTest.php b/components/ILIAS/MetaData/tests/Presentation/ElementsTest.php index e2312a25495f..89b685ce3b0d 100755 --- a/components/ILIAS/MetaData/tests/Presentation/ElementsTest.php +++ b/components/ILIAS/MetaData/tests/Presentation/ElementsTest.php @@ -33,8 +33,7 @@ class ElementsTest extends TestCase { protected function getElements(): Elements { - $format = $this->createMock(DateFormat::class); - $util = new class ($format) extends NullUtilities { + $util = new class () extends NullUtilities { public function txt(string $key): string { return 'translated ' . $key; diff --git a/components/ILIAS/MetaData/tests/Presentation/UtilitiesTest.php b/components/ILIAS/MetaData/tests/Presentation/UtilitiesTest.php index d0f994cf5d52..692f40f83491 100755 --- a/components/ILIAS/MetaData/tests/Presentation/UtilitiesTest.php +++ b/components/ILIAS/MetaData/tests/Presentation/UtilitiesTest.php @@ -22,6 +22,9 @@ use PHPUnit\Framework\TestCase; use ILIAS\Data\DateFormat\DateFormat; +use ILIAS\Refinery\Factory as Refinery; +use ILIAS\Refinery\Encode\Group as EncodeGroup; +use ILIAS\Refinery\Transformation; class UtilitiesTest extends TestCase { @@ -44,7 +47,16 @@ protected function setUp(): void $user = $this->createMock(\ilObjUser::class); $user->method('getDateFormat')->willReturn($this->format); - $this->utilities = new Utilities($lng, $user); + $refinery = $this->createMock(Refinery::class); + $encoding_group = $this->createMock(EncodeGroup::class); + $transformation = $this->createMock(Transformation::class); + $transformation->method('transform')->willReturnCallback(function ($arg) { + return '~encoded:' . $arg . '~'; + }); + $encoding_group->method('htmlSpecialCharsAsEntities')->willReturn($transformation); + $refinery->method('encode')->willReturn($encoding_group); + + $this->utilities = new Utilities($lng, $user, $refinery); } public function testGetUserDateFormat(): void @@ -78,4 +90,12 @@ public function testTxtFill(): void $this->utilities->txtFill('wrong key', 'first', 'second') ); } + + public function testSanitizeForHTML(): void + { + $this->assertSame( + '~encoded:some text~', + $this->utilities->sanitizeForHTML('some text') + ); + } } diff --git a/components/ILIAS/MetaData/tests/Services/CopyrightHelper/CopyrightTest.php b/components/ILIAS/MetaData/tests/Services/CopyrightHelper/CopyrightTest.php index 656365b9cf38..e605d922f5c5 100644 --- a/components/ILIAS/MetaData/tests/Services/CopyrightHelper/CopyrightTest.php +++ b/components/ILIAS/MetaData/tests/Services/CopyrightHelper/CopyrightTest.php @@ -41,7 +41,6 @@ protected function getLegacyComponent(): MockObject|Content { return $this->getMockBuilder(ILegacy::class) ->disableOriginalConstructor() - ->addMethods(['exposeData']) ->getMock(); } @@ -49,6 +48,8 @@ protected function getRenderer(): RendererInterface { $legacy_component = $this->getLegacyComponent(); return new class ($legacy_component, $this->any()) extends NullRenderer { + public ?string $exposed_copyright_data = null; + public function __construct( protected MockObject|Content $legacy, protected AnyInvokedCount $any @@ -57,11 +58,8 @@ public function __construct( public function toUIComponents(CopyrightDataInterface $copyright): array { - $legacy_clone = clone $this->legacy; - $legacy_clone->expects($this->any) - ->method('exposeData') - ->willReturn($copyright->exposed_data); - return [$legacy_clone]; + $this->exposed_copyright_data = $copyright->exposed_data; + return [$this->legacy]; } public function toString(CopyrightDataInterface $copyright): string @@ -264,8 +262,9 @@ public function testDescription(): void public function testPresentAsUIComponents(): void { + $renderer = $this->getRenderer(); $copyright = new Copyright( - $this->getRenderer(), + $renderer, $this->getIdentifierHandler(), $this->getEntry( false, @@ -281,7 +280,7 @@ public function testPresentAsUIComponents(): void $this->assertCount(1, $components); /** @noinspection PhpUndefinedMethodInspection */ - $this->assertSame('data of copyright', $components[0]->exposeData()); + $this->assertSame('data of copyright', $renderer->exposed_copyright_data); } public function testPresentAsString(): void diff --git a/components/ILIAS/MetaData/tests/Vocabularies/Dispatch/Info/InfosTest.php b/components/ILIAS/MetaData/tests/Vocabularies/Dispatch/Info/InfosTest.php index 679ff0979f2c..36366df39fde 100644 --- a/components/ILIAS/MetaData/tests/Vocabularies/Dispatch/Info/InfosTest.php +++ b/components/ILIAS/MetaData/tests/Vocabularies/Dispatch/Info/InfosTest.php @@ -118,9 +118,7 @@ public static function activeCountProvider(): array ]; } - /** - * @dataProvider activeCountProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('activeCountProvider')] public function testIsDeactivatableStandard( int $active_controlled_vocabs, bool $is_standard_vocab_active, @@ -151,9 +149,7 @@ public function testIsDeactivatableControlledString(): void $this->assertTrue($infos->isDeactivatable($vocab)); } - /** - * @dataProvider activeCountProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('activeCountProvider')] public function testIsDeactivatableControlledVocabValue( int $active_controlled_vocabs, bool $is_standard_vocab_active, @@ -264,9 +260,7 @@ public function testCanBeDeletedControlledString(): void $this->assertTrue($infos->canBeDeleted($vocab)); } - /** - * @dataProvider activeCountProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('activeCountProvider')] public function testCanBeDeletedControlledVocabValue( int $active_controlled_vocabs, bool $is_standard_vocab_active, diff --git a/components/ILIAS/MetaData/tests/Vocabularies/Dispatch/Presentation/PresentationTest.php b/components/ILIAS/MetaData/tests/Vocabularies/Dispatch/Presentation/PresentationTest.php index 435fa340aec9..98c2c7a8e3c1 100644 --- a/components/ILIAS/MetaData/tests/Vocabularies/Dispatch/Presentation/PresentationTest.php +++ b/components/ILIAS/MetaData/tests/Vocabularies/Dispatch/Presentation/PresentationTest.php @@ -273,9 +273,7 @@ public static function singleValueProvider(): array ]; } - /** - * @dataProvider singleValueProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('singleValueProvider')] public function testPresentableLabels( string $value, bool $is_in_copyright, @@ -314,9 +312,7 @@ public function testPresentableLabels( ); } - /** - * @dataProvider singleValueProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('singleValueProvider')] public function testPresentableLabelsWithUnknownVocab( string $value, bool $is_in_copyright, diff --git a/components/ILIAS/MetaData/tests/XML/Writer/Standard/StandardTest.php b/components/ILIAS/MetaData/tests/XML/Writer/Standard/StandardTest.php index 6d3417a89cb6..3e795c70efc7 100644 --- a/components/ILIAS/MetaData/tests/XML/Writer/Standard/StandardTest.php +++ b/components/ILIAS/MetaData/tests/XML/Writer/Standard/StandardTest.php @@ -291,6 +291,43 @@ public function testWrite(): void $this->assertXmlStringEqualsXmlString($expected_xml, $xml->asXML()); } + public function testWriteWithDuplicateElementNames(): void + { + $set_array = [ + 'name' => 'el1', + 'type' => Type::NULL, + 'value' => '', + 'subs' => [ + [ + 'name' => 'name', + 'type' => Type::STRING, + 'value' => 'value1', + 'subs' => [] + ], + [ + 'name' => 'name', + 'type' => Type::STRING, + 'value' => 'value2', + 'subs' => [] + ] + ] + ]; + + $expected_xml = << + + value1 + value2 + +XML; + + $writer = $this->getStandardWriter(); + $set = $this->getSet($set_array); + $xml = $writer->write($set); + + $this->assertXmlStringEqualsXmlString($expected_xml, $xml->asXML()); + } + public function testWriteWithLanguageNone(): void { $set_array = [ @@ -708,4 +745,80 @@ public function testWriteWithoutDataCarryingElement(): void $this->assertXmlStringEqualsXmlString($expected_xml, $xml->asXML()); } + + public function testWriteWithDataContainingSpecialCharacters(): void + { + $set_array = [ + 'name' => 'el1', + 'type' => Type::NULL, + 'value' => '', + 'subs' => [ + [ + 'name' => 'el1.1', + 'type' => Type::VOCAB_VALUE, + 'value' => 'This contains !@#$%^&*(){}[]<>?=+\/|"\' a bunch of special characters.', + 'subs' => [] + ] + ] + ]; + + $expected_xml = << + + This contains !@#$%^&*(){}[]<>?=+\/|"' a bunch of special characters. + +XML; + + $writer = $this->getStandardWriter(); + $set = $this->getSet($set_array); + $xml = $writer->write($set); + + $this->assertXmlStringEqualsXmlString($expected_xml, $xml->asXML()); + } + + public function testWriteWithLangStringContainingSpecialCharacters(): void + { + $set_array = [ + 'name' => 'el1', + 'type' => Type::NULL, + 'value' => '', + 'subs' => [ + [ + 'name' => 'el1.1', + 'type' => Type::NULL, + 'value' => '', + 'specials' => [SpecialCase::LANGSTRING], + 'subs' => [ + [ + 'name' => 'string', + 'type' => Type::STRING, + 'value' => 'This contains !@#$%^&*(){}[]<>?=+\/|"\' a bunch of special characters.', + 'subs' => [] + ], + [ + 'name' => 'language', + 'type' => Type::LANG, + 'value' => 'br', + 'subs' => [] + ] + ] + ] + ] + ]; + + $expected_xml = << + + + This contains !@#$%^&*(){}[]<>?=+\/|"' a bunch of special characters. + + +XML; + + $writer = $this->getStandardWriter(); + $set = $this->getSet($set_array); + $xml = $writer->write($set); + + $this->assertXmlStringEqualsXmlString($expected_xml, $xml->asXML()); + } } diff --git a/components/ILIAS/Multilingualism/README.md b/components/ILIAS/Multilingualism/README.md index 1c5f145f6a3c..ce69dbaae600 100755 --- a/components/ILIAS/Multilingualism/README.md +++ b/components/ILIAS/Multilingualism/README.md @@ -23,7 +23,7 @@ The naming of variables/concepts has become inconsistent over time. ### Default Language -- The `ilObjectTranslation` class contains methods to get the default language, description and title, as well to set the default title and description. +- The `ILIAS\ILIASObject\Properties\Translations\Translations` class contains methods to get the default language, description and title, as well to set the default title and description. - Default language here means: Fallback language, if given, master language otherwise. ## Storing title/description diff --git a/components/ILIAS/Multilingualism/classes/class.ilMultilingualism.php b/components/ILIAS/Multilingualism/classes/class.ilMultilingualism.php index 60595c70215a..3f9c533a395a 100755 --- a/components/ILIAS/Multilingualism/classes/class.ilMultilingualism.php +++ b/components/ILIAS/Multilingualism/classes/class.ilMultilingualism.php @@ -50,7 +50,7 @@ private function __construct( $this->setType($a_type); if ($this->getObjId() <= 0) { - throw new ilObjectException("ilObjectTranslation: No object ID passed."); + throw new ilObjectException("Translation: No object ID passed."); } $this->read(); diff --git a/components/ILIAS/Multilingualism/classes/class.ilMultilingualismGUI.php b/components/ILIAS/Multilingualism/classes/class.ilMultilingualismGUI.php index 296b50aa8104..e14f09abd373 100755 --- a/components/ILIAS/Multilingualism/classes/class.ilMultilingualismGUI.php +++ b/components/ILIAS/Multilingualism/classes/class.ilMultilingualismGUI.php @@ -244,7 +244,7 @@ public function getMultiLangForm(bool $a_add = false): ilPropertyFormGUI // master language if (!$a_add) { - $si = new ilSelectInputGUI($lng->txt("obj_master_lang"), "master_lang"); + $si = new ilSelectInputGUI($lng->txt("obj_base_lang"), "master_lang"); $si->setOptions($options); $si->setValue($ilUser->getLanguage()); $form->addItem($si); diff --git a/components/ILIAS/Multilingualism/classes/class.ilMultilingualismTableGUI.php b/components/ILIAS/Multilingualism/classes/class.ilMultilingualismTableGUI.php index d8ca4a148a12..814fc1b1fef2 100755 --- a/components/ILIAS/Multilingualism/classes/class.ilMultilingualismTableGUI.php +++ b/components/ILIAS/Multilingualism/classes/class.ilMultilingualismTableGUI.php @@ -103,7 +103,7 @@ protected function fillRow(array $a_set): void } $this->tpl->parseCurrentBlock(); } elseif ($a_set["lang"] == $this->master_lang) { - $this->tpl->setVariable("MASTER_LANG", $lng->txt("obj_master_lang")); + $this->tpl->setVariable("MASTER_LANG", $lng->txt("obj_base_lang")); } if ($this->incl_desc) { diff --git a/components/ILIAS/MyStaff/classes/ListCertificates/class.ilMStListCertificatesGUI.php b/components/ILIAS/MyStaff/classes/ListCertificates/class.ilMStListCertificatesGUI.php index 075d234198e8..33129c42d869 100755 --- a/components/ILIAS/MyStaff/classes/ListCertificates/class.ilMStListCertificatesGUI.php +++ b/components/ILIAS/MyStaff/classes/ListCertificates/class.ilMStListCertificatesGUI.php @@ -1,4 +1,5 @@ getOrgUnitOfUser($mst_lco_usr_id)) as $orgu_id) { if ($DIC->access()->checkAccess("read", "", $orgu_id) && !ilObject::_isInTrash($orgu_id)) { $org_units = $this->getTextRepresentationOfOrgUnits(); - $link = ilLink::_getStaticLink($orgu_id, 'orgu'); - $actions[] = $this->ui_fac->link()->standard($org_units[$orgu_id], $link); + if (isset($org_units[$orgu_id])) { + $link = ilLink::_getStaticLink($orgu_id, 'orgu'); + $actions[] = $this->ui_fac->link()->standard($org_units[$orgu_id], $link); + } } } diff --git a/components/ILIAS/MyStaff/classes/ListUsers/class.ilMStListUsersGUI.php b/components/ILIAS/MyStaff/classes/ListUsers/class.ilMStListUsersGUI.php index 924583106f27..d2011f145605 100755 --- a/components/ILIAS/MyStaff/classes/ListUsers/class.ilMStListUsersGUI.php +++ b/components/ILIAS/MyStaff/classes/ListUsers/class.ilMStListUsersGUI.php @@ -1,4 +1,5 @@ getOrgUnitOfUser($mst_lco_usr_id)) as $orgu_id) { if ($DIC->access()->checkAccess("read", "", $orgu_id) && !ilObject::_isInTrash($orgu_id)) { $org_units = $this->getTextRepresentationOfOrgUnits(); - $link = ilLink::_getStaticLink($orgu_id, 'orgu'); - $actions[] = $this->ui_fac->link()->standard($org_units[$orgu_id], $link); + if (isset($org_units[$orgu_id])) { + $link = ilLink::_getStaticLink($orgu_id, 'orgu'); + $actions[] = $this->ui_fac->link()->standard($org_units[$orgu_id], $link); + } } } diff --git a/components/ILIAS/MyStaff/classes/ShowUser/class.ilMStShowUserGUI.php b/components/ILIAS/MyStaff/classes/ShowUser/class.ilMStShowUserGUI.php index 8bff3b22a86c..d336e88bcf07 100755 --- a/components/ILIAS/MyStaff/classes/ShowUser/class.ilMStShowUserGUI.php +++ b/components/ILIAS/MyStaff/classes/ShowUser/class.ilMStShowUserGUI.php @@ -1,4 +1,5 @@ $lng->txt("news_period_1_week"), - 30 => $lng->txt("news_period_1_month"), - 366 => $lng->txt("news_period_1_year") + "7" => $lng->txt("news_period_1_week"), + "30" => $lng->txt("news_period_1_month"), + "366" => $lng->txt("news_period_1_year") ]; return $options; diff --git a/components/ILIAS/News/Timeline/class.ilTimelineGUI.php b/components/ILIAS/News/Timeline/class.ilTimelineGUI.php index 6e0a1ae68db6..e7ca7496f79c 100755 --- a/components/ILIAS/News/Timeline/class.ilTimelineGUI.php +++ b/components/ILIAS/News/Timeline/class.ilTimelineGUI.php @@ -49,8 +49,12 @@ public function addItem(ilTimelineItemInt $a_item): void public function render( bool $a_items_only = false ): string { - $this->tpl->addJavaScript("assets/js/Timeline.js"); - $this->tpl->addJavaScript("./components/ILIAS/News/Timeline/libs/jquery-dynamic-max-height-master/src/jquery.dynamicmaxheight.js"); + $debug = false; + if ($debug) { + $this->tpl->addJavaScript("../components/ILIAS/News/resources/Timeline.js"); + } else { + $this->tpl->addJavaScript("assets/js/Timeline.js"); + } $t = new ilTemplate("tpl.timeline.html", true, true, "components/ILIAS/News/Timeline"); if (!$a_items_only) { diff --git a/components/ILIAS/News/classes/Provider/NewsMainBarProvider.php b/components/ILIAS/News/classes/Provider/NewsMainBarProvider.php index ee79c867486b..59e0a9826494 100755 --- a/components/ILIAS/News/classes/Provider/NewsMainBarProvider.php +++ b/components/ILIAS/News/classes/Provider/NewsMainBarProvider.php @@ -50,7 +50,7 @@ public function getStaticSubItems(): array ->withNonAvailableReason($this->dic->ui()->factory()->legacy()->content($this->dic->language()->txt('component_not_active'))) ->withAvailableCallable( static function () use ($dic): bool { - return !($dic->settings()->get("block_activated_news") === null); + return (bool) $dic->settings()->get("block_activated_news"); } ), ]; diff --git a/components/ILIAS/News/classes/class.ilNewsItem.php b/components/ILIAS/News/classes/class.ilNewsItem.php index 1aa6bc19cce3..5cf92c067290 100755 --- a/components/ILIAS/News/classes/class.ilNewsItem.php +++ b/components/ILIAS/News/classes/class.ilNewsItem.php @@ -16,6 +16,8 @@ * *********************************************************************/ +use ILIAS\MediaObjects\MediaObjectManager; + const NEWS_NOTICE = 0; const NEWS_MESSAGE = 1; const NEWS_WARNING = 2; @@ -40,6 +42,7 @@ class ilNewsItem { private int $mob_cnt_download = 0; + protected MediaObjectManager $media_manager; protected int $mob_cnt_play = 0; protected ilDBInterface $db; protected ilTree $tree; @@ -91,6 +94,7 @@ public function __construct(int $a_id = 0) } $this->limitation = true; $this->log = $DIC->logger()->news(); + $this->media_manager = $DIC->mediaObjects()->internal()->domain()->mediaObject(); } public function setId(int $a_id): void @@ -1920,15 +1924,10 @@ public function deliverMobFile( $m_item = $mob->getMediaItem($a_purpose); if ($m_item->getLocationType() !== "Reference") { - $file = $mob_dir . "/" . $m_item->getLocation(); - if (file_exists($file) && is_file($file)) { - if ($a_increase_download_cnt) { - $this->increaseDownloadCounter(); - } - ilFileDelivery::deliverFileLegacy($file, $m_item->getLocation(), "", false, false, false); - return true; + $this->media_manager->deliverEntry($mob->getId(), "/" . $m_item->getLocation()); + if ($a_increase_download_cnt) { + $this->increaseDownloadCounter(); } - $this->main_tpl->setOnScreenMessage('failure', "File not found!", true); return false; } diff --git a/components/ILIAS/News/classes/class.ilNewsTimelineGUI.php b/components/ILIAS/News/classes/class.ilNewsTimelineGUI.php index 8a7484a378db..f6e993dcc122 100755 --- a/components/ILIAS/News/classes/class.ilNewsTimelineGUI.php +++ b/components/ILIAS/News/classes/class.ilNewsTimelineGUI.php @@ -238,19 +238,18 @@ public function getHTML(?ilPropertyFormGUI $form = null): string if (count($this->news_data) > 0) { $ttpl = new ilTemplate("tpl.news_timeline.html", true, true, "components/ILIAS/News"); $ttpl->setVariable("NEWS", $timeline->render()); - $edit_modal = $this->getEditModal($form); - $ttpl->setVariable("EDIT_MODAL", $this->ui->renderer()->render($edit_modal)); - //$ttpl->setVariable("DELETE_MODAL", $this->getDeleteModal()); $this->renderDeleteModal($ttpl); + $this->renderEditModal($form, $ttpl); $ttpl->setVariable("LOADER", ilUtil::getImagePath("media/loader.svg")); $this->tpl->setContent($ttpl->get()); $html = $ttpl->get(); } else { if ($this->getEnableAddNews()) { + $ttpl = new ilTemplate("tpl.news_timeline.html", true, true, "components/ILIAS/News"); $this->tpl->setOnScreenMessage('info', $this->lng->txt("news_timline_add_entries_info")); - $edit_modal = $this->getEditModal(); - $this->tpl->setContent($this->ui->renderer()->render($edit_modal)); - $html = $this->ui->renderer()->render($edit_modal); + $this->renderEditModal($form, $ttpl); + $this->tpl->setContent($ttpl->get()); + $html = $ttpl->get(); } else { $mess = $this->ui->factory()->messageBox()->info( $this->lng->txt("news_timline_no_entries") @@ -264,7 +263,12 @@ public function getHTML(?ilPropertyFormGUI $form = null): string $this->lng->toJS("update"); $this->lng->toJS("save"); - $this->tpl->addJavaScript("assets/js/News.js"); + $debug = false; + if ($debug) { + $this->tpl->addJavaScript("../components/ILIAS/News/resources/News.js"); + } else { + $this->tpl->addJavaScript("assets/js/News.js"); + } $this->notes->gui()->initJavascript(); return $html; } @@ -477,6 +481,16 @@ protected function renderDeleteModal(ilTemplate $tpl): void $tpl->setVariable("SIGNAL_ID", $c["signal"]); } + protected function renderEditModal(?ilPropertyFormGUI $form, ilTemplate $tpl): void + { + $edit_modal = $this->getEditModal($form); + $signal = $edit_modal->getShowSignal()->getId(); + $close_signal = $edit_modal->getCloseSignal()->getId(); + $tpl->setVariable("SHOW_EDIT_SIGNAL", $signal); + $tpl->setVariable("CLOSE_EDIT_SIGNAL", $close_signal); + $tpl->setVariable("EDIT_MODAL", $this->ui->renderer()->render($edit_modal)); + } + protected function downloadMob(): void { $news_id = $this->std_request->getNewsId(); diff --git a/components/ILIAS/News/classes/class.ilNewsTimelineItemGUI.php b/components/ILIAS/News/classes/class.ilNewsTimelineItemGUI.php index d4115024d992..d14e2ef8dd20 100755 --- a/components/ILIAS/News/classes/class.ilNewsTimelineItemGUI.php +++ b/components/ILIAS/News/classes/class.ilNewsTimelineItemGUI.php @@ -307,7 +307,6 @@ public function renderFooter(): string //$html .= $this->ctrl->getHTML($comments_gui); $this->ctrl->setParameterByClass("ilnewstimelinegui", "news_id", $this->std_request->getNewsId()); - return $html . $this->renderMediaModal($i); } diff --git a/components/ILIAS/News/classes/class.ilPDNewsGUI.php b/components/ILIAS/News/classes/class.ilPDNewsGUI.php index cf92f2721128..d5efbe5f3fa1 100755 --- a/components/ILIAS/News/classes/class.ilPDNewsGUI.php +++ b/components/ILIAS/News/classes/class.ilPDNewsGUI.php @@ -18,6 +18,9 @@ use ILIAS\News\StandardGUIRequest; use ILIAS\Repository\Filter\FilterAdapterGUI; +use ILIAS\News\InternalDomainService; +use ILIAS\News\Dashboard\DashboardNewsManager; +use ILIAS\News\InternalGUIService; /** * News on PD @@ -27,9 +30,10 @@ */ class ilPDNewsGUI { - protected \ILIAS\News\Dashboard\DashboardNewsManager $dash_news_manager; + protected InternalDomainService $domain; + protected DashboardNewsManager $dash_news_manager; protected \ILIAS\News\Dashboard\DashboardSessionRepository $dash_news_repo; - protected \ILIAS\News\InternalGUIService $gui; + protected InternalGUIService $gui; protected ilGlobalTemplateInterface $tpl; protected ilLanguage $lng; protected ilCtrl $ctrl; @@ -68,6 +72,9 @@ public function __construct() $this->gui = $DIC->news() ->internal() ->gui(); + $this->domain = $DIC->news() + ->internal() + ->domain(); $this->dash_news_repo = $DIC->news() ->internal() ->repo() @@ -82,6 +89,10 @@ public function executeCommand(): bool { $next_class = $this->ctrl->getNextClass(); + if (!$this->domain->settings()->get("block_activated_news")) { + return false; + } + switch ($next_class) { case "ilnewstimelinegui": $t = $this->gui->dashboard()->getTimelineGUI(); diff --git a/components/ILIAS/News/resources/News.js b/components/ILIAS/News/resources/News.js index ade248924d36..4dbd5fd9a550 100644 --- a/components/ILIAS/News/resources/News.js +++ b/components/ILIAS/News/resources/News.js @@ -1,41 +1,42 @@ il.News = { - items: {}, - - current_id: 0, - - ajax_url: "", - - requestRunning: false, - - scroll_init: false, - - init: function () { - var t = il.News; - $('#form_news_edit_form').closest('.il-modal-roundtrip').find('.modal-footer').hide(); - $("#news_btn_cancel_update").on("click", function (e) { - e.preventDefault(); - $(".ilAdminRow .alert").remove(); - $('#form_news_edit_form').closest('.il-modal-roundtrip').modal('hide'); - }); - $("#news_btn_update").on("click", function (e) { - var t = il.News; - //e.preventDefault(); - t.save(); - $('#form_news_edit_form').closest('.il-modal-roundtrip').modal('hide'); - }); - t.moreOnScroll(); - }, - - moreOnScroll: function() { - var w = $('main'), t = il.News; + items: {}, + + current_id: 0, + + ajax_url: '', + + requestRunning: false, + + scroll_init: false, + + init() { + const t = il.News; + $('#form_news_edit_form').closest('.il-modal-roundtrip').find('.modal-footer').hide(); + $('#news_btn_cancel_update').on('click', (e) => { + e.preventDefault(); + $('.ilAdminRow .alert').remove(); + t.closeEditModal(); + }); + $('#news_btn_update').on('click', (e) => { + const t = il.News; + // e.preventDefault(); + t.save(); + t.closeEditModal(); + }); + t.moreOnScroll(); + }, + + moreOnScroll() { + const w = $('main'); const + t = il.News; console.log(w); if (!t.scroll_init) { - w.on('scroll', function () { - var main = $('main'); - var sp = main.scrollTop(); // scroll position (starting with 0) - var vh = main.height(); // visible height - var th = main[0].scrollHeight; // total height + w.on('scroll', () => { + const main = $('main'); + const sp = main.scrollTop(); // scroll position (starting with 0) + const vh = main.height(); // visible height + const th = main[0].scrollHeight; // total height if (sp + vh + 60 > th) { t.moreNews(); } @@ -44,252 +45,272 @@ il.News = { } }, - startMoreRequest: function () { - var t = il.News; - if (t.requestRunning) { - return false; - } - t.requestRunning = true; - t.showLoader(true); - - return true; - }, - - stopMoreRequest: function () { - var t = il.News; - t.requestRunning = false; - t.showLoader(false); - }, - - moreNews: function() { - var t = il.News; - if (!t.startMoreRequest()) { - return; - } - //console.log("get more news"); - - t.scroll_init = false; - $(window).off('scroll'); - - $.ajax({ - url: il.News.ajax_url + "&cmd=loadMore", - type: "POST", - dataType: "json", - data: { - 'rendered_news' : $.map(il.News.items, function(e) { - return e.id - }) - } - }).done(function (r) { - if (r.data !== undefined && r.data.html !== '') - { - t.appendNews(r); - //il.News.addScrollToBottomListener(); - t.stopMoreRequest(); - } - else - { - t.stopMoreRequest(); - } - }).fail(function (e) { - t.stopMoreRequest(); - }); - }, - - showLoader : function(s) { - if(s) { - $('.ilNewsTimelineMoreLoader').removeClass('ilHidden'); - } else { - $('.ilNewsTimelineMoreLoader').addClass('ilHidden'); - } - }, - - appendNews: function (r) { - var t = il.News; - if (r.html == "") { - return; - } - //console.log(r.data); - for (var i in r.data) { - t.items[i] = r.data[i]; - } - $("ul.ilTimeline").append(r.html); - - $('.dynamic-height-active').removeClass("dynamic-height-active"); - $('.js-dynamic-show-hide').css("display", "").off("click"); - $('.dynamic-height-wrap').css('max-height', ""); - $('.dynamic-max-height').dynamicMaxHeight(); - - il.Timeline.compressEntries(); - il.MediaObjects.autoInitPlayers(); - t.moreOnScroll(); - }, - - setAjaxUrl: function (url) { - var t = il.News; - - t.ajax_url = url; - }, - - setItems: function (items) { - var t = il.News; - - t.items = items; - }, - - create: function(keep_values) { - var t = il.News; - - t.current_id = 0; - - $('#form_news_edit_form').closest('.il-modal-roundtrip').find('.modal-title').html(il.Language.txt("create")); - $('#news_btn_update').attr("value", il.Language.txt("save")); - if (!keep_values) { - $("#news_title").val(""); - $("#news_content").val(""); - $("#news_content_long").val(""); - $(".help-block.alert").remove(); - } - if (typeof tinyMCE != "undefined" && tinyMCE.get('news_content')) { - tinyMCE.get('news_content').setContent(""); - } - $('#form_news_edit_form').closest('.il-modal-roundtrip').find('input[name="media_delete"]').css("display", "none"); - $('#form_news_edit_form').closest('.il-modal-roundtrip').find('label[for="media_delete"]').css("display", "none"); - $('#form_news_edit_form').closest('.il-modal-roundtrip').modal('show'); - - return false; - }, - - edit: function(id, keep_values) { - var t = il.News; - t.current_id = id; - - $('#form_news_edit_form').closest('.il-modal-roundtrip').find('.modal-title').html(il.Language.txt("edit")); - $('#news_btn_update').attr("value", il.Language.txt("save")); - if (!keep_values) { - $("#news_title").val(t.items[id].title); - $("#news_visibility input[value='" + t.items[id].visibility + "']").prop('checked', true); - if (typeof tinyMCE != "undefined" && tinyMCE.get('news_content')) { - tinyMCE.get('news_content').setContent(t.items[id].content); - } else { - $("#news_content").val(t.items[id].content); - } - $(".help-block.alert").remove(); - } - - if (t.items[id].mob_id > 0) { - $('#form_news_edit_form').closest('.il-modal-roundtrip').find('input[name="media_delete"]').css("display", ""); - $('#form_news_edit_form').closest('.il-modal-roundtrip').find('label[for="media_delete"]').css("display", ""); - $('#form_news_edit_form').closest('.il-modal-roundtrip').find('input[name="media_delete"]').prop("checked", false); - } else { - $('#form_news_edit_form').closest('.il-modal-roundtrip').find('input[name="media_delete"]').css("display", "none"); - $('#form_news_edit_form').closest('.il-modal-roundtrip').find('label[for="media_delete"]').css("display", "none"); - } - - - $('#form_news_edit_form').closest('.il-modal-roundtrip').modal('show'); - - return false; - }, - - save: function () { - var t = il.News, cmd, d, content; - - if (typeof tinyMCE != "undefined" && tinyMCE.get('news_content')) { - content = tinyMCE.get('news_content').getContent(); - } else { - content = $("#news_content").val(); - } - // data - d = { - news_title: $("#news_title").val(), - news_visibility: $("#news_visibility input[type='radio']:checked").val(), - news_content: content, - news_content_long: "" - } - - if (t.current_id > 0) { - d.id = t.current_id; - cmd = "update"; - } else { - cmd = "save"; - } - - $("#id").val(d.id); - $("#news_action").val(cmd); - - // $("#form_news_edit_form").submit(); - - return; - - //console.log(d); return; - - $.ajax({ - url : t.ajax_url + "&cmd=" + cmd, - type: "POST", - data : d, - success: function(data, s, j) { - console.log(data); return false; - window.location.href = t.ajax_url + "&cmd=show"; - }, - error: function (j, s, e) - { - window.location.href = t.ajax_url + "&cmd=show"; - } - }); - - }, - - delete: function(id) { - var t = il.News; - t.current_id = id; - - $("#news_delete_news_title").html(t.items[id].title); - - const newsData = document.querySelector("[data-news-type='init']"); - if (newsData) { - const signalId = newsData.dataset.newsDeleteModalSignal; - $(document).trigger( - signalId, - { - id: signalId, - triggerer: $(this), - options: JSON.parse('[]'), - }, - ); - } - - return false; - }, - - remove: function () { - var t = il.News, cmd, d, content; - - cmd = "remove"; - - d = { - id: t.current_id - }; - - $.ajax({ - url : t.ajax_url + "&cmd=" + cmd, - type: "POST", - data : d, - success: function(data, s, j) { - window.location.href = t.ajax_url + "&cmd=show"; - }, - error: function (j, s, e) - { - window.location.href = t.ajax_url + "&cmd=show"; - } - }); - - } + startMoreRequest() { + const t = il.News; + if (t.requestRunning) { + return false; + } + t.requestRunning = true; + t.showLoader(true); + + return true; + }, + + stopMoreRequest() { + const t = il.News; + t.requestRunning = false; + t.showLoader(false); + }, + + moreNews() { + const t = il.News; + if (!t.startMoreRequest()) { + return; + } + // console.log("get more news"); + + t.scroll_init = false; + $(window).off('scroll'); + + $.ajax({ + url: `${il.News.ajax_url}&cmd=loadMore`, + type: 'POST', + dataType: 'json', + data: { + rendered_news: $.map(il.News.items, (e) => e.id), + }, + }).done((r) => { + if (r.data !== undefined && r.data.html !== '') { + t.appendNews(r); + // il.News.addScrollToBottomListener(); + t.stopMoreRequest(); + } else { + t.stopMoreRequest(); + } + }).fail((e) => { + t.stopMoreRequest(); + }); + }, + + showLoader(s) { + if (s) { + $('.ilNewsTimelineMoreLoader').removeClass('ilHidden'); + } else { + $('.ilNewsTimelineMoreLoader').addClass('ilHidden'); + } + }, + + appendNews(r) { + const t = il.News; + if (r.html == '') { + return; + } + // console.log(r.data); + for (const i in r.data) { + t.items[i] = r.data[i]; + } + $('ul.ilTimeline').append(r.html); + + $('.dynamic-height-active').removeClass('dynamic-height-active'); + $('.js-dynamic-show-hide').css('display', '').off('click'); + $('.dynamic-height-wrap').css('max-height', ''); + // $('.dynamic-max-height').dynamicMaxHeight(); + + il.Timeline.compressEntries(); + il.MediaObjects.autoInitPlayers(); + t.moreOnScroll(); + }, + + setAjaxUrl(url) { + const t = il.News; + + t.ajax_url = url; + }, + setItems(items) { + const t = il.News; + t.items = items; + }, + + create(keep_values) { + const t = il.News; + + t.current_id = 0; + + $('#form_news_edit_form').closest('.il-modal-roundtrip').find('.modal-title').html(il.Language.txt('create')); + $('#news_btn_update').attr('value', il.Language.txt('save')); + if (!keep_values) { + $('#news_title').val(''); + $('#news_content').val(''); + $('#news_content_long').val(''); + $('.help-block.alert').remove(); + } + if (typeof tinyMCE !== 'undefined' && tinyMCE.get('news_content')) { + tinyMCE.get('news_content').setContent(''); + } + $('#form_news_edit_form').closest('.il-modal-roundtrip').find('input[name="media_delete"]').css('display', 'none'); + $('#form_news_edit_form').closest('.il-modal-roundtrip').find('label[for="media_delete"]').css('display', 'none'); + t.showEditModal(); + + return false; + }, + + showEditModal() { + const newsData = document.querySelector("[data-news-type='init']"); + if (newsData) { + const signalId = newsData.dataset.newsEditModalSignal; + $(document).trigger( + signalId, + { + id: signalId, + triggerer: $(this), + options: JSON.parse('[]'), + }, + ); + } + }, + + closeEditModal() { + const newsData = document.querySelector("[data-news-type='init']"); + if (newsData) { + const signalId = newsData.dataset.newsEditCloseSignal; + $(document).trigger( + signalId, + { + id: signalId, + triggerer: $(this), + options: JSON.parse('[]'), + }, + ); + } + }, + + edit(id, keep_values) { + const t = il.News; + t.current_id = id; + + $('#form_news_edit_form').closest('.il-modal-roundtrip').find('.modal-title').html(il.Language.txt('edit')); + $('#news_btn_update').attr('value', il.Language.txt('save')); + if (!keep_values) { + $('#news_title').val(t.items[id].title); + $(`#news_visibility input[value='${t.items[id].visibility}']`).prop('checked', true); + if (typeof tinyMCE !== 'undefined' && tinyMCE.get('news_content')) { + tinyMCE.get('news_content').setContent(t.items[id].content); + } else { + $('#news_content').val(t.items[id].content); + } + $('.help-block.alert').remove(); + } + + if (t.items[id].mob_id > 0) { + $('#form_news_edit_form').closest('.il-modal-roundtrip').find('input[name="media_delete"]').css('display', ''); + $('#form_news_edit_form').closest('.il-modal-roundtrip').find('label[for="media_delete"]').css('display', ''); + $('#form_news_edit_form').closest('.il-modal-roundtrip').find('input[name="media_delete"]').prop('checked', false); + } else { + $('#form_news_edit_form').closest('.il-modal-roundtrip').find('input[name="media_delete"]').css('display', 'none'); + $('#form_news_edit_form').closest('.il-modal-roundtrip').find('label[for="media_delete"]').css('display', 'none'); + } + + t.showEditModal(); + + return false; + }, + + save() { + const t = il.News; let cmd; let d; let + content; + + if (typeof tinyMCE !== 'undefined' && tinyMCE.get('news_content')) { + content = tinyMCE.get('news_content').getContent(); + } else { + content = $('#news_content').val(); + } + // data + d = { + news_title: $('#news_title').val(), + news_visibility: $("#news_visibility input[type='radio']:checked").val(), + news_content: content, + news_content_long: '', + }; + + if (t.current_id > 0) { + d.id = t.current_id; + cmd = 'update'; + } else { + cmd = 'save'; + } + + $('#id').val(d.id); + $('#news_action').val(cmd); + + // $("#form_news_edit_form").submit(); + + return; + + // console.log(d); return; + + $.ajax({ + url: `${t.ajax_url}&cmd=${cmd}`, + type: 'POST', + data: d, + success(data, s, j) { + console.log(data); return false; + window.location.href = `${t.ajax_url}&cmd=show`; + }, + error(j, s, e) { + window.location.href = `${t.ajax_url}&cmd=show`; + }, + }); + }, + + delete(id) { + const t = il.News; + t.current_id = id; + + $('#news_delete_news_title').html(t.items[id].title); + + const newsData = document.querySelector("[data-news-type='init']"); + if (newsData) { + const signalId = newsData.dataset.newsDeleteModalSignal; + $(document).trigger( + signalId, + { + id: signalId, + triggerer: $(this), + options: JSON.parse('[]'), + }, + ); + } + + return false; + }, + + remove() { + const t = il.News; let cmd; let d; let + content; + + cmd = 'remove'; + + d = { + id: t.current_id, + }; + + $.ajax({ + url: `${t.ajax_url}&cmd=${cmd}`, + type: 'POST', + data: d, + success(data, s, j) { + window.location.href = `${t.ajax_url}&cmd=show`; + }, + error(j, s, e) { + window.location.href = `${t.ajax_url}&cmd=show`; + }, + }); + }, }; -$(function() { - il.News.init(); +$(() => { + il.News.init(); }); diff --git a/components/ILIAS/News/resources/Timeline.js b/components/ILIAS/News/resources/Timeline.js index f49a1d37b481..86eefbda4827 100644 --- a/components/ILIAS/News/resources/Timeline.js +++ b/components/ILIAS/News/resources/Timeline.js @@ -1,90 +1,89 @@ il.Timeline = { - compressEntries: function () { - var minspace, prev, el_top, prev_badge_top, el, mt, mt2, mt3, prev_top, d; - var t = il.Timeline; - t.removeRedundantBadges(); - - $("ul.ilTimeline > li") - .css("margin-top", "0px"); - - // if we do not have a float right element (narrow sreen view) > do not compress - d = $(".ilTimelinePanel:eq(1)"); - if (!d.length) { - return; - } - if (window.getComputedStyle(d.get(0),null).getPropertyValue("float") != "right") { - return; - } - - // minimum space is the heigth of the badge - minspace = $("ul.ilTimeline div.ilTimelineBadge").outerHeight(); - - //console.log(minspace); - - $("ul.ilTimeline > li") - .each(function () { - el = this; - prev = $(el).prev("li"); - if (prev.length) { - - // y position of previous element - prev_top = $(prev).position().top; - - // y position of element - el_top = $(el).position().top; - - // at least two badges lower than the last on the other side - mt = prev_top + (1.2 * minspace) - el_top; - - // if an element exists over our element, move up to element - prev2 = $(prev).prev("li"); - if (prev2.length) { - prev2_bottom = $(prev2).position().top + $(prev2).outerHeight(true); - mt2 = prev2_bottom - el_top; - if (mt2 > mt) { - mt = mt2; - } - } - - // if a previous badge exists, do not go futher than the badge - if ($(el).prevAll("li").find(".ilTimelineBadge").length > 0) { - prev_badge_top = $(el).prevAll("li").find(".ilTimelineBadge").first().position().top; - if (prev_badge_top > 0) { - mt3 = prev_badge_top + minspace - el_top; - if (mt3 > mt) { - mt = mt3; - } - } - } - - if (mt < 0) { - $(el).css("margin-top", mt + "px"); - } - } - }); - - }, - - removeRedundantBadges: function() { - var last_el; - $(".ilTimelineBadge").each(function () { - - if (typeof last_el != "undefined" && $(this).html() == $(last_el).html()) { - $(last_el).remove(); - } - last_el = this; - }); - } + compressEntries() { + let minspace; let prev; let el_top; let prev_badge_top; let el; let mt; let mt2; let mt3; let prev_top; let + d; + const t = il.Timeline; + t.removeRedundantBadges(); + + $('ul.ilTimeline > li') + .css('margin-top', '0px'); + + // if we do not have a float right element (narrow sreen view) > do not compress + d = $('.ilTimelinePanel:eq(1)'); + if (!d.length) { + return; + } + if (window.getComputedStyle(d.get(0), null).getPropertyValue('float') != 'right') { + return; + } + + // minimum space is the heigth of the badge + minspace = $('ul.ilTimeline div.ilTimelineBadge').outerHeight(); + + // console.log(minspace); + + $('ul.ilTimeline > li') + .each(function () { + el = this; + prev = $(el).prev('li'); + if (prev.length) { + // y position of previous element + prev_top = $(prev).position().top; + + // y position of element + el_top = $(el).position().top; + + // at least two badges lower than the last on the other side + mt = prev_top + (1.2 * minspace) - el_top; + + // if an element exists over our element, move up to element + prev2 = $(prev).prev('li'); + if (prev2.length) { + prev2_bottom = $(prev2).position().top + $(prev2).outerHeight(true); + mt2 = prev2_bottom - el_top; + if (mt2 > mt) { + mt = mt2; + } + } + + // if a previous badge exists, do not go futher than the badge + if ($(el).prevAll('li').find('.ilTimelineBadge').length > 0) { + prev_badge_top = $(el).prevAll('li').find('.ilTimelineBadge').first() + .position().top; + if (prev_badge_top > 0) { + mt3 = prev_badge_top + minspace - el_top; + if (mt3 > mt) { + mt = mt3; + } + } + } + + if (mt < 0) { + $(el).css('margin-top', `${mt}px`); + } + } + }); + }, + + removeRedundantBadges() { + let last_el; + $('.ilTimelineBadge').each(function () { + if (typeof last_el !== 'undefined' && $(this).html() == $(last_el).html()) { + $(last_el).remove(); + } + last_el = this; + }); + }, }; -$(function () { - //$('.dynamic-max-height').dynamicMaxHeight(); - il.Timeline.compressEntries(); - $(window).resize(il.Timeline.compressEntries); +$(() => { + // $('.dynamic-max-height').dynamicMaxHeight(); + il.Timeline.compressEntries(); + $(window).resize(il.Timeline.compressEntries); }); -$(window).on("load", function() { - $('.dynamic-max-height').dynamicMaxHeight(); - il.Timeline.compressEntries(); - $(window).resize(il.Timeline.compressEntries); -}); \ No newline at end of file +$(window).on('load', () => { + // $('.dynamic-max-height').dynamicMaxHeight(); + il.Timeline.compressEntries(); + $(window).resize(il.Timeline.compressEntries); +}); diff --git a/components/ILIAS/News/templates/default/tpl.news_timeline.html b/components/ILIAS/News/templates/default/tpl.news_timeline.html index 5f050a8f683f..2cbaffc2caaf 100755 --- a/components/ILIAS/News/templates/default/tpl.news_timeline.html +++ b/components/ILIAS/News/templates/default/tpl.news_timeline.html @@ -1,7 +1,7 @@ {NEWS} {EDIT_MODAL} {DELETE_MODAL} - +
diff --git a/components/ILIAS/Notes/Export/class.ilNotesDataSet.php b/components/ILIAS/Notes/Export/class.ilNotesDataSet.php index 1dd1b7c089c4..74499e96c005 100755 --- a/components/ILIAS/Notes/Export/class.ilNotesDataSet.php +++ b/components/ILIAS/Notes/Export/class.ilNotesDataSet.php @@ -42,7 +42,7 @@ public function __construct() public function getSupportedVersions(): array { - return array("4.3.0"); + return array("4.3.0", "10.0"); } protected function getXmlNamespace(string $a_entity, string $a_schema_version): string @@ -58,6 +58,7 @@ protected function getTypes( if ($a_entity === "user_notes") { switch ($a_version) { case "4.3.0": + case "10.0": return array( "Id" => "integer", "RepObjId" => "integer", @@ -73,6 +74,15 @@ protected function getTypes( ); } } + if ($a_entity === "comments_settings") { + switch ($a_version) { + case "10.0": + return array( + "Id" => "integer", + "Active" => "integer" + ); + } + } return []; } @@ -87,6 +97,7 @@ public function readData( if ($a_entity === "user_notes") { switch ($a_version) { case "4.3.0": + case "10.0": $this->getDirectDataFromQuery("SELECT id, rep_obj_id, obj_id, obj_type, type, " . " author, note_text, creation_date, label, subject, no_repository " . " FROM note " . @@ -96,6 +107,18 @@ public function readData( break; } } + if ($a_entity === "comments_settings") { + switch ($a_version) { + case "10.0": + foreach($a_ids as $id) { + $this->data[] = [ + "Id" => $id, + "Active" => (int) $this->notes_manager->commentsActive((int) $id) + ]; + } + break; + } + } } public function importRecord( @@ -138,6 +161,12 @@ public function importRecord( } } break; + case "comments_settings": + $obj_id = (int) $a_mapping->getMapping("components/ILIAS/ILIASObject", "obj", $a_rec["Id"]); + if ($obj_id > 0 && ((bool) ($a_rec["Active"] ?? false) === true)) { + $this->notes_manager->activateComments($obj_id, true); + } + break; } } } diff --git a/components/ILIAS/Notes/Export/class.ilNotesExporter.php b/components/ILIAS/Notes/Export/class.ilNotesExporter.php index 67667690b9eb..c9226bf2789f 100755 --- a/components/ILIAS/Notes/Export/class.ilNotesExporter.php +++ b/components/ILIAS/Notes/Export/class.ilNotesExporter.php @@ -48,12 +48,18 @@ public function getValidSchemaVersions( string $a_entity ): array { return array( + "10.0" => array( + "namespace" => "https://www.ilias.de/Services/Notes/note/10", + "xsd_file" => "ilias_notes_10.xsd", + "uses_dataset" => true, + "min" => "10.0", + "max" => ""), "4.3.0" => array( "namespace" => "https://www.ilias.de/Services/Notes/note/4_3", "xsd_file" => "ilias_usr_4_3.xsd", "uses_dataset" => true, "min" => "4.3.0", - "max" => "") + "max" => "9.99") ); } } diff --git a/components/ILIAS/Notes/Service/class.InternalGUIService.php b/components/ILIAS/Notes/Service/class.InternalGUIService.php index d10b4ce3d2af..b0638f92a3f2 100755 --- a/components/ILIAS/Notes/Service/class.InternalGUIService.php +++ b/components/ILIAS/Notes/Service/class.InternalGUIService.php @@ -84,7 +84,7 @@ public function initJavascript( $tpl->addOnLoadCode("ilNotes.setAjaxUrl('" . $ajax_url . "');"); $tpl->addOnLoadCode('ilNotes.setModalTemplate("' . addslashes(json_encode($modal_template["template"])) . '");'); $tpl->addOnLoadCode("ilNotes.setShowSignal('" . $modal_template["show"] . "');"); - $tpl->addOnLoadCode("ilNotes.setCloseSignal('" . $modal_template["close"] . "');"); + $tpl->addOnLoadCode("ilNotes.setHideSignal('" . $modal_template["close"] . "');"); } public function getModalTemplate(): array diff --git a/components/ILIAS/Notification/classes/class.ilNotificationAppEventListener.php b/components/ILIAS/Notification/classes/class.ilNotificationAppEventListener.php index c5fdf5f2d96e..3471aea325b5 100755 --- a/components/ILIAS/Notification/classes/class.ilNotificationAppEventListener.php +++ b/components/ILIAS/Notification/classes/class.ilNotificationAppEventListener.php @@ -26,7 +26,7 @@ public static function handleEvent( string $a_event, array $a_parameter ): void { - if ($a_component === 'components/ILIAS/Object' && $a_event === 'delete') { + if ($a_component === 'components/ILIAS/ILIASObject' && $a_event === 'delete') { if ($a_parameter['obj_id'] > 0) { $set = new ilObjNotificationSettings($a_parameter['obj_id']); $set->delete(); diff --git a/components/ILIAS/Notifications/Notifications.php b/components/ILIAS/Notifications/Notifications.php index bfe8d34cac56..9b856aec150c 100644 --- a/components/ILIAS/Notifications/Notifications.php +++ b/components/ILIAS/Notifications/Notifications.php @@ -40,7 +40,7 @@ public function init( $contribute[Component\Resource\PublicAsset::class] = fn() => new Component\Resource\ComponentJS($this, "notifications.js"); $contribute[Component\Resource\PublicAsset::class] = fn() => - new Component\Resource\ComponentJS($this, "browser_notifications.js"); + new Component\Resource\ComponentJS($this, "js/dist/BrowserNotifications.min.js"); $contribute[Component\Resource\PublicAsset::class] = fn() => new Component\Resource\ComponentCSS($this, "osd.css"); $contribute[Component\Resource\PublicAsset::class] = fn() => diff --git a/components/ILIAS/Notifications/ROADMAP.md b/components/ILIAS/Notifications/ROADMAP.md index 5f7d6b946307..c5c7d7847702 100755 --- a/components/ILIAS/Notifications/ROADMAP.md +++ b/components/ILIAS/Notifications/ROADMAP.md @@ -8,11 +8,6 @@ It should have its own checkbox within the notification administration. * Get rid of time dependency in `\ILIAS\Notifications\Repository\ilNotificationOSDRepository` and `\ILIAS\Notifications\ilNotificationDatabaseHandler`, use `\ILIAS\Data\Clock\ClockFactory` interface instead -### Update of Toast Interface - -`VanishTime` and `DelayTime` should be moved to the interface, if the values could be configured in the respective consumers. -As long as this is not the case, they keep an implementation detail. - ## Mid Term ## Long Term diff --git a/components/ILIAS/Notifications/classes/Provider/NotificationsToastProvider.php b/components/ILIAS/Notifications/classes/Provider/NotificationsToastProvider.php index 2b48b840ea57..cf663405e450 100755 --- a/components/ILIAS/Notifications/classes/Provider/NotificationsToastProvider.php +++ b/components/ILIAS/Notifications/classes/Provider/NotificationsToastProvider.php @@ -61,8 +61,6 @@ public function getToasts(): array ) ->withIcon($this->getIconByType($notification->getType())) ->withDescription($notification->getObject()->shortDescription) - ->withVanishTime((int) $settings->get('osd_vanish', (string) Toast::DEFAULT_VANISH_TIME)) - ->withDelayTime((int) $settings->get('osd_delay', (string) Toast::DEFAULT_DELAY_TIME)) ->withClosedCallable(static function () use ($osd_repository, $notification) { $osd_repository->deleteOSDNotificationById($notification->getId()); }); diff --git a/components/ILIAS/Notifications/classes/Setup/ilNotificationUpdateSteps.php b/components/ILIAS/Notifications/classes/Setup/ilNotificationUpdateSteps.php index 77284a06c8d1..cc3b1170577e 100755 --- a/components/ILIAS/Notifications/classes/Setup/ilNotificationUpdateSteps.php +++ b/components/ILIAS/Notifications/classes/Setup/ilNotificationUpdateSteps.php @@ -127,16 +127,6 @@ public function step_5(): void public function step_6(): void { - $this->db->insert('settings', [ - 'module' => [ilDBConstants::T_TEXT, 'notifications'], - 'keyword' => [ilDBConstants::T_TEXT, 'osd_vanish'], - 'value' => [ilDBConstants::T_INTEGER, 5] - ]); - $this->db->insert('settings', [ - 'module' => [ilDBConstants::T_TEXT, 'notifications'], - 'keyword' => [ilDBConstants::T_TEXT, 'osd_delay'], - 'value' => [ilDBConstants::T_INTEGER, 500] - ]); } public function step_7(): void @@ -160,11 +150,6 @@ public function step_9(): void [ilDBConstants::T_TEXT], ['osd_interval'] ); - $this->db->manipulateF( - "UPDATE settings SET value = CONCAT(value , '000') WHERE keyword = %s", - [ilDBConstants::T_TEXT], - ['osd_vanish'] - ); $this->db->manipulateF( 'UPDATE usr_pref SET keyword = %s WHERE keyword = %s', [ilDBConstants::T_TEXT, ilDBConstants::T_TEXT], diff --git a/components/ILIAS/Notifications/classes/class.ilObjNotificationAdminGUI.php b/components/ILIAS/Notifications/classes/class.ilObjNotificationAdminGUI.php index cf37371418fa..d56200b7a0c5 100755 --- a/components/ILIAS/Notifications/classes/class.ilObjNotificationAdminGUI.php +++ b/components/ILIAS/Notifications/classes/class.ilObjNotificationAdminGUI.php @@ -50,7 +50,7 @@ public function executeCommand(): void } $this->prepareOutput(); - $this->tabs_gui->activateTab('view'); + $this->tabs_gui->activateTab('settings'); switch (strtolower($this->ctrl->getNextClass())) { case strtolower(ilPermissionGUI::class): @@ -66,6 +66,25 @@ public function executeCommand(): void } } + public function getAdminTabs(): void + { + if ($this->checkPermissionBool('visible,read')) { + $this->tabs_gui->addTab( + 'settings', + $this->lng->txt('settings'), + $this->ctrl->getLinkTarget($this, 'editSettings') + ); + } + + if ($this->checkPermissionBool('edit_permission')) { + $this->tabs_gui->addTab( + 'perm_settings', + $this->lng->txt('perm_settings'), + $this->ctrl->getLinkTargetByClass([$this::class, ilPermissionGUI::class], 'perm') + ); + } + } + /** * @throws ilCtrlException */ @@ -79,8 +98,6 @@ public function showOSDSettings(?Form $form = null): void } else { $values['enable_osd'] = [ 'osd_interval' => (int) $settings->get('osd_interval'), - 'osd_vanish' => (int) $settings->get('osd_vanish'), - 'osd_delay' => (int) $settings->get('osd_delay'), 'osd_play_sound' => (bool) $settings->get('osd_play_sound'), ]; } @@ -109,16 +126,11 @@ public function saveOSDSettings(): void $DIC->notifications()->system()->clear('osd'); $settings->set('enable_osd', '0'); $settings->delete('osd_interval'); - $settings->delete('osd_vanish'); - $settings->delete('osd_delay'); $settings->delete('osd_play_sound'); } else { $settings->set('enable_osd', '1'); $settings->set('osd_interval', ((string) $data['osd']['enable_osd']['osd_interval'])); - $settings->set('osd_vanish', ((string) $data['osd']['enable_osd']['osd_vanish'])); - $settings->set('osd_delay', ((string) $data['osd']['enable_osd']['osd_delay'])); $settings->set('osd_play_sound', ($data['osd']['enable_osd']['osd_play_sound']) ? '1' : '0'); - } } $this->showOSDSettings($form); @@ -144,40 +156,13 @@ static function ($value) { }, $this->lng->txt('osd_error_refresh_interval_too_small') )), - 'osd_vanish' => $this->dic->ui()->factory()->input()->field()->numeric( - $this->lng->txt('osd_vanish'), - $this->lng->txt('osd_vanish_desc') - ) - ->withRequired(true) - ->withValue(5000) - ->withAdditionalTransformation($this->dic->refinery()->custom()->constraint( - static function ($value) { - return $value >= 1000; - }, - $this->lng->txt('osd_error_presentation_time_too_small') - )), - 'osd_delay' => $this->dic->ui()->factory()->input()->field()->numeric( - $this->lng->txt('osd_delay'), - $this->lng->txt('osd_delay_desc') - ) - ->withRequired(true) - ->withValue(500), 'osd_play_sound' => $this->dic->ui()->factory()->input()->field()->checkbox( $this->lng->txt('osd_play_sound'), $this->lng->txt('osd_play_sound_desc') ) ], $this->lng->txt('enable_osd') - )->withByline( - $this->lng->txt('enable_osd_desc') - )->withAdditionalTransformation( - $this->dic->refinery()->custom()->constraint( - static function ($value) { - return $value === null || ($value['osd_interval'] > $value['osd_delay'] + $value['osd_vanish']); - }, - $this->lng->txt('osd_error_refresh_interval_smaller_than_delay_and_vanish_combined') - ) - ); + )->withByline($this->lng->txt('enable_osd_desc')); if ($values !== null) { $enable_osd = $enable_osd->withValue($values['enable_osd'] ?? null); diff --git a/components/ILIAS/Notifications/classes/ilNotificationOSDGUI.php b/components/ILIAS/Notifications/classes/ilNotificationOSDGUI.php index c505fc953538..f7fd81c7a72b 100755 --- a/components/ILIAS/Notifications/classes/ilNotificationOSDGUI.php +++ b/components/ILIAS/Notifications/classes/ilNotificationOSDGUI.php @@ -24,7 +24,6 @@ use iljQueryUtil; use ilLanguage; use ilObjUser; -use ilPlayerUtil; use ilSetting; use ilTemplate; @@ -67,7 +66,6 @@ public function populatePage(): void ); iljQueryUtil::initjQuery($this->page); - ilPlayerUtil::initMediaElementJs($this->page); $this->page->addJavaScript('assets/js/notifications.js'); $this->page->addCSS('assets/css/osd.css'); diff --git a/components/ILIAS/Notifications/resources/browser_notifications.js b/components/ILIAS/Notifications/resources/browser_notifications.js deleted file mode 100644 index a9c959107378..000000000000 --- a/components/ILIAS/Notifications/resources/browser_notifications.js +++ /dev/null @@ -1,279 +0,0 @@ -(function (root, scope, factory) { - scope.BrowserNotifications = factory(root, root.jQuery); -}(window, il, function init(root, $) { - "use strict"; - - const - PERMISSION_GRANTED = "granted", - PERMISSION_DEFAULT = "default", - PERMISSION_DENIED = "denied"; - - class BrowserNotification { - /** - * - * @param {string} title - * @param {Object} options - */ - constructor(title, options = {}) { - if (typeof title !== "string") { - throw new Error('First argument (title) must be a string.'); - } - - if (typeof options !== "object") { - throw new Error('Second argument (options) must be an object.'); - } - - // https://caniuse.com/#feat=mdn-javascript_operators_destructuring_rest_in_objects - /* const { - onShow = null, - onClose = null, - onClick = null, - onError = null, - closeOnClick = false, - timeout = null, - ...rest - } = options; - */ - let onShow = null, - onClose = null, - onClick = null, - onError = null, - closeOnClick = false, - timeout = null, - rest = {}; - - for (let [key, value] of Object.entries(options)) { - if ('onShow' === key) { - onShow = value; - } else if ('onClose' === key) { - onClose = value; - } else if ('onClick' === key) { - onClick = value; - } else if ('onError' === key) { - onError = value; - } else if ('closeOnClick' === key) { - closeOnClick = value; - } else if ('timeout' === key) { - timeout = value; - } else { - rest[key] = value; - } - } - - this.title = title; - this.options = rest; - this.closeOnClick = closeOnClick; - this.timeout = timeout; - - if ($.isFunction(onShow)) { - this.onShow = onShow; - } - - if ($.isFunction(onClick)) { - this.onClick = onClick; - } - - if ($.isFunction(onClose)) { - this.onClose = onClose; - } - - if ($.isFunction(onError)) { - this.onError = onError; - } - } - - /** - * - */ - show() { - this.n = new root.Notification(this.title, this.options); - this.n.addEventListener('show', this, false); - this.n.addEventListener('error', this, false); - this.n.addEventListener('close', this, false); - this.n.addEventListener('click', this, false); - } - - /** - * - * @param e - */ - onShowNotification(e) { - if (this.onShow) { - this.onShow(e); - } - - if (!this.options.requireInteraction && this.timeout && !isNaN(this.timeout)) { - root.setTimeout(this.n.close.bind(this.n), this.timeout * 1000); - } - }; - - /** - * - * @param e - */ - onCloseNotification(e) { - if (this.onClose) { - this.onClose(e); - } - this.destroy(); - }; - - /** - * - * @param e - */ - onClickNotification(e) { - if (this.onClick) { - this.onClick(e); - } - - if (this.closeOnClick) { - this.close(); - } - }; - - /** - * - * @param e - */ - onErrorNotification (e) { - if (this.onError) { - this.onError(e); - } - this.destroy(); - }; - - /** - * - */ - destroy() { - this.n.removeEventListener('show', this, false); - this.n.removeEventListener('error', this, false); - this.n.removeEventListener('close', this, false); - this.n.removeEventListener('click', this, false); - }; - - /** - * - */ - close() { - this.n.close(); - }; - - /** - * - * @param e - */ - handleEvent(e) { - switch (e.type) { - case 'show': - this.onShowNotification(e); - break; - - case 'close': - this.onCloseNotification(e); - break; - - case 'click': - this.onClickNotification(e); - break; - - case 'error': - this.onErrorNotification(e); - break; - } - }; - } - - let methods = {}; - - /** - * - * @returns {boolean} - */ - methods.isSupported = function() { - if (root.location.protocol !== "https:") { - return false; - } - - if (!root.Notification || !root.Notification.requestPermission) { - return false; - } - - return true; - }; - - /** - * - * @returns {boolean} - */ - methods.isBlocked = function() { - return ( - root.Notification && - root.Notification.permission && - root.Notification.permission === PERMISSION_DENIED - ); - }; - - /** - * - * @returns {boolean} - */ - methods.isGranted = function() { - return !methods.needsPermission(); - }; - - /** - * - * @returns {boolean} - */ - methods.needsPermission = function() { - return !( - root.Notification && - root.Notification.permission && - root.Notification.permission === PERMISSION_GRANTED - ); - }; - - /** - * - * @param {string} title - * @param {Object} options - * @returns {*} - */ - methods.notification = function(title, options) { - return new BrowserNotification(title, options); - }; - - /** - * - * @returns {Promise} - */ - methods.requestPermission = function() { - return new Promise(function(resolve, reject) { - setTimeout(() => { - const pc = (permission) => { - switch (permission) { - case PERMISSION_GRANTED: - resolve(permission); - break; - - case PERMISSION_DEFAULT: - case PERMISSION_DENIED: - default: - reject(permission); - break; - } - }; - - let np = root.Notification.requestPermission(pc); - // This stunt is necessary because of old Safari browsers - if (np && typeof np.then === "function") { - np.then(pc).catch(() => reject()); - } - }, 0); - }); - }; - - return methods; -})); \ No newline at end of file diff --git a/components/ILIAS/Notifications/resources/js/dist/BrowserNotifications.min.js b/components/ILIAS/Notifications/resources/js/dist/BrowserNotifications.min.js new file mode 100644 index 000000000000..0701295c3bb9 --- /dev/null +++ b/components/ILIAS/Notifications/resources/js/dist/BrowserNotifications.min.js @@ -0,0 +1,15 @@ +/** + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + */ +this.il=this.il||{},this.il.BrowserNotifications=function(){"use strict";class i{constructor(i,o={}){if("string"!=typeof i)throw new Error("First argument (title) must be a string.");if("object"!=typeof o)throw new Error("Second argument (options) must be an object.");const{onShow:t=null,onClose:n=null,onClick:e=null,onError:s=null,closeOnClick:r=!1,timeout:c=null,...h}=o;this.title=i,this.options=h,this.closeOnClick=r,this.timeout=c,this.onShow="function"==typeof t?t:null,this.onClose="function"==typeof n?n:null,this.onClick="function"==typeof e?e:null,this.onError="function"==typeof s?s:null}show(){this.n=new window.Notification(this.title,this.options),["show","error","close","click"].forEach((i=>this.n.addEventListener(i,this)))}destroy(){this.n&&["show","error","close","click"].forEach((i=>this.n.removeEventListener(i,this)))}close(){this.n?.close()}handleEvent(i){switch(i.type){case"show":this.#i(i);break;case"close":this.#o(i);break;case"click":this.#t(i);break;case"error":this.#n(i);break;default:throw new Error(`Unknown event type: ${i.type}`)}}#i(i){this.onShow?.(i),this.options.requireInteraction||"number"!=typeof this.timeout||Number.isNaN(this.timeout)||window.setTimeout((()=>this.n?.close()),1e3*this.timeout)}#o(i){this.onClose?.(i),this.destroy()}#t(i){this.onClick?.(i),this.closeOnClick&&this.close()}#n(i){this.onError?.(i),this.destroy()}}const o="granted",t="denied";return{isSupported:()=>"https:"===window.location.protocol&&"Notification"in window&&"function"==typeof window.Notification.requestPermission,isBlocked:()=>window.Notification&&window.Notification.permission&&window.Notification.permission===t,isGranted(){return!this.needsPermission()},needsPermission:()=>window.Notification&&window.Notification.permission&&window.Notification.permission===o,notification:(o,t={})=>new i(o,t),requestPermission:()=>new Promise(((i,t)=>{window.setTimeout((()=>{const n=n=>{if(n===o)i(n);else t(n)},e=window.Notification.requestPermission(n);e?.then&&e.then(n).catch((()=>t()))}),0)}))}}(); diff --git a/components/ILIAS/Notifications/resources/js/rollup.config.js b/components/ILIAS/Notifications/resources/js/rollup.config.js new file mode 100644 index 000000000000..278767f2a5a0 --- /dev/null +++ b/components/ILIAS/Notifications/resources/js/rollup.config.js @@ -0,0 +1,40 @@ +/** + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + * + ******************************************************************** */ + +import terser from '@rollup/plugin-terser'; +import copyright from '../../../../../scripts/Copyright-Checker/copyright.js'; +import preserveCopyright from '../../../../../scripts/Copyright-Checker/preserveCopyright.js'; + +export default { + input: './src/index.js', + output: { + file: './dist/BrowserNotifications.min.js', + format: 'iife', + name: 'il.BrowserNotifications', + banner: copyright, + plugins: [ + terser({ + format: { + comments: preserveCopyright, + }, + }), + ], + globals: { + window: 'window', + }, + }, + external: ['window'], +}; diff --git a/components/ILIAS/Notifications/resources/js/src/BrowserNotification.js b/components/ILIAS/Notifications/resources/js/src/BrowserNotification.js new file mode 100644 index 000000000000..4dd18fada93e --- /dev/null +++ b/components/ILIAS/Notifications/resources/js/src/BrowserNotification.js @@ -0,0 +1,118 @@ +/** + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + * + ******************************************************************** */ + +export default class BrowserNotification { + /** + * @param {string} title - Title of the notification. + * @param {Object} [options={}] - Additional options and event handlers. + * @param {Function} [options.onShow] - Called on 'show' event. + * @param {Function} [options.onClose] - Called on 'close' event. + * @param {Function} [options.onClick] - Called on 'click' event. + * @param {Function} [options.onError] - Called on 'error' event. + * @param {boolean} [options.closeOnClick=false] - Close on click? + * @param {number|null} [options.timeout=null] - Auto-close after N seconds. + */ + constructor(title, options = {}) { + if (typeof title !== 'string') { + throw new Error('First argument (title) must be a string.'); + } + if (typeof options !== 'object') { + throw new Error('Second argument (options) must be an object.'); + } + + const { + onShow = null, + onClose = null, + onClick = null, + onError = null, + closeOnClick = false, + timeout = null, + ...rest + } = options; + + this.title = title; + this.options = rest; + this.closeOnClick = closeOnClick; + this.timeout = timeout; + this.onShow = typeof onShow === 'function' ? onShow : null; + this.onClose = typeof onClose === 'function' ? onClose : null; + this.onClick = typeof onClick === 'function' ? onClick : null; + this.onError = typeof onError === 'function' ? onError : null; + } + + show() { + this.n = new window.Notification(this.title, this.options); + ['show', 'error', 'close', 'click'].forEach((type) => this.n.addEventListener(type, this)); + } + + destroy() { + if (!this.n) return; + ['show', 'error', 'close', 'click'].forEach((type) => this.n.removeEventListener(type, this)); + } + + close() { + this.n?.close(); + } + + /** + * @param {Event} e + */ + handleEvent(e) { + switch (e.type) { + case 'show': this.#onShow(e); break; + case 'close': this.#onClose(e); break; + case 'click': this.#onClick(e); break; + case 'error': this.#onError(e); break; + default: throw new Error(`Unknown event type: ${e.type}`); + } + } + + /** + * @param {Event} e + */ + #onShow(e) { + this.onShow?.(e); + if (!this.options.requireInteraction && typeof this.timeout === 'number' && !Number.isNaN(this.timeout)) { + window.setTimeout(() => this.n?.close(), this.timeout * 1000); + } + } + + /** + * @param {Event} e + */ + #onClose(e) { + this.onClose?.(e); + this.destroy(); + } + + /** + * @param {Event} + */ + #onClick(e) { + this.onClick?.(e); + if (this.closeOnClick) { + this.close(); + } + } + + /** + * @param {Event} + */ + #onError(e) { + this.onError?.(e); + this.destroy(); + } +} diff --git a/components/ILIAS/Notifications/resources/js/src/index.js b/components/ILIAS/Notifications/resources/js/src/index.js new file mode 100644 index 000000000000..4f1d9ec09fe7 --- /dev/null +++ b/components/ILIAS/Notifications/resources/js/src/index.js @@ -0,0 +1,103 @@ +/** + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + * + ******************************************************************** */ + +import BrowserNotification from './BrowserNotification.js'; + +const PERMISSION_DEFAULT = 'default'; +const PERMISSION_GRANTED = 'granted'; +const PERMISSION_DENIED = 'denied'; + +const BrowserNotifications = { + /** + * @returns {boolean} + */ + isSupported() { + return window.location.protocol === 'https:' + && 'Notification' in window + && typeof window.Notification.requestPermission === 'function'; + }, + + /** + * @returns {boolean} + */ + isBlocked() { + return window.Notification + && window.Notification.permission + && window.Notification.permission === PERMISSION_DENIED; + }, + + /** + * @returns {boolean} + */ + isGranted() { + return !this.needsPermission(); + }, + + /** + * @returns {boolean} + */ + needsPermission() { + return window.Notification + && window.Notification.permission + && window.Notification.permission === PERMISSION_GRANTED; + }, + + /** + * @param {string} title - The title of the notification. + * @param {Object} [options={}] - Notification options and event callbacks. + * @param {Function} [options.onShow] - Called on 'show'. + * @param {Function} [options.onClose] - Called on 'close'. + * @param {Function} [options.onClick] - Called on 'click'. + * @param {Function} [options.onError] - Called on 'error'. + * @param {boolean} [options.closeOnClick=false] - Whether to close on click. + * @param {number|null} [options.timeout=null] - Auto-close timeout in seconds. + * @returns {BrowserNotification} + */ + notification(title, options = {}) { + return new BrowserNotification(title, options); + }, + + /** + * @returns {Promise} The resulting permission value. + */ + requestPermission() { + return new Promise((resolve, reject) => { + window.setTimeout(() => { + const handle = (permission) => { + switch (permission) { + case PERMISSION_GRANTED: + resolve(permission); + break; + + case PERMISSION_DEFAULT: + case PERMISSION_DENIED: + default: + reject(permission); + break; + } + }; + + const result = window.Notification.requestPermission(handle); + // This stunt is necessary because of old Safari browsers + if (result?.then) { + result.then(handle).catch(() => reject()); + } + }, 0); + }); + }, +}; + +export default BrowserNotifications; diff --git a/components/ILIAS/OnScreenChat/classes/class.ilOnScreenChatGUI.php b/components/ILIAS/OnScreenChat/classes/class.ilOnScreenChatGUI.php index 23d2fbd4ac6e..67cbc03c068e 100755 --- a/components/ILIAS/OnScreenChat/classes/class.ilOnScreenChatGUI.php +++ b/components/ILIAS/OnScreenChat/classes/class.ilOnScreenChatGUI.php @@ -313,7 +313,7 @@ public static function initializeFrontend(ilGlobalTemplateInterface $page): void $page->addJavaScript('assets/js/socket.io.min.js'); $page->addJavaScript('assets/js/Chatroom.min.js'); $page->addJavaScript('assets/js/moment-with-locales.min.js'); - $page->addJavaScript('assets/js/browser_notifications.js'); + $page->addJavaScript('assets/js/BrowserNotifications.min.js'); $page->addJavaScript('assets/js/onscreenchat-notifications.js'); $page->addJavaScript('assets/js/moment.js'); $page->addJavaScript('assets/js/chat.js'); diff --git a/components/ILIAS/OpenIdConnect/classes/class.ilOpenIdConnectUserSync.php b/components/ILIAS/OpenIdConnect/classes/class.ilOpenIdConnectUserSync.php index e76e12da93b1..6b3b31b40e37 100755 --- a/components/ILIAS/OpenIdConnect/classes/class.ilOpenIdConnectUserSync.php +++ b/components/ILIAS/OpenIdConnect/classes/class.ilOpenIdConnectUserSync.php @@ -315,7 +315,20 @@ protected function parseRoleAssignments(): array } if (is_array($this->user_info->{$role_attribute})) { - $roles_claim = array_map(trim(...), $this->user_info->{$role_attribute}); + $roles_claim = array_map( + static function (mixed $value): string { + return match (true) { + is_string($value) => trim($value), + $value instanceof stdClass && property_exists($value, 'id') => trim((string) $value->id), + $value instanceof stdClass && property_exists($value, 'value') => trim((string) $value->value), + default => throw new DomainException(sprintf( + 'Unexpected role value type, please check your provider configuration: %s', + print_r($value, true) + )), + }; + }, + $this->user_info->{$role_attribute} + ); if (!in_array($role_value, $roles_claim, true)) { $this->logger->debug('User account has no ' . $role_value); continue; diff --git a/components/ILIAS/OrgUnit/classes/ARHelper/BaseCommands.php b/components/ILIAS/OrgUnit/classes/ARHelper/BaseCommands.php index 8567da62ddcf..0e7a9ee490a7 100755 --- a/components/ILIAS/OrgUnit/classes/ARHelper/BaseCommands.php +++ b/components/ILIAS/OrgUnit/classes/ARHelper/BaseCommands.php @@ -1,4 +1,5 @@ query->has($this->row_id_token->getName())) { + if ($this->query->has($this->row_id_token->getName())) { return $this->query->retrieve( $this->row_id_token->getName(), - $this->refinery->custom()->transformation(fn($v) => (int)array_shift($v)) + $this->refinery->custom()->transformation(fn($v) => (int) array_shift($v)) ); } throw new \Exception('no position-id in query'); diff --git a/components/ILIAS/OrgUnit/classes/ARHelper/BaseForm.php b/components/ILIAS/OrgUnit/classes/ARHelper/BaseForm.php index ddc6f2b7abbe..41a94c840640 100755 --- a/components/ILIAS/OrgUnit/classes/ARHelper/BaseForm.php +++ b/components/ILIAS/OrgUnit/classes/ARHelper/BaseForm.php @@ -1,4 +1,5 @@ tabs_gui = $DIC->tabs(); $this->toolbar = $DIC->toolbar(); $this->lng = $DIC->language(); - $this->ilAccess = $DIC->access(); + $this->ilAccess = $DIC->access(); $this->lng->loadLanguageModule('user'); if (!$this->ilAccess->checkaccess("write", "", $this->parent_gui->getObject()->getRefId())) { $main_tpl->setOnScreenMessage('failure', $this->lng->txt("permission_denied"), true); diff --git a/components/ILIAS/OrgUnit/classes/Extension/class.ilOrgUnitExtension.php b/components/ILIAS/OrgUnit/classes/Extension/class.ilOrgUnitExtension.php index 88ff7bc5aed8..5b9f46040790 100755 --- a/components/ILIAS/OrgUnit/classes/Extension/class.ilOrgUnitExtension.php +++ b/components/ILIAS/OrgUnit/classes/Extension/class.ilOrgUnitExtension.php @@ -1,4 +1,5 @@ logger->write(__FILE__ . ":" . __LINE__ . " User with id $user_id could not be found."); $this->tpl->setOnScreenMessage('failure', $this->lng->txt('user_not_found_to_delete')); } - if (!$tmp_obj = ilObjectFactory::getInstanceByObjId((int)$user_id, false)) { + if (!$tmp_obj = ilObjectFactory::getInstanceByObjId((int) $user_id, false)) { continue; } $tmp_obj->delete(); @@ -218,7 +218,7 @@ public function deleteUsers(): void $confirm->setConfirm($this->lng->txt('delete'), 'performDeleteUsers'); $confirm->setCancel($this->lng->txt('cancel'), 'index'); foreach ($_POST['id'] as $user) { - $name = ilObjUser::_lookupName((int)$user); + $name = ilObjUser::_lookupName((int) $user); $confirm->addItem( 'user_ids[]', $user, @@ -262,7 +262,7 @@ public function assignRoles(): void $f_result[$counter][] = ilLegacyFormElementsUtil::formCheckbox( in_array($role['obj_id'], $ass_roles) ? true : false, 'role_ids[]', - (string)$role['obj_id'], + (string) $role['obj_id'], $disabled ); $f_result[$counter][] = $role_obj->getTitle(); diff --git a/components/ILIAS/OrgUnit/classes/Positions/Authorities/OrgUnitAuthorityRepository.php b/components/ILIAS/OrgUnit/classes/Positions/Authorities/OrgUnitAuthorityRepository.php index 676837c09493..55169135274b 100755 --- a/components/ILIAS/OrgUnit/classes/Positions/Authorities/OrgUnitAuthorityRepository.php +++ b/components/ILIAS/OrgUnit/classes/Positions/Authorities/OrgUnitAuthorityRepository.php @@ -1,4 +1,5 @@ getMulti()) { - $output = "
{$output}
"; + $output = "
{$output}
"; $config = json_encode($this->input_options); $options = json_encode([ 'limit' => 999999, @@ -391,7 +390,10 @@ public function insert(\ilTemplate $a_tpl): void 'locale' => $this->lng->getLangKey() ]); global $tpl; - $tpl->addOnLoadCode("il.DataCollection.genericMultiLineInit('{$this->getFieldId()}',$config,$options);"); + $tpl->addOnLoadCode(" + il.DataCollection.genericMultiLineInit('{$this->getFieldId()}',$config,$options); + document.body.querySelector('#{$this->getFieldId()}').removeAttribute('style'); + "); } $a_tpl->setCurrentBlock("prop_generic"); diff --git a/components/ILIAS/OrgUnit/classes/Positions/Operation/OrgUnitOperationContextRepository.php b/components/ILIAS/OrgUnit/classes/Positions/Operation/OrgUnitOperationContextRepository.php index 3598398f92ff..3b15ecc08d66 100755 --- a/components/ILIAS/OrgUnit/classes/Positions/Operation/OrgUnitOperationContextRepository.php +++ b/components/ILIAS/OrgUnit/classes/Positions/Operation/OrgUnitOperationContextRepository.php @@ -1,4 +1,5 @@ db->fetchAssoc($res)) { - $operation = (new ilOrgUnitOperation((int)$rec['operation_id'])) - ->withOperationString((string)$rec['operation_string']) - ->withDescription((string)$rec["description"]) - ->withListOrder((int)$rec["list_order"]) - ->withContextId((int)$rec['context_id']); + $operation = (new ilOrgUnitOperation((int) $rec['operation_id'])) + ->withOperationString((string) $rec['operation_string']) + ->withDescription((string) $rec["description"]) + ->withListOrder((int) $rec["list_order"]) + ->withContextId((int) $rec['context_id']); $ret[] = $operation; } @@ -199,11 +200,11 @@ public function getOperationsByContextId(int $context_id): array $ret = []; while ($rec = $this->db->fetchAssoc($res)) { - $operation = (new ilOrgUnitOperation((int)$rec['operation_id'])) - ->withOperationString((string)$rec['operation_string']) - ->withDescription((string)$rec["description"]) - ->withListOrder((int)$rec["list_order"]) - ->withContextId((int)$rec['context_id']); + $operation = (new ilOrgUnitOperation((int) $rec['operation_id'])) + ->withOperationString((string) $rec['operation_string']) + ->withDescription((string) $rec["description"]) + ->withListOrder((int) $rec["list_order"]) + ->withContextId((int) $rec['context_id']); $ret[] = $operation; } @@ -224,11 +225,11 @@ public function getOperationsByContextName(string $context): array $ret = []; while ($rec = $this->db->fetchAssoc($res)) { - $operation = (new ilOrgUnitOperation((int)$rec['operation_id'])) - ->withOperationString((string)$rec['operation_string']) - ->withDescription((string)$rec["description"]) - ->withListOrder((int)$rec["list_order"]) - ->withContextId((int)$rec['context_id']); + $operation = (new ilOrgUnitOperation((int) $rec['operation_id'])) + ->withOperationString((string) $rec['operation_string']) + ->withDescription((string) $rec["description"]) + ->withListOrder((int) $rec["list_order"]) + ->withContextId((int) $rec['context_id']); $ret[] = $operation; } diff --git a/components/ILIAS/OrgUnit/classes/Positions/Operation/class.ilOrgUnitOperationQueries.php b/components/ILIAS/OrgUnit/classes/Positions/Operation/class.ilOrgUnitOperationQueries.php index ad77e67697b5..5064fb8f4139 100755 --- a/components/ILIAS/OrgUnit/classes/Positions/Operation/class.ilOrgUnitOperationQueries.php +++ b/components/ILIAS/OrgUnit/classes/Positions/Operation/class.ilOrgUnitOperationQueries.php @@ -1,4 +1,5 @@ operationRepo->getById((int)$operation_id); + $ret[] = $this->operationRepo->getById((int) $operation_id); } return $ret; } diff --git a/components/ILIAS/OrgUnit/classes/Positions/Permissions/class.ilOrgUnitPermissionGUI.php b/components/ILIAS/OrgUnit/classes/Positions/Permissions/class.ilOrgUnitPermissionGUI.php index 56413a63cf4f..531e0bdcba1b 100755 --- a/components/ILIAS/OrgUnit/classes/Positions/Permissions/class.ilOrgUnitPermissionGUI.php +++ b/components/ILIAS/OrgUnit/classes/Positions/Permissions/class.ilOrgUnitPermissionGUI.php @@ -1,4 +1,5 @@ getAllChildren($orgu_id); + $recursive_orgu_ids = array_merge($recursive_orgu_ids, $tree->getAllChildren($orgu_id)); } - return $recursive_orgu_ids; } @@ -425,13 +425,13 @@ protected function getUserDataByOrgUnitsAndPosition( ; $res = $this->db->query($query); - if($count_only) { + if ($count_only) { return $this->db->numRows($res); } $users = []; while ($rec = $this->db->fetchAssoc($res)) { - $rec['active'] = (bool)$rec['active']; + $rec['active'] = (bool) $rec['active']; $users[] = $rec; } return $users; @@ -457,9 +457,10 @@ public function getRows( $orgu_ids = $additional_parameters['orgu_ids']; $position_id = $additional_parameters['position_id']; $lp_visible = $additional_parameters['lp_visible_ref_ids']; + $write_access = $additional_parameters['write_access']; foreach ($this->getUserDataByOrgUnitsAndPosition($orgu_ids, $position_id, false, $range, $order) as $record) { - $row_id = implode('_', [(string)$position_id, (string)$record['usr_id']]); + $row_id = implode('_', [(string) $position_id, (string) $record['usr_id']]); yield $row_builder->buildDataRow($row_id, $record) ->withDisabledAction( 'show_learning_progress', @@ -468,7 +469,12 @@ public function getRows( && ilObjUserTracking::_enabledLearningProgress() && ilObjUserTracking::_enabledUserRelatedData() ) + ) + ->withDisabledAction( + 'remove', + !$write_access ); + } } } diff --git a/components/ILIAS/OrgUnit/classes/Positions/UserAssignment/class.ilOrgUnitUserAssignmentGUI.php b/components/ILIAS/OrgUnit/classes/Positions/UserAssignment/class.ilOrgUnitUserAssignmentGUI.php index 974fa6e6b75a..604e2f7dda45 100755 --- a/components/ILIAS/OrgUnit/classes/Positions/UserAssignment/class.ilOrgUnitUserAssignmentGUI.php +++ b/components/ILIAS/OrgUnit/classes/Positions/UserAssignment/class.ilOrgUnitUserAssignmentGUI.php @@ -19,7 +19,6 @@ declare(strict_types=1); use ILIAS\components\OrgUnit\ARHelper\BaseCommands; - use ILIAS\UI\Component\Table; /** @@ -101,11 +100,14 @@ protected function index(): void $types = $this->positionRepo->getArray('id', 'title'); $this->ctrl->setParameterByClass(ilRepositorySearchGUI::class, 'addusertype', 'staff'); - ilRepositorySearchGUI::fillAutoCompleteToolbar($this, $this->toolbar, array( - 'auto_complete_name' => $this->lng->txt('user'), - 'user_type' => $types, - 'submit_name' => $this->lng->txt('add'), - )); + + if ($this->access->checkAccess("write", "", $this->getParentRefId())) { + ilRepositorySearchGUI::fillAutoCompleteToolbar($this, $this->toolbar, array( + 'auto_complete_name' => $this->lng->txt('user'), + 'user_type' => $types, + 'submit_name' => $this->lng->txt('add'), + )); + } $tables = []; foreach ($this->positionRepo->getPositionsForOrgUnit($this->getParentRefId()) as $ilOrgUnitPosition) { @@ -247,7 +249,7 @@ protected function getStaffTable( ]; $remove_cmd = self::CMD_REMOVE_CONFIRM; - if($recursive) { + if ($recursive) { $remove_cmd = self::CMD_REMOVE_RECURSIVELY_CONFIRM; $columns['orgu_title'] = $this->ui_factory->table()->column()->text($this->lng->txt("obj_orgu")); } @@ -272,13 +274,14 @@ protected function getStaffTable( ); return $this->ui_factory->table() - ->data($position->getTitle(), $columns, $this->assignmentRepo) + ->data($this->assignmentRepo, $position->getTitle(), $columns) ->withId(implode('.', ['orgustaff',$this->getParentRefId(),$position->getId()])) ->withActions($actions) ->withAdditionalParameters([ 'position_id' => $position->getId(), 'orgu_ids' => $orgu_ids, - 'lp_visible_ref_ids' => $lp_visible + 'lp_visible_ref_ids' => $lp_visible, + 'write_access' => $this->access->checkAccess("write", "", $this->getParentRefId()) ]) ->withRequest($this->request); } @@ -288,7 +291,7 @@ protected function getStaffTable( */ protected function getPositionAndUserIdFromTableQuery(): array { - if($this->query->has($this->row_id_token->getName())) { + if ($this->query->has($this->row_id_token->getName())) { return $this->query->retrieve( $this->row_id_token->getName(), $this->refinery->custom()->transformation( diff --git a/components/ILIAS/OrgUnit/classes/Positions/UserAssignment/class.ilOrgUnitUserAssignmentQueries.php b/components/ILIAS/OrgUnit/classes/Positions/UserAssignment/class.ilOrgUnitUserAssignmentQueries.php index 899cda43c575..f2113413cb21 100755 --- a/components/ILIAS/OrgUnit/classes/Positions/UserAssignment/class.ilOrgUnitUserAssignmentQueries.php +++ b/components/ILIAS/OrgUnit/classes/Positions/UserAssignment/class.ilOrgUnitUserAssignmentQueries.php @@ -1,4 +1,5 @@ getAssignmentRepo()->get($user_id, $position_id, $orgu_id); if (!$assignment) { - throw new ilException('UserAssignment not found'); + throw new ilException('UserAssignment not found'); } return $assignment; } diff --git a/components/ILIAS/OrgUnit/classes/Positions/class.ilOrgUnitPosition.php b/components/ILIAS/OrgUnit/classes/Positions/class.ilOrgUnitPosition.php index 3d3c8a4be1ef..30475fc939be 100755 --- a/components/ILIAS/OrgUnit/classes/Positions/class.ilOrgUnitPosition.php +++ b/components/ILIAS/OrgUnit/classes/Positions/class.ilOrgUnitPosition.php @@ -1,4 +1,5 @@ getAllPositions($range, $order) as $pos) { - $row_id = (string)$pos->getId(); + $row_id = (string) $pos->getId(); $record = [ 'title' => $pos->getTitle(), - 'description' => $pos->getDescription() . 'dd', + 'description' => $pos->getDescription(), 'authorities' => implode("
", $this->getAuthorityDescription($pos->getAuthorities())), 'is_core_position' => $pos->isCorePosition(), ]; diff --git a/components/ILIAS/OrgUnit/classes/Positions/class.ilOrgUnitPositionFormGUI.php b/components/ILIAS/OrgUnit/classes/Positions/class.ilOrgUnitPositionFormGUI.php index ae686f4917b1..1219a26eb796 100755 --- a/components/ILIAS/OrgUnit/classes/Positions/class.ilOrgUnitPositionFormGUI.php +++ b/components/ILIAS/OrgUnit/classes/Positions/class.ilOrgUnitPositionFormGUI.php @@ -1,4 +1,5 @@ ui_factory->table() - ->data('', $columns, $this->positionRepo) + ->data($this->positionRepo, '', $columns) ->withId('orgu_positions') ->withActions($actions); } diff --git a/components/ILIAS/OrgUnit/classes/Provider/OrgUnitMainBarProvider.php b/components/ILIAS/OrgUnit/classes/Provider/OrgUnitMainBarProvider.php index 93fac552aade..9ca0da18a11d 100755 --- a/components/ILIAS/OrgUnit/classes/Provider/OrgUnitMainBarProvider.php +++ b/components/ILIAS/OrgUnit/classes/Provider/OrgUnitMainBarProvider.php @@ -1,4 +1,5 @@ setTypeWhiteList($this->getTreeWhiteList()); $tree->setRootId(ilObjOrgUnit::getRootOrgRefId()); - $ref_id = (int)($_GET['item_ref_id'] ?? $_GET['ref_id'] ?? 0); + $ref_id = (int) ($_GET['item_ref_id'] ?? $_GET['ref_id'] ?? 0); if ($ref_id !== 0) { - $tree->setPathOpen((int)$ref_id); + $tree->setPathOpen((int) $ref_id); } $tree->setOrderField('title'); diff --git a/components/ILIAS/OrgUnit/classes/Settings/class.ilObjOrgUnitSettingsFormGUI.php b/components/ILIAS/OrgUnit/classes/Settings/class.ilObjOrgUnitSettingsFormGUI.php index 5b52488c8f04..a777db9db3dd 100755 --- a/components/ILIAS/OrgUnit/classes/Settings/class.ilObjOrgUnitSettingsFormGUI.php +++ b/components/ILIAS/OrgUnit/classes/Settings/class.ilObjOrgUnitSettingsFormGUI.php @@ -1,4 +1,5 @@ irss->manage()->find($identifier)) { + if ($rid = $this->irss->manage()->find($identifier)) { $this->irss->manage()->remove($rid, new ilOrgUnitTypeStakeholder()); } } @@ -783,7 +783,7 @@ public function removeIconFromIrss(string $identifier): void public function getIconSrc(): string { $rid = $this->irss->manage()->find($this->getIconIdentifier()); - if(!$rid) { + if (!$rid) { return ''; } return $this->irss->consume()->src($rid)->getSrc(); diff --git a/components/ILIAS/OrgUnit/classes/Types/class.ilOrgUnitTypeCustomIconsFormGUI.php b/components/ILIAS/OrgUnit/classes/Types/class.ilOrgUnitTypeCustomIconsFormGUI.php index 6a7ea7d00e4b..2d06b6097f11 100755 --- a/components/ILIAS/OrgUnit/classes/Types/class.ilOrgUnitTypeCustomIconsFormGUI.php +++ b/components/ILIAS/OrgUnit/classes/Types/class.ilOrgUnitTypeCustomIconsFormGUI.php @@ -1,4 +1,5 @@ lng->txt('file_allowed_suffixes') . ' .svg' ); - if($current_identifier) { + if ($current_identifier) { $input = $input->withValue([$current_identifier]); } @@ -256,10 +256,10 @@ private function updateCustomIcons(): void $data = $form->getData(); - if(!is_null($data)) { + if (!is_null($data)) { $new_icon_id = current($data) ? current($data) : ''; $identifier = $type->getIconIdentifier(); - if($identifier && $new_icon_id == '') { + if ($identifier && $new_icon_id == '') { $type->removeIconFromIrss($identifier); } $type = $type->withIconIdentifier($new_icon_id); @@ -287,10 +287,10 @@ protected function getCurrentOrgUnitType(): ilOrgUnitType protected function getRowIdFromQuery(): int { - if($this->query->has($this->row_id_token->getName())) { + if ($this->query->has($this->row_id_token->getName())) { return $this->query->retrieve( $this->row_id_token->getName(), - $this->refinery->custom()->transformation(fn($v) => (int)array_shift($v)) + $this->refinery->custom()->transformation(fn($v) => (int) array_shift($v)) ); } return 0; @@ -338,10 +338,10 @@ function (?array $record_ids) use ($type, $selected_ids) { $record_ids_removed = array_diff($selected_ids, $record_ids); $record_ids_added = array_diff($record_ids, $selected_ids); foreach ($record_ids_added as $record_id) { - $type->assignAdvancedMDRecord((int)$record_id); + $type->assignAdvancedMDRecord((int) $record_id); } foreach ($record_ids_removed as $record_id) { - $type->deassignAdvancedMdRecord((int)$record_id); + $type->deassignAdvancedMdRecord((int) $record_id); } return true; } @@ -373,7 +373,7 @@ private function updateAMD(): void ) ->withRequest($this->request); - if($form->getData()) { + if ($form->getData()) { $this->tpl->setOnScreenMessage('success', $this->lng->txt('msg_obj_modified'), true); $this->ctrl->redirect($this); } else { @@ -425,7 +425,7 @@ protected function getTable(): Table\Data ]; return $this->ui_factory->table() - ->data('', $columns, $this->getTableDataRetrieval()) + ->data($this->getTableDataRetrieval(), '', $columns) ->withId('orgu_types') ->withActions($actions); } @@ -481,7 +481,7 @@ public function getRows( $records = array_slice($records, $range->getStart(), $range->getLength()); } foreach ($records as $record) { - $row_id = (string)$record['id']; + $row_id = (string) $record['id']; yield $row_builder->buildDataRow($row_id, $record); } } @@ -499,7 +499,7 @@ protected function getEditForm(ilOrgUnitType $type): StandardForm { $title = $this->lng->txt('orgu_type_add'); $action = $this->getSingleTypeLinkTarget('update'); - if($type->getId()) { + if ($type->getId()) { $title = $this->lng->txt('orgu_type_edit'); } @@ -550,7 +550,7 @@ function ($v) use ($type) { //: ilOrgUnitType $type->setDescription($description, $lang_code); } return $type; - } catch(ilOrgUnitTypePluginException $e) { + } catch (ilOrgUnitTypePluginException $e) { return $e->getMessage(); } } @@ -579,7 +579,7 @@ private function update(): void ->withRequest($this->request); $type = $form->getData(); - if($type && $type instanceof ilOrgUnitType) { + if ($type && $type instanceof ilOrgUnitType) { $type->save(); $this->tpl->setOnScreenMessage('success', $this->lng->txt('msg_obj_modified'), true); $this->ctrl->redirect($this); diff --git a/components/ILIAS/OrgUnit/classes/Types/class.ilOrgUnitTypeHookPlugin.php b/components/ILIAS/OrgUnit/classes/Types/class.ilOrgUnitTypeHookPlugin.php index 1553533955c8..dbc538259d4d 100755 --- a/components/ILIAS/OrgUnit/classes/Types/class.ilOrgUnitTypeHookPlugin.php +++ b/components/ILIAS/OrgUnit/classes/Types/class.ilOrgUnitTypeHookPlugin.php @@ -1,4 +1,5 @@ * @author : Martin Studer * @author : Stefan Wanzenried - * @ilCtrl_IsCalledBy ilObjOrgUnitGUI: ilAdministrationGUI, ilObjPluginDispatchGUI + * @ilCtrl_IsCalledBy ilObjOrgUnitGUI: ilAdministrationGUI, ilObjPluginDispatchGUI, ilRepositoryGUI * @ilCtrl_Calls ilObjOrgUnitGUI: ilPermissionGUI, ilPageObjectGUI * @ilCtrl_Calls ilObjOrgUnitGUI: ilObjUserGUI, ilObjUserFolderGUI * @ilCtrl_Calls ilObjOrgUnitGUI: ilInfoScreenGUI, ilObjStyleSheetGUI * @ilCtrl_Calls ilObjOrgUnitGUI: ilCommonActionDispatcherGUI * @ilCtrl_Calls ilObjOrgUnitGUI: ilColumnGUI, ilObjectCopyGUI, ilUserTableGUI * @ilCtrl_Calls ilObjOrgUnitGUI: ilDidacticTemplateGUI, illearningprogressgui - * @ilCtrl_Calls ilObjOrgUnitGUI: ilObjectTranslationGUI, ilLocalUserGUI, ilOrgUnitExportGUI + * @ilCtrl_Calls ilObjOrgUnitGUI: ILIAS\ILIASObject\Properties\Translations\TranslationGUI, ilLocalUserGUI, ilOrgUnitExportGUI * @ilCtrl_Calls ilObjOrgUnitGUI: ilExtIdGUI * @ilCtrl_Calls ilObjOrgUnitGUI: ilOrgUnitSimpleImportGUI, ilOrgUnitSimpleUserImportGUI * @ilCtrl_Calls ilObjOrgUnitGUI: ilOrgUnitTypeGUI, ilOrgUnitPositionGUI @@ -246,10 +247,22 @@ public function executeCommand(): void $ilOrgUnitExportGUI->addFormat('xml'); $this->ctrl->forwardCommand($ilOrgUnitExportGUI); break; - case strtolower(ilObjectTranslationGUI::class): + case strtolower(TranslationGUI::class): $this->tabs_gui->activateTab(self::TAB_SETTINGS); $this->setSubTabsSettings('edit_translations'); - $translations_gui = new ilObjectTranslationGUI($this); + $translations_gui = new TranslationGUI( + $this->getObject(), + $this->lng, + $this->access, + $this->user, + $this->ctrl, + $this->tpl, + $this->ui_factory, + $this->ui_renderer, + $this->http, + $this->refinery, + $this->toolbar + ); $translations_gui->supportContentTranslation(false); $this->ctrl->forwardCommand($translations_gui); break; @@ -288,6 +301,7 @@ public function executeCommand(): void case 'render': case 'cancel': case 'cancelDelete': + case 'return': $this->view(); break; case 'performPaste': @@ -408,9 +422,12 @@ public function view(): void $this->tabs_gui->removeSubTab("ordering"); // Mantis 0014728 } - public function showPossibleSubObjects(): void + protected function showPossibleSubObjects(): void { $subtypes = $this->getCreatableObjectTypes(); + if ($subtypes === []) { + return; + } $gui = new ILIAS\ILIASObject\Creation\AddNewItemGUI( [$this->buildGroup( self::class, @@ -588,7 +605,7 @@ protected function setSubTabsSettings(string $active_tab_id): void $this->tabs_gui->addSubTab( "edit_translations", $this->lng->txt("obj_multilinguality"), - $this->ctrl->getLinkTargetByClass("ilobjecttranslationgui", "listTranslations") + $this->ctrl->getLinkTargetByClass(TranslationGUI::class, "listTranslations") ); $ilOrgUnitType = $this->object->getOrgUnitType(); @@ -604,7 +621,7 @@ protected function setSubTabsSettings(string $active_tab_id): void $this->tabs_gui->setSubTabActive($active_tab_id); switch ($next_class) { - case 'iltranslationgui': + case strtolower(TranslationGUI::class): $this->tabs_gui->setSubTabActive("edit_translations"); break; case '': @@ -634,7 +651,7 @@ public function setContentSubTabs(): void $this->tabs_gui->addSubTab( "import", $this->lng->txt("import"), - $this->ctrl->getLinkTargetByClass("ilOrgUnitSimpleImportGUI", "chooseImport") + $this->ctrl->getLinkTargetByClass([self::class, ilOrgUnitSimpleImportGUI::class], "chooseImport") ); } } diff --git a/components/ILIAS/OrgUnit/classes/class.ilObjOrgUnitListGUI.php b/components/ILIAS/OrgUnit/classes/class.ilObjOrgUnitListGUI.php index 2105adfae466..a1cef4d4d082 100755 --- a/components/ILIAS/OrgUnit/classes/class.ilObjOrgUnitListGUI.php +++ b/components/ILIAS/OrgUnit/classes/class.ilObjOrgUnitListGUI.php @@ -1,4 +1,5 @@ @@ -76,7 +79,7 @@ public function insertInfoScreenCommand(): void if ($this->std_cmd_only) { return; } - $cmd_link = $this->ctrl->getLinkTargetByClass("ilinfoscreengui", "showSummary"); + $cmd_link = $this->ctrl->getLinkTargetByClass([ilObjOrgUnitGUI::class, ilInfoScreenGUI::class], "showSummary"); $cmd_frame = $this->getCommandFrame("infoScreen"); $this->insertCommand( diff --git a/components/ILIAS/OrgUnit/classes/class.ilOrgUnitAppEventListener.php b/components/ILIAS/OrgUnit/classes/class.ilOrgUnitAppEventListener.php index e6c80aff5b7e..7d121c977450 100755 --- a/components/ILIAS/OrgUnit/classes/class.ilOrgUnitAppEventListener.php +++ b/components/ILIAS/OrgUnit/classes/class.ilOrgUnitAppEventListener.php @@ -1,4 +1,5 @@ access = $DIC->access(); $this->settings = $DIC->settings(); $this->http_post = $DIC->http()->wrapper()->post(); - $this->http_query= $DIC->http()->wrapper()->query(); + $this->http_query = $DIC->http()->wrapper()->query(); $this->refinery = $DIC["refinery"]; } diff --git a/components/ILIAS/OrgUnit/classes/class.ilOrgUnitExportGUI.php b/components/ILIAS/OrgUnit/classes/class.ilOrgUnitExportGUI.php index 712ddaadf81e..a7214f49be77 100755 --- a/components/ILIAS/OrgUnit/classes/class.ilOrgUnitExportGUI.php +++ b/components/ILIAS/OrgUnit/classes/class.ilOrgUnitExportGUI.php @@ -33,7 +33,7 @@ class ilOrgUnitExportGUI extends ilExportGUI public function __construct(ilObjOrgUnitGUI $a_parent_gui, /*null|ilObject|ilObjOrgUnit*/ ?ilObject $a_main_obj = null) { - parent::__construct($a_parent_gui, $a_main_obj); + parent::__construct($a_parent_gui, $a_main_obj, false, false); global $DIC; $ilToolbar = $DIC->toolbar(); diff --git a/components/ILIAS/OrgUnit/classes/class.ilOrgUnitExportOptionXML.php b/components/ILIAS/OrgUnit/classes/class.ilOrgUnitExportOptionXML.php index 223afa6e4f2c..5f00816aea4e 100644 --- a/components/ILIAS/OrgUnit/classes/class.ilOrgUnitExportOptionXML.php +++ b/components/ILIAS/OrgUnit/classes/class.ilOrgUnitExportOptionXML.php @@ -62,7 +62,7 @@ public function isObjectSupported(ObjectId $object_id): bool public function getLabel(): string { $this->lng->loadLanguageModule('exp'); - return $this->lng->txt('exp_format_dropdown-xml'); + return $this->lng->txt('simple_xml'); } public function onExportOptionSelected(ilExportHandlerConsumerContextInterface $context): void diff --git a/components/ILIAS/OrgUnit/classes/class.ilOrgUnitExporter.php b/components/ILIAS/OrgUnit/classes/class.ilOrgUnitExporter.php index 4d7e337f7097..8084345377eb 100755 --- a/components/ILIAS/OrgUnit/classes/class.ilOrgUnitExporter.php +++ b/components/ILIAS/OrgUnit/classes/class.ilOrgUnitExporter.php @@ -1,4 +1,5 @@ encodePassword(str_repeat('a', 5000), ''); } - /** - * @depends testInstanceCanBeCreated - */ + #[Depends('testInstanceCanBeCreated')] public function testPasswordVerificationShouldFailIfTheRawPasswordExceedsTheSupportedLength( ilArgon2idPasswordEncoder $encoder ): void { $this->assertFalse($encoder->isPasswordValid('encoded', str_repeat('a', 5000), '')); } - /** - * @depends testInstanceCanBeCreated - */ + #[Depends('testInstanceCanBeCreated')] public function testNameShouldBeArgon2id(ilArgon2idPasswordEncoder $encoder): void { $this->assertSame('argon2id', $encoder->getName()); } - /** - * @depends testInstanceCanBeCreated - */ + #[Depends('testInstanceCanBeCreated')] public function testEncoderDoesNotRelyOnSalts(ilArgon2idPasswordEncoder $encoder): void { $this->assertFalse($encoder->requiresSalt()); } - /** - * @depends testInstanceCanBeCreated - */ + #[Depends('testInstanceCanBeCreated')] public function testReencodingIsDetectedWhenNecessary(ilArgon2idPasswordEncoder $encoder): void { $raw = self::PASSWORD; diff --git a/components/ILIAS/Password/tests/encoders/ilBcryptPasswordEncoderTest.php b/components/ILIAS/Password/tests/encoders/ilBcryptPasswordEncoderTest.php index 8d1924256a11..b23a89f1cc3c 100755 --- a/components/ILIAS/Password/tests/encoders/ilBcryptPasswordEncoderTest.php +++ b/components/ILIAS/Password/tests/encoders/ilBcryptPasswordEncoderTest.php @@ -19,28 +19,17 @@ declare(strict_types=1); use org\bovigo\vfs; +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Depends; -/** - * Class ilBcryptPasswordEncoderTest - * @author Michael Jansen - * @package ServicesPassword - */ final class ilBcryptPasswordEncoderTest extends ilPasswordBaseTestCase { - /** @var string */ - private const VALID_COSTS = '08'; - - /** @var string */ - private const PASSWORD = 'password'; - - /** @var string */ - private const WRONG_PASSWORD = 'wrong_password'; - - /** @var string */ - private const CLIENT_SALT = 'homer!12345_/'; - - /** @var string */ - private const PASSWORD_SALT = 'salt'; + private const string VALID_COSTS = '08'; + private const string PASSWORD = 'password'; + private const string WRONG_PASSWORD = 'wrong_password'; + private const string CLIENT_SALT = 'homer!12345_/'; + private const string PASSWORD_SALT = 'salt'; private vfs\vfsStreamDirectory $testDirectory; private string $testDirectoryUrl; @@ -129,10 +118,7 @@ public function testInstanceCanBeCreated(): ilBcryptPasswordEncoder return $encoder; } - /** - * @depends testInstanceCanBeCreated - * @throws ilPasswordException - */ + #[Depends('testInstanceCanBeCreated')] public function testCostsCanBeRetrievedWhenCostsAreSet(ilBcryptPasswordEncoder $encoder): void { $expected = '04'; @@ -141,41 +127,29 @@ public function testCostsCanBeRetrievedWhenCostsAreSet(ilBcryptPasswordEncoder $ $this->assertSame($expected, $encoder->getCosts()); } - /** - * @depends testInstanceCanBeCreated - * @throws ilPasswordException - */ + #[Depends('testInstanceCanBeCreated')] public function testCostsCannotBeSetAboveRange(ilBcryptPasswordEncoder $encoder): void { $this->expectException(ilPasswordException::class); $encoder->setCosts('32'); } - /** - * @depends testInstanceCanBeCreated - * @throws ilPasswordException - */ + #[Depends('testInstanceCanBeCreated')] public function testCostsCannotBeSetBelowRange(ilBcryptPasswordEncoder $encoder): void { $this->expectException(ilPasswordException::class); $encoder->setCosts('3'); } - /** - * @doesNotPerformAssertions - * @depends testInstanceCanBeCreated - * @dataProvider costsProvider - * @throws ilPasswordException - */ + #[DoesNotPerformAssertions] + #[Depends('testInstanceCanBeCreated')] + #[DataProvider('costsProvider')] public function testCostsCanBeSetInRange(string $costs, ilBcryptPasswordEncoder $encoder): void { $encoder->setCosts($costs); } - /** - * @depends testInstanceCanBeCreated - * @throws ilPasswordException - */ + #[Depends('testInstanceCanBeCreated')] public function testPasswordShouldBeCorrectlyEncodedAndVerified( ilBcryptPasswordEncoder $encoder ): ilBcryptPasswordEncoder { @@ -187,10 +161,7 @@ public function testPasswordShouldBeCorrectlyEncodedAndVerified( return $encoder; } - /** - * @depends testInstanceCanBeCreated - * @throws ilPasswordException - */ + #[Depends('testInstanceCanBeCreated')] public function testExceptionIsRaisedIfThePasswordExceedsTheSupportedLengthOnEncoding( ilBcryptPasswordEncoder $encoder ): void { @@ -199,10 +170,7 @@ public function testExceptionIsRaisedIfThePasswordExceedsTheSupportedLengthOnEnc $encoder->encodePassword(str_repeat('a', 5000), self::PASSWORD_SALT); } - /** - * @depends testInstanceCanBeCreated - * @throws ilPasswordException - */ + #[Depends('testInstanceCanBeCreated')] public function testPasswordVerificationShouldFailIfTheRawPasswordExceedsTheSupportedLength( ilBcryptPasswordEncoder $encoder ): void { @@ -210,25 +178,19 @@ public function testPasswordVerificationShouldFailIfTheRawPasswordExceedsTheSupp $this->assertFalse($encoder->isPasswordValid('encoded', str_repeat('a', 5000), self::PASSWORD_SALT)); } - /** - * @depends testInstanceCanBeCreated - */ + #[Depends('testInstanceCanBeCreated')] public function testEncoderReliesOnSalts(ilBcryptPasswordEncoder $encoder): void { $this->assertTrue($encoder->requiresSalt()); } - /** - * @depends testInstanceCanBeCreated - */ + #[Depends('testInstanceCanBeCreated')] public function testEncoderDoesNotSupportReencoding(ilBcryptPasswordEncoder $encoder): void { $this->assertFalse($encoder->requiresReencoding('hello')); } - /** - * @depends testInstanceCanBeCreated - */ + #[Depends('testInstanceCanBeCreated')] public function testNameShouldBeBcrypt(ilBcryptPasswordEncoder $encoder): void { $this->assertSame('bcrypt', $encoder->getName()); @@ -330,10 +292,7 @@ public function testExceptionIfPasswordsContainA8BitCharacterAndBackwardCompatib $encoder->encodePassword(self::PASSWORD . chr(195), self::PASSWORD_SALT); } - /** - * @doesNotPerformAssertions - * @throws ilPasswordException - */ + #[DoesNotPerformAssertions] public function testNoExceptionIfPasswordsContainA8BitCharacterAndBackwardCompatibilityIsEnabledWithIgnoredSecurityFlaw(): void { $this->skipIfvfsStreamNotSupported(); diff --git a/components/ILIAS/Password/tests/encoders/ilBcryptPhpPasswordEncoderTest.php b/components/ILIAS/Password/tests/encoders/ilBcryptPhpPasswordEncoderTest.php index e971a00c46ab..56c9481db55d 100755 --- a/components/ILIAS/Password/tests/encoders/ilBcryptPhpPasswordEncoderTest.php +++ b/components/ILIAS/Password/tests/encoders/ilBcryptPhpPasswordEncoderTest.php @@ -18,21 +18,15 @@ declare(strict_types=1); -/** - * Class ilBcryptPhpPasswordEncoderTest - * @author Michael Jansen - * @package ServicesPassword - */ +use PHPUnit\Framework\Attributes\Depends; +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; +use PHPUnit\Framework\Attributes\DataProvider; + final class ilBcryptPhpPasswordEncoderTest extends ilPasswordBaseTestCase { - /** @var string */ - private const VALID_COSTS = '08'; - - /** @var string */ - private const PASSWORD = 'password'; - - /** @var string */ - private const WRONG_PASSWORD = 'wrong_password'; + private const string VALID_COSTS = '08'; + private const string PASSWORD = 'password'; + private const string WRONG_PASSWORD = 'wrong_password'; /** * @return array @@ -61,10 +55,7 @@ public function testInstanceCanBeCreated(): ilBcryptPhpPasswordEncoder return $encoder; } - /** - * @depends testInstanceCanBeCreated - * @throws ilPasswordException - */ + #[Depends('testInstanceCanBeCreated')] public function testCostsCanBeRetrievedWhenCostsAreSet(ilBcryptPhpPasswordEncoder $encoder): void { $expected = '04'; @@ -73,41 +64,29 @@ public function testCostsCanBeRetrievedWhenCostsAreSet(ilBcryptPhpPasswordEncode $this->assertSame($expected, $encoder->getCosts()); } - /** - * @depends testInstanceCanBeCreated - * @throws ilPasswordException - */ + #[Depends('testInstanceCanBeCreated')] public function testCostsCannotBeSetAboveRange(ilBcryptPhpPasswordEncoder $encoder): void { $this->expectException(ilPasswordException::class); $encoder->setCosts('32'); } - /** - * @depends testInstanceCanBeCreated - * @throws ilPasswordException - */ + #[Depends('testInstanceCanBeCreated')] public function testCostsCannotBeSetBelowRange(ilBcryptPhpPasswordEncoder $encoder): void { $this->expectException(ilPasswordException::class); $encoder->setCosts('3'); } - /** - * @depends testInstanceCanBeCreated - * @dataProvider costsProvider - * @doesNotPerformAssertions - * @throws ilPasswordException - */ + #[Depends('testInstanceCanBeCreated')] + #[DataProvider('costsProvider')] + #[DoesNotPerformAssertions] public function testCostsCanBeSetInRange(string $costs, ilBcryptPhpPasswordEncoder $encoder): void { $encoder->setCosts($costs); } - /** - * @depends testInstanceCanBeCreated - * @throws ilPasswordException - */ + #[Depends('testInstanceCanBeCreated')] public function testPasswordShouldBeCorrectlyEncodedAndVerified( ilBcryptPhpPasswordEncoder $encoder ): ilBcryptPhpPasswordEncoder { @@ -119,10 +98,7 @@ public function testPasswordShouldBeCorrectlyEncodedAndVerified( return $encoder; } - /** - * @depends testInstanceCanBeCreated - * @throws ilPasswordException - */ + #[Depends('testInstanceCanBeCreated')] public function testExceptionIsRaisedIfThePasswordExceedsTheSupportedLengthOnEncoding( ilBcryptPhpPasswordEncoder $encoder ): void { @@ -131,10 +107,7 @@ public function testExceptionIsRaisedIfThePasswordExceedsTheSupportedLengthOnEnc $encoder->encodePassword(str_repeat('a', 5000), ''); } - /** - * @depends testInstanceCanBeCreated - * @throws ilPasswordException - */ + #[Depends('testInstanceCanBeCreated')] public function testPasswordVerificationShouldFailIfTheRawPasswordExceedsTheSupportedLength( ilBcryptPhpPasswordEncoder $encoder ): void { @@ -142,18 +115,13 @@ public function testPasswordVerificationShouldFailIfTheRawPasswordExceedsTheSupp $this->assertFalse($encoder->isPasswordValid('encoded', str_repeat('a', 5000), '')); } - /** - * @depends testInstanceCanBeCreated - */ + #[Depends('testInstanceCanBeCreated')] public function testNameShouldBeBcryptPhp(ilBcryptPhpPasswordEncoder $encoder): void { $this->assertSame('bcryptphp', $encoder->getName()); } - /** - * @depends testInstanceCanBeCreated - * @throws ilPasswordException - */ + #[Depends('testInstanceCanBeCreated')] public function testCostsCanBeDeterminedDynamically(ilBcryptPhpPasswordEncoder $encoder): void { $costs_default = $encoder->benchmarkCost(); @@ -166,18 +134,13 @@ public function testCostsCanBeDeterminedDynamically(ilBcryptPhpPasswordEncoder $ $this->assertNotEquals($costs_default, $costs_target); } - /** - * @depends testInstanceCanBeCreated - */ + #[Depends('testInstanceCanBeCreated')] public function testEncoderDoesNotRelyOnSalts(ilBcryptPhpPasswordEncoder $encoder): void { $this->assertFalse($encoder->requiresSalt()); } - /** - * @depends testInstanceCanBeCreated - * @throws ilPasswordException - */ + #[Depends('testInstanceCanBeCreated')] public function testReencodingIsDetectedWhenNecessary(ilBcryptPhpPasswordEncoder $encoder): void { $raw = self::PASSWORD; diff --git a/components/ILIAS/Password/tests/encoders/ilMd5PasswordEncoderTest.php b/components/ILIAS/Password/tests/encoders/ilMd5PasswordEncoderTest.php index 1480a0ea244e..1f849d1f6fdc 100755 --- a/components/ILIAS/Password/tests/encoders/ilMd5PasswordEncoderTest.php +++ b/components/ILIAS/Password/tests/encoders/ilMd5PasswordEncoderTest.php @@ -18,11 +18,8 @@ declare(strict_types=1); -/** - * Class ilMd5PasswordEncoderTest - * @author Michael Jansen - * @package ServicesPassword - */ +use PHPUnit\Framework\Attributes\Depends; + final class ilMd5PasswordEncoderTest extends ilPasswordBaseTestCase { public function testInstanceCanBeCreated(): ilMd5PasswordEncoder @@ -32,44 +29,31 @@ public function testInstanceCanBeCreated(): ilMd5PasswordEncoder return $encoder; } - /** - * @depends testInstanceCanBeCreated - * @throws ilPasswordException - */ + #[Depends('testInstanceCanBeCreated')] public function testPasswordShouldBeCorrectlyEncoded(ilMd5PasswordEncoder $encoder): void { $this->assertSame(md5('password'), $encoder->encodePassword('password', '')); } - /** - * @depends testInstanceCanBeCreated - * @throws ilPasswordException - */ + #[Depends('testInstanceCanBeCreated')] public function testPasswordCanBeVerified(ilMd5PasswordEncoder $encoder): void { $this->assertTrue($encoder->isPasswordValid(md5('password'), 'password', '')); } - /** - * @depends testInstanceCanBeCreated - */ + #[Depends('testInstanceCanBeCreated')] public function testEncoderDoesNotRelyOnSalts(ilMd5PasswordEncoder $encoder): void { $this->assertFalse($encoder->requiresSalt()); } - /** - * @depends testInstanceCanBeCreated - */ + #[Depends('testInstanceCanBeCreated')] public function testEncoderDoesNotSupportReencoding(ilMd5PasswordEncoder $encoder): void { $this->assertFalse($encoder->requiresReencoding('hello')); } - /** - * @depends testInstanceCanBeCreated - * @throws ilPasswordException - */ + #[Depends('testInstanceCanBeCreated')] public function testExceptionIsRaisedIfThePasswordExceedsTheSupportedLengthOnEncoding( ilMd5PasswordEncoder $encoder ): void { @@ -77,19 +61,14 @@ public function testExceptionIsRaisedIfThePasswordExceedsTheSupportedLengthOnEnc $encoder->encodePassword(str_repeat('a', 5000), ''); } - /** - * @depends testInstanceCanBeCreated - * @throws ilPasswordException - */ + #[Depends('testInstanceCanBeCreated')] public function testPasswordVerificationShouldFailIfTheRawPasswordExceedsTheSupportedLength( ilMd5PasswordEncoder $encoder ): void { $this->assertFalse($encoder->isPasswordValid('encoded', str_repeat('a', 5000), '')); } - /** - * @depends testInstanceCanBeCreated - */ + #[Depends('testInstanceCanBeCreated')] public function testNameShouldBeMd5(ilMd5PasswordEncoder $encoder): void { $this->assertSame('md5', $encoder->getName()); diff --git a/components/ILIAS/Password/tests/ilPasswordBaseTestCase.php b/components/ILIAS/Password/tests/ilPasswordBaseTestCase.php index 4c4f3401218c..3e6af29d7048 100644 --- a/components/ILIAS/Password/tests/ilPasswordBaseTestCase.php +++ b/components/ILIAS/Password/tests/ilPasswordBaseTestCase.php @@ -20,10 +20,6 @@ use PHPUnit\Framework\TestCase; -/** - * @author Michael Jansen - * @version $Id$ - */ abstract class ilPasswordBaseTestCase extends TestCase { } diff --git a/components/ILIAS/Poll/classes/BlockGUI/Results/class.ilPollResultsHandler.php b/components/ILIAS/Poll/classes/BlockGUI/Results/class.ilPollResultsHandler.php index f1307082f867..dbcc900cee09 100755 --- a/components/ILIAS/Poll/classes/BlockGUI/Results/class.ilPollResultsHandler.php +++ b/components/ILIAS/Poll/classes/BlockGUI/Results/class.ilPollResultsHandler.php @@ -44,11 +44,11 @@ public function __construct( $this->total_votes = (int) ($res['total'] ?? 0); $res = (array) ($res['perc'] ?? []); $this->answer_percentages = array_map( - fn (array $a) => (float) ($a['perc'] ?? 0), + fn(array $a) => (float) ($a['perc'] ?? 0), $res ); $this->answer_totals = array_map( - fn (array $a) => (int) ($a['abs'] ?? 0), + fn(array $a) => (int) ($a['abs'] ?? 0), $res ); } diff --git a/components/ILIAS/Poll/classes/BlockGUI/Results/class.ilPollResultsRenderer.php b/components/ILIAS/Poll/classes/BlockGUI/Results/class.ilPollResultsRenderer.php index d0e1a2260caf..5c556e54463a 100755 --- a/components/ILIAS/Poll/classes/BlockGUI/Results/class.ilPollResultsRenderer.php +++ b/components/ILIAS/Poll/classes/BlockGUI/Results/class.ilPollResultsRenderer.php @@ -27,10 +27,10 @@ class ilPollResultsRenderer { - protected const SINGLE_BAR_WIDTH = 0.65; - protected const SINGLE_BAR_COLOR = '#4C6586'; - protected const STACKED_BAR_WIDTH = 0.95; - protected const STACKED_BAR_COLORS = [ + protected const float SINGLE_BAR_WIDTH = 0.65; + protected const string SINGLE_BAR_COLOR = '#4C6586'; + protected const float STACKED_BAR_WIDTH = 0.95; + protected const array STACKED_BAR_COLORS = [ '#35485F', '#F06B05', '#374E1D', diff --git a/components/ILIAS/Poll/classes/BlockGUI/class.ilPollContentRenderer.php b/components/ILIAS/Poll/classes/BlockGUI/class.ilPollContentRenderer.php index 5406d62e5476..0e7d5e990d35 100755 --- a/components/ILIAS/Poll/classes/BlockGUI/class.ilPollContentRenderer.php +++ b/components/ILIAS/Poll/classes/BlockGUI/class.ilPollContentRenderer.php @@ -73,7 +73,7 @@ public function render( ): void { $this->renderAnchor($tpl, $poll->getId()); $this->renderAvailability($tpl, $poll); - $this->renderDescription($tpl, $poll->getDescription()); + $this->renderDescription($tpl, $poll->getLongDescription()); if (!$this->state->hasQuestion($poll)) { $this->renderNoQuestionMessage($tpl); @@ -313,7 +313,7 @@ protected function renderDescription( if ($description) { $tpl->setVariable( "TXT_DESC", - $this->refinery->encode()->htmlSpecialCharsAsEntities()->transform(nl2br($description)) + nl2br($this->refinery->encode()->htmlSpecialCharsAsEntities()->transform($description)) ); } } diff --git a/components/ILIAS/Poll/classes/Image/I/Repository/Wrapper/DB/HandlerInterface.php b/components/ILIAS/Poll/classes/Image/I/Repository/Wrapper/DB/HandlerInterface.php index f18ea80f22c9..9810a0e773cc 100644 --- a/components/ILIAS/Poll/classes/Image/I/Repository/Wrapper/DB/HandlerInterface.php +++ b/components/ILIAS/Poll/classes/Image/I/Repository/Wrapper/DB/HandlerInterface.php @@ -26,7 +26,7 @@ interface HandlerInterface { - public const TABLE_NAME = "il_poll_image"; + public const string TABLE_NAME = "il_poll_image"; public function insert( ilPollImageRepositoryKeyInterface $key, diff --git a/components/ILIAS/Poll/classes/class.ilObjPoll.php b/components/ILIAS/Poll/classes/class.ilObjPoll.php index 39e1a8fcfcb4..7aaee687031b 100755 --- a/components/ILIAS/Poll/classes/class.ilObjPoll.php +++ b/components/ILIAS/Poll/classes/class.ilObjPoll.php @@ -52,13 +52,13 @@ class ilObjPoll extends ilObject2 protected bool $show_comments = false; protected int $show_results_as = 1; - public const VIEW_RESULTS_ALWAYS = 1; - public const VIEW_RESULTS_NEVER = 2; - public const VIEW_RESULTS_AFTER_VOTE = 3; - public const VIEW_RESULTS_AFTER_PERIOD = 4; + public const int VIEW_RESULTS_ALWAYS = 1; + public const int VIEW_RESULTS_NEVER = 2; + public const int VIEW_RESULTS_AFTER_VOTE = 3; + public const int VIEW_RESULTS_AFTER_PERIOD = 4; - public const SHOW_RESULTS_AS_BARCHART = 1; - public const SHOW_RESULTS_AS_STACKED_CHART = 2; + public const int SHOW_RESULTS_AS_BARCHART = 1; + public const int SHOW_RESULTS_AS_STACKED_CHART = 2; public function __construct(int $a_id = 0, bool $a_reference = true) { diff --git a/components/ILIAS/Poll/classes/class.ilPollDataSet.php b/components/ILIAS/Poll/classes/class.ilPollDataSet.php index 6cd8327492df..3b023d18aeee 100755 --- a/components/ILIAS/Poll/classes/class.ilPollDataSet.php +++ b/components/ILIAS/Poll/classes/class.ilPollDataSet.php @@ -34,7 +34,7 @@ */ class ilPollDataSet extends ilDataSet { - protected const ENTITY = "poll"; + protected const string ENTITY = "poll"; protected NotesService $notes; protected DataFactory $data_factory; protected ilObjuser $user; diff --git a/components/ILIAS/Portfolio/Export/PortfolioHtmlExport.php b/components/ILIAS/Portfolio/Export/PortfolioHtmlExport.php index 5e69097f41c8..d0395b48117a 100755 --- a/components/ILIAS/Portfolio/Export/PortfolioHtmlExport.php +++ b/components/ILIAS/Portfolio/Export/PortfolioHtmlExport.php @@ -100,7 +100,11 @@ public function exportHtml(): void { $this->init(); - $this->export_util->exportSystemStyle(); + $this->export_util->exportSystemStyle( + [ + "icon_prtf.svg" + ] + ); $this->export_util->exportCOPageFiles( $this->content_style_domain->getEffectiveStyleId(), $this->portfolio->getType() diff --git a/components/ILIAS/Portfolio/Export/class.ilPortfolioDataSet.php b/components/ILIAS/Portfolio/Export/class.ilPortfolioDataSet.php index 59524f41e73c..afab0267d577 100755 --- a/components/ILIAS/Portfolio/Export/class.ilPortfolioDataSet.php +++ b/components/ILIAS/Portfolio/Export/class.ilPortfolioDataSet.php @@ -43,7 +43,7 @@ public function __construct() public function getSupportedVersions(): array { - return array("4.4.0", "5.0.0"); + return array("4.4.0", "5.0.0", "10.0"); } protected function getXmlNamespace(string $a_entity, string $a_schema_version): string @@ -68,6 +68,16 @@ protected function getTypes(string $a_entity, string $a_version): array "Ppic" => "integer", "Dir" => "directory" ); + case "10.0": + return array( + "Id" => "integer", + "Title" => "text", + "Description" => "text", + "Comments" => "integer", + "BgColor" => "text", + "FontColor" => "text", + "Ppic" => "integer" + ); } } @@ -75,6 +85,7 @@ protected function getTypes(string $a_entity, string $a_version): array switch ($a_version) { case "4.4.0": case "5.0.0": + case "10.0": return array( "Id" => "integer", "PortfolioId" => "integer", @@ -107,8 +118,9 @@ public function readData(string $a_entity, string $a_version, array $a_ids): voi break; case "5.0.0": + case "10.0": $this->getDirectDataFromQuery("SELECT prtf.id,od.title,od.description," . - "prtf.bg_color,prtf.font_color,prtf.img,prtf.ppic" . + "prtf.bg_color,prtf.font_color,prtf.ppic" . " FROM usr_portfolio prtf" . " JOIN object_data od ON (od.obj_id = prtf.id)" . " WHERE " . $ilDB->in("prtf.id", $a_ids, false, "integer") . @@ -121,6 +133,7 @@ public function readData(string $a_entity, string $a_version, array $a_ids): voi switch ($a_version) { case "4.4.0": case "5.0.0": + case "10.0": $this->getDirectDataFromQuery("SELECT id,portfolio_id,title,order_nr,type" . " FROM usr_portfolio_page" . " WHERE " . $ilDB->in("portfolio_id", $a_ids, false, "integer")); @@ -149,9 +162,6 @@ public function getXmlRecord( array $a_set ): array { if ($a_entity === "prtt") { - $dir = ilObjPortfolioTemplate::initStorage($a_set["Id"]); - $a_set["Dir"] = $dir; - $a_set["Comments"] = $this->notes->domain()->commentsActive((int) $a_set["Id"]); } @@ -186,16 +196,6 @@ public function importRecord( $newObj->setProfilePicture($a_rec["Ppic"]); $newObj->update(); - // handle image(s) - if ($a_rec["Img"]) { - $dir = str_replace("..", "", $a_rec["Dir"]); - if ($dir !== "" && $this->getImportDirectory() !== "") { - $source_dir = $this->getImportDirectory() . "/" . $dir; - $target_dir = ilObjPortfolioTemplate::initStorage($newObj->getId()); - ilFileUtils::rCopy($source_dir, $target_dir); - } - } - $a_mapping->addMapping("components/ILIAS/Portfolio", "prtt", $a_rec["Id"], $newObj->getId()); $a_mapping->addMapping("components/ILIAS/ILIASObject", "obj", $a_rec["Id"], $newObj->getId()); break; @@ -203,6 +203,9 @@ public function importRecord( case "portfolio_page": $prtt_id = (int) $a_mapping->getMapping("components/ILIAS/Portfolio", "prtt", $a_rec["PortfolioId"]); if ($prtt_id) { + if ((int) $a_rec["Type"] != ilPortfolioTemplatePage::TYPE_PAGE) { + return; + } $newObj = new ilPortfolioTemplatePage(); $newObj->setPortfolioId($prtt_id); $newObj->setTitle($a_rec["Title"]); diff --git a/components/ILIAS/Portfolio/Export/class.ilPortfolioExporter.php b/components/ILIAS/Portfolio/Export/class.ilPortfolioExporter.php index b67e1bc9c241..f07c1714b1f5 100755 --- a/components/ILIAS/Portfolio/Export/class.ilPortfolioExporter.php +++ b/components/ILIAS/Portfolio/Export/class.ilPortfolioExporter.php @@ -82,6 +82,12 @@ public function getValidSchemaVersions( string $a_entity ): array { return array( + "10.0" => array( + "namespace" => "https://www.ilias.de/Modules/Portfolio/10", + "xsd_file" => "ilias_portfolio_10.xsd", + "uses_dataset" => true, + "min" => "10.0", + "max" => ""), "4.4.0" => array( "namespace" => "https://www.ilias.de/Modules/Portfolio/4_4", "xsd_file" => "ilias_portfolio_4_4.xsd", @@ -93,7 +99,7 @@ public function getValidSchemaVersions( "xsd_file" => "ilias_portfolio_5_0.xsd", "uses_dataset" => true, "min" => "5.0.0", - "max" => "") + "max" => "9.99") ); } } diff --git a/components/ILIAS/Portfolio/Page/class.ilPCAMDFormGUI.php b/components/ILIAS/Portfolio/Page/class.ilPCAMDFormGUI.php index 23344ff384b1..d1cd2275e56c 100755 --- a/components/ILIAS/Portfolio/Page/class.ilPCAMDFormGUI.php +++ b/components/ILIAS/Portfolio/Page/class.ilPCAMDFormGUI.php @@ -141,6 +141,11 @@ public function getTemplateForm(bool $edit = false): Form\Standard return $f->input()->container()->form()->standard($form_action, ["sec" => $section1]); } + public function create_amdfrm(): void + { + $this->create(); + } + public function create(): void { $request = $this->http_request; diff --git a/components/ILIAS/Portfolio/Page/class.ilPCConsultationHoursGUI.php b/components/ILIAS/Portfolio/Page/class.ilPCConsultationHoursGUI.php index a15f125dd6a5..6cb0eaff4890 100755 --- a/components/ILIAS/Portfolio/Page/class.ilPCConsultationHoursGUI.php +++ b/components/ILIAS/Portfolio/Page/class.ilPCConsultationHoursGUI.php @@ -164,6 +164,11 @@ protected function initForm(bool $a_insert = false): ilPropertyFormGUI return $form; } + public function create_consultation_hours(): void + { + $this->create(); + } + public function create(): void { $form = $this->initForm(true); diff --git a/components/ILIAS/Portfolio/Page/class.ilPCMyCoursesGUI.php b/components/ILIAS/Portfolio/Page/class.ilPCMyCoursesGUI.php index f36b1581aec7..ffb5795c390d 100755 --- a/components/ILIAS/Portfolio/Page/class.ilPCMyCoursesGUI.php +++ b/components/ILIAS/Portfolio/Page/class.ilPCMyCoursesGUI.php @@ -113,6 +113,11 @@ protected function initForm($a_insert = false): ilPropertyFormGUI return $form; } + public function create_my_courses(): void + { + $this->create(); + } + public function create(): void { $form = $this->initForm(true); diff --git a/components/ILIAS/Portfolio/Page/class.ilPortfolioPageTableGUI.php b/components/ILIAS/Portfolio/Page/class.ilPortfolioPageTableGUI.php index 77d201e0eb4b..cf8ec7ddca50 100755 --- a/components/ILIAS/Portfolio/Page/class.ilPortfolioPageTableGUI.php +++ b/components/ILIAS/Portfolio/Page/class.ilPortfolioPageTableGUI.php @@ -119,6 +119,7 @@ protected function fillRow(array $a_set): void // copy //$action_item = ilLinkButton::getInstance(); + $txt = ""; if ((int) $a_set["type"] === ilPortfolioPage::TYPE_PAGE) { $txt = $lng->txt('prtf_copy_pg'); } diff --git a/components/ILIAS/Portfolio/PrintView/class.PortfolioPrintViewProviderGUI.php b/components/ILIAS/Portfolio/PrintView/class.PortfolioPrintViewProviderGUI.php index 4591b3b93162..61a249b80f44 100755 --- a/components/ILIAS/Portfolio/PrintView/class.PortfolioPrintViewProviderGUI.php +++ b/components/ILIAS/Portfolio/PrintView/class.PortfolioPrintViewProviderGUI.php @@ -197,6 +197,7 @@ public function getPages(): array $cover_tpl->setVariable("TXT_AUTHOR", $lng->txt("prtf_author")); $cover_tpl->setVariable("TXT_LINK", $lng->txt("prtf_link")); $cover_tpl->setVariable("TXT_DATE", $lng->txt("prtf_date_of_print")); + $cover_tpl->setVariable("TXT_TABLE_OF_CONTENTS", $lng->txt("prtf_table_of_contents")); $author = \ilObjUser::_lookupName($this->portfolio->getOwner()); $author_str = $author["firstname"] . " " . $author["lastname"]; diff --git a/components/ILIAS/Portfolio/classes/class.ilObjPortfolio.php b/components/ILIAS/Portfolio/classes/class.ilObjPortfolio.php index aab74ba1c31c..e448738e023e 100755 --- a/components/ILIAS/Portfolio/classes/class.ilObjPortfolio.php +++ b/components/ILIAS/Portfolio/classes/class.ilObjPortfolio.php @@ -33,9 +33,12 @@ protected function deleteAllPages(): void // delete pages $pages = ilPortfolioPage::getAllPortfolioPages($this->id); foreach ($pages as $page) { - $page_obj = new ilPortfolioPage($page["id"]); - $page_obj->setPortfolioId($this->id); - $page_obj->delete(); + try { + $page_obj = new ilPortfolioPage($page["id"]); + $page_obj->setPortfolioId($this->id); + $page_obj->delete(); + } catch (Exception $e) { + } } } @@ -95,27 +98,6 @@ public static function deleteUserPortfolios(int $a_user_id): void } } - public function deleteImage(): void - { - if ($this->id) { - parent::deleteImage(); - $this->handleQuotaUpdate(); - } - } - - public function uploadImage(array $a_upload): bool - { - if (parent::uploadImage($a_upload)) { - $this->handleQuotaUpdate(); - return true; - } - return false; - } - - protected function handleQuotaUpdate(): void - { - } - public static function getAvailablePortfolioLinksForUserIds( array $a_owner_ids, ?string $a_back_url = null @@ -126,8 +108,9 @@ public static function getAvailablePortfolioLinksForUserIds( $params = null; if ($a_back_url) { - $params = array("back_url" => rawurlencode($a_back_url)); + //$params = array("back_url" => rawurlencode($a_back_url)); } + $params = []; foreach ($access_handler->getShardObjectsDataForUserIds($a_owner_ids) as $owner_id => $items) { foreach ($items as $id => $title) { diff --git a/components/ILIAS/Portfolio/classes/class.ilObjPortfolioBase.php b/components/ILIAS/Portfolio/classes/class.ilObjPortfolioBase.php index 71b32d6eb70c..515f77479166 100755 --- a/components/ILIAS/Portfolio/classes/class.ilObjPortfolioBase.php +++ b/components/ILIAS/Portfolio/classes/class.ilObjPortfolioBase.php @@ -198,7 +198,6 @@ protected function doDelete(): void $ilDB = $this->db; $this->deleteAllPages(); - $this->deleteImage(); $ilDB->manipulate("DELETE FROM usr_portfolio" . " WHERE id = " . $ilDB->quote($this->id, "integer")); diff --git a/components/ILIAS/Portfolio/classes/class.ilObjPortfolioBaseGUI.php b/components/ILIAS/Portfolio/classes/class.ilObjPortfolioBaseGUI.php index 79dc12d293ee..ddd2ef9bdd3b 100755 --- a/components/ILIAS/Portfolio/classes/class.ilObjPortfolioBaseGUI.php +++ b/components/ILIAS/Portfolio/classes/class.ilObjPortfolioBaseGUI.php @@ -39,7 +39,6 @@ abstract class ilObjPortfolioBaseGUI extends ilObject2GUI protected int $requested_user_page; protected string $requested_back_url = ""; protected \ILIAS\DI\UIServices $ui; - protected \ILIAS\HTTP\Services $http; protected \ILIAS\Style\Content\GUIService $content_style_gui; protected \ILIAS\Style\Content\Object\ObjectFacade $content_style_domain; @@ -65,8 +64,6 @@ public function __construct( ->gui() ->standardRequest(); - $this->http = $DIC->http(); - parent::__construct($a_id, $a_id_type, $a_parent_node_id); $this->user_id = $ilUser->getId(); diff --git a/components/ILIAS/Portfolio/classes/class.ilObjPortfolioGUI.php b/components/ILIAS/Portfolio/classes/class.ilObjPortfolioGUI.php index 1b0ddeb6785e..da55cd9d71e8 100755 --- a/components/ILIAS/Portfolio/classes/class.ilObjPortfolioGUI.php +++ b/components/ILIAS/Portfolio/classes/class.ilObjPortfolioGUI.php @@ -143,6 +143,7 @@ public function executeCommand(): void $this->addLocator(); $this->setTabs(); $this->tabs_gui->activateTab("settings"); + $this->setSettingsSubTabs("properties"); $gui = $this->gui->settings()->settingsGUI( $this->object->getId(), false diff --git a/components/ILIAS/Portfolio/templates/default/tpl.prtf_cover.html b/components/ILIAS/Portfolio/templates/default/tpl.prtf_cover.html index 229726ac8253..d469dd73816b 100755 --- a/components/ILIAS/Portfolio/templates/default/tpl.prtf_cover.html +++ b/components/ILIAS/Portfolio/templates/default/tpl.prtf_cover.html @@ -28,7 +28,7 @@

{PORTFOLIO_TITLE}

-

Table of Contents

+

{TXT_TABLE_OF_CONTENTS}

{ITEM_TITLE}

diff --git a/components/ILIAS/PrivacySecurity/classes/class.ilObjPrivacySecurityGUI.php b/components/ILIAS/PrivacySecurity/classes/class.ilObjPrivacySecurityGUI.php index d2ccded64847..15deda655616 100755 --- a/components/ILIAS/PrivacySecurity/classes/class.ilObjPrivacySecurityGUI.php +++ b/components/ILIAS/PrivacySecurity/classes/class.ilObjPrivacySecurityGUI.php @@ -178,10 +178,12 @@ protected function initPrivacyForm(): ilPropertyFormGUI $group->addOption($check); $check = new ilCheckboxOption(); $check->setTitle($this->lng->txt('ps_export_confirm')); + $check->setInfo($this->lng->txt('ps_export_confirm_info')); $check->setValue('export_confirm_course'); $group->addOption($check); $check = new ilCheckboxOption(); $check->setTitle($this->lng->txt('ps_export_confirm_group')); + $check->setInfo($this->lng->txt('ps_export_confirm_group_info')); $check->setValue('export_confirm_group'); $group->addOption($check); $check = new ilCheckboxOption(); diff --git a/components/ILIAS/PrivacySecurity/classes/class.ilSecuritySettingsChecker.php b/components/ILIAS/PrivacySecurity/classes/class.ilSecuritySettingsChecker.php index 8562cfb1b8b0..f4f740bd08f9 100755 --- a/components/ILIAS/PrivacySecurity/classes/class.ilSecuritySettingsChecker.php +++ b/components/ILIAS/PrivacySecurity/classes/class.ilSecuritySettingsChecker.php @@ -252,11 +252,9 @@ public static function generatePasswords(int $a_number): array ? $security->getPasswordMinLength() : 6; $max = ($security->getPasswordMaxLength() > 0) - ? $security->getPasswordMaxLength() - : 10; - if ($min > $max) { - $max = $max + 1; - } + ? max($security->getPasswordMaxLength(), $min) + : max($min, 10); + $random = new ilRandom(); $length = $random->int($min, $max); $next = $random->int(1, 2); @@ -272,12 +270,12 @@ public static function generatePasswords(int $a_number): array for ($j = 0; $j < $security->getPasswordNumberOfUppercaseChars(); $j++) { switch ($next) { case 1: - $pw .= $consonants_uc[$random->int(0, strlen($consonants_uc) - 1)]; + $pw .= $consonants_uc[$random->getInt(0, strlen($consonants_uc) - 1)]; $next = 2; break; case 2: - $pw .= $vowels_uc[$random->int(0, strlen($vowels_uc) - 1)]; + $pw .= $vowels_uc[$random->getInt(0, strlen($vowels_uc) - 1)]; $next = 1; break; } @@ -285,23 +283,23 @@ public static function generatePasswords(int $a_number): array } if ($security->isPasswordCharsAndNumbersEnabled()) { - $pw .= $numbers[$random->int(0, strlen($numbers) - 1)]; + $pw .= $numbers[$random->getInt(0, strlen($numbers) - 1)]; } if ($security->isPasswordSpecialCharsEnabled()) { - $pw .= $special[$random->int(0, strlen($special) - 1)]; + $pw .= $special[$random->getInt(0, strlen($special) - 1)]; } $num_lcase_chars = max($security->getPasswordNumberOfLowercaseChars(), $length - strlen($pw)); for ($j = 0; $j < $num_lcase_chars; $j++) { switch ($next) { case 1: - $pw .= $consonants[$random->int(0, strlen($consonants) - 1)]; + $pw .= $consonants[$random->getInt(0, strlen($consonants) - 1)]; $next = 2; break; case 2: - $pw .= $vowels[$random->int(0, strlen($vowels) - 1)]; + $pw .= $vowels[$random->getInt(0, strlen($vowels) - 1)]; $next = 1; break; } diff --git a/components/ILIAS/QTI/classes/class.ilQTIParser.php b/components/ILIAS/QTI/classes/class.ilQTIParser.php index 3a6ca7c970e5..ffd86b20cb6e 100755 --- a/components/ILIAS/QTI/classes/class.ilQTIParser.php +++ b/components/ILIAS/QTI/classes/class.ilQTIParser.php @@ -196,7 +196,8 @@ public function __construct( ?string $a_xml_file, int $a_mode = self::IL_MO_PARSE_QTI, int $a_qpl_id = 0, - array $import_idents = [] + array $import_idents = [], + private array $mappings = [] ) { /** @var ILIAS\DI\Container $DIC */ global $DIC; @@ -255,9 +256,8 @@ public function getTestObject(): ilObjTest */ public function setHandlers($a_xml_parser): void { - xml_set_object($a_xml_parser, $this); - xml_set_element_handler($a_xml_parser, 'handlerBeginTag', 'handlerEndTag'); - xml_set_character_data_handler($a_xml_parser, 'handlerCharacterData'); + xml_set_element_handler($a_xml_parser, $this->handlerBeginTag(...), $this->handlerEndTag(...)); + xml_set_character_data_handler($a_xml_parser, $this->handlerCharacterData(...)); } public function startParsing(): void @@ -578,7 +578,7 @@ public function handlerParseEndTag($a_xml_parser, string $a_name): void switch (strtolower($a_name)) { case "assessment": if (is_object($this->tst_object)) { - $this->tst_object->fromXML($this->assessment); + $this->tst_object->fromXML($this->assessment, $this->mappings); } $this->in_assessment = false; break; diff --git a/components/ILIAS/QTI/tests/ilQTIAssessmentcontrolTest.php b/components/ILIAS/QTI/tests/ilQTIAssessmentcontrolTest.php index 8b329de2f793..ff849f23b234 100755 --- a/components/ILIAS/QTI/tests/ilQTIAssessmentcontrolTest.php +++ b/components/ILIAS/QTI/tests/ilQTIAssessmentcontrolTest.php @@ -31,18 +31,14 @@ public function testConstruct(): ilQTIAssessmentcontrol return $instance; } - /** - * @depends testConstruct - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstruct')] public function testGetView(ilQTIAssessmentcontrol $instance): void { $this->assertEquals('All', $instance->getView()); } - /** - * @dataProvider validViews - * @depends testGetView - */ + #[\PHPUnit\Framework\Attributes\Depends('testGetView')] + #[\PHPUnit\Framework\Attributes\DataProvider('validViews')] public function testSetViewValid(string $view): void { $instance = new ilQTIAssessmentcontrol(); @@ -50,9 +46,7 @@ public function testSetViewValid(string $view): void $this->assertEquals($view, $instance->getView()); } - /** - * @depends testSetViewValid - */ + #[\PHPUnit\Framework\Attributes\Depends('testSetViewValid')] public function testSetViewInvalid(): void { $instance = new ilQTIAssessmentcontrol(); @@ -60,10 +54,8 @@ public function testSetViewInvalid(): void $this->assertEquals('All', $instance->getView()); } - /** - * @dataProvider switches - * @depends testConstruct - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstruct')] + #[\PHPUnit\Framework\Attributes\DataProvider('switches')] public function testSwitchInitializeValue(string $suffix): void { $instance = new ilQTIAssessmentcontrol(); @@ -72,10 +64,8 @@ public function testSwitchInitializeValue(string $suffix): void $this->assertEquals('', $instance->$get()); } - /** - * @dataProvider switches - * @depends testConstruct - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstruct')] + #[\PHPUnit\Framework\Attributes\DataProvider('switches')] public function testSwitchValuesConsideredAsYes(string $suffix): void { $instance = new ilQTIAssessmentcontrol(); @@ -90,10 +80,8 @@ public function testSwitchValuesConsideredAsYes(string $suffix): void } - /** - * @dataProvider switches - * @depends testConstruct - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstruct')] + #[\PHPUnit\Framework\Attributes\DataProvider('switches')] public function testSwitchValuesConsideredAsNo(string $suffix): void { $instance = new ilQTIAssessmentcontrol(); diff --git a/components/ILIAS/QTI/tests/ilQTIDecvarTest.php b/components/ILIAS/QTI/tests/ilQTIDecvarTest.php index 910aa6bca7bc..db049a580473 100755 --- a/components/ILIAS/QTI/tests/ilQTIDecvarTest.php +++ b/components/ILIAS/QTI/tests/ilQTIDecvarTest.php @@ -34,9 +34,7 @@ public function testSetGetVarname(): void $this->assertEquals('Some input.', $instance->getVarname()); } - /** - * @dataProvider vartypes - */ + #[\PHPUnit\Framework\Attributes\DataProvider('vartypes')] public function testSetGetVartype(string $input, ?string $expected): void { $instance = new ilQTIDecvar(); diff --git a/components/ILIAS/QTI/tests/ilQTIItemfeedbackTest.php b/components/ILIAS/QTI/tests/ilQTIItemfeedbackTest.php index b9cc5ac8297d..95a002aa72e3 100755 --- a/components/ILIAS/QTI/tests/ilQTIItemfeedbackTest.php +++ b/components/ILIAS/QTI/tests/ilQTIItemfeedbackTest.php @@ -27,10 +27,8 @@ public function testConstruct(): void $this->assertInstanceOf(ilQTIItemfeedback::class, new ilQTIItemfeedback()); } - /** - * @depends testConstruct - * @dataProvider views - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstruct')] + #[\PHPUnit\Framework\Attributes\DataProvider('views')] public function testSetGetView(string $input, ?string $expected): void { $instance = new ilQTIItemfeedback(); diff --git a/components/ILIAS/QTI/tests/ilQTIMattextTest.php b/components/ILIAS/QTI/tests/ilQTIMattextTest.php index e5ca86e82528..732290876e0c 100755 --- a/components/ILIAS/QTI/tests/ilQTIMattextTest.php +++ b/components/ILIAS/QTI/tests/ilQTIMattextTest.php @@ -55,9 +55,7 @@ public function testSetGetUri(): void $this->assertEquals('Some input.', $instance->getUri()); } - /** - * @dataProvider xmlSpaces - */ + #[\PHPUnit\Framework\Attributes\DataProvider('xmlSpaces')] public function testSetGetXmlspace(string $input, ?string $expected): void { $instance = new ilQTIMattext(); diff --git a/components/ILIAS/QTI/tests/ilQTIRenderFibTest.php b/components/ILIAS/QTI/tests/ilQTIRenderFibTest.php index 35de04eab72f..bf9b6d5a0f34 100755 --- a/components/ILIAS/QTI/tests/ilQTIRenderFibTest.php +++ b/components/ILIAS/QTI/tests/ilQTIRenderFibTest.php @@ -41,9 +41,7 @@ public function testSetGetMaxnumber(): void $this->assertEquals('Some input.', $instance->getMaxnumber()); } - /** - * @dataProvider prompts - */ + #[\PHPUnit\Framework\Attributes\DataProvider('prompts')] public function testSetGetPrompt(string $input, ?string $expected): void { $instance = new ilQTIRenderFib(); @@ -51,9 +49,7 @@ public function testSetGetPrompt(string $input, ?string $expected): void $this->assertEquals($expected, $instance->getPrompt()); } - /** - * @dataProvider fibtypes - */ + #[\PHPUnit\Framework\Attributes\DataProvider('fibtypes')] public function testSetGetFibtype(string $input, ?string $expected): void { $instance = new ilQTIRenderFib(); diff --git a/components/ILIAS/QTI/tests/ilQTIRespconditionTest.php b/components/ILIAS/QTI/tests/ilQTIRespconditionTest.php index 80986a01cafa..5830350e323c 100755 --- a/components/ILIAS/QTI/tests/ilQTIRespconditionTest.php +++ b/components/ILIAS/QTI/tests/ilQTIRespconditionTest.php @@ -27,9 +27,7 @@ public function testConstruct(): void $this->assertInstanceOf(ilQTIRespcondition::class, new ilQTIRespcondition()); } - /** - * @dataProvider continues - */ + #[\PHPUnit\Framework\Attributes\DataProvider('continues')] public function testSetGetContinue(string $input, ?string $expected): void { $instance = new ilQTIRespcondition(); diff --git a/components/ILIAS/QTI/tests/ilQTIResponseLabelTest.php b/components/ILIAS/QTI/tests/ilQTIResponseLabelTest.php index 784fcd7325cf..4105f08d63be 100755 --- a/components/ILIAS/QTI/tests/ilQTIResponseLabelTest.php +++ b/components/ILIAS/QTI/tests/ilQTIResponseLabelTest.php @@ -27,9 +27,7 @@ public function testConstruct(): void $this->assertInstanceOf(ilQTIResponseLabel::class, new ilQTIResponseLabel()); } - /** - * @dataProvider rshuffles - */ + #[\PHPUnit\Framework\Attributes\DataProvider('rshuffles')] public function testSetGetRshuffle(string $input, ?string $expected): void { $instance = new ilQTIResponseLabel(); @@ -37,9 +35,7 @@ public function testSetGetRshuffle(string $input, ?string $expected): void $this->assertEquals($expected, $instance->getRshuffle()); } - /** - * @dataProvider areas - */ + #[\PHPUnit\Framework\Attributes\DataProvider('areas')] public function testSetGetRarea(string $input, ?string $expected): void { $instance = new ilQTIResponseLabel(); @@ -47,9 +43,7 @@ public function testSetGetRarea(string $input, ?string $expected): void $this->assertEquals($expected, $instance->getRarea()); } - /** - * @dataProvider rranges - */ + #[\PHPUnit\Framework\Attributes\DataProvider('rranges')] public function testSetGetRrange(string $input, ?string $expected): void { $instance = new ilQTIResponseLabel(); diff --git a/components/ILIAS/QTI/tests/ilQTIResponseTest.php b/components/ILIAS/QTI/tests/ilQTIResponseTest.php index bcc3f5f2ecaf..047e10b3a271 100755 --- a/components/ILIAS/QTI/tests/ilQTIResponseTest.php +++ b/components/ILIAS/QTI/tests/ilQTIResponseTest.php @@ -34,9 +34,7 @@ public function testSetGetIdent(): void $this->assertEquals('Some input.', $instance->getIdent()); } - /** - * @dataProvider rtimings - */ + #[\PHPUnit\Framework\Attributes\DataProvider('rtimings')] public function testSetGetRtiming(string $input, ?string $expected): void { $instance = new ilQTIResponse(); @@ -44,9 +42,7 @@ public function testSetGetRtiming(string $input, ?string $expected): void $this->assertEquals($expected, $instance->getRtiming()); } - /** - * @dataProvider numtypes - */ + #[\PHPUnit\Framework\Attributes\DataProvider('numtypes')] public function testSetGetNumtype(string $input, ?string $expected): void { $instance = new ilQTIResponse(); diff --git a/components/ILIAS/QTI/tests/ilQTIResponseVarTest.php b/components/ILIAS/QTI/tests/ilQTIResponseVarTest.php index f93afd06cf6d..8d1b784dc0ce 100755 --- a/components/ILIAS/QTI/tests/ilQTIResponseVarTest.php +++ b/components/ILIAS/QTI/tests/ilQTIResponseVarTest.php @@ -34,9 +34,7 @@ public function testSetGetVartype(): void $this->assertEquals('Some input.', $instance->getVartype()); } - /** - * @dataProvider cases - */ + #[\PHPUnit\Framework\Attributes\DataProvider('cases')] public function testSetGetCase(string $input, ?string $expected): void { $instance = new ilQTIResponseVar('a'); @@ -58,9 +56,7 @@ public function testSetGetIndex(): void $this->assertEquals('Some input.', $instance->getIndex()); } - /** - * @dataProvider setMatches - */ + #[\PHPUnit\Framework\Attributes\DataProvider('setMatches')] public function testSetGetSetmatch(string $input, ?string $expected): void { $instance = new ilQTIResponseVar('a'); @@ -68,9 +64,7 @@ public function testSetGetSetmatch(string $input, ?string $expected): void $this->assertEquals($expected, $instance->getSetmatch()); } - /** - * @dataProvider areaTypes - */ + #[\PHPUnit\Framework\Attributes\DataProvider('areaTypes')] public function testSetGetAreatype(string $input, ?string $expected): void { $instance = new ilQTIResponseVar('a'); diff --git a/components/ILIAS/QTI/tests/ilQTISetvarTest.php b/components/ILIAS/QTI/tests/ilQTISetvarTest.php index 3977c7310eff..7d2ea03aa1c9 100755 --- a/components/ILIAS/QTI/tests/ilQTISetvarTest.php +++ b/components/ILIAS/QTI/tests/ilQTISetvarTest.php @@ -27,9 +27,7 @@ public function testConstruct(): void $this->assertInstanceOf(ilQTISetvar::class, new ilQTISetvar()); } - /** - * @dataProvider actions - */ + #[\PHPUnit\Framework\Attributes\DataProvider('actions')] public function testSetGetAction(string $input, ?string $expected): void { $instance = new ilQTISetvar(); diff --git a/components/ILIAS/RTE/classes/class.ilRTEGlobalTemplate.php b/components/ILIAS/RTE/classes/class.ilRTEGlobalTemplate.php index 1eae55d82ef6..2ddab893bcf7 100755 --- a/components/ILIAS/RTE/classes/class.ilRTEGlobalTemplate.php +++ b/components/ILIAS/RTE/classes/class.ilRTEGlobalTemplate.php @@ -191,8 +191,6 @@ public function loadStandardTemplate(): void iljQueryUtil::initjQuery(); iljQueryUtil::initjQueryUI(); - ilUIFramework::init(); - $this->addBlockFile('CONTENT', 'content', 'tpl.adm_content.html'); $this->addBlockFile('STATUSLINE', 'statusline', 'tpl.statusline.html'); diff --git a/components/ILIAS/Randomization/classes/class.ilRandom.php b/components/ILIAS/Randomization/classes/class.ilRandom.php deleted file mode 100755 index 4f0db6885f2a..000000000000 --- a/components/ILIAS/Randomization/classes/class.ilRandom.php +++ /dev/null @@ -1,47 +0,0 @@ - - * @author Michael Jansen - */ -class ilRandom -{ - private function logIfPossible(callable $c): void - { - global $DIC; - - if (isset($DIC['ilLoggerFactory'])) { - $c($DIC->logger()->rnd()); - } - } - - public function int(int $min = 0, int $max = PHP_INT_MAX): int - { - try { - return random_int($min, $max); - } catch (Throwable $e) { - $this->logIfPossible(static function (ilLogger $logger): void { - $logger->logStack(ilLogLevel::ERROR); - $logger->error('No suitable random number generator found.'); - }); - throw $e; - } - } -} diff --git a/components/ILIAS/Randomization/maintenance.json b/components/ILIAS/Randomization/maintenance.json deleted file mode 100755 index a89c22091fd1..000000000000 --- a/components/ILIAS/Randomization/maintenance.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "maintenance_model": "Classic", - "first_maintainer": "", - "second_maintainer": "", - "implicit_maintainers": [], - "coordinator": [ - "" - ], - "tester": "", - "testcase_writer": "", - "path": "Services/QTI", - "belong_to_component": "None", - "used_in_components": [ - "AssessmentQuestion" - ] -} \ No newline at end of file diff --git a/components/ILIAS/Randomization/phpunit.xml b/components/ILIAS/Randomization/phpunit.xml deleted file mode 100755 index c9f7fbebd406..000000000000 --- a/components/ILIAS/Randomization/phpunit.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - classes - - - - - - - ./test/ilServicesRandomizationSuite.php - - - - - diff --git a/components/ILIAS/Randomization/service.xml b/components/ILIAS/Randomization/service.xml deleted file mode 100755 index 793dd7ff5554..000000000000 --- a/components/ILIAS/Randomization/service.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/components/ILIAS/Randomization/tests/ilRandomTest.php b/components/ILIAS/Randomization/tests/ilRandomTest.php deleted file mode 100755 index 8396ea91eba2..000000000000 --- a/components/ILIAS/Randomization/tests/ilRandomTest.php +++ /dev/null @@ -1,85 +0,0 @@ -assertInstanceOf(\ilRandom::class, new ilRandom()); - } - - /** - * @dataProvider intArguments - */ - public function testIntSuccessfully(int ...$arguments): void - { - $this->expectNotToPerformAssertions(); - - $random = new \ilRandom(); - try { - $random->int(...$arguments); - } catch (Error $e) { - $this->fail('Expected no exception.'); - } - } - - public function testIntWithInvalidArguments(): void - { - $this->expectException(Error::class); - $random = new \ilRandom(); - - $random->int(10, 9); - } - - public function testLogIfPossible(): void - { - $this->expectException(Error::class); - - $logger = $this->getMockBuilder(\ilLogger::class)->disableOriginalConstructor()->getMock(); - $logger->expects(self::once())->method('logStack')->with(\ilLogLevel::ERROR); - $logger->expects(self::once())->method('error'); - - $factory = $this->getMockBuilder(ilLoggerFactory::class)->disableOriginalConstructor()->getMock(); - $factory->expects(self::once())->method('getComponentLogger')->with('rnd')->willReturn($logger); - - $GLOBALS['DIC'] = new Container(); - $GLOBALS['DIC']['ilLoggerFactory'] = static function () use ($factory): ilLoggerFactory { - return $factory; - }; - $random = new \ilRandom(); - $random->int(10, 9); - - unset($GLOBALS['DIC']); - } - - public static function intArguments(): array - { - return [ - 'No arguments can be provided' => [], - 'One argument can be provided' => [34], - '2 arguments can be provided' => [-20, 30], - 'The limit is inclusive' => [8, 8] - ]; - } -} diff --git a/components/ILIAS/Rating/README-technical.md b/components/ILIAS/Rating/README-technical.md new file mode 100755 index 000000000000..3a4543ffc878 --- /dev/null +++ b/components/ILIAS/Rating/README-technical.md @@ -0,0 +1,11 @@ +# Rating Service + +## Auto Activate Rating + +This feature is currently not implemented in a coherent way. + +- ilObjectServiceSettingsGUI embeds the settings +- Setting is saved in ilContainer::_lookupContainerSetting with key ilObjectServiceSettingsGUI::AUTO_RATING_NEW_OBJECTS +- ilObject holds a function selfOrParentWithRatingEnabled() which checks the setting +- This method is called in ilObject->handleAutoRating -> ilObject->hasAutoRating +- Finally this method is called in ilObject->putInTree. If the consuming components do not call this method, auto-rating activation will not work. \ No newline at end of file diff --git a/components/ILIAS/Rating/classes/class.ilRatingGUI.php b/components/ILIAS/Rating/classes/class.ilRatingGUI.php index bf60d871f3e0..e50a5169bb2b 100755 --- a/components/ILIAS/Rating/classes/class.ilRatingGUI.php +++ b/components/ILIAS/Rating/classes/class.ilRatingGUI.php @@ -585,7 +585,7 @@ public function getHTML( ); $popover = $f->popover()->standard( - $f->legacy($this->renderDetails("rtov_", $may_rate, $categories, $a_onclick)) + $f->legacy()->content($this->renderDetails("rtov_", $may_rate, $categories, $a_onclick)) ); $button = $button->withOnClick($popover->getShowSignal()); $button = $button->withHelpTopics( @@ -593,9 +593,9 @@ public function getHTML( ); $elements = [$popover, $button]; } else { - $button = $button->withOnLoadCode(function ($id) { - return "return false;"; - }); + /*$button = $button->withOnLoadCode(function ($id) { + return ""; + });*/ $button = $button->withHelpTopics( ...$f->helpTopics(...$tt_topics) ); diff --git a/components/ILIAS/Refinery/docs/CONTRIBUTING.md b/components/ILIAS/Refinery/docs/CONTRIBUTING.md index 6fcc68e8ce87..e26e3916a36b 100755 --- a/components/ILIAS/Refinery/docs/CONTRIBUTING.md +++ b/components/ILIAS/Refinery/docs/CONTRIBUTING.md @@ -11,7 +11,7 @@ please follow it in all your interactions with the project. ## Roles The ILIAS project has the role of the coordinator. -[Check the documentation](/docs/documentation/maintenance-coordinator.md) +[Check the documentation](../../UI/docs/COMMUNITY.md) about the function of this role. ## Reporting Bugs diff --git a/components/ILIAS/Refinery/tests/ByTrying/ByTryingTransformTest.php b/components/ILIAS/Refinery/tests/ByTrying/ByTryingTransformTest.php index 917cb3ad5789..c99f966b704c 100755 --- a/components/ILIAS/Refinery/tests/ByTrying/ByTryingTransformTest.php +++ b/components/ILIAS/Refinery/tests/ByTrying/ByTryingTransformTest.php @@ -22,6 +22,7 @@ use ILIAS\Refinery\ConstraintViolationException; use ILIAS\Tests\Refinery\TestCase; use ILIAS\Data\Factory as DataFactory; +use PHPUnit\Framework\Attributes\DataProvider; require_once("./components/ILIAS/Refinery/tests/TestCase.php"); @@ -55,12 +56,8 @@ public static function NullOrNumericDataProvider(): array ]; } - /** - * @dataProvider NullOrNumericDataProvider - * @param mixed $value - * @param mixed $expected - */ - public function testNullOrNumeric($value, $expected): void + #[DataProvider('NullOrNumericDataProvider')] + public function testNullOrNumeric(mixed $value, mixed $expected): void { $transformation = $this->refinery->byTrying([ $this->refinery->numeric()->isNumeric(), @@ -87,12 +84,8 @@ public static function NullOrNumericOrStringDataProvider(): array ]; } - /** - * @dataProvider NullOrNumericOrStringDataProvider - * @param mixed $value - * @param mixed $expected - */ - public function testNullOrNumericOrString($value, $expected): void + #[DataProvider('NullOrNumericOrStringDataProvider')] + public function testNullOrNumericOrString(mixed $value, mixed $expected): void { $transformation = $this->refinery->byTrying([ $this->refinery->kindlyTo()->null(), @@ -118,12 +111,8 @@ public static function StringOrNullDataProvider(): array ]; } - /** - * @dataProvider StringOrNullDataProvider - * @param mixed $value - * @param mixed $expected - */ - public function testStringOrNull($value, $expected): void + #[DataProvider('StringOrNullDataProvider')] + public function testStringOrNull(mixed $value, mixed $expected): void { $transformation = $this->refinery->byTrying([ $this->refinery->to()->string(), diff --git a/components/ILIAS/Refinery/tests/Encode/Transformation/HTMLAttributeValueTest.php b/components/ILIAS/Refinery/tests/Encode/Transformation/HTMLAttributeValueTest.php index f21786c62626..a4246fcfd883 100644 --- a/components/ILIAS/Refinery/tests/Encode/Transformation/HTMLAttributeValueTest.php +++ b/components/ILIAS/Refinery/tests/Encode/Transformation/HTMLAttributeValueTest.php @@ -24,6 +24,7 @@ use ILIAS\Refinery\Encode\Transformation\HTMLAttributeValue; use ValueError; use TypeError; +use PHPUnit\Framework\Attributes\DataProvider; require_once __DIR__ . '/ProvideUTF8CodepointRange.php'; @@ -36,9 +37,7 @@ public function testConstruct(): void $this->assertInstanceOf(HTMLAttributeValue::class, new HTMLAttributeValue()); } - /** - * @dataProvider provideTransformData - */ + #[DataProvider('provideTransformData')] public function testTransform(string $exptected, string $in, string $method): void { $this->$method($exptected, (new HTMLAttributeValue())->transform($in)); diff --git a/components/ILIAS/Refinery/tests/Encode/Transformation/HTMLSpecialCharsAsEntitiesTest.php b/components/ILIAS/Refinery/tests/Encode/Transformation/HTMLSpecialCharsAsEntitiesTest.php index 14a35323a315..e55305f4ace0 100644 --- a/components/ILIAS/Refinery/tests/Encode/Transformation/HTMLSpecialCharsAsEntitiesTest.php +++ b/components/ILIAS/Refinery/tests/Encode/Transformation/HTMLSpecialCharsAsEntitiesTest.php @@ -24,6 +24,7 @@ use ILIAS\Refinery\Encode\Transformation\HTMLSpecialCharsAsEntities; use ValueError; use TypeError; +use PHPUnit\Framework\Attributes\DataProvider; class HTMLSpecialCharsAsEntitiesTest extends TestCase { @@ -32,9 +33,7 @@ public function testConstruct(): void $this->assertInstanceOf(HTMLSpecialCharsAsEntities::class, new HTMLSpecialCharsAsEntities()); } - /** - * @dataProvider provideTransformData - */ + #[DataProvider('provideTransformData')] public function testTransform(string $exptected, string $in): void { $this->assertSame($exptected, (new HTMLSpecialCharsAsEntities())->transform($in)); diff --git a/components/ILIAS/Refinery/tests/Encode/Transformation/JsonTest.php b/components/ILIAS/Refinery/tests/Encode/Transformation/JsonTest.php index 7713e7e4228f..9938cf024138 100644 --- a/components/ILIAS/Refinery/tests/Encode/Transformation/JsonTest.php +++ b/components/ILIAS/Refinery/tests/Encode/Transformation/JsonTest.php @@ -23,6 +23,7 @@ use PHPUnit\Framework\TestCase; use ILIAS\Refinery\Encode\Transformation\Json; use JsonException; +use PHPUnit\Framework\Attributes\DataProvider; require_once __DIR__ . '/ProvideUTF8CodepointRange.php'; @@ -35,9 +36,7 @@ public function testConstruct(): void $this->assertInstanceOf(Json::class, new Json()); } - /** - * @dataProvider provideTransformData - */ + #[DataProvider('provideTransformData')] public function testTransform(string $exptected, string $in, string $method): void { $this->$method($exptected, (new Json())->transform($in)); diff --git a/components/ILIAS/Refinery/tests/Encode/Transformation/URLTest.php b/components/ILIAS/Refinery/tests/Encode/Transformation/URLTest.php index c765bef504f1..2909c18b3ee4 100644 --- a/components/ILIAS/Refinery/tests/Encode/Transformation/URLTest.php +++ b/components/ILIAS/Refinery/tests/Encode/Transformation/URLTest.php @@ -24,6 +24,7 @@ use ILIAS\Refinery\Encode\Transformation\URL; use ValueError; use TypeError; +use PHPUnit\Framework\Attributes\DataProvider; class URLTest extends TestCase { @@ -32,9 +33,7 @@ public function testConstruct(): void $this->assertInstanceOf(URL::class, new URL()); } - /** - * @dataProvider provideTransformData - */ + #[DataProvider('provideTransformData')] public function testTransform(string $exptected, string $in): void { $this->assertSame($exptected, (new URL())->transform($in)); diff --git a/components/ILIAS/Refinery/tests/KindlyTo/Transformation/BooleanTransformationTest.php b/components/ILIAS/Refinery/tests/KindlyTo/Transformation/BooleanTransformationTest.php index f59cf20c99de..139be7f9573c 100755 --- a/components/ILIAS/Refinery/tests/KindlyTo/Transformation/BooleanTransformationTest.php +++ b/components/ILIAS/Refinery/tests/KindlyTo/Transformation/BooleanTransformationTest.php @@ -23,6 +23,7 @@ use ILIAS\Refinery\KindlyTo\Transformation\BooleanTransformation; use PHPUnit\Framework\TestCase; use ILIAS\Refinery\ConstraintViolationException; +use PHPUnit\Framework\Attributes\DataProvider; class BooleanTransformationTest extends TestCase { @@ -33,23 +34,16 @@ protected function setUp(): void $this->transformation = new BooleanTransformation(); } - /** - * @dataProvider BooleanTestDataProvider - * @param mixed $originVal - * @param bool $expectedVal - */ - public function testBooleanTransformation($originVal, bool $expectedVal): void + #[DataProvider('BooleanTestDataProvider')] + public function testBooleanTransformation(mixed $originVal, bool $expectedVal): void { $transformedValue = $this->transformation->transform($originVal); $this->assertIsBool($transformedValue); $this->assertSame($expectedVal, $transformedValue); } - /** - * @dataProvider TransformationFailureDataProvider - * @param mixed $failingValue - */ - public function testTransformIsInvalid($failingValue): void + #[DataProvider('TransformationFailureDataProvider')] + public function testTransformIsInvalid(mixed $failingValue): void { $this->expectException(ConstraintViolationException::class); $this->transformation->transform($failingValue); diff --git a/components/ILIAS/Refinery/tests/KindlyTo/Transformation/DateTimeTransformationTest.php b/components/ILIAS/Refinery/tests/KindlyTo/Transformation/DateTimeTransformationTest.php index 8f6484ad1afd..66f45ea157ee 100755 --- a/components/ILIAS/Refinery/tests/KindlyTo/Transformation/DateTimeTransformationTest.php +++ b/components/ILIAS/Refinery/tests/KindlyTo/Transformation/DateTimeTransformationTest.php @@ -25,6 +25,7 @@ use ILIAS\Refinery\ConstraintViolationException; use ILIAS\Refinery\KindlyTo\Transformation\DateTimeTransformation; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; class DateTimeTransformationTest extends TestCase { @@ -35,12 +36,8 @@ protected function setUp(): void $this->transformation = new DateTimeTransformation(); } - /** - * @dataProvider DateTimeTransformationDataProvider - * @param mixed $originVal - * @param DateTimeImmutable $expectedVal - */ - public function testDateTimeISOTransformation($originVal, DateTimeImmutable $expectedVal): void + #[DataProvider('DateTimeTransformationDataProvider')] + public function testDateTimeISOTransformation(mixed $originVal, DateTimeImmutable $expectedVal): void { $transformedValue = $this->transformation->transform($originVal); $this->assertIsObject($transformedValue); @@ -48,10 +45,7 @@ public function testDateTimeISOTransformation($originVal, DateTimeImmutable $exp $this->assertEquals($expectedVal, $transformedValue); } - /** - * @dataProvider TransformationFailureDataProvider - * @param string$failingValue - */ + #[DataProvider('TransformationFailureDataProvider')] public function testTransformIsInvalid(string $failingValue): void { $this->expectException(ConstraintViolationException::class); diff --git a/components/ILIAS/Refinery/tests/KindlyTo/Transformation/DictionaryTransformationTest.php b/components/ILIAS/Refinery/tests/KindlyTo/Transformation/DictionaryTransformationTest.php index 457f3625fca8..d68b1ccdbf19 100755 --- a/components/ILIAS/Refinery/tests/KindlyTo/Transformation/DictionaryTransformationTest.php +++ b/components/ILIAS/Refinery/tests/KindlyTo/Transformation/DictionaryTransformationTest.php @@ -25,14 +25,15 @@ use ILIAS\Refinery\ConstraintViolationException; use PHPUnit\Framework\TestCase; use stdClass; +use PHPUnit\Framework\Attributes\DataProvider; class DictionaryTransformationTest extends TestCase { /** - * @dataProvider DictionaryTransformationDataProvider * @param array $originVal * @param array $expectedVal */ + #[DataProvider('DictionaryTransformationDataProvider')] public function testDictionaryTransformation(array $originVal, array $expectedVal): void { $transformation = new DictionaryTransformation(new StringTransformation()); @@ -41,11 +42,8 @@ public function testDictionaryTransformation(array $originVal, array $expectedVa $this->assertEquals($expectedVal, $transformedValue); } - /** - * @dataProvider TransformationFailingDataProvider - * @param mixed $failingVal - */ - public function testTransformationFailures($failingVal): void + #[DataProvider('TransformationFailingDataProvider')] + public function testTransformationFailures(mixed $failingVal): void { $this->expectException(ConstraintViolationException::class); $transformation = new DictionaryTransformation(new StringTransformation()); diff --git a/components/ILIAS/Refinery/tests/KindlyTo/Transformation/FloatTransformationTest.php b/components/ILIAS/Refinery/tests/KindlyTo/Transformation/FloatTransformationTest.php index da2c1377a536..8dc3eaea7954 100755 --- a/components/ILIAS/Refinery/tests/KindlyTo/Transformation/FloatTransformationTest.php +++ b/components/ILIAS/Refinery/tests/KindlyTo/Transformation/FloatTransformationTest.php @@ -23,6 +23,7 @@ use ILIAS\Refinery\ConstraintViolationException; use ILIAS\Refinery\KindlyTo\Transformation\FloatTransformation; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; class FloatTransformationTest extends TestCase { @@ -33,23 +34,16 @@ protected function setUp(): void $this->transformation = new FloatTransformation(); } - /** - * @dataProvider FloatTestDataProvider - * @param mixed $originVal - * @param float $expectedVal - */ - public function testFloatTransformation($originVal, float $expectedVal): void + #[DataProvider('FloatTestDataProvider')] + public function testFloatTransformation(mixed $originVal, float $expectedVal): void { $transformedValue = $this->transformation->transform($originVal); $this->assertIsFloat($transformedValue); $this->assertEquals($expectedVal, $transformedValue); } - /** - * @dataProvider FailingTransformationDataProvider - * @param mixed $failingVal - */ - public function testFailingTransformations($failingVal): void + #[DataProvider('FailingTransformationDataProvider')] + public function testFailingTransformations(mixed $failingVal): void { $this->expectNotToPerformAssertions(); try { diff --git a/components/ILIAS/Refinery/tests/KindlyTo/Transformation/IntegerTransformationTest.php b/components/ILIAS/Refinery/tests/KindlyTo/Transformation/IntegerTransformationTest.php index 9833616e83da..e0ad407a6d51 100755 --- a/components/ILIAS/Refinery/tests/KindlyTo/Transformation/IntegerTransformationTest.php +++ b/components/ILIAS/Refinery/tests/KindlyTo/Transformation/IntegerTransformationTest.php @@ -23,6 +23,7 @@ use ILIAS\Refinery\ConstraintViolationException; use ILIAS\Refinery\KindlyTo\Transformation\IntegerTransformation; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; class IntegerTransformationTest extends TestCase { @@ -33,23 +34,16 @@ protected function setUp(): void $this->transformation = new IntegerTransformation(); } - /** - * @dataProvider IntegerTestDataProvider - * @param mixed $originVal - * @param int $expectedVal - */ - public function testIntegerTransformation($originVal, int $expectedVal): void + #[DataProvider('IntegerTestDataProvider')] + public function testIntegerTransformation(mixed $originVal, int $expectedVal): void { $transformedValue = $this->transformation->transform($originVal); $this->assertIsInt($transformedValue); $this->assertEquals($expectedVal, $transformedValue); } - /** - * @dataProvider TransformationFailureDataProvider - * @param mixed $failingValue - */ - public function testTransformIsInvalid($failingValue): void + #[DataProvider('TransformationFailureDataProvider')] + public function testTransformIsInvalid(mixed $failingValue): void { $this->expectException(ConstraintViolationException::class); $this->transformation->transform($failingValue); diff --git a/components/ILIAS/Refinery/tests/KindlyTo/Transformation/ListTransformationTest.php b/components/ILIAS/Refinery/tests/KindlyTo/Transformation/ListTransformationTest.php index ee404662a75d..83434c8693ef 100755 --- a/components/ILIAS/Refinery/tests/KindlyTo/Transformation/ListTransformationTest.php +++ b/components/ILIAS/Refinery/tests/KindlyTo/Transformation/ListTransformationTest.php @@ -24,15 +24,12 @@ use ILIAS\Refinery\To\Transformation\StringTransformation; use PHPUnit\Framework\TestCase; use UnexpectedValueException; +use PHPUnit\Framework\Attributes\DataProvider; class ListTransformationTest extends TestCase { - /** - * @dataProvider ArrayToListTransformationDataProvider - * @param mixed $originValue - * @param mixed $expectedValue - */ - public function testListTransformation($originValue, $expectedValue): void + #[DataProvider('ArrayToListTransformationDataProvider')] + public function testListTransformation(mixed $originValue, mixed $expectedValue): void { $transformList = new ListTransformation(new StringTransformation()); $transformedValue = $transformList->transform($originValue); @@ -40,11 +37,8 @@ public function testListTransformation($originValue, $expectedValue): void $this->assertEquals($expectedValue, $transformedValue); } - /** - * @dataProvider ArrayFailureDataProvider - * @param mixed $origValue - */ - public function testFailingTransformations($origValue): void + #[DataProvider('ArrayFailureDataProvider')] + public function testFailingTransformations(mixed $origValue): void { $this->expectException(UnexpectedValueException::class); $transformList = new ListTransformation(new StringTransformation()); diff --git a/components/ILIAS/Refinery/tests/KindlyTo/Transformation/NullTransformationTest.php b/components/ILIAS/Refinery/tests/KindlyTo/Transformation/NullTransformationTest.php index 1c892ce3a35f..7096582b848c 100755 --- a/components/ILIAS/Refinery/tests/KindlyTo/Transformation/NullTransformationTest.php +++ b/components/ILIAS/Refinery/tests/KindlyTo/Transformation/NullTransformationTest.php @@ -21,6 +21,7 @@ use ILIAS\Refinery\KindlyTo\Transformation\NullTransformation; use PHPUnit\Framework\TestCase; use ILIAS\Refinery\ConstraintViolationException; +use PHPUnit\Framework\Attributes\DataProvider; class NullTransformationTest extends TestCase { @@ -48,13 +49,8 @@ public static function NullTestDataProvider(): array ]; } - /** - * @dataProvider NullTestDataProvider - * @param mixed $value - * @param bool $valid - * @throws Exception - */ - public function testNullTransformation($value, bool $valid): void + #[DataProvider('NullTestDataProvider')] + public function testNullTransformation(mixed $value, bool $valid): void { if (!$valid) { $this->expectException(ConstraintViolationException::class); diff --git a/components/ILIAS/Refinery/tests/KindlyTo/Transformation/RecordTransformationTest.php b/components/ILIAS/Refinery/tests/KindlyTo/Transformation/RecordTransformationTest.php index ed6a980425e2..eebc38b505c8 100755 --- a/components/ILIAS/Refinery/tests/KindlyTo/Transformation/RecordTransformationTest.php +++ b/components/ILIAS/Refinery/tests/KindlyTo/Transformation/RecordTransformationTest.php @@ -25,6 +25,7 @@ use ILIAS\Refinery\KindlyTo\Transformation\RecordTransformation; use ILIAS\Refinery\KindlyTo\Transformation\StringTransformation; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; class RecordTransformationTest extends TestCase { @@ -33,10 +34,10 @@ class RecordTransformationTest extends TestCase private const SECOND_INT_KEY = 'integerKey2'; /** - * @dataProvider RecordTransformationDataProvider * @param array $originVal * @param array $expectedVal */ + #[DataProvider('RecordTransformationDataProvider')] public function testRecordTransformationIsValid(array $originVal, array $expectedVal): void { $recTransform = new RecordTransformation( @@ -51,9 +52,9 @@ public function testRecordTransformationIsValid(array $originVal, array $expecte } /** - * @dataProvider RecordFailureDataProvider * @param array $origVal */ + #[DataProvider('RecordFailureDataProvider')] public function testRecordTransformationFailures(array $origVal): void { $this->expectNotToPerformAssertions(); @@ -89,9 +90,9 @@ public function testInvalidArray(): void } /** - * @dataProvider RecordValueInvalidDataProvider * @param array $originalValue */ + #[DataProvider('RecordValueInvalidDataProvider')] public function testInvalidValueDoesNotMatch(array $originalValue): void { $this->expectNotToPerformAssertions(); diff --git a/components/ILIAS/Refinery/tests/KindlyTo/Transformation/StringTransformationTest.php b/components/ILIAS/Refinery/tests/KindlyTo/Transformation/StringTransformationTest.php index 541d4242b175..753f2b9cc17d 100755 --- a/components/ILIAS/Refinery/tests/KindlyTo/Transformation/StringTransformationTest.php +++ b/components/ILIAS/Refinery/tests/KindlyTo/Transformation/StringTransformationTest.php @@ -23,6 +23,7 @@ use ILIAS\Refinery\KindlyTo\Transformation\StringTransformation; use PHPUnit\Framework\TestCase; use stdClass; +use PHPUnit\Framework\Attributes\DataProvider; class StringTransformationTest extends TestCase { @@ -33,12 +34,8 @@ protected function setUp(): void $this->transformation = new StringTransformation(); } - /** - * @dataProvider StringTestDataProvider - * @param mixed $originVal - * @param string $expectedVal - */ - public function testStringTransformation($originVal, string $expectedVal): void + #[DataProvider('StringTestDataProvider')] + public function testStringTransformation(mixed $originVal, string $expectedVal): void { $transformedValue = $this->transformation->transform($originVal); $this->assertIsString($transformedValue); diff --git a/components/ILIAS/Refinery/tests/KindlyTo/Transformation/TupleTransformationTest.php b/components/ILIAS/Refinery/tests/KindlyTo/Transformation/TupleTransformationTest.php index de25d04ce72d..a494b6471ca6 100755 --- a/components/ILIAS/Refinery/tests/KindlyTo/Transformation/TupleTransformationTest.php +++ b/components/ILIAS/Refinery/tests/KindlyTo/Transformation/TupleTransformationTest.php @@ -24,16 +24,17 @@ use ILIAS\Refinery\KindlyTo\Transformation\IntegerTransformation; use ILIAS\Refinery\KindlyTo\Transformation\TupleTransformation; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; class TupleTransformationTest extends TestCase { private const TUPLE_KEY = 'hello'; /** - * @dataProvider TupleTransformationDataProvider * @param array $originVal * @param array $expectedVal */ + #[DataProvider('TupleTransformationDataProvider')] public function testTupleTransformation(array $originVal, array $expectedVal): void { $transformation = new TupleTransformation( @@ -48,9 +49,9 @@ public function testTupleTransformation(array $originVal, array $expectedVal): v } /** - * @dataProvider TupleFailingTransformationDataProvider * @param array $failingVal */ + #[DataProvider('TupleFailingTransformationDataProvider')] public function testNewTupleIsIncorrect(array $failingVal): void { $this->expectNotToPerformAssertions(); @@ -70,9 +71,9 @@ public function testNewTupleIsIncorrect(array $failingVal): void } /** - * @dataProvider TupleTooManyValuesDataProvider * @param array $tooManyValues */ + #[DataProvider('TupleTooManyValuesDataProvider')] public function testTupleTooManyValues(array $tooManyValues): void { $this->expectNotToPerformAssertions(); diff --git a/components/ILIAS/Refinery/tests/Logical/Constraint/LogicalOrTest.php b/components/ILIAS/Refinery/tests/Logical/Constraint/LogicalOrTest.php index 49fa15cdae21..08180ebff73a 100755 --- a/components/ILIAS/Refinery/tests/Logical/Constraint/LogicalOrTest.php +++ b/components/ILIAS/Refinery/tests/Logical/Constraint/LogicalOrTest.php @@ -22,28 +22,19 @@ use ILIAS\Refinery\Factory as Refinery; use ILIAS\Refinery\Logical\LogicalOr; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; class LogicalOrTest extends TestCase { - /** - * @dataProvider constraintsProvider - * @param LogicalOr $constraint - * @param mixed $okValue - * @param mixed $errorValue - */ - public function testAccept(LogicalOr $constraint, $okValue, $errorValue): void + #[DataProvider('constraintsProvider')] + public function testAccept(LogicalOr $constraint, mixed $okValue, mixed $errorValue): void { $this->assertTrue($constraint->accepts($okValue)); $this->assertFalse($constraint->accepts($errorValue)); } - /** - * @dataProvider constraintsProvider - * @param LogicalOr $constraint - * @param mixed $okValue - * @param mixed $errorValue - */ - public function testCheck(LogicalOr $constraint, $okValue, $errorValue): void + #[DataProvider('constraintsProvider')] + public function testCheck(LogicalOr $constraint, mixed $okValue, mixed $errorValue): void { $raised = false; @@ -65,25 +56,15 @@ public function testCheck(LogicalOr $constraint, $okValue, $errorValue): void $this->assertFalse($raised); } - /** - * @dataProvider constraintsProvider - * @param LogicalOr $constraint - * @param mixed $okValue - * @param mixed $errorValue - */ - public function testProblemWith(LogicalOr $constraint, $okValue, $errorValue): void + #[DataProvider('constraintsProvider')] + public function testProblemWith(LogicalOr $constraint, mixed $okValue, mixed $errorValue): void { $this->assertNull($constraint->problemWith($okValue)); $this->assertIsString($constraint->problemWith($errorValue)); } - /** - * @dataProvider constraintsProvider - * @param LogicalOr $constraint - * @param mixed $okValue - * @param mixed $errorValue - */ - public function testRestrict(LogicalOr $constraint, $okValue, $errorValue): void + #[DataProvider('constraintsProvider')] + public function testRestrict(LogicalOr $constraint, mixed $okValue, mixed $errorValue): void { $rf = new DataFactory(); $ok = $rf->ok($okValue); @@ -100,13 +81,8 @@ public function testRestrict(LogicalOr $constraint, $okValue, $errorValue): void $this->assertSame($error, $result); } - /** - * @dataProvider constraintsProvider - * @param LogicalOr $constraint - * @param mixed $okValue - * @param mixed $errorValue - */ - public function testWithProblemBuilder(LogicalOr $constraint, $okValue, $errorValue): void + #[DataProvider('constraintsProvider')] + public function testWithProblemBuilder(LogicalOr $constraint, mixed $okValue, mixed $errorValue): void { $new_constraint = $constraint->withProblemBuilder(static function (): string { return "This was a vault"; diff --git a/components/ILIAS/Refinery/tests/Parser/ABNF/BrickTest.php b/components/ILIAS/Refinery/tests/Parser/ABNF/BrickTest.php index 16e566d0f108..96e5aed025cf 100755 --- a/components/ILIAS/Refinery/tests/Parser/ABNF/BrickTest.php +++ b/components/ILIAS/Refinery/tests/Parser/ABNF/BrickTest.php @@ -28,6 +28,7 @@ use ILIAS\Refinery\Parser\ABNF\Intermediate; use ILIAS\Refinery\Transformation; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; class BrickTest extends TestCase { @@ -154,9 +155,7 @@ public function testSequence(): void $this->assertEquals('d', $result->value()['second']); } - /** - * @dataProvider repeatProvider - */ + #[DataProvider('repeatProvider')] public function testRepeat(int $min, ?int $max, array $succeed, array $fail): void { $brick = new Brick(); @@ -185,9 +184,7 @@ public static function repeatProvider(): array ]; } - /** - * @dataProvider characterProvider - */ + #[DataProvider('characterProvider')] public function testCharacters(string $method, string $input, bool $isOk): void { $brick = new Brick(); @@ -239,9 +236,7 @@ public static function characterProvider(): array ]; } - /** - * @dataProvider emptyStringProvider - */ + #[DataProvider('emptyStringProvider')] public function testEmptyString(string $input, bool $isOk): void { $brick = new Brick(); diff --git a/components/ILIAS/Refinery/tests/Parser/ABNF/IntermediateTest.php b/components/ILIAS/Refinery/tests/Parser/ABNF/IntermediateTest.php index c0b5eaef9a87..525ce6f8db5f 100755 --- a/components/ILIAS/Refinery/tests/Parser/ABNF/IntermediateTest.php +++ b/components/ILIAS/Refinery/tests/Parser/ABNF/IntermediateTest.php @@ -26,6 +26,7 @@ use ILIAS\Refinery\Parser\ABNF\Character; use PHPUnit\Framework\TestCase; use stdClass; +use PHPUnit\Framework\Attributes\Depends; class IntermediateTest extends TestCase { @@ -40,21 +41,19 @@ public function testValue(): void { $intermediate = new Intermediate('input'); - $this->assertEquals(ord('i'), $intermediate->value()); + $this->assertEquals(\ord('i'), $intermediate->value()); } - /** - * @depends testValue - */ + #[Depends('testValue')] public function testAccept(): void { $intermediate = new Intermediate('input'); - $this->assertEquals(ord('i'), $intermediate->value()); + $this->assertEquals(\ord('i'), $intermediate->value()); $next = $intermediate->accept(); $this->assertTrue($next->isOK()); - $this->assertEquals(ord('n'), $next->value()->value()); + $this->assertEquals(\ord('n'), $next->value()->value()); } public function testReject(): void @@ -65,17 +64,15 @@ public function testReject(): void $this->assertFalse($next->isOK()); } - /** - * @depends testValue - * @depends testAccept - */ + #[Depends('testValue')] + #[Depends('testAccept')] public function testAccepted(): void { $expected = 'in'; $intermediate = new Intermediate('input'); $this->assertEquals([], $intermediate->accepted()); $accepted = $intermediate->accept()->value()->accept()->value()->accepted(); - $this->assertTrue(is_array($accepted)); + $this->assertIsArray($accepted); $actual = ''; foreach ($accepted as $character) { $this->assertInstanceOf(Character::class, $character); @@ -84,9 +81,7 @@ public function testAccepted(): void $this->assertEquals($expected, $actual); } - /** - * @depends testAccept - */ + #[Depends('testAccept')] public function testDone(): void { $intermediate = new Intermediate('ab'); @@ -94,22 +89,17 @@ public function testDone(): void $this->assertTrue($intermediate->accept()->value()->accept()->value()->done()); } - /** - * @depends testAccepted - * @depends testAccepted - * @depends testValue - */ + #[Depends('testAccepted')] + #[Depends('testValue')] public function testOnlyTodo(): void { $intermediate = new Intermediate('ab'); $intermediate = $intermediate->accept()->value()->onlyTodo(); $this->assertEmpty($intermediate->accepted()); - $this->assertEquals(ord('b'), $intermediate->value()); + $this->assertEquals(\ord('b'), $intermediate->value()); } - /** - * @depends testAccept - */ + #[Depends('testAccept')] public function testTransformOnlyWithCharacters(): void { $intermediate = new Intermediate('hej'); @@ -125,9 +115,7 @@ public function testTransformOnlyWithCharacters(): void $this->assertEquals($ok, $result); } - /** - * @depends testAccepted - */ + #[Depends('testAccepted')] public function testPush(): void { $intermediate = new Intermediate('hej'); @@ -136,10 +124,8 @@ public function testPush(): void $this->assertEquals([$value], $intermediate->push([$value])->accepted()); } - /** - * @depends testPush - * @depends testAccept - */ + #[Depends('testPush')] + #[Depends('testAccept')] public function testTransformWithTransformedValues(): void { $intermediate = new Intermediate('hej'); @@ -151,7 +137,7 @@ public function testTransformWithTransformedValues(): void $intermediate = $intermediate->push([$dummy]); $result = $intermediate->transform(function (array $accepted) use ($dummy, $ok): Result { - $this->assertEquals(2, count($accepted)); + $this->assertCount(2, $accepted); $this->assertInstanceOf(Character::class, $accepted[0]); $this->assertEquals('h', $accepted[0]->value()); $this->assertEquals($dummy, $accepted[1]); diff --git a/components/ILIAS/Refinery/tests/Parser/ABNF/TransformTest.php b/components/ILIAS/Refinery/tests/Parser/ABNF/TransformTest.php index fc9b6fe0b43c..08c06900f71b 100755 --- a/components/ILIAS/Refinery/tests/Parser/ABNF/TransformTest.php +++ b/components/ILIAS/Refinery/tests/Parser/ABNF/TransformTest.php @@ -29,6 +29,7 @@ use ILIAS\Refinery\Transformation; use Closure; use stdClass; +use PHPUnit\Framework\Attributes\Depends; class TransformTest extends TestCase { @@ -37,9 +38,7 @@ public function testConstruct(): void $this->assertInstanceOf(Transform::class, new Transform()); } - /** - * @depends testConstruct - */ + #[Depends('testConstruct')] public function testTo(): void { $transform = new Transform(); @@ -64,9 +63,7 @@ public function testTo(): void $this->assertEquals($end, $x->value()); } - /** - * @depends testConstruct - */ + #[Depends('testConstruct')] public function testFailedToTransform(): void { $transform = new Transform(); @@ -90,9 +87,7 @@ public function testFailedToTransform(): void $this->assertEquals($end, $x->value()); } - /** - * @depends testConstruct - */ + #[Depends('testConstruct')] public function testFailedToParse(): void { $transform = new Transform(); diff --git a/components/ILIAS/Refinery/tests/Password/Constraint/PasswordContraintsTest.php b/components/ILIAS/Refinery/tests/Password/Constraint/PasswordContraintsTest.php index b26fb9f4603a..6e04e851311f 100755 --- a/components/ILIAS/Refinery/tests/Password/Constraint/PasswordContraintsTest.php +++ b/components/ILIAS/Refinery/tests/Password/Constraint/PasswordContraintsTest.php @@ -20,6 +20,7 @@ use ILIAS\Refinery\Constraint; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; class PasswordContraintsTest extends TestCase { @@ -82,11 +83,11 @@ public function toJS($key): void } /** - * @dataProvider constraintsProvider * @param Constraint $constraint * @param ILIAS\Data\Password[] $ok_values * @param ILIAS\Data\Password[] $error_values */ + #[DataProvider('constraintsProvider')] public function testAccept(Constraint $constraint, array $ok_values, array $error_values): void { foreach ($ok_values as $ok_value) { diff --git a/components/ILIAS/Refinery/tests/String/Encoding/EncodingTest.php b/components/ILIAS/Refinery/tests/String/Encoding/EncodingTest.php index ad639ef2454b..308fcbd69bf9 100644 --- a/components/ILIAS/Refinery/tests/String/Encoding/EncodingTest.php +++ b/components/ILIAS/Refinery/tests/String/Encoding/EncodingTest.php @@ -21,6 +21,7 @@ namespace ILIAS\Refinery\String\Encoding; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; class EncodingTest extends TestCase { @@ -47,9 +48,7 @@ public static function latin1StringProvider(): array return $strings; } - /** - * @dataProvider latin1StringProvider - */ + #[DataProvider('latin1StringProvider')] public function testLatin1ToUTF8( string $latin_1_string, string $expected_utf8 @@ -75,9 +74,7 @@ public static function asciiStringProvider(): array return $strings; } - /** - * @dataProvider asciiStringProvider - */ + #[DataProvider('asciiStringProvider')] public function testAsciiToUTF8( string $latin_1_string, string $expected_utf8 diff --git a/components/ILIAS/Refinery/tests/String/EstimatedReadingTimeTest.php b/components/ILIAS/Refinery/tests/String/EstimatedReadingTimeTest.php index 127e772fdd6f..a598016bfdb0 100755 --- a/components/ILIAS/Refinery/tests/String/EstimatedReadingTimeTest.php +++ b/components/ILIAS/Refinery/tests/String/EstimatedReadingTimeTest.php @@ -28,6 +28,7 @@ use PHPUnit\Framework\TestCase; use stdClass; use InvalidArgumentException; +use PHPUnit\Framework\Attributes\DataProvider; class EstimatedReadingTimeTest extends TestCase { @@ -93,11 +94,8 @@ public static function unsupportedButKnownEntitiesProvider(): array ]); } - /** - * @dataProvider inputProvider - * @param mixed $from - */ - public function testExceptionIsRaisedIfInputIsNotAString($from): void + #[DataProvider('inputProvider')] + public function testExceptionIsRaisedIfInputIsNotAString(mixed $from): void { $this->expectException(InvalidArgumentException::class); $readingTimeTrafo = $this->refinery->string()->estimatedReadingTime(true); @@ -165,10 +163,7 @@ public function testXTHMLCommentsMustNotAffectReadingTime(): void ); } - /** - * @dataProvider unsupportedButKnownEntitiesProvider - * @param string $text - */ + #[DataProvider('unsupportedButKnownEntitiesProvider')] public function testNoExceptionIsRaisedIfHtmlContainsUnsupportedEntities(string $text): void { $reading_time_trafo = $this->refinery->string()->estimatedReadingTime(true); diff --git a/components/ILIAS/Refinery/tests/String/MakeClickableTest.php b/components/ILIAS/Refinery/tests/String/MakeClickableTest.php index b052517e78f2..8b3cf97e9c15 100755 --- a/components/ILIAS/Refinery/tests/String/MakeClickableTest.php +++ b/components/ILIAS/Refinery/tests/String/MakeClickableTest.php @@ -23,6 +23,7 @@ use PHPUnit\Framework\TestCase; use ILIAS\Refinery\String\MakeClickable; use ILIAS\Refinery\ConstraintViolationException; +use PHPUnit\Framework\Attributes\DataProvider; class MakeClickableTest extends TestCase { @@ -39,18 +40,14 @@ public function testTransformFailure(): void $clickable->transform(3); } - /** - * @dataProvider provideInputInNewTab - */ + #[DataProvider('provideInputInNewTab')] public function testTransformSuccessInNewTab(string $expected, string $input): void { $clickable = new MakeClickable(); $this->assertEquals($expected, $clickable->transform($input)); } - /** - * @dataProvider provideInputWithoutAttributes - */ + #[DataProvider('provideInputWithoutAttributes')] public function testTransformSuccess(string $expected, string $input): void { $clickable = new MakeClickable(false); diff --git a/components/ILIAS/Refinery/tests/String/MarkdownFormattingToHTMLTest.php b/components/ILIAS/Refinery/tests/String/MarkdownFormattingToHTMLTest.php index 56eb798305fc..96730d8a6abd 100755 --- a/components/ILIAS/Refinery/tests/String/MarkdownFormattingToHTMLTest.php +++ b/components/ILIAS/Refinery/tests/String/MarkdownFormattingToHTMLTest.php @@ -25,6 +25,7 @@ use PHPUnit\Framework\TestCase; use ILIAS\Language\Language; use ILIAS\Refinery\Transformation; +use PHPUnit\Framework\Attributes\DataProvider; class MarkdownFormattingToHTMLTest extends TestCase { @@ -57,9 +58,7 @@ public static function stringProvider(): array ]; } - /** - * @dataProvider stringProvider - */ + #[DataProvider('stringProvider')] public function testTransformationToHTML( string $markdown_string, string $expected_html, diff --git a/components/ILIAS/Refinery/tests/String/UTFNormalTest.php b/components/ILIAS/Refinery/tests/String/UTFNormalTest.php index 8e5bf182921e..7a455aa7c7df 100755 --- a/components/ILIAS/Refinery/tests/String/UTFNormalTest.php +++ b/components/ILIAS/Refinery/tests/String/UTFNormalTest.php @@ -25,6 +25,7 @@ use PHPUnit\Framework\TestCase; use ILIAS\Refinery\String\Transformation\UTFNormalTransformation; use ILIAS\Refinery\Transformation; +use PHPUnit\Framework\Attributes\DataProvider; class UTFNormalTest extends TestCase { @@ -62,9 +63,7 @@ public static function stringProvider(): array ]; } - /** - * @dataProvider stringProvider - */ + #[DataProvider('stringProvider')] public function testNormalization( string $string, string $expected_form_c, diff --git a/components/ILIAS/Refinery/tests/To/Transformation/InArrayTransformationTest.php b/components/ILIAS/Refinery/tests/To/Transformation/InArrayTransformationTest.php index b6c3aa94e3b8..379b828fbc48 100644 --- a/components/ILIAS/Refinery/tests/To/Transformation/InArrayTransformationTest.php +++ b/components/ILIAS/Refinery/tests/To/Transformation/InArrayTransformationTest.php @@ -25,6 +25,7 @@ use PHPUnit\Framework\TestCase; use ILIAS\Language\Language; use UnexpectedValueException; +use PHPUnit\Framework\Attributes\DataProvider; class InArrayTransformationTest extends TestCase { @@ -34,9 +35,7 @@ public function testConstruct(): void $this->assertInstanceOf(InArrayTransformation::class, new InArrayTransformation([], $language)); } - /** - * @dataProvider memberProvider - */ + #[DataProvider('memberProvider')] public function testAccept(string $value, bool $successful): void { $language = $this->getMockBuilder(Language::class)->disableOriginalConstructor()->getMock(); @@ -45,9 +44,7 @@ public function testAccept(string $value, bool $successful): void $this->assertSame($successful, $transformation->accepts($value)); } - /** - * @dataProvider memberProvider - */ + #[DataProvider('memberProvider')] public function testTransform(string $value, bool $successful): void { if (!$successful) { @@ -60,9 +57,7 @@ public function testTransform(string $value, bool $successful): void $this->assertSame($value, $transformation->transform($value)); } - /** - * @dataProvider memberProvider - */ + #[DataProvider('memberProvider')] public function testApplyTo(string $value, bool $successful): void { $language = $this->getMockBuilder(Language::class)->disableOriginalConstructor()->getMock(); diff --git a/components/ILIAS/Registration/classes/class.ilAccountRegistrationGUI.php b/components/ILIAS/Registration/classes/class.ilAccountRegistrationGUI.php index c0c8df89996a..ec181d8932f1 100755 --- a/components/ILIAS/Registration/classes/class.ilAccountRegistrationGUI.php +++ b/components/ILIAS/Registration/classes/class.ilAccountRegistrationGUI.php @@ -371,6 +371,9 @@ protected function createUser(int $a_role): string } $this->userObj = new ilObjUser(); + if ((int) $this->settings->get('auth_mode') !== ilAuthUtils::AUTH_LOCAL) { + $this->userObj->setAuthMode('local'); + } $this->user_profile->skipGroup("preferences"); $this->user_profile->skipGroup("settings"); diff --git a/components/ILIAS/Registration/classes/class.ilRegistrationCode.php b/components/ILIAS/Registration/classes/class.ilRegistrationCode.php index f62cd1a8d953..381f9f62edcd 100755 --- a/components/ILIAS/Registration/classes/class.ilRegistrationCode.php +++ b/components/ILIAS/Registration/classes/class.ilRegistrationCode.php @@ -84,150 +84,6 @@ protected static function generateRandomCode(): string return $code; } - public static function getCodesData( - string $order_field, - string $order_direction, - int $offset, - int $limit, - string $filter_code, - int $filter_role, - string $filter_generated, - string $filter_access_limitation - ): array { - global $DIC; - - $ilDB = $DIC->database(); - - // filter - $where = self::filterToSQL($filter_code, $filter_role, $filter_generated, $filter_access_limitation); - - // count query - $set = $ilDB->query("SELECT COUNT(*) AS cnt FROM " . self::DB_TABLE . $where); - $cnt = 0; - if ($rec = $ilDB->fetchAssoc($set)) { - $cnt = $rec["cnt"]; - } - - $sql = "SELECT * FROM " . self::DB_TABLE . $where; - if ($order_field) { - if ($order_field === 'generated') { - $order_field = 'generated_on'; - } - $sql .= " ORDER BY " . $order_field . " " . $order_direction; - } - - // set query - $ilDB->setLimit($limit, $offset); - $set = $ilDB->query($sql); - $result = []; - while ($rec = $ilDB->fetchAssoc($set)) { - $rec['generated'] = $rec['generated_on']; - $result[] = $rec; - } - return ["cnt" => $cnt, "set" => $result]; - } - - public static function loadCodesByIds(array $ids): array - { - global $DIC; - - $ilDB = $DIC->database(); - - $set = $ilDB->query("SELECT * FROM " . self::DB_TABLE . " WHERE " . $ilDB->in( - "code_id", - $ids, - false, - "integer" - )); - $result = []; - while ($rec = $ilDB->fetchAssoc($set)) { - $result[] = $rec; - } - return $result; - } - - public static function deleteCodes(array $ids): bool - { - global $DIC; - - $ilDB = $DIC->database(); - if (count($ids)) { - return (bool) $ilDB->manipulate("DELETE FROM " . self::DB_TABLE . " WHERE " . $ilDB->in( - "code_id", - $ids, - false, - "integer" - )); - } - return false; - } - - public static function getGenerationDates(): array - { - global $DIC; - - $ilDB = $DIC->database(); - - $set = $ilDB->query("SELECT DISTINCT(generated_on) genr FROM " . self::DB_TABLE . " ORDER BY genr"); - $result = []; - while ($rec = $ilDB->fetchAssoc($set)) { - $result[] = $rec["genr"]; - } - return $result; - } - - private static function filterToSQL( - string $filter_code, - ?int $filter_role, - string $filter_generated, - string $filter_access_limitation - ): string { - global $DIC; - - $ilDB = $DIC['ilDB']; - - $where = []; - if ($filter_code) { - $where[] = $ilDB->like("code", "text", "%" . $filter_code . "%"); - } - if ($filter_role) { - $where[] = "role = " . $ilDB->quote($filter_role, "integer"); - } - if ($filter_generated) { - $where[] = "generated_on = " . $ilDB->quote($filter_generated, "text"); - } - if ($filter_access_limitation) { - $where[] = "alimit = " . $ilDB->quote($filter_access_limitation, "text"); - } - if (count($where)) { - return " WHERE " . implode(" AND ", $where); - } - - return ""; - } - - public static function getCodesForExport( - string $filter_code, - ?int $filter_role, - string $filter_generated, - string $filter_access_limitation - ): array { - global $DIC; - - $ilDB = $DIC->database(); - - // filter - $where = self::filterToSQL($filter_code, $filter_role, $filter_generated, $filter_access_limitation); - - // set query - $set = $ilDB->query("SELECT code FROM " . self::DB_TABLE . $where . " ORDER BY code_id"); - $result = []; - while ($rec = $ilDB->fetchAssoc($set)) { - $result[] = $rec["code"]; - } - return $result; - } - public static function isUnusedCode(string $code): bool { global $DIC; diff --git a/components/ILIAS/Registration/classes/class.ilRegistrationCodesTableGUI.php b/components/ILIAS/Registration/classes/class.ilRegistrationCodesTableGUI.php deleted file mode 100755 index ba506f944eae..000000000000 --- a/components/ILIAS/Registration/classes/class.ilRegistrationCodesTableGUI.php +++ /dev/null @@ -1,270 +0,0 @@ - - * @version $Id$ - * @ilCtrl_Calls ilRegistrationCodesTableGUI: - * @ingroup ServicesRegistration - */ -class ilRegistrationCodesTableGUI extends ilTable2GUI -{ - protected ilAccessHandler $access; - protected ilRbacReview $rbacreview; - - public array $filter = []; - /** @var array */ - protected array $role_map = []; - - /** - * Constructor - */ - public function __construct(object $a_parent_obj, string $a_parent_cmd) - { - global $DIC; - - $this->access = $DIC->access(); - $this->rbacreview = $DIC->rbac()->review(); - $this->setId("registration_code"); - parent::__construct($a_parent_obj, $a_parent_cmd); - - $this->addColumn("", "", "1", true); - foreach ($this->getSelectedColumns() as $c => $caption) { - if ($c === "role_local" || $c === "alimit") { - $c = ""; - } - $this->addColumn($this->lng->txt($caption), $c); - } - - $this->setExternalSorting(true); - $this->setExternalSegmentation(true); - $this->setEnableHeader(true); - $this->setFormAction($this->ctrl->getFormAction($this->parent_obj, "listCodes")); - $this->setRowTemplate("tpl.code_list_row.html", "components/ILIAS/Registration"); - $this->setEnableTitle(true); - $this->initFilter(); - $this->setFilterCommand("applyCodesFilter"); - $this->setResetCommand("resetCodesFilter"); - $this->setDefaultOrderField("generated"); // #11341 - $this->setDefaultOrderDirection("desc"); - - $this->setSelectAllCheckbox("id[]"); - $this->setTopCommands(true); - - if ($this->access->checkAccess("write", '', $a_parent_obj->ref_id)) { - $this->addMultiCommand("deleteConfirmation", $this->lng->txt("delete")); - } - $this->addCommandButton("exportCodes", $this->lng->txt("registration_codes_export")); - $this->getItems(); - } - - /** - * Get user items - */ - private function getItems(): void - { - $this->determineOffsetAndOrder(); - - // #12737 - if (!array_key_exists($this->getOrderField(), $this->getSelectedColumns())) { - $this->setOrderField($this->getDefaultOrderField()); - } - - $codes_data = ilRegistrationCode::getCodesData( - $this->getOrderField(), - $this->getOrderDirection(), - $this->getOffset(), - $this->getLimit(), - (string) $this->filter["code"], - (int) $this->filter["role"], - (string) $this->filter["generated"], - (string) $this->filter["alimit"] - ); - - if (count($codes_data["set"]) === 0 && $this->getOffset() > 0) { - $this->resetOffset(); - $codes_data = ilRegistrationCode::getCodesData( - $this->getOrderField(), - $this->getOrderDirection(), - $this->getOffset(), - $this->getLimit(), - (string) $this->filter["code"], - (int) $this->filter["role"], - (string) $this->filter["generated"], - (string) $this->filter["alimit"] - ); - } - - $options = []; - $this->role_map = []; - foreach ($this->rbacreview->getGlobalRoles() as $role_id) { - if (!in_array($role_id, [SYSTEM_ROLE_ID, ANONYMOUS_ROLE_ID], true)) { - $this->role_map[$role_id] = ilObject::_lookupTitle($role_id); - } - } - - $result = []; - foreach ($codes_data["set"] as $k => $code) { - $result[$k]["code"] = $code["code"]; - $result[$k]["code_id"] = $code["code_id"]; - - $result[$k]["generated"] = ilDatePresentation::formatDate(new ilDateTime($code["generated"], IL_CAL_UNIX)); - - if ($code["used"]) { - $result[$k]["used"] = ilDatePresentation::formatDate(new ilDateTime($code["used"], IL_CAL_UNIX)); - } else { - $result[$k]["used"] = ""; - } - - if ($code["role"]) { - $result[$k]["role"] = $this->role_map[$code["role"]] ?? $this->lng->txt('deleted'); - } else { - $result[$k]["role"] = ""; - } - - if ($code["role_local"]) { - $local = []; - foreach (explode(";", $code["role_local"]) as $role_id) { - $role = ilObject::_lookupTitle((int) $role_id); - if ($role) { - $local[] = $role; - } - } - if (count($local)) { - sort($local); - $result[$k]["role_local"] = implode("
", $local); - } - } else { - $result[$k]["role_local"] = ""; - } - - if ($code["alimit"]) { - switch ($code["alimit"]) { - case "unlimited": - $result[$k]["alimit"] = $this->lng->txt("reg_access_limitation_none"); - break; - - case "absolute": - $result[$k]["alimit"] = $this->lng->txt("reg_access_limitation_mode_absolute_target") . - ": " . ilDatePresentation::formatDate(new ilDate($code["alimitdt"], IL_CAL_DATE)); - break; - - case "relative": - $limit_caption = []; - $limit = unserialize($code["alimitdt"], ['allowed_classes' => false]); - if ((int) $limit["d"]) { - $limit_caption[] = (int) $limit["d"] . " " . $this->lng->txt("days"); - } - if ((int) $limit["m"]) { - $limit_caption[] = (int) $limit["m"] . " " . $this->lng->txt("months"); - } - if ((int) $limit["y"]) { - $limit_caption[] = (int) $limit["y"] . " " . $this->lng->txt("years"); - } - if (count($limit_caption)) { - $result[$k]["alimit"] = $this->lng->txt("reg_access_limitation_mode_relative_target") . - ": " . implode(", ", $limit_caption); - } - break; - } - } - } - - $this->setMaxCount((int) $codes_data["cnt"]); - $this->setData($result); - } - - /** - * Init filter - */ - public function initFilter(): void - { - // code - $ti = new ilTextInputGUI($this->lng->txt("registration_code"), "query"); - $ti->setMaxLength(ilRegistrationCode::CODE_LENGTH); - $ti->setSize(20); - $ti->setSubmitFormOnEnter(true); - $this->addFilterItem($ti); - $ti->readFromSession(); - $this->filter["code"] = $ti->getValue(); - - // role - - $this->role_map = []; - foreach ($this->rbacreview->getGlobalRoles() as $role_id) { - if (!in_array($role_id, [SYSTEM_ROLE_ID, ANONYMOUS_ROLE_ID], true)) { - $this->role_map[$role_id] = ilObject::_lookupTitle($role_id); - } - } - - $options = ["" => $this->lng->txt("registration_roles_all")] + - $this->role_map; - $si = new ilSelectInputGUI($this->lng->txt("role"), "role"); - $si->setOptions($options); - $this->addFilterItem($si); - $si->readFromSession(); - $this->filter["role"] = $si->getValue(); - - // access limitation - $options = [ - "" => $this->lng->txt("registration_codes_access_limitation_all"), - "unlimited" => $this->lng->txt("reg_access_limitation_none"), - "absolute" => $this->lng->txt("reg_access_limitation_mode_absolute"), - "relative" => $this->lng->txt("reg_access_limitation_mode_relative") - ]; - $si = new ilSelectInputGUI($this->lng->txt("reg_access_limitations"), "alimit"); - $si->setOptions($options); - $this->addFilterItem($si); - $si->readFromSession(); - $this->filter["alimit"] = $si->getValue(); - - // generated - $options = ["" => $this->lng->txt("registration_generated_all")]; - foreach (ilRegistrationCode::getGenerationDates() as $date) { - $options[$date] = ilDatePresentation::formatDate(new ilDateTime($date, IL_CAL_UNIX)); - } - $si = new ilSelectInputGUI($this->lng->txt("registration_generated"), "generated"); - $si->setOptions($options); - $this->addFilterItem($si); - $si->readFromSession(); - $this->filter["generated"] = $si->getValue(); - } - - public function getSelectedColumns(): array - { - return [ - "code" => "registration_code", - "role" => "registration_codes_roles", - "role_local" => "registration_codes_roles_local", - "alimit" => "reg_access_limitations", - "generated" => "registration_generated", - "used" => "registration_used" - ]; - } - - protected function fillRow(array $a_set): void - { - $this->tpl->setVariable("ID", $a_set["code_id"]); - foreach (array_keys($this->getSelectedColumns()) as $c) { - $this->tpl->setVariable("VAL_" . strtoupper($c), $a_set[$c] ?? ""); - } - } -} diff --git a/components/ILIAS/Registration/classes/class.ilRegistrationSettingsGUI.php b/components/ILIAS/Registration/classes/class.ilRegistrationSettingsGUI.php index 31a67b9a30ff..78bfa2b06c0b 100755 --- a/components/ILIAS/Registration/classes/class.ilRegistrationSettingsGUI.php +++ b/components/ILIAS/Registration/classes/class.ilRegistrationSettingsGUI.php @@ -18,6 +18,13 @@ declare(strict_types=1); +use ILIAS\UI\Factory; +use ILIAS\UI\Renderer as UIRenderer; +use ILIAS\Data\Factory as DataFactory; +use ILIAS\Registration\RegistrationCodesTable; +use ILIAS\Registration\RegistrationCodeRepository; +use ILIAS\Registration\RegistrationFilterComponent; + /** * Class ilRegistrationSettingsGUI * @author Stefan Meyer @@ -44,9 +51,15 @@ class ilRegistrationSettingsGUI protected ilErrorHandling $error; protected ilTabsGUI $tabs; protected ilToolbarGUI $toolbar; - protected \ILIAS\HTTP\Services $http; - protected \ILIAS\Refinery\Factory $refinery; - + protected readonly ILIAS\HTTP\Services $http; + protected readonly ILIAS\Refinery\Factory $refinery; + protected readonly Factory $ui_factory; + protected readonly UIRenderer $ui_renderer; + protected readonly ilUIService $ui_service; + protected readonly ilObjUser $user; + protected readonly RegistrationCodeRepository $code_repository; + protected readonly RegistrationFilterComponent $registration_code_filter; + protected readonly RegistrationCodesTable $registration_codes_table; protected ilRegistrationSettings $registration_settings; protected ?ilRegistrationRoleAssignments $assignments_obj = null; protected ?ilRegistrationRoleAccessLimitations $access_limitations_obj = null; @@ -75,6 +88,11 @@ public function __construct() $this->http = $DIC->http(); $this->refinery = $DIC->refinery(); $this->ref_id = $this->initRefIdFromQuery(); + $this->ui_factory = $DIC->ui()->factory(); + $this->ui_renderer = $DIC->ui()->renderer(); + $this->ui_service = $DIC->uiService(); + $this->user = $DIC->user(); + $this->code_repository = new RegistrationCodeRepository($DIC->database()); } protected function initRefIdFromQuery(): int @@ -92,6 +110,14 @@ public function executeCommand(): void { $next_class = $this->ctrl->getNextClass($this); $cmd = $this->ctrl->getCmd(); + + if ($this->http->wrapper()->query()->has('registration_codes_table_action')) { + $cmd = $this->http->wrapper()->query()->retrieve( + 'registration_codes_table_action', + $this->refinery->kindlyTo()->string() + ); + } + switch ($next_class) { default: if (!$cmd) { @@ -780,16 +806,53 @@ private function initRoleAccessLimitations(): void public function listCodes(): void { - $this->checkAccess("visible,read"); + $this->checkAccess('visible,read'); $this->setSubTabs('registration_codes'); - if ($this->checkAccessBool("write")) { + if ($this->checkAccessBool('write')) { $this->toolbar->addButton( - $this->lng->txt("registration_codes_add"), - $this->ctrl->getLinkTarget($this, "addCodes") + $this->lng->txt('registration_codes_add'), + $this->ctrl->getLinkTarget($this, 'addCodes') ); } - $ctab = new ilRegistrationCodesTableGUI($this, "listCodes"); - $this->tpl->setContent($ctab->getHTML()); + $this->tpl->setContent($this->ui_renderer->render([ + $this->getRegistrationFilter()->getFilterComponent(), + $this->getRegistrationCodeTable()->getTableComponent($this->getRegistrationFilter()) + ])); + } + + public function getRegistrationCodeTable(): RegistrationCodesTable + { + if (!isset($this->registration_codes_table)) { + $this->registration_codes_table = new RegistrationCodesTable( + $this->http->request(), + $this->lng, + $this->ui_factory, + new DataFactory(), + $this->rbacreview, + $this->ctrl->getLinkTarget($this, 'listCodes', '', true), + $this->user, + $this->code_repository, + $this->checkAccessBool('write'), + ); + } + + return $this->registration_codes_table; + } + + public function getRegistrationFilter(): RegistrationFilterComponent + { + if (!isset($this->registration_code_filter)) { + $this->registration_code_filter = new RegistrationFilterComponent( + $this->ctrl->getLinkTarget($this, 'listCodes', '', true), + $this->ui_factory, + $this->ui_service, + $this->lng, + $this->rbacreview, + $this->code_repository + ); + } + + return $this->registration_code_filter; } public function initAddCodesForm(): ilPropertyFormGUI @@ -999,81 +1062,78 @@ public function deleteCodes(): void ) ); } - ilRegistrationCode::deleteCodes($ids); + $this->code_repository->deleteCodes($ids); $this->tpl->setOnScreenMessage('success', $this->lng->txt('info_deleted'), true); $this->ctrl->redirect($this, "listCodes"); } public function deleteConfirmation(): void { - $this->checkAccess("write"); - - $ids = []; - if ($this->http->wrapper()->post()->has('id')) { - $ids = $this->http->wrapper()->post()->retrieve( - 'id', + $this->checkAccess('write'); + $codes = []; + if ($this->http->wrapper()->query()->has('registration_codes_code_ids')) { + $ids = $this->http->wrapper()->query()->retrieve( + 'registration_codes_code_ids', $this->refinery->kindlyTo()->listOf( - $this->refinery->kindlyTo()->int() + $this->refinery->kindlyTo()->string() ) ); + if ($ids === ['ALL_OBJECTS']) { + $codes = $this->code_repository->getCodesByFilter($this->getRegistrationFilter()->getFilterData()); + } else { + $ids = $this->refinery->kindlyTo()->listOf($this->refinery->kindlyTo()->int())->transform($ids); + $codes = $this->code_repository->loadCodesByIds($ids); + } } - if (!count($ids)) { + if (!count($codes)) { $this->tpl->setOnScreenMessage('failure', $this->lng->txt('err_select_one'), true); $this->ctrl->redirect($this, 'listCodes'); } $this->setSubTabs('registration_codes'); $gui = new ilConfirmationGUI(); - $gui->setHeaderText($this->lng->txt("info_delete_sure")); - $gui->setCancel($this->lng->txt("cancel"), "listCodes"); - $gui->setConfirm($this->lng->txt("confirm"), "deleteCodes"); - $gui->setFormAction($this->ctrl->getFormAction($this, "deleteCodes")); - - $data = ilRegistrationCode::loadCodesByIds($ids); - foreach ($data as $code) { - $gui->addItem("id[]", (string) $code["code_id"], $code["code"]); + $gui->setHeaderText($this->lng->txt('info_delete_sure')); + $gui->setCancel($this->lng->txt('cancel'), 'listCodes'); + $gui->setConfirm($this->lng->txt('confirm'), 'deleteCodes'); + $gui->setFormAction($this->ctrl->getFormAction($this, 'deleteCodes')); + + foreach ($codes as $code) { + $gui->addItem('id[]', (string) $code['code_id'], $code['code']); } $this->tpl->setContent($gui->getHTML()); } - public function resetCodesFilter(): void - { - $utab = new ilRegistrationCodesTableGUI($this, "listCodes"); - $utab->resetOffset(); - $utab->resetFilter(); - - $this->listCodes(); - } - - public function applyCodesFilter(): void - { - $utab = new ilRegistrationCodesTableGUI($this, "listCodes"); - $utab->resetOffset(); - $utab->writeFilterToSession(); - - $this->listCodes(); - } - public function exportCodes(): void { - $this->checkAccess('read'); - $utab = new ilRegistrationCodesTableGUI($this, "listCodes"); - - $codes = ilRegistrationCode::getCodesForExport( - $utab->filter["code"], - $utab->filter["role"] ? (int) $utab->filter["role"] : null, - $utab->filter["generated"], - $utab->filter["alimit"] - ); + $codes = []; + if ($this->http->wrapper()->query()->has('registration_codes_code_ids')) { + $ids = $this->http->wrapper()->query()->retrieve( + 'registration_codes_code_ids', + $this->refinery->kindlyTo()->listOf( + $this->refinery->kindlyTo()->string() + ) + ); + if ($ids === ['ALL_OBJECTS']) { + $codes = $this->code_repository->getCodesByFilter($this->getRegistrationFilter()->getFilterData()); + } else { + $ids = $this->refinery->kindlyTo()->listOf($this->refinery->kindlyTo()->int())->transform($ids); + $codes = $this->code_repository->loadCodesByIds($ids); + } + + $codes = array_filter(array_map( + static fn($code) => (string) ($code['code'] ?? ''), + $codes + )); + } if (count($codes)) { ilUtil::deliverData( implode("\r\n", $codes), - "ilias_registration_codes_" . date("d-m-Y") . ".txt", - "text/plain" + 'ilias_registration_codes_' . date('d-m-Y') . '.txt', + 'text/plain' ); } else { - $this->tpl->setOnScreenMessage('failure', $this->lng->txt("registration_export_codes_no_data")); + $this->tpl->setOnScreenMessage('failure', $this->lng->txt('registration_export_codes_no_data')); $this->listCodes(); } } diff --git a/components/ILIAS/Registration/resources/confirmReg.php b/components/ILIAS/Registration/resources/confirmReg.php index fc3fc56b41a4..ab3e650f3654 100755 --- a/components/ILIAS/Registration/resources/confirmReg.php +++ b/components/ILIAS/Registration/resources/confirmReg.php @@ -30,4 +30,3 @@ ilStartUpGUI::setForcedCommand('confirmRegistration'); $DIC->ctrl()->callBaseClass(ilStartUpGUI::class); $DIC->http()->close(); -exit(); diff --git a/components/ILIAS/Registration/src/CodeFilter.php b/components/ILIAS/Registration/src/CodeFilter.php new file mode 100644 index 000000000000..cf1ff965b778 --- /dev/null +++ b/components/ILIAS/Registration/src/CodeFilter.php @@ -0,0 +1,80 @@ +code = (string) ($filter['code'] ?? ''); + $clone->role = (int) ($filter['role'] ?? 0); + $clone->generated = (string) ($filter['generated'] ?? ''); + $clone->access_limitation = (string) ($filter['access_limitation'] ?? ''); + + return $clone; + } + + public function getCode(): string + { + return $this->code; + } + + public function getRole(): int + { + return $this->role; + } + + public function getGenerated(): string + { + return $this->generated; + } + + public function getAccessLimitation(): string + { + return $this->access_limitation; + } + + /** + * @return array{code: string, role: int, generated: string, access_limitation: string} + */ + public function getData(): array + { + return [ + 'code' => $this->getCode(), + 'role' => $this->getRole(), + 'generated' => $this->getGenerated(), + 'access_limitation' => $this->getAccessLimitation(), + ]; + } +} diff --git a/components/ILIAS/Registration/src/RegistrationCodeRepository.php b/components/ILIAS/Registration/src/RegistrationCodeRepository.php new file mode 100644 index 000000000000..efc0d433dd7c --- /dev/null +++ b/components/ILIAS/Registration/src/RegistrationCodeRepository.php @@ -0,0 +1,200 @@ +getCode()) { + $where[] = $this->db->like('code', ilDBConstants::T_TEXT, '%' . $code_filter->getCode() . '%'); + } + if ($code_filter->getRole()) { + $where[] = 'role = ' . $this->db->quote($code_filter->getRole(), ilDBConstants::T_INTEGER); + } + if ($code_filter->getGenerated()) { + $where[] = 'generated_on = ' . $this->db->quote($code_filter->getGenerated(), ilDBConstants::T_TEXT); + } + if ($code_filter->getAccessLimitation()) { + $where[] = 'alimit = ' . $this->db->quote($code_filter->getAccessLimitation(), ilDBConstants::T_TEXT); + } + if ($where !== []) { + return ' WHERE ' . implode(' AND ', $where); + } + + return ''; + } + + public function getTotalCodeCount( + ?CodeFilter $code_filter = null + ): int { + $set = $this->db->query('SELECT COUNT(*) AS cnt FROM ' . self::TABLE_NAME . ($code_filter ? $this->filterToSQL($code_filter) : '')); + $cnt = 0; + if ($rec = $this->db->fetchAssoc($set)) { + $cnt = (int) ($rec['cnt'] ?? 0); + } + + return $cnt; + } + + /** + * @return listfilterToSQL($code_filter) : ''); + if ($order_field) { + if ($order_field === 'generated') { + $order_field = 'generated_on'; + } + $sql .= ' ORDER BY ' . $order_field . ' ' . $order_direction; + } + + $this->db->setLimit($limit, $offset); + $set = $this->db->query($sql); + $result = []; + while ($rec = $this->db->fetchAssoc($set)) { + $rec['generated'] = (int) $rec['generated_on']; + unset($rec['generated_on']); + $result[] = $rec; + } + + return $result; + } + + /** + * @param list $ids + * @return listdb->query('SELECT * FROM ' . self::TABLE_NAME . ' WHERE ' . $this->db->in( + 'code_id', + $ids, + false, + ilDBConstants::T_INTEGER + )); + $result = []; + while ($rec = $this->db->fetchAssoc($set)) { + $result[] = $rec; + } + + return $result; + } + + /** + * @param list $ids + */ + public function deleteCodes(array $ids): bool + { + if (\count($ids)) { + return (bool) $this->db->manipulate('DELETE FROM ' . self::TABLE_NAME . ' WHERE ' . $this->db->in( + 'code_id', + $ids, + false, + ilDBConstants::T_INTEGER + )); + } + + return false; + } + + /** + * @return list + */ + public function getGenerationDates(): array + { + $set = $this->db->query('SELECT DISTINCT(generated_on) genr FROM ' . self::TABLE_NAME . ' ORDER BY genr'); + $result = []; + while ($rec = $this->db->fetchAssoc($set)) { + $result[] = (int) $rec['genr']; + } + + return $result; + } + + /** + * @return listdb->query( + 'SELECT * FROM ' . self::TABLE_NAME . + ($code_filter ? $this->filterToSQL($code_filter) : '') + ); + $result = []; + while ($rec = $this->db->fetchAssoc($set)) { + $result[] = $rec; + } + + return $result; + } +} diff --git a/components/ILIAS/Registration/src/RegistrationCodesTable.php b/components/ILIAS/Registration/src/RegistrationCodesTable.php new file mode 100644 index 000000000000..3596ee34fc13 --- /dev/null +++ b/components/ILIAS/Registration/src/RegistrationCodesTable.php @@ -0,0 +1,298 @@ +getRecords($range, $order, $filter_data); + foreach ($records as $record) { + yield $row_builder->buildDataRow((string) $record['code_id'], $record); + } + } + + public function getTableComponent(RegistrationFilterComponent $filter): DataTable + { + $query_params_namespace = ['registration', 'codes']; + $table_uri = $this->data_factory->uri(ILIAS_HTTP_PATH . '/' . $this->action); + $url_builder = new URLBuilder($table_uri); + /** @var URLBuilder $url_builder */ + [$url_builder, $action_parameter_token, $row_id_token] = $url_builder->acquireParameters( + $query_params_namespace, + 'table_action', + 'code_ids', + ); + + return $this->ui_factory->table() + ->data( + $this, + '', + $this->getColumns() + ) + ->withId( + 'registration_code' + ) + ->withFilter($filter->getFilterData()->getData()) + ->withOrder(new Order('generated', Order::DESC)) + ->withRequest($this->http_request) + ->withActions($this->getActions($url_builder, $action_parameter_token, $row_id_token)); + } + + /** + * @param array{code: string, role: int, generated: string, access_limitation: string} $filter_data + */ + public function getTotalRowCount(?array $filter_data, ?array $additional_parameters): ?int + { + return $this->code_repository->getTotalCodeCount( + (new CodeFilter())->withData($filter_data) + ); + } + + /** + * @param array{code: string, role: int, generated: string, access_limitation: string} $filter_data + * @return list + */ + private function getRecords(Range $range, Order $order, ?array $filter_data): array + { + [$order_field, $order_direction] = $order->join( + [], + fn(array $ret, string $key, string $value): array => [$key, $value] + ); + $filter = (new CodeFilter())->withData($filter_data); + + $codes_data = $this->code_repository->getCodesData( + $order_field, + $order_direction, + $range->getStart(), + $range->getLength(), + $filter + ); + + if (\count($codes_data) === 0 && $range->getStart() > 0) { + $codes_data = $this->code_repository->getCodesData( + $order_field, + $order_direction, + 0, + $range->getLength(), + $filter + ); + } + + if ((int) $this->actor->getTimeFormat() === ilCalendarSettings::TIME_FORMAT_12) { + $date_format = $this->data_factory->dateFormat()->withTime12($this->actor->getDateFormat()); + } else { + $date_format = $this->data_factory->dateFormat()->withTime24($this->actor->getDateFormat()); + } + + $role_map = []; + foreach ($this->rbac_review->getGlobalRoles() as $role_id) { + if (!\in_array($role_id, [SYSTEM_ROLE_ID, ANONYMOUS_ROLE_ID], true)) { + $role_map[$role_id] = ilObject::_lookupTitle($role_id); + } + } + + $result = []; + foreach ($codes_data as $k => $code) { + $result[$k]['code'] = $code['code']; + $result[$k]['code_id'] = (int) $code['code_id']; + + $result[$k]['generated'] = (new DateTimeImmutable('@' . $code['generated']))->setTimezone( + new DateTimeZone($this->actor->getTimeZone()) + ); + if ($code['used']) { + $result[$k]['used'] = $date_format->applyTo( + (new DateTimeImmutable('@' . $code['used']))->setTimezone( + new DateTimeZone($this->actor->getTimeZone()) + ) + ); + } else { + $result[$k]['used'] = null; + } + + if ($code['role']) { + $result[$k]['role'] = $role_map[$code['role']] ?? $this->lng->txt('deleted'); + } else { + $result[$k]['role'] = ''; + } + + if (\is_string($code['role_local'])) { + $local = []; + foreach (explode(';', $code['role_local']) as $role_id) { + $role = ilObject::_lookupTitle((int) $role_id); + if ($role) { + $local[] = $role; + } + } + if (\count($local)) { + sort($local); + $result[$k]['role_local'] = implode('
', $local); + } + } else { + $result[$k]['role_local'] = ''; + } + + if ($code['alimit']) { + switch ($code['alimit']) { + case 'unlimited': + $result[$k]['alimit'] = $this->lng->txt('reg_access_limitation_none'); + break; + + case 'absolute': + $result[$k]['alimit'] = $this->lng->txt('reg_access_limitation_mode_absolute_target') . + ': ' . + $date_format->applyTo( + (new DateTimeImmutable('@' . $code['alimitdt']))->setTimezone( + new DateTimeZone($this->actor->getTimeZone()) + ) + ); + break; + + case 'relative': + $limit_caption = []; + $limit = unserialize($code['alimitdt'], ['allowed_classes' => false]); + if ((int) $limit['d']) { + $limit_caption[] = (int) $limit['d'] . ' ' . $this->lng->txt('days'); + } + if ((int) $limit['m']) { + $limit_caption[] = (int) $limit['m'] . ' ' . $this->lng->txt('months'); + } + if ((int) $limit['y']) { + $limit_caption[] = (int) $limit['y'] . ' ' . $this->lng->txt('years'); + } + if (\count($limit_caption)) { + $result[$k]['alimit'] = $this->lng->txt('reg_access_limitation_mode_relative_target') . + ': ' . implode(', ', $limit_caption); + } + break; + } + } + } + + return $result; + } + + /** + * @return array + */ + public function getActions( + URLBuilder $url_builder, + URLBuilderToken $action_parameter_token, + URLBuilderToken $row_id_token + ): array { + $actions = [ + $this->ui_factory->table()->action()->multi( + $this->lng->txt('registration_codes_export'), + $url_builder->withParameter($action_parameter_token, 'exportCodes'), + $row_id_token + ), + ]; + if ($this->has_permission_to_delete) { + $actions[] = $this->ui_factory->table()->action()->multi( + $this->lng->txt('delete'), + $url_builder->withParameter($action_parameter_token, 'deleteConfirmation'), + $row_id_token + ); + } + + return $actions; + } + + /** + * @return array + */ + private function getColumns(): array + { + if ((int) $this->actor->getTimeFormat() === ilCalendarSettings::TIME_FORMAT_12) { + $date_format = $this->data_factory->dateFormat()->withTime12($this->actor->getDateFormat()); + } else { + $date_format = $this->data_factory->dateFormat()->withTime24($this->actor->getDateFormat()); + } + + return [ + 'code' => $this->ui_factory->table()->column() + ->text($this->lng->txt('registration_code')), + 'role' => $this->ui_factory->table()->column() + ->text($this->lng->txt('registration_codes_roles')), + 'role_local' => $this->ui_factory->table()->column() + ->text($this->lng->txt('registration_codes_roles_local')) + ->withIsSortable(false), + 'alimit' => $this->ui_factory->table()->column() + ->text($this->lng->txt('reg_access_limitations')) + ->withIsSortable(false), + 'generated' => $this->ui_factory->table()->column() + ->date($this->lng->txt('registration_generated'), $date_format), + 'used' => $this->ui_factory->table()->column() + ->text($this->lng->txt('registration_used')), + ]; + } +} diff --git a/components/ILIAS/Registration/src/RegistrationFilterComponent.php b/components/ILIAS/Registration/src/RegistrationFilterComponent.php new file mode 100644 index 000000000000..aa3cdf18b90d --- /dev/null +++ b/components/ILIAS/Registration/src/RegistrationFilterComponent.php @@ -0,0 +1,114 @@ +ui_factory->input()->field(); + + foreach ($this->getFilterFields($field_factory) as $filter_id => $filter) { + [$filter_inputs[$filter_id], $is_input_initially_rendered[$filter_id]] = $filter; + } + + $this->filter = $this->ui_service->filter()->standard( + 'participant_filter', + $this->action, + $filter_inputs, + $is_input_initially_rendered, + true, + true + ); + } + + public function getFilterComponent(): FilterComponent + { + return $this->filter; + } + + public function getFilterData(): CodeFilter + { + return (new CodeFilter())->withData($this->ui_service->filter()->getData($this->filter)); + } + + /** + * @return array + */ + public function getFilterFields(Factory $field_factory): array + { + $filters = [ + 'code' => [ + $field_factory->text($this->lng->txt('registration_code'))->withMaxLength( + ilRegistrationCode::CODE_LENGTH + ), + true + ], + ]; + $role_map = []; + foreach ($this->rbac_review->getGlobalRoles() as $role_id) { + if (!\in_array($role_id, [SYSTEM_ROLE_ID, ANONYMOUS_ROLE_ID], true)) { + $role_map[$role_id] = ilObject::_lookupTitle($role_id); + } + } + + $options = ['' => $this->lng->txt('registration_roles_all')] + $role_map; + $filters['role'] = [$field_factory->select($this->lng->txt('role'), $options), true]; + + $options = [ + '' => $this->lng->txt('registration_codes_access_limitation_all'), + 'unlimited' => $this->lng->txt('reg_access_limitation_none'), + 'absolute' => $this->lng->txt('reg_access_limitation_mode_absolute'), + 'relative' => $this->lng->txt('reg_access_limitation_mode_relative') + ]; + $filters['alimit'] = [$field_factory->select($this->lng->txt('reg_access_limitations'), $options), true]; + + $options = ['' => $this->lng->txt('registration_generated_all')]; + foreach ($this->code_repository->getGenerationDates() as $date) { + $options[$date] = ilDatePresentation::formatDate(new ilDateTime($date, IL_CAL_UNIX)); + } + $filters['generated'] = [$field_factory->select($this->lng->txt('registration_generated'), $options), true]; + + return $filters; + } +} diff --git a/components/ILIAS/Registration/templates/default/tpl.code_list_row.html b/components/ILIAS/Registration/templates/default/tpl.code_list_row.html deleted file mode 100755 index 44078db7c5a1..000000000000 --- a/components/ILIAS/Registration/templates/default/tpl.code_list_row.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - {VAL_CODE} - - - {VAL_ROLE} - - - {VAL_ROLE_LOCAL} - - - {VAL_ALIMIT} - - - {VAL_GENERATED} - - - {VAL_USED} - - \ No newline at end of file diff --git a/components/ILIAS/RemoteCourse/classes/class.ilObjRemoteCourseGUI.php b/components/ILIAS/RemoteCourse/classes/class.ilObjRemoteCourseGUI.php index c65546ce958e..e4d1d7d596f6 100755 --- a/components/ILIAS/RemoteCourse/classes/class.ilObjRemoteCourseGUI.php +++ b/components/ILIAS/RemoteCourse/classes/class.ilObjRemoteCourseGUI.php @@ -70,7 +70,7 @@ protected function availabilityToString(): string protected function addCustomEditForm(ilPropertyFormGUI $a_form): void { $radio_grp = new ilRadioGroupInputGUI($this->lng->txt('crs_visibility'), 'activation_type'); - $radio_grp->setValue($this->object->getAvailabilityType()); + $radio_grp->setValue((string) $this->object->getAvailabilityType()); $radio_grp->setDisabled(true); $radio_opt = new ilRadioOption( diff --git a/components/ILIAS/Repository/Administration/class.ilModulesTableGUI.php b/components/ILIAS/Repository/Administration/class.ilModulesTableGUI.php index 990b940e13a2..ff9855007cc9 100755 --- a/components/ILIAS/Repository/Administration/class.ilModulesTableGUI.php +++ b/components/ILIAS/Repository/Administration/class.ilModulesTableGUI.php @@ -93,9 +93,10 @@ public function getComponents(): void $obj_types = []; // parse modules + /* this does not work anymore since 10 foreach ($this->component_repository->getComponents() as $mod) { if ($mod->getType() !== ilComponentInfo::TYPE_MODULES) { - continue; + //continue; } $has_repo = false; $rep_types = $objDefinition::getRepositoryObjectTypesForComponent( @@ -123,8 +124,28 @@ public function getComponents(): void ]; } } + }*/ + + foreach ($this->obj_definition->getAllRepositoryTypes(false) as $id) { + if ($this->obj_definition->isAllowedInRepository($id)) { + if ($this->obj_definition->isSystemObject($id)) { + continue; + } + if (in_array($id, ["lng", "rolt", "sty", "tax", "usr"])) { + continue; + } + $obj_types[$id] = [ + "object" => $this->obj_definition->getClassName($id), + "caption" => $lng->txt("obj_" . $id), + "subdir" => "", + "grp" => $this->obj_definition->getGroupOfObj($id), + "default_pos" => $this->obj_definition->getPositionByType($id), + ]; + } } + + // parse plugins $obj_types = $this->getPluginComponents($obj_types, ilComponentInfo::TYPE_SERVICES, "Repository", "robj"); $obj_types = $this->getPluginComponents($obj_types, ilComponentInfo::TYPE_MODULES, "OrgUnit", "orguext"); @@ -173,7 +194,7 @@ protected function fillRow(array $a_set): void { if ((int) $a_set["pos_group"] !== $this->old_grp_id) { $this->tpl->setCurrentBlock("pos_grp_bl"); - $this->tpl->setVariable("TXT_POS_GRP", $this->pos_group_options[$a_set["pos_group"]]); + $this->tpl->setVariable("TXT_POS_GRP", $this->pos_group_options[$a_set["pos_group"]] ?? ""); $this->tpl->parseCurrentBlock(); $this->tpl->setCurrentBlock("tbl_content"); diff --git a/components/ILIAS/Repository/Administration/class.ilObjRepositorySettings.php b/components/ILIAS/Repository/Administration/class.ilObjRepositorySettings.php index d378594a4980..a6bf5c9d0b3e 100755 --- a/components/ILIAS/Repository/Administration/class.ilObjRepositorySettings.php +++ b/components/ILIAS/Repository/Administration/class.ilObjRepositorySettings.php @@ -155,17 +155,18 @@ protected static function getAllObjTypes(): array global $DIC; $component_repository = $DIC["component.repository"]; + /** @var ilObjectDefinition $objDefinition */ $objDefinition = $DIC["objDefinition"]; $res = []; // parse modules foreach ($component_repository->getComponents() as $mod) { - if ($mod->getType() !== ilComponentInfo::TYPE_MODULES) { + if (!in_array($mod->getType(), ilComponentInfo::TYPES, true)) { continue; } $has_repo = false; - $rep_types = $objDefinition->getRepositoryObjectTypesForComponent(ilComponentInfo::TYPE_MODULES, $mod->getName()); + $rep_types = $objDefinition->getRepositoryObjectTypesForComponent(ilComponentInfo::TYPE_COMPONENT, $mod->getName()); if (count($rep_types) > 0) { foreach ($rep_types as $ridx => $rt) { // we only want to display repository modules diff --git a/components/ILIAS/Repository/Administration/class.ilObjRepositorySettingsGUI.php b/components/ILIAS/Repository/Administration/class.ilObjRepositorySettingsGUI.php index 858dd8997228..0784728a7022 100755 --- a/components/ILIAS/Repository/Administration/class.ilObjRepositorySettingsGUI.php +++ b/components/ILIAS/Repository/Administration/class.ilObjRepositorySettingsGUI.php @@ -38,7 +38,6 @@ class ilObjRepositorySettingsGUI extends ilObjectGUI protected AdministrationGUIRequest $admin_gui_request; protected ilErrorHandling $error; protected ilSetting $folder_settings; - protected GlobalHttpState $http; protected UIFactory $factory; protected UIRenderer $renderer; protected RefFactory $refinery; @@ -56,7 +55,6 @@ public function __construct( $this->rbacsystem = $DIC->rbac()->system(); $this->settings = $DIC->settings(); $this->folder_settings = new ilSetting('fold'); - $this->http = $DIC->http(); $this->ctrl = $DIC->ctrl(); $this->lng = $DIC->language(); $this->toolbar = $DIC->toolbar(); @@ -313,7 +311,7 @@ protected function initSettingsForm(): StandardForm $fav = $f->checkbox( $this->lng->txt("rep_favourites"), $this->lng->txt("rep_favourites_info") - )->withValue((bool) $ilSetting->get("rep_favourites")); + )->withValue($ilSetting->get("rep_favourites", "0") === "1"); //TODO split this up into two sections $settings = $f->section( @@ -415,7 +413,7 @@ public function saveSettings(): void $ilSetting->set( "rep_favourites", - (string) $data["rep_favourites"] + $data["rep_favourites"] ? "1" : "0" ); if ($data["rep_export_limitation"][0] === 'rep_export_unlimited') { diff --git a/components/ILIAS/Repository/GlobalScreen/classes/RepositoryOpenGraphExposer.php b/components/ILIAS/Repository/GlobalScreen/classes/RepositoryOpenGraphExposer.php index 346605c91613..79f8730bbca9 100755 --- a/components/ILIAS/Repository/GlobalScreen/classes/RepositoryOpenGraphExposer.php +++ b/components/ILIAS/Repository/GlobalScreen/classes/RepositoryOpenGraphExposer.php @@ -29,6 +29,7 @@ use ILIAS\Data\Meta\Html\OpenGraph\Image as OGImage; use ILIAS\MetaData\Services\ServicesInterface as LOMServices; use ILIAS\MetaData\Services\Reader\ReaderInterface as LOMReader; +use ILIAS\ILIASObject\Properties\Translations\Translations; /** * @author Thibeau Fuhrer @@ -76,7 +77,7 @@ public function getContentModification(CalledContexts $screen_context_stack): ?C private function exposeObjectOpenGraphMetaData(\ilObject $object): void { - $object_translation = \ilObjectTranslation::getInstance($object->getId()); + $object_translation = $object->getObjectProperties()->getPropertyTranslations(); $additional_locale_count = 0; $additional_locales = []; diff --git a/components/ILIAS/Repository/LastVisited/class.ilNavigationHistory.php b/components/ILIAS/Repository/LastVisited/class.ilNavigationHistory.php index 2e5302ff5c72..5603140d2c0d 100755 --- a/components/ILIAS/Repository/LastVisited/class.ilNavigationHistory.php +++ b/components/ILIAS/Repository/LastVisited/class.ilNavigationHistory.php @@ -167,8 +167,12 @@ public function getItems(): array if ($rec["sub_obj_id"] != "") { $title = $rec["title"]; } elseif ($rec["type"] === "sess") { - $sess = new ilObjSession($rec["ref_id"]); - $title = $sess->getPresentationTitle(); + try { + $sess = new ilObjSession((int) $rec["ref_id"]); + $title = $sess->getPresentationTitle(); + } catch (ilObjectTypeMismatchException) { + $title = ilObject::_lookupTitle(ilObject::_lookupObjId((int) $rec["ref_id"])); + } } else { $title = ilObject::_lookupTitle(ilObject::_lookupObjId((int) $rec["ref_id"])); } diff --git a/components/ILIAS/Repository/Ownership/OwnershipManagementRetrieval.php b/components/ILIAS/Repository/Ownership/OwnershipManagementRetrieval.php new file mode 100644 index 000000000000..2f657185317c --- /dev/null +++ b/components/ILIAS/Repository/Ownership/OwnershipManagementRetrieval.php @@ -0,0 +1,137 @@ +setData($this->objects, $this->selected_type); + } + + public function setData(array $objects, string $selected_type): void + { + $access = $this->domain->access(); + $tree = $this->domain->repositoryTree(); + $is_admin = false; + $a_type = ''; + + if (!$this->user_id) { + $is_admin = $access->checkAccess('visible', '', SYSTEM_FOLDER_ID); + } + + $this->data = []; + if (empty($objects[$selected_type])) { + return; + } + + foreach ($objects[$selected_type] as $id => $item) { + $refs = \ilObject::_getAllReferences($id); + if ($refs) { + foreach ($refs as $ref_id) { + if (!$tree->isDeleted($ref_id)) { + if ($this->user_id) { + $readable = $access->checkAccessOfUser( + $this->user_id, + 'read', + '', + $ref_id, + $a_type + ); + } else { + $readable = $is_admin; + } + + $this->data[] = [ + 'obj_id' => $id, + 'ref_id' => $ref_id, + 'id' => $ref_id, + 'type' => \ilObject::_lookupType($id), + 'title' => $item, + 'path' => $this->buildPath($ref_id), + 'readable' => $readable + ]; + } + } + } + } + } + + public function isFieldNumeric(string $field): bool + { + return in_array($field, ['id', 'obj_id', 'ref_id']); + } + + + protected function buildPath(int $ref_id): string + { + $tree = $this->domain->repositoryTree(); + $path = '...'; + $counter = 0; + $path_full = $tree->getPathFull($ref_id); + foreach ($path_full as $data) { + if (++$counter < (count($path_full) - 2)) { + continue; + } + if ($ref_id != $data['ref_id']) { + $path .= ' » ' . $data['title']; + } + } + + return $path; + } + + public function getData( + array $fields, + ?Range $range = null, + ?Order $order = null, + array $filter = [], + array $parameters = [] + ): \Generator { + $data = $this->data; + + $data = $this->applyOrder($data, $order); + $data = $this->applyRange($data, $range); + + foreach ($data as $row) { + yield $row; + } + } + + public function count(array $filter, array $parameters): int + { + return count($this->data); + } +} diff --git a/components/ILIAS/Repository/Ownership/OwnershipManagementTableBuilder.php b/components/ILIAS/Repository/Ownership/OwnershipManagementTableBuilder.php new file mode 100644 index 000000000000..def08ea47bb0 --- /dev/null +++ b/components/ILIAS/Repository/Ownership/OwnershipManagementTableBuilder.php @@ -0,0 +1,149 @@ +title; + } + + protected function getRetrieval(): RetrievalInterface + { + return new OwnershipManagementRetrieval( + $this->domain, + $this->user_id, + $this->objects, + $this->selected_type + ); + } + + protected function transformRow(array $data_row): array + { + $f = $this->gui->ui()->factory(); + $refinery = $this->domain->refinery(); + + $icon = $f->symbol()->icon()->standard( + $data_row['type'], + $data_row['title'], + Standard::MEDIUM + ); + + return [ + 'id' => $data_row['ref_id'], + 'icon' => $icon, + 'title' => $refinery->encode()->htmlSpecialCharsAsEntities()->transform( + $data_row['title'] + ), + 'path' => $data_row['path'], + 'type' => $data_row['type'], + 'readable' => $data_row['readable'] + ]; + } + + protected function activeAction(string $action, array $data_row): bool + { + if (!$data_row['readable']) { + return false; + } + + if ($action === 'export') { + $obj_definition = $this->domain->objectDefinition(); + $type = $data_row['type']; + if (in_array($type, ['crsr', 'catr', 'grpr']) || !$obj_definition->allowExport($type)) { + return false; + } + } + + if ($action === 'show') { + return true; // Show action should always be available for readable items + } + + if (!method_exists($this->parent_gui, 'isReadOnly')) { + return true; + } + + return !$this->parent_gui->isReadOnly(); + } + + protected function build(TableAdapterGUI $table): TableAdapterGUI + { + $lng = $this->domain->lng(); + + $table = $table + ->iconColumn('icon', $lng->txt('type'), false) + ->textColumn('title', $lng->txt('title'), true) + ->textColumn('path', $lng->txt('path')); + + $table = $table->singleAction( + 'show', + $lng->txt('show') + ); + + $table = $table->singleAction( + 'move', + $lng->txt('move') + ); + + $table = $table->singleAction( + 'changeOwner', + $lng->txt('change_owner') + ); + + $table = $table->singleAction( + 'export', + $lng->txt('export') + ); + + $table = $table->singleAction( + 'delete', + $lng->txt('delete') + ); + + return $table; + } +} diff --git a/components/ILIAS/Repository/Ownership/Service/class.GUIService.php b/components/ILIAS/Repository/Ownership/Service/class.GUIService.php new file mode 100755 index 000000000000..727b609cbcc6 --- /dev/null +++ b/components/ILIAS/Repository/Ownership/Service/class.GUIService.php @@ -0,0 +1,68 @@ +gui_service = $gui_service; + $this->domain_service = $domain_service; + } + + public function ownershipManagementGUI(?int $user_id = null, bool $read_only = false): \ilObjectOwnershipManagementGUI + { + return new \ilObjectOwnershipManagementGUI( + $this->domain_service, + $this->gui_service, + $user_id, + $read_only + ); + } + + public function ownershipManagementTableBuilder( + int $user_id, + string $title, + array $objects, + string $selected_type, + object $parent_gui, + string $parent_cmd + ): OwnershipManagementTableBuilder { + return new OwnershipManagementTableBuilder( + $this->domain_service, + $this->gui_service, + $user_id, + $title, + $objects, + $selected_type, + $parent_gui, + $parent_cmd + ); + } +} diff --git a/components/ILIAS/ILIASObject/classes/class.ilObjectOwnershipManagementGUI.php b/components/ILIAS/Repository/Ownership/class.ilObjectOwnershipManagementGUI.php similarity index 60% rename from components/ILIAS/ILIASObject/classes/class.ilObjectOwnershipManagementGUI.php rename to components/ILIAS/Repository/Ownership/class.ilObjectOwnershipManagementGUI.php index 90c9bf19ecd4..bb51343b3f61 100755 --- a/components/ILIAS/ILIASObject/classes/class.ilObjectOwnershipManagementGUI.php +++ b/components/ILIAS/Repository/Ownership/class.ilObjectOwnershipManagementGUI.php @@ -18,48 +18,35 @@ declare(strict_types=1); -use ILIAS\UI\Factory as UIFactory; +use ILIAS\Repository\InternalDomainService; +use ILIAS\Repository\InternalGUIService; -/** - * Class ilObjectOwnershipManagementGUI - * - * @author Jörg Lützenkirchen - * - * @ilCtrl_Calls ilObjectOwnershipManagementGUI: - */ class ilObjectOwnershipManagementGUI { public const P_OWNID = 'ownid'; - protected ilObjUser $user; protected ilCtrl $ctrl; - protected ilGlobalTemplateInterface $tpl; - protected UIFactory $ui_factory; - protected ilToolbarGUI $toolbar; protected ilLanguage $lng; - protected ilObjectDefinition $obj_definition; - protected ilTree $tree; protected int $user_id; protected int $own_id = 0; protected bool $read_only; private ilObjectRequestRetriever $retriever; - public function __construct(?int $user_id = null, bool $read_only = false) - { - global $DIC; - - $this->user = $DIC['ilUser']; - $this->ctrl = $DIC['ilCtrl']; - $this->tpl = $DIC['tpl']; - $this->ui_factory = $DIC['ui.factory']; - $this->toolbar = $DIC['ilToolbar']; - $this->lng = $DIC['lng']; - $this->obj_definition = $DIC['objDefinition']; - $this->tree = $DIC['tree']; - $this->retriever = new ilObjectRequestRetriever($DIC->http()->wrapper(), $DIC['refinery']); + public function __construct( + protected InternalDomainService $domain, + protected InternalGUIService $gui, + ?int $user_id = null, + bool $read_only = false + ) { + $this->ctrl = $this->gui->ctrl(); + $this->lng = $this->domain->lng(); + $this->retriever = new ilObjectRequestRetriever( + $this->gui->http()->wrapper(), + $this->domain->refinery() + ); $this->lng->loadLanguageModule('obj'); - $this->user_id = $this->user->getId(); + $this->user_id = $domain->user()->getId(); if (!is_null($user_id)) { $this->user_id = $user_id; } @@ -81,12 +68,20 @@ public function executeCommand(): void public function listObjects(): void { $objects = ilObject::getAllOwnedRepositoryObjects($this->user_id); - - $tbl = new ilObjectOwnershipManagementTableGUI($this, 'listObjects', $this->user_id); + $mt = $this->gui->mainTemplate(); + $toolbar = $this->gui->toolbar(); + $f = $this->gui->ui()->factory(); if ($objects === []) { - $tbl->setTitle($this->lng->txt('user_owns_no_objects')); - $this->tpl->setContent($tbl->getHTML()); + $table_builder = $this->gui->ownership()->ownershipManagementTableBuilder( + $this->user_id, + $this->lng->txt('user_owns_no_objects'), + [], + '', + $this, + 'listObjects' + ); + $mt->setContent($table_builder->getTable()->render()); return; } @@ -97,68 +92,70 @@ public function listObjects(): void $this->ctrl->setParameterByClass(self::class, 'type', $type); $target = $this->ctrl->getLinkTargetByClass(self::class, 'listObjects'); $label = $this->getLabelForObjectType($type); - $options[$type] = $this->ui_factory->button()->shy($label, $target); + $options[$type] = $f->button()->shy($label, $target); } asort($options); $selected_type = $this->retriever->getMaybeString('type') ?? array_keys($options)[0]; unset($options[$selected_type]); - $dropdown = $this->ui_factory->dropdown()->standard($options)->withLabel( + $dropdown = $f->dropdown()->standard($options)->withLabel( $this->lng->txt('select_object_type') ); - $this->toolbar->addStickyItem($dropdown); + $toolbar->addStickyItem($dropdown); if (is_array($objects[$selected_type]) && $objects[$selected_type] !== []) { ilObject::fixMissingTitles($selected_type, $objects[$selected_type]); } - $tbl->setTitle($this->getLabelForObjectType($selected_type)); - $tbl->initItems($objects[$selected_type]); + $table_builder = $this->gui->ownership()->ownershipManagementTableBuilder( + $this->user_id, + $this->getLabelForObjectType($selected_type), + $objects, + $selected_type, + $this, + 'listObjects' + ); + $this->ctrl->setParameterByClass(self::class, 'type', $selected_type); - $this->tpl->setContent($tbl->getHTML()); - } - private function getLabelForObjectType(string $type): string - { - if ($this->obj_definition->isPlugin($type)) { - return $this->lng->txt($type, 'obj_' . $type); + if ($table_builder->getTable()->handleCommand()) { + return; } - return $this->lng->txt('objs_' . $type); + $mt->setContent($table_builder->getTable()->render()); } - public function applyFilter(): void + private function getLabelForObjectType(string $type): string { - $tbl = new ilObjectOwnershipManagementTableGUI($this, 'listObjects', $this->user_id); - $tbl->resetOffset(); - $tbl->writeFilterToSession(); - $this->listObjects(); - } + $obj_definition = $this->domain->objectDefinition(); + if ($obj_definition->isPlugin($type)) { + return ilObjectPlugin::getPluginObjectByType($type) + ->txt('obj_' . $type); + } - public function resetFilter(): void - { - $tbl = new ilObjectOwnershipManagementTableGUI($this, 'listObjects', $this->user_id); - $tbl->resetOffset(); - $tbl->resetFilter(); - $this->listObjects(); + return $this->lng->txt('objs_' . $type); } protected function redirectParentCmd(int $ref_id, string $cmd): void { - $parent = $this->tree->getParentId($ref_id); - $this->ctrl->setParameterByClass('ilRepositoryGUI', 'ref_id', $parent); - $this->ctrl->setParameterByClass('ilRepositoryGUI', 'item_ref_id', $ref_id); - $this->ctrl->setParameterByClass('ilRepositoryGUI', 'cmd', $cmd); - $this->ctrl->redirectByClass('ilRepositoryGUI'); + $tree = $this->domain->repositoryTree(); + $parent = $tree->getParentId($ref_id); + $this->ctrl->setParameterByClass(ilRepositoryGUI::class, 'ref_id', $parent); + $this->ctrl->setParameterByClass(ilRepositoryGUI::class, 'item_ref_id', $ref_id); + $this->ctrl->setParameterByClass(ilRepositoryGUI::class, 'cmd', $cmd); + $this->ctrl->redirectByClass(ilRepositoryGUI::class); } protected function redirectCmd(int $ref_id, string $class, ?string $cmd = null): void { - $node = $this->tree->getNodeData($ref_id); - $gui_class = 'ilObj' . $this->obj_definition->getClassName($node['type']) . 'GUI'; + $tree = $this->domain->repositoryTree(); + $obj_definition = $this->domain->objectDefinition(); + + $node = $tree->getNodeData($ref_id); + $gui_class = 'ilObj' . $obj_definition->getClassName($node['type']) . 'GUI'; $path = ['ilRepositoryGUI', $gui_class, $class]; if ($class == 'ilExportGUI') { @@ -187,42 +184,48 @@ protected function redirectCmd(int $ref_id, string $class, ?string $cmd = null): $this->ctrl->redirectByClass($path); } - public function delete(): void + public function delete(int $id): void { $this->checkReadOnly(); $this->redirectParentCmd( - $this->own_id, + $id, 'delete' ); } - public function move(): void + public function show(int $id): void + { + $link = \ilLink::_getLink($id); + $this->ctrl->redirectToURL($link); + } + + public function move(int $id): void { $this->checkReadOnly(); $this->redirectParentCmd( - $this->own_id, + $id, 'cut' ); } - public function export(): void + public function export(int $id): void { $this->checkReadOnly(); $this->redirectCmd( - $this->own_id, + $id, ilExportGUI::class ); } - public function changeOwner(): void + public function changeOwner(int $id): void { $this->checkReadOnly(); $this->redirectCmd( - $this->own_id, + $id, ilPermissionGUI::class, 'owner' ); diff --git a/components/ILIAS/Repository/PluginSlot/class.ilObjectPluginGUI.php b/components/ILIAS/Repository/PluginSlot/class.ilObjectPluginGUI.php index 3910d76d850f..c8c5ba17acd8 100755 --- a/components/ILIAS/Repository/PluginSlot/class.ilObjectPluginGUI.php +++ b/components/ILIAS/Repository/PluginSlot/class.ilObjectPluginGUI.php @@ -230,35 +230,6 @@ protected function supportsCloning(): bool return true; } - /** - * Init object creation form - */ - protected function initCreateForm(string $new_type): ilPropertyFormGUI - { - $form = new ilPropertyFormGUI(); - $form->setTarget("_top"); - $form->setFormAction($this->ctrl->getFormAction($this, "save")); - $form->setTitle($this->txt($new_type . "_new")); - - // title - $ti = new ilTextInputGUI($this->lng->txt("title"), "title"); - $ti->setSize(min(40, ilObject::TITLE_LENGTH)); - $ti->setMaxLength(ilObject::TITLE_LENGTH); - $ti->setRequired(true); - $form->addItem($ti); - - // description - $ta = new ilTextAreaInputGUI($this->lng->txt("description"), "desc"); - $ta->setCols(40); - $ta->setRows(2); - $form->addItem($ta); - - $form->addCommandButton("save", $this->txt($new_type . "_add")); - $form->addCommandButton("cancel", $this->lng->txt("cancel")); - - return $form; - } - /** * Init object update form */ diff --git a/components/ILIAS/Repository/PluginSlot/class.ilRepositoryObjectPlugin.php b/components/ILIAS/Repository/PluginSlot/class.ilRepositoryObjectPlugin.php index 7207fdac39ec..af66890529c0 100755 --- a/components/ILIAS/Repository/PluginSlot/class.ilRepositoryObjectPlugin.php +++ b/components/ILIAS/Repository/PluginSlot/class.ilRepositoryObjectPlugin.php @@ -60,7 +60,6 @@ public static function _getImagePath(string $a_ctype, string $a_cname, string $a } $d = $plugin->getPath(); - return $d . "/templates/images/" . $a_img; } @@ -71,7 +70,7 @@ public static function _getIcon(string $a_type): string global $DIC; $component_repository = $DIC["component.repository"]; return self::_getImagePath( - ilComponentInfo::TYPE_SERVICES, + ilComponentInfo::TYPES[0], "Repository", "robj", $component_repository->getPluginById($a_type)->getName(), diff --git a/components/ILIAS/CAS/CAS.php b/components/ILIAS/Repository/Service/ExternalGUIService.php old mode 100644 new mode 100755 similarity index 55% rename from components/ILIAS/CAS/CAS.php rename to components/ILIAS/Repository/Service/ExternalGUIService.php index e0e68f453c55..3950009eabb0 --- a/components/ILIAS/CAS/CAS.php +++ b/components/ILIAS/Repository/Service/ExternalGUIService.php @@ -18,20 +18,22 @@ declare(strict_types=1); -namespace ILIAS; +namespace ILIAS\Repository; -class CAS implements Component\Component +use ILIAS\DI\Container; +use ilObjectOwnershipManagementGUI; + +class ExternalGUIService { - public function init( - array | \ArrayAccess &$define, - array | \ArrayAccess &$implement, - array | \ArrayAccess &$use, - array | \ArrayAccess &$contribute, - array | \ArrayAccess &$seek, - array | \ArrayAccess &$provide, - array | \ArrayAccess &$pull, - array | \ArrayAccess &$internal, - ): void { - // ... + public function __construct( + protected InternalGUIService $internal_gui + ) { + } + + public function ownershipManagementGUI( + ?int $user_id = null, + bool $read_only = false + ): ilObjectOwnershipManagementGUI { + return $this->internal_gui->ownership()->ownershipManagementGUI($user_id, $read_only); } } diff --git a/components/ILIAS/Repository/Service/Filter/class.FilterAdapterGUI.php b/components/ILIAS/Repository/Service/Filter/class.FilterAdapterGUI.php index 535c073783fe..c5fc1f93c6ee 100755 --- a/components/ILIAS/Repository/Service/Filter/class.FilterAdapterGUI.php +++ b/components/ILIAS/Repository/Service/Filter/class.FilterAdapterGUI.php @@ -82,7 +82,9 @@ public function select(string $key, string $title, array $options, bool $activat { $field = $this->ui->factory()->input()->field()->select($title, $options); if (!is_null($value)) { - $field = $field->withValue($value); + if (isset($options[$value])) { + $field = $field->withValue($value); + } } $this->addField( $key, diff --git a/components/ILIAS/Repository/Service/Form/NotEmptyConstraint.php b/components/ILIAS/Repository/Service/Form/NotEmptyConstraint.php new file mode 100644 index 000000000000..b67e64dabcdb --- /dev/null +++ b/components/ILIAS/Repository/Service/Form/NotEmptyConstraint.php @@ -0,0 +1,43 @@ +loadLanguageModule("rep"); + parent::__construct( + static function ($value): bool { + return trim($value) !== ''; + }, + static function ($txt, $value): string { + return $txt("rep_input_not_empty"); + }, + $data_factory, + $lng + ); + } +} diff --git a/components/ILIAS/Repository/Service/Form/class.FormAdapterGUI.php b/components/ILIAS/Repository/Service/Form/class.FormAdapterGUI.php index 425fa5e40b1c..504fc5329c94 100755 --- a/components/ILIAS/Repository/Service/Form/class.FormAdapterGUI.php +++ b/components/ILIAS/Repository/Service/Form/class.FormAdapterGUI.php @@ -22,6 +22,7 @@ use ILIAS\UI\Component\Input\Container\Form; use ILIAS\UI\Component\Input\Container\Form\FormInput; +use ILIAS\Data\Factory; /** * @author Alexander Killing @@ -31,7 +32,6 @@ class FormAdapterGUI use StdObjProperties; protected const DEFAULT_SECTION = "@internal_default_section"; - protected bool $in_modal = false; protected string $submit_caption = ""; protected \ilLanguage $lng; protected const ASYNC_NONE = 0; @@ -85,13 +85,13 @@ public function __construct( $this->http = $DIC->http(); $this->lng = $DIC->language(); $this->refinery = $DIC->refinery(); - $this->lng = $DIC->language(); $this->main_tpl = $DIC->ui()->mainTemplate(); $this->user = $DIC->user(); $this->data = new \ILIAS\Data\Factory(); $this->submit_caption = $submit_caption; self::initJavascript(); $this->initStdObjProperties($DIC); + $this->lng->loadLanguageModule("rep"); } public static function getOnLoadCode(): string @@ -112,7 +112,13 @@ public static function initJavascript(): void $r = $DIC->ui()->renderer(); if (!self::$initialised) { $main_tpl = $DIC->ui()->mainTemplate(); - $main_tpl->addJavaScript("assets/js/repository.js"); + $debug = false; + if ($debug) { + $main_tpl->addJavaScript("../components/ILIAS/Repository/resources/repository.js"); + } else { + $main_tpl->addJavaScript("assets/js/repository.js"); + } + $main_tpl->addOnLoadCode(self::getOnLoadCode()); // render dummy components to load the necessary .js needed for async processing @@ -126,7 +132,6 @@ public static function initJavascript(): void public function asyncModal(): self { $this->async_mode = self::ASYNC_MODAL; - $this->in_modal = true; return $this; } @@ -138,7 +143,6 @@ public function async(): self public function syncModal(): self { - $this->in_modal = true; return $this; } @@ -214,7 +218,14 @@ public function hidden( public function required($required = true): self { if ($required && ($field = $this->getLastField())) { - $field = $field->withRequired(true); + if ($field instanceof \ILIAS\UI\Component\Input\Field\Text) { + $field = $field->withRequired(true, new NotEmpty( + new Factory(), + $this->lng + )); + } else { + $field = $field->withRequired(true); + } $this->replaceLastField($field); } return $this; @@ -501,7 +512,10 @@ public function end(): self $this->current_switch["description"] ); if (!is_null($this->current_switch["value"])) { - $field = $field->withValue($this->current_switch["value"]); + $cvalue = $this->current_switch["value"]; + if (isset($this->current_switch["groups"][$cvalue])) { + $field = $field->withValue($cvalue); + } } $key = $this->current_switch["key"]; $this->current_switch = null; @@ -547,6 +561,13 @@ public function file( $ctrl_path ); + foreach (["application/x-compressed", "application/x-zip-compressed"] as $zipmime) { + if (in_array("application/zip", $mime_types) && + !in_array($zipmime, $mime_types)) { + $mime_types[] = $zipmime; + } + } + if (count($mime_types) > 0) { $description .= $this->lng->txt("rep_allowed_types") . ": " . implode(", ", $mime_types); @@ -645,7 +666,7 @@ protected function replaceLastField(FormInput $field): void } } - protected function getForm(): Form\Standard + public function getForm(): Form\Standard { $ctrl = $this->ctrl; @@ -756,13 +777,6 @@ public function render(): string } else { $html = $this->ui->renderer()->renderAsync($this->getForm()) . ""; } - if ($this->in_modal) { - if ($this->async_mode === self::ASYNC_MODAL) { - $html = str_replace("
object_prop = ilObjectDIC::dic()['object_properties_agregator']; + $this->object_prop = LocalDIC::dic()['properties.aggregator']; } public function addStdTitleAndDescription( @@ -64,7 +65,7 @@ public function saveStdTitleAndDescription( ): void { $obj_prop = $this->object_prop->getFor($obj_id, $type); $obj_prop->storePropertyTitleAndDescription( - new \ilObjectPropertyTitleAndDescription( + new TitleAndDescription( $this->getData("title"), $this->getData("description") ) @@ -77,7 +78,7 @@ public function saveStdTitle( ): void { $obj_prop = $this->object_prop->getFor($obj_id, $type); $obj_prop->storePropertyTitleAndDescription( - new \ilObjectPropertyTitleAndDescription( + new TitleAndDescription( $this->getData("title"), "" ) @@ -100,7 +101,10 @@ public function saveStdTile( string $type ): void { $obj_prop = $this->object_prop->getFor($obj_id, $type); - $obj_prop->storePropertyTileImage($this->getData("tile")); + $tile = $this->getData("tile"); + if (!is_null($tile)) { + $obj_prop->storePropertyTileImage($this->getData("tile")); + } } public function addOnline( diff --git a/components/ILIAS/Repository/Service/HTML/HTMLUtil.php b/components/ILIAS/Repository/Service/HTML/HTMLUtil.php index 101a9b518bfa..3a60b16f412c 100644 --- a/components/ILIAS/Repository/Service/HTML/HTMLUtil.php +++ b/components/ILIAS/Repository/Service/HTML/HTMLUtil.php @@ -32,7 +32,11 @@ public function __construct() public function escape(string $input): string { - return htmlentities($input); + return htmlspecialchars( + $input, + ENT_QUOTES | ENT_SUBSTITUTE, + 'utf-8' + ); } public function strip(string $input): string diff --git a/components/ILIAS/Repository/Service/IRSS/IRSSWrapper.php b/components/ILIAS/Repository/Service/IRSS/IRSSWrapper.php index 5a6e01256394..8de1092f300a 100755 --- a/components/ILIAS/Repository/Service/IRSS/IRSSWrapper.php +++ b/components/ILIAS/Repository/Service/IRSS/IRSSWrapper.php @@ -34,9 +34,14 @@ use ILIAS\Filesystem\Stream\ZIPStream; use ILIAS\ResourceStorage\Resource\StorableResource; use ILIAS\components\ResourceStorage\Container\Wrapper\ZipReader; +use ILIAS\FileDelivery\Delivery\Disposition; +use ILIAS\Filesystem\Util\Archive\ZipOptions; +use ILIAS\Filesystem\Filesystems; class IRSSWrapper { + protected $filesystems; + protected \ILIAS\FileDelivery\Services $file_delivery; protected \ILIAS\FileUpload\FileUpload $upload; protected \ILIAS\ResourceStorage\Services $irss; protected Archives $archives; @@ -53,6 +58,8 @@ public function __construct( $this->archives = $DIC->archives(); $this->upload = $DIC->upload(); $this->archives = $DIC->archives(); + $this->file_delivery = $DIC->fileDelivery(); + $this->filesystems = $DIC->filesystem(); } protected function getNewCollectionId(): ResourceCollectionIdentification @@ -269,7 +276,8 @@ public function deliverFile(string $rid): void { $id = $this->getResourceIdForIdString($rid); if ($id) { - $this->irss->consume()->download($id)->run(); + $revision = $this->irss->manage()->getCurrentRevision($id); + $this->irss->consume()->download($id)->overrideFileName($revision->getTitle())->run(); } } @@ -409,6 +417,54 @@ public function getStreamOfContainerEntry( ); } + public function getContainerEntryInfo( + string $container_id, + string $path + ): array { + $reader = new ZipReader( + $this->irss->consume()->stream($this->getResourceIdForIdString($container_id))->getStream() + ); + return $reader->getItem($path)[1]; + } + + public function deliverContainerEntry( + string $container_id, + string $path + ): void { + $reader = new ZipReader( + $this->irss->consume()->stream($this->getResourceIdForIdString($container_id))->getStream() + ); + [$stream, $info] = $reader->getItem($path); + + $this->file_delivery->delivery()->deliver( + $stream, + $info['basename'], + $info['mime_type'], + Disposition::ATTACHMENT + ); + } + + /** + * Is there a better way to check this? + */ + public function hasContainerEntry( + string $rid, + string $entry + ): bool { + $zip_path = $this->stream($rid)?->getMetadata("uri"); + try { + $stream = Streams::ofFileInsideZIP( + $zip_path, + $entry + ); + $stream->close(); + return true; + } catch (\Exception $e) { + return false; + } + } + + // this currently does not work due to issues in the irss /* public function importContainerFromZipUploadResult( @@ -502,22 +558,28 @@ public function addContainerDirToTargetContainer( public function createContainer( ResourceStakeholder $stakeholder, - string $title = "" + string $title = "container.zip" ): string { + if ($title === "") { + throw new \ilException("Container title missing."); + } // create empty container resource. empty zips are not allowed, we need at least one file which is hidden + $tmp_dir_info = new \SplFileInfo(\ilFileUtils::ilTempnam()); + $this->filesystems->temp()->createDir($tmp_dir_info->getFilename()); + $tmp_dir = $tmp_dir_info->getRealPath(); + $options = (new ZipOptions()) + ->withZipOutputName($title) + ->withZipOutputPath($tmp_dir); $empty_zip = $this->archives->zip( - [] + [], + $options ); - - if ($title === "") { - $title = null; - } - $rid = $this->irss->manageContainer()->containerFromStream( $empty_zip->get(), $stakeholder, $title ); + \ilFileUtils::delDir($tmp_dir); return $rid->serialize(); } @@ -539,13 +601,17 @@ public function createContainerFromLocalDir( string $local_dir_path, ResourceStakeholder $stakeholder, string $container_path = "", - bool $recursive = true + bool $recursive = true, + string $title = "container.zip" ): string { $real_dir_path = realpath($local_dir_path); - $rid = $this->createContainer($stakeholder); + $rid = $this->createContainer($stakeholder, $title); if ($recursive) { $iterator = new \RecursiveIteratorIterator( - new \RecursiveDirectoryIterator($local_dir_path, \RecursiveDirectoryIterator::SKIP_DOTS), + new \RecursiveDirectoryIterator( + $local_dir_path, + \RecursiveDirectoryIterator::SKIP_DOTS | \RecursiveDirectoryIterator::CURRENT_AS_SELF + ), \RecursiveIteratorIterator::SELF_FIRST ); } else { @@ -694,11 +760,23 @@ public function importFileFromLegacyUploadToContainer( $id = $this->getResourceIdForIdString($rid); if (!is_null($id)) { - $this->irss->manageContainer()->addUploadToContainer( - $id, - $result, - $target_path - ); + // if target path is a directory, addUploadToContainer + // can be used, the original filename will be appended + if ($target_path === "" || str_ends_with($target_path, "/")) { + $this->irss->manageContainer()->addUploadToContainer( + $id, + $result, + "/" + ); + } else { + // we have a full path given (renaming the + // original name) + $this->addLocalFileToContainer( + $rid, + $result->getPath(), + $target_path + ); + } } } } @@ -750,6 +828,7 @@ public function removePathFromContainer( } } + // currently broken, see https://mantis.ilias.de/view.php?id=44135 public function renameContainer( string $rid, string $title diff --git a/components/ILIAS/Repository/Service/Modal/class.ModalAdapterGUI.php b/components/ILIAS/Repository/Service/Modal/class.ModalAdapterGUI.php index c1bc1b304297..d8717e007e8e 100755 --- a/components/ILIAS/Repository/Service/Modal/class.ModalAdapterGUI.php +++ b/components/ILIAS/Repository/Service/Modal/class.ModalAdapterGUI.php @@ -139,15 +139,24 @@ protected function getModalWithContent(): Modal\RoundTrip { $modal = []; if (!is_null($this->form)) { - $this->ui_content = [$this->ui->factory()->legacy()->content($this->form->render())]; + $modal = $this->ui->factory()->modal()->roundtrip( + $this->getTitle(), + null, + $this->form->getForm()->getInputs(), + $this->form->getForm()->getPostURL() + ); + } else { + $modal = $this->ui->factory()->modal()->roundtrip($this->getTitle(), $this->ui_content); } - $modal = $this->ui->factory()->modal()->roundtrip($this->getTitle(), $this->ui_content); if (count($this->action_buttons) > 0) { $modal = $modal->withActionButtons($this->action_buttons); } if ($this->cancel_label !== "") { $modal = $modal->withCancelButtonLabel($this->cancel_label); } + $modal = $modal->withAdditionalOnLoadCode(function ($id) { + return "il.repository.ui.initModal('$id');"; + }); return $modal; } diff --git a/components/ILIAS/Repository/Service/Profile/ProfileAdapter.php b/components/ILIAS/Repository/Service/Profile/ProfileAdapter.php index d33a68fc6f84..eaed8d4d8620 100755 --- a/components/ILIAS/Repository/Service/Profile/ProfileAdapter.php +++ b/components/ILIAS/Repository/Service/Profile/ProfileAdapter.php @@ -45,4 +45,12 @@ public function getDeletedUserNamePresentation(): string { return $this->lng->txt("rep_deleted_account"); } + + public function getNamePresentation(int $user_id, bool $force = false): string + { + if (!$this->exists($user_id)) { + return $this->getDeletedUserNamePresentation(); + } + return \ilUserUtil::getNamePresentation($user_id, false, false, "", $force); + } } diff --git a/components/ILIAS/LearningModule/Editing/RetrievalInterface.php b/components/ILIAS/Repository/Service/RetrievalInterface.php similarity index 90% rename from components/ILIAS/LearningModule/Editing/RetrievalInterface.php rename to components/ILIAS/Repository/Service/RetrievalInterface.php index 4441b4112c6d..caa36899a642 100644 --- a/components/ILIAS/LearningModule/Editing/RetrievalInterface.php +++ b/components/ILIAS/Repository/Service/RetrievalInterface.php @@ -18,7 +18,7 @@ declare(strict_types=1); -namespace ILIAS\LearningModule\Table; +namespace ILIAS\Repository; use ILIAS\Data\Range; use ILIAS\Data\Order; @@ -37,4 +37,8 @@ public function count( array $filter, array $parameters ): int; + + public function isFieldNumeric( + string $field + ): bool; } diff --git a/components/ILIAS/Repository/Service/Table/CommonTableBuilder.php b/components/ILIAS/Repository/Service/Table/CommonTableBuilder.php new file mode 100644 index 000000000000..b008a03c2a8b --- /dev/null +++ b/components/ILIAS/Repository/Service/Table/CommonTableBuilder.php @@ -0,0 +1,84 @@ +table = new TableAdapterGUI( + $this->getId(), + $this->getTitle(), + $this->getRetrieval(), + $parent_gui, + $parent_cmd, + $this->getNamespace(), + $this->getOrderingCommand(), + \Closure::fromCallable([$this, 'activeAction']), + \Closure::fromCallable([$this, 'transformRow']), + $numeric_ids + ); + $this->table = $this->build($this->table); + } + + abstract protected function getId(): string; + + abstract protected function getTitle(): string; + + abstract protected function getRetrieval(): RetrievalInterface; + + protected function getNamespace(): string + { + return ""; + } + + protected function getOrderingCommand(): string + { + return ""; + } + + protected function activeAction(string $action, array $data_row): bool + { + return true; + } + + /** + * transform raw data array to table row data array + */ + protected function transformRow(array $data_row): array + { + return $data_row; + } + + abstract protected function build(TableAdapterGUI $table): TableAdapterGUI; + + final public function getTable( + ): TableAdapterGUI { + return $this->table; + } +} diff --git a/components/ILIAS/Repository/Service/Table/OrderingRetrieval.php b/components/ILIAS/Repository/Service/Table/OrderingRetrieval.php new file mode 100644 index 000000000000..1764c287eae1 --- /dev/null +++ b/components/ILIAS/Repository/Service/Table/OrderingRetrieval.php @@ -0,0 +1,60 @@ +retrieval->getData( + $visible_column_ids + ) as $data) { + if ($this->row_transformer) { + $table_data = ($this->row_transformer)($data); + } else { + $table_data = $data; + } + $row = $row_builder->buildOrderingRow((string) $data["id"], $table_data); + if ($this->active_action_closure) { + foreach ($this->actions as $action) { + if (!($this->active_action_closure)($action, $data)) { + $row = $row->withDisabledAction($action); + } + } + } + yield $row; + } + } +} diff --git a/components/ILIAS/LearningModule/Editing/class.TableAdapterGUI.php b/components/ILIAS/Repository/Service/Table/TableAdapterGUI.php similarity index 82% rename from components/ILIAS/LearningModule/Editing/class.TableAdapterGUI.php rename to components/ILIAS/Repository/Service/Table/TableAdapterGUI.php index f8466192a7fe..06f31a1f6a9f 100755 --- a/components/ILIAS/LearningModule/Editing/class.TableAdapterGUI.php +++ b/components/ILIAS/Repository/Service/Table/TableAdapterGUI.php @@ -18,13 +18,14 @@ declare(strict_types=1); -namespace ILIAS\LearningModule\Table; +namespace ILIAS\Repository\Table; use ILIAS\UI\Component\Table\Column\Column; use ILIAS\UI\Component\Table\Table; use ILIAS\UI\URLBuilder; use ILIAS\Repository\BaseGUIRequest; use ILIAS\UI\URLBuilderToken; +use ILIAS\Repository\RetrievalInterface; class TableAdapterGUI { @@ -54,7 +55,11 @@ public function __construct( protected RetrievalInterface $retrieval, protected object $parent_gui, protected string $parent_cmd = "tableCommand", - protected string $namespace = "" + protected string $namespace = "", + protected string $ordering_cmd = "", + protected ?\Closure $active_action_closure = null, + protected ?\Closure $row_transformer = null, + protected bool $numeric_ids = true ) { global $DIC; $this->ui = $DIC->ui(); @@ -67,6 +72,7 @@ public function __construct( if ($namespace === "") { $this->namespace = $id; } + $this->order_cmd = $ordering_cmd; $form_action = $this->df->uri( ILIAS_HTTP_PATH . '/' . @@ -82,19 +88,22 @@ public function __construct( } - public function ordering( - string $order_cmd + public function textColumn( + string $key, + string $title, + bool $sortable = false ): self { - $this->order_cmd = $order_cmd; + $column = $this->ui->factory()->table()->column()->text($title)->withIsSortable($sortable); + $this->addColumn($key, $column); return $this; } - public function textColumn( + public function iconColumn( string $key, string $title, bool $sortable = false ): self { - $column = $this->ui->factory()->table()->column()->text($title)->withIsSortable($sortable); + $column = $this->ui->factory()->table()->column()->statusIcon($title)->withIsSortable($sortable); $this->addColumn($key, $column); return $this; } @@ -108,11 +117,14 @@ public function singleAction( return $this; } - public function redirect( + public function singleRedirectAction( + string $action, + string $title, array $class_path, string $cmd = "", string $id_param = "" ): self { + $this->addAction(self::SINGLE, $action, $title); $act = $this->actions[$this->last_action_key] ?? false; if ($act && $act["type"] === self::SINGLE) { $act["redirect_class_path"] = $class_path; @@ -182,20 +194,28 @@ protected function replaceLastColumn(Column $column): void } } - public function getItemIds(): array + protected function getItemIds(): array { - $ids = $this->intArray($this->row_id_token->getName()); + if ($this->numeric_ids) { + $ids = $this->intArray($this->row_id_token->getName()); + } else { + $ids = $this->strArray($this->row_id_token->getName()); + } if (count($ids) > 0) { return $ids; // from table multi action } - $ids = $this->intArray("interruptive_items"); // from confirmation + if ($this->numeric_ids) { + $ids = $this->intArray("interruptive_items"); // from confirmation + } else { + $ids = $this->strArray("interruptive_items"); // from confirmation + } if (count($ids) > 0) { return $ids; } return []; } - public function handleCommand(): void + public function handleCommand(): bool { $action = $this->str($this->action_parameter_token->getName()); if ($action !== "") { @@ -214,10 +234,13 @@ public function handleCommand(): void $this->ctrl->redirectByClass($this->actions[$action]["redirect_class_path"], $cmd); } $this->parent_gui->$action($id); + return true; } else { $this->parent_gui->$action($this->getItemIds()); + return true; } } + return false; } protected function getTable(): Table @@ -263,22 +286,32 @@ protected function getTable(): Table ILIAS_HTTP_PATH . '/' . $this->ctrl->getLinkTarget($this->parent_gui, $this->order_cmd) ); - $table_retrieval = new OrderingBinding($this->retrieval); + $table_retrieval = new OrderingRetrieval( + $this->retrieval, + array_keys($actions), + $this->active_action_closure, + $this->row_transformer + ); $this->table = $this ->ui ->factory() ->table() - ->ordering($this->title, $columns, $table_retrieval, $uri) + ->ordering($table_retrieval, $uri, $this->title, $columns) ->withId($this->id) ->withActions($actions) ->withRequest($this->http->request()); } else { - $table_retrieval = new TableRetrieval($this->retrieval); + $table_retrieval = new TableRetrieval( + $this->retrieval, + array_keys($actions), + $this->active_action_closure, + $this->row_transformer + ); $this->table = $this ->ui ->factory() ->table() - ->data($this->title, $columns, $table_retrieval) + ->data($table_retrieval, $this->title, $columns) ->withId($this->id) ->withActions($actions) ->withRequest($this->http->request()); diff --git a/components/ILIAS/LearningModule/Editing/TableRetrieval.php b/components/ILIAS/Repository/Service/Table/TableRetrieval.php similarity index 63% rename from components/ILIAS/LearningModule/Editing/TableRetrieval.php rename to components/ILIAS/Repository/Service/Table/TableRetrieval.php index d53ffa201a43..b3e2b61d2871 100644 --- a/components/ILIAS/LearningModule/Editing/TableRetrieval.php +++ b/components/ILIAS/Repository/Service/Table/TableRetrieval.php @@ -18,16 +18,20 @@ declare(strict_types=1); -namespace ILIAS\LearningModule\Table; +namespace ILIAS\Repository\Table; use ILIAS\UI\Component\Table; use ILIAS\Data\Range; use ILIAS\Data\Order; +use ILIAS\Repository\RetrievalInterface; class TableRetrieval implements Table\DataRetrieval { public function __construct( - protected RetrievalInterface $retrieval + protected RetrievalInterface $retrieval, + protected array $actions, + protected ?\Closure $active_action_closure, + protected ?\Closure $row_transformer ) { } @@ -46,7 +50,20 @@ public function getRows( $filter_data ?? [], $additional_parameters ?? [] ) as $data) { - yield $row_builder->buildDataRow((string) $data["id"], $data); + if ($this->row_transformer) { + $table_data = ($this->row_transformer)($data); + } else { + $table_data = $data; + } + $row = $row_builder->buildDataRow((string) $data["id"], $table_data); + if ($this->active_action_closure) { + foreach ($this->actions as $action) { + if (!($this->active_action_closure)($action, $data)) { + $row = $row->withDisabledAction($action); + } + } + } + yield $row; } } diff --git a/components/ILIAS/Repository/Service/class.InternalGUIService.php b/components/ILIAS/Repository/Service/class.InternalGUIService.php index 18ea94766d57..f416972cc1e9 100755 --- a/components/ILIAS/Repository/Service/class.InternalGUIService.php +++ b/components/ILIAS/Repository/Service/class.InternalGUIService.php @@ -73,4 +73,13 @@ public function standardRequest(): StandardGUIRequest $this->domain_service->refinery() ); } + + public function ownership(): Ownership\GUIService + { + return new Ownership\GUIService( + $this->domain_service, + $this + ); + } + } diff --git a/components/ILIAS/Repository/Service/class.Service.php b/components/ILIAS/Repository/Service/class.Service.php index b87819ebc3f8..2cb5f203e52a 100755 --- a/components/ILIAS/Repository/Service/class.Service.php +++ b/components/ILIAS/Repository/Service/class.Service.php @@ -41,4 +41,10 @@ public function internal(): InternalService { return new InternalService($this->DIC); } + + public function gui(): ExternalGUIService + { + return new ExternalGUIService($this->internal()->gui()); + } + } diff --git a/components/ILIAS/Repository/Service/trait.GlobalDICDomainServices.php b/components/ILIAS/Repository/Service/trait.GlobalDICDomainServices.php index 60d140380ee2..1185c70a51c0 100755 --- a/components/ILIAS/Repository/Service/trait.GlobalDICDomainServices.php +++ b/components/ILIAS/Repository/Service/trait.GlobalDICDomainServices.php @@ -142,4 +142,9 @@ public function staticUrl(): StaticUrl { return $this->DIC['static_url']; } + + public function database(): \ilDBInterface + { + return $this->DIC->database(); + } } diff --git a/components/ILIAS/Repository/Service/trait.GlobalDICGUIServices.php b/components/ILIAS/Repository/Service/trait.GlobalDICGUIServices.php index 871700b77cb3..db395bd4dd05 100755 --- a/components/ILIAS/Repository/Service/trait.GlobalDICGUIServices.php +++ b/components/ILIAS/Repository/Service/trait.GlobalDICGUIServices.php @@ -85,6 +85,19 @@ public function mainTemplate(): \ilGlobalTemplateInterface return $this->DIC->ui()->mainTemplate(); } + public function initFetch(): void + { + $main_tpl = $this->mainTemplate(); + $main_tpl->addJavaScript("assets/js/repository.js"); + } + + public function clearAsnyOnloadCode(): void + { + $this->ui()->renderer()->renderAsync( + $this->ui()->factory()->legacy()->content("") + ); + } + public function upload(): FileUpload { return $this->DIC->upload(); diff --git a/components/ILIAS/Repository/Service/trait.RetrievalBase.php b/components/ILIAS/Repository/Service/trait.RetrievalBase.php new file mode 100644 index 000000000000..67f8ae0b8cb0 --- /dev/null +++ b/components/ILIAS/Repository/Service/trait.RetrievalBase.php @@ -0,0 +1,56 @@ +get())[0]; + $order_direction = $order->get()[$order_field]; + + if (count(array_column($data, $order_field)) === 0) { + return $data; + } + array_multisort( + array_column($data, $order_field), + $order_direction === 'ASC' ? SORT_ASC : SORT_DESC, + $this->isFieldNumeric($order_field) ? SORT_NUMERIC : SORT_STRING, + $data + ); + } + return $data; + } + + protected function applyRange(array $data, ?Range $range = null): array + { + if ($range !== null) { + $offset = $range->getStart(); + $limit = $range->getLength(); + $data = array_slice($data, $offset, $limit); + } + return $data; + } +} diff --git a/components/ILIAS/Repository/classes/class.ilRepositoryGUI.php b/components/ILIAS/Repository/classes/class.ilRepositoryGUI.php index 6e175dea06a2..19435096cab0 100755 --- a/components/ILIAS/Repository/classes/class.ilRepositoryGUI.php +++ b/components/ILIAS/Repository/classes/class.ilRepositoryGUI.php @@ -126,7 +126,6 @@ public function executeCommand(): void $lng = $this->lng; $ilHelp = $this->help; $ilErr = $this->error; - if ( ($this->user->isAnonymous() || !($this->user->getId() >= 1)) && !ilPublicSectionSettings::getInstance()->isEnabledForDomain( @@ -185,6 +184,7 @@ public function executeCommand(): void } if ($this->ctrl->getCmd() !== "showRepTree") { + $this->ctrl->setParameterByClass($next_class, "item_ref_id", $this->request->getItemRefId()); $this->ctrl->redirectByClass($next_class, $this->ctrl->getCmd()); } } diff --git a/components/ILIAS/Repository/resources/repository.js b/components/ILIAS/Repository/resources/repository.js index 2ac3222442b8..245cbe03971c 100755 --- a/components/ILIAS/Repository/resources/repository.js +++ b/components/ILIAS/Repository/resources/repository.js @@ -1,10 +1,9 @@ -"use strict"; /* global il, $ */ il = il || {}; il.repository = il.repository || {}; -il.repository.ui = (function(il, $) { +il.repository.ui = (function (il, $) { // All functions now have direct access to each other const sendAsync = function (form, replace = null) { @@ -23,60 +22,56 @@ il.repository.ui = (function(il, $) { }).then(response => { response.text().then(text => { if (replace) { + // this keeps the dialog open (full replacement would close) const marker = "component"; var $new_content = $("
" + text + "
"); var $marked_new_content = $new_content.find("[data-replace-marker='" + marker + "']").first(); - if ($marked_new_content.length == 0) { - // if marker does not come with the new content, we put the new content into the existing element $(replace).html(text); - } else { + // get new id of the root and set it + const tpl = document.createElement('template'); + tpl.innerHTML = text.trim(); + const id = tpl.content.firstElementChild?.id ?? null; + if (id) { + replace.id = id; + } + // if marker is in new content, we replace the complete old node with the marker // with the new marked node $(replace).find("[data-replace-marker='" + marker + "']").first() .replaceWith($marked_new_content); - // append included script (which will not be part of the marked node - $(replace).find("[data-replace-marker='" + marker + "']").first() + // append included script (which will not be part of the marked node + $(replace).find(`[data-replace-marker='${marker}']`).first() .after($new_content.find("[data-replace-marker='script']")); - } } } - ); + }); }); }; const initForms = function () { - document.querySelectorAll("form[data-rep-modal-form='async']:not([data-rep-form-initialised='1'])").forEach(f => { - f.addEventListener("submit", (event) => { - event.preventDefault(); - const modal = f.closest(".c-modal"); - sendAsync(f, modal); - }); - f.querySelectorAll(".c-form__actions").forEach(b => { - b.style.display='none'; - }); - f.dataset.repFormInitialised = '1'; - }); - document.querySelectorAll("form[data-rep-modal-form='sync']:not([data-rep-form-initialised='1'])").forEach(f => { - f.querySelectorAll(".c-form__actions").forEach(b => { - b.style.display='none'; - }); - f.dataset.repFormInitialised = '1'; - }); }; - const init = function() { + const initModal = function (id) { + const modal = document.getElementById(id); + const buttons = modal.querySelectorAll('.modal-footer button'); + if (buttons.length >= 2) { + const penultimate = buttons[buttons.length - 2]; + penultimate.remove(); + } + modal.dataset.modalInitialised = '1'; + }; + + const init = function () { initForms(); }; const submitModalForm = function(event, sentAsync) { - console.log("one"); const f = event.target.closest(".c-modal").querySelector(".modal-body").querySelector("form"); - console.log(f); const modal = f.closest(".c-modal"); if (sentAsync) { sendAsync(f, modal); @@ -87,14 +82,15 @@ il.repository.ui = (function(il, $) { return { init: init, - submitModalForm: submitModalForm + submitModalForm: submitModalForm, + initModal: initModal, }; }(il, $)); -il.repository.core = (function() { +il.repository.core = (function () { let httpPath = ''; - const init = function(path) { + const init = function (path) { httpPath = path; }; @@ -102,19 +98,19 @@ il.repository.core = (function() { function setInnerHTML(el, html) { el.innerHTML = html; - Array.from(el.querySelectorAll("script")) - .forEach( oldScriptEl => { - const newScriptEl = document.createElement("script"); + Array.from(el.querySelectorAll('script')) + .forEach((oldScriptEl) => { + const newScriptEl = document.createElement('script'); - Array.from(oldScriptEl.attributes).forEach( attr => { - newScriptEl.setAttribute(attr.name, attr.value) - }); + Array.from(oldScriptEl.attributes).forEach((attr) => { + newScriptEl.setAttribute(attr.name, attr.value); + }); - const scriptText = document.createTextNode(oldScriptEl.innerHTML); - newScriptEl.appendChild(scriptText); + const scriptText = document.createTextNode(oldScriptEl.innerHTML); + newScriptEl.appendChild(scriptText); - oldScriptEl.parentNode.replaceChild(newScriptEl, oldScriptEl); - }); + oldScriptEl.parentNode.replaceChild(newScriptEl, oldScriptEl); + }); } function setOuterHTML(el_id, html) { @@ -122,19 +118,19 @@ il.repository.core = (function() { el.outerHTML = html; el = document.getElementById(el_id); - Array.from(el.querySelectorAll("script")) - .forEach( oldScriptEl => { - const newScriptEl = document.createElement("script"); + Array.from(el.querySelectorAll('script')) + .forEach((oldScriptEl) => { + const newScriptEl = document.createElement('script'); - Array.from(oldScriptEl.attributes).forEach( attr => { - newScriptEl.setAttribute(attr.name, attr.value) - }); + Array.from(oldScriptEl.attributes).forEach((attr) => { + newScriptEl.setAttribute(attr.name, attr.value); + }); - const scriptText = document.createTextNode(oldScriptEl.innerHTML); - newScriptEl.appendChild(scriptText); + const scriptText = document.createTextNode(oldScriptEl.innerHTML); + newScriptEl.appendChild(scriptText); - oldScriptEl.parentNode.replaceChild(newScriptEl, oldScriptEl); - }); + oldScriptEl.parentNode.replaceChild(newScriptEl, oldScriptEl); + }); } function trigger(name, el = null, details = null) { @@ -151,11 +147,10 @@ il.repository.core = (function() { } function fetchJson(url = '', params = {}) { - - let fetch_url = getFetchUrl(url); - let url_params = new URLSearchParams(fetch_url.search.slice(1)); + const fetch_url = getFetchUrl(url); + const url_params = new URLSearchParams(fetch_url.search.slice(1)); for (const [key, value] of Object.entries(params)) { - url_params.append(key, value) + url_params.append(key, value); } fetch_url.search = url_params; @@ -165,10 +160,10 @@ il.repository.core = (function() { cache: 'no-cache', credentials: 'same-origin', headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', }, redirect: 'follow', - referrerPolicy: 'same-origin' + referrerPolicy: 'same-origin', }); } @@ -182,15 +177,15 @@ il.repository.core = (function() { fetch_url = new URL(url); } catch (error) { // relative paths - fetch_url = new URL(httpPath + "/" + url); + fetch_url = new URL(`${httpPath}/${url}`); } return fetch_url; } function fetchHtml(url = '', params = {}, post = false) { - let fetch_url = getFetchUrl(url); + const fetch_url = getFetchUrl(url); let formData; - let url_params = new URLSearchParams(fetch_url.search.slice(1)); + const url_params = new URLSearchParams(fetch_url.search.slice(1)); if (!post) { for (const [key, value] of Object.entries(params)) { url_params.append(key, value); @@ -203,70 +198,83 @@ il.repository.core = (function() { } fetch_url.search = url_params; - const method = (post) ? "POST" : "GET" ; - let config = { - method: method, + const method = (post) ? 'POST' : 'GET'; + const config = { + method, mode: 'same-origin', cache: 'no-cache', credentials: 'same-origin', redirect: 'follow', - referrerPolicy: 'same-origin' + referrerPolicy: 'same-origin', }; if (post) { config.body = formData; } return new Promise((resolve, reject) => { - fetch(fetch_url.href, config).then(response => { + fetch(fetch_url.href, config).then((response) => { if (response.ok) { - //const statusText = response.statusText; - response.text().then(text => - resolve(text) - ).catch(); + // const statusText = response.statusText; + response.text().then((text) => resolve(text)).catch(); } }).catch(); }); } - function fetchReplaceInner(el, url = '', params = {}) { + function fetchReplaceInner(el, url = '', params = {}, cb = null) { fetchHtml(url, params) - .then(html => { - setInnerHTML(el, html) - }).catch(); + .then((html) => { + setInnerHTML(el, html); + if (cb) { + cb(); + } + }).catch(); } function fetchReplace(el_id, url = '', params = {}) { fetchHtml(url, params) - .then(html => { - setOuterHTML(el_id, html) - }).catch(); + .then((html) => { + setOuterHTML(el_id, html); + }).catch(); } function fetchUrl(url = '', params = {}, args = {}, success_cb = null) { - _fetchHtml(url, params) - .then(response => { + const fetch_url = getFetchUrl(url); + const url_params = new URLSearchParams(fetch_url.search.slice(1)); + for (const [key, value] of Object.entries(params)) { + url_params.append(key, value); + } + fetch_url.search = url_params; + const config = { + method: 'GET', + mode: 'same-origin', + cache: 'no-cache', + credentials: 'same-origin', + redirect: 'follow', + referrerPolicy: 'same-origin', + }; + fetch(fetch_url.href, config).then((response) => { if (response.ok) { - //const statusText = response.statusText; - response.text().then(text => { - if (success_cb) { - success_cb({ - text: text, - args: args - }); - } + // const statusText = response.statusText; + response.text().then((text) => { + if (success_cb) { + success_cb({ + text, + args, + }); } - ).catch(); + }).catch(); } }).catch(); } return { - setInnerHTML: setInnerHTML, - setOuterHTML: setOuterHTML, - fetchHtml: fetchHtml, - fetchReplace: fetchReplace, - fetchReplaceInner: fetchReplaceInner, - trigger: trigger, - init: init + setInnerHTML, + setOuterHTML, + fetchHtml, + fetchUrl, + fetchReplace, + fetchReplaceInner, + trigger, + init, }; - }()); diff --git a/components/ILIAS/ResourceStorage/classes/Collections/View/RequestToDataTable.php b/components/ILIAS/ResourceStorage/classes/Collections/View/RequestToDataTable.php index 91183adb89d7..57cf9b9b9156 100755 --- a/components/ILIAS/ResourceStorage/classes/Collections/View/RequestToDataTable.php +++ b/components/ILIAS/ResourceStorage/classes/Collections/View/RequestToDataTable.php @@ -79,6 +79,7 @@ public function getComponents(): \Generator protected function buildTable(): Data { return $this->ui_factory->table()->data( + $this, '', // $this->request->getTitle() we already have the title in the panel [ self::F_TITLE => $this->ui_factory->table()->column()->text( @@ -95,7 +96,6 @@ protected function buildTable(): Data $this->language->txt(self::F_TYPE) )->withIsSortable(false), ], - $this )->withRequest( $this->http->request() )->withActions( diff --git a/components/ILIAS/ResourceStorage/classes/Container/DataProvider/TableDataProvider.php b/components/ILIAS/ResourceStorage/classes/Container/DataProvider/TableDataProvider.php index ba4e1cc4394a..688ef11da557 100755 --- a/components/ILIAS/ResourceStorage/classes/Container/DataProvider/TableDataProvider.php +++ b/components/ILIAS/ResourceStorage/classes/Container/DataProvider/TableDataProvider.php @@ -65,7 +65,25 @@ public function getEntries(): array $this->view_request->getWrapper()->getEntries() ); - // Currently no sorting is implemented + /** @var Dir[]|File[] $entries_at_current_level */ + usort($entries_at_current_level, function (File|Dir $a, File|Dir $b): int { + $size_a = $a instanceof Dir ? 0 : $a->getSize(); + $size_b = $b instanceof Dir ? 0 : $b->getSize(); + $type_a = $a instanceof Dir ? '' : $a->getMimeType(); + $type_b = $b instanceof Dir ? '' : $b->getMimeType(); + return match ($this->view_request->getSortation()) { + Request::BY_CREATION_DATE_DESC => $b->getModificationDate()->getTimestamp() <=> $a->getModificationDate()->getTimestamp(), + Request::BY_CREATION_DATE_ASC => $b->getModificationDate()->getTimestamp() <=> $a->getModificationDate()->getTimestamp(), + Request::BY_SIZE_DESC => $size_a - $size_b, + Request::BY_SIZE_ASC => $size_b - $size_a, + Request::BY_TITLE_DESC => strcasecmp($b->getTitle(), $a->getTitle()), + Request::BY_TITLE_ASC => strcasecmp($a->getTitle(), $b->getTitle()), + Request::BY_TYPE_DESC => strcasecmp($type_a, $type_b), + Request::BY_TYPE_ASC => strcasecmp($type_b, $type_a), + default => strcasecmp($a->getTitle(), $b->getTitle()), + }; + }); + return $entries_at_current_level; } diff --git a/components/ILIAS/ResourceStorage/classes/Container/View/ActionBuilder/ExternalActionProvider.php b/components/ILIAS/ResourceStorage/classes/Container/View/ActionBuilder/ExternalActionProvider.php index efa22ceb0709..c4cd333df12b 100755 --- a/components/ILIAS/ResourceStorage/classes/Container/View/ActionBuilder/ExternalActionProvider.php +++ b/components/ILIAS/ResourceStorage/classes/Container/View/ActionBuilder/ExternalActionProvider.php @@ -23,6 +23,7 @@ use ILIAS\components\ResourceStorage\Container\View\ActionBuilder\ActionProvider; use ILIAS\components\ResourceStorage\Container\View\ActionBuilder\TopAction; use ILIAS\components\ResourceStorage\Container\View\ActionBuilder\SingleAction; +use ILIAS\UI\Implementation\Component\Modal\RoundTrip; /** * @author Fabian Schmid @@ -30,7 +31,6 @@ */ final class ExternalActionProvider implements ActionProvider { - /** * @var SingleAction[] */ @@ -40,14 +40,19 @@ final class ExternalActionProvider implements ActionProvider */ private array $top_actions = []; + private array $top_actions_modals = []; + public function addSingleAction(string $key, SingleAction $action): void { $this->single_actions[$key] = $action; } - public function addTopAction(string $key, TopAction $action): void + public function addTopAction(string $key, TopAction $action, ?RoundTrip $modal = null): void { $this->top_actions[$key] = $action; + if ($modal) { + $this->top_actions_modals[] = $modal; + } } public function getTopActions(): array @@ -62,7 +67,7 @@ public function getSingleActions(Request $view_request): array public function getComponents(): array { - return []; + return $this->top_actions_modals; } } diff --git a/components/ILIAS/ResourceStorage/classes/Container/View/Configuration.php b/components/ILIAS/ResourceStorage/classes/Container/View/Configuration.php index d1f71d3d0579..0bcc3ae3343b 100755 --- a/components/ILIAS/ResourceStorage/classes/Container/View/Configuration.php +++ b/components/ILIAS/ResourceStorage/classes/Container/View/Configuration.php @@ -24,6 +24,8 @@ use ILIAS\ResourceStorage\Resource\StorableContainerResource; use ILIAS\Data\URI; use ILIAS\components\ResourceStorage\Container\View\ActionBuilder\ExternalSingleAction; +use ILIAS\components\ResourceStorage\Container\View\ActionBuilder\TopAction; +use ILIAS\UI\Component\Modal\RoundTrip; /** * @author Fabian Schmid @@ -31,6 +33,7 @@ final class Configuration { private ExternalActionProvider $action_provider; + private ExternalActionProvider $top_action_provider; private \ilCtrlInterface $ctrl; public function __construct( @@ -74,6 +77,13 @@ public function withExternalAction( return $this; } + public function withExternalTopAction(string $key, TopAction $topAction, ?RoundTrip $modal = null): self + { + $this->action_provider->addTopAction($key, $topAction, $modal); + + return $this; + } + public function getContainer(): StorableContainerResource { return $this->container; @@ -130,5 +140,4 @@ public function getActionProvider(): ExternalActionProvider { return $this->action_provider; } - } diff --git a/components/ILIAS/ResourceStorage/classes/Container/View/Request.php b/components/ILIAS/ResourceStorage/classes/Container/View/Request.php index 70dd4e1533e4..a42bf0c2e78c 100755 --- a/components/ILIAS/ResourceStorage/classes/Container/View/Request.php +++ b/components/ILIAS/ResourceStorage/classes/Container/View/Request.php @@ -80,6 +80,14 @@ final class Request * @var string */ public const BY_SIZE_ASC = 'by_size_asc'; + /** + * @var string + */ + public const BY_TYPE_DESC = 'by_type_desc'; + /** + * @var string + */ + public const BY_TYPE_ASC = 'by_type_asc'; /** * @var string */ diff --git a/components/ILIAS/ResourceStorage/classes/Container/View/RequestToDataTable.php b/components/ILIAS/ResourceStorage/classes/Container/View/RequestToDataTable.php index d7dbfd568247..b4154b49582f 100755 --- a/components/ILIAS/ResourceStorage/classes/Container/View/RequestToDataTable.php +++ b/components/ILIAS/ResourceStorage/classes/Container/View/RequestToDataTable.php @@ -165,23 +165,23 @@ public function getComponents(): \Generator protected function buildTable(): Data { return $this->ui_factory->table()->data( + $this, $this->request->getTitle(), // we already have the title in the panel [ self::F_TITLE => $this->ui_factory->table()->column()->text( $this->language->txt(self::F_TITLE) - )->withIsSortable(false), + )->withIsSortable(true), self::F_SIZE => $this->ui_factory->table()->column()->text( $this->language->txt(self::F_SIZE) - )->withIsSortable(false), + )->withIsSortable(true), self::F_MODIFICATION_DATE => $this->ui_factory->table()->column()->date( $this->language->txt(self::F_MODIFICATION_DATE), $this->data_factory->dateFormat()->germanLong() - )->withIsSortable(false), + )->withIsSortable(true), self::F_TYPE => $this->ui_factory->table()->column()->text( $this->language->txt(self::F_TYPE) - )->withIsSortable(false), + )->withIsSortable(true), ], - $this )->withRequest( $this->http->request() )->withActions( @@ -203,7 +203,15 @@ public function getRows( $regex_storage = []; - foreach ($this->data_provider->getEntries() as $entry) { + $entries = $this->data_provider->getEntries(); + // cut entries + $entries = array_slice( + $entries, + $range->getStart(), + $range->getLength() + ); + + foreach ($entries as $entry) { $is_dir = $entry instanceof Dir; $path_inside_zip = $entry->getPathInsideZIP(); @@ -295,6 +303,12 @@ private function initSortingAndOrdering(Range $range, Order $order): void case self::F_MODIFICATION_DATE . '_' . Order::DESC: $this->data_provider->getViewRequest()->setSortation(Request::BY_CREATION_DATE_DESC); break; + case self::F_TYPE . '_' . Order::ASC: + $this->data_provider->getViewRequest()->setSortation(Request::BY_TYPE_ASC); + break; + case self::F_TYPE . '_' . Order::DESC: + $this->data_provider->getViewRequest()->setSortation(Request::BY_TYPE_DESC); + break; } } diff --git a/components/ILIAS/ResourceStorage/classes/Container/Wrapper/ContainerWrapper.php b/components/ILIAS/ResourceStorage/classes/Container/Wrapper/ContainerWrapper.php index 114810f6bb04..d139b0d1df93 100755 --- a/components/ILIAS/ResourceStorage/classes/Container/Wrapper/ContainerWrapper.php +++ b/components/ILIAS/ResourceStorage/classes/Container/Wrapper/ContainerWrapper.php @@ -125,7 +125,8 @@ public function unzip(string $path_inside_zip): bool } // save stream to temporary file - $tmp_file = tempnam(sys_get_temp_dir(), 'ilias_zip_'); + $tmp_directory = defined('CLIENT_DATA_DIR') ? \CLIENT_DATA_DIR . '/temp' : sys_get_temp_dir(); + $tmp_file = tempnam($tmp_directory, 'ilias_zip_'); /** @var ZIPStream $stream */ $return = file_put_contents($tmp_file, $stream->detach()); diff --git a/components/ILIAS/ResourceStorage/classes/Container/class.ilContainerResourceGUI.php b/components/ILIAS/ResourceStorage/classes/Container/class.ilContainerResourceGUI.php index 7b02731ba8c0..a830c9cd2fa3 100755 --- a/components/ILIAS/ResourceStorage/classes/Container/class.ilContainerResourceGUI.php +++ b/components/ILIAS/ResourceStorage/classes/Container/class.ilContainerResourceGUI.php @@ -273,7 +273,13 @@ private function addDirectory(): void } $modal = $this->standard_action_provider->getAddDirectoryModal()->withRequest($this->http->request()); - $directory_name = $this->view_request->getPath() . $modal->getData()[0] ?? ''; + $directory_name = $modal->getData()[0] ?? ''; + if (empty($directory_name)) { + $this->main_tpl->setOnScreenMessage('failure', $this->language->txt('msg_error_adding_directory'), true); + $this->ctrl->redirect($this, self::CMD_INDEX); + return; + } + $directory_name = $this->view_request->getPath() . $directory_name; $success = $this->irss->manageContainer()->createDirectoryInsideContainer( $this->view_configuration->getContainer()->getIdentification(), @@ -333,7 +339,12 @@ private function postUpload(): void $this->abortWithPermissionDenied(); return; } - $this->main_tpl->setOnScreenMessage('success', $this->language->txt('rids_appended'), true); + if ($this->http->request()->getParsedBody() === []) { // nothing uploaded + $this->main_tpl->setOnScreenMessage('failure', $this->language->txt('rids_appended_failed'), true); + } else { + $this->main_tpl->setOnScreenMessage('success', $this->language->txt('rids_appended'), true); + } + $this->ctrl->redirect($this, self::CMD_INDEX); } diff --git a/components/ILIAS/ResourceStorage/classes/IRSSEventLogObserver.php b/components/ILIAS/ResourceStorage/classes/IRSSEventLogObserver.php new file mode 100644 index 000000000000..75b5f5f7b256 --- /dev/null +++ b/components/ILIAS/ResourceStorage/classes/IRSSEventLogObserver.php @@ -0,0 +1,60 @@ + + */ +class IRSSEventLogObserver implements Observer +{ + public function __construct(private \ilLogger $logger) + { + } + + public function getId(): string + { + return self::class; + } + + private function appendData(string $to_message, ?Data $data = null): string + { + return $to_message . ': ' . ($data ? json_encode($data->getArrayCopy()) : ''); + } + + + public function update(Event $event, ?Data $data): void + { + match ($event->value) { + Event::COLLECTION_RESOURCE_ADDED => $this->logger->info($this->appendData("Collection resource added", $data)), + Event::FLAVOUR_BUILD_SUCCESS => $this->logger->info($this->appendData("Flavour build success", $data)), + Event::FLAVOUR_BUILD_FAILED => $this->logger->warning($this->appendData("Flavour build failed", $data)), + default => $this->logger->debug($this->appendData($event->value, $data)) + }; + } + + public function updateFailed(\Throwable $e, Event $event, ?Data $data): void + { + // nothing to do + } + +} diff --git a/components/ILIAS/ResourceStorage/classes/Setup/class.ilResourceStorageMigrationHelper.php b/components/ILIAS/ResourceStorage/classes/Setup/class.ilResourceStorageMigrationHelper.php index 238eb7462a2e..36e73ed7aa3f 100755 --- a/components/ILIAS/ResourceStorage/classes/Setup/class.ilResourceStorageMigrationHelper.php +++ b/components/ILIAS/ResourceStorage/classes/Setup/class.ilResourceStorageMigrationHelper.php @@ -336,7 +336,8 @@ public function moveDirectoryToContainerResource( $zip->addDirectory($absolute_path_to_directory); try { $zip_stream = $zip->get(); - } catch (Throwable) { + } catch (Throwable $e) { + $zip->destroy(); return null; // could not create zip } @@ -357,6 +358,9 @@ public function moveDirectoryToContainerResource( $resource->addStakeholder($this->stakeholder); $this->resource_builder->store($resource); + // ZIP archive is not needed anymore + $zip->destroy(); + return $resource->getIdentification(); } diff --git a/components/ILIAS/ResourceStorage/service.xml b/components/ILIAS/ResourceStorage/service.xml index 24e8eec3a4dd..b2f9e4feaff5 100755 --- a/components/ILIAS/ResourceStorage/service.xml +++ b/components/ILIAS/ResourceStorage/service.xml @@ -3,4 +3,5 @@ + diff --git a/components/ILIAS/ResourceStorage/src/Events/Event.php b/components/ILIAS/ResourceStorage/src/Events/Event.php index f70130445735..5a66840e2e45 100644 --- a/components/ILIAS/ResourceStorage/src/Events/Event.php +++ b/components/ILIAS/ResourceStorage/src/Events/Event.php @@ -47,4 +47,7 @@ enum Event: string * event string for all possible events. */ case ALL = '*'; + + case FLAVOUR_BUILD_SUCCESS = 'flavour:build:success'; + case FLAVOUR_BUILD_FAILED = 'flavour:build:failed'; } diff --git a/components/ILIAS/ResourceStorage/src/Events/FlavourData.php b/components/ILIAS/ResourceStorage/src/Events/FlavourData.php new file mode 100644 index 000000000000..f7fd376b02dd --- /dev/null +++ b/components/ILIAS/ResourceStorage/src/Events/FlavourData.php @@ -0,0 +1,48 @@ + + * @internal + */ +class FlavourData extends Data +{ + public function __construct( + ResourceIdentification $rid, + FlavourDefinition $definition, + Flavour $flavour, + ?\Throwable $e = null + ) { + $data = [ + 'rid' => $rid->serialize(), + 'definition' => $definition->getInternalName(), + 'flavour' => $flavour->getPersistingName(), + 'exception' => $e ? $e->getMessage() : null + ]; + parent::__construct($data, \ArrayObject::ARRAY_AS_PROPS); + } + +} diff --git a/components/ILIAS/ResourceStorage/src/Flavour/Engine/ExifEngine.php b/components/ILIAS/ResourceStorage/src/Flavour/Engine/ExifEngine.php new file mode 100644 index 000000000000..63f58956f288 --- /dev/null +++ b/components/ILIAS/ResourceStorage/src/Flavour/Engine/ExifEngine.php @@ -0,0 +1,57 @@ + + */ +class ExifEngine implements Engine +{ + use PHPMemoryLimit; + + public function __construct() + { + } + + public function supports(string $suffix): bool + { + return true; + } + + public function isRunning(): bool + { + return extension_loaded('exif'); + } + + public function read(string $file_path): array + { + try { + $exif = exif_read_data($file_path); + if ($exif === false) { + return []; + } + return $exif; + } catch (\Throwable $e) { + return []; + } + } + +} diff --git a/components/ILIAS/ResourceStorage/src/Flavour/Engine/ImagickEngine.php b/components/ILIAS/ResourceStorage/src/Flavour/Engine/ImagickEngine.php index da2ee88681ec..80300e5d70c2 100755 --- a/components/ILIAS/ResourceStorage/src/Flavour/Engine/ImagickEngine.php +++ b/components/ILIAS/ResourceStorage/src/Flavour/Engine/ImagickEngine.php @@ -26,6 +26,8 @@ class ImagickEngine implements Engine { use PHPMemoryLimit; + + /** @var list */ protected array $whitelist = [ 'jpg', 'jpeg', @@ -40,27 +42,28 @@ class ImagickEngine implements Engine 'pdf', 'svg', ]; + /** @var list */ protected array $supported; public function __construct() { $this->supported = array_intersect( array_map( - fn($item): string => strtolower($item), - \Imagick::queryFormats() + static fn(string $item): string => strtolower($item), + $this->isRunning() ? \Imagick::queryFormats() : [] ), $this->whitelist - ) ?? []; + ); } public function supports(string $suffix): bool { - return in_array(strtolower($suffix), $this->supported, true); + return \in_array(strtolower($suffix), $this->supported, true); } public function isRunning(): bool { - return extension_loaded('imagick') && class_exists(\Imagick::class); + return \extension_loaded('imagick') && class_exists(\Imagick::class); } } diff --git a/components/ILIAS/ResourceStorage/src/Flavour/FlavourBuilder.php b/components/ILIAS/ResourceStorage/src/Flavour/FlavourBuilder.php index 3e370ebc7dc2..4347622fee36 100755 --- a/components/ILIAS/ResourceStorage/src/Flavour/FlavourBuilder.php +++ b/components/ILIAS/ResourceStorage/src/Flavour/FlavourBuilder.php @@ -1,4 +1,5 @@ @@ -49,7 +53,8 @@ public function __construct( private readonly Factory $flavour_machine_factory, private readonly ResourceBuilder $resource_builder, private readonly StorageHandlerFactory $storage_handler_factory, - private readonly StreamAccess $stream_access + private readonly StreamAccess $stream_access, + private readonly Subject $events ) { } @@ -248,6 +253,7 @@ protected function runMachine( $stream->rewind(); } catch (\Throwable) { // error while reading file stream, cannot process + $this->events->notify(Event::FLAVOUR_BUILD_FAILED, new FlavourData($rid, $definition, $flavour, $t)); return $flavour; } @@ -279,8 +285,9 @@ protected function runMachine( $result->getIndex() ); } - } catch (\Throwable) { + } catch (\Throwable $t) { // error while processing stream, cannot process + $this->events->notify(Event::FLAVOUR_BUILD_FAILED, new FlavourData($rid, $definition, $flavour, $t)); return $flavour; } @@ -289,6 +296,8 @@ protected function runMachine( $this->storeFlavourStreams($flavour, $storable_streams); } + $this->events->notify(Event::FLAVOUR_BUILD_SUCCESS, new FlavourData($rid, $definition, $flavour)); + return $flavour; } diff --git a/components/ILIAS/ResourceStorage/src/Flavour/Machine/DefaultMachines/CropSquare.php b/components/ILIAS/ResourceStorage/src/Flavour/Machine/DefaultMachines/CropSquare.php index dd80bf26b57f..6b1841d9379c 100755 --- a/components/ILIAS/ResourceStorage/src/Flavour/Machine/DefaultMachines/CropSquare.php +++ b/components/ILIAS/ResourceStorage/src/Flavour/Machine/DefaultMachines/CropSquare.php @@ -1,4 +1,5 @@ @@ -43,7 +47,6 @@ public function getId(): string return self::ID; } - public function canHandleDefinition(FlavourDefinition $definition): bool { return $definition instanceof CropToSquare; @@ -54,7 +57,6 @@ public function dependsOnEngine(): ?string return GDEngine::class; } - public function processStream( FileInformation $information, FileStream $stream, @@ -68,13 +70,21 @@ public function processStream( return; } - $stream_path = $stream->getMetadata('uri'); + $stream_path = $stream->getMetadata()['uri'] ?? ''; + $must_flip = $this->maybeRotate($stream_path, $image); + if ($stream_path === 'php://memory') { [$width, $height] = getimagesizefromstring((string) $stream); } else { [$width, $height] = getimagesize($stream_path); } + if ($must_flip) { + $tmp = $width; + $width = $height; + $height = $tmp; + } + if ($width > $height) { $y = 0; $x = (int) (($width - $height) / 2); @@ -88,6 +98,7 @@ public function processStream( $size = (int) $for_definition->getMaxSize(); $thumb = imagecreatetruecolor($size, $size); + imagecopyresampled( $thumb, $image, @@ -101,7 +112,6 @@ public function processStream( $smallest_side ); - imagedestroy($image); $stream = $this->to($thumb, $for_definition->getQuality()); @@ -113,4 +123,53 @@ public function processStream( $for_definition->persist() ); } + + protected function maybeRotate(string $stream_path, \GdImage &$image): bool + { + // if PHP exif is installed, this is quite easy + $exif = new ExifEngine(); + if ($exif->isRunning() && ($exif_data = $exif->read($stream_path)) !== []) { + switch ($exif_data['Orientation'] ?? null) { + case 8: + $image = imagerotate($image, 90, 0); + return true; + case 3: + $image = imagerotate($image, 180, 0); + return false; + case 6: + $image = imagerotate($image, -90, 0); + return true; + default: + return false; + } + } + // otherwise we can use Imagick (if installed) + $imagick = new ImagickEngine(); + if ($imagick->isRunning()) { + $imagick = new \Imagick($stream_path); + $image_orientation = $imagick->getImageOrientation(); + switch ($image_orientation) { + case \Imagick::ORIENTATION_RIGHTTOP: + $imagick->rotateImage('none', 90); + $imagick->setImageOrientation(\Imagick::ORIENTATION_TOPLEFT); + $image = $this->from(Streams::ofString($imagick->getImageBlob())); + return true; + case \Imagick::ORIENTATION_BOTTOMRIGHT: + $imagick->rotateImage('none', 180); + $imagick->setImageOrientation(\Imagick::ORIENTATION_TOPLEFT); + $image = $this->from(Streams::ofString($imagick->getImageBlob())); + return false; + case \Imagick::ORIENTATION_LEFTBOTTOM: + $imagick->rotateImage('none', -90); + $imagick->setImageOrientation(\Imagick::ORIENTATION_TOPLEFT); + $image = $this->from(Streams::ofString($imagick->getImageBlob())); + return true; + default: + return false; + } + } + + // we did not find any way to rotate the image + return false; + } } diff --git a/components/ILIAS/ResourceStorage/src/Preloader/SecureString.php b/components/ILIAS/ResourceStorage/src/Preloader/SecureString.php index 0abd799f3914..ff983b905787 100755 --- a/components/ILIAS/ResourceStorage/src/Preloader/SecureString.php +++ b/components/ILIAS/ResourceStorage/src/Preloader/SecureString.php @@ -30,10 +30,26 @@ trait SecureString { protected function secure(string $string): string { + // Normalize UTF-8 string, remove any invalid sequences + $temp_string = mb_convert_encoding($string, 'UTF-8', 'UTF-8'); + + // Remove invalid UTF-8 sequences that could be 4-byte UTF-8 (utf8mb4) + $temp_string = preg_replace('/[\x{10000}-\x{10FFFF}]/u', '', $temp_string); + + // Remove control characters + $temp_string = preg_replace('#\p{C}+#u', '', $temp_string); + if ($temp_string === null) { + return ''; + // we could harden that by throwing an exception + // throw new \RuntimeException( + // 'Failed to remove control characters from string `' . $string . '`. with message: ' + // . preg_last_error_msg() + // ); + } + + // Sanitize by stripping HTML tags and encoding special characters return htmlspecialchars( - strip_tags( - (string) preg_replace('#\p{C}+#u', '', $string) - ), + strip_tags($temp_string), ENT_QUOTES, 'UTF-8', false diff --git a/components/ILIAS/ResourceStorage/src/Resource/InfoResolver/StreamInfoResolver.php b/components/ILIAS/ResourceStorage/src/Resource/InfoResolver/StreamInfoResolver.php index 49072cf59bfb..1d089ad2cfb6 100755 --- a/components/ILIAS/ResourceStorage/src/Resource/InfoResolver/StreamInfoResolver.php +++ b/components/ILIAS/ResourceStorage/src/Resource/InfoResolver/StreamInfoResolver.php @@ -129,7 +129,8 @@ protected function initFileName(?string $file_name = null): void return; } $this->file_name = basename($this->path); - if ($this->file_name === 'memory' || $this->file_name === 'input') { // in case the stream is ofString or of php://input + // in case the stream is ofString or of php://input, php://memory or php://input + if ($this->file_name === 'memory' || $this->file_name === 'input' || $this->file_name === 'temp') { $this->file_name = $this->getRevisionTitle(); } } diff --git a/components/ILIAS/ResourceStorage/src/Resource/ResourceBuilder.php b/components/ILIAS/ResourceStorage/src/Resource/ResourceBuilder.php index 47852de1e9ea..8a4752a162e5 100755 --- a/components/ILIAS/ResourceStorage/src/Resource/ResourceBuilder.php +++ b/components/ILIAS/ResourceStorage/src/Resource/ResourceBuilder.php @@ -362,7 +362,7 @@ function () use ($resource): void { $this->resource_repository->store($resource); foreach ($resource->getAllRevisionsIncludingDraft() as $revision) { - $this->storeRevision($revision); + $this->storeRevision($revision, $resource); } foreach ($resource->getStakeholders() as $stakeholder) { @@ -415,7 +415,7 @@ public function clone(StorableResource $resource): StorableResource * @description Store one Revision * @throws FileNamePolicyException */ - public function storeRevision(Revision $revision): void + public function storeRevision(Revision $revision, ?StorableResource $resource = null): void { $storage_handler = empty($revision->getStorageID()) ? $this->primary_storage_handler @@ -437,6 +437,19 @@ public function storeRevision(Revision $revision): void } $this->revision_repository->store($revision); $this->information_repository->store($revision->getInformation(), $revision); + + // we replace the revision with the populated one + if ($resource !== null) { + $replace_revision = new FileRevision($revision->getIdentification()); + $replace_revision->setVersionNumber($revision->getVersionNumber()); + $replace_revision->setOwnerId($revision->getOwnerId()); + $replace_revision->setTitle($revision->getTitle()); + $replace_revision->setStatus($revision->getStatus()); + $replace_revision->setInformation($revision->getInformation()); + $replace_revision->setStorageID($revision->getStorageID()); + + $resource->replaceRevision($replace_revision); + } } /** @@ -491,9 +504,9 @@ public function extractStream(Revision $revision): FileStream { switch (true) { case $revision instanceof FileStreamRevision: - return $revision->getStream(); + return Streams::ofReattachableResource($revision->getStream()->detach()); case $revision instanceof UploadedFileRevision: - return Streams::ofResource(fopen($revision->getUpload()->getPath(), 'rb')); + return Streams::ofReattachableResource(fopen($revision->getUpload()->getPath(), 'rb')); case $revision instanceof CloneRevision: return $revision->getRevisionToClone()->getStream(); case $revision instanceof FileRevision: diff --git a/components/ILIAS/ResourceStorage/src/Services.php b/components/ILIAS/ResourceStorage/src/Services.php index ba1e175aecb8..811632eb5446 100755 --- a/components/ILIAS/ResourceStorage/src/Services.php +++ b/components/ILIAS/ResourceStorage/src/Services.php @@ -121,7 +121,8 @@ public function __construct( $machine_factory, $resource_builder, $storage_handler_factory, - $stream_access + $stream_access, + $this->events ); $this->flavours = new Flavours( diff --git a/components/ILIAS/ResourceStorage/tests/Flavours/FlavourTest.php b/components/ILIAS/ResourceStorage/tests/Flavours/FlavourTest.php index 840a439fed14..b3e5cdb1a309 100755 --- a/components/ILIAS/ResourceStorage/tests/Flavours/FlavourTest.php +++ b/components/ILIAS/ResourceStorage/tests/Flavours/FlavourTest.php @@ -35,6 +35,7 @@ use ILIAS\ResourceStorage\StorageHandler\StorageHandler; use ILIAS\ResourceStorage\StorageHandler\StorageHandlerFactory; use ILIAS\ResourceStorage\Consumer\StreamAccess\StreamResolver; +use ILIAS\ResourceStorage\Events\Subject; /** * Class FlavorTest @@ -80,7 +81,8 @@ public function testDefinitionVariantNameLengths(): void $this->machine_factory, $this->resource_builder, $this->storage_handler_factory, - $this->stream_access + $this->stream_access, + new Subject() ); // Length OK @@ -118,7 +120,8 @@ public function testHasFlavour(): void $this->machine_factory, $this->resource_builder, $this->storage_handler_factory, - $this->stream_access + $this->stream_access, + new Subject() ); // Expectations @@ -167,7 +170,8 @@ public function testNewFlavour(): void $this->machine_factory, $this->resource_builder, $this->storage_handler_factory, - $this->stream_access + $this->stream_access, + new Subject() ); // Expectations diff --git a/components/ILIAS/RootFolder/classes/class.ilObjRootFolderGUI.php b/components/ILIAS/RootFolder/classes/class.ilObjRootFolderGUI.php index 05db80da210c..e98f67ec675d 100755 --- a/components/ILIAS/RootFolder/classes/class.ilObjRootFolderGUI.php +++ b/components/ILIAS/RootFolder/classes/class.ilObjRootFolderGUI.php @@ -19,6 +19,7 @@ declare(strict_types=1); use ILIAS\RootFolder\StandardGUIRequest; +use ILIAS\ILIASObject\Properties\Translations\TranslationGUI; /** * Class ilObjRootFolderGUI @@ -27,7 +28,7 @@ * * @ilCtrl_Calls ilObjRootFolderGUI: ilPermissionGUI, ilContainerPageGUI * @ilCtrl_Calls ilObjRootFolderGUI: ilColumnGUI, ilObjectCopyGUI, ilObjectContentStyleSettingsGUI - * @ilCtrl_Calls ilObjRootFolderGUI: ilCommonActionDispatcherGUI, ilObjectTranslationGUI + * @ilCtrl_Calls ilObjRootFolderGUI: ilCommonActionDispatcherGUI, ILIAS\ILIASObject\Properties\Translations\TranslationGUI * @ilCtrl_Calls ilObjRootFolderGUI: ilRepositoryTrashGUI */ class ilObjRootFolderGUI extends ilContainerGUI @@ -156,11 +157,23 @@ public function executeCommand(): void $this->ctrl->forwardCommand($gui); break; - case 'ilobjecttranslationgui': + case strtolower(TranslationGUI::class): $this->checkPermissionBool("write"); $this->prepareOutput(); $this->setEditTabs("settings_trans"); - $transgui = new ilObjectTranslationGUI($this); + $transgui = new TranslationGUI( + $this->getObject(), + $this->lng, + $this->access, + $this->user, + $this->ctrl, + $this->tpl, + $this->ui_factory, + $this->ui_renderer, + $this->http, + $this->refinery, + $this->toolbar + ); $this->ctrl->forwardCommand($transgui); break; @@ -247,7 +260,7 @@ protected function setEditTabs( $this->tabs_gui->addSubTab( "settings_trans", $this->lng->txt("obj_multilinguality"), - $this->ctrl->getLinkTargetByClass("ilobjecttranslationgui", "") + $this->ctrl->getLinkTargetByClass(TranslationGUI::class, "") ); diff --git a/components/ILIAS/Saml/classes/class.ilAuthFrontendCredentialsSaml.php b/components/ILIAS/Saml/classes/class.ilAuthFrontendCredentialsSaml.php index 39297240e1f7..18ca54d1b002 100755 --- a/components/ILIAS/Saml/classes/class.ilAuthFrontendCredentialsSaml.php +++ b/components/ILIAS/Saml/classes/class.ilAuthFrontendCredentialsSaml.php @@ -20,10 +20,6 @@ use Psr\Http\Message\ServerRequestInterface; -/** - * Class ilAuthFrontendCredentialsSaml - * @author Michael Jansen - */ final class ilAuthFrontendCredentialsSaml extends ilAuthFrontendCredentials { private string $return_to = ''; diff --git a/components/ILIAS/Saml/classes/class.ilAuthProviderSaml.php b/components/ILIAS/Saml/classes/class.ilAuthProviderSaml.php index 07ec116f52e9..f77740844a91 100755 --- a/components/ILIAS/Saml/classes/class.ilAuthProviderSaml.php +++ b/components/ILIAS/Saml/classes/class.ilAuthProviderSaml.php @@ -18,17 +18,14 @@ declare(strict_types=1); -/** - * Class ilAuthProviderSaml - */ class ilAuthProviderSaml extends ilAuthProvider implements ilAuthProviderAccountMigrationInterface { - private const LOG_COMPONENT = 'auth'; + private const string LOG_COMPONENT = 'auth'; - private const ERR_WRONG_LOGIN = 'err_wrong_login'; + private const string ERR_WRONG_LOGIN = 'err_wrong_login'; - private const SESSION_TMP_ATTRIBUTES = 'tmp_attributes'; - private const SESSION_TMP_RETURN_TO = 'tmp_return_to'; + private const string SESSION_TMP_ATTRIBUTES = 'tmp_attributes'; + private const string SESSION_TMP_RETURN_TO = 'tmp_return_to'; private ilSamlIdp $idp; private readonly ilLanguage $lng; diff --git a/components/ILIAS/Saml/classes/class.ilSamlAuthFactory.php b/components/ILIAS/Saml/classes/class.ilSamlAuthFactory.php index 0bd4c57e0bbf..4f51a4af715e 100755 --- a/components/ILIAS/Saml/classes/class.ilSamlAuthFactory.php +++ b/components/ILIAS/Saml/classes/class.ilSamlAuthFactory.php @@ -20,12 +20,9 @@ use ILIAS\Filesystem\Exception\IOException; -/** - * Class ilSamlAuthFactory - */ final class ilSamlAuthFactory { - private const METADATA_PATH = 'auth/saml/config'; + private const string METADATA_PATH = 'auth/saml/config'; /** * @throws Exception diff --git a/components/ILIAS/Saml/classes/class.ilSamlIdp.php b/components/ILIAS/Saml/classes/class.ilSamlIdp.php index 47cd18df1fda..06d1d9dbe209 100755 --- a/components/ILIAS/Saml/classes/class.ilSamlIdp.php +++ b/components/ILIAS/Saml/classes/class.ilSamlIdp.php @@ -18,21 +18,17 @@ declare(strict_types=1); -/** - * Class ilSamlIdp - * @author Michael Jansen - */ final class ilSamlIdp { - private const PROP_IDP_ID = 'idp_id'; - private const PROP_IS_ACTIVE = 'is_active'; - private const PROP_DEFAULT_ROLE_ID = 'default_role_id'; - private const PROP_UID_CLAIM = 'uid_claim'; - private const PROP_LOGIN_CLAIM = 'login_claim'; - private const PROP_ENTITY_ID = 'entity_id'; - private const PROP_SYNC_STATUS = 'sync_status'; - private const PROP_ALLOW_LOCAL_AUTH = 'allow_local_auth'; - private const PROP_ACCOUNT_MIGR_STATUS = 'account_migr_status'; + private const string PROP_IDP_ID = 'idp_id'; + private const string PROP_IS_ACTIVE = 'is_active'; + private const string PROP_DEFAULT_ROLE_ID = 'default_role_id'; + private const string PROP_UID_CLAIM = 'uid_claim'; + private const string PROP_LOGIN_CLAIM = 'login_claim'; + private const string PROP_ENTITY_ID = 'entity_id'; + private const string PROP_SYNC_STATUS = 'sync_status'; + private const string PROP_ALLOW_LOCAL_AUTH = 'allow_local_auth'; + private const string PROP_ACCOUNT_MIGR_STATUS = 'account_migr_status'; private readonly ilDBInterface $db; /** @var self[] */ diff --git a/components/ILIAS/Saml/classes/class.ilSamlIdpTableGUI.php b/components/ILIAS/Saml/classes/class.ilSamlIdpTableGUI.php index ec8080e1225d..22cd852ff0e6 100755 --- a/components/ILIAS/Saml/classes/class.ilSamlIdpTableGUI.php +++ b/components/ILIAS/Saml/classes/class.ilSamlIdpTableGUI.php @@ -18,24 +18,24 @@ declare(strict_types=1); -final class ilSamlIdpTableGUI implements \ILIAS\UI\Component\Table\DataRetrieval +final readonly class ilSamlIdpTableGUI implements \ILIAS\UI\Component\Table\DataRetrieval { /** @var list */ private array $idps; - private readonly ILIAS\UI\URLBuilder $url_builder; - private readonly ILIAS\UI\URLBuilderToken $action_parameter_token; - private readonly ILIAS\UI\URLBuilderToken $row_id_token; + private ILIAS\UI\URLBuilder $url_builder; + private ILIAS\UI\URLBuilderToken $action_parameter_token; + private ILIAS\UI\URLBuilderToken $row_id_token; public function __construct( - private readonly ilSamlSettingsGUI $parent_gui, - private readonly \ILIAS\UI\Factory $ui_factory, - private readonly \ILIAS\UI\Renderer $ui_renderer, - private readonly ilLanguage $lng, - private readonly ilCtrlInterface $ctrl, - private readonly \Psr\Http\Message\ServerRequestInterface $http_request, - private readonly \ILIAS\Data\Factory $df, - private readonly string $parent_cmd, - private readonly bool $has_write_access + private ilSamlSettingsGUI $parent_gui, + private \ILIAS\UI\Factory $ui_factory, + private \ILIAS\UI\Renderer $ui_renderer, + private ilLanguage $lng, + private ilCtrlInterface $ctrl, + private \Psr\Http\Message\ServerRequestInterface $http_request, + private \ILIAS\Data\Factory $df, + private string $parent_cmd, + private bool $has_write_access ) { $this->idps = ilSamlIdp::getAllIdps(); @@ -60,9 +60,9 @@ public function get(): \ILIAS\UI\Component\Table\Data return $this->ui_factory ->table() ->data( + $this, $this->lng->txt('auth_saml_idps'), $this->getColumnDefinition(), - $this ) ->withId(self::class) ->withOrder(new \ILIAS\Data\Order('title', \ILIAS\Data\Order::ASC)) diff --git a/components/ILIAS/Saml/classes/class.ilSamlMappedUserAttributeValueParser.php b/components/ILIAS/Saml/classes/class.ilSamlMappedUserAttributeValueParser.php index bad99b4b2a76..35bad7ec27cb 100755 --- a/components/ILIAS/Saml/classes/class.ilSamlMappedUserAttributeValueParser.php +++ b/components/ILIAS/Saml/classes/class.ilSamlMappedUserAttributeValueParser.php @@ -18,20 +18,16 @@ declare(strict_types=1); -/** - * Class ilSamlMappedUserAttributeValueParser - * @author Michael Jansen - */ -final class ilSamlMappedUserAttributeValueParser +final readonly class ilSamlMappedUserAttributeValueParser { - private const ATTR_REGEX = '/^(.*?)(\|(\d+))?$/'; + private const string ATTR_REGEX = '/^(.*?)(\|(\d+))?$/'; /** * @param array $userData */ public function __construct( - private readonly ilExternalAuthUserAttributeMappingRule $rule, - private readonly array $userData + private ilExternalAuthUserAttributeMappingRule $rule, + private array $userData ) { } diff --git a/components/ILIAS/Saml/classes/class.ilSamlSettings.php b/components/ILIAS/Saml/classes/class.ilSamlSettings.php index 5754dcb8527c..8f69ecb1235f 100755 --- a/components/ILIAS/Saml/classes/class.ilSamlSettings.php +++ b/components/ILIAS/Saml/classes/class.ilSamlSettings.php @@ -18,10 +18,6 @@ declare(strict_types=1); -/** - * Class ilSamlSettings - * @author Michael Jansen - */ final class ilSamlSettings { private static ?self $instance = null; diff --git a/components/ILIAS/Saml/classes/class.ilSamlSettingsGUI.php b/components/ILIAS/Saml/classes/class.ilSamlSettingsGUI.php index 897b8eafc1fd..f78784d30286 100755 --- a/components/ILIAS/Saml/classes/class.ilSamlSettingsGUI.php +++ b/components/ILIAS/Saml/classes/class.ilSamlSettingsGUI.php @@ -24,46 +24,42 @@ use ILIAS\Data\Factory; use ILIAS\UI\Component\Input\Container\Form\Standard as StandardForm; -/** - * Class ilSamlSettingsGUI - * @author Michael Jansen - */ final class ilSamlSettingsGUI { - private const VIEW_MODE_GLOBAL = 1; - private const VIEW_MODE_SINGLE = 2; + private const int VIEW_MODE_GLOBAL = 1; + private const int VIEW_MODE_SINGLE = 2; - public const DEFAULT_CMD = 'listIdps'; + public const string DEFAULT_CMD = 'listIdps'; - private const PERMISSION_WRITE = 'write'; + private const string PERMISSION_WRITE = 'write'; - private const REQUEST_PARAM_SAML_IDP_ID = 'saml_idp_id'; - private const REQUEST_PARAM_SAML_IDP_IDS = 'saml_idp_ids'; + private const string REQUEST_PARAM_SAML_IDP_ID = 'saml_idp_id'; + private const string REQUEST_PARAM_SAML_IDP_IDS = 'saml_idp_ids'; - private const MESSAGE_TYPE_FAILURE = 'failure'; - private const MESSAGE_TYPE_SUCCESS = 'success'; + private const string MESSAGE_TYPE_FAILURE = 'failure'; + private const string MESSAGE_TYPE_SUCCESS = 'success'; - private const LNG_SAVED_SUCCESSFULLY = 'saved_successfully'; - private const LNG_AUTH_SAML_USER_MAPPING = 'auth_saml_user_mapping'; - private const LNG_LOGIN_FORM = 'login_form'; - private const LNG_CANCEL = 'cancel'; + private const string LNG_SAVED_SUCCESSFULLY = 'saved_successfully'; + private const string LNG_AUTH_SAML_USER_MAPPING = 'auth_saml_user_mapping'; + private const string LNG_LOGIN_FORM = 'login_form'; + private const string LNG_CANCEL = 'cancel'; - private const CMD_SHOW_SETTINGS = 'showSettings'; - private const CMD_SAVE_NEW_IDP = 'saveNewIdp'; - private const CMD_SAVE_SETTINGS = 'saveSettings'; - private const CMD_SHOW_IDP_SETTINGS = 'showIdpSettings'; - private const CMT_SAVE_IDP_SETTINGS = 'saveIdpSettings'; - private const CMD_SAVE = 'save'; - private const CMD_SAVE_USER_ATTRIBUTE_MAPPING = 'saveUserAttributeMapping'; + private const string CMD_SHOW_SETTINGS = 'showSettings'; + private const string CMD_SAVE_NEW_IDP = 'saveNewIdp'; + private const string CMD_SAVE_SETTINGS = 'saveSettings'; + private const string CMD_SHOW_IDP_SETTINGS = 'showIdpSettings'; + private const string CMT_SAVE_IDP_SETTINGS = 'saveIdpSettings'; + private const string CMD_SAVE = 'save'; + private const string CMD_SAVE_USER_ATTRIBUTE_MAPPING = 'saveUserAttributeMapping'; - private const PROP_UPDATE_SUFFIX = '_update'; + private const string PROP_UPDATE_SUFFIX = '_update'; - private const METADATA_STORAGE_KEY = 'metadata'; + private const string METADATA_STORAGE_KEY = 'metadata'; /** * @var string[] */ - private const GLOBAL_COMMANDS = [ + private const array GLOBAL_COMMANDS = [ self::DEFAULT_CMD, 'showAddIdpForm', self::CMD_SHOW_SETTINGS, @@ -75,7 +71,7 @@ final class ilSamlSettingsGUI /** * @var string[] */ - private const GLOBAL_ENTITY_COMMANDS = [ + private const array GLOBAL_ENTITY_COMMANDS = [ 'deactivateIdp', 'activateIdp', 'confirmDeleteIdp', @@ -85,7 +81,7 @@ final class ilSamlSettingsGUI /** * @var string[] */ - private const IGNORED_USER_FIELDS = [ + private const array IGNORED_USER_FIELDS = [ 'mail_incoming_mail', 'preferences', 'hide_own_online_status', @@ -238,7 +234,7 @@ public function executeCommand(): void } $ipdId = $this->getIdpIdOrZero(); if ($ipdId > 0) { - $this->ctrl->saveParameter($this, self::REQUEST_PARAM_SAML_IDP_ID); + $this->ctrl->setParameter($this, self::REQUEST_PARAM_SAML_IDP_ID, $ipdId); } if (!in_array(strtolower($cmd), array_map('strtolower', self::GLOBAL_COMMANDS), true)) { if (0 === $ipdId) { diff --git a/components/ILIAS/Saml/classes/class.ilSimpleSAMLphpConfigTemplateHandler.php b/components/ILIAS/Saml/classes/class.ilSimpleSAMLphpConfigTemplateHandler.php index ba4e843e2254..07dd42603174 100755 --- a/components/ILIAS/Saml/classes/class.ilSimpleSAMLphpConfigTemplateHandler.php +++ b/components/ILIAS/Saml/classes/class.ilSimpleSAMLphpConfigTemplateHandler.php @@ -20,13 +20,9 @@ use ILIAS\Filesystem\Filesystem; -/** - * Class ilSimpleSAMLphpConfigTemplateHandler - * @author Michael Jansen - */ -final class ilSimpleSAMLphpConfigTemplateHandler +final readonly class ilSimpleSAMLphpConfigTemplateHandler { - public function __construct(private readonly Filesystem $fs) + public function __construct(private Filesystem $fs) { } diff --git a/components/ILIAS/Saml/classes/class.ilSimpleSAMLphpWrapper.php b/components/ILIAS/Saml/classes/class.ilSimpleSAMLphpWrapper.php index a5dd631dab9d..2a908bf025e8 100755 --- a/components/ILIAS/Saml/classes/class.ilSimpleSAMLphpWrapper.php +++ b/components/ILIAS/Saml/classes/class.ilSimpleSAMLphpWrapper.php @@ -18,16 +18,12 @@ declare(strict_types=1); -/** - * Class ilSimpleSAMLphpWrapper - * @author Michael Jansen - */ -final class ilSimpleSAMLphpWrapper implements ilSamlAuth +final readonly class ilSimpleSAMLphpWrapper implements ilSamlAuth { - private const ILIAS = 'ilias'; + private const string ILIAS = 'ilias'; - private readonly SimpleSAML\Configuration $config; - private readonly SimpleSAML\Auth\Simple $authSource; + private SimpleSAML\Configuration $config; + private SimpleSAML\Auth\Simple $authSource; public function __construct(string $authSourceName, string $configurationPath) { @@ -74,7 +70,7 @@ public function protectResource(): void $this->authSource->requireAuth(); } - public function storeParam(string $key, $value): void + public function storeParam(string $key, mixed $value): void { $session = SimpleSAML\Session::getSessionFromRequest(); $session->setData(self::ILIAS, $key, $value); diff --git a/components/ILIAS/Saml/classes/class.ilSimpleSAMLphplIdpDiscovery.php b/components/ILIAS/Saml/classes/class.ilSimpleSAMLphplIdpDiscovery.php index 02a6acd6a140..6ad30974bca5 100755 --- a/components/ILIAS/Saml/classes/class.ilSimpleSAMLphplIdpDiscovery.php +++ b/components/ILIAS/Saml/classes/class.ilSimpleSAMLphplIdpDiscovery.php @@ -18,13 +18,9 @@ declare(strict_types=1); -/** - * Class ilSimpleSAMLphplIdpDiscovery - * @author Michael Jansen - */ final class ilSimpleSAMLphplIdpDiscovery extends SimpleSAML\XHTML\IdPDisco implements ilSamlIdpDiscovery { - private const METADATA_DIRECTORY = 'auth/saml/metadata'; + private const string METADATA_DIRECTORY = 'auth/saml/metadata'; public function __construct() { diff --git a/components/ILIAS/Saml/classes/form/class.ilSamlIdpMetadataInputGUI.php b/components/ILIAS/Saml/classes/form/class.ilSamlIdpMetadataInputGUI.php index 2baedbc149f7..968659552974 100755 --- a/components/ILIAS/Saml/classes/form/class.ilSamlIdpMetadataInputGUI.php +++ b/components/ILIAS/Saml/classes/form/class.ilSamlIdpMetadataInputGUI.php @@ -20,7 +20,7 @@ final class ilSamlIdpMetadataInputGUI extends ilTextAreaInputGUI { - private const AUTH_SAML_ADD_IDP_MD_ERROR = 'auth_saml_add_idp_md_error'; + private const string AUTH_SAML_ADD_IDP_MD_ERROR = 'auth_saml_add_idp_md_error'; public function __construct(string $title, string $httpPostVar, protected ilSamlIdpXmlMetadataParser $idpMetadataParser) { diff --git a/components/ILIAS/Saml/classes/form/class.ilSamlIdpMetadataPurifier.php b/components/ILIAS/Saml/classes/form/class.ilSamlIdpMetadataPurifier.php index dcde6ad48bc6..0ea344383dc4 100755 --- a/components/ILIAS/Saml/classes/form/class.ilSamlIdpMetadataPurifier.php +++ b/components/ILIAS/Saml/classes/form/class.ilSamlIdpMetadataPurifier.php @@ -18,10 +18,6 @@ declare(strict_types=1); -/** - * Class ilSamlIdpMetadataPurifier - * @author Michael Jansen - */ final class ilSamlIdpMetadataPurifier implements ilHtmlPurifierInterface { public function purify(string $html): string diff --git a/components/ILIAS/Saml/exceptions/class.ilSamlException.php b/components/ILIAS/Saml/exceptions/class.ilSamlException.php index 5fff6325c6be..12febf42c060 100755 --- a/components/ILIAS/Saml/exceptions/class.ilSamlException.php +++ b/components/ILIAS/Saml/exceptions/class.ilSamlException.php @@ -18,10 +18,6 @@ declare(strict_types=1); -/** - * Class ilSamlException - * @author Michael Jansen - */ final class ilSamlException extends Exception { } diff --git a/components/ILIAS/Saml/interfaces/interface.ilSamlAuth.php b/components/ILIAS/Saml/interfaces/interface.ilSamlAuth.php index b9ae21bb3441..203b66aebed6 100755 --- a/components/ILIAS/Saml/interfaces/interface.ilSamlAuth.php +++ b/components/ILIAS/Saml/interfaces/interface.ilSamlAuth.php @@ -18,10 +18,6 @@ declare(strict_types=1); -/** - * Interface ilSamlAuth - * @author Michael Jansen - */ interface ilSamlAuth { public function getAuthId(): string; @@ -31,10 +27,7 @@ public function getAuthId(): string; */ public function protectResource(): void; - /** - * @param mixed $value - */ - public function storeParam(string $key, $value): void; + public function storeParam(string $key, mixed $value): void; public function isAuthenticated(): bool; diff --git a/components/ILIAS/Saml/interfaces/interface.ilSamlIdpDiscovery.php b/components/ILIAS/Saml/interfaces/interface.ilSamlIdpDiscovery.php index 605d91ca53c2..ef370442ef1a 100755 --- a/components/ILIAS/Saml/interfaces/interface.ilSamlIdpDiscovery.php +++ b/components/ILIAS/Saml/interfaces/interface.ilSamlIdpDiscovery.php @@ -18,10 +18,6 @@ declare(strict_types=1); -/** - * Interface ilSamlAuth - * @author Michael Jansen - */ interface ilSamlIdpDiscovery { /** diff --git a/components/ILIAS/Saml/tests/ilSamlMappedUserAttributeValueParserTest.php b/components/ILIAS/Saml/tests/ilSamlMappedUserAttributeValueParserTest.php index d2c65fa83d24..e03ec1f8c75d 100755 --- a/components/ILIAS/Saml/tests/ilSamlMappedUserAttributeValueParserTest.php +++ b/components/ILIAS/Saml/tests/ilSamlMappedUserAttributeValueParserTest.php @@ -20,10 +20,6 @@ use PHPUnit\Framework\TestCase; -/** - * Class ilSamlMappedUserAttributeValueParserTest - * @author Michael Jansen - */ class ilSamlMappedUserAttributeValueParserTest extends TestCase { protected function getMappingRuleMock(string $externalAttributeReference): ilExternalAuthUserAttributeMappingRule diff --git a/components/ILIAS/Scorm2004/Scorm2004.php b/components/ILIAS/Scorm2004/Scorm2004.php index 24e54dda58b2..d8be3810e7e6 100644 --- a/components/ILIAS/Scorm2004/Scorm2004.php +++ b/components/ILIAS/Scorm2004/Scorm2004.php @@ -37,5 +37,49 @@ public function init( $contribute[Component\Resource\PublicAsset::class] = fn() => new Component\Resource\Endpoint($this, "storeScorm.php"); + + $contribute[Component\Resource\PublicAsset::class] = static fn() => new class () implements Component\Resource\PublicAsset { + public function getSource(): string + { + return "components/ILIAS/Scorm2004/scripts"; + } + public function getTarget(): string + { + return "components/ILIAS/Scorm2004/scripts"; + } + }; + + $contribute[Component\Resource\PublicAsset::class] = static fn() => new class () implements Component\Resource\PublicAsset { + public function getSource(): string + { + return "components/ILIAS/Scorm2004/templates/default/images"; + } + public function getTarget(): string + { + return "components/ILIAS/Scorm2004/templates/default/images"; + } + }; + + $contribute[Component\Resource\PublicAsset::class] = static fn() => new class () implements Component\Resource\PublicAsset { + public function getSource(): string + { + return "components/ILIAS/Scorm2004/templates/default/images"; + } + public function getTarget(): string + { + return "components/ILIAS/UI/resources/images/scorm2004"; + } + }; + + $contribute[Component\Resource\PublicAsset::class] = static fn() => new class () implements Component\Resource\PublicAsset { + public function getSource(): string + { + return "components/ILIAS/Scorm2004/templates/default/player.css"; + } + public function getTarget(): string + { + return "components/ILIAS/Scorm2004/templates/default/player.css"; + } + }; } } diff --git a/components/ILIAS/Scorm2004/classes/class.ilObjSCORM2004LearningModule.php b/components/ILIAS/Scorm2004/classes/class.ilObjSCORM2004LearningModule.php index 988a793defb0..3e98489cd8d5 100755 --- a/components/ILIAS/Scorm2004/classes/class.ilObjSCORM2004LearningModule.php +++ b/components/ILIAS/Scorm2004/classes/class.ilObjSCORM2004LearningModule.php @@ -486,8 +486,8 @@ public function importSuccess(string $a_file): bool $obj_id = $this->getID(); $users = array(); $usersToDelete = array(); - $fields = fgetcsv($fhandle, 4096, ';'); - while (($csv_rows = fgetcsv($fhandle, 4096, ";")) !== false) { + $fields = fgetcsv($fhandle, 4096, ';', '"', '\\'); + while (($csv_rows = fgetcsv($fhandle, 4096, ";", '"', '\\')) !== false) { $user_id = 0; $data = array_combine($fields, $csv_rows); //no check the format - sufficient to import users @@ -502,13 +502,11 @@ public function importSuccess(string $a_file): bool $user_id = (int) $data["user"]; } if ($user_id > 0) { - $last_access = ilUtil::now(); - if (isset($data['Date'])) { - $date_ex = explode('.', $data['Date']); - $last_access = implode('-', array($date_ex[2], $date_ex[1], $date_ex[0])); - } - if (isset($data['LastAccess'])) { - $last_access = $data['LastAccess']; + $last_access = new DateTimeImmutable('now'); + if (isset($data['LastAccess']) && $data['LastAccess']) { + $last_access = $this->kindlyToDateTime('Y-m-d H:i:s', $data['LastAccess']); + } elseif (isset($data['Date']) && $data['Date']) { + $last_access = $this->kindlyToDateTime('d.m.Y', $data['Date']); } $status = ilLPStatus::LP_STATUS_COMPLETED_NUM; @@ -564,7 +562,7 @@ public function importSuccess(string $a_file): bool (cp_node_id,user_id,completion_status,c_timestamp,cmi_node_id) VALUES(%s,%s,%s,%s,%s)', array('integer','integer','text','timestamp','integer'), - array($sco_id,$user_id,'completed',$last_access,$nextId) + [$sco_id, $user_id, 'completed', $last_access?->format('Y-m-d H:i:s'), $nextId] ); } else { $doUpdate = false; @@ -584,7 +582,7 @@ public function importSuccess(string $a_file): bool 'completion_status' => array('text', 'completed'), 'success_status' => array('text', ''), 'suspend_data' => array('text', ''), - 'c_timestamp' => array('timestamp', $last_access) + 'c_timestamp' => array('timestamp', $last_access?->format('Y-m-d H:i:s')), ), array( 'user_id' => array('integer', $user_id), diff --git a/components/ILIAS/Scorm2004/classes/class.ilSCORM13PlayerGUI.php b/components/ILIAS/Scorm2004/classes/class.ilSCORM13PlayerGUI.php index 9c2ae8eccce9..2efd554f465c 100755 --- a/components/ILIAS/Scorm2004/classes/class.ilSCORM13PlayerGUI.php +++ b/components/ILIAS/Scorm2004/classes/class.ilSCORM13PlayerGUI.php @@ -409,8 +409,8 @@ public function getPlayer(): void $config['get_gobjective_url'] = 'ilias.php?baseClass=ilSAHSPresentationGUI' . '&cmd=getGobjective&ref_id=' . $this->ref_id; $config['ping_url'] = 'ilias.php?baseClass=ilSAHSPresentationGUI' . '&cmd=pingSession&ref_id=' . $this->ref_id; $config['scorm_player_unload_url'] = $unload_url; - $config['post_log_url'] = 'ilias.php?baseClass=ilSAHSPresentationGUI' . '&cmd=postLogEntry&ref_id=' . $this->ref_id; - $config['livelog_url'] = 'ilias.php?baseClass=ilSAHSPresentationGUI' . '&cmd=liveLogContent&ref_id=' . $this->ref_id; + $config['post_log_url'] = $DIC->ctrl()->getLinkTarget($this, 'postLogEntry'); + $config['livelog_url'] = $DIC->ctrl()->getLinkTarget($this, 'liveLogContent'); $config['package_url'] = $this->getDataDirectory() . "/"; //editor @@ -836,7 +836,7 @@ public function readSharedData(int $sco_node_id): void exit(); } - if ($dataStores["readPermissions"] != null && array_sum($dataStores["readPermissions"]) != 0) { + if (isset($dataStores["readPermissions"]) && $dataStores["readPermissions"] != null && array_sum($dataStores["readPermissions"]) != 0) { //If there exists at least one readSharedData permission, then //fill in the existing values (if any) already in the store. @@ -1662,7 +1662,7 @@ public function debugGUI(): void $this->tpl->setVariable('PASSWORD_ENTER', $lng->txt("debugwindow_password_enter")); $this->tpl->setVariable('DEBUG_URL','ilias.php?baseClass=ilSAHSPresentationGUI' .'&cmd=debugGUI&ref_id='.$this->ref_id); } else {*/ - $this->tpl = new ilGlobalTemplate("tpl.scorm2004.debug.html", false, false, "../components/ILIAS/Scorm2004"); + $this->tpl = new ilGlobalTemplate("tpl.scorm2004.debug.html", false, false, "components/ILIAS/Scorm2004"); $this->tpl->setVariable('CONSOLE', $lng->txt("debugwindow_console")); $this->tpl->setVariable('LOGS', $lng->txt("debugwindow_logs")); $this->tpl->setVariable('COMMENT', $lng->txt("debugwindow_comment")); @@ -1676,9 +1676,9 @@ public function debugGUI(): void $this->tpl->setVariable('FILENAME', $lng->txt("debugwindow_filename")); $this->tpl->setVariable('DATE', $lng->txt("debugwindow_date")); $this->tpl->setVariable('ACTION', $lng->txt("debugwindow_action")); - $this->tpl->setVariable('RECORD_IMG', ilUtil::getImagePath("record.png", "../components/ILIAS/Scorm2004")); - $this->tpl->setVariable('STOP_IMG', ilUtil::getImagePath("stop.png", "../components/ILIAS/Scorm2004")); - $this->tpl->setVariable('COMMENT_IMG', ilUtil::getImagePath("comment.png", "../components/ILIAS/Scorm2004")); + $this->tpl->setVariable('RECORD_IMG', "components/ILIAS/Scorm2004/templates/default/images/record.png"); + $this->tpl->setVariable('STOP_IMG', "components/ILIAS/Scorm2004/templates/default/images/stop.png"); + $this->tpl->setVariable('COMMENT_IMG', "components/ILIAS/Scorm2004/templates/default/images/comment.png"); $logfile = $this->logFileName() . ".html"; $this->tpl->setVariable('LOGFILE', $this->logFileName() . ".html"); $this->tpl->setVariable('FILES_DATA', json_encode($this->getLogFileList($lng->txt("debugwindow_delete"), $lng->txt("debugwindow_download"), $lng->txt("debugwindow_open")))); diff --git a/components/ILIAS/Scorm2004/classes/class.ilSCORM2004StoreData.php b/components/ILIAS/Scorm2004/classes/class.ilSCORM2004StoreData.php index 35a40ccf54dd..aed6340f0057 100755 --- a/components/ILIAS/Scorm2004/classes/class.ilSCORM2004StoreData.php +++ b/components/ILIAS/Scorm2004/classes/class.ilSCORM2004StoreData.php @@ -313,9 +313,12 @@ public static function setCMIData( if ($i_set > 3) { $i_set -= 4; if ($getInteractions) { - $q = 'DELETE FROM cmi_correct_response - WHERE cmi_interaction_id IN ( - SELECT cmi_interaction.cmi_interaction_id FROM cmi_interaction WHERE cmi_interaction.cmi_node_id = %s)'; + $q = ' + DELETE cmir + FROM cmi_correct_response cmir + INNER JOIN cmi_interaction cmii ON cmii.cmi_interaction_id = cmir.cmi_interaction_id + WHERE cmii.cmi_node_id = %s + '; $ilDB->manipulateF($q, array('integer'), array($cmi_node_id)); } } @@ -678,7 +681,7 @@ public static function syncGlobalStatus(int $userId, int $packageId, int $refId, // update learning progress if ($new_global_status != null) {//could only happen when synchronising from SCORM Offline Player - ilLPStatus::writeStatus($packageId, $userId, $new_global_status, (int) $data->percentageCompleted); + ilLPStatusWrapper::_updateStatus($packageId, $userId); // here put code for soap to MaxCMS e.g. when if($saved_global_status != $new_global_status) } diff --git a/components/ILIAS/Scorm2004/templates/default/tpl.scorm2004.debug.html b/components/ILIAS/Scorm2004/templates/default/tpl.scorm2004.debug.html index 210a61229bb2..a7cc6eeffd34 100755 --- a/components/ILIAS/Scorm2004/templates/default/tpl.scorm2004.debug.html +++ b/components/ILIAS/Scorm2004/templates/default/tpl.scorm2004.debug.html @@ -1,205 +1,254 @@ - - + - - Debugger - - - - - - - - - - - - - - - - - - - - - + + X Debugger + + - - -
- -
- - -
-
-
- -
-
-
-
-
- -
-
{COMMENT_ENTER}
-
- - -
- -
-
+ +
+
+ + +
+ + +
+
+ +
+ +
+ +
+ + + + + + + + + +
{FILENAME}{DATE}
+
+
+ +
+
+
{COMMENT_ENTER}
+ + + +
+ const recordImg = '{RECORD_IMG}'; + const stopImg = '{STOP_IMG}'; + let recording = window.opener?.logActive ?? false; + let filesData = {FILES_DATA}; // Injected as JS object + const tableBody = document.querySelector("#logTable tbody"); + + // Initial tab handling + document.querySelectorAll('.tab-btn').forEach(btn => { + btn.addEventListener('click', () => { + document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active')); + document.querySelectorAll('.tab-content').forEach(tab => tab.classList.remove('active')); + btn.classList.add('active'); + document.getElementById(btn.dataset.tab).classList.add('active'); + }); + }); + + function formatDate(dateObj) { + if (!(dateObj instanceof Date)) return ''; + return new Intl.DateTimeFormat('de-DE', { + day: '2-digit', month: '2-digit', year: 'numeric', + hour: '2-digit', minute: '2-digit' + }).format(dateObj); + } + + function renderTable(data) { + tableBody.innerHTML = ''; + data.forEach(row => { + const tr = document.createElement('tr'); + const date = (typeof row.date === 'string') ? new Date(row.date) : row.date; + tr.innerHTML = ` + ${row.filename} + ${formatDate(date)} + + `; + tableBody.appendChild(tr); + }); + } + + function sortBy(key, desc = false) { + filesData.sort((a, b) => { + if (key === "date") { + return desc ? new Date(b.date) - new Date(a.date) : new Date(a.date) - new Date(b.date); + } + return desc ? String(b[key]).localeCompare(String(a[key])) : String(a[key]).localeCompare(String(b[key])); + }); + renderTable(filesData); + } + + // Sorting logic + let currentSort = {key: "date", desc: true}; + document.querySelectorAll("#logTable th[data-key]").forEach(th => { + th.addEventListener('click', () => { + const key = th.dataset.key; + const desc = currentSort.key === key ? !currentSort.desc : true; + currentSort = {key, desc}; + sortBy(key, desc); + }); + }); + + function switchRecording() { + recording = !recording; + window.opener.logActive = recording; + document.getElementById("state").src = recording ? stopImg : recordImg; + document.getElementById("state").title = recording ? "{STOP_RECORDING}" : "{START_RECORDING}"; + if (!recording) { + window.opener.createSummary?.(); + } + } + + function loadLiveLog() { + const iframe = document.getElementById("livelog"); + iframe.src = iframe.src; + } + + function updateLiveLog() { + setTimeout(loadLiveLog, 2000); + } + + function deleteFile(filename) { + if (confirm("{DELETE_LOGFILE} " + filename + " ?")) { + window.opener.sendLogEntry("", 'DELETE', "filename", filename, "", ""); + } + } + + function showCommentDialog() { + document.getElementById("dialogBackdrop").style.display = "block"; + document.getElementById("commentDialog").style.display = "block"; + } + + function hideCommentDialog() { + document.getElementById("dialogBackdrop").style.display = "none"; + document.getElementById("commentDialog").style.display = "none"; + } + + function submitComment() { + const comment = document.getElementById("commentInput").value.trim(); + if (comment === "") { + alert("{COMMENT_ENTER}."); + return; + } + window.opener.sendLogEntry("", "COMMENT", "", comment, "", ""); + hideCommentDialog(); + } + + // Initial setup + (function init() { + document.getElementById("state").src = recording ? stopImg : recordImg; + document.getElementById("state").title = recording ? "{STOP_RECORDING}" : "{START_RECORDING}"; + renderTable(filesData); + })(); + + diff --git a/components/ILIAS/ScormAicc/Editing/classes/class.ilSAHSEditGUI.php b/components/ILIAS/ScormAicc/Editing/classes/class.ilSAHSEditGUI.php index 377b92f5f462..1fa90f5c9231 100755 --- a/components/ILIAS/ScormAicc/Editing/classes/class.ilSAHSEditGUI.php +++ b/components/ILIAS/ScormAicc/Editing/classes/class.ilSAHSEditGUI.php @@ -87,12 +87,22 @@ public function executeCommand(): void "lm" ); - $next_class = $this->ctrl->getNextClass($this); - $cmd = $this->ctrl->getCmd(); + $next_class = $this->ctrl->getNextClass($this) ?? ''; + $cmd = $this->ctrl->getCmd() ?? ''; $obj_id = ilObject::_lookupObjectId($this->refId); $type = ilObjSAHSLearningModule::_lookupSubType($obj_id); + if ($next_class === '') { + switch ($type) { + case "scorm": + $this->ctrl->redirectByClass(ilObjSCORMLearningModuleGUI::class); + + case "scorm2004": + $this->ctrl->redirectByClass(ilObjSCORM2004LearningModuleGUI::class); + } + } + switch ($type) { case "scorm": $this->slm_gui = new ilObjSCORMLearningModuleGUI([], $this->refId, true, false); @@ -103,27 +113,6 @@ public function executeCommand(): void break; } - if ($next_class == "") { - switch ($type) { - case "scorm2004": - // @todo: removed deprecated ilCtrl methods, this needs inspection by a maintainer. - // $this->ctrl->setCmdClass("ilobjscorm2004learningmodulegui"); - - $this->ctrl->redirectByClass(ilObjSCORM2004LearningModuleGUI::class, $this->ctrl->getCmd()); - - break; - - case "scorm": - // @todo: removed deprecated ilCtrl methods, this needs inspection by a maintainer. - // $this->ctrl->setCmdClass("ilobjscormlearningmodulegui"); - - $this->ctrl->redirectByClass(ilObjSCORMLearningModuleGUI::class, $this->ctrl->getCmd()); - - break; - } - $next_class = $this->ctrl->getNextClass($this); - } - switch ($next_class) { case "ilobjscormlearningmodulegui": case "ilobjscorm2004learningmodulegui": @@ -154,8 +143,7 @@ public function executeCommand(): void } } } - // @todo: removed deprecated ilCtrl methods, this needs inspection by a maintainer. - // $this->ctrl->setCmd("export"); + ilUtil::redirect("ilias.php?baseClass=ilSAHSEditGUI&cmd=export&ref_id=" . $this->refId); break; diff --git a/components/ILIAS/ScormAicc/classes/Certificate/class.ilScormPlaceholderValues.php b/components/ILIAS/ScormAicc/classes/Certificate/class.ilScormPlaceholderValues.php index 74474e35b039..cbf01536a6b2 100755 --- a/components/ILIAS/ScormAicc/classes/Certificate/class.ilScormPlaceholderValues.php +++ b/components/ILIAS/ScormAicc/classes/Certificate/class.ilScormPlaceholderValues.php @@ -104,7 +104,7 @@ public function getPlaceholderValues(int $userId, int $objId): array } $max_points = $object->getMaxPoints(); - $txtMaxPoints = $max_points; + $txtMaxPoints = (string) $max_points; if (is_null($max_points)) { $txtMaxPoints = $this->language->txt('certificate_points_notavailable'); } elseif ($max_points != floor($max_points)) { @@ -128,8 +128,10 @@ public function getPlaceholderValues(int $userId, int $objId): array $placeHolders['DATETIME_COMPLETED'] = ''; if ($completionDate !== '') { - $placeHolders['DATE_COMPLETED'] = $this->dateHelper->formatDate($completionDate); - $placeHolders['DATETIME_COMPLETED'] = $this->dateHelper->formatDateTime($completionDate); + /** @var ilObjUser $user */ + $user = $this->objectHelper->getInstanceByObjId($userId); + $placeHolders['DATE_COMPLETED'] = $this->dateHelper->formatDate($completionDate, $user); + $placeHolders['DATETIME_COMPLETED'] = $this->dateHelper->formatDateTime($completionDate, $user); } $olp = $this->objectLPHelper->getInstance($object->getId()); diff --git a/components/ILIAS/ScormAicc/classes/SCORM/class.ilObjSCORMTracking.php b/components/ILIAS/ScormAicc/classes/SCORM/class.ilObjSCORMTracking.php index cccccc89114d..fce4d5bdb86c 100755 --- a/components/ILIAS/ScormAicc/classes/SCORM/class.ilObjSCORMTracking.php +++ b/components/ILIAS/ScormAicc/classes/SCORM/class.ilObjSCORMTracking.php @@ -142,10 +142,9 @@ public static function storeJsApiCmi(int $user_id, int $obj_id, object $data): b } // update status - // if ($b_updateStatus == true) { - // include_once("../components/ILIAS/Tracking/classes/class.ilLPStatusWrapper.php"); - // ilLPStatusWrapper::_updateStatus($obj_id, $user_id); - // } + if ($b_updateStatus === true) { + ilLPStatusWrapper::_updateStatus($obj_id, $user_id); + } return true; } @@ -220,8 +219,8 @@ public static function syncGlobalStatus(int $userId, int $packageId, int $refId, //end sync access number and time in read event table // update learning progress - if ($new_global_status != null) { - ilLPStatus::writeStatus($packageId, $userId, $new_global_status, $data->percentageCompleted); + if ($new_global_status !== null) { + ilLPStatusWrapper::_updateStatus($packageId, $userId); // here put code for soap to MaxCMS e.g. when if($saved_global_status != $new_global_status) } diff --git a/components/ILIAS/ScormAicc/classes/SCORM/class.ilSCORMPackageParser.php b/components/ILIAS/ScormAicc/classes/SCORM/class.ilSCORMPackageParser.php index 7fea6c2c11f3..5a03338b9496 100755 --- a/components/ILIAS/ScormAicc/classes/SCORM/class.ilSCORMPackageParser.php +++ b/components/ILIAS/ScormAicc/classes/SCORM/class.ilSCORMPackageParser.php @@ -64,9 +64,8 @@ public function __construct(object $a_slm_object, string $a_xml_file) */ public function setHandlers($a_xml_parser): void { - xml_set_object($a_xml_parser, $this); - xml_set_element_handler($a_xml_parser, 'handlerBeginTag', 'handlerEndTag'); - xml_set_character_data_handler($a_xml_parser, 'handlerCharacterData'); + xml_set_element_handler($a_xml_parser, $this->handlerBeginTag(...), $this->handlerEndTag(...)); + xml_set_character_data_handler($a_xml_parser, $this->handlerCharacterData(...)); } /** diff --git a/components/ILIAS/ScormAicc/classes/class.ilObjSAHSLearningModuleGUI.php b/components/ILIAS/ScormAicc/classes/class.ilObjSAHSLearningModuleGUI.php index 1ab30ffb28b0..ba86b41fe125 100755 --- a/components/ILIAS/ScormAicc/classes/class.ilObjSAHSLearningModuleGUI.php +++ b/components/ILIAS/ScormAicc/classes/class.ilObjSAHSLearningModuleGUI.php @@ -180,11 +180,9 @@ public function executeCommand(): void protected function infoScreen(): void { - // @todo: removed deprecated ilCtrl methods, this needs inspection by a maintainer. - // $this->ctrl->setCmd("showSummary"); - // $this->ctrl->setCmdClass("ilinfoscreengui"); - - $this->ctrl->redirectByClass(ilInfoScreenGUI::class, "showSummary"); + if (strtolower($this->ctrl->getCmd() ?? '') === 'infoscreen') { + $this->ctrl->redirectByClass(ilInfoScreenGUI::class, 'showSummary'); + } $this->infoScreenForward(); } diff --git a/components/ILIAS/ScormAicc/classes/class.ilObjSCORMLearningModule.php b/components/ILIAS/ScormAicc/classes/class.ilObjSCORMLearningModule.php index e7c53648abf2..3edcb3a3f1e4 100755 --- a/components/ILIAS/ScormAicc/classes/class.ilObjSCORMLearningModule.php +++ b/components/ILIAS/ScormAicc/classes/class.ilObjSCORMLearningModule.php @@ -565,7 +565,7 @@ public function importTrackingData(string $a_file): bool $fhandle = fopen($a_file, "r"); //the top line is the field names - $fields = fgetcsv($fhandle, 2 ** 16, ';'); + $fields = fgetcsv($fhandle, 2 ** 16, ';', '"', '\\'); //lets check the import method fclose($fhandle); @@ -599,10 +599,10 @@ public function importSuccess(string $a_file): bool $fhandle = fopen($a_file, "r"); $obj_id = $this->getID(); - $fields = fgetcsv($fhandle, 2 ** 16, ';'); + $fields = fgetcsv($fhandle, 2 ** 16, ';', '"', '\\'); $users = array(); $usersToDelete = array(); - while (($csv_rows = fgetcsv($fhandle, 2 ** 16, ";")) !== false) { + while (($csv_rows = fgetcsv($fhandle, 2 ** 16, ";", '"', '\\')) !== false) { $user_id = 0; $data = array_combine($fields, $csv_rows); //no check the format - sufficient to import users @@ -618,13 +618,11 @@ public function importSuccess(string $a_file): bool } if ($user_id > 0) { - $last_access = ilUtil::now(); - if (isset($data['Date'])) { - $date_ex = explode('.', $data['Date']); - $last_access = implode('-', array($date_ex[2], $date_ex[1], $date_ex[0])); - } - if (isset($data['LastAccess'])) { - $last_access = $data['LastAccess']; + $last_access = new DateTimeImmutable('now'); + if (isset($data['LastAccess']) && $data['LastAccess']) { + $last_access = $this->kindlyToDateTime('Y-m-d H:i:s', $data['LastAccess']); + } elseif (isset($data['Date']) && $data['Date']) { + $last_access = $this->kindlyToDateTime('d.m.Y', $data['Date']); } $status = ilLPStatus::LP_STATUS_COMPLETED_NUM; @@ -685,7 +683,7 @@ public function importSuccess(string $a_file): bool 'scorm_tracking', array( 'rvalue' => array('clob', 'completed'), - 'c_timestamp' => array('timestamp', $last_access) + 'c_timestamp' => array('timestamp', $last_access?->format('Y-m-d H:i:s')) ), array( 'user_id' => array('integer', $user_id), @@ -701,7 +699,7 @@ public function importSuccess(string $a_file): bool 'sco_id' => array('integer', $sco_id), 'lvalue' => array('text', 'cmi.core.lesson_status'), 'rvalue' => array('clob', 'completed'), - 'c_timestamp' => array('timestamp', $last_access) + 'c_timestamp' => array('timestamp', $last_access?->format('Y-m-d H:i:s')) )); } } @@ -720,9 +718,32 @@ public function importSuccess(string $a_file): bool return true; } + protected function kindlyToDateTime( + string $maybe_datetime, + string $format, + ?DateTimeImmutable $default = null + ): ?DateTimeImmutable { + $datetime = $default; + + if ($maybe_datetime === '0000-00-00 00:00:00' || $maybe_datetime === '0000-00-00') { + return null; + } + + try { + $parsed_date = DateTimeImmutable::createFromFormat($format, $maybe_datetime); + if ($parsed_date !== false) { + $datetime = $parsed_date; + } + } catch (Throwable) { + // Ignore + } + + return $datetime; + } + public function importSuccessForSahsUser( int $user_id, - string $last_access, + ?DateTimeImmutable $last_access, int $status, ?int $attempts = null, ?int $percentage_completed = null, @@ -739,7 +760,7 @@ public function importSuccessForSahsUser( $ilDB->update( 'sahs_user', array( - 'last_access' => array('timestamp', $last_access), + 'last_access' => array('timestamp', $last_access?->format('Y-m-d H:i:s')), 'status' => array('integer', $status), 'package_attempts' => array('integer', $attempts), 'percentage_completed' => array('integer', $percentage_completed), @@ -754,7 +775,7 @@ public function importSuccessForSahsUser( $ilDB->insert('sahs_user', array( 'obj_id' => array('integer', $this->getID()), 'user_id' => array('integer', $user_id), - 'last_access' => array('timestamp', $last_access), + 'last_access' => array('timestamp', $last_access?->format('Y-m-d H:i:s')), 'status' => array('integer', $status), 'package_attempts' => array('integer', $attempts), 'percentage_completed' => array('integer', $percentage_completed), @@ -804,13 +825,13 @@ private function importRaw(string $a_file): bool $fhandle = fopen($a_file, "r"); - $fields = fgetcsv($fhandle, 2 ** 16, ';'); + $fields = fgetcsv($fhandle, 2 ** 16, ';', '"', '\\'); $users = array(); $a_last_access = array(); $a_time = array(); $a_package_attempts = array(); $a_module_version = array(); - while (($csv_rows = fgetcsv($fhandle, 2 ** 16, ";")) !== false) { + while (($csv_rows = fgetcsv($fhandle, 2 ** 16, ";", '"', '\\')) !== false) { $data = array_combine($fields, $csv_rows); if ($data['Userid']) { $user_id = $this->parseUserId($data['Userid']); @@ -836,26 +857,19 @@ private function importRaw(string $a_file): bool continue; } - $c_timestamp = ""; - if ($data['Timestamp']) { - $c_timestamp = $data['Timestamp']; - } - if ($data[$lng->txt("c_timestamp")]) { - $c_timestamp = $data[$lng->txt("c_timestamp")]; - } - if ($c_timestamp == "") { - $date = new DateTime(); - $c_timestamp = $date->getTimestamp(); - } else { - if ($a_last_access[$user_id]) { - if ($a_last_access[$user_id] < $c_timestamp) { - $a_last_access[$user_id] = $c_timestamp; - } - } else { - $a_last_access[$user_id] = $c_timestamp; + $c_timestamp = null; + foreach ([$lng->txt("c_timestamp"), 'Timestamp'] as $key) { + if (!empty($data[$key])) { + $c_timestamp = $this->kindlyToDateTime('Y-m-d H:i:s', $data[$key]); + break; } } + $c_timestamp ??= new DateTimeImmutable('now'); + if (!isset($a_last_access[$user_id]) || $a_last_access[$user_id] < $c_timestamp) { + $a_last_access[$user_id] = $c_timestamp; + } + if (!$data['Key']) { continue; } @@ -935,7 +949,7 @@ private function importRaw(string $a_file): bool $sco_total_time_sec = $a_time[$user_id]; } $last_access = null; - if ($a_last_access[$user_id]) { + if (isset($a_last_access[$user_id])) { $last_access = $a_last_access[$user_id]; } // $status = ilLPStatusWrapper::_determineStatus($this->getId(),$user_id); diff --git a/components/ILIAS/ScormAicc/classes/class.ilSAHSPresentationGUI.php b/components/ILIAS/ScormAicc/classes/class.ilSAHSPresentationGUI.php index 792daa5a61fe..43242b8ffa5c 100755 --- a/components/ILIAS/ScormAicc/classes/class.ilSAHSPresentationGUI.php +++ b/components/ILIAS/ScormAicc/classes/class.ilSAHSPresentationGUI.php @@ -191,9 +191,10 @@ public function view(): void */ public function infoScreen(): void { - // @todo: removed deprecated ilCtrl methods, this needs inspection by a maintainer. - // $this->ctrl->setCmd("showSummary"); - // $this->ctrl->setCmdClass("ilinfoscreengui"); + if (strtolower($this->ctrl->getCmd() ?? '') === 'infoscreen') { + $this->ctrl->redirectByClass(ilInfoScreenGUI::class, 'showSummary'); + } + $this->outputInfoScreen(); } @@ -267,20 +268,14 @@ public function outputInfoScreen(): void { global $DIC; $ilAccess = $DIC->access(); - $refId = $this->refId;//$this->slm_gui->object->getRefId(); - - //$this->tpl->setHeaderPageTitle("PAGETITLE", " - ".$this->lm->getTitle()); - - // set style sheets - // $this->tpl->setStyleSheetLocation(ilUtil::getStyleSheetLocation()); - + $refId = $this->refId; + $this->setInfoTabs("info_short"); $this->lng->loadLanguageModule("meta"); $info = new ilInfoScreenGUI($this->slm_gui); $info->enablePrivateNotes(); - //$info->enableLearningProgress(); $info->enableNews(); if ($ilAccess->checkAccess("write", "", $refId)) { @@ -305,7 +300,6 @@ public function outputInfoScreen(): void $this->slm_gui->getObject()->getType() ); - // forward the command $this->ctrl->forwardCommand($info); $this->tpl->printToStdout(); } diff --git a/components/ILIAS/ScormAicc/scripts/basisAPI.js b/components/ILIAS/ScormAicc/scripts/basisAPI.js index 79e6772ce27f..ddb1e92852bb 100755 --- a/components/ILIAS/ScormAicc/scripts/basisAPI.js +++ b/components/ILIAS/ScormAicc/scripts/basisAPI.js @@ -107,7 +107,7 @@ function sendRequest (url, data, callback, user, password, headers) { if (r.content) { if (r.content.indexOf("login.php")>-1) { - window.location.href = "./Modules/Scorm2004/templates/default/session_timeout.html"; + window.location.href = "./components/ILIAS/Scorm2004/templates/default/session_timeout.html"; } } @@ -247,7 +247,7 @@ function IliasLaunch(i_l){ b_launched=false; setTimeout("API.IliasAbortSco("+iv.launchId+")",5000); iv.launchId=i_l; - frames.sahs_content.document.location.replace('./Modules/ScormAicc/templates/default/dummy.html'); + frames.sahs_content.document.location.replace('./components/ILIAS/ScormAicc/templates/default/dummy.html'); } status4tree(iv.launchId,'running'); } diff --git a/components/ILIAS/ScormAicc/tests/ilScormPlaceholderValuesTest.php b/components/ILIAS/ScormAicc/tests/ilScormPlaceholderValuesTest.php index f6a6353d0075..d169fc477884 100755 --- a/components/ILIAS/ScormAicc/tests/ilScormPlaceholderValuesTest.php +++ b/components/ILIAS/ScormAicc/tests/ilScormPlaceholderValuesTest.php @@ -71,11 +71,19 @@ public function testGetPlaceholderValues(): void $objectMock->method('getId') ->willReturn(500); - $objectHelper = $this->getMockBuilder(ilCertificateObjectHelper::class) + $user_object = $this->getMockBuilder(ilObjUser::class) + ->disableOriginalConstructor() ->getMock(); + $objectHelper = $this->getMockBuilder(ilCertificateObjectHelper::class) + ->getMock(); $objectHelper->method('getInstanceByObjId') - ->willReturn($objectMock); + ->willReturnMap( + [ + [200, $objectMock], + [10, $user_object] + ] + ); $utilHelper = $this->getMockBuilder(ilCertificateUtilHelper::class) ->getMock(); diff --git a/components/ILIAS/Search/ROADMAP.md b/components/ILIAS/Search/ROADMAP.md index 655b56fe7c65..8605e30710b2 100755 --- a/components/ILIAS/Search/ROADMAP.md +++ b/components/ILIAS/Search/ROADMAP.md @@ -10,8 +10,19 @@ blocks without a functional ListGUI [2]. ## Short Term +### Clean up Abandon Advanced Search + +The following settings can be deleted: + +- module: `common`, keyword: `hide_adv_search` +- everything in module `lucene_adv_search` + +The following rows from database tables can be deleted: + +- in `usr_search` where `search_type` is 1, 4, or 6 ## Mid Term + - Add subtypes to lucene index and extend the search result xml accordingly. ## Long Term diff --git a/components/ILIAS/Search/classes/Lucene/class.ilLuceneAdvancedQueryParser.php b/components/ILIAS/Search/classes/Lucene/class.ilLuceneAdvancedQueryParser.php deleted file mode 100755 index 5af3d17ccfa8..000000000000 --- a/components/ILIAS/Search/classes/Lucene/class.ilLuceneAdvancedQueryParser.php +++ /dev/null @@ -1,81 +0,0 @@ - -* -* -* @ingroup ServicesSearch -*/ -class ilLuceneAdvancedQueryParser extends ilLuceneQueryParser -{ - protected ilLuceneAdvancedSearchFields $field_definition; - /** - * @var array|string - */ - protected $query_data; - - /** - * Constructor - */ - public function __construct($a_query_data) - { - parent::__construct(''); - - $this->field_definition = ilLuceneAdvancedSearchFields::getInstance(); - $this->query_data = $a_query_data; - } - - /** - * Get field definition settings - */ - public function getFieldDefinition(): ilLuceneAdvancedSearchFields - { - return $this->field_definition; - } - - /** - * @return array|string - */ - public function getQueryData() - { - if (is_array($this->query_data)) { - return $this->query_data; - } - return $this->query_data ?? ''; - } - - public function parse(): void - { - foreach ((array) $this->getQueryData() as $field => $query) { - if (!is_array($query) && !trim((string) $query)) { - continue; - } - $parsed = $this->getFieldDefinition()->parseFieldQuery((string) $field, $query); - if (strlen($parsed)) { - $this->parsed_query .= " +("; - $this->parsed_query .= $parsed; - $this->parsed_query .= ") "; - } - } - } -} diff --git a/components/ILIAS/Search/classes/Lucene/class.ilLuceneAdvancedSearchActivationTableGUI.php b/components/ILIAS/Search/classes/Lucene/class.ilLuceneAdvancedSearchActivationTableGUI.php deleted file mode 100755 index 9bbd5bf60430..000000000000 --- a/components/ILIAS/Search/classes/Lucene/class.ilLuceneAdvancedSearchActivationTableGUI.php +++ /dev/null @@ -1,80 +0,0 @@ - -* -* -* @ingroup -*/ -class ilLuceneAdvancedSearchActivationTableGUI extends ilTable2GUI -{ - protected ilAccess $access; - - public function __construct($a_parent_obj, $a_parent_cmd = '') - { - global $DIC; - - $this->access = $DIC->access(); - - parent::__construct($a_parent_obj, $a_parent_cmd); - $this->addColumn('', 'id', '0px'); - $this->addColumn($this->lng->txt('title'), 'title', '60%'); - $this->addColumn($this->lng->txt('type'), 'type', '40%'); - $this->setRowTemplate('tpl.lucene_activation_row.html', 'components/ILIAS/Search'); - $this->disable('sort'); - $this->setLimit(100); - $this->setSelectAllCheckbox('fid'); - $this->setFormAction($this->ctrl->getFormAction($a_parent_obj)); - - if ($this->access->checkAccess('write', '', $this->getParentObject()->getObject()->getRefId())) { - $this->addMultiCommand('saveAdvancedLuceneSettings', $this->lng->txt('lucene_activate_field')); - } - } - - /** - * Fill template row - */ - protected function fillRow(array $a_set): void - { - $this->tpl->setVariable('VAL_ID', $a_set['id']); - $this->tpl->setVariable('VAL_CHECKED', $a_set['active'] ? 'checked="checked"' : ''); - $this->tpl->setVariable('VAL_TITLE', $a_set['title']); - $this->tpl->setVariable('VAL_TYPE', $a_set['type']); - } - - public function parse(ilLuceneAdvancedSearchSettings $settings): void - { - $content = []; - foreach (ilLuceneAdvancedSearchFields::getFields() as $field => $translation) { - $tmp_arr['id'] = $field; - $tmp_arr['active'] = $settings->isActive($field); - $tmp_arr['title'] = $translation; - - $tmp_arr['type'] = (substr($field, 0, 3) == 'lom') ? - $this->lng->txt('search_lom') : - $this->lng->txt('search_adv_md'); - - $content[] = $tmp_arr; - } - $this->setData($content); - } -} diff --git a/components/ILIAS/Search/classes/Lucene/class.ilLuceneAdvancedSearchFields.php b/components/ILIAS/Search/classes/Lucene/class.ilLuceneAdvancedSearchFields.php deleted file mode 100755 index aaff05464320..000000000000 --- a/components/ILIAS/Search/classes/Lucene/class.ilLuceneAdvancedSearchFields.php +++ /dev/null @@ -1,808 +0,0 @@ - -* -* -* @ingroup ServicesSearch -*/ -class ilLuceneAdvancedSearchFields -{ - public const ONLINE_QUERY = 1; - public const OFFLINE_QUERY = 2; - - private static ?ilLuceneAdvancedSearchFields $instance = null; - private ilLuceneAdvancedSearchSettings $settings; - - protected ilLanguage $lng; - protected ilObjUser $user; - - private static array $fields = []; - private array $active_fields = []; - - private static array $sections = []; - private array $active_sections = []; - - - protected function __construct() - { - global $DIC; - - $this->lng = $DIC->language(); - $this->lng->loadLanguageModule('meta'); - $this->user = $DIC->user(); - - $this->settings = ilLuceneAdvancedSearchSettings::getInstance(); - - $this->readFields(); - $this->readSections(); - } - - public static function getInstance(): ilLuceneAdvancedSearchFields - { - if (self::$instance instanceof ilLuceneAdvancedSearchFields) { - return self::$instance; - } - return self::$instance = new ilLuceneAdvancedSearchFields(); - } - - /** - * Return an array of all meta data fields - * @return array - */ - public static function getFields(): array - { - global $DIC; - - $lng = $DIC->language(); - - $lng->loadLanguageModule('meta'); - - $fields['lom_content'] = $lng->txt('content'); - - if (ilSearchSettings::getInstance()->enabledLucene()) { - $fields['general_offline'] = $lng->txt('lucene_offline_filter'); - } - //'lom_type' = $lng->txt('type'); - $fields['lom_language'] = $lng->txt('language'); - $fields['lom_keyword'] = $lng->txt('meta_keyword'); - $fields['lom_coverage'] = $lng->txt('meta_coverage'); - $fields['lom_structure'] = $lng->txt('meta_structure'); - $fields['lom_status'] = $lng->txt('meta_status'); - $fields['lom_version'] = $lng->txt('meta_version'); - $fields['lom_contribute'] = $lng->txt('meta_contribute'); - $fields['lom_format'] = $lng->txt('meta_format'); - $fields['lom_operating_system'] = $lng->txt('meta_operating_system'); - $fields['lom_browser'] = $lng->txt('meta_browser'); - $fields['lom_interactivity'] = $lng->txt('meta_interactivity_type'); - $fields['lom_resource'] = $lng->txt('meta_learning_resource_type'); - $fields['lom_level'] = $lng->txt('meta_interactivity_level'); - $fields['lom_density'] = $lng->txt('meta_semantic_density'); - $fields['lom_user_role'] = $lng->txt('meta_intended_end_user_role'); - $fields['lom_context'] = $lng->txt('meta_context'); - $fields['lom_difficulty'] = $lng->txt('meta_difficulty'); - $fields['lom_costs'] = $lng->txt('meta_cost'); - $fields['lom_copyright'] = $lng->txt('meta_copyright_and_other_restrictions'); - $fields['lom_purpose'] = $lng->txt('meta_purpose'); - $fields['lom_taxon'] = $lng->txt('meta_taxon'); - - // Append all advanced meta data fields - foreach (ilAdvancedMDRecord::_getRecords() as $record) { - if ($record->getParentObject() > 0) { - if (!ilObject::_hasUntrashedReference($record->getParentObject())) { - continue; - } - } - - foreach (ilAdvancedMDFieldDefinition::getInstancesByRecordId($record->getRecordId(), true) as $def) { - $field_translations = ilAdvancedMDFieldTranslations::getInstanceByRecordId($record->getRecordId()); - $fields['adv_' . $def->getFieldId()] = $field_translations->getTitleForLanguage($def->getFieldId(), $lng->getLangKey()); - } - } - - return $fields; - } - - /** - * Get all active fields - * @return array - */ - public function getActiveFields(): array - { - return $this->active_fields; - } - - public function getActiveSections(): array - { - return $this->active_sections; - } - - /** - * @param string | array $a_query - */ - public function getFormElement($a_query, string $a_field_name, ilPropertyFormGUI $a_form): ?ilFormPropertyGUI - { - $a_post_name = 'query[' . $a_field_name . ']'; - - if (!is_array($a_query)) { - $a_query = array(); - } - - switch ($a_field_name) { - case 'general_offline': - $offline_options = array( - '0' => $this->lng->txt('search_any'), - self::ONLINE_QUERY => $this->lng->txt('search_option_online'), - self::OFFLINE_QUERY => $this->lng->txt('search_option_offline') - ); - $offline = new ilSelectInputGUI($this->active_fields[$a_field_name], $a_post_name); - $offline->setOptions($offline_options); - $offline->setValue($a_query['general_offline'] ?? ''); - return $offline; - - case 'lom_content': - $text = new ilTextInputGUI($this->active_fields[$a_field_name], $a_post_name); - $text->setSubmitFormOnEnter(true); - $text->setValue($a_query['lom_content'] ?? ''); - $text->setSize(30); - $text->setMaxLength(255); - return $text; - - // General - case 'lom_language': - $select = new ilSelectInputGUI($this->active_fields[$a_field_name], $a_post_name); - $select->setValue($a_query['lom_language'] ?? ''); - $select->setOptions((array) ilMDUtilSelect::_getLanguageSelect( - '', - $a_field_name, - array(0 => $this->lng->txt('search_any')), - true - )); - return $select; - - case 'lom_keyword': - $text = new ilTextInputGUI($this->active_fields[$a_field_name], $a_post_name); - $text->setSubmitFormOnEnter(true); - $text->setValue($a_query['lom_keyword'] ?? ''); - $text->setSize(30); - $text->setMaxLength(255); - return $text; - - case 'lom_coverage': - $text = new ilTextInputGUI($this->active_fields[$a_field_name], $a_post_name); - $text->setSubmitFormOnEnter(true); - $text->setValue($a_query['lom_coverage'] ?? ''); - $text->setSize(30); - $text->setMaxLength(255); - return $text; - - case 'lom_structure': - $select = new ilSelectInputGUI($this->active_fields[$a_field_name], $a_post_name); - $select->setValue($a_query['lom_structure'] ?? ''); - $select->setOptions((array) ilMDUtilSelect::_getStructureSelect( - '', - $a_field_name, - array(0 => $this->lng->txt('search_any')), - true - )); - return $select; - - // Lifecycle - case 'lom_status': - $select = new ilSelectInputGUI($this->active_fields[$a_field_name], $a_post_name); - $select->setValue($a_query['lom_status'] ?? ''); - $select->setOptions((array) ilMDUtilSelect::_getStatusSelect( - '', - $a_field_name, - array(0 => $this->lng->txt('search_any')), - true - )); - return $select; - - case 'lom_version': - $text = new ilTextInputGUI($this->active_fields[$a_field_name], $a_post_name); - $text->setSubmitFormOnEnter(true); - $text->setValue($a_query['lom_version'] ?? ''); - $text->setSize(30); - $text->setMaxLength(255); - return $text; - - case 'lom_contribute': - $select = new ilSelectInputGUI($this->active_fields[$a_field_name], 'query[' . 'lom_role' . ']'); - $select->setValue($a_query['lom_role'] ?? ''); - $select->setOptions((array) ilMDUtilSelect::_getRoleSelect( - '', - $a_field_name, - array(0 => $this->lng->txt('search_any')), - true - )); - - $text = new ilTextInputGUI($this->lng->txt('meta_entry'), 'query[' . 'lom_role_entry' . ']'); - $text->setValue($a_query['lom_role_entry'] ?? ''); - $text->setSize(30); - $text->setMaxLength(255); - - $select->addSubItem($text); - return $select; - - // Technical - case 'lom_format': - $select = new ilSelectInputGUI($this->active_fields[$a_field_name], $a_post_name); - $select->setValue($a_query['lom_format'] ?? ''); - $select->setOptions((array) ilMDUtilSelect::_getFormatSelect( - '', - $a_field_name, - array(0 => $this->lng->txt('search_any')), - true - )); - return $select; - - case 'lom_operating_system': - $select = new ilSelectInputGUI($this->active_fields[$a_field_name], $a_post_name); - $select->setValue($a_query['lom_operating_system'] ?? ''); - $select->setOptions((array) ilMDUtilSelect::_getOperatingSystemSelect( - '', - $a_field_name, - array(0 => $this->lng->txt('search_any')), - true - )); - return $select; - - case 'lom_browser': - $select = new ilSelectInputGUI($this->active_fields[$a_field_name], $a_post_name); - $select->setValue($a_query['lom_browser'] ?? ''); - $select->setOptions((array) ilMDUtilSelect::_getBrowserSelect( - '', - $a_field_name, - array(0 => $this->lng->txt('search_any')), - true - )); - return $select; - - // Education - case 'lom_interactivity': - $select = new ilSelectInputGUI($this->active_fields[$a_field_name], $a_post_name); - $select->setValue($a_query['lom_interactivity'] ?? ''); - $select->setOptions((array) ilMDUtilSelect::_getInteractivityTypeSelect( - '', - $a_field_name, - array(0 => $this->lng->txt('search_any')), - true - )); - return $select; - - case 'lom_resource': - $select = new ilSelectInputGUI($this->active_fields[$a_field_name], $a_post_name); - $select->setValue($a_query['lom_resource'] ?? ''); - $select->setOptions((array) ilMDUtilSelect::_getLearningResourceTypeSelect( - '', - $a_field_name, - array(0 => $this->lng->txt('search_any')), - true - )); - return $select; - - case 'lom_level': - $range = new ilCustomInputGUI($this->active_fields[$a_field_name]); - $html = $this->getRangeSelect( - $this->lng->txt('from'), - (string) ilMDUtilSelect::_getInteractivityLevelSelect( - $a_query['lom_level_start'] ?? '', - 'query[' . 'lom_level_start' . ']', - array(0 => $this->lng->txt('search_any')) - ), - $this->lng->txt('until'), - (string) ilMDUtilSelect::_getInteractivityLevelSelect( - $a_query['lom_level_end'] ?? '', - 'query[' . 'lom_level_end' . ']', - array(0 => $this->lng->txt('search_any')) - ) - ); - $range->setHtml($html); - return $range; - - case 'lom_density': - $range = new ilCustomInputGUI($this->active_fields[$a_field_name]); - $html = $this->getRangeSelect( - $this->lng->txt('from'), - (string) ilMDUtilSelect::_getSemanticDensitySelect( - $a_query['lom_density_start'] ?? '', - 'query[' . 'lom_density_start' . ']', - array(0 => $this->lng->txt('search_any')) - ), - $this->lng->txt('until'), - (string) ilMDUtilSelect::_getSemanticDensitySelect( - $a_query['lom_density_end'] ?? '', - 'query[' . 'lom_density_end' . ']', - array(0 => $this->lng->txt('search_any')) - ) - ); - $range->setHtml($html); - return $range; - - - case 'lom_user_role': - $select = new ilSelectInputGUI($this->active_fields[$a_field_name], $a_post_name); - $select->setValue($a_query['lom_user_role'] ?? ''); - $select->setOptions((array) ilMDUtilSelect::_getIntendedEndUserRoleSelect( - '', - $a_field_name, - array(0 => $this->lng->txt('search_any')), - true - )); - return $select; - - case 'lom_context': - $select = new ilSelectInputGUI($this->active_fields[$a_field_name], $a_post_name); - $select->setValue($a_query['lom_context'] ?? ''); - $select->setOptions((array) ilMDUtilSelect::_getContextSelect( - '', - $a_field_name, - array(0 => $this->lng->txt('search_any')), - true - )); - return $select; - - case 'lom_difficulty': - $range = new ilCustomInputGUI($this->active_fields[$a_field_name]); - $html = $this->getRangeSelect( - $this->lng->txt('from'), - (string) ilMDUtilSelect::_getDifficultySelect( - $a_query['lom_difficulty_start'] ?? '', - 'query[' . 'lom_difficulty_start' . ']', - array(0 => $this->lng->txt('search_any')) - ), - $this->lng->txt('until'), - (string) ilMDUtilSelect::_getDifficultySelect( - $a_query['lom_difficulty_end'] ?? '', - 'query[' . 'lom_difficulty_end' . ']', - array(0 => $this->lng->txt('search_any')) - ) - ); - $range->setHtml($html); - return $range; - - // Rights - case 'lom_costs': - $select = new ilSelectInputGUI($this->active_fields[$a_field_name], $a_post_name); - $select->setValue($a_query['lom_costs'] ?? ''); - $select->setOptions((array) ilMDUtilSelect::_getCostsSelect( - '', - $a_field_name, - array(0 => $this->lng->txt('search_any')), - true - )); - return $select; - - case 'lom_copyright': - $select = new ilSelectInputGUI($this->active_fields[$a_field_name], $a_post_name); - $select->setValue($a_query['lom_copyright'] ?? ''); - $select->setOptions((array) ilMDUtilSelect::_getCopyrightAndOtherRestrictionsSelect( - '', - $a_field_name, - array(0 => $this->lng->txt('search_any')), - true - )); - return $select; - - - - // Classification - case 'lom_purpose': - $select = new ilSelectInputGUI($this->active_fields[$a_field_name], $a_post_name); - $select->setValue($a_query['lom_purpose'] ?? ''); - $select->setOptions((array) ilMDUtilSelect::_getPurposeSelect( - '', - $a_field_name, - array(0 => $this->lng->txt('search_any')), - true - )); - return $select; - - case 'lom_taxon': - $text = new ilTextInputGUI($this->active_fields[$a_field_name], $a_post_name); - $text->setSubmitFormOnEnter(true); - $text->setValue($a_query['lom_taxon'] ?? ''); - $text->setSize(30); - $text->setMaxLength(255); - return $text; - - default: - if (substr($a_field_name, 0, 3) != 'adv') { - break; - } - - // Advanced meta data - $field_id = substr($a_field_name, 4); - $field = ilAdvancedMDFieldDefinition::getInstance((int) $field_id); - - $field_form = ilADTFactory::getInstance()->getSearchBridgeForDefinitionInstance($field->getADTDefinition(), true, false); - $field_form->setForm($a_form); - $field_form->setElementId($a_post_name); - $field_form->setTitle($this->active_fields[$a_field_name]); - $field_form->addToForm(); - - // #17071 - reload search values - if (is_array($a_query) && - array_key_exists($a_field_name, $a_query)) { - $field_form->importFromPost((array) $a_query); - $field_form->validate(); - } - return null; - } - return null; - } - - - /** - * Called from ilLuceneAdvancedQueryParser - * Parse a field specific query - * @param string $a_field - * @param string | array $a_query - */ - public function parseFieldQuery(string $a_field, $a_query): string - { - switch ($a_field) { - case 'lom_content': - return $a_query; - - case 'general_offline': - - switch ($a_query) { - case self::OFFLINE_QUERY: - return 'offline:1'; - - default: - return '-offline:1'; - } - - // General - // no break - case 'lom_language': - return 'lomLanguage:' . $a_query; - - case 'lom_keyword': - return 'lomKeyword:' . $a_query; - - case 'lom_coverage': - return 'lomCoverage:' . $a_query; - - case 'lom_structure': - return 'lomStructure:' . $a_query; - - // Lifecycle - case 'lom_status': - return 'lomStatus:' . $a_query; - - case 'lom_version': - return 'lomVersion:' . $a_query; - - // Begin Contribute - case 'lom_role': - return 'lomRole:' . $a_query; - - case 'lom_role_entry': - return 'lomRoleEntity:' . $a_query; - // End contribute - - // Technical - case 'lom_format': - return 'lomFormat:' . $a_query; - - case 'lom_operating_system': - return 'lomOS:' . $a_query; - - case 'lom_browser': - return 'lomBrowser:' . $a_query; - - // Educational - case 'lom_interactivity': - return 'lomInteractivity:' . $a_query; - - case 'lom_resource': - return 'lomResource:' . $a_query; - - case 'lom_level_start': - $q_string = ''; - $options = (array) ilMDUtilSelect::_getInteractivityLevelSelect(0, 'lom_level', array(), true); - for ($i = $a_query; $i <= count($options); $i++) { - if (strlen($q_string)) { - $q_string .= 'OR '; - } - $q_string .= ('lomLevel:"' . $options[$i] . '" '); - } - return $q_string; - - case 'lom_level_end': - $q_string = ''; - $options = (array) ilMDUtilSelect::_getInteractivityLevelSelect(0, 'lom_level', array(), true); - for ($i = 1; $i <= $a_query; $i++) { - if (strlen($q_string)) { - $q_string .= 'OR '; - } - $q_string .= ('lomLevel:"' . $options[$i] . '" '); - } - return $q_string; - - case 'lom_density_start': - $q_string = ''; - $options = (array) ilMDUtilSelect::_getSemanticDensitySelect(0, 'lom_density', array(), true); - for ($i = $a_query; $i <= count($options); $i++) { - if (strlen($q_string)) { - $q_string .= 'OR '; - } - $q_string .= ('lomDensity:"' . $options[$i] . '" '); - } - return $q_string; - - case 'lom_density_end': - $q_string = ''; - $options = (array) ilMDUtilSelect::_getSemanticDensitySelect(0, 'lom_density', array(), true); - for ($i = 1; $i <= $a_query; $i++) { - if (strlen($q_string)) { - $q_string .= 'OR '; - } - $q_string .= ('lomDensity:"' . $options[$i] . '" '); - } - return $q_string; - - case 'lom_user_role': - return 'lomUserRole:' . $a_query; - - case 'lom_context': - return 'lomContext:' . $a_query; - - case 'lom_difficulty_start': - $q_string = ''; - $options = (array) ilMDUtilSelect::_getDifficultySelect(0, 'lom_difficulty', array(), true); - for ($i = $a_query; $i <= count($options); $i++) { - if (strlen($q_string)) { - $q_string .= 'OR '; - } - $q_string .= ('lomDifficulty:"' . $options[$i] . '" '); - } - return $q_string; - - case 'lom_difficulty_end': - $q_string = ''; - $options = (array) ilMDUtilSelect::_getDifficultySelect(0, 'lom_difficulty', array(), true); - for ($i = 1; $i <= $a_query; $i++) { - if (strlen($q_string)) { - $q_string .= 'OR '; - } - $q_string .= ('lomDifficulty:"' . $options[$i] . '" '); - } - return $q_string; - - // Rights - case 'lom_costs': - return 'lomCosts:' . $a_query; - - case 'lom_copyright': - return 'lomCopyright:' . $a_query; - - // Classification - case 'lom_purpose': - return 'lomPurpose:' . $a_query; - - case 'lom_taxon': - return 'lomTaxon:' . $a_query; - - default: - if (substr($a_field, 0, 3) != 'adv') { - break; - } - - // Advanced meta data - $field_id = substr($a_field, 4); - try { - // field might be invalid (cached query) - $field = ilAdvancedMDFieldDefinition::getInstance((int) $field_id); - } catch (Exception $ex) { - return ''; - } - - $adv_query = $field->getLuceneSearchString($a_query); - if ($adv_query) { - // #17558 - if (!is_array($adv_query)) { - return 'advancedMetaData_' . $field_id . ': ' . $adv_query; - } else { - $res = array(); - foreach ($adv_query as $adv_query_item) { - $res[] = 'advancedMetaData_' . $field_id . ': ' . $adv_query_item; - } - return '(' . implode(' OR ', $res) . ')'; - } - } - } - return ''; - } - - - /** - * Read active fields - */ - protected function readFields(): void - { - foreach (self::getFields() as $name => $translation) { - if ($this->settings->isActive($name)) { - $this->active_fields[$name] = $translation; - } - } - } - - /** - * Read active sections - */ - protected function readSections(): void - { - foreach ($this->getActiveFields() as $field_name => $translation) { - switch ($field_name) { - // Default section - case 'lom_content': - $this->active_sections['default']['fields'][] = 'lom_content'; - $this->active_sections['default']['name'] = ''; - break; - - case 'general_offline': - $this->active_sections['default']['fields'][] = 'general_offline'; - $this->active_sections['default']['name'] = ''; - break; - - case 'lom_type': - $this->active_sections['default']['fields'][] = 'lom_type'; - $this->active_sections['default']['name'] = ''; - break; - - // General - case 'lom_language': - $this->active_sections['general']['fields'][] = 'lom_language'; - $this->active_sections['general']['name'] = $this->lng->txt('meta_general'); - break; - case 'lom_keyword': - $this->active_sections['general']['fields'][] = 'lom_keyword'; - $this->active_sections['general']['name'] = $this->lng->txt('meta_general'); - break; - case 'lom_coverage': - $this->active_sections['general']['fields'][] = 'lom_coverage'; - $this->active_sections['general']['name'] = $this->lng->txt('meta_general'); - break; - case 'lom_structure': - $this->active_sections['general']['fields'][] = 'lom_structure'; - $this->active_sections['general']['name'] = $this->lng->txt('meta_general'); - break; - - // Lifecycle - case 'lom_status': - $this->active_sections['lifecycle']['fields'][] = 'lom_status'; - $this->active_sections['lifecycle']['name'] = $this->lng->txt('meta_lifecycle'); - break; - case 'lom_version': - $this->active_sections['lifecycle']['fields'][] = 'lom_version'; - $this->active_sections['lifecycle']['name'] = $this->lng->txt('meta_lifecycle'); - break; - case 'lom_contribute': - $this->active_sections['lifecycle']['fields'][] = 'lom_contribute'; - $this->active_sections['lifecycle']['name'] = $this->lng->txt('meta_lifecycle'); - break; - - // Technical - case 'lom_format': - $this->active_sections['technical']['fields'][] = 'lom_format'; - $this->active_sections['technical']['name'] = $this->lng->txt('meta_technical'); - break; - case 'lom_operating_system': - $this->active_sections['technical']['fields'][] = 'lom_operating_system'; - $this->active_sections['technical']['name'] = $this->lng->txt('meta_technical'); - break; - case 'lom_browser': - $this->active_sections['technical']['fields'][] = 'lom_browser'; - $this->active_sections['technical']['name'] = $this->lng->txt('meta_technical'); - break; - - // Education - case 'lom_interactivity': - $this->active_sections['education']['fields'][] = 'lom_interactivity'; - $this->active_sections['education']['name'] = $this->lng->txt('meta_education'); - break; - case 'lom_resource': - $this->active_sections['education']['fields'][] = 'lom_resource'; - $this->active_sections['education']['name'] = $this->lng->txt('meta_education'); - break; - case 'lom_level': - $this->active_sections['education']['fields'][] = 'lom_level'; - $this->active_sections['education']['name'] = $this->lng->txt('meta_education'); - break; - case 'lom_density': - $this->active_sections['education']['fields'][] = 'lom_density'; - $this->active_sections['education']['name'] = $this->lng->txt('meta_education'); - break; - case 'lom_user_role': - $this->active_sections['education']['fields'][] = 'lom_user_role'; - $this->active_sections['education']['name'] = $this->lng->txt('meta_education'); - break; - case 'lom_context': - $this->active_sections['education']['fields'][] = 'lom_context'; - $this->active_sections['education']['name'] = $this->lng->txt('meta_education'); - break; - case 'lom_difficulty': - $this->active_sections['education']['fields'][] = 'lom_difficulty'; - $this->active_sections['education']['name'] = $this->lng->txt('meta_education'); - break; - - // Rights - case 'lom_costs': - $this->active_sections['rights']['fields'][] = 'lom_costs'; - $this->active_sections['rights']['name'] = $this->lng->txt('meta_rights'); - break; - case 'lom_copyright': - $this->active_sections['rights']['fields'][] = 'lom_copyright'; - $this->active_sections['rights']['name'] = $this->lng->txt('meta_rights'); - break; - - // Classification - case 'lom_purpose': - $this->active_sections['classification']['fields'][] = 'lom_purpose'; - $this->active_sections['classification']['name'] = $this->lng->txt('meta_classification'); - break; - case 'lom_taxon': - $this->active_sections['classification']['fields'][] = 'lom_taxon'; - $this->active_sections['classification']['name'] = $this->lng->txt('meta_classification'); - break; - - default: - if (substr($field_name, 0, 3) != 'adv') { - break; - } - - // Advanced meta data - $field_id = substr($field_name, 4); - $field = ilAdvancedMDFieldDefinition::getInstance((int) $field_id); - $record_id = $field->getRecordId(); - - $translations = ilAdvancedMDRecordTranslations::getInstanceByRecordId($record_id); - $this->active_sections['adv_record_' . $record_id]['fields'][] = $field_name; - $this->active_sections['adv_record_' . $record_id]['name'] = $translations->getTitleForLanguage($this->user->getLanguage()); - break; - } - } - } - - /** - * get a range selection - */ - protected function getRangeSelect( - string $txt_from, - string $select_from, - string $txt_until, - string $select_until - ): string { - $tpl = new ilTemplate('tpl.range_search.html', true, true, 'components/ILIAS/Search'); - $tpl->setVariable('TXT_FROM', $txt_from); - $tpl->setVariable('FROM', $select_from); - $tpl->setVariable('TXT_UPTO', $txt_until); - $tpl->setVariable('UPTO', $select_until); - return $tpl->get(); - } -} diff --git a/components/ILIAS/Search/classes/Lucene/class.ilLuceneAdvancedSearchGUI.php b/components/ILIAS/Search/classes/Lucene/class.ilLuceneAdvancedSearchGUI.php deleted file mode 100755 index a7631990fcbf..000000000000 --- a/components/ILIAS/Search/classes/Lucene/class.ilLuceneAdvancedSearchGUI.php +++ /dev/null @@ -1,387 +0,0 @@ - -* -* @ilCtrl_IsCalledBy ilLuceneAdvancedSearchGUI: ilSearchControllerGUI -* @ilCtrl_Calls ilLuceneAdvancedSearchGUI: ilObjectGUI, ilContainerGUI -* @ilCtrl_Calls ilLuceneAdvancedSearchGUI: ilObjCategoryGUI, ilObjCourseGUI, ilObjFolderGUI, ilObjGroupGUI -* @ilCtrl_Calls ilLuceneAdvancedSearchGUI: ilObjStudyProgrammeGUI -* @ilCtrl_Calls ilLuceneAdvancedSearchGUI: ilObjRootFolderGUI, ilObjectCopyGUI -* -* @ingroup ServicesSearch -*/ -class ilLuceneAdvancedSearchGUI extends ilSearchBaseGUI -{ - protected ilTabsGUI $tabs_gui; - protected ilHelpGUI $help; - - protected ilLuceneAdvancedSearchFields $fields; - - protected ?array $admin_panel_commands; - protected ?array $admin_view_button; - protected ?array $creation_selector; - protected ?string $page_form_action; - - - /** - * Constructor - */ - public function __construct() - { - global $DIC; - - $this->tabs_gui = $DIC->tabs(); - $this->help = $DIC['ilHelp']; - parent::__construct(); - - $this->fields = ilLuceneAdvancedSearchFields::getInstance(); - $this->initUserSearchCache(); - } - - /** - * Execute Command - */ - public function executeCommand(): void - { - $next_class = $this->ctrl->getNextClass($this); - $cmd = $this->ctrl->getCmd(); - - $this->prepareOutput(); - switch ($next_class) { - case 'ilobjectcopygui': - $this->ctrl->setReturn($this); - $cp = new ilObjectCopyGUI($this); - $this->ctrl->forwardCommand($cp); - break; - - - default: - if (!$cmd) { - $cmd = "showSavedResults"; - } - $this->handleCommand($cmd); - break; - } - } - - - /** - * Show saved results - */ - public function showSavedResults(): void - { - $qp = new ilLuceneAdvancedQueryParser($this->search_cache->getQuery()); - $qp->parse(); - $searcher = ilLuceneSearcher::getInstance($qp); - $searcher->search(); - - // Load saved results - $filter = ilLuceneSearchResultFilter::getInstance($this->user->getId()); - $filter->loadFromDb(); - - // Highlight - if ($filter->getResultObjIds()) { - $searcher->highlight($filter->getResultObjIds()); - } - - $this->tpl->addBlockFile('ADM_CONTENT', 'adm_content', 'tpl.lucene_adv_search.html', 'components/ILIAS/Search'); - $presentation = new ilSearchResultPresentation($this); - $presentation->setResults($filter->getResultIds()); - $presentation->setSearcher($searcher); - - - // TODO: other handling required - $this->addPager($filter, 'max_page'); - $presentation->setPreviousNext($this->prev_link, $this->next_link); - - if ($presentation->render()) { - $this->tpl->setVariable('SEARCH_RESULTS', $presentation->getHTML()); - } elseif (strlen(trim($qp->getQuery()))) { - $this->tpl->setOnScreenMessage('info', $this->lng->txt('search_no_match')); - } - - // and finally add search form - $this->initFormSearch(); - $this->tpl->setVariable('SEARCH_TABLE', $this->form->getHTML()); - - if ($filter->getResultIds()) { - $this->fillAdminPanel(); - } - } - - /** - * Show search form - */ - protected function initFormSearch(): void - { - $this->form = new ilPropertyFormGUI(); - $this->form->setFormAction($this->ctrl->getFormAction($this, 'search')); - $this->form->setTitle($this->lng->txt('search_advanced')); - $this->form->addCommandButton('search', $this->lng->txt('search')); - $this->form->addCommandButton('reset', $this->lng->txt('reset')); - - foreach ($this->fields->getActiveSections() as $definition) { - if ($definition['name'] != 'default') { - $section = new ilFormSectionHeaderGUI(); - $section->setTitle($definition['name']); - $this->form->addItem($section); - } - - foreach ($definition['fields'] as $field_name) { - if (is_object($element = $this->fields->getFormElement($this->search_cache->getQuery(), $field_name, $this->form))) { - $this->form->addItem($element); - } - } - } - } - - /** - * Search from main menu - */ - protected function remoteSearch(): void - { - $root_id = 0; - if ($this->http->wrapper()->post()->has('root_id')) { - $root_id = $this->http->wrapper()->post()->retrieve( - 'root_id', - $this->refinery->kindlyTo()->int() - ); - } - $queryString = ''; - if ($this->http->wrapper()->post()->has('queryString')) { - $queryString = $this->http->wrapper()->post()->retrieve( - 'queryString', - $this->refinery->kindlyTo()->string() - ); - } - $this->search_cache->setRoot($root_id); - $this->search_cache->setQuery(['lom_content' => $queryString]); - $this->search_cache->save(); - $this->search(); - } - - protected function search(): void - { - if (!is_array($this->search_cache->getQuery())) { - // TOD: handle empty advances search - $this->tpl->setOnScreenMessage('info', $this->lng->txt('msg_no_search_string')); - $this->showSavedResults(); - return; - } - ilSession::clear('max_page'); - $this->search_cache->deleteCachedEntries(); - - // Reset details - ilSubItemListGUI::resetDetails(); - - $this->performSearch(); - } - - /** - * Reset search form - */ - protected function reset(): void - { - $this->search_cache->setQuery(array()); - $this->search_cache->save(); - $this->showSavedResults(); - } - - /** - * Perform search - */ - protected function performSearch(): void - { - ilSession::clear('vis_references'); - $qp = new ilLuceneAdvancedQueryParser($this->search_cache->getQuery()); - $qp->parse(); - if (!strlen(trim($qp->getQuery()))) { - $this->tpl->setOnScreenMessage('info', $this->lng->txt('msg_no_search_string')); - $this->showSavedResults(); - return; - } - - $searcher = ilLuceneSearcher::getInstance($qp); - $searcher->search(); - - // Filter results - $filter = ilLuceneSearchResultFilter::getInstance($this->user->getId()); - $filter->addFilter(new ilLucenePathFilter($this->search_cache->getRoot())); - $filter->setCandidates($searcher->getResult()); - $filter->filter(); - - if ($filter->getResultObjIds()) { - $searcher->highlight($filter->getResultObjIds()); - } - - // Show results - $this->tpl->addBlockFile('ADM_CONTENT', 'adm_content', 'tpl.lucene_adv_search.html', 'components/ILIAS/Search'); - $presentation = new ilSearchResultPresentation($this); - $presentation->setResults($filter->getResultIds()); - $presentation->setSearcher($searcher); - - // TODO: other handling required - $this->addPager($filter, 'max_page'); - $presentation->setPreviousNext($this->prev_link, $this->next_link); - - if ($presentation->render()) { - $this->tpl->setVariable('SEARCH_RESULTS', $presentation->getHTML()); - } else { - $this->tpl->setOnScreenMessage('info', $this->lng->txt('search_no_match')); - } - - // and finally add search form - $this->initFormSearch(); - $this->tpl->setVariable('SEARCH_TABLE', $this->form->getHTML()); - - if ($filter->getResultIds()) { - $this->fillAdminPanel(); - } - } - - /** - * Add admin panel command - */ - public function prepareOutput(): void - { - parent::prepareOutput(); - $this->getTabs(); - } - - /** - * get tabs - */ - protected function getTabs(): void - { - $this->help->setScreenIdComponent("src_luc"); - - $this->tabs_gui->addTarget('search', $this->ctrl->getLinkTargetByClass('illucenesearchgui')); - - if (ilSearchSettings::getInstance()->isLuceneUserSearchEnabled()) { - $this->tabs_gui->addTarget('search_user', $this->ctrl->getLinkTargetByClass('illuceneusersearchgui')); - } - - if ( - !ilSearchSettings::getInstance()->getHideAdvancedSearch() and - $this->fields->getActiveFields()) { - $this->tabs_gui->addTarget('search_advanced', $this->ctrl->getLinkTarget($this)); - } - - $this->tabs_gui->setTabActive('search_advanced'); - } - - /** - * Init user search cache - * - * @access private - * - */ - protected function initUserSearchCache(): void - { - $this->search_cache = ilUserSearchCache::_getInstance($this->user->getId()); - $this->search_cache->switchSearchType(ilUserSearchCache::LUCENE_ADVANCED); - $page_number = $this->initPageNumberFromQuery(); - if ($page_number) { - $this->search_cache->setResultPageNumber($page_number); - } - if ($this->http->wrapper()->post()->has('query')) { - $this->search_cache->setQuery($this->http->request()->getParsedBody()['query'] ?? []); - } - } - - /** - * @todo Couldn't find any of these template variables anywhere. Is this still currently used? - */ - protected function fillAdminPanel(): void - { - $adm_view_cmp = $adm_cmds = $creation_selector = $adm_view = false; - - // admin panel commands - if (isset($this->admin_panel_commands) && (count((array) $this->admin_panel_commands) > 0)) { - foreach ($this->admin_panel_commands as $cmd) { - $this->tpl->setCurrentBlock("lucene_admin_panel_cmd"); - $this->tpl->setVariable("LUCENE_PANEL_CMD", $cmd["cmd"]); - $this->tpl->setVariable("LUCENE_TXT_PANEL_CMD", $cmd["txt"]); - $this->tpl->parseCurrentBlock(); - } - - $adm_cmds = true; - } - if ($adm_cmds) { - $this->tpl->setCurrentBlock("lucene_adm_view_components"); - $this->tpl->setVariable("LUCENE_ADM_IMG_ARROW", ilUtil::getImagePath("nav/arrow_upright.svg")); - $this->tpl->setVariable("LUCENE_ADM_ALT_ARROW", $this->lng->txt("actions")); - $this->tpl->parseCurrentBlock(); - $adm_view_cmp = true; - } - - // admin view button - if (isset($this->admin_view_button) && is_array($this->admin_view_button)) { - if (is_array($this->admin_view_button)) { - $this->tpl->setCurrentBlock("lucene_admin_button"); - $this->tpl->setVariable( - "LUCENE_ADMIN_MODE_LINK", - $this->admin_view_button["link"] - ); - $this->tpl->setVariable( - "LUCENE_TXT_ADMIN_MODE", - $this->admin_view_button["txt"] - ); - $this->tpl->parseCurrentBlock(); - } - $this->tpl->setCurrentBlock("lucene_admin_view"); - $this->tpl->parseCurrentBlock(); - $adm_view = true; - } - - // creation selector - if (isset($this->creation_selector) && is_array($this->creation_selector)) { - $this->tpl->setCurrentBlock("lucene_add_commands"); - if ($adm_cmds) { - $this->tpl->setVariable("LUCENE_ADD_COM_WIDTH", 'width="1"'); - } - $this->tpl->setVariable( - "LUCENE_SELECT_OBJTYPE_REPOS", - $this->creation_selector["options"] - ); - $this->tpl->setVariable( - "LUCENE_BTN_NAME_REPOS", - $this->creation_selector["command"] - ); - $this->tpl->setVariable( - "LUCENE_TXT_ADD_REPOS", - $this->creation_selector["txt"] - ); - $this->tpl->parseCurrentBlock(); - $creation_selector = true; - } - if ($adm_view || $creation_selector) { - $this->tpl->setCurrentBlock("lucene_adm_panel"); - if ($adm_view_cmp) { - $this->tpl->setVariable("LUCENE_ADM_TBL_WIDTH", 'width:"100%";'); - } - $this->tpl->parseCurrentBlock(); - } - } -} diff --git a/components/ILIAS/Search/classes/Lucene/class.ilLuceneAdvancedSearchSettings.php b/components/ILIAS/Search/classes/Lucene/class.ilLuceneAdvancedSearchSettings.php deleted file mode 100755 index 6ea230edcfd4..000000000000 --- a/components/ILIAS/Search/classes/Lucene/class.ilLuceneAdvancedSearchSettings.php +++ /dev/null @@ -1,76 +0,0 @@ - -* @ingroup ServicesSearch -*/ -class ilLuceneAdvancedSearchSettings -{ - private static ?ilLuceneAdvancedSearchSettings $instance = null; - private array $fields = []; - - protected ilSetting $storage; - - /** - * Constructor - */ - private function __construct() - { - $this->storage = new ilSetting('lucene_adv_search'); - $this->read(); - } - - public static function getInstance(): ilLuceneAdvancedSearchSettings - { - if (self::$instance instanceof ilLuceneAdvancedSearchSettings) { - return self::$instance; - } - return self::$instance = new ilLuceneAdvancedSearchSettings(); - } - - /** - * check if field is active - */ - public function isActive(string $a_field): bool - { - return $this->fields[$a_field] ?: false; - } - - public function setActive(string $a_field, bool $a_status): void - { - $this->fields[$a_field] = $a_status; - } - - public function save(): void - { - foreach ($this->fields as $name => $status) { - $this->storage->set($name, $status ? "1" : "0"); - } - } - - private function read(): void - { - foreach (ilLuceneAdvancedSearchFields::getFields() as $name => $translation) { - $this->fields[$name] = (bool) $this->storage->get($name, 'true'); - } - } -} diff --git a/components/ILIAS/Search/classes/Lucene/class.ilLuceneSearchGUI.php b/components/ILIAS/Search/classes/Lucene/class.ilLuceneSearchGUI.php index ec0cd642a649..4247fbb1fede 100755 --- a/components/ILIAS/Search/classes/Lucene/class.ilLuceneSearchGUI.php +++ b/components/ILIAS/Search/classes/Lucene/class.ilLuceneSearchGUI.php @@ -22,18 +22,14 @@ use ILIAS\UI\Renderer as UIRenderer; /** -* @classDescription GUI for simple Lucene search -* -* @author Stefan Meyer -* -* @ilCtrl_IsCalledBy ilLuceneSearchGUI: ilSearchControllerGUI -* @ilCtrl_Calls ilLuceneSearchGUI: ilObjectGUI, ilContainerGUI -* @ilCtrl_Calls ilLuceneSearchGUI: ilObjCategoryGUI, ilObjCourseGUI, ilObjFolderGUI, ilObjGroupGUI -* @ilCtrl_Calls ilLuceneSearchGUI: ilObjStudyProgrammeGUI -* @ilCtrl_Calls ilLuceneSearchGUI: ilObjRootFolderGUI, ilObjectCopyGUI -* -* @ingroup ServicesSearch -*/ + * @author Stefan Meyer + * + * @ilCtrl_IsCalledBy ilLuceneSearchGUI: ilSearchControllerGUI + * @ilCtrl_Calls ilLuceneSearchGUI: ilObjectGUI, ilContainerGUI + * @ilCtrl_Calls ilLuceneSearchGUI: ilObjCategoryGUI, ilObjCourseGUI, ilObjFolderGUI, ilObjGroupGUI + * @ilCtrl_Calls ilLuceneSearchGUI: ilObjStudyProgrammeGUI + * @ilCtrl_Calls ilLuceneSearchGUI: ilObjRootFolderGUI, ilObjectCopyGUI + */ class ilLuceneSearchGUI extends ilSearchBaseGUI { protected ilTabsGUI $tabs; @@ -41,8 +37,6 @@ class ilLuceneSearchGUI extends ilSearchBaseGUI protected UIFactory $ui_factory; protected UIRenderer $ui_renderer; - protected ilLuceneAdvancedSearchFields $fields; - protected ?int $root_node; protected array $admin_panel_commands = []; protected array $admin_view_button = []; @@ -64,7 +58,6 @@ public function __construct() $this->ui_factory = $DIC->ui()->factory(); $this->ui_renderer = $DIC->ui()->renderer(); - $this->fields = ilLuceneAdvancedSearchFields::getInstance(); $this->initFilter(self::SEARCH_FORM_LUCENE); $this->initUserSearchCache(); } @@ -207,7 +200,6 @@ protected function showSavedResults(): bool /** * Search (button pressed) - * @return void */ protected function search(): void { @@ -323,19 +315,9 @@ protected function getTabs(): void $this->tabs->addTarget('search_user', $this->ctrl->getLinkTargetByClass('illuceneusersearchgui')); } - if ($this->fields->getActiveFields() && !ilSearchSettings::getInstance()->getHideAdvancedSearch()) { - $this->tabs->addTarget('search_advanced', $this->ctrl->getLinkTargetByClass('illuceneAdvancedSearchgui')); - } - $this->tabs->setTabActive('search'); } - /** - * Init user search cache - * - * @access private - * - */ protected function initUserSearchCache(): void { $this->search_cache = ilUserSearchCache::_getInstance($this->user->getId()); @@ -520,17 +502,12 @@ protected function setPageFormAction(string $a_action): void $this->page_form_action = $a_action; } - /** - * Show search form - * @return void - */ protected function showSearchForm(): void { $this->tpl->addBlockFile('ADM_CONTENT', 'adm_content', 'tpl.search.html', 'components/ILIAS/Search'); $this->renderSearch($this->search_cache->getQuery(), $this->search_cache->getRoot()); } - /** * Parse creation date */ @@ -562,5 +539,4 @@ protected function parseCreationFilter(): string return ''; } - // end-patch creation_date } diff --git a/components/ILIAS/Search/classes/Lucene/class.ilLuceneUserSearchGUI.php b/components/ILIAS/Search/classes/Lucene/class.ilLuceneUserSearchGUI.php index 0198fccc9dc3..9ed33b56648a 100755 --- a/components/ILIAS/Search/classes/Lucene/class.ilLuceneUserSearchGUI.php +++ b/components/ILIAS/Search/classes/Lucene/class.ilLuceneUserSearchGUI.php @@ -17,15 +17,12 @@ *********************************************************************/ declare(strict_types=1); + /** - * @classDescription GUI for Lucene user search - * * @author Stefan Meyer * * @ilCtrl_Calls ilLuceneUserSearchGUI: ilPublicUserProfileGUI * @ilCtrl_IsCalledBy ilLuceneUserSearchGUI: ilSearchControllerGUI - * - * @ingroup ServicesSearch */ class ilLuceneUserSearchGUI extends ilSearchBaseGUI { @@ -134,10 +131,6 @@ protected function remoteSearch(): void $this->search(); } - /** - * Show saved results - * @return void - */ protected function showSavedResults(): void { if (strlen($this->search_cache->getQuery())) { @@ -150,7 +143,6 @@ protected function showSavedResults(): void /** * Search (button pressed) - * @return void */ protected function search(): void { @@ -207,14 +199,6 @@ protected function getTabs(): void $this->tabs->addTarget('search_user', $this->ctrl->getLinkTargetByClass('illuceneusersearchgui')); } - $fields = ilLuceneAdvancedSearchFields::getInstance(); - - if ( - !ilSearchSettings::getInstance()->getHideAdvancedSearch() and - $fields->getActiveFields()) { - $this->tabs->addTarget('search_advanced', $this->ctrl->getLinkTargetByClass('illuceneadvancedsearchgui')); - } - $this->tabs->setTabActive('search_user'); } @@ -246,7 +230,6 @@ protected function initUserSearchCache(): void /** * Show search form - * @return boolean */ protected function showSearchForm() { diff --git a/components/ILIAS/Search/classes/ObjGUI/class.ilObjSearchSettingsFormGUI.php b/components/ILIAS/Search/classes/ObjGUI/class.ilObjSearchSettingsFormGUI.php index adb9163961b2..7a75aec7f008 100755 --- a/components/ILIAS/Search/classes/ObjGUI/class.ilObjSearchSettingsFormGUI.php +++ b/components/ILIAS/Search/classes/ObjGUI/class.ilObjSearchSettingsFormGUI.php @@ -17,6 +17,7 @@ *********************************************************************/ declare(strict_types=1); + use ILIAS\HTTP\GlobalHttpState; use ILIAS\DI\UIServices; use ILIAS\UI\Factory; @@ -123,7 +124,6 @@ protected function update(): void if (!is_null($main_data['filter'])) { $settings->setLuceneItemFilter((array) $main_data['filter']); } - $settings->setHideAdvancedSearch((bool) $main_data['hide_adv_search']); $settings->enableDateFilter((bool) $main_data['cdate']); $settings->setAutoCompleteLength((int) $user_data['auto_complete_length']); $settings->showInactiveUser((bool) $user_data['inactive_user']); @@ -222,11 +222,6 @@ protected function initForm(bool $read_only): StandardForm $this->lng->txt('search_cdate_filter_info') )->withValue($settings->isDateFilterEnabled()); - // hide advanced search - $hide_adv = $field_factory->checkbox( - $this->lng->txt('search_hide_adv_search') - )->withValue($settings->getHideAdvancedSearch()); - // number of auto complete entries $options = [ 5 => 5, @@ -262,7 +257,6 @@ protected function initForm(bool $read_only): StandardForm 'max_hits' => $hits, 'search_type' => $type, 'operator' => $operator, - 'hide_adv_search' => $hide_adv, 'filter' => $item_filter, 'cdate' => $cdate ], diff --git a/components/ILIAS/Search/classes/ObjGUI/class.ilObjSearchSettingsGUI.php b/components/ILIAS/Search/classes/ObjGUI/class.ilObjSearchSettingsGUI.php index d795c51905f5..2083739af371 100755 --- a/components/ILIAS/Search/classes/ObjGUI/class.ilObjSearchSettingsGUI.php +++ b/components/ILIAS/Search/classes/ObjGUI/class.ilObjSearchSettingsGUI.php @@ -17,6 +17,7 @@ *********************************************************************/ declare(strict_types=1); + use ILIAS\HTTP\GlobalHttpState; use ILIAS\Refinery\Factory; use ILIAS\DI\UIServices; @@ -28,8 +29,6 @@ */ class ilObjSearchSettingsGUI extends ilObjectGUI { - private GlobalHttpState $http; - protected Factory $refinery; protected UIServices $ui; protected ilLogger $src_logger; protected ilObjUser $user; @@ -40,8 +39,6 @@ public function __construct($a_data, $a_id, $a_call_by_reference, $a_prepare_out { global $DIC; - $this->http = $DIC->http(); - $this->refinery = $DIC->refinery(); $this->ui = $DIC->ui(); $this->src_logger = $DIC->logger()->src(); $this->user = $DIC->user(); @@ -108,10 +105,6 @@ public function executeCommand(): void $this->redirectToLuceneSettings(); break; - case 'advancedLuceneSettings': - $this->advancedLuceneSettingsObject(); - break; - default: $cmd .= "Object"; $this->$cmd(); @@ -175,14 +168,6 @@ protected function getTabs(): void ); } - if ($this->rbac_system->checkAccess('read', $this->object->getRefId())) { - $this->tabs_gui->addTab( - 'lucene_advanced_settings', - $this->lng->txt('lucene_advanced_settings'), - $this->ctrl->getLinkTarget($this, 'advancedLuceneSettings') - ); - } - if ($this->rbac_system->checkAccess('read', $this->object->getRefId())) { $this->tabs_gui->addTab( 'lucene_settings_tab', @@ -202,39 +187,4 @@ protected function getTabs(): void ); } } - - protected function advancedLuceneSettingsObject(): void - { - $this->tabs_gui->setTabActive('lucene_advanced_settings'); - - $table = new ilLuceneAdvancedSearchActivationTableGUI($this, 'advancedLuceneSettings'); - $table->setTitle($this->lng->txt('lucene_advanced_settings_table')); - $table->parse(ilLuceneAdvancedSearchSettings::getInstance()); - - $this->tpl->setContent($table->getHTML()); - } - - protected function saveAdvancedLuceneSettingsObject(): void - { - if (!$this->access->checkAccess('write', '', $this->object->getRefId())) { - $this->tpl->setOnScreenMessage('failure', $this->lng->txt('permission_denied'), true); - $this->ctrl->redirect($this, 'settings'); - } - - $enabled_md_ids = new SplFixedArray(0); - if ($this->http->wrapper()->post()->has('fid')) { - $enabled_md_ids = $this->http->wrapper()->post()->retrieve( - 'fid', - $this->refinery->kindlyTo()->listOf($this->refinery->kindlyTo()->string()) - ); - } - - $settings = ilLuceneAdvancedSearchSettings::getInstance(); - foreach (ilLuceneAdvancedSearchFields::getFields() as $field => $translation) { - $settings->setActive($field, in_array($field, (array) $enabled_md_ids)); - } - $settings->save(); - $this->tpl->setOnScreenMessage('success', $this->lng->txt('settings_saved'), true); - $this->ctrl->redirect($this, 'advancedLuceneSettings'); - } } diff --git a/components/ILIAS/Search/classes/Provider/SearchMetaBarProvider.php b/components/ILIAS/Search/classes/Provider/SearchMetaBarProvider.php index 0e5c4884c7c2..a61ebc13a0be 100755 --- a/components/ILIAS/Search/classes/Provider/SearchMetaBarProvider.php +++ b/components/ILIAS/Search/classes/Provider/SearchMetaBarProvider.php @@ -83,7 +83,7 @@ public function getMetaBarItems(): array ->withLegacyContent($content()) ->withSymbol($this->dic->ui()->factory()->symbol()->glyph()->search()) ->withTitle($this->dic->language()->txt("search")) - ->withPosition(1) + ->withPosition(2) ->withAvailableCallable( function () { return $this->dic->rbac()->system()->checkAccess('search', ilSearchSettings::_getSearchSettingRefId()); diff --git a/components/ILIAS/Search/classes/class.ilAdvancedSearchGUI.php b/components/ILIAS/Search/classes/class.ilAdvancedSearchGUI.php deleted file mode 100755 index f22a0f58b79e..000000000000 --- a/components/ILIAS/Search/classes/class.ilAdvancedSearchGUI.php +++ /dev/null @@ -1,991 +0,0 @@ - -* -* @ilCtrl_Calls ilAdvancedSearchGUI: ilObjectGUI, ilContainerGUI -* @ilCtrl_Calls ilAdvancedSearchGUI: ilObjCategoryGUI, ilObjCourseGUI, ilObjFolderGUI, ilObjGroupGUI -* @ilCtrl_Calls ilAdvancedSearchGUI: ilObjStudyProgrammeGUI -* @ilCtrl_Calls ilAdvancedSearchGUI: ilObjRootFolderGUI, ilObjectCopyGUI, ilPropertyFormGUI -* -* @package ilias-search -* -*/ -class ilAdvancedSearchGUI extends ilSearchBaseGUI -{ - public const TYPE_LOM = 1; - public const TYPE_ADV_MD = 2; - - protected string $last_section = 'adv_search'; - - protected ilLuceneAdvancedSearchFields $fields; - - - - - private bool $stored = false; - private array $options = array(); - protected array $filter = array(); - - protected ilTabsGUI $tabs_gui; - protected ilHelpGUI $help_gui; - - public function __construct() - { - global $DIC; - - $this->tabs_gui = $DIC->tabs(); - $this->help_gui = $DIC->help(); - - - parent::__construct(); - - $this->lng->loadLanguageModule('meta'); - $this->fields = ilLuceneAdvancedSearchFields::getInstance(); - - $this->__setSearchOptions(); - } - - public function getRootNode(): int - { - return ROOT_FOLDER_ID; - } - - - public function executeCommand(): bool - { - $next_class = $this->ctrl->getNextClass($this); - $cmd = $this->ctrl->getCmd(); - - switch ($next_class) { - case "ilpropertyformgui": - - - case 'ilobjectcopygui': - $this->prepareOutput(); - $this->ctrl->setReturn($this, ''); - - $cp = new ilObjectCopyGUI($this); - $this->ctrl->forwardCommand($cp); - break; - - default: - $this->initUserSearchCache(); - if (!$cmd) { - $last_sub_section = (int) ilSession::get('search_last_sub_section'); - switch ($last_sub_section) { - case self::TYPE_ADV_MD: - $cmd = "showSavedAdvMDResults"; - break; - - default: - $cmd = "showSavedResults"; - break; - } - } - $this->prepareOutput(); - $this->handleCommand($cmd); - break; - } - return true; - } - public function reset(): void - { - $this->initSearchType(self::TYPE_LOM); - $this->options = array(); - $this->search_cache->setQuery(array()); - $this->search_cache->save(); - $this->showSearch(); - } - - public function searchInResults(): bool - { - $this->initSearchType(self::TYPE_LOM); - $this->search_mode = 'in_results'; - $this->search_cache->setResultPageNumber(1); - ilSession::clear('adv_max_page'); - $this->performSearch(); - - return true; - } - - protected function remoteSearch(): void - { - $root_id = 0; - if ($this->http->wrapper()->post()->has('root_id')) { - $root_id = $this->http->wrapper()->post()->retrieve( - 'root_id', - $this->refinery->kindlyTo()->int() - ); - } - $queryString = ''; - if ($this->http->wrapper()->post()->has('queryString')) { - $queryString = $this->http->wrapper()->post()->retrieve( - 'queryString', - $this->refinery->kindlyTo()->string() - ); - } - $this->search_cache->setRoot($root_id); - $this->search_cache->setResultPageNumber(1); - $this->search_cache->setQuery(array('lom_content' => $queryString)); - $this->search_cache->save(); - - $this->options = $this->search_cache->getQuery(); - $this->options['type'] = 'all'; - - $this->performSearch(); - } - - - - public function performSearch(): bool - { - global $DIC; - $this->initSearchType(self::TYPE_LOM); - $page_number = $this->initPageNumberFromQuery(); - if (!$page_number and $this->search_mode != 'in_results') { - ilSession::clear('adv_max_page'); - $this->search_cache->deleteCachedEntries(); - } - - if ($this->http->wrapper()->post()->has('query')) { - $this->search_cache->setQuery( - $this->getQueryFromPost() - ); - } - $res = new ilSearchResult(); - if ($res_con = $this->__performContentSearch()) { - $this->__storeEntries($res, $res_con); - } - if ($res_lan = $this->__performLanguageSearch()) { - $this->__storeEntries($res, $res_lan); - } - if ($res_gen = $this->__performGeneralSearch()) { - $this->__storeEntries($res, $res_gen); - } - if ($res_lif = $this->__performLifecycleSearch()) { - $this->__storeEntries($res, $res_lif); - } - if ($res_con = $this->__performContributeSearch()) { - $this->__storeEntries($res, $res_con); - } - if ($res_ent = $this->__performEntitySearch()) { - $this->__storeEntries($res, $res_ent); - } - if ($res_req = $this->__performRequirementSearch()) { - $this->__storeEntries($res, $res_req); - } - if ($res_for = $this->__performFormatSearch()) { - $this->__storeEntries($res, $res_for); - } - if ($res_edu = $this->__performEducationalSearch()) { - $this->__storeEntries($res, $res_edu); - } - if ($res_typ = $this->__performTypicalAgeRangeSearch()) { - $this->__storeEntries($res, $res_typ); - } - if ($res_rig = $this->__performRightsSearch()) { - $this->__storeEntries($res, $res_rig); - } - if ($res_cla = $this->__performClassificationSearch()) { - $this->__storeEntries($res, $res_cla); - } - if ($res_tax = $this->__performTaxonSearch()) { - $this->__storeEntries($res, $res_tax); - } - if ($res_key = $this->__performKeywordSearch()) { - $this->__storeEntries($res, $res_key); - } - - $this->searchAdvancedMD($res); - - if ($this->search_mode == 'in_results') { - $old_result_obj = new ilSearchResult($this->user->getId()); - $old_result_obj->read(ilUserSearchCache::ADVANCED_MD_SEARCH); - - $res->diffEntriesFromResult(); - } - - $res->filter($this->getRootNode(), (ilSearchSettings::getInstance()->getDefaultOperator() == ilSearchSettings::OPERATOR_AND)); - $res->save(); - $this->showSearch(); - - if (!count($res->getResults())) { - $this->tpl->setOnScreenMessage('info', $this->lng->txt('search_no_match')); - } - - if ($res->isLimitReached()) { - #$message = sprintf($this->lng->txt('search_limit_reached'),$this->settings->getMaxHits()); - #ilUtil::sendInfo($message); - } - - $this->addPager($res, 'adv_max_page'); - - $presentation = new ilSearchResultPresentation($this, ilSearchResultPresentation::MODE_STANDARD); - $presentation->setResults($res->getResultsForPresentation()); - $presentation->setPreviousNext($this->prev_link, $this->next_link); - - if ($presentation->render()) { - $this->tpl->setVariable('RESULTS', $presentation->getHTML()); - } - return true; - } - - - protected function initAdvancedMetaDataForm(): ?ilPropertyFormGUI - { - if (is_object($this->form)) { - return $this->form; - } - $this->form = new ilPropertyFormGUI(); - $this->form->setFormAction($this->ctrl->getFormAction($this, 'performAdvMDSearch')); - $this->form->setTitle($this->lng->txt('adv_md_search_title')); - $this->form->addCommandButton('performAdvMDSearch', $this->lng->txt('search')); - #$this->form->setSubformMode('right'); - - $content = new ilTextInputGUI($this->lng->txt('meta_title') . '/' . - $this->lng->txt('meta_keyword') . '/' . - $this->lng->txt('meta_description'), 'title'); - $content->setValue($this->options['title']); - $content->setSize(30); - $content->setMaxLength(255); - // $content->setSubformMode('right'); - $group = new ilRadioGroupInputGUI('', 'title_ao'); - $group->setValue($this->options['title_ao']); - $radio_option = new ilRadioOption($this->lng->txt("search_any_word"), '0'); - $group->addOption($radio_option); - $radio_option = new ilRadioOption($this->lng->txt("search_all_words"), '1'); - $group->addOption($radio_option); - $content->addSubItem($group); - $this->form->addItem($content); - - $type = new ilSelectInputGUI($this->lng->txt('type'), 'type'); - $options['adv_all'] = $this->lng->txt('search_any'); - foreach (ilAdvancedMDRecord::_getActivatedObjTypes() as $obj_type) { - $options[$obj_type] = $this->lng->txt('objs_' . $obj_type); - } - $type->setOptions($options); - $type->setValue($this->options['type']); - $this->form->addItem($type); - - $record_gui = new ilAdvancedMDRecordGUI(ilAdvancedMDRecordGUI::MODE_SEARCH); - $record_gui->setPropertyForm($this->form); - $record_gui->setSearchValues($this->options); - $record_gui->parse(); - return null; - } - - - protected function performAdvMDSearch(): bool - { - $this->initSearchType(self::TYPE_ADV_MD); - $page_number = $this->initPageNumberFromQuery(); - if (!$page_number and $this->search_mode != 'in_results') { - ilSession::clear('adv_max_page'); - $this->search_cache->delete(); - } - $res = new ilSearchResult(); - if ($res_tit = $this->__performTitleSearch()) { - $this->__storeEntries($res, $res_tit); - } - $this->searchAdvancedMD($res); - if ($this->search_mode == 'in_results') { - $old_result_obj = new ilSearchResult($this->user->getId()); - $old_result_obj->read(ilUserSearchCache::ADVANCED_MD_SEARCH); - - $res->diffEntriesFromResult(); - } - $res->filter($this->getRootNode(), true); - $res->save(); - $this->showAdvMDSearch(); - - if (!count($res->getResults())) { - $this->tpl->setOnScreenMessage('info', $this->lng->txt('search_no_match')); - } - - if ($res->isLimitReached()) { - #$message = sprintf($this->lng->txt('search_limit_reached'),$this->settings->getMaxHits()); - #ilUtil::sendInfo($message); - } - - $this->addPager($res, 'adv_max_page'); - - $presentation = new ilSearchResultPresentation($this, ilSearchResultPresentation::MODE_STANDARD); - $presentation->setResults($res->getResultsForPresentation()); - $presentation->setPreviousNext($this->prev_link, $this->next_link); - - if ($presentation->render()) { - $this->tpl->setVariable('RESULTS', $presentation->getHTML()); - } - return true; - } - - - public function showAdvMDSearch(): bool - { - $session_options = ilSession::get('search_adv_md'); - if ($session_options !== null) { - $this->options = $session_options; - } - $this->setSubTabs(); - $this->tabs_gui->setSubTabActive('search_adv_md'); - - $this->tpl->addBlockFile('ADM_CONTENT', 'adm_content', 'tpl.advanced_adv_search.html', 'components/ILIAS/Search'); - - $this->initAdvancedMetaDataForm(); - $this->tpl->setVariable('SEARCH_FORM', $this->form->getHTML()); - return true; - } - - - protected function initFormSearch(): bool - { - $this->form = new ilPropertyFormGUI(); - $this->form->setFormAction($this->ctrl->getFormAction($this, 'performSearch')); - $this->form->setTitle($this->lng->txt('search_advanced')); - $this->form->addCommandButton('performSearch', $this->lng->txt('search')); - $this->form->addCommandButton('reset', $this->lng->txt('reset')); - foreach ($this->fields->getActiveSections() as $definition) { - if ($definition['name'] != 'default') { - $section = new ilFormSectionHeaderGUI(); - $section->setTitle($definition['name']); - $this->form->addItem($section); - } - - foreach ($definition['fields'] as $field_name) { - if (is_object($element = $this->fields->getFormElement($this->search_cache->getQuery(), $field_name, $this->form))) { - $this->form->addItem($element); - } - } - } - return true; - } - - - - public function showSearch(): bool - { - $this->setSubTabs(); - $this->tabs_gui->setSubTabActive('search_lom'); - - $this->tpl->addBlockFile('ADM_CONTENT', 'adm_content', 'tpl.advanced_search.html', 'components/ILIAS/Search'); - - $this->initFormSearch(); - $this->tpl->setVariable('SEARCH_FORM', $this->form->getHTML()); - return true; - } - - public function prepareOutput(): void - { - parent::prepareOutput(); - - $this->help_gui->setScreenIdComponent("src"); - - $this->tabs_gui->addTab( - "search", - $this->lng->txt("search"), - $this->ctrl->getLinkTargetByClass('ilsearchgui') - ); - $this->tabs_gui->addTab( - "adv_search", - $this->lng->txt("search_advanced"), - $this->ctrl->getLinkTarget($this) - ); - $this->tabs_gui->activateTab("adv_search"); - } - - - private function showSavedAdvMDResults(): bool - { - $this->initSearchType(self::TYPE_ADV_MD); - $result_obj = new ilSearchResult($this->user->getId()); - $result_obj->read(ilUserSearchCache::ADVANCED_MD_SEARCH); - - $this->showAdvMDSearch(); - - // Show them - if (count($result_obj->getResults())) { - $this->addPager($result_obj, 'adv_max_page'); - - $presentation = new ilSearchResultPresentation($this, ilSearchResultPresentation::MODE_STANDARD); - $presentation->setResults($result_obj->getResultsForPresentation()); - $presentation->setPreviousNext($this->prev_link, $this->next_link); - - if ($presentation->render()) { - $this->tpl->setVariable('RESULTS', $presentation->getHTML()); - } - } - - return true; - } - - - public function showSavedResults(): bool - { - $this->initSearchType(self::TYPE_LOM); - $result_obj = new ilSearchResult($this->user->getId()); - $result_obj->read(ilUserSearchCache::ADVANCED_SEARCH); - - $this->showSearch(); - - // Show them - if (count($result_obj->getResults())) { - $this->addPager($result_obj, 'adv_max_page'); - - $presentation = new ilSearchResultPresentation($this, ilSearchResultPresentation::MODE_STANDARD); - $presentation->setResults($result_obj->getResultsForPresentation()); - $presentation->setPreviousNext($this->prev_link, $this->next_link); - - if ($presentation->render()) { - $this->tpl->setVariable('RESULTS', $presentation->getHTML()); - } - } - - return true; - } - - public function __performContentSearch(): ?ilSearchResult - { - if (!($this->options['lom_content'] ?? null)) { - return null; - } - - $res = new ilSearchResult(); - - $query_parser = new ilQueryParser(ilUtil::stripSlashes($this->options['lom_content'])); - #$query_parser->setCombination($this->options['content_ao']); - $query_parser->setCombination(ilQueryParser::QP_COMBINATION_OR); - $query_parser->parse(); - - if (!isset($this->options['type'])) { - if ($tit_res = $this->__performTitleSearch()) { - $res->mergeEntries($tit_res); - } - - return $res; - } - - if ($this->options['type'] == 'all' or $this->options['type'] == 'lms') { - // LM content search - $lm_search = ilObjectSearchFactory::_getLMContentSearchInstance($query_parser); - $res_cont = $lm_search->performSearch(); - $res->mergeEntries($res_cont); - } - if ($this->options['type'] == 'all' or $this->options['type'] == 'tst') { - $tst_search = ilObjectSearchFactory::_getTestSearchInstance($query_parser); - $res_tes = $tst_search->performSearch(); - $res->mergeEntries($res_tes); - } - if ($this->options['type'] == 'all' or $this->options['type'] == 'mep') { - $med_search = ilObjectSearchFactory::_getMediaPoolSearchInstance($query_parser); - $res_med = $med_search->performSearch(); - $res->mergeEntries($res_med); - } - if ($this->options['type'] == 'all' or $this->options['type'] == 'glo') { - $glo_search = ilObjectSearchFactory::_getGlossaryDefinitionSearchInstance($query_parser); - $res_glo = $glo_search->performSearch(); - $res->mergeEntries($res_glo); - } - if ($this->options['type'] == 'all' or $this->options['type'] == 'webr') { - $web_search = ilObjectSearchFactory::_getWebresourceSearchInstance($query_parser); - $res_web = $web_search->performSearch(); - $res->mergeEntries($res_web); - } - if ($tit_res = $this->__performTitleSearch()) { - $res->mergeEntries($tit_res); - } - - return $res; - } - - - public function __performTitleSearch(): ?ilSearchResult - { - if (!($this->options['lom_content'] ?? null)) { - return null; - } - - - $query_parser = new ilQueryParser(ilUtil::stripSlashes($this->options['lom_content'])); - #$query_parser->setCombination($this->options['title_ao']); - $query_parser->setCombination(ilQueryParser::QP_COMBINATION_OR); - $query_parser->parse(); - $meta_search = ilObjectSearchFactory::_getAdvancedSearchInstance($query_parser); - - $meta_search->setFilter($this->filter); - $meta_search->setMode('title_description'); - $meta_search->setOptions($this->options); - $res_tit = $meta_search->performSearch(); - - $meta_search->setMode('keyword_all'); - $res_key = $meta_search->performSearch(); - - // merge them - $res_tit->mergeEntries($res_key); - - - return $res_tit; - } - - - - public function __performGeneralSearch(): ?ilSearchResult - { - if ( - !($this->options['lom_coverage'] ?? null) and - !($this->options['lom_structure'] ?? null) - ) { - return null; - } - - - if (($this->options['lom_coverage'] ?? null)) { - $query_parser = new ilQueryParser(ilUtil::stripSlashes($this->options['lom_coverage'])); - #$query_parser->setCombination($this->options['coverage_ao']); - $query_parser->setCombination(ilQueryParser::QP_COMBINATION_OR); - $query_parser->parse(); - } else { - $query_parser = new ilQueryParser(''); - } - $meta_search = ilObjectSearchFactory::_getAdvancedSearchInstance($query_parser); - $meta_search->setFilter($this->filter); - $meta_search->setMode('general'); - $meta_search->setOptions($this->options); - $res = $meta_search->performSearch(); - - return $res; - } - - public function __performLifecycleSearch(): ?ilSearchResult - { - // Return if 'any' - if ( - !($this->options['lom_status'] ?? null) and - !($this->options['lom_version'] ?? null) - ) { - return null; - } - - $query_parser = new ilQueryParser(ilUtil::stripSlashes($this->options['lom_version'])); - #$query_parser->setCombination($this->options['version_ao']); - $query_parser->setCombination(ilQueryParser::QP_COMBINATION_OR); - $query_parser->parse(); - - $meta_search = ilObjectSearchFactory::_getAdvancedSearchInstance($query_parser); - $meta_search->setFilter($this->filter); - $meta_search->setMode('lifecycle'); - $meta_search->setOptions($this->options); - $res = $meta_search->performSearch(); - - return $res; - } - public function __performLanguageSearch(): ?ilSearchResult - { - if (!($this->options['lom_language'] ?? null)) { - return null; - } - - - $meta_search = ilObjectSearchFactory::_getAdvancedSearchInstance(new ilQueryParser('')); - $meta_search->setFilter($this->filter); - $meta_search->setMode('language'); - $meta_search->setOptions($this->options); - $res = $meta_search->performSearch(); - - return $res; - } - public function __performContributeSearch(): ?ilSearchResult - { - if (!strlen($this->options['lom_role'] ?? '')) { - return null; - } - - - $meta_search = ilObjectSearchFactory::_getAdvancedSearchInstance(new ilQueryParser('')); - $meta_search->setFilter($this->filter); - $meta_search->setMode('contribute'); - $meta_search->setOptions($this->options); - $res = $meta_search->performSearch(); - - return $res; - } - public function __performEntitySearch(): ?ilSearchResult - { - // Return if 'any' - if (!($this->options['lom_role_entry'] ?? null)) { - return null; - } - - - $query_parser = new ilQueryParser(ilUtil::stripSlashes($this->options['lom_role_entry'])); - #$query_parser->setCombination($this->options['entity_ao']); - $query_parser->setCombination(ilQueryParser::QP_COMBINATION_OR); - $query_parser->parse(); - - $meta_search = ilObjectSearchFactory::_getAdvancedSearchInstance($query_parser); - $meta_search->setFilter($this->filter); - $meta_search->setMode('entity'); - $meta_search->setOptions($this->options); - $res = $meta_search->performSearch(); - - return $res; - } - - - public function __performRequirementSearch(): ?ilSearchResult - { - $meta_search = ilObjectSearchFactory::_getAdvancedSearchInstance(new ilQueryParser('')); - $meta_search->setFilter($this->filter); - $meta_search->setMode('requirement'); - $meta_search->setOptions($this->options); - $res = $meta_search->performSearch(); - - return $res; - } - public function __performFormatSearch(): ?ilSearchResult - { - $meta_search = ilObjectSearchFactory::_getAdvancedSearchInstance(new ilQueryParser('')); - $meta_search->setFilter($this->filter); - $meta_search->setMode('format'); - $meta_search->setOptions($this->options); - $res = $meta_search->performSearch(); - - return $res; - } - public function __performEducationalSearch(): ?ilSearchResult - { - $meta_search = ilObjectSearchFactory::_getAdvancedSearchInstance(new ilQueryParser('')); - $meta_search->setFilter($this->filter); - $meta_search->setMode('educational'); - $meta_search->setOptions($this->options); - $res = $meta_search->performSearch(); - - return $res; - } - public function __performTypicalAgeRangeSearch(): ?ilSearchResult - { - $meta_search = ilObjectSearchFactory::_getAdvancedSearchInstance(new ilQueryParser('')); - $meta_search->setFilter($this->filter); - $meta_search->setMode('typical_age_range'); - $meta_search->setOptions($this->options); - $res = $meta_search->performSearch(); - - return $res; - } - public function __performRightsSearch(): ?ilSearchResult - { - if ( - !($this->options['lom_copyright'] ?? null) and - !($this->options['lom_costs'] ?? null) - ) { - return null; - } - - - $meta_search = ilObjectSearchFactory::_getAdvancedSearchInstance(new ilQueryParser('')); - $meta_search->setFilter($this->filter); - $meta_search->setMode('rights'); - $meta_search->setOptions($this->options); - $res = $meta_search->performSearch(); - - return $res; - } - - public function __performClassificationSearch(): ?ilSearchResult - { - // Return if 'any' - if (!($this->options['lom_purpose'] ?? null)) { - return null; - } - - - $meta_search = ilObjectSearchFactory::_getAdvancedSearchInstance(new ilQueryParser('')); - $meta_search->setFilter($this->filter); - $meta_search->setMode('classification'); - $meta_search->setOptions($this->options); - $res = $meta_search->performSearch(); - - return $res; - } - - public function __performTaxonSearch(): ?ilSearchResult - { - // Return if 'any' - if (!($this->options['lom_taxon'] ?? null)) { - return null; - } - - $query_parser = new ilQueryParser(ilUtil::stripSlashes($this->options['lom_taxon'])); - $query_parser->setCombination(ilQueryParser::QP_COMBINATION_OR); - $query_parser->parse(); - - $meta_search = ilObjectSearchFactory::_getAdvancedSearchInstance($query_parser); - $meta_search->setFilter($this->filter); - $meta_search->setMode('taxon'); - $meta_search->setOptions($this->options); - $res = $meta_search->performSearch(); - - return $res; - } - - private function searchAdvancedMD(ilSearchResult $res): void - { - $this->initFormSearch(); - - foreach (array_keys($this->options) as $key) { - if (substr((string) $key, 0, 3) != 'adv') { - continue; - } - - // :TODO: ? - if (!$key) { - continue; - } - - $field_id = substr($key, 4); - $field = ilAdvancedMDFieldDefinition::getInstance((int) $field_id); - - $field_form = ilADTFactory::getInstance()->getSearchBridgeForDefinitionInstance($field->getADTDefinition(), true, false); - $field_form->setElementId("query[" . $key . "]"); - $field_form->setForm($this->form); - - // reload search values - $field_form->importFromPost($this->options); - $field_form->validate(); - - $parser_value = $field->getSearchQueryParserValue($field_form); - if (!strlen($parser_value)) { - continue; - } - $adv_md_search = ilObjectSearchFactory::_getAdvancedMDSearchInstance(new ilQueryParser($parser_value)); - $adv_md_search->setFilter($this->filter); - $adv_md_search->setDefinition($field); - $adv_md_search->setSearchElement($field_form); - $res_field = $adv_md_search->performSearch(); - $this->__storeEntries($res, $res_field); - } - } - - public function __performKeywordSearch(): ?ilSearchResult - { - // Return if 'any' - if (!($this->options['lom_keyword'] ?? null)) { - return null; - } - - $query_parser = new ilQueryParser(ilUtil::stripSlashes($this->options['lom_keyword'])); - $query_parser->setCombination(ilQueryParser::QP_COMBINATION_OR); - $query_parser->parse(); - - $meta_search = ilObjectSearchFactory::_getAdvancedSearchInstance($query_parser); - $meta_search->setFilter($this->filter); - $meta_search->setMode('keyword'); - $meta_search->setOptions($this->options); - $res = $meta_search->performSearch(); - return $res; - } - - public function __setSearchOptions(): bool - { - $query = $this->getQueryFromPost(); - - $post_cmd = (array) ($this->http->request()->getParsedBody()['cmd'] ?? []); - - if (isset($post_cmd['performSearch'])) { - $this->options = $query; - ilSession::set('search_adv', $this->options); - } elseif (isset($post_cmd['performAdvMDSearch'])) { - $this->options = (array) $this->http->request()->getParsedBody(); - ilSession::set('search_adv_md', $this->options); - } else { - $this->options = ilSession::get('search_adv') ?? []; - } - $this->filter = array(); - - $this->options['type'] = 'all'; - switch ($this->options['type']) { - case 'cat': - $this->filter[] = 'cat'; - break; - - case 'webr': - $this->filter[] = 'webr'; - break; - - case 'lms': - $this->filter[] = 'lm'; - $this->filter[] = 'dbk'; - $this->filter[] = 'pg'; - $this->filter[] = 'st'; - $this->filter[] = 'sahs'; - $this->filter[] = 'htlm'; - break; - - case 'glo': - $this->filter[] = 'glo'; - break; - - case 'tst': - $this->filter[] = 'tst'; - $this->filter[] = 'svy'; - $this->filter[] = 'qpl'; - $this->filter[] = 'spl'; - break; - - case 'mep': - $this->filter[] = 'mep'; - $this->filter[] = 'mob'; - $this->filter[] = 'mpg'; - break; - - case 'crs': - $this->filter[] = 'crs'; - break; - - case 'file': - $this->filter[] = 'file'; - break; - - case 'adv_all': - $this->filter = ilAdvancedMDRecord::_getActivatedObjTypes(); - break; - - case 'all': - default: - $this->filter[] = 'cat'; - $this->filter[] = 'sess'; - $this->filter[] = 'webr'; - $this->filter[] = 'crs'; - $this->filter[] = 'mep'; - $this->filter[] = 'tst'; - $this->filter[] = 'svy'; - $this->filter[] = 'qpl'; - $this->filter[] = 'spl'; - $this->filter[] = 'glo'; - $this->filter[] = 'lm'; - $this->filter[] = 'dbk'; - $this->filter[] = 'pg'; - $this->filter[] = 'st'; - $this->filter[] = 'sahs'; - $this->filter[] = 'htlm'; - $this->filter[] = 'file'; - $this->filter[] = 'mob'; - $this->filter[] = 'mpg'; - } - return true; - } - - public function __getFilterSelect(): string - { - $options = array('all' => $this->lng->txt('search_any'), - 'crs' => $this->lng->txt('objs_crs'), - 'lms' => $this->lng->txt('obj_lrss'), - 'glo' => $this->lng->txt('objs_glo'), - 'mep' => $this->lng->txt('objs_mep'), - 'tst' => $this->lng->txt('search_tst_svy'), - 'file' => $this->lng->txt('objs_file'), - 'webr' => $this->lng->txt('objs_webr'), - 'sess' => $this->lng->txt('objs_sess') - ); - - - return ilLegacyFormElementsUtil::formSelect($this->options['type'], 'search_adv[type]', $options, false, true); - } - - - public function __storeEntries(ilSearchResult $res, ilSearchResult $new_res): bool - { - if (!$this->stored) { - $res->mergeEntries($new_res); - $this->stored = true; - return true; - } else { - $res->intersectEntries($new_res); - return true; - } - } - - private function initUserSearchCache(): void - { - $this->search_cache = ilUserSearchCache::_getInstance($this->user->getId()); - $this->search_cache->switchSearchType(ilUserSearchCache::ADVANCED_SEARCH); - $page_number = $this->initPageNumberFromQuery(); - if ($page_number) { - $this->search_cache->setResultPageNumber($page_number); - } - $post_cmd = (array) ($this->http->request()->getParsedBody()['cmd'] ?? []); - $post_query = (array) ($this->http->request()->getParsedBody()['query'] ?? []); - if ($post_cmd['performSearch'] ?? null) { - $this->search_cache->setQuery($post_query['lomContent'] ?? ''); - $this->search_cache->save(); - } - } - - public function setSubTabs(): bool - { - if (!count(ilAdvancedMDFieldDefinition::getSearchableDefinitionIds())) { - return true; - } - $this->tabs_gui->addSubTabTarget('search_lom', $this->ctrl->getLinkTarget($this, 'showSavedResults')); - return true; - } - - - private function toUnixTime(array $date, array $time = array()): int - { - return mktime($time['h'], $time['m'], 0, $date['m'], $date['d'], $date['y']); - } - - - private function initSearchType(int $type): void - { - if ($type == self::TYPE_LOM) { - ilSession::set('search_last_sub_section', self::TYPE_LOM); - $this->search_cache->switchSearchType(ilUserSearchCache::ADVANCED_SEARCH); - } else { - ilSession::set('search_last_sub_section', self::TYPE_ADV_MD); - $this->search_cache->switchSearchType(ilUserSearchCache::ADVANCED_MD_SEARCH); - } - } - - private function getQueryFromPost(): array - { - $query = []; - if ($this->http->wrapper()->post()->has('query')) { - $query = $this->http->wrapper()->post()->retrieve( - 'query', - $this->refinery->kindlyTo()->dictOf( - $this->refinery->byTrying([ - $this->refinery->kindlyTo()->string(), - $this->refinery->kindlyTo()->dictOf( - $this->refinery->kindlyTo()->string() - ) - ]) - ) - ); - } - return $query; - } -} diff --git a/components/ILIAS/Search/classes/class.ilSearchAppEventListener.php b/components/ILIAS/Search/classes/class.ilSearchAppEventListener.php index 702a3ac94358..40895e938cac 100755 --- a/components/ILIAS/Search/classes/class.ilSearchAppEventListener.php +++ b/components/ILIAS/Search/classes/class.ilSearchAppEventListener.php @@ -36,6 +36,7 @@ public static function handleEvent(string $a_component, string $a_event, array $ return; } + // only for files in the moment if (!isset($a_parameter['obj_type'])) { $type = ilObject::_lookupType($a_parameter['obj_id']); @@ -51,7 +52,7 @@ public static function handleEvent(string $a_component, string $a_event, array $ break; case 'components/ILIAS/Help': - case 'components/ILIAS/Object': + case 'components/ILIAS/ILIASObject': switch ($a_event) { case 'undelete': diff --git a/components/ILIAS/Search/classes/class.ilSearchBaseGUI.php b/components/ILIAS/Search/classes/class.ilSearchBaseGUI.php index d6a77bbe6d2c..6df84f5ab418 100755 --- a/components/ILIAS/Search/classes/class.ilSearchBaseGUI.php +++ b/components/ILIAS/Search/classes/class.ilSearchBaseGUI.php @@ -23,7 +23,7 @@ use ILIAS\HTTP\GlobalHttpState; use ILIAS\Refinery\Factory; use ILIAS\Object\ImplementsCreationCallback; -use ILIAS\Object\CreationCallbackTrait; +use ILIAS\ILIASObject\Creation\CreationCallbackTrait; /** * Class ilSearchBaseGUI diff --git a/components/ILIAS/Search/classes/class.ilSearchControllerGUI.php b/components/ILIAS/Search/classes/class.ilSearchControllerGUI.php index 8221a106ad27..41f0ff8498ba 100755 --- a/components/ILIAS/Search/classes/class.ilSearchControllerGUI.php +++ b/components/ILIAS/Search/classes/class.ilSearchControllerGUI.php @@ -22,19 +22,14 @@ use ILIAS\Refinery\Factory; /** - * Class ilObjSearchController - * * @author Stefan Meyer * - * @package ilias-search - * - * @ilCtrl_Calls ilSearchControllerGUI: ilSearchGUI, ilAdvancedSearchGUI - * @ilCtrl_Calls ilSearchControllerGUI: ilLuceneSearchGUI, ilLuceneAdvancedSearchGUI, ilLuceneUserSearchGUI - * + * @ilCtrl_Calls ilSearchControllerGUI: ilSearchGUI + * @ilCtrl_Calls ilSearchControllerGUI: ilLuceneSearchGUI ilLuceneUserSearchGUI */ class ilSearchControllerGUI implements ilCtrlBaseClassInterface { - public const TYPE_USER_SEARCH = -1; + public const int TYPE_USER_SEARCH = -1; protected ilObjUser $user; protected ilCtrl $ctrl; @@ -45,11 +40,6 @@ class ilSearchControllerGUI implements ilCtrlBaseClassInterface protected GlobalHttpState $http; protected Factory $refinery; - - /** - * Constructor - * @access public - */ public function __construct() { global $DIC; @@ -76,10 +66,6 @@ public function executeCommand(): void $this->ctrl->forwardCommand(new ilLuceneSearchGUI()); break; - case 'illuceneadvancedsearchgui': - $this->ctrl->forwardCommand(new ilLuceneAdvancedSearchGUI()); - break; - case 'illuceneusersearchgui': if ($this->user->getId() === ANONYMOUS_USER_ID) { $this->ilias->raiseError($this->lng->txt("permission_denied"), $this->ilias->error_obj->MESSAGE); @@ -87,10 +73,6 @@ public function executeCommand(): void $this->ctrl->forwardCommand(new ilLuceneUserSearchGUI()); break; - case 'iladvancedsearchgui': - $this->ctrl->forwardCommand(new ilAdvancedSearchGUI()); - break; - case 'ilsearchgui': default: $search_gui = new ilSearchGUI(); diff --git a/components/ILIAS/Search/classes/class.ilSearchGUI.php b/components/ILIAS/Search/classes/class.ilSearchGUI.php index e453a198338f..bca5894c1b53 100755 --- a/components/ILIAS/Search/classes/class.ilSearchGUI.php +++ b/components/ILIAS/Search/classes/class.ilSearchGUI.php @@ -22,18 +22,14 @@ use ILIAS\UI\Renderer as UIRenderer; /** -* Class ilSearchGUI -* -* GUI class for 'simple' search -* -* @author Stefan Meyer -* @ilCtrl_Calls ilSearchGUI: ilObjectGUI, ilContainerGUI -* @ilCtrl_Calls ilSearchGUI: ilObjCategoryGUI, ilObjCourseGUI, ilObjFolderGUI, ilObjGroupGUI -* @ilCtrl_Calls ilSearchGUI: ilObjStudyProgrammeGUI -* @ilCtrl_Calls ilSearchGUI: ilObjRootFolderGUI, ilObjectCopyGUI -* -* @ingroup ServicesSearch -*/ + * GUI class for 'simple' search + * + * @author Stefan Meyer + * + * @ilCtrl_Calls ilSearchGUI: ilObjectGUI, ilContainerGUI + * @ilCtrl_Calls ilSearchGUI: ilObjCategoryGUI, ilObjCourseGUI, ilObjFolderGUI, ilObjGroupGUI + * @ilCtrl_Calls ilSearchGUI: ilObjStudyProgrammeGUI + */ class ilSearchGUI extends ilSearchBaseGUI { private array $details; @@ -47,10 +43,6 @@ class ilSearchGUI extends ilSearchBaseGUI protected UIFactory $ui_factory; protected UIRenderer $ui_renderer; - /** - * Constructor - * @access public - */ public function __construct() { global $DIC; @@ -138,11 +130,6 @@ public function __construct() } } - - /** - * Control - * @access public - */ public function executeCommand(): void { $next_class = $this->ctrl->getNextClass($this); @@ -167,9 +154,8 @@ public function executeCommand(): void } /** - * Set/get type of search (detail or 'fast' search) - * @access public - */ + * Set/get type of search (detail or 'fast' search) + */ public function setType(int $a_type): void { $session_search = ilSession::get('search'); @@ -181,10 +167,10 @@ public function getType(): int { return $this->type ?? self::SEARCH_FAST; } + /** - * Set/get combination of search ('and' or 'or') - * @access public - */ + * Set/get combination of search ('and' or 'or') + */ public function setCombination(string $a_combination): void { $session_search = ilSession::get('search') ?? []; @@ -195,36 +181,34 @@ public function getCombination(): string { return $this->combination ?: self::SEARCH_OR; } - /** - * Set/get search string - * @access public - */ + public function setString(string $a_str): void { $session_search = ilSession::get('search') ?? []; $session_search['string'] = $this->string = $a_str; ilSession::set('search', $session_search); } + public function getString(): string { return $this->string; } + /** - * Set/get details (object types for details search) - * @access public - */ + * Set/get details (object types for details search) + */ public function setDetails(array $a_details): void { $session_search = ilSession::get('search') ?? []; $session_search['details'] = $this->details = $a_details; ilSession::set('search', $session_search); } + public function getDetails(): array { return $this->details ?? []; } - public function getRootNode(): int { return $this->root_node ?: ROOT_FOLDER_ID; @@ -235,7 +219,6 @@ public function setRootNode(int $a_node_id): void ilSession::set('search_root', $this->root_node = $a_node_id); } - public function remoteSearch(): void { $root_id = 0; @@ -258,8 +241,8 @@ public function remoteSearch(): void } /** - * Data resource for autoComplete - */ + * Data resource for autoComplete + */ public function autoComplete(): void { $query = ''; @@ -350,9 +333,6 @@ public function performSearchFilter(): void $this->performSearch(); } - /** - * Perform search - */ public function performSearch(): void { $page_number = $this->initPageNumberFromQuery(); @@ -435,8 +415,6 @@ public function performSearch(): void } } - - public function prepareOutput(): void { parent::prepareOutput(); @@ -449,14 +427,6 @@ public function prepareOutput(): void $this->ctrl->getLinkTarget($this) ); - if (!$this->settings->getHideAdvancedSearch()) { - $this->tabs_gui->addTab( - "adv_search", - $this->lng->txt("search_advanced"), - $this->ctrl->getLinkTargetByClass('iladvancedsearchgui') - ); - } - $this->tabs_gui->activateTab("search"); } @@ -547,10 +517,9 @@ protected function __performDetailsSearch(ilQueryParser $query_parser, ilSearchR } /** - * parse query string, using query parser instance - * @return ilQueryParser | string + * parse query string, using query parser instance */ - public function __parseQueryString() + public function __parseQueryString(): ilQueryParser|string { $query_parser = new ilQueryParser(ilUtil::stripSlashes($this->getString())); $query_parser->setCombination($this->getCombination()); @@ -561,6 +530,7 @@ public function __parseQueryString() } return $query_parser; } + /** * Search in obect title,desctiption */ @@ -607,10 +577,8 @@ protected function parseEndDateFromCreationFilter(): ?ilDate } /** - * Search in object meta data (keyword) - * @return ilSearchResult result object - * @access public - */ + * Search in object meta data (keyword) + */ public function __searchMeta(ilQueryParser $query_parser, string $a_type): ilSearchResult { $meta_search = ilObjectSearchFactory::_getMetaDataSearchInstance($query_parser); @@ -636,10 +604,11 @@ public function __searchMeta(ilQueryParser $query_parser, string $a_type): ilSea } return $meta_search->performSearch(); } + /** - * Get object type for filter (If detail search is enabled) + * Get object type for filter (If detail search is enabled) * @return string[] - */ + */ public function __getFilter(): array { if ($this->getType() != self::SEARCH_DETAILS) { diff --git a/components/ILIAS/Search/classes/class.ilSearchResult.php b/components/ILIAS/Search/classes/class.ilSearchResult.php index bcf70a549716..01ded1beb414 100755 --- a/components/ILIAS/Search/classes/class.ilSearchResult.php +++ b/components/ILIAS/Search/classes/class.ilSearchResult.php @@ -17,14 +17,12 @@ *********************************************************************/ /** -* searchResult stores all result of a search query. -* Offers methods like mergeResults. To merge result sets of different queries. -* -* -* @author Stefan Meyer -* -* @package ilias-search -*/ + * searchResult stores all result of a search query. + * Offers methods like mergeResults. To merge result sets of different queries. + * + * + * @author Stefan Meyer + */ class ilSearchResult { private string $permission = 'visible'; @@ -52,12 +50,6 @@ class ilSearchResult protected ilLogger $logger; - - - /** - * Constructor - * @access public - */ public function __construct(int $a_user_id = 0) { global $DIC; @@ -78,8 +70,8 @@ public function __construct(int $a_user_id = 0) } /** - * Set the required permission for the rbac checks in function 'filter()' - */ + * Set the required permission for the rbac checks in function 'filter()' + */ public function setRequiredPermission(string $a_permission): void { $this->permission = $a_permission; @@ -90,7 +82,6 @@ public function getRequiredPermission(): string return $this->permission; } - public function setUserId(int $a_user_id): void { $this->user_id = $a_user_id; @@ -131,10 +122,10 @@ public function isOffsetReached(int $a_counter): bool * * add search result entry * Entries are stored with 'obj_id'. This method is typically called to store db query results. - * @param int object object_id - * @param string obj_type 'lm' or 'crs' ... - * @param array value position of query parser words in query string - * @param int child id e.g id of page or chapter + * @param int $a_obj_id object object_id + * @param string $a_type obj_type 'lm' or 'crs' ... + * @param array $found value position of query parser words in query string + * @param int $a_child_id child id e.g id of page or chapter * @return void */ public function addEntry(int $a_obj_id, string $a_type, array $found, int $a_child_id = 0): void @@ -164,11 +155,6 @@ public function addEntry(int $a_obj_id, string $a_type, array $found, int $a_chi } } - /** - * - * Check number of entries - * @access public - */ public function numEntries(): int { return count($this->getEntries()); @@ -177,8 +163,6 @@ public function numEntries(): int /** * * merge entries of this instance and another result object - * @param object result_obj - * @access public */ public function mergeEntries(ilSearchResult $result_obj): void { @@ -250,8 +234,7 @@ public function getResults(): array } /** - * get result ids - * @return int[] result ids + * @return int[] */ public function getResultIds(): array { @@ -271,7 +254,6 @@ public function getResultsByObjId(): array return $tmp_res; } - /** * Get unique results. Return an array of obj_id (No multiple results for references) * Results are stored with 'ref_id'. This method is typically called after checking access of entries. @@ -312,8 +294,6 @@ public function getSubitemIds(): array return $res; } - - /** * Filter search result. * Do RBAC checks. @@ -374,10 +354,8 @@ public function filter( if (!ilDate::_before($creation_date, $creation_filter_date_end)) { continue; } - } else { - if (!ilDate::_within($creation_date, $creation_filter_date_start, $creation_filter_date_end)) { - continue; - } + } elseif (!ilDate::_within($creation_date, $creation_filter_date_start, $creation_filter_date_end)) { + continue; } } @@ -438,9 +416,7 @@ public function filter( } /** - * * Filter search area of result set - * @access public */ public function filterResults(int $a_root_node): void { @@ -454,34 +430,20 @@ public function filterResults(int $a_root_node): void } } - - /** - * - * Save search results - * @param int DEFAULT_SEARCH or ADVANCED_SEARCH - */ public function save(int $a_type = ilUserSearchCache::DEFAULT_SEARCH): void { $this->search_cache->save(); } - /** - * - * read search results - * @param int DEFAULT_SEARCH or ADVANCED_SEARCH - * @access public - */ + public function read(int $a_type = ilUserSearchCache::DEFAULT_SEARCH): void { $this->results = $this->search_cache->getResults(); } - // PRIVATE /** - * - * Update childs for a specific entry - * @param int object object_id - * @param array array of child ids. E.g 'pg', 'st' - * @access private + * @param int $a_obj_id + * @param array $a_childs array of child ids. E.g 'pg', 'st' + * @return bool */ public function __updateEntryChilds(int $a_obj_id, array $a_childs): bool { @@ -495,6 +457,7 @@ public function __updateEntryChilds(int $a_obj_id, array $a_childs): bool } return false; } + /** * Update child ids for a specific result */ @@ -509,8 +472,6 @@ public function __updateResultChilds(int $a_ref_id, array $a_childs): bool return false; } - - public function __initSearchSettingsObject(): void { $this->search_settings = new ilSearchSettings(); @@ -519,12 +480,6 @@ public function __initSearchSettingsObject(): void } } - /** - * Init user search cache - * - * @access private - * - */ protected function initUserSearchCache(): void { $this->search_cache = ilUserSearchCache::_getInstance($this->getUserId()); @@ -554,8 +509,8 @@ public function preventOverwritingMaxhits(?bool $a_flag = null) * Every callback function should support the following parameters: * array of ids. E.g: ref_id = 5,array(obj_id = 1,type = 'crs'), * The function should return true or false. - * @param object class of callback function - * @param string name of callback method + * @param object $a_class class of callback function + * @param string $a_method name of callback method */ public function addObserver(object $a_class, string $a_method): bool { @@ -577,4 +532,4 @@ public function callListeners(int $a_ref_id, array $a_data): bool } return true; } -} // END class.Search +} diff --git a/components/ILIAS/Search/classes/class.ilSearchSettings.php b/components/ILIAS/Search/classes/class.ilSearchSettings.php index d44e3eeb27ec..330f669a4efc 100755 --- a/components/ILIAS/Search/classes/class.ilSearchSettings.php +++ b/components/ILIAS/Search/classes/class.ilSearchSettings.php @@ -19,21 +19,15 @@ declare(strict_types=1); /** -* Class ilObjSearchSettingsGUI -* -* @author Stefan Meyer -* -* @extends ilObjectGUI -* @package ilias-core -*/ - + * @author Stefan Meyer + */ class ilSearchSettings { - public const LIKE_SEARCH = 0; - public const LUCENE_SEARCH = 2; + public const int LIKE_SEARCH = 0; + public const int LUCENE_SEARCH = 2; - public const OPERATOR_AND = 1; - public const OPERATOR_OR = 2; + public const int OPERATOR_AND = 1; + public const int OPERATOR_OR = 2; protected static ?ilSearchSettings $instance = null; @@ -50,7 +44,6 @@ class ilSearchSettings protected bool $show_limited_user = true; protected bool $lucene = false; - protected bool $hide_adv_search = false; protected bool $lucene_mime_filter_enabled = false; protected array $lucene_mime_filter = array(); protected bool $prefix_wildcard = false; @@ -131,7 +124,6 @@ public function getEnabledLuceneItemFilterDefinitions(): array return $enabled; } - // begin-patch mime_filter public function getEnabledLuceneMimeFilterDefinitions(): array { if (!$this->isLuceneItemFilterEnabled()) { @@ -227,14 +219,6 @@ public function setFragmentCount(int $a_count): void $this->fragmentCount = $a_count; } - public function getHideAdvancedSearch(): bool - { - return $this->hide_adv_search; - } - public function setHideAdvancedSearch(bool $a_status): void - { - $this->hide_adv_search = $a_status; - } public function getAutoCompleteLength(): int { return $this->auto_complete_length; @@ -334,10 +318,6 @@ public function isLuceneUserSearchEnabled(): bool return $this->user_search; } - /** - * Enable lucene user search - * @param bool $a_status - */ public function enableLuceneUserSearch(bool $a_status): void { $this->user_search = $a_status; @@ -351,10 +331,6 @@ public function showInactiveUser(bool $a_visible): void $this->show_inactiv_user = $a_visible; } - /** - * are inactive user visible in user search - * @return bool - */ public function isInactiveUserVisible(): bool { return $this->show_inactiv_user; @@ -394,7 +370,6 @@ public function update(): void $this->setting->set('lucene_fragment_count', (string) $this->getFragmentCount()); $this->setting->set('lucene_max_subitems', (string) $this->getMaxSubitems()); $this->setting->set('lucene_last_index_time', (string) $this->getLastIndexTime()->get(IL_CAL_UNIX)); - $this->setting->set('hide_adv_search', (string) $this->getHideAdvancedSearch()); $this->setting->set('auto_complete_length', (string) $this->getAutoCompleteLength()); $this->setting->set('lucene_item_filter_enabled', (string) $this->isLuceneItemFilterEnabled()); $this->setting->set('lucene_item_filter', serialize($this->getLuceneItemFilter())); @@ -408,7 +383,6 @@ public function update(): void $this->setting->set('search_date_filter', (string) $this->isDateFilterEnabled()); } - // PRIVATE protected function __read() { $this->setMaxHits((int) $this->setting->get('search_max_hits', '10')); @@ -422,7 +396,6 @@ protected function __read() } else { $this->setLastIndexTime(null); } - $this->setHideAdvancedSearch((bool) $this->setting->get('hide_adv_search', '0')); $this->setAutoCompleteLength((int) $this->setting->get('auto_complete_length', (string) $this->getAutoCompleteLength())); $this->enableLuceneItemFilter((bool) $this->setting->get('lucene_item_filter_enabled', (string) $this->isLuceneItemFilterEnabled())); $filter = (string) $this->setting->get('lucene_item_filter', serialize($this->getLuceneItemFilter())); diff --git a/components/ILIAS/Search/classes/class.ilUserSearchCache.php b/components/ILIAS/Search/classes/class.ilUserSearchCache.php index fd1cd72dedcc..1b9be37a6fc5 100755 --- a/components/ILIAS/Search/classes/class.ilUserSearchCache.php +++ b/components/ILIAS/Search/classes/class.ilUserSearchCache.php @@ -17,27 +17,20 @@ *********************************************************************/ declare(strict_types=1); + /** -* Class for storing search result. Allows paging of result sets -* -* -* @author Stefan Meyer -* -* -* @ilCtrl_Calls -* @ingroup ServicesSearch -*/ + * Class for storing search result. Allows paging of result sets + * + * @author Stefan Meyer + */ class ilUserSearchCache { - public const DEFAULT_SEARCH = 0; - public const ADVANCED_SEARCH = 1; - public const ADVANCED_MD_SEARCH = 4; - public const LUCENE_DEFAULT = 5; - public const LUCENE_ADVANCED = 6; + public const int DEFAULT_SEARCH = 0; + public const int LUCENE_DEFAULT = 5; - public const LAST_QUERY = 7; + public const int LAST_QUERY = 7; - public const LUCENE_USER_SEARCH = 8; + public const int LUCENE_USER_SEARCH = 8; private static ?ilUserSearchCache $instance = null; protected ilDBInterface $db; @@ -127,7 +120,7 @@ public function getResults(): array * Set results * * @access public - * @param array(int => array(int,int,string)) array(ref_id => array(ref_id,obj_id,type)) + * @param array $a_results (int => array(int,int,string)) array(ref_id => array(ref_id,obj_id,type)) * */ public function setResults(array $a_results): void @@ -139,7 +132,7 @@ public function setResults(array $a_results): void * Append result * * @access public - * @param array(int,int,string) array(ref_id,obj_id,type) + * @param array $a_result_item (int,int,string) array(ref_id,obj_id,type) * */ public function addResult(array $a_result_item): bool @@ -166,26 +159,11 @@ public function isFailed(int $a_ref_id): bool return in_array($a_ref_id, $this->failed); } - /** - * Append checked id - * - * @access public - * @param int checked reference id - * @param int checked obj_id - * - */ public function appendToChecked(int $a_ref_id, int $a_obj_id): void { $this->checked[$a_ref_id] = $a_obj_id; } - /** - * Check if reference was already checked - * - * @access public - * @param int ref_id - * - */ public function isChecked(int $a_ref_id): bool { return array_key_exists($a_ref_id, $this->checked) and $this->checked[$a_ref_id]; @@ -222,24 +200,13 @@ public function getResultPageNumber(): int return $this->page_number ?: 1; } - /** - * set query - * @param mixed query string or array (for advanced search) - * @return void - */ - public function setQuery($a_query): void + public function setQuery(string $a_query): void { $this->query = $a_query; } - /** - * @return string|array query string or array (for advanced search) - */ - public function getQuery() + public function getQuery(): string { - if (is_array($this->query)) { - return $this->query; - } return $this->query ?? ''; } @@ -248,11 +215,6 @@ public function getQuery() */ public function getUrlEncodedQuery(): string { - if (is_array($this->getQuery())) { - $query = $this->getQuery(); - - return urlencode(str_replace('"', '.', $query['lom_content'])); - } return urlencode(str_replace('"', '.', $this->getQuery())); } @@ -264,10 +226,6 @@ public function setRoot(int $a_root): void $this->root = $a_root; } - /** - * get root node - * @return int - */ public function getRoot(): int { return $this->root ?: ROOT_FOLDER_ID; @@ -303,10 +261,6 @@ public function getCreationFilter(): array return $this->creation_filter; } - - /** - * delete cached entries - */ public function deleteCachedEntries(): void { if ($this->isAnonymous()) { @@ -353,10 +307,6 @@ public function deleteCachedEntries(): void $this->failed = array(); } - /** - * Delete cached entries for anonymous user - * @return bool - */ public function deleteCachedEntriesAnonymous(): bool { $this->setResultPageNumber(1); @@ -367,8 +317,6 @@ public function deleteCachedEntriesAnonymous(): bool return true; } - - public function delete(): bool { $query = "DELETE FROM usr_search " . @@ -436,13 +384,6 @@ public function saveForAnonymous(): void ilSession::set('usr_search_cache', $session_usr_search); } - - /** - * Read user entries - * - * @access private - * - */ private function read(): void { $this->failed = array(); diff --git a/components/ILIAS/Search/service.xml b/components/ILIAS/Search/service.xml index 1932c03249b6..33b83feb08f6 100755 --- a/components/ILIAS/Search/service.xml +++ b/components/ILIAS/Search/service.xml @@ -12,7 +12,7 @@ - + diff --git a/components/ILIAS/Search/templates/default/tpl.lucene_activation_row.html b/components/ILIAS/Search/templates/default/tpl.lucene_activation_row.html deleted file mode 100755 index 402911eef181..000000000000 --- a/components/ILIAS/Search/templates/default/tpl.lucene_activation_row.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - {VAL_TITLE} - - - {TXT_MD_TYPE}: {VAL_TYPE} - - - - - {VAL_TYPE} - - diff --git a/components/ILIAS/Search/templates/default/tpl.lucene_adv_search.html b/components/ILIAS/Search/templates/default/tpl.lucene_adv_search.html deleted file mode 100755 index cd03ee993c84..000000000000 --- a/components/ILIAS/Search/templates/default/tpl.lucene_adv_search.html +++ /dev/null @@ -1,3 +0,0 @@ -{SEARCH_TABLE} -
-{SEARCH_RESULTS} diff --git a/components/ILIAS/Search/templates/default/tpl.range_search.html b/components/ILIAS/Search/templates/default/tpl.range_search.html deleted file mode 100755 index 895f4e24b5b8..000000000000 --- a/components/ILIAS/Search/templates/default/tpl.range_search.html +++ /dev/null @@ -1 +0,0 @@ -{TXT_FROM} {FROM} {TXT_UPTO} {UPTO} \ No newline at end of file diff --git a/components/ILIAS/Search/tests/ilObjSearchRpcClientCoordinatorTest.php b/components/ILIAS/Search/tests/ilObjSearchRpcClientCoordinatorTest.php index 6d8b2df82598..4d5c85ae712b 100755 --- a/components/ILIAS/Search/tests/ilObjSearchRpcClientCoordinatorTest.php +++ b/components/ILIAS/Search/tests/ilObjSearchRpcClientCoordinatorTest.php @@ -46,11 +46,10 @@ public function testRefreshLuceneSettings(): void $rpc_client = $this->getMockBuilder(ilRpcClient::class) ->disableOriginalConstructor() - ->addMethods(['refreshSettings']) ->getMock(); $rpc_client->expects($this->once()) - ->method('refreshSettings') - ->with('id_test'); + ->method('__call') + ->with('refreshSettings', ['id_test']); $coord = $this->getMockBuilder(ilObjSearchRpcClientCoordinator::class) ->setConstructorArgs([$settings, $src_logger]) @@ -90,11 +89,10 @@ public function testRefreshLuceneSettingsException(): void $rpc_client = $this->getMockBuilder(ilRpcClient::class) ->disableOriginalConstructor() - ->addMethods(['refreshSettings']) ->getMock(); $rpc_client->expects($this->once()) - ->method('refreshSettings') - ->with('id_test') + ->method('__call') + ->with('refreshSettings', ['id_test']) ->willThrowException(new Exception('message')); $coord = $this->getMockBuilder(ilObjSearchRpcClientCoordinator::class) diff --git a/components/ILIAS/Session/classes/class.ilObjSessionGUI.php b/components/ILIAS/Session/classes/class.ilObjSessionGUI.php index 251f4954c320..2b2cd686e9fd 100755 --- a/components/ILIAS/Session/classes/class.ilObjSessionGUI.php +++ b/components/ILIAS/Session/classes/class.ilObjSessionGUI.php @@ -35,7 +35,6 @@ class ilObjSessionGUI extends ilObjectGUI implements ilDesktopItemHandling protected ilAppEventHandler $event; protected \ILIAS\FileUpload\FileUpload $upload; protected ilHelpGUI $help; - protected \ILIAS\HTTP\Services $http; protected \ILIAS\Refinery\Factory $refinery; public ilLanguage $lng; @@ -87,7 +86,6 @@ public function __construct($a_data, $a_id, $a_call_by_reference, $a_prepare_out $this->event = $DIC->event(); $this->upload = $DIC->upload(); $this->help = $DIC->help(); - $this->http = $DIC->http(); $this->refinery = $DIC->refinery(); $this->type = "sess"; diff --git a/components/ILIAS/Session/classes/class.ilSessionDataSet.php b/components/ILIAS/Session/classes/class.ilSessionDataSet.php index 635253fb8beb..8618aeddd678 100755 --- a/components/ILIAS/Session/classes/class.ilSessionDataSet.php +++ b/components/ILIAS/Session/classes/class.ilSessionDataSet.php @@ -401,7 +401,7 @@ public function importRecord(string $a_entity, array $a_types, array $a_rec, ilI $this->current_obj = $newObj; $a_mapping->addMapping("components/ILIAS/Session", "sess", $a_rec["Id"], (string) $newObj->getId()); - $a_mapping->addMapping('components/ILIAS/Object', 'objs', $a_rec['Id'], (string) $newObj->getId()); + $a_mapping->addMapping('components/ILIAS/ILIASObject', 'objs', $a_rec['Id'], (string) $newObj->getId()); $a_mapping->addMapping('components/ILIAS/AdvancedMetaData', 'parent', $a_rec['Id'], (string) $newObj->getId()); $a_mapping->addMapping( "components/ILIAS/MetaData", diff --git a/components/ILIAS/Session/classes/class.ilSessionExporter.php b/components/ILIAS/Session/classes/class.ilSessionExporter.php index 0be841dd8d82..23f2e0aa026f 100755 --- a/components/ILIAS/Session/classes/class.ilSessionExporter.php +++ b/components/ILIAS/Session/classes/class.ilSessionExporter.php @@ -72,13 +72,13 @@ public function getXmlExportTailDependencies(string $a_entity, string $a_target_ // service settings $deps[] = array( - "component" => "components/ILIAS/Object", + "component" => "components/ILIAS/ILIASObject", "entity" => "service_settings", "ids" => $a_ids); // tile image $deps[] = array( - "component" => "components/ILIAS/Object", + "component" => "components/ILIAS/ILIASObject", "entity" => "tile", "ids" => $a_ids); diff --git a/components/ILIAS/Setup/Setup.php b/components/ILIAS/Setup/Setup.php index a9f43efbbddf..f24547c714b6 100644 --- a/components/ILIAS/Setup/Setup.php +++ b/components/ILIAS/Setup/Setup.php @@ -50,6 +50,13 @@ public function init( $pull[\ILIAS\Data\Factory::class] ); + $define[] = \ILIAS\Setup\AgentFinder::class; + $implement[\ILIAS\Setup\AgentFinder::class] = static fn(): \ILIAS\Setup\ImplementationOfAgentFinder => + $internal["agent_finder"]; + + $contribute[\ILIAS\Component\Activities\Activity::class] = static fn() => + new \ILIAS\Setup\Activities\GetStatus(); + $internal["command.install"] = static fn() => new \ILIAS\Setup\CLI\InstallCommand( $internal["agent_finder"], diff --git a/components/ILIAS/Setup/src/Activities/GetStatus.php b/components/ILIAS/Setup/src/Activities/GetStatus.php new file mode 100644 index 000000000000..6c6728d064d1 --- /dev/null +++ b/components/ILIAS/Setup/src/Activities/GetStatus.php @@ -0,0 +1,56 @@ +interface_finder->getMatchingClassNames( Agent::class, [], @@ -160,13 +160,13 @@ protected function getPluginNames(): \Generator { $directories = new \RecursiveIteratorIterator( - new \RecursiveDirectoryIterator(__DIR__ . "/../../../../public/Customizing/plugins/") + new \RecursiveDirectoryIterator(__DIR__ . "/../../../../public/Customizing/global/plugins") ); $names = []; foreach ($directories as $dir) { $groups = []; - if (preg_match("%^" . __DIR__ . "/[.][.]/[.][.]/[.][.]/[.][.]/public/Customizing/plugins/((\\w+/){2})([^/\.]+)(/|$)%", (string) $dir, $groups)) { - $name = $groups[3]; + if (preg_match("%^" . __DIR__ . "/[.][.]/[.][.]/[.][.]/[.][.]/public/Customizing/global/plugins/(Services|Modules)/((\\w+/){2})([^/\.]+)(/|$)%", (string) $dir, $groups)) { + $name = $groups[4]; if (isset($names[$name])) { continue; } diff --git a/components/ILIAS/Setup/tests/Metrics/MetricTest.php b/components/ILIAS/Setup/tests/Metrics/MetricTest.php index 0bf593b8235e..8d29f3499a6a 100755 --- a/components/ILIAS/Setup/tests/Metrics/MetricTest.php +++ b/components/ILIAS/Setup/tests/Metrics/MetricTest.php @@ -31,9 +31,7 @@ class MetricTest extends TestCase { - /** - * @dataProvider metricProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('metricProvider')] public function testConstructMetric(string $stability, string $type, $value, string $description, bool $success): void { if (!$success) { @@ -115,9 +113,7 @@ public static function metricProvider(): array ]; } - /** - * @dataProvider typedMetricsProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('typedMetricsProvider')] public function testToYAML(M $metric, string $expected): void { $this->assertEquals($expected, $metric->toYAML()); @@ -228,9 +224,7 @@ public function testExtractBySeverity(): void $this->assertEquals($expected_rest, $rest); } - /** - * @dataProvider typedMetricsProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('typedMetricsProvider')] public function testToArrayWithFlatValues(M $metric, string $expected): void { $this->assertEquals($expected, $metric->toArray()); diff --git a/components/ILIAS/Skill/Level/class.ilSkillUserLevelDBRepository.php b/components/ILIAS/Skill/Level/class.ilSkillUserLevelDBRepository.php index 45ad1860af25..88b51c393e43 100755 --- a/components/ILIAS/Skill/Level/class.ilSkillUserLevelDBRepository.php +++ b/components/ILIAS/Skill/Level/class.ilSkillUserLevelDBRepository.php @@ -455,13 +455,21 @@ public function getAllHistoricLevelEntriesOfUser( int $skill_id, int $a_tref_id, int $a_user_id = 0, - int $a_eval_by = 0 + int $a_eval_by = 0, + int $trigger_object_id = 0, + string $trigger_user = "" ): array { $ilDB = $this->db; $by = ($a_eval_by != ilBasicSkill::EVAL_BY_ALL) ? " AND self_eval = " . $ilDB->quote($a_eval_by, "integer") : ""; + $trigger_obj_str = ($trigger_object_id > 0) + ? " AND trigger_obj_id = " . $ilDB->quote($trigger_object_id, "integer") + : ""; + $trigger_user_str = ($trigger_user !== "") + ? " AND trigger_user_id = " . $ilDB->quote($trigger_user, "text") + : ""; $set = $ilDB->query( $q = "SELECT * FROM skl_user_skill_level " . @@ -469,6 +477,8 @@ public function getAllHistoricLevelEntriesOfUser( " AND tref_id = " . $ilDB->quote($a_tref_id, "integer") . " AND user_id = " . $ilDB->quote($a_user_id, "integer") . $by . + $trigger_obj_str . + $trigger_user_str . " ORDER BY status_date DESC" ); $levels = []; @@ -488,23 +498,51 @@ public function getAllHistoricLevelEntriesOfUser( return $levels; } + public function getLastLevelEntryOfUser( + int $skill_id, + int $a_tref_id, + int $a_user_id, + int $a_object_id = 0, + int $a_self_eval = 0, + string $trigger_user = "" + ): int { + foreach ($this->getAllHistoricLevelEntriesOfUser( + $skill_id, + $a_tref_id, + $a_user_id, + $a_self_eval, + $a_object_id, + $trigger_user + ) as $level) { + return $level["level_id"]; + } + return 0; + } + + public function getMaxLevelPerObject( int $skill_id, array $levels, int $a_tref_id, int $a_object_id, int $a_user_id = 0, - int $a_self_eval = 0 + int $a_self_eval = 0, + string $trigger_user = "" ): int { $ilDB = $this->db; + $tr_user = ""; + if ($trigger_user !== "") { + $tr_user = " AND trigger_user_id = " . $ilDB->quote($skill_id, "text"); + } + $set = $ilDB->query( $q = "SELECT level_id FROM skl_user_has_level " . " WHERE trigger_obj_id = " . $ilDB->quote($a_object_id, "integer") . " AND skill_id = " . $ilDB->quote($skill_id, "integer") . " AND tref_id = " . $ilDB->quote($a_tref_id, "integer") . " AND user_id = " . $ilDB->quote($a_user_id, "integer") . - " AND self_eval = " . $ilDB->quote($a_self_eval, "integer") + " AND self_eval = " . $ilDB->quote($a_self_eval, "integer") . $tr_user ); $has_level = []; diff --git a/components/ILIAS/Skill/Node/class.ilBasicSkill.php b/components/ILIAS/Skill/Node/class.ilBasicSkill.php index 31d7c294b2fc..5d5a01b506ea 100755 --- a/components/ILIAS/Skill/Node/class.ilBasicSkill.php +++ b/components/ILIAS/Skill/Node/class.ilBasicSkill.php @@ -451,7 +451,8 @@ public function getMaxLevelPerObject( int $a_tref_id, int $a_object_id, int $a_user_id = 0, - int $a_self_eval = 0 + int $a_self_eval = 0, + string $trigger_user = "" ): int { if ($a_user_id == 0) { $a_user_id = $this->user->getId(); @@ -465,10 +466,32 @@ public function getMaxLevelPerObject( $a_tref_id, $a_object_id, $a_user_id, - $a_self_eval + $a_self_eval, + $trigger_user ); } + public function getLastLevelEntryOfUser( + int $a_tref_id, + int $a_user_id, + int $a_object_id = 0, + int $a_self_eval = 0, + string $trigger_user = "" + ): int { + if ($a_user_id == 0) { + $a_user_id = $this->user->getId(); + } + return $this->bsc_skl_usr_lvl_db_rep->getLastLevelEntryOfUser( + $this->getId(), + $a_tref_id, + $a_user_id, + $a_object_id, + $a_self_eval, + $trigger_user + ); + } + + public function getNextLevelFulfilmentPerObject( int $a_tref_id, int $a_object_id, diff --git a/components/ILIAS/Skill/Personal/class.ilPersonalSkillsGUI.php b/components/ILIAS/Skill/Personal/class.ilPersonalSkillsGUI.php index 19a184ab43bf..858bf48b6cd5 100755 --- a/components/ILIAS/Skill/Personal/class.ilPersonalSkillsGUI.php +++ b/components/ILIAS/Skill/Personal/class.ilPersonalSkillsGUI.php @@ -160,10 +160,7 @@ class ilPersonalSkillsGUI */ protected array $requested_wsp_ids = []; - /** - * @var string[] - */ - protected array $trigger_user_filter = []; + protected string $trigger_user_filter = ""; public function __construct() { @@ -293,18 +290,15 @@ public function setTriggerObjectsFilter(array $trigger_objects_filter): void $this->trigger_objects_filter = $trigger_objects_filter; } - /** - * @return string[] - */ - public function getTriggerUserFilter(): array + public function getTriggerUserFilter(): string { return $this->trigger_user_filter; } /** - * @param string[] $trigger_user_filter + * @param string $trigger_user_filter */ - public function setTriggerUserFilter(array $trigger_user_filter): void + public function setTriggerUserFilter(string $trigger_user_filter): void { $this->trigger_user_filter = $trigger_user_filter; } @@ -593,7 +587,6 @@ public function renderSkillHTML( $acc->addItem($lng->txt('skmg_skill_levels'), $skl_lvl_desc); $panel_comps[] = $this->ui_fac->legacy()->content($acc->getHTML()); } - $prof_comp_head_rendered = false; $has_at_least_one_entry = false; if ($this->getProfileId() > 0) { @@ -1399,7 +1392,6 @@ public function getGapAnalysisHTML(int $a_user_id = 0, ?array $a_skills = null): // needed fix for profiles in gap view, because there is no filter shown (yet) $this->getFilter()->clear(); - if ($a_skills == null) { foreach ($this->getObjectSkills() as $s) { $a_skills[] = array( @@ -1436,20 +1428,29 @@ public function getGapAnalysisHTML(int $a_user_id = 0, ?array $a_skills = null): } // get actual levels for gap analysis - $this->actual_levels = $this->profile_completion_manager->getActualMaxLevels( - $user_id, - $skills, - $this->gap_mode, - $this->gap_mode_type, - $this->gap_mode_obj_id - ); - $this->next_level_fuls = $this->profile_completion_manager->getActualNextLevelFulfilments( - $user_id, - $skills, - $this->gap_mode, - $this->gap_mode_type, - $this->gap_mode_obj_id - ); + if ($this->getTriggerUserFilter() !== "") { // this must not be the actual max level + $this->actual_levels = $this->profile_completion_manager->getLastLevelPerObjectAndTriggerUser( + $user_id, + $skills, + $this->gap_mode_obj_id, + $this->getTriggerUserFilter() + ); + } else { + $this->actual_levels = $this->profile_completion_manager->getActualMaxLevels( + $user_id, + $skills, + $this->gap_mode, + $this->gap_mode_type, + $this->gap_mode_obj_id + ); + $this->next_level_fuls = $this->profile_completion_manager->getActualNextLevelFulfilments( + $user_id, + $skills, + $this->gap_mode, + $this->gap_mode_type, + $this->gap_mode_obj_id + ); + } $bc_skills = []; $html = ""; @@ -1551,7 +1552,7 @@ protected function getBarChartHTML(array $skills): string } } if ($this->actual_levels[$l->getBaseSkillId()][$l->getTrefId()] == $lv["id"]) { - $perc = $this->next_level_fuls[$l->getBaseSkillId()][$l->getTrefId()]; + $perc = $this->next_level_fuls[$l->getBaseSkillId()][$l->getTrefId()] ?? 0.0; $points[$eval_dim] = $cnt + $perc; $tooltips[$eval_dim] = null; if ($perc > 0) { @@ -1971,9 +1972,7 @@ protected function getNonLatestEntriesForSkillHTML( array $level_data ): string { $lng = $this->lng; - $tpl = new ilTemplate("tpl.skill_entries_non_latest.html", true, true, "components/ILIAS/Skill"); - $user_entries = $skill->getAllHistoricLevelEntriesOfUser($bs["tref"], $user->getId(), $eval_type); $user_entries_filtered = $this->getFilteredEntriesForSkill( $user_entries, @@ -2018,13 +2017,12 @@ protected function getFilteredEntriesForSkill( // get date of self evaluation $se_date = $this->self_evaluation_manager->getSelfEvaluationDate($user->getId(), $top_skill_id, $bs["tref"], $bs["id"]); $se_rendered = $se_date == ""; - $filtered_entries = []; foreach ($entries as $level_entry) { if (count($this->getTriggerObjectsFilter()) && !in_array($level_entry['trigger_obj_id'], $this->getTriggerObjectsFilter())) { continue; } - if (count($this->getTriggerUserFilter()) && !in_array($level_entry['trigger_user_id'], $this->getTriggerUserFilter())) { + if ($this->getTriggerUserFilter() !== "" && $level_entry['trigger_user_id'] != $this->getTriggerUserFilter()) { continue; } diff --git a/components/ILIAS/Skill/Profile/class.SkillProfileCompletionManager.php b/components/ILIAS/Skill/Profile/class.SkillProfileCompletionManager.php index c6f1dc0a47cb..e2b6c225e189 100755 --- a/components/ILIAS/Skill/Profile/class.SkillProfileCompletionManager.php +++ b/components/ILIAS/Skill/Profile/class.SkillProfileCompletionManager.php @@ -59,7 +59,8 @@ public function getActualMaxLevels( array $skills, string $gap_mode = "", string $gap_mode_type = "", - int $gap_mode_obj_id = 0 + int $gap_mode_obj_id = 0, + string $trigger_user = "" ): array { // get actual levels for gap analysis $actual_levels = []; @@ -77,13 +78,13 @@ public function getActualMaxLevels( $max = 0; foreach ($sub_objects as $ref_id) { $obj_id = \ilContainerReference::_lookupObjectId($ref_id); - $max_tmp = $bs->getMaxLevelPerObject($sk->getTrefId(), $obj_id, $user_id); + $max_tmp = $bs->getMaxLevelPerObject($sk->getTrefId(), $obj_id, $user_id, 0, $trigger_user); if ($max_tmp > $max) { $max = $max_tmp; } } } else { - $max = $bs->getMaxLevelPerObject($sk->getTrefId(), $gap_mode_obj_id, $user_id); + $max = $bs->getMaxLevelPerObject($sk->getTrefId(), $gap_mode_obj_id, $user_id, 0, $trigger_user); } } else { $max = $bs->getMaxLevel($sk->getTrefId(), $user_id); @@ -94,6 +95,32 @@ public function getActualMaxLevels( return $actual_levels; } + /** + * This does not include any container logic + * currently only used for survey, individual assessment + */ + public function getLastLevelPerObjectAndTriggerUser( + int $user_id, + array $skills, + int $obj_id, + string $trigger_user + ): array { + // get actual levels for gap analysis + $last_levels = []; + foreach ($skills as $sk) { + $bs = new \ilBasicSkill($sk->getBaseSkillId()); + $last = $bs->getLastLevelEntryOfUser( + $sk->getTrefId(), + $user_id, + $obj_id, + 0, + $trigger_user + ); + $last_levels[$sk->getBaseSkillId()][$sk->getTrefId()] = $last; + } + return $last_levels; + } + public function getActualLastLevels( int $user_id, array $skills, diff --git a/components/ILIAS/Skill/Profile/class.ilSkillProfileGUI.php b/components/ILIAS/Skill/Profile/class.ilSkillProfileGUI.php index 0d332c61992f..64d5dff9027c 100755 --- a/components/ILIAS/Skill/Profile/class.ilSkillProfileGUI.php +++ b/components/ILIAS/Skill/Profile/class.ilSkillProfileGUI.php @@ -185,8 +185,7 @@ public function executeCommand(): void "showUsers", "assignUser", "assignRole", "confirmUserRemoval", "removeUsers", "exportProfiles", "showImportForm", "importProfiles", "saveLevelOrder", "createLocal", "saveLocal", - "listLocalProfiles", "showLevelsWithLocalContext", "showObjects", - "showLevelsWithTableContext"))) { + "listLocalProfiles", "showLevelsWithLocalContext", "showObjects"))) { $this->$cmd(); } break; @@ -520,16 +519,6 @@ public function deleteProfiles(): void //// skill profile levels //// - public function showLevelsWithTableContext(): void - { - $ilCtrl = $this->ctrl; - - if ($this->requested_table_profile_action === "editProfile" && !empty($this->requested_table_profile_ids)) { - $ilCtrl->setParameter($this, "sprof_id", $this->requested_table_profile_ids[0]); - $ilCtrl->redirect($this, "showLevels"); - } - } - public function showLevels(): void { $tpl = $this->tpl; diff --git a/components/ILIAS/Skill/Table/classes/class.AssignMaterialsTable.php b/components/ILIAS/Skill/Table/classes/class.AssignMaterialsTable.php index 79c4299f0a39..5b92c8c56c2f 100755 --- a/components/ILIAS/Skill/Table/classes/class.AssignMaterialsTable.php +++ b/components/ILIAS/Skill/Table/classes/class.AssignMaterialsTable.php @@ -80,7 +80,7 @@ public function getComponent(): UI\Component\Table\Data $title = $this->node_manager->getWrittenPath($this->basic_skill_id); $table = $this->ui_fac->table() - ->data($title, $columns, $data_retrieval) + ->data($data_retrieval, $title, $columns) ->withId( self::class . "_" . $this->top_skill_id . "_" . diff --git a/components/ILIAS/Skill/Table/classes/class.AssignedObjectsTable.php b/components/ILIAS/Skill/Table/classes/class.AssignedObjectsTable.php index d60cb1ae0dc0..48b9ba5d7ab7 100755 --- a/components/ILIAS/Skill/Table/classes/class.AssignedObjectsTable.php +++ b/components/ILIAS/Skill/Table/classes/class.AssignedObjectsTable.php @@ -68,7 +68,7 @@ public function getComponent(): UI\Component\Table\Data $data_retrieval = $this->getDataRetrieval(); $table = $this->ui_fac->table() - ->data($this->lng->txt("skmg_assigned_objects"), $columns, $data_retrieval) + ->data($data_retrieval, $this->lng->txt("skmg_assigned_objects"), $columns) ->withId( self::class . "_" . $this->parent_obj::class . "_" . diff --git a/components/ILIAS/Skill/Table/classes/class.LevelResourcesTable.php b/components/ILIAS/Skill/Table/classes/class.LevelResourcesTable.php index 05b8624d30b5..e117d94f7e4e 100755 --- a/components/ILIAS/Skill/Table/classes/class.LevelResourcesTable.php +++ b/components/ILIAS/Skill/Table/classes/class.LevelResourcesTable.php @@ -128,7 +128,7 @@ public function getComponent(): UI\Component\Table\Data } $table = $this->ui_fac->table() - ->data($this->lng->txt("skmg_suggested_resources"), $columns, $data_retrieval) + ->data($data_retrieval, $this->lng->txt("skmg_suggested_resources"), $columns) ->withId( self::class . "_" . $this->base_skill_id . "_" . diff --git a/components/ILIAS/Skill/Table/classes/class.ProfileTable.php b/components/ILIAS/Skill/Table/classes/class.ProfileTable.php index da6933cd9d09..9f84ccb41fdb 100755 --- a/components/ILIAS/Skill/Table/classes/class.ProfileTable.php +++ b/components/ILIAS/Skill/Table/classes/class.ProfileTable.php @@ -109,7 +109,7 @@ public function getComponent(): UI\Component\Table\Data } $table = $this->ui_fac->table() - ->data($this->lng->txt("skmg_skill_profiles"), $columns, $data_retrieval) + ->data($data_retrieval, $this->lng->txt("skmg_skill_profiles"), $columns) ->withId( self::class . "_" . $this->skill_tree_id @@ -123,7 +123,7 @@ public function getComponent(): UI\Component\Table\Data protected function getColumns(): array { $columns = [ - "title" => $this->ui_fac->table()->column()->text($this->lng->txt("title")), + "title" => $this->ui_fac->table()->column()->link($this->lng->txt("title")), "context" => $this->ui_fac->table()->column()->text($this->lng->txt("context")) ->withIsSortable(false), "users" => $this->ui_fac->table()->column()->text($this->lng->txt("users")) @@ -139,17 +139,6 @@ protected function getActions(): array { $query_params_namespace = ["skl_profile_table"]; - $uri_edit = $this->df->uri( - ILIAS_HTTP_PATH . "/" . $this->ctrl->getLinkTargetByClass("ilskillprofilegui", "showLevelsWithTableContext") - ); - $url_builder_edit = new UI\URLBuilder($uri_edit); - list($url_builder_edit, $action_parameter_token_edit, $row_id_token_edit) = - $url_builder_edit->acquireParameters( - $query_params_namespace, - "action", - "profile_ids" - ); - $url_builder_delete = new UI\URLBuilder($this->df->uri($this->request->getUri()->__toString())); list($url_builder_delete, $action_parameter_token_delete, $row_id_token_delete) = $url_builder_delete->acquireParameters( @@ -169,13 +158,7 @@ protected function getActions(): array "profile_ids" ); - $actions = [ - "edit" => $this->ui_fac->table()->action()->single( - $this->tree_access_manager->hasManageProfilesPermission() ? $this->lng->txt("edit") : $this->lng->txt("show"), - $url_builder_edit->withParameter($action_parameter_token_edit, "editProfile"), - $row_id_token_edit - ) - ]; + $actions = []; if ($this->tree_access_manager->hasManageProfilesPermission()) { $actions["delete"] = $this->ui_fac->table()->action()->multi( $this->lng->txt("delete"), @@ -198,14 +181,18 @@ protected function getDataRetrieval(): UI\Component\Table\DataRetrieval $data_retrieval = new class ( $this->lng, $this->skill_tree_id, - $this->profile_manager + $this->profile_manager, + $this->ui_fac, + $this->ctrl ) implements UI\Component\Table\DataRetrieval { use TableRecords; public function __construct( protected \ilLanguage $lng, protected int $skill_tree_id, - protected Profile\SkillProfileManager $skill_profile_manager + protected Profile\SkillProfileManager $skill_profile_manager, + protected UI\Factory $ui_fac, + protected \ilCtrl $ctrl ) { } @@ -244,7 +231,12 @@ protected function getRecords(?Data\Range $range = null, ?Data\Order $order = nu $i = 0; foreach ($profiles as $profile) { $records[$i]["profile_id"] = $profile->getId(); - $records[$i]["title"] = $profile->getTitle(); + $this->ctrl->setParameterByClass("ilskillprofilegui", "sprof_id", $profile->getId()); + $records[$i]["title"] = $this->ui_fac->link()->standard( + $profile->getTitle(), + $this->ctrl->getLinkTargetByClass("ilskillprofilegui", "showLevels") + ); + $this->ctrl->clearParameterByClass("ilskillprofilegui", "sprof_id"); $profile_ref_id = $this->skill_profile_manager->lookupRefId($profile->getId()); $profile_obj_id = \ilContainerReference::_lookupObjectId($profile_ref_id); $profile_obj_title = \ilObject::_lookupTitle($profile_obj_id); diff --git a/components/ILIAS/Skill/Table/classes/class.ProfileUserAssignmentTable.php b/components/ILIAS/Skill/Table/classes/class.ProfileUserAssignmentTable.php index 477c2829bf0b..4a2087d7d921 100755 --- a/components/ILIAS/Skill/Table/classes/class.ProfileUserAssignmentTable.php +++ b/components/ILIAS/Skill/Table/classes/class.ProfileUserAssignmentTable.php @@ -107,7 +107,7 @@ public function getComponent(): UI\Component\Table\Data } $table = $this->ui_fac->table() - ->data($this->lng->txt("skmg_assigned_users"), $columns, $data_retrieval) + ->data($data_retrieval, $this->lng->txt("skmg_assigned_users"), $columns) ->withId( self::class . "_" . $this->profile->getId() diff --git a/components/ILIAS/Skill/Table/classes/class.TreeTable.php b/components/ILIAS/Skill/Table/classes/class.TreeTable.php index 5bdbb5698684..0586842709cc 100755 --- a/components/ILIAS/Skill/Table/classes/class.TreeTable.php +++ b/components/ILIAS/Skill/Table/classes/class.TreeTable.php @@ -64,7 +64,7 @@ public function getComponent(): UI\Component\Table\Data $data_retrieval = $this->getDataRetrieval(); $table = $this->ui_fac->table() - ->data($this->lng->txt("skmg_skill_trees"), $columns, $data_retrieval) + ->data($data_retrieval, $this->lng->txt("skmg_skill_trees"), $columns) ->withId(self::class) ->withActions($actions) ->withRequest($this->request); @@ -75,7 +75,7 @@ public function getComponent(): UI\Component\Table\Data protected function getColumns(): array { $columns = [ - "title" => $this->ui_fac->table()->column()->text($this->lng->txt("title")) + "title" => $this->ui_fac->table()->column()->link($this->lng->txt("title")) ]; return $columns; @@ -85,17 +85,6 @@ protected function getActions(): array { $query_params_namespace = ["skl_tree_table"]; - $uri_edit = $this->df->uri( - ILIAS_HTTP_PATH . "/" . $this->ctrl->getLinkTargetByClass("ilobjskilltreegui", "editSkills") - ); - $url_builder_edit = new UI\URLBuilder($uri_edit); - list($url_builder_edit, $action_parameter_token_edit, $row_id_token_edit) = - $url_builder_edit->acquireParameters( - $query_params_namespace, - "action", - "tree_ids" - ); - $uri_delete = $this->df->uri( ILIAS_HTTP_PATH . "/" . $this->ctrl->getLinkTargetByClass("ilobjskilltreegui", "delete") ); @@ -107,13 +96,7 @@ protected function getActions(): array "tree_ids" ); - $actions = [ - "edit" => $this->ui_fac->table()->action()->single( - $this->lng->txt("edit"), - $url_builder_edit->withParameter($action_parameter_token_edit, "editTree"), - $row_id_token_edit - ) - ]; + $actions = []; if ($this->skill_management_access_manager->hasCreateTreePermission()) { $actions["delete"] = $this->ui_fac->table()->action()->multi( $this->lng->txt("delete"), @@ -130,14 +113,18 @@ protected function getDataRetrieval(): UI\Component\Table\DataRetrieval $data_retrieval = new class ( $this->skill_manager, $this->skill_tree_manager, - $this->skill_tree_factory + $this->skill_tree_factory, + $this->ui_fac, + $this->ctrl ) implements UI\Component\Table\DataRetrieval { use TableRecords; public function __construct( protected Service\SkillInternalManagerService $skill_manager, protected Tree\SkillTreeManager $skill_tree_manager, - protected Tree\SkillTreeFactory $skill_tree_factory + protected Tree\SkillTreeFactory $skill_tree_factory, + protected UI\Factory $ui_fac, + protected \ilCtrl $ctrl ) { } @@ -186,7 +173,12 @@ function (\ilObjSkillTree $skillTree): array { $tree_obj = $item["tree"]; $tree = $this->skill_tree_factory->getTreeById($tree_obj->getId()); $records[$i]["tree_id"] = $tree->readRootId(); - $records[$i]["title"] = $tree_obj->getTitle(); + $this->ctrl->setParameterByClass("ilobjskilltreegui", "ref_id", $tree_obj->getRefId()); + $records[$i]["title"] = $this->ui_fac->link()->standard( + $tree_obj->getTitle(), + $this->ctrl->getLinkTargetByClass("ilobjskilltreegui", "editSkills") + ); + $this->ctrl->clearParameterByClass("ilobjskilltreegui", "ref_id"); $i++; } diff --git a/components/ILIAS/Skill/Tree/class.ilObjSkillTreeGUI.php b/components/ILIAS/Skill/Tree/class.ilObjSkillTreeGUI.php index df3850885819..19db5b6ea155 100755 --- a/components/ILIAS/Skill/Tree/class.ilObjSkillTreeGUI.php +++ b/components/ILIAS/Skill/Tree/class.ilObjSkillTreeGUI.php @@ -546,16 +546,7 @@ public function editSkills(): void $ilTabs->activateTab("skills"); - if ($this->requested_table_action === "editTree" && !empty($this->requested_table_tree_ids)) { - $node_id = (int) $this->requested_table_tree_ids[0]; - $tree_id = $this->skill_tree_repo->getTreeIdForNodeId($node_id); - $tree_obj = $this->skill_tree_manager->getTree($tree_id); - $ilCtrl->setParameter($this, "ref_id", $tree_obj->getRefId()); - $ilCtrl->setParameter($this, "node_id", $node_id); - } else { - $ilCtrl->setParameter($this, "node_id", $this->skill_tree->readRootId()); - } - + $ilCtrl->setParameterByClass(self::class, "node_id", $this->skill_tree->readRootId()); $ilCtrl->redirectByClass("ilskillrootgui", "listSkills"); } diff --git a/components/ILIAS/Skill/Tree/class.ilSkillSelectorGUI.php b/components/ILIAS/Skill/Tree/class.ilSkillSelectorGUI.php index ba30f61f6a45..bdd2b1604f90 100755 --- a/components/ILIAS/Skill/Tree/class.ilSkillSelectorGUI.php +++ b/components/ILIAS/Skill/Tree/class.ilSkillSelectorGUI.php @@ -84,7 +84,7 @@ public function getNodeHref($a_node): string // we have a tree id like : // and make a "common" skill id in format : - $id_parts = explode(":", $a_node["id"]); + $id_parts = explode(":", (string) $a_node["id"]); if (!isset($id_parts[1]) || $id_parts[1] == 0) { // skill in main tree $skill_id = $a_node["id"]; diff --git a/components/ILIAS/Skill/Tree/class.ilVirtualSkillTreeExplorerGUI.php b/components/ILIAS/Skill/Tree/class.ilVirtualSkillTreeExplorerGUI.php index e22e70b59507..17956609e7b7 100755 --- a/components/ILIAS/Skill/Tree/class.ilVirtualSkillTreeExplorerGUI.php +++ b/components/ILIAS/Skill/Tree/class.ilVirtualSkillTreeExplorerGUI.php @@ -26,8 +26,9 @@ * * @author Alex Killing */ -class ilVirtualSkillTreeExplorerGUI extends ilExplorerBaseGUI +class ilVirtualSkillTreeExplorerGUI extends ilExplorerBaseGUI implements \ILIAS\UI\Component\Tree\TreeRecursion { + protected \ILIAS\DI\UIServices $ui; protected ilLanguage $lng; protected ilVirtualSkillTree $vtree; protected SkillTreeManager $skill_tree_manager; @@ -42,6 +43,7 @@ public function __construct(string $a_id, $a_parent_obj, string $a_parent_cmd, i $this->lng = $DIC->language(); $this->ctrl = $DIC->ctrl(); + $this->ui = $DIC->ui(); parent::__construct($a_id, $a_parent_obj, $a_parent_cmd); $this->skill_tree_manager = $DIC->skills()->internal()->manager()->getTreeManager(); @@ -224,4 +226,104 @@ public function getNodeIconAlt($a_node): string return $lng->txt($a_node["type"]); } + + public function getHTML(): string + { + return $this->render(); + } + + protected function render(): string + { + $r = $this->ui->renderer(); + + return $r->render([ + $this->getTreeComponent() + ]); + } + + public function getTreeComponent(): \ILIAS\UI\Implementation\Component\Tree\Tree + { + $f = $this->ui->factory(); + $tree = $this->vtree; + + if (!$this->getSkipRootNode()) { + $data = array( + $tree->getRootNode() + ); + } else { + $data = $tree->getChildsOfNode((string) ($tree->getRootNode()["id"])); + } + + //$label = $this->vtree->getNodeTitle($tree->getRootNode()); + //if ($label === "" && $this->getNodeContent($this->getRootNode())) { + // $label = $this->getNodeContent($this->getRootNode()); + //} + $label = "test"; + + $tree = $f->tree()->expandable($label, $this) + ->withData($data) + ->withHighlightOnNodeClick(true); + + return $tree; + } + + public function getChildren($record, $environment = null): array + { + return $this->getChildsOfNode((string) $record["id"]); + } + + protected function createNode( + \ILIAS\UI\Component\Tree\Node\Factory $factory, + $record + ): \ILIAS\UI\Component\Tree\Node\Node { + $nodeIconPath = $this->getNodeIcon($record); + + $icon = null; + if ($nodeIconPath !== '') { + $icon = $this->ui + ->factory() + ->symbol() + ->icon() + ->custom($nodeIconPath, $this->getNodeIconAlt($record)); + } + + return $factory->simple($this->getNodeContent($record), $icon); + } + + public function build( + \ILIAS\UI\Component\Tree\Node\Factory $factory, + $record, + $environment = null + ): \ILIAS\UI\Component\Tree\Node\Node { + $node = $this->createNode($factory, $record); + + $href = $this->getNodeHref($record); + if ($href !== '' && '#' !== $href && $this->isNodeClickable($record)) { + $node = $node->withLink(new \ILIAS\Data\URI(ILIAS_HTTP_PATH . '/' . $href)); + } + + if ($this->isNodeOpen((int) $this->getNodeId($record))) { + $node = $node->withExpanded(true); + } + + /* + $nodeStateToggleCmdClasses = $this->getNodeStateToggleCmdClasses($record); + $cmdClass = end($nodeStateToggleCmdClasses); + + if (is_string($cmdClass) && $cmdClass !== '') { + $node = $node->withAdditionalOnLoadCode(function ($id) use ($record, $nodeStateToggleCmdClasses, $cmdClass): string { + $serverNodeId = $this->getNodeId($record); + + $this->ctrl->setParameterByClass($cmdClass, $this->node_parameter_name, $serverNodeId); + $url = $this->ctrl->getLinkTargetByClass($nodeStateToggleCmdClasses, 'toggleExplorerNodeState', '', true, false); + $this->ctrl->setParameterByClass($cmdClass, $this->node_parameter_name, null); + + $javascript = "il.UI.tree.registerToggleNodeAsyncAction('$id', '$url', 'prior_state');"; + + return $javascript; + }); + }*/ + + return $node; + } } diff --git a/components/ILIAS/Skill/tests/Profile/SkillProfileCompletionManagerTest.php b/components/ILIAS/Skill/tests/Profile/SkillProfileCompletionManagerTest.php index 72fd31d073b4..8837165a421e 100644 --- a/components/ILIAS/Skill/tests/Profile/SkillProfileCompletionManagerTest.php +++ b/components/ILIAS/Skill/tests/Profile/SkillProfileCompletionManagerTest.php @@ -22,7 +22,6 @@ use PHPUnit\Framework\TestCase; - class SkillProfileCompletionManagerTest extends TestCase { protected function getManagerMock(): SkillProfileCompletionManager @@ -42,7 +41,8 @@ public function getActualMaxLevels( array $skills, string $gap_mode = "", string $gap_mode_type = "", - int $gap_mode_obj_id = 0 + int $gap_mode_obj_id = 0, + string $trigger_user = "" ): array { $actual_levels = []; diff --git a/components/ILIAS/StaticURL/src/Handler/HandlerService.php b/components/ILIAS/StaticURL/src/Handler/HandlerService.php index 74ba558edd84..12b08eeeb541 100755 --- a/components/ILIAS/StaticURL/src/Handler/HandlerService.php +++ b/components/ILIAS/StaticURL/src/Handler/HandlerService.php @@ -95,7 +95,9 @@ public function performRedirect(URI $base_uri): void $full_uri = $this->appendUnknownParameters($this->context, $full_uri); // Read the comment below } else { // Perform Redirect - $uri_path = $response->getURIPath(); + $uri_path = $response->getURIPath() ?? ''; + $base_path = $base_uri->getPath() ?? ''; + $uri_path = str_replace($base_path, '', $uri_path); $full_uri = $base_uri . '/' . trim((string) $uri_path, '/'); } diff --git a/components/ILIAS/StaticURL/tests/URIBuilderTest.php b/components/ILIAS/StaticURL/tests/URIBuilderTest.php index a851d36dd34f..b82c10fd9a62 100755 --- a/components/ILIAS/StaticURL/tests/URIBuilderTest.php +++ b/components/ILIAS/StaticURL/tests/URIBuilderTest.php @@ -45,7 +45,7 @@ public static function getILIAS_HTTP_Paths(): array ['http://test9.ilias.de/goto.php', 'http://test9.ilias.de'], ['http://test9.ilias.de/go/hello', 'http://test9.ilias.de'], ['http://test9.ilias.de/go/hello', 'http://test9.ilias.de'], - ['http://test9.ilias.de/Customizing/global/plugins/Services/index.php', 'http://test9.ilias.de'], + ['http://test9.ilias.de/Customizing/plugins/Services/index.php', 'http://test9.ilias.de'], ]; } diff --git a/components/ILIAS/StudyProgramme/classes/Certificate/class.ilStudyProgrammePlaceholderValues.php b/components/ILIAS/StudyProgramme/classes/Certificate/class.ilStudyProgrammePlaceholderValues.php index 220f11174ac0..23f9c1eca2a8 100644 --- a/components/ILIAS/StudyProgramme/classes/Certificate/class.ilStudyProgrammePlaceholderValues.php +++ b/components/ILIAS/StudyProgramme/classes/Certificate/class.ilStudyProgrammePlaceholderValues.php @@ -62,10 +62,14 @@ public function getPlaceholderValues(int $userId, int $objId): array { $object = $this->objectHelper->getInstanceByObjId($objId); $placeholders = $this->defaultPlaceholderValuesObject->getPlaceholderValues($userId, $objId); - $assignments = $object->getAssignmentsOfSingleProgramForUser($userId); - $latest_progress = $this->getRelevantProgressFromAssignments($assignments); - $type = $object->getSubType(); + $latest_progress = false; + $ass_id = $object->getCertificateRelevantAssignmentIds($userId); + if ($ass_id !== []) { + $latest_progress = $object->getSpecificAssignment(current($ass_id))->getProgressTree(); + } + + $type = $object->getSubType(); $placeholders['PRG_TITLE'] = \ilLegacyFormElementsUtil::prepareFormOutput($object->getTitle()); $placeholders['PRG_DESCRIPTION'] = \ilLegacyFormElementsUtil::prepareFormOutput($object->getDescription()); $placeholders['PRG_TYPE'] = \ilLegacyFormElementsUtil::prepareFormOutput($type ? $type->getTitle() : ''); @@ -104,66 +108,4 @@ public function getPlaceholderValuesForPreview(int $userId, int $objId): array return $placeholders; } - protected function getRelevantProgressFromAssignments(array $assignments): ?\ilPRGProgress - { - if (count($assignments) === 0) { - return null; - } - $latest_progress = null; - $latest_successful = $this->getLatestSuccessfulAssignment($assignments); - if ($latest_successful) { - $latest_progress = $latest_successful->getProgressTree(); - } - return $latest_progress; - } - - protected function getLatestSuccessfulAssignment(array $assignments): ?\ilPRGAssignment - { - $successful = array_filter( - $assignments, - fn($ass) => $ass->getProgressTree()->isSuccessful() - ); - if (count($successful) === 0) { - return null; - } - - //is there an unlimited validity? if so, take latest. - $unlimited = array_filter( - $successful, - fn($ass) => is_null($ass->getProgressTree()->getValidityOfQualification()) - ); - if (count($unlimited) > 0) { - $successful = $unlimited; - usort($successful, static function (\ilPRGAssignment $a, \ilPRGAssignment $b): int { - $a_dat = $a->getProgressTree()->getCompletionDate(); - $b_dat = $b->getProgressTree()->getCompletionDate(); - if ($a_dat > $b_dat) { - return -1; - } elseif ($a_dat < $b_dat) { - return 1; - } else { - return 0; - } - }); - } else { - //there are (only) limited validities: take the one that lasts the longest. - $limited = array_filter( - $successful, - fn($ass) => !is_null($ass->getProgressTree()->getValidityOfQualification()) - ); - $successful = $limited; - usort($successful, static function (\ilPRGAssignment $a, \ilPRGAssignment $b): int { - $a_dat = $a->getProgressTree()->getValidityOfQualification(); - $b_dat = $b->getProgressTree()->getValidityOfQualification(); - if ($a_dat > $b_dat) { - return -1; - } elseif ($a_dat < $b_dat) { - return 1; - } else { - return 0; - } - }); - } - return array_shift($successful); - } } diff --git a/components/ILIAS/StudyProgramme/classes/class.ilObjStudyProgramme.php b/components/ILIAS/StudyProgramme/classes/class.ilObjStudyProgramme.php index 09e2708b7bcb..4cbe71d60e7d 100755 --- a/components/ILIAS/StudyProgramme/classes/class.ilObjStudyProgramme.php +++ b/components/ILIAS/StudyProgramme/classes/class.ilObjStudyProgramme.php @@ -19,6 +19,7 @@ declare(strict_types=1); use ILIAS\Filesystem\Filesystem; +use ILIAS\ILIASObject\Properties\AdditionalProperties\Icon\Factory as CustomIconFactory; class ilObjStudyProgramme extends ilContainer { @@ -64,7 +65,7 @@ class ilObjStudyProgramme extends ilContainer protected Filesystem $webdir; protected ilObjUser $ilUser; protected ?ilObjectFactoryWrapper $object_factory = null; - protected ilObjectCustomIconFactory $custom_icon_factory; + protected CustomIconFactory $custom_icon_factory; protected ilLogger $logger; /** @@ -506,13 +507,6 @@ public function getChildren(bool $include_references = false): array if ($include_references && $this->reference_children === null) { $this->reference_children = []; $ref_child_ref_ids = $this->tree->getChildsByType($this->getRefId(), "prgr"); - foreach ($this->children as $prg) { - $ref_child_ref_ids = - array_merge( - $this->tree->getChildsByType($prg->getRefId(), "prgr"), - $ref_child_ref_ids - ); - } foreach ( array_unique( array_map( @@ -754,38 +748,40 @@ public function applyToSubTreeNodes(Closure $fun, bool $include_references = fal /** * Get courses in this program that the given user already completed. */ - public function getCompletedCourses(int $usr_id): array + public function getCompletedCourses(ilPRGAssignment $assignment): array { - $node_data = $this->tree->getNodeData($this->getRefId()); - $crsrs = $this->tree->getSubTree($node_data, true, ["crsr"]); - - $completed_crss = array(); - foreach ($crsrs as $ref) { - $crs_id = (int) ilContainerReference::_lookupTargetId((int) $ref["obj_id"]); - $crs_ref_id = (int) ilContainerReference::_lookupTargetRefId((int) $ref["obj_id"]); - - if (ilObject::_exists((int) $ref['ref_id'], true) && - is_null(ilObject::_lookupDeletedDate((int) $ref['ref_id'])) && - ilObject::_exists($crs_id, false) && - is_null(ilObject::_lookupDeletedDate($crs_ref_id)) && - ilLPStatus::_hasUserCompleted($crs_id, $usr_id) + $completed_crss = []; + $f = function ($prg) use (&$completed_crss, $assignment) { + if ($prg->isActive() && + $assignment->getProgressForNode($prg->getId())->isRelevant() ) { - $containing_prg = self::getInstanceByRefId((int) $ref["parent"]); - if ($containing_prg->isActive()) { - $completed_crss[] = [ - "crs_id" => $crs_id - , "prg_ref_id" => (int) $ref["parent"] - , "prg_obj_id" => $containing_prg->getId() - , "crsr_ref_id" => (int) $ref["child"] - , "crsr_id" => (int) $ref["obj_id"] - , "crs_ref_id" => (int) $crs_ref_id - , "crs_id" => (int) $crs_id - - , "title" => ilContainerReference::_lookupTitle((int) $ref["obj_id"]) - ]; + foreach ($prg->getLPChildren() as $child) { + $crs_id = (int) ilContainerReference::_lookupTargetId((int) $child->getId()); + $crs_ref_id = (int) ilContainerReference::_lookupTargetRefId((int) $child->getId()); + $crsr_ref_id = (int) $child->getRefId(); + + if (ilObject::_exists($crsr_ref_id, true) && + is_null(ilObject::_lookupDeletedDate($crsr_ref_id)) && + ilObject::_exists($crs_id, false) && + is_null(ilObject::_lookupDeletedDate($crs_ref_id)) && + ilLPStatus::_hasUserCompleted($crs_id, $assignment->getUserId()) + ) { + $completed_crss[] = [ + 'crs_id' => $crs_id, + 'prg_ref_id' => $prg->getRefId(), + 'prg_obj_id' => $prg->getId(), + 'crsr_ref_id' => $crsr_ref_id, + 'crsr_id' => $child->getId(), + 'crs_ref_id' => $crs_ref_id, + 'title' => ilContainerReference::_lookupTitle((int) $child->getId()), + ]; + } } + return true; } - } + return false; + }; + $this->applyToSubTreeNodes($f, true); return $completed_crss; } @@ -1151,6 +1147,14 @@ public function hasAssignmentsOfSingleProgramForUser(int $usr_id): bool return count($this->getAssignmentsOfSingleProgramForUser($usr_id)) > 0; } + public function getCertificateRelevantAssignmentIds(int ...$usr_ids): array + { + return $this->assignment_repository->getCertificateRelevantAssignmentIds( + $this->getId(), + ...$usr_ids + ); + } + //////////////////////////////////// // USER PROGRESS @@ -1484,7 +1488,11 @@ public static function removeMemberFromProgrammes(string $src_type, int $src_id, continue; } - if (!is_null($next_membership_source) && $next_membership_source->isEnabled()) { + if ( + $next_membership_source !== null + && $next_membership_source?->isEnabled() + && $next_membership_source->getSourceId() !== $src_id + ) { $new_src_type = $next_membership_source->getSourceType(); $assigned_by = ilStudyProgrammeAutoMembershipSource::SOURCE_MAPPING[$new_src_type]; $assignment = $assignment->withLastChange($assigned_by, $now); @@ -1864,6 +1872,9 @@ public function acknowledgeCourses( public function canBeCompleted(ilPRGProgress $progress): bool { + if ($this->getStatus() === ilStudyProgrammeSettings::STATUS_DRAFT) { + return false; + } if ($this->getLPMode() == ilStudyProgrammeSettings::MODE_LP_COMPLETED) { return true; } diff --git a/components/ILIAS/StudyProgramme/classes/class.ilObjStudyProgrammeAutoCategoriesGUI.php b/components/ILIAS/StudyProgramme/classes/class.ilObjStudyProgrammeAutoCategoriesGUI.php index 888cae970f78..c8b6005678bf 100755 --- a/components/ILIAS/StudyProgramme/classes/class.ilObjStudyProgrammeAutoCategoriesGUI.php +++ b/components/ILIAS/StudyProgramme/classes/class.ilObjStudyProgrammeAutoCategoriesGUI.php @@ -329,6 +329,13 @@ function ($id) use ($form_id) { function ($id) use ($form) { $selector_post_var = self::F_CATEGORY_REF; $js = $form->getItemByPostVar($selector_post_var)->getOnloadCode(); + $js[] = " + document.getElementById('$id').onclose = ()=> { + let smodal = document.querySelectorAll('body dialog') + .item(document.querySelectorAll('body dialog').length-1); + smodal?.remove(); + } + "; return implode(';', $js); } ); diff --git a/components/ILIAS/StudyProgramme/classes/class.ilObjStudyProgrammeGUI.php b/components/ILIAS/StudyProgramme/classes/class.ilObjStudyProgrammeGUI.php index f68c9620936d..357a5a4c55cc 100755 --- a/components/ILIAS/StudyProgramme/classes/class.ilObjStudyProgrammeGUI.php +++ b/components/ILIAS/StudyProgramme/classes/class.ilObjStudyProgrammeGUI.php @@ -21,6 +21,7 @@ use ILIAS\Container\Content\ViewManager; use ILIAS\Refinery; use ILIAS\HTTP\Wrapper\RequestWrapper; +use ILIAS\ILIASObject\Properties\Translations\TranslationGUI; /** * Class ilObjStudyProgrammeGUI class @@ -32,7 +33,7 @@ * @ilCtrl_Calls ilObjStudyProgrammeGUI: ilObjStudyProgrammeMembersGUI * @ilCtrl_Calls ilObjStudyProgrammeGUI: ilObjStudyProgrammeAutoMembershipsGUI * @ilCtrl_Calls ilObjStudyProgrammeGUI: ilObjectCopyGUI - * @ilCtrl_Calls ilObjStudyProgrammeGUI: ilObjectTranslationGUI + * @ilCtrl_Calls ilObjStudyProgrammeGUI: ILIAS\ILIASObject\Properties\Translations\TranslationGUI * @ilCtrl_Calls ilObjStudyProgrammeGUI: ilCertificateGUI * @ilCtrl_Calls ilObjStudyProgrammeGUI: ilObjStudyProgrammeAutoCategoriesGUI * @ilCtrl_Calls ilObjStudyProgrammeGUI: ilContainerGUI @@ -221,12 +222,24 @@ public function executeCommand(): void $gui = new ilobjectcopygui($this); $this->ctrl->forwardCommand($gui); break; - case 'ilobjecttranslationgui': + case strtolower(TranslationGUI::class): $this->denyAccessIfNot(ilPRGPermissionsHelper::ROLEPERM_WRITE); $this->getSubTabs('settings'); $this->tabs_gui->activateTab(self::TAB_SETTINGS); $this->tabs_gui->activateSubTab('settings_trans'); - $transgui = new ilObjectTranslationGUI($this); + $transgui = new TranslationGUI( + $this->getObject(), + $this->lng, + $this->access, + $this->user, + $this->ctrl, + $this->tpl, + $this->ui_factory, + $this->ui_renderer, + $this->http, + $this->refinery, + $this->toolbar + ); $this->ctrl->forwardCommand($transgui); break; case "ilcertificategui": @@ -306,8 +319,6 @@ public function executeCommand(): void $this->view(); break; case "delete": - $this->tabs_gui->clearTargets(); - $this->tabs_gui->setBackTarget($this->lng->txt("back"), $this->ctrl->getLinkTarget($this)); parent::deleteObject(); break; case 'confirmedDelete': @@ -581,26 +592,10 @@ public function getSubTabs(string $parent_tab): void case self::TAB_VIEW_CONTENT: case self::SUBTAB_VIEW_MANAGE: case 'view': + case 'delete': $this->addStandardContainerSubTabs(true); - ; break; - case false: - if ($this->checkAccess(ilPRGPermissionsHelper::ROLEPERM_READ)) { - $this->tabs_gui->addSubTab( - self::TAB_VIEW_CONTENT, - $this->lng->txt("view"), - $this->getLinkTarget("view") - ); - } - if ($this->checkAccess(ilPRGPermissionsHelper::ROLEPERM_WRITE)) { - $this->tabs_gui->addSubTab( - self::SUBTAB_VIEW_MANAGE, - $this->lng->txt("cntr_manage"), - $this->getLinkTarget(self::SUBTAB_VIEW_MANAGE) - ); - } - break; case 'settings': $this->tabs_gui->addSubTab( 'settings', @@ -619,7 +614,7 @@ public function getSubTabs(string $parent_tab): void $this->tabs_gui->addSubTab( "settings_trans", $this->lng->txt("obj_multilinguality"), - $this->ctrl->getLinkTargetByClass("ilobjecttranslationgui", "") + $this->ctrl->getLinkTargetByClass(TranslationGUI::class, "") ); $validator = new ilCertificateActiveValidator(); @@ -758,9 +753,8 @@ public static function _goto(string $target): void { global $DIC; $ilCtrl = $DIC['ilCtrl']; - $id = explode("_", $target); $ilCtrl->setTargetScript('ilias.php'); - $ilCtrl->setParameterByClass("ilobjstudyprogrammegui", "ref_id", $id[0]); + $ilCtrl->setParameterByClass("ilobjstudyprogrammegui", "ref_id", $target); $ilCtrl->redirectByClass(array("ilRepositoryGUI", "ilobjstudyprogrammegui"), "view"); } diff --git a/components/ILIAS/StudyProgramme/classes/class.ilObjStudyProgrammeIndividualPlanGUI.php b/components/ILIAS/StudyProgramme/classes/class.ilObjStudyProgrammeIndividualPlanGUI.php index bb22895799a6..6ef872bc6893 100755 --- a/components/ILIAS/StudyProgramme/classes/class.ilObjStudyProgrammeIndividualPlanGUI.php +++ b/components/ILIAS/StudyProgramme/classes/class.ilObjStudyProgrammeIndividualPlanGUI.php @@ -249,7 +249,7 @@ protected function updateDeadlines(array $deadlines, ilPRGMessageCollection $msg if (trim($deadline) === '') { $deadline = null; } else { - $deadline = DateTimeImmutable::createFromFormat('d.m.Y', $deadline); + $deadline = DateTimeImmutable::createFromFormat('Y-m-d', $deadline); } $cur_deadline = $progress->getDeadline(); diff --git a/components/ILIAS/StudyProgramme/classes/class.ilObjStudyProgrammeMembersGUI.php b/components/ILIAS/StudyProgramme/classes/class.ilObjStudyProgrammeMembersGUI.php index b28dbd50ffcd..3b0dad229b60 100755 --- a/components/ILIAS/StudyProgramme/classes/class.ilObjStudyProgrammeMembersGUI.php +++ b/components/ILIAS/StudyProgramme/classes/class.ilObjStudyProgrammeMembersGUI.php @@ -197,7 +197,6 @@ public function executeCommand(): void case "confirmedAcknowledgeAllCourses": $this->confirmedAcknowledgeAllCourses(); break; - case "mailUserMulti": $this->mailToSelectedUsers(); break; @@ -210,6 +209,17 @@ public function executeCommand(): void case "confirmedRemovalOfCertificate": $this->confirmedRemovalOfCertificate(); break; + case "deliverCertificate": + if (!$this->permissions->may(ilOrgUnitOperation::OP_MANAGE_MEMBERS)) { + $this->tpl->setOnScreenMessage("failure", $this->lng->txt("permission_denied"), true); + $this->ctrl->redirect($this, "view"); + } + $usr_id = $this->http_wrapper->query()->retrieve( + 'cert_usr_id', + $this->refinery->kindlyTo()->int() + ); + $this->deliverCertificate($this->object->getId(), $usr_id); + break; default: throw new ilException("ilObjStudyProgrammeMembersGUI: Command not supported: $cmd"); @@ -354,7 +364,7 @@ public function addUsers(array $user_ids) foreach ($user_ids as $user_id) { $ass = $prg->assignUser((int) $user_id); $assignments[] = $ass; - if ($prg->getCompletedCourses((int) $user_id)) { + if ($prg->getCompletedCourses($ass)) { $with_courses[] = $ass; } } @@ -389,7 +399,7 @@ public function viewCompletedCourses(array $assignments): Form $ass_ids = []; foreach ($assignments as $ass) { $ass_ids[] = $ass->getId(); - $completed_crss = $prg->getCompletedCourses($ass->getUserId()); + $completed_crss = $prg->getCompletedCourses($ass); $label = sprintf( "%s (%s)", @@ -685,11 +695,6 @@ protected function getAssignmentsFromQuery(): array fn($ass_id) => $this->assignment_db->get((int) $ass_id), $ass_ids ); - - $assignments = array_filter( - $assignments, - fn($ass) => $ass->getRootId() === $prg->getId() - ); return $assignments; } @@ -701,10 +706,10 @@ public function confirmedAcknowledgeAllCourses() foreach ($assignments as $ass) { $ass_ids[] = $ass->getId(); - $completed_crss = $prg->getCompletedCourses($ass->getUserId()); + $completed_crss = $prg->getCompletedCourses($ass); $nodes = []; foreach ($completed_crss as $opt) { - $nodes[] = [$opt['prg_obj_id'], $opt['crsr_id']]; + $nodes[] = [$opt['prg_obj_id'], $opt['crs_id']]; } $prg->acknowledgeCourses( $ass->getId(), @@ -1155,4 +1160,15 @@ public function confirmedRemovalOfCertificate(): void $this->showSuccessMessage("successfully_removed_certificate"); $this->ctrl->redirect($this, "view"); } + + protected function deliverCertificate(int $obj_id, int $usr_id): void + { + $repository = new ilUserCertificateRepository(); + $pdf_action = new ilCertificatePdfAction( + new ilPdfGenerator($repository), + new ilCertificateUtilHelper(), + $this->lng->txt('error_creating_certificate_pdf') + ); + $pdf_action->downloadPdf($usr_id, $obj_id); + } } diff --git a/components/ILIAS/StudyProgramme/classes/class.ilObjStudyProgrammeSettingsGUI.php b/components/ILIAS/StudyProgramme/classes/class.ilObjStudyProgrammeSettingsGUI.php index 06a4c94623f9..db0c83d1a43f 100755 --- a/components/ILIAS/StudyProgramme/classes/class.ilObjStudyProgrammeSettingsGUI.php +++ b/components/ILIAS/StudyProgramme/classes/class.ilObjStudyProgrammeSettingsGUI.php @@ -22,6 +22,7 @@ use ILIAS\UI\Implementation\Component\Input\Field\Factory as InputFieldFactory; use ILIAS\UI\Renderer; use ILIAS\MetaData\Services\ServicesInterface as LOMServices; +use ILIAS\ILIASObject\Properties\Translations\Translations; /** * @ilCtrl_Calls ilObjStudyProgrammeSettingsGUI: ilStudyProgrammeCommonSettingsGUI @@ -202,7 +203,7 @@ function ($values) use ($prg) { protected function buildFormElements( InputFieldFactory $ff, - ilObjectTranslation $trans, + Translations $trans, array $sp_types, ilStudyProgrammeSettings $settings ): array { @@ -234,7 +235,7 @@ protected function buildFormElements( protected function getEditSection( InputFieldFactory $ff, - ilObjectTranslation $trans + Translations $trans ): ILIAS\UI\Component\Input\Field\Section { $lang = '?'; foreach ($this->lom_services->dataHelper()->getAllLanguages() as $language) { @@ -254,7 +255,7 @@ protected function getEditSection( ], $this->txt("prg_edit"), $this->txt("language") . ": " . $lang . - ' » ' . $this->txt("obj_more_translations") . '' ); } diff --git a/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeAppEventListener.php b/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeAppEventListener.php index aeaa14b9a50d..96b9de034256 100755 --- a/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeAppEventListener.php +++ b/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeAppEventListener.php @@ -63,7 +63,7 @@ public static function handleEvent(string $component, string $event, array $para break; } break; - case "components/ILIAS/Object": + case "components/ILIAS/ILIASObject": switch ($event) { case "delete": case "toTrash": diff --git a/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeDIC.php b/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeDIC.php index a8b91a36b638..3fb05ef8a639 100755 --- a/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeDIC.php +++ b/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeDIC.php @@ -53,7 +53,8 @@ public static function specificDicFor(ilObjStudyProgramme $prg): Container ilExportFieldsInfo::_getInstanceByType('prg'), $dic['repo.assignment'], $DIC['lng'], - $dic['permissionhelper'] + $dic['permissionhelper'], + new ilCertificateDownloadValidator() ); }; @@ -345,7 +346,8 @@ protected static function buildDIC(): Container ilExportFieldsInfo::_getInstanceByType('prg'), $dic['repo.assignment'], $DIC['lng'], - $dic['permissionhelper'] + $dic['permissionhelper'], + new ilCertificateDownloadValidator() ); }; diff --git a/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeDashboardViewGUI.php b/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeDashboardViewGUI.php index 3b949914a6da..eb5f5750c09e 100755 --- a/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeDashboardViewGUI.php +++ b/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeDashboardViewGUI.php @@ -66,7 +66,12 @@ public function initData(): void $properties[$this->lng->txt('prg_dash_label_valid')] = $row->getExpiryDate() ?: $row->getValidity(); if ($cert_link = $this->maybeGetCertificateLink($this->user->getId(), $prg->getId(), $prg->getRefId())) { - $properties[$this->lng->txt('certificate')] = $cert_link; + $repo_assignment = ilStudyProgrammeDIC::dic()['repo.assignment']; + $longest_lasting = $repo_assignment->getLongestValidAssignment($prg->getId(), $this->user->getId()); + if ($longest_lasting === null + || $row->getAssignmentId() === $longest_lasting->getId()) { + $properties[$this->lng->txt('certificate')] = $cert_link; + } } } else { $properties[$this->lng->txt('prg_dash_label_finish_until')] = $row->getDeadline(); diff --git a/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeIndividualPlanTableGUI.php b/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeIndividualPlanTableGUI.php index 55820786b577..5346cd92275e 100755 --- a/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeIndividualPlanTableGUI.php +++ b/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeIndividualPlanTableGUI.php @@ -108,11 +108,12 @@ protected function fillRow(array $a_set): void $this->tpl->setVariable("POINTS_CURRENT", $a_set["points_current"]); $this->tpl->setVariable( "POINTS_REQUIRED", - $this->getRequiredPointsInput($a_set["progress_id"], $a_set["status"], (string)$a_set["points_required"]) + $this->getRequiredPointsInput($a_set["progress_id"], $a_set["status"], (string) $a_set["points_required"]) ); $this->tpl->setVariable("MANUAL_STATUS", $this->getManualStatusSelect( $a_set["progress_id"], - (int) $a_set["status"] + (int) $a_set["status"], + (int) $a_set["program_status"], )); $this->tpl->setVariable("POSSIBLE", $a_set["possible"] ? $this->possible_image : $this->not_possible_image); $this->tpl->setVariable("CHANGED_BY", $a_set["changed_by"]); @@ -191,7 +192,9 @@ function ($node) use ($prg_id, $ass_id, $usr_id, &$plan, $prg) { "title" => $node->getTitle(), "points_current" => $progress->getCurrentAmountOfPoints(), "points_required" => $progress->getAmountOfPoints(), - "possible" => $progress->isSuccessful() || $programme->canBeCompleted($progress) || !$progress->isRelevant(), + "possible" => $progress->isSuccessful() + || $programme->canBeCompleted($progress) + || (!$progress->isRelevant() && $programme->getStatus() !== ilStudyProgrammeSettings::STATUS_DRAFT), "changed_by" => ilObjUser::_lookupLogin($progress->getLastChangeBy()), "completion_by" => $completion_by, "progress_id" => $progress->getId(), @@ -207,7 +210,7 @@ function ($node) use ($prg_id, $ass_id, $usr_id, &$plan, $prg) { return $plan; } - protected function getManualStatusSelect(string $progress_id, int $status): string + protected function getManualStatusSelect(string $progress_id, int $status, int $node_lifecycle_status): string { $parent = $this->getParentObject(); $options = [ @@ -222,8 +225,9 @@ protected function getManualStatusSelect(string $progress_id, int $status): stri $options = array_filter( $options, - static function ($o) use ($allowed, $parent): bool { - return in_array($o, $allowed) || $o === $parent::MANUAL_STATUS_NONE; + static function ($o) use ($allowed, $parent, $node_lifecycle_status): bool { + return (in_array($o, $allowed) || $o === $parent::MANUAL_STATUS_NONE) + && ($node_lifecycle_status !== ilStudyProgrammeSettings::STATUS_DRAFT); }, ARRAY_FILTER_USE_KEY ); diff --git a/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeMailTemplateContext.php b/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeMailTemplateContext.php index 668bd503a91a..61c22939c891 100755 --- a/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeMailTemplateContext.php +++ b/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeMailTemplateContext.php @@ -24,19 +24,36 @@ class ilStudyProgrammeMailTemplateContext extends ilMailTemplateContext { public const ID = 'prg_context_manual'; - private const TITLE = "study_programme_title"; - private const DESCRIPTION = "study_programme_description"; - private const TYPE = "study_programme_type"; - private const LINK = "study_programme_link"; - private const ORG_UNIT = "study_programme_org_units"; - private const STATUS = "study_programme_status"; - private const COMPLETION_DATE = "study_programme_completion_date"; - private const COMPLETED_BY = "study_programme_completion_by"; - private const POINTS_REQUIRED = "study_programme_points_required"; - private const POINTS_CURRENT = "study_programme_points_current"; - private const DEADLINE = "study_programme_deadline"; - private const EXPIRE_DATE = "study_programme_expire_date"; - private const VALIDITY = "study_programme_validity"; + //placeholder(ids) + private const TITLE = 'STUDY_PROGRAMME_TITLE'; + private const DESCRIPTION = 'STUDY_PROGRAMME_DESCRIPTION'; + private const TYPE = 'STUDY_PROGRAMME_TYPE'; + private const LINK = 'STUDY_PROGRAMME_LINK'; + private const ORG_UNIT = 'STUDY_PROGRAMME_ORG_UNITS'; + private const STATUS = 'STUDY_PROGRAMME_STATUS'; + private const COMPLETION_DATE = 'STUDY_PROGRAMME_COMPLETION_DATE'; + private const COMPLETED_BY = 'STUDY_PROGRAMME_COMPLETED_BY'; + private const POINTS_REQUIRED = 'STUDY_PROGRAMME_POINTS_REQUIRED'; + private const POINTS_CURRENT = 'STUDY_PROGRAMME_POINTS_CURRENT'; + private const DEADLINE = 'STUDY_PROGRAMME_DEADLINE'; + private const EXPIRE_DATE = 'STUDY_PROGRAMME_EXPIRE_DATE'; + private const VALIDITY = 'STUDY_PROGRAMME_VALIDITY'; + + private const PLACEHOLDER_TRANSLATIONS = [ + self::TITLE => 'prg_title', + self::DESCRIPTION => 'prg_description', + self::TYPE => 'prg_type', + self::LINK => 'prg_link', + self::ORG_UNIT => 'prg_orgus', + self::STATUS => 'prg_status', + self::COMPLETION_DATE => 'prg_completion_date', + self::COMPLETED_BY => 'prg_completion_by', + self::POINTS_REQUIRED => 'prg_points_required', + self::POINTS_CURRENT => 'prg_points_current', + self::DEADLINE => 'prg_deadline', + self::EXPIRE_DATE => 'prg_expiry_date', + self::VALIDITY => 'prg_validity', + ]; private const DATE_FORMAT = 'd.m.Y'; @@ -82,72 +99,12 @@ public function getDescription(): string public function getSpecificPlaceholders(): array { $placeholders = []; - - $placeholders[self::TITLE] = [ - 'placeholder' => 'STUDY_PROGRAMME_TITLE', - 'label' => $this->lng->txt(self::TITLE) - ]; - - $placeholders[self::DESCRIPTION] = [ - 'placeholder' => 'STUDY_PROGRAMME_DESCRIPTION', - 'label' => $this->lng->txt(self::DESCRIPTION) - ]; - - $placeholders[self::TYPE] = [ - 'placeholder' => 'STUDY_PROGRAMME_TYPE', - 'label' => $this->lng->txt(self::TYPE) - ]; - - $placeholders[self::LINK] = [ - 'placeholder' => 'STUDY_PROGRAMME_LINK', - 'label' => $this->lng->txt(self::LINK) - ]; - - $placeholders[self::ORG_UNIT] = [ - 'placeholder' => 'STUDY_PROGRAMME_ORG_UNITS', - 'label' => $this->lng->txt(self::ORG_UNIT) - ]; - - $placeholders[self::STATUS] = [ - 'placeholder' => 'STUDY_PROGRAMME_STATUS', - 'label' => $this->lng->txt(self::STATUS) - ]; - - $placeholders[self::COMPLETION_DATE] = [ - 'placeholder' => 'STUDY_PROGRAMME_COMPLETION_DATE', - 'label' => $this->lng->txt(self::COMPLETION_DATE) - ]; - - $placeholders[self::COMPLETED_BY] = [ - 'placeholder' => 'STUDY_PROGRAMME_COMPLETED_BY', - 'label' => $this->lng->txt(self::COMPLETED_BY) - ]; - - $placeholders[self::POINTS_REQUIRED] = [ - 'placeholder' => 'STUDY_PROGRAMME_POINTS_REQUIRED', - 'label' => $this->lng->txt(self::POINTS_REQUIRED) - ]; - - $placeholders[self::POINTS_CURRENT] = [ - 'placeholder' => 'STUDY_PROGRAMME_POINTS_CURRENT', - 'label' => $this->lng->txt(self::POINTS_CURRENT) - ]; - - $placeholders[self::DEADLINE] = [ - 'placeholder' => 'STUDY_PROGRAMME_DEADLINE', - 'label' => $this->lng->txt(self::DEADLINE) - ]; - - $placeholders[self::EXPIRE_DATE] = [ - 'placeholder' => 'STUDY_PROGRAMME_EXPIRE_DATE', - 'label' => $this->lng->txt(self::EXPIRE_DATE) - ]; - - $placeholders[self::VALIDITY] = [ - 'placeholder' => 'STUDY_PROGRAMME_VALIDITY', - 'label' => $this->lng->txt(self::VALIDITY) - ]; - + foreach (self::PLACEHOLDER_TRANSLATIONS as $id => $label) { + $placeholders[$id] = [ + 'placeholder' => $id, + 'label' => $this->lng->txt($label) + ]; + } return $placeholders; } @@ -159,30 +116,18 @@ public function resolveSpecificPlaceholder( array $context_parameters, ?ilObjUser $recipient = null ): string { + if (is_null($recipient)) { return ''; } - if (!in_array($placeholder_id, [ - self::TITLE, - self::DESCRIPTION, - self::TYPE, - self::LINK, - self::ORG_UNIT, - self::STATUS, - self::COMPLETION_DATE, - self::COMPLETED_BY, - self::POINTS_REQUIRED, - self::POINTS_CURRENT, - self::DEADLINE, - self::EXPIRE_DATE, - self::VALIDITY - ])) { + $placeholder_id = strtoupper($placeholder_id); + if (! array_key_exists($placeholder_id, self::PLACEHOLDER_TRANSLATIONS)) { return ''; } /** @var ilObjStudyProgramme $obj */ - $prg = ilObjectFactory::getInstanceByRefId((int)$context_parameters['ref_id']); + $prg = ilObjectFactory::getInstanceByRefId((int) $context_parameters['ref_id']); $assignments = $prg->getAssignmentsOfSingleProgramForUser($recipient->getId()); $latest = $this->getLatestAssignment($assignments); $latest_successful = $this->getLatestSuccessfulAssignment($assignments); @@ -201,7 +146,7 @@ public function resolveSpecificPlaceholder( } break; case self::LINK: - $string = ilLink::_getLink((int)$context_parameters['ref_id'], 'prg') . ' '; + $string = ilLink::_getLink((int) $context_parameters['ref_id'], 'prg') . ' '; break; case self::ORG_UNIT: $string = ilObjUser::lookupOrgUnitsRepresentation($recipient->getId()); diff --git a/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeMembersTableGUI.php b/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeMembersTableGUI.php index 9f0cb7a2190c..02bf7e2a80d6 100755 --- a/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeMembersTableGUI.php +++ b/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeMembersTableGUI.php @@ -36,7 +36,7 @@ class ilStudyProgrammeMembersTableGUI extends ilTable2GUI public function __construct( int $prg_obj_id, - int $prg_ref_id, + protected int $prg_ref_id, ilObjStudyProgrammeMembersGUI $parent_obj, ilPRGPermissionsHelper $permissions, Data\Factory $data_factory, @@ -52,7 +52,6 @@ public function __construct( $this->prg_obj_id = $prg_obj_id; $this->prg_user_table = $prg_user_table; $this->custom_filter = $custom_filter; - parent::__construct($parent_obj, $parent_cmd, $template_context); $this->data_factory = $data_factory; $this->ui_factory = $ui_factory; @@ -64,6 +63,8 @@ public function __construct( $this->prg = ilObjStudyProgramme::getInstanceByRefId($prg_ref_id); $this->prg_has_lp_children = $parent_obj->getStudyProgramme()->hasLPChildren(); + parent::__construct($parent_obj, $parent_cmd, $template_context); + $this->setEnableTitle(true); $this->setTopCommands(false); $this->setEnableHeader(true); @@ -83,7 +84,7 @@ public function __construct( } $selected = $this->getSelectedColumns(); - foreach ($this->prg_user_table->getColumns($prg_obj_id) as $column) { + foreach ($this->prg_user_table->getColumns($prg_obj_id, false, $this->prg->isCertificateActive()) as $column) { [$col, $lng_var, $optional, $lp, $no_lp] = $column; $show_by_lp = ($this->prg_has_lp_children && $lp) || (!$this->prg_has_lp_children && $no_lp); @@ -238,6 +239,13 @@ protected function fillRow($row): void $this->tpl->setVariable("UDF", $row->getGender()); $this->tpl->parseCurrentBlock(); break; + case 'cert_relevance': + $cert = ''; + if ($row->getCertificateRelevance()) { + $cert = $this->getCertificateLink($row->getUsrId()); + } + $this->tpl->setVariable("CERT_RELEVANCE", $cert); + break; default: $value = $row->getUserInformation()->getUserData($column) ?? ''; $this->tpl->setCurrentBlock('udf'); @@ -247,7 +255,8 @@ protected function fillRow($row): void } $actions = $this->getPossibleActions( $row->isRootProgress(), - $row->getStatusRaw() + $row->getStatusRaw(), + $row->getNodeLifecycleStatus() ); $this->tpl->setVariable( @@ -281,6 +290,9 @@ protected function buildActionDropDown( case ilObjStudyProgrammeMembersGUI::ACTION_ACKNOWLEDGE_COURSES: case ilObjStudyProgrammeMembersGUI::ACTION_CHANGE_DEADLINE: case ilObjStudyProgrammeMembersGUI::ACTION_CHANGE_EXPIRE_DATE: + case ilObjStudyProgrammeMembersGUI::ACTION_UPDATE_CERTIFICATE: + case ilObjStudyProgrammeMembersGUI::ACTION_REMOVE_CERTIFICATE: + if (!$edit_individual_plan) { continue 2; } @@ -315,7 +327,8 @@ protected function getLinkTargetForAction(string $action, string $prgrs_id, int public function getSelectableColumns(): array { $cols = []; - foreach ($this->prg_user_table->getColumns($this->prg_obj_id) as $column) { + + foreach ($this->prg_user_table->getColumns($this->prg_obj_id, false, $this->prg->isCertificateActive()) as $column) { [$col, $lng_var, $optional, $lp, $no_lp] = $column; if ($optional) { $cols[$col] = ["txt" => $lng_var]; @@ -406,7 +419,8 @@ protected function getFilterValues(): array */ protected function getPossibleActions( bool $is_root, - int $status + int $status, + int $node_lifecyle_status ): array { $actions = []; @@ -414,7 +428,6 @@ protected function getPossibleActions( $actions[] = ilObjStudyProgrammeMembersGUI::ACTION_SHOW_INDIVIDUAL_PLAN; $actions[] = ilObjStudyProgrammeMembersGUI::ACTION_REMOVE_USER; $actions[] = ilObjStudyProgrammeMembersGUI::ACTION_UPDATE_FROM_CURRENT_PLAN; - $actions[] = ilObjStudyProgrammeMembersGUI::ACTION_ACKNOWLEDGE_COURSES; $actions[] = ilObjStudyProgrammeMembersGUI::ACTION_CHANGE_DEADLINE; $actions[] = ilObjStudyProgrammeMembersGUI::ACTION_CHANGE_EXPIRE_DATE; } @@ -424,6 +437,7 @@ protected function getPossibleActions( } if ($status == ilPRGProgress::STATUS_IN_PROGRESS) { $actions[] = ilObjStudyProgrammeMembersGUI::ACTION_MARK_ACCREDITED; + $actions[] = ilObjStudyProgrammeMembersGUI::ACTION_ACKNOWLEDGE_COURSES; } if (! $is_root && @@ -432,7 +446,9 @@ protected function getPossibleActions( ) { $actions[] = ilObjStudyProgrammeMembersGUI::ACTION_UNMARK_RELEVANT; } - if ($status == ilPRGProgress::STATUS_NOT_RELEVANT) { + if ($status == ilPRGProgress::STATUS_NOT_RELEVANT + && $node_lifecyle_status == ilStudyProgrammeAssessmentSettings::STATUS_ACTIVE + ) { $actions[] = ilObjStudyProgrammeMembersGUI::ACTION_MARK_RELEVANT; } if ($status == ilPRGProgress::STATUS_COMPLETED || @@ -470,4 +486,16 @@ protected function getCompletionLink(int $target_obj_id, string $title): string } return $link; } + + protected function getCertificateLink(int $usr_id): string + { + $this->ctrl->setParameter($this->parent_obj, 'cert_usr_id', $usr_id); + $cert_url = $this->ctrl->getLinkTarget($this->parent_obj, 'deliverCertificate'); + $this->ctrl->setParameter($this->parent_obj, 'cert_usr_id', null); + $icon = $this->ui_renderer->render( + $this->ui_factory->symbol()->icon()->standard('cert', 'relevant', 'small') + ); + $link = $this->ui_factory->link()->standard($icon, $cert_url); + return $this->ui_renderer->render($link); + } } diff --git a/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeUserTable.php b/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeUserTable.php index 59d9d818a6fa..44df03ff1788 100755 --- a/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeUserTable.php +++ b/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeUserTable.php @@ -64,30 +64,20 @@ class ilStudyProgrammeUserTable 'pgs_id' => 'prgrs_id' ]; - protected ilDBInterface $db; - protected ilExportFieldsInfo $export_fields_info; - protected ilLanguage $lng; - protected ilPRGPermissionsHelper $permissions; protected array $user_ids_viewer_may_read_learning_progress_of; - protected ilPRGAssignmentDBRepository $assignment_repo; public function __construct( - ilDBInterface $db, - ilExportFieldsInfo $export_fields_info, - ilPRGAssignmentDBRepository $assignment_repo, - ilLanguage $lng, - ilPRGPermissionsHelper $permissions + protected ilDBInterface $db, + protected ilExportFieldsInfo $export_fields_info, + protected ilPRGAssignmentDBRepository $assignment_repo, + protected ilLanguage $lng, + protected ilPRGPermissionsHelper $permissions, + protected ilCertificateDownloadValidator $cert_validator ) { - $this->db = $db; - $this->export_fields_info = $export_fields_info; - $this->assignment_repo = $assignment_repo; - $this->lng = $lng; - $this->permissions = $permissions; + $this->lng->loadLanguageModule("prg"); $this->user_ids_viewer_may_read_learning_progress_of = $this->permissions->getUserIdsSusceptibleTo( ilOrgUnitOperation::OP_READ_LEARNING_PROGRESS ); - - $this->lng->loadLanguageModule("prg"); } protected function getUserDataColumns(int $prg_id): array @@ -107,11 +97,15 @@ protected function getPrgColumns(): array $k[1] = $this->lng->txt($k[1]); $cols[$k[0]] = $k; } + return $cols; } - public function getColumns(int $prg_id, bool $add_active_column = false): array - { + public function getColumns( + int $prg_id, + bool $add_active_column = false, + bool $add_cert_column = false + ): array { $prg_cols = $this->getPrgColumns(); $prg_cols_pre = array_slice($prg_cols, 0, 2); $prg_cols_post = array_slice($prg_cols, 2); @@ -125,6 +119,9 @@ public function getColumns(int $prg_id, bool $add_active_column = false): array if ($add_active_column) { $columns["active"] = ["active", $this->lng->txt("active"), true, true, true]; } + if ($add_cert_column) { + $columns["cert_relevance"] = ["cert_relevance", $this->lng->txt("cert_relevance"), true, true, true]; + } return $columns; } @@ -151,7 +148,18 @@ public function fetchData( $valid_user_ids, $custom_filters ); - $rows = array_map(fn($ass) => $this->toRow($ass, $prg_id), $data); + + $root_assignemnts = array_filter( + $data, + fn($ass) => $ass->getRootId() === $prg_id + ); + $root_usr_ids = array_map(fn($r) => $r->getUserid(), $root_assignemnts); + $cert_ass_ids = $this->assignment_repo->getCertificateRelevantAssignmentIds( + $prg_id, + ...$root_usr_ids + ); + + $rows = array_map(fn($ass) => $this->toRow($ass, $prg_id, $cert_ass_ids), $data); $rows = $this->postOrder($rows, $order); if ($limit) { $offset = $offset ?? 0; @@ -163,7 +171,7 @@ public function fetchData( public function fetchSingleUserRootAssignments(int $usr_id): array { $data = $this->assignment_repo->getForUser($usr_id); - $row = array_map(fn($ass) => $this->toRow($ass, $ass->getRootId()), $data); + $row = array_map(fn($ass) => $this->toRow($ass, $ass->getRootId(), []), $data); return $row; } @@ -180,7 +188,7 @@ protected function includeLearningProgress(int $usr_id): bool || in_array($usr_id, $this->user_ids_viewer_may_read_learning_progress_of); } - protected function toRow(ilPRGAssignment $ass, int $node_id): ilStudyProgrammeUserTableRow + protected function toRow(ilPRGAssignment $ass, int $node_id, array $cert_ass_ids): ilStudyProgrammeUserTableRow { $pgs = $ass->getProgressForNode($node_id); $row = new ilStudyProgrammeUserTableRow( @@ -199,6 +207,7 @@ protected function toRow(ilPRGAssignment $ass, int $node_id): ilStudyProgrammeUs $points_reachable = (string) $pgs->getAmountOfPoints(); } + $prg_lifecycle_status = $prg_node->getStatus(); $row = $row ->withUserActiveRaw($ass->getUserInformation()->isActive()) ->withUserActive($this->activeToRepresent($ass->getUserInformation()->isActive())) @@ -238,6 +247,11 @@ protected function toRow(ilPRGAssignment $ass, int $node_id): ilStudyProgrammeUs ) ->withValidity($show_lp ? $this->validToRepresent($pgs) : '') ->withRestartDate($ass->getRestartDate() ? $ass->getRestartDate()->format($this->getUserDateFormat()) : '') + ->withNodeLifecycleStatus($prg_lifecycle_status) + ->withCertificateRelevance( + in_array($ass->getId(), $cert_ass_ids) + && $this->cert_validator->isCertificateDownloadable($ass->getUserId(), $ass->getRootId()) + ) ; return $row; } @@ -377,6 +391,10 @@ protected function postOrder(array $list, \ILIAS\Data\Order $order): array if (is_numeric($a[$aspect])) { return $a[$aspect] <=> $b[$aspect]; } + if (is_bool($a[$aspect])) { + return (int) $a[$aspect] <=> (int) $b[$aspect]; + } + return strcmp($a[$aspect], $b[$aspect]); }); diff --git a/components/ILIAS/StudyProgramme/classes/ilStudyProgrammeUserTableRow.php b/components/ILIAS/StudyProgramme/classes/ilStudyProgrammeUserTableRow.php index 165e8534c69d..a891e358c478 100755 --- a/components/ILIAS/StudyProgramme/classes/ilStudyProgrammeUserTableRow.php +++ b/components/ILIAS/StudyProgramme/classes/ilStudyProgrammeUserTableRow.php @@ -53,6 +53,8 @@ class ilStudyProgrammeUserTableRow protected string $expiry_date; protected string $validity; protected string $restart_date; + protected int $lifecycle_status; + protected bool $cert_relevance; public function __construct( protected int $ass_id, @@ -354,10 +356,37 @@ public function getRestartDate(): string return $this->restart_date; } + public function withNodeLifecycleStatus(int $lifecycle_status): self + { + if (! in_array($lifecycle_status, \ilStudyProgrammeAssessmentSettings::$STATUS)) { + throw new \LogicException('Invalid status: ' . $lifecycle_status); + } + $clone = clone $this; + $clone->lifecycle_status = $lifecycle_status; + return $clone; + } + + public function getNodeLifecycleStatus(): int + { + return $this->lifecycle_status; + } + + public function withCertificateRelevance(bool $cert_relevance): self + { + $clone = clone $this; + $clone->cert_relevance = $cert_relevance; + return $clone; + } + public function getCertificateRelevance(): bool + { + return $this->cert_relevance; + } + + public function toArray(): array { $ret = [ - 'prgrs_id' => (string)$this->getId(), + 'prgrs_id' => (string) $this->getId(), 'name' => $this->getName(), 'active_raw' => $this->isUserActiveRaw(), 'active' => $this->getUserActive(), @@ -378,7 +407,8 @@ public function toArray(): array 'assigned_by' => $this->getAssignmentBy(), 'deadline' => $this->getDeadline(), 'expiry_date' => $this->getExpiryDate(), - 'validity' => $this->getValidity() + 'validity' => $this->getValidity(), + 'cert_relevance' => $this->getCertificateRelevance(), ]; foreach ($this->user_information->getAvailableUserFields() as $user_field) { diff --git a/components/ILIAS/StudyProgramme/classes/model/Assignments/class.ilPRGAssignmentDBRepository.php b/components/ILIAS/StudyProgramme/classes/model/Assignments/class.ilPRGAssignmentDBRepository.php index bc3480a7ab87..04555331991f 100755 --- a/components/ILIAS/StudyProgramme/classes/model/Assignments/class.ilPRGAssignmentDBRepository.php +++ b/components/ILIAS/StudyProgramme/classes/model/Assignments/class.ilPRGAssignmentDBRepository.php @@ -105,7 +105,11 @@ public function createFor( $this->insertAssignmentRowDB($row); $this->progresses = []; - $query = 'SELECT firstname, lastname, login, active, email, gender, title' . PHP_EOL + $user_data_fields = array_filter( + $this->user_data_fields, + static fn($field) => !str_starts_with($field, 'udf_') && $field !== 'org_units' + ); + $query = 'SELECT ' . implode(',', $user_data_fields) . PHP_EOL . 'FROM usr_data WHERE usr_id = ' . $this->db->quote($usr_id, 'integer'); $res = $this->db->query($query); $row = array_merge($row, $this->db->fetchAssoc($res)); @@ -615,9 +619,9 @@ protected function buildUserInformation(array $row): ilPRGUserInformation $udf_data = new ilUserDefinedData((int) $row[self::ASSIGNMENT_FIELD_USR_ID]); $user_data_values = []; foreach ($this->user_data_fields as $field) { - switch($field) { + switch ($field) { case 'active': - $user_data_values[$field] = (bool)$row[$field]; + $user_data_values[$field] = (bool) $row[$field]; break; case 'org_units': //$user_data_values[$field] = ilObjUser::lookupOrgUnitsRepresentation((int) $row[self::ASSIGNMENT_FIELD_USR_ID]); @@ -798,7 +802,7 @@ public function resetRiskyToFailSentFor(ilPRGAssignment $ass): void public function getLatestAssignment(int $root_prg_obj_id, int $usr_id): ?ilPRGAssignment { $assignments = $this->getForUserOnNode($usr_id, $root_prg_obj_id); - if($assignments === []) { + if ($assignments === []) { return null; } usort( @@ -813,18 +817,18 @@ public function getLatestAssignment(int $root_prg_obj_id, int $usr_id): ?ilPRGAs public function getLongestValidAssignment(int $root_prg_obj_id, int $usr_id): ?ilPRGAssignment { $assignments = $this->getForUserOnNode($usr_id, $root_prg_obj_id); - if($assignments === []) { + if ($assignments === []) { return null; } $now = new \DateTimeImmutable(); $valid = array_filter($assignments, fn($ass) => $ass->getProgressTree()->hasValidQualification($now)); - if($valid === []) { + if ($valid === []) { return null; } $unlimited = array_filter($valid, fn($ass) => $ass->getProgressTree()->getValidityOfQualification() === null); - if($unlimited !== []) { + if ($unlimited !== []) { usort( $unlimited, fn(ilPRGAssignment $a, ilPRGAssignment $b) @@ -842,4 +846,30 @@ public function getLongestValidAssignment(int $root_prg_obj_id, int $usr_id): ?i $valid = array_reverse($valid); return current($valid); } + + public function getCertificateRelevantAssignmentIds(int $prg_obj_id, int ...$usr_id): array + { + $query = 'SELECT assignment_id FROM (' . PHP_EOL + . 'SELECT usr_id, assignment_id, ROW_NUMBER() OVER (' . PHP_EOL + . 'PARTITION BY usr_id' . PHP_EOL + . 'ORDER BY' . PHP_EOL + . 'CASE WHEN vq_date IS NULL THEN 1 ELSE 0 END DESC,' . PHP_EOL + . 'vq_date DESC,' . PHP_EOL + . 'completion_date DESC' . PHP_EOL + . ') AS row_numbers' . PHP_EOL + . 'FROM prg_usr_progress' . PHP_EOL + . 'WHERE prg_id = ' . $this->db->quote($prg_obj_id, 'integer') . PHP_EOL + . 'AND ' . $this->db->in('usr_id', $usr_id, false, 'integer') . PHP_EOL + . 'AND ' . $this->db->in('status', [ilPRGProgress::STATUS_COMPLETED, ilPRGProgress::STATUS_ACCREDITED], false, 'integer') . PHP_EOL + . ') ranked ' . PHP_EOL + . 'WHERE row_numbers = 1'; + + $res = $this->db->query($query); + $row = array_map( + fn($r) => $r['assignment_id'], + $this->db->fetchAll($res) + ); + + return $row; + } } diff --git a/components/ILIAS/StudyProgramme/classes/model/Assignments/ilPRGAssignmentActions.php b/components/ILIAS/StudyProgramme/classes/model/Assignments/ilPRGAssignmentActions.php index 519f2ac6fae2..45b76294cce5 100755 --- a/components/ILIAS/StudyProgramme/classes/model/Assignments/ilPRGAssignmentActions.php +++ b/components/ILIAS/StudyProgramme/classes/model/Assignments/ilPRGAssignmentActions.php @@ -84,6 +84,10 @@ protected function recalculateProgressStatus( ->withStatus(ilPRGProgress::STATUS_COMPLETED) ->withCompletion($completing_crs_id, $this->getNow()); + if (!$progress->getValidityOfQualification()) { + $settings = $settings_repo->get($progress->getNodeId())->getValidityOfQualificationSettings(); + $progress = $this->updateProgressValidityFromSettings($settings, $progress); + } $this->notifyProgressSuccess($progress); } @@ -504,9 +508,8 @@ function ($pgs) use ($settings_repo, $triggering_obj_id): ilPRGProgress { $pgs = $pgs->succeed($now, $triggering_obj_id) ->withCurrentAmountOfPoints($pgs->getAmountOfPoints()); $this->notifyScoreChange($pgs); - - $settings = $settings_repo->get($pgs->getNodeId()); - $pgs = $this->updateProgressValidityFromSettings($settings->getValidityOfQualificationSettings(), $pgs); + $settings = $settings_repo->get($pgs->getNodeId())->getValidityOfQualificationSettings(); + $pgs = $this->updateProgressValidityFromSettings($settings, $pgs); } $this->notifyProgressSuccess($pgs); diff --git a/components/ILIAS/StudyProgramme/classes/model/Assignments/ilPRGAssignmentFilter.php b/components/ILIAS/StudyProgramme/classes/model/Assignments/ilPRGAssignmentFilter.php index ee4e2f5c3afa..56f39d067940 100755 --- a/components/ILIAS/StudyProgramme/classes/model/Assignments/ilPRGAssignmentFilter.php +++ b/components/ILIAS/StudyProgramme/classes/model/Assignments/ilPRGAssignmentFilter.php @@ -93,6 +93,11 @@ public function toConditions(): array list($from, $to) = array_values($value); if ($to || $from) { $conditions[] = 'deadline IS NOT NULL'; + $conditions[] = ilPRGAssignmentDBRepository::PROGRESS_FIELD_STATUS + . ' NOT IN (' . + ilPRGProgress::STATUS_COMPLETED . ',' . + ilPRGProgress::STATUS_ACCREDITED + . ')'; } if ($from) { $from = $from->get(IL_CAL_DATE); diff --git a/components/ILIAS/StudyProgramme/classes/model/AutoMemberships/class.ilStudyProgrammeMembershipSourceReaderOrgu.php b/components/ILIAS/StudyProgramme/classes/model/AutoMemberships/class.ilStudyProgrammeMembershipSourceReaderOrgu.php index cf9509d57df2..4ecf66512835 100755 --- a/components/ILIAS/StudyProgramme/classes/model/AutoMemberships/class.ilStudyProgrammeMembershipSourceReaderOrgu.php +++ b/components/ILIAS/StudyProgramme/classes/model/AutoMemberships/class.ilStudyProgrammeMembershipSourceReaderOrgu.php @@ -27,7 +27,7 @@ public function __construct( protected OrgUnitUserAssignmentRepository $orgu_assignment_repo, protected int $src_id, protected bool $search_recursive, - protected int $exclude_id + protected ?int $exclude_id ) { } diff --git a/components/ILIAS/StudyProgramme/classes/model/Types/class.ilStudyProgrammeTypeDBRepository.php b/components/ILIAS/StudyProgramme/classes/model/Types/class.ilStudyProgrammeTypeDBRepository.php index 4f5d71bd726d..d2b08962b6d3 100755 --- a/components/ILIAS/StudyProgramme/classes/model/Types/class.ilStudyProgrammeTypeDBRepository.php +++ b/components/ILIAS/StudyProgramme/classes/model/Types/class.ilStudyProgrammeTypeDBRepository.php @@ -709,9 +709,9 @@ public function getTranslationByTypeIdMemberLang( public function getTable(): DataTable\Data { return $this->ui_factory->table()->data( + $this, $this->lng->txt('prg_subtypes'), $this->getColums(), - $this ); } diff --git a/components/ILIAS/StudyProgramme/module.xml b/components/ILIAS/StudyProgramme/module.xml index c3a5b546c010..273481e38b01 100755 --- a/components/ILIAS/StudyProgramme/module.xml +++ b/components/ILIAS/StudyProgramme/module.xml @@ -30,7 +30,7 @@ - + diff --git a/components/ILIAS/StudyProgramme/templates/default/tpl.members_table_row.html b/components/ILIAS/StudyProgramme/templates/default/tpl.members_table_row.html index f0e81a1a7d9b..65e40e3b3373 100755 --- a/components/ILIAS/StudyProgramme/templates/default/tpl.members_table_row.html +++ b/components/ILIAS/StudyProgramme/templates/default/tpl.members_table_row.html @@ -78,6 +78,12 @@ {VALIDITY} + + + {CERT_RELEVANCE} + + + {ACTIONS} diff --git a/components/ILIAS/StudyProgramme/tests/ilObjStudyProgrammeCertificateTest.php b/components/ILIAS/StudyProgramme/tests/ilObjStudyProgrammeCertificateTest.php deleted file mode 100755 index 17b011b97d6c..000000000000 --- a/components/ILIAS/StudyProgramme/tests/ilObjStudyProgrammeCertificateTest.php +++ /dev/null @@ -1,111 +0,0 @@ -getRelevantProgressFromAssignments($assignments); - } -} - -class ilObjStudyProgrammeCertificateTest extends \PHPUnit\Framework\TestCase -{ - protected $backupGlobals = false; - protected PRGPlaceholderMock $placeholder_mock; - protected array $assignments; - - public function setUp(): void - { - $pgs = (new ilPRGProgress(11, ilPRGProgress::STATUS_COMPLETED)) - ->withCompletion(7, new DateTimeImmutable('2023-12-01')) - ->withValidityOfQualification(new DateTimeImmutable('2023-12-31')); - $ass0 = (new ilPRGAssignment(1, 6)) - ->withProgressTree($pgs); - - $pgs = new ilPRGProgress(12, ilPRGProgress::STATUS_COMPLETED); - $ass1 = (new ilPRGAssignment(1, 6)) - ->withProgressTree($pgs); - - $pgs = (new ilPRGProgress(13, ilPRGProgress::STATUS_IN_PROGRESS)) - ->withCompletion(7, new DateTimeImmutable('2023-12-02')) - ->withValidityOfQualification(new DateTimeImmutable('2023-12-30')); - $ass2 = (new ilPRGAssignment(1, 6)) - ->withProgressTree($pgs); - - $pgs = (new ilPRGProgress(14, ilPRGProgress::STATUS_COMPLETED)) - ->withCompletion(7, new DateTimeImmutable('2023-11-01')); - $ass3 = (new ilPRGAssignment(1, 6)) - ->withProgressTree($pgs); - - $this->assignments = [ - $ass0, $ass1, $ass2, $ass3 - ]; - $this->placeholder_mock = new PRGPlaceholderMock(); - } - - public function testPRGCertificateLatestProgressNoAssignments(): void - { - $assignments = []; - $pgs = $this->placeholder_mock->getRelevantProgress($assignments); - $this->assertNull($pgs); - } - - public function testPRGCertificateLatestProgressUnsuccesfulAssignments(): void - { - $assignments = [$this->assignments[1]]; - $pgs = $this->placeholder_mock->getRelevantProgress($assignments); - $this->assertEquals($this->assignments[1]->getProgressTree(), $pgs); - } - - public function testPRGCertificateLatestProgressWithOnlySuccessfulAssignments(): void - { - $assignments = [ - $this->assignments[0], - $this->assignments[2], - $this->assignments[3] - ]; - $pgs = $this->placeholder_mock->getRelevantProgress($assignments); - $this->assertEquals($this->assignments[3]->getProgressTree(), $pgs); - } - - public function testPRGCertificateLatestProgressWithMixedAssignments(): void - { - $assignments = $this->assignments; - $pgs = $this->placeholder_mock->getRelevantProgress($assignments); - $this->assertEquals($this->assignments[3]->getProgressTree(), $pgs); - } - - public function testPRGCertificateLatestProgressWithOnlyLimitedAssignments(): void - { - $assignments = [ - $this->assignments[0], - $this->assignments[2] - ]; - $pgs = $this->placeholder_mock->getRelevantProgress($assignments); - $this->assertEquals($this->assignments[0]->getProgressTree(), $pgs); - } -} diff --git a/components/ILIAS/StudyProgramme/tests/model/Assignments/ilStudyProgrammeProgressTest.php b/components/ILIAS/StudyProgramme/tests/model/Assignments/ilStudyProgrammeProgressTest.php index c882b323dfcb..04ea67c0f704 100755 --- a/components/ILIAS/StudyProgramme/tests/model/Assignments/ilStudyProgrammeProgressTest.php +++ b/components/ILIAS/StudyProgramme/tests/model/Assignments/ilStudyProgrammeProgressTest.php @@ -256,9 +256,7 @@ public function testPRGProgressInvalidStatus(): void $pgs = (new ilPRGProgress(123))->withStatus(777); } - /** - * @dataProvider ilPRGProgressStatus - */ + #[\PHPUnit\Framework\Attributes\DataProvider('ilPRGProgressStatus')] public function testPRGProgressAllowedTransitionsForInProgress(int $status): void { $pgs = (new ilPRGProgress(123))->withStatus(ilPRGProgress::STATUS_IN_PROGRESS); @@ -275,9 +273,7 @@ public function testPRGProgressAllowedTransitionsForInProgress(int $status): voi } } - /** - * @dataProvider ilPRGProgressStatus - */ + #[\PHPUnit\Framework\Attributes\DataProvider('ilPRGProgressStatus')] public function testPRGProgressAllowedTransitionsForAccredited($status) { $pgs = (new ilPRGProgress(123)) @@ -296,9 +292,7 @@ public function testPRGProgressAllowedTransitionsForAccredited($status) } } - /** - * @dataProvider ilPRGProgressStatus - */ + #[\PHPUnit\Framework\Attributes\DataProvider('ilPRGProgressStatus')] public function testPRGProgressAllowedTransitionsForCompleted($status) { $pgs = (new ilPRGProgress(123)) @@ -315,9 +309,7 @@ public function testPRGProgressAllowedTransitionsForCompleted($status) } } - /** - * @dataProvider ilPRGProgressStatus - */ + #[\PHPUnit\Framework\Attributes\DataProvider('ilPRGProgressStatus')] public function testPRGProgressAllowedTransitionsForFailed($status) { $pgs = (new ilPRGProgress(123)) @@ -335,9 +327,7 @@ public function testPRGProgressAllowedTransitionsForFailed($status) } } - /** - * @dataProvider ilPRGProgressStatus - */ + #[\PHPUnit\Framework\Attributes\DataProvider('ilPRGProgressStatus')] public function testPRGProgressAllowedTransitionsForIrrelevant($status): void { $pgs = (new ilPRGProgress(123))->withStatus(ilPRGProgress::STATUS_NOT_RELEVANT); diff --git a/components/ILIAS/StudyProgramme/tests/model/AutoCategories/ilStudyProgrammeAutoCategoryTest.php b/components/ILIAS/StudyProgramme/tests/model/AutoCategories/ilStudyProgrammeAutoCategoryTest.php index 051e12b40d9d..cb8daf202c42 100755 --- a/components/ILIAS/StudyProgramme/tests/model/AutoCategories/ilStudyProgrammeAutoCategoryTest.php +++ b/components/ILIAS/StudyProgramme/tests/model/AutoCategories/ilStudyProgrammeAutoCategoryTest.php @@ -50,9 +50,7 @@ public function testConstruction(): ilStudyProgrammeAutoCategory return $ac; } - /** - * @depends testConstruction - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstruction')] public function testGetPrgObjId(ilStudyProgrammeAutoCategory $ac): void { $this->assertEquals( @@ -61,9 +59,7 @@ public function testGetPrgObjId(ilStudyProgrammeAutoCategory $ac): void ); } - /** - * @depends testConstruction - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstruction')] public function testGetCategoryRefId(ilStudyProgrammeAutoCategory $ac): void { $this->assertEquals( @@ -72,9 +68,7 @@ public function testGetCategoryRefId(ilStudyProgrammeAutoCategory $ac): void ); } - /** - * @depends testConstruction - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstruction')] public function testGetLastEditorId(ilStudyProgrammeAutoCategory $ac): void { $this->assertEquals( @@ -83,9 +77,7 @@ public function testGetLastEditorId(ilStudyProgrammeAutoCategory $ac): void ); } - /** - * @depends testConstruction - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstruction')] public function testGetLastEdited(ilStudyProgrammeAutoCategory $ac): void { $this->assertEquals( diff --git a/components/ILIAS/StudyProgramme/tests/model/AutoMemberships/ilStudyProgrammeAutoMembershipsSourceTest.php b/components/ILIAS/StudyProgramme/tests/model/AutoMemberships/ilStudyProgrammeAutoMembershipsSourceTest.php index 000b4f815cce..97b8a77307b5 100755 --- a/components/ILIAS/StudyProgramme/tests/model/AutoMemberships/ilStudyProgrammeAutoMembershipsSourceTest.php +++ b/components/ILIAS/StudyProgramme/tests/model/AutoMemberships/ilStudyProgrammeAutoMembershipsSourceTest.php @@ -57,9 +57,7 @@ public function testConstruction(): ilStudyProgrammeAutoMembershipSource return $ams; } - /** - * @depends testConstruction - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstruction')] public function testGetPrgObjId(ilStudyProgrammeAutoMembershipSource $ams): void { $this->assertEquals( @@ -68,9 +66,7 @@ public function testGetPrgObjId(ilStudyProgrammeAutoMembershipSource $ams): void ); } - /** - * @depends testConstruction - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstruction')] public function testGetSourceType(ilStudyProgrammeAutoMembershipSource $ams): void { $this->assertEquals( @@ -78,9 +74,7 @@ public function testGetSourceType(ilStudyProgrammeAutoMembershipSource $ams): vo $ams->getSourceType() ); } - /** - * @depends testConstruction - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstruction')] public function testGetSourceId(ilStudyProgrammeAutoMembershipSource $ams): void { $this->assertEquals( @@ -89,9 +83,7 @@ public function testGetSourceId(ilStudyProgrammeAutoMembershipSource $ams): void ); } - /** - * @depends testConstruction - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstruction')] public function testGetLastEditorId(ilStudyProgrammeAutoMembershipSource $ams): void { $this->assertEquals( @@ -100,9 +92,7 @@ public function testGetLastEditorId(ilStudyProgrammeAutoMembershipSource $ams): ); } - /** - * @depends testConstruction - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstruction')] public function testGetLastEdited(ilStudyProgrammeAutoMembershipSource $ams): void { $this->assertEquals( diff --git a/components/ILIAS/StudyProgramme/tests/model/Settings/ilStudyProgrammeSettingsRepositoryTest.php b/components/ILIAS/StudyProgramme/tests/model/Settings/ilStudyProgrammeSettingsRepositoryTest.php index a8be62c1ec90..83778bc42742 100755 --- a/components/ILIAS/StudyProgramme/tests/model/Settings/ilStudyProgrammeSettingsRepositoryTest.php +++ b/components/ILIAS/StudyProgramme/tests/model/Settings/ilStudyProgrammeSettingsRepositoryTest.php @@ -42,9 +42,7 @@ public function test_init(): ilStudyProgrammeSettingsDBRepository return $repo; } - /** - * @depends test_init - */ + #[\PHPUnit\Framework\Attributes\Depends('test_init')] public function testPRGRepoEditAndUpdate(ilStudyProgrammeSettingsDBRepository $repo) { $this->markTestSkipped('Failed for some unknown reason.'); @@ -125,9 +123,7 @@ public function testPRGRepoEditAndUpdate(ilStudyProgrammeSettingsDBRepository $r $this->assertEquals($set->getPoints(), 10); } - /** - * @depends testPRGRepoEditAndUpdate - */ + #[\PHPUnit\Framework\Attributes\Depends('testPRGRepoEditAndUpdate')] public function testPRGRepoDelete() { $this->expectException(\LogicException::class); diff --git a/components/ILIAS/StudyProgramme/tests/model/Types/ilStudyProgrammeTypeTranslationTest.php b/components/ILIAS/StudyProgramme/tests/model/Types/ilStudyProgrammeTypeTranslationTest.php index 060c31c3ffa8..6f515e062bc8 100755 --- a/components/ILIAS/StudyProgramme/tests/model/Types/ilStudyProgrammeTypeTranslationTest.php +++ b/components/ILIAS/StudyProgramme/tests/model/Types/ilStudyProgrammeTypeTranslationTest.php @@ -27,9 +27,7 @@ public function test_init_and_id() return $tt; } - /** - * @depends test_init_and_id - */ + #[\PHPUnit\Framework\Attributes\Depends('test_init_and_id')] public function test_prg_type_id($tt) { $this->assertEquals(0, $tt->getPrgTypeId()); @@ -38,9 +36,7 @@ public function test_prg_type_id($tt) } - /** - * @depends test_init_and_id - */ + #[\PHPUnit\Framework\Attributes\Depends('test_init_and_id')] public function test_lang($tt) { $this->assertEquals('', $tt->getLang()); @@ -48,9 +44,7 @@ public function test_lang($tt) $this->assertEquals('de', $tt->getLang()); } - /** - * @depends test_init_and_id - */ + #[\PHPUnit\Framework\Attributes\Depends('test_init_and_id')] public function test_member($tt) { $this->assertEquals('', $tt->getMember()); @@ -58,9 +52,7 @@ public function test_member($tt) $this->assertEquals('a_member', $tt->getMember()); } - /** - * @depends test_init_and_id - */ + #[\PHPUnit\Framework\Attributes\Depends('test_init_and_id')] public function test_value($tt) { $this->assertEquals('', $tt->getValue()); diff --git a/components/ILIAS/StudyProgrammeReference/classes/class.ilObjStudyProgrammeReferenceGUI.php b/components/ILIAS/StudyProgrammeReference/classes/class.ilObjStudyProgrammeReferenceGUI.php index 0228c3cc0cac..71724be18051 100755 --- a/components/ILIAS/StudyProgrammeReference/classes/class.ilObjStudyProgrammeReferenceGUI.php +++ b/components/ILIAS/StudyProgrammeReference/classes/class.ilObjStudyProgrammeReferenceGUI.php @@ -32,31 +32,74 @@ public function __construct( $this->target_type = 'prg'; $this->reference_type = 'prgr'; parent::__construct($data, $id, $call_by_reference, $prepare_output); + $this->lng->loadLanguageModule('prg'); } public static function _goto(string $target): void { - $target = (int) $target; - $target_ref_id = ilContainerReference::_lookupTargetRefId(ilObject::_lookupObjId($target)); - ilObjStudyProgrammeGUI::_goto($target_ref_id . "_"); + global $DIC; + $access = $DIC['ilAccess']; + if ($access->checkAccess('write', '', (int) $target)) { + $ilCtrl = $DIC['ilCtrl']; + $ilCtrl->setTargetScript('ilias.php'); + $ilCtrl->setParameterByClass(self::class, "ref_id", $target); + $ilCtrl->redirectByClass([ilRepositoryGUI::class, self::class], "view"); + } else { + $target_ref_id = ilContainerReference::_lookupTargetRefId(ilObject::_lookupObjId((int) $target)); + ilObjStudyProgrammeGUI::_goto((string) $target_ref_id); + } } public function saveObject(): void { $ilAccess = $this->access; + $target_id = $this->cont_request->getTargetId(); + $create = true; - if (!(int) $_REQUEST['target_id']) { + if ($target_id === 0) { + $this->tpl->setOnScreenMessage("failure", $this->lng->txt('select_object_to_link')); $this->createObject(); + $create = false; } - if (!$ilAccess->checkAccess('visible', '', (int) $_REQUEST['target_id'])) { + if ($create && !$ilAccess->checkAccess('visible', '', $target_id)) { $this->tpl->setOnScreenMessage("failure", $this->lng->txt('permission_denied')); $this->createObject(); + $create = false; } - if ($this->tryingToCreateCircularReference((int) $_REQUEST['target_id'], (int) $_REQUEST['ref_id'])) { + if ($create && $this->tryingToCreateCircularReference($target_id, $this->cont_request->getRefId())) { $this->tpl->setOnScreenMessage("failure", $this->lng->txt('prgr_may_not_create_circular_reference')); $this->createObject(); + $create = false; + } + if ($create) { + parent::saveObject(); + } + } + + public function updateObject(): void + { + $form = $this->initForm(); + $form->checkInput(); + $target_id = (int) $form->getInput('target_id'); + $self_id = (int) $this->object->getRefId(); + $container_id = $this->tree->getParentId($self_id); + $do_update = true; + + if (!$this->access->checkAccess('visible', '', $target_id)) { + $this->tpl->setOnScreenMessage("failure", $this->lng->txt('permission_denied'), true); + $this->editObject($form); + $do_update = false; + } + + if ($do_update && $this->tryingToCreateCircularReference($target_id, $container_id)) { + $this->tpl->setOnScreenMessage("failure", $this->lng->txt('prgr_may_not_create_circular_reference')); + $this->editObject($form); + $do_update = false; + } + + if ($do_update) { + parent::updateObject(); } - parent::saveObject(); } public function putObjectInTree(ilObject $obj, $parent_node_id = null): void diff --git a/components/ILIAS/StudyProgrammeReference/classes/class.ilObjStudyProgrammeReferenceListGUI.php b/components/ILIAS/StudyProgrammeReference/classes/class.ilObjStudyProgrammeReferenceListGUI.php index 6f603e049574..f808fd9be3b7 100755 --- a/components/ILIAS/StudyProgrammeReference/classes/class.ilObjStudyProgrammeReferenceListGUI.php +++ b/components/ILIAS/StudyProgrammeReference/classes/class.ilObjStudyProgrammeReferenceListGUI.php @@ -118,6 +118,7 @@ public function initItem( } else { $this->info_screen_enabled = true; } + $this->cut_enabled = $this->getParentType() !== 'prg'; } public function getProperties(): array @@ -185,12 +186,7 @@ public function getListItemHTML( int $a_context = self::CONTEXT_REPOSITORY ): string { $target_obj_id = ilContainerReference::_lookupTargetId($a_obj_id); - - $prg_dic = \ilStudyProgrammeDIC::dic(); - $assignment_repo = $prg_dic['repo.assignment']; - $has_assignments = $assignment_repo->countAllForNodeIsContained($target_obj_id) > 0; - - if ($this->getCheckboxStatus() && $has_assignments) { + if ($this->getCheckboxStatus() && $this->hasAssignments($target_obj_id)) { $this->setAdditionalInformation($this->lng->txt("prg_can_not_manage_in_repo")); $this->enableCheckbox(false); } else { @@ -209,4 +205,18 @@ public function getListItemHTML( $a_asynch_url ); } + + private function hasAssignments(int $obj_id): bool + { + $assignment_repo = ilStudyProgrammeDIC::dic()['repo.assignment']; + $target_obj_id = ilContainerReference::_lookupTargetId($obj_id); + return $assignment_repo->countAllForNodeIsContained($target_obj_id) > 0; + } + + private function getParentType(): string + { + $parent_data = $this->tree->getParentNodeData($this->getCommandId()); + return $parent_data["type"]; + } + } diff --git a/components/ILIAS/Style/Content/Characteristic/class.CharacteristicDBRepo.php b/components/ILIAS/Style/Content/Characteristic/class.CharacteristicDBRepo.php index f15472580f5e..9b05190423d2 100755 --- a/components/ILIAS/Style/Content/Characteristic/class.CharacteristicDBRepo.php +++ b/components/ILIAS/Style/Content/Characteristic/class.CharacteristicDBRepo.php @@ -440,6 +440,24 @@ public function replaceParameter( } } + public function getAllParametersOfCharacteristic( + int $style_id, + string $type, + string $characteristic + ): array { + $set = $this->db->queryF( + "SELECT * FROM style_parameter " . + " WHERE style_id = %s AND class = %s AND type = %s", + ["integer", "string", "string"], + [$style_id, $characteristic, $type] + ); + $data = []; + while ($rec = $this->db->fetchAssoc($set)) { + $data[] = $rec; + } + return $data; + } + public function deleteParameter( int $style_id, string $tag, diff --git a/components/ILIAS/Style/Content/Characteristic/class.CharacteristicManager.php b/components/ILIAS/Style/Content/Characteristic/class.CharacteristicManager.php index ab4e657a3dc6..c372ccc3fb8b 100755 --- a/components/ILIAS/Style/Content/Characteristic/class.CharacteristicManager.php +++ b/components/ILIAS/Style/Content/Characteristic/class.CharacteristicManager.php @@ -351,30 +351,42 @@ public function copyCharacteristicFromSource( $from_style = new ilObjStyleSheet($source_style_id); // todo fix using mq_id - $pars = $from_style->getParametersOfClass($source_style_type, $source_char); - - $colors = array(); - foreach ($pars as $p => $v) { - if (substr($v, 0, 1) == "!") { - $colors[] = substr($v, 1); + foreach (["", ":hover", ":before"] as $char_extension) { + $colors = array(); + //foreach ($pars as $p => $v) { + foreach ($this->repo->getAllParametersOfCharacteristic( + $from_style->getId(), + $source_style_type, + $source_char . $char_extension + ) as $param) { + $p = $param["parameter"]; + $v = $param["value"]; + if (substr($v, 0, 1) == "!") { + $colors[] = substr($v, 1); + } + if ($param["mq_id"] > 0) { + continue; + } + $this->replaceParameter( + ilObjStyleSheet::_determineTag($source_style_type), + $new_char . $char_extension, + $p, + $v, + $source_style_type, + 0, + (bool) $param["custom"] + ); } - $this->replaceParameter( - ilObjStyleSheet::_determineTag($source_style_type), - $new_char, - $p, - $v, - $source_style_type - ); - } - // copy colors - foreach ($colors as $c) { - if (!$this->color_repo->colorExists($this->style_id, $c)) { - $this->color_repo->addColor( - $this->style_id, - $c, - $from_style->getColorCodeForName($c) - ); + // copy colors + foreach ($colors as $c) { + if (!$this->color_repo->colorExists($this->style_id, $c)) { + $this->color_repo->addColor( + $this->style_id, + $c, + $from_style->getColorCodeForName($c) + ); + } } } } diff --git a/components/ILIAS/Style/Content/Characteristic/class.ilStyleCharacteristicGUI.php b/components/ILIAS/Style/Content/Characteristic/class.ilStyleCharacteristicGUI.php index c9feda6cd22b..8a38782c5139 100755 --- a/components/ILIAS/Style/Content/Characteristic/class.ilStyleCharacteristicGUI.php +++ b/components/ILIAS/Style/Content/Characteristic/class.ilStyleCharacteristicGUI.php @@ -388,7 +388,7 @@ public function initCharacteristicForm(): ilPropertyFormGUI $form->setTitle($lng->txt("sty_add_characteristic")); $form->addCommandButton("saveCharacteristic", $lng->txt("save")); - $form->addCommandButton("edit", $lng->txt("cancel")); + $form->addCommandButton("listCharacteristics", $lng->txt("cancel")); $form->setFormAction($ilCtrl->getFormAction($this)); return $form; @@ -1042,6 +1042,20 @@ public function copyCharacteristics(): void $lng = $this->domain_service->lng(); $chars = $this->request->getCharacteristics(); + + // check, if type can be copied (is expanable) + foreach ($chars as $c) { + $type_arr = explode(".", $c); + if (!ilObjStyleSheet::_isExpandable($type_arr[0] ?? "")) { + $this->main_tpl->setOnScreenMessage( + 'failure', + $lng->txt("sty_cannot_be_copied") . ": " . $lng->txt("sty_type_" . $type_arr[0] ?? ""), + true + ); + $ilCtrl->redirect($this, "listCharacteristics"); + } + } + if (count($chars) == 0) { $this->main_tpl->setOnScreenMessage('failure', $lng->txt("no_checkbox"), true); } else { diff --git a/components/ILIAS/Style/Content/Images/class.ImageFileRepo.php b/components/ILIAS/Style/Content/Images/class.ImageFileRepo.php index b12b86d1865c..78e6d548cbfd 100755 --- a/components/ILIAS/Style/Content/Images/class.ImageFileRepo.php +++ b/components/ILIAS/Style/Content/Images/class.ImageFileRepo.php @@ -28,6 +28,8 @@ use ILIAS\FileUpload\Location; use ILIAS\FileUpload\DTO\UploadResult; use ILIAS\Repository\IRSS\IRSSWrapper; +use ILIAS\Filesystem\Stream\ZIPStream; +use ILIAS\Filesystem\Stream\FileStream; class ImageFileRepo { @@ -120,6 +122,28 @@ public function getImages( } } + public function getImageStream( + string $rid, + string $image + ): ZIPStream { + return $this->irss->getStreamOfContainerEntry( + $rid, + "images/" . $image + ); + } + + public function addStream( + string $rid, + string $image, + FileStream $stream + ): void { + $this->irss->addStreamToContainer( + $rid, + $stream, + "images/" . $image + ); + } + // get full web path for relative file path public function getWebPath(string $path): string { @@ -130,10 +154,10 @@ public function getWebPath(string $path): string } // get image data object by filename - public function getByFilename(int $style_id, string $filename): ?Image + public function getByFilename(int $style_id, string $rid, string $filename): ?Image { /** @var Image $i */ - foreach ($this->getImages($style_id) as $i) { + foreach ($this->getImages($style_id, $rid) as $i) { if ($i->getFilename() == $filename) { return $i; } diff --git a/components/ILIAS/Style/Content/Images/class.ImageManager.php b/components/ILIAS/Style/Content/Images/class.ImageManager.php index a6aee981b280..6aef3a6c7fac 100755 --- a/components/ILIAS/Style/Content/Images/class.ImageManager.php +++ b/components/ILIAS/Style/Content/Images/class.ImageManager.php @@ -27,6 +27,8 @@ use ILIAS\FileUpload\DTO\UploadResult; use ILIAS\ResourceStorage\Stakeholder\ResourceStakeholder; use ILIAS\Style\Content\Style\StyleRepo; +use ILIAS\Filesystem\Stream\Stream; +use ILIAS\Filesystem\Util\Convert\Images; /** * Main business logic for content style images @@ -34,6 +36,7 @@ */ class ImageManager { + protected Images $img_convert; protected ImageFileRepo $repo; protected StyleRepo $style_repo; protected Access\StyleAccessManager $access_manager; @@ -52,6 +55,7 @@ public function __construct( $this->access_manager = $access_manager; $this->style_id = $style_id; $this->image_conversion = $DIC->fileConverters()->legacyImages(); + $this->img_convert = $DIC->fileConverters()->images(); } /** @@ -85,7 +89,8 @@ public function getWebPath(Image $image): string // get image data object by filename public function getByFilename(string $filename): Image { - return $this->repo->getByFilename($this->style_id, $filename); + $rid = $this->style_repo->readRid($this->style_id); + return $this->repo->getByFilename($this->style_id, $rid, $filename); } // resize image @@ -95,16 +100,26 @@ public function resizeImage( int $height, bool $constrain_proportions ): void { + $rid = $this->style_repo->readRid($this->style_id); if ($this->filenameExists($filename)) { - $file = $this->getWebPath($this->getByFilename($filename)); - - $this->image_conversion->resizeToFixedSize( - $file, - $file, + // the zip stream is not seekable, which is needed by Imagick + // so we create a seekable stream first + $tempStream = fopen('php://temp', 'w+'); + stream_copy_to_stream($this->repo->getImageStream($rid, $filename)->detach(), $tempStream); + rewind($tempStream); + $stream = new Stream($tempStream); + + $converter = $this->img_convert->resizeToFixedSize( + $stream, $width, - $height, - $constrain_proportions + $height + ); + $this->repo->addStream( + $rid, + $filename, + $converter->getStream() ); + fclose($tempStream); } } diff --git a/components/ILIAS/Style/Content/Style/StyleRepo.php b/components/ILIAS/Style/Content/Style/StyleRepo.php index b81523632c3f..f1f3ac7c8113 100644 --- a/components/ILIAS/Style/Content/Style/StyleRepo.php +++ b/components/ILIAS/Style/Content/Style/StyleRepo.php @@ -145,15 +145,15 @@ public function getPath( "style.css" ); if ($add_random) { - $random = new \ilRandom(); - $rand = $random->int(1, 999999); + $random = new \Random\Randomizer(); + $rand = $random->getInt(1, 999999); $path .= "?dummy=$rand"; } } else { $path = \ilFileUtils::getWebspaceDir("output") . "/css/style_" . $style_id . ".css"; if ($add_random) { - $random = new \ilRandom(); - $rand = $random->int(1, 999999); + $random = new \Random\Randomizer(); + $rand = $random->getInt(1, 999999); $path .= "?dummy=$rand"; } if ($add_token) { diff --git a/components/ILIAS/Style/Content/classes/class.ilContentStyleSettingsGUI.php b/components/ILIAS/Style/Content/classes/class.ilContentStyleSettingsGUI.php index b31742913d21..ed5a1e543b7a 100755 --- a/components/ILIAS/Style/Content/classes/class.ilContentStyleSettingsGUI.php +++ b/components/ILIAS/Style/Content/classes/class.ilContentStyleSettingsGUI.php @@ -191,11 +191,12 @@ public function edit(): void $this->ctrl->getLinkTarget($this, "createStyle") ); + /* $modal = $this->gui->modal($this->lng->txt("import")) ->form($this->getImportForm()); $modal_c = $modal->getTriggerButtonComponents($this->lng->txt("import"), false); $this->toolbar->addComponent($modal_c["button"]); - $rendered_modal = $this->gui->ui()->renderer()->render($modal_c["modal"]); + $rendered_modal = $this->gui->ui()->renderer()->render($modal_c["modal"]);*/ $this->toolbar->addSeparator(); diff --git a/components/ILIAS/Style/Content/classes/class.ilObjStyleSheet.php b/components/ILIAS/Style/Content/classes/class.ilObjStyleSheet.php index bf5e16ab75e7..5ac5044f6498 100755 --- a/components/ILIAS/Style/Content/classes/class.ilObjStyleSheet.php +++ b/components/ILIAS/Style/Content/classes/class.ilObjStyleSheet.php @@ -1574,8 +1574,8 @@ public static function getContentStylePath( global $DIC; $ilSetting = $DIC->settings(); - $random = new \ilRandom(); - $rand = $random->int(1, 999999); + $random = new \Random\Randomizer(); + $rand = $random->getInt(1, 999999); // check global fixed content style $fixed_style = $ilSetting->get("fixed_content_style_id"); diff --git a/components/ILIAS/Style/Content/classes/class.ilObjStyleSheetGUI.php b/components/ILIAS/Style/Content/classes/class.ilObjStyleSheetGUI.php index ef9e44d75ed8..e2c945ca4035 100755 --- a/components/ILIAS/Style/Content/classes/class.ilObjStyleSheetGUI.php +++ b/components/ILIAS/Style/Content/classes/class.ilObjStyleSheetGUI.php @@ -192,9 +192,48 @@ public function createObject(): void $forms[] = $this->getImportForm(); $forms[] = $this->getCloneForm(); - $tpl->setContent($this->getCreationFormsHTML($this->getCreateForm())); + $tpl->setContent($this->getRenderedCreationFormsHTML($forms)); } + protected function getRenderedCreationFormsHTML(array $forms): string + { + // #13168- sanity check + foreach ($forms as $id => $form) { + if (!$form instanceof ilPropertyFormGUI) { + unset($forms[$id]); + } + } + + $acc = new ilAccordionGUI(); + $acc->setBehaviour(ilAccordionGUI::FIRST_OPEN); + $cnt = 1; + foreach ($forms as $form_type => $cf) { + $htpl = new ilTemplate("tpl.creation_acc_head.html", true, true, "components/ILIAS/ILIASObject"); + + // using custom form titles (used for repository plugins) + $form_title = ""; + if (method_exists($this, "getCreationFormTitle")) { + //$form_title = $this->getCreationFormTitle($form_type); + } + if (!$form_title) { + $form_title = $cf->getTitle(); + } + + // move title from form to accordion + $htpl->setVariable("TITLE", $this->lng->txt("option") . " " . $cnt . ": " . $form_title); + $cf->setTitle(''); + $cf->setTitleIcon(''); + $cf->setTableWidth("100%"); + + $acc->addItem($htpl->get(), $cf->getHTML()); + + $cnt++; + } + + return "
" . $acc->getHTML() . "
"; + } + + protected function getCreationFormTitle(): string { return $this->lng->txt("sty_create_new_stylesheet"); diff --git a/components/ILIAS/Style/Content/classes/class.ilStyleImportParser.php b/components/ILIAS/Style/Content/classes/class.ilStyleImportParser.php index ccd5342c1511..b64ed2481f76 100755 --- a/components/ILIAS/Style/Content/classes/class.ilStyleImportParser.php +++ b/components/ILIAS/Style/Content/classes/class.ilStyleImportParser.php @@ -71,9 +71,8 @@ public function __construct( */ public function setHandlers($a_xml_parser): void { - xml_set_object($a_xml_parser, $this); - xml_set_element_handler($a_xml_parser, 'handlerBeginTag', 'handlerEndTag'); - xml_set_character_data_handler($a_xml_parser, 'handlerCharacterData'); + xml_set_element_handler($a_xml_parser, $this->handlerBeginTag(...), $this->handlerEndTag(...)); + xml_set_character_data_handler($a_xml_parser, $this->handlerCharacterData(...)); } /** diff --git a/components/ILIAS/Style/System/classes/Documentation/class.ilKSDocumentationEntryGUI.php b/components/ILIAS/Style/System/classes/Documentation/class.ilKSDocumentationEntryGUI.php index 8c2bfb4f5662..1254b07118e3 100755 --- a/components/ILIAS/Style/System/classes/Documentation/class.ilKSDocumentationEntryGUI.php +++ b/components/ILIAS/Style/System/classes/Documentation/class.ilKSDocumentationEntryGUI.php @@ -117,9 +117,7 @@ public function createUIComponentOfEntry(): Report } $content_part_1 = $this->f->legacy()->content($example); $code = str_replace('" . $geshi->parse_code() . '
'; + $code_html = "
" . htmlspecialchars($code) . '
'; $content_part_2 = $this->f->legacy()->content($code_html); $content = [$content_part_1, $content_part_2]; $sub_panels[] = $this->f->panel()->sub($title, $content); diff --git a/components/ILIAS/Style/System/classes/Overview/class.ilSystemStyleOverviewGUI.php b/components/ILIAS/Style/System/classes/Overview/class.ilSystemStyleOverviewGUI.php index 660fbd11e5fc..b5a6828a9a5d 100755 --- a/components/ILIAS/Style/System/classes/Overview/class.ilSystemStyleOverviewGUI.php +++ b/components/ILIAS/Style/System/classes/Overview/class.ilSystemStyleOverviewGUI.php @@ -144,11 +144,11 @@ public function getAssignmentCreationModal(string $style_name = ""): ?\ILIAS\UI\ } $default = "default:delos"; - if($style_name == "Delos") { + if ($style_name == "Delos") { $default = key($options); } - if(count($options) == 0) { + if (count($options) == 0) { return null; } @@ -243,8 +243,10 @@ public function moveUserStyles(): void } else { ilObjUser::_moveUsersToStyle($old_skin, $old_style, $new_skin, $new_style); } + $message = sprintf($this->lng->txt('sty_move_user_styles_saved'), $old_skin, $new_skin); - $this->message_stack->addMessage(new ilSystemStyleMessage($this->lng->txt('msg_obj_modified'))); + $this->message_stack->addMessage(new ilSystemStyleMessage($message, ilSystemStyleMessage::TYPE_SUCCESS)); + $this->message_stack->sendMessages(); $this->ctrl->redirect($this, 'edit'); } diff --git a/components/ILIAS/Style/System/classes/Overview/class.ilSystemStylesTableGUI.php b/components/ILIAS/Style/System/classes/Overview/class.ilSystemStylesTableGUI.php index 0112cb2ff37c..64e4dd3d37d5 100755 --- a/components/ILIAS/Style/System/classes/Overview/class.ilSystemStylesTableGUI.php +++ b/components/ILIAS/Style/System/classes/Overview/class.ilSystemStylesTableGUI.php @@ -194,7 +194,7 @@ protected function fillRow(array $a_set): void /** @noinspection PhpIfWithCommonPartsInspection */ - if ($this->isManagementEnabled() && $a_set['skin_id'] != 'other') { + if ($this->isManagementEnabled()) { $this->ctrl->setParameterByClass(ilSystemStyleConfigGUI::class, 'skin_id', $a_set['skin_id']); $this->ctrl->setParameterByClass(ilSystemStyleConfigGUI::class, 'style_id', $a_set['style_id']); @@ -207,7 +207,7 @@ protected function fillRow(array $a_set): void $this->ctrl->getLinkTargetByClass('ilsystemstyleconfiggui', 'assignStyle') ); } else { - if ($a_set['skin_id'] != 'default') { + if ($a_set['skin_id'] != 'default' && $a_set['skin_id'] != 'other') { $action_items[] = $this->ui->factory()->link()->standard( $this->lng->txt('export'), $this->ctrl->getLinkTargetByClass(ilSystemStyleOverviewGUI::class, 'export') @@ -223,7 +223,7 @@ protected function fillRow(array $a_set): void $this->modals[] = $assignment_modal; $action_items[] = $this->ui->factory()->button()->shy( - $this->lng->txt('user_assignment'), + $this->lng->txt('sty_change_user_assignment'), "#" )->withOnClick($assignment_modal->getShowSignal()); } diff --git a/components/ILIAS/Style/System/classes/Style/class.ilSkinFactory.php b/components/ILIAS/Style/System/classes/Style/class.ilSkinFactory.php index d1dd58fb7c9c..7f1fa412f911 100755 --- a/components/ILIAS/Style/System/classes/Style/class.ilSkinFactory.php +++ b/components/ILIAS/Style/System/classes/Style/class.ilSkinFactory.php @@ -95,7 +95,7 @@ public function skinStyleContainerFromId( throw new ilSystemStyleException(ilSystemStyleException::NO_SKIN_ID); } - if ($skin_id != 'default') { + if ($skin_id != 'default' && $skin_id != 'other') { return new ilSkinStyleContainer( $this->lng, $this->skinFromXML($this->config->getCustomizingSkinPath() . $skin_id . '/template.xml'), diff --git a/components/ILIAS/Style/System/classes/class.ilImagePathResolver.php b/components/ILIAS/Style/System/classes/class.ilImagePathResolver.php index 34dfd99701a3..68cbe1f21523 100755 --- a/components/ILIAS/Style/System/classes/class.ilImagePathResolver.php +++ b/components/ILIAS/Style/System/classes/class.ilImagePathResolver.php @@ -39,7 +39,7 @@ public function resolveImagePath(string $image_path): string if (is_object($styleDefinition) && $current_skin != "default") { $image_dir = $styleDefinition->getImageDirectory($current_style); - $skin_img = "./Customizing/global/skin/" . + $skin_img = "./Customizing/skin/" . $current_skin . "/" . $current_style . "/" . $image_dir . "/" . $image_path; } diff --git a/components/ILIAS/Style/System/classes/class.ilStyleDefinition.php b/components/ILIAS/Style/System/classes/class.ilStyleDefinition.php index 68e109ee990f..7d5a0cfda1cb 100755 --- a/components/ILIAS/Style/System/classes/class.ilStyleDefinition.php +++ b/components/ILIAS/Style/System/classes/class.ilStyleDefinition.php @@ -282,7 +282,7 @@ public static function getCurrentStyle(): ?string $DIC->refinery()->kindlyTo()->string() ); $target_arr = explode('_', $target); - $ref_id = $target_arr[1]; + $ref_id = $target_arr[1] ?? ''; } // check whether any ref id assigns a new style diff --git a/components/ILIAS/CSV/classes/class.ilCSVReader.php b/components/ILIAS/Survey/CSV/CSVReader.php old mode 100755 new mode 100644 similarity index 92% rename from components/ILIAS/CSV/classes/class.ilCSVReader.php rename to components/ILIAS/Survey/CSV/CSVReader.php index bd730a9e266d..dd15c7b823d2 --- a/components/ILIAS/CSV/classes/class.ilCSVReader.php +++ b/components/ILIAS/Survey/CSV/CSVReader.php @@ -16,7 +16,12 @@ * *********************************************************************/ -class ilCSVReader +namespace ILIAS\Survey; + +use ilUtil; +use RuntimeException; + +class CSVReader { /** * @var resource @@ -26,13 +31,14 @@ class ilCSVReader private array $data = []; private string $separator = ';'; private string $delimiter = '""'; + private string $escape = '\\'; private int $length = 1024; private function parse(): void { $row = 0; - while (($line = fgetcsv($this->file_resource, $this->length, $this->separator)) !== false) { + while (($line = fgetcsv($this->file_resource, $this->length, $this->separator, $this->escape)) !== false) { $line_count = count($line); for ($col = 0; $col < $line_count; $col++) { $this->data[$row][$col] = $this->unquote($line[$col]); diff --git a/components/ILIAS/Survey/Constraints/class.SurveyConstraintsTableGUI.php b/components/ILIAS/Survey/Constraints/class.SurveyConstraintsTableGUI.php index 76dfd1d9565e..d5245161fe4c 100755 --- a/components/ILIAS/Survey/Constraints/class.SurveyConstraintsTableGUI.php +++ b/components/ILIAS/Survey/Constraints/class.SurveyConstraintsTableGUI.php @@ -125,17 +125,11 @@ protected function initItems(ilObjSurvey $a_survey): void $id = $counter; } - $icontype = "question.png"; - if ($data["questionblock_id"] > 0) { - $icontype = "questionblock.png"; - } - $tbl_data[] = array( "counter" => $counter, "id" => $id, "title" => $title, "type" => $type, - "icon" => ilUtil::getImagePath($icontype, "components/ILIAS/Survey"), "content" => $content, "constraints" => $parsed, "conjunction" => $conjunction @@ -171,7 +165,6 @@ protected function fillRow(array $a_set): void $this->tpl->setVariable("COUNTER", $a_set["counter"]); $this->tpl->setVariable("TITLE", $a_set["title"]); $this->tpl->setVariable("TYPE", $a_set["type"]); - $this->tpl->setVariable("ICON_HREF", $a_set["icon"]); $this->tpl->setVariable("ICON_ALT", $a_set["type"]); $this->tpl->setVariable("CONTENT", $a_set["content"]); diff --git a/components/ILIAS/Survey/Evaluation/class.EvaluationManager.php b/components/ILIAS/Survey/Evaluation/class.EvaluationManager.php index eb4c96512889..cc5502734827 100755 --- a/components/ILIAS/Survey/Evaluation/class.EvaluationManager.php +++ b/components/ILIAS/Survey/Evaluation/class.EvaluationManager.php @@ -178,16 +178,19 @@ public function getSelectableRaters(): array return $raters; } - public function getCurrentRater(): string + public function getCurrentRater(bool $fallback_to_first = false): string { $req_rater_id = $this->requested_rater_id; - $valid = array_map(static function ($i): int { - return (int) $i["user_id"]; + $valid = array_map(static function ($i): string { + return (string) $i["user_id"]; }, $this->getSelectableRaters()); if (in_array($req_rater_id, $valid, true)) { return $req_rater_id; } + if ($fallback_to_first && count($this->getSelectableRaters()) > 0) { + return $this->getSelectableRaters()[0]["user_id"]; + } return ""; } diff --git a/components/ILIAS/Survey/Evaluation/class.ilSurveyEvaluationGUI.php b/components/ILIAS/Survey/Evaluation/class.ilSurveyEvaluationGUI.php index c5eeabb67955..5cb847097c75 100755 --- a/components/ILIAS/Survey/Evaluation/class.ilSurveyEvaluationGUI.php +++ b/components/ILIAS/Survey/Evaluation/class.ilSurveyEvaluationGUI.php @@ -346,6 +346,7 @@ public function exportCumulatedResults( // parse answer data in evaluation results $ov_row = 2; + $question_index = 1; foreach ($this->object->getSurveyQuestions() as $qdata) { $q_eval = SurveyQuestion::_instanciateQuestionEvaluation($qdata["question_id"], $finished_ids); $q_res = $q_eval->getResults(); @@ -372,7 +373,7 @@ public function exportCumulatedResults( if ($details) { switch ($this->request->getExportFormat()) { case self::TYPE_XLS: - $this->exportResultsDetailsExcel($excel, $q_eval, $q_res, $do_title, $do_label); + $this->exportResultsDetailsExcel($excel, $q_eval, $q_res, $do_title, $do_label, $question_index++); break; } } @@ -415,7 +416,8 @@ protected function exportResultsDetailsExcel( SurveyQuestionEvaluation $a_eval, $a_results, bool $a_do_title, - bool $a_do_label + bool $a_do_label, + int $question_index ): void { $question_res = $a_results; $matrix = false; @@ -425,7 +427,7 @@ protected function exportResultsDetailsExcel( } $question = $question_res->getQuestion(); - $a_excel->addSheet($question->getTitle()); + $a_excel->addSheet($question_index . "_" . $question->getTitle()); // question "overview" @@ -1257,12 +1259,15 @@ public function competenceEval(): void $ilToolbar->addFormButton($lng->txt("select"), "competenceEval"); $pskills_gui = new ilPersonalSkillsGUI(); - $rater = $this->evaluation_manager->getCurrentRater(); + $rater = $this->evaluation_manager->getCurrentRater( + $this->object->getMode() === ilObjSurvey::MODE_IND_FEEDB + ); if ($rater !== "") { if (strpos($rater ?? "", "u") === 0) { $rater = substr($rater, 1); } - $pskills_gui->setTriggerUserFilter([$rater]); + $pskills_gui->setTriggerUserFilter($rater); + $pskills_gui->setHistoryView(true); } if (strpos($comp_eval_mode ?? "", "gap_") === 0) { diff --git a/components/ILIAS/Survey/Export/class.ilSurveyImporter.php b/components/ILIAS/Survey/Export/class.ilSurveyImporter.php index 257283e348f6..8f2eacddd1c9 100755 --- a/components/ILIAS/Survey/Export/class.ilSurveyImporter.php +++ b/components/ILIAS/Survey/Export/class.ilSurveyImporter.php @@ -80,12 +80,17 @@ public function importXmlRepresentation( ilImportMapping $a_mapping ): void { if ($a_entity === "svy") { - // Container import => test object already created - if (!($new_id = $a_mapping->getMapping('components/ILIAS/Container', 'objs', $a_id))) { // case ii, non container - $new_id = $a_mapping->getMapping("components/ILIAS/Survey", "svy", 0); + + // container import + if ($new_id = $a_mapping->getMapping('components/ILIAS/Container', 'objs', $a_id)) { + $newObj = ilObjectFactory::getInstanceByObjId((int) $new_id, false); + } else { + $newObj = new ilObjSurvey(); // direct survey import + $newObj->setType('svy'); + $newObj->setTitle("dummy"); + $newObj->create(true); } - /** @var ilObjSurvey $newObj */ - $newObj = ilObjectFactory::getInstanceByObjId($new_id, false); + $a_mapping->addMapping('components/ILIAS/Survey', 'svy', $a_id, (string) $newObj->getId()); $this->setSurvey($newObj); diff --git a/components/ILIAS/Survey/Mode/IndividualFeedback/class.UIModifier.php b/components/ILIAS/Survey/Mode/IndividualFeedback/class.UIModifier.php index 8833958de8b3..7d2e73d421b9 100755 --- a/components/ILIAS/Survey/Mode/IndividualFeedback/class.UIModifier.php +++ b/components/ILIAS/Survey/Mode/IndividualFeedback/class.UIModifier.php @@ -178,14 +178,14 @@ public function addRaterSelectionToToolbar( if (count($raters) > 0) { $options = []; - $options["-"] = $lng->txt("svy_all_raters"); + //$options["-"] = $lng->txt("svy_all_raters"); foreach ($raters as $rater) { $options[$rater["user_id"]] = $rater["name"]; } $rat = new \ilSelectInputGUI($lng->txt("svy_rater"), "rater_id"); $rat->setOptions($options); - $rat->setValue($evaluation_manager->getCurrentRater()); + $rat->setValue($evaluation_manager->getCurrentRater(true)); $toolbar->addInputItem($rat, true); $this->gui->button( diff --git a/components/ILIAS/Survey/Mode/interface.ModeProvider.php b/components/ILIAS/Survey/Mode/interface.ModeProvider.php index 5ed1800ae46e..4a6649b8f82b 100755 --- a/components/ILIAS/Survey/Mode/interface.ModeProvider.php +++ b/components/ILIAS/Survey/Mode/interface.ModeProvider.php @@ -30,6 +30,8 @@ interface ModeProvider { public function setInternalService(InternalService $service): void; + public function getTitle(): string; + public function getId(): int; public function getFeatureConfig(): FeatureConfig; diff --git a/components/ILIAS/Survey/Mode/trait.ModeProviderBase.php b/components/ILIAS/Survey/Mode/trait.ModeProviderBase.php index 917bc051f84c..112b20094062 100755 --- a/components/ILIAS/Survey/Mode/trait.ModeProviderBase.php +++ b/components/ILIAS/Survey/Mode/trait.ModeProviderBase.php @@ -53,4 +53,19 @@ public function setInternalService(InternalService $service): void { $this->service = $service; } + + public function getTitle(): string + { + $lng = $this->service->domain()->lng(); + switch ($this->getId()) { + case 1: + return $lng->txt("survey_360_mode"); + case 2: + return $lng->txt("svy_self_ev_mode"); + case 3: + return $lng->txt("svy_ind_feedb_mode"); + default: + return $lng->txt("default"); + } + } } diff --git a/components/ILIAS/Survey/Participants/class.ilSurveyParticipantsGUI.php b/components/ILIAS/Survey/Participants/class.ilSurveyParticipantsGUI.php index 9f37edff05eb..70fb9d0f124f 100755 --- a/components/ILIAS/Survey/Participants/class.ilSurveyParticipantsGUI.php +++ b/components/ILIAS/Survey/Participants/class.ilSurveyParticipantsGUI.php @@ -16,6 +16,7 @@ * *********************************************************************/ +use ILIAS\Survey\CSVReader; use ILIAS\Survey\Participants; /** @@ -737,7 +738,7 @@ protected function importAccessCodesActionObject(): void $existing[$item["code"]] = $item["id"]; } - $reader = new ilCSVReader(); + $reader = new CSVReader(); $reader->open($_FILES['codes']['tmp_name']); foreach ($reader->getCsvAsArray() as $row) { // numeric check of used column due to #26176 @@ -1011,7 +1012,7 @@ public function importExternalRecipientsFromFileObject(): void } if (trim($_FILES['externalmails']['tmp_name'])) { - $reader = new ilCSVReader(); + $reader = new CSVReader(); $reader->open($_FILES['externalmails']['tmp_name']); $data = $reader->getCsvAsArray(); $fields = array_shift($data); diff --git a/components/ILIAS/Survey/PrintView/class.ResultsOverviewPrintViewProviderGUI.php b/components/ILIAS/Survey/PrintView/class.ResultsOverviewPrintViewProviderGUI.php index 5fadff209087..d5ac3a4272d6 100755 --- a/components/ILIAS/Survey/PrintView/class.ResultsOverviewPrintViewProviderGUI.php +++ b/components/ILIAS/Survey/PrintView/class.ResultsOverviewPrintViewProviderGUI.php @@ -78,7 +78,7 @@ public function getSelectionForm(): ?ilPropertyFormGUI public function getOnSubmitCode(): string { - return "event.preventDefault(); if(il.Accordion) { il.Accordion.preparePrint(); } " . + return "event.preventDefault(); document.querySelector('.modal-dialog button.close').click(); if(il.Accordion) { il.Accordion.preparePrint(); } " . "window.setTimeout(() => { window.print();}, 500);"; } } diff --git a/components/ILIAS/Survey/Service/class.InternalDomainService.php b/components/ILIAS/Survey/Service/class.InternalDomainService.php index f2b97cb7cc7f..3534a9807a22 100755 --- a/components/ILIAS/Survey/Service/class.InternalDomainService.php +++ b/components/ILIAS/Survey/Service/class.InternalDomainService.php @@ -27,6 +27,7 @@ use ILIAS\Survey\Editing\EditManager; use ILIAS\Survey\Sequence\SequenceManager; use ILIAS\Survey\Metadata\MetadataManager; +use ILIAS\Survey\Mode\ModeProvider; class InternalDomainService { @@ -64,6 +65,11 @@ public function modeFeatureConfig(int $mode): FeatureConfig return $mode_provider->getFeatureConfig(); } + public function modeProvider(int $mode): ModeProvider + { + return $this->mode_factory->getModeById($mode); + } + public function participants(): Participants\DomainService { return new Participants\DomainService( diff --git a/components/ILIAS/Survey/Settings/class.SettingsFormGUI.php b/components/ILIAS/Survey/Settings/class.SettingsFormGUI.php index 7b9e57ede262..487ff971ea59 100755 --- a/components/ILIAS/Survey/Settings/class.SettingsFormGUI.php +++ b/components/ILIAS/Survey/Settings/class.SettingsFormGUI.php @@ -206,6 +206,14 @@ public function withGeneral( $desc->setValue($survey->getLongDescription()); $form->addItem($desc); + $ne = new \ilNonEditableValueGUI($lng->txt("type")); + $ne->setValue( + $this->domain_service + ->modeProvider($survey->getMode()) + ->getTitle() + ); + $form->addItem($ne); + if ($feature_config->usesAppraisees()) { $self_rate = new \ilCheckboxInputGUI($lng->txt("survey_360_self_raters"), "self_rate"); $self_rate->setInfo($lng->txt("survey_360_self_raters_info")); @@ -253,7 +261,14 @@ public function withActivation( $online->setChecked(!$survey->getOfflineStatus()); $form->addItem($online); - $dur = new \ilDateDurationInputGUI($lng->txt('rep_time_based_availability'), "access_period"); + $time_based_aval = new \ilCheckboxInputGUI($lng->txt('rep_time_based_availability'), 'time_based_avail'); + $time_based_aval->setChecked( + (int) $survey->getActivationStartDate() > 0 || + (int) $survey->getActivationEndDate() > 0 + ); + $form->addItem($time_based_aval); + + $dur = new \ilDateDurationInputGUI($lng->txt('rep_time_period'), "access_period"); $dur->setShowTime(true); $date = $survey->getActivationStartDate(); $dur->setStart($date @@ -263,12 +278,12 @@ public function withActivation( $dur->setEnd($date ? new \ilDateTime($date, IL_CAL_UNIX) : null); - $form->addItem($dur); + $time_based_aval->addSubItem($dur); $visible = new \ilCheckboxInputGUI($lng->txt('rep_activation_limited_visibility'), 'access_visiblity'); $visible->setInfo($lng->txt('svy_activation_limited_visibility_info')); $visible->setChecked($survey->getActivationVisibility()); - $dur->addSubItem($visible); + $time_based_aval->addSubItem($visible); return $form; } @@ -840,7 +855,8 @@ public function saveForm( // activation $period = $form->getItemByPostVar("access_period"); - if ($period->getStart() && $period->getEnd()) { + $tb = $form->getInput("time_based_avail"); + if ($tb && $period->getStart() && $period->getEnd()) { $survey->setActivationLimited(true); $survey->setActivationVisibility((bool) $form->getInput("access_visiblity")); $survey->setActivationStartDate($period->getStart()->get(IL_CAL_UNIX)); diff --git a/components/ILIAS/Survey/classes/class.ilObjSurveyAccess.php b/components/ILIAS/Survey/classes/class.ilObjSurveyAccess.php index cd8c3d7f007f..b24f9a8c9bac 100755 --- a/components/ILIAS/Survey/classes/class.ilObjSurveyAccess.php +++ b/components/ILIAS/Survey/classes/class.ilObjSurveyAccess.php @@ -217,7 +217,6 @@ public static function _hasEvaluationAccess( ): bool { $evaluation_access = self::_lookupEvaluationAccess($a_obj_id); $svy_mode = self::_lookupMode($a_obj_id); - if ($svy_mode === ilObjSurvey::MODE_IND_FEEDB) { $svy = new ilObjSurvey($a_obj_id, false); $svy->read(); @@ -279,7 +278,10 @@ public static function _hasEvaluationAccess( case ilObjSurvey::RESULTS_SELF_EVAL_NONE: return false; default: - return true; + global $DIC; + $run_manager = $DIC->survey()->internal()->domain() + ->execution()->run($svy, $user_id); + return $run_manager->hasFinished(); } // no break @@ -326,13 +328,13 @@ public static function _hasEvaluationAccess( public static function _lookupFinished( int $a_obj_id, int $a_user_id = 0 - ): int { + ): ?int { global $DIC; $ilDB = $DIC->database(); $ilUser = $DIC->user(); - $finished = 0; + $finished = null; if ($a_user_id === 0) { $a_user_id = $ilUser->getId(); } diff --git a/components/ILIAS/Survey/classes/class.ilObjSurveyGUI.php b/components/ILIAS/Survey/classes/class.ilObjSurveyGUI.php index 547c34bffd9b..def0fdefc755 100755 --- a/components/ILIAS/Survey/classes/class.ilObjSurveyGUI.php +++ b/components/ILIAS/Survey/classes/class.ilObjSurveyGUI.php @@ -648,7 +648,7 @@ public function importSurveyObject(): void $newObj = new ilObjSurvey(); $newObj->setType($new_type); $newObj->setTitle("dummy"); - $newObj->create(true); + $newObj->create(); $this->putObjectInTree($newObj); // copy uploaded file to import directory @@ -774,7 +774,7 @@ public static function _goto( ->execution()->runSession(); $sess->setCode(ilObject::_lookupObjId($ref_id), $a_access_code); $ctrl->setParameterByClass("ilObjSurveyGUI", "ref_id", $ref_id); - $ctrl->redirectByClass("ilObjSurveyGUI", "infoScreen"); + $ctrl->redirectByClass("ilObjSurveyGUI", "run"); } // write permission -> info screen @@ -786,16 +786,21 @@ public static function _goto( // read permission, evaluation access and finished run -> evaluation if ($ilAccess->checkAccess("visible", "", $ref_id) || $ilAccess->checkAccess("read", "", $ref_id)) { - $domain_service = $DIC->survey()->internal()->domain(); - $am = $domain_service->access($ref_id, $DIC->user()->getId()); - $survey = new ilObjSurvey($ref_id); - $run_manager = $domain_service->execution()->run($survey, $DIC->user()->getId()); - if ($am->canAccessEvaluation()) { + if ($ilAccess->checkAccess("read", "", $ref_id)) { + $domain_service = $DIC->survey()->internal()->domain(); + $am = $domain_service->access($ref_id, $DIC->user()->getId()); + $survey = new ilObjSurvey($ref_id); + $run_manager = $domain_service->execution()->run($survey, $DIC->user()->getId()); + if ($am->canAccessEvaluation()) { + $ctrl->setParameterByClass("ilObjSurveyGUI", "ref_id", $ref_id); + $ctrl->redirectByClass(["ilObjSurveyGUI", "ilSurveyEvaluationGUI"], "openEvaluation"); + } + $ctrl->setParameterByClass("ilObjSurveyGUI", "ref_id", $ref_id); + $ctrl->redirectByClass("ilObjSurveyGUI", "run"); + } else { $ctrl->setParameterByClass("ilObjSurveyGUI", "ref_id", $ref_id); - $ctrl->redirectByClass(["ilObjSurveyGUI", "ilSurveyEvaluationGUI"], "openEvaluation"); + $ctrl->redirectByClass(["ilObjSurveyGUI", "ilInfoScreenGUI"]); } - $ctrl->setParameterByClass("ilObjSurveyGUI", "ref_id", $ref_id); - $ctrl->redirectByClass("ilObjSurveyGUI", "run"); } elseif ($ilAccess->checkAccess("read", "", ROOT_FOLDER_ID)) { $main_tpl->setOnScreenMessage('failure', sprintf( $lng->txt("msg_no_perm_read_item"), diff --git a/components/ILIAS/Survey/templates/default/tpl.il_svy_svy_content.html b/components/ILIAS/Survey/templates/default/tpl.il_svy_svy_content.html index bb4cc0a54472..114766ab9ca0 100755 --- a/components/ILIAS/Survey/templates/default/tpl.il_svy_svy_content.html +++ b/components/ILIAS/Survey/templates/default/tpl.il_svy_svy_content.html @@ -1,10 +1,10 @@ -
-
{NEW_PBAR}
+
+
{NEW_PBAR}

-
+ - - - diff --git a/components/ILIAS/Test/templates/default/tpl.il_as_tst_pass_overview_participants.html b/components/ILIAS/Test/templates/default/tpl.il_as_tst_pass_overview_participants.html deleted file mode 100755 index 6acd7caa9c35..000000000000 --- a/components/ILIAS/Test/templates/default/tpl.il_as_tst_pass_overview_participants.html +++ /dev/null @@ -1,6 +0,0 @@ -

{TEXT_HEADING}

-{USER_DATA} - -{GRADING_MESSAGE} - -{PASS_OVERVIEW} diff --git a/components/ILIAS/Test/templates/default/tpl.il_as_tst_pass_overview_row.html b/components/ILIAS/Test/templates/default/tpl.il_as_tst_pass_overview_row.html index f24ac175d2d3..18b2e37b9915 100755 --- a/components/ILIAS/Test/templates/default/tpl.il_as_tst_pass_overview_row.html +++ b/components/ILIAS/Test/templates/default/tpl.il_as_tst_pass_overview_row.html @@ -5,7 +5,6 @@ - diff --git a/components/ILIAS/Test/templates/default/tpl.toplist_tbl_rows.html b/components/ILIAS/Test/templates/default/tpl.toplist_tbl_rows.html index 05ab5ccf1b2d..d5df67bdf228 100755 --- a/components/ILIAS/Test/templates/default/tpl.toplist_tbl_rows.html +++ b/components/ILIAS/Test/templates/default/tpl.toplist_tbl_rows.html @@ -14,10 +14,6 @@ - - - - diff --git a/components/ILIAS/Test/templates/default/tpl.workingtime.js b/components/ILIAS/Test/templates/default/tpl.workingtime.js index a48296cfa02e..1f7cae9d8a39 100755 --- a/components/ILIAS/Test/templates/default/tpl.workingtime.js +++ b/components/ILIAS/Test/templates/default/tpl.workingtime.js @@ -62,11 +62,11 @@ test_time_min = (response - test_time_sec) / 60; setWorkingTime(); } else { - $("#listofquestions").attr('action', redirectUrl).submit(); + window.location.href = redirectUrl; } }) .fail( - $("#listofquestions").attr('action', redirectUrl).submit() + window.location.href = redirectUrl ); } diff --git a/components/ILIAS/Test/tests/AccessFileUploadPreviewTest.php b/components/ILIAS/Test/tests/AccessFileUploadPreviewTest.php index ec6863c49093..acea6b3ade12 100644 --- a/components/ILIAS/Test/tests/AccessFileUploadPreviewTest.php +++ b/components/ILIAS/Test/tests/AccessFileUploadPreviewTest.php @@ -63,9 +63,7 @@ public function testFalseWithInvalidId(): void $this->assertFalse($result->value()); } - /** - * @dataProvider types - */ + #[\PHPUnit\Framework\Attributes\DataProvider('types')] public function testWithTypes(?string $type, bool $permitted, ?string $requires_permission): void { $database = $this->getMockBuilder(ilDBInterface::class)->disableOriginalConstructor()->getMock(); diff --git a/components/ILIAS/Test/tests/AccessQuestionImageTest.php b/components/ILIAS/Test/tests/AccessQuestionImageTest.php index 589203f2b2cc..a4ca9edbf9a7 100755 --- a/components/ILIAS/Test/tests/AccessQuestionImageTest.php +++ b/components/ILIAS/Test/tests/AccessQuestionImageTest.php @@ -32,9 +32,7 @@ public function testConstruct(): void $this->assertInstanceOf(AccessQuestionImage::class, new AccessQuestionImage($readable)); } - /** - * @dataProvider invalidPaths - */ + #[\PHPUnit\Framework\Attributes\DataProvider('invalidPaths')] public function testIsPermittedWithInvalidPath(string $path): void { $readable = $this->getMockBuilder(Readable::class)->disableOriginalConstructor()->getMock(); @@ -56,9 +54,7 @@ public static function invalidPaths(): array ]; } - /** - * @dataProvider isPermittedProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('isPermittedProvider')] public function testIsPermittedWithValidPath(bool $is_readable): void { $readable = $this->getMockBuilder(Readable::class)->disableOriginalConstructor()->getMock(); diff --git a/components/ILIAS/Test/tests/Certificate/CertificateSettingsTestFormRepositoryTest.php b/components/ILIAS/Test/tests/Certificate/CertificateSettingsTestFormRepositoryTest.php index b728d6eaac65..daa30df4cc13 100644 --- a/components/ILIAS/Test/tests/Certificate/CertificateSettingsTestFormRepositoryTest.php +++ b/components/ILIAS/Test/tests/Certificate/CertificateSettingsTestFormRepositoryTest.php @@ -83,9 +83,7 @@ public function testCreate(): void $this->assertSame($form_mock, $result); } - /** - * @doesNotPerformAssertions - */ + #[\PHPUnit\Framework\Attributes\DoesNotPerformAssertions] public function testSave(): void { $language = $this->getMockBuilder(\ilLanguage::class) diff --git a/components/ILIAS/Test/tests/Exceptions/ilTestEvaluationExceptionTest.php b/components/ILIAS/Test/tests/Exceptions/ilTestEvaluationExceptionTest.php index 4b0eba3f82e2..51b95d58be69 100644 --- a/components/ILIAS/Test/tests/Exceptions/ilTestEvaluationExceptionTest.php +++ b/components/ILIAS/Test/tests/Exceptions/ilTestEvaluationExceptionTest.php @@ -18,9 +18,7 @@ class ilTestEvaluationExceptionTest extends ilTestBaseTestCase { - /** - * @dataProvider constructDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('constructDataProvider')] public function testConstruct(array $input, array $output): void { $ilTestEvaluationException = isset($input['code']) @@ -46,9 +44,7 @@ public static function constructDataProvider(): array ]; } - /** - * @dataProvider exceptionDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('exceptionDataProvider')] public function testException(array $input, array $output): void { $this->expectException(ilTestEvaluationException::class); diff --git a/components/ILIAS/Test/tests/Exceptions/ilTestExceptionTest.php b/components/ILIAS/Test/tests/Exceptions/ilTestExceptionTest.php index e748731cbb2c..1ce770c848ac 100644 --- a/components/ILIAS/Test/tests/Exceptions/ilTestExceptionTest.php +++ b/components/ILIAS/Test/tests/Exceptions/ilTestExceptionTest.php @@ -18,9 +18,7 @@ class ilTestExceptionTest extends ilTestBaseTestCase { - /** - * @dataProvider constructDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('constructDataProvider')] public function testConstruct(array $input, array $output): void { $ilTestException = isset($input['code']) @@ -46,9 +44,7 @@ public static function constructDataProvider(): array ]; } - /** - * @dataProvider exceptionDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('exceptionDataProvider')] public function testException(array $input, array $output): void { $this->expectException(ilTestException::class); diff --git a/components/ILIAS/Test/tests/Exceptions/ilTestMissingQuestionPoolIdParameterExceptionTest.php b/components/ILIAS/Test/tests/Exceptions/ilTestMissingQuestionPoolIdParameterExceptionTest.php index 249fee582491..d0babe814211 100644 --- a/components/ILIAS/Test/tests/Exceptions/ilTestMissingQuestionPoolIdParameterExceptionTest.php +++ b/components/ILIAS/Test/tests/Exceptions/ilTestMissingQuestionPoolIdParameterExceptionTest.php @@ -18,9 +18,7 @@ class ilTestMissingQuestionPoolIdParameterExceptionTest extends ilTestBaseTestCase { - /** - * @dataProvider constructDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('constructDataProvider')] public function testConstruct(array $input, array $output): void { $ilTestMissingQuestionPoolIdParameterException = isset($input['code']) @@ -46,9 +44,7 @@ public static function constructDataProvider(): array ]; } - /** - * @dataProvider exceptionDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('exceptionDataProvider')] public function testException(array $input, array $output): void { $this->expectException(ilTestMissingQuestionPoolIdParameterException::class); diff --git a/components/ILIAS/Test/tests/Exceptions/ilTestMissingSourcePoolDefinitionParameterExceptionTest.php b/components/ILIAS/Test/tests/Exceptions/ilTestMissingSourcePoolDefinitionParameterExceptionTest.php index 24a5b52c49df..62cd07dd045f 100644 --- a/components/ILIAS/Test/tests/Exceptions/ilTestMissingSourcePoolDefinitionParameterExceptionTest.php +++ b/components/ILIAS/Test/tests/Exceptions/ilTestMissingSourcePoolDefinitionParameterExceptionTest.php @@ -18,9 +18,7 @@ class ilTestMissingSourcePoolDefinitionParameterExceptionTest extends ilTestBaseTestCase { - /** - * @dataProvider constructDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('constructDataProvider')] public function testConstruct(array $input, array $output): void { $ilTestMissingSourcePoolDefinitionParameterException = isset($input['code']) @@ -46,9 +44,7 @@ public static function constructDataProvider(): array ]; } - /** - * @dataProvider exceptionDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('exceptionDataProvider')] public function testException(array $input, array $output): void { $this->expectException(ilTestMissingSourcePoolDefinitionParameterException::class); diff --git a/components/ILIAS/Test/tests/Exceptions/ilTestNoNextRequestableHintExistsExceptionTest.php b/components/ILIAS/Test/tests/Exceptions/ilTestNoNextRequestableHintExistsExceptionTest.php index 65c5df4ab8cc..d1f720defaf3 100644 --- a/components/ILIAS/Test/tests/Exceptions/ilTestNoNextRequestableHintExistsExceptionTest.php +++ b/components/ILIAS/Test/tests/Exceptions/ilTestNoNextRequestableHintExistsExceptionTest.php @@ -18,9 +18,7 @@ class ilTestNoNextRequestableHintExistsExceptionTest extends ilTestBaseTestCase { - /** - * @dataProvider constructDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('constructDataProvider')] public function testConstruct(array $input, array $output): void { $ilTestNoNextRequestableHintExistsException = isset($input['code']) @@ -46,9 +44,7 @@ public static function constructDataProvider(): array ]; } - /** - * @dataProvider exceptionDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('exceptionDataProvider')] public function testException(array $input, array $output): void { $this->expectException(ilTestNoNextRequestableHintExistsException::class); diff --git a/components/ILIAS/Test/tests/Exceptions/ilTestQuestionPoolNotAvailableAsSourcePoolExceptionTest.php b/components/ILIAS/Test/tests/Exceptions/ilTestQuestionPoolNotAvailableAsSourcePoolExceptionTest.php index d96e2b0bea0f..81063cc42c44 100644 --- a/components/ILIAS/Test/tests/Exceptions/ilTestQuestionPoolNotAvailableAsSourcePoolExceptionTest.php +++ b/components/ILIAS/Test/tests/Exceptions/ilTestQuestionPoolNotAvailableAsSourcePoolExceptionTest.php @@ -23,9 +23,7 @@ class ilTestQuestionPoolNotAvailableAsSourcePoolExceptionTest extends ilTestBaseTestCase { - /** - * @dataProvider constructDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('constructDataProvider')] public function testConstruct(array $input, array $output): void { $ilTestQuestionPoolNotAvailableAsSourcePoolException = isset($input['code']) @@ -50,9 +48,7 @@ public static function constructDataProvider(): array ]; } - /** - * @dataProvider exceptionDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('exceptionDataProvider')] public function testException(array $input, array $output): void { $this->expectException(ilTestQuestionPoolNotAvailableAsSourcePoolException::class); diff --git a/components/ILIAS/Test/tests/ExportImport/ExportFactoryTest.php b/components/ILIAS/Test/tests/ExportImport/ExportFactoryTest.php index ea4bef846521..24c8a81ec1d5 100755 --- a/components/ILIAS/Test/tests/ExportImport/ExportFactoryTest.php +++ b/components/ILIAS/Test/tests/ExportImport/ExportFactoryTest.php @@ -45,7 +45,8 @@ protected function setUp(): void $this->createMock(\ilComponentFactory::class), $this->createMock(\ILIAS\FileDelivery\Services::class), $this->createMock(\ilObjUser::class), - $this->createMock(GeneralQuestionPropertiesRepository::class) + $this->createMock(GeneralQuestionPropertiesRepository::class), + $this->createMock(\ILIAS\ResourceStorage\Services::class) ); } diff --git a/components/ILIAS/Test/tests/ExportImport/ExportFixedQuestionSetTest.php b/components/ILIAS/Test/tests/ExportImport/ExportFixedQuestionSetTest.php index ae77c034fe06..cd87aeac6e75 100755 --- a/components/ILIAS/Test/tests/ExportImport/ExportFixedQuestionSetTest.php +++ b/components/ILIAS/Test/tests/ExportImport/ExportFixedQuestionSetTest.php @@ -31,10 +31,12 @@ class ExportFixedQuestionSetTest extends \ilTestBaseTestCase protected function setUp(): void { + global $DIC; parent::setUp(); $this->addGlobal_ilErr(); $this->addGlobal_ilias(); + $this->addGlobal_resourceStorage(); $this->testObj = new ExportFixedQuestionSet( $this->createMock(\ILIAS\Language\Language::class), @@ -45,7 +47,8 @@ protected function setUp(): void $this->createMock(\ilComponentRepository::class), $this->createMock(\ILIAS\TestQuestionPool\Questions\GeneralQuestionPropertiesRepository::class), $this->createMock(\ILIAS\FileDelivery\Services::class), - $this->createMock(\ilObjTest::class) + $this->createMock(\ilObjTest::class), + $DIC['resource_storage'] ); } diff --git a/components/ILIAS/Test/tests/ExportImport/ExportRandomQuestionSetTest.php b/components/ILIAS/Test/tests/ExportImport/ExportRandomQuestionSetTest.php index e960efda2bdf..6c10d8527cf0 100755 --- a/components/ILIAS/Test/tests/ExportImport/ExportRandomQuestionSetTest.php +++ b/components/ILIAS/Test/tests/ExportImport/ExportRandomQuestionSetTest.php @@ -35,6 +35,7 @@ protected function setUp(): void parent::setUp(); $this->addGlobal_ilErr(); + $this->addGlobal_resourceStorage(); $this->testObj = new ExportRandomQuestionSet( $this->createMock(\ILIAS\Language\Language::class), @@ -45,7 +46,8 @@ protected function setUp(): void $DIC['component.repository'], $this->createMock(\ILIAS\TestQuestionPool\Questions\GeneralQuestionPropertiesRepository::class), $this->createMock(\ILIAS\FileDelivery\Services::class), - $this->getTestObjMock() + $this->getTestObjMock(), + $DIC['resource_storage'] ); } diff --git a/components/ILIAS/Test/tests/Presentation/TestScreenGUITest.php b/components/ILIAS/Test/tests/Presentation/TestScreenGUITest.php index e2daad04ce03..7f61afa36fb5 100644 --- a/components/ILIAS/Test/tests/Presentation/TestScreenGUITest.php +++ b/components/ILIAS/Test/tests/Presentation/TestScreenGUITest.php @@ -37,6 +37,7 @@ public function testConstruct(): void $this->createMock(Refinery::class), $this->createMock(\ilCtrl::class), $this->createMock(\ilGlobalTemplateInterface::class), + $this->createMock(\ILIAS\Style\Content\Service::class), $this->createMock(HTTPServices::class), $this->createMock(\ILIAS\Test\Presentation\TabsManager::class), $this->createMock(\ilAccessHandler::class), diff --git a/components/ILIAS/Test/tests/Questions/Presentation/QuestionsTableTest.php b/components/ILIAS/Test/tests/Questions/Presentation/QuestionsTableTest.php index c18294a66fb5..bf376cbd148d 100644 --- a/components/ILIAS/Test/tests/Questions/Presentation/QuestionsTableTest.php +++ b/components/ILIAS/Test/tests/Questions/Presentation/QuestionsTableTest.php @@ -85,6 +85,7 @@ protected function setUp(): void $this->table_gui = new QuestionsTable( $DIC['ui.factory'], + $DIC['refinery'], $DIC['http']->request(), $actions, $DIC['lng'], diff --git a/components/ILIAS/Test/tests/Results/Data/AttemptResultTest.php b/components/ILIAS/Test/tests/Results/Data/AttemptResultTest.php index d1044f2337be..32142265348f 100644 --- a/components/ILIAS/Test/tests/Results/Data/AttemptResultTest.php +++ b/components/ILIAS/Test/tests/Results/Data/AttemptResultTest.php @@ -30,9 +30,7 @@ public static function getSettingsDataProvider(): array ]; } - /** - * @dataProvider getActiveIdDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getActiveIdDataProvider')] public function testGetActiveId(int $IO): void { $ilTestPassResult = new AttemptResult( @@ -52,9 +50,7 @@ public static function getActiveIdDataProvider(): array ]; } - /** - * @dataProvider getPassDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getPassDataProvider')] public function testGetAttempt(int $IO): void { $ilTestPassResult = new AttemptResult( @@ -74,9 +70,7 @@ public static function getPassDataProvider(): array ]; } - /** - * @dataProvider getQuestionResultsDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getQuestionResultsDataProvider')] public function testGetQuestionResults(\Closure $IO): void { $IO = $IO($this); diff --git a/components/ILIAS/Test/tests/Results/Data/QuestionResultTest.php b/components/ILIAS/Test/tests/Results/Data/QuestionResultTest.php index 1679cfe59042..12af93b4b05d 100755 --- a/components/ILIAS/Test/tests/Results/Data/QuestionResultTest.php +++ b/components/ILIAS/Test/tests/Results/Data/QuestionResultTest.php @@ -38,7 +38,6 @@ public function testTestQuestionResultBasicProperties(): void $feedback = 'give it another try', $worked_through = true, $answered = true, - $requested_hints = 2, $recapitulation = 'some recap' ); @@ -53,6 +52,5 @@ public function testTestQuestionResultBasicProperties(): void $this->assertTrue($qr->isWorkedThrough()); $this->assertTrue($qr->isAnswered()); $this->assertEquals($recapitulation, $qr->getContentForRecapitulation()); - $this->assertEquals($requested_hints, $qr->getNumberOfRequestedHints()); } } diff --git a/components/ILIAS/Test/tests/Results/Presentation/SettingsTest.php b/components/ILIAS/Test/tests/Results/Presentation/SettingsTest.php index e4442cf8a3e7..74c18493cac5 100755 --- a/components/ILIAS/Test/tests/Results/Presentation/SettingsTest.php +++ b/components/ILIAS/Test/tests/Results/Presentation/SettingsTest.php @@ -30,7 +30,6 @@ public function testTestResultsSettingsDefaults(): void $trs = new Settings(0); $this->assertFalse($trs->getShowHiddenQuestions()); $this->assertFalse($trs->getShowOptionalQuestions()); - $this->assertFalse($trs->getShowHints()); $this->assertTrue($trs->getShowBestSolution()); $this->assertTrue($trs->getShowFeedback()); $this->assertFalse($trs->getQuestionTextOnly()); @@ -39,19 +38,17 @@ public function testTestResultsSettingsDefaults(): void public function testTestResultsSettingsBasicProps(): void { - $trs = new Settings(0, true, true, true, true, true, true, true); + $trs = new Settings(0, true, true, true, true, true, true); $this->assertTrue($trs->getShowHiddenQuestions()); $this->assertTrue($trs->getShowOptionalQuestions()); - $this->assertTrue($trs->getShowHints()); $this->assertTrue($trs->getShowBestSolution()); $this->assertTrue($trs->getShowFeedback()); $this->assertTrue($trs->getQuestionTextOnly()); $this->assertTrue($trs->getShowRecapitulation()); - $trs = new Settings(0, false, false, false, false, false, false, false); + $trs = new Settings(0, false, false, false, false, false, false); $this->assertFalse($trs->getShowHiddenQuestions()); $this->assertFalse($trs->getShowOptionalQuestions()); - $this->assertFalse($trs->getShowHints()); $this->assertFalse($trs->getShowBestSolution()); $this->assertFalse($trs->getShowFeedback()); $this->assertFalse($trs->getQuestionTextOnly()); diff --git a/components/ILIAS/Test/tests/Scoring/Marks/MarkSchemaTest.php b/components/ILIAS/Test/tests/Scoring/Marks/MarkSchemaTest.php index 128d95586830..97d52c57fc01 100755 --- a/components/ILIAS/Test/tests/Scoring/Marks/MarkSchemaTest.php +++ b/components/ILIAS/Test/tests/Scoring/Marks/MarkSchemaTest.php @@ -200,9 +200,7 @@ public function testCreateSimpleSchemaCustom() ); } - /** - * @doesNotPerformAssertions - */ + #[\PHPUnit\Framework\Attributes\DoesNotPerformAssertions] public function testSaveToDb_regular() { /* diff --git a/components/ILIAS/Test/tests/Scoring/Settings/ScoreSettingsTest.php b/components/ILIAS/Test/tests/Scoring/Settings/ScoreSettingsTest.php index 2b62714cd631..f4778ceaaaac 100755 --- a/components/ILIAS/Test/tests/Scoring/Settings/ScoreSettingsTest.php +++ b/components/ILIAS/Test/tests/Scoring/Settings/ScoreSettingsTest.php @@ -115,8 +115,6 @@ public function testScoreSettingsGamification(): void $this->assertFalse($s->withHighscoreScore(false)->getHighscoreScore()); $this->assertTrue($s->withHighscorePercentage(true)->getHighscorePercentage()); $this->assertFalse($s->withHighscorePercentage(false)->getHighscorePercentage()); - $this->assertTrue($s->withHighscoreHints(true)->getHighscoreHints()); - $this->assertFalse($s->withHighscoreHints(false)->getHighscoreHints()); $this->assertTrue($s->withHighscoreWTime(true)->getHighscoreWTime()); $this->assertFalse($s->withHighscoreWTime(false)->getHighscoreWTime()); $this->assertTrue($s->withHighscoreOwnTable(true)->getHighscoreOwnTable()); @@ -477,7 +475,6 @@ public function testScoreSettingsSectionGamification(): void ['tst_highscore_achieved_ts', 'tst_highscore_achieved_ts_description'], ['tst_highscore_score', 'tst_highscore_score_description'], ['tst_highscore_percentage', 'tst_highscore_percentage_description'], - ['tst_highscore_hints', 'tst_highscore_hints_description'], ['tst_highscore_wtime', 'tst_highscore_wtime_description'] ]; foreach ($opts as $index => $entry) { @@ -550,7 +547,6 @@ public function __construct($s) $this->assertIsBool($t->getHighscoreAchievedTS()); $this->assertIsBool($t->getHighscoreScore()); $this->assertIsBool($t->getHighscorePercentage()); - $this->assertIsBool($t->getHighscoreHints()); $this->assertIsBool($t->getHighscoreWTime()); $this->assertIsBool($t->getHighscoreOwnTable()); $this->assertIsBool($t->getHighscoreTopTable()); diff --git a/components/ILIAS/Test/tests/Scoring/Toplist/DataRetrievalTest.php b/components/ILIAS/Test/tests/Scoring/Toplist/DataRetrievalTest.php index 677c474cd671..53877410f407 100755 --- a/components/ILIAS/Test/tests/Scoring/Toplist/DataRetrievalTest.php +++ b/components/ILIAS/Test/tests/Scoring/Toplist/DataRetrievalTest.php @@ -70,7 +70,6 @@ public function test_getColumns_shouldReturnAllColumns(): void $this->testObjMock->method('getHighscoreAchievedTS')->willReturn(true); $this->testObjMock->method('getHighscoreScore')->willReturn(true); $this->testObjMock->method('getHighscorePercentage')->willReturn(true); - $this->testObjMock->method('getHighscoreHints')->willReturn(true); $this->testObjMock->method('getHighscoreWTime')->willReturn(true); $columns = $this->tableObj->getColumns(); @@ -82,7 +81,6 @@ public function test_getColumns_shouldReturnAllColumns(): void $this->assertArrayHasKey('achieved', $columns); $this->assertArrayHasKey('score', $columns); $this->assertArrayHasKey('percentage', $columns); - $this->assertArrayHasKey('hints', $columns); $this->assertArrayHasKey('workingtime', $columns); } @@ -91,7 +89,6 @@ public function test_getColumns_shouldReturnOnlySelectedColumns(): void $this->testObjMock->method('getHighscoreAchievedTS')->willReturn(true); $this->testObjMock->method('getHighscoreScore')->willReturn(false); $this->testObjMock->method('getHighscorePercentage')->willReturn(true); - $this->testObjMock->method('getHighscoreHints')->willReturn(true); $this->testObjMock->method('getHighscoreWTime')->willReturn(false); $columns = $this->tableObj->getColumns(); @@ -99,7 +96,6 @@ public function test_getColumns_shouldReturnOnlySelectedColumns(): void $this->assertNotEmpty($columns); $this->assertArrayHasKey('achieved', $columns); $this->assertArrayHasKey('percentage', $columns); - $this->assertArrayHasKey('hints', $columns); $this->assertArrayNotHasKey('score', $columns); $this->assertArrayNotHasKey('workingtime', $columns); diff --git a/components/ILIAS/Test/tests/Settings/MainSettings/MainSettingsTest.php b/components/ILIAS/Test/tests/Settings/MainSettings/MainSettingsTest.php index e2b5559d7a36..a7caf975fd4f 100644 --- a/components/ILIAS/Test/tests/Settings/MainSettings/MainSettingsTest.php +++ b/components/ILIAS/Test/tests/Settings/MainSettings/MainSettingsTest.php @@ -31,9 +31,7 @@ class MainSettingsTest extends ilTestBaseTestCase { - /** - * @dataProvider throwOnDifferentTestIdDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('throwOnDifferentTestIdDataProvider')] public function testThrowOnDifferentTestId(int $IO): void { $test_settings = $this->createConfiguredMock(TestSettings::class, ['getTestId' => $IO]); @@ -64,9 +62,7 @@ public static function throwOnDifferentTestIdDataProvider(): array ]; } - /** - * @dataProvider throwOnDifferentTestIdExceptionDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('throwOnDifferentTestIdExceptionDataProvider')] public function testThrowOnDifferentTestIdException(array $input): void { $test_settings = $this->createMock(TestSettings::class); @@ -96,9 +92,7 @@ public static function throwOnDifferentTestIdExceptionDataProvider(): array ]; } - /** - * @dataProvider getAndWithTestIdDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithTestIdDataProvider')] public function testGetAndWithTestId(int $IO): void { $main_settings = (new MainSettings( @@ -152,9 +146,7 @@ public static function getAndWithTestIdDataProvider(): array ]; } - /** - * @dataProvider getAndWithGeneralSettingsDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithGeneralSettingsDataProvider')] public function testGetAndWithGeneralSettings(\Closure $IO): void { $IO = $IO($this); @@ -183,9 +175,7 @@ public static function getAndWithGeneralSettingsDataProvider(): array ]]; } - /** - * @dataProvider getAndWithIntroductionSettingsDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithIntroductionSettingsDataProvider')] public function testGetAndWithIntroductionSettings(\Closure $IO): void { $IO = $IO($this); @@ -214,9 +204,7 @@ public static function getAndWithIntroductionSettingsDataProvider(): array ]]; } - /** - * @dataProvider getAndWithAccessSettingsDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithAccessSettingsDataProvider')] public function testGetAndWithAccessSettings(\Closure $IO): void { $IO = $IO($this); @@ -245,9 +233,7 @@ public static function getAndWithAccessSettingsDataProvider(): array ]]; } - /** - * @dataProvider getAndWithTestBehaviourSettingsDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithTestBehaviourSettingsDataProvider')] public function testGetAndWithTestBehaviourSettings(\Closure $IO): void { $IO = $IO($this); @@ -276,9 +262,7 @@ public static function getAndWithTestBehaviourSettingsDataProvider(): array ]]; } - /** - * @dataProvider getAndWithQuestionBehaviourSettingsDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithQuestionBehaviourSettingsDataProvider')] public function testGetAndWithQuestionBehaviourSettings(\Closure $IO): void { $IO = $IO($this); @@ -307,9 +291,7 @@ public static function getAndWithQuestionBehaviourSettingsDataProvider(): array ]]; } - /** - * @dataProvider getAndWithParticipantFunctionalitySettingsDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithParticipantFunctionalitySettingsDataProvider')] public function testGetAndWithParticipantFunctionalitySettings(\Closure $IO): void { $IO = $IO($this); @@ -338,9 +320,7 @@ public static function getAndWithParticipantFunctionalitySettingsDataProvider(): ]]; } - /** - * @dataProvider getAndWithFinishingSettingsDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithFinishingSettingsDataProvider')] public function testGetAndWithFinishingSettings(\Closure $IO): void { $IO = $IO($this); @@ -369,9 +349,7 @@ public static function getAndWithFinishingSettingsDataProvider(): array ]]; } - /** - * @dataProvider getAndWithAdditionalSettingsDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithAdditionalSettingsDataProvider')] public function testGetAndWithAdditionalSettings(\Closure $IO): void { $IO = $IO($this); diff --git a/components/ILIAS/Test/tests/Settings/MainSettings/SettingsAccessTest.php b/components/ILIAS/Test/tests/Settings/MainSettings/SettingsAccessTest.php index d42c1d862ddf..64167239fc71 100644 --- a/components/ILIAS/Test/tests/Settings/MainSettings/SettingsAccessTest.php +++ b/components/ILIAS/Test/tests/Settings/MainSettings/SettingsAccessTest.php @@ -22,9 +22,7 @@ class SettingsAccessTest extends ilTestBaseTestCase { - /** - * @dataProvider getAndWithStartTimeEnabledDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithStartTimeEnabledDataProvider')] public function testGetAndWithStartTimeEnabled(bool $io): void { $settings_access = (new SettingsAccess(0))->withStartTimeEnabled($io); @@ -41,9 +39,7 @@ public static function getAndWithStartTimeEnabledDataProvider(): array ]; } - /** - * @dataProvider getAndWithStartTimeDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithStartTimeDataProvider')] public function testGetAndWithStartTime(?DateTimeImmutable $io): void { $settings_access = (new SettingsAccess(0))->withStartTime($io); @@ -60,9 +56,7 @@ public static function getAndWithStartTimeDataProvider(): array ]; } - /** - * @dataProvider getAndWithEndTimeEnabledDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithEndTimeEnabledDataProvider')] public function testGetAndWithEndTimeEnabled(bool $io): void { $settings_access = (new SettingsAccess(0))->withEndTimeEnabled($io); @@ -79,9 +73,7 @@ public static function getAndWithEndTimeEnabledDataProvider(): array ]; } - /** - * @dataProvider getAndWithEndTimeDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithEndTimeDataProvider')] public function testGetAndWithEndTime(?DateTimeImmutable $io): void { $settings_access = (new SettingsAccess(0))->withEndTime($io); @@ -98,9 +90,7 @@ public static function getAndWithEndTimeDataProvider(): array ]; } - /** - * @dataProvider getAndWithPasswordEnabledDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithPasswordEnabledDataProvider')] public function testGetAndWithPasswordEnabled(bool $io): void { $settings_access = (new SettingsAccess(0))->withPasswordEnabled($io); @@ -117,9 +107,7 @@ public static function getAndWithPasswordEnabledDataProvider(): array ]; } - /** - * @dataProvider getAndWithPasswordDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithPasswordDataProvider')] public function testGetAndWithPassword(?string $io): void { $settings_access = (new SettingsAccess(0))->withPassword($io); @@ -137,9 +125,7 @@ public static function getAndWithPasswordDataProvider(): array ]; } - /** - * @dataProvider getAndWithFixedParticipantsDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithFixedParticipantsDataProvider')] public function testGetAndWithFixedParticipants(bool $io): void { $settings_access = (new SettingsAccess(0))->withFixedParticipants($io); diff --git a/components/ILIAS/Test/tests/Settings/MainSettings/SettingsAdditionalTest.php b/components/ILIAS/Test/tests/Settings/MainSettings/SettingsAdditionalTest.php index 89f62f07cfd9..ede3e66332af 100644 --- a/components/ILIAS/Test/tests/Settings/MainSettings/SettingsAdditionalTest.php +++ b/components/ILIAS/Test/tests/Settings/MainSettings/SettingsAdditionalTest.php @@ -22,9 +22,7 @@ class SettingsAdditionalTest extends ilTestBaseTestCase { - /** - * @dataProvider getSkillsServiceEnabledDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getSkillsServiceEnabledDataProvider')] public function testGetAndWithSkillsServiceEnabled(bool $io): void { $settings_additional = (new SettingsAdditional(0))->withSkillsServiceEnabled($io); @@ -41,9 +39,7 @@ public static function getSkillsServiceEnabledDataProvider(): array ]; } - /** - * @dataProvider getHideInfoTabDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getHideInfoTabDataProvider')] public function testGetAndWithHideInfoTab(bool $io): void { $settings_additional = (new SettingsAdditional(0))->withHideInfoTab($io); diff --git a/components/ILIAS/Test/tests/Settings/MainSettings/SettingsFinishingTest.php b/components/ILIAS/Test/tests/Settings/MainSettings/SettingsFinishingTest.php index 11120dc750fc..34fcd6ed1347 100644 --- a/components/ILIAS/Test/tests/Settings/MainSettings/SettingsFinishingTest.php +++ b/components/ILIAS/Test/tests/Settings/MainSettings/SettingsFinishingTest.php @@ -22,9 +22,7 @@ class SettingsFinishingTest extends ilTestBaseTestCase { - /** - * @dataProvider getAndWithConcludingRemarksEnabledDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithConcludingRemarksEnabledDataProvider')] public function testGetAndWithShowAnswerOverview(bool $io): void { $settings_finishing = (new SettingsFinishing(0))->withShowAnswerOverview($io); @@ -41,9 +39,7 @@ public static function getAndWithConcludingRemarksEnabledDataProvider(): array ]; } - /** - * @dataProvider getAndWithConcludingRemarksEnabledDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithConcludingRemarksEnabledDataProvider')] public function testGetAndWithConcludingRemarksEnabled(bool $io): void { $settings_finishing = (new SettingsFinishing(0))->withConcludingRemarksEnabled($io); @@ -52,9 +48,7 @@ public function testGetAndWithConcludingRemarksEnabled(bool $io): void $this->assertEquals($io, $settings_finishing->getConcludingRemarksEnabled()); } - /** - * @dataProvider getAndWithConcludingRemarksTextDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithConcludingRemarksTextDataProvider')] public function testGetAndWithConcludingRemarksText(?string $io): void { $settings_finishing = new SettingsFinishing( @@ -76,9 +70,7 @@ public static function getAndWithConcludingRemarksTextDataProvider(): array ]; } - /** - * @dataProvider getAndWithConcludingRemarksPageIdDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithConcludingRemarksPageIdDataProvider')] public function testGetAndWithConcludingRemarksPageId(?int $io): void { $settings_finishing = (new SettingsFinishing(0))->withConcludingRemarksPageId($io); @@ -97,9 +89,7 @@ public static function getAndWithConcludingRemarksPageIdDataProvider(): array ]; } - /** - * @dataProvider getAndWithRedirectionModeDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithRedirectionModeDataProvider')] public function testGetAndWithRedirectionMode(int $io): void { $settings_finishing = (new SettingsFinishing(0))->withRedirectionMode($io); @@ -117,9 +107,7 @@ public static function getAndWithRedirectionModeDataProvider(): array ]; } - /** - * @dataProvider getAndWithRedirectionUrlDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithRedirectionUrlDataProvider')] public function testGetAndWithRedirectionUrl(?string $io): void { $settings_finishing = (new SettingsFinishing(0))->withRedirectionUrl($io); @@ -137,9 +125,7 @@ public static function getAndWithRedirectionUrlDataProvider(): array ]; } - /** - * @dataProvider getAndWithMailNotificationContentTypeDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithMailNotificationContentTypeDataProvider')] public function testGetAndWithMailNotificationContentType(int $io): void { $settings_finishing = (new SettingsFinishing(0))->withMailNotificationContentType($io); @@ -157,9 +143,7 @@ public static function getAndWithMailNotificationContentTypeDataProvider(): arra ]; } - /** - * @dataProvider getAndWithAlwaysSendMailNotificationDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithAlwaysSendMailNotificationDataProvider')] public function testGetAndWithAlwaysSendMailNotification(bool $io): void { $settings_finishing = (new SettingsFinishing(0))->withAlwaysSendMailNotification($io); diff --git a/components/ILIAS/Test/tests/Settings/MainSettings/SettingsGeneralTest.php b/components/ILIAS/Test/tests/Settings/MainSettings/SettingsGeneralTest.php index 5ba6197c9072..4d2c0cc0e0e1 100644 --- a/components/ILIAS/Test/tests/Settings/MainSettings/SettingsGeneralTest.php +++ b/components/ILIAS/Test/tests/Settings/MainSettings/SettingsGeneralTest.php @@ -22,9 +22,7 @@ class SettingsGeneralTest extends ilTestBaseTestCase { - /** - * @dataProvider getAndWithQuestionSetTypeDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithQuestionSetTypeDataProvider')] public function testGetAndWithQuestionSetType(string $io): void { $Settings_general = (new SettingsGeneral(0))->withQuestionSetType($io); @@ -41,9 +39,7 @@ public static function getAndWithQuestionSetTypeDataProvider(): array ]; } - /** - * @dataProvider getAndWithAnonymityDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithAnonymityDataProvider')] public function testGetAndWithAnonymity(bool $io): void { $Settings_general = (new SettingsGeneral(0))->withAnonymity($io); diff --git a/components/ILIAS/Test/tests/Settings/MainSettings/SettingsIntroductionTest.php b/components/ILIAS/Test/tests/Settings/MainSettings/SettingsIntroductionTest.php index 700b977fe80c..a64c652b034f 100644 --- a/components/ILIAS/Test/tests/Settings/MainSettings/SettingsIntroductionTest.php +++ b/components/ILIAS/Test/tests/Settings/MainSettings/SettingsIntroductionTest.php @@ -22,9 +22,7 @@ class SettingsIntroductionTest extends ilTestBaseTestCase { - /** - * @dataProvider getAndWithIntroductionEnabledDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithIntroductionEnabledDataProvider')] public function testGetAndWithIntroductionEnabled(bool $io): void { $settings_introduction = (new SettingsIntroduction(0))->withIntroductionEnabled($io); @@ -41,9 +39,7 @@ public static function getAndWithIntroductionEnabledDataProvider(): array ]; } - /** - * @dataProvider getAndWithIntroductionTextDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithIntroductionTextDataProvider')] public function testGetAndWithIntroductionText(string $io): void { $settings_introduction = (new SettingsIntroduction(0))->withIntroductionText($io); @@ -60,9 +56,7 @@ public static function getAndWithIntroductionTextDataProvider(): array ]; } - /** - * @dataProvider getAndWithIntroductionPageIdDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithIntroductionPageIdDataProvider')] public function testGetAndWithIntroductionPageId(?int $io): void { $settings_introduction = (new SettingsIntroduction(0))->withIntroductionPageId($io); @@ -81,9 +75,7 @@ public static function getAndWithIntroductionPageIdDataProvider(): array ]; } - /** - * @dataProvider getAndWithExamConditionsCheckboxEnabledDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithExamConditionsCheckboxEnabledDataProvider')] public function testGetAndWithExamConditionsCheckboxEnabled(bool $io): void { $settings_introduction = (new SettingsIntroduction(0))->withExamConditionsCheckboxEnabled($io); diff --git a/components/ILIAS/Test/tests/Settings/MainSettings/SettingsParticipantFunctionalityTest.php b/components/ILIAS/Test/tests/Settings/MainSettings/SettingsParticipantFunctionalityTest.php index 9456f7439b1b..10721f7c1eaf 100644 --- a/components/ILIAS/Test/tests/Settings/MainSettings/SettingsParticipantFunctionalityTest.php +++ b/components/ILIAS/Test/tests/Settings/MainSettings/SettingsParticipantFunctionalityTest.php @@ -22,9 +22,7 @@ class SettingsParticipantFunctionalityTest extends ilTestBaseTestCase { - /** - * @dataProvider getAndWithUsePreviousAnswerAllowedDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithUsePreviousAnswerAllowedDataProvider')] public function testGetAndWithUsePreviousAnswerAllowed(bool $io): void { $Settings_participant_functionality = (new SettingsParticipantFunctionality(0))->withUsePreviousAnswerAllowed($io); @@ -41,9 +39,7 @@ public static function getAndWithUsePreviousAnswerAllowedDataProvider(): array ]; } - /** - * @dataProvider getAndWithSuspendTestAllowedDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithSuspendTestAllowedDataProvider')] public function testGetAndWithSuspendTestAllowed(bool $io): void { $Settings_participant_functionality = (new SettingsParticipantFunctionality(0))->withSuspendTestAllowed($io); @@ -60,9 +56,7 @@ public static function getAndWithSuspendTestAllowedDataProvider(): array ]; } - /** - * @dataProvider getAndWithPostponedQuestionsMoveToEndDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithPostponedQuestionsMoveToEndDataProvider')] public function testGetAndWithPostponedQuestionsMoveToEnd(bool $io): void { $Settings_participant_functionality = (new SettingsParticipantFunctionality(0))->withPostponedQuestionsMoveToEnd($io); @@ -79,9 +73,7 @@ public static function getAndWithPostponedQuestionsMoveToEndDataProvider(): arra ]; } - /** - * @dataProvider getAndWithQuestionListEnabledDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithQuestionListEnabledDataProvider')] public function testGetAndWithQuestionListEnabled(bool $io): void { $Settings_participant_functionality = (new SettingsParticipantFunctionality(0))->withQuestionListEnabled($io); @@ -98,9 +90,7 @@ public static function getAndWithQuestionListEnabledDataProvider(): array ]; } - /** - * @dataProvider getAndWithUsrPassOverviewModeDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithUsrPassOverviewModeDataProvider')] public function testGetAndWithUsrPassOverviewMode(int $io): void { $Settings_participant_functionality = (new SettingsParticipantFunctionality(0))->withUsrPassOverviewMode($io); @@ -118,9 +108,7 @@ public static function getAndWithUsrPassOverviewModeDataProvider(): array ]; } - /** - * @dataProvider getAndWithUsrPassOverviewEnabledDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithUsrPassOverviewEnabledDataProvider')] public function testGetAndWithQuestionMarkingEnabled(bool $io): void { $Settings_participant_functionality = (new SettingsParticipantFunctionality(0)); diff --git a/components/ILIAS/Test/tests/Settings/MainSettings/SettingsQuestionBehaviourTest.php b/components/ILIAS/Test/tests/Settings/MainSettings/SettingsQuestionBehaviourTest.php index eefc3527bb7d..bf5023c991b6 100644 --- a/components/ILIAS/Test/tests/Settings/MainSettings/SettingsQuestionBehaviourTest.php +++ b/components/ILIAS/Test/tests/Settings/MainSettings/SettingsQuestionBehaviourTest.php @@ -42,9 +42,7 @@ private function getTestInstance(): SettingsQuestionBehaviour ); } - /** - * @dataProvider getAndWithQuestionTitleOutputModeDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithQuestionTitleOutputModeDataProvider')] public function testGetAndWithQuestionTitleOutputMode(int $io): void { $Settings_question_behaviour = $this->getTestInstance()->withQuestionTitleOutputMode($io); @@ -62,9 +60,7 @@ public static function getAndWithQuestionTitleOutputModeDataProvider(): array ]; } - /** - * @dataProvider getAndWithInstantFeedbackDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithInstantFeedbackDataProvider')] public function testGetAndWithAutosaveEnabled(bool $io): void { $Settings_question_behaviour = $this->getTestInstance()->withAutosaveEnabled($io); @@ -81,9 +77,7 @@ public static function getAndWithInstantFeedbackDataProvider(): array ]; } - /** - * @dataProvider getAndWithAutosaveIntervalDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithAutosaveIntervalDataProvider')] public function testGetAndWithAutosaveInterval(int $io): void { $Settings_question_behaviour = $this->getTestInstance()->withAutosaveInterval($io); @@ -101,9 +95,7 @@ public static function getAndWithAutosaveIntervalDataProvider(): array ]; } - /** - * @dataProvider getAndWithShuffleQuestionsDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithShuffleQuestionsDataProvider')] public function testGetAndWithShuffleQuestions(bool $io): void { $Settings_question_behaviour = $this->getTestInstance()->withShuffleQuestions($io); @@ -120,28 +112,7 @@ public static function getAndWithShuffleQuestionsDataProvider(): array ]; } - /** - * @dataProvider getAndWithQuestionHintsEnabledDataProvider - */ - public function testGetAndWithQuestionHintsEnabled(bool $io): void - { - $Settings_question_behaviour = $this->getTestInstance()->withQuestionHintsEnabled($io); - - $this->assertInstanceOf(SettingsQuestionBehaviour::class, $Settings_question_behaviour); - $this->assertEquals($io, $Settings_question_behaviour->getQuestionHintsEnabled()); - } - - public static function getAndWithQuestionHintsEnabledDataProvider(): array - { - return [ - [false], - [true] - ]; - } - - /** - * @dataProvider getAndWithInstantFeedbackPointsEnabledDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithInstantFeedbackPointsEnabledDataProvider')] public function testGetAndWithInstantFeedbackPointsEnabled(bool $io): void { $Settings_question_behaviour = $this->getTestInstance()->withInstantFeedbackPointsEnabled($io); @@ -158,9 +129,7 @@ public static function getAndWithInstantFeedbackPointsEnabledDataProvider(): arr ]; } - /** - * @dataProvider getAndWithInstantFeedbackGenericEnabledDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithInstantFeedbackGenericEnabledDataProvider')] public function testGetAndWithInstantFeedbackGenericEnabled(bool $io): void { $Settings_question_behaviour = $this->getTestInstance()->withInstantFeedbackGenericEnabled($io); @@ -177,9 +146,7 @@ public static function getAndWithInstantFeedbackGenericEnabledDataProvider(): ar ]; } - /** - * @dataProvider getAndWithInstantFeedbackSpecificEnabledDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithInstantFeedbackSpecificEnabledDataProvider')] public function testGetAndWithInstantFeedbackSpecificEnabled(bool $io): void { $Settings_question_behaviour = $this->getTestInstance()->withInstantFeedbackSpecificEnabled($io); @@ -196,9 +163,7 @@ public static function getAndWithInstantFeedbackSpecificEnabledDataProvider(): a ]; } - /** - * @dataProvider getAndWithInstantFeedbackSolutionEnabledDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithInstantFeedbackSolutionEnabledDataProvider')] public function testGetAndWithInstantFeedbackSolutionEnabled(bool $io): void { $Settings_question_behaviour = $this->getTestInstance()->withInstantFeedbackSolutionEnabled($io); @@ -215,9 +180,7 @@ public static function getAndWithInstantFeedbackSolutionEnabledDataProvider(): a ]; } - /** - * @dataProvider getAndWithForceInstantFeedbackOnNextQuestionDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithForceInstantFeedbackOnNextQuestionDataProvider')] public function testGetAndWithForceInstantFeedbackOnNextQuestion(bool $io): void { $Settings_question_behaviour = $this->getTestInstance()->withForceInstantFeedbackOnNextQuestion($io); @@ -234,9 +197,7 @@ public static function getAndWithForceInstantFeedbackOnNextQuestionDataProvider( ]; } - /** - * @dataProvider getAndWithLockAnswerOnInstantFeedbackEnabledDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithLockAnswerOnInstantFeedbackEnabledDataProvider')] public function testGetAndWithLockAnswerOnInstantFeedbackEnabled(bool $io): void { $Settings_question_behaviour = $this->getTestInstance()->withLockAnswerOnInstantFeedbackEnabled($io); @@ -253,9 +214,7 @@ public static function getAndWithLockAnswerOnInstantFeedbackEnabledDataProvider( ]; } - /** - * @dataProvider getAndWithLockAnswerOnNextQuestionEnabledDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithLockAnswerOnNextQuestionEnabledDataProvider')] public function testGetAndWithLockAnswerOnNextQuestionEnabled(bool $io): void { $Settings_question_behaviour = $this->getTestInstance()->withLockAnswerOnNextQuestionEnabled($io); diff --git a/components/ILIAS/Test/tests/Settings/MainSettings/SettingsTestBehaviourTest.php b/components/ILIAS/Test/tests/Settings/MainSettings/SettingsTestBehaviourTest.php index aa8314cc2020..329a8c91d446 100644 --- a/components/ILIAS/Test/tests/Settings/MainSettings/SettingsTestBehaviourTest.php +++ b/components/ILIAS/Test/tests/Settings/MainSettings/SettingsTestBehaviourTest.php @@ -22,9 +22,7 @@ class SettingsTestBehaviourTest extends ilTestBaseTestCase { - /** - * @dataProvider getAndWithNumberOfTriesDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithNumberOfTriesDataProvider')] public function testGetAndWithNumberOfTries(int $io): void { $Settings_test_behaviour = (new SettingsTestBehaviour(0))->withNumberOfTries($io); @@ -42,9 +40,7 @@ public static function getAndWithNumberOfTriesDataProvider(): array ]; } - /** - * @dataProvider getAndWithBlockAfterPassedEnabledDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithBlockAfterPassedEnabledDataProvider')] public function testGetAndWithBlockAfterPassedEnabled(): void { $Settings_test_behaviour = (new SettingsTestBehaviour(0))->withBlockAfterPassedEnabled(true); @@ -61,9 +57,7 @@ public static function getAndWithBlockAfterPassedEnabledDataProvider(): array ]; } - /** - * @dataProvider getAndWithPassWaitingDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithPassWaitingDataProvider')] public function testGetAndWithPassWaiting(?string $io): void { $Settings_test_behaviour = (new SettingsTestBehaviour(0))->withPassWaiting($io); @@ -80,9 +74,7 @@ public static function getAndWithPassWaitingDataProvider(): array ]; } - /** - * @dataProvider getAndWithProcessingTimeEnabledDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithProcessingTimeEnabledDataProvider')] public function testGetAndWithProcessingTimeEnabled(bool $io): void { $Settings_test_behaviour = (new SettingsTestBehaviour(0))->withProcessingTimeEnabled($io); @@ -99,9 +91,7 @@ public static function getAndWithProcessingTimeEnabledDataProvider(): array ]; } - /** - * @dataProvider getAndWithProcessingTimeDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithProcessingTimeDataProvider')] public function testGetAndWithProcessingTime(?string $io): void { $Settings_test_behaviour = (new SettingsTestBehaviour(0))->withProcessingTime($io); @@ -119,9 +109,7 @@ public static function getAndWithProcessingTimeDataProvider(): array ]; } - /** - * @dataProvider getAndWithResetProcessingTimeDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithResetProcessingTimeDataProvider')] public function testGetAndWithResetProcessingTime(bool $io): void { $Settings_test_behaviour = (new SettingsTestBehaviour(0))->withResetProcessingTime($io); @@ -138,9 +126,7 @@ public static function getAndWithResetProcessingTimeDataProvider(): array ]; } - /** - * @dataProvider getAndWithKioskModeDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithKioskModeDataProvider')] public function testGetAndWithKioskMode(int $io): void { $Settings_test_behaviour = (new SettingsTestBehaviour(0))->withKioskMode($io); @@ -159,9 +145,7 @@ public static function getAndWithKioskModeDataProvider(): array } // ExamIdInTestPassEnabled - /** - * @dataProvider getAndWithExamIdInTestPassEnabledDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithExamIdInTestPassEnabledDataProvider')] public function testGetAndWithExamIdInTestPassEnabled(bool $io): void { $Settings_test_behaviour = (new SettingsTestBehaviour(0))->withExamIdInTestAttemptEnabled($io); diff --git a/components/ILIAS/Test/tests/Settings/ScoreReporting/SettingsGamificationTest.php b/components/ILIAS/Test/tests/Settings/ScoreReporting/SettingsGamificationTest.php index 4dff915f1992..1ef978512753 100644 --- a/components/ILIAS/Test/tests/Settings/ScoreReporting/SettingsGamificationTest.php +++ b/components/ILIAS/Test/tests/Settings/ScoreReporting/SettingsGamificationTest.php @@ -29,9 +29,7 @@ public function testConstruct(): void $this->assertInstanceOf(SettingsGamification::class, $gamificationTest); } - /** - * @dataProvider getAndWithHighscoreEnabledDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithHighscoreEnabledDataProvider')] public function testGetAndWithHighscoreEnabled(bool $IO): void { $gamificationTest = new SettingsGamification(0); @@ -47,9 +45,7 @@ public static function getAndWithHighscoreEnabledDataProvider(): array ]; } - /** - * @dataProvider getAndWithHighscoreOwnTableDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithHighscoreOwnTableDataProvider')] public function testGetAndWithHighscoreOwnTable(bool $IO): void { $gamificationTest = new SettingsGamification(0); @@ -65,9 +61,7 @@ public static function getAndWithHighscoreOwnTableDataProvider(): array ]; } - /** - * @dataProvider getAndWithHighscoreTopTableDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithHighscoreTopTableDataProvider')] public function testGetAndWithHighscoreTopTable(bool $IO): void { $gamificationTest = new SettingsGamification(0); @@ -83,9 +77,7 @@ public static function getAndWithHighscoreTopTableDataProvider(): array ]; } - /** - * @dataProvider getAndWithHighscoreTopNumDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithHighscoreTopNumDataProvider')] public function testGetAndWithHighscoreTopNum(int $IO): void { $gamificationTest = new SettingsGamification(0); @@ -102,9 +94,7 @@ public static function getAndWithHighscoreTopNumDataProvider(): array ]; } - /** - * @dataProvider getAndWithHighscoreAnonDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithHighscoreAnonDataProvider')] public function testGetAndWithHighscoreAnon(bool $IO): void { $gamificationTest = new SettingsGamification(0); @@ -120,9 +110,7 @@ public static function getAndWithHighscoreAnonDataProvider(): array ]; } - /** - * @dataProvider getAndWithHighscoreAchievedTSDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithHighscoreAchievedTSDataProvider')] public function testGetAndWithHighscoreAchievedTS(bool $IO): void { $gamificationTest = new SettingsGamification(0); @@ -138,9 +126,7 @@ public static function getAndWithHighscoreAchievedTSDataProvider(): array ]; } - /** - * @dataProvider getAndWithHighscoreScoreDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithHighscoreScoreDataProvider')] public function testGetAndWithHighscoreScore(bool $IO): void { $gamificationTest = new SettingsGamification(0); @@ -156,9 +142,7 @@ public static function getAndWithHighscoreScoreDataProvider(): array ]; } - /** - * @dataProvider getAndWithHighscorePercentageDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithHighscorePercentageDataProvider')] public function testGetAndWithHighscorePercentage(bool $IO): void { $gamificationTest = new SettingsGamification(0); @@ -174,27 +158,7 @@ public static function getAndWithHighscorePercentageDataProvider(): array ]; } - /** - * @dataProvider getAndWithHighscoreHintsDataProvider - */ - public function testGetAndWithHighscoreHints(bool $IO): void - { - $gamificationTest = new SettingsGamification(0); - $gamificationTest = $gamificationTest->withHighscoreHints($IO); - $this->assertEquals($IO, $gamificationTest->getHighscoreHints()); - } - - public static function getAndWithHighscoreHintsDataProvider(): array - { - return [ - [true], - [false] - ]; - } - - /** - * @dataProvider getAndWithHighscoreWTimeDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithHighscoreWTimeDataProvider')] public function testGetAndWithHighscoreWTime(bool $IO): void { $gamificationTest = new SettingsGamification(0); diff --git a/components/ILIAS/Test/tests/Settings/ScoreReporting/SettingsResultDetailsTest.php b/components/ILIAS/Test/tests/Settings/ScoreReporting/SettingsResultDetailsTest.php index 638ab44ef976..cf229e17699f 100644 --- a/components/ILIAS/Test/tests/Settings/ScoreReporting/SettingsResultDetailsTest.php +++ b/components/ILIAS/Test/tests/Settings/ScoreReporting/SettingsResultDetailsTest.php @@ -29,9 +29,7 @@ public function testConstruct(): void $this->assertInstanceOf(SettingsResultDetails::class, $settingsResultDetails); } - /** - * @dataProvider getAndWithResultsPresentationDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithResultsPresentationDataProvider')] public function testGetAndWithResultsPresentation(int $IO): void { $settingsResultDetails = new SettingsResultDetails(0); @@ -48,9 +46,7 @@ public static function getAndWithResultsPresentationDataProvider(): array ]; } - /** - * @dataProvider getAndShowExamIdInTestResultsDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndShowExamIdInTestResultsDataProvider')] public function testGetAndShowExamIdInTestResults(bool $IO): void { $settingsResultDetails = new SettingsResultDetails(0); @@ -66,9 +62,7 @@ public static function getAndShowExamIdInTestResultsDataProvider(): array ]; } - /** - * @dataProvider getAndWithShowPassDetailsDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithShowPassDetailsDataProvider')] public function testGetAndWithShowPassDetails(bool $IO): void { $settingsResultDetails = new SettingsResultDetails(0); @@ -84,9 +78,7 @@ public static function getAndWithShowPassDetailsDataProvider(): array ]; } - /** - * @dataProvider getAndWithShowSolutionPrintviewDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithShowSolutionPrintviewDataProvider')] public function testGetAndWithShowSolutionPrintview(bool $IO): void { $settingsResultDetails = new SettingsResultDetails(0); @@ -102,9 +94,7 @@ public static function getAndWithShowSolutionPrintviewDataProvider(): array ]; } - /** - * @dataProvider getAndWithShowSolutionFeedbackDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithShowSolutionFeedbackDataProvider')] public function testGetShowSolutionFeedback(bool $IO): void { $settingsResultDetails = new SettingsResultDetails(0); @@ -120,9 +110,7 @@ public static function getAndWithShowSolutionFeedbackDataProvider(): array ]; } - /** - * @dataProvider getAndWithShowSolutionAnswersOnlyDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithShowSolutionAnswersOnlyDataProvider')] public function testGetAndWithShowSolutionAnswersOnly(bool $IO): void { $settingsResultDetails = new SettingsResultDetails(0); @@ -138,9 +126,7 @@ public static function getAndWithShowSolutionAnswersOnlyDataProvider(): array ]; } - /** - * @dataProvider getAndWithShowSolutionSignatureDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithShowSolutionSignatureDataProvider')] public function testGetAndWithShowSolutionSignature(bool $IO): void { $settingsResultDetails = new SettingsResultDetails(0); @@ -156,9 +142,7 @@ public static function getAndWithShowSolutionSignatureDataProvider(): array ]; } - /** - * @dataProvider getAndWithShowSolutionSuggestedDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithShowSolutionSuggestedDataProvider')] public function testGetAndWithShowSolutionSuggested(bool $IO): void { $settingsResultDetails = new SettingsResultDetails(0); @@ -174,9 +158,7 @@ public static function getAndWithShowSolutionSuggestedDataProvider(): array ]; } - /** - * @dataProvider getAndWithShowSolutionListComparisonDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithShowSolutionListComparisonDataProvider')] public function testGetAndWithShowSolutionListComparison(bool $IO): void { $settingsResultDetails = new SettingsResultDetails(0); @@ -192,9 +174,7 @@ public static function getAndWithShowSolutionListComparisonDataProvider(): array ]; } - /** - * @dataProvider getAndWithExportSettingsDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithExportSettingsDataProvider')] public function testGetAndWithExportSettings(int $IO): void { $settingsResultDetails = new SettingsResultDetails(0); diff --git a/components/ILIAS/Test/tests/Settings/ScoreReporting/SettingsResultSummaryTest.php b/components/ILIAS/Test/tests/Settings/ScoreReporting/SettingsResultSummaryTest.php index 6b09254a158f..f9d98117f15f 100644 --- a/components/ILIAS/Test/tests/Settings/ScoreReporting/SettingsResultSummaryTest.php +++ b/components/ILIAS/Test/tests/Settings/ScoreReporting/SettingsResultSummaryTest.php @@ -31,9 +31,7 @@ public function testConstruct(): void $this->assertInstanceOf(SettingsResultSummary::class, $settingsResultSummary); } - /** - * @dataProvider getAndWithScoreReportingDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithScoreReportingDataProvider')] public function testGetAndWithScoreReporting(ScoreReportingTypes $IO): void { $this->assertEquals( @@ -51,9 +49,7 @@ public static function getAndWithScoreReportingDataProvider(): array ]; } - /** - * @dataProvider getAndWithReportingDateDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithReportingDateDataProvider')] public function testGetAndWithReportingDate(?\DateTimeImmutable $IO): void { $settingsResultSummary = new SettingsResultSummary(0); @@ -69,9 +65,7 @@ public static function getAndWithReportingDateDataProvider(): array ]; } - /** - * @dataProvider getAndWithShowGradingStatusEnabledDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithShowGradingStatusEnabledDataProvider')] public function testGetAndWithShowGradingStatusEnabled(bool $IO): void { $settingsResultSummary = new SettingsResultSummary(0); @@ -87,9 +81,7 @@ public static function getAndWithShowGradingStatusEnabledDataProvider(): array ]; } - /** - * @dataProvider getAndWithShowGradingMarkEnabledDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithShowGradingMarkEnabledDataProvider')] public function testGetAndWithShowGradingMarkEnabled(bool $IO): void { $settingsResultSummary = new SettingsResultSummary(0); @@ -105,9 +97,7 @@ public static function getAndWithShowGradingMarkEnabledDataProvider(): array ]; } - /** - * @dataProvider getAndWithPassDeletionAllowedDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithPassDeletionAllowedDataProvider')] public function testGetAndWithPassDeletionAllowed(bool $IO): void { $settingsResultSummary = new SettingsResultSummary(0); @@ -123,9 +113,7 @@ public static function getAndWithPassDeletionAllowedDataProvider(): array ]; } - /** - * @dataProvider getAndWithShowPassDetailsDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithShowPassDetailsDataProvider')] public function testGetAndWithShowPassDetails(bool $IO): void { $settingsResultSummary = new SettingsResultSummary(0); diff --git a/components/ILIAS/Test/tests/Settings/ScoreReporting/SettingsScoringTest.php b/components/ILIAS/Test/tests/Settings/ScoreReporting/SettingsScoringTest.php index bc9a2ecde912..3e569881b877 100644 --- a/components/ILIAS/Test/tests/Settings/ScoreReporting/SettingsScoringTest.php +++ b/components/ILIAS/Test/tests/Settings/ScoreReporting/SettingsScoringTest.php @@ -29,9 +29,7 @@ public function testConstruct(): void $this->assertInstanceOf(SettingsScoring::class, $settingsScoring); } - /** - * @dataProvider getAndWithCountSystemDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithCountSystemDataProvider')] public function testGetAndWithCountSystem(bool $IO): void { $settingsScoring = new SettingsScoring(0); @@ -47,9 +45,7 @@ public static function getAndWithCountSystemDataProvider(): array ]; } - /** - * @dataProvider getAndWithScoreCuttingDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithScoreCuttingDataProvider')] public function testGetAndWithScoreCutting(bool $IO): void { $settingsScoring = new SettingsScoring(0); @@ -65,9 +61,7 @@ public static function getAndWithScoreCuttingDataProvider(): array ]; } - /** - * @dataProvider getAndWithPassScoringDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndWithPassScoringDataProvider')] public function testGetAndWithPassScoring(bool $IO): void { $settingsScoring = new SettingsScoring(0); diff --git a/components/ILIAS/Test/tests/ilObjTestGUITest.php b/components/ILIAS/Test/tests/ilObjTestGUITest.php index 3aadfb8303fc..b588989a1906 100755 --- a/components/ILIAS/Test/tests/ilObjTestGUITest.php +++ b/components/ILIAS/Test/tests/ilObjTestGUITest.php @@ -64,9 +64,7 @@ protected function setUp(): void $this->addGlobal_ilHelp(); $this->addGlobal_ilObjDataCache(); $this->addGlobal_ilRbacAdmin(); - $this->addGlobal_objectService(); $this->addGlobal_GlobalScreenService(); - $this->addGlobal_resourceStorage(); $this->testObj = $this->getNewTestGUI(); } diff --git a/components/ILIAS/Test/tests/ilObjTestListGUITest.php b/components/ILIAS/Test/tests/ilObjTestListGUITest.php index 1d7e9ecfd842..6c1519054fe7 100644 --- a/components/ILIAS/Test/tests/ilObjTestListGUITest.php +++ b/components/ILIAS/Test/tests/ilObjTestListGUITest.php @@ -39,9 +39,7 @@ public function testConstruct(): void $this->assertInstanceOf(ilObjTestListGUI::class, $this->testObj); } - /** - * @dataProvider createDefaultCommandDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('createDefaultCommandDataProvider')] public function testCreateDefaultCommand(array $IO): void { $this->assertEquals($IO, $this->testObj->createDefaultCommand($IO)); diff --git a/components/ILIAS/Test/tests/ilObjTestVerificationGUITest.php b/components/ILIAS/Test/tests/ilObjTestVerificationGUITest.php index 09d7b04bc0f2..d4f0366db1f6 100644 --- a/components/ILIAS/Test/tests/ilObjTestVerificationGUITest.php +++ b/components/ILIAS/Test/tests/ilObjTestVerificationGUITest.php @@ -35,6 +35,7 @@ protected function setUp(): void $this->addGlobal_ilObjDataCache(); $this->addGlobal_ilSetting(); $this->addGlobal_filesystem(); + $this->addGlobal_http(); } public function testConstruct(): void diff --git a/components/ILIAS/Test/tests/ilTestBaseTestCase.php b/components/ILIAS/Test/tests/ilTestBaseTestCase.php index 9c5dc24b6cda..61aa0d011651 100755 --- a/components/ILIAS/Test/tests/ilTestBaseTestCase.php +++ b/components/ILIAS/Test/tests/ilTestBaseTestCase.php @@ -53,6 +53,7 @@ protected function setUp(): void $this->addGlobal_ilErr(); $this->addGlobal_tree(); $this->addGlobal_lng(); + $this->addGlobal_resourceStorage(); $this->addGlobal_ilAppEventHandler(); $this->addGlobal_objDefinition(); $this->addGlobal_refinery(); @@ -68,6 +69,9 @@ protected function setUp(): void $this->addGlobal_ilCtrl(); $this->addGlobal_ilBench(); $this->addGlobal_ilSetting(); + $this->addGlobal_objectService(); + $this->addGlobal_objectMetadata(); + $this->addGlobal_resourceStorage(); $this->defineGlobalConstants(); diff --git a/components/ILIAS/Test/tests/ilTestBaseTestCaseTrait.php b/components/ILIAS/Test/tests/ilTestBaseTestCaseTrait.php index 43b9496d4285..eac9ac8d2ddf 100644 --- a/components/ILIAS/Test/tests/ilTestBaseTestCaseTrait.php +++ b/components/ILIAS/Test/tests/ilTestBaseTestCaseTrait.php @@ -305,10 +305,14 @@ protected function addGlobal_skillService(): void $this->setGlobalVariable('skill', $this->createMock(ILIAS\Skill\Service\SkillService::class)); } + protected function addGlobal_objectMetadata(): void + { + $this->setGlobalVariable('learning_object_metadata', $this->createMock(ILIAS\MetaData\Services\ServicesInterface::class)); + } + protected function addGlobal_objectService(): void { - global $DIC; - $DIC['object.customicons.factory'] = $this->getMockBuilder(ilObjectCustomIconFactory::class)->disableOriginalConstructor()->getMock(); + $this->setGlobalVariable('object.customicons.factory', $this->getMockBuilder(ILIAS\ILIASObject\Properties\AdditionalProperties\Icon\Factory::class)->disableOriginalConstructor()->getMock()); $object_mock = $this->getMockBuilder(\ilObjectService::class)->disableOriginalConstructor()->getMock(); $this->setGlobalVariable('object', $object_mock); diff --git a/components/ILIAS/Test/tests/ilTestEvaluationDataTest.php b/components/ILIAS/Test/tests/ilTestEvaluationDataTest.php index 6fa141a0c3f7..1c7b02b44e25 100755 --- a/components/ILIAS/Test/tests/ilTestEvaluationDataTest.php +++ b/components/ILIAS/Test/tests/ilTestEvaluationDataTest.php @@ -94,8 +94,6 @@ public function testEvaluationFactory(): void "answeredquestions" => 1, "workingtime" => 28, "tstamp" => 1731941437, - "hint_count" => 0, - "hint_points" => 0, "obligations_answered" => true, "exam_id" => "I0_T355_A7_P1", "usr_id" => 6, diff --git a/components/ILIAS/Test/tests/ilTestEvaluationPassDataTest.php b/components/ILIAS/Test/tests/ilTestEvaluationPassDataTest.php index 71b35d4c326a..cc4ae64e4655 100755 --- a/components/ILIAS/Test/tests/ilTestEvaluationPassDataTest.php +++ b/components/ILIAS/Test/tests/ilTestEvaluationPassDataTest.php @@ -169,22 +169,6 @@ public function testGetAnsweredQuestionCount(): void $this->assertEquals(count($expected), $this->testObj->getAnsweredQuestionCount()); } - public function testRequestedHintsCount(): void - { - $requestedHintsCount = 5; - $this->testObj->setRequestedHintsCount($requestedHintsCount); - - $this->assertEquals($requestedHintsCount, $this->testObj->getRequestedHintsCount()); - } - - public function testDeductedHintPoints(): void - { - $deductedHintPoints = 5; - $this->testObj->setDeductedHintPoints($deductedHintPoints); - - $this->assertEquals($deductedHintPoints, $this->testObj->getDeductedHintPoints()); - } - public function testExamId(): void { $exam_id = '5'; diff --git a/components/ILIAS/Test/tests/ilTestLPTest.php b/components/ILIAS/Test/tests/ilTestLPTest.php index 7c293cabf721..86932d38efdc 100644 --- a/components/ILIAS/Test/tests/ilTestLPTest.php +++ b/components/ILIAS/Test/tests/ilTestLPTest.php @@ -24,9 +24,7 @@ public function testConstruct(): void $this->assertInstanceOf(ilTestLP::class, $ilTestLPTest); } - /** - * @dataProvider getDefaultModesDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getDefaultModesDataProvider')] public function testGetDefaultModes(bool $input, array $output): void { $this->assertEquals($output, ilTestLP::getDefaultModes($input)); diff --git a/components/ILIAS/Test/tests/ilTestPassFinishTasksTest.php b/components/ILIAS/Test/tests/ilTestPassFinishTasksTest.php index 9e440b7b0770..80e262f85d42 100755 --- a/components/ILIAS/Test/tests/ilTestPassFinishTasksTest.php +++ b/components/ILIAS/Test/tests/ilTestPassFinishTasksTest.php @@ -32,7 +32,7 @@ protected function setUp(): void $this->testObj = new ilTestPassFinishTasks( $this->createMock(ilTestSession::class), - 0, + $this->createMock(ilObjTest::class), $this->createMock(ILIAS\Test\Results\Data\Repository::class) ); } diff --git a/components/ILIAS/Test/tests/ilTestQuestionPoolSelectorExplorerTest.php b/components/ILIAS/Test/tests/ilTestQuestionPoolSelectorExplorerTest.php index 4585b1ed2135..dc584c059546 100644 --- a/components/ILIAS/Test/tests/ilTestQuestionPoolSelectorExplorerTest.php +++ b/components/ILIAS/Test/tests/ilTestQuestionPoolSelectorExplorerTest.php @@ -49,9 +49,7 @@ public function testConstruct(): void $this->assertInstanceOf(ilRepositorySelectorExplorerGUI::class, $this->testObj); } - /** - * @dataProvider getAndSetAvailableQuestionPoolsDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('getAndSetAvailableQuestionPoolsDataProvider')] public function testGetAndSetAvailableQuestionPools(array $IO): void { $this->assertEquals([], $this->testObj->getAvailableQuestionPools()); diff --git a/components/ILIAS/Test/tests/ilTestResultsToXMLTest.php b/components/ILIAS/Test/tests/ilTestResultsToXMLTest.php index 661836e5f6ec..7dc6c6ecd4cf 100755 --- a/components/ILIAS/Test/tests/ilTestResultsToXMLTest.php +++ b/components/ILIAS/Test/tests/ilTestResultsToXMLTest.php @@ -28,11 +28,14 @@ class ilTestResultsToXMLTest extends ilTestBaseTestCase protected function setUp(): void { + global $DIC; parent::setUp(); $this->testObj = new ilTestResultsToXML( 0, - $this->createMock(ilDBInterface::class), + $DIC['ilDB'], + $DIC['resource_storage'], + '', false ); } diff --git a/components/ILIAS/Test/tests/ilTestServiceGUITest.php b/components/ILIAS/Test/tests/ilTestServiceGUITest.php index 4f5b6728342c..a6e7e838a923 100755 --- a/components/ILIAS/Test/tests/ilTestServiceGUITest.php +++ b/components/ILIAS/Test/tests/ilTestServiceGUITest.php @@ -70,9 +70,7 @@ public function testObjectiveOrientedContainer(): void $this->assertEquals($mock, $this->testObj->getObjectiveOrientedContainer()); } - /** - * @dataProvider buildFixedShufflerSeedDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('buildFixedShufflerSeedDataProvider')] public function testBuildFixedShufflerSeed(int $question_id, int $pass_id, int $active_id, int $return): void { $reflection = new ReflectionClass(ilTestShuffler::class); diff --git a/components/ILIAS/Test/tests/tables/ilTestExportTableGUITest.php b/components/ILIAS/Test/tests/tables/ilTestExportTableGUITest.php deleted file mode 100755 index 3162fa27781d..000000000000 --- a/components/ILIAS/Test/tests/tables/ilTestExportTableGUITest.php +++ /dev/null @@ -1,72 +0,0 @@ - - */ -class ilTestExportTableGUITest extends ilTestBaseTestCase -{ - private ilTestExportTableGUI $tableGui; - private ilObjTestGUI $parentObj_mock; - - protected function setUp(): void - { - parent::setUp(); - - $lng_mock = $this->createMock(ilLanguage::class); - $ctrl_mock = $this->createMock(ilCtrl::class); - $ctrl_mock->expects($this->any()) - ->method("getFormAction") - ->willReturnCallback(function () { - return "testFormAction"; - }); - - $this->setGlobalVariable("lng", $lng_mock); - $this->setGlobalVariable("ilCtrl", $ctrl_mock); - $this->setGlobalVariable("tpl", $this->createMock(ilGlobalPageTemplate::class)); - $this->setGlobalVariable("component.repository", $this->createMock(ilComponentRepository::class)); - $component_factory = $this->createMock(ilComponentFactory::class); - $component_factory->method("getActivePluginsInSlot")->willReturn(new ArrayIterator()); - $this->setGlobalVariable("component.factory", $component_factory); - $this->setGlobalVariable("ilDB", $this->createMock(ilDBInterface::class)); - $this->setGlobalVariable("ilAccess", $this->createMock(ilAccessHandler::class)); - - $this->parentObj_mock = $this->getMockBuilder(ilObjTestGUI::class)->disableOriginalConstructor()->onlyMethods(['getObject'])->getMock(); - $this->parentObj_mock->expects($this->any())->method('getObject')->willReturn($this->getTestObjMock()); - $this->tableGui = new ilTestExportTableGUI( - $this->parentObj_mock, - "", - $this->getTestObjMock() - ); - } - - public function test_instantiateObject_shouldReturnInstance(): void - { - $this->assertInstanceOf(ilTestExportTableGUI::class, $this->tableGui); - } - - public function testNumericOrdering(): void - { - $this->assertTrue($this->tableGui->numericOrdering("size")); - $this->assertTrue($this->tableGui->numericOrdering("date")); - $this->assertFalse($this->tableGui->numericOrdering("randomString")); - } -} diff --git a/components/ILIAS/Test/tests/tables/ilTestPassDetailsOverviewTableGUITest.php b/components/ILIAS/Test/tests/tables/ilTestPassDetailsOverviewTableGUITest.php index 4369ac3a0e99..6851ee95d520 100755 --- a/components/ILIAS/Test/tests/tables/ilTestPassDetailsOverviewTableGUITest.php +++ b/components/ILIAS/Test/tests/tables/ilTestPassDetailsOverviewTableGUITest.php @@ -80,15 +80,6 @@ public function testAnswerListAnchorEnabled(): void $this->assertTrue($this->tableGui->getAnswerListAnchorEnabled()); } - public function testShowHintCount(): void - { - $this->assertIsBool($this->tableGui->getShowHintCount()); - $this->tableGui->setShowHintCount(false); - $this->assertFalse($this->tableGui->getShowHintCount()); - $this->tableGui->setShowHintCount(true); - $this->assertTrue($this->tableGui->getShowHintCount()); - } - public function testShowSuggestedSolution(): void { $this->assertIsBool($this->tableGui->getShowSuggestedSolution()); diff --git a/components/ILIAS/TestQuestionPool/PRIVACY.md b/components/ILIAS/TestQuestionPool/PRIVACY.md index 4ad0a591f196..73d75d63fcd9 100755 --- a/components/ILIAS/TestQuestionPool/PRIVACY.md +++ b/components/ILIAS/TestQuestionPool/PRIVACY.md @@ -9,12 +9,6 @@ are blurred - which makes them subject for refactoring, too - it is advised to n but always at both. ## Data being stored - -- **Hint Tracking of Questions by User**: - Keyed via the "active_id", the information about users who have received "hints" during answering a question, is tracked. - This information is revealed in solution views in the Test module to the users themselves as well as users with administrative permissions on the objects and those users who have permission to work on manual scoring and corrections. - The data is required for keeping track of and adjusting scoring in question-usage. - - **Authorship of Questions**: Authors of questions are stored in the TestQuestionPool as reference to the users id. The data is required for copyright purposes as well as to enable communication with the author. @@ -35,9 +29,6 @@ The owners of questions are revealed in editing forms of questions as well as ta - overviews to accounts with the edit permission to the test question pool object. ## Data being deleted -- **Hint Tracking of Questions by User**: - The storage of this information is tied to the use of the question in tests and will be deleted together with data - pertaining test-participation in the object the data were they were gathered. - **Authorship of Questions**: The storage of this information is tied to the lifecycle of the question it is attached to and so the deletion happens in the removal of a question by a user account with edit permissions to the question @@ -49,10 +40,6 @@ The storage of this information is tied to the lifecycle of the question it is a ## Data being exported -- **Hint Tracking of Questions by User**: - The hint tracking data are included in exports of the test object where the information was gathered. The information -is made available in result detail exports and archive-exports for long-term storage and can be triggered by accounts -with edit permissions on the test object. - **Authorship of Questions**: Authorship of questions is exported with the questions. In the test question pool, this is the case when questions or the pool as a whole is exported by account with edit permissions on the test question pool object. diff --git a/components/ILIAS/TestQuestionPool/classes/Setup/class.ilTestQuestionPool9DBUpdateSteps.php b/components/ILIAS/TestQuestionPool/classes/Setup/class.ilTestQuestionPool9DBUpdateSteps.php index 279d9ba9e959..60fb61f80016 100755 --- a/components/ILIAS/TestQuestionPool/classes/Setup/class.ilTestQuestionPool9DBUpdateSteps.php +++ b/components/ILIAS/TestQuestionPool/classes/Setup/class.ilTestQuestionPool9DBUpdateSteps.php @@ -85,4 +85,17 @@ public function step_6(): void ['type' => 'text', 'length' => 124] ); } + + public function step_7(): void + { + $table = 'tst_rnd_quest_set_qpls'; + $table_column = 'pool_title'; + if ($this->db->tableColumnExists($table, $table_column)) { + $this->db->modifyTableColumn( + $table, + $table_column, + ['type' => ilDBConstants::T_TEXT, 'length' => 255], + ); + } + } } diff --git a/components/ILIAS/TestQuestionPool/classes/class.assClozeGapCombination.php b/components/ILIAS/TestQuestionPool/classes/class.assClozeGapCombination.php index 23c382331a21..d3f5a223e704 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assClozeGapCombination.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assClozeGapCombination.php @@ -18,33 +18,36 @@ class assClozeGapCombination { - public function loadFromDb($question_id): array + public function __construct( + private readonly ilDBInterface $db + ) { + + } + + public function loadFromDb(int $question_id): array { - global $DIC; - $ilDB = $DIC['ilDB']; - $result = $ilDB->queryF( - ' - SELECT combinations.combination_id, - combinations.gap_fi, - combinations.answer, - combinations.row_id, - combinations.points, - combinations.best_solution, - combinations.question_fi, - cloze.cloze_type - FROM qpl_a_cloze_combi_res AS combinations - INNER JOIN qpl_a_cloze AS cloze - WHERE combinations.question_fi = cloze.question_fi - AND combinations.gap_fi = cloze.gap_id - AND combinations.question_fi = %s - ORDER BY combination_id, row_id, gap_fi ASC - ', + $result = $this->db->queryF( + 'SELECT combinations.combination_id, + combinations.gap_fi, + combinations.answer, + combinations.row_id, + combinations.points, + combinations.best_solution, + combinations.question_fi, + cloze.cloze_type + FROM qpl_a_cloze_combi_res AS combinations + INNER JOIN qpl_a_cloze AS cloze + WHERE combinations.question_fi = cloze.question_fi + AND combinations.gap_fi = cloze.gap_id + AND combinations.question_fi = %s + ORDER BY combination_id, row_id, gap_fi ASC + ', ['integer'], [$question_id] ); $return_array = []; - while ($data = $ilDB->fetchAssoc($result)) { + while ($data = $this->db->fetchAssoc($result)) { if (isset($return_array[$data['combination_id'] . '::' . $data['gap_fi']])) { continue; } @@ -63,10 +66,9 @@ public function loadFromDb($question_id): array return array_values($return_array); } - public function getCleanCombinationArray($question_id): array + public function getCleanCombinationArray(int $question_id): array { - $assClozeGapCombinationObj = new assClozeGapCombination(); - $combination_from_db = $assClozeGapCombinationObj->loadFromDb($question_id); + $combination_from_db = $this->loadFromDb($question_id); $clean_array = []; foreach ($combination_from_db as $key => $value) { $clean_array[$value['cid']][$value['row_id']][$value['gap_fi']]['answer'] = $value['answer']; @@ -76,10 +78,11 @@ public function getCleanCombinationArray($question_id): array return $clean_array; } - public function saveGapCombinationToDb($question_id, $gap_combinations, $gap_values): void - { - global $DIC; - $ilDB = $DIC['ilDB']; + public function saveGapCombinationToDb( + int $question_id, + array $gap_combinations, + array $gap_values + ): void { $best_solutions = []; for ($i = 0; $i < count($gap_combinations['points']); $i++) { $highest_points = 0; @@ -98,7 +101,7 @@ public function saveGapCombinationToDb($question_id, $gap_combinations, $gap_val } else { $best_solution = 0; } - $ilDB->manipulateF( + $this->db->manipulateF( 'INSERT INTO qpl_a_cloze_combi_res (combination_id, question_fi, gap_fi, row_id, answer, points, best_solution) VALUES (%s, %s, %s, %s, %s, %s, %s)', [ @@ -124,19 +127,17 @@ public function saveGapCombinationToDb($question_id, $gap_combinations, $gap_val } } } - public static function importGapCombinationToDb($question_id, $gap_combinations): void + public function importGapCombinationToDb(int $question_id, array $gap_combinations): void { - global $DIC; - $ilDB = $DIC['ilDB']; - - foreach ($gap_combinations as $key => $row) { + foreach ($gap_combinations as $row) { if (is_object($row)) { $row = get_object_vars($row); } if ($question_id != -1) { - $ilDB->manipulateF( + $this->db->manipulateF( 'INSERT INTO qpl_a_cloze_combi_res - (combination_id, question_fi, gap_fi, row_id, answer, points, best_solution) VALUES (%s, %s, %s, %s, %s, %s, %s)', + (combination_id, question_fi, gap_fi, row_id, answer, points, best_solution) + VALUES (%s, %s, %s, %s, %s, %s, %s)', [ 'integer', 'integer', @@ -160,12 +161,9 @@ public static function importGapCombinationToDb($question_id, $gap_combinations) } } - public static function clearGapCombinationsFromDb($question_id): void + public function clearGapCombinationsFromDb($question_id): void { - global $DIC; - $ilDB = $DIC['ilDB']; - - $ilDB->manipulateF( + $this->db->manipulateF( 'DELETE FROM qpl_a_cloze_combi_res WHERE question_fi = %s', [ 'integer' ], [ $question_id ] @@ -174,105 +172,72 @@ public static function clearGapCombinationsFromDb($question_id): void public function combinationExistsForQid($question_id): bool { - global $DIC; - $ilDB = $DIC['ilDB']; - - $result = $ilDB->queryF( + $result = $this->db->queryF( 'SELECT * FROM qpl_a_cloze_combi_res WHERE question_fi = %s ORDER BY gap_fi ASC', ['integer'], [$question_id] ); if ($result->numRows() > 0) { return true; - } else { - return false; } + return false; } public function getGapsWhichAreUsedInCombination($question_id): array { - global $DIC; - $ilDB = $DIC['ilDB']; - - $result = $ilDB->queryF( - 'SELECT gap_fi, combination_id FROM ' . $ilDB->quoteIdentifier('qpl_a_cloze_combi_res') . ' WHERE question_fi = %s GROUP BY gap_fi, combination_id', + $result = $this->db->queryF( + 'SELECT gap_fi, combination_id FROM ' + . $this->db->quoteIdentifier('qpl_a_cloze_combi_res') + . ' WHERE question_fi = %s GROUP BY gap_fi, combination_id', ['integer'], [$question_id] ); $gaps = []; if ($result->numRows() > 0) { - while ($data = $ilDB->fetchAssoc($result)) { + while ($data = $this->db->fetchAssoc($result)) { $gaps[$data['gap_fi']] = $data['combination_id']; } } return $gaps; } - public function getMaxPointsForCombination(int $question_id, int $combination_id = -1): float - { - global $DIC; - $ilDB = $DIC['ilDB']; + public function getMaxPointsForCombination( + int $question_id, + int $combination_id = -1 + ): float { + $result = $this->fetchResult($question_id, $combination_id); - if ($combination_id == -1) { - $result = $ilDB->queryF( - 'SELECT combination_id, points FROM qpl_a_cloze_combi_res WHERE question_fi = %s AND best_solution=1 GROUP BY combination_id, points', + $points = 0.0; + while (($data = $this->db->fetchAssoc($result)) !== null) { + $points += $data['points']; + } + return $points; + } + + private function fetchResult( + int $question_id, + int $combination_id + ): ilPDOStatement { + if ($combination_id === -1) { + return $this->db->queryF( + 'SELECT combination_id, points' . PHP_EOL + . 'FROM qpl_a_cloze_combi_res' . PHP_EOL + . 'WHERE question_fi = %s' . PHP_EOL + . 'AND best_solution=1' . PHP_EOL + . 'GROUP BY combination_id, points', ['integer'], [$question_id] ); - if ($result->numRows() > 0) { - $points = 0; - while ($data = $ilDB->fetchAssoc($result)) { - $points += $data['points']; - } - return $points; - } - } else { - $result = $ilDB->queryF( - 'SELECT combination_id, points FROM qpl_a_cloze_combi_res WHERE question_fi = %s AND combination_id = %s AND best_solution=1 GROUP BY combination_id, points', - ['integer', 'integer'], - [$question_id, $combination_id] - ); - if ($result->numRows() > 0) { - $points = 0; - while ($data = $ilDB->fetchAssoc($result)) { - $points += $data['points']; - } - return $points; - } } - return 0; - } - - public function getBestSolutionCombination($question_id) - { - global $DIC; - $ilDB = $DIC['ilDB']; - $lng = $DIC['lng']; - - $result = $ilDB->queryF( - 'SELECT * FROM qpl_a_cloze_combi_res WHERE question_fi = %s AND best_solution=1 ORDER BY gap_fi', - ['integer'], - [$question_id] + return $this->db->queryF( + 'SELECT combination_id, points' . PHP_EOL + . 'FROM qpl_a_cloze_combi_res' . PHP_EOL + . 'WHERE question_fi = %s' . PHP_EOL + . 'AND combination_id = %s' . PHP_EOL + . 'AND best_solution=1' . PHP_EOL + . 'GROUP BY combination_id, points', + ['integer', 'integer'], + [$question_id, $combination_id] ); - if ($result->numRows() > 0) { - $return_string = '
'; - $combination_id = 0; - $points = 0; - while ($data = $ilDB->fetchAssoc($result)) { - if ($combination_id != $data['combination_id']) { - $combination_id = $data['combination_id']; - $return_string .= $points; - $return_string .= '
'; - $return_string .= $data['answer'] . '|'; - } else { - $return_string .= $data['answer'] . '|'; - } - - $points = ' (' . $data['points'] . ' ' . $lng->txt('points') . ')'; - } - return rtrim($return_string, '|') . $points; - } else { - return 0; - } } } diff --git a/components/ILIAS/TestQuestionPool/classes/class.assClozeTest.php b/components/ILIAS/TestQuestionPool/classes/class.assClozeTest.php index 5bd6a2b6f010..4e74cfa497a3 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assClozeTest.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assClozeTest.php @@ -51,7 +51,7 @@ class assClozeTest extends assQuestion implements ilObjQuestionScoringAdjustable * @var array */ protected $gap_combinations = []; - protected bool $gap_combinations_exists = false; + protected bool $gap_combinations_exist = false; private string $start_tag = '[gap]'; private string $end_tag = '[/gap]'; @@ -235,8 +235,7 @@ public function loadFromDb(int $question_id): void } } } - $assClozeGapCombinationObj = new assClozeGapCombination(); - $check_for_gap_combinations = $assClozeGapCombinationObj->loadFromDb($question_id); + $check_for_gap_combinations = (new assClozeGapCombination($this->db))->loadFromDb($question_id); if (count($check_for_gap_combinations) != 0) { $this->setGapCombinationsExists(true); $this->setGapCombinations($check_for_gap_combinations); @@ -742,10 +741,10 @@ public function setGapAnswerUpperBound($gap_index, $order, $bound): void */ public function getMaximumPoints(): float { - $assClozeGapCombinationObj = new assClozeGapCombination(); + $assClozeGapCombinationObj = new assClozeGapCombination($this->db); $points = 0; $gaps_used_in_combination = []; - if ($assClozeGapCombinationObj->combinationExistsForQid($this->getId())) { + if ($this->gap_combinations_exist) { $points = $assClozeGapCombinationObj->getMaxPointsForCombination($this->getId()); $gaps_used_in_combination = $assClozeGapCombinationObj->getGapsWhichAreUsedInCombination($this->getId()); } @@ -782,11 +781,18 @@ public function getMaximumPoints(): float return $points; } - public function copyGapCombination($orgID, $newID): void - { - $assClozeGapCombinationObj = new assClozeGapCombination(); - $array = $assClozeGapCombinationObj->loadFromDb($orgID); - $assClozeGapCombinationObj->importGapCombinationToDb($newID, $array); + public function cloneQuestionTypeSpecificProperties( + \assQuestion $target + ): \assQuestion { + if ($this->gap_combinations_exist) { + $gap_combination = new assClozeGapCombination($this->db); + $gap_combination->clearGapCombinationsFromDb($target->getId()); + $gap_combination->importGapCombinationToDb( + $target->getId(), + $this->gap_combinations, + ); + } + return $target; } /** @@ -1228,7 +1234,7 @@ public function getRTETextWithMediaObjects(): string } public function getGapCombinationsExists(): bool { - return $this->gap_combinations_exists; + return $this->gap_combinations_exist; } public function getGapCombinations(): array @@ -1238,7 +1244,7 @@ public function getGapCombinations(): array public function setGapCombinationsExists($value): void { - $this->gap_combinations_exists = $value; + $this->gap_combinations_exist = $value; } public function setGapCombinations($value): void @@ -1265,7 +1271,7 @@ public function toJSON(): string $result = [ 'id' => $this->getId(), 'type' => (string) $this->getQuestionType(), - 'title' => $this->getTitle(), + 'title' => $this->getTitleForHTMLOutput(), 'question' => $this->formatSAQuestion($this->getQuestion()), 'clozetext' => $this->formatSAQuestion($this->getClozeText()), 'nr_of_tries' => $this->getNrOfTries(), @@ -1394,7 +1400,7 @@ public function calculateCombinationResult($user_result): array { $points = 0; - $assClozeGapCombinationObj = new assClozeGapCombination(); + $assClozeGapCombinationObj = new assClozeGapCombination($this->db); $gap_used_in_combination = []; if ($assClozeGapCombinationObj->combinationExistsForQid($this->getId())) { $combinations_for_question = $assClozeGapCombinationObj->getCleanCombinationArray($this->getId()); @@ -1466,9 +1472,9 @@ protected function calculateReachedPointsForSolution(?array $user_result, array { $points = 0.0; - $assClozeGapCombinationObj = new assClozeGapCombination(); + $assClozeGapCombinationObj = new assClozeGapCombination($this->db); $combinations[1] = []; - if ($assClozeGapCombinationObj->combinationExistsForQid($this->getId())) { + if ($this->gap_combinations_exist) { $combinations = $this->calculateCombinationResult($user_result); $points = $combinations[0]; } @@ -1570,7 +1576,6 @@ public function calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSes } $reached_points = $this->calculateReachedPointsForSolution($user_solution); - $reached_points = $this->deductHintPointsFromReachedPoints($preview_session, $reached_points); return $this->ensureNonNegativePoints($reached_points); } @@ -1620,7 +1625,7 @@ public function toLog(AdditionalInformationGenerator $additional_info): array { $result = [ AdditionalInformationGenerator::KEY_QUESTION_TYPE => (string) $this->getQuestionType(), - AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitle(), + AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitleForHTMLOutput(), AdditionalInformationGenerator::KEY_QUESTION_TEXT => $this->formatSAQuestion($this->getQuestion()), AdditionalInformationGenerator::KEY_QUESTION_CLOZE_CLOZETEXT => $this->formatSAQuestion($this->getClozeText()), AdditionalInformationGenerator::KEY_QUESTION_SHUFFLE_ANSWER_OPTIONS => $additional_info diff --git a/components/ILIAS/TestQuestionPool/classes/class.assClozeTestGUI.php b/components/ILIAS/TestQuestionPool/classes/class.assClozeTestGUI.php index 7a6ffa7a133b..0699f4a7901b 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assClozeTestGUI.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assClozeTestGUI.php @@ -20,7 +20,6 @@ use ILIAS\UI\Renderer as UIRenderer; use ILIAS\Refinery\Random\Group as RandomGroup; use ILIAS\Refinery\Random\Seed; -use ILIAS\HTTP\Wrapper\ArrayBasedRequestWrapper; /** * Cloze test question GUI representation @@ -87,6 +86,7 @@ class assClozeTestGUI extends assQuestionGUI implements ilGuiQuestionScoringAdju private RandomGroup $randomGroup; private UIFactory $ui_factory; private UIRenderer $ui_renderer; + private ilDBInterface $db; /** * assClozeTestGUI constructor @@ -97,8 +97,9 @@ public function __construct(int $id = -1) { parent::__construct(); global $DIC; - $this->ui_factory = $DIC->ui()->factory(); - $this->ui_renderer = $DIC->ui()->renderer(); + $this->ui_factory = $DIC['ui.factory']; + $this->ui_renderer = $DIC['ui.renderer']; + $this->db = $DIC['ilDB']; $this->object = new assClozeTest(); if ($id >= 0) { @@ -225,8 +226,8 @@ public function writeAnswerSpecificPostData(ilPropertyFormGUI $form): void } } - $ass_cloze_gab_combination = new assClozeGapCombination(); - $ass_cloze_gab_combination::clearGapCombinationsFromDb($this->object->getId()); + $ass_cloze_gab_combination = new assClozeGapCombination($this->db); + $ass_cloze_gab_combination->clearGapCombinationsFromDb($this->object->getId()); $gap_combination = $this->request_data_collector->rawArray('gap_combination', 4); if ($gap_combination !== []) { @@ -235,6 +236,7 @@ public function writeAnswerSpecificPostData(ilPropertyFormGUI $form): void $gap_combination, $this->request_data_collector->rawArray('gap_combination_values') ); + $this->object->setGapCombinationsExists(true); } } @@ -419,7 +421,7 @@ private function getAddGapButtonClickClosure(string $gap_type): Closure public function populateAnswerSpecificFormPart(ilPropertyFormGUI $form): ilPropertyFormGUI { $json = $this->populateJSON(); - $assClozeGapCombinationObject = new assClozeGapCombination(); + $assClozeGapCombinationObject = new assClozeGapCombination($this->db); $combination_exists = $assClozeGapCombinationObject->combinationExistsForQid($this->object->getId()); $combinations = []; if ($combination_exists) { @@ -860,7 +862,7 @@ public function renderSolutionOutput( ): ?string { $template = new ilTemplate("tpl.il_as_qpl_cloze_question_output_solution.html", true, true, "components/ILIAS/TestQuestionPool"); $output = $this->object->getClozeTextForHTMLOutput(); - $assClozeGapCombinationObject = new assClozeGapCombination(); + $assClozeGapCombinationObject = new assClozeGapCombination($this->db); $check_for_gap_combinations = $assClozeGapCombinationObject->loadFromDb($this->object->getId()); foreach ($this->object->getGaps() as $gap_index => $gap) { @@ -1667,7 +1669,7 @@ protected function saveGapCombinationCorrectionFormProperties(ilPropertyFormGUI } } - $assClozeGapCombinationObject = new assClozeGapCombination(); + $assClozeGapCombinationObject = new assClozeGapCombination($this->db); $assClozeGapCombinationObject->clearGapCombinationsFromDb($this->object->getId()); $assClozeGapCombinationObject->saveGapCombinationToDb( @@ -1675,5 +1677,6 @@ protected function saveGapCombinationCorrectionFormProperties(ilPropertyFormGUI $combinationPoints, $combinationValues ); + $this->object->setGapCombinationsExists(true); } } diff --git a/components/ILIAS/TestQuestionPool/classes/class.assErrorText.php b/components/ILIAS/TestQuestionPool/classes/class.assErrorText.php index 03201cf27f3f..f447095fc596 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assErrorText.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assErrorText.php @@ -267,7 +267,6 @@ public function calculateReachedPoints( public function calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSession $preview_session) { $reached_points = $this->getPointsForSelectedPositions($preview_session->getParticipantsSolution() ?? []); - $reached_points = $this->deductHintPointsFromReachedPoints($preview_session, $reached_points); return $this->ensureNonNegativePoints($reached_points); } @@ -730,7 +729,7 @@ public function toJSON(): string $result = []; $result['id'] = $this->getId(); $result['type'] = (string) $this->getQuestionType(); - $result['title'] = $this->getTitle(); + $result['title'] = $this->getTitleForHTMLOutput(); $result['question'] = $this->formatSAQuestion($this->getQuestion()); $result['text'] = ilRTE::_replaceMediaObjectImageSrc($this->getErrorText(), 0); $result['nr_of_tries'] = $this->getNrOfTries(); @@ -939,7 +938,7 @@ public function toLog(AdditionalInformationGenerator $additional_info): array { $result = [ AdditionalInformationGenerator::KEY_QUESTION_TYPE => (string) $this->getQuestionType(), - AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitle(), + AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitleForHTMLOutput(), AdditionalInformationGenerator::KEY_QUESTION_TEXT => $this->formatSAQuestion($this->getQuestion()), AdditionalInformationGenerator::KEY_QUESTION_ERRORTEXT_ERRORTEXT => ilRTE::_replaceMediaObjectImageSrc($this->getErrorText(), 0), AdditionalInformationGenerator::KEY_QUESTION_SHUFFLE_ANSWER_OPTIONS => $additional_info diff --git a/components/ILIAS/TestQuestionPool/classes/class.assErrorTextGUI.php b/components/ILIAS/TestQuestionPool/classes/class.assErrorTextGUI.php index d9e899b5e7cf..e0e7abcf7693 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assErrorTextGUI.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assErrorTextGUI.php @@ -221,6 +221,7 @@ public function analyze(): void $this->writePostData(true); $this->saveTaxonomyAssignments(); $this->object->setErrorsFromParsedErrorText(); + $this->tabs->activateTab('edit_question'); $this->editQuestion(); } diff --git a/components/ILIAS/TestQuestionPool/classes/class.assFileUpload.php b/components/ILIAS/TestQuestionPool/classes/class.assFileUpload.php index 32c03a3bc601..87fd4898d2ba 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assFileUpload.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assFileUpload.php @@ -915,7 +915,7 @@ public function toLog(AdditionalInformationGenerator $additional_info): array { return [ AdditionalInformationGenerator::KEY_QUESTION_TYPE => (string) $this->getQuestionType(), - AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitle(), + AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitleForHTMLOutput(), AdditionalInformationGenerator::KEY_QUESTION_TEXT => $this->formatSAQuestion($this->getQuestion()), AdditionalInformationGenerator::KEY_QUESTION_REACHABLE_POINTS => $this->getPoints(), AdditionalInformationGenerator::KEY_QUESTION_UPLOAD_MAXSIZE => $this->getMaxFilesizeAsString(), diff --git a/components/ILIAS/TestQuestionPool/classes/class.assFormulaQuestion.php b/components/ILIAS/TestQuestionPool/classes/class.assFormulaQuestion.php index 44996848a77f..faa83bc2488e 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assFormulaQuestion.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assFormulaQuestion.php @@ -687,7 +687,6 @@ public function loadFromDb(int $question_id): void $this->setId($question_id); $this->setTitle((string) $data["title"]); $this->setComment((string) $data["description"]); - //$this->setSuggestedSolution($data["solution_hint"]); $this->setPoints($data['points']); $this->setOriginalId($data["original_id"]); $this->setObjId($data["obj_fi"]); @@ -822,7 +821,9 @@ public function calculateReachedPoints( if (!array_key_exists($matches[1], $user_solution)) { $user_solution[$matches[1]] = []; } - $user_solution[$matches[1]]['unit'] = $solution_value['value2']; + $user_solution[$matches[1]]['unit'] = $this->unitrepository->getUnit( + $this->refinery->kindlyTo()->int()->transform($solution_value['value2']), + ); } } @@ -832,7 +833,7 @@ public function calculateReachedPoints( $this->getVariables(), $this->getResults(), $user_solution[$result->getResult()]['value'] ?? '', - $user_solution[$result->getResult()]['unit'] ?? '', + $user_solution[$result->getResult()]['unit'] ?? null, $this->unitrepository->getUnits() ); } @@ -846,21 +847,16 @@ public function calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSes $points = 0; foreach ($this->getResults() as $result) { - $v = isset($user_solution[$result->getResult()]) ? $user_solution[$result->getResult()] : null; - $u = isset($user_solution[$result->getResult() . '_unit']) ? $user_solution[$result->getResult() . '_unit'] : null; - + $unit_id = $user_solution[$result->getResult() . '_unit'] ?? null; $points += $result->getReachedPoints( $this->getVariables(), $this->getResults(), - $v, - $u, + $user_solution[$result->getResult()] ?? '', + $unit_id !== null ? $this->unitrepository->getUnit($unit_id) : null, $this->unitrepository->getUnits() ); } - - $reachedPoints = $this->deductHintPointsFromReachedPoints($previewSession, $points); - - return $this->ensureNonNegativePoints($reachedPoints); + return $this->ensureNonNegativePoints($points); } protected function isValidSolutionResultValue(string $submittedValue): bool @@ -1115,7 +1111,7 @@ public function getBestSolution(array $solutions): array $check_unit = false; if (array_key_exists($result_name, $available_units) && $available_units[$result_name] !== null) { - $check_unit = in_array($user_solution[$result_name]['unit'], $available_units[$result_name]); + $check_unit = in_array($user_solution[$result_name]['unit'] ?? null, $available_units[$result_name]); } if ($check_unit == true) { @@ -1278,7 +1274,7 @@ public function toLog(AdditionalInformationGenerator $additional_info): array { return [ AdditionalInformationGenerator::KEY_QUESTION_TYPE => (string) $this->getQuestionType(), - AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitle(), + AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitleForHTMLOutput(), AdditionalInformationGenerator::KEY_QUESTION_TEXT => $this->formatSAQuestion($this->getQuestion()), AdditionalInformationGenerator::KEY_QUESTION_FORMULA_VARIABLES => $this->buildVariablesForLog( $this->getVariables(), diff --git a/components/ILIAS/TestQuestionPool/classes/class.assFormulaQuestionGUI.php b/components/ILIAS/TestQuestionPool/classes/class.assFormulaQuestionGUI.php index dc4e3d497111..51de98df7517 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assFormulaQuestionGUI.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assFormulaQuestionGUI.php @@ -189,6 +189,7 @@ protected function writePostData(bool $always = false): int $this->editQuestion(); return 1; } else { + $this->saveTaxonomyAssignments(); $this->resetSavedPreviewSession(); return 0; } diff --git a/components/ILIAS/TestQuestionPool/classes/class.assFormulaQuestionResult.php b/components/ILIAS/TestQuestionPool/classes/class.assFormulaQuestionResult.php index 9635ced7a050..d45c8f00d9c0 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assFormulaQuestionResult.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assFormulaQuestionResult.php @@ -16,6 +16,8 @@ * *********************************************************************/ +use ILIAS\Refinery\Factory as Refinery; + /** * Formula Question Result * @author Helmut Schottmüller @@ -29,7 +31,8 @@ class assFormulaQuestionResult public const RESULT_FRAC = 2; public const RESULT_CO_FRAC = 3; - private \ilGlobalTemplateInterface $main_tpl; + private ilGlobalTemplateInterface $main_tpl; + private Refinery $refinery; private $available_units = []; private ?float $range_min = null; @@ -52,6 +55,7 @@ public function __construct( ) { global $DIC; $this->main_tpl = $DIC->ui()->mainTemplate(); + $this->refinery = $DIC->refinery(); $this->setRangeMin($range_min_txt); $this->setRangeMax($range_max_txt); @@ -103,7 +107,7 @@ public function calculateFormula($variables, $results, $question_id = 0, $use_pr if (preg_match_all("/(\\\$v\\d+)/ims", $formula, $matches)) { foreach ($matches[1] as $variable) { $varObj = $variables[$variable]; - if (!is_object($varObj)) { + if (!is_object($varObj) || !is_numeric($varObj->getValue())) { continue; } $value = $varObj->getBaseValue(); @@ -225,7 +229,7 @@ public function isCorrect($variables, $results, $value, $unit = null): bool if (preg_match_all("/(\\\$v\\d+)/ims", $formula, $matches)) { foreach ($matches[1] as $variable) { $varObj = $variables[$variable]; - if (!is_object($varObj)) { + if (!is_object($varObj) || !is_numeric($varObj->getValue())) { continue; } @@ -400,131 +404,102 @@ protected function isInTolerance($user_answer, $expected, $tolerated_percentage) && $user_answer <= $upper_boundary; } - protected function checkSign($v1, $v2): bool + private function checkSign(float $v1, float $v2): bool { - if ((($v1 >= 0) && ($v2 >= 0)) || (($v1 <= 0) && ($v2 <= 0))) { - return true; - } else { - return false; - } + return ($v1 >= 0.0 && $v2 >= 0.0) || ($v1 <= 0.0 && $v2 <= 0.0); } - public function getReachedPoints($variables, $results, $value, $unit, $units) - { + /** + * @param assFormulaQuestionUnit[] $units + */ + public function getReachedPoints( + array $variables, + array $results, + string $answer_value, + ?assFormulaQuestionUnit $answer_unit, + array $units + ): float { if ($this->getRatingSimple()) { - if ($this->isCorrect($variables, $results, $value, $units[$unit] ?? null)) { - return $this->getPoints(); - } else { - return 0; + return $this->isCorrect($variables, $results, $answer_value, $answer_unit) + ? $this->getPoints() + : 0.0; + } + + $result = $this->calculateCorrectResult($variables, $results); + $float_value = $this->transformAnswerValueAccordingToType($answer_value, $answer_unit); + + $points = 0.0; + if ($answer_unit instanceof assFormulaQuestionUnit && $answer_unit instanceof assFormulaQuestionUnit) { + $base1 = $units[$answer_unit->getBaseUnit()] ?? null; + $base2 = $units[$answer_unit->getBaseUnit()] ?? null; + if ( + $base1 instanceof assFormulaQuestionUnit + && $base2 instanceof assFormulaQuestionUnit + && $base1->getId() === $base2->getId() + ) { + $points += ilMath::_mul($this->getPoints(), ilMath::_div($this->getRatingUnit(), 100)); } - } else { - $points = 0; - $formula = $this->substituteFormula($variables, $results); + } - if (preg_match_all("/(\\\$v\\d+)/ims", $formula, $matches)) { - foreach ($matches[1] as $variable) { - $varObj = $variables[$variable]; - if (!is_object($varObj)) { - continue; - } - if ($varObj->getUnit() != null) { - //convert unit and value to baseunit - if ($varObj->getUnit()->getBaseUnit() != -1) { - $tmp_value = $varObj->getValue() * $varObj->getUnit()->getFactor(); - } else { - $tmp_value = $varObj->getValue(); - } - } else { - $tmp_value = $varObj->getValue(); - } - $formula = preg_replace("/\\\$" . substr($variable, 1) . "(?![0-9]+)/", "(" . $tmp_value . ")" . "\\1", $formula); - } - } + if ($float_value === null) { + return $points; + } - $math = new EvalMath(); - $math->suppress_errors = true; - $result = $math->evaluate($formula); + if ($this->checkSign($result, $float_value)) { + $points += ilMath::_mul($this->getPoints(), ilMath::_div($this->getRatingSign(), 100)); + } - // result_type extension - switch ($this->getResultType()) { - case assFormulaQuestionResult::RESULT_DEC: - if ((substr_count($value, '.') == 1) || (substr_count($value, ',') == 1)) { - $exp_val = $value; - $frac_value = str_replace(',', '.', $exp_val); - } else { - $frac_value = $value; - } - $check_fraction = true; - break; - case assFormulaQuestionResult::RESULT_FRAC: - $exp_val = explode('/', $value); - if (count($exp_val) == 1) { - $frac_value = ilMath::_div($exp_val[0], 1, $this->getPrecision()); - if (ilMath::_equals(abs($frac_value), abs($result), $this->getPrecision())) { - $check_fraction = true; - } else { - $check_fraction = false; - } - } else { - $frac_value = ilMath::_div($exp_val[0], $exp_val[1], $this->getPrecision()); - if (ilMath::_equals(abs($frac_value), abs($result), $this->getPrecision())) { - $check_fraction = true; - } - } - break; - case assFormulaQuestionResult::RESULT_CO_FRAC: - $exp_val = explode('/', $value); - if (count($exp_val) == 1) { - $check_fraction = false; - } else { - $frac_value = ilMath::_div($exp_val[0], $exp_val[1], $this->getPrecision()); - if (self::isCoprimeFraction($exp_val[0], $exp_val[1])) { - $check_fraction = true; - } - } - break; - case assFormulaQuestionResult::RESULT_NO_SELECTION: - default: - $check_fraction = true; - break; - } + if ($this->isInTolerance(abs($float_value), abs($result), $this->getTolerance())) { + $points += ilMath::_mul($this->getPoints(), ilMath::_div($this->getRatingValue(), 100)); + } - // result unit!! - if ($this->getUnit() !== null) { - // if expected resultunit != baseunit convert to resultunit - if ($this->getUnit()->getBaseUnit() != -1) { - $result = ilMath::_div($result, $this->getUnit()->getFactor(), $this->getPrecision()); - } else { - //if resultunit == baseunit calculate to get correct precision - $result = ilMath::_mul($result, $this->getUnit()->getFactor(), $this->getPrecision()); - } - } + return $points; + } - if (is_object($unit)) { - if (isset($frac_value)) { - $value = ilMath::_mul($frac_value, $unit->getFactor(), 100); - } - } + private function calculateCorrectResult(array $variables, array $results): float + { + return round( + $this->calculateFormula($variables, $results), + $this->precision + ); + } - if ($this->checkSign($result, $value)) { - $points += ilMath::_mul($this->getPoints(), ilMath::_div($this->getRatingSign(), 100)); - } + private function transformAnswerValueAccordingToType( + string $value, + ?assFormulaQuestionUnit $unit + ): ?float { + switch ($this->getResultType()) { + case self::RESULT_DEC: + break; + case self::RESULT_FRAC: + case self::RESULT_CO_FRAC: + $exp_val = explode('/', $value); + $value = ilMath::_div( + $exp_val[0], + count($exp_val) === 1 ? 1 : $exp_val[1], + $this->getPrecision() + ); + break; + case self::RESULT_NO_SELECTION: + default: + $exp_val = explode('/', $value); - if ($this->isInTolerance(abs($value), abs($result), $this->getTolerance())) { - $points += ilMath::_mul($this->getPoints(), ilMath::_div($this->getRatingValue(), 100)); - } - if ($this->getUnit() !== null) { - $base1 = $units[$unit] ?? null; - if (is_object($base1)) { - $base1 = $units[$base1->getBaseUnit()]; - } - $base2 = $units[$this->getUnit()->getBaseUnit()]; - if (is_object($base1) && is_object($base2) && $base1->getId() == $base2->getId()) { - $points += ilMath::_mul($this->getPoints(), ilMath::_div($this->getRatingUnit(), 100)); + if (count($exp_val) === 2 + && (float) $exp_val[1] === 0.0) { + return null; } - } - return $points; + + $value = ilMath::_div( + $exp_val[0], + count($exp_val) === 1 ? 1 : $exp_val[1], + 100 + ); } + + return $this->refinery->byTrying([ + $this->refinery->kindlyTo()->float(), + $this->refinery->always(null), + ])->transform(round($value, $this->precision)); } public function getResultInfo($variables, $results, $value, $unit, $units): array diff --git a/components/ILIAS/TestQuestionPool/classes/class.assImagemapQuestion.php b/components/ILIAS/TestQuestionPool/classes/class.assImagemapQuestion.php index d3d19e619ccf..75cab3fd676e 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assImagemapQuestion.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assImagemapQuestion.php @@ -445,9 +445,7 @@ public function calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSes $reached_points = $this->calculateReachedPointsForSolution(is_array($solution_data) ? array_values($solution_data) : []); - return $this->ensureNonNegativePoints( - $this->deductHintPointsFromReachedPoints($preview_session, $reached_points) - ); + return $this->ensureNonNegativePoints($reached_points); } /** @@ -613,7 +611,7 @@ public function toJSON(): string $result = []; $result['id'] = $this->getId(); $result['type'] = (string) $this->getQuestionType(); - $result['title'] = $this->getTitle(); + $result['title'] = $this->getTitleForHTMLOutput(); $result['question'] = $this->formatSAQuestion($this->getQuestion()); $result['nr_of_tries'] = $this->getNrOfTries(); $result['shuffle'] = $this->getShuffle(); @@ -820,7 +818,7 @@ public function toLog(AdditionalInformationGenerator $additional_info): array { $result = [ AdditionalInformationGenerator::KEY_QUESTION_TYPE => (string) $this->getQuestionType(), - AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitle(), + AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitleForHTMLOutput(), AdditionalInformationGenerator::KEY_QUESTION_TEXT => $this->formatSAQuestion($this->getQuestion()), AdditionalInformationGenerator::KEY_QUESTION_SHUFFLE_ANSWER_OPTIONS => $additional_info ->getTrueFalseTagForBool($this->getShuffle()), diff --git a/components/ILIAS/TestQuestionPool/classes/class.assKprimChoice.php b/components/ILIAS/TestQuestionPool/classes/class.assKprimChoice.php index f11d7e944cd1..d4021da98f9a 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assKprimChoice.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assKprimChoice.php @@ -714,7 +714,7 @@ public function toJSON(): string $result = []; $result['id'] = $this->getId(); $result['type'] = $this->getQuestionType(); - $result['title'] = $this->getTitle(); + $result['title'] = $this->getTitleForHTMLOutput(); $result['question'] = $this->formatSAQuestion($this->getQuestion()); $result['instruction'] = $this->getInstructionTextTranslation( $this->lng, @@ -825,7 +825,7 @@ public function toLog(AdditionalInformationGenerator $additional_info): array { $result = [ AdditionalInformationGenerator::KEY_QUESTION_TYPE => (string) $this->getQuestionType(), - AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitle(), + AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitleForHTMLOutput(), AdditionalInformationGenerator::KEY_QUESTION_TEXT => $this->formatSAQuestion($this->getQuestion()), AdditionalInformationGenerator::KEY_QUESTION_KPRIM_OPTION_LABEL => $additional_info ->getTagForLangVar($this->getLangVarForOptionLabel($this->getOptionLabel())), diff --git a/components/ILIAS/TestQuestionPool/classes/class.assLongMenu.php b/components/ILIAS/TestQuestionPool/classes/class.assLongMenu.php index 2e8fb845caa8..81dde64d6bad 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assLongMenu.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assLongMenu.php @@ -702,7 +702,7 @@ public function toJSON(): string $result = []; $result['id'] = $this->getId(); $result['type'] = (string) $this->getQuestionType(); - $result['title'] = $this->getTitle(); + $result['title'] = $this->getTitleForHTMLOutput(); $result['question'] = $this->formatSAQuestion($this->getQuestion()); $replaced_quesiton_text = $this->getLongMenuTextValue(); $result['lmtext'] = $this->formatSAQuestion($replaced_quesiton_text); @@ -734,7 +734,7 @@ public function toLog(AdditionalInformationGenerator $additional_info): array { return [ AdditionalInformationGenerator::KEY_QUESTION_TYPE => (string) $this->getQuestionType(), - AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitle(), + AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitleForHTMLOutput(), AdditionalInformationGenerator::KEY_QUESTION_TEXT => $this->formatSAQuestion($this->getQuestion()), AdditionalInformationGenerator::KEY_QUESTION_LONGMENU_TEXT => $this->formatSAQuestion($this->getLongMenuTextValue()), AdditionalInformationGenerator::KEY_QUESTION_SHUFFLE_ANSWER_OPTIONS => $additional_info diff --git a/components/ILIAS/TestQuestionPool/classes/class.assMatchingQuestion.php b/components/ILIAS/TestQuestionPool/classes/class.assMatchingQuestion.php index b906bd138724..fa5c34243f11 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assMatchingQuestion.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assMatchingQuestion.php @@ -1115,7 +1115,7 @@ public function toJSON(): string $result['id'] = $this->getId(); $result['type'] = (string) $this->getQuestionType(); - $result['title'] = $this->getTitle(); + $result['title'] = $this->getTitleForHTMLOutput(); $result['question'] = $this->formatSAQuestion($this->getQuestion()); $result['nr_of_tries'] = $this->getNrOfTries(); $result['matching_mode'] = $this->getMatchingMode(); @@ -1346,7 +1346,7 @@ public function toLog(AdditionalInformationGenerator $additional_info): array { $result = [ AdditionalInformationGenerator::KEY_QUESTION_TYPE => (string) $this->getQuestionType(), - AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitle(), + AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitleForHTMLOutput(), AdditionalInformationGenerator::KEY_QUESTION_TEXT => $this->formatSAQuestion($this->getQuestion()), AdditionalInformationGenerator::KEY_QUESTION_SHUFFLE_ANSWER_OPTIONS => $additional_info ->getTrueFalseTagForBool($this->getShuffle()), diff --git a/components/ILIAS/TestQuestionPool/classes/class.assMultipleChoice.php b/components/ILIAS/TestQuestionPool/classes/class.assMultipleChoice.php index d617932b94c4..5de214063b0c 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assMultipleChoice.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assMultipleChoice.php @@ -95,11 +95,11 @@ public function setSelectionLimit(?int $selection_limit): void public function isComplete(): bool { - if (strlen($this->title) and ($this->author) and ($this->question) and (count($this->answers)) and ($this->getMaximumPoints() > 0)) { - return true; - } else { - return false; - } + return $this->title !== '' + && $this->author !== '' + && $this->question !== '' + && $this->getAnswerCount() > 0 + && $this->getMaximumPoints() >= 0; } public function saveToDb(?int $original_id = null): void @@ -335,16 +335,11 @@ public function flushAnswers(): void */ public function getMaximumPoints(): float { - $points = 0; - $allpoints = 0; - foreach ($this->answers as $key => $value) { - if ($value->getPoints() > $value->getPointsUnchecked()) { - $allpoints += $value->getPoints(); - } else { - $allpoints += $value->getPointsUnchecked(); - } + $total_max_points = 0.0; + foreach ($this->getAnswers() as $answer) { + $total_max_points += max($answer->getPointsChecked(), $answer->getPointsUnchecked()); } - return $allpoints; + return $total_max_points; } public function calculateReachedPoints( @@ -503,7 +498,7 @@ public function saveAnswerSpecificDataToDb(): void // Reorder feedback $feedback_order_db = intval($feedback_option['answer']); - $db_answer_id = $db_answer_id_for_order[$feedback_order_db]; + $db_answer_id = $db_answer_id_for_order[$feedback_order_db] ?? null; // This cuts feedback that currently would have no corresponding answer // This case can happen while copying "broken" questions // Or when saving a question with less answers than feedback @@ -672,7 +667,7 @@ public function toJSON(): string $result = []; $result['id'] = $this->getId(); $result['type'] = (string) $this->getQuestionType(); - $result['title'] = $this->getTitle(); + $result['title'] = $this->getTitleForHTMLOutput(); $result['question'] = $this->formatSAQuestion($this->getQuestion()); $result['nr_of_tries'] = $this->getNrOfTries(); $result['shuffle'] = $this->getShuffle(); @@ -885,7 +880,7 @@ public function toLog(AdditionalInformationGenerator $additional_info): array { $result = [ AdditionalInformationGenerator::KEY_QUESTION_TYPE => (string) $this->getQuestionType(), - AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitle(), + AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitleForHTMLOutput(), AdditionalInformationGenerator::KEY_QUESTION_TEXT => $this->formatSAQuestion($this->getQuestion()), AdditionalInformationGenerator::KEY_QUESTION_SHUFFLE_ANSWER_OPTIONS => $additional_info ->getTrueFalseTagForBool($this->getShuffle()), diff --git a/components/ILIAS/TestQuestionPool/classes/class.assMultipleChoiceGUI.php b/components/ILIAS/TestQuestionPool/classes/class.assMultipleChoiceGUI.php index 61d1ad37ced2..3152d30871e1 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assMultipleChoiceGUI.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assMultipleChoiceGUI.php @@ -126,7 +126,7 @@ public function editQuestion( $form->getItemByPostVar('selection_limit')->setMaxValue(count($this->request_data_collector->raw('choice')['answer'] ?? [])); $form->setValuesByPost(); - $errors = !$form->checkInput(); + $errors = !$this->checkMaxPointsNotNegative($form) || !$form->checkInput(); if ($errors) { $checkonly = false; } @@ -138,6 +138,28 @@ public function editQuestion( return $errors; } + private function checkMaxPointsNotNegative(ilPropertyFormGUI $form): bool + { + $choice = $form->getItemByPostVar('choice'); + if (!$choice instanceof ilMultipleChoiceWizardInputGUI) { + return true; + } + + $answers = $choice->getValues(); + $total_max_points = 0; + /** @var ASS_AnswerMultipleResponseImage $answer */ + foreach ($answers as $answer) { + $total_max_points += max($answer->getPointsChecked(), $answer->getPointsUnchecked()); + } + + if ($total_max_points < 0) { + $choice->setAlert($this->lng->txt('total_max_points_cannot_be_negative')); + return false; + } + + return true; + } + public function addBasicQuestionFormProperties(ilPropertyFormGUI $form): void { parent::addBasicQuestionFormProperties($form); @@ -246,7 +268,7 @@ public function renderSolutionOutput( $ok = false; $checked = false; foreach ($user_solution as $mc_solution) { - if (strcmp($mc_solution, $answer_id) == 0) { + if ((string) $mc_solution === (string) $answer_id) { $checked = true; } } @@ -290,7 +312,7 @@ public function renderSolutionOutput( if (($show_feedback || !$this->isTestPresentationContext()) && $show_inline_feedback) { if ($this->object->getSpecificFeedbackSetting() == 2) { foreach ($user_solution as $mc_solution) { - if (strcmp($mc_solution, $answer_id) == 0) { + if ((string) $mc_solution === (string) $answer_id) { $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation( $this->object->getId(), 0, @@ -350,7 +372,7 @@ public function renderSolutionOutput( $template->setVariable("RESULT_OUTPUT", sprintf("(" . $this->lng->txt("checkbox_checked") . " = $resulttextchecked, " . $this->lng->txt("checkbox_unchecked") . " = $resulttextunchecked)", $pointschecked, $pointsunchecked)); } foreach ($user_solution as $mc_solution) { - if (strcmp($mc_solution, $answer_id) == 0) { + if ((string) $mc_solution === (string) $answer_id) { if ($this->renderPurposeSupportsFormHtml() || $this->isRenderPurposePrintPdf()) { $template->setVariable("SOLUTION_IMAGE", ilUtil::getHtmlPath(ilUtil::getImagePath("object/checkbox_checked.png"))); $template->setVariable("SOLUTION_ALT", $this->lng->txt("checked")); @@ -458,7 +480,7 @@ public function getPreview( $template->setVariable("ANSWER_ID", $answer_id); $template->setVariable("ANSWER_TEXT", ilLegacyFormElementsUtil::prepareTextareaOutput($answer->getAnswertext(), true)); foreach ($user_solution as $mc_solution) { - if (strcmp($mc_solution, $answer_id) == 0) { + if ((string) $mc_solution === (string) $answer_id) { $template->setVariable("CHECKED_ANSWER", " checked=\"checked\""); } } @@ -567,7 +589,7 @@ public function getTestOutput( $template->setVariable("ANSWER_ID", $answer_id); $template->setVariable("ANSWER_TEXT", ilLegacyFormElementsUtil::prepareTextareaOutput($answer->getAnswertext(), true)); foreach ($user_solution as $mc_solution) { - if (strcmp($mc_solution, $answer_id) == 0) { + if ((string) $mc_solution === (string) $answer_id) { $template->setVariable("CHECKED_ANSWER", " checked=\"checked\""); } } @@ -882,7 +904,7 @@ private function populateSpecificFeedbackInline($user_solution, $answer_id, $tem { if ($this->object->getSpecificFeedbackSetting() == 2) { foreach ($user_solution as $mc_solution) { - if (strcmp($mc_solution, $answer_id) == 0) { + if ((string) $mc_solution === (string) $answer_id) { $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation($this->object->getId(), 0, $answer_id); if (strlen($fb)) { $template->setCurrentBlock("feedback"); diff --git a/components/ILIAS/TestQuestionPool/classes/class.assNumeric.php b/components/ILIAS/TestQuestionPool/classes/class.assNumeric.php index ff7a8d7ed7ff..173a6465f25a 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assNumeric.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assNumeric.php @@ -146,8 +146,6 @@ public function calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSes $points = $this->getPoints(); } - $reachedPoints = $this->deductHintPointsFromReachedPoints($previewSession, $points); - return $this->ensureNonNegativePoints($reachedPoints); } @@ -421,7 +419,7 @@ public function toLog(AdditionalInformationGenerator $additional_info): array { return [ AdditionalInformationGenerator::KEY_QUESTION_TYPE => (string) $this->getQuestionType(), - AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitle(), + AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitleForHTMLOutput(), AdditionalInformationGenerator::KEY_QUESTION_TEXT => $this->formatSAQuestion($this->getQuestion()), AdditionalInformationGenerator::KEY_QUESTION_SHUFFLE_ANSWER_OPTIONS => $additional_info ->getTrueFalseTagForBool($this->getShuffle()), diff --git a/components/ILIAS/TestQuestionPool/classes/class.assOrderingHorizontal.php b/components/ILIAS/TestQuestionPool/classes/class.assOrderingHorizontal.php index 55f0956ba079..e13b31f39d39 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assOrderingHorizontal.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assOrderingHorizontal.php @@ -399,7 +399,7 @@ public function toJSON(): string $result = []; $result['id'] = $this->getId(); $result['type'] = (string) $this->getQuestionType(); - $result['title'] = $this->getTitle(); + $result['title'] = $this->getTitleForHTMLOutput(); $result['question'] = $this->formatSAQuestion($this->getQuestion()); $result['nr_of_tries'] = $this->getNrOfTries(); $result['shuffle'] = true; @@ -534,7 +534,7 @@ public function toLog(AdditionalInformationGenerator $additional_info): array { return [ AdditionalInformationGenerator::KEY_QUESTION_TYPE => (string) $this->getQuestionType(), - AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitle(), + AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitleForHTMLOutput(), AdditionalInformationGenerator::KEY_QUESTION_TEXT => $this->formatSAQuestion($this->getQuestion()), AdditionalInformationGenerator::KEY_QUESTION_TEXTSIZE => ((int) $this->getTextSize()) ? (int) $this->getTextSize() : 100, AdditionalInformationGenerator::KEY_QUESTION_CORRECT_ANSWER_OPTIONS => $this->getOrderText(), diff --git a/components/ILIAS/TestQuestionPool/classes/class.assOrderingQuestion.php b/components/ILIAS/TestQuestionPool/classes/class.assOrderingQuestion.php index ef1e9618c843..c02c7e6b4208 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assOrderingQuestion.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assOrderingQuestion.php @@ -168,6 +168,7 @@ protected function cloneQuestionTypeSpecificProperties( $list->distributeNewRandomIdentifiers(); $target->setOrderingElementList($list); $this->cloneImages($this->getId(), $this->getObjId(), $target->getId(), $target->getObjId()); + $target->saveToDb(); return $target; } @@ -501,10 +502,7 @@ public function calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSes ['allowed_classes' => true] ); - $reached_points = $this->deductHintPointsFromReachedPoints( - $preview_session, - $this->calculateReachedPointsForSolution($solution_ordering_element_list) - ); + $reached_points = $this->calculateReachedPointsForSolution($solution_ordering_element_list); return $this->ensureNonNegativePoints($reached_points); } @@ -839,7 +837,7 @@ public function toJSON(): string $result = []; $result['id'] = $this->getId(); $result['type'] = (string) $this->getQuestionType(); - $result['title'] = $this->getTitle(); + $result['title'] = $this->getTitleForHTMLOutput(); $result['question'] = $this->formatSAQuestion($this->getQuestion()); $result['nr_of_tries'] = $this->getNrOfTries(); $result['shuffle'] = true; @@ -1261,7 +1259,7 @@ public function toLog(AdditionalInformationGenerator $additional_info): array { return [ AdditionalInformationGenerator::KEY_QUESTION_TYPE => (string) $this->getQuestionType(), - AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitle(), + AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitleForHTMLOutput(), AdditionalInformationGenerator::KEY_QUESTION_TEXT => $this->formatSAQuestion($this->getQuestion()), AdditionalInformationGenerator::KEY_QUESTION_ORDERING_NESTING_TYPE => array_reduce( $this->getOrderingTypeLangVars($this->getOrderingType()), diff --git a/components/ILIAS/TestQuestionPool/classes/class.assQuestion.php b/components/ILIAS/TestQuestionPool/classes/class.assQuestion.php index 1cce7bf67720..dd4cd671e4ad 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assQuestion.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assQuestion.php @@ -262,8 +262,7 @@ final public function fromXML( ?int $tst_id, ?ilObject &$tst_object, int &$question_counter, - array $import_mapping, - array $solutionhints = [] + array $import_mapping ): array { $classname = $this->getQuestionType() . "Import"; $import = new $classname($this); @@ -277,34 +276,9 @@ final public function fromXML( $question_counter, $import_mapping ); - - foreach ($solutionhints as $hint) { - $this->importHint($import->getQuestionId(), $hint); - } - return $new_import_mapping; } - private function importHint(int $question_id, array $hint_array): void - { - $hint = new ilAssQuestionHint(); - $hint->setQuestionId($question_id); - $hint->setIndex($hint_array['index'] ?? ''); - $hint->setPoints($hint_array['points'] ?? ''); - if ($this->getAdditionalContentEditingMode() === self::ADDITIONAL_CONTENT_EDITING_MODE_IPE) { - $hint->save(); - $hint_page = (new ilAssHintPage()); - $hint_page->setParentId($question_id); - $hint_page->setId($hint->getId()); - $hint_page->setXMLContent($hint_array['txt']); - $hint_page->createFromXML(); - return; - } - - $hint->setText($hint_array['txt'] ?? ''); - $hint->save(); - } - /** * Returns a QTI xml representation of the question * @@ -367,7 +341,7 @@ public function getTitle(): string public function getTitleForHTMLOutput(): string { - return $this->refinery->string()->stripTags()->transform($this->title); + return $this->refinery->encode()->htmlSpecialCharsAsEntities()->transform($this->title); } public function getTitleFilenameCompliant(): string @@ -512,10 +486,12 @@ public function getSuggestedSolutionOutput(): string ); ilWACSignedPath::setTokenMaxLifetimeInSeconds(60); + $path_to_solution = $this->getSuggestedSolutionPathWeb() . $solution->getFilename(); + if (!file_exists($path_to_solution)) { + break; + } $output[] = '' . $possible_texts[0] . ''; @@ -563,10 +539,6 @@ final public function getAdjustedReachedPoints(int $active_id, int $pass, bool $ { // determine reached points for submitted solution $reached_points = $this->calculateReachedPoints($active_id, $pass, $authorized_solution); - $hint_tracking = new ilAssQuestionHintTracking($this->getId(), $active_id, $pass); - $requests_statistic_data = $hint_tracking->getRequestStatisticDataByQuestionAndTestpass(); - $reached_points = $reached_points - $requests_statistic_data->getRequestsPoints(); - // adjust reached points regarding to tests scoring options $reached_points = $this->adjustReachedPointsByScoringOptions($reached_points, $active_id); @@ -580,9 +552,6 @@ final public function calculateResultsFromSolution(int $active_id, int $pass): v { // determine reached points for submitted solution $reached_points = $this->calculateReachedPoints($active_id, $pass); - $questionHintTracking = new ilAssQuestionHintTracking($this->getId(), $active_id, $pass); - $requests_statistic_data = $questionHintTracking->getRequestStatisticDataByQuestionAndTestpass(); - $reached_points = $reached_points - $requests_statistic_data->getRequestsPoints(); // adjust reached points regarding to tests scoring options $reached_points = $this->adjustReachedPointsByScoringOptions($reached_points, $active_id); @@ -595,7 +564,7 @@ final public function calculateResultsFromSolution(int $active_id, int $pass): v $existing_solutions = $this->lookupForExistingSolutions($active_id, $pass); $this->getProcessLocker()->executeUserQuestionResultUpdateOperation( - function () use ($active_id, $pass, $reached_points, $requests_statistic_data, $existing_solutions) { + function () use ($active_id, $pass, $reached_points, $existing_solutions) { $query = " DELETE FROM tst_test_result @@ -626,8 +595,6 @@ function () use ($active_id, $pass, $reached_points, $requests_statistic_data, $ 'pass' => ['integer', $pass], 'points' => ['float', $reached_points], 'tstamp' => ['integer', time()], - 'hint_count' => ['integer', $requests_statistic_data->getRequestsCount()], - 'hint_points' => ['float', $requests_statistic_data->getRequestsPoints()], 'answered' => ['integer', true] ]; @@ -959,8 +926,6 @@ public function delete(int $question_id): void } catch (Exception $e) { $this->log->root()->error("EXCEPTION: Error deleting the media objects of question {$question_id}: {$e}"); } - ilAssQuestionHintTracking::deleteRequestsByQuestionIds([$question_id]); - ilAssQuestionHintList::deleteHintsByQuestionIds([$question_id]); $assignmentList = new ilAssQuestionSkillAssignmentList($this->db); $assignmentList->setParentObjId($obj_id); $assignmentList->setQuestionIdFilter($question_id); @@ -1064,8 +1029,9 @@ public function clonePageOfQuestion(int $a_q_id): void { if ($a_q_id > 0) { $page = new ilAssQuestionPage($a_q_id); - - $xml = str_replace("il__qst_" . $a_q_id, "il__qst_" . $this->id, $page->getXMLContent()); + $page->buildDom(); + ilPCPlugged::handleCopiedPluggedContent($page, $page->getDomDoc()); + $xml = str_replace("il__qst_" . $a_q_id, "il__qst_" . $this->id, $page->getXMLFromDom()); $this->page->setXMLContent($xml); $this->page->updateFromXML(); } @@ -1445,7 +1411,6 @@ protected function onDuplicate( $this->copySuggestedSolutions($duplicate_question_id); $this->cloneSuggestedSolutionFiles($original_parent_id, $original_question_id); $this->feedbackOBJ->duplicateFeedback($original_question_id, $duplicate_question_id); - $this->duplicateQuestionHints($original_question_id, $duplicate_question_id); $this->duplicateSkillAssignments($original_parent_id, $original_question_id, $duplicate_parent_id, $duplicate_question_id); $this->duplicateComments($original_parent_id, $original_question_id, $duplicate_parent_id, $duplicate_question_id); } @@ -1464,7 +1429,6 @@ protected function onCopy(int $sourceParentId, int $sourceQuestionId, int $targe $this->copySuggestedSolutions($targetQuestionId); $this->duplicateSuggestedSolutionFiles($sourceParentId, $sourceQuestionId); $this->feedbackOBJ->duplicateFeedback($sourceQuestionId, $targetQuestionId); - $this->duplicateQuestionHints($sourceQuestionId, $targetQuestionId); $this->duplicateSkillAssignments($sourceParentId, $sourceQuestionId, $targetParentId, $targetQuestionId); $this->duplicateComments($sourceParentId, $sourceQuestionId, $targetParentId, $targetQuestionId); } @@ -1761,7 +1725,6 @@ public function syncWithOriginal(): void $original = $this->cloneQuestionTypeSpecificProperties($original); $this->cloneXHTMLMediaObjectsOfQuestion($original->getId()); $this->afterSyncWithOriginal($this->getOriginalId(), $this->getId(), $this->getObjId(), $original_parent_id); - $this->cloneHints($this->id, $this->original_id); } /** @@ -1834,20 +1797,9 @@ public function isWriteable(): bool return ilObjQuestionPool::_isWriteable($this->getObjId(), $this->getCurrentUser()->getId()); } - public function deductHintPointsFromReachedPoints(ilAssQuestionPreviewSession $preview_session, $reached_points): ?float - { - $hint_tracking = new ilAssQuestionPreviewHintTracking($this->db, $preview_session); - $requests_statistic_data = $hint_tracking->getRequestStatisticData(); - return $reached_points - $requests_statistic_data->getRequestsPoints(); - } - public function calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSession $preview_session) { - $reached_points = $this->deductHintPointsFromReachedPoints( - $preview_session, - $this->calculateReachedPointsForSolution($preview_session->getParticipantsSolution()) - ); - + $reached_points = $this->calculateReachedPointsForSolution($preview_session->getParticipantsSolution()); return $this->ensureNonNegativePoints($reached_points); } @@ -1925,6 +1877,8 @@ public static function _setReachedPoints( ): void { global $DIC; $ilDB = $DIC['ilDB']; + /** @var ILIAS\TestQuestionPool\Questions\GeneralQuestionPropertiesRepository $question_properties_repository */ + $question_properties_repository = QuestionPoolDIC::dic()['question.general_properties.repository']; if ($points > $maxpoints) { return; @@ -1965,12 +1919,13 @@ public static function _setReachedPoints( return; } - $test_id = ilObjTest::_lookupTestObjIdForQuestionId($question_id); - if ($test_id === null) { + $test_obj_id = $question_properties_repository->getForQuestionId($question_id) + ->getParentObjectId(); + if ($test_obj_id === null) { return; } $test = new ilObjTest( - $test_id, + $test_obj_id, false ); $test->updateTestPassResults($active_id, $pass); @@ -2020,44 +1975,6 @@ public function getQuestionTypeID(): int return 0; } - public function cloneHints( - int $source_question_id, - int $target_question_id - ): void { - // delete hints of the original - $this->db->manipulateF( - "DELETE FROM qpl_hints WHERE qht_question_fi = %s", - ['integer'], - [$target_question_id] - ); - - // get hints of the actual question - $result = $this->db->queryF( - "SELECT * FROM qpl_hints WHERE qht_question_fi = %s", - ['integer'], - [$source_question_id] - ); - - // save hints to the original - if ($this->db->numRows($result) < 1) { - return; - } - - while ($row = $this->db->fetchAssoc($result)) { - $next_id = $this->db->nextId('qpl_hints'); - $this->db->insert( - 'qpl_hints', - [ - 'qht_hint_id' => ['integer', $next_id], - 'qht_question_fi' => ['integer', $target_question_id], - 'qht_hint_index' => ['integer', $row["qht_hint_index"]], - 'qht_hint_points' => ['float', $row["qht_hint_points"]], - 'qht_hint_text' => ['text', $row["qht_hint_text"]], - ] - ); - } - } - protected function getRTETextWithMediaObjects(): string { // must be called in parent classes. add additional RTE text in the parent @@ -2066,13 +1983,6 @@ protected function getRTETextWithMediaObjects(): string $collected .= $this->feedbackOBJ->getGenericFeedbackContent($this->getId(), false); $collected .= $this->feedbackOBJ->getGenericFeedbackContent($this->getId(), true); $collected .= $this->feedbackOBJ->getAllSpecificAnswerFeedbackContents($this->getId()); - - $questionHintList = ilAssQuestionHintList::getListByQuestionId($this->getId()); - foreach ($questionHintList as $questionHint) { - /* @var $questionHint ilAssQuestionHint */ - $collected .= $questionHint->getText(); - } - return $collected; } @@ -2300,25 +2210,6 @@ public static function lookupParentObjId(int $question_id): ?int return $row['obj_fi'] ?? null; } - protected function duplicateQuestionHints(int $original_question_id, int $duplicate_question_id): void - { - $hintIds = ilAssQuestionHintList::duplicateListForQuestion($original_question_id, $duplicate_question_id); - - if ($this->isAdditionalContentEditingModePageObject()) { - foreach ($hintIds as $originalHintId => $duplicateHintId) { - $this->ensureHintPageObjectExists($originalHintId); - $originalPageObject = new ilAssHintPage($originalHintId); - $originalXML = $originalPageObject->getXMLContent(); - - $duplicatePageObject = new ilAssHintPage(); - $duplicatePageObject->setId($duplicateHintId); - $duplicatePageObject->setParentId($this->getId()); - $duplicatePageObject->setXMLContent($originalXML); - $duplicatePageObject->createFromXML(); - } - } - } - protected function duplicateSkillAssignments(int $srcParentId, int $srcQuestionId, int $trgParentId, int $trgQuestionId): void { $assignmentList = new ilAssQuestionSkillAssignmentList($this->db); @@ -2363,16 +2254,6 @@ public function syncSkillAssignments(int $srcParentId, int $srcQuestionId, int $ $this->duplicateSkillAssignments($srcParentId, $srcQuestionId, $trgParentId, $trgQuestionId); } - public function ensureHintPageObjectExists($pageObjectId): void - { - if (!ilAssHintPage::_exists('qht', $pageObjectId)) { - $pageObject = new ilAssHintPage(); - $pageObject->setParentId($this->getId()); - $pageObject->setId($pageObjectId); - $pageObject->createFromXML(); - } - } - public function isAnswered(int $active_id, int $pass): bool { $numExistingSolutionRecords = assQuestion::getNumExistingSolutionRecords($active_id, $pass, $this->getId()); diff --git a/components/ILIAS/TestQuestionPool/classes/class.assQuestionGUI.php b/components/ILIAS/TestQuestionPool/classes/class.assQuestionGUI.php index 16a862e06f94..6276986107a4 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assQuestionGUI.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assQuestionGUI.php @@ -610,17 +610,9 @@ public function getPresentationJavascripts(): array public function getQuestionTemplate(): void { - // @todo Björn: Maybe this has to be changed for PHP 7/ILIAS 5.2.x (ilObjTestGUI::executeCommand, switch -> default case -> $this->prepareOutput(); already added a template to the CONTENT variable wrapped in a block named content) - if (!$this->tpl->blockExists('content')) { - $this->tpl->addBlockFile("CONTENT", "content", "tpl.il_as_qpl_content.html", "components/ILIAS/TestQuestionPool"); - } - // @todo Björn: Maybe this has to be changed for PHP 7/ILIAS 5.2.x (ilObjTestGUI::executeCommand, switch -> default case -> $this->prepareOutput(); already added a template to the STATUSLINE variable wrapped in a block named statusline) - if (!$this->tpl->blockExists('statusline')) { - $this->tpl->addBlockFile("STATUSLINE", "statusline", "tpl.statusline.html"); - } // @todo Björn: Maybe this has to be changed for PHP 7/ILIAS 5.2.x because ass[XYZ]QuestionGUI::editQuestion is called multiple times if (!$this->tpl->blockExists('adm_content')) { - $this->tpl->addBlockFile("ADM_CONTENT", "adm_content", "tpl.il_as_question.html", "components/ILIAS/TestQuestionPool"); + $this->tpl->addBlockFile('ADM_CONTENT', 'adm_content', 'tpl.il_as_question.html', 'components/ILIAS/TestQuestionPool'); } } @@ -709,22 +701,18 @@ public function saveReturn(): void $old_id = $this->request_data_collector->getQuestionId(); $this->setAdditionalContentEditingModeFromPost(); $result = $this->writePostData(); - if ($result == 0) { - $this->object->getCurrentUser()->setPref("tst_lastquestiontype", $this->object->getQuestionType()); - $this->object->getCurrentUser()->writePref("tst_lastquestiontype", $this->object->getQuestionType()); - $this->object->saveToDb($old_id); + if ($result !== 0) { + $this->tabs_gui->setTabActive('edit_question'); + return; + } - $this->questionrepository->questionExistsInPool($this->object->getOriginalId()); + $this->object->getCurrentUser()->setPref('tst_lastquestiontype', $this->object->getQuestionType()); + $this->object->getCurrentUser()->writePref('tst_lastquestiontype', $this->object->getQuestionType()); + $this->object->saveToDb($old_id); - if (ilSession::get("info") != null) { - $this->tpl->setOnScreenMessage('success', $this->lng->txt("msg_obj_modified"), true); - } else { - $this->tpl->setOnScreenMessage('success', $this->lng->txt("msg_obj_modified"), true); - } - $this->ctrl->redirectByClass(ilAssQuestionPreviewGUI::class, ilAssQuestionPreviewGUI::CMD_SHOW); - } - $tabs = $this->tabs_gui; - $tabs->setTabActive('edit_question'); + $this->questionrepository->questionExistsInPool($this->object->getOriginalId()); + $this->tpl->setOnScreenMessage('success', $this->lng->txt('msg_obj_modified'), true); + $this->ctrl->redirectByClass(ilAssQuestionPreviewGUI::class, ilAssQuestionPreviewGUI::CMD_SHOW); } public function saveQuestion(): bool @@ -1074,7 +1062,7 @@ public function outQuestionType(): string } } - return $this->questionrepository->getForQuestionId($this->object->getId())->getTypeName($this->lng); + return $this->lng->txt($this->object->getQuestionType()); } protected function getTypeOptions(): array @@ -1566,7 +1554,6 @@ protected function setDefaultTabs(ilTabsGUI $tabs_gui): void $this->addTab_Question($tabs_gui); $this->addTab_QuestionFeedback($tabs_gui); - $this->addTab_QuestionHints($tabs_gui); $this->addTab_SuggestedSolution($tabs_gui, static::class); } @@ -1618,31 +1605,6 @@ protected function addTab_QuestionFeedback(ilTabsGUI $tabs): void $tabs->addTarget('feedback', $tabLink, $tabCommands, $this->ctrl->getCmdClass(), ''); } - protected function addTab_QuestionHints(ilTabsGUI $tabs): void - { - switch (strtolower($this->ctrl->getCmdClass())) { - case 'ilassquestionhintsgui': - $tab_commands = self::getCommandsFromClassConstants(ilAssQuestionHintsGUI::class); - break; - - case 'ilassquestionhintgui': - $tab_commands = self::getCommandsFromClassConstants(ilAssQuestionHintGUI::class); - break; - - default: - $tab_commands = []; - } - - $this->ctrl->setParameterByClass(ilAssQuestionHintsGUI::class, 'q_id', $this->object->getId()); - $tabs->addTarget( - 'tst_question_hints_tab', - $this->ctrl->getLinkTargetByClass(ilAssQuestionHintsGUI::class, ilAssQuestionHintsGUI::CMD_SHOW_LIST), - $tab_commands, - $this->ctrl->getCmdClass(), - '' - ); - } - protected function addTab_Question(ilTabsGUI $tabs_gui): void { $tabs_gui->addTarget( @@ -1796,11 +1758,6 @@ protected function buildBasicEditFormObject(): ilPropertyFormGUI return $form; } - public function showHints(): void - { - $this->ctrl->redirectByClass(ilAssQuestionHintsGUI::class, ilAssQuestionHintsGUI::CMD_SHOW_LIST); - } - protected function escapeTemplatePlaceholders(string $text): string { return str_replace(['{','}'], ['{','}'], $text); @@ -1862,7 +1819,7 @@ protected function generateCorrectnessIconsForCorrectness(int $correctness): str $label = $this->lng->txt("answer_is_wrong"); break; case self::CORRECTNESS_MOSTLY_OK: - $icon_name = 'standard/icon_ok.svg'; + $icon_name = 'standard/icon_mostly_ok.svg'; $label = $this->lng->txt("answer_is_not_correct_but_positive"); break; case self::CORRECTNESS_OK: @@ -2034,7 +1991,7 @@ public function getQuestionSyncModal(string $cmd, string $cmd_class = ''): strin )->withAffectedItems([ $this->ui->factory()->modal()->interruptiveItem()->standard( (string) $this->object->getOriginalId(), - $this->object->getTitle() + $this->object->getTitleForHTMLOutput() ) ])->withActionButtonLabel($this->lng->txt('sync_question_to_pool')); return $this->ui->renderer()->render( diff --git a/components/ILIAS/TestQuestionPool/classes/class.assSingleChoice.php b/components/ILIAS/TestQuestionPool/classes/class.assSingleChoice.php index 058c5d06951f..5d5126702cdb 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assSingleChoice.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assSingleChoice.php @@ -353,8 +353,8 @@ public function calculateReachedPointsFromPreviewSession( $points = $answer->getPoints(); } } - $reached_points = $this->deductHintPointsFromReachedPoints($preview_session, $points); - return $this->ensureNonNegativePoints($reached_points); + + return $this->ensureNonNegativePoints($points); } public function saveWorkingData( @@ -505,7 +505,7 @@ public function saveAnswerSpecificDataToDb(): void // Reorder feedback $feedback_order_db = intval($feedback_option['answer']); - $db_answer_id = $db_answer_id_for_order[$feedback_order_db]; + $db_answer_id = $db_answer_id_for_order[$feedback_order_db] ?? null; // This cuts feedback that currently would have no corresponding answer // This case can happen while copying "broken" questions // Or when saving a question with less answers than feedback @@ -683,7 +683,7 @@ public function toJSON(): string $result = []; $result['id'] = $this->getId(); $result['type'] = (string) $this->getQuestionType(); - $result['title'] = $this->getTitle(); + $result['title'] = $this->getTitleForHTMLOutput(); $result['question'] = $this->formatSAQuestion($this->getQuestion()); $result['nr_of_tries'] = $this->getNrOfTries(); $result['shuffle'] = $this->getShuffle(); @@ -885,7 +885,7 @@ public function toLog(AdditionalInformationGenerator $additional_info): array { $result = [ AdditionalInformationGenerator::KEY_QUESTION_TYPE => (string) $this->getQuestionType(), - AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitle(), + AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitleForHTMLOutput(), AdditionalInformationGenerator::KEY_QUESTION_TEXT => $this->formatSAQuestion($this->getQuestion()), AdditionalInformationGenerator::KEY_QUESTION_SHUFFLE_ANSWER_OPTIONS => $additional_info ->getTrueFalseTagForBool($this->getShuffle()), diff --git a/components/ILIAS/TestQuestionPool/classes/class.assSingleChoiceGUI.php b/components/ILIAS/TestQuestionPool/classes/class.assSingleChoiceGUI.php index e7e24c20f409..9668dfa0ffbb 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assSingleChoiceGUI.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assSingleChoiceGUI.php @@ -258,21 +258,16 @@ public function renderSolutionOutput( $keys = $this->getChoiceKeys(); foreach ($keys as $answer_id) { $answer = $this->object->answers[$answer_id]; - if (($active_id > 0) && (!$show_correct_solution)) { - if ($graphical_output) { - $correctness_icon = $this->generateCorrectnessIconsForCorrectness(self::CORRECTNESS_NOT_OK); - - if (strcmp($user_solution, $answer_id) == 0) { - if ($answer->getPoints() == $this->object->getMaximumPoints()) { - $correctness_icon = $this->generateCorrectnessIconsForCorrectness(self::CORRECTNESS_OK); - } elseif ($answer->getPoints() > 0) { - $correctness_icon = $this->generateCorrectnessIconsForCorrectness(self::CORRECTNESS_MOSTLY_OK); - } - } - $template->setCurrentBlock('icon_ok'); - $template->setVariable('ICON_OK', $correctness_icon); - $template->parseCurrentBlock(); - } + if ($active_id > 0 && !$show_correct_solution && $graphical_output) { + $correctness = $this->generateCorrectness( + (string) $user_solution, + (string) $answer_id, + $answer->getPoints(), + $this->object->getMaximumPoints() + ); + $template->setCurrentBlock('icon_ok'); + $template->setVariable('ICON_OK', $this->generateCorrectnessIconsForCorrectness($correctness)); + $template->parseCurrentBlock(); } if ($answer->hasImage()) { $template->setCurrentBlock('answer_image'); @@ -298,7 +293,7 @@ public function renderSolutionOutput( $template->setVariable('ANSWER_TEXT', ilLegacyFormElementsUtil::prepareTextareaOutput($answer->getAnswertext(), true)); if ($this->renderPurposeSupportsFormHtml() || $this->isRenderPurposePrintPdf()) { - if (strcmp($user_solution, $answer_id) == 0) { + if ((string) $user_solution === (string) $answer_id) { $template->setVariable('SOLUTION_IMAGE', ilUtil::getHtmlPath(ilUtil::getImagePath('object/radiobutton_checked.png'))); $template->setVariable('SOLUTION_ALT', $this->lng->txt('checked')); } else { @@ -309,7 +304,7 @@ public function renderSolutionOutput( $template->setVariable('QID', $this->object->getId()); $template->setVariable('SUFFIX', $show_correct_solution ? 'bestsolution' : 'usersolution'); $template->setVariable('SOLUTION_VALUE', $answer_id); - if (strcmp($user_solution, $answer_id) == 0) { + if ((string) $user_solution === (string) $answer_id) { $template->setVariable('SOLUTION_CHECKED', 'checked'); } } @@ -351,6 +346,28 @@ public function renderSolutionOutput( return $solutionoutput; } + private function generateCorrectness( + string $user_solution, + string $answer_id, + float $answer_points, + float $maximum_points + ): int { + if ($user_solution === $answer_id + && $answer_points === $maximum_points + || $user_solution !== $answer_id + && $answer_points === 0.0 + ) { + return self::CORRECTNESS_OK; + } + + if ($user_solution === $answer_id + && $answer_points > 0.0) { + return self::CORRECTNESS_MOSTLY_OK; + } + + return self::CORRECTNESS_NOT_OK; + } + public function getPreview( bool $show_question_only = false, bool $show_inline_feedback = false @@ -401,7 +418,7 @@ public function getPreview( if (is_object($this->getPreviewSession())) { $user_solution = $this->getPreviewSession()->getParticipantsSolution(); - if ($user_solution === (string) $answer_id) { + if ((string) $user_solution === (string) $answer_id) { $template->setVariable('CHECKED_ANSWER', ' checked="checked"'); } } @@ -472,13 +489,14 @@ public function getTestOutput( $template->parseCurrentBlock(); } } + if ($show_specific_inline_feedback) { $this->populateInlineFeedback($template, $answer_id, $user_solution); } $template->setCurrentBlock('answer_row'); $template->setVariable('ANSWER_ID', $answer_id); $template->setVariable('ANSWER_TEXT', ilLegacyFormElementsUtil::prepareTextareaOutput($answer->getAnswertext(), true)); - if (strcmp($user_solution, $answer_id) == 0) { + if ($user_solution === (string) $answer_id) { $template->setVariable('CHECKED_ANSWER', ' checked="checked"'); } $template->parseCurrentBlock(); @@ -612,7 +630,7 @@ public function writeAnswerSpecificPostData(ilPropertyFormGUI $form): void $answertext = $answer; $this->object->addAnswer( $answertext, - $choice['points'][$index], + $this->refinery->kindlyTo()->float()->transform($choice['points'][$index]), $index, null, $choice['answer_id'][$index] @@ -759,7 +777,7 @@ private function populateInlineFeedback($template, $answer_id, $user_solution): break; case 2: - if (strcmp((string) $user_solution, $answer_id) == 0) { + if ((string) $user_solution === (string) $answer_id) { $feedbackOutputRequired = true; } break; diff --git a/components/ILIAS/TestQuestionPool/classes/class.assTextQuestion.php b/components/ILIAS/TestQuestionPool/classes/class.assTextQuestion.php index ff9ad77c4a11..77a37a884a00 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assTextQuestion.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assTextQuestion.php @@ -154,14 +154,14 @@ public function loadFromDb(int $question_id): void } $result = $this->db->queryF( - "SELECT * FROM qpl_a_essay WHERE question_fi = %s", + 'SELECT * FROM qpl_a_essay WHERE question_fi = %s', ['integer'], [$this->getId()] ); $this->flushAnswers(); while ($row = $this->db->fetchAssoc($result)) { - $this->addAnswer($row['answertext'], $row['points']); + $this->addAnswer($row['answertext'] ?? '', $row['points'] ?? 0.0); } parent::loadFromDb($question_id); @@ -518,7 +518,7 @@ public function toJSON(): string $result = []; $result['id'] = $this->getId(); $result['type'] = (string) $this->getQuestionType(); - $result['title'] = $this->getTitle(); + $result['title'] = $this->getTitleForHTMLOutput(); $result['question'] = $this->formatSAQuestion($this->getQuestion()); $result['nr_of_tries'] = $this->getNrOfTries(); $result['shuffle'] = $this->getShuffle(); @@ -782,7 +782,7 @@ public function toLog(AdditionalInformationGenerator $additional_info): array { return [ AdditionalInformationGenerator::KEY_QUESTION_TYPE => (string) $this->getQuestionType(), - AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitle(), + AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitleForHTMLOutput(), AdditionalInformationGenerator::KEY_QUESTION_TEXT => $this->formatSAQuestion($this->getQuestion()), AdditionalInformationGenerator::KEY_QUESTION_REACHABLE_POINTS => $this->getMaximumPoints(), AdditionalInformationGenerator::KEY_QUESTION_TEXT_WORDCOUNT_ENABLED => $additional_info diff --git a/components/ILIAS/TestQuestionPool/classes/class.assTextQuestionGUI.php b/components/ILIAS/TestQuestionPool/classes/class.assTextQuestionGUI.php index 98bfaf9fce3b..3745bdf45ed5 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assTextQuestionGUI.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assTextQuestionGUI.php @@ -561,7 +561,7 @@ public function addSuggestedSolution(): void } $this->object->saveToDb(); $this->ctrl->setParameter($this, 'q_id', $this->object->getId()); - $this->tpl->setVariable('HEADER', $this->object->getTitle()); + $this->tpl->setVariable('HEADER', $this->object->getTitleForHTMLOutput()); $this->getQuestionTemplate(); } diff --git a/components/ILIAS/TestQuestionPool/classes/class.assTextSubset.php b/components/ILIAS/TestQuestionPool/classes/class.assTextSubset.php index ca9d3ddf3829..d85b05d91412 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assTextSubset.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assTextSubset.php @@ -529,7 +529,7 @@ public function toJSON(): string $result = []; $result['id'] = $this->getId(); $result['type'] = (string) $this->getQuestionType(); - $result['title'] = $this->getTitle(); + $result['title'] = $this->getTitleForHTMLOutput(); $result['question'] = $this->formatSAQuestion($this->getQuestion()); $result['nr_of_tries'] = $this->getNrOfTries(); $result['matching_method'] = $this->getTextRating(); @@ -692,7 +692,7 @@ public function toLog(AdditionalInformationGenerator $additional_info): array { return [ AdditionalInformationGenerator::KEY_QUESTION_TYPE => (string) $this->getQuestionType(), - AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitle(), + AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitleForHTMLOutput(), AdditionalInformationGenerator::KEY_QUESTION_TEXT => $this->formatSAQuestion($this->getQuestion()), AdditionalInformationGenerator::KEY_QUESTION_TEXT_MATCHING_METHOD => $additional_info->getTagForLangVar( $this->getMatchingMethodLangVar($this->getTextRating()) diff --git a/components/ILIAS/TestQuestionPool/classes/class.assTextSubsetGUI.php b/components/ILIAS/TestQuestionPool/classes/class.assTextSubsetGUI.php index 9ba160b27c81..0229a4308e13 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assTextSubsetGUI.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assTextSubsetGUI.php @@ -223,7 +223,17 @@ public function renderSolutionOutput( } } $template->setCurrentBlock("textsubset_row"); - $template->setVariable("SOLUTION", $this->escapeTemplatePlaceholders($user_solutions[$i]["value1"])); + $template->setVariable( + 'SOLUTION', + $this->escapeTemplatePlaceholders( + htmlspecialchars( + $user_solutions[$i]['value1'], + ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, + null, + false + ) + ) + ); $template->setVariable("COUNTER", $i + 1); if ($result_output) { $points = $user_solutions[$i]["points"]; @@ -341,7 +351,7 @@ public function writeAnswerSpecificPostData(ilPropertyFormGUI $form): void foreach ($this->answers_from_post as $index => $answertext) { $this->object->addAnswer( - htmlentities(assQuestion::extendedTrim($answertext)), + assQuestion::extendedTrim($answertext), $this->refinery->kindlyTo()->float()->transform($answers['points'][$index]), $index ); diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilAnswerWizardInputGUI.php b/components/ILIAS/TestQuestionPool/classes/class.ilAnswerWizardInputGUI.php index 8fe8e8d032c7..ad876b74dbae 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.ilAnswerWizardInputGUI.php +++ b/components/ILIAS/TestQuestionPool/classes/class.ilAnswerWizardInputGUI.php @@ -385,4 +385,19 @@ protected function getTemplate(): string { return "tpl.prop_answerwizardinput.html"; } + + protected function prepareFormOutput(float|string $input, bool $strip = false): string + { + $input_string = $this->refinery->kindlyTo()->string()->transform($input); + return str_replace( + ['{', '}', '\\'], + ['{', '}', '\'], + htmlspecialchars( + $strip ? ilUtil::stripSlashes($input_string) : $input_string, + ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, + null, + false + ) + ); + } } diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilAssHintPage.php b/components/ILIAS/TestQuestionPool/classes/class.ilAssHintPage.php deleted file mode 100755 index d82fa2fe11c6..000000000000 --- a/components/ILIAS/TestQuestionPool/classes/class.ilAssHintPage.php +++ /dev/null @@ -1,37 +0,0 @@ - - * @version $Id$ - * - * @ingroup components\ILIASTestQuestionPool - */ -class ilAssHintPage extends ilPageObject -{ - /** - * Get parent type - * @return string parent type - */ - public function getParentType(): string - { - return "qht"; - } -} diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilAssHintPageConfig.php b/components/ILIAS/TestQuestionPool/classes/class.ilAssHintPageConfig.php deleted file mode 100755 index 44177237cd46..000000000000 --- a/components/ILIAS/TestQuestionPool/classes/class.ilAssHintPageConfig.php +++ /dev/null @@ -1,34 +0,0 @@ - - * @version $Id$ - * @ingroup components\ILIASTestQuestionPool - */ -class ilAssHintPageConfig extends ilPageConfig -{ - /** - * Init - */ - public function init(): void - { - } -} diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilAssHintPageGUI.php b/components/ILIAS/TestQuestionPool/classes/class.ilAssHintPageGUI.php deleted file mode 100755 index 571d46fb5e42..000000000000 --- a/components/ILIAS/TestQuestionPool/classes/class.ilAssHintPageGUI.php +++ /dev/null @@ -1,48 +0,0 @@ - - * - * @ilCtrl_Calls ilAssHintPageGUI: ilPageEditorGUI, ilEditClipboardGUI, ilMDEditorGUI - * @ilCtrl_Calls ilAssHintPageGUI: ilPublicUserProfileGUI, ilNoteGUI - * @ilCtrl_Calls ilAssHintPageGUI: ilPropertyFormGUI, ilInternalLinkGUI - * - * @ingroup components\ILIASTestQuestionPool - * ilasshintpagegui - */ -class ilAssHintPageGUI extends ilPageObjectGUI -{ - /** - * Constructor - */ - public function __construct($a_id = 0, $a_old_nr = 0) - { - parent::__construct("qht", $a_id, $a_old_nr); - $this->setTemplateOutput(false); - } - - public function preview(): string - { - $page = parent::preview(); - $this->tabs_gui->activateTab("pg"); - return $page; - } -} diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilAssHtmlPurifier.php b/components/ILIAS/TestQuestionPool/classes/class.ilAssHtmlPurifier.php index cc1f0e3f465c..06b8fdd31b53 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.ilAssHtmlPurifier.php +++ b/components/ILIAS/TestQuestionPool/classes/class.ilAssHtmlPurifier.php @@ -41,6 +41,15 @@ protected function getPurifierConfigInstance(): HTMLPurifier_Config $config->set('HTML.Doctype', 'XHTML 1.0 Strict'); $config->set('HTML.AllowedElements', $this->getAllowedElements()); $config->set('HTML.ForbiddenAttributes', 'div@style'); + $config->autoFinalize = false; + $config->set( + 'URI.AllowedSchemes', + array_merge( + $config->get('URI.AllowedSchemes'), + ['data' => true] + ) + ); + $config->autoFinalize = true; if ($def = $config->maybeGetRawHTMLDefinition()) { $def->addAttribute('img', 'data-id', 'Number'); $def->addAttribute('a', 'target', 'Enum#_blank,_self,_target,_top'); diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionFeedbackEditingGUI.php b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionFeedbackEditingGUI.php index 76987ece34ca..0f11796add46 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionFeedbackEditingGUI.php +++ b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionFeedbackEditingGUI.php @@ -26,6 +26,7 @@ * * @ilCtrl_Calls ilAssQuestionFeedbackEditingGUI: ilAssGenFeedbackPageGUI, ilAssSpecFeedbackPageGUI * @ilCtrl_Calls ilAssQuestionFeedbackEditingGUI: ilPropertyFormGUI + * @ilCtrl_isCalledBy ilAssQuestionFeedbackEditingGUI: ilRepositoryGUI */ class ilAssQuestionFeedbackEditingGUI { diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHint.php b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHint.php deleted file mode 100755 index 89a43600b873..000000000000 --- a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHint.php +++ /dev/null @@ -1,400 +0,0 @@ - - * @version $Id$ - * - * @package Modules/TestQuestionPool - */ -class ilAssQuestionHint -{ - public const PAGE_OBJECT_TYPE = 'qht'; - - /** - * this is the primary key for a hint single hint - * - * @access private - * @var integer - */ - private $id = null; - - /** - * the id of question this hint relates to - * - * @access private - * @var integer - */ - private $questionId = null; - - /** - * a list of hints is offered step by step - * regarding to the order based on this index - * - * @access private - * @var integer - */ - private $index = null; - - /** - * the points the have to be ground-off - * when a user resorts to this hint - * - * @access private - * @var float - */ - private $points = null; - - /** - * the hint text itself - * - * @access private - * @var string - */ - private $text = null; - - /** - * Constructor - * - * @access public - */ - public function __construct() - { - } - - /** - * returns the hint id - * - * @access public - * @return integer $id - */ - public function getId(): ?int - { - return $this->id; - } - - /** - * sets the passed hint id - * - * @access public - * @param integer $id - */ - public function setId($id): void - { - $this->id = (int) $id; - } - - /** - * returns the question id the hint currently relates to - * - * @access public - * @return integer $questionId - */ - public function getQuestionId(): ?int - { - return $this->questionId; - } - - /** - * sets the passed question id so hint relates to it - * - * @access public - * @param integer $questionId - */ - public function setQuestionId($questionId): void - { - $this->questionId = (int) $questionId; - } - - /** - * returns the ordering index of hint - * - * @access public - * @return integer $index - */ - public function getIndex(): ?int - { - return $this->index; - } - - /** - * sets the passed hint ordering index - * - * @access public - * @param integer $index - */ - public function setIndex($index): void - { - $this->index = (int) $index; - } - - /** - * returns the points to ground-off for this hint - * - * @access public - * @return float $points - */ - public function getPoints(): ?float - { - return $this->points; - } - - /** - * sets the passed points to ground-off for this hint - * - * @access public - * @param float $points - */ - public function setPoints($points): void - { - $this->points = abs((float) $points); - } - - /** - * returns the hint text - * - * @access public - * @return string $text - */ - public function getText(): ?string - { - return $this->text; - } - - /** - * sets the passed hint text - * - * @access public - * @param string $text - */ - public function setText($text): void - { - if ($text !== null) { - $this->text = $this->getHtmlQuestionContentPurifier()->purify($text); - } - } - - /** - * loads the hint dataset with passed id from database - * and assigns it the to this hint object instance - * - * @access public - * @global ilDBInterface $ilDB - * @param integer $id - * @return boolean $success - */ - public function load($id): bool - { - global $DIC; - $ilDB = $DIC['ilDB']; - - $query = " - SELECT qht_hint_id, - qht_question_fi, - qht_hint_index, - qht_hint_points, - qht_hint_text - - FROM qpl_hints - - WHERE qht_hint_id = %s - "; - - $res = $ilDB->queryF( - $query, - ['integer'], - [(int) $id] - ); - - while ($row = $ilDB->fetchAssoc($res)) { - self::assignDbRow($this, $row); - - return true; - } - - return false; - } - - /** - * saves the current hint object state to database. it performs an insert or update, - * depending on the current initialisation of the hint id property - * - * a valid initialised id leads to an update, a non or invalid initialised id leads to an insert - * - * @access public - * @return boolean $success - */ - public function save(): bool - { - if ($this->getId()) { - return $this->update(); - } else { - return $this->insert(); - } - } - - /** - * persists the current object state to database by updating - * an existing dataset identified by hint id - * - * @access private - * @global ilDBInterface $ilDB - * @return boolean $success - */ - private function update(): bool - { - global $DIC; - $ilDB = $DIC['ilDB']; - - return $ilDB->update( - 'qpl_hints', - [ - 'qht_question_fi' => ['integer', $this->getQuestionId()], - 'qht_hint_index' => ['integer', $this->getIndex()], - 'qht_hint_points' => ['float', $this->getPoints()], - 'qht_hint_text' => ['clob', $this->getText()] - ], - [ - 'qht_hint_id' => ['integer', $this->getId()] - ] - ); - } - - /** - * persists the current object state to database by inserting - * a new dataset with a new hint id fetched from primary key sequence - * - * @access private - * @global ilDBInterface $ilDB - * @return boolean $success - */ - private function insert(): bool - { - global $DIC; - $ilDB = $DIC['ilDB']; - - $this->setId($ilDB->nextId('qpl_hints')); - - return $ilDB->insert('qpl_hints', [ - 'qht_hint_id' => ['integer', $this->getId()], - 'qht_question_fi' => ['integer', $this->getQuestionId()], - 'qht_hint_index' => ['integer', $this->getIndex()], - 'qht_hint_points' => ['float', $this->getPoints()], - 'qht_hint_text' => ['clob', $this->getText()] - ]); - } - - /** - * deletes the persisted hint object in database by deleting - * the hint dataset identified by hint id - * - * @return integer $affectedRows - */ - public function delete(): int - { - return self::deleteById($this->getId()); - } - - /** - * assigns the field elements of passed hint db row array to the - * corresponding hint object properties of passed hint object instance - * - * @access public - * @static - * @param self $questionHint - * @param array $hintDbRow - */ - public static function assignDbRow(self $questionHint, $hintDbRow): void - { - foreach ($hintDbRow as $field => $value) { - switch ($field) { - case 'qht_hint_id': $questionHint->setId($value); - break; - case 'qht_question_fi': $questionHint->setQuestionId($value); - break; - case 'qht_hint_index': $questionHint->setIndex($value); - break; - case 'qht_hint_points': $questionHint->setPoints($value); - break; - case 'qht_hint_text': $questionHint->setText($value); - break; - - default: throw new ilTestQuestionPoolException("invalid db field identifier ($field) given!"); - } - } - } - - /** - * deletes the persisted hint object in database by deleting - * the hint dataset identified by hint id - * - * @access public - * @static - * @global ilDBInterface $ilDB - * @param integer $hintId - * @return integer $affectedRows - */ - public static function deleteById($hintId): int - { - global $DIC; - $ilDB = $DIC['ilDB']; - - $query = " - DELETE FROM qpl_hints - WHERE qht_hint_id = %s - "; - - return $ilDB->manipulateF( - $query, - ['integer'], - [$hintId] - ); - } - - /** - * creates a hint object instance, loads the persisted hint dataset - * identified by passed hint id from database and assigns it as object state - * - * @access public - * @static - * @param integer $hintId - * @return self $hintInstance - */ - public static function getInstanceById($hintId): ilAssQuestionHint - { - $questionHint = new self(); - $questionHint->load($hintId); - return $questionHint; - } - - public function getPageObjectType(): string - { - return self::PAGE_OBJECT_TYPE; - } - - public static function getHintIndexLabel(ilLanguage $lng, $hintIndex): string - { - return sprintf($lng->txt('tst_question_hints_index_column_label'), $hintIndex); - } - - protected function getHtmlQuestionContentPurifier(): ilAssHtmlUserSolutionPurifier - { - return ilHtmlPurifierFactory::getInstanceByType('qpl_usersolution'); - } -} diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintAbstractGUI.php b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintAbstractGUI.php deleted file mode 100755 index 4a8eef0c384b..000000000000 --- a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintAbstractGUI.php +++ /dev/null @@ -1,61 +0,0 @@ - - * @author Grégory Saive - * @version $Id$ - * - * @package Modules/TestQuestionPool - */ -abstract class ilAssQuestionHintAbstractGUI -{ - protected RequestDataCollector $request_data_collector; - protected ?assQuestionGUI $question_gui = null; - protected ?assQuestion $question_obj = null; - protected ilTabsGUI $tabs; - protected ilLanguage $lng; - protected ilCtrl $ctrl; - - /** - * Constructor - * - * @access public - * @param assQuestionGUI $question_gui - */ - public function __construct(assQuestionGUI $question_gui) - { - global $DIC; - $this->tabs = $DIC->tabs(); - $this->lng = $DIC['lng']; - $this->ctrl = $DIC['ilCtrl']; - - $local_dic = QuestionPoolDIC::dic(); - $this->request_data_collector = $local_dic['request_data_collector']; - - $this->question_gui = $question_gui; - $this->question_obj = $question_gui->getObject(); - } -} diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintGUI.php b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintGUI.php deleted file mode 100755 index 5261a266bdd2..000000000000 --- a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintGUI.php +++ /dev/null @@ -1,233 +0,0 @@ - - * @author Grégory Saive - * @version $Id$ - - * - * @ilCtrl_Calls ilAssQuestionHintGUI: ilAssHintPageGUI - */ -class ilAssQuestionHintGUI extends ilAssQuestionHintAbstractGUI -{ - public const CMD_SHOW_FORM = 'showForm'; - public const CMD_SAVE_FORM = 'saveForm'; - public const CMD_CANCEL_FORM = 'cancelForm'; - private \ilGlobalTemplateInterface $main_tpl; - private GeneralQuestionPropertiesRepository $questionrepository; - - public function __construct(assQuestionGUI $questionGUI) - { - parent::__construct($questionGUI); - global $DIC; - $this->main_tpl = $DIC->ui()->mainTemplate(); - - $local_dic = QuestionPoolDIC::dic(); - $this->questionrepository = $local_dic['question.general_properties.repository']; - } - - public function executeCommand() - { - $cmd = $this->ctrl->getCmd(self::CMD_SHOW_FORM); - $nextClass = $this->ctrl->getNextClass($this); - - switch ($nextClass) { - case 'ilasshintpagegui': - $forwarder = new ilAssQuestionHintPageObjectCommandForwarder( - $this->question_obj, - $this->ctrl, - $this->tabs, - $this->lng - ); - $forwarder->setPresentationMode(ilAssQuestionHintPageObjectCommandForwarder::PRESENTATION_MODE_AUTHOR); - $forwarder->forward(); - break; - - default: - $this->tabs->setTabActive('tst_question_hints_tab'); - $cmd .= 'Cmd'; - $this->$cmd(); - break; - } - - return true; - } - - private function showFormCmd(?ilPropertyFormGUI $form = null): void - { - if ($form instanceof ilPropertyFormGUI) { - $form->setValuesByPost(); - } elseif ($this->request_data_collector->isset('hint_id') && (int) $this->request_data_collector->raw('hint_id')) { - $questionHint = new ilAssQuestionHint(); - - if (!$questionHint->load((int) $this->request_data_collector->raw('hint_id'))) { - $this->main_tpl->setOnScreenMessage( - 'failure', - 'invalid hint id given: ' . $this->request_data_collector->string('hint_id'), - true - ); - $this->ctrl->redirectByClass(ilAssQuestionHintsGUI::class, ilAssQuestionHintsGUI::CMD_SHOW_LIST); - } - - $form = $this->buildForm($questionHint); - } else { - $form = $this->buildForm(); - } - - $this->main_tpl->setContent($form->getHTML()); - } - - private function saveFormCmd(): void - { - $questionHint = new ilAssQuestionHint(); - if ($this->request_data_collector->isset('hint_id')) { - $questionHint->load((int) $this->request_data_collector->int('hint_id')); - - $hintJustCreated = false; - $form = $this->buildForm($questionHint); - } else { - $questionHint->setQuestionId($this->question_obj->getId()); - - $questionHint->setIndex( - ilAssQuestionHintList::getNextIndexByQuestionId($this->question_obj->getId()) - ); - - $hintJustCreated = true; - $form = $this->buildForm(); - } - - if ($form->checkInput()) { - $questionHint->setText($form->getInput('hint_text')); - $questionHint->setPoints($form->getInput('hint_points')); - - $questionHint->save(); - $this->main_tpl->setOnScreenMessage('success', $this->lng->txt('tst_question_hints_form_saved_msg'), true); - - if (!$this->question_obj->isAdditionalContentEditingModePageObject()) { - $this->question_obj->updateTimestamp(); - } - - if ($this->question_gui->needsSyncQuery()) { - $this->ctrl->redirectByClass( - ilAssQuestionHintsGUI::class, - ilAssQuestionHintsGUI::CMD_CONFIRM_SYNC - ); - } - - if ($hintJustCreated && $this->question_obj->isAdditionalContentEditingModePageObject()) { - $this->ctrl->setParameterByClass(ilAssHintPageGUI::class, 'hint_id', $questionHint->getId()); - $this->ctrl->redirectByClass(ilAssHintPageGUI::class, 'edit'); - } else { - $this->ctrl->redirectByClass(ilAssQuestionHintsGUI::class, ilAssQuestionHintsGUI::CMD_SHOW_LIST); - } - } - - $this->main_tpl->setOnScreenMessage('failure', $this->lng->txt('tst_question_hints_form_invalid_msg')); - $this->showFormCmd($form); - } - - private function cancelFormCmd(): void - { - - $this->ctrl->redirectByClass(ilAssQuestionHintsGUI::class); - } - - private function buildForm(?ilAssQuestionHint $questionHint = null): ilPropertyFormGUI - { - $form = new ilPropertyFormGUI(); - $form->setTableWidth('100%'); - - if (!$this->question_obj->isAdditionalContentEditingModePageObject()) { - // form input: hint text - - $areaInp = new ilTextAreaInputGUI($this->lng->txt('tst_question_hints_form_label_hint_text'), 'hint_text'); - $areaInp->setRequired(true); - $areaInp->setRows(10); - $areaInp->setCols(80); - - if (!$this->question_obj->getPreventRteUsage()) { - $areaInp->setUseRte(true); - } - - $areaInp->setRteTags(ilObjAdvancedEditing::_getUsedHTMLTags("assessment")); - - $areaInp->setRTESupport($this->question_obj->getId(), 'qpl', 'assessment'); - - $form->addItem($areaInp); - } - - // form input: hint points - - $numInp = new ilNumberInputGUI($this->lng->txt('tst_question_hints_form_label_hint_points'), 'hint_points'); - $numInp->allowDecimals(true); - $numInp->setRequired(true); - $numInp->setSize(3); - - $form->addItem($numInp); - - if ($questionHint instanceof ilAssQuestionHint) { - // build form title for an existing hint - - $form->setTitle(sprintf( - $this->lng->txt('tst_question_hints_form_header_edit'), - $questionHint->getIndex(), - $this->question_obj->getTitle() - )); - - $hiddenInp = new ilHiddenInputGUI('hint_id'); - $form->addItem($hiddenInp); - - if (!$this->question_obj->isAdditionalContentEditingModePageObject()) { - $areaInp->setValue($questionHint->getText()); - } - - $numInp->setValue((string) $questionHint->getPoints()); - - $hiddenInp->setValue((string) $questionHint->getId()); - } else { - // build form title for a new hint - $form->setTitle(sprintf( - $this->lng->txt('tst_question_hints_form_header_create'), - $this->question_obj->getTitle() - )); - } - - if ($this->question_obj->isAdditionalContentEditingModePageObject()) { - if ($questionHint instanceof ilAssQuestionHint) { - $saveCmdLabel = $this->lng->txt('tst_question_hints_form_cmd_save_points'); - } else { - $saveCmdLabel = $this->lng->txt('tst_question_hints_form_cmd_save_points_and_edit_page'); - } - } else { - $saveCmdLabel = $this->lng->txt('tst_question_hints_form_cmd_save'); - } - - $form->setFormAction($this->ctrl->getFormAction($this)); - - $form->addCommandButton(self::CMD_SAVE_FORM, $saveCmdLabel); - $form->addCommandButton(self::CMD_CANCEL_FORM, $this->lng->txt('cancel')); - - return $form; - } -} diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintList.php b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintList.php deleted file mode 100755 index 5f3273f28565..000000000000 --- a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintList.php +++ /dev/null @@ -1,300 +0,0 @@ - - * @version $Id$ - * - * @package components\ILIAS/TestQuestionPool - * @implements Iterator - */ -class ilAssQuestionHintList implements Iterator -{ - /** @var list */ - private array $questionHints = []; - - public function current(): ilAssQuestionHint - { - return current($this->questionHints); - } - - public function rewind(): void - { - reset($this->questionHints); - } - - public function next(): void - { - next($this->questionHints); - } - - public function key(): int - { - return key($this->questionHints); - } - - public function valid(): bool - { - return key($this->questionHints) !== null; - } - - public function __construct() - { - } - - public function addHint(ilAssQuestionHint $questionHint): void - { - $this->questionHints[] = $questionHint; - } - - /** - * @param int $hintId - */ - public function getHint($hintId): ilAssQuestionHint - { - foreach ($this as $questionHint) { - /* @var $questionHint ilAssQuestionHint */ - - if ($questionHint->getId() == $hintId) { - return $questionHint; - } - } - - throw new ilTestQuestionPoolException("hint with id $hintId does not exist in this list"); - } - - /** - * @param int $hintId - */ - public function hintExists($hintId): bool - { - foreach ($this as $questionHint) { - /* @var ilAssQuestionHint $questionHint */ - if ($questionHint->getId() == $hintId) { - return true; - } - } - - return false; - } - - /** - * re-indexes the list's hints sequentially by current order (starting with index "1") - * ATTENTION: it also persists this index to db by performing an update of hint object via id. - * do not re-index any hint list objects unless this lists contain ALL hint objects for a SINGLE question - * and no more hints apart of that. - */ - public function reIndex(): void - { - $counter = 0; - - foreach ($this as $questionHint) { - /* @var $questionHint ilAssQuestionHint */ - - $questionHint->setIndex(++$counter); - $questionHint->save(); - } - } - - /** - * duplicates a hint list from given original question id to - * given duplicate question id and returns an array of duplicate hint ids - * mapped to the corresponding original hint ids - * @param int $originalQuestionId - * @param int $duplicateQuestionId - * @return array $hintIds containing the map from original hint ids to duplicate hint ids - */ - public static function duplicateListForQuestion($originalQuestionId, $duplicateQuestionId): array - { - $hintIds = []; - - $questionHintList = self::getListByQuestionId($originalQuestionId); - - foreach ($questionHintList as $questionHint) { - /* @var $questionHint ilAssQuestionHint */ - - $originalHintId = $questionHint->getId(); - - $questionHint->setId(0); - $questionHint->setQuestionId($duplicateQuestionId); - - $questionHint->save(); - - $duplicateHintId = $questionHint->getId(); - - $hintIds[$originalHintId] = $duplicateHintId; - } - - return $hintIds; - } - - /** - * returns an array with data of the hints in this list - * that is adopted to be used as table gui data - * @return list - */ - public function getTableData(): array - { - $tableData = []; - - foreach ($this as $questionHint) { - /* @var $questionHint ilAssQuestionHint */ - - $tableData[] = [ - 'hint_id' => $questionHint->getId(), - 'hint_index' => $questionHint->getIndex(), - 'hint_points' => $questionHint->getPoints(), - 'hint_text' => $questionHint->getText() - ]; - } - - return $tableData; - } - - /** - * instantiates a question hint list for the passed question id - * @param int $questionId - */ - public static function getListByQuestionId($questionId): ilAssQuestionHintList - { - global $DIC; - $ilDB = $DIC->database(); - - $query = " - SELECT qht_hint_id, - qht_question_fi, - qht_hint_index, - qht_hint_points, - qht_hint_text - - FROM qpl_hints - - WHERE qht_question_fi = %s - - ORDER BY qht_hint_index ASC - "; - - $res = $ilDB->queryF( - $query, - ['integer'], - [(int) $questionId] - ); - - $questionHintList = new self(); - - while ($row = $ilDB->fetchAssoc($res)) { - $questionHint = new ilAssQuestionHint(); - - ilAssQuestionHint::assignDbRow($questionHint, $row); - - $questionHintList->addHint($questionHint); - } - - return $questionHintList; - } - - /** - * instantiates a question hint list for the passed hint ids - * @param list $hintIds - */ - public static function getListByHintIds($hintIds): ilAssQuestionHintList - { - global $DIC; - $ilDB = $DIC->database(); - - $qht_hint_id__IN__hintIds = $ilDB->in('qht_hint_id', (array) $hintIds, false, 'integer'); - - $query = " - SELECT qht_hint_id, - qht_question_fi, - qht_hint_index, - qht_hint_points, - qht_hint_text - - FROM qpl_hints - - WHERE $qht_hint_id__IN__hintIds - - ORDER BY qht_hint_index ASC - "; - - $res = $ilDB->query($query); - - $questionHintList = new self(); - - while ($row = $ilDB->fetchAssoc($res)) { - $questionHint = new ilAssQuestionHint(); - - ilAssQuestionHint::assignDbRow($questionHint, $row); - - $questionHintList->addHint($questionHint); - } - - return $questionHintList; - } - - /** - * determines the next index to be used for a new hint - * that is to be added to the list of existing hints - * regarding to the question with passed question id - * @param int $questionId - */ - public static function getNextIndexByQuestionId($questionId): int - { - global $DIC; - $ilDB = $DIC->database(); - - $query = " - SELECT 1 + COALESCE( MAX(qht_hint_index), 0 ) next_index - - FROM qpl_hints - - WHERE qht_question_fi = %s - "; - - $res = $ilDB->queryF( - $query, - ['integer'], - [(int) $questionId] - ); - $row = $ilDB->fetchAssoc($res); - - return is_array($row) ? (int) $row['next_index'] : 1; - } - - /** - * Deletes all question hints relating to questions included in given question ids - * @param list $questionIds - */ - public static function deleteHintsByQuestionIds(array $questionIds): int - { - global $DIC; - $ilDB = $DIC->database(); - - $__qht_question_fi__IN__questionIds = $ilDB->in('qht_question_fi', $questionIds, false, 'integer'); - - $query = " - DELETE FROM qpl_hints - WHERE $__qht_question_fi__IN__questionIds - "; - - return $ilDB->manipulate($query); - } -} diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintPageObjectCommandForwarder.php b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintPageObjectCommandForwarder.php deleted file mode 100755 index a8039bd96241..000000000000 --- a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintPageObjectCommandForwarder.php +++ /dev/null @@ -1,245 +0,0 @@ - - * @version $Id$ - * - * @package Modules/TestQuestionPool - */ -class ilAssQuestionHintPageObjectCommandForwarder extends ilAssQuestionAbstractPageObjectCommandForwarder -{ - /** - * presentation mode for authoring - */ - public const PRESENTATION_MODE_AUTHOR = 'PRESENTATION_MODE_AUTHOR'; - - /** - * presentation mode for authoring - */ - public const PRESENTATION_MODE_PREVIEW = 'PRESENTATION_MODE_PREVIEW'; - - /** - * presentation mode for requesting - */ - public const PRESENTATION_MODE_REQUEST = 'PRESENTATION_MODE_REQUEST'; - - /** - * currently set presentation mode - * - * @var string - */ - protected $presentationMode; - - /** - * object instance of question hint - * - * @access protected - * @var ilAssQuestionHint - */ - protected $questionHint; - - /** - * Constructor - * - * @access public - * @param assQuestion $questionOBJ - * @param ilCtrl $ctrl - * @param ilTabsGUI $tabs - * @param ilLanguage $lng - */ - public function __construct(assQuestion $questionOBJ, ilCtrl $ctrl, ilTabsGUI $tabs, ilLanguage $lng) - { - global $DIC; - $main_tpl = $DIC->ui()->mainTemplate(); - parent::__construct($questionOBJ, $ctrl, $tabs, $lng); - - $this->questionHint = new ilAssQuestionHint(); - - if (!$this->request->isset('hint_id') || !(int) $this->request->raw('hint_id') || !$this->questionHint->load((int) $this->request->raw('hint_id'))) { - $main_tpl->setOnScreenMessage('failure', 'invalid hint id given: ' . (int) $this->request->raw('hint_id'), true); - $this->ctrl->redirectByClass('ilAssQuestionHintsGUI', ilAssQuestionHintsGUI::CMD_SHOW_LIST); - } - } - - /** - * forward method - * - * @throws ilTestQuestionPoolException - */ - public function forward(): void - { - switch ($this->getPresentationMode()) { - case self::PRESENTATION_MODE_AUTHOR: - - $pageObjectGUI = $this->buildAuthorPresentationPageObjectGUI(); - break; - - case self::PRESENTATION_MODE_PREVIEW: - - $pageObjectGUI = $this->buildPreviewPresentationPageObjectGUI(); - break; - - case self::PRESENTATION_MODE_REQUEST: - - $pageObjectGUI = $this->buildRequestPresentationPageObjectGUI(); - break; - } - - $this->ctrl->setParameter($pageObjectGUI, 'hint_id', $this->questionHint->getId()); - - $this->ctrl->forwardCommand($pageObjectGUI); - } - - /** - * forwards the command to page object gui for author presentation - */ - private function buildPreviewPresentationPageObjectGUI(): ilAssHintPageGUI - { - $this->tabs->setBackTarget( - $this->lng->txt('tst_question_hints_back_to_hint_list'), - $this->ctrl->getLinkTargetByClass('ilAssQuestionHintsGUI', ilAssQuestionHintsGUI::CMD_SHOW_LIST) - ); - - $pageObjectGUI = $this->getPageObjectGUI( - $this->questionHint->getPageObjectType(), - $this->questionHint->getId() - ); - - $pageObjectGUI->setEnabledTabs(false); - - $pageObjectGUI->setPresentationTitle( - ilAssQuestionHint::getHintIndexLabel($this->lng, $this->questionHint->getIndex()) - ); - - return $pageObjectGUI; - } - - /** - * forwards the command to page object gui for author presentation - */ - private function buildRequestPresentationPageObjectGUI(): ilAssHintPageGUI - { - $this->tabs->setBackTarget( - $this->lng->txt('tst_question_hints_back_to_hint_list'), - $this->ctrl->getLinkTargetByClass('ilAssQuestionHintRequestGUI', ilAssQuestionHintRequestGUI::CMD_SHOW_LIST) - ); - - $pageObjectGUI = $this->getPageObjectGUI( - $this->questionHint->getPageObjectType(), - $this->questionHint->getId() - ); - - $pageObjectGUI->setEnabledTabs(false); - $pageObjectGUI->setEnableEditing(false); - $pageObjectGUI->setTemplateTargetVar('ADM_CONTENT'); - $pageObjectGUI->setTemplateOutput(true); - - $pageObjectGUI->setPresentationTitle( - ilAssQuestionHint::getHintIndexLabel($this->lng, $this->questionHint->getIndex()) - ); - - return $pageObjectGUI; - } - - /** - * forwards the command to page object gui for author presentation - */ - private function buildAuthorPresentationPageObjectGUI(): ilAssHintPageGUI - { - $this->tabs->setBackTarget( - $this->lng->txt('tst_question_hints_back_to_hint_list'), - $this->ctrl->getLinkTargetByClass('ilAssQuestionHintsGUI', ilAssQuestionHintsGUI::CMD_SHOW_LIST) - ); - - $this->ensurePageObjectExists( - $this->questionHint->getPageObjectType(), - $this->questionHint->getId() - ); - - $pageObjectGUI = $this->getPageObjectGUI( - $this->questionHint->getPageObjectType(), - $this->questionHint->getId() - ); - $pageObjectGUI->setTemplateTargetVar('ADM_CONTENT'); - $pageObjectGUI->setTemplateOutput(true); - $pageObjectGUI->setEnabledTabs(true); - - return $pageObjectGUI; - } - - /** - * getter for presentation mode - * - * @return string - */ - public function getPresentationMode(): ?string - { - return $this->presentationMode; - } - - /** - * setter for presentation mode - * - * @param string $presentationMode - * @throws ilTestQuestionPoolException - */ - public function setPresentationMode($presentationMode): void - { - switch ($presentationMode) { - case self::PRESENTATION_MODE_AUTHOR: - case self::PRESENTATION_MODE_PREVIEW: - case self::PRESENTATION_MODE_REQUEST: - - $this->presentationMode = $presentationMode; - break; - - default: throw new ilTestQuestionPoolException('invalid presentation mode given: ' . $presentationMode); - } - } - - /** - * instantiates, initialises and returns a page object gui object - */ - protected function getPageObjectGUI($pageObjectType, $pageObjectId): ilAssHintPageGUI - { - $pageObjectGUI = new ilAssHintPageGUI($pageObjectId); - $pageObjectGUI->obj->addUpdateListener( - $this->questionOBJ, - 'updateTimestamp' - ); - return $pageObjectGUI; - } - - /** - * ensures an existing page object with giben type/id - * - * @access protected - */ - protected function ensurePageObjectExists($pageObjectType, $pageObjectId): void - { - if (!ilAssHintPage::_exists($pageObjectType, $pageObjectId, '', true)) { - $pageObject = new ilAssHintPage(); - $pageObject->setParentId($this->questionOBJ->getId()); - $pageObject->setId($pageObjectId); - $pageObject->createFromXML(); - } - } -} diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintRequestGUI.php b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintRequestGUI.php deleted file mode 100755 index cf83f1ecd0b7..000000000000 --- a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintRequestGUI.php +++ /dev/null @@ -1,242 +0,0 @@ - - * @version $Id$ - * - * @package Modules/TestQuestionPool - * - * @ilCtrl_Calls ilAssQuestionHintRequestGUI: ilAssQuestionHintsTableGUI - * @ilCtrl_Calls ilAssQuestionHintRequestGUI: ilConfirmationGUI, ilPropertyFormGUI, ilAssHintPageGUI - */ -class ilAssQuestionHintRequestGUI extends ilAssQuestionHintAbstractGUI -{ - public const CMD_SHOW_LIST = 'showList'; - public const CMD_SHOW_HINT = 'showHint'; - public const CMD_CONFIRM_REQUEST = 'confirmRequest'; - public const CMD_PERFORM_REQUEST = 'performRequest'; - public const CMD_BACK_TO_QUESTION = 'backToQuestion'; - - public function __construct( - private ilTestPlayerAbstractGUI|ilAssQuestionPreviewGUI $parent_gui, - private string $parent_cmd, - assQuestionGUI $question_gui, - private $question_hint_tracking, - protected ilCtrl $ctrl, - protected ilLanguage $lng, - private ilGlobalTemplateInterface $tpl, - protected ilTabsGUI $tabs, - private GlobalScreen $global_screen - ) { - - parent::__construct($question_gui); - } - - public function executeCommand() - { - $cmd = $this->ctrl->getCmd(self::CMD_SHOW_LIST); - $next_class = $this->ctrl->getNextClass($this); - - switch ($next_class) { - case 'ilasshintpagegui': - $forwarder = new ilAssQuestionHintPageObjectCommandForwarder( - $this->question_obj, - $this->ctrl, - $this->tabs, - $this->lng - ); - $forwarder->setPresentationMode(ilAssQuestionHintPageObjectCommandForwarder::PRESENTATION_MODE_REQUEST); - $forwarder->forward(); - return ''; - - default: - $cmd .= 'Cmd'; - return $this->$cmd(); - } - } - - private function showListCmd(): void - { - if ($this->global_screen->tool()->context()->current()->getAdditionalData() - ->exists(ilTestPlayerLayoutProvider::TEST_PLAYER_VIEW_TITLE)) { - $this->global_screen->tool()->context()->current()->getAdditionalData()->replace( - ilTestPlayerLayoutProvider::TEST_PLAYER_VIEW_TITLE, - $this->parent_gui->getObject()->getTitle() . ' - ' . $this->lng->txt('show_requested_question_hints') - ); - } - - $question_hint_list = $this->question_hint_tracking->getRequestedHintsList(); - - $table = new ilAssQuestionHintsTableGUI( - $this->question_obj, - $question_hint_list, - $this, - self::CMD_SHOW_LIST - ); - - $this->populateContent($this->ctrl->getHtml($table), $this->tpl); - } - - private function showHintCmd(): void - { - if (!$this->request_data_collector->isset('hintId') || $this->request_data_collector->int('hintId') === 0) { - throw new ilTestException('no hint id given'); - } - - $is_requested = $this->question_hint_tracking->isRequested($this->request_data_collector->int('hintId')); - - if (!$is_requested) { - throw new ilTestException('hint with given id is not yet requested for given testactive and testpass'); - } - - $question_hint = ilAssQuestionHint::getInstanceById((int) $this->request_data_collector->raw('hintId')); - - if ($this->global_screen->tool()->context()->current()->getAdditionalData() - ->exists(ilTestPlayerLayoutProvider::TEST_PLAYER_VIEW_TITLE)) { - $this->global_screen->tool()->context()->current()->getAdditionalData()->replace( - ilTestPlayerLayoutProvider::TEST_PLAYER_VIEW_TITLE, - $this->parent_gui->getObject()->getTitle() . ' - ' . sprintf( - $this->lng->txt('tst_question_hints_form_header_edit'), - $question_hint->getIndex(), - $this->request_data_collector->int('sequence') ?? 0 - ) - ); - } - - $form = new ilPropertyFormGUI(); - $form->setFormAction($this->ctrl->getFormAction($this)); - $form->setTableWidth('100%'); - $form->setTitle(sprintf( - $this->lng->txt('tst_question_hints_form_header_edit'), - $question_hint->getIndex(), - $this->question_obj->getTitle() - )); - $form->addCommandButton(self::CMD_BACK_TO_QUESTION, $this->lng->txt('tst_question_hints_back_to_question')); - - $num_existing_requests = $this->question_hint_tracking->getNumExistingRequests(); - - if ($num_existing_requests > 1) { - $form->addCommandButton(self::CMD_SHOW_LIST, $this->lng->txt('show_requested_question_hints')); - } - - // form input: hint text - - $non_editable_hint_text = new ilNonEditableValueGUI($this->lng->txt('tst_question_hints_form_label_hint_text'), 'hint_text', true); - $non_editable_hint_text->setValue(ilLegacyFormElementsUtil::prepareTextareaOutput($question_hint->getText(), true)); - $form->addItem($non_editable_hint_text); - - // form input: hint points - - $non_editable_hint_point = new ilNonEditableValueGUI($this->lng->txt('tst_question_hints_form_label_hint_points'), 'hint_points'); - $non_editable_hint_point->setValue($question_hint->getPoints()); - $form->addItem($non_editable_hint_point); - - $this->populateContent($this->ctrl->getHtml($form), $this->tpl); - } - - private function confirmRequestCmd(): void - { - if ($this->global_screen->tool()->context()->current()->getAdditionalData() - ->exists(ilTestPlayerLayoutProvider::TEST_PLAYER_VIEW_TITLE)) { - $this->global_screen->tool()->context()->current()->getAdditionalData()->replace( - ilTestPlayerLayoutProvider::TEST_PLAYER_VIEW_TITLE, - $this->parent_gui->getObject()->getTitle() . ' - ' . $this->lng->txt('tst_question_hints_confirm_request') - ); - } - - try { - $next_requestable_hint = $this->question_hint_tracking->getNextRequestableHint(); - } catch (ilTestNoNextRequestableHintExistsException $e) { - $this->ctrl->redirect($this, self::CMD_BACK_TO_QUESTION); - } - - $confirmation = new ilConfirmationGUI(); - - $form_action = ilUtil::appendUrlParameterString( - $this->ctrl->getFormAction($this), - "hintId={$next_requestable_hint->getId()}" - ); - - $confirmation->setFormAction($form_action); - - $confirmation->setConfirm($this->lng->txt('tst_question_hints_confirm_request'), self::CMD_PERFORM_REQUEST); - $confirmation->setCancel($this->lng->txt('tst_question_hints_cancel_request'), self::CMD_BACK_TO_QUESTION); - - if ($next_requestable_hint->getPoints() == 0.0) { - $confirmation->setHeaderText($this->lng->txt('tst_question_hints_request_confirmation_no_deduction')); - } else { - $confirmation->setHeaderText(sprintf( - $this->lng->txt('tst_question_hints_request_confirmation'), - $next_requestable_hint->getIndex(), - $next_requestable_hint->getPoints() - )); - } - - $this->populateContent($this->ctrl->getHtml($confirmation), $this->tpl); - } - - private function performRequestCmd(): void - { - if (!$this->request_data_collector->isset('hintId') || !(int) $this->request_data_collector->raw('hintId')) { - throw new ilTestException('no hint id given'); - } - - try { - $next_requestable_hint = $this->question_hint_tracking->getNextRequestableHint(); - } catch (ilTestNoNextRequestableHintExistsException $e) { - $this->ctrl->redirect($this, self::CMD_BACK_TO_QUESTION); - } - - if ($next_requestable_hint->getId() != (int) $this->request_data_collector->raw('hintId')) { - throw new ilTestException('given hint id does not relate to the next requestable hint'); - } - - $this->question_hint_tracking->storeRequest($next_requestable_hint); - - $redirectTarget = $this->getHintPresentationLinkTarget($next_requestable_hint->getId(), false); - - ilUtil::redirect($redirectTarget); - } - - private function backToQuestionCmd(): void - { - $this->ctrl->redirect($this->parent_gui, $this->parent_cmd); - } - - private function populateContent($content, $tpl): void - { - $tpl->setContent($content); - return; - } - - public function getHintPresentationLinkTarget($hint_id, $xml_style = true): string - { - if ($this->question_obj->isAdditionalContentEditingModePageObject()) { - $this->ctrl->setParameterByClass('ilasshintpagegui', 'hint_id', $hint_id); - return $this->ctrl->getLinkTargetByClass('ilAssHintPageGUI', '', '', false, $xml_style); - } - - $this->ctrl->setParameter($this, 'hintId', $hint_id); - return $this->ctrl->getLinkTarget($this, self::CMD_SHOW_HINT, '', false, $xml_style); - } -} diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintRequestStatisticData.php b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintRequestStatisticData.php deleted file mode 100755 index 4a12e5cc511b..000000000000 --- a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintRequestStatisticData.php +++ /dev/null @@ -1,93 +0,0 @@ - - * @version $Id$ - * - * @package Modules/TestQuestionPool - */ -class ilAssQuestionHintRequestStatisticData -{ - /** - * The sum of points deducted - * - * @var float - */ - private $requestsPoints = null; - - /** - * The number of hint requests - * - * @var integer - */ - private $requestsCount = null; - - /** - * Constructor - */ - public function __construct() - { - } - - /** - * Getter for requestsPoints - * - * @access public - * @return float $requestsPoints - */ - public function getRequestsPoints(): ?float - { - return $this->requestsPoints; - } - - /** - * Setter for requestsPoints - * - * @access public - * @param float $requestsPoints - */ - public function setRequestsPoints($requestsPoints): void - { - $this->requestsPoints = abs($requestsPoints); - } - - /** - * Getter for requestsCount - * - * @access public - * @return integer $requestsCount - */ - public function getRequestsCount(): ?int - { - return $this->requestsCount; - } - - /** - * Setter for requestsCount - * - * @access public - * @param integer $requestsCount - */ - public function setRequestsCount($requestsCount): void - { - $this->requestsCount = $requestsCount; - } -} diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintRequestStatisticRegister.php b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintRequestStatisticRegister.php deleted file mode 100755 index c05c2e92052e..000000000000 --- a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintRequestStatisticRegister.php +++ /dev/null @@ -1,53 +0,0 @@ - - */ -class ilAssQuestionHintRequestStatisticRegister -{ - /** - * @var array - */ - protected $requests_by_test_pass_and_question_id = []; - - public function addRequestByTestPassIndexAndQuestionId( - int $pass, - int $question_id, - ilAssQuestionHintRequestStatisticData $request - ): void { - if (!isset($this->requests_by_test_pass_and_question_id[$pass])) { - $this->requests_by_test_pass_and_question_id[$pass] = []; - } - - $this->requests_by_test_pass_and_question_id[$pass][$question_id] = $request; - } - - public function getRequestByTestPassIndexAndQuestionId( - int $pass, - int $question_id - ): ?ilAssQuestionHintRequestStatisticData { - if (isset($this->requests_by_test_pass_and_question_id[$pass]) - && isset($this->requests_by_test_pass_and_question_id[$pass][$question_id])) { - return $this->requests_by_test_pass_and_question_id[$pass][$question_id]; - } - return null; - } -} diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php deleted file mode 100755 index 434ed7a673da..000000000000 --- a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php +++ /dev/null @@ -1,458 +0,0 @@ - - * @version $Id$ - * - * @package Modules/TestQuestionPool - */ -class ilAssQuestionHintTracking -{ - private $questionId; - - private $activeId; - - private $pass; - - public function __construct($questionId, $activeId, $pass) - { - $this->questionId = $questionId; - $this->activeId = $activeId; - $this->pass = $pass; - } - - public function setActiveId($activeId): void - { - $this->activeId = $activeId; - } - - public function getActiveId() - { - return $this->activeId; - } - - public function setPass($pass): void - { - $this->pass = $pass; - } - - public function getPass() - { - return $this->pass; - } - - public function setQuestionId($questionId): void - { - $this->questionId = $questionId; - } - - public function getQuestionId() - { - return $this->questionId; - } - - /** - * Returns the fact wether there exists hint requests for the given - * question relating to the given testactive and testpass or not - * - * @access public - * @global ilDBInterface $ilDB - * @return boolean $requestsExist - */ - public function requestsExist(): bool - { - if (self::getNumExistingRequests($this->getQuestionId(), $this->getActiveId(), $this->getPass()) > 0) { - return true; - } - - return false; - } - - /** - * Returns the number existing hint requests for the given - * question relating to the given testactive and testpass or not - * - * @access public - * @global ilDBInterface $ilDB - * @return integer $numExisingRequests - */ - public function getNumExistingRequests(): int - { - global $DIC; - $ilDB = $DIC['ilDB']; - - $query = " - SELECT COUNT(qhtr_track_id) cnt - - FROM qpl_hint_tracking - - WHERE qhtr_question_fi = %s - AND qhtr_active_fi = %s - AND qhtr_pass = %s - "; - - $res = $ilDB->queryF( - $query, - ['integer', 'integer', 'integer'], - [$this->getQuestionId(), $this->getActiveId(), $this->getPass()] - ); - - $row = $ilDB->fetchAssoc($res); - - return $row['cnt']; - } - - /** - * Returns the fact wether (further) hint requests are possible for the given - * question relating to the given testactive and testpass or not - * - * @access public - * @global ilDBInterface $ilDB - * @return boolean $requestsPossible - */ - public function requestsPossible(): bool - { - global $DIC; - $ilDB = $DIC['ilDB']; - - $query = " - SELECT COUNT(qht_hint_id) cnt_available, - COUNT(qhtr_track_id) cnt_requested - - FROM qpl_hints - - LEFT JOIN qpl_hint_tracking - ON qhtr_hint_fi = qht_hint_id - AND qhtr_active_fi = %s - AND qhtr_pass = %s - - WHERE qht_question_fi = %s - "; - - $res = $ilDB->queryF( - $query, - ['integer', 'integer', 'integer'], - [$this->getActiveId(), $this->getPass(), $this->getQuestionId()] - ); - - $row = $ilDB->fetchAssoc($res); - - if ($row['cnt_available'] > $row['cnt_requested']) { - return true; - } - - return false; - } - - /** - * Returns the fact wether the hint for given id is requested - * for the given testactive and testpass - * - * @access public - * @global ilDBInterface $ilDB - * @param integer $hintId - * @return boolean $isRequested - */ - public function isRequested($hintId): bool - { - global $DIC; - $ilDB = $DIC['ilDB']; - - $query = " - SELECT COUNT(qhtr_track_id) cnt - - FROM qpl_hint_tracking - - WHERE qhtr_hint_fi = %s - AND qhtr_active_fi = %s - AND qhtr_pass = %s - "; - - $res = $ilDB->queryF( - $query, - ['integer', 'integer', 'integer'], - [$hintId, $this->getActiveId(), $this->getPass()] - ); - - $row = $ilDB->fetchAssoc($res); - - if ($row['cnt'] > 0) { - return true; - } - - return false; - } - - /** - * Returns the next requestable hint for given question - * relating to given testactive and testpass - * - * @access public - * @global ilDBInterface $ilDB - * @return ilAssQuestionHint $nextRequestableHint - * @throws ilTestException - */ - public function getNextRequestableHint(): ilAssQuestionHint - { - global $DIC; - $ilDB = $DIC['ilDB']; - - $query = " - SELECT qht_hint_id - - FROM qpl_hints - - LEFT JOIN qpl_hint_tracking - ON qhtr_hint_fi = qht_hint_id - AND qhtr_active_fi = %s - AND qhtr_pass = %s - - WHERE qht_question_fi = %s - AND qhtr_track_id IS NULL - - ORDER BY qht_hint_index ASC - "; - - $ilDB->setLimit(1); - - $res = $ilDB->queryF( - $query, - ['integer', 'integer', 'integer'], - [$this->getActiveId(), $this->getPass(), $this->getQuestionId()] - ); - - while ($row = $ilDB->fetchAssoc($res)) { - $nextHint = ilAssQuestionHint::getInstanceById($row['qht_hint_id']); - - return $nextHint; - } - - throw new ilTestNoNextRequestableHintExistsException( - "no next hint found for questionId={$this->getQuestionId()}, activeId={$this->getActiveId()}, pass={$this->getPass()}" - ); - } - - /** - * Returns an object of class ilAssQuestionHintList containing objects - * of class ilAssQuestionHint for all allready requested hints - * relating to the given question, testactive and testpass - * - * @access public - * @global ilDBInterface $ilDB - * @return ilAssQuestionHintList $requestedHintsList - */ - public function getRequestedHintsList(): ilAssQuestionHintList - { - global $DIC; - $ilDB = $DIC['ilDB']; - - $query = " - SELECT qhtr_hint_fi - - FROM qpl_hint_tracking - - WHERE qhtr_question_fi = %s - AND qhtr_active_fi = %s - AND qhtr_pass = %s - "; - - $res = $ilDB->queryF( - $query, - ['integer', 'integer', 'integer'], - [$this->getQuestionId(), $this->getActiveId(), $this->getPass()] - ); - - $hintIds = []; - - while ($row = $ilDB->fetchAssoc($res)) { - $hintIds[] = $row['qhtr_hint_fi']; - } - - $requestedHintsList = ilAssQuestionHintList::getListByHintIds($hintIds); - - return $requestedHintsList; - } - - /** - * Tracks the given hint as requested for the given - * question, testactive and testpass - * - * @access public - * @global ilDBInterface $ilDB - * @param ilAssQuestionHint $questionHint - */ - public function storeRequest(ilAssQuestionHint $questionHint): void - { - global $DIC; - $ilDB = $DIC['ilDB']; - - $trackId = $ilDB->nextId('qpl_hint_tracking'); - - $ilDB->insert('qpl_hint_tracking', [ - 'qhtr_track_id' => ['integer', $trackId], - 'qhtr_active_fi' => ['integer', $this->getActiveId()], - 'qhtr_pass' => ['integer', $this->getPass()], - 'qhtr_question_fi' => ['integer', $this->getQuestionId()], - 'qhtr_hint_fi' => ['integer', $questionHint->getId()], - ]); - } - - /** - * Returns a question hint request statistic data container - * containing the statistics for all requests relating to given ... - * - question - * - testactive - * - testpass - * - * @access public - * @global ilDBInterface $ilDB - * @return ilAssQuestionHintRequestStatisticData $requestsStatisticData - */ - public function getRequestStatisticDataByQuestionAndTestpass(): ilAssQuestionHintRequestStatisticData - { - global $DIC; - $ilDB = $DIC['ilDB']; - - $query = " - SELECT COUNT(qhtr_track_id) requests_count, - SUM(qht_hint_points) requests_points - - FROM qpl_hint_tracking - - INNER JOIN qpl_hints - ON qht_hint_id = qhtr_hint_fi - - WHERE qhtr_question_fi = %s - AND qhtr_active_fi = %s - AND qhtr_pass = %s - "; - - $res = $ilDB->queryF( - $query, - ['integer', 'integer', 'integer'], - [$this->getQuestionId(), $this->getActiveId(), $this->getPass()] - ); - - $row = $ilDB->fetchAssoc($res); - - if ($row['requests_points'] === null) { - $row['requests_points'] = 0; - } - - $requestsStatisticData = new ilAssQuestionHintRequestStatisticData(); - $requestsStatisticData->setRequestsCount($row['requests_count']); - $requestsStatisticData->setRequestsPoints($row['requests_points']); - - return $requestsStatisticData; - } - - /** - * @param integer $activeId - * @return ilAssQuestionHintRequestStatisticRegister - */ - public static function getRequestRequestStatisticDataRegisterByActiveId($activeId): ilAssQuestionHintRequestStatisticRegister - { - global $DIC; - $db = $DIC->database(); - - $query = " - SELECT qhtr_pass requests_pass, - qhtr_question_fi requests_question, - COUNT(qhtr_track_id) requests_count, - SUM(qht_hint_points) requests_points - - FROM qpl_hint_tracking - - INNER JOIN qpl_hints - ON qht_hint_id = qhtr_hint_fi - - WHERE qhtr_active_fi = %s - - GROUP BY qhtr_pass, qhtr_question_fi - "; - - $res = $db->queryF( - $query, - ['integer'], - [$activeId] - ); - - $register = new ilAssQuestionHintRequestStatisticRegister(); - - while ($row = $db->fetchAssoc($res)) { - if ($row['requests_points'] === null) { - $row['requests_points'] = 0; - } - - $requestsStatisticData = new ilAssQuestionHintRequestStatisticData(); - $requestsStatisticData->setRequestsCount($row['requests_count']); - $requestsStatisticData->setRequestsPoints($row['requests_points']); - - $register->addRequestByTestPassIndexAndQuestionId($row['requests_pass'], $row['requests_question'], $requestsStatisticData); - } - - return $register; - } - - /** - * Deletes all hint requests relating to a question included in given question ids - * @param array[integer] $questionIds - */ - public static function deleteRequestsByQuestionIds($questionIds): void - { - /** - * @var $ilDB ilDBInterface - */ - global $DIC; - $ilDB = $DIC['ilDB']; - - $__question_fi__IN__questionIds = $ilDB->in('qhtr_question_fi', $questionIds, false, 'integer'); - - $query = " - DELETE FROM qpl_hint_tracking - WHERE $__question_fi__IN__questionIds - "; - - $ilDB->manipulate($query); - } - - /** - * Deletes all hint requests relating to a testactive included in given active ids - * - * @access public - * @global ilDBInterface $ilDB - * @param array[integer] $activeIds - */ - public static function deleteRequestsByActiveIds($activeIds): void - { - global $DIC; - $ilDB = $DIC['ilDB']; - - $__active_fi__IN__activeIds = $ilDB->in('qhtr_active_fi', $activeIds, false, 'integer'); - - $query = " - DELETE FROM qpl_hint_tracking - WHERE $__active_fi__IN__activeIds - "; - - $ilDB->manipulate($query); - } -} diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintsGUI.php b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintsGUI.php deleted file mode 100755 index 83b2bb56a67d..000000000000 --- a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintsGUI.php +++ /dev/null @@ -1,615 +0,0 @@ - - * @version $Id$ - * - * @package Modules/TestQuestionPool - * - * @ilCtrl_Calls ilAssQuestionHintsGUI: ilAssQuestionHintsTableGUI - * @ilCtrl_Calls ilAssQuestionHintsGUI: ilAssHintPageGUI - * @ilCtrl_Calls ilAssQuestionHintsGUI: ilToolbarGUI, ilConfirmationGUI - */ -class ilAssQuestionHintsGUI extends ilAssQuestionHintAbstractGUI -{ - /** - * command constants - */ - public const CMD_SHOW_LIST = 'showList'; - public const CMD_SHOW_HINT = 'showHint'; - public const CMD_CONFIRM_DELETE = 'confirmDelete'; - public const CMD_PERFORM_DELETE = 'performDelete'; - public const CMD_SAVE_LIST_ORDER = 'saveListOrder'; - public const CMD_CUT_TO_ORDERING_CLIPBOARD = 'cutToOrderingClipboard'; - public const CMD_PASTE_FROM_ORDERING_CLIPBOARD_BEFORE = 'pasteFromOrderingClipboardBefore'; - public const CMD_PASTE_FROM_ORDERING_CLIPBOARD_AFTER = 'pasteFromOrderingClipboardAfter'; - public const CMD_RESET_ORDERING_CLIPBOARD = 'resetOrderingClipboard'; - public const CMD_CONFIRM_SYNC = 'confirmSync'; - public const CMD_SYNC = 'sync'; - - private ?ilAssQuestionHintsOrderingClipboard $hintOrderingClipboard = null; - private GeneralQuestionPropertiesRepository $questionrepository; - protected bool $editingEnabled = false; - private \ilGlobalTemplateInterface $main_tpl; - - public function __construct(assQuestionGUI $questionGUI) - { - global $DIC; - $this->main_tpl = $DIC->ui()->mainTemplate(); - $this->ctrl = $DIC->ctrl(); - - $local_dic = QuestionPoolDIC::dic(); - $this->questionrepository = $local_dic['question.general_properties.repository']; - - parent::__construct($questionGUI); - - $this->hintOrderingClipboard = new ilAssQuestionHintsOrderingClipboard($questionGUI->getObject()); - } - - public function isEditingEnabled(): bool - { - return $this->editingEnabled; - } - - public function setEditingEnabled(bool $editingEnabled): void - { - $this->editingEnabled = $editingEnabled; - } - - public function executeCommand(): void - { - global $DIC; - $ilHelp = $DIC['ilHelp']; - $ilHelp->setScreenIdComponent('qpl'); - - $DIC->ui()->mainTemplate()->setCurrentBlock("ContentStyle"); - $DIC->ui()->mainTemplate()->setVariable("LOCATION_CONTENT_STYLESHEET", ilObjStyleSheet::getContentStylePath(0)); - $DIC->ui()->mainTemplate()->parseCurrentBlock(); - - $cmd = $this->ctrl->getCmd(self::CMD_SHOW_LIST); - $nextClass = $this->ctrl->getNextClass($this); - - switch ($nextClass) { - case 'ilassquestionhintgui': - if (!$this->isEditingEnabled()) { - return; - } - - $gui = new ilAssQuestionHintGUI($this->question_gui); - $this->ctrl->forwardCommand($gui); - break; - - case 'ilasshintpagegui': - if ($this->isEditingEnabled()) { - $presentationMode = ilAssQuestionHintPageObjectCommandForwarder::PRESENTATION_MODE_AUTHOR; - } else { - $presentationMode = ilAssQuestionHintPageObjectCommandForwarder::PRESENTATION_MODE_PREVIEW; - } - - $forwarder = new ilAssQuestionHintPageObjectCommandForwarder( - $this->question_obj, - $this->ctrl, - $this->tabs, - $this->lng - ); - $forwarder->setPresentationMode($presentationMode); - $forwarder->forward(); - break; - - default: - $this->tabs->setTabActive('tst_question_hints_tab'); - $cmd .= 'Cmd'; - $this->$cmd(); - break; - } - } - - private function showListCmd(string $additional_content = ''): void - { - $this->initHintOrderingClipboardNotification(); - - $toolbar = new ilToolbarGUI(); - - $questionHintList = ilAssQuestionHintList::getListByQuestionId($this->question_obj->getId()); - - if ($this->isEditingEnabled()) { - if ($this->hintOrderingClipboard->hasStored()) { - $questionHintList = $this->getQuestionHintListWithoutHintStoredInOrderingClipboard($questionHintList); - - $toolbar->addButton( - $this->lng->txt('tst_questions_hints_toolbar_cmd_reset_ordering_clipboard'), - $this->ctrl->getLinkTarget($this, self::CMD_RESET_ORDERING_CLIPBOARD) - ); - } else { - $toolbar->addButton( - $this->lng->txt('tst_questions_hints_toolbar_cmd_add_hint'), - $this->ctrl->getLinkTargetByClass('ilAssQuestionHintGUI', ilAssQuestionHintGUI::CMD_SHOW_FORM) - ); - } - - $tableMode = ilAssQuestionHintsTableGUI::TBL_MODE_ADMINISTRATION; - } else { - $tableMode = ilAssQuestionHintsTableGUI::TBL_MODE_TESTOUTPUT; - } - - $table = new ilAssQuestionHintsTableGUI( - $this->question_obj, - $questionHintList, - $this, - self::CMD_SHOW_LIST, - $tableMode, - $this->hintOrderingClipboard - ); - - $this->main_tpl->setContent($toolbar->getHTML() . $table->getHTML() . $additional_content); - } - - private function confirmDeleteCmd(): void - { - $hint_ids = $this->fetchHintIdsParameter(); - - if (!count($hint_ids)) { - $this->main_tpl->setOnScreenMessage( - 'failure', - $this->lng->txt('tst_question_hints_delete_hints_missing_selection_msg'), - true - ); - $this->ctrl->redirectByClass(self::class); - } - - $confirmation = new ilConfirmationGUI(); - - $confirmation->setHeaderText($this->lng->txt('tst_question_hints_delete_hints_confirm_header')); - $confirmation->setFormAction($this->ctrl->getFormAction($this)); - $confirmation->setConfirm($this->lng->txt('tst_question_hints_delete_hints_confirm_cmd'), self::CMD_PERFORM_DELETE); - $confirmation->setCancel($this->lng->txt('cancel'), self::CMD_SHOW_LIST); - - $questionHintList = ilAssQuestionHintList::getListByQuestionId($this->question_obj->getId()); - - foreach ($questionHintList as $questionHint) { - /* @var $questionHint ilAssQuestionHint */ - - if (in_array($questionHint->getId(), $hint_ids)) { - $confirmation->addItem('hint_ids[]', $questionHint->getId(), sprintf( - $this->lng->txt('tst_question_hints_delete_hints_confirm_item'), - $questionHint->getIndex(), - $questionHint->getText() - )); - } - } - - $this->main_tpl->setContent($this->ctrl->getHtml($confirmation)); - } - - private function performDeleteCmd(): void - { - if (!$this->isEditingEnabled()) { - return; - } - - $hintIds = $this->fetchHintIdsParameter(); - - if (!count($hintIds)) { - $this->main_tpl->setOnScreenMessage('failure', $this->lng->txt('tst_question_hints_delete_hints_missing_selection_msg'), true); - $this->ctrl->redirectByClass(self::class); - } - - $questionCompleteHintList = ilAssQuestionHintList::getListByQuestionId($this->question_obj->getId()); - - $questionRemainingHintList = new ilAssQuestionHintList(); - - foreach ($questionCompleteHintList as $questionHint) { - /* @var $questionHint ilAssQuestionHint */ - - if (in_array($questionHint->getId(), $hintIds)) { - $questionHint->delete(); - } else { - $questionRemainingHintList->addHint($questionHint); - } - } - - $questionRemainingHintList->reIndex(); - - $this->main_tpl->setOnScreenMessage('success', $this->lng->txt('tst_question_hints_delete_success_msg'), true); - - if ($this->question_gui->needsSyncQuery()) { - $this->ctrl->redirectByClass( - ilAssQuestionHintsGUI::class, - ilAssQuestionHintsGUI::CMD_CONFIRM_SYNC - ); - } - - $this->ctrl->redirectByClass(self::class); - } - - private function saveListOrderCmd(): void - { - if (!$this->isEditingEnabled()) { - return; - } - - $hintIndexes = $this->fetchHintIndexesParameter(); - - if (!count($hintIndexes)) { - $this->main_tpl->setOnScreenMessage( - 'failure', - $this->lng->txt('tst_question_hints_save_order_unkown_failure_msg'), - true - ); - $this->ctrl->redirectByClass(self::class); - } - - $curQuestionHintList = ilAssQuestionHintList::getListByQuestionId($this->question_obj->getId()); - - $newQuestionHintList = new ilAssQuestionHintList(); - - foreach (array_keys($hintIndexes) as $hintId) { - if (!$curQuestionHintList->hintExists($hintId)) { - $this->main_tpl->setOnScreenMessage( - 'failure', - $this->lng->txt('tst_question_hints_save_order_unkown_failure_msg'), - true - ); - $this->ctrl->redirectByClass(self::class); - } - - $questionHint = $curQuestionHintList->getHint($hintId); - - $newQuestionHintList->addHint($questionHint); - } - - $newQuestionHintList->reIndex(); - - $this->main_tpl->setOnScreenMessage('success', $this->lng->txt('tst_question_hints_save_order_success_msg'), true); - - if ($this->question_gui->needsSyncQuery()) { - $this->ctrl->redirectByClass( - ilAssQuestionHintsGUI::class, - ilAssQuestionHintsGUI::CMD_CONFIRM_SYNC - ); - } - - $this->ctrl->redirectByClass(self::class); - } - - private function cutToOrderingClipboardCmd(): void - { - if (!$this->isEditingEnabled()) { - return; - } - - $moveHintIds = $this->fetchHintIdsParameter(); - $this->checkForSingleHintIdAndRedirectOnFailure($moveHintIds); - - $moveHintId = current($moveHintIds); - - $this->checkForExistingHintRelatingToCurrentQuestionAndRedirectOnFailure($moveHintId); - - $this->hintOrderingClipboard->setStored($moveHintId); - - $this->ctrl->redirect($this, self::CMD_SHOW_LIST); - } - - private function pasteFromOrderingClipboardBeforeCmd(): void - { - if (!$this->isEditingEnabled()) { - return; - } - - $targetHintIds = $this->fetchHintIdsParameter(); - $this->checkForSingleHintIdAndRedirectOnFailure($targetHintIds); - - $targetHintId = current($targetHintIds); - - $this->checkForExistingHintRelatingToCurrentQuestionAndRedirectOnFailure($targetHintId); - - $curQuestionHintList = ilAssQuestionHintList::getListByQuestionId($this->question_obj->getId()); - $newQuestionHintList = new ilAssQuestionHintList($this->question_obj->getId()); - - foreach ($curQuestionHintList as $questionHint) { - /* @var $questionHint ilAssQuestionHint */ - - if ($questionHint->getId() == $this->hintOrderingClipboard->getStored()) { - continue; - } - - if ($questionHint->getId() == $targetHintId) { - $targetQuestionHint = $questionHint; - - $pasteQuestionHint = ilAssQuestionHint::getInstanceById($this->hintOrderingClipboard->getStored()); - - $newQuestionHintList->addHint($pasteQuestionHint); - } - - $newQuestionHintList->addHint($questionHint); - } - - $successMsg = sprintf( - $this->lng->txt('tst_question_hints_paste_before_success_msg'), - $pasteQuestionHint->getIndex(), - $targetQuestionHint->getIndex() - ); - - $newQuestionHintList->reIndex(); - - $this->hintOrderingClipboard->resetStored(); - - $this->main_tpl->setOnScreenMessage('success', $successMsg, true); - - $this->ctrl->redirect($this, self::CMD_SHOW_LIST); - } - - /** - * pastes a hint from ordering clipboard after the selected one - * - * @access private - * @global ilCtrl $ilCtrl - * @global ilLanguage $lng - */ - private function pasteFromOrderingClipboardAfterCmd(): void - { - if (!$this->isEditingEnabled()) { - return; - } - - global $DIC; - $ilCtrl = $DIC['ilCtrl']; - $lng = $DIC['lng']; - - $targetHintIds = $this->fetchHintIdsParameter(); - $this->checkForSingleHintIdAndRedirectOnFailure($targetHintIds); - - $targetHintId = current($targetHintIds); - - $this->checkForExistingHintRelatingToCurrentQuestionAndRedirectOnFailure($targetHintId); - - $curQuestionHintList = ilAssQuestionHintList::getListByQuestionId($this->question_obj->getId()); - $newQuestionHintList = new ilAssQuestionHintList($this->question_obj->getId()); - - foreach ($curQuestionHintList as $questionHint) { - /* @var $questionHint ilAssQuestionHint */ - - if ($questionHint->getId() == $this->hintOrderingClipboard->getStored()) { - continue; - } - - $newQuestionHintList->addHint($questionHint); - - if ($questionHint->getId() == $targetHintId) { - $targetQuestionHint = $questionHint; - - $pasteQuestionHint = ilAssQuestionHint::getInstanceById($this->hintOrderingClipboard->getStored()); - - $newQuestionHintList->addHint($pasteQuestionHint); - } - } - - $successMsg = sprintf( - $lng->txt('tst_question_hints_paste_after_success_msg'), - $pasteQuestionHint->getIndex(), - $targetQuestionHint->getIndex() - ); - - $newQuestionHintList->reIndex(); - - $this->hintOrderingClipboard->resetStored(); - - $this->main_tpl->setOnScreenMessage('success', $successMsg, true); - - $ilCtrl->redirect($this, self::CMD_SHOW_LIST); - } - - /** - * resets the ordering clipboard - * - * @access private - * @global ilCtrl $ilCtrl - * @global ilLanguage $lng - */ - private function resetOrderingClipboardCmd(): void - { - global $DIC; - $ilCtrl = $DIC['ilCtrl']; - $lng = $DIC['lng']; - - $this->hintOrderingClipboard->resetStored(); - - $this->main_tpl->setOnScreenMessage('info', $lng->txt('tst_question_hints_ordering_clipboard_resetted'), true); - $ilCtrl->redirect($this, self::CMD_SHOW_LIST); - } - - /** - * inits the notification telling the user, - * that a hint is stored to hint ordering clipboard - * - * @access private - * @global ilLanguage $lng - */ - private function initHintOrderingClipboardNotification(): void - { - global $DIC; - $lng = $DIC['lng']; - - if (!$this->hintOrderingClipboard->hasStored()) { - return; - } - - $questionHint = ilAssQuestionHint::getInstanceById($this->hintOrderingClipboard->getStored()); - - $this->main_tpl->setOnScreenMessage('info', sprintf( - $lng->txt('tst_question_hints_item_stored_in_ordering_clipboard'), - $questionHint->getIndex() - )); - } - - /** - * checks for an existing hint relating to current question and redirects - * with corresponding failure message on failure - * - * @access private - * @param integer $hintId - */ - private function checkForExistingHintRelatingToCurrentQuestionAndRedirectOnFailure($hintId): void - { - $questionHintList = ilAssQuestionHintList::getListByQuestionId($this->question_obj->getId()); - - if (!$questionHintList->hintExists($hintId)) { - $this->main_tpl->setOnScreenMessage('failure', $this->lng->txt('tst_question_hints_invalid_hint_id'), true); - $this->ctrl->redirect($this, self::CMD_SHOW_LIST); - } - } - - private function getQuestionHintListWithoutHintStoredInOrderingClipboard( - ilAssQuestionHintList $questionHintList - ): ilAssQuestionHintList { - $filteredQuestionHintList = new ilAssQuestionHintList(); - - foreach ($questionHintList as $questionHint) { - if ($questionHint->getId() !== $this->hintOrderingClipboard->getStored()) { - $filteredQuestionHintList->addHint($questionHint); - } - } - - return $filteredQuestionHintList; - } - - private function checkForSingleHintIdAndRedirectOnFailure(array $hint_ids): void - { - if ($hint_ids === []) { - $this->main_tpl->setOnScreenMessage( - 'failure', - $this->lng->txt('tst_question_hints_cut_hints_missing_selection_msg'), - true - ); - $this->ctrl->redirect($this, self::CMD_SHOW_LIST); - } - - if (count($hint_ids) > 1) { - $this->main_tpl->setOnScreenMessage( - 'failure', - $this->lng->txt('tst_question_hints_cut_hints_single_selection_msg'), - true - ); - $this->ctrl->redirect($this, self::CMD_SHOW_LIST); - } - } - - private function fetchHintIdsParameter(): array - { - $hint_ids = [$this->request_data_collector->int('hint_id')]; - if ($hint_ids[0] !== 0) { - return $hint_ids; - } - - return $this->request_data_collector->intArray('hint_ids'); - } - - private function fetchHintIndexesParameter(): array - { - $hint_indexes = $this->request_data_collector->intArray('hint_indexes'); - asort($hint_indexes); - return $hint_indexes; - } - - public function confirmSyncCmd(): void - { - $modal = $this->question_gui->getQuestionSyncModal(self::CMD_SYNC, self::class); - $this->showListCmd($modal); - } - - public function syncCmd(): void - { - $this->question_obj->syncWithOriginal(); - $this->showListCmd(); - } - - public function getHintPresentationLinkTarget( - int $hint_id, - bool $xml_style = true - ): string { - if ($this->question_obj->isAdditionalContentEditingModePageObject()) { - $this->ctrl->setParameterByClass('ilasshintpagegui', 'hint_id', $hint_id); - return $this->ctrl->getLinkTargetByClass('ilAssHintPageGUI', '', '', false, $xml_style); - } - - $this->ctrl->setParameter($this, 'hintId', $hint_id); - return $this->ctrl->getLinkTarget($this, self::CMD_SHOW_HINT, '', false, $xml_style); - } - - private function showHintCmd(): void - { - if (!$this->request_data_collector->isset('hintId') || !(int) $this->request_data_collector->raw('hintId')) { - throw new ilTestException('no hint id given'); - } - - $this->tabs->clearTargets(); - $this->tabs->clearSubTabs(); - - $this->tabs->setBackTarget( - $this->lng->txt('tst_question_hints_back_to_hint_list'), - $this->ctrl->getLinkTargetByClass(self::class, self::CMD_SHOW_LIST) - ); - - $questionHint = ilAssQuestionHint::getInstanceById((int) $this->request_data_collector->raw('hintId')); - - // build form - - $form = new ilPropertyFormGUI(); - - $form->setFormAction($this->ctrl->getFormAction($this)); - - $form->setTableWidth('100%'); - - $form->setTitle(sprintf( - $this->lng->txt('tst_question_hints_form_header_edit'), - $questionHint->getIndex(), - $this->question_obj->getTitle() - )); - - // form input: hint text - - $nonEditableHintText = new ilNonEditableValueGUI( - $this->lng->txt('tst_question_hints_form_label_hint_text'), - 'hint_text', - true - ); - $nonEditableHintText->setValue( - ilLegacyFormElementsUtil::prepareTextareaOutput($questionHint->getText(), true) - ); - $form->addItem($nonEditableHintText); - - // form input: hint points - - $nonEditableHintPoints = new ilNonEditableValueGUI( - $this->lng->txt('tst_question_hints_form_label_hint_points'), - 'hint_points' - ); - $nonEditableHintPoints->setValue($questionHint->getPoints()); - $form->addItem($nonEditableHintPoints); - - $this->main_tpl->setContent($form->getHTML()); - } -} diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintsOrderingClipboard.php b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintsOrderingClipboard.php deleted file mode 100755 index 6848da0cbf72..000000000000 --- a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintsOrderingClipboard.php +++ /dev/null @@ -1,132 +0,0 @@ - - * @version $Id$ - * - * @package Modules/TestQuestionPool - */ -class ilAssQuestionHintsOrderingClipboard -{ - /** - * the id of question the stored hint relates to - * - * @access private - * @var integer - */ - private $questionId = null; - - /** - * Constructor - * - * @access public - * @param assQuestion $questionOBJ - */ - public function __construct(assQuestion $questionOBJ) - { - $this->questionId = $questionOBJ->getId(); - - $class = ilSession::get(__CLASS__); - if ($class == null) { - ilSession::set(__CLASS__, []); - } - - if (!isset($class[$this->questionId])) { - $class[$this->questionId] = null; - ilSession::set(__CLASS__, $class); - } - } - - /** - * resets the clipboard by ensuring no hint is stored - * - * @access public - */ - public function resetStored(): void - { - $class = ilSession::get(__CLASS__); - unset($class[$this->questionId]); - ilSession::set(__CLASS__, $class); - //$_SESSION[__CLASS__][$this->questionId] = null; - } - - /** - * sets the passed hint id, so relating hint - * is deemed to be cut to clipboard - * - * @access public - * @param integer $hintId - */ - public function setStored($hintId): void - { - $class = ilSession::get(__CLASS__); - $class[$this->questionId] = $hintId; - ilSession::set(__CLASS__, $class); - //$_SESSION[__CLASS__][$this->questionId] = $hintId; - } - - /** - * returns the hint id currently stored in clipboard - * - * @access public - * @return integer $hintId - */ - public function getStored(): int - { - $class = ilSession::get(__CLASS__); - return $class[$this->questionId]; - } - - /** - * returns the fact wether the hint relating to the passed hint id - * is stored in clipboard or not - * - * @access public - * @param integer $hintId - * @return boolean $isStored - */ - public function isStored($hintId): bool - { - $class = ilSession::get(__CLASS__); - if ($class[$this->questionId] === $hintId) { - return true; - } - - return false; - } - - /** - * returns the fact wether any hint is stored in clipboard currently or not - * - * @access public - * @return boolean $hasStored - */ - public function hasStored(): bool - { - $class = ilSession::get(__CLASS__); - if ($class[$this->questionId] !== null) { - return true; - } - - return false; - } -} diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintsTableGUI.php b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintsTableGUI.php deleted file mode 100755 index eae87ab8a312..000000000000 --- a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionHintsTableGUI.php +++ /dev/null @@ -1,342 +0,0 @@ - - * @version $Id$ - * - * @package Modules/TestQuestionPool - */ -class ilAssQuestionHintsTableGUI extends ilTable2GUI -{ - /** - * the factor the ordering position value is multiplicated with - * (so the user gets non decimal gaps for reordering .. e.g. 10, 20, 30 .. not 1, 2, 3) - */ - public const INDEX_TO_POSITION_FACTOR = 10; - - /** - * the available table modes controlling the tables behaviour - */ - public const TBL_MODE_TESTOUTPUT = '1'; - public const TBL_MODE_ADMINISTRATION = '2'; - - /** - * the object instance for current question - * - * @access private - * @var assQuestion - */ - private $questionOBJ = null; - - /** - * the table mode controlling the tables behaviour - * (either self::TBL_MODE_TESTOUTPUT or self::TBL_MODE_ADMINISTRATION) - * - * @var string - */ - private $tableMode = null; - - private $hintOrderingClipboard = null; - private \ILIAS\DI\UIServices $ui; - - /** - * Constructor - * - * @access public - * @global ilCtrl $ilCtrl - * @global ilLanguage $lng - * @param assQuestion $questionOBJ - * @param ilAssQuestionHintList $questionHintList - * @param ilAssQuestionHintsGUI $parentGUI - * @param string $parentCmd - */ - public function __construct( - assQuestion $questionOBJ, - ilAssQuestionHintList $questionHintList, - ilAssQuestionHintAbstractGUI $parentGUI, - $parentCmd, - $tableMode = self::TBL_MODE_TESTOUTPUT, - ?ilAssQuestionHintsOrderingClipboard $hintOrderingClipboard = null - ) { - global $DIC; - $lng = $DIC['lng']; - $this->ui = $DIC->ui(); - - $this->questionOBJ = $questionOBJ; - $this->tableMode = $tableMode; - $this->hintOrderingClipboard = $hintOrderingClipboard; - - $this->setPrefix('tsthints' . $tableMode); - $this->setId('tsthints' . $tableMode); - - parent::__construct($parentGUI, $parentCmd); - - $this->setTitle(sprintf($lng->txt('tst_question_hints_table_header'), $questionOBJ->getTitle())); - $this->setNoEntriesText($lng->txt('tst_question_hints_table_no_items')); - - // we don't take care about offset/limit values, so this avoids segmentation in general - // --> required for ordering via clipboard feature - $this->setExternalSegmentation(true); - - $tableData = $questionHintList->getTableData(); - - if ($this->questionOBJ->isAdditionalContentEditingModePageObject()) { - foreach ($tableData as $key => $data) { - $this->questionOBJ->ensureHintPageObjectExists($data['hint_id']); - $pageObjectGUI = new ilAssHintPageGUI($data['hint_id']); - $pageObjectGUI->setOutputMode("presentation"); - $tableData[$key]['hint_text'] = $pageObjectGUI->presentation(); - } - } - - $this->setData($tableData); - - if ($this->tableMode == self::TBL_MODE_ADMINISTRATION) { - $this->setRowTemplate('tpl.tst_question_hints_administration_table_row.html', 'components/ILIAS/TestQuestionPool'); - - $this->setSelectAllCheckbox('hint_ids[]'); - - $rowCount = count($tableData); - $this->initAdministrationColumns($rowCount); - $this->initAdministrationCommands($rowCount); - } else { - $this->setRowTemplate('tpl.tst_question_hints_testoutput_table_row.html', 'components/ILIAS/TestQuestionPool'); - - $this->initTestoutputColumns(); - $this->initTestoutputCommands(); - } - } - - /** - * inits the required command buttons / multi selection commands - * for administration table mode - * - * @access private - * @global ilCtrl $ilCtrl - * @global ilLanguage $lng - * @param integer $rowCount - */ - private function initAdministrationCommands($rowCount): void - { - global $DIC; - $ilCtrl = $DIC['ilCtrl']; - $lng = $DIC['lng']; - - $this->setFormAction($ilCtrl->getFormAction($this->parent_obj)); - - if ($this->hintOrderingClipboard->hasStored()) { - $this->addMultiCommand( - ilAssQuestionHintsGUI::CMD_PASTE_FROM_ORDERING_CLIPBOARD_BEFORE, - $lng->txt('tst_questions_hints_table_multicmd_paste_hint_before') - ); - - $this->addMultiCommand( - ilAssQuestionHintsGUI::CMD_PASTE_FROM_ORDERING_CLIPBOARD_AFTER, - $lng->txt('tst_questions_hints_table_multicmd_paste_hint_after') - ); - } elseif ($rowCount > 0) { - $this->addMultiCommand( - ilAssQuestionHintsGUI::CMD_CONFIRM_DELETE, - $lng->txt('tst_questions_hints_table_multicmd_delete_hint') - ); - - if ($rowCount > 1) { - $this->addMultiCommand( - ilAssQuestionHintsGUI::CMD_CUT_TO_ORDERING_CLIPBOARD, - $lng->txt('tst_questions_hints_table_multicmd_cut_hint') - ); - } - - $this->addCommandButton( - ilAssQuestionHintsGUI::CMD_SAVE_LIST_ORDER, - $lng->txt('tst_questions_hints_table_cmd_save_order') - ); - } - } - - /** - * inits the required command buttons / multi selection commands - * for testoutput table mode - * - * @access private - * @global ilCtrl $ilCtrl - * @global ilLanguage $lng - */ - private function initTestoutputCommands(): void - { - if ($this->parent_obj instanceof ilAssQuestionHintsGUI) { - return; - } - - global $DIC; - $ilCtrl = $DIC['ilCtrl']; - $lng = $DIC['lng']; - - $this->setFormAction($ilCtrl->getFormAction($this->parent_obj)); - - $this->addCommandButton( - ilAssQuestionHintRequestGUI::CMD_BACK_TO_QUESTION, - $lng->txt('tst_question_hints_back_to_question') - ); - } - - /** - * inits the required columns - * for administration table mode - * - * @access private - * @global ilLanguage $lng - * @param integer $rowCount - */ - private function initAdministrationColumns($rowCount): void - { - global $DIC; - $lng = $DIC['lng']; - - $this->addColumn('', '', '30', true); - - $this->addColumn($lng->txt('tst_question_hints_table_column_hint_order'), 'hint_index', '60'); - $this->addColumn($lng->txt('tst_question_hints_table_column_hint_text'), 'hint_text'); - $this->addColumn($lng->txt('tst_question_hints_table_column_hint_points'), 'hint_points', '250'); - - $this->addColumn('', '', '100'); - - $this->setDefaultOrderField("hint_index"); - $this->setDefaultOrderDirection("asc"); - - if ($rowCount < 1) { - $this->disable('header'); - } - } - - /** - * inits the required columns - * for testoutput table mode - * - * @access private - * @global ilLanguage $lng - */ - private function initTestoutputColumns(): void - { - global $DIC; - $lng = $DIC['lng']; - - $this->addColumn($lng->txt('tst_question_hints_table_column_hint_index'), 'hint_index', '200'); - $this->addColumn($lng->txt('tst_question_hints_table_column_hint_text'), 'hint_text'); - $this->addColumn($lng->txt('tst_question_hints_table_column_hint_points'), 'hint_points', '200'); - - $this->setDefaultOrderField("hint_index"); - $this->setDefaultOrderDirection("asc"); - } - - /** - * returns the fact wether the passed field - * is to be ordered numerically or not - * @access public - * @param string $a_field - * @return boolean $numericOrdering - */ - public function numericOrdering(string $a_field): bool - { - switch ($a_field) { - case 'hint_index': - case 'hint_points': - - return true; - } - - return false; - } - - /** - * renders a table row by filling wor data to table row template - * @access public - * @param array $a_set - *@global ilLanguage $lng - * @global ilCtrl $ilCtrl - */ - public function fillRow(array $a_set): void - { - global $DIC; - $ilCtrl = $DIC['ilCtrl']; - $lng = $DIC['lng']; - - if ($this->tableMode == self::TBL_MODE_ADMINISTRATION) { - $actions = []; - - if ($this->questionOBJ->isAdditionalContentEditingModePageObject()) { - $actions[] = $this->ui->factory()->button()->shy( - $lng->txt('tst_question_hints_table_link_edit_hint_points'), - ilUtil::appendUrlParameterString( - $ilCtrl->getLinkTargetByClass('ilAssQuestionHintGUI', ilAssQuestionHintGUI::CMD_SHOW_FORM), - "hint_id={$a_set['hint_id']}", - true - ) - ); - - $actions[] = $this->ui->factory()->button()->shy( - $lng->txt('tst_question_hints_table_link_edit_hint_page'), - ilUtil::appendUrlParameterString( - $ilCtrl->getLinkTargetByClass('ilasshintpagegui', 'edit'), - "hint_id={$a_set['hint_id']}", - true - ) - ); - } else { - $actions[] = $this->ui->factory()->button()->shy( - $lng->txt('tst_question_hints_table_link_edit_hint'), - ilUtil::appendUrlParameterString( - $ilCtrl->getLinkTargetByClass('ilAssQuestionHintGUI', ilAssQuestionHintGUI::CMD_SHOW_FORM), - "hint_id={$a_set['hint_id']}", - true - ) - ); - } - - $actions[] = $this->ui->factory()->button()->shy( - $lng->txt('tst_question_hints_table_link_delete_hint'), - ilUtil::appendUrlParameterString( - $ilCtrl->getLinkTarget($this->parent_obj, ilAssQuestionHintsGUI::CMD_CONFIRM_DELETE), - "hint_id={$a_set['hint_id']}", - true - ) - ); - - $list = $this->ui->factory()->dropdown()->standard($actions)->withLabel($lng->txt('actions')); - - $this->tpl->setVariable('ACTIONS', $this->ui->renderer()->render($list)); - $this->tpl->setVariable('HINT_ID', $a_set['hint_id']); - $hintIndex = $a_set['hint_index'] * self::INDEX_TO_POSITION_FACTOR; - - } else { - $showHref = $this->parent_obj->getHintPresentationLinkTarget($a_set['hint_id']); - - $this->tpl->setVariable('HINT_HREF', $showHref); - - $hintIndex = ilAssQuestionHint::getHintIndexLabel($lng, $a_set['hint_index']); - } - - $this->tpl->setVariable('HINT_INDEX', $hintIndex); - $txt = ilLegacyFormElementsUtil::prepareTextareaOutput($a_set['hint_text'], true); - $this->tpl->setVariable('HINT_TEXT', $txt); - $this->tpl->setVariable('HINT_POINTS', $a_set['hint_points']); - } -} diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionList.php b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionList.php index a76fb8404dff..a41afe4b789b 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionList.php +++ b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionList.php @@ -170,8 +170,8 @@ public function setJoinObjectData(bool $a_val): void private function getParentObjFilterExpression(): ?string { - if ($this->getParentObjId()) { - return "qpl_questions.obj_fi = {$this->db->quote($this->getParentObjId(), ilDBConstants::T_INTEGER)}"; + if ($this->parentObjId) { + return "qpl_questions.obj_fi = {$this->db->quote($this->parentObjId, ilDBConstants::T_INTEGER)}"; } if (!empty($this->parentObjIdsFilter)) { @@ -229,22 +229,6 @@ private function handleFeedbackJoin(string $tableJoin): string return $tableJoin; } - private function handleHintJoin(string $tableJoin): string - { - $feedback_join = match ($this->fieldFilters['hints'] ?? null) { - 'true' => 'INNER', - 'false' => 'LEFT', - default => null - }; - - if (isset($feedback_join)) { - $SQL = "$feedback_join JOIN qpl_hints ON qpl_hints.qht_question_fi = qpl_questions.question_id "; - $tableJoin .= !str_contains($tableJoin, $SQL) ? $SQL : ''; - } - - return $tableJoin; - } - private function getTaxonomyFilterExpressions(): array { $expressions = $this->getFilterByAssignedTaxonomyIdsExpression(); @@ -367,13 +351,6 @@ private function getQuestionIdsFilterExpressions(): array return $expressions; } - private function getParentObjectIdFilterExpression(): ?string - { - return $this->parentObjId - ? "qpl_questions.obj_fi = {$this->db->quote($this->parentObjId, ilDBConstants::T_INTEGER)}" - : null; - } - private function getAnswerStatusFilterExpressions(): array { return match ($this->answerStatusFilter) { @@ -411,7 +388,6 @@ private function getTableJoinExpression(): string } $tableJoin = $this->handleFeedbackJoin($tableJoin); - $tableJoin = $this->handleHintJoin($tableJoin); if ($this->answerStatusActiveId) { $tableJoin .= " @@ -436,10 +412,6 @@ private function getConditionalFilterExpression(): string $conditions[] = $this->getParentObjFilterExpression(); } - if ($this->getParentObjectIdFilterExpression() !== null) { - $conditions[] = $this->getParentObjectIdFilterExpression(); - } - $conditions = array_merge( $conditions, $this->getQuestionIdsFilterExpressions(), @@ -477,7 +449,7 @@ private function getSelectFieldsExpression(): string } $select_fields[] = $this->generateFeedbackSubquery(); - $select_fields[] = $this->generateHintSubquery(); + $select_fields[] = $this->generateTaxonomySubquery(); $select_fields = implode(', ', $select_fields); @@ -508,12 +480,6 @@ private function generateFeedbackSubquery(): string return "CASE $feedback_case_subquery ELSE FALSE END AS feedback"; } - private function generateHintSubquery(): string - { - $hint_subquery = 'SELECT 1 FROM qpl_hints WHERE qpl_hints.qht_question_fi = qpl_questions.question_id'; - return "CASE WHEN EXISTS ($hint_subquery) THEN TRUE ELSE FALSE END AS hints"; - } - private function generateTaxonomySubquery(): string { $tax_node_assignment_table = 'tax_node_assignment'; @@ -539,13 +505,6 @@ private function getHavingFilterExpression(): string continue; } - - if ($fieldName === 'hints') { - $fieldValue = strtoupper($fieldValue); - if (in_array($fieldValue, ['TRUE', 'FALSE'], true)) { - $expressions[] = "hints IS $fieldValue"; - } - } } $having = implode(' AND ', $expressions); @@ -600,10 +559,9 @@ public function load(): void { $this->checkFilters(); - $tags_trafo = $this->refinery->string()->stripTags(); + $tags_trafo = $this->refinery->encode()->htmlSpecialCharsAsEntities(); - $query = $this->buildQuery(); - $res = $this->db->query($query); + $res = $this->db->query($this->buildQuery()); while ($row = $this->db->fetchAssoc($res)) { $row = ilAssQuestionType::completeMissingPluginName($row); @@ -612,12 +570,11 @@ public function load(): void } $row['title'] = $tags_trafo->transform($row['title'] ?? ' '); - $row['description'] = $tags_trafo->transform($row['description'] !== '' && $row['description'] !== null ? $row['description'] : ' '); + $row['description'] = $tags_trafo->transform($row['description'] ?? ''); $row['author'] = $tags_trafo->transform($row['author']); $row['taxonomies'] = $this->loadTaxonomyAssignmentData($row['obj_fi'], $row['question_id']); $row['ttype'] = $this->lng->txt($row['type_tag']); $row['feedback'] = $row['feedback'] === 1; - $row['hints'] = $row['hints'] === 1; $row['comments'] = $this->getNumberOfCommentsForQuestion($row['question_id']); if ( diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionPreviewGUI.php b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionPreviewGUI.php index fa5c77b42478..055fbf70ebc2 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionPreviewGUI.php +++ b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionPreviewGUI.php @@ -37,7 +37,6 @@ * * @ilCtrl_Calls ilAssQuestionPreviewGUI: ilAssQuestionPreviewToolbarGUI * @ilCtrl_Calls ilAssQuestionPreviewGUI: ilAssQuestionRelatedNavigationBarGUI - * @ilCtrl_Calls ilAssQuestionPreviewGUI: ilAssQuestionHintRequestGUI * @ilCtrl_Calls ilAssQuestionPreviewGUI: ilAssGenFeedbackPageGUI * @ilCtrl_Calls ilAssQuestionPreviewGUI: ilAssSpecFeedbackPageGUI * @ilCtrl_Calls ilAssQuestionPreviewGUI: ilCommentGUI @@ -49,8 +48,6 @@ class ilAssQuestionPreviewGUI public const CMD_STATISTICS = 'assessment'; public const CMD_INSTANT_RESPONSE = 'instantResponse'; public const CMD_HANDLE_QUESTION_ACTION = 'handleQuestionAction'; - public const CMD_GATEWAY_CONFIRM_HINT_REQUEST = 'gatewayConfirmHintRequest'; - public const CMD_GATEWAY_SHOW_HINT_LIST = 'gatewayShowHintList'; public const TAB_ID_QUESTION = 'question'; @@ -62,7 +59,6 @@ class ilAssQuestionPreviewGUI private ?assQuestion $question_obj = null; private ?ilAssQuestionPreviewSettings $preview_settings = null; private ?ilAssQuestionPreviewSession $preview_session = null; - private ?ilAssQuestionPreviewHintTracking $hint_tracking = null; private ?string $info_message = null; @@ -133,7 +129,7 @@ public function initQuestion(assQuestionGUI $question_gui, int $parent_obj_id): $this->tabs->clearTargets(); $this->tabs->addTarget( self::TAB_ID_QUESTION, - $this->ctrl->getLinkTargetByClass('ilAssQuestionPreviewGUI', self::CMD_SHOW), + $this->ctrl->getLinkTargetByClass(self::class, self::CMD_SHOW), '', [strtolower(__CLASS__)] ); @@ -141,11 +137,11 @@ public function initQuestion(assQuestionGUI $question_gui, int $parent_obj_id): $q_type = $this->question_obj->getQuestionType(); $classname = $q_type . 'GUI'; $this->tabs->addTarget( - "statistics", - $this->ctrl->getLinkTargetByClass('ilAssQuestionPreviewGUI', "assessment"), - ["assessment"], + 'statistics', + $this->ctrl->getLinkTargetByClass(self::class, 'assessment'), + ['assessment'], $classname, - "" + '' ); $this->question_gui->populateJavascriptFilesRequiredForWorkForm($this->tpl); @@ -168,19 +164,14 @@ public function initPreviewSession(int $user_id, int $question_id): void $this->preview_session->init(); } - public function initHintTracking(): void - { - $this->hint_tracking = new ilAssQuestionPreviewHintTracking($this->db, $this->preview_session); - } - public function initStyleSheets(): void { - $this->tpl->setCurrentBlock("ContentStyle"); - $this->tpl->setVariable("LOCATION_CONTENT_STYLESHEET", ilObjStyleSheet::getContentStylePath(0)); + $this->tpl->setCurrentBlock('ContentStyle'); + $this->tpl->setVariable('LOCATION_CONTENT_STYLESHEET', ilObjStyleSheet::getContentStylePath(0)); $this->tpl->parseCurrentBlock(); - $this->tpl->setCurrentBlock("SyntaxStyle"); - $this->tpl->setVariable("LOCATION_SYNTAX_STYLESHEET", ilObjStyleSheet::getSyntaxStylePath()); + $this->tpl->setCurrentBlock('SyntaxStyle'); + $this->tpl->setVariable('LOCATION_SYNTAX_STYLESHEET', ilObjStyleSheet::getSyntaxStylePath()); $this->tpl->parseCurrentBlock(); } @@ -197,20 +188,6 @@ public function executeCommand(): void $nextClass = $this->ctrl->getNextClass($this); switch ($nextClass) { - case 'ilassquestionhintrequestgui': - $gui = new ilAssQuestionHintRequestGUI( - $this, - self::CMD_SHOW, - $this->question_gui, - $this->hint_tracking, - $this->ctrl, - $this->lng, - $this->tpl, - $this->tabs, - $this->global_screen - ); - $this->ctrl->forwardCommand($gui); - break; case 'ilassspecfeedbackpagegui': case 'ilassgenfeedbackpagegui': $forwarder = new ilAssQuestionFeedbackPageObjectCommandForwarder($this->question_obj, $this->ctrl, $this->tabs, $this->lng); @@ -333,7 +310,6 @@ public function resetCmd(): void { $this->preview_session->setRandomizerSeed(null); $this->preview_session->setParticipantsSolution(null); - $this->preview_session->resetRequestedHints(); $this->preview_session->setInstantResponseActive(false); $this->tpl->setOnScreenMessage('info', $this->lng->txt('qst_preview_reset_msg'), true); @@ -406,7 +382,7 @@ private function populateQuestionOutput(ilTemplate $tpl): void $page_gui->setQuestionHTML([$this->question_obj->getId() => $question_html]); - $page_gui->setPresentationTitle($this->question_obj->getTitle()); + $page_gui->setPresentationTitle($this->question_obj->getTitleForHTMLOutput()); $tpl->setVariable('QUESTION_OUTPUT', $page_gui->preview()); // \ilPageObjectGUI::preview sets an undefined tab, so the "question" tab has to be activated again @@ -457,15 +433,7 @@ private function getQuestionNavigationHtml(): string $navGUI = new ilAssQuestionRelatedNavigationBarGUI($this->ctrl, $this->lng); $navGUI->setInstantResponseCmd(self::CMD_INSTANT_RESPONSE); - $navGUI->setHintRequestCmd(self::CMD_GATEWAY_CONFIRM_HINT_REQUEST); - $navGUI->setHintListCmd(self::CMD_GATEWAY_SHOW_HINT_LIST); - $navGUI->setInstantResponseEnabled($this->preview_settings->isInstantFeedbackNavigationRequired()); - $navGUI->setHintProvidingEnabled($this->preview_settings->isHintProvidingEnabled()); - - $navGUI->setHintRequestsPossible($this->hint_tracking->requestsPossible()); - $navGUI->setHintRequestsExist($this->hint_tracking->requestsExist()); - return $this->ctrl->getHTML($navGUI); } @@ -573,34 +541,6 @@ public function saveQuestionSolution(): bool return $this->question_obj->persistPreviewState($this->preview_session); } - public function gatewayConfirmHintRequestCmd(): void - { - if (!$this->saveQuestionSolution()) { - $this->preview_session->setInstantResponseActive(false); - $this->showCmd(); - return; - } - - $this->ctrl->redirectByClass( - 'ilAssQuestionHintRequestGUI', - ilAssQuestionHintRequestGUI::CMD_CONFIRM_REQUEST - ); - } - - public function gatewayShowHintListCmd(): void - { - if (!$this->saveQuestionSolution()) { - $this->preview_session->setInstantResponseActive(false); - $this->showCmd(); - return; - } - - $this->ctrl->redirectByClass( - 'ilAssQuestionHintRequestGUI', - ilAssQuestionHintRequestGUI::CMD_SHOW_LIST - ); - } - /** * @return Transformation */ diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionPreviewHintTracking.php b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionPreviewHintTracking.php deleted file mode 100755 index f6a2c247222e..000000000000 --- a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionPreviewHintTracking.php +++ /dev/null @@ -1,142 +0,0 @@ - - * @version $Id$ - * - * @package components\ILIAS/Test - */ -class ilAssQuestionPreviewHintTracking -{ - /** - * @var ilDBInterface - */ - private $db; - - /** - * @var ilAssQuestionPreviewSession - */ - private $previewSession; - - public function __construct(ilDBInterface $db, ilAssQuestionPreviewSession $previewSession) - { - $this->db = $db; - $this->previewSession = $previewSession; - } - - public function requestsExist(): bool - { - return ( - $this->previewSession->getNumRequestedHints() > 0 - ); - } - - public function requestsPossible(): bool - { - $query = " - SELECT COUNT(qht_hint_id) cnt_available - FROM qpl_hints - WHERE qht_question_fi = %s - "; - - $res = $this->db->queryF( - $query, - ['integer'], - [$this->previewSession->getQuestionId()] - ); - - $row = $this->db->fetchAssoc($res); - - if ($row['cnt_available'] > $this->previewSession->getNumRequestedHints()) { - return true; - } - - return false; - } - - public function getNextRequestableHint(): ilAssQuestionHint - { - $query = " - SELECT qht_hint_id - - FROM qpl_hints - - WHERE qht_question_fi = %s - - ORDER BY qht_hint_index ASC - "; - - $res = $this->db->queryF( - $query, - ['integer'], - [$this->previewSession->getQuestionId()] - ); - - while ($row = $this->db->fetchAssoc($res)) { - if (!$this->isRequested($row['qht_hint_id'])) { - return ilAssQuestionHint::getInstanceById($row['qht_hint_id']); - } - } - - throw new ilTestException( - "no next hint found for questionId={$this->previewSession->getQuestionId()}, userId={$this->previewSession->getUserId()}" - ); - } - - public function storeRequest(ilAssQuestionHint $questionHint): void - { - $this->previewSession->addRequestedHint($questionHint->getId()); - } - - public function isRequested($hintId): bool - { - return $this->previewSession->isHintRequested($hintId); - } - - public function getNumExistingRequests(): int - { - return $this->previewSession->getNumRequestedHints(); - } - - public function getRequestedHintsList(): ilAssQuestionHintList - { - $hintIds = $this->previewSession->getRequestedHints(); - - $requestedHintsList = ilAssQuestionHintList::getListByHintIds($hintIds); - - return $requestedHintsList; - } - - public function getRequestStatisticData(): ilAssQuestionHintRequestStatisticData - { - $count = 0; - $points = 0; - - foreach ($this->getRequestedHintsList() as $hint) { - $count++; - $points += $hint->getPoints(); - } - - $requestsStatisticData = new ilAssQuestionHintRequestStatisticData(); - $requestsStatisticData->setRequestsCount($count); - $requestsStatisticData->setRequestsPoints($points); - - return $requestsStatisticData; - } -} diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionPreviewSession.php b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionPreviewSession.php index 0cf29e17f558..44aa7d27de94 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionPreviewSession.php +++ b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionPreviewSession.php @@ -1,4 +1,5 @@ issetSessionValue(self::SESSION_SUBINDEX_PARTICIPANT_SOLUTION); } - public function getNumRequestedHints(): int - { - if (!$this->issetSessionValue(self::SESSION_SUBINDEX_REQUESTED_HINTS)) { - return 0; - } - $hints = $this->readSessionValue(self::SESSION_SUBINDEX_REQUESTED_HINTS); - - if (!is_array($hints)) { - return 0; - } - - return count($hints); - } - - public function isHintRequested($hintId): bool - { - if ($this->issetSessionValue(self::SESSION_SUBINDEX_REQUESTED_HINTS)) { - $requestedHints = $this->readSessionValue(self::SESSION_SUBINDEX_REQUESTED_HINTS); - return isset($requestedHints[$hintId]); - } - - return false; - } - - public function addRequestedHint($hintId): void - { - $requestedHints = $this->getRequestedHints(); - $requestedHints[$hintId] = $hintId; - $this->saveSessionValue(self::SESSION_SUBINDEX_REQUESTED_HINTS, $requestedHints); - } - - public function getRequestedHints() - { - if ($this->issetSessionValue(self::SESSION_SUBINDEX_REQUESTED_HINTS)) { - return $this->readSessionValue(self::SESSION_SUBINDEX_REQUESTED_HINTS); - } - - return []; - } - - public function resetRequestedHints(): void - { - $this->saveSessionValue(self::SESSION_SUBINDEX_REQUESTED_HINTS, []); - } - public function setRandomizerSeed($seed): void { $this->saveSessionValue(self::SESSION_SUBINDEX_RANDOMIZER_SEED, $seed); diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionPreviewSettings.php b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionPreviewSettings.php index b1a93dc12aad..a5c0b3e4eb30 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionPreviewSettings.php +++ b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionPreviewSettings.php @@ -30,13 +30,8 @@ class ilAssQuestionPreviewSettings * @var bool */ protected $reachedPointsEnabled = false; - private $genericFeedbackEnabled = false; - private $specificFeedbackEnabled = false; - - private $hintProvidingEnabled = false; - private $bestSolutionEnabled = false; public function __construct($contextRefId) @@ -68,7 +63,6 @@ private function initSettingsWithTestObject(): void $this->setGenericFeedbackEnabled($testOBJ->getGenericAnswerFeedback()); $this->setSpecificFeedbackEnabled($testOBJ->getSpecificAnswerFeedback()); - $this->setHintProvidingEnabled($testOBJ->isOfferingQuestionHintsEnabled()); $this->setBestSolutionEnabled($testOBJ->getInstantFeedbackSolution()); $this->setReachedPointsEnabled($testOBJ->getAnswerFeedbackPoints()); } @@ -79,7 +73,6 @@ private function initSettingsFromPostParameters(): void $this->setGenericFeedbackEnabled(true); $this->setSpecificFeedbackEnabled(true); - $this->setHintProvidingEnabled(true); $this->setBestSolutionEnabled(true); $this->setReachedPointsEnabled(true); } @@ -130,16 +123,6 @@ public function isSpecificFeedbackEnabled(): bool return $this->specificFeedbackEnabled; } - public function setHintProvidingEnabled(bool $hintProvidingEnabled): void - { - $this->hintProvidingEnabled = $hintProvidingEnabled; - } - - public function isHintProvidingEnabled(): bool - { - return $this->hintProvidingEnabled; - } - public function setBestSolutionEnabled($bestSolutionEnabled): void { $this->bestSolutionEnabled = $bestSolutionEnabled; @@ -166,9 +149,4 @@ public function isInstantFeedbackNavigationRequired(): bool return false; } - - public function isHintProvidingNavigationRequired(): bool - { - return $this->isHintProvidingEnabled(); - } } diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionRelatedNavigationBarGUI.php b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionRelatedNavigationBarGUI.php index 693e46398be7..1d95c3a1f3c4 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionRelatedNavigationBarGUI.php +++ b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionRelatedNavigationBarGUI.php @@ -33,77 +33,15 @@ class ilAssQuestionRelatedNavigationBarGUI * @var ilLanguage */ protected $lng; - protected $instantResponseCmd; - protected $instantResponseEnabled; - protected $hintProvidingEnabled; - - protected $hintRequestsPossible; - - protected $hintRequestsExist; - - protected $hintRequestCmd; - - protected $hintListCmd; - public function __construct(ilCtrl $ctrl, ilLanguage $lng) { $this->ctrl = $ctrl; $this->lng = $lng; } - public function getHintListCmd() - { - return $this->hintListCmd; - } - - public function setHintListCmd($hintListCmd): void - { - $this->hintListCmd = $hintListCmd; - } - - public function getHintRequestCmd() - { - return $this->hintRequestCmd; - } - - public function setHintRequestCmd($hintRequestCmd): void - { - $this->hintRequestCmd = $hintRequestCmd; - } - - public function setHintRequestsExist($hintRequestsExist): void - { - $this->hintRequestsExist = $hintRequestsExist; - } - - public function doesHintRequestsExist() - { - return $this->hintRequestsExist; - } - - public function setHintRequestsPossible($hintRequestsPossible): void - { - $this->hintRequestsPossible = $hintRequestsPossible; - } - - public function areHintRequestsPossible() - { - return $this->hintRequestsPossible; - } - - public function setHintProvidingEnabled($hintProvidingEnabled): void - { - $this->hintProvidingEnabled = $hintProvidingEnabled; - } - - public function isHintProvidingEnabled() - { - return $this->hintProvidingEnabled; - } - public function setInstantResponseEnabled($instantFeedbackEnabled): void { $this->instantResponseEnabled = $instantFeedbackEnabled; @@ -139,32 +77,6 @@ public function getHTML(): string $parseQuestionRelatedNavigation = true; } - if ($this->isHintProvidingEnabled()) { - if ($this->areHintRequestsPossible()) { - if ($this->doesHintRequestsExist()) { - $buttonText = $this->lng->txt("button_request_next_question_hint"); - } else { - $buttonText = $this->lng->txt("button_request_question_hint"); - } - - $navTpl->setCurrentBlock("button_request_next_question_hint"); - $navTpl->setVariable("CMD_REQUEST_NEXT_QUESTION_HINT", $this->getHintRequestCmd()); - $navTpl->setVariable("TEXT_REQUEST_NEXT_QUESTION_HINT", $buttonText); - $navTpl->parseCurrentBlock(); - - $parseQuestionRelatedNavigation = true; - } - - if ($this->doesHintRequestsExist()) { - $navTpl->setCurrentBlock("button_show_requested_question_hints"); - $navTpl->setVariable("CMD_SHOW_REQUESTED_QUESTION_HINTS", $this->getHintListCmd()); - $navTpl->setVariable("TEXT_SHOW_REQUESTED_QUESTION_HINTS", $this->lng->txt("show_requested_question_hints")); - $navTpl->parseCurrentBlock(); - - $parseQuestionRelatedNavigation = true; - } - } - if ($parseQuestionRelatedNavigation) { $navTpl->setCurrentBlock("question_related_navigation"); $navTpl->parseCurrentBlock(); diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionSkillAssignment.php b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionSkillAssignment.php index e5946f6be87f..08484e1e7e2b 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionSkillAssignment.php +++ b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionSkillAssignment.php @@ -34,16 +34,16 @@ class ilAssQuestionSkillAssignment public const EVAL_MODE_BY_QUESTION_SOLUTION = 'solution'; private ilDBInterface $db; - private int $parentObjId; - private int $questionId; - private int $skillBaseId; - private int $skillTrefId; - private int $skillPoints; - private string $skillTitle = ''; - private string $skillPath = ''; - private string $evalMode; - - private ilAssQuestionSolutionComparisonExpressionList $solutionComparisonExpressionList; + private int $parent_obj_id; + private int $question_id; + private int $skill_base_id; + private int $skill_tref_id; + private int $skill_points; + private string $skill_title = ''; + private string $skill_path = ''; + private string $eval_mode; + + private ilAssQuestionSolutionComparisonExpressionList $solution_comparison_expression_list; private SkillTreeService $skill_tree_service; public function __construct(ilDBInterface $db) @@ -52,7 +52,7 @@ public function __construct(ilDBInterface $db) $this->db = $db; - $this->solutionComparisonExpressionList = new ilAssQuestionSolutionComparisonExpressionList($this->db); + $this->solution_comparison_expression_list = new ilAssQuestionSolutionComparisonExpressionList($this->db); $this->skill_tree_service = $DIC->skills()->tree(); } @@ -70,7 +70,7 @@ public function loadFromDb(): void $res = $this->db->queryF( $query, ['integer', 'integer', 'integer', 'integer'], - [$this->parentObjId, $this->questionId, $this->skillBaseId, $this->skillTrefId] + [$this->parent_obj_id, $this->question_id, $this->skill_base_id, $this->skill_tref_id] ); $row = $this->db->fetchAssoc($res); @@ -88,7 +88,7 @@ public function loadFromDb(): void public function loadComparisonExpressions(): void { $this->initSolutionComparisonExpressionList(); - $this->solutionComparisonExpressionList->load(); + $this->solution_comparison_expression_list->load(); } public function saveToDb(): void @@ -126,7 +126,7 @@ public function saveToDb(): void public function saveComparisonExpressions(): void { $this->initSolutionComparisonExpressionList(); - $this->solutionComparisonExpressionList->save(); + $this->solution_comparison_expression_list->save(); } public function deleteFromDb(): void @@ -151,7 +151,7 @@ public function deleteFromDb(): void public function deleteComparisonExpressions(): void { $this->initSolutionComparisonExpressionList(); - $this->solutionComparisonExpressionList->delete(); + $this->solution_comparison_expression_list->delete(); } public function dbRecordExists(): bool @@ -199,52 +199,52 @@ public function isSkillUsed(): bool public function setSkillPoints(int $skillPoints): void { - $this->skillPoints = $skillPoints; + $this->skill_points = $skillPoints; } public function getSkillPoints(): int { - return $this->skillPoints; + return $this->skill_points; } public function setQuestionId(int $questionId): void { - $this->questionId = $questionId; + $this->question_id = $questionId; } public function getQuestionId(): int { - return $this->questionId; + return $this->question_id; } public function setSkillBaseId(int $skillBaseId): void { - $this->skillBaseId = $skillBaseId; + $this->skill_base_id = $skillBaseId; } public function getSkillBaseId(): int { - return $this->skillBaseId; + return $this->skill_base_id; } public function setSkillTrefId(int $skillTrefId): void { - $this->skillTrefId = $skillTrefId; + $this->skill_tref_id = $skillTrefId; } public function getSkillTrefId(): int { - return $this->skillTrefId; + return $this->skill_tref_id; } public function setParentObjId(int $parentObjId): void { - $this->parentObjId = $parentObjId; + $this->parent_obj_id = $parentObjId; } public function getParentObjId(): int { - return $this->parentObjId; + return $this->parent_obj_id; } public function loadAdditionalSkillData(): void @@ -272,7 +272,7 @@ public function loadAdditionalSkillData(): void $root_node = reset($path); array_unshift( $nodes, - $this->skill_tree_service->getObjSkillTreeById($root_node['skl_tree_id'])->getTitle() + $this->skill_tree_service->getObjSkillTreeById($root_node['skl_tree_id'])->getTitleForHTMLOutput() ); $this->setSkillPath(implode(' > ', $nodes)); @@ -280,63 +280,63 @@ public function loadAdditionalSkillData(): void public function setSkillTitle($skillTitle): void { - $this->skillTitle = $skillTitle; + $this->skill_title = $skillTitle; } public function getSkillTitle(): ?string { - return $this->skillTitle; + return $this->skill_title; } public function setSkillPath($skillPath): void { - $this->skillPath = $skillPath; + $this->skill_path = $skillPath; } public function getSkillPath(): ?string { - return $this->skillPath; + return $this->skill_path; } public function getEvalMode(): string { - return $this->evalMode; + return $this->eval_mode; } public function setEvalMode(string $evalMode): void { - $this->evalMode = $evalMode; + $this->eval_mode = $evalMode; } public function hasEvalModeBySolution(): bool { - return $this->evalMode === self::EVAL_MODE_BY_QUESTION_SOLUTION; + return $this->eval_mode === self::EVAL_MODE_BY_QUESTION_SOLUTION; } public function initSolutionComparisonExpressionList(): void { - $this->solutionComparisonExpressionList->setQuestionId($this->getQuestionId()); - $this->solutionComparisonExpressionList->setSkillBaseId($this->getSkillBaseId()); - $this->solutionComparisonExpressionList->setSkillTrefId($this->getSkillTrefId()); + $this->solution_comparison_expression_list->setQuestionId($this->getQuestionId()); + $this->solution_comparison_expression_list->setSkillBaseId($this->getSkillBaseId()); + $this->solution_comparison_expression_list->setSkillTrefId($this->getSkillTrefId()); } public function getSolutionComparisonExpressionList(): ilAssQuestionSolutionComparisonExpressionList { - return $this->solutionComparisonExpressionList; + return $this->solution_comparison_expression_list; } public function getMaxSkillPoints(): int { if ($this->hasEvalModeBySolution()) { - $maxPoints = 0; + $max_points = 0; - foreach ($this->solutionComparisonExpressionList->get() as $expression) { - if ($expression->getPoints() > $maxPoints) { - $maxPoints = $expression->getPoints(); + foreach ($this->solution_comparison_expression_list->get() as $expression) { + if ($expression->getPoints() > $max_points) { + $max_points = $expression->getPoints(); } } - return $maxPoints; + return $max_points; } return $this->getSkillPoints(); diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionSkillAssignmentsGUI.php b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionSkillAssignmentsGUI.php index 284f5d211bc8..78ced47cf573 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionSkillAssignmentsGUI.php +++ b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionSkillAssignmentsGUI.php @@ -456,12 +456,6 @@ private function buildSkillQuestionAssignmentPropertiesForm( $form->setQuestion($question); $form->setAssignment($assignment); $form->setManipulationEnabled($this->isAssignmentEditingEnabled()); - - $form->setManipulationEnabled( - $this->isAssignmentEditingEnabled() && - $question->getOriginalId() === null // Manipulation is not permitted if question comes from a pool - ); - $form->build(); return $form; @@ -493,7 +487,7 @@ private function isSyncOriginalPossibleAndAllowed($questionId): bool $parentObjId = assQuestion::lookupParentObjId($questionData['original_id']); - if (!$this->doesObjectTypeMatch($parentObjId)) { + if ($parentObjId === null || !$this->doesObjectTypeMatch($parentObjId)) { return false; } @@ -542,7 +536,6 @@ private function buildTableGUI(): ilAssQuestionSkillAssignmentsTableGUI { $table = new ilAssQuestionSkillAssignmentsTableGUI($this, self::CMD_SHOW_SKILL_QUEST_ASSIGNS, $this->ctrl, $this->lng); $table->setManipulationsEnabled($this->isAssignmentEditingEnabled()); - $table->setManipulationAllowedList($this->buildManipulationAllowedList()); $table->init(); return $table; @@ -556,19 +549,6 @@ private function buildSkillQuestionAssignmentList(): ilAssQuestionSkillAssignmen return $assignmentList; } - /** - * Questions from a question pool may not be edited (JF 3 MAR 2024) and are filtered out here. - * - * @return array - */ - private function buildManipulationAllowedList(): array - { - return array_map( - static fn(array $question_data) => $question_data['original_id'] === null, - $this->question_list->getQuestionDataArray() - ); - } - /** * @return ilSkillSelectorGUI */ @@ -620,7 +600,7 @@ private function buildQuestionPage(assQuestionGUI $question_gui) $pageGUI->setOutputMode("presentation"); $pageGUI->setRenderPageContainer(true); - $pageGUI->setPresentationTitle($question_gui->getObject()->getTitle()); + $pageGUI->setPresentationTitle($question_gui->getObject()->getTitleForHTMLOutput()); $question = $question_gui->getObject(); $question->setShuffle(false); // dirty, but works ^^ diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionUserSolutionAdopter.php b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionUserSolutionAdopter.php index a6ad66b21588..274caba7b1e1 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionUserSolutionAdopter.php +++ b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionUserSolutionAdopter.php @@ -175,7 +175,7 @@ protected function adoptSourceResult(int $question_id, int $source_pass): void $this->db->execute($this->getPreparedInsertResultRecordStatement(), [ $result_id, $this->getActiveId(), $question_id, $this->getTargetPass(), time(), - $row['points'], $row['manual'], $row['hint_count'], $row['hint_points'], $row['answered'] + $row['points'], $row['manual'], $row['answered'] ]); } @@ -244,7 +244,7 @@ protected function getPreparedSelectResultRecordStatement(): ilDBStatement { if (self::$prepared_select_result_record_statement === null) { $query = " - SELECT points, manual, hint_count, hint_points, answered FROM tst_test_result + SELECT points, manual, answered FROM tst_test_result WHERE active_fi = ? AND question_fi = ? AND pass = ? "; @@ -263,15 +263,15 @@ protected function getPreparedInsertResultRecordStatement(): ilDBStatement $query = " INSERT INTO tst_test_result ( test_result_id, active_fi, question_fi, pass, tstamp, - points, manual, hint_count, hint_points, answered + points, manual, answered ) VALUES ( - ?, ?, ?, ?, ?, ?, ?, ?, ?, ? + ?, ?, ?, ?, ?, ?, ?, ? ) "; self::$prepared_insert_result_record_statement = $this->db->prepareManip( $query, - ['integer', 'integer', 'integer', 'integer', 'integer', 'integer', 'integer', 'integer', 'integer', 'integer'] + ['integer', 'integer', 'integer', 'integer', 'integer', 'integer', 'integer', 'integer'] ); } diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilErrorTextWizardInputGUI.php b/components/ILIAS/TestQuestionPool/classes/class.ilErrorTextWizardInputGUI.php index 9fbc0b7d4ec2..4966d56dd1e3 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.ilErrorTextWizardInputGUI.php +++ b/components/ILIAS/TestQuestionPool/classes/class.ilErrorTextWizardInputGUI.php @@ -226,7 +226,7 @@ public function checkInput(): bool return false; } foreach ($points as $point) { - if ($point < 0) { + if ($point <= 0) { $this->setAlert($this->lng->txt('positive_numbers_required')); return false; } diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilObjQuestionPool.php b/components/ILIAS/TestQuestionPool/classes/class.ilObjQuestionPool.php index ee3b55058cc0..dfef7eeb1a78 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.ilObjQuestionPool.php +++ b/components/ILIAS/TestQuestionPool/classes/class.ilObjQuestionPool.php @@ -458,10 +458,17 @@ public function exportXMLMediaObjects(&$a_xml_writer, $a_inst, $a_target_dir, &$ { foreach ($this->mob_ids as $mob_id) { $expLog->write(date('[y-m-d H:i:s] ') . 'Media Object ' . $mob_id); - if (ilObjMediaObject::_exists($mob_id)) { - $media_obj = new ilObjMediaObject($mob_id); - $media_obj->exportXML($a_xml_writer, $a_inst); - $media_obj->exportFiles($a_target_dir); + if (ilObjMediaObject::_exists((int) $mob_id)) { + $target_dir = $a_target_dir . DIRECTORY_SEPARATOR . 'objects' + . DIRECTORY_SEPARATOR . 'il_' . IL_INST_ID . '_mob_' . $mob_id; + ilFileUtils::createDirectory($target_dir); + $media_obj = new ilObjMediaObject((int) $mob_id); + $media_obj->exportXML($a_xml_writer, (int) $a_inst); + foreach ($media_obj->getMediaItems() as $item) { + $stream = $item->getLocationStream(); + file_put_contents($target_dir . DIRECTORY_SEPARATOR . $item->getLocation(), $stream); + $stream->close(); + } unset($media_obj); } } @@ -477,7 +484,7 @@ public function exportFileItems($target_dir, &$expLog): void $expLog->write(date("[y-m-d H:i:s] ") . "File Item " . $file_id); $file_dir = $target_dir . '/objects/il_' . IL_INST_ID . '_file_' . $file_id; ilFileUtils::makeDir($file_dir); - $file_obj = new ilObjFile($file_id, false); + $file_obj = new ilObjFile((int) $file_id, false); $source_file = $file_obj->getFile($file_obj->getVersion()); if (!is_file($source_file)) { $source_file = $file_obj->getFile(); diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilObjQuestionPoolGUI.php b/components/ILIAS/TestQuestionPool/classes/class.ilObjQuestionPoolGUI.php index 48140ed2daf4..a7cf3222f1fa 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.ilObjQuestionPoolGUI.php +++ b/components/ILIAS/TestQuestionPool/classes/class.ilObjQuestionPoolGUI.php @@ -20,7 +20,7 @@ use ILIAS\TestQuestionPool\QuestionPoolDIC; use ILIAS\TestQuestionPool\RequestDataCollector; -use ILIAS\TestQuestionPool\Presentation\QuestionTable; +use ILIAS\TestQuestionPool\Questions\Presentation\QuestionTable; use ILIAS\TestQuestionPool\Questions\GeneralQuestionPropertiesRepository; use ILIAS\Test\Settings\GlobalSettings\GlobalTestSettings; use ILIAS\Taxonomy\Service; @@ -53,7 +53,7 @@ * @ilCtrl_Calls ilObjQuestionPoolGUI: assNumericGUI, assTextSubsetGUI, assSingleChoiceGUI, ilPropertyFormGUI * @ilCtrl_Calls ilObjQuestionPoolGUI: assTextQuestionGUI, ilObjectMetaDataGUI, ilPermissionGUI, ilObjectCopyGUI * @ilCtrl_Calls ilObjQuestionPoolGUI: ilExportGUI, ilInfoScreenGUI, ilTaxonomySettingsGUI, ilCommonActionDispatcherGUI - * @ilCtrl_Calls ilObjQuestionPoolGUI: ilAssQuestionHintsGUI, ilAssQuestionFeedbackEditingGUI, ilLocalUnitConfigurationGUI + * @ilCtrl_Calls ilObjQuestionPoolGUI: ilAssQuestionFeedbackEditingGUI, ilLocalUnitConfigurationGUI * @ilCtrl_Calls ilObjQuestionPoolGUI: ilObjQuestionPoolSettingsGeneralGUI, assFormulaQuestionGUI * @ilCtrl_Calls ilObjQuestionPoolGUI: ilAssQuestionPreviewGUI * @ilCtrl_Calls ilObjQuestionPoolGUI: assKprimChoiceGUI, assLongMenuGUI @@ -70,7 +70,6 @@ class ilObjQuestionPoolGUI extends ilObjectGUI implements ilCtrlBaseClassInterfa public const SUPPORTED_IMPORT_MIME_TYPES = [MimeType::APPLICATION__ZIP, MimeType::TEXT__XML]; public const DEFAULT_CMD = 'questions'; - private HTTPServices $http; protected Service $taxonomy; protected ilDBInterface $db; protected ilComponentLogger $log; @@ -106,7 +105,6 @@ public function __construct() $this->navigation_history = $DIC['ilNavigationHistory']; $this->ui_service = $DIC->uiService(); $this->taxonomy = $DIC->taxonomy(); - $this->http = $DIC->http(); $this->archives = $DIC->archives(); $this->content_style = $DIC->contentStyle(); @@ -200,7 +198,6 @@ public function executeCommand(): void } $this->ctrl->saveParameterByClass(ilAssQuestionPreviewGUI::class, 'q_id'); - $this->ctrl->saveParameterByClass(ilAssQuestionHintRequestGUI::class, 'q_id'); $this->ctrl->saveParameter($this, 'q_id'); $gui = new ilAssQuestionPreviewGUI( $this->ctrl, @@ -240,7 +237,6 @@ public function executeCommand(): void ); $gui->initPreviewSettings($this->object->getRefId()); $gui->initPreviewSession($this->user->getId(), $this->fetchAuthoringQuestionIdParamater()); - $gui->initHintTracking(); $this->ctrl->clearParameterByClass(self::class, 'q_id'); $this->tabs_gui->setBackTarget( $this->lng->txt('backtocallingpool'), @@ -302,8 +298,8 @@ public function executeCommand(): void $page_gui->setQuestionHTML([$question_gui->getObject()->getId() => $question_gui->getPreview(true)]); $page_gui->setTemplateTargetVar('ADM_CONTENT'); $page_gui->setOutputMode('edit'); - $page_gui->setHeader($question->getTitle()); - $page_gui->setPresentationTitle($question->getTitle()); + $page_gui->setHeader($question->getTitleForHTMLOutput()); + $page_gui->setPresentationTitle($question->getTitleForHTMLOutput()); $ret = $this->ctrl->forwardCommand($page_gui); if ($ret != '') { $this->tpl->setContent($ret); @@ -330,45 +326,6 @@ public function executeCommand(): void $this->infoScreenForward(); break; - case 'ilassquestionhintsgui': - if (!$this->access->checkAccess('write', '', $this->object->getRefId())) { - $this->redirectAfterMissingWrite(); - } - - $this->ctrl->setReturn($this, self::DEFAULT_CMD); - $question_gui = assQuestionGUI::_getQuestionGUI( - $q_type, - $this->fetchAuthoringQuestionIdParamater() - ); - $question = $question_gui->getObject(); - $question->setObjId($this->object->getId()); - $question_gui->setObject($question); - $question_gui->setQuestionTabs(); - - if ($this->questionrepository->isInActiveTest($question_gui->getObject()->getObjId())) { - $this->tpl->setOnScreenMessage( - 'failure', - $this->lng->txt('question_is_part_of_running_test'), - true - ); - $this->ctrl->redirectByClass('ilAssQuestionPreviewGUI', ilAssQuestionPreviewGUI::CMD_SHOW); - } - - $this->help->setScreenIdComponent('qpl'); - - if ($this->object->getType() == 'qpl' && $write_access) { - $question_gui->addHeaderAction(); - } - $gui = new ilAssQuestionHintsGUI($question_gui); - - $gui->setEditingEnabled( - $this->access->checkAccess('write', '', $this->object->getRefId()) - ); - - $this->ctrl->forwardCommand($gui); - - break; - case 'illocalunitconfigurationgui': if (!$this->access->checkAccess('write', '', $this->object->getRefId())) { $this->error->raiseError($this->lng->txt('permission_denied'), $this->error->WARNING); @@ -544,7 +501,6 @@ public function executeCommand(): void $this->ctrl->setParameterByClass("ilAssQuestionPageGUI", "q_id", current($ids)); $this->ctrl->setParameterByClass("ilAssQuestionPreviewGUI", "q_id", current($ids)); $this->ctrl->setParameterByClass('ilAssQuestionFeedbackEditingGUI', 'q_id', current($ids)); - $this->ctrl->setParameterByClass('ilAssQuestionHintsGUI', 'q_id', current($ids)); $this->ctrl->setParameterByClass($class, "q_id", current($ids)); switch ($action) { @@ -568,10 +524,6 @@ public function executeCommand(): void $url = $this->ctrl->getLinkTargetByClass('ilAssQuestionFeedbackEditingGUI', ilAssQuestionFeedbackEditingGUI::CMD_SHOW); $this->ctrl->redirectToURL($url); break; - case 'hints': - $url = $this->ctrl->getLinkTargetByClass('ilAssQuestionHintsGUI', ilAssQuestionHintsGUI::CMD_SHOW_LIST); - $this->ctrl->redirectToURL($url); - break; case 'move': $this->moveQuestions($ids); $this->ctrl->redirect($this, self::DEFAULT_CMD); @@ -665,13 +617,12 @@ public function executeCommand(): void $this->help->setScreenIdComponent('qpl'); - $question_gui->setQuestionTabs(); - if ($qid === 0 && $question_gui->cmdNeedsExistingQuestion($cmd)) { $question_gui->getObject()->createNewQuestion(); - $question_gui->setQuestionTabs(); } + $question_gui->setQuestionTabs(); + if (!in_array($cmd, ['save', 'saveReturn'])) { $question_gui->$cmd(); return; @@ -752,6 +703,14 @@ public function download_paragraphObject(): void public function importVerifiedFileObject(): void { + if ($this->creation_mode + && !$this->checkPermissionBool('create', '', $this->request_data_collector->string('new_type')) + || !$this->creation_mode + && !$this->checkPermissionBool('read', '', $this->object->getType())) { + $this->redirectAfterMissingWrite(); + return; + } + $file_to_import = ilSession::get('path_to_import_file'); list($subdir, $importdir, $xmlfile, $qtifile) = $this->buildImportDirectoriesFromImportFile($file_to_import); @@ -1433,7 +1392,7 @@ public function addLocatorItems(): void $question = $question_gui->getObject(); $question->setObjId($this->object->getId()); $question_gui->setObject($question); - $title = $question_gui->getObject()->getTitle(); + $title = $question_gui->getObject()->getTitleForHTMLOutput(); if (!$title) { $title = $this->lng->txt('new') . ': ' . $this->questionrepository->getForQuestionId( $question_gui->getObject()->getId() @@ -1463,7 +1422,7 @@ public function setTitleAndDescription(): void $question = $question_gui->getObject(); $question->setObjId($this->object->getId()); $question_gui->setObject($question); - $title = $this->object->getTitle() . ': ' . $question_gui->getObject()->getTitle(); + $title = $this->object->getTitle() . ': ' . $question_gui->getObject()->getTitleForHTMLOutput(); if (!$title) { $title = $this->lng->txt('new') . ': ' . $this->questionrepository->getForQuestionId( $question_gui->getObject()->getId() @@ -1746,6 +1705,7 @@ public function infoScreenForward(): void public static function _goto($a_target): void { + /** @var ILIAS\DI\Container $DIC */ global $DIC; $main_tpl = $DIC->ui()->mainTemplate(); $ilAccess = $DIC['ilAccess']; @@ -1753,14 +1713,16 @@ public static function _goto($a_target): void $lng = $DIC['lng']; $ctrl = $DIC['ilCtrl']; - if ($ilAccess->checkAccess('write', '', (int) $a_target) - || $ilAccess->checkAccess('read', '', (int) $a_target) + $target_ref_id = (int) $a_target; + + if ($ilAccess->checkAccess('write', '', $target_ref_id) + || $ilAccess->checkAccess('read', '', $target_ref_id) ) { $ctrl->setParameterByClass(ilObjQuestionPoolGUI::class, 'ref_id', $a_target); $ctrl->redirectByClass([ilRepositoryGUI::class, ilObjQuestionPoolGUI::class], self::DEFAULT_CMD); return; } - if ($ilAccess->checkAccess('visible', '', $a_target)) { + if ($ilAccess->checkAccess('visible', '', $target_ref_id)) { $DIC->ctrl()->setParameterByClass(ilInfoScreenGUI::class, 'ref_id', $a_target); $DIC->ctrl()->redirectByClass( [ @@ -1775,7 +1737,7 @@ public static function _goto($a_target): void 'info', sprintf( $lng->txt('msg_no_perm_read_item'), - ilObject::_lookupTitle(ilObject::_lookupObjId($a_target)) + ilObject::_lookupTitle(ilObject::_lookupObjId($target_ref_id)) ), true ); diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilObjQuestionPoolSettingsGeneralGUI.php b/components/ILIAS/TestQuestionPool/classes/class.ilObjQuestionPoolSettingsGeneralGUI.php index 7480e16bcf53..4fd50c2ced52 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.ilObjQuestionPoolSettingsGeneralGUI.php +++ b/components/ILIAS/TestQuestionPool/classes/class.ilObjQuestionPoolSettingsGeneralGUI.php @@ -24,6 +24,7 @@ use ILIAS\UI\Renderer as UIRenderer; use Psr\Http\Message\ServerRequestInterface as HttpRequest; use ILIAS\UI\Component\MessageBox\MessageBox; +use ILIAS\ILIASObject\Properties\CoreProperties\TitleAndDescription; /** * GUI class that manages the editing of general test question pool settings/properties @@ -120,7 +121,7 @@ private function saveFormCmd(): void private function performSaveForm($data): void { $title_and_description = $data['general_settings']['title_and_description'] ?? null; - if ($title_and_description instanceof ilObjectPropertyTitleAndDescription) { + if ($title_and_description instanceof TitleAndDescription) { $this->poolOBJ->getObjectProperties()->storePropertyTitleAndDescription( $title_and_description ); diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilObjQuestionPoolXMLParser.php b/components/ILIAS/TestQuestionPool/classes/class.ilObjQuestionPoolXMLParser.php index ec222ddaf06e..ee4ab378571e 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.ilObjQuestionPoolXMLParser.php +++ b/components/ILIAS/TestQuestionPool/classes/class.ilObjQuestionPoolXMLParser.php @@ -51,9 +51,8 @@ public function __construct(ilObjQuestionPool $poolOBJ, ?string $xmlFile) public function setHandlers($a_xml_parser): void { - xml_set_object($a_xml_parser, $this); - xml_set_element_handler($a_xml_parser, 'handlerBeginTag', 'handlerEndTag'); - xml_set_character_data_handler($a_xml_parser, 'handlerCharacterData'); + xml_set_element_handler($a_xml_parser, $this->handlerBeginTag(...), $this->handlerEndTag(...)); + xml_set_character_data_handler($a_xml_parser, $this->handlerCharacterData(...)); } public function handlerBeginTag($xmlParser, $tagName, $tagAttributes): void diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilQuestionPageParser.php b/components/ILIAS/TestQuestionPool/classes/class.ilQuestionPageParser.php index 0138674770bf..f8e332b7d9bb 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.ilQuestionPageParser.php +++ b/components/ILIAS/TestQuestionPool/classes/class.ilQuestionPageParser.php @@ -24,7 +24,7 @@ * @author Alexander Killing * @deprecated use COPage dependency to export/import COPages instead */ -class ilQuestionPageParser extends ilMDSaxParser +class ilQuestionPageParser extends ilSaxParser { /** * @var false @@ -34,8 +34,6 @@ class ilQuestionPageParser extends ilMDSaxParser * @var false */ protected bool $in_glossary_definition = false; - protected array $media_meta_cache = []; - protected bool $media_meta_start = false; protected array $pg_mapping = []; protected array $mobs_with_int_links = []; protected bool $inside_code = false; @@ -54,7 +52,6 @@ class ilQuestionPageParser extends ilMDSaxParser public array $st_into_tree = []; public array $container = []; public bool $in_page_object = false; // are we currently within a PageObject? true/false - public bool $in_meta_data = false; // are we currently within MetaData? true/false public bool $in_media_object = false; public bool $in_file_item = false; public bool $in_glossary = false; @@ -69,8 +66,6 @@ class ilQuestionPageParser extends ilMDSaxParser public ?ilMapArea $map_area = null; // current map area public array $link_targets = []; // stores all objects by import id public array $qst_mapping = []; - public bool $metadata_parsing_disabled = false; - public bool $in_meta_meta_data = false; protected array $glossary_term_map = []; protected ilLogger $log; protected ?ilGlossaryTerm $glossary_term = null; @@ -107,7 +102,6 @@ public function __construct( $this->inside_code = false; $this->qst_mapping = []; $this->content_type = $this->content_object->getType(); - $this->metadata_parsing_disabled = false; if ($this->content_type !== 'tst' && $this->content_type !== 'qpl') { $this->lm_tree = new ilLMTree($this->content_object->getId()); @@ -119,9 +113,8 @@ public function __construct( */ public function setHandlers($xml_parser): void { - xml_set_object($xml_parser, $this); - xml_set_element_handler($xml_parser, 'handlerBeginTag', 'handlerEndTag'); - xml_set_character_data_handler($xml_parser, 'handlerCharacterData'); + xml_set_element_handler($xml_parser, $this->handlerBeginTag(...), $this->handlerEndTag(...)); + xml_set_character_data_handler($xml_parser, $this->handlerCharacterData(...)); } public function setImportMapping(?ilImportMapping $mapping = null): void @@ -263,36 +256,10 @@ public function processPagesToParse(): void } } - - /** - * copy multimedia object files from import zip file to mob directory - */ - public function copyMobFiles(): void - { - foreach ($this->mob_mapping as $origin_id => $mob_id) { - if (empty($origin_id)) { - continue; - } - - $obj_dir = $origin_id; - $source_dir = $this->importdir . DIRECTORY_SEPARATOR . 'objects' . DIRECTORY_SEPARATOR . $obj_dir; - $target_dir = ilFileUtils::getWebspaceDir() . DIRECTORY_SEPARATOR . 'mobs/mm_' . $mob_id; - - if (is_dir($source_dir)) { - ilFileUtils::makeDir($target_dir); - - if (is_dir($target_dir)) { - ilLoggerFactory::getLogger('mob')->debug('s:-$source_dir-,t:-$target_dir-'); - ilFileUtils::rCopy(realpath($source_dir), realpath($target_dir)); - } - } - } - } - /** * copy files of file items */ - public function copyFileItems(): void + private function copyFileItems(): void { foreach ($this->file_item_mapping as $origin_id => $file_id) { if (empty($origin_id)) { @@ -423,14 +390,14 @@ public function handlerBeginTag($a_xml_parser, string $a_name, array $a_attribs) if ($a_name == 'MediaObject') { $this->in_media_object = true; } - $this->media_meta_start = true; - $this->media_meta_cache = []; $this->media_object = new ilObjMediaObject(); + $this->media_object->create(false, false); break; case 'MediaAlias': $this->media_object->setAlias(true); $this->media_object->setImportId($a_attribs['OriginId']); + $this->mob_mapping[$this->media_object->getImportId()] = $this->media_object->getId(); if (is_object($this->page_object)) { $this->page_object->needsImportParsing(true); } @@ -635,130 +602,34 @@ public function handlerBeginTag($a_xml_parser, string $a_name, array $a_attribs) } break; - //////////////////////////////////////////////// - /// Meta Data Section - //////////////////////////////////////////////// - case 'MetaData': - $this->in_meta_data = true; - // media obejct meta data handling - // is done in the 'Identifier' begin tag processing - // the rest is done here - if (!$this->in_media_object) { - if ($this->content_type != 'tst' && $this->content_type != 'qpl') { - // type pg/st - if ($this->current_object->getType() == 'st' - || $this->current_object->getType() == 'pg') { - // late creation of page object - if ($this->current_object->getType() == 'pg') { - $this->lm_page_object->create(true); - } - $this->md = new ilMD( - $this->content_object->getId(), - $this->current_object->getId(), - $this->current_object->getType() - ); - } - // type term - elseif ($this->current_object->getType() == 'term') { - $this->md = new ilMD( - $this->glossary_object->getId(), - $this->current_object->getId(), - $this->current_object->getType() - ); - } - // type lm, dbk, glo - else { - if ($this->processMeta()) { - $this->md = new ilMD( - $this->current_object->getId(), - 0, - $this->current_object->getType() - ); - } - } - } else { - // type qpl or tst - $this->md = new ilMD( - $this->content_object->getId(), - 0, - $this->current_object->getType() - ); - if ($this->md->getGeneral() != false) { - $this->metadata_parsing_disabled = true; - $this->enableMDParsing(false); - } - } - } - break; - // Identifier case 'Identifier': - // begin-patch optes_lok_export - if ($this->in_meta_data && $this->current_object instanceof ilStructureObject) { - if ($this->mapping instanceof ilImportMapping) { - $import_id_parsed = ilUtil::parseImportId($a_attribs['Entry']); - if ($import_id_parsed['type'] == 'st') { - $this->mapping->addMapping( - 'components/ILIAS/LearningModule', - 'lm_tree', - $import_id_parsed['id'], - $this->current_object->getId() - ); - } + if ($this->in_file_item) { + if (!isset($this->file_item_mapping[$a_attribs['Entry']]) + || $this->file_item_mapping[$a_attribs['Entry']] === '') { + $this->file_item->create(); + $this->file_item->setImportId($a_attribs['Entry']); + $this->file_item_mapping[$a_attribs['Entry']] = $this->file_item->getId(); } } - // end-patch optes_lok_export - - // please note: Meta-Metadata and MetaData are different tags! - if (!$this->in_meta_meta_data) { - if ($this->in_meta_data && !$this->in_glossary_definition) { - if (!$this->in_media_object) { - $this->current_object->setImportId($a_attribs['Entry']); - } - // #40680 add a link target only if it is an internal ILIAS link - // Export from IMS UCAN sets something like 'IMSm-i1e79762' - $parsed = $this->parseLinkTarget($a_attribs["Entry"]); - if (isset($parsed)) { - $this->link_targets[$a_attribs['Entry']] = $a_attribs['Entry']; - } - } - if ($this->in_file_item) { - if (!isset($this->file_item_mapping[$a_attribs['Entry']]) - || $this->file_item_mapping[$a_attribs['Entry']] === '') { - $this->file_item->create(); - $this->file_item->setImportId($a_attribs['Entry']); - $this->file_item_mapping[$a_attribs['Entry']] = $this->file_item->getId(); - } - } - if ($this->in_meta_data && $this->in_media_object) { - $mob_id = $this->mob_mapping[$a_attribs['Entry']]; - - // within learning module import, usually a media object - // has already been created with a media alias tag - if ($mob_id > 0) { - $this->media_object = new ilObjMediaObject($mob_id); - } else { // in glossaries the media objects precede the definitions - // so we don't have an object already - $this->media_object = new ilObjMediaObject(); - $this->media_object->create(true, false); - $this->mob_mapping[$a_attribs['Entry']] - = $this->media_object->getId(); - } - $this->media_object->setImportId($a_attribs['Entry']); - $this->md = new ilMD( - 0, - $this->media_object->getId(), - 'mob' - ); - $this->emptyMediaMetaCache($a_xml_parser); + if ($this->in_media_object) { + $mob_id = $this->mob_mapping[$a_attribs['Entry']]; + + // within learning module import, usually a media object + // has already been created with a media alias tag + if ($mob_id > 0) { + $this->media_object = new ilObjMediaObject($mob_id); + } else { // in glossaries the media objects precede the definitions + // so we don't have an object already + $this->media_object = new ilObjMediaObject(); + $this->media_object->create(false, false); + $this->mob_mapping[$a_attribs['Entry']] + = $this->media_object->getId(); } + $this->media_object->setImportId($a_attribs['Entry']); } break; - case 'Meta-Metadata': - $this->in_meta_meta_data = true; - break; - // Internal Link case 'IntLink': if (is_object($this->page_object)) { @@ -798,7 +669,7 @@ public function handlerBeginTag($a_xml_parser, string $a_name, array $a_attribs) // append content to page xml content if (($this->in_page_object || $this->in_glossary_definition) - && !$this->in_meta_data && !$this->in_media_object) { + && !$this->in_media_object) { if ($a_name == 'Definition') { $app_name = 'PageObject'; $app_attribs = []; @@ -814,32 +685,6 @@ public function handlerBeginTag($a_xml_parser, string $a_name, array $a_attribs) $this->page_object->appendXMLContent($this->buildTag('start', $app_name, $app_attribs)); } - - - // call meta data handler - if ($this->in_meta_data && $this->processMeta()) { - // cache beginning of meta data within media object tags - // (we need to know the id at the begin of meta elements within - // media objects, after the 'Identifier' tag has been processed - // we send the cached data to the meta xml handler) - if ($this->in_media_object && $this->media_meta_start) { - $this->media_meta_cache[] = - ['type' => 'handlerBeginTag', 'par1' => $a_name, 'par2' => $a_attribs]; - } else { - if ($a_name == 'Identifier') { - if (!$this->in_media_object) { - $a_attribs['Entry'] = 'il__' . $this->current_object->getType() . - '_' . $this->current_object->getId(); - } else { - $a_attribs['Entry'] = 'il__mob' . - '_' . $this->media_object->getId(); - } - $a_attribs['Catalog'] = 'ILIAS'; - } - - parent::handlerBeginTag($a_xml_parser, $a_name, $a_attribs); - } - } } public function processMeta(): bool @@ -858,22 +703,9 @@ public function processMeta(): bool public function handlerEndTag($a_xml_parser, string $a_name): void { - // call meta data handler - if ($this->in_meta_data && $this->processMeta()) { - // cache beginning of meta data within media object tags - // (we need to know the id, after that we send the cached data - // to the meta xml handler) - if ($this->in_media_object && $this->media_meta_start) { - $this->media_meta_cache[] = - ['type' => 'handlerEndTag', 'par1' => $a_name]; - } else { - parent::handlerEndTag($a_xml_parser, $a_name); - } - } - // append content to page xml content if (($this->in_page_object || $this->in_glossary_definition) - && !$this->in_meta_data && !$this->in_media_object) { + && !$this->in_media_object) { $app_name = ($a_name == 'Definition') ? 'PageObject' : $a_name; @@ -979,7 +811,7 @@ public function handlerEndTag($a_xml_parser, string $a_name): void // create media object // media items are saves for mobs outside of // pages only - $this->media_object->create(true, false); + $this->media_object->create(false, false); // collect mobs with internal links if ($this->media_object->containsIntLink()) { @@ -1017,11 +849,34 @@ public function handlerEndTag($a_xml_parser, string $a_name): void break; - case 'MediaItem': case 'MediaAliasItem': $this->in_media_item = false; $this->media_object->addMediaItem($this->media_item); break; + case 'MediaItem': + $this->in_media_item = false; + $import_dir = $this->importdir . DIRECTORY_SEPARATOR . 'objects' . DIRECTORY_SEPARATOR . $this->media_object->getImportId(); + if (!file_exists($import_dir) + || !is_dir($import_dir)) { + $this->media_object->addMediaItem($this->media_item); + break; + } + + $dir_handle = opendir($import_dir); + while (($file = readdir($dir_handle)) !== false) { + if ($file !== $this->media_item->getLocation()) { + continue; + } + + $this->media_object->addMediaItemFromLocalFile( + $this->media_item->getPurpose(), + $import_dir . DIRECTORY_SEPARATOR . $file, + $file + ); + break; + } + closedir($dir_handle); + break; case 'MapArea': $this->in_map_area = false; @@ -1035,97 +890,6 @@ public function handlerEndTag($a_xml_parser, string $a_name): void } break; - case 'MetaData': - $this->in_meta_data = false; - if (strtolower(get_class($this->current_object)) == 'illmpageobject' && !$this->in_media_object) { - // Metadaten eines PageObjects sichern in NestedSet - if (is_object($this->lm_page_object)) { - // update title/description of page object - $this->current_object->MDUpdateListener('General'); - ilLMObject::_writeImportId( - $this->current_object->getId(), - $this->current_object->getImportId() - ); - } - } elseif ((strtolower(get_class($this->current_object)) == 'ilobjquestionpool' || - strtolower(get_class($this->current_object)) == 'ilobjtest') && - !$this->in_media_object) { - // !$this->in_media_object && !$this->in_page_object) - // changed for imports of ILIAS 2 Tests where PageObjects could have - // Metadata sections (Helmut Schottmüller, 2005-12-02) - if ($this->metadata_parsing_disabled) { - $this->enableMDParsing(true); - } else { - if ($this->in_page_object && !is_null($this->page_object)) { - /* - $this->page_object->MDUpdateListener('General'); - ilLMObject::_writeImportId( - $this->page_object->getId(), - $this->page_object->getImportId() - );*/ - } else { - $this->current_object->MDUpdateListener('General'); - ilLMObject::_writeImportId( - $this->current_object->getId(), - $this->current_object->getImportId() - ); - } - } - } elseif (strtolower(get_class($this->current_object)) == 'ilstructureobject') { // save structure object at the end of its meta block - // determine parent - $cnt = count($this->structure_objects); - if ($cnt > 1) { - $parent_id = $this->structure_objects[$cnt - 2]->getId(); - } else { - $parent_id = $this->lm_tree->getRootId(); - } - - $this->st_into_tree[] = ['id' => $this->current_object->getId(), - 'parent' => $parent_id]; - - // update title/description of structure object - $this->current_object->MDUpdateListener('General'); - ilLMObject::_writeImportId( - $this->current_object->getId(), - $this->current_object->getImportId() - ); - } elseif (strtolower(get_class($this->current_object)) == 'ilobjlearningmodule' || - strtolower(get_class($this->current_object)) == 'ilobjcontentobject' || - (strtolower(get_class($this->current_object)) == 'ilobjglossary' && $this->in_glossary)) { - // todo: saving of md? getting title/descr and - // set it for current object - } elseif (strtolower(get_class($this->current_object)) == 'ilglossaryterm' && !$this->in_media_object) { - // now on top - - $this->page_object->setId($this->glossary_term->getId()); - $this->page_object->updateFromXML(); - - // todo: saving of md? getting title/descr and - // set it for current object - } - - - if (strtolower(get_class($this->current_object)) == 'ilobjlearningmodule' || - strtolower(get_class($this->current_object)) == 'ilobjglossary') { - if (strtolower(get_class($this->current_object)) == 'ilobjglossary' && - $this->content_object->getType() != 'glo') { - $this->current_object->setTitle($this->content_object->getTitle() . ' - ' . - $this->lng->txt('glossary')); - } - - $this->current_object->MDUpdateListener('General'); - } - - if ($this->in_media_object) { - $this->media_object->MDUpdateListener('General'); - } - - break; - - case 'Meta-Metadata': - $this->in_meta_meta_data = false; - break; - case 'FileItem': $this->in_file_item = false; // only update new file items @@ -1178,9 +942,6 @@ public function handlerEndTag($a_xml_parser, string $a_name): void break; case 'Title': - if ($this->in_meta_data && !$this->in_media_object) { - $this->current_object->setTitle(trim($this->chr_data)); - } if ($this->in_media_object) { $this->media_object->setTitle(trim($this->chr_data)); } @@ -1247,19 +1008,6 @@ public function handlerEndTag($a_xml_parser, string $a_name): void public function handlerCharacterData($a_xml_parser, string $a_data): void { - // call meta data handler - if ($this->in_meta_data && $this->processMeta()) { - // cache beginning of meta data within media object tags - // (we need to know the id, after that we send the cached data - // to the meta xml handler) - if ($this->in_media_object && $this->media_meta_start) { - $this->media_meta_cache[] = - ['type' => 'handlerCharacterData', 'par1' => $a_data]; - } else { - parent::handlerCharacterData($a_xml_parser, $a_data); - } - } - // the parser converts '>' to '>' and '<' to '<' // in character data, but we don't want that, because it's the // way we mask user html in our content, so we convert back... @@ -1280,7 +1028,7 @@ public function handlerCharacterData($a_xml_parser, string $a_data): void // append all data to page, if we are within PageObject, // but not within MetaData or MediaObject if (($this->in_page_object || $this->in_glossary_definition) - && !$this->in_meta_data && !$this->in_media_object) { + && !$this->in_media_object) { $this->page_object->appendXMLContent($a_data); } @@ -1295,41 +1043,6 @@ public function handlerCharacterData($a_xml_parser, string $a_data): void } } - /** - * @param resource $a_xml_parser - */ - public function emptyMediaMetaCache($a_xml_parser): void - { - foreach ($this->media_meta_cache as $cache_entry) { - switch ($cache_entry['type']) { - case 'handlerBeginTag': - parent::handlerBeginTag( - $a_xml_parser, - $cache_entry['par1'], - $cache_entry['par2'] - ); - break; - - case 'handlerEndTag': - parent::handlerEndTag( - $a_xml_parser, - $cache_entry['par1'] - ); - break; - - case 'handlerCharacterData': - parent::handlerCharacterData( - $a_xml_parser, - $cache_entry['par1'] - ); - break; - } - } - - $this->media_meta_start = false; - $this->media_meta_cache[] = []; - } - /** * @return mixed[] */ diff --git a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assClozeTestExport.php b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assClozeTestExport.php index fa71d5ca4869..38df4fc4c97e 100755 --- a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assClozeTestExport.php +++ b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assClozeTestExport.php @@ -436,8 +436,6 @@ public function toXML($a_include_header = true, $a_include_binary = true, $a_shu $a_xml_writer->xmlEndTag("itemfeedback"); } - $a_xml_writer = $this->addSolutionHints($a_xml_writer); - $a_xml_writer->xmlEndTag("item"); $a_xml_writer->xmlEndTag("questestinterop"); diff --git a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assErrorTextExport.php b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assErrorTextExport.php index 2e9cb893501c..7e10367f03a8 100755 --- a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assErrorTextExport.php +++ b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assErrorTextExport.php @@ -118,8 +118,6 @@ public function toXML($a_include_header = true, $a_include_binary = true, $a_shu $this->addAnswerSpecificFeedback($a_xml_writer, $this->object->getErrorData()); - $a_xml_writer = $this->addSolutionHints($a_xml_writer); - $a_xml_writer->xmlEndTag("item"); $a_xml_writer->xmlEndTag("questestinterop"); diff --git a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assFileUploadExport.php b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assFileUploadExport.php index d73128de3fe6..729ca485edea 100755 --- a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assFileUploadExport.php +++ b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assFileUploadExport.php @@ -1,4 +1,5 @@ exportFeedbackOnly($a_xml_writer); - $a_xml_writer = $this->addSolutionHints($a_xml_writer); - $a_xml_writer->xmlEndTag("item"); $a_xml_writer->xmlEndTag("questestinterop"); diff --git a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assFormulaQuestionExport.php b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assFormulaQuestionExport.php index cce066bc2bd8..97bb8528607a 100755 --- a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assFormulaQuestionExport.php +++ b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assFormulaQuestionExport.php @@ -1,4 +1,5 @@ addGenericFeedback($a_xml_writer); - $a_xml_writer = $this->addSolutionHints($a_xml_writer); - $a_xml_writer->xmlEndTag("item"); $a_xml_writer->xmlEndTag("questestinterop"); diff --git a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assImagemapQuestionExport.php b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assImagemapQuestionExport.php index 9f1efadbf12f..0820fc8f17a2 100755 --- a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assImagemapQuestionExport.php +++ b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assImagemapQuestionExport.php @@ -1,4 +1,5 @@ xmlEndTag("itemfeedback"); } - $a_xml_writer = $this->addSolutionHints($a_xml_writer); - $a_xml_writer->xmlEndTag("item"); $a_xml_writer->xmlEndTag("questestinterop"); diff --git a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assKprimChoiceExport.php b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assKprimChoiceExport.php index 6625a01b7462..cc1ba7f8c060 100755 --- a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assKprimChoiceExport.php +++ b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assKprimChoiceExport.php @@ -1,4 +1,5 @@ xmlEndTag('itemfeedback'); } - $a_xml_writer = $this->addSolutionHints($a_xml_writer); - $a_xml_writer->xmlEndTag("item"); $a_xml_writer->xmlEndTag("questestinterop"); diff --git a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assLongMenuExport.php b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assLongMenuExport.php index f6635a8e62ed..74f602d0676f 100755 --- a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assLongMenuExport.php +++ b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assLongMenuExport.php @@ -1,4 +1,5 @@ xmlEndTag("itemfeedback"); } - $xml = $this->addSolutionHints($xml); - $xml->xmlEndTag("item"); $xml->xmlEndTag("questestinterop"); diff --git a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assMatchingQuestionExport.php b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assMatchingQuestionExport.php index 960ca8e9480f..6f370f5b40f5 100755 --- a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assMatchingQuestionExport.php +++ b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assMatchingQuestionExport.php @@ -353,8 +353,6 @@ public function toXML($a_include_header = true, $a_include_binary = true, $a_shu $a_xml_writer->xmlEndTag("itemfeedback"); } - $a_xml_writer = $this->addSolutionHints($a_xml_writer); - $a_xml_writer->xmlEndTag("item"); $a_xml_writer->xmlEndTag("questestinterop"); diff --git a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assMultipleChoiceExport.php b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assMultipleChoiceExport.php index 324972cb732e..472c8b9dd5b7 100755 --- a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assMultipleChoiceExport.php +++ b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assMultipleChoiceExport.php @@ -328,8 +328,6 @@ public function toXML($a_include_header = true, $a_include_binary = true, $a_shu $a_xml_writer->xmlEndTag("itemfeedback"); } - $a_xml_writer = $this->addSolutionHints($a_xml_writer); - $a_xml_writer->xmlEndTag("item"); $a_xml_writer->xmlEndTag("questestinterop"); diff --git a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assNumericExport.php b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assNumericExport.php index 77649d250cdf..9a2c6c24b5d6 100755 --- a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assNumericExport.php +++ b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assNumericExport.php @@ -1,4 +1,5 @@ xmlEndTag("itemfeedback"); } - $a_xml_writer = $this->addSolutionHints($a_xml_writer); - $a_xml_writer->xmlEndTag("item"); $a_xml_writer->xmlEndTag("questestinterop"); diff --git a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assOrderingHorizontalExport.php b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assOrderingHorizontalExport.php index a0878883d361..aee266f640af 100755 --- a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assOrderingHorizontalExport.php +++ b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assOrderingHorizontalExport.php @@ -1,4 +1,5 @@ addGenericFeedback($a_xml_writer); - $a_xml_writer = $this->addSolutionHints($a_xml_writer); - $a_xml_writer->xmlEndTag("item"); $a_xml_writer->xmlEndTag("questestinterop"); diff --git a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assOrderingQuestionExport.php b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assOrderingQuestionExport.php index eb4ceb497118..de04e8117f9d 100755 --- a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assOrderingQuestionExport.php +++ b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assOrderingQuestionExport.php @@ -1,4 +1,5 @@ xmlEndTag("itemfeedback"); } - $a_xml_writer = $this->addSolutionHints($a_xml_writer); - $a_xml_writer->xmlEndTag("item"); $a_xml_writer->xmlEndTag("questestinterop"); diff --git a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assQuestionExport.php b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assQuestionExport.php index 54c6b2cfc896..a6c0a2755b23 100755 --- a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assQuestionExport.php +++ b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assQuestionExport.php @@ -1,4 +1,5 @@ object->getId(); - $list = ilAssQuestionHintList::getListByQuestionId($question_id); - - foreach ($list as $hint) { - $attrs = [ - 'index' => $hint->getIndex(), - 'points' => $hint->getPoints() - ]; - if ($this->object->isAdditionalContentEditingModePageObject()) { - try { - $data = (new ilAssHintPage($hint->getId()))->getXMLContent(); - } catch (Exception $e) { - continue; - } - } else { - $data = $hint->getText(); - } - - $writer->xmlElement(self::ITEM_SOLUTIONHINT, $attrs, $data); - } - return $writer; - } - protected function addSuggestedSolution(ilXmlWriter $writer): ilXmlWriter { $solution = $this->object->getSuggestedSolution(); @@ -283,7 +257,7 @@ protected function addSuggestedSolution(ilXmlWriter $writer): ilXmlWriter } $mattext['value'] = 'il_' . IL_INST_ID . '_' . $matches[2] . '_' . $matches[3]; if ($matches[1] !== '') { - $mattext['value'] = $solution['internal_link']; + $mattext['value'] = $solution->getInternalLink(); } } diff --git a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assSingleChoiceExport.php b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assSingleChoiceExport.php index afbfbd4939c8..d9aedc377e7a 100755 --- a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assSingleChoiceExport.php +++ b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assSingleChoiceExport.php @@ -311,8 +311,6 @@ public function toXML($a_include_header = true, $a_include_binary = true, $a_shu $a_xml_writer->xmlEndTag("itemfeedback"); } - $a_xml_writer = $this->addSolutionHints($a_xml_writer); - $a_xml_writer->xmlEndTag("item"); $a_xml_writer->xmlEndTag("questestinterop"); $xml = $a_xml_writer->xmlDumpMem(false); diff --git a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assTextQuestionExport.php b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assTextQuestionExport.php index 43f4bb86d425..009fb070ac68 100755 --- a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assTextQuestionExport.php +++ b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assTextQuestionExport.php @@ -1,4 +1,5 @@ xmlEndTag("itemfeedback"); } - $a_xml_writer = $this->addSolutionHints($a_xml_writer); - $a_xml_writer->xmlEndTag("item"); $a_xml_writer->xmlEndTag("questestinterop"); diff --git a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assTextSubsetExport.php b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assTextSubsetExport.php index 768669a79d87..8fbbf6a12d04 100755 --- a/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assTextSubsetExport.php +++ b/components/ILIAS/TestQuestionPool/classes/export/qti12/class.assTextSubsetExport.php @@ -1,4 +1,5 @@ xmlEndTag("itemfeedback"); } - $a_xml_writer = $this->addSolutionHints($a_xml_writer); - $a_xml_writer->xmlEndTag("item"); $a_xml_writer->xmlEndTag("questestinterop"); diff --git a/components/ILIAS/TestQuestionPool/classes/forms/class.ilAssAnswerCorrectionsInputGUI.php b/components/ILIAS/TestQuestionPool/classes/forms/class.ilAssAnswerCorrectionsInputGUI.php index 2e82cc7e9308..f52443a7676c 100755 --- a/components/ILIAS/TestQuestionPool/classes/forms/class.ilAssAnswerCorrectionsInputGUI.php +++ b/components/ILIAS/TestQuestionPool/classes/forms/class.ilAssAnswerCorrectionsInputGUI.php @@ -69,12 +69,12 @@ public function insert(ilTemplate $a_tpl): void $tpl->setVariable('POST_VAR', $this->getPostVar()); $tpl->setVariable('ROW_NUMBER', $i); $tpl->setVariable('POINTS_ID', $this->getPostVar() . "[points][$i]"); - $tpl->setVariable('POINTS', ilLegacyFormElementsUtil::prepareFormOutput($value->getPoints())); + $tpl->setVariable('POINTS', $this->prepareFormOutput($value->getPoints())); $tpl->parseCurrentBlock(); } $tpl->setCurrentBlock('row'); - $tpl->setVariable('ANSWER', ilLegacyFormElementsUtil::prepareFormOutput($value->getAnswertext())); + $tpl->setVariable('ANSWER', $this->prepareFormOutput($value->getAnswertext())); $tpl->parseCurrentBlock(); $i++; } diff --git a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assClozeTestImport.php b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assClozeTestImport.php index de9048559dcc..a0e2f8b971ef 100755 --- a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assClozeTestImport.php +++ b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assClozeTestImport.php @@ -1,4 +1,5 @@ db = $DIC['ilDB']; + + parent::__construct($a_object); + } + /** * Creates a question from a QTI file * @@ -295,9 +307,9 @@ public function fromXML( $this->object->saveToDb(); if (is_array($combinations) && count($combinations) > 0) { - assClozeGapCombination::clearGapCombinationsFromDb($this->object->getId()); - assClozeGapCombination::importGapCombinationToDb($this->object->getId(), $combinations); - $gap_combinations = new assClozeGapCombination(); + $gap_combinations = new assClozeGapCombination($this->db); + $gap_combinations->clearGapCombinationsFromDb($this->object->getId()); + $gap_combinations->importGapCombinationToDb($this->object->getId(), $combinations); $gap_combinations->loadFromDb($this->object->getId()); $this->object->setGapCombinations($gap_combinations); $this->object->setGapCombinationsExists(true); @@ -350,21 +362,12 @@ public function fromXML( $this->object->saveToDb(); $this->importSuggestedSolutions($this->object->getId(), $item->suggested_solutions); - if (isset($tst_id) && $tst_id !== $questionpool_id) { - $qpl_qid = $this->object->getId(); - $tst_qid = $this->object->duplicate(true, '', '', -1, $tst_id); - $tst_object->questions[$question_counter++] = $tst_qid; - $import_mapping[$item->getIdent()] = ['pool' => $qpl_qid, 'test' => $tst_qid]; - return $import_mapping; - } - - if ($tst_id > 0) { - $tst_object->questions[$question_counter++] = $this->object->getId(); - $import_mapping[$item->getIdent()] = ['pool' => 0, 'test' => $this->object->getId()]; - return $import_mapping; - } - - $import_mapping[$item->getIdent()] = ['pool' => $this->object->getId(), 'test' => 0]; + $import_mapping[$item->getIdent()] = $this->addQuestionToParentObjectAndBuildMappingEntry( + $questionpool_id, + $tst_id, + $question_counter, + $tst_object + ); return $import_mapping; } diff --git a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assErrorTextImport.php b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assErrorTextImport.php index 8c561423c97e..2f34cae75ead 100755 --- a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assErrorTextImport.php +++ b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assErrorTextImport.php @@ -110,14 +110,12 @@ public function fromXML( } $this->object->saveToDb(); $this->importSuggestedSolutions($this->object->getId(), $item->suggested_solutions); - if ($tst_id > 0) { - $q_1_id = $this->object->getId(); - $question_id = $this->object->duplicate(true, "", "", -1, $tst_id); - $tst_object->questions[$question_counter++] = $question_id; - $import_mapping[$item->getIdent()] = ["pool" => $q_1_id, "test" => $question_id]; - } else { - $import_mapping[$item->getIdent()] = ["pool" => $this->object->getId(), "test" => 0]; - } + $import_mapping[$item->getIdent()] = $this->addQuestionToParentObjectAndBuildMappingEntry( + $questionpool_id, + $tst_id, + $question_counter, + $tst_object + ); return $import_mapping; } } diff --git a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assFileUploadImport.php b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assFileUploadImport.php index 80982d2819d2..b3ab518f25c4 100755 --- a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assFileUploadImport.php +++ b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assFileUploadImport.php @@ -1,4 +1,5 @@ object->saveToDb(); $this->importSuggestedSolutions($this->object->getId(), $item->suggested_solutions); - if ($tst_id > 0) { - $q_1_id = $this->object->getId(); - $question_id = $this->object->duplicate(true, "", "", -1, $tst_id); - $tst_object->questions[$question_counter++] = $question_id; - $import_mapping[$item->getIdent()] = ["pool" => $q_1_id, "test" => $question_id]; - } else { - $import_mapping[$item->getIdent()] = ["pool" => $this->object->getId(), "test" => 0]; - } + $import_mapping[$item->getIdent()] = $this->addQuestionToParentObjectAndBuildMappingEntry( + $questionpool_id, + $tst_id, + $question_counter, + $tst_object + ); return $import_mapping; } } diff --git a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assFormulaQuestionImport.php b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assFormulaQuestionImport.php index d65a5ac032e7..abdc136edeb9 100755 --- a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assFormulaQuestionImport.php +++ b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assFormulaQuestionImport.php @@ -116,14 +116,12 @@ public function fromXML( ); $this->object->saveToDb(); $this->importSuggestedSolutions($this->object->getId(), $item->suggested_solutions); - if ($tst_id > 0) { - $q_1_id = $this->object->getId(); - $question_id = $this->object->duplicate(); - $tst_object->questions[$question_counter++] = $question_id; - $import_mapping[$item->getIdent()] = ["pool" => $q_1_id, "test" => $question_id]; - } else { - $import_mapping[$item->getIdent()] = ["pool" => $this->object->getId(), "test" => 0]; - } + $import_mapping[$item->getIdent()] = $this->addQuestionToParentObjectAndBuildMappingEntry( + $questionpool_id, + $tst_id, + $question_counter, + $tst_object + ); return $import_mapping; } } diff --git a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assImagemapQuestionImport.php b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assImagemapQuestionImport.php index 1007deb12581..8f907e7a47d2 100755 --- a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assImagemapQuestionImport.php +++ b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assImagemapQuestionImport.php @@ -255,14 +255,12 @@ public function fromXML( ); } $this->object->saveToDb(); - if ($tst_id > 0) { - $q_1_id = $this->object->getId(); - $question_id = $this->object->duplicate(true, "", "", -1, $tst_id); - $tst_object->questions[$question_counter++] = $question_id; - $import_mapping[$item->getIdent()] = ["pool" => $q_1_id, "test" => $question_id]; - } else { - $import_mapping[$item->getIdent()] = ["pool" => $this->object->getId(), "test" => 0]; - } + $import_mapping[$item->getIdent()] = $this->addQuestionToParentObjectAndBuildMappingEntry( + $questionpool_id, + $tst_id, + $question_counter, + $tst_object + ); return $import_mapping; } } diff --git a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assKprimChoiceImport.php b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assKprimChoiceImport.php index 8524ef4e2ed1..b714a0897dca 100755 --- a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assKprimChoiceImport.php +++ b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assKprimChoiceImport.php @@ -1,4 +1,5 @@ getPresentation(); foreach ($presentation->order as $entry) { - switch ($entry["type"]) { - case "response": - $response = $presentation->response[$entry["index"]]; - $rendertype = $response->getRenderType(); - switch (strtolower(get_class($response->getRenderType()))) { - case "ilqtirenderchoice": - $shuffle = $rendertype->getShuffle(); - $answerorder = 0; - $foundimage = false; - foreach ($rendertype->response_labels as $response_label) { - $ident = $response_label->getIdent(); - $answertext = ""; - $answerimage = []; - foreach ($response_label->material as $mat) { - $embedded = false; - for ($m = 0; $m < $mat->getMaterialCount(); $m++) { - $foundmat = $mat->getMaterial($m); - if (strcmp($foundmat["type"], "mattext") == 0) { - } - if (strcmp($foundmat["type"], "matimage") == 0) { - if (strlen($foundmat["material"]->getEmbedded())) { - $embedded = true; - } - } - } - if ($embedded) { - for ($m = 0; $m < $mat->getMaterialCount(); $m++) { - $foundmat = $mat->getMaterial($m); - if (strcmp($foundmat["type"], "mattext") == 0) { - $answertext .= $foundmat["material"]->getContent(); - } - if (strcmp($foundmat["type"], "matimage") == 0) { - $foundimage = true; - $answerimage = [ - "imagetype" => $foundmat["material"]->getImageType(), - "label" => $foundmat["material"]->getLabel(), - "content" => $foundmat["material"]->getContent() - ]; - } - } - } else { - $answertext = $this->QTIMaterialToString($mat); - } - } + if ($entry['type'] !== 'response') { + continue; + } - $answers[$ident] = [ - "answertext" => $answertext, - "imagefile" => $answerimage, - "answerorder" => $ident - ]; - } - break; + $response = $presentation->response[$entry['index']]; + $rendertype = $response->getRenderType(); + if (strtolower(get_class($response->getRenderType())) !== 'ilqtirenderchoice') { + continue; + } + + $shuffle = $rendertype->getShuffle(); + $foundimage = false; + foreach ($rendertype->response_labels as $response_label) { + $ident = $response_label->getIdent(); + $answertext = ''; + $answerimage = []; + foreach ($response_label->material as $mat) { + $embedded = false; + for ($m = 0; $m < $mat->getMaterialCount(); $m++) { + $foundmat = $mat->getMaterial($m); + if ($foundmat['type'] === 'matimage' + && $foundmat['material']->getEmbedded() !== '') { + $embedded = true; + } } - break; + if (!$embedded) { + $answertext = $this->QTIMaterialToString($mat); + continue; + } + + for ($m = 0; $m < $mat->getMaterialCount(); $m++) { + $foundmat = $mat->getMaterial($m); + if ($foundmat['type'] === 'mattext') { + $answertext .= $foundmat['material']->getContent(); + } + if ($foundmat['type'] === 'matimage') { + $foundimage = true; + $answerimage = [ + 'imagetype' => $foundmat['material']->getImageType(), + 'label' => $foundmat['material']->getLabel(), + 'content' => $foundmat['material']->getContent() + ]; + } + } + } + + $answers[$ident] = [ + 'answertext' => $answertext, + 'imagefile' => $answerimage, + 'answerorder' => $ident + ]; } } @@ -109,83 +107,70 @@ public function fromXML( foreach ($resprocessing->outcomes->decvar as $decvar) { if ($decvar->getVarname() == 'SCORE') { $this->object->setPoints($decvar->getMaxvalue()); - + $this->object->setScorePartialSolutionEnabled(false); if ($decvar->getMinvalue() > 0) { $this->object->setScorePartialSolutionEnabled(true); - } else { - $this->object->setScorePartialSolutionEnabled(false); } } } foreach ($resprocessing->respcondition as $respcondition) { - if (!count($respcondition->setvar)) { + if ($respcondition->setvar === []) { foreach ($respcondition->getConditionvar()->varequal as $varequal) { $ident = $varequal->respident; $answers[$ident]['correctness'] = (bool) $varequal->getContent(); - break; } foreach ($respcondition->displayfeedback as $feedbackpointer) { - if (strlen($feedbackpointer->getLinkrefid())) { - foreach ($item->itemfeedback as $ifb) { - if (strcmp($ifb->getIdent(), $feedbackpointer->getLinkrefid()) == 0) { - // found a feedback for the identifier - if (count($ifb->material)) { - foreach ($ifb->material as $material) { - $feedbacks[$ident] = $material; - } - } - if ((count($ifb->flow_mat) > 0)) { - foreach ($ifb->flow_mat as $fmat) { - if (count($fmat->material)) { - foreach ($fmat->material as $material) { - $feedbacks[$ident] = $material; - } - } - } - } + if ($feedbackpointer->getLinkrefid() === '') { + continue; + } + foreach ($item->itemfeedback as $ifb) { + if ($ifb->getIdent() !== $feedbackpointer->getLinkrefid()) { + continue; + } + + foreach ($ifb->material as $material) { + $feedbacks[$ident] = $material; + } + foreach ($ifb->flow_mat as $fmat) { + foreach ($fmat->material as $material) { + $feedbacks[$ident] = $material; } } } } - } else { - foreach ($respcondition->displayfeedback as $feedbackpointer) { - if (strlen($feedbackpointer->getLinkrefid())) { - foreach ($item->itemfeedback as $ifb) { - if ($ifb->getIdent() == "response_allcorrect") { - // found a feedback for the identifier - if (count($ifb->material)) { - foreach ($ifb->material as $material) { - $feedbacksgeneric[1] = $material; - } - } - if ((count($ifb->flow_mat) > 0)) { - foreach ($ifb->flow_mat as $fmat) { - if (count($fmat->material)) { - foreach ($fmat->material as $material) { - $feedbacksgeneric[1] = $material; - } - } - } - } - } elseif ($ifb->getIdent() == "response_onenotcorrect") { - // found a feedback for the identifier - if (count($ifb->material)) { - foreach ($ifb->material as $material) { - $feedbacksgeneric[0] = $material; - } - } - if ((count($ifb->flow_mat) > 0)) { - foreach ($ifb->flow_mat as $fmat) { - if (count($fmat->material)) { - foreach ($fmat->material as $material) { - $feedbacksgeneric[0] = $material; - } - } - } - } + + continue; + } + + foreach ($respcondition->displayfeedback as $feedbackpointer) { + if ($feedbackpointer->getLinkrefid() === '') { + continue; + } + + foreach ($item->itemfeedback as $ifb) { + if ($ifb->getIdent() === 'response_allcorrect') { + foreach ($ifb->material as $material) { + $feedbacksgeneric[1] = $material; + } + foreach ($ifb->flow_mat as $fmat) { + foreach ($fmat->material as $material) { + $feedbacksgeneric[1] = $material; + } + } + continue; + } + + if ($ifb->getIdent() === 'response_onenotcorrect') { + // found a feedback for the identifier + foreach ($ifb->material as $material) { + $feedbacksgeneric[0] = $material; + } + foreach ($ifb->flow_mat as $fmat) { + foreach ($fmat->material as $material) { + $feedbacksgeneric[0] = $material; } } } @@ -213,21 +198,20 @@ public function fromXML( $this->object->saveToDb(); - foreach ($answers as $answerData) { + $answer_objects = []; + foreach ($answers as $answer_data) { $answer = new ilAssKprimChoiceAnswer(); $answer->setImageFsDir($this->object->getImagePath()); $answer->setImageWebDir($this->object->getImagePathWeb()); - - $answer->setPosition($answerData['answerorder']); - $answer->setAnswertext($answerData['answertext']); - $answer->setCorrectness($answerData['correctness']); - - if (isset($answerData['imagefile']['label'])) { - $answer->setImageFile($answerData['imagefile']['label']); + $answer->setPosition($answer_data['answerorder']); + $answer->setAnswertext($answer_data['answertext']); + $answer->setCorrectness($answer_data['correctness']); + if (isset($answer_data['imagefile']['label'])) { + $answer->setImageFile($answer_data['imagefile']['label']); } - - $this->object->addAnswer($answer); + $answer_objects[] = $answer; } + $this->object->setAnswers($answer_objects); // additional content editing mode information $this->object->setAdditionalContentEditingMode( $this->fetchAdditionalContentEditingModeInformation($item) @@ -236,29 +220,30 @@ public function fromXML( $this->object->saveToDb(); foreach ($answers as $answer) { - if (is_array($answer["imagefile"]) && (count($answer["imagefile"]) > 0)) { - $image = base64_decode($answer["imagefile"]["content"]); - $imagepath = $this->object->getImagePath(); - if (!file_exists($imagepath)) { - ilFileUtils::makeDirParents($imagepath); - } - $imagepath .= $answer["imagefile"]["label"]; - if ($fh = fopen($imagepath, "wb")) { - $imagefile = fwrite($fh, $image); - fclose($fh); - $this->object->generateThumbForFile( - $answer["imagefile"]["label"], - $this->object->getImagePath(), - $this->object->getThumbSize() - ); - } + if (!is_array($answer['imagefile']) || $answer['imagefile'] === []) { + continue; + } + $image = base64_decode($answer['imagefile']['content']); + $imagepath = $this->object->getImagePath(); + if (!file_exists($imagepath)) { + ilFileUtils::makeDirParents($imagepath); + } + $imagepath .= $answer['imagefile']['label']; + if ($fh = fopen($imagepath, 'wb')) { + $imagefile = fwrite($fh, $image); + fclose($fh); + $this->object->generateThumbForFile( + $answer['imagefile']['label'], + $this->object->getImagePath(), + $this->object->getThumbSize() + ); } } - $feedbackSetting = $item->getMetadataEntry('feedback_setting'); - if (!is_null($feedbackSetting)) { - $this->object->feedbackOBJ->saveSpecificFeedbackSetting($this->object->getId(), $feedbackSetting); - $this->object->setSpecificFeedbackSetting($feedbackSetting); + $feedback_setting = $item->getMetadataEntry('feedback_setting'); + if (!is_null($feedback_setting)) { + $this->object->feedbackOBJ->saveSpecificFeedbackSetting($this->object->getId(), $feedback_setting); + $this->object->setSpecificFeedbackSetting($feedback_setting); } // handle the import of media objects in XHTML code @@ -282,8 +267,7 @@ public function fromXML( $media_object = ilObjMediaObject::_saveTempFileAsMediaObject(basename($importfile), $importfile, false); ilObjMediaObject::_saveUsage($media_object->getId(), "qpl:html", $this->object->getId()); $questiontext = str_replace("src=\"" . $mob["mob"] . "\"", "src=\"" . "il_" . IL_INST_ID . "_mob_" . $media_object->getId() . "\"", $questiontext); - foreach ($answers as $key => $value) { - $answer_obj = $answers[$key]; + foreach ($answers as $answer_obj) { if ($answer_obj->getAnswertext() === null) { continue; } @@ -298,8 +282,7 @@ public function fromXML( } } $this->object->setQuestion(ilRTE::_replaceMediaObjectImageSrc($questiontext, 1)); - foreach ($answers as $key => $value) { - $answer_obj = $answers[$key]; + foreach ($answers as $answer_obj) { if ($answer_obj->getAnswertext() === null) { continue; } @@ -322,14 +305,12 @@ public function fromXML( } $this->object->saveToDb(); $this->importSuggestedSolutions($this->object->getId(), $item->suggested_solutions); - if ($tst_id > 0) { - $q_1_id = $this->object->getId(); - $question_id = $this->object->duplicate(true, "", "", -1, $tst_id); - $tst_object->questions[$question_counter++] = $question_id; - $import_mapping[$item->getIdent()] = ["pool" => $q_1_id, "test" => $question_id]; - } else { - $import_mapping[$item->getIdent()] = ["pool" => $this->object->getId(), "test" => 0]; - } + $import_mapping[$item->getIdent()] = $this->addQuestionToParentObjectAndBuildMappingEntry( + $questionpool_id, + $tst_id, + $question_counter, + $tst_object + ); return $import_mapping; } } diff --git a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assLongMenuImport.php b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assLongMenuImport.php index de06b40d173b..dd29333867d7 100755 --- a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assLongMenuImport.php +++ b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assLongMenuImport.php @@ -1,4 +1,5 @@ object->saveToDb(); - if (count($item->suggested_solutions)) { - foreach ($item->suggested_solutions as $suggested_solution) { - $this->object->setSuggestedSolution($suggested_solution["solution"]->getContent(), $suggested_solution["gap_index"], true); - } - $this->object->saveToDb(); - } - if ($tst_id > 0) { - $q_1_id = $this->object->getId(); - $question_id = $this->object->duplicate(true, "", "", -1, $tst_id); - $tst_object->questions[$question_counter++] = $question_id; - $import_mapping[$item->getIdent()] = ["pool" => $q_1_id, "test" => $question_id]; - } else { - $import_mapping[$item->getIdent()] = ["pool" => $this->object->getId(), "test" => 0]; - } + $import_mapping[$item->getIdent()] = $this->addQuestionToParentObjectAndBuildMappingEntry( + $questionpool_id, + $tst_id, + $question_counter, + $tst_object + ); return $import_mapping; } diff --git a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assMatchingQuestionImport.php b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assMatchingQuestionImport.php index 768e4ce6968f..038f670acb63 100755 --- a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assMatchingQuestionImport.php +++ b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assMatchingQuestionImport.php @@ -297,14 +297,12 @@ public function fromXML( ); } $this->object->saveToDb(); - if ($tst_id > 0) { - $q_1_id = $this->object->getId(); - $question_id = $this->object->duplicate(true, "", "", -1, $tst_id); - $tst_object->questions[$question_counter++] = $question_id; - $import_mapping[$item->getIdent()] = ["pool" => $q_1_id, "test" => $question_id]; - } else { - $import_mapping[$item->getIdent()] = ["pool" => $this->object->getId(), "test" => 0]; - } + $import_mapping[$item->getIdent()] = $this->addQuestionToParentObjectAndBuildMappingEntry( + $questionpool_id, + $tst_id, + $question_counter, + $tst_object + ); return $import_mapping; } diff --git a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assMultipleChoiceImport.php b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assMultipleChoiceImport.php index 5dd0432459e0..1a299cc2aaec 100755 --- a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assMultipleChoiceImport.php +++ b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assMultipleChoiceImport.php @@ -206,6 +206,7 @@ public function fromXML( $this->object->setObjId($questionpool_id); $this->object->setShuffle($shuffle); $this->object->setSelectionLimit($selectionLimit); + $this->object->setIsSingleline(false); $this->object->setThumbSize( $this->deduceThumbSizeFromImportValue((int) $item->getMetadataEntry('thumb_size')) ); @@ -308,14 +309,12 @@ public function fromXML( } $this->object->saveToDb(); $this->importSuggestedSolutions($this->object->getId(), $item->suggested_solutions); - if ($tst_id > 0) { - $q_1_id = $this->object->getId(); - $question_id = $this->object->duplicate(true, "", "", -1, $tst_id); - $tst_object->questions[$question_counter++] = $question_id; - $import_mapping[$item->getIdent()] = ["pool" => $q_1_id, "test" => $question_id]; - } else { - $import_mapping[$item->getIdent()] = ["pool" => $this->object->getId(), "test" => 0]; - } + $import_mapping[$item->getIdent()] = $this->addQuestionToParentObjectAndBuildMappingEntry( + $questionpool_id, + $tst_id, + $question_counter, + $tst_object + ); return $import_mapping; } } diff --git a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assNumericImport.php b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assNumericImport.php index 539f5a1ad12e..be1876d868cb 100755 --- a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assNumericImport.php +++ b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assNumericImport.php @@ -170,14 +170,12 @@ public function fromXML( ); } $this->object->saveToDb(); - if ($tst_id > 0) { - $q_1_id = $this->object->getId(); - $question_id = $this->object->duplicate(true, "", "", -1, $tst_id); - $tst_object->questions[$question_counter++] = $question_id; - $import_mapping[$item->getIdent()] = ["pool" => $q_1_id, "test" => $question_id]; - } else { - $import_mapping[$item->getIdent()] = ["pool" => $this->object->getId(), "test" => 0]; - } + $import_mapping[$item->getIdent()] = $this->addQuestionToParentObjectAndBuildMappingEntry( + $questionpool_id, + $tst_id, + $question_counter, + $tst_object + ); return $import_mapping; } } diff --git a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assOrderingHorizontalImport.php b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assOrderingHorizontalImport.php index 5016872277dc..2fe4108cfc1b 100755 --- a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assOrderingHorizontalImport.php +++ b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assOrderingHorizontalImport.php @@ -107,14 +107,12 @@ public function fromXML( } $this->object->saveToDb(); $this->importSuggestedSolutions($this->object->getId(), $item->suggested_solutions); - if ($tst_id > 0) { - $q_1_id = $this->object->getId(); - $question_id = $this->object->duplicate(true, "", "", -1, $tst_id); - $tst_object->questions[$question_counter++] = $question_id; - $import_mapping[$item->getIdent()] = ["pool" => $q_1_id, "test" => $question_id]; - } else { - $import_mapping[$item->getIdent()] = ["pool" => $this->object->getId(), "test" => 0]; - } + $import_mapping[$item->getIdent()] = $this->addQuestionToParentObjectAndBuildMappingEntry( + $questionpool_id, + $tst_id, + $question_counter, + $tst_object + ); return $import_mapping; } } diff --git a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assOrderingQuestionImport.php b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assOrderingQuestionImport.php index 2fd70ffd16fb..62585dc7e0ca 100755 --- a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assOrderingQuestionImport.php +++ b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assOrderingQuestionImport.php @@ -1,4 +1,5 @@ object->saveToDb(); - if (isset($tst_id) && $tst_id !== $questionpool_id) { - $qplQid = $this->object->getId(); - $tstQid = $this->object->duplicate(true, '', '', -1, $tst_id); - $tst_object->questions[$question_counter++] = $tstQid; - $import_mapping[$item->getIdent()] = ["pool" => $qplQid, "test" => $tstQid]; - return $import_mapping; - } - - if ($tst_id > 0) { - $tst_object->questions[$question_counter++] = $this->object->getId(); - $import_mapping[$item->getIdent()] = ["pool" => 0, "test" => $this->object->getId()]; - return $import_mapping; - } - - $import_mapping[$item->getIdent()] = ["pool" => $this->object->getId(), "test" => 0]; + $import_mapping[$item->getIdent()] = $this->addQuestionToParentObjectAndBuildMappingEntry( + $questionpool_id, + $tst_id, + $question_counter, + $tst_object + ); return $import_mapping; } diff --git a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assQuestionImport.php b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assQuestionImport.php index aa8e6fe338bd..1892242f8f5e 100755 --- a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assQuestionImport.php +++ b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assQuestionImport.php @@ -1,4 +1,5 @@ object->getThumbSize(); } - if ($size < $this->object->getMaximumThumbSize()) { - return $this->object->getMaximumThumbSize(); + if ($size < $this->object->getMinimumThumbSize()) { + return $this->object->getMinimumThumbSize(); } if ($size > $this->object->getMaximumThumbSize()) { @@ -383,4 +384,23 @@ protected function deduceThumbSizeFromImportValue(?int $size): int return $size; } + + protected function addQuestionToParentObjectAndBuildMappingEntry( + int $questionpool_id, + ?int $tst_id, + int &$question_counter, + ?ilObjTest &$tst_object + ): array { + if ($tst_id !== null && $tst_id === $questionpool_id) { + $tst_object->questions[$question_counter++] = $this->object->getId(); + return ['pool' => 0, 'test' => $this->object->getId()]; + } + + if ($tst_id > 0) { + $question_id = $this->object->duplicate(true, '', '', -1, $tst_id); + $tst_object->questions[$question_counter++] = $question_id; + return ['pool' => $this->object->getId(), 'test' => $question_id]; + } + return ['pool' => $this->object->getId(), 'test' => 0]; + } } diff --git a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assSingleChoiceImport.php b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assSingleChoiceImport.php index b0493d36acfa..af1b8f86cef1 100755 --- a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assSingleChoiceImport.php +++ b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assSingleChoiceImport.php @@ -200,6 +200,7 @@ public function fromXML( $this->object->setQuestion($this->QTIMaterialToString($item->getQuestiontext())); $this->object->setObjId($questionpool_id); $this->object->setShuffle($shuffle); + $this->object->setIsSingleline(false); $this->object->setThumbSize( $this->deduceThumbSizeFromImportValue((int) $item->getMetadataEntry('thumb_size')) ); @@ -304,14 +305,12 @@ public function fromXML( } $this->object->saveToDb(); $this->importSuggestedSolutions($this->object->getId(), $item->suggested_solutions); - if ($tst_id > 0) { - $q_1_id = $this->object->getId(); - $question_id = $this->object->duplicate(true, "", "", -1, $tst_id); - $tst_object->questions[$question_counter++] = $question_id; - $import_mapping[$item->getIdent()] = ["pool" => $q_1_id, "test" => $question_id]; - } else { - $import_mapping[$item->getIdent()] = ["pool" => $this->object->getId(), "test" => 0]; - } + $import_mapping[$item->getIdent()] = $this->addQuestionToParentObjectAndBuildMappingEntry( + $questionpool_id, + $tst_id, + $question_counter, + $tst_object + ); return $import_mapping; } } diff --git a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assTextQuestionImport.php b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assTextQuestionImport.php index 54d0c89ada85..15addfb54645 100755 --- a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assTextQuestionImport.php +++ b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assTextQuestionImport.php @@ -208,14 +208,12 @@ public function fromXML( ); } $this->object->saveToDb(); - if ($tst_id > 0) { - $q_1_id = $this->object->getId(); - $question_id = $this->object->duplicate(true, "", "", -1, $tst_id); - $tst_object->questions[$question_counter++] = $question_id; - $import_mapping[$item->getIdent()] = ["pool" => $q_1_id, "test" => $question_id]; - } else { - $import_mapping[$item->getIdent()] = ["pool" => $this->object->getId(), "test" => 0]; - } + $import_mapping[$item->getIdent()] = $this->addQuestionToParentObjectAndBuildMappingEntry( + $questionpool_id, + $tst_id, + $question_counter, + $tst_object + ); return $import_mapping; } diff --git a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assTextSubsetImport.php b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assTextSubsetImport.php index d0b5346453a7..9f21b8305fe9 100755 --- a/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assTextSubsetImport.php +++ b/components/ILIAS/TestQuestionPool/classes/import/qti12/class.assTextSubsetImport.php @@ -188,14 +188,12 @@ public function fromXML( ); } $this->object->saveToDb(); - if ($tst_id > 0) { - $q_1_id = $this->object->getId(); - $question_id = $this->object->duplicate(true, "", "", -1, $tst_id); - $tst_object->questions[$question_counter++] = $question_id; - $import_mapping[$item->getIdent()] = ["pool" => $q_1_id, "test" => $question_id]; - } else { - $import_mapping[$item->getIdent()] = ["pool" => $this->object->getId(), "test" => 0]; - } + $import_mapping[$item->getIdent()] = $this->addQuestionToParentObjectAndBuildMappingEntry( + $questionpool_id, + $tst_id, + $question_counter, + $tst_object + ); return $import_mapping; } } diff --git a/components/ILIAS/TestQuestionPool/classes/questions/LogicalAnswerCompare/ilAssLacCompositeEvaluator.php b/components/ILIAS/TestQuestionPool/classes/questions/LogicalAnswerCompare/ilAssLacCompositeEvaluator.php index d40c1b14c3ff..2083b8144dab 100755 --- a/components/ILIAS/TestQuestionPool/classes/questions/LogicalAnswerCompare/ilAssLacCompositeEvaluator.php +++ b/components/ILIAS/TestQuestionPool/classes/questions/LogicalAnswerCompare/ilAssLacCompositeEvaluator.php @@ -1,4 +1,5 @@ getSolutionForKey($index); $answer = $question->getAvailableAnswerOptions($index - 1); + $unit_repository = $question->getUnitrepository(); $unit = $solutions->getSolutionForKey($index . "_unit"); - $key = null; - if (is_array($unit)) { - $key = $unit['value']; - } + $key = isset($unit["value"]) + ? $unit_repository->getUnit((int) $unit["value"]) + : null; $max_points = $answer->getPoints(); // @PHP8-CR @@ -176,7 +177,13 @@ private function evaluateSubTree(ilAssLacAbstractComposite $composite): bool // to a later date and eventually task this to T&A TechSquad for analysis. // Candidate: //$points = $question->getReachedPoints($question->getVariables(), $question->getResults(), $result["value"], $key, $question->getUnitrepository()->getUnits()); - $points = $answer->getReachedPoints($question->getVariables(), $question->getResults(), $result["value"], $key, $question->getUnitrepository()->getUnits()); + $points = $answer->getReachedPoints( + $question->getVariables(), + $question->getResults(), + $result["value"] ?? "", + $key, + $unit_repository->getUnits() + ); $percentage = 0; if ($max_points != 0) { diff --git a/components/ILIAS/TestQuestionPool/classes/questions/class.ilAssQuestionSkillAssignmentXmlParser.php b/components/ILIAS/TestQuestionPool/classes/questions/class.ilAssQuestionSkillAssignmentXmlParser.php index cff4fb14696e..8f6fc739211a 100755 --- a/components/ILIAS/TestQuestionPool/classes/questions/class.ilAssQuestionSkillAssignmentXmlParser.php +++ b/components/ILIAS/TestQuestionPool/classes/questions/class.ilAssQuestionSkillAssignmentXmlParser.php @@ -133,9 +133,8 @@ public function setCurExpression(?ilAssQuestionSolutionComparisonExpressionImpor public function setHandlers($a_xml_parser): void { - xml_set_object($a_xml_parser, $this); - xml_set_element_handler($a_xml_parser, 'handlerBeginTag', 'handlerEndTag'); - xml_set_character_data_handler($a_xml_parser, 'handlerCharacterData'); + xml_set_element_handler($a_xml_parser, $this->handlerBeginTag(...), $this->handlerEndTag(...)); + xml_set_character_data_handler($a_xml_parser, $this->handlerCharacterData(...)); } public function handlerBeginTag($xmlParser, $tagName, $tagAttributes): void diff --git a/components/ILIAS/TestQuestionPool/classes/tables/class.ilAnswerFrequencyStatisticTableGUI.php b/components/ILIAS/TestQuestionPool/classes/tables/class.ilAnswerFrequencyStatisticTableGUI.php index 6ee9ad31b3e6..07408c0ffeb9 100755 --- a/components/ILIAS/TestQuestionPool/classes/tables/class.ilAnswerFrequencyStatisticTableGUI.php +++ b/components/ILIAS/TestQuestionPool/classes/tables/class.ilAnswerFrequencyStatisticTableGUI.php @@ -112,7 +112,7 @@ public function initColumns(): void public function fillRow(array $a_set): void { $this->tpl->setCurrentBlock('answer'); - $this->tpl->setVariable('ANSWER', ilHtmlPurifierFactory::getInstanceByType('qpl_usersolution')->purify($a_set['answer'])); + $this->tpl->setVariable('ANSWER', htmlspecialchars((string) $a_set['answer'], ENT_QUOTES)); $this->tpl->parseCurrentBlock(); $this->tpl->setCurrentBlock('frequency'); @@ -142,7 +142,7 @@ protected function buildAddAnswerAction($data): string $this->refinery, $this->language, $this->ctrl - ))->buildAddAnswerModal($this->question->getTitle(), $data); + ))->buildAddAnswerModal($this->question->getTitleForHTMLOutput(), $data); $show_modal_button = $this->ui_factory->button()->standard( $this->language->txt('tst_corr_add_as_answer_btn'), diff --git a/components/ILIAS/TestQuestionPool/classes/tables/class.ilAssQuestionSkillAssignmentsTableGUI.php b/components/ILIAS/TestQuestionPool/classes/tables/class.ilAssQuestionSkillAssignmentsTableGUI.php index d7dd8c4c755e..d3ad44afe860 100755 --- a/components/ILIAS/TestQuestionPool/classes/tables/class.ilAssQuestionSkillAssignmentsTableGUI.php +++ b/components/ILIAS/TestQuestionPool/classes/tables/class.ilAssQuestionSkillAssignmentsTableGUI.php @@ -27,43 +27,25 @@ */ class ilAssQuestionSkillAssignmentsTableGUI extends ilTable2GUI { - private ilAssQuestionSkillAssignmentList $skillQuestionAssignmentList; + private ilAssQuestionSkillAssignmentList $skill_question_assignment_list; private RequestDataCollector $request_data_collector; - private bool $loadSkillPointsFromRequest = false; - private bool $manipulationsEnabled; - - /** - * @var array Question ID => Manipulation allowed - */ - private array $manipulations_allowed_list = []; + private bool $load_skill_points_from_request = false; + private bool $manipulations_enabled; public function setSkillQuestionAssignmentList(ilAssQuestionSkillAssignmentList $assignmentList): void { - $this->skillQuestionAssignmentList = $assignmentList; - } - - public function setManipulationsEnabled(bool $manipulationsEnabled): void - { - $this->manipulationsEnabled = $manipulationsEnabled; + $this->skill_question_assignment_list = $assignmentList; } - /** - * @param array $manipulations_allowed_list - */ - public function setManipulationAllowedList(array $manipulations_allowed_list): void + public function areManipulationsEnabled(): bool { - $this->manipulations_allowed_list = $manipulations_allowed_list; + return $this->manipulations_enabled; } - private function areManipulationsPossible(): bool - { - return $this->manipulationsEnabled && array_filter($this->manipulations_allowed_list) !== []; - } - - private function isManipulationAllowedForQuestion(int $q_id): bool + public function setManipulationsEnabled(bool $manipulationsEnabled): void { - return $this->manipulationsEnabled && $this->manipulations_allowed_list[$q_id]; + $this->manipulations_enabled = $manipulationsEnabled; } public function __construct( @@ -95,20 +77,11 @@ public function __construct( public function init(): void { $this->initColumns(); - - if ($this->areManipulationsPossible()) { - $this->setFormAction($this->ctrl->getFormAction($this->parent_obj)); - - $this->addCommandButton( - ilAssQuestionSkillAssignmentsGUI::CMD_SAVE_SKILL_POINTS, - $this->lng->txt('tst_save_comp_points') - ); - } } public function loadSkillPointsFromRequest(bool $loadSkillPointsFromRequest): void { - $this->loadSkillPointsFromRequest = $loadSkillPointsFromRequest; + $this->load_skill_points_from_request = $loadSkillPointsFromRequest; } private function initColumns(): void @@ -122,7 +95,7 @@ private function initColumns(): void public function fillRow(array $a_set): void { - $assignments = $this->skillQuestionAssignmentList->getAssignmentsByQuestionId($a_set['question_id']); + $assignments = $this->skill_question_assignment_list->getAssignmentsByQuestionId($a_set['question_id']); $this->ctrl->setParameter($this->parent_obj, 'q_id', $a_set['question_id']); @@ -146,14 +119,9 @@ public function fillRow(array $a_set): void $this->tpl->setVariable('COMPETENCE', $assignment->getSkillTitle()); $this->tpl->setVariable('COMPETENCE_PATH', $assignment->getSkillPath()); $this->tpl->setVariable('EVAL_MODE', $this->getEvalModeLabel($assignment)); + $this->tpl->setVariable('SKILL_POINTS', $assignment->getMaxSkillPoints()); - if ($this->isSkillPointInputRequired($assignment)) { - $this->tpl->setVariable('SKILL_POINTS', $this->buildSkillPointsInput($assignment)); - } else { - $this->tpl->setVariable('SKILL_POINTS', $assignment->getMaxSkillPoints()); - } - - if (($key + 1) < $num_assigns || $this->isManipulationAllowedForQuestion($assignment->getQuestionId())) { + if (($key + 1) < $num_assigns || $this->areManipulationsEnabled()) { $this->tpl->parseCurrentBlock(); $this->tpl->setCurrentBlock('tbl_content'); @@ -161,7 +129,7 @@ public function fillRow(array $a_set): void } } - if ($this->isManipulationAllowedForQuestion($a_set['question_id'])) { + if ($this->areManipulationsEnabled()) { $this->tpl->setCurrentBlock('actions_col'); $this->tpl->setVariable('ACTION', $this->getManageCompetenceAssignsActionLink()); $this->tpl->parseCurrentBlock(); @@ -187,7 +155,7 @@ private function getRowspan(array $assignments): int return 1; } - return $this->areManipulationsPossible() ? $cnt + 1 : $cnt; + return $this->areManipulationsEnabled() ? $cnt + 1 : $cnt; } private function getManageCompetenceAssignsActionLink(): string @@ -214,7 +182,7 @@ private function getCompetenceAssignPropertiesFormLink(ilAssQuestionSkillAssignm $this->ctrl->setParameter($this->parent_obj, 'skill_tref_id', null); return $this->buildActionLink($href, $this->lng->txt( - $this->isManipulationAllowedForQuestion($assignment->getQuestionId()) + $this->areManipulationsEnabled() ? 'tst_edit_competence_assign' : 'tst_view_competence_assign' )); @@ -225,38 +193,6 @@ private function buildActionLink(string $href, string $label): string return "{$label}"; } - private function buildActionColumnHTML($assignments): string - { - $actions = []; - - /* PHP8: This appears to be an incomplete feature: Removal of skill assignment is nowhere found other than - here, ilAssQuestionSkillAssignmentsGUI::CMD_REMOVE_SKILL_QUEST_ASSIGN is undefined. Defusing for now. - - foreach ($assignments as $assignment) { - $this->ctrl->setParameter($this->parent_obj, 'skill_base_id', $assignment->getSkillBaseId()); - $this->ctrl->setParameter($this->parent_obj, 'skill_tref_id', $assignment->getSkillTrefId()); - - $href = $this->ctrl->getLinkTarget( - $this->parent_obj, - ilAssQuestionSkillAssignmentsGUI::CMD_REMOVE_SKILL_QUEST_ASSIGN - ); - - $label = $this->lng->txt('tst_remove_competence'); - - $actions[] = $this->buildActionLink($href, $label); - } - */ - - $href = $this->ctrl->getLinkTarget( - $this->parent_obj, - ilAssQuestionSkillAssignmentsGUI::CMD_SHOW_SKILL_SELECT - ); - - $actions[] = $this->buildActionLink($href, $this->lng->txt('tst_assign_competence')); - - return implode('
', $actions); - } - private function getEvalModeLabel(ilAssQuestionSkillAssignment $assignment): string { return $this->lng->txt( @@ -265,35 +201,4 @@ private function getEvalModeLabel(ilAssQuestionSkillAssignment $assignment): str : 'qpl_skill_point_eval_mode_quest_result' ); } - - private function buildSkillPointsInput(ilAssQuestionSkillAssignment $assignment): string - { - $assignmentKey = implode(':', [ - $assignment->getSkillBaseId(), - $assignment->getSkillTrefId(), - $assignment->getQuestionId() - ]); - - if ($this->loadSkillPointsFromRequest) { - $skill_points = $this->request_data_collector->strArray('skill_points'); - $points = ilUtil::stripSlashes($skill_points[$assignmentKey] ?? ''); - } else { - $points = $assignment->getSkillPoints(); - } - - return ""; - } - - private function isSkillPointInputRequired(ilAssQuestionSkillAssignment $assignment): bool - { - if (!$this->isManipulationAllowedForQuestion($assignment->getQuestionId())) { - return false; - } - - if ($assignment->hasEvalModeBySolution()) { - return false; - } - - return true; - } } diff --git a/components/ILIAS/TestQuestionPool/classes/tables/class.ilKprimChoiceAnswerFreqStatTableGUI.php b/components/ILIAS/TestQuestionPool/classes/tables/class.ilKprimChoiceAnswerFreqStatTableGUI.php index 39824133b0ca..bedffd6a4d69 100755 --- a/components/ILIAS/TestQuestionPool/classes/tables/class.ilKprimChoiceAnswerFreqStatTableGUI.php +++ b/components/ILIAS/TestQuestionPool/classes/tables/class.ilKprimChoiceAnswerFreqStatTableGUI.php @@ -26,7 +26,7 @@ */ class ilKprimChoiceAnswerFreqStatTableGUI extends ilAnswerFrequencyStatisticTableGUI { - protected function getTrueOptionLabel() + private function getTrueOptionLabel(): string { return $this->question->getTrueOptionLabelTranslation( $this->language, @@ -34,7 +34,7 @@ protected function getTrueOptionLabel() ); } - protected function getFalseOptionLabel() + private function getFalseOptionLabel(): string { return $this->question->getFalseOptionLabelTranslation( $this->language, @@ -42,13 +42,11 @@ protected function getFalseOptionLabel() ); } - public function initColumns(): void { - $lng = $this->language; - $this->addColumn($lng->txt('tst_corr_answ_stat_tbl_header_answer'), ''); - $this->addColumn($lng->txt('tst_corr_answ_stat_tbl_header_frequency') . ': ' . $this->getTrueOptionLabel(), ''); - $this->addColumn($lng->txt('tst_corr_answ_stat_tbl_header_frequency') . ': ' . $this->getFalseOptionLabel(), ''); + $this->addColumn($this->language->txt('tst_corr_answ_stat_tbl_header_answer'), ''); + $this->addColumn($this->language->txt('tst_corr_answ_stat_tbl_header_frequency') . ': ' . $this->getTrueOptionLabel(), ''); + $this->addColumn($this->language->txt('tst_corr_answ_stat_tbl_header_frequency') . ': ' . $this->getFalseOptionLabel(), ''); foreach ($this->getData() as $row) { if (isset($row['addable'])) { diff --git a/components/ILIAS/TestQuestionPool/classes/tables/class.ilQuestionBrowserTableGUI.php b/components/ILIAS/TestQuestionPool/classes/tables/class.ilQuestionBrowserTableGUI.php index e5dae988de0b..2a7deefc1c74 100755 --- a/components/ILIAS/TestQuestionPool/classes/tables/class.ilQuestionBrowserTableGUI.php +++ b/components/ILIAS/TestQuestionPool/classes/tables/class.ilQuestionBrowserTableGUI.php @@ -470,11 +470,6 @@ public function fillRow(array $a_set): void $feedbackHref = $this->ctrl->getLinkTargetByClass('ilAssQuestionFeedbackEditingGUI', ilAssQuestionFeedbackEditingGUI::CMD_SHOW); $this->ctrl->setParameterByClass('ilAssQuestionFeedbackEditingGUI', 'q_id', null); $actions[] = $this->ui_factory->link()->standard($this->lng->txt('tst_feedback'), $feedbackHref); - - $this->ctrl->setParameterByClass('ilAssQuestionHintsGUI', 'q_id', $a_set['question_id']); - $hintsHref = $this->ctrl->getLinkTargetByClass('ilAssQuestionHintsGUI', ilAssQuestionHintsGUI::CMD_SHOW_LIST); - $this->ctrl->setParameterByClass('ilAssQuestionHintsGUI', 'q_id', null); - $actions[] = $this->ui_factory->link()->standard($this->lng->txt('tst_question_hints_tab'), $hintsHref); } if ($this->isQuestionCommentingEnabled()) { diff --git a/components/ILIAS/TestQuestionPool/module.xml b/components/ILIAS/TestQuestionPool/module.xml index b555d9f246f6..f0de2563b4bb 100755 --- a/components/ILIAS/TestQuestionPool/module.xml +++ b/components/ILIAS/TestQuestionPool/module.xml @@ -32,7 +32,6 @@ - diff --git a/components/ILIAS/TestQuestionPool/resources/js/dist/longMenuQuestionPlayer.js b/components/ILIAS/TestQuestionPool/resources/js/dist/longMenuQuestionPlayer.js index c8e6da2f2a4d..712131d637a3 100644 --- a/components/ILIAS/TestQuestionPool/resources/js/dist/longMenuQuestionPlayer.js +++ b/components/ILIAS/TestQuestionPool/resources/js/dist/longMenuQuestionPlayer.js @@ -30,8 +30,7 @@ return a.length > b.length ? a : b; }); input.setAttribute('size', longest.length); - input.addEventListener('keyup', onChangeHandler); - input.addEventListener('focus', onChangeHandler); + input.addEventListener('keydown', keyHandler); } }); @@ -45,6 +44,41 @@ ); }; + const keyHandler = (e) => { + if (e.key === 'Enter' && e.target.nodeName === 'LI') { + e.stopImmediatePropagation(); + e.preventDefault(); + onSelectHandler(e); + return; + } + + if (e.key === 'ArrowDown') { + e.stopImmediatePropagation(); + e.preventDefault(); + if (e.target.nextElementSibling?.nodeName === 'UL') { + e.target.nextElementSibling.firstElementChild.focus(); + } + + if (e.target.nodeName === 'LI' && e.target.nextElementSibling !== null) { + e.target.nextElementSibling.focus(); + } + return; + } + + if (e.key === 'ArrowUp' && e.target.nodeName === 'LI') { + e.stopImmediatePropagation(); + e.preventDefault(); + if (e.target.previousElementSibling === null) { + e.target.parentElement.previousElementSibling.focus(); + } else { + e.target.previousElementSibling.focus(); + } + return; + } + + onChangeHandler(e); + } + const onChangeHandler = (e) => { const name = e.target.name; const index = name.substring(name.indexOf('[') + 1, name.indexOf(']')); diff --git a/components/ILIAS/TestQuestionPool/resources/js/dist/matching.js b/components/ILIAS/TestQuestionPool/resources/js/dist/matching.js index d71da7b3c11e..cb03bf4bc3ec 100644 --- a/components/ILIAS/TestQuestionPool/resources/js/dist/matching.js +++ b/components/ILIAS/TestQuestionPool/resources/js/dist/matching.js @@ -12,4 +12,4 @@ * https://www.ilias.de * https://github.com/ILIAS-eLearning */ -!function(e){"use strict";const t="1:1",n="n:n",o="source_area",s="ilMatchingQuestionTerm",l="c-test__definition",c="c-test__term",i="c-test__dropzone";let r,a,d;function u(e,t,l){r===n&&function(e,t,n){n.parentNode.classList.contains(s)&&n.remove(),t.parentNode.classList.contains(o)&&e.remove()}(e,t,l)}function f(){a.querySelectorAll(`.${s}`).forEach((e=>{var n;r!==t?null!==(n=e).firstElementChild&&n.lastElementChild.classList.contains(i)||n.append(d.cloneNode()):function(e){const t=e.firstElementChild;null!==t?t.classList.contains(c)&&null!==t.nextElementSibling&&t.nextElementSibling.remove():e.prepend(d.cloneNode())}(e)}))}function h(e,t,n,c){!function(e,t,n){const c=e.dataset;if(!n.classList.contains(o)){const e=n.closest(`.${l}`).querySelector("input"),t=JSON.parse(e.value),o=t.indexOf(c.id);o>-1&&t.splice(o,1),e.value=JSON.stringify(t)}if(t.parentNode.classList.contains(s)){const e=t.closest(`.${l}`),n=JSON.parse(e.querySelector("input").value);n.push(c.id),e.querySelector("input").value=JSON.stringify(n)}}(e,t,c),u(e,t,n)}function v(e){f();const t=a.querySelector(`.${o}`);null!==t.firstElementChild&&t.firstElementChild.classList.contains(i)||t.prepend(d.cloneNode()),e.parentNode.querySelectorAll(`.${i}`).forEach((e=>{e.remove()})),r===n&&a.querySelectorAll(`.${s}`).forEach((t=>{null!==t.lastElementChild&&t.lastElementChild.classList.contains(i)||t.append(d.cloneNode()),null!==t.querySelector(`[data-id='${e.dataset.id}']`)&&t.querySelector(`.${i}`)?.remove()}))}function p(e,n,o){a=e,r=o,function(){const e=a.querySelectorAll(`.${c}`);let t=0;e.forEach((e=>{e.offsetHeight{t.style.height=`${e.item(0).offsetHeight}px`})),d=a.querySelector(`.${i}`)}(),n(r===t?"move":"copy",a,c,i,h,v)}const m="c-test__dropzone--active",g="c-test__dropzone--hover";let E,y,L,S,$,q,N,A,x;function _(e){setTimeout((()=>{T(e.target),e.dataTransfer.dropEffect=E,e.dataTransfer.effectAllowed=E,e.dataTransfer.setDragImage(N,0,0)}),0)}function D(e){e.preventDefault(),e.stopPropagation(),T(e.target.closest(`.${L}`));const t=N.offsetWidth,n=N.offsetHeight;A=N.cloneNode(!0),N.parentNode.insertBefore(A,N),N.style.position="fixed",N.style.left=e.touches[0].clientX-t/2+"px",N.style.top=e.touches[0].clientY-n/2+"px",N.style.width=`${t}px`,N.style.height=`${n}px`,N.addEventListener("touchmove",Y),N.addEventListener("touchend",J)}function T(e){N=e,N.style.opacity=.5,q(N),y.querySelectorAll(`.${S}`).forEach((e=>{P(e),e.classList.add(m)})),N.querySelectorAll(`.${S}`).forEach((e=>{e.classList.remove(m)}))}function Y(e){e.preventDefault(),N.style.left=e.touches[0].clientX-N.offsetWidth/2+"px",N.style.top=e.touches[0].clientY-N.offsetHeight/2+"px";const{documentElement:t}=y.ownerDocument;e.touches[0].clientY>.8*t.clientHeight&&t.scroll({left:0,top:.8*e.touches[0].pageY,behavior:"smooth"}),e.touches[0].clientY<.2*t.clientHeight&&t.scroll({left:0,top:.8*e.touches[0].pageY,behavior:"smooth"});const n=y.ownerDocument.elementsFromPoint(e.changedTouches[0].clientX,e.changedTouches[0].clientY).filter((e=>e.classList.contains(S)));0===n.length&&void 0!==x&&(x.classList.remove(g),x=void 0),1===n.length&&x!==n[0]&&(void 0!==x&&x.classList.remove(g),[x]=n,x.classList.add(g))}function C(e){e.preventDefault()}function H(e){e.target.classList.add(g)}function b(e){e.target.classList.remove(g)}function w(){N.removeAttribute("style"),y.querySelectorAll(`.${S}`).forEach((e=>{e.classList.remove(m),e.classList.remove(g)}))}function O(e){e.preventDefault(),X(e.target)}function J(e){e.preventDefault();const t=y.ownerDocument.elementsFromPoint(e.changedTouches[0].clientX,e.changedTouches[0].clientY).filter((e=>e.classList.contains(S)));w(),A.remove(),1===t.length&&X(t[0])}function X(e){const t=N.parentNode;let n=N;"move"!==E&&(n=N.cloneNode(!0),n.style.opacity=null,z(n)),e.parentNode.insertBefore(n,e),$(n,e,N,t)}function z(e){e.addEventListener("dragstart",_),e.addEventListener("dragend",w),e.addEventListener("touchstart",D)}function P(e){e.removeEventListener("dragover",C),e.removeEventListener("dragenter",H),e.removeEventListener("dragleave",b),e.removeEventListener("drop",O),e.addEventListener("dragover",C),e.addEventListener("dragenter",H),e.addEventListener("dragleave",b),e.addEventListener("drop",O)}function B(e,t,n,o,s,l){E=e,y=t,L=n,S=o,$=s,q=l,y.querySelectorAll(`.${L}`).forEach(z),y.querySelectorAll(`.${S}`).forEach(P)}e.test=e.test||{},e.test.matching=e.test.matching||{},e.test.matching.init=(e,t)=>p(e,B,t)}(il); +!function(e){"use strict";const t="1:1",n="n:n",o="source_area",s="ilMatchingQuestionTerm",c="c-test__definition",l="c-test__term",i="c-test__dropzone";function r(e,t,c,l){l===n&&function(e,t,n){n.parentNode.classList.contains(s)&&n.remove(),t.parentNode.classList.contains(o)&&e.remove()}(e,t,c)}function a(e,n,o){e.querySelectorAll(`.${s}`).forEach((e=>{n!==t?function(e,t){null!==e.firstElementChild&&e.lastElementChild.classList.contains(i)||e.append(t.cloneNode())}(e,o):function(e,t){const n=e.firstElementChild;null!==n?n.classList.contains(l)&&null!==n.nextElementSibling&&n.nextElementSibling.remove():e.prepend(t.cloneNode())}(e,o)}))}function d(e,t,n,l,i){!function(e,t,n){const l=e.dataset;if(!n.classList.contains(o)){const e=n.closest(`.${c}`).querySelector("input"),t=JSON.parse(e.value),o=t.indexOf(l.id);o>-1&&t.splice(o,1),e.value=JSON.stringify(t)}if(t.parentNode.classList.contains(s)){const e=t.closest(`.${c}`),n=JSON.parse(e.querySelector("input").value);n.push(l.id),e.querySelector("input").value=JSON.stringify(n)}}(e,t,l),r(e,t,n,i)}function u(e,c,r){const u=function(e){const t=e.querySelectorAll(`.${l}`);let n=0;return t.forEach((e=>{e.offsetHeight{e.style.height=`${t.item(0).offsetHeight}px`})),e.querySelector(`.${i}`)}(e);c(r===t?"move":"copy",e,l,i,((e,t,n,o)=>{d(e,t,n,o,r)}),(t=>{!function(e,t,c,l){a(t,c,l);const r=t.querySelector(`.${o}`);null!==r.firstElementChild&&r.firstElementChild.classList.contains(i)||r.prepend(l.cloneNode()),e.parentNode.querySelectorAll(`.${i}`).forEach((e=>{e.remove()})),c===n&&t.querySelectorAll(`.${s}`).forEach((t=>{null!==t.lastElementChild&&t.lastElementChild.classList.contains(i)||t.append(l.cloneNode()),null!==t.querySelector(`[data-id='${e.dataset.id}']`)&&t.querySelector(`.${i}`)?.remove()}))}(t,e,r,u)}))}const f="c-test__dropzone--active",h="c-test__dropzone--hover";function v(e,t,n,o,s,c){let l,i,r;function a(t){setTimeout((()=>{u(t.target),t.dataTransfer.dropEffect=e,t.dataTransfer.effectAllowed=e,t.dataTransfer.setDragImage(r,0,0)}),0)}function d(e){e.preventDefault(),e.stopPropagation(),u(e.target.closest(`.${n}`));const t=r.offsetWidth,o=r.offsetHeight;l=r.cloneNode(!0),r.parentNode.insertBefore(l,r),r.style.position="fixed",r.style.left=e.touches[0].clientX-t/2+"px",r.style.top=e.touches[0].clientY-o/2+"px",r.style.width=`${t}px`,r.style.height=`${o}px`,r.addEventListener("touchmove",v),r.addEventListener("touchend",L)}function u(e){r=e,r.style.opacity=.5,c(r),t.querySelectorAll(`.${o}`).forEach((e=>{q(e),e.classList.add(f)})),r.querySelectorAll(`.${o}`).forEach((e=>{e.classList.remove(f)}))}function v(e){e.preventDefault(),r.style.left=e.touches[0].clientX-r.offsetWidth/2+"px",r.style.top=e.touches[0].clientY-r.offsetHeight/2+"px";const{documentElement:n}=t.ownerDocument;e.touches[0].clientY>.8*n.clientHeight&&n.scroll({left:0,top:.8*e.touches[0].pageY,behavior:"smooth"}),e.touches[0].clientY<.2*n.clientHeight&&n.scroll({left:0,top:.8*e.touches[0].pageY,behavior:"smooth"});const s=t.ownerDocument.elementsFromPoint(e.changedTouches[0].clientX,e.changedTouches[0].clientY).filter((e=>e.classList.contains(o)));0===s.length&&void 0!==i&&(i.classList.remove(h),i=void 0),1===s.length&&i!==s[0]&&(void 0!==i&&i.classList.remove(h),[i]=s,i.classList.add(h))}function p(e){e.preventDefault()}function m(e){e.target.classList.add(h)}function g(e){e.target.classList.remove(h)}function E(){r.removeAttribute("style"),t.querySelectorAll(`.${o}`).forEach((e=>{e.classList.remove(f),e.classList.remove(h)}))}function y(e){e.preventDefault(),S(e.target)}function L(e){e.preventDefault();const n=t.ownerDocument.elementsFromPoint(e.changedTouches[0].clientX,e.changedTouches[0].clientY).filter((e=>e.classList.contains(o)));E(),l.remove(),1===n.length&&S(n[0])}function S(t){const n=r.parentNode;let o=r;"move"!==e&&(o=r.cloneNode(!0),o.style.opacity=null,$(o)),t.parentNode.insertBefore(o,t),s(o,t,r,n)}function $(e){e.addEventListener("dragstart",a),e.addEventListener("dragend",E),e.addEventListener("touchstart",d)}function q(e){e.removeEventListener("dragover",p),e.removeEventListener("dragenter",m),e.removeEventListener("dragleave",g),e.removeEventListener("drop",y),e.addEventListener("dragover",p),e.addEventListener("dragenter",m),e.addEventListener("dragleave",g),e.addEventListener("drop",y)}t.querySelectorAll(`.${n}`).forEach($),t.querySelectorAll(`.${o}`).forEach(q)}e.test=e.test||{},e.test.matching=e.test.matching||{},e.test.matching.init=(e,t)=>u(e,v,t)}(il); diff --git a/components/ILIAS/TestQuestionPool/resources/js/dist/orderinghorizontal.js b/components/ILIAS/TestQuestionPool/resources/js/dist/orderinghorizontal.js index 802a4be93433..3b53217898fb 100644 --- a/components/ILIAS/TestQuestionPool/resources/js/dist/orderinghorizontal.js +++ b/components/ILIAS/TestQuestionPool/resources/js/dist/orderinghorizontal.js @@ -12,4 +12,4 @@ * https://www.ilias.de * https://github.com/ILIAS-eLearning */ -!function(e){"use strict";const t="{::}",n="answers",o="c-test__dropzone";let s;function r(){const e=[];s.querySelectorAll(`.${n} > div > span`).forEach((t=>{e.push(t.textContent)})),s.nextElementSibling.value=e.join(t)}function i(e){!function(){const e=s.querySelector(`.${o}`);s.querySelectorAll(`.${n}`).forEach((t=>{t.previousElementSibling?.classList.contains(o)||t.parentNode.insertBefore(e.cloneNode(),t),t.nextElementSibling?.classList.contains(o)||t.parentNode.insertBefore(e.cloneNode(),t.nextElementSibling)})),s.querySelectorAll(`.${o} + .${o}`).forEach((e=>{e.remove()}))}(),e.previousElementSibling?.classList.contains(o)&&e.previousElementSibling.remove(),e.nextElementSibling?.classList.contains(o)&&e.nextElementSibling.remove()}function l(e,t){s=e,function(){const e=s.querySelectorAll(`.${n}`);let t=0;e.forEach((e=>{t+=e.offsetWidth})),s.querySelectorAll(`.${o}`).forEach((n=>{n.style.width=t/e.length+"px",n.style.height=`${e.item(0).offsetHeight}px`}))}(),t("move",s,n,o,r,i)}const c="c-test__dropzone--active",a="c-test__dropzone--hover";let d,f,u,h,v,g,m,p,E;function L(e){setTimeout((()=>{S(e.target),e.dataTransfer.dropEffect=d,e.dataTransfer.effectAllowed=d,e.dataTransfer.setDragImage(m,0,0)}),0)}function y(e){e.preventDefault(),e.stopPropagation(),S(e.target.closest(`.${u}`));const t=m.offsetWidth,n=m.offsetHeight;p=m.cloneNode(!0),m.parentNode.insertBefore(p,m),m.style.position="fixed",m.style.left=e.touches[0].clientX-t/2+"px",m.style.top=e.touches[0].clientY-n/2+"px",m.style.width=`${t}px`,m.style.height=`${n}px`,m.addEventListener("touchmove",$),m.addEventListener("touchend",N)}function S(e){m=e,m.style.opacity=.5,g(m),f.querySelectorAll(`.${h}`).forEach((e=>{w(e),e.classList.add(c)})),m.querySelectorAll(`.${h}`).forEach((e=>{e.classList.remove(c)}))}function $(e){e.preventDefault(),m.style.left=e.touches[0].clientX-m.offsetWidth/2+"px",m.style.top=e.touches[0].clientY-m.offsetHeight/2+"px";const{documentElement:t}=f.ownerDocument;e.touches[0].clientY>.8*t.clientHeight&&t.scroll({left:0,top:.8*e.touches[0].pageY,behavior:"smooth"}),e.touches[0].clientY<.2*t.clientHeight&&t.scroll({left:0,top:.8*e.touches[0].pageY,behavior:"smooth"});const n=f.ownerDocument.elementsFromPoint(e.changedTouches[0].clientX,e.changedTouches[0].clientY).filter((e=>e.classList.contains(h)));0===n.length&&void 0!==E&&(E.classList.remove(a),E=void 0),1===n.length&&E!==n[0]&&(void 0!==E&&E.classList.remove(a),[E]=n,E.classList.add(a))}function x(e){e.preventDefault()}function A(e){e.target.classList.add(a)}function b(e){e.target.classList.remove(a)}function q(){m.removeAttribute("style"),f.querySelectorAll(`.${h}`).forEach((e=>{e.classList.remove(c),e.classList.remove(a)}))}function D(e){e.preventDefault(),T(e.target)}function N(e){e.preventDefault();const t=f.ownerDocument.elementsFromPoint(e.changedTouches[0].clientX,e.changedTouches[0].clientY).filter((e=>e.classList.contains(h)));q(),p.remove(),1===t.length&&T(t[0])}function T(e){const t=m.parentNode;let n=m;"move"!==d&&(n=m.cloneNode(!0),n.style.opacity=null,Y(n)),e.parentNode.insertBefore(n,e),v(n,e,m,t)}function Y(e){e.addEventListener("dragstart",L),e.addEventListener("dragend",q),e.addEventListener("touchstart",y)}function w(e){e.removeEventListener("dragover",x),e.removeEventListener("dragenter",A),e.removeEventListener("dragleave",b),e.removeEventListener("drop",D),e.addEventListener("dragover",x),e.addEventListener("dragenter",A),e.addEventListener("dragleave",b),e.addEventListener("drop",D)}function z(e,t,n,o,s,r){d=e,f=t,u=n,h=o,v=s,g=r,f.querySelectorAll(`.${u}`).forEach(Y),f.querySelectorAll(`.${h}`).forEach(w)}e.test=e.test||{},e.test.orderinghorizontal=e.test.orderinghorizontal||{},e.test.orderinghorizontal.init=e=>l(e,z)}(il); +!function(e){"use strict";const t="{::}",n="answers",o="c-test__dropzone";function s(e,t){!function(e){const t=e.querySelector(`.${o}`);e.querySelectorAll(`.${n}`).forEach((e=>{e.previousElementSibling?.classList.contains(o)||e.parentNode.insertBefore(t.cloneNode(),e),e.nextElementSibling?.classList.contains(o)||e.parentNode.insertBefore(t.cloneNode(),e.nextElementSibling)})),e.querySelectorAll(`.${o} + .${o}`).forEach((e=>{e.remove()}))}(t),e.previousElementSibling?.classList.contains(o)&&e.previousElementSibling.remove(),e.nextElementSibling?.classList.contains(o)&&e.nextElementSibling.remove()}function r(e,r){!function(e){const t=e.querySelectorAll(`.${n}`);let s=0;t.forEach((e=>{s+=e.offsetWidth})),e.querySelectorAll(`.${o}`).forEach((e=>{e.style.width=s/t.length+"px",e.style.height=`${t.item(0).offsetHeight}px`}))}(e),r("move",e,n,o,(()=>{!function(e){const o=[];e.querySelectorAll(`.${n} > div > span`).forEach((e=>{o.push(e.textContent)})),e.nextElementSibling.value=o.join(t)}(e)}),(t=>{s(t,e)}))}const i="c-test__dropzone--active",l="c-test__dropzone--hover";function c(e,t,n,o,s,r){let c,a,d;function f(t){setTimeout((()=>{h(t.target),t.dataTransfer.dropEffect=e,t.dataTransfer.effectAllowed=e,t.dataTransfer.setDragImage(d,0,0)}),0)}function u(e){e.preventDefault(),e.stopPropagation(),h(e.target.closest(`.${n}`));const t=d.offsetWidth,o=d.offsetHeight;c=d.cloneNode(!0),d.parentNode.insertBefore(c,d),d.style.position="fixed",d.style.left=e.touches[0].clientX-t/2+"px",d.style.top=e.touches[0].clientY-o/2+"px",d.style.width=`${t}px`,d.style.height=`${o}px`,d.addEventListener("touchmove",v),d.addEventListener("touchend",y)}function h(e){d=e,d.style.opacity=.5,r(d),t.querySelectorAll(`.${o}`).forEach((e=>{x(e),e.classList.add(i)})),d.querySelectorAll(`.${o}`).forEach((e=>{e.classList.remove(i)}))}function v(e){e.preventDefault(),d.style.left=e.touches[0].clientX-d.offsetWidth/2+"px",d.style.top=e.touches[0].clientY-d.offsetHeight/2+"px";const{documentElement:n}=t.ownerDocument;e.touches[0].clientY>.8*n.clientHeight&&n.scroll({left:0,top:.8*e.touches[0].pageY,behavior:"smooth"}),e.touches[0].clientY<.2*n.clientHeight&&n.scroll({left:0,top:.8*e.touches[0].pageY,behavior:"smooth"});const s=t.ownerDocument.elementsFromPoint(e.changedTouches[0].clientX,e.changedTouches[0].clientY).filter((e=>e.classList.contains(o)));0===s.length&&void 0!==a&&(a.classList.remove(l),a=void 0),1===s.length&&a!==s[0]&&(void 0!==a&&a.classList.remove(l),[a]=s,a.classList.add(l))}function g(e){e.preventDefault()}function m(e){e.target.classList.add(l)}function p(e){e.target.classList.remove(l)}function E(){d.removeAttribute("style"),t.querySelectorAll(`.${o}`).forEach((e=>{e.classList.remove(i),e.classList.remove(l)}))}function L(e){e.preventDefault(),S(e.target)}function y(e){e.preventDefault();const n=t.ownerDocument.elementsFromPoint(e.changedTouches[0].clientX,e.changedTouches[0].clientY).filter((e=>e.classList.contains(o)));E(),c.remove(),1===n.length&&S(n[0])}function S(t){const n=d.parentNode;let o=d;"move"!==e&&(o=d.cloneNode(!0),o.style.opacity=null,$(o)),t.parentNode.insertBefore(o,t),s(o,t,d,n)}function $(e){e.addEventListener("dragstart",f),e.addEventListener("dragend",E),e.addEventListener("touchstart",u)}function x(e){e.removeEventListener("dragover",g),e.removeEventListener("dragenter",m),e.removeEventListener("dragleave",p),e.removeEventListener("drop",L),e.addEventListener("dragover",g),e.addEventListener("dragenter",m),e.addEventListener("dragleave",p),e.addEventListener("drop",L)}t.querySelectorAll(`.${n}`).forEach($),t.querySelectorAll(`.${o}`).forEach(x)}e.test=e.test||{},e.test.orderinghorizontal=e.test.orderinghorizontal||{},e.test.orderinghorizontal.init=e=>r(e,c)}(il); diff --git a/components/ILIAS/TestQuestionPool/resources/js/dist/orderingvertical.js b/components/ILIAS/TestQuestionPool/resources/js/dist/orderingvertical.js index 66ccc5fbadfd..087ab4d1cd58 100644 --- a/components/ILIAS/TestQuestionPool/resources/js/dist/orderingvertical.js +++ b/components/ILIAS/TestQuestionPool/resources/js/dist/orderingvertical.js @@ -12,4 +12,4 @@ * https://www.ilias.de * https://github.com/ILIAS-eLearning */ -!function(e){"use strict";const t='[name*="[position]"',n='[name*="[indentation]"',o="dd-item",r="c-test__dropzone";let l;function s(e,r){!function(e,t){let r=0,s=t.parentElement.parentElement;for(;s!==l;)s=s.parentElement.parentElement,r+=1;e.querySelector(n).value=r,e.querySelectorAll(`.${o}`).forEach((e=>{r+=1,e.querySelector(n).value=r}))}(e,r),function(){let e=0;l.querySelectorAll(`.${o}`).forEach((n=>{n.querySelector(t).value=e,e+=1}))}()}function i(e){!function(){const e=l.querySelector(`.${r}`);l.querySelectorAll(`.${o}`).forEach((t=>{t.previousElementSibling?.classList.contains(r)||t.parentNode.insertBefore(e.cloneNode(),t),t.nextElementSibling?.classList.contains(r)||t.parentNode.insertBefore(e.cloneNode(),t.nextElementSibling)})),l.querySelectorAll(`.${r} + .${r}`).forEach((e=>{e.remove()}))}(),e.previousElementSibling?.classList.contains(r)&&e.previousElementSibling.remove(),e.nextElementSibling?.classList.contains(r)&&e.nextElementSibling.remove()}function c(e,t){l=e,function(){const e=l.querySelectorAll(`.${o}`);let t=0;e.forEach((e=>{e.offsetHeight{t.style.height=`${e.item(0).offsetHeight}px`}))}(),t("move",l,o,r,s,i)}const a="c-test__dropzone--active",d="c-test__dropzone--hover";let f,u,v,h,m,g,p,E,y;function L(e){setTimeout((()=>{$(e.target),e.dataTransfer.dropEffect=f,e.dataTransfer.effectAllowed=f,e.dataTransfer.setDragImage(p,0,0)}),0)}function S(e){e.preventDefault(),e.stopPropagation(),$(e.target.closest(`.${v}`));const t=p.offsetWidth,n=p.offsetHeight;E=p.cloneNode(!0),p.parentNode.insertBefore(E,p),p.style.position="fixed",p.style.left=e.touches[0].clientX-t/2+"px",p.style.top=e.touches[0].clientY-n/2+"px",p.style.width=`${t}px`,p.style.height=`${n}px`,p.addEventListener("touchmove",q),p.addEventListener("touchend",T)}function $(e){p=e,p.style.opacity=.5,g(p),u.querySelectorAll(`.${h}`).forEach((e=>{_(e),e.classList.add(a)})),p.querySelectorAll(`.${h}`).forEach((e=>{e.classList.remove(a)}))}function q(e){e.preventDefault(),p.style.left=e.touches[0].clientX-p.offsetWidth/2+"px",p.style.top=e.touches[0].clientY-p.offsetHeight/2+"px";const{documentElement:t}=u.ownerDocument;e.touches[0].clientY>.8*t.clientHeight&&t.scroll({left:0,top:.8*e.touches[0].pageY,behavior:"smooth"}),e.touches[0].clientY<.2*t.clientHeight&&t.scroll({left:0,top:.8*e.touches[0].pageY,behavior:"smooth"});const n=u.ownerDocument.elementsFromPoint(e.changedTouches[0].clientX,e.changedTouches[0].clientY).filter((e=>e.classList.contains(h)));0===n.length&&void 0!==y&&(y.classList.remove(d),y=void 0),1===n.length&&y!==n[0]&&(void 0!==y&&y.classList.remove(d),[y]=n,y.classList.add(d))}function A(e){e.preventDefault()}function x(e){e.target.classList.add(d)}function b(e){e.target.classList.remove(d)}function D(){p.removeAttribute("style"),u.querySelectorAll(`.${h}`).forEach((e=>{e.classList.remove(a),e.classList.remove(d)}))}function N(e){e.preventDefault(),Y(e.target)}function T(e){e.preventDefault();const t=u.ownerDocument.elementsFromPoint(e.changedTouches[0].clientX,e.changedTouches[0].clientY).filter((e=>e.classList.contains(h)));D(),E.remove(),1===t.length&&Y(t[0])}function Y(e){const t=p.parentNode;let n=p;"move"!==f&&(n=p.cloneNode(!0),n.style.opacity=null,H(n)),e.parentNode.insertBefore(n,e),m(n,e,p,t)}function H(e){e.addEventListener("dragstart",L),e.addEventListener("dragend",D),e.addEventListener("touchstart",S)}function _(e){e.removeEventListener("dragover",A),e.removeEventListener("dragenter",x),e.removeEventListener("dragleave",b),e.removeEventListener("drop",N),e.addEventListener("dragover",A),e.addEventListener("dragenter",x),e.addEventListener("dragleave",b),e.addEventListener("drop",N)}function w(e,t,n,o,r,l){f=e,u=t,v=n,h=o,m=r,g=l,u.querySelectorAll(`.${v}`).forEach(H),u.querySelectorAll(`.${h}`).forEach(_)}e.test=e.test||{},e.test.orderingvertical=e.test.orderingvertical||{},e.test.orderingvertical.init=e=>c(e,w)}(il); +!function(e){"use strict";const t='[name*="[position]"',n='[name*="[indentation]"',o="dd-item",r="c-test__dropzone";function s(e,r,s){!function(e,t,r){let s=0,l=t.parentElement.parentElement;for(;l!==r;)l=l.parentElement.parentElement,s+=1;e.querySelector(n).value=s,e.querySelectorAll(`.${o}`).forEach((e=>{s+=1,e.querySelector(n).value=s}))}(e,r,s),function(e){let n=0;e.querySelectorAll(`.${o}`).forEach((e=>{e.querySelector(t).value=n,n+=1}))}(s)}function l(e,t){!function(e){const t=e.querySelector(`.${r}`);e.querySelectorAll(`.${o}`).forEach((e=>{e.previousElementSibling?.classList.contains(r)||e.parentNode.insertBefore(t.cloneNode(),e),e.nextElementSibling?.classList.contains(r)||e.parentNode.insertBefore(t.cloneNode(),e.nextElementSibling)})),e.querySelectorAll(`.${r} + .${r}`).forEach((e=>{e.remove()}))}(t),e.previousElementSibling?.classList.contains(r)&&e.previousElementSibling.remove(),e.nextElementSibling?.classList.contains(r)&&e.nextElementSibling.remove()}function i(e,t){!function(e){const t=e.querySelectorAll(`.${o}`);let n=0;t.forEach((e=>{e.offsetHeight{e.style.height=`${t.item(0).offsetHeight}px`}))}(e),t("move",e,o,r,((t,n)=>{s(t,n,e)}),(t=>{l(t,e)}))}const c="c-test__dropzone--active",a="c-test__dropzone--hover";function d(e,t,n,o,r,s){let l,i,d;function f(t){setTimeout((()=>{v(t.target),t.dataTransfer.dropEffect=e,t.dataTransfer.effectAllowed=e,t.dataTransfer.setDragImage(d,0,0)}),0)}function u(e){e.preventDefault(),e.stopPropagation(),v(e.target.closest(`.${n}`));const t=d.offsetWidth,o=d.offsetHeight;l=d.cloneNode(!0),d.parentNode.insertBefore(l,d),d.style.position="fixed",d.style.left=e.touches[0].clientX-t/2+"px",d.style.top=e.touches[0].clientY-o/2+"px",d.style.width=`${t}px`,d.style.height=`${o}px`,d.addEventListener("touchmove",h),d.addEventListener("touchend",L)}function v(e){d=e,d.style.opacity=.5,s(d),t.querySelectorAll(`.${o}`).forEach((e=>{q(e),e.classList.add(c)})),d.querySelectorAll(`.${o}`).forEach((e=>{e.classList.remove(c)}))}function h(e){e.preventDefault(),d.style.left=e.touches[0].clientX-d.offsetWidth/2+"px",d.style.top=e.touches[0].clientY-d.offsetHeight/2+"px";const{documentElement:n}=t.ownerDocument;e.touches[0].clientY>.8*n.clientHeight&&n.scroll({left:0,top:.8*e.touches[0].pageY,behavior:"smooth"}),e.touches[0].clientY<.2*n.clientHeight&&n.scroll({left:0,top:.8*e.touches[0].pageY,behavior:"smooth"});const r=t.ownerDocument.elementsFromPoint(e.changedTouches[0].clientX,e.changedTouches[0].clientY).filter((e=>e.classList.contains(o)));0===r.length&&void 0!==i&&(i.classList.remove(a),i=void 0),1===r.length&&i!==r[0]&&(void 0!==i&&i.classList.remove(a),[i]=r,i.classList.add(a))}function m(e){e.preventDefault()}function g(e){e.target.classList.add(a)}function p(e){e.target.classList.remove(a)}function E(){d.removeAttribute("style"),t.querySelectorAll(`.${o}`).forEach((e=>{e.classList.remove(c),e.classList.remove(a)}))}function y(e){e.preventDefault(),S(e.target)}function L(e){e.preventDefault();const n=t.ownerDocument.elementsFromPoint(e.changedTouches[0].clientX,e.changedTouches[0].clientY).filter((e=>e.classList.contains(o)));E(),l.remove(),1===n.length&&S(n[0])}function S(t){const n=d.parentNode;let o=d;"move"!==e&&(o=d.cloneNode(!0),o.style.opacity=null,$(o)),t.parentNode.insertBefore(o,t),r(o,t,d,n)}function $(e){e.addEventListener("dragstart",f),e.addEventListener("dragend",E),e.addEventListener("touchstart",u)}function q(e){e.removeEventListener("dragover",m),e.removeEventListener("dragenter",g),e.removeEventListener("dragleave",p),e.removeEventListener("drop",y),e.addEventListener("dragover",m),e.addEventListener("dragenter",g),e.addEventListener("dragleave",p),e.addEventListener("drop",y)}t.querySelectorAll(`.${n}`).forEach($),t.querySelectorAll(`.${o}`).forEach(q)}e.test=e.test||{},e.test.orderingvertical=e.test.orderingvertical||{},e.test.orderingvertical.init=e=>i(e,d)}(il); diff --git a/components/ILIAS/TestQuestionPool/resources/js/dist/question_handling.js b/components/ILIAS/TestQuestionPool/resources/js/dist/question_handling.js index 38ab96282a27..6f25750db675 100755 --- a/components/ILIAS/TestQuestionPool/resources/js/dist/question_handling.js +++ b/components/ILIAS/TestQuestionPool/resources/js/dist/question_handling.js @@ -643,7 +643,7 @@ ilias.questions.initClozeTest = function(a_id) { input = jQuery.create('select', {'id': a_id+"_"+closecounter, 'class': 'ilc_qinput_ClozeGapSelect'}); let items = questions[a_id].gaps[closecounter].item; - if (questions[a_id].shuffle === true) { + if (questions[a_id].gaps[closecounter].shuffle === true) { items = shuffleItems(items); } for (var i=0;i { - startMoving(event.target); - event.dataTransfer.dropEffect = dragType; - event.dataTransfer.effectAllowed = dragType; - event.dataTransfer.setDragImage(draggedElement, 0, 0); - }, 0); -} + /** + * @param {Event} event + */ + function dragstartHandler(event) { + setTimeout(() => { + startMoving(event.target); + event.dataTransfer.dropEffect = dragType; + event.dataTransfer.effectAllowed = dragType; + event.dataTransfer.setDragImage(draggedElement, 0, 0); + }, 0); + } -/** - * @param {Event} event - */ -function touchstartHandler(event) { - event.preventDefault(); - event.stopPropagation(); - startMoving(event.target.closest(`.${draggableClass}`)); - const width = draggedElement.offsetWidth; - const height = draggedElement.offsetHeight; - clonedElementForTouch = draggedElement.cloneNode(true); - draggedElement.parentNode.insertBefore(clonedElementForTouch, draggedElement); - draggedElement.style.position = 'fixed'; - draggedElement.style.left = `${event.touches[0].clientX - width / 2}px`; - draggedElement.style.top = `${event.touches[0].clientY - height / 2}px`; - draggedElement.style.width = `${width}px`; - draggedElement.style.height = `${height}px`; - draggedElement.addEventListener('touchmove', touchmoveHandler); - draggedElement.addEventListener('touchend', touchendHandler); -} + /** + * @param {Event} event + */ + function touchstartHandler(event) { + event.preventDefault(); + event.stopPropagation(); + startMoving(event.target.closest(`.${draggableClass}`)); + const width = draggedElement.offsetWidth; + const height = draggedElement.offsetHeight; + clonedElementForTouch = draggedElement.cloneNode(true); + draggedElement.parentNode.insertBefore(clonedElementForTouch, draggedElement); + draggedElement.style.position = 'fixed'; + draggedElement.style.left = `${event.touches[0].clientX - width / 2}px`; + draggedElement.style.top = `${event.touches[0].clientY - height / 2}px`; + draggedElement.style.width = `${width}px`; + draggedElement.style.height = `${height}px`; + draggedElement.addEventListener('touchmove', touchmoveHandler); + draggedElement.addEventListener('touchend', touchendHandler); + } -/** - * @param {DOMElement} target - * @returns {void} - */ -function startMoving(target) { - draggedElement = target; - draggedElement.style.opacity = 0.5; + /** + * @param {DOMElement} target + * @returns {void} + */ + function startMoving(target) { + draggedElement = target; + draggedElement.style.opacity = 0.5; + + onStartPrepareHandler(draggedElement); + + parentElement.querySelectorAll(`.${placeholderClass}`).forEach( + (elem) => { + addPlaceholderEventListeners(elem); + elem.classList.add(activeClass); + }, + ); + + draggedElement.querySelectorAll(`.${placeholderClass}`).forEach( + (elem) => { elem.classList.remove(activeClass); }, + ); + } - onStartPrepareHandler(draggedElement); + /** + * @param {Event} event + */ + function touchmoveHandler(event) { + event.preventDefault(); + draggedElement.style.left = `${event.touches[0].clientX - draggedElement.offsetWidth / 2}px`; + draggedElement.style.top = `${event.touches[0].clientY - draggedElement.offsetHeight / 2}px`; + + const { documentElement } = parentElement.ownerDocument; + if (event.touches[0].clientY > documentElement.clientHeight * 0.8) { + documentElement.scroll({ + left: 0, + top: event.touches[0].pageY * 0.8, + behavior: 'smooth', + }); + } - parentElement.querySelectorAll(`.${placeholderClass}`).forEach( - (elem) => { - addPlaceholderEventListeners(elem); - elem.classList.add(activeClass); - }, - ); + if (event.touches[0].clientY < documentElement.clientHeight * 0.2) { + documentElement.scroll({ + left: 0, + top: event.touches[0].pageY * 0.8, + behavior: 'smooth', + }); + } - draggedElement.querySelectorAll(`.${placeholderClass}`).forEach( - (elem) => { elem.classList.remove(activeClass); }, - ); -} + const element = parentElement.ownerDocument.elementsFromPoint( + event.changedTouches[0].clientX, + event.changedTouches[0].clientY, + ).filter((elem) => elem.classList.contains(placeholderClass)); -/** - * @param {Event} event - */ -function touchmoveHandler(event) { - event.preventDefault(); - draggedElement.style.left = `${event.touches[0].clientX - draggedElement.offsetWidth / 2}px`; - draggedElement.style.top = `${event.touches[0].clientY - draggedElement.offsetHeight / 2}px`; + if ((element.length === 0 && typeof currentHoverElementForTouch !== 'undefined')) { + currentHoverElementForTouch.classList.remove(hoverClass); + currentHoverElementForTouch = undefined; + } - const { documentElement } = parentElement.ownerDocument; - if (event.touches[0].clientY > documentElement.clientHeight * 0.8) { - documentElement.scroll({ - left: 0, - top: event.touches[0].pageY * 0.8, - behavior: 'smooth', - }); + if (element.length === 1 && currentHoverElementForTouch !== element[0]) { + if (typeof currentHoverElementForTouch !== 'undefined') { + currentHoverElementForTouch.classList.remove(hoverClass); + } + [currentHoverElementForTouch] = element; + currentHoverElementForTouch.classList.add(hoverClass); + } } - if (event.touches[0].clientY < documentElement.clientHeight * 0.2) { - documentElement.scroll({ - left: 0, - top: event.touches[0].pageY * 0.8, - behavior: 'smooth', - }); + /** + * @param {Event} event + */ + function dragoverHandler(event) { + event.preventDefault(); } - const element = parentElement.ownerDocument.elementsFromPoint( - event.changedTouches[0].clientX, - event.changedTouches[0].clientY, - ).filter((elem) => elem.classList.contains(placeholderClass)); - - if ((element.length === 0 && typeof currentHoverElementForTouch !== 'undefined')) { - currentHoverElementForTouch.classList.remove(hoverClass); - currentHoverElementForTouch = undefined; + /** + * @param {Event} event + */ + function dragenterHandler(event) { + event.target.classList.add(hoverClass); } - if (element.length === 1 && currentHoverElementForTouch !== element[0]) { - if (typeof currentHoverElementForTouch !== 'undefined') { - currentHoverElementForTouch.classList.remove(hoverClass); - } - [currentHoverElementForTouch] = element; - currentHoverElementForTouch.classList.add(hoverClass); + /** + * @param {Event} event + */ + function dragleaveHandler(event) { + event.target.classList.remove(hoverClass); } -} -/** - * @param {Event} event - */ -function dragoverHandler(event) { - event.preventDefault(); -} - -/** - * @param {Event} event - */ -function dragenterHandler(event) { - event.target.classList.add(hoverClass); -} - -/** - * @param {Event} event - */ -function dragleaveHandler(event) { - event.target.classList.remove(hoverClass); -} - -function dragendHandler() { - draggedElement.removeAttribute('style'); - parentElement.querySelectorAll(`.${placeholderClass}`).forEach( - (elem) => { - elem.classList.remove(activeClass); - elem.classList.remove(hoverClass); - }, - ); -} + function dragendHandler() { + draggedElement.removeAttribute('style'); + parentElement.querySelectorAll(`.${placeholderClass}`).forEach( + (elem) => { + elem.classList.remove(activeClass); + elem.classList.remove(hoverClass); + }, + ); + } -/** - * @param {event} event - */ -function dropHandler(event) { - event.preventDefault(); - stopMoving(event.target); -} + /** + * @param {event} event + */ + function dropHandler(event) { + event.preventDefault(); + stopMoving(event.target); + } -/** - * @param {event} event - */ -function touchendHandler(event) { - event.preventDefault(); + /** + * @param {event} event + */ + function touchendHandler(event) { + event.preventDefault(); - const element = parentElement.ownerDocument.elementsFromPoint( - event.changedTouches[0].clientX, - event.changedTouches[0].clientY, - ).filter((elem) => elem.classList.contains(placeholderClass)); + const element = parentElement.ownerDocument.elementsFromPoint( + event.changedTouches[0].clientX, + event.changedTouches[0].clientY, + ).filter((elem) => elem.classList.contains(placeholderClass)); - dragendHandler(); - clonedElementForTouch.remove(); + dragendHandler(); + clonedElementForTouch.remove(); - if (element.length === 1) { - stopMoving(element[0]); + if (element.length === 1) { + stopMoving(element[0]); + } } -} -/** - * @param {DOMElement} target - * @returns {void} - */ -function stopMoving(target) { - const source = draggedElement.parentNode; - let dropElement = draggedElement; - if (dragType !== 'move') { - dropElement = draggedElement.cloneNode(true); - dropElement.style.opacity = null; - addDragEventListeners(dropElement); + /** + * @param {DOMElement} target + * @returns {void} + */ + function stopMoving(target) { + const source = draggedElement.parentNode; + let dropElement = draggedElement; + if (dragType !== 'move') { + dropElement = draggedElement.cloneNode(true); + dropElement.style.opacity = null; + addDragEventListeners(dropElement); + } + target.parentNode.insertBefore(dropElement, target); + onChangeHandler(dropElement, target, draggedElement, source); } - target.parentNode.insertBefore(dropElement, target); - onChangeHandler(dropElement, target, draggedElement, source); -} -/** - * @param {DOMElement} elem - * @returns {void} - */ -function addDragEventListeners(elem) { - elem.addEventListener('dragstart', dragstartHandler); - elem.addEventListener('dragend', dragendHandler); - elem.addEventListener('touchstart', touchstartHandler); -} - -/** - * @param {DOMElement} elem - * @returns {void} - */ -function addPlaceholderEventListeners(elem) { - elem.removeEventListener('dragover', dragoverHandler); - elem.removeEventListener('dragenter', dragenterHandler); - elem.removeEventListener('dragleave', dragleaveHandler); - elem.removeEventListener('drop', dropHandler); - elem.addEventListener('dragover', dragoverHandler); - elem.addEventListener('dragenter', dragenterHandler); - elem.addEventListener('dragleave', dragleaveHandler); - elem.addEventListener('drop', dropHandler); -} + /** + * @param {DOMElement} elem + * @returns {void} + */ + function addDragEventListeners(elem) { + elem.addEventListener('dragstart', dragstartHandler); + elem.addEventListener('dragend', dragendHandler); + elem.addEventListener('touchstart', touchstartHandler); + } -/** - * @param {DOMDocument} documentParam - * @param {string} draggableClass - * @param {string} placeholderClassParam - * @param {function} onChangeHandlerParam This handler is here to do two things: - * Put the Placeholders in the right place after a change and trigger any other - * changes necessary to make the parent usecase work. + /** + * @param {DOMElement} elem + * @returns {void} */ -export default function makeDraggable( - dragTypeParam, - parentElementParam, - draggableClassParam, - placeholderClassParam, - onChangeHandlerParam, - onStartPrepareHandlerParam, -) { - dragType = dragTypeParam; - parentElement = parentElementParam; - draggableClass = draggableClassParam; - placeholderClass = placeholderClassParam; - onChangeHandler = onChangeHandlerParam; - onStartPrepareHandler = onStartPrepareHandlerParam; + function addPlaceholderEventListeners(elem) { + elem.removeEventListener('dragover', dragoverHandler); + elem.removeEventListener('dragenter', dragenterHandler); + elem.removeEventListener('dragleave', dragleaveHandler); + elem.removeEventListener('drop', dropHandler); + elem.addEventListener('dragover', dragoverHandler); + elem.addEventListener('dragenter', dragenterHandler); + elem.addEventListener('dragleave', dragleaveHandler); + elem.addEventListener('drop', dropHandler); + } + parentElement.querySelectorAll(`.${draggableClass}`).forEach(addDragEventListeners); parentElement.querySelectorAll(`.${placeholderClass}`).forEach(addPlaceholderEventListeners); } diff --git a/components/ILIAS/TestQuestionPool/resources/js/src/matching/matching.js b/components/ILIAS/TestQuestionPool/resources/js/src/matching/matching.js index ee0c5420f9a0..1e4987562fb8 100644 --- a/components/ILIAS/TestQuestionPool/resources/js/src/matching/matching.js +++ b/components/ILIAS/TestQuestionPool/resources/js/src/matching/matching.js @@ -14,8 +14,8 @@ */ import il from 'ilias'; -import matchingHandler from './matchingHandler'; -import makeDraggable from '../makeDraggable'; +import matchingHandler from './matchingHandler.js'; +import makeDraggable from '../makeDraggable.js'; il.test = il.test || {}; il.test.matching = il.test.matching || {}; diff --git a/components/ILIAS/TestQuestionPool/resources/js/src/matching/matchingHandler.js b/components/ILIAS/TestQuestionPool/resources/js/src/matching/matchingHandler.js index 4cb3123509db..eb599d0fdd4b 100644 --- a/components/ILIAS/TestQuestionPool/resources/js/src/matching/matchingHandler.js +++ b/components/ILIAS/TestQuestionPool/resources/js/src/matching/matchingHandler.js @@ -48,22 +48,7 @@ const termElementClass = 'c-test__term'; */ const placeholderClass = 'c-test__dropzone'; -/** - * @type {String} - */ -let matchingType; - -/** - * @type {DOMElement} - */ -let parentElement; - -/** - * @type {DOMElement} - */ -let placeholderElement; - -function setup() { +function setup(parentElement) { const answers = parentElement.querySelectorAll(`.${termElementClass}`); let elementHeight = 0; answers.forEach( @@ -78,10 +63,10 @@ function setup() { elem.style.height = `${answers.item(0).offsetHeight}px`; }, ); - placeholderElement = parentElement.querySelector(`.${placeholderClass}`); + return parentElement.querySelector(`.${placeholderClass}`); } -function updatePlaceholderElementsOneToOne(targetArea) { +function updatePlaceholderElementsOneToOne(targetArea, placeholderElement) { const firstChild = targetArea.firstElementChild; if (firstChild === null) { targetArea.prepend(placeholderElement.cloneNode()); @@ -93,7 +78,7 @@ function updatePlaceholderElementsOneToOne(targetArea) { } } -function updatePlaceholderElementsManyToMany(targetArea) { +function updatePlaceholderElementsManyToMany(targetArea, placeholderElement) { if (targetArea.firstElementChild === null || !targetArea.lastElementChild.classList.contains(placeholderClass)) { targetArea.append(placeholderElement.cloneNode()); @@ -109,20 +94,20 @@ function updateAnswerElementsManyToMany(droppedElement, target, draggedElement) } } -function updateTerms(droppedElement, target, draggedElement) { +function updateTerms(droppedElement, target, draggedElement, matchingType) { if (matchingType === matchingTypeManyToMany) { updateAnswerElementsManyToMany(droppedElement, target, draggedElement); } } -function updatePlaceholders() { +function updatePlaceholders(parentElement, matchingType, placeholderElement) { parentElement.querySelectorAll(`.${targetAreasClass}`).forEach( (elem) => { if (matchingType === matchingTypeOneToOne) { - updatePlaceholderElementsOneToOne(elem); + updatePlaceholderElementsOneToOne(elem, placeholderElement); return; } - updatePlaceholderElementsManyToMany(elem); + updatePlaceholderElementsManyToMany(elem, placeholderElement); }, ); } @@ -146,13 +131,13 @@ function updateValues(droppedElement, target, source) { } } -function changeHandler(droppedElement, target, draggedElement, source) { +function changeHandler(droppedElement, target, draggedElement, source, matchingType) { updateValues(droppedElement, target, source); - updateTerms(droppedElement, target, draggedElement); + updateTerms(droppedElement, target, draggedElement, matchingType); } -function onStartPrepareHandler(draggedElement) { - updatePlaceholders(); +function onStartPrepareHandler(draggedElement, parentElement, matchingType, placeholderElement) { + updatePlaceholders(parentElement, matchingType, placeholderElement); const sourceArea = parentElement.querySelector(`.${sourceAreaClass}`); if (sourceArea.firstElementChild === null || !sourceArea.firstElementChild.classList.contains(placeholderClass)) { @@ -179,19 +164,21 @@ function onStartPrepareHandler(draggedElement) { } export default function matchingHandler( - parentElementParam, + parentElement, makeDraggable, - matchingTypeParam, + matchingType, ) { - parentElement = parentElementParam; - matchingType = matchingTypeParam; - setup(); + const placeholderElement = setup(parentElement); makeDraggable( matchingType === matchingTypeOneToOne ? 'move' : 'copy', parentElement, termElementClass, placeholderClass, - changeHandler, - onStartPrepareHandler, + (droppedElement, target, draggedElement, source) => { + changeHandler(droppedElement, target, draggedElement, source, matchingType); + }, + (draggedElement) => { + onStartPrepareHandler(draggedElement, parentElement, matchingType, placeholderElement); + }, ); } diff --git a/components/ILIAS/TestQuestionPool/resources/js/src/orderinghorizontal/orderinghorizontal.js b/components/ILIAS/TestQuestionPool/resources/js/src/orderinghorizontal/orderinghorizontal.js index 8777045f1b5b..97aaa0037561 100644 --- a/components/ILIAS/TestQuestionPool/resources/js/src/orderinghorizontal/orderinghorizontal.js +++ b/components/ILIAS/TestQuestionPool/resources/js/src/orderinghorizontal/orderinghorizontal.js @@ -14,8 +14,8 @@ */ import il from 'ilias'; -import orderingHandler from './orderinghorizontalHandler'; -import makeDraggable from '../makeDraggable'; +import orderingHandler from './orderinghorizontalHandler.js'; +import makeDraggable from '../makeDraggable.js'; il.test = il.test || {}; il.test.orderinghorizontal = il.test.orderinghorizontal || {}; diff --git a/components/ILIAS/TestQuestionPool/resources/js/src/orderinghorizontal/orderinghorizontalHandler.js b/components/ILIAS/TestQuestionPool/resources/js/src/orderinghorizontal/orderinghorizontalHandler.js index 12e3f9cb4868..ad112acde960 100644 --- a/components/ILIAS/TestQuestionPool/resources/js/src/orderinghorizontal/orderinghorizontalHandler.js +++ b/components/ILIAS/TestQuestionPool/resources/js/src/orderinghorizontal/orderinghorizontalHandler.js @@ -28,12 +28,7 @@ const answerElementClass = 'answers'; */ const placeholderClass = 'c-test__dropzone'; -/** - * @type {DOMElement} - */ -let parentElement; - -function setup() { +function setup(parentElement) { const answers = parentElement.querySelectorAll(`.${answerElementClass}`); let elementWidth = 0; answers.forEach((elem) => { elementWidth += elem.offsetWidth; }); @@ -45,7 +40,7 @@ function setup() { ); } -function updatePlaceholders() { +function updatePlaceholders(parentElement) { const placeholderElement = parentElement.querySelector(`.${placeholderClass}`); parentElement.querySelectorAll(`.${answerElementClass}`).forEach( @@ -67,7 +62,7 @@ function updatePlaceholders() { ); } -function changeHandler() { +function changeHandler(parentElement) { const currentAnswer = []; parentElement.querySelectorAll(`.${answerElementClass} > div > span`).forEach( (elem) => { currentAnswer.push(elem.textContent); }, @@ -75,8 +70,8 @@ function changeHandler() { parentElement.nextElementSibling.value = currentAnswer.join(answerSeparator); } -function onStartPrepareHandler(draggedElement) { - updatePlaceholders(); +function onStartPrepareHandler(draggedElement, parentElement) { + updatePlaceholders(parentElement); if (draggedElement.previousElementSibling?.classList.contains(placeholderClass)) { draggedElement.previousElementSibling.remove(); } @@ -86,15 +81,14 @@ function onStartPrepareHandler(draggedElement) { } } -export default function orderingHorizontalHandler(parentElementParam, makeDraggable) { - parentElement = parentElementParam; - setup(); +export default function orderingHorizontalHandler(parentElement, makeDraggable) { + setup(parentElement); makeDraggable( 'move', parentElement, answerElementClass, placeholderClass, - changeHandler, - onStartPrepareHandler, + () => { changeHandler(parentElement); }, + (draggedElement) => { onStartPrepareHandler(draggedElement, parentElement); }, ); } diff --git a/components/ILIAS/TestQuestionPool/resources/js/src/orderingvertical/orderingvertical.js b/components/ILIAS/TestQuestionPool/resources/js/src/orderingvertical/orderingvertical.js index fd7005fee0f2..a3964fa51ab4 100644 --- a/components/ILIAS/TestQuestionPool/resources/js/src/orderingvertical/orderingvertical.js +++ b/components/ILIAS/TestQuestionPool/resources/js/src/orderingvertical/orderingvertical.js @@ -14,8 +14,8 @@ */ import il from 'ilias'; -import orderingHandler from './orderingverticalHandler'; -import makeDraggable from '../makeDraggable'; +import orderingHandler from './orderingverticalHandler.js'; +import makeDraggable from '../makeDraggable.js'; il.test = il.test || {}; il.test.orderingvertical = il.test.orderingvertical || {}; diff --git a/components/ILIAS/TestQuestionPool/resources/js/src/orderingvertical/orderingverticalHandler.js b/components/ILIAS/TestQuestionPool/resources/js/src/orderingvertical/orderingverticalHandler.js index 110f7f1fa527..811ae7afa284 100644 --- a/components/ILIAS/TestQuestionPool/resources/js/src/orderingvertical/orderingverticalHandler.js +++ b/components/ILIAS/TestQuestionPool/resources/js/src/orderingvertical/orderingverticalHandler.js @@ -33,12 +33,7 @@ const answerElementClass = 'dd-item'; */ const placeholderClass = 'c-test__dropzone'; -/** - * @type {DOMElement} - */ -let parentElement; - -function setup() { +function setup(parentElement) { const answers = parentElement.querySelectorAll(`.${answerElementClass}`); let elementHeight = 0; answers.forEach( @@ -55,7 +50,7 @@ function setup() { ); } -function updatePlaceholders() { +function updatePlaceholders(parentElement) { const placeholderElement = parentElement.querySelector(`.${placeholderClass}`); parentElement.querySelectorAll(`.${answerElementClass}`).forEach( (elem) => { @@ -76,7 +71,7 @@ function updatePlaceholders() { ); } -function updateIndentationInputs(draggedElement, target) { +function updateIndentationInputs(draggedElement, target, parentElement) { let i = 0; let root = target.parentElement.parentElement; while (root !== parentElement) { @@ -93,7 +88,7 @@ function updateIndentationInputs(draggedElement, target) { ); } -function updatePositionInputs() { +function updatePositionInputs(parentElement) { let p = 0; parentElement.querySelectorAll(`.${answerElementClass}`).forEach( (elem) => { @@ -103,13 +98,13 @@ function updatePositionInputs() { ); } -function changeHandler(draggedElement, target) { - updateIndentationInputs(draggedElement, target); - updatePositionInputs(); +function changeHandler(draggedElement, target, parentElement) { + updateIndentationInputs(draggedElement, target, parentElement); + updatePositionInputs(parentElement); } -function onStartPrepareHandler(draggedElement) { - updatePlaceholders(); +function onStartPrepareHandler(draggedElement, parentElement) { + updatePlaceholders(parentElement); if (draggedElement.previousElementSibling?.classList.contains(placeholderClass)) { draggedElement.previousElementSibling.remove(); } @@ -119,15 +114,14 @@ function onStartPrepareHandler(draggedElement) { } } -export default function orderingVerticalHandler(parentElementParam, makeDraggable) { - parentElement = parentElementParam; - setup(); +export default function orderingVerticalHandler(parentElement, makeDraggable) { + setup(parentElement); makeDraggable( 'move', parentElement, answerElementClass, placeholderClass, - changeHandler, - onStartPrepareHandler, + (draggedElement, target) => { changeHandler(draggedElement, target, parentElement); }, + (draggedElement) => { onStartPrepareHandler(draggedElement, parentElement); }, ); } diff --git a/components/ILIAS/TestQuestionPool/src/ManipulateImagesInChoiceQuestionsTrait.php b/components/ILIAS/TestQuestionPool/src/ManipulateImagesInChoiceQuestionsTrait.php index bf473d2b920b..316c5260aeed 100644 --- a/components/ILIAS/TestQuestionPool/src/ManipulateImagesInChoiceQuestionsTrait.php +++ b/components/ILIAS/TestQuestionPool/src/ManipulateImagesInChoiceQuestionsTrait.php @@ -79,7 +79,7 @@ public function rebuildThumbnails( } foreach ($answers as $answer) { - if ($answer->getImage() === '') { + if ($answer->getImage() === null || $answer->getImage() === '') { continue; } diff --git a/components/ILIAS/TestQuestionPool/src/Presentation/QuestionTable.php b/components/ILIAS/TestQuestionPool/src/Questions/Presentation/QuestionTable.php similarity index 97% rename from components/ILIAS/TestQuestionPool/src/Presentation/QuestionTable.php rename to components/ILIAS/TestQuestionPool/src/Questions/Presentation/QuestionTable.php index 346173155f36..a20e0aa18afb 100755 --- a/components/ILIAS/TestQuestionPool/src/Presentation/QuestionTable.php +++ b/components/ILIAS/TestQuestionPool/src/Questions/Presentation/QuestionTable.php @@ -18,7 +18,7 @@ declare(strict_types=1); -namespace ILIAS\TestQuestionPool\Presentation; +namespace ILIAS\TestQuestionPool\Questions\Presentation; use ILIAS\UI\Factory as UIFactory; use ILIAS\UI\Renderer as UIRenderer; @@ -61,9 +61,9 @@ public function __construct( public function getTable(): Table\Data { return $this->ui_factory->table()->data( + $this, $this->lng->txt('questions'), $this->getColums(), - $this ) ->withActions($this->getActions()) ->withId('qpt' . $this->parent_obj_id . '_' . $this->request_ref_id); @@ -155,12 +155,11 @@ public function getColums(): array 'title' => $f->link($this->lng->txt('title')), 'description' => $f->text($this->lng->txt('description'))->withIsOptional(true, true), 'ttype' => $f->text($this->lng->txt('question_type'))->withIsOptional(true, true), - 'points' => $f->number($this->lng->txt('points'))->withIsOptional(true, true), + 'points' => $f->number($this->lng->txt('points'))->withDecimals(2)->withIsOptional(true, true), 'author' => $f->text($this->lng->txt('author'))->withIsOptional(true, true), 'lifecycle' => $f->text($this->lng->txt('qst_lifecycle'))->withIsOptional(true, true), 'taxonomies' => $f->text($this->lng->txt('qpl_settings_subtab_taxonomies'))->withIsOptional(true, true), 'feedback' => $f->boolean($this->lng->txt('feedback'), $icon_yes, $icon_no)->withIsOptional(true, true), - 'hints' => $f->boolean($this->lng->txt('hints'), $icon_yes, $icon_no)->withIsOptional(true, true), 'created' => $f->date( $this->lng->txt('create_date'), $this->current_user->getDateTimeFormat() @@ -305,7 +304,6 @@ public function getRows( ->withDisabledAction('copy', $no_write_access) ->withDisabledAction('delete', $no_write_access) ->withDisabledAction('feedback', $no_write_access) - ->withDisabledAction('hints', $no_write_access) ; } } @@ -341,7 +339,6 @@ protected function getActions(): array $this->buildAction('edit_question', 'single'), $this->buildAction('edit_page', 'single'), $this->buildAction('feedback', 'single'), - $this->buildAction('hints', 'single'), $this->buildAction(\ilBulkEditQuestionsGUI::CMD_EDITTAUTHOR, 'multi'), $this->buildAction(\ilBulkEditQuestionsGUI::CMD_EDITLIFECYCLE, 'multi'), $this->buildAction(\ilBulkEditQuestionsGUI::CMD_EDITTAXONOMIES, 'multi'), @@ -386,7 +383,7 @@ protected function postOrder(array $list, \ILIAS\Data\Order $order): array $aspect_b = $b[$aspect]; } - return strcmp($aspect_a, $aspect_b); + return strcoll($aspect_a, $aspect_b); }); if ($direction === $order::DESC) { diff --git a/components/ILIAS/TestQuestionPool/templates/default/tpl.il_as_qpl_longmenu_question_text_gap.html b/components/ILIAS/TestQuestionPool/templates/default/tpl.il_as_qpl_longmenu_question_text_gap.html index c1724ef9fc83..bad2e730213a 100755 --- a/components/ILIAS/TestQuestionPool/templates/default/tpl.il_as_qpl_longmenu_question_text_gap.html +++ b/components/ILIAS/TestQuestionPool/templates/default/tpl.il_as_qpl_longmenu_question_text_gap.html @@ -1,4 +1,4 @@ - + {ICON_OK} \ No newline at end of file diff --git a/components/ILIAS/TestQuestionPool/templates/default/tpl.qst_question_related_navigation.html b/components/ILIAS/TestQuestionPool/templates/default/tpl.qst_question_related_navigation.html index ed3c179843a3..8c573ae37476 100755 --- a/components/ILIAS/TestQuestionPool/templates/default/tpl.qst_question_related_navigation.html +++ b/components/ILIAS/TestQuestionPool/templates/default/tpl.qst_question_related_navigation.html @@ -3,11 +3,5 @@ - - - - - - diff --git a/components/ILIAS/TestQuestionPool/templates/default/tpl.tst_question_hints_administration_table_row.html b/components/ILIAS/TestQuestionPool/templates/default/tpl.tst_question_hints_administration_table_row.html deleted file mode 100755 index 540aab7cbb3a..000000000000 --- a/components/ILIAS/TestQuestionPool/templates/default/tpl.tst_question_hints_administration_table_row.html +++ /dev/null @@ -1,17 +0,0 @@ -
- - - - - - \ No newline at end of file diff --git a/components/ILIAS/TestQuestionPool/templates/default/tpl.tst_question_hints_testoutput_table_row.html b/components/ILIAS/TestQuestionPool/templates/default/tpl.tst_question_hints_testoutput_table_row.html deleted file mode 100755 index d191883336d0..000000000000 --- a/components/ILIAS/TestQuestionPool/templates/default/tpl.tst_question_hints_testoutput_table_row.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - \ No newline at end of file diff --git a/components/ILIAS/TestQuestionPool/tests/QuestionTableTest.php b/components/ILIAS/TestQuestionPool/tests/QuestionTableTest.php index 0b7b66065e2d..de68b49372e2 100644 --- a/components/ILIAS/TestQuestionPool/tests/QuestionTableTest.php +++ b/components/ILIAS/TestQuestionPool/tests/QuestionTableTest.php @@ -16,7 +16,7 @@ * *********************************************************************/ -use ILIAS\TestQuestionPool\Presentation\QuestionTable; +use ILIAS\TestQuestionPool\Questions\Presentation\QuestionTable; use ILIAS\UI\Factory as UIFactory; use ILIAS\UI\Renderer as UIRenderer; use ILIAS\Data\Factory as DataFactory; diff --git a/components/ILIAS/TestQuestionPool/tests/SuggestedSolutionTest.php b/components/ILIAS/TestQuestionPool/tests/SuggestedSolutionTest.php index ea11e6d2cf0c..e85fd18f876f 100755 --- a/components/ILIAS/TestQuestionPool/tests/SuggestedSolutionTest.php +++ b/components/ILIAS/TestQuestionPool/tests/SuggestedSolutionTest.php @@ -108,9 +108,7 @@ public function testSuggestedSolutionFile(): SuggestedSolutionFile } - /** - * @depends testSuggestedSolutionFile - */ + #[\PHPUnit\Framework\Attributes\Depends('testSuggestedSolutionFile')] public function testSuggestedSolutionMutatorsFile(SuggestedSolutionFile $sugsol): void { $values = [ diff --git a/components/ILIAS/TestQuestionPool/tests/assBaseTestCase.php b/components/ILIAS/TestQuestionPool/tests/assBaseTestCase.php index 833d87c20245..649e20f74de1 100755 --- a/components/ILIAS/TestQuestionPool/tests/assBaseTestCase.php +++ b/components/ILIAS/TestQuestionPool/tests/assBaseTestCase.php @@ -42,6 +42,8 @@ protected function setUp(): void $this->addGlobal_tpl(); $this->addGlobal_lng(); + $this->addGlobal_objectService(); + $this->addGlobal_objectMetadata(); $this->addGlobal_ilObjDataCache(); $this->addGlobal_ilAccess(); $this->addGlobal_ilHelp(); diff --git a/components/ILIAS/TestQuestionPool/tests/assClozeGapCombinationTest.php b/components/ILIAS/TestQuestionPool/tests/assClozeGapCombinationTest.php index ff5952cc2331..316c21188f7d 100644 --- a/components/ILIAS/TestQuestionPool/tests/assClozeGapCombinationTest.php +++ b/components/ILIAS/TestQuestionPool/tests/assClozeGapCombinationTest.php @@ -35,7 +35,7 @@ protected function setUp(): void { parent::setUp(); - $this->object = new assClozeGapCombination(); + $this->object = new assClozeGapCombination($this->createMock(ilDBInterface::class)); } public function testConstruct(): void diff --git a/components/ILIAS/TestQuestionPool/tests/assClozeGapTest.php b/components/ILIAS/TestQuestionPool/tests/assClozeGapTest.php index f1916693f05a..87a96fed2b74 100755 --- a/components/ILIAS/TestQuestionPool/tests/assClozeGapTest.php +++ b/components/ILIAS/TestQuestionPool/tests/assClozeGapTest.php @@ -36,7 +36,7 @@ protected function setUp(): void parent::setUp(); $util_mock = $this->createMock('ilUtil', ['stripSlashes'], [], '', false); - $util_mock->expects($this->any())->method('stripSlashes')->will($this->returnArgument(0)); + $util_mock->expects($this->any())->method('stripSlashes')->willReturnArgument(0); $this->setGlobalVariable('ilUtils', $util_mock); } @@ -407,7 +407,7 @@ public function test_getBestSolutionOutput_shouldReturnBestSolutionOutput_CaseTe $item4 = new assAnswerCloze('Esther', 4.0, 3); $lng_mock = $this->createMock('ilLanguage', ['txt'], [], '', false); - $lng_mock->expects($this->any())->method('txt')->will($this->returnValue('Test')); + $lng_mock->expects($this->any())->method('txt')->willReturn('Test'); global $DIC; unset($DIC['lng']); $DIC['lng'] = $lng_mock; @@ -434,7 +434,7 @@ public function test_getBestSolutionOutput_shouldReturnBestSolutionOutput_CaseTe $item4 = new assAnswerCloze('Esther', 4, 3); $lng_mock = $this->createMock('ilLanguage', ['txt'], [], '', false); - $lng_mock->expects($this->any())->method('txt')->will($this->returnValue('or')); + $lng_mock->expects($this->any())->method('txt')->willReturn('or'); global $DIC; unset($DIC['lng']); $DIC['lng'] = $lng_mock; @@ -463,7 +463,7 @@ public function test_getBestSolutionOutput_shouldReturnBestSolutionOutput_CaseNu $item4 = new assAnswerCloze(100, 4.0, 3); $lng_mock = $this->createMock('ilLanguage', ['txt'], [], '', false); - $lng_mock->expects($this->any())->method('txt')->will($this->returnValue('Test')); + $lng_mock->expects($this->any())->method('txt')->willReturn('Test'); global $DIC; unset($DIC['lng']); $DIC['lng'] = $lng_mock; @@ -490,7 +490,7 @@ public function test_getBestSolutionOutput_shouldReturnEmptyStringOnUnknownType_ $item4 = new assAnswerCloze(100, 4.0, 3); $lng_mock = $this->createMock('ilLanguage', ['txt'], [], '', false); - $lng_mock->expects($this->any())->method('txt')->will($this->returnValue('Test')); + $lng_mock->expects($this->any())->method('txt')->willReturn('Test'); global $DIC; unset($DIC['lng']); $DIC['lng'] = $lng_mock; diff --git a/components/ILIAS/TestQuestionPool/tests/assClozeTestGUITest.php b/components/ILIAS/TestQuestionPool/tests/assClozeTestGUITest.php index 05b9a38520a9..bc4405b852d6 100755 --- a/components/ILIAS/TestQuestionPool/tests/assClozeTestGUITest.php +++ b/components/ILIAS/TestQuestionPool/tests/assClozeTestGUITest.php @@ -47,7 +47,7 @@ protected function setUp(): void ->disableOriginalConstructor() ->onlyMethods(['txt']) ->getMock(); - $lng_mock->method('txt')->will($this->returnValue('Test')); + $lng_mock->method('txt')->willReturn('Test'); $this->setGlobalVariable('lng', $lng_mock); $ilias_mock = new stdClass(); diff --git a/components/ILIAS/TestQuestionPool/tests/assClozeTestTest.php b/components/ILIAS/TestQuestionPool/tests/assClozeTestTest.php index 8aed4543b959..499c8a6b5bac 100755 --- a/components/ILIAS/TestQuestionPool/tests/assClozeTestTest.php +++ b/components/ILIAS/TestQuestionPool/tests/assClozeTestTest.php @@ -42,7 +42,7 @@ protected function setUp(): void ->disableOriginalConstructor() ->onlyMethods(['txt']) ->getMock(); - $lng_mock->method('txt')->will($this->returnValue('Test')); + $lng_mock->method('txt')->willReturn('Test'); $this->setGlobalVariable('lng', $lng_mock); $this->setGlobalVariable('ilias', $this->getIliasMock()); diff --git a/components/ILIAS/TestQuestionPool/tests/assErrorTextGUITest.php b/components/ILIAS/TestQuestionPool/tests/assErrorTextGUITest.php index 2e34b87fd181..8107162824ce 100755 --- a/components/ILIAS/TestQuestionPool/tests/assErrorTextGUITest.php +++ b/components/ILIAS/TestQuestionPool/tests/assErrorTextGUITest.php @@ -44,7 +44,7 @@ protected function setUp(): void ->disableOriginalConstructor() ->onlyMethods(['txt']) ->getMock(); - $lng_mock->method('txt')->will($this->returnValue('Test')); + $lng_mock->method('txt')->willReturn('Test'); $this->setGlobalVariable('lng', $lng_mock); $this->setGlobalVariable('ilias', $this->getIliasMock()); diff --git a/components/ILIAS/TestQuestionPool/tests/assErrorTextTest.php b/components/ILIAS/TestQuestionPool/tests/assErrorTextTest.php index 8484cc8bcf0d..c66991961a8a 100755 --- a/components/ILIAS/TestQuestionPool/tests/assErrorTextTest.php +++ b/components/ILIAS/TestQuestionPool/tests/assErrorTextTest.php @@ -39,7 +39,6 @@ protected function setUp(): void $this->setGlobalVariable('ilCtrl', $ilCtrl_mock); $lng_mock = $this->createMock('ilLanguage', ['txt'], [], '', false); - //$lng_mock->expects( $this->once() )->method( 'txt' )->will( $this->returnValue('Test') ); $this->setGlobalVariable('lng', $lng_mock); $this->setGlobalVariable('ilias', $this->getIliasMock()); diff --git a/components/ILIAS/TestQuestionPool/tests/assFileUploadGUITest.php b/components/ILIAS/TestQuestionPool/tests/assFileUploadGUITest.php index c577697272de..d1c61c45fd69 100755 --- a/components/ILIAS/TestQuestionPool/tests/assFileUploadGUITest.php +++ b/components/ILIAS/TestQuestionPool/tests/assFileUploadGUITest.php @@ -44,7 +44,7 @@ protected function setUp(): void ->disableOriginalConstructor() ->onlyMethods(['txt']) ->getMock(); - $lng_mock->method('txt')->will($this->returnValue('Test')); + $lng_mock->method('txt')->willReturn('Test'); $this->setGlobalVariable('lng', $lng_mock); $this->setGlobalVariable('ilias', $this->getIliasMock()); diff --git a/components/ILIAS/TestQuestionPool/tests/assFileUploadTest.php b/components/ILIAS/TestQuestionPool/tests/assFileUploadTest.php index 9feff0031572..7a3c6bf6fe86 100755 --- a/components/ILIAS/TestQuestionPool/tests/assFileUploadTest.php +++ b/components/ILIAS/TestQuestionPool/tests/assFileUploadTest.php @@ -42,7 +42,7 @@ protected function setUp(): void ->disableOriginalConstructor() ->onlyMethods(['txt']) ->getMock(); - $lng_mock->method('txt')->will($this->returnValue('Test')); + $lng_mock->method('txt')->willReturn('Test'); $this->setGlobalVariable('lng', $lng_mock); $this->setGlobalVariable('ilias', $this->getIliasMock()); diff --git a/components/ILIAS/TestQuestionPool/tests/assFormulaQuestionGUITest.php b/components/ILIAS/TestQuestionPool/tests/assFormulaQuestionGUITest.php index e433ac39e85a..cdfd936ff936 100755 --- a/components/ILIAS/TestQuestionPool/tests/assFormulaQuestionGUITest.php +++ b/components/ILIAS/TestQuestionPool/tests/assFormulaQuestionGUITest.php @@ -44,7 +44,7 @@ protected function setUp(): void ->disableOriginalConstructor() ->onlyMethods(['txt']) ->getMock(); - $lng_mock->method('txt')->will($this->returnValue('Test')); + $lng_mock->method('txt')->willReturn('Test'); $this->setGlobalVariable('lng', $lng_mock); $this->setGlobalVariable('ilias', $this->getIliasMock()); diff --git a/components/ILIAS/TestQuestionPool/tests/assFormulaQuestionTest.php b/components/ILIAS/TestQuestionPool/tests/assFormulaQuestionTest.php index 0a2cc2ab6c97..0ff46e57802e 100755 --- a/components/ILIAS/TestQuestionPool/tests/assFormulaQuestionTest.php +++ b/components/ILIAS/TestQuestionPool/tests/assFormulaQuestionTest.php @@ -17,6 +17,7 @@ *********************************************************************/ use ILIAS\DI\Container; +use ILIAS\Refinery\Factory; /** * Unit tests @@ -37,7 +38,7 @@ protected function setUp(): void ->disableOriginalConstructor() ->getMock(); $lng->method('txt') - ->will($this->returnArgument(0)); + ->willReturnArgument(0); $this->setGlobalVariable('lng', $lng); } diff --git a/components/ILIAS/TestQuestionPool/tests/assImagemapQuestionTest.php b/components/ILIAS/TestQuestionPool/tests/assImagemapQuestionTest.php index 1d5e30dc84a7..74717c92144d 100755 --- a/components/ILIAS/TestQuestionPool/tests/assImagemapQuestionTest.php +++ b/components/ILIAS/TestQuestionPool/tests/assImagemapQuestionTest.php @@ -42,7 +42,7 @@ protected function setUp(): void ->disableOriginalConstructor() ->onlyMethods(['txt']) ->getMock(); - $lng_mock->method('txt')->will($this->returnValue('Test')); + $lng_mock->method('txt')->willReturn('Test'); $this->setGlobalVariable('lng', $lng_mock); $this->setGlobalVariable('ilias', $this->getIliasMock()); diff --git a/components/ILIAS/TestQuestionPool/tests/assLongMenuTest.php b/components/ILIAS/TestQuestionPool/tests/assLongMenuTest.php index 5707e2feb5cb..f4c3081ba53b 100755 --- a/components/ILIAS/TestQuestionPool/tests/assLongMenuTest.php +++ b/components/ILIAS/TestQuestionPool/tests/assLongMenuTest.php @@ -48,7 +48,6 @@ protected function setUp(): void $this->setGlobalVariable('ilCtrl', $ilCtrl_mock); $lng_mock = $this->createMock('ilLanguage', ['txt'], [], '', false); - //$lng_mock->expects( $this->once() )->method( 'txt' )->will( $this->returnValue('Test') ); $this->setGlobalVariable('lng', $lng_mock); $this->setGlobalVariable('ilias', $this->getIliasMock()); diff --git a/components/ILIAS/TestQuestionPool/tests/assMatchingQuestionGUITest.php b/components/ILIAS/TestQuestionPool/tests/assMatchingQuestionGUITest.php index 8331647d03ea..27325953cbc3 100755 --- a/components/ILIAS/TestQuestionPool/tests/assMatchingQuestionGUITest.php +++ b/components/ILIAS/TestQuestionPool/tests/assMatchingQuestionGUITest.php @@ -44,7 +44,7 @@ protected function setUp(): void ->disableOriginalConstructor() ->onlyMethods(['txt']) ->getMock(); - $lng_mock->method('txt')->will($this->returnValue('Test')); + $lng_mock->method('txt')->willReturn('Test'); $this->setGlobalVariable('lng', $lng_mock); $this->setGlobalVariable('ilias', $this->getIliasMock()); diff --git a/components/ILIAS/TestQuestionPool/tests/assMatchingQuestionTest.php b/components/ILIAS/TestQuestionPool/tests/assMatchingQuestionTest.php index 6620bc9fd7c3..bd533b6143e0 100755 --- a/components/ILIAS/TestQuestionPool/tests/assMatchingQuestionTest.php +++ b/components/ILIAS/TestQuestionPool/tests/assMatchingQuestionTest.php @@ -42,7 +42,7 @@ protected function setUp(): void ->disableOriginalConstructor() ->onlyMethods(['txt']) ->getMock(); - $lng_mock->method('txt')->will($this->returnValue('Test')); + $lng_mock->method('txt')->willReturn('Test'); $this->setGlobalVariable('lng', $lng_mock); $this->setGlobalVariable('ilias', $this->getIliasMock()); diff --git a/components/ILIAS/TestQuestionPool/tests/assMultipleChoiceGUITest.php b/components/ILIAS/TestQuestionPool/tests/assMultipleChoiceGUITest.php index abc64ee5c40d..5e5a87e1b804 100755 --- a/components/ILIAS/TestQuestionPool/tests/assMultipleChoiceGUITest.php +++ b/components/ILIAS/TestQuestionPool/tests/assMultipleChoiceGUITest.php @@ -46,7 +46,7 @@ protected function setUp(): void ->disableOriginalConstructor() ->onlyMethods(['txt']) ->getMock(); - $lng_mock->method('txt')->will($this->returnValue('Test')); + $lng_mock->method('txt')->willReturn('Test'); $this->setGlobalVariable('lng', $lng_mock); $this->setGlobalVariable('ilias', $this->getIliasMock()); diff --git a/components/ILIAS/TestQuestionPool/tests/assNumericTest.php b/components/ILIAS/TestQuestionPool/tests/assNumericTest.php index 3fe3c5a6f519..eda47169364b 100755 --- a/components/ILIAS/TestQuestionPool/tests/assNumericTest.php +++ b/components/ILIAS/TestQuestionPool/tests/assNumericTest.php @@ -42,7 +42,7 @@ protected function setUp(): void ->disableOriginalConstructor() ->onlyMethods(['txt']) ->getMock(); - $lng_mock->method('txt')->will($this->returnValue('Test')); + $lng_mock->method('txt')->willReturn('Test'); $this->setGlobalVariable('lng', $lng_mock); $this->setGlobalVariable('ilias', $this->getIliasMock()); diff --git a/components/ILIAS/TestQuestionPool/tests/assOrderingHorizontalGUITest.php b/components/ILIAS/TestQuestionPool/tests/assOrderingHorizontalGUITest.php index 25175ca6f282..f6d89add5031 100755 --- a/components/ILIAS/TestQuestionPool/tests/assOrderingHorizontalGUITest.php +++ b/components/ILIAS/TestQuestionPool/tests/assOrderingHorizontalGUITest.php @@ -39,7 +39,7 @@ protected function setUp(): void $this->setGlobalVariable('ilCtrl', $ilCtrl_mock); $lng_mock = $this->createMock('ilLanguage', ['txt'], [], '', false); - $lng_mock->expects($this->any())->method('txt')->will($this->returnValue('Test')); + $lng_mock->expects($this->any())->method('txt')->willReturn('Test'); $this->setGlobalVariable('lng', $lng_mock); $this->setGlobalVariable('ilias', $this->getIliasMock()); diff --git a/components/ILIAS/TestQuestionPool/tests/assOrderingHorizontalTest.php b/components/ILIAS/TestQuestionPool/tests/assOrderingHorizontalTest.php index 918467ec06dc..b7901076de60 100755 --- a/components/ILIAS/TestQuestionPool/tests/assOrderingHorizontalTest.php +++ b/components/ILIAS/TestQuestionPool/tests/assOrderingHorizontalTest.php @@ -42,7 +42,7 @@ protected function setUp(): void ->disableOriginalConstructor() ->onlyMethods(['txt']) ->getMock(); - $lng_mock->method('txt')->will($this->returnValue('Test')); + $lng_mock->method('txt')->willReturn('Test'); $this->setGlobalVariable('lng', $lng_mock); $this->setGlobalVariable('ilias', $this->getIliasMock()); diff --git a/components/ILIAS/TestQuestionPool/tests/assOrderingQuestionGUITest.php b/components/ILIAS/TestQuestionPool/tests/assOrderingQuestionGUITest.php index 1f658f69ac7d..597613e25dc1 100755 --- a/components/ILIAS/TestQuestionPool/tests/assOrderingQuestionGUITest.php +++ b/components/ILIAS/TestQuestionPool/tests/assOrderingQuestionGUITest.php @@ -44,7 +44,7 @@ protected function setUp(): void ->disableOriginalConstructor() ->onlyMethods(['txt']) ->getMock(); - $lng_mock->method('txt')->will($this->returnValue('Test')); + $lng_mock->method('txt')->willReturn('Test'); $this->setGlobalVariable('lng', $lng_mock); $this->setGlobalVariable('ilias', $this->getIliasMock()); diff --git a/components/ILIAS/TestQuestionPool/tests/assOrderingQuestionTest.php b/components/ILIAS/TestQuestionPool/tests/assOrderingQuestionTest.php index a0956193fadb..b1055a2d9169 100755 --- a/components/ILIAS/TestQuestionPool/tests/assOrderingQuestionTest.php +++ b/components/ILIAS/TestQuestionPool/tests/assOrderingQuestionTest.php @@ -42,7 +42,7 @@ protected function setUp(): void ->disableOriginalConstructor() ->onlyMethods(['txt']) ->getMock(); - $lng_mock->method('txt')->will($this->returnValue('Test')); + $lng_mock->method('txt')->willReturn('Test'); $this->setGlobalVariable('lng', $lng_mock); $this->setGlobalVariable('ilias', $this->getIliasMock()); @@ -66,9 +66,7 @@ public function testOrderingElementListDefaults(): ilAssOrderingElementList return $list; } - /** - * @depends testOrderingElementListDefaults - */ + #[\PHPUnit\Framework\Attributes\Depends('testOrderingElementListDefaults')] public function testOrderingElementListMutation(ilAssOrderingElementList $list) { $original = $list; @@ -84,9 +82,7 @@ public function testOrderingElementDefaults(): ilAssOrderingElement return $element; } - /** - * @depends testOrderingElementDefaults - */ + #[\PHPUnit\Framework\Attributes\Depends('testOrderingElementDefaults')] public function testOrderingElementMutation(ilAssOrderingElement $element) { $original = $element; diff --git a/components/ILIAS/TestQuestionPool/tests/assSingleChoiceGUITest.php b/components/ILIAS/TestQuestionPool/tests/assSingleChoiceGUITest.php index 08bec562da24..f44050dab6e3 100755 --- a/components/ILIAS/TestQuestionPool/tests/assSingleChoiceGUITest.php +++ b/components/ILIAS/TestQuestionPool/tests/assSingleChoiceGUITest.php @@ -46,7 +46,7 @@ protected function setUp(): void ->disableOriginalConstructor() ->onlyMethods(['txt']) ->getMock(); - $lng_mock->method('txt')->will($this->returnValue('Test')); + $lng_mock->method('txt')->willReturn('Test'); $this->setGlobalVariable('lng', $lng_mock); $this->setGlobalVariable('ilias', $this->getIliasMock()); diff --git a/components/ILIAS/TestQuestionPool/tests/assTextQuestionGUITest.php b/components/ILIAS/TestQuestionPool/tests/assTextQuestionGUITest.php index 47dd48a88b1f..27dfaf6ae753 100755 --- a/components/ILIAS/TestQuestionPool/tests/assTextQuestionGUITest.php +++ b/components/ILIAS/TestQuestionPool/tests/assTextQuestionGUITest.php @@ -39,7 +39,7 @@ protected function setUp(): void $this->setGlobalVariable('ilCtrl', $ilCtrl_mock); $lng_mock = $this->createMock('ilLanguage', ['txt'], [], '', false); - $lng_mock->expects($this->any())->method('txt')->will($this->returnValue('Test')); + $lng_mock->expects($this->any())->method('txt')->willReturn('Test'); $this->setGlobalVariable('lng', $lng_mock); $this->setGlobalVariable('ilias', $this->getIliasMock()); diff --git a/components/ILIAS/TestQuestionPool/tests/assTextQuestionTest.php b/components/ILIAS/TestQuestionPool/tests/assTextQuestionTest.php index 8024f6dcecd5..d1164d1c51d3 100755 --- a/components/ILIAS/TestQuestionPool/tests/assTextQuestionTest.php +++ b/components/ILIAS/TestQuestionPool/tests/assTextQuestionTest.php @@ -42,7 +42,7 @@ protected function setUp(): void ->disableOriginalConstructor() ->onlyMethods(['txt']) ->getMock(); - $lng_mock->method('txt')->will($this->returnValue('Test')); + $lng_mock->method('txt')->willReturn('Test'); $this->setGlobalVariable('lng', $lng_mock); $this->setGlobalVariable('ilias', $this->getIliasMock()); diff --git a/components/ILIAS/TestQuestionPool/tests/assTextSubsetGUITest.php b/components/ILIAS/TestQuestionPool/tests/assTextSubsetGUITest.php index e9f5d94ed773..149dcb05d399 100755 --- a/components/ILIAS/TestQuestionPool/tests/assTextSubsetGUITest.php +++ b/components/ILIAS/TestQuestionPool/tests/assTextSubsetGUITest.php @@ -44,7 +44,7 @@ protected function setUp(): void ->disableOriginalConstructor() ->onlyMethods(['txt']) ->getMock(); - $lng_mock->method('txt')->will($this->returnValue('Test')); + $lng_mock->method('txt')->willReturn('Test'); $this->setGlobalVariable('lng', $lng_mock); $this->setGlobalVariable('ilias', $this->getIliasMock()); diff --git a/components/ILIAS/TestQuestionPool/tests/assTextSubsetTest.php b/components/ILIAS/TestQuestionPool/tests/assTextSubsetTest.php index 410742153ffa..a3deae5c9847 100755 --- a/components/ILIAS/TestQuestionPool/tests/assTextSubsetTest.php +++ b/components/ILIAS/TestQuestionPool/tests/assTextSubsetTest.php @@ -39,7 +39,6 @@ protected function setUp(): void $this->setGlobalVariable('ilCtrl', $ilCtrl_mock); $lng_mock = $this->createMock('ilLanguage', ['txt'], [], '', false); - //$lng_mock->expects($this->once())->method('txt')->will($this->returnValue('Test')); $this->setGlobalVariable('lng', $lng_mock); $this->setGlobalVariable('ilias', $this->getIliasMock()); diff --git a/components/ILIAS/TestQuestionPool/tests/ilAssHintPageConfigTest.php b/components/ILIAS/TestQuestionPool/tests/ilAssHintPageConfigTest.php deleted file mode 100644 index 3bcabf266436..000000000000 --- a/components/ILIAS/TestQuestionPool/tests/ilAssHintPageConfigTest.php +++ /dev/null @@ -1,50 +0,0 @@ - -* -* @ingroup components\ILIASTestQuestionPool -* -* This test was automatically generated. -*/ -class ilAssHintPageConfigTest extends assBaseTestCase -{ - protected $backupGlobals = false; - - private ilAssHintPageConfig $object; - - protected function setUp(): void - { - parent::setUp(); - - // $this->object = new ilAssHintPageConfig(); - } - - // public function testConstruct(): void - // { - // $this->assertInstanceOf(ilAssHintPageConfig::class, $this->object); - // } - - public function testSuppressWarning(): void - { - $this->assertTrue(true); - } -} diff --git a/components/ILIAS/TestQuestionPool/tests/ilAssHintPageGUITest.php b/components/ILIAS/TestQuestionPool/tests/ilAssHintPageGUITest.php deleted file mode 100644 index 973758b8c7f6..000000000000 --- a/components/ILIAS/TestQuestionPool/tests/ilAssHintPageGUITest.php +++ /dev/null @@ -1,52 +0,0 @@ - -* -* @ingroup components\ILIASTestQuestionPool -* -* This test was automatically generated. -*/ -class ilAssHintPageGUITest extends assBaseTestCase -{ - protected $backupGlobals = false; - - private ilAssHintPageGUI $object; - - protected function setUp(): void - { - parent::setUp(); - - $this->addGlobal_ilToolbar(); - - // $this->object = new ilAssHintPageGUI(); - } - - // public function testConstruct(): void - // { - // $this->assertInstanceOf(ilAssHintPageGUI::class, $this->object); - // } - - public function testSuppressWarning(): void - { - $this->assertTrue(true); - } -} diff --git a/components/ILIAS/TestQuestionPool/tests/ilAssHintPageTest.php b/components/ILIAS/TestQuestionPool/tests/ilAssHintPageTest.php deleted file mode 100644 index a96829a0a314..000000000000 --- a/components/ILIAS/TestQuestionPool/tests/ilAssHintPageTest.php +++ /dev/null @@ -1,52 +0,0 @@ - -* -* @ingroup components\ILIASTestQuestionPool -* -* This test was automatically generated. -*/ -class ilAssHintPageTest extends assBaseTestCase -{ - protected $backupGlobals = false; - - private ilAssHintPage $object; - - protected function setUp(): void - { - parent::setUp(); - - $this->addGlobal_objDefinition(); - - // $this->object = new ilAssHintPage(); - } - - // public function testConstruct(): void - // { - // $this->assertInstanceOf(ilAssHintPage::class, $this->object); - // } - - public function testSuppressWarning(): void - { - $this->assertTrue(true); - } -} diff --git a/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintAbstractGUITest.php b/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintAbstractGUITest.php deleted file mode 100644 index 0c2dd10b40a7..000000000000 --- a/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintAbstractGUITest.php +++ /dev/null @@ -1,47 +0,0 @@ - -* -* @ingroup components\ILIASTestQuestionPool -* -* This test was automatically generated. -*/ -class ilAssQuestionHintAbstractGUITest extends assBaseTestCase -{ - protected $backupGlobals = false; - - private ilAssQuestionHintAbstractGUI $object; - - protected function setUp(): void - { - parent::setUp(); - - $assQuestionGUI = $this->createMock(assQuestionGUI::class); - - $this->object = new class ($assQuestionGUI) extends ilAssQuestionHintAbstractGUI {}; - } - - public function testConstruct(): void - { - $this->assertInstanceOf(ilAssQuestionHintAbstractGUI::class, $this->object); - } -} diff --git a/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintGUITest.php b/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintGUITest.php deleted file mode 100644 index 60aca260a120..000000000000 --- a/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintGUITest.php +++ /dev/null @@ -1,47 +0,0 @@ - -* -* @ingroup components\ILIASTestQuestionPool -* -* This test was automatically generated. -*/ -class ilAssQuestionHintGUITest extends assBaseTestCase -{ - protected $backupGlobals = false; - - private ilAssQuestionHintGUI $object; - - protected function setUp(): void - { - parent::setUp(); - - $assQuestionGUI = $this->createMock(assQuestionGUI::class); - - $this->object = new ilAssQuestionHintGUI($assQuestionGUI); - } - - public function testConstruct(): void - { - $this->assertInstanceOf(ilAssQuestionHintGUI::class, $this->object); - } -} diff --git a/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintListTest.php b/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintListTest.php deleted file mode 100755 index 5c0f34bef1f7..000000000000 --- a/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintListTest.php +++ /dev/null @@ -1,41 +0,0 @@ - -* -* @ingroup components\ILIASTestQuestionPool -*/ -class ilAssQuestionHintListTest extends assBaseTestCase -{ - protected $backupGlobals = false; - - protected function setUp(): void - { - chdir(__DIR__ . '/../../../../'); - } - - public function test_instantiateObject_shouldReturnInstance(): void - { - $instance = new ilAssQuestionHintList(); - - $this->assertInstanceOf(ilAssQuestionHintList::class, $instance); - } -} diff --git a/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintPageObjectCommandForwarderTest.php b/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintPageObjectCommandForwarderTest.php deleted file mode 100644 index 68d9dddb30b7..000000000000 --- a/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintPageObjectCommandForwarderTest.php +++ /dev/null @@ -1,50 +0,0 @@ - -* -* @ingroup components\ILIASTestQuestionPool -* -* This test was automatically generated. -*/ -class ilAssQuestionHintPageObjectCommandForwarderTest extends assBaseTestCase -{ - protected $backupGlobals = false; - - private ilAssQuestionHintPageObjectCommandForwarder $object; - - protected function setUp(): void - { - parent::setUp(); - - $questionOBJ = $this->createMock(assQuestion::class); - $ctrl = $this->createMock(ilCtrl::class); - $tabs = $this->createMock(ilTabsGUI::class); - $lng = $this->createMock(ilLanguage::class); - - $this->object = new ilAssQuestionHintPageObjectCommandForwarder($questionOBJ, $ctrl, $tabs, $lng); - } - - public function testConstruct(): void - { - $this->assertInstanceOf(ilAssQuestionHintPageObjectCommandForwarder::class, $this->object); - } -} diff --git a/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintRequestGUITest.php b/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintRequestGUITest.php deleted file mode 100644 index f5407faf8f3c..000000000000 --- a/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintRequestGUITest.php +++ /dev/null @@ -1,57 +0,0 @@ - -* -* @ingroup components\ILIASTestQuestionPool -* -* This test was automatically generated. -*/ -class ilAssQuestionHintRequestGUITest extends assBaseTestCase -{ - protected $backupGlobals = false; - - private ilAssQuestionHintRequestGUI $object; - - protected function setUp(): void - { - parent::setUp(); - - $assQuestionGUI = $this->createMock(assQuestionGUI::class); - - $this->object = new ilAssQuestionHintRequestGUI( - $this->createMock(ilTestPlayerAbstractGUI::class), - '', - $assQuestionGUI, - null, - $this->createMock(ilCtrl::class), - $this->createMock(ilLanguage::class), - $this->createMock(ilGlobalTemplateInterface::class), - $this->createMock(ilTabsGUI::class), - $this->createMock(ILIAS\GlobalScreen\Services::class) - ); - } - - public function testConstruct(): void - { - $this->assertInstanceOf(ilAssQuestionHintRequestGUI::class, $this->object); - } -} diff --git a/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintRequestStatisticDataTest.php b/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintRequestStatisticDataTest.php deleted file mode 100755 index 4ede1052575b..000000000000 --- a/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintRequestStatisticDataTest.php +++ /dev/null @@ -1,41 +0,0 @@ - -* -* @ingroup components\ILIASTestQuestionPool -*/ -class ilAssQuestionHintRequestStatisticDataTest extends assBaseTestCase -{ - protected $backupGlobals = false; - - protected function setUp(): void - { - chdir(__DIR__ . '/../../../../'); - } - - public function test_instantiateObject_shouldReturnInstance(): void - { - $instance = new ilAssQuestionHintRequestStatisticData(); - - $this->assertInstanceOf(ilAssQuestionHintRequestStatisticData::class, $instance); - } -} diff --git a/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintRequestStatisticRegisterTest.php b/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintRequestStatisticRegisterTest.php deleted file mode 100644 index aefe34da7ad7..000000000000 --- a/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintRequestStatisticRegisterTest.php +++ /dev/null @@ -1,45 +0,0 @@ - -* -* @ingroup components\ILIASTestQuestionPool -* -* This test was automatically generated. -*/ -class ilAssQuestionHintRequestStatisticRegisterTest extends assBaseTestCase -{ - protected $backupGlobals = false; - - private ilAssQuestionHintRequestStatisticRegister $object; - - protected function setUp(): void - { - parent::setUp(); - - $this->object = new ilAssQuestionHintRequestStatisticRegister(); - } - - public function testConstruct(): void - { - $this->assertInstanceOf(ilAssQuestionHintRequestStatisticRegister::class, $this->object); - } -} diff --git a/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintTest.php b/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintTest.php deleted file mode 100755 index a08a61058f30..000000000000 --- a/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintTest.php +++ /dev/null @@ -1,41 +0,0 @@ - -* -* @ingroup components\ILIASTestQuestionPool -*/ -class ilAssQuestionHintTest extends assBaseTestCase -{ - protected $backupGlobals = false; - - protected function setUp(): void - { - chdir(__DIR__ . '/../../../../'); - } - - public function test_instantiateObject_shouldReturnInstance(): void - { - $instance = new ilAssQuestionHint(); - - $this->assertInstanceOf(ilAssQuestionHint::class, $instance); - } -} diff --git a/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintTrackingTest.php b/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintTrackingTest.php deleted file mode 100755 index d0043ee0af15..000000000000 --- a/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintTrackingTest.php +++ /dev/null @@ -1,41 +0,0 @@ - -* -* @ingroup components\ILIASTestQuestionPool -*/ -class ilAssQuestionHintTrackingTest extends assBaseTestCase -{ - protected $backupGlobals = false; - - protected function setUp(): void - { - chdir(__DIR__ . '/../../../../'); - } - - public function test_instantiateObject_shouldReturnInstance(): void - { - $instance = new ilAssQuestionHintTracking(null, null, null); - - $this->assertInstanceOf(ilAssQuestionHintTracking::class, $instance); - } -} diff --git a/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintsGUITest.php b/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintsGUITest.php deleted file mode 100644 index 0efeaf45bcec..000000000000 --- a/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintsGUITest.php +++ /dev/null @@ -1,47 +0,0 @@ - -* -* @ingroup components\ILIASTestQuestionPool -* -* This test was automatically generated. -*/ -class ilAssQuestionHintsGUITest extends assBaseTestCase -{ - protected $backupGlobals = false; - - private ilAssQuestionHintsGUI $object; - - protected function setUp(): void - { - parent::setUp(); - - $assQuestionGUI = $this->createMock(assQuestionGUI::class); - - $this->object = new ilAssQuestionHintsGUI($assQuestionGUI); - } - - public function testConstruct(): void - { - $this->assertInstanceOf(ilAssQuestionHintsGUI::class, $this->object); - } -} diff --git a/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintsOrderingClipboardTest.php b/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintsOrderingClipboardTest.php deleted file mode 100644 index 91ea96862ea9..000000000000 --- a/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintsOrderingClipboardTest.php +++ /dev/null @@ -1,45 +0,0 @@ - -* -* @ingroup components\ILIASTestQuestionPool -* -* This test was automatically generated. -*/ -class ilAssQuestionHintsOrderingClipboardTest extends assBaseTestCase -{ - protected $backupGlobals = false; - - private ilAssQuestionHintsOrderingClipboard $object; - - protected function setUp(): void - { - parent::setUp(); - - $this->object = new ilAssQuestionHintsOrderingClipboard($this->createMock(assQuestion::class)); - } - - public function testConstruct(): void - { - $this->assertInstanceOf(ilAssQuestionHintsOrderingClipboard::class, $this->object); - } -} diff --git a/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintsTableGUITest.php b/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintsTableGUITest.php deleted file mode 100644 index f9ea8c2cc418..000000000000 --- a/components/ILIAS/TestQuestionPool/tests/ilAssQuestionHintsTableGUITest.php +++ /dev/null @@ -1,52 +0,0 @@ - -* -* @ingroup components\ILIASTestQuestionPool -* -* This test was automatically generated. -*/ -class ilAssQuestionHintsTableGUITest extends assBaseTestCase -{ - protected $backupGlobals = false; - - private ilAssQuestionHintsTableGUI $object; - - protected function setUp(): void - { - parent::setUp(); - - $questionOBJ = $this->createMock(assQuestion::class); - $questionHintList = $this->createMock(ilAssQuestionHintList::class); - $parentGUI = $this->createMock(ilAssQuestionHintAbstractGUI::class); - $parentCmd = ''; - $tableMode = ilAssQuestionHintsTableGUI::TBL_MODE_TESTOUTPUT; - $hintOrderingClipboard = $this->createMock(ilAssQuestionHintsOrderingClipboard::class); - - $this->object = new ilAssQuestionHintsTableGUI($questionOBJ, $questionHintList, $parentGUI, $parentCmd, $tableMode, $hintOrderingClipboard); - } - - public function testConstruct(): void - { - $this->assertInstanceOf(ilAssQuestionHintsTableGUI::class, $this->object); - } -} diff --git a/components/ILIAS/TestQuestionPool/tests/ilAssQuestionPreviewHintTrackingTest.php b/components/ILIAS/TestQuestionPool/tests/ilAssQuestionPreviewHintTrackingTest.php deleted file mode 100644 index 3173c9008ce5..000000000000 --- a/components/ILIAS/TestQuestionPool/tests/ilAssQuestionPreviewHintTrackingTest.php +++ /dev/null @@ -1,45 +0,0 @@ - -* -* @ingroup components\ILIASTestQuestionPool -* -* This test was automatically generated. -*/ -class ilAssQuestionPreviewHintTrackingTest extends assBaseTestCase -{ - protected $backupGlobals = false; - - private ilAssQuestionPreviewHintTracking $object; - - protected function setUp(): void - { - parent::setUp(); - - $this->object = new ilAssQuestionPreviewHintTracking($this->createMock(ilDBInterface::class), $this->createMock(ilAssQuestionPreviewSession::class)); - } - - public function testConstruct(): void - { - $this->assertInstanceOf(ilAssQuestionPreviewHintTracking::class, $this->object); - } -} diff --git a/components/ILIAS/TestQuestionPool/tests/ilAssQuestionSkillAssignmentRegistryTest.php b/components/ILIAS/TestQuestionPool/tests/ilAssQuestionSkillAssignmentRegistryTest.php index 05e55a91c9ed..bcbc3d2d8a1e 100755 --- a/components/ILIAS/TestQuestionPool/tests/ilAssQuestionSkillAssignmentRegistryTest.php +++ b/components/ILIAS/TestQuestionPool/tests/ilAssQuestionSkillAssignmentRegistryTest.php @@ -36,36 +36,36 @@ public function setUp(): void } /** - * @dataProvider serializedData * @param $value * @param $chunkSize * @param callable $preCallback * @param callable $postCallback */ + #[\PHPUnit\Framework\Attributes\DataProvider('serializedData')] public function testSkillAssignmentsCanBetStoredAndFetchedBySerializationStrategy($value, $chunkSize, callable $preCallback, callable $postCallback): void { $this->markTestSkipped('Data Provider needs to be revisited.'); $settingsMock = $this->getMockBuilder('ilSetting')->disableOriginalConstructor()->onlyMethods(['set', 'get', 'delete'])->getMock(); - $settingsMock->expects($this->any())->method('set')->will( - $this->returnCallback(function ($key, $value) { + $settingsMock->expects($this->any())->method('set')->willReturnCallback( + function ($key, $value) { $this->storage[$key] = $value; - }) + } ); - $settingsMock->expects($this->any())->method('get')->will( - $this->returnCallback(function ($key, $value) { + $settingsMock->expects($this->any())->method('get')->willReturnCallback( + function ($key, $value) { return $this->storage[$key] ?? $value; - }) + } ); - $settingsMock->expects($this->any())->method('delete')->will( - $this->returnCallback(function ($key, $value) { + $settingsMock->expects($this->any())->method('delete')->willReturnCallback( + function ($key) { if (isset($this->storage[$key])) { unset($this->storage[$key]); } - }) + } ); $valueToTest = $preCallback($value); @@ -79,9 +79,7 @@ public function testSkillAssignmentsCanBetStoredAndFetchedBySerializationStrateg $this->assertEquals($value, $postCallback($actual)); } - /** - * @doesNotPerformAssertions - */ + #[\PHPUnit\Framework\Attributes\DoesNotPerformAssertions] public function testInvalidChunkSizeWillRaiseException(): void { $settingsMock = $this->getMockBuilder('ilSetting')->disableOriginalConstructor()->onlyMethods(['set', 'get', 'delete'])->getMock(); @@ -117,11 +115,11 @@ protected static function getTestData(callable $pre, callable $post): array $assignment->setEvalMode(\ilAssQuestionSkillAssignment::EVAL_MODE_BY_QUESTION_SOLUTION); $assignment->setImportSkillTitle('phpunit' . $i); $assignment->setImportSkillPath('phpunit' . $i); - $random = new ilRandom(); - $assignment->setSkillPoints($random->int(0, PHP_INT_MAX)); - $assignment->setImportQuestionId($random->int(0, PHP_INT_MAX)); - $assignment->setImportSkillBaseId($random->int(0, PHP_INT_MAX)); - $assignment->setImportSkillTrefId($random->int(0, PHP_INT_MAX)); + $random = new \Random\Randomizer(); + $assignment->setSkillPoints($random->getInt(0, PHP_INT_MAX)); + $assignment->setImportQuestionId($random->getInt(0, PHP_INT_MAX)); + $assignment->setImportSkillBaseId($random->getInt(0, PHP_INT_MAX)); + $assignment->setImportSkillTrefId($random->getInt(0, PHP_INT_MAX)); $assignmentList->addAssignment($assignment); } diff --git a/components/ILIAS/Tracking/Tracking.php b/components/ILIAS/Tracking/Tracking.php index b597fb4fe297..d1d354693a78 100644 --- a/components/ILIAS/Tracking/Tracking.php +++ b/components/ILIAS/Tracking/Tracking.php @@ -36,7 +36,11 @@ public function init( array | \ArrayAccess &$pull, array | \ArrayAccess &$internal, ): void { - $contribute[SetupAgentInterface::class] = fn() => new SetupAgent($pull[Refinery::class]); + $contribute[\ILIAS\Setup\Agent::class] = static fn() => + new \ilTrackingSetupAgent( + $pull[\ILIAS\Refinery\Factory::class] + ); + $contribute[Component\Resource\PublicAsset::class] = fn() => new Component\Resource\ComponentJS($this, "ilObjStat.js"); } diff --git a/components/ILIAS/Tracking/classes/Setup/class.ilTrackingSetupAgent.php b/components/ILIAS/Tracking/classes/Setup/class.ilTrackingSetupAgent.php new file mode 100644 index 000000000000..72edc0fb43bf --- /dev/null +++ b/components/ILIAS/Tracking/classes/Setup/class.ilTrackingSetupAgent.php @@ -0,0 +1,35 @@ +db = $db; } + /** + * Remove the unused table 'catch_write_events' + */ public function step_1(): void { - if ($this->db->tableExists('settings')) { - $query = 'DELETE FROM settings WHERE module = ' . $this->db->quote('login_settings', 'text') - . ' AND ' . $this->db->like('keyword', ilDBConstants::T_TEXT, 'login_message_%'); - - $this->db->manipulate($query); + if ($this->db->tableExists('catch_write_events')) { + $this->db->dropTable('catch_write_events'); } } } diff --git a/components/ILIAS/Tracking/classes/class.ilChangeEvent.php b/components/ILIAS/Tracking/classes/class.ilChangeEvent.php index 4f60b7e1de5c..9e0443783e22 100755 --- a/components/ILIAS/Tracking/classes/class.ilChangeEvent.php +++ b/components/ILIAS/Tracking/classes/class.ilChangeEvent.php @@ -147,7 +147,7 @@ public static function _recordReadEvent( "integer" ) . ", "; $read_count_init = max(1, (int) $a_ext_rc); - $read_count_diff = max(1, (int) $a_ext_rc) - $row->read_count; + $read_count_diff = max(1, (int) $a_ext_rc) - (int) ($row?->read_count ?? 0); } else { $read_count = 'read_count = read_count + 1, '; $read_count_init = 1; @@ -532,39 +532,16 @@ function (ilDBInterface $ilDB) use ($a_now, $a_minimum) { * @param $usr_id int The user. * @param $timestamp string|null timestamp. * @return void + * + * This method was only used to write to catch_write_events, + * but the methods reading out that table are not used anywhere. + * @deprecated, will be removed with ILIAS 11 */ public static function _catchupWriteEvents( int $obj_id, int $usr_id, ?string $timestamp = null ): void { - global $DIC; - - $ilDB = $DIC['ilDB']; - - $query = "SELECT obj_id FROM catch_write_events " . - "WHERE obj_id = " . $ilDB->quote($obj_id, 'integer') . " " . - "AND usr_id = " . $ilDB->quote($usr_id, 'integer'); - $res = $ilDB->query($query); - if ($res->numRows()) { - $ts = ($timestamp == null) - ? ilUtil::now() - : $timestamp; - } else { - $ts = ilUtil::now(); - } - - // alex, use replace due to bug #10406 - $ilDB->replace( - "catch_write_events", - array( - "obj_id" => array("integer", $obj_id), - "usr_id" => array("integer", $usr_id) - ), - array( - "ts" => array("timestamp", $ts) - ) - ); } /** @@ -573,52 +550,16 @@ public static function _catchupWriteEvents( * @param $obj_id int The object * @param $usr_id int The user who is interested into these events. * @return array with rows from table write_event + * + * This method is unused, so the table it read from was + * removed for performance reasons. + * @deprecated, will be removed with ILIAS 11 */ public static function _lookupUncaughtWriteEvents( int $obj_id, int $usr_id ): array { - global $DIC; - - $ilDB = $DIC['ilDB']; - $q = "SELECT ts " . - "FROM catch_write_events " . - "WHERE obj_id=" . $ilDB->quote($obj_id, 'integer') . " " . - "AND usr_id=" . $ilDB->quote($usr_id, 'integer'); - $r = $ilDB->query($q); - $catchup = null; - while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) { - $catchup = $row['ts']; - } - - if ($catchup == null) { - $query = sprintf( - 'SELECT * FROM write_event ' . - 'WHERE obj_id = %s ' . - 'AND usr_id <> %s ' . - 'ORDER BY ts DESC', - $ilDB->quote($obj_id, 'integer'), - $ilDB->quote($usr_id, 'integer') - ); - $res = $ilDB->query($query); - } else { - $query = sprintf( - 'SELECT * FROM write_event ' . - 'WHERE obj_id = %s ' . - 'AND usr_id <> %s ' . - 'AND ts >= %s ' . - 'ORDER BY ts DESC', - $ilDB->quote($obj_id, 'integer'), - $ilDB->quote($usr_id, 'integer'), - $ilDB->quote($catchup, 'timestamp') - ); - $res = $ilDB->query($query); - } - $events = array(); - while ($row = $ilDB->fetchAssoc($res)) { - $events[] = $row; - } - return $events; + return []; } /** @@ -629,56 +570,14 @@ public static function _lookupUncaughtWriteEvents( * @return int 0 = object is unchanged, * 1 = object is new, * 2 = object has changed + * + * This method is unused, so the table it read from was + * removed for performance reasons. + * @deprecated, will be removed with ILIAS 11 */ public static function _lookupChangeState(int $obj_id, int $usr_id): int { - global $DIC; - - $ilDB = $DIC['ilDB']; - - $q = "SELECT ts " . - "FROM catch_write_events " . - "WHERE obj_id=" . $ilDB->quote($obj_id, 'integer') . " " . - "AND usr_id=" . $ilDB->quote($usr_id, 'integer'); - $r = $ilDB->query($q); - $catchup = null; - while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) { - $catchup = $row['ts']; - } - - if ($catchup == null) { - $ilDB->setLimit(1); - $query = sprintf( - 'SELECT * FROM write_event ' . - 'WHERE obj_id = %s ' . - 'AND usr_id <> %s ', - $ilDB->quote($obj_id, 'integer'), - $ilDB->quote($usr_id, 'integer') - ); - $res = $ilDB->query($query); - } else { - $ilDB->setLimit(1); - $query = sprintf( - 'SELECT * FROM write_event ' . - 'WHERE obj_id = %s ' . - 'AND usr_id <> %s ' . - 'AND ts > %s ', - $ilDB->quote($obj_id, 'integer'), - $ilDB->quote($usr_id, 'integer'), - $ilDB->quote($catchup, 'timestamp') - ); - $res = $ilDB->query($query); - } - - $numRows = $res->numRows(); - if ($numRows > 0) { - $row = $ilDB->fetchAssoc($res); - // if we have write events, and user never catched one, report as new (1) - // if we have write events, and user catched an old write event, report as changed (2) - return ($catchup == null) ? 1 : 2; - } else { - return 0; // user catched all write events, report as unchanged (0) - } + return 0; } /** diff --git a/components/ILIAS/Tracking/classes/class.ilLPTableBaseGUI.php b/components/ILIAS/Tracking/classes/class.ilLPTableBaseGUI.php index 4bccab7826a0..8cfb53962f48 100755 --- a/components/ILIAS/Tracking/classes/class.ilLPTableBaseGUI.php +++ b/components/ILIAS/Tracking/classes/class.ilLPTableBaseGUI.php @@ -411,22 +411,11 @@ public function initBaseFilter( if ($this->filter["hide"]) { // create options from current value $types = $this->getCurrentFilter(true); - $type = $types["type"]; - $options = array(); - if ($type == 'lres') { - $type = array('lm', 'sahs', 'htlm'); - } else { - $type = array($type); - } + $options = []; foreach ($this->filter["hide"] as $obj_id) { - if (in_array( - $this->ilObjDataCache->lookupType((int) $obj_id), - $type - )) { - $options[$obj_id] = $this->ilObjDataCache->lookupTitle( - (int) $obj_id - ); - } + $options[$obj_id] = $this->ilObjDataCache->lookupTitle( + (int) $obj_id + ); } $msi->setOptions($options); } @@ -531,6 +520,7 @@ protected function getPossibleTypes( $options['cmix'] = $this->lng->txt('objs_cmix'); $options['lti'] = $this->lng->txt('objs_lti'); $options['lso'] = $this->lng->txt('objs_lso'); + $options['dcl'] = $this->lng->txt('objs_dcl'); if ($a_allow_undefined_lp) { $options['root'] = $this->lng->txt('obj_reps'); diff --git a/components/ILIAS/Tracking/classes/class.ilLearningProgressGUI.php b/components/ILIAS/Tracking/classes/class.ilLearningProgressGUI.php index 763ee4e864c7..b16e1dc78bed 100755 --- a/components/ILIAS/Tracking/classes/class.ilLearningProgressGUI.php +++ b/components/ILIAS/Tracking/classes/class.ilLearningProgressGUI.php @@ -129,8 +129,13 @@ public function executeCommand() public function __setCmdClass(string $a_class): void { - //$this->ctrl->setCmdClass($a_class); - if (strtolower($this->ctrl->getCmdClass()) !== strtolower($a_class)) { + /** + * TODO: maybe this can be replaced by nextClass === ''? + */ + if ( + strtolower($this->ctrl->getCmdClass()) !== strtolower($a_class) && + strtolower($this->ctrl->getNextClass()) !== strtolower($a_class) + ) { $this->ctrl->redirectByClass($a_class); } } diff --git a/components/ILIAS/Tracking/classes/class.ilTrackingAppEventListener.php b/components/ILIAS/Tracking/classes/class.ilTrackingAppEventListener.php index 94f221de144a..fe68dcf63c3d 100755 --- a/components/ILIAS/Tracking/classes/class.ilTrackingAppEventListener.php +++ b/components/ILIAS/Tracking/classes/class.ilTrackingAppEventListener.php @@ -39,7 +39,7 @@ public static function handleEvent( ): void { $obj_id = $a_parameter['obj_id'] ?? null; switch ($a_component) { - case 'components/ILIAS/Object': + case 'components/ILIAS/ILIASObject': switch ($a_event) { case 'toTrash': $olp = ilObjectLP::getInstance($obj_id); diff --git a/components/ILIAS/Tracking/classes/collection/class.ilLPCollectionOfRepositoryObjects.php b/components/ILIAS/Tracking/classes/collection/class.ilLPCollectionOfRepositoryObjects.php index 3e17af335ed8..f374c2f25002 100755 --- a/components/ILIAS/Tracking/classes/collection/class.ilLPCollectionOfRepositoryObjects.php +++ b/components/ILIAS/Tracking/classes/collection/class.ilLPCollectionOfRepositoryObjects.php @@ -270,20 +270,32 @@ public static function hasGroupedItems(int $a_obj_id): bool return $res->numRows() ? true : false; } - protected function getGroupingIds(array $a_item_ids): array + protected function getNonGroupedItems(array $a_item_ids): array { - global $DIC; + $grouped_item_ids = []; - $ilDB = $DIC['ilDB']; + $query = "SELECT item_id FROM ut_lp_collections" . + " WHERE obj_id = " . $this->db->quote($this->obj_id, ilDBConstants::T_INTEGER) . + " AND " . $this->db->in("item_id", $a_item_ids, false, ilDBConstants::T_INTEGER) . + " AND grouping_id > " . $this->db->quote(0, ilDBConstants::T_INTEGER); + $res = $this->db->query($query); + while ($row = $res->fetchObject()) { + $grouped_item_ids[] = $row->item_id; + } - $grouping_ids = array(); + return array_diff($a_item_ids, $grouped_item_ids); + } + + protected function getGroupingIds(array $a_item_ids): array + { + $grouping_ids = []; $query = "SELECT grouping_id FROM ut_lp_collections" . - " WHERE obj_id = " . $this->db->quote($this->obj_id, "integer") . - " AND " . $this->db->in("item_id", $a_item_ids, false, "integer") . - " AND grouping_id > " . $this->db->quote(0, "integer"); + " WHERE obj_id = " . $this->db->quote($this->obj_id, ilDBConstants::T_INTEGER) . + " AND " . $this->db->in("item_id", $a_item_ids, false, ilDBConstants::T_INTEGER) . + " AND grouping_id > " . $this->db->quote(0, ilDBConstants::T_INTEGER); $res = $this->db->query($query); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { + while ($row = $res->fetchObject()) { $grouping_ids[] = $row->grouping_id; } @@ -311,7 +323,9 @@ public function deactivateEntries(array $a_item_ids): void public function activateEntries(array $a_item_ids): void { - parent::activateEntries($a_item_ids); + // 44683: only activate non-grouped items via parent + $non_grouped_ids = $this->getNonGroupedItems($a_item_ids); + parent::activateEntries($non_grouped_ids); $grouping_ids = $this->getGroupingIds($a_item_ids); if ($grouping_ids) { diff --git a/components/ILIAS/Tracking/classes/collection/class.ilLPCollectionOfSCOs.php b/components/ILIAS/Tracking/classes/collection/class.ilLPCollectionOfSCOs.php index 846afa5594ae..4b330a86e4a1 100755 --- a/components/ILIAS/Tracking/classes/collection/class.ilLPCollectionOfSCOs.php +++ b/components/ILIAS/Tracking/classes/collection/class.ilLPCollectionOfSCOs.php @@ -180,8 +180,8 @@ protected function itemsAreEqual(int $item_a_id, int $item_b_id): bool return ( $res_a && $res_b - && ($res_a['import_id'] == $res_b['import_id']) - && ($res_a['identifierref'] == $res_b['identifierref']) + && ($res_a['id'] == $res_b['id']) + && ($res_a['resourceid'] == $res_b['resourceid']) ); default: return false; diff --git a/components/ILIAS/Tracking/classes/object_statistics/class.ilLPObjectStatisticsTableGUI.php b/components/ILIAS/Tracking/classes/object_statistics/class.ilLPObjectStatisticsTableGUI.php index 4f0a8c16db61..130691ee9cf1 100755 --- a/components/ILIAS/Tracking/classes/object_statistics/class.ilLPObjectStatisticsTableGUI.php +++ b/components/ILIAS/Tracking/classes/object_statistics/class.ilLPObjectStatisticsTableGUI.php @@ -113,7 +113,7 @@ public function getSelectableColumns(): array 'txt' => $this->lng->txt('trac_reference_ids_column'), 'default' => false, 'optional' => true, - 'sortable' => true, + 'sortable' => false, 'width' => '5%' ]; $columns['paths'] = [ diff --git a/components/ILIAS/Tracking/classes/repository_statistics/class.ilLPListOfSettingsGUI.php b/components/ILIAS/Tracking/classes/repository_statistics/class.ilLPListOfSettingsGUI.php index 4ab3edd2e7f9..fad99d179ed4 100755 --- a/components/ILIAS/Tracking/classes/repository_statistics/class.ilLPListOfSettingsGUI.php +++ b/components/ILIAS/Tracking/classes/repository_statistics/class.ilLPListOfSettingsGUI.php @@ -555,6 +555,10 @@ protected function handleLPUsageInfo(): string $params["gotolp"] = 1; } + $tpl->setVariable("EXPAND_GLYPH", $this->ui_renderer->render( + $this->ui_factory->symbol()->glyph()->expand()->withUnavailableAction() + )); + if ($this->access->checkAccess("read", "", $parent_ref_id) && $parent_ref_id != $ref_id) { // #17170 $tpl->setCurrentBlock("parent_link_bl"); diff --git a/components/ILIAS/Tracking/classes/repository_statistics/class.ilTrMatrixTableGUI.php b/components/ILIAS/Tracking/classes/repository_statistics/class.ilTrMatrixTableGUI.php index 3eb32fc9f847..c96891c5abc4 100755 --- a/components/ILIAS/Tracking/classes/repository_statistics/class.ilTrMatrixTableGUI.php +++ b/components/ILIAS/Tracking/classes/repository_statistics/class.ilTrMatrixTableGUI.php @@ -160,6 +160,7 @@ public function __construct( ); } $this->setExportFormats(array(self::EXPORT_CSV, self::EXPORT_EXCEL)); + $this->setSelectAllCheckbox('uid'); } public function initFilter(): void diff --git a/components/ILIAS/Tracking/classes/repository_statistics/class.ilTrObjectUsersPropsTableGUI.php b/components/ILIAS/Tracking/classes/repository_statistics/class.ilTrObjectUsersPropsTableGUI.php index 02866a331b8b..56ba89d466d3 100755 --- a/components/ILIAS/Tracking/classes/repository_statistics/class.ilTrObjectUsersPropsTableGUI.php +++ b/components/ILIAS/Tracking/classes/repository_statistics/class.ilTrObjectUsersPropsTableGUI.php @@ -535,8 +535,8 @@ protected function fillRowExcel( ): void { $cnt = 0; foreach ($this->getSelectedColumns() as $c) { - if ($c != 'status') { - $val = $this->parseValue($c, $a_set[$c], $this->type); + if ($c !== 'status') { + $val = $this->parseValue($c, $a_set[$c] ?? null, $this->type); } else { $val = ilLearningProgressBaseGUI::_getStatusText( (int) $a_set[$c] @@ -559,8 +559,8 @@ protected function fillHeaderCSV(ilCSVWriter $a_csv): void protected function fillRowCSV(ilCSVWriter $a_csv, array $a_set): void { foreach ($this->getSelectedColumns() as $c) { - if ($c != 'status') { - $val = $this->parseValue($c, $a_set[$c], $this->type); + if ($c !== 'status') { + $val = $this->parseValue($c, $a_set[$c] ?? null, $this->type); } else { $val = ilLearningProgressBaseGUI::_getStatusText( (int) $a_set[$c] diff --git a/components/ILIAS/Tracking/classes/status/class.ilLPStatusCollection.php b/components/ILIAS/Tracking/classes/status/class.ilLPStatusCollection.php index 0e302da0a907..0ab1a4b6458a 100755 --- a/components/ILIAS/Tracking/classes/status/class.ilLPStatusCollection.php +++ b/components/ILIAS/Tracking/classes/status/class.ilLPStatusCollection.php @@ -455,6 +455,15 @@ protected static function getMembers(int $a_obj_id): array ); return $member_obj->getMembers(); } + if ($grp_id = $tree->checkForParentType( + $folder_ref_id, + 'grp' + )) { + $member_obj = ilGroupParticipants::_getInstanceByObjId( + ilObject::_lookupObjId($grp_id) + ); + return $member_obj->getMembers(); + } break; case 'lso': diff --git a/components/ILIAS/Tracking/service.xml b/components/ILIAS/Tracking/service.xml index 9803e32764fe..af13202ac075 100755 --- a/components/ILIAS/Tracking/service.xml +++ b/components/ILIAS/Tracking/service.xml @@ -11,7 +11,7 @@ - + diff --git a/components/ILIAS/Tracking/templates/default/tpl.lp_obj_settings_tree_info.html b/components/ILIAS/Tracking/templates/default/tpl.lp_obj_settings_tree_info.html index d291227b9d70..1a83a8c7eea0 100755 --- a/components/ILIAS/Tracking/templates/default/tpl.lp_obj_settings_tree_info.html +++ b/components/ILIAS/Tracking/templates/default/tpl.lp_obj_settings_tree_info.html @@ -1,6 +1,6 @@
- » + {EXPAND_GLYPH} {PARENT_TYPE_ALT} {PARENT_NOLINK_TITLE} diff --git a/components/ILIAS/Tracking/tests/ilLPStatusIconsTest.php b/components/ILIAS/Tracking/tests/ilLPStatusIconsTest.php index 5f68dedf5ab2..1181ee365682 100755 --- a/components/ILIAS/Tracking/tests/ilLPStatusIconsTest.php +++ b/components/ILIAS/Tracking/tests/ilLPStatusIconsTest.php @@ -117,9 +117,9 @@ public function testGetInstanceForInvalidVariant(): void } /** - * @depends testTripleton * @param array $instances */ + #[\PHPUnit\Framework\Attributes\Depends('testTripleton')] public function testSomeExamplesForImagePathsByStatus(array $instances): void { $path1 = $instances['long']->getImagePathInProgress(); @@ -136,9 +136,9 @@ public function testSomeExamplesForImagePathsByStatus(array $instances): void } /** - * @depends testTripleton * @param array $instances */ + #[\PHPUnit\Framework\Attributes\Depends('testTripleton')] public function testImagePathRunningForLongVariant(array $instances): void { $this->expectException(ilLPException::class); @@ -146,9 +146,9 @@ public function testImagePathRunningForLongVariant(array $instances): void } /** - * @depends testTripleton * @param array $instances */ + #[\PHPUnit\Framework\Attributes\Depends('testTripleton')] public function testImagePathAssetForLongVariant(array $instances): void { $this->expectException(ilLPException::class); @@ -156,9 +156,9 @@ public function testImagePathAssetForLongVariant(array $instances): void } /** - * @depends testTripleton * @param array $instances */ + #[\PHPUnit\Framework\Attributes\Depends('testTripleton')] public function testSomeExamplesForRenderedIcons(array $instances): void { //try rendering some icons @@ -178,9 +178,9 @@ public function testSomeExamplesForRenderedIcons(array $instances): void } /** - * @depends testTripleton * @param array $instances */ + #[\PHPUnit\Framework\Attributes\Depends('testTripleton')] public function testRenderScormIcons(array $instances): void { $this->expectException(ilLPException::class); diff --git a/components/ILIAS/Tree/classes/class.ilPathGUI.php b/components/ILIAS/Tree/classes/class.ilPathGUI.php index 801b9756080a..f932b4874749 100755 --- a/components/ILIAS/Tree/classes/class.ilPathGUI.php +++ b/components/ILIAS/Tree/classes/class.ilPathGUI.php @@ -224,8 +224,14 @@ protected function buildTitle(int $a_obj_id): string protected function buildLink(int $ref_id, string $type): string { if ($this->objectDefinition->isAdministrationObject($type)) { + $current_parameters = $this->ctrl->getParameterArrayByClass(ilAdministrationGUI::class); $this->ctrl->setParameterByClass(ilAdministrationGUI::class, 'ref_id', $ref_id); - return $this->ctrl->getLinkTargetByClass(ilAdministrationGUI::class, 'jump'); + $link = $this->ctrl->getLinkTargetByClass(ilAdministrationGUI::class, 'jump'); + $this->ctrl->clearParameterByClass(ilAdministrationGUI::class, 'ref_id'); + if (isset($current_parameters['ref_id'])) { + $this->ctrl->setParameterByClass(ilAdministrationGUI::class, 'ref_id', $current_parameters['ref_id']); + } + return $link; } return $this->refinery->encode()->htmlAttributeValue()->transform( ilLink::_getLink($ref_id, $type) diff --git a/components/ILIAS/UI/UI.php b/components/ILIAS/UI/UI.php index 23e9f72afb30..af7500f10103 100644 --- a/components/ILIAS/UI/UI.php +++ b/components/ILIAS/UI/UI.php @@ -165,6 +165,8 @@ public function init( $internal[UI\Implementation\Component\Prompt\Factory::class]; $provide[UI\Implementation\Component\Prompt\State\Factory::class] = static fn() => $internal[UI\Implementation\Component\Prompt\State\Factory::class]; + $provide[UI\Implementation\Component\Input\UploadLimitResolver::class] = static fn() => + $internal[UI\Implementation\Component\Input\UploadLimitResolver::class]; // ================================================================================= $internal[UI\Implementation\Factory::class] = static fn() => @@ -591,7 +593,7 @@ public function init( $contribute[Component\Resource\PublicAsset::class] = fn() => new Component\Resource\OfComponent($this, "ui-examples", "assets"); $contribute[Component\Resource\PublicAsset::class] = static fn() => - new Component\Resource\NodeModule("@yaireo/tagify/dist/tagify.min.js"); + new Component\Resource\NodeModule("@yaireo/tagify/dist/tagify.js"); $contribute[Component\Resource\PublicAsset::class] = static fn() => new Component\Resource\NodeModule("@yaireo/tagify/dist/tagify.css"); $contribute[Component\Resource\PublicAsset::class] = static fn() => @@ -602,6 +604,8 @@ public function init( new Component\Resource\ComponentJS($this, "js/MainControls/dist/footer.min.js"); $contribute[Component\Resource\PublicAsset::class] = fn() => new Component\Resource\ComponentJS($this, "js/Input/ViewControl/dist/input.viewcontrols.min.js"); + $contribute[Component\Resource\PublicAsset::class] = fn() => + new Component\Resource\ComponentJS($this, "js/MathJax/mathjax_config.js"); /* those are contributed by MediaObjects @@ -619,6 +623,26 @@ public function init( new Component\Resource\NodeModule("webui-popover/dist/jquery.webui-popover.min.js"); */ + // This is included via anonymous classes + // because MathJax resources are taken from node_modules and they may be directories + foreach (['tex-chtml-full.js', 'a11y', 'adaptors', 'input', 'output', 'sre', 'ui'] as $asset) { + $contribute[Component\Resource\PublicAsset::class] = static fn( + ) => new readonly class ($asset) implements Component\Resource\PublicAsset { + public function __construct(private string $asset) + { + } + + public function getSource(): string + { + return 'node_modules/mathjax/es5/' . $this->asset; + } + + public function getTarget(): string + { + return 'node_modules/mathjax/es5/' . $this->asset; + } + }; + }; // This is included via anonymous classes as a testament to the fact, that // the templates-folder should probably be moved to some component. diff --git a/components/ILIAS/UI/docs/ROADMAP.md b/components/ILIAS/UI/docs/ROADMAP.md index a9deebf4a9c0..6b081487dd96 100755 --- a/components/ILIAS/UI/docs/ROADMAP.md +++ b/components/ILIAS/UI/docs/ROADMAP.md @@ -12,12 +12,12 @@ are explained in [Usage](#usage). ## Short Term ### Outer Content (advanced, variable) -This Component is basically what could be hooked into the [Standard Layout](Component/Layout/Page/Factory.php) as +This Component is basically what could be hooked into the [Standard Layout](../src/Component/Layout/Page/Factory.php) as content (currently provided as array of Legacy Components). Most Probably it should be able to hold the title section (not yet part of the UI Components, see below), the Tabs (not yet Part of the UI Components, see below) and the Inner Content holding the workspace for the current context (not yet Part of the UI Components, see below). -Note; One important aspect here, will be to clarify at some point the relation to the [Global Screen](../GlobalScreen). +Note; One important aspect here, will be to clarify at some point the relation to the [Global Screen](../../GlobalScreen). ### Title Section (advanced, variable) This Component will probably hold the Icon, title, description and the actions (maybe along with the used glyphs) of the @@ -27,7 +27,7 @@ when to provide an Icon, restrictions of the Title (lengths, nouns vs verbs etc. subjects: [Feature Wiki](https://docu.ilias.de/goto_docu_wiki_wpage_6080_1357.html). However, this has not been decided yet and is thus most certainly up for discussion. -Note; One important aspect here, will be to clarify at some point the relation to the [Global Screen](../GlobalScreen). +Note; One important aspect here, will be to clarify at some point the relation to the [Global Screen](../../GlobalScreen). The need for a title section component was also discovered while discussing [this PR for the Test & Assessment kiosk mode header](https://github.com/ILIAS-eLearning/ILIAS/pull/7311). This shows that there are use cases beyond the content page title and actions that should be taken into account. Maybe the UI Entity could serve as inspiration for how properties and actions in a title sections could be arranged with effective visual weighting by relevance and semantic grouping. @@ -37,7 +37,7 @@ Tabs and Sub Tabs (noun vs verbs, length, amount of words etc.) and rules for th in Forms shown in Tabs. Also, one would have to look into the issue that currently "<-- Back" actions are mixed into the Tabs. We will need to decide, whether we will still use this concept in the future. -Note; One important aspect here, will be to clarify at some point the relation to the [Global Screen](../GlobalScreen). +Note; One important aspect here, will be to clarify at some point the relation to the [Global Screen](../../GlobalScreen). ### Inner Content This will most probably mainly contain an array of Components used in the Content Section. An interesting @@ -251,6 +251,21 @@ This should be updated to e.g. an event listener attached to the document, which either calls the dropdown's show method if the event target is the desired dropdown, or the hide method if the target is something else. +### Remove jQuery from NotificationItem and Counter + +The notification-item and counter UI components heavily depend on jQuery, which makes +them impossible to (unit) test. During the migration of the JavaScript unit tests to the +Node.js environment, we needed to disable their unit tests for this reason. The issue +can be resolved by removing jQuery from these components altogether, as the use of jQuery +is prohibited by our code-style anyway. + +### Implement Toast as ES6 module + +The toast component is not yet implemented as an ES6 module, which makes it impossible to +test. During the migration of the JavaScript unit tests to the Node.js environment, we +needed to disable these unit tests for this reason. The issue can be resolved by implementing +the component as an ES6 module, which can be imported using ES6 import specifiers. + ## Long Term ### Mark Some Components as Internal diff --git a/components/ILIAS/UI/docs/ilias-arrows.md b/components/ILIAS/UI/docs/ilias-arrows.md index a801361cfd6c..1d93a10f9ee2 100644 --- a/components/ILIAS/UI/docs/ilias-arrows.md +++ b/components/ILIAS/UI/docs/ilias-arrows.md @@ -1,29 +1,43 @@ -In this paper, we examine the different types of arrow icons used in ILIAS and explore whether we can reduce their quantity. Our aim is to improve the user experience by organizing the arrows based on their functions and simplifying their variety to create a more consistent appearance across the platform. Additionally, our goal is to make the styling of the system easier, more efficient, and thorough. +In this paper, we examine the different types of arrow icons used in ILIAS and explore whether we can reduce their +quantity. Our aim is to improve the user experience by organizing the arrows based on their functions and simplifying +their variety to create a more consistent appearance across the platform. Additionally, our goal is to make the styling +of the system easier, more efficient, and thorough. # Issues with the current arrow types and their usage -As you navigate ILIAS, you'll come across different arrow icons in various spots, such as the main bar, breadcrumb menu, and different buttons like the action button. As we delved deeper, we discovered even more instances where arrows are used, prompting us to question their necessity. +As you navigate ILIAS, you'll come across different arrow icons in various spots, such as the main bar, breadcrumb menu, +and different buttons like the action button. As we delved deeper, we discovered even more instances where arrows are +used, prompting us to question their necessity. -By pinpointing and organizing these specific uses of arrows and taking usability into account, we observed that many arrows serve similar functions but appear differently. This lack of consistency can be confusing for users. Additionally, we examined how these different arrow types are implemented. +By pinpointing and organizing these specific uses of arrows and taking usability into account, we observed that many +arrows serve similar functions but appear differently. This lack of consistency can be confusing for users. +Additionally, we examined how these different arrow types are implemented. # Group 1: Navigation arrows + ## Test navigation -In the test area of ILIAS where you can navigate between the test content and questions you can find these navigation buttons with the following arrow type: +In the test area of ILIAS where you can navigate between the test content and questions you can find these navigation +buttons with the following arrow type: ![test-nav](https://files.ilias.de/images/arrows/test-navigation.png) -**Some thoughts:** The arrow icon currently used is quite bulky, taking up a significant amount of space within the button area. This disrupts the balance between the label and the icon, creating a visual imbalance. Overall, it attracts undue attention to the button, potentially diverting the user's focus away from the test content. +**Some thoughts:** The arrow icon currently used is quite bulky, taking up a significant amount of space within the +button area. This disrupts the balance between the label and the icon, creating a visual imbalance. Overall, it attracts +undue attention to the button, potentially diverting the user's focus away from the test content. **Integration:** This arrow is embedded via font means as a glyphicon. ## Survey navigation -In the survey area of ILIAS where you can navigate between questions you can find these navigation buttons with the following arrow type: +In the survey area of ILIAS where you can navigate between questions you can find these navigation buttons with the +following arrow type: ![survey](https://files.ilias.de/images/arrows/survey-navigation.png) -**Some thoughts:** The arrow icon appears rather static and outdated in terms of its look and feel, likely because it's embedded in the HTML as a value-text. While both icons aim to assist navigation between questions, they are displayed quite differently from the test navigation icon. +**Some thoughts:** The arrow icon appears rather static and outdated in terms of its look and feel, likely because it's +embedded in the HTML as a value-text. While both icons aim to assist navigation between questions, they are displayed +quite differently from the test navigation icon. **Integration:** This arrow is embedded via HTML value. @@ -33,53 +47,69 @@ When you open up a lightbox to navigate between pictures you can find these navi ![lightbox](https://files.ilias.de/images/arrows/lightbox-navigation.png) -**Some thoughts:** This arrow type is simple and easily recognizable by the user when it comes to its simple purpose which the icon needs to fulfill. The icon is minimalist yet easy to understand, without using embellishments such as duplicating itself or giving it an extra line in the middle. +**Some thoughts:** This arrow type is simple and easily recognizable by the user when it comes to its simple purpose +which the icon needs to fulfill. The icon is minimalist yet easy to understand, without using embellishments such as +duplicating itself or giving it an extra line in the middle. **Integration:** This arrow is embedded via font means as a glyphicon. ## Viewcontrol navigation (example: calendar) -To navigate between certain informations such as the dates in a calendar you can find the following arrow which is very similar to the arrow in the lightbox navigation: +To navigate between certain informations such as the dates in a calendar you can find the following arrow which is very +similar to the arrow in the lightbox navigation: ![viewcontrol](https://files.ilias.de/images/arrows/viewcontrol-navigation-calendar.png) -**Some thoughts:** Using a minimalistic and straightforward arrow icon for the view controls is a smart choice, ensuring it aligns seamlessly with other arrow types in the view controls area, like the dropdown icon. +**Some thoughts:** Using a minimalistic and straightforward arrow icon for the view controls is a smart choice, ensuring +it aligns seamlessly with other arrow types in the view controls area, like the dropdown icon. **Integration:** This arrow is embedded via font means as a glyphicon. ## Results and recommendation for navigation arrows -This group can easily use a single arrow icon since all arrows are for navigation. Unlike the test and survey icons that take up a lot of space inside the button or compete with the button label, the lightbox and view controls use a simple arrow that works well everywhere. The recommendation for this group of arrows is to replace the test and survey arrows with the sleek arrow used in the lightbox and view controls sections. +This group can easily use a single arrow icon since all arrows are for navigation. Unlike the test and survey icons that +take up a lot of space inside the button or compete with the button label, the lightbox and view controls use a simple +arrow that works well everywhere. The recommendation for this group of arrows is to replace the test and survey arrows +with the sleek arrow used in the lightbox and view controls sections. # Group 2: sortation + ## Descending and ascending sortation -In tables you have the opportunity to sort your results. The arrows direction — pointing up for ascending and down for descending — clearly indicates the sorting order. The following icon is being discussed: +In tables you have the opportunity to sort your results. The arrows direction — pointing up for ascending and down for +descending — clearly indicates the sorting order. The following icon is being discussed: ![sortation1](https://files.ilias.de/images/arrows/sortation-descending-ascending.png) -**Some thoughts:** Clearly indicating to the user the sorting direction is a crucial and intuitive feature and also an important information that should be retained. The arrow effectively represents its intended function. +**Some thoughts:** Clearly indicating to the user the sorting direction is a crucial and intuitive feature and also an +important information that should be retained. The arrow effectively represents its intended function. **Integration:** This arrow is embedded via font means as a glyphicon. ## Sorting alphabetically and by location -In tables and menues you also have the opportunity to sort your results by alphabet or by location. The following icon is being discussed: +In tables and menues you also have the opportunity to sort your results by alphabet or by location. The following icon +is being discussed: ![sortation2](https://files.ilias.de/images/arrows/sortation-alphabetically.png) -**Some thoughts:** This type of arrow doesn't require a specific direction, so the icon should be more adaptable, as it currently is. The arrow effectively represents its intended function. +**Some thoughts:** This type of arrow doesn't require a specific direction, so the icon should be more adaptable, as it +currently is. The arrow effectively represents its intended function. **Integration:** The arrow is embedded via font. Stylings used: content: "⇵"; ## Results and recommendation for the sortation arrows -Both icons should maintain their functionalities, whether indicating sorting direction or serving a more general purpose. A noticeable difference between the two icons is their distinct styling. While the first icon appears bulky and prominent, the second is delicate and slim. The styling should be harmonized to maintain a consistent look and feel. +Both icons should maintain their functionalities, whether indicating sorting direction or serving a more general +purpose. A noticeable difference between the two icons is their distinct styling. While the first icon appears bulky and +prominent, the second is delicate and slim. The styling should be harmonized to maintain a consistent look and feel. # Group 3: Trees and hierarchy arrows + ## Breadcrumb -When the goal is to display a clickable path and indicate the user's navigation history, breadcrumbs are used. The following arrow icon displays the direction for it: +When the goal is to display a clickable path and indicate the user's navigation history, breadcrumbs are used. The +following arrow icon displays the direction for it: ![breadcrumb](https://files.ilias.de/images/arrows/breadcrumb.png) @@ -88,11 +118,12 @@ Stylings used: font-family: "il-icons"; content: "\e606"; ## Tree view -Tree views also present a clickable path and illustrate hierarchy, but here the direction is downward rather than to the right. +Tree views also present a clickable path and illustrate hierarchy, but here the direction is downward rather than to the +right. ![tree](https://files.ilias.de/images/arrows/tree-view.png) -**Integration:** This arrow is embedded via font means as a glyphicon +**Integration:** This arrow is embedded via font means as a glyphicon Stylings used: font-family: "il-icons"; content: "\e604"; ## Other cases @@ -105,16 +136,20 @@ Here you can see that there are some other cases where a hierarchy or a clickabl ## Results and recommendation for hierarchy/tree arrows -Both breadcrumbs and the arrows in the hierarchy and tree view serve the same function: to indicate a clickable path, whether it's to the right or downwards. Therefore, it's recommended to use a consistent icon for this purpose. To stay with the minimalistic and simple style, using the breadcrumb or the tree view icon at this point would be most appropriate. +Both breadcrumbs and the arrows in the hierarchy and tree view serve the same function: to indicate a clickable path, +whether it's to the right or downwards. Therefore, it's recommended to use a consistent icon for this purpose. To stay +with the minimalistic and simple style, using the breadcrumb or the tree view icon at this point would be most +appropriate. # Group 4: Dropdowns + ## Filter dropdown Wherever filtering options are available, you'll find the filter symbol accompanied by an arrow preceding the label.: ![filter](https://files.ilias.de/images/arrows/filter-dropdown.png) -**Integration:** This arrow is embedded via font means as a glyphicon +**Integration:** This arrow is embedded via font means as a glyphicon Stylings used: font-family: "il-icons"; content: "\e604"; ## Viewcontrols dropdown caret @@ -143,13 +178,19 @@ The .ilc_Accordion - element is used when building content for ILIAS: ![accordion](https://files.ilias.de/images/arrows/accordion.png) -**Integration:** This arrow is an SVG-file (image) which is used as an background-image (background-image: tree_col.svg). +**Integration:** This arrow is an SVG-file (image) which is used as an background-image (background-image: +tree_col.svg). ## Results and recommendation for dropdown arrows -For dropdowns, an arrow icon isn't always essential. When it comes to accordions, there are various symbol designs to consider. A frequently used alternative is the plus icon instead of the arrow. Using a different icon instead of an arrow, as currently used in the accordion, has the advantage of reducing the risk of it being mistaken for a clickable link. Arrows pointing directly at labels can sometimes lead to confusion. +For dropdowns, an arrow icon isn't always essential. When it comes to accordions, there are various symbol designs to +consider. A frequently used alternative is the plus icon instead of the arrow. Using a different icon instead of an +arrow, as currently used in the accordion, has the advantage of reducing the risk of it being mistaken for a clickable +link. Arrows pointing directly at labels can sometimes lead to confusion. -Implementing a plus icon for the .ilc-Accordion would be an intriguing option here. Not only would it introduce variety into the theme, moving away from using arrows exclusively, but it would also provide a meaningful representation of the functionality of the .ilc-Accordion. +Implementing a plus icon for the .ilc-Accordion would be an intriguing option here. Not only would it introduce variety +into the theme, moving away from using arrows exclusively, but it would also provide a meaningful representation of the +functionality of the .ilc-Accordion. For the expanded view, the plus icon could be rotated to give it a tilted appearance resembling an "x" for "close": @@ -157,9 +198,11 @@ For the expanded view, the plus icon could be rotated to give it a tilted appear Source: https://uxmovement.com/navigation/where-to-place-your-accordion-menu-icons/ -For traditional dropdown menus, which can be visually separated from the .ilc-Accordion, the Viewcontrols dropdown caret can be recommended. This visually separates the dropdowns effectively from, for example, the hierarchy areas/trees. +For traditional dropdown menus, which can be visually separated from the .ilc-Accordion, the Viewcontrols dropdown caret +can be recommended. This visually separates the dropdowns effectively from, for example, the hierarchy areas/trees. # Group 5: Action button + ## Different variations There are some different versions of the action button. @@ -178,9 +221,16 @@ In addition to that the action button is often seen without the label in two dif ![not filled](https://files.ilias.de/images/arrows/course-action-button.png) -**Some thoughts:** Currently, the default button and the action button look quite similar. The default button should receive more focus and attention than the action button. This is because the action button typically includes menu settings, whereas the default button executes a specific action or task upon clicking, which should be more prominent than the action button that expands a menu. To reduce the focus on the action button, it's advisable to style it differently. For instance, using a less vibrant color scheme and aligning its design more closely with the view controls, where it primarily appears, would be beneficial. +**Some thoughts:** Currently, the default button and the action button look quite similar. The default button should +receive more focus and attention than the action button. This is because the action button typically includes menu +settings, whereas the default button executes a specific action or task upon clicking, which should be more prominent +than the action button that expands a menu. To reduce the focus on the action button, it's advisable to style it +differently. For instance, using a less vibrant color scheme and aligning its design more closely with the view +controls, where it primarily appears, would be beneficial. -Reducing the styling to only the reversed version of the button, arguing that it already has a slightly noticeable design, might not be advantageous for the button. This could potentially cause it to get lost next to the other viewcontrols. +Reducing the styling to only the reversed version of the button, arguing that it already has a slightly noticeable +design, might not be advantageous for the button. This could potentially cause it to get lost next to the other +viewcontrols. # Link icon @@ -188,26 +238,77 @@ A double arrow is used to indicate a link: ![link](https://files.ilias.de/images/arrows/link-icon.png) -**Some thoughts:** For listing links, an arrow isn't strictly necessary. Lists can be represented using other symbols, such as a bullet point. So, this is another valid scenario where we can avoid using another version of the arrow icon. +**Some thoughts:** For listing links, an arrow isn't strictly necessary. Lists can be represented using other symbols, +such as a bullet point. So, this is another valid scenario where we can avoid using another version of the arrow icon. # Conclusion -We examined the various arrow icons used in ILIAS and provided recommendations for optimizing their usage to create a more consistent user interface and simplify system design. +We examined the various arrow icons used in ILIAS and provided recommendations for optimizing their usage to create a +more consistent user interface and simplify system design. -**Navigation Arrows:** It is recommended to standardize all navigation elements with a sleek, minimalist arrow icon. The current bulky design of the test and survey navigation should be replaced with the simple and clear icon used in the lightbox and view control navigation. +**Navigation Arrows:** It is recommended to standardize all navigation elements with a sleek, minimalist arrow icon. The +current bulky design of the test and survey navigation should be replaced with the simple and clear icon used in the +lightbox and view control navigation. -**Sortation Arrows:** Sorting arrows should maintain their functionality, but their styles should be harmonized. The bulky sorting arrow should be replaced with a slimmer design that aligns with the overall visual language of the system. +**Sortation Arrows:** Sorting arrows should maintain their functionality, but their styles should be harmonized. The +bulky sorting arrow should be replaced with a slimmer design that aligns with the overall visual language of the system. -**Hierarchy/Tree Arrows:** Both the breadcrumb and tree view arrows should be unified. A consistent, minimalist symbol should be used to enhance usability and visual consistency. +**Hierarchy/Tree Arrows:** Both the breadcrumb and tree view arrows should be unified. A consistent, minimalist symbol +should be used to enhance usability and visual consistency. -**Dropdown Arrows:** For dropdowns, an arrow icon is not always necessary. Instead, a plus symbol can be used, which can be rotated to an "x" for "close" when needed. This reduces confusion and improves the user experience. If a plus icon is not desired, it is advisable to standardize the icons to one downward-pointing arrow version (such as the caret which is used for the dropdown in the calendar). +**Dropdown Arrows:** For dropdowns, an arrow icon is not always necessary. Instead, a plus symbol can be used, which can +be rotated to an "x" for "close" when needed. This reduces confusion and improves the user experience. If a plus icon is +not desired, it is advisable to standardize the icons to one downward-pointing arrow version (such as the caret which is +used for the dropdown in the calendar). -**Action Buttons:** Action buttons should be less prominent than default buttons. A reduced color scheme and a simpler design that matches the view controls are recommended. The currently used different versions should be reduced to a single, less conspicuous version. +**Action Buttons:** Action buttons should be less prominent than default buttons. A reduced color scheme and a simpler +design that matches the view controls are recommended. The currently used different versions should be reduced to a +single, less conspicuous version. -**Link Icon:** The double arrow icon for links can be replaced with other symbols, such as bullet points, to reduce the variety of arrow icons and avoid confusion. +**Link Icon:** The double arrow icon for links can be replaced with other symbols, such as bullet points, to reduce the +variety of arrow icons and avoid confusion. -We are focusing on establishing a uniform visual language within ILIAS. By implementing the above recommendations, we aim to create a cohesive overall appearance for the icons. The used icon combinations will provide a simple and minimalist look yet achieve clear recognition regarding their functionality. This uniformity will enhance the user experience by making the interface more intuitive, user-friendly and visually consistent while creating an easier-to-maintain user interface. +We are focusing on establishing a uniform visual language within ILIAS. By implementing the above recommendations, we +aim to create a cohesive overall appearance for the icons. The used icon combinations will provide a simple and +minimalist look yet achieve clear recognition regarding their functionality. This uniformity will enhance the user +experience by making the interface more intuitive, user-friendly and visually consistent while creating an +easier-to-maintain user interface. ## Next Steps -The next steps involve initiating the unification process through collaboration between a designer and a software developer. This process will include systematically addressing each section and group of icons identified in this study. For each group, we will clarify the tasks involved and replace the icons that need to be exchanged, one by one. +The next steps involve initiating the unification process through collaboration between a designer and a software +developer. This process will include systematically addressing each section and group of icons identified in this study. +For each group, we will clarify the tasks involved and replace the icons that need to be exchanged, one by one. + +## Results of Implementation + +### Group 1: Navigation arrows + +* Test and survey navigation arrows replaced with the lightbox and view control navigation arrow. +* The lightbox navigation arrows have been changed from links to buttons. +* The Survey navigation arrow has been changed from an input type to a button. + +## Group 2: Sortation + +* Implementation of Sortation arrows (e.g. in Tables) is pending due to the need for a new glyph. The Glyph will be + derived from the View Control Sorting Glyph. +* The View Control Sortation Glyph has been updated to use the same glyph as the Input View Control Sortation Glyph, + which has the filled-out arrows. + +### Group 3: Trees and hierarchy arrows + +* Breadcrumb arrows already use the same glyph as the tree view arrows. +* A Mantis Issue has been created for the Breadcrumb arrows due to the bold font in Safari. +* Hierarchy in Learning Progress has been updated to use the same glyph as the Breadcrumb arrows. + +### Group 4: Dropdowns + +* Dropdowns and Caret are also postponed until more dropdowns are switched to Kitchen Sink UI components. + +### Group 5: Action button + +* Action Buttons are also postponed until the glyph gets a new design. + +### Group 6: Link icon + +* The Link icon has been updated to use a bullet point instead of the double arrow. \ No newline at end of file diff --git a/components/ILIAS/UI/resources/fonts/Iconfont/il-icons.eot b/components/ILIAS/UI/resources/fonts/Iconfont/il-icons.eot index 41246fa3f1fd..5799dffedf80 100644 Binary files a/components/ILIAS/UI/resources/fonts/Iconfont/il-icons.eot and b/components/ILIAS/UI/resources/fonts/Iconfont/il-icons.eot differ diff --git a/components/ILIAS/UI/resources/fonts/Iconfont/il-icons.json b/components/ILIAS/UI/resources/fonts/Iconfont/il-icons.json index c224200c839b..5a149efa4407 100644 --- a/components/ILIAS/UI/resources/fonts/Iconfont/il-icons.json +++ b/components/ILIAS/UI/resources/fonts/Iconfont/il-icons.json @@ -1,1202 +1,1232 @@ { "selection": [ { - "order": 611, + "order": 215, "ligatures": "", "prevSize": 32, "name": "user-female" }, { - "order": 612, + "order": 216, "ligatures": "", "prevSize": 32, "name": "people" }, { - "order": 613, + "order": 217, "ligatures": "", "prevSize": 32, "name": "user-follow" }, { - "order": 614, + "order": 218, "ligatures": "", "prevSize": 32, "name": "user-following" }, { - "order": 615, + "order": 219, "ligatures": "", "prevSize": 32, "name": "user-unfollow" }, { - "order": 616, + "order": 220, "ligatures": "", "prevSize": 32, "name": "user" }, { - "order": 617, + "order": 221, "ligatures": "", "prevSize": 32, "name": "trophy" }, { - "order": 618, + "order": 222, "ligatures": "", "prevSize": 32, "name": "speedometer" }, { - "order": 619, + "order": 223, "ligatures": "", "prevSize": 32, "name": "social-youtube" }, { - "order": 620, + "order": 224, "ligatures": "", "prevSize": 32, "name": "social-twitter" }, { - "order": 621, + "order": 225, "ligatures": "", "prevSize": 32, "name": "social-tumblr" }, { - "order": 622, + "order": 226, "ligatures": "", "prevSize": 32, "name": "social-facebook" }, { - "order": 623, + "order": 227, "ligatures": "", "prevSize": 32, "name": "social-dropbox" }, { - "order": 624, + "order": 228, "ligatures": "", "prevSize": 32, "name": "social-dribbble" }, { - "order": 625, + "order": 229, "ligatures": "", "prevSize": 32, "name": "shield" }, { - "order": 626, + "order": 230, "ligatures": "", "prevSize": 32, "name": "screen-tablet" }, { - "order": 627, + "order": 231, "ligatures": "", "prevSize": 32, "name": "screen-smartphone" }, { - "order": 628, + "order": 232, "ligatures": "", "prevSize": 32, "name": "screen-desktop" }, { - "order": 629, + "order": 233, "ligatures": "", "prevSize": 32, "name": "plane" }, { - "order": 630, + "order": 234, "ligatures": "", "prevSize": 32, "name": "notebook" }, { - "order": 631, + "order": 235, "ligatures": "", "prevSize": 32, "name": "mustache" }, { - "order": 632, + "order": 236, "ligatures": "", "prevSize": 32, "name": "mouse" }, { - "order": 633, + "order": 237, "ligatures": "", "prevSize": 32, "name": "magnet" }, { - "order": 634, + "order": 238, "ligatures": "", "prevSize": 32, "name": "magic-wand" }, { - "order": 635, + "order": 239, "ligatures": "", "prevSize": 32, "name": "hourglass" }, { - "order": 636, + "order": 240, "ligatures": "", "prevSize": 32, "name": "graduation" }, { - "order": 637, + "order": 241, "ligatures": "", "prevSize": 32, "name": "ghost" }, { - "order": 638, + "order": 242, "ligatures": "", "prevSize": 32, "name": "game-controller" }, { - "order": 639, + "order": 243, "ligatures": "", "prevSize": 32, "name": "fire" }, { - "order": 640, + "order": 244, "ligatures": "", "prevSize": 32, "name": "eyeglass" }, { - "order": 641, + "order": 245, "ligatures": "", "prevSize": 32, "name": "envelope-open" }, { - "order": 642, + "order": 246, "ligatures": "", "prevSize": 32, "name": "envolope-letter" }, { - "order": 643, + "order": 247, "ligatures": "", "prevSize": 32, "name": "energy" }, { - "order": 644, + "order": 248, "ligatures": "", "prevSize": 32, "name": "emotsmile" }, { - "order": 645, + "order": 249, "ligatures": "", "prevSize": 32, "name": "disc" }, { - "order": 646, + "order": 250, "ligatures": "", "prevSize": 32, "name": "cursor-move" }, { - "order": 647, + "order": 251, "ligatures": "", "prevSize": 32, "name": "crop" }, { - "order": 648, + "order": 252, "ligatures": "", "prevSize": 32, "name": "credit-card" }, { - "order": 649, + "order": 253, "ligatures": "", "prevSize": 32, "name": "chemistry" }, { - "order": 650, + "order": 254, "ligatures": "", "prevSize": 32, "name": "bell" }, { - "order": 651, + "order": 255, "ligatures": "", "prevSize": 32, "name": "badge" }, { - "order": 652, + "order": 256, "ligatures": "", "prevSize": 32, "name": "anchor" }, { - "order": 653, + "order": 257, "ligatures": "", "prevSize": 32, "name": "wallet" }, { - "order": 654, + "order": 258, "ligatures": "", "prevSize": 32, "name": "vector" }, { - "order": 655, + "order": 259, "ligatures": "", "prevSize": 32, "name": "speech" }, { - "order": 656, + "order": 260, "ligatures": "", "prevSize": 32, "name": "puzzle" }, { - "order": 657, + "order": 261, "ligatures": "", "prevSize": 32, "name": "printer" }, { - "order": 658, + "order": 262, "ligatures": "", "prevSize": 32, "name": "present" }, { - "order": 659, + "order": 263, "ligatures": "", "prevSize": 32, "name": "playlist" }, { - "order": 660, + "order": 264, "ligatures": "", "prevSize": 32, "name": "pin" }, { - "order": 661, + "order": 265, "ligatures": "", "prevSize": 32, "name": "picture" }, { - "order": 662, + "order": 266, "ligatures": "", "prevSize": 32, "name": "map" }, { - "order": 663, + "order": 267, "ligatures": "", "prevSize": 32, "name": "layers" }, { - "order": 664, + "order": 268, "ligatures": "", "prevSize": 32, "name": "handbag" }, { - "order": 665, + "order": 269, "ligatures": "", "prevSize": 32, "name": "globe-alt" }, { - "order": 666, + "order": 270, "ligatures": "", "prevSize": 32, "name": "globe" }, { - "order": 667, + "order": 271, "ligatures": "", "prevSize": 32, "name": "frame" }, { - "order": 668, + "order": 272, "ligatures": "", "prevSize": 32, "name": "folder-alt" }, { - "order": 669, + "order": 273, "ligatures": "", "prevSize": 32, "name": "film" }, { - "order": 670, + "order": 274, "ligatures": "", "prevSize": 32, "name": "feed" }, { - "order": 671, + "order": 275, "ligatures": "", "prevSize": 32, "name": "earphones-alt" }, { - "order": 672, + "order": 276, "ligatures": "", "prevSize": 32, "name": "earphones" }, { - "order": 673, + "order": 277, "ligatures": "", "prevSize": 32, "name": "drop" }, { - "order": 674, + "order": 278, "ligatures": "", "prevSize": 32, "name": "drawar" }, { - "order": 675, + "order": 279, "ligatures": "", "prevSize": 32, "name": "docs" }, { - "order": 676, + "order": 280, "ligatures": "", "prevSize": 32, "name": "directions" }, { - "order": 677, + "order": 281, "ligatures": "", "prevSize": 32, "name": "direction" }, { - "order": 678, + "order": 282, "ligatures": "", "prevSize": 32, "name": "diamond" }, { - "order": 679, + "order": 283, "ligatures": "", "prevSize": 32, "name": "cup" }, { - "order": 680, + "order": 284, "ligatures": "", "prevSize": 32, "name": "compass" }, { - "order": 681, + "order": 285, "ligatures": "", "prevSize": 32, "name": "call-out" }, { - "order": 682, + "order": 286, "ligatures": "", "prevSize": 32, "name": "call-in" }, { - "order": 683, + "order": 287, "ligatures": "", "prevSize": 32, "name": "call-end" }, { - "order": 684, + "order": 288, "ligatures": "", "prevSize": 32, "name": "calculator" }, { - "order": 685, + "order": 289, "ligatures": "", "prevSize": 32, "name": "bubbles" }, { - "order": 686, + "order": 290, "ligatures": "", "prevSize": 32, "name": "briefcase" }, { - "order": 687, + "order": 291, "ligatures": "", "prevSize": 32, "name": "book-open" }, { - "order": 688, + "order": 292, "ligatures": "", "prevSize": 32, "name": "basket-loaded" }, { - "order": 689, + "order": 293, "ligatures": "", "prevSize": 32, "name": "basket" }, { - "order": 690, + "order": 294, "ligatures": "", "prevSize": 32, "name": "bag" }, { - "order": 691, + "order": 295, "ligatures": "", "prevSize": 32, "name": "action-undo" }, { - "order": 692, + "order": 296, "ligatures": "", "prevSize": 32, "name": "action-redo" }, { - "order": 693, + "order": 297, "ligatures": "", "prevSize": 32, "name": "wrench" }, { - "order": 694, + "order": 298, "ligatures": "", "prevSize": 32, "name": "umbrella" }, { - "order": 695, + "order": 299, "ligatures": "", "prevSize": 32, "name": "trash" }, { - "order": 696, + "order": 300, "ligatures": "", "prevSize": 32, "name": "tag" }, { - "order": 697, + "order": 301, "ligatures": "", "prevSize": 32, "name": "support" }, { - "order": 698, + "order": 302, "ligatures": "", "prevSize": 32, "name": "size-fullscreen" }, { - "order": 699, + "order": 303, "ligatures": "", "prevSize": 32, "name": "size-actual" }, { - "order": 700, + "order": 304, "ligatures": "", "prevSize": 32, "name": "shuffle" }, { - "order": 701, + "order": 305, "ligatures": "", "prevSize": 32, "name": "share-alt" }, { - "order": 702, + "order": 306, "ligatures": "", "prevSize": 32, "name": "share" }, { - "order": 703, + "order": 307, "ligatures": "", "prevSize": 32, "name": "launch" }, { - "order": 704, + "order": 308, "ligatures": "", "prevSize": 32, "name": "question" }, { - "order": 705, + "order": 309, "ligatures": "", "prevSize": 32, "name": "pie-chart" }, { - "order": 706, + "order": 310, "ligatures": "", "prevSize": 32, "name": "pencil" }, { - "order": 707, + "order": 311, "ligatures": "", "prevSize": 32, "name": "note" }, { - "order": 708, + "order": 312, "ligatures": "", "prevSize": 32, "name": "music-tone-alt" }, { - "order": 709, + "order": 313, "ligatures": "", "prevSize": 32, "name": "music-tone" }, { - "order": 710, + "order": 314, "ligatures": "", "prevSize": 32, "name": "microphone" }, { - "order": 711, + "order": 315, "ligatures": "", "prevSize": 32, "name": "loop" }, { - "order": 712, + "order": 316, "ligatures": "", "prevSize": 32, "name": "logout" }, { - "order": 713, + "order": 317, "ligatures": "", "prevSize": 32, "name": "login" }, { - "order": 714, + "order": 318, "ligatures": "", "prevSize": 32, "name": "list" }, { - "order": 715, + "order": 319, "ligatures": "", "prevSize": 32, "name": "like" }, { - "order": 716, + "order": 320, "ligatures": "", "prevSize": 32, "name": "home" }, { - "order": 717, + "order": 321, "ligatures": "", "prevSize": 32, "name": "grid" }, { - "order": 718, + "order": 322, "ligatures": "", "prevSize": 32, "name": "graph" }, { - "order": 719, + "order": 323, "ligatures": "", "prevSize": 32, "name": "equalizer" }, { - "order": 720, + "order": 324, "ligatures": "", "prevSize": 32, "name": "dislike" }, { - "order": 721, + "order": 325, "ligatures": "", "prevSize": 32, "name": "cursor" }, { - "order": 722, + "order": 326, "ligatures": "", "prevSize": 32, "name": "control-start" }, { - "order": 723, + "order": 327, "ligatures": "", "prevSize": 32, "name": "control-rewind" }, { - "order": 724, + "order": 328, "ligatures": "", "prevSize": 32, "name": "control-play" }, { - "order": 725, + "order": 329, "ligatures": "", "prevSize": 32, "name": "control-pause" }, { - "order": 726, + "order": 330, "ligatures": "", "prevSize": 32, "name": "control-forward" }, { - "order": 727, + "order": 331, "ligatures": "", "prevSize": 32, "name": "control-end" }, { - "order": 728, + "order": 332, "ligatures": "", "prevSize": 32, "name": "calender" }, { - "order": 729, + "order": 333, "ligatures": "", "prevSize": 32, "name": "bulb" }, { - "order": 730, + "order": 334, "ligatures": "", "prevSize": 32, "name": "chart" }, { - "order": 731, + "order": 335, "ligatures": "", "prevSize": 32, "name": "arrow-up-circle" }, { - "order": 732, + "order": 336, "ligatures": "", "prevSize": 32, "name": "arrow-right-circle" }, { - "order": 733, + "order": 337, "ligatures": "", "prevSize": 32, "name": "arrow-left-circle" }, { - "order": 734, + "order": 338, "ligatures": "", "prevSize": 32, "name": "arrow-down-circle" }, { - "order": 735, + "order": 339, "ligatures": "", "prevSize": 32, "name": "ban" }, { - "order": 736, + "order": 340, "ligatures": "", "prevSize": 32, "name": "bubble" }, { - "order": 737, + "order": 341, "ligatures": "", "prevSize": 32, "name": "camrecorder" }, { - "order": 738, + "order": 342, "ligatures": "", "prevSize": 32, "name": "camera" }, { - "order": 739, + "order": 343, "ligatures": "", "prevSize": 32, - "name": "check" + "name": "ok" }, { - "order": 740, + "order": 344, "ligatures": "", "prevSize": 32, "name": "clock" }, { - "order": 741, + "order": 345, "ligatures": "", "prevSize": 32, "name": "close" }, { - "order": 742, + "order": 346, "ligatures": "", "prevSize": 32, "name": "cloud-download" }, { - "order": 743, + "order": 347, "ligatures": "", "prevSize": 32, "name": "cloud-upload" }, { - "order": 744, + "order": 348, "ligatures": "", "prevSize": 32, "name": "doc" }, { - "order": 745, + "order": 349, "ligatures": "", "prevSize": 32, "name": "envolope" }, { - "order": 746, + "order": 350, "ligatures": "", "prevSize": 32, "name": "eye" }, { - "order": 747, + "order": 351, "ligatures": "", "prevSize": 32, "name": "flag" }, { - "order": 748, + "order": 352, "ligatures": "", "prevSize": 32, "name": "folder" }, { - "order": 749, + "order": 353, "ligatures": "", "prevSize": 32, "name": "heart" }, { - "order": 750, + "order": 354, "ligatures": "", "prevSize": 32, "name": "info" }, { - "order": 751, + "order": 355, "ligatures": "", "prevSize": 32, "name": "key" }, { - "order": 752, + "order": 356, "ligatures": "", "prevSize": 32, "name": "link" }, { - "order": 753, + "order": 357, "ligatures": "", "prevSize": 32, "name": "lock" }, { - "order": 754, + "order": 358, "ligatures": "", "prevSize": 32, "name": "lock-open" }, { - "order": 755, + "order": 359, "ligatures": "", "prevSize": 32, "name": "magnifier" }, { - "order": 756, + "order": 360, "ligatures": "", "prevSize": 32, "name": "magnifier-add" }, { - "order": 757, + "order": 361, "ligatures": "", "prevSize": 32, "name": "magnifier-remove" }, { - "order": 758, + "order": 362, "ligatures": "", "prevSize": 32, "name": "paper-clip" }, { - "order": 759, + "order": 363, "ligatures": "", "prevSize": 32, "name": "paper-plane" }, { - "order": 760, + "order": 364, "ligatures": "", "prevSize": 32, "name": "plus" }, { - "order": 761, + "order": 365, "ligatures": "", "prevSize": 32, "name": "location-pin" }, { - "order": 762, + "order": 366, "ligatures": "", "prevSize": 32, "name": "power" }, { - "order": 763, + "order": 367, "ligatures": "", "prevSize": 32, "name": "refresh" }, { - "order": 764, + "order": 368, "ligatures": "", "prevSize": 32, "name": "reload" }, { - "order": 765, + "order": 369, "ligatures": "", "prevSize": 32, "name": "settings" }, { - "order": 766, + "order": 370, "ligatures": "", "prevSize": 32, "name": "star" }, { - "order": 767, + "order": 371, "ligatures": "", "prevSize": 32, "name": "symble-female" }, { - "order": 768, + "order": 372, "ligatures": "", "prevSize": 32, "name": "symbol-male" }, { - "order": 769, + "order": 373, "ligatures": "", "prevSize": 32, "name": "target" }, { - "order": 770, + "order": 374, "ligatures": "", "prevSize": 32, "name": "volume-1" }, { - "order": 771, + "order": 375, "ligatures": "", "prevSize": 32, "name": "volume-2" }, { - "order": 772, + "order": 376, "ligatures": "", "prevSize": 32, "name": "volume-off" }, { - "order": 773, + "order": 377, "ligatures": "", "prevSize": 32, "name": "phone" }, { - "order": 774, + "order": 378, "ligatures": "", "prevSize": 32, "name": "menu" }, { - "order": 775, + "order": 379, "ligatures": "", "prevSize": 32, "name": "options-vertical" }, { - "order": 776, + "order": 380, "ligatures": "", "prevSize": 32, "name": "options" }, { - "order": 777, + "order": 381, "ligatures": "", "prevSize": 32, "name": "arrow-down" }, { - "order": 778, + "order": 382, "ligatures": "", "prevSize": 32, "name": "arrow-left" }, { - "order": 779, + "order": 383, "ligatures": "", "prevSize": 32, "name": "arrow-right" }, { - "order": 780, + "order": 384, "ligatures": "", "prevSize": 32, "name": "arrow-up" }, { - "order": 781, + "order": 385, "ligatures": "", "prevSize": 32, "name": "paypal" }, { - "order": 782, + "order": 386, "ligatures": "", "prevSize": 32, "name": "social-instagram" }, { - "order": 783, + "order": 387, "ligatures": "", "prevSize": 32, "name": "social-linkedin" }, { - "order": 784, + "order": 388, "ligatures": "", "prevSize": 32, "name": "social-pintarest" }, { - "order": 785, + "order": 389, "ligatures": "", "prevSize": 32, "name": "social-github" }, { - "order": 786, + "order": 390, "ligatures": "", "prevSize": 32, "name": "social-google" }, { - "order": 787, + "order": 391, "ligatures": "", "prevSize": 32, "name": "social-reddit" }, { - "order": 788, + "order": 392, "ligatures": "", "prevSize": 32, "name": "social-skype" }, { - "order": 789, + "order": 393, "ligatures": "", "prevSize": 32, "name": "social-behance" }, { - "order": 790, + "order": 394, "ligatures": "", "prevSize": 32, "name": "social-foursqare" }, { - "order": 791, + "order": 395, "ligatures": "", "prevSize": 32, "name": "social-soundcloud" }, { - "order": 792, + "order": 396, "ligatures": "", "prevSize": 32, "name": "social-spotify" }, { - "order": 793, + "order": 397, "ligatures": "", "prevSize": 32, "name": "social-stumbleupon" }, { - "order": 794, + "order": 398, "ligatures": "", "prevSize": 32, "name": "minus" }, { - "order": 795, + "order": 399, "ligatures": "", "prevSize": 32, "name": "organization" }, { - "order": 796, + "order": 400, "ligatures": "", "prevSize": 32, "name": "exclamation" }, { - "order": 797, + "order": 401, "ligatures": "", "prevSize": 32, "name": "lang" }, { - "order": 798, + "order": 402, "ligatures": "", "prevSize": 32, "name": "event" }, { - "order": 799, + "order": 403, "ligatures": "", "prevSize": 32, "name": "social-steam" }, { - "order": 800, + "order": 404, "name": "bulletlist", "prevSize": 32 }, { - "order": 801, + "order": 405, "prevSize": 32, "name": "numberedlist" }, { - "order": 802, + "order": 406, "name": "listindent", "prevSize": 32 }, { - "order": 803, + "order": 407, "name": "listoutdent", "prevSize": 32 }, { - "order": 804, + "order": 408, "name": "filter", "prevSize": 32 }, { - "order": 805, + "order": 409, + "name": "columnselection", + "prevSize": 32 + }, + { + "order": 410, "name": "enlarge", "prevSize": 32 }, { - "order": 806, + "order": 411, "name": "preview", "prevSize": 32 }, { - "order": 807, + "order": 412, "name": "ListView", "prevSize": 32 }, { - "order": 808, + "order": 413, "name": "TileView", "prevSize": 32 }, { - "order": 809, + "order": 414, "name": "reset", "prevSize": 32 }, { - "order": 810, + "order": 415, "name": "sort", "prevSize": 32 }, { - "order": 811, + "order": 416, "name": "apply", "prevSize": 32 }, { - "order": 812, - "name": "columnselection", + "order": 417, + "name": "drag-drop-handle", + "prevSize": 32 + }, + { + "order": 418, + "name": "unselect", + "prevSize": 32 + }, + { + "order": 419, + "name": "select", + "prevSize": 32 + }, + { + "order": 420, + "name": "clear", + "prevSize": 32 + }, + { + "order": 421, + "name": "checked", + "prevSize": 32 + }, + { + "order": 422, + "name": "unchecked", "prevSize": 32 } ], @@ -1206,7 +1236,7 @@ "width": 320, "height": 320 }, - "iconsHash": 747128600 + "iconsHash": 1953044038 }, "height": 1024, "prevSize": 32, @@ -3176,6 +3206,16 @@ "defaultCode": 59652, "grid": 0 }, + { + "paths": [ + "M406.144 850.784h-338.784v-677.568h338.784v677.568zM808.416 395.136l63.52-82.592v-202.848h-868.096v804.608h868.128v-213.152l-15.872 21.184-47.648 63.52v64.928h-338.784v-677.568h338.784v221.92zM1020.16 396.992l-84.544-63.712-185.312 245.92-110.816-99.776-70.848 78.688 196.672 177.056s2.24 0.096 2.24 0.096l252.608-338.272z" + ], + "tags": [ + "dtcol" + ], + "defaultCode": 59653, + "grid": 0 + }, { "paths": [ "M66.56 66.56h212.16v-63.36h-233.28c-23.36 0-42.24 18.88-42.24 42.24v233.28h63.68v-212.16zM45.44 1020.8h233.28v-63.68h-212.16v-212.16h-63.36v233.28c0 23.36 18.88 42.56 42.56 42.56zM957.44 278.72h63.68v-233.28c0-23.36-18.88-42.56-42.56-42.56h-233.28v63.68h212.16v212.16zM1021.12 978.56v-233.28h-63.68v212.16h-212.16v63.68h233.28c23.36 0 42.56-18.88 42.56-42.56z", @@ -3261,20 +3301,74 @@ }, { "paths": [ - "M406.144 850.784h-338.784v-677.568h338.784v677.568zM808.416 395.136l63.52-82.592v-202.848h-868.096v804.608h868.128v-213.152l-15.872 21.184-47.648 63.52v64.928h-338.784v-677.568h338.784v221.92zM1020.16 396.992l-84.544-63.712-185.312 245.92-110.816-99.776-70.848 78.688 196.672 177.056s2.24 0.096 2.24 0.096l252.608-338.272z" + "M447.399 138.060c0 68.612-55.621 124.233-124.233 124.233h-0c-68.612 0-124.233-55.621-124.233-124.233l0 0c0-68.612 55.621-124.233 124.233-124.233l-0 0c68.612 0 124.233 55.621 124.233 124.233v-0z", + "M449.884 510.758c0 68.612-55.621 124.233-124.233 124.233h-0c-68.612 0-124.233-55.621-124.233-124.233l0 0c0-68.612 55.621-124.233 124.233-124.233l0 0c68.612 0 124.233 55.621 124.233 124.233v0z", + "M452.368 883.455c0 68.612-55.621 124.233-124.233 124.233v-0c-68.612 0-124.233-55.621-124.233-124.233v-0c-0-68.612 55.621-124.233 124.233-124.233l0 0c68.612 0 124.233 55.621 124.233 124.233l-0-0z", + "M820.097 140.545c0 68.612-55.621 124.233-124.233 124.233v0c-68.612 0-124.233-55.621-124.233-124.233v0c0-68.612 55.621-124.233 124.233-124.233v0c68.612 0 124.233 55.621 124.233 124.233v0z", + "M822.581 513.242c0 68.612-55.621 124.233-124.233 124.233h-0c-68.612 0-124.233-55.621-124.233-124.233l0 0c-0-68.612 55.621-124.233 124.233-124.233h0c68.612 0 124.233 55.621 124.233 124.233h-0z", + "M825.066 885.94c0 68.612-55.621 124.233-124.233 124.233h-0c-68.612 0-124.233-55.621-124.233-124.233h0c-0-68.612 55.621-124.233 124.233-124.233h0c68.612 0 124.233 55.621 124.233 124.233l-0-0z" ], - "grid": 0, "tags": [ - "dtcol" + "dndhandle" ], - "defaultCode": 59653 + "defaultCode": 59661, + "grid": 0 + }, + { + "paths": [ + "M800 800h-576v-576h576v576zM880 848v-672c0-17.6-14.4-32-32-32h-672c-17.6 0-32 14.4-32 32v672c0 17.6 14.4 32 32 32h672c17.6 0 32-14.4 32-32z" + ], + "tags": [ + "unselect" + ], + "defaultCode": 59664, + "grid": 0 + }, + { + "paths": [ + "M389.76 479.36l-58.56 65.28 162.88 146.56h1.92l209.28-280-70.080-52.8-153.6 203.52-91.84-82.56zM800 800h-576v-576h576v576zM880 848v-672c0-17.6-14.4-32-32-32h-672c-17.6 0-32 14.4-32 32v672c0 17.6 14.4 32 32 32h672c17.6 0 32-14.4 32-32z" + ], + "tags": [ + "select" + ], + "defaultCode": 59665, + "grid": 0 + }, + { + "paths": [ + "M672 576h-320v-128h320v128zM880 848v-672c0-17.6-14.4-32-32-32h-672c-17.6 0-32 14.4-32 32v672c0 17.6 14.4 32 32 32h672c17.6 0 32-14.4 32-32z" + ], + "tags": [ + "clear" + ], + "defaultCode": 59666, + "grid": 0 + }, + { + "paths": [ + "M784.96 345.92l-305.28 408.96h-2.88l-237.76-214.080 85.76-95.040 134.080 120.64 224-297.28 102.080 76.8zM896 832v-640c0-35.2-28.8-64-64-64h-640c-35.2 0-64 28.8-64 64v640c0 35.2 28.8 64 64 64h640c35.2 0 64-28.8 64-64z" + ], + "tags": [ + "checked" + ], + "defaultCode": 59667, + "grid": 0 + }, + { + "paths": [ + "M756.48 674.88l-162.88-162.88 162.88-162.88-81.6-81.6-162.88 162.88-162.88-162.88-81.6 81.6 162.88 162.88-162.88 162.88 81.6 81.6 162.88-162.88 162.88 162.88 81.6-81.6zM848 832c0 8.96-7.040 16-16 16h-640c-8.96 0-16-7.040-16-16v-640c0-8.96 7.040-16 16-16h640c8.96 0 16 7.040 16 16v640zM896 832v-640c0-35.2-28.8-64-64-64h-640c-35.2 0-64 28.8-64 64v640c0 35.2 28.8 64 64 64h640c35.2 0 64-28.8 64-64z" + ], + "tags": [ + "unchecked" + ], + "defaultCode": 59668, + "grid": 0 } ], "colorThemes": [], "colorThemeIdx": 0, "preferences": { "showGlyphs": true, - "showCodes": true, "showQuickUse": true, "showQuickUse2": true, "showSVGs": true, @@ -3296,10 +3390,11 @@ "useClassSelector": true, "color": 0, "bgColor": 16777215, - "name": "icomoon", - "classSelector": ".icon" + "classSelector": ".icon", + "name": "icomoon" }, "historySize": 50, + "showCodes": true, "gridSize": 16 }, "IcoMoonType": "icon-set" diff --git a/components/ILIAS/UI/resources/fonts/Iconfont/il-icons.svg b/components/ILIAS/UI/resources/fonts/Iconfont/il-icons.svg index 5e6554fa0ef3..1a6ce8888a27 100644 --- a/components/ILIAS/UI/resources/fonts/Iconfont/il-icons.svg +++ b/components/ILIAS/UI/resources/fonts/Iconfont/il-icons.svg @@ -135,7 +135,7 @@ - + @@ -204,9 +204,15 @@ - - + + + + + + + + \ No newline at end of file diff --git a/components/ILIAS/UI/resources/fonts/Iconfont/il-icons.ttf b/components/ILIAS/UI/resources/fonts/Iconfont/il-icons.ttf index 39fb8f458398..7de5f9df5c51 100644 Binary files a/components/ILIAS/UI/resources/fonts/Iconfont/il-icons.ttf and b/components/ILIAS/UI/resources/fonts/Iconfont/il-icons.ttf differ diff --git a/components/ILIAS/UI/resources/fonts/Iconfont/il-icons.woff b/components/ILIAS/UI/resources/fonts/Iconfont/il-icons.woff index 6ed4fae79c6f..ad247bb355d5 100644 Binary files a/components/ILIAS/UI/resources/fonts/Iconfont/il-icons.woff and b/components/ILIAS/UI/resources/fonts/Iconfont/il-icons.woff differ diff --git a/components/ILIAS/UI/resources/images/auth/cas_login_button.png b/components/ILIAS/UI/resources/images/auth/cas_login_button.png deleted file mode 100755 index cecb171d78d0..000000000000 Binary files a/components/ILIAS/UI/resources/images/auth/cas_login_button.png and /dev/null differ diff --git a/components/ILIAS/UI/resources/images/logo/ilias_logo_centered.png b/components/ILIAS/UI/resources/images/logo/ilias_logo_centered.png new file mode 100644 index 000000000000..daf857cc7cd0 Binary files /dev/null and b/components/ILIAS/UI/resources/images/logo/ilias_logo_centered.png differ diff --git a/components/ILIAS/UI/resources/js/Core/dist/core.js b/components/ILIAS/UI/resources/js/Core/dist/core.js index c2cacc6eb22e..9615eab5c013 100644 --- a/components/ILIAS/UI/resources/js/Core/dist/core.js +++ b/components/ILIAS/UI/resources/js/Core/dist/core.js @@ -70,7 +70,7 @@ ******************************************************************** */ - /** + /** * This represents one tooltip on the page. */ class Tooltip { @@ -110,7 +110,7 @@ #main = null; constructor(element) { - this.#container = element.parentElement; + this.#container = element.closest('.c-tooltip__container'); this.#element = element; this.#document = element.ownerDocument; this.#window = this.#document.defaultView || this.#document.parentWindow; diff --git a/components/ILIAS/UI/resources/js/Core/src/core.Tooltip.js b/components/ILIAS/UI/resources/js/Core/src/core.Tooltip.js index ef54c2af879d..98ce70e961ad 100755 --- a/components/ILIAS/UI/resources/js/Core/src/core.Tooltip.js +++ b/components/ILIAS/UI/resources/js/Core/src/core.Tooltip.js @@ -15,7 +15,7 @@ ******************************************************************** */ - /** +/** * This represents one tooltip on the page. */ class Tooltip { @@ -55,7 +55,7 @@ class Tooltip { #main = null; constructor(element) { - this.#container = element.parentElement; + this.#container = element.closest('.c-tooltip__container'); this.#element = element; this.#document = element.ownerDocument; this.#window = this.#document.defaultView || this.#document.parentWindow; diff --git a/components/ILIAS/UI/resources/js/Core/src/core.URLBuilder.js b/components/ILIAS/UI/resources/js/Core/src/core.URLBuilder.js index 9f74b63c9aa6..e78572a383dc 100755 --- a/components/ILIAS/UI/resources/js/Core/src/core.URLBuilder.js +++ b/components/ILIAS/UI/resources/js/Core/src/core.URLBuilder.js @@ -15,7 +15,7 @@ ******************************************************************** */ -import URLBuilderToken from './core.URLBuilderToken'; +import URLBuilderToken from './core.URLBuilderToken.js'; const URLBuilderUrlMaxLength = 2048; const URLBuilderSeparator = '_'; diff --git a/components/ILIAS/UI/resources/js/Core/src/core.URLBuilderToken.js b/components/ILIAS/UI/resources/js/Core/src/core.URLBuilderToken.js index 46a9d0704ff8..659c9855d7c8 100755 --- a/components/ILIAS/UI/resources/js/Core/src/core.URLBuilderToken.js +++ b/components/ILIAS/UI/resources/js/Core/src/core.URLBuilderToken.js @@ -14,7 +14,7 @@ * ******************************************************************** */ -import createRandomString from './createRandomString'; +import createRandomString from './createRandomString.js'; const URLBuilderTokenSeparator = '_'; diff --git a/components/ILIAS/UI/resources/js/Dropdown/dist/dropdown.js b/components/ILIAS/UI/resources/js/Dropdown/dist/dropdown.js index b4f63c37aa56..3bae077f0c29 100644 --- a/components/ILIAS/UI/resources/js/Dropdown/dist/dropdown.js +++ b/components/ILIAS/UI/resources/js/Dropdown/dist/dropdown.js @@ -12,4 +12,4 @@ * https://www.ilias.de * https://github.com/ILIAS-eLearning */ -!function(t){"use strict";class e{#t;#e;#i;#n;constructor(t){if(this.#e=t,this.#t=t.ownerDocument,this.#i=this.#e.querySelector(":scope > button"),null===this.#i)throw new Error("Dropdown: Expected exactly one button in dropdown element.",this.#e);if(this.#n=this.#e.querySelector(":scope > ul"),null===this.#n)throw new Error("Dropdown: Expected exactly one ul in dropdown element.",this.#e);this.#i.addEventListener("click",this.#s)}#o=t=>{27===t.key&&this.hide()};#s=t=>{t.stopPropagation(),this.show()};#d=()=>{this.hide()};#l=()=>{const t=this.#t.documentElement.clientWidth;this.#i.getBoundingClientRect().left+this.#n.getBoundingClientRect().width>t?(this.#n.classList.remove("dropdown-menu__right"),this.#n.classList.add("dropdown-menu__left")):(this.#n.classList.remove("dropdown-menu__left"),this.#n.classList.add("dropdown-menu__right"))};show(){il.UI.dropdown.opened?.hide(),il.UI.dropdown.opened=this,this.#n.style.display="block",this.#l(),this.#i.setAttribute("aria-expanded","true"),this.#t.addEventListener("keydown",this.#o),this.#t.addEventListener("click",this.#d),this.#i.removeEventListener("click",this.#s)}hide(){this.#n.style.display="none",this.#i.setAttribute("aria-expanded","false"),this.#t.removeEventListener("keydown",this.#o),this.#t.removeEventListener("click",this.#d),this.#i.addEventListener("click",this.#s)}}t.UI=t.UI||{},t.UI.dropdown={},t.UI.dropdown.opened=null,t.UI.dropdown.init=function(t){return new e(t)}}(il); +!function(t){"use strict";class e{#t;#e;#i;#n;constructor(t){if(this.#e=t,this.#t=t.ownerDocument,this.#i=this.#e.querySelector(":scope > button"),null===this.#i)throw new Error("Dropdown: Expected exactly one button in dropdown element.",this.#e);if(this.#n=this.#e.querySelector(".dropdown-menu"),null===this.#n)throw new Error("Dropdown: Expected exactly a dropdown element.",this.#e);this.#i.addEventListener("click",this.#s)}#o=t=>{27===t.key&&this.hide()};#s=t=>{t.stopPropagation(),this.show()};#d=()=>{this.hide()};#l=()=>{const t=this.#t.documentElement.clientWidth;this.#i.getBoundingClientRect().left+this.#n.getBoundingClientRect().width>t?(this.#n.classList.remove("dropdown-menu__right"),this.#n.classList.add("dropdown-menu__left")):(this.#n.classList.remove("dropdown-menu__left"),this.#n.classList.add("dropdown-menu__right"))};show(){il.UI.dropdown.opened?.hide(),il.UI.dropdown.opened=this,this.#n.style.display="block",this.#l(),this.#i.setAttribute("aria-expanded","true"),this.#t.addEventListener("keydown",this.#o),this.#t.addEventListener("click",this.#d),this.#i.removeEventListener("click",this.#s)}hide(){this.#n.style.display="none",this.#i.setAttribute("aria-expanded","false"),this.#t.removeEventListener("keydown",this.#o),this.#t.removeEventListener("click",this.#d),this.#i.addEventListener("click",this.#s)}}t.UI=t.UI||{},t.UI.dropdown={},t.UI.dropdown.opened=null,t.UI.dropdown.init=function(t){return new e(t)}}(il); diff --git a/components/ILIAS/UI/resources/js/Dropdown/src/Dropdown.js b/components/ILIAS/UI/resources/js/Dropdown/src/Dropdown.js index ce857db48e6f..fb973fac1e28 100644 --- a/components/ILIAS/UI/resources/js/Dropdown/src/Dropdown.js +++ b/components/ILIAS/UI/resources/js/Dropdown/src/Dropdown.js @@ -46,9 +46,9 @@ export default class Dropdown { throw new Error('Dropdown: Expected exactly one button in dropdown element.', this.#element); } - this.#list = this.#element.querySelector(':scope > ul'); + this.#list = this.#element.querySelector('.dropdown-menu'); if (this.#list === null) { - throw new Error('Dropdown: Expected exactly one ul in dropdown element.', this.#element); + throw new Error('Dropdown: Expected exactly a dropdown element.', this.#element); } this.#button.addEventListener('click', this.#showOnClick); @@ -88,7 +88,6 @@ export default class Dropdown { } else { this.#list.classList.remove('dropdown-menu__left'); this.#list.classList.add('dropdown-menu__right'); - } }; diff --git a/components/ILIAS/UI/resources/js/Dropzone/File/dropzone.js b/components/ILIAS/UI/resources/js/Dropzone/File/dropzone.js index 5daa1671d8b4..f541eba98331 100755 --- a/components/ILIAS/UI/resources/js/Dropzone/File/dropzone.js +++ b/components/ILIAS/UI/resources/js/Dropzone/File/dropzone.js @@ -12,7 +12,7 @@ * https://www.ilias.de * https://github.com/ILIAS-eLearning * - * ******************************************************************* + ********************************************************************* * * this script is responsible for the dropzone highlighting and * file processing. @@ -32,6 +32,7 @@ il.UI = il.UI || {}; dropzone: '.ui-dropzone', dropzone_container: '.ui-dropzone-container', file_input: '.ui-input-file', + cancel_button: '.modal-footer button[data-dismiss]', }; /** @@ -76,6 +77,10 @@ il.UI = il.UI || {}; file_input_id: dropzone.find(SELECTOR.file_input).closest('.c-input').attr('id'), }; + dropzone.find(SELECTOR.cancel_button).on('click', () => { + cancelFileUploads(dropzone_id); + }); + initDropzoneEventListeners(dropzone); initGlobalEventListeners(); } @@ -126,6 +131,21 @@ il.UI = il.UI || {}; } } + let hasEventFiles = function (event) { + // check if "Files" is in the event.originalEvent.dataTransfer.types + if (event.originalEvent.dataTransfer.types.length === 0) { + return false; + } + let is_file = false; + for (let i = 0; i < event.originalEvent.dataTransfer.types.length; i++) { + if (event.originalEvent.dataTransfer.types[i] === 'Files') { + is_file = true; + break; + } + } + return is_file; + } + let removeAllFilesFromQueue = function (dropzone_id) { if (typeof dropzones[dropzone_id] === 'undefined') { console.error(`Error: tried accessing unknown dropzone '${dropzone_id}'.`); @@ -134,10 +154,21 @@ il.UI = il.UI || {}; il.UI.Input.File.removeAllFilesFromQueue(dropzones[dropzone_id].file_input_id); } + let cancelFileUploads = function (dropzone_id) { + if (typeof dropzones[dropzone_id] === 'undefined') { + console.error(`Error: tried accessing unknown dropzone '${dropzone_id}'.`); + return; + } + il.UI.Input.File.cancelFleUpload(dropzones[dropzone_id].file_input_id); + } + /** * @param {Event} event */ let highlightCurrentDropzoneHook = function (event) { + if(!hasEventFiles(event)) { + return; + } event.preventDefault(); let current_dropzone = $(event.currentTarget); current_dropzone.addClass(CSS.highlight_current); @@ -155,6 +186,10 @@ il.UI = il.UI || {}; * @param {Event} event */ let highlightPossibleDropzones = function (event) { + if(!hasEventFiles(event)) { + return; + } + disableDefaultEventBehaviour(event); drag_enter_counter++; diff --git a/components/ILIAS/UI/resources/js/Input/Field/file.js b/components/ILIAS/UI/resources/js/Input/Field/file.js index f5c5b6a9d378..b9580e590e5c 100755 --- a/components/ILIAS/UI/resources/js/Input/Field/file.js +++ b/components/ILIAS/UI/resources/js/Input/Field/file.js @@ -46,7 +46,7 @@ il.UI.Input = il.UI.Input || {}; expand_glyph: '[data-action="expand"] .glyph', collapse_glyph: '[data-action="collapse"] .glyph', form_submit_buttons: '.il-standard-form-cmd > button', - modal_form_controls: '.modal-footer > button', + modal_form_controls: '.modal-footer button', progress_container: '.ui-input-file-input-progress-container', progress_indicator: '.ui-input-file-input-progress-indicator', @@ -83,13 +83,6 @@ il.UI.Input = il.UI.Input || {}; */ let current_dropzone = 0; - /** - * When a form is processed that holds an instance of this input, - * this holds an instance of it, so it can be submitted manually. - * @type {jQuery|{}} - */ - let current_form = {}; - /** * Holds a list of Dropzone instances mapped to the file input id. * @type {Dropzone[]} @@ -168,6 +161,7 @@ il.UI.Input = il.UI.Input || {}; chunking: should_upload_be_chunked, forceChunking: should_upload_be_chunked, chunkSize: chunk_size_in_bytes, + form: action_button.closest('form'), // override default rendering function. addedfile: file => { @@ -176,6 +170,8 @@ il.UI.Input = il.UI.Input || {}; } ); + dropzones[input_id].options.form.should_submit = true; + initGlobalFileEventListeners(); initDropzoneEventListeners(dropzones[input_id]); maybeToggleActionButtonAndErrorMessage(input_id); @@ -209,11 +205,15 @@ il.UI.Input = il.UI.Input || {}; let initDropzoneEventListeners = function (dropzone) { document.getElementById(dropzone.options.input_id) .closest('form') - .addEventListener('submit', processFormSubmissionHook); + .addEventListener('submit', (event) => { + processFormSubmissionHook(dropzone, event); + }); dropzone.on('maxfilesexceeded', alertMaxFilesReachedHook); dropzone.on('maxfilesreached', disableActionButtonHook); - dropzone.on('queuecomplete', submitCurrentFormHook); + dropzone.on('queuecomplete', () => { + submitCurrentFormHook(dropzone); + }); dropzone.on('processing', enableAutoProcessingHook); dropzone.on('success', setResourceStorageIdHook); dropzone.on('error', function () { @@ -256,7 +256,6 @@ il.UI.Input = il.UI.Input || {}; let removal_glyph = $(this); let input_id = removal_glyph.closest(SELECTOR.file_input).attr('id'); let dropzone = dropzones[input_id]; - current_form.errors = false; if (typeof dropzone === 'undefined') { console.error(`Error: tried to remove file from uninitialized input: '${input_id}'`); @@ -266,9 +265,12 @@ il.UI.Input = il.UI.Input || {}; let file_entry = removal_glyph.closest(SELECTOR.file_list_entry); let file_entry_input = getFileEntryInput(file_entry); + dropzone.options.autoProcessQueue = false; + dropzone.options.form.should_submit = false; dropzone.options.current_file_count--; maybeRemoveFileFromQueue(dropzone, file_entry_input.attr('id')); maybeToggleActionButtonAndErrorMessage(input_id); + setFormControlsDisabledState(dropzone.options.form, false); file_entry.remove(); // only remove files that have a file id and are therefore stored @@ -282,31 +284,17 @@ il.UI.Input = il.UI.Input || {}; } /** + * @param {Dropzone} dropzone * @param {SubmitEvent} event */ - let processFormSubmissionHook = function (event) { + let processFormSubmissionHook = function (dropzone, event) { // emitter will be an HTMLFormElement, but once the proper emitter is set // for NoSubmit signals, this can also be an HTMLButtonElement. - current_form = $(this); - current_form.errors = false; + dropzone.options.form.should_submit = true; event.preventDefault(); - - // NoSubmit forms will have disconnected buttons, since they will currently - // only be used in modals, this ternary can be used. Note that this will - // most likely break in the future and we should definitely refactor this. - let form_controls = (this.parentNode.classList.contains('modal-body')) ? - this.closest('.modal-content').querySelectorAll(SELECTOR.modal_form_controls) : - this.querySelectorAll(SELECTOR.form_submit_buttons); - - // disable all form controls to prevent user from cancelling the upload. - form_controls.forEach(function (element) { - if (element instanceof HTMLButtonElement) { - element.disabled = true; - } - }); - - processCurrentFormDropzones(event); + setFormControlsDisabledState(dropzone.options.form, true); + processCurrentFormDropzones(dropzone, event); } let toggleExpansionGlyphsHook = function () { @@ -347,8 +335,8 @@ il.UI.Input = il.UI.Input || {}; } // abort if the given file is not an allowed file type. - if (dropzones[input_id].options.acceptedFiles !== null && - !dropzones[input_id].options.acceptedFiles.includes(file.type) + if (dropzones[input_id].options.acceptedFiles && + !Dropzone.isValidFile(file, dropzones[input_id].options.acceptedFiles) ) { displayErrorMessage( I18N.invalid_mime.replace('%s', file.type), @@ -410,7 +398,7 @@ il.UI.Input = il.UI.Input || {}; let dropzone = dropzones[file_id_input.closest(SELECTOR.file_input).attr('id')]; if (typeof response.status === 'undefined' || 1 !== response.status) { - current_form.errors = true; + dropzone.options.form.should_submit = false; response.responseText = response.message; ajaxResponseFailureHook(response, file_preview); return false; @@ -420,11 +408,11 @@ il.UI.Input = il.UI.Input || {}; file_id_input.val(response[dropzone.options.file_identifier]); } - let submitCurrentFormHook = function () { + let submitCurrentFormHook = function (dropzone) { // submit the current form only if all dropzones // were processed. - if (current_form.errors === false && ++current_dropzone === current_dropzone_count) { - current_form.submit(); + if (dropzone.options.form.should_submit === true && ++current_dropzone === current_dropzone_count) { + dropzone.options.form.submit(); } } @@ -537,6 +525,38 @@ il.UI.Input = il.UI.Input || {}; } } + /** + * @param {string} input_id + */ + let cancelFileUpload = function (input_id) { + if (typeof dropzones[input_id] === 'undefined') { + console.error(`Error: tried to access unknown input '${input_id}'.`); + return; + } + + dropzones[input_id].options.autoProcessQueue = false; + dropzones[input_id].options.form.should_submit = false; + + setFormControlsDisabledState(dropzones[input_id].options.form, false); + + for (let i = 0; i < dropzones[input_id].files.length; ++i) { + let file = dropzones[input_id].files[i]; + dropzones[input_id].removeFile(file); + registerDropzoneFile(dropzones[input_id], file); + + // remove any progress indicators + let file_id_input = $(`#${file.input_id}`); + let file_preview = file_id_input.closest(SELECTOR.file_list_entry); + if (file_preview) { + let progressContainer = file_preview.find(SELECTOR.progress_container); + if (progressContainer) { + progressContainer.css('display', 'none'); + } + file.progress_storage = 0; + } + } + }; + let removeAllFilesFromQueue = function (input_id) { if (typeof dropzones[input_id] === 'undefined') { console.error(`Error: tried to access unknown input '${input_id}'.`); @@ -551,7 +571,7 @@ il.UI.Input = il.UI.Input || {}; file_preview.remove(); } - dropzones[input_id].removeAllFiles(); + dropzones[input_id].removeAllFiles(true); maybeToggleActionButtonAndErrorMessage(input_id); } @@ -598,9 +618,9 @@ il.UI.Input = il.UI.Input || {}; } } - let processCurrentFormDropzones = function (event) { + let processCurrentFormDropzones = function (dropzone, event) { // retrieve all file inputs of the current form. - let file_inputs = current_form.find(SELECTOR.file_input); + let file_inputs = $(dropzone.options.form).find(SELECTOR.file_input); current_dropzone_count = file_inputs.length; if (typeof file_inputs[Symbol.iterator] === 'function') { @@ -617,7 +637,7 @@ il.UI.Input = il.UI.Input || {}; } } if (to_process === 0) { - current_form.submit(); + dropzone.options.form.submit(); } } else { let input_id = file_inputs.attr('id'); @@ -626,11 +646,31 @@ il.UI.Input = il.UI.Input || {}; if (0 !== dropzone.files.length) { dropzone.processQueue(); } else { - current_form.submit(); + dropzone.options.form.submit(); } } } + /** + * @param {HTMLFormElement} form + * @param {boolean} state + */ + let setFormControlsDisabledState = function (form, state) { + // NoSubmit forms will have disconnected buttons, since they will currently + // only be used in modals, this ternary can be used. Note that this will + // most likely break in the future and we should definitely refactor this. + let form_controls = (form.parentNode.classList.contains('modal-body')) ? + form.closest('.modal-content').querySelectorAll(SELECTOR.modal_form_controls) : + form.querySelectorAll(SELECTOR.form_submit_buttons); + + form_controls.forEach(function (element) { + // ignores cancel button of modals + if (element instanceof HTMLButtonElement && !element.hasAttribute('data-dismiss')) { + element.disabled = state; + } + }); + } + /** * @param {jQuery} file_entry * @return {jQuery} the file-id input @@ -724,6 +764,7 @@ il.UI.Input = il.UI.Input || {}; return { removeAllFilesFromQueue: removeAllFilesFromQueue, renderFileEntry: renderFileEntryHook, + cancelFleUpload: cancelFileUpload, init: init, } })($) diff --git a/components/ILIAS/UI/resources/js/Input/Field/src/Markdown/markdown.class.js b/components/ILIAS/UI/resources/js/Input/Field/src/Markdown/markdown.class.js index d8c20c15cbaa..f99682a0b2f0 100755 --- a/components/ILIAS/UI/resources/js/Input/Field/src/Markdown/markdown.class.js +++ b/components/ILIAS/UI/resources/js/Input/Field/src/Markdown/markdown.class.js @@ -13,7 +13,7 @@ * https://github.com/ILIAS-eLearning */ -import Textarea from '../Textarea/textarea.class'; +import Textarea from '../Textarea/textarea.class.js'; /** * @type {string} diff --git a/components/ILIAS/UI/resources/js/Input/Field/src/Markdown/markdown.factory.js b/components/ILIAS/UI/resources/js/Input/Field/src/Markdown/markdown.factory.js index 6c82a6fa683c..545404d2bbb4 100755 --- a/components/ILIAS/UI/resources/js/Input/Field/src/Markdown/markdown.factory.js +++ b/components/ILIAS/UI/resources/js/Input/Field/src/Markdown/markdown.factory.js @@ -1,5 +1,20 @@ -import PreviewRenderer from "./preview.renderer"; -import Markdown from "./markdown.class"; +/** + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + */ + +import PreviewRenderer from "./preview.renderer.js"; +import Markdown from "./markdown.class.js"; /** * @author Thibeau Fuhrer diff --git a/components/ILIAS/UI/resources/js/Input/Field/src/Markdown/preview.renderer.js b/components/ILIAS/UI/resources/js/Input/Field/src/Markdown/preview.renderer.js index be9f1acd2e9c..64369d1fca2a 100755 --- a/components/ILIAS/UI/resources/js/Input/Field/src/Markdown/preview.renderer.js +++ b/components/ILIAS/UI/resources/js/Input/Field/src/Markdown/preview.renderer.js @@ -1,3 +1,18 @@ +/** + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + */ + /** * @author Thibeau Fuhrer */ diff --git a/components/ILIAS/UI/resources/js/Input/Field/src/Textarea/textarea.factory.js b/components/ILIAS/UI/resources/js/Input/Field/src/Textarea/textarea.factory.js index 4d9c24006770..c92730ced15e 100755 --- a/components/ILIAS/UI/resources/js/Input/Field/src/Textarea/textarea.factory.js +++ b/components/ILIAS/UI/resources/js/Input/Field/src/Textarea/textarea.factory.js @@ -1,4 +1,19 @@ -import Textarea from "./textarea.class"; +/** + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + */ + +import Textarea from "./textarea.class.js"; /** * @author Thibeau Fuhrer diff --git a/components/ILIAS/UI/resources/js/MainControls/dist/maincontrols.min.js b/components/ILIAS/UI/resources/js/MainControls/dist/maincontrols.min.js index f6e57b2f525d..5f37bc629541 100644 --- a/components/ILIAS/UI/resources/js/MainControls/dist/maincontrols.min.js +++ b/components/ILIAS/UI/resources/js/MainControls/dist/maincontrols.min.js @@ -12,4 +12,4 @@ * https://www.ilias.de * https://github.com/ILIAS-eLearning */ -!function(t,e){"use strict";const n="engaged",s="il-maincontrols-metabar",a="il-metabar-slates",i="il-metabar-more-button",o="il-metabar-more-slate",r="il-maincontrols-slate",l="engaged";function c(t){t.removeClass(n),t.attr("aria-expanded",!1)}class u{#t;#e;#n;#s;#a;#i;#o;constructor(t,e,n,s,a,i){this.#t=t,this.#e=e,this.#s=n,this.#a=s,this.#i=a,this.#o=i}registerSignals(t,e){this.#t(document).on(t,((t,e)=>(this.#r(t,e),this.#s()&&this.#i(),!1))),this.#t(document).on(e,(()=>(this.onClickDisengageAll(),!1))),this.#t(`.${s}`).on("click",(()=>{this.#n=!0})),this.#t("body").on("click",(()=>{this.#n?this.#n=!1:this.onClickDisengageAll()})),this.#t(`.${a} > .${r}`).on("focusout",(t=>{if(!this.#s()){const e=t.relatedTarget,n=t.currentTarget;null==e||this.#t.contains(n,e)||this.onClickDisengageAll()}}))}#r(t,e){const s=e.triggerer;!function(t){return t.hasClass(n)}(s)?(this.disengageAll(),function(t){t.addClass(n),t.attr("aria-expanded",!0)}(s),setTimeout((()=>{const t=s[0];this.focusInEngagedSlate(t)}),10)):c(s)}onClickDisengageAll(){this.disengageAllButtons(),this.disengageAllSlates()}disengageAllButtons(){document.querySelectorAll(`#${this.#e}.${s} > li > .btn.${n}`).forEach((t=>{c($(t))}));document.querySelectorAll(`#${this.#e}.${s} > li > .${a} > .${o} > .il-maincontrols-slate-content > .btn.${n}`).forEach((t=>{c($(t))}))}disengageAllSlates(){this.getEngagedSlates().each(((t,e)=>{this.#o(this.#t(e))}))}disengageAll(){this.disengageAllSlates(),this.disengageAllButtons()}getEngagedSlates(){const t=`#${this.#e} .${r}.${l}`;return this.#t(t)}focusInEngagedSlate(t){const e=function(t,e){let n=t.parentElement;for(;n&&!n.classList.contains(e);)n=n.parentElement;return n||console.warn(`No parent element with class "${e}" found.`),n}(t,s);if(e){const t=e.querySelector(`.${r}.${l}`);if(t){const e=t.querySelector("input, button");e&&e.focus()}}}init(){this.#l(),this.#c(),this.#s()?this.#u():this.#h(),this.#t(`.${s}`).css("visibility","visible"),this.#t(`#${this.#e} .${a}`).children(`.${r}`).attr("aria-hidden",!0)}#u(){this.#g(),this.#d().hide(),this.getMoreButton().show(),this.#y()}#h(){this.getMoreButton().hide(),this.#d().show()}#l(){if(0===this.getMoreButton().length){const t=this.#t(`#${this.#e}.${s}`).find(".btn, .il-link").last();this.#t(t).addClass(i)}}#c(){if(0===this.#S().length){const t=this.#t(`#${this.#e} .${a}`).children(`.${r}`).last();this.#t(t).addClass(o)}}getMoreButton(){return this.#t(`.${i}`)}#S(){return this.#t(`.${o}`)}#d(){return this.#t(`#${this.#e}.${s}`).children("li").children(".btn, .il-link").not(`.${i}`)}#g(){const t=this.#S().children(".il-maincontrols-slate-content");0===t.children().length&&this.#d().clone(!0,!0).appendTo(t)}#y(){const t=this.#a.getCounterObjectOrNull(this.#S());t&&this.#a.getCounterObject(this.getMoreButton()).setNoveltyTo(t.getNoveltyCount()).setStatusTo(t.getStatusCount())}}const h="engaged",g="disengaged";function d(t){return t.hasClass(h)}function y(t){t.removeClass(g),t.addClass(h),t.attr("aria-expanded","true"),t.attr("aria-hidden","false")}function S(t){t.removeClass(h),t.addClass(g),t.attr("aria-expanded","false"),t.attr("aria-hidden","true")}function m(t){d(t)?S(t):y(t)}var f=function(t){var e="il-counter",n={getCounterObject:function(t){let e;return e=s(t),console.assert(e.length>0,"Passed jQuery Object does not contain a counter"),a(e)},getCounterObjectOrNull:function(t){let e;return e=s(t),0===e.length?null:a(e)}},s=function(n){console.assert(n instanceof t,"$object_containing_counter is not a jQuery Object, param: "+n);var s=n;return n.hasClass(e)||(s=n.find("."+e)),s},a=function(e){return b.bind({})(e,t)};return n},b=function(t,e){const n=" Counter does not exist in the DOM. Make sure the respective Counter type has been rendered before applying this operations.",s=" is not a number";this.getStatusCount=function(){return r(o(t))},this.getNoveltyCount=function(){return r(i(t))},this.hasNoveltyObject=function(){return i(t).length>0},this.hasStatusObject=function(){return o(t).length>0},this.setNoveltyTo=function(e){console.assert(this.hasNoveltyObject(),"Novelty "+n),console.assert("number"==typeof e,e+s);var a=i(t);return a.html(e),0===e?a.hide():a.show(),this},this.setStatusTo=function(e){console.assert(this.hasStatusObject(),"Status "+n),console.assert("number"==typeof e,e+s);var a=o(t);return a.html(e),0===e?a.hide():a.show(),this},this.incrementNoveltyCount=function(e){return console.assert(this.hasNoveltyObject(),"Novelty "+n),console.assert("number"==typeof e,e+s),l((function(t){t.hasNoveltyObject()&&t.setNoveltyTo(t.getNoveltyCount()+e)}),t),this},this.decrementNoveltyCount=function(e){return console.assert(this.hasNoveltyObject(),"Novelty "+n),console.assert("number"==typeof e,e+s),l((function(t){t.hasNoveltyObject()&&t.setNoveltyTo(t.getNoveltyCount()-e)}),t),this},this.incrementStatusCount=function(e){return console.assert(this.hasStatusObject(),"Status "+n),console.assert("number"==typeof e,e+s),l((function(t){t.hasStatusObject()&&t.setStatusTo(t.getStatusCount()+e)}),t),this},this.decrementStatusCount=function(e){return console.assert(this.hasStatusObject(),"Status "+n),console.assert("number"==typeof e,e+s),l((function(t){t.hasStatusObject()&&t.setStatusTo(t.getStatusCount()-e)}),t),this},this.setTotalNoveltyToStatusCount=function(){return console.assert(this.hasStatusObject(),"Status "+n),console.assert(this.hasNoveltyObject(),"Novelty "+n),this.incrementStatusCount(this.getNoveltyCount()).setNoveltyTo(0)};var a={getNoveltyCount:this.getNoveltyCount,getStatusCount:this.getStatusCount,hasNoveltyObject:this.hasNoveltyObject,hasStatusObject:this.hasStatusObject,setNoveltyTo:this.setNoveltyTo,setStatusTo:this.setStatusTo,incrementNoveltyCount:this.incrementNoveltyCount,decrementNoveltyCount:this.decrementNoveltyCount,incrementStatusCount:this.incrementStatusCount,decrementStatusCount:this.decrementStatusCount,setTotalNoveltyToStatusCount:this.setTotalNoveltyToStatusCount},i=function(t){return t.find(".il-counter-novelty")},o=function(t){return t.find(".il-counter-status")},r=function(t){var n=0;return t.each((function(){var t=e(this).text();n+=parseInt(t)})),n},l=function(t,n){n.each((function(){var n=f(e).getCounterObject(e(this));t(n,e(this))}))};return a};t.UI=t.UI||{},t.UI.maincontrols=t.UI.maincontrols||{},t.UI.maincontrols.metabar=new class{#t;#m=[];#s;#a;#i;#o;constructor(t,e,n,s,a){this.#t=t,this.#s=e,this.#a=n,this.#i=s,this.#o=a}init(t){if(void 0!==this.#m[t])throw new Error(`Metabar with id '${t}' has already been initialized.`);this.#m[t]=new u(this.#t,t,this.#s,this.#a,this.#i,this.#o)}get(t){return this.#m[t]??null}disengageAll(){Object.values(this.#m).forEach((t=>t.disengageAll()))}}(e,t.UI.page.isSmallScreen,f(e),(()=>t.UI.maincontrols.mainbar.disengageAll()),(e=>t.UI.maincontrols.slate.disengage(e))),t.UI.maincontrols.slate=new class{#t;#f;#b;constructor(t,e,n){this.#t=t,this.#f=e,this.#b=n}onSignal(t,e,n,s){const a=this.#t(`#${s}`),{triggerer:i}=n,o=i.parents(".il-metabar-more-slate").length>0;if("toggle"===t)this.#p(a,i,o);else if("engage"===t)y(a);else{if("replace"!==t)throw new Error(`No such SignalType: ${t}`);this.#C(s,n)}}#p(t,e,n){const s=t.closest(".il-maincontrols-metabar").attr("id"),a=this.#b.get(s);e.attr("id")!==a.getMoreButton().attr("id")?(m(t),n||(d(t)?(e.addClass(h),e.removeClass(g),t.trigger("in_view")):(e.removeClass(h),e.addClass(g)))):a.getEngagedSlates().length>0?a.disengageAllSlates():m(t)}disengage=S;#C(t,e){const{url:n}=e.options;this.#f(t,n,"content")}}(e,function(t){return function(e,n,s){t.ajax({url:n,dataType:"html"}).done((function(n){var a=t("
"+n+"
"),i=a.find("[data-replace-marker='"+s+"']").first();0==i.length?t("#"+e+" [data-replace-marker='"+s+"']").html(n):(t("#"+e+" [data-replace-marker='"+s+"']").first().replaceWith(i),t("#"+e+" [data-replace-marker='"+s+"']").first().after(a.find("[data-replace-marker='script']")))}))}}(e),t.UI.maincontrols.metabar)}(il,$); +!function(t,e){"use strict";const n="engaged",s="il-maincontrols-metabar",a="il-metabar-slates",i="il-metabar-more-button",o="il-metabar-more-slate",r="il-maincontrols-slate",l="engaged";function c(t){t.removeClass(n),t.attr("aria-expanded",!1)}class u{#t;#e;#n;#s;#a;#i;#o;constructor(t,e,n,s,a,i){this.#t=t,this.#e=e,this.#s=n,this.#a=s,this.#i=a,this.#o=i}registerSignals(t,e){this.#t(document).on(t,((t,e)=>(this.#r(t,e),this.#s()&&this.#i(),!1))),this.#t(document).on(e,(()=>(this.onClickDisengageAll(),!1))),this.#t(`.${s}`).on("click",(()=>{this.#n=!0})),this.#t("body").on("click",(()=>{this.#n?this.#n=!1:this.onClickDisengageAll()})),this.#t(`.${a} > .${r}`).on("focusout",(t=>{if(!this.#s()){const e=t.relatedTarget,n=t.currentTarget;null===e||n.contains(e)||this.onClickDisengageAll()}}))}#r(t,e){const s=e.triggerer;!function(t){return t.hasClass(n)}(s)?(this.disengageAll(),function(t){t.addClass(n),t.attr("aria-expanded",!0)}(s),setTimeout((()=>{const t=s[0];this.focusInEngagedSlate(t)}),10)):c(s)}onClickDisengageAll(){this.disengageAllButtons(),this.disengageAllSlates()}disengageAllButtons(){document.querySelectorAll(`#${this.#e}.${s} > li > .btn.${n}`).forEach((t=>{c($(t))}));document.querySelectorAll(`#${this.#e}.${s} > li > .${a} > .${o} > .il-maincontrols-slate-content > .btn.${n}`).forEach((t=>{c($(t))}))}disengageAllSlates(){this.getEngagedSlates().each(((t,e)=>{this.#o(this.#t(e))}))}disengageAll(){this.disengageAllSlates(),this.disengageAllButtons()}getEngagedSlates(){const t=`#${this.#e} .${r}.${l}`;return this.#t(t)}focusInEngagedSlate(t){const e=function(t,e){let n=t.parentElement;for(;n&&!n.classList.contains(e);)n=n.parentElement;return n||console.warn(`No parent element with class "${e}" found.`),n}(t,s);if(e){const t=e.querySelector(`.${r}.${l}`);if(t){const e=t.querySelector("input, button");e&&e.focus()}}}init(){this.#l(),this.#c(),this.#s()?this.#u():this.#h(),this.#t(`.${s}`).css("visibility","visible"),this.#t(`#${this.#e} .${a}`).children(`.${r}`).attr("aria-hidden",!0)}#u(){this.#g(),this.#d().hide(),this.getMoreButton().show(),this.#y()}#h(){this.getMoreButton().hide(),this.#d().show()}#l(){if(0===this.getMoreButton().length){const t=this.#t(`#${this.#e}.${s}`).find(".btn, .il-link").last();this.#t(t).addClass(i)}}#c(){if(0===this.#S().length){const t=this.#t(`#${this.#e} .${a}`).children(`.${r}`).last();this.#t(t).addClass(o)}}getMoreButton(){return this.#t(`.${i}`)}#S(){return this.#t(`.${o}`)}#d(){return this.#t(`#${this.#e}.${s}`).children("li").children(".btn, .il-link").not(`.${i}`)}#g(){const t=this.#S().children(".il-maincontrols-slate-content");0===t.children().length&&this.#d().clone(!0,!0).appendTo(t)}#y(){const t=this.#a.getCounterObjectOrNull(this.#S());t&&this.#a.getCounterObject(this.getMoreButton()).setNoveltyTo(t.getNoveltyCount()).setStatusTo(t.getStatusCount())}}const h="engaged",g="disengaged";function d(t){return t.hasClass(h)}function y(t){t.removeClass(g),t.addClass(h),t.attr("aria-expanded","true"),t.attr("aria-hidden","false")}function S(t){t.removeClass(h),t.addClass(g),t.attr("aria-expanded","false"),t.attr("aria-hidden","true")}function m(t){d(t)?S(t):y(t)}var f=function(t){var e="il-counter",n={getCounterObject:function(t){let e;return e=s(t),console.assert(e.length>0,"Passed jQuery Object does not contain a counter"),a(e)},getCounterObjectOrNull:function(t){let e;return e=s(t),0===e.length?null:a(e)}},s=function(n){console.assert(n instanceof t,"$object_containing_counter is not a jQuery Object, param: "+n);var s=n;return n.hasClass(e)||(s=n.find("."+e)),s},a=function(e){return b.bind({})(e,t)};return n},b=function(t,e){const n=" Counter does not exist in the DOM. Make sure the respective Counter type has been rendered before applying this operations.",s=" is not a number";this.getStatusCount=function(){return r(o(t))},this.getNoveltyCount=function(){return r(i(t))},this.hasNoveltyObject=function(){return i(t).length>0},this.hasStatusObject=function(){return o(t).length>0},this.setNoveltyTo=function(e){console.assert(this.hasNoveltyObject(),"Novelty "+n),console.assert("number"==typeof e,e+s);var a=i(t);return a.html(e),0===e?a.hide():a.show(),this},this.setStatusTo=function(e){console.assert(this.hasStatusObject(),"Status "+n),console.assert("number"==typeof e,e+s);var a=o(t);return a.html(e),0===e?a.hide():a.show(),this},this.incrementNoveltyCount=function(e){return console.assert(this.hasNoveltyObject(),"Novelty "+n),console.assert("number"==typeof e,e+s),l((function(t){t.hasNoveltyObject()&&t.setNoveltyTo(t.getNoveltyCount()+e)}),t),this},this.decrementNoveltyCount=function(e){return console.assert(this.hasNoveltyObject(),"Novelty "+n),console.assert("number"==typeof e,e+s),l((function(t){t.hasNoveltyObject()&&t.setNoveltyTo(t.getNoveltyCount()-e)}),t),this},this.incrementStatusCount=function(e){return console.assert(this.hasStatusObject(),"Status "+n),console.assert("number"==typeof e,e+s),l((function(t){t.hasStatusObject()&&t.setStatusTo(t.getStatusCount()+e)}),t),this},this.decrementStatusCount=function(e){return console.assert(this.hasStatusObject(),"Status "+n),console.assert("number"==typeof e,e+s),l((function(t){t.hasStatusObject()&&t.setStatusTo(t.getStatusCount()-e)}),t),this},this.setTotalNoveltyToStatusCount=function(){return console.assert(this.hasStatusObject(),"Status "+n),console.assert(this.hasNoveltyObject(),"Novelty "+n),this.incrementStatusCount(this.getNoveltyCount()).setNoveltyTo(0)};var a={getNoveltyCount:this.getNoveltyCount,getStatusCount:this.getStatusCount,hasNoveltyObject:this.hasNoveltyObject,hasStatusObject:this.hasStatusObject,setNoveltyTo:this.setNoveltyTo,setStatusTo:this.setStatusTo,incrementNoveltyCount:this.incrementNoveltyCount,decrementNoveltyCount:this.decrementNoveltyCount,incrementStatusCount:this.incrementStatusCount,decrementStatusCount:this.decrementStatusCount,setTotalNoveltyToStatusCount:this.setTotalNoveltyToStatusCount},i=function(t){return t.find(".il-counter-novelty")},o=function(t){return t.find(".il-counter-status")},r=function(t){var n=0;return t.each((function(){var t=e(this).text();n+=parseInt(t)})),n},l=function(t,n){n.each((function(){var n=f(e).getCounterObject(e(this));t(n,e(this))}))};return a};t.UI=t.UI||{},t.UI.maincontrols=t.UI.maincontrols||{},t.UI.maincontrols.metabar=new class{#t;#m=[];#s;#a;#i;#o;constructor(t,e,n,s,a){this.#t=t,this.#s=e,this.#a=n,this.#i=s,this.#o=a}init(t){if(void 0!==this.#m[t])throw new Error(`Metabar with id '${t}' has already been initialized.`);this.#m[t]=new u(this.#t,t,this.#s,this.#a,this.#i,this.#o)}get(t){return this.#m[t]??null}disengageAll(){Object.values(this.#m).forEach((t=>t.disengageAll()))}}(e,(()=>t.UI.page.isSmallScreen()),f(e),(()=>t.UI.maincontrols.mainbar.disengageAll()),(e=>t.UI.maincontrols.slate.disengage(e))),t.UI.maincontrols.slate=new class{#t;#f;#b;constructor(t,e,n){this.#t=t,this.#f=e,this.#b=n}onSignal(t,e,n,s){const a=this.#t(`#${s}`),{triggerer:i}=n,o=i.parents(".il-metabar-more-slate").length>0;if("toggle"===t)this.#p(a,i,o);else if("engage"===t)y(a);else{if("replace"!==t)throw new Error(`No such SignalType: ${t}`);this.#C(s,n)}}#p(t,e,n){const s=t.closest(".il-maincontrols-metabar").attr("id"),a=this.#b.get(s);e.attr("id")!==a.getMoreButton().attr("id")?(m(t),n||(d(t)?(e.addClass(h),e.removeClass(g),t.trigger("in_view")):(e.removeClass(h),e.addClass(g)))):a.getEngagedSlates().length>0?a.disengageAllSlates():m(t)}disengage=S;#C(t,e){const{url:n}=e.options;this.#f(t,n,"content")}}(e,function(t){return function(e,n,s){t.ajax({url:n,dataType:"html"}).done((function(n){var a=t("
"+n+"
"),i=a.find("[data-replace-marker='"+s+"']").first();0==i.length?t("#"+e+" [data-replace-marker='"+s+"']").html(n):(t("#"+e+" [data-replace-marker='"+s+"']").first().replaceWith(i),t("#"+e+" [data-replace-marker='"+s+"']").first().after(a.find("[data-replace-marker='script']")))}))}}(e),t.UI.maincontrols.metabar)}(il,$); diff --git a/components/ILIAS/UI/resources/js/MainControls/rollup.config.js b/components/ILIAS/UI/resources/js/MainControls/rollup.config.js index 7f10728e5991..e00b3ba52758 100755 --- a/components/ILIAS/UI/resources/js/MainControls/rollup.config.js +++ b/components/ILIAS/UI/resources/js/MainControls/rollup.config.js @@ -15,8 +15,8 @@ ******************************************************************** */ import terser from '@rollup/plugin-terser'; -import copyright from '../../../../../../scripts/Copyright-Checker/copyright'; -import preserveCopyright from '../../../../../../scripts/Copyright-Checker/preserveCopyright'; +import copyright from '../../../../../../scripts/Copyright-Checker/copyright.js'; +import preserveCopyright from '../../../../../../scripts/Copyright-Checker/preserveCopyright.js'; export default [ { diff --git a/components/ILIAS/UI/resources/js/MainControls/src/maincontrols.js b/components/ILIAS/UI/resources/js/MainControls/src/maincontrols.js index bc22aac0ff43..62fe95c398ea 100755 --- a/components/ILIAS/UI/resources/js/MainControls/src/maincontrols.js +++ b/components/ILIAS/UI/resources/js/MainControls/src/maincontrols.js @@ -15,17 +15,17 @@ import il from 'il'; import $ from 'jquery'; -import MetabarFactory from './metabar.factory'; -import Slate from './slate.class'; -import replaceContent from '../../Core/src/core.replaceContent'; -import { counterFactory } from '../../Counter/src/counter.main'; +import MetabarFactory from './metabar.factory.js'; +import Slate from './slate.class.js'; +import replaceContent from '../../Core/src/core.replaceContent.js'; +import { counterFactory } from '../../Counter/src/counter.main.js'; il.UI = il.UI || {}; il.UI.maincontrols = il.UI.maincontrols || {}; il.UI.maincontrols.metabar = new MetabarFactory( $, - il.UI.page.isSmallScreen, + () => il.UI.page.isSmallScreen(), counterFactory($), () => il.UI.maincontrols.mainbar.disengageAll(), (slate) => il.UI.maincontrols.slate.disengage(slate), diff --git a/components/ILIAS/UI/resources/js/MainControls/src/metabar.class.js b/components/ILIAS/UI/resources/js/MainControls/src/metabar.class.js index 7944fb2af748..c15a5adf2c34 100755 --- a/components/ILIAS/UI/resources/js/MainControls/src/metabar.class.js +++ b/components/ILIAS/UI/resources/js/MainControls/src/metabar.class.js @@ -197,7 +197,7 @@ export default class Metabar { // 42143: focusInEngagedSlate() set a focus in engaged slates. Clicking outside a focusable // element, but inside the slate triggers focusout with 'nextFocusTarget = null'. // We don't want a click inside the slate to close it, so we exclude this focusout case. - if (nextFocusTarget != null && !this.#jquery.contains(currentSlate, nextFocusTarget)) { + if (nextFocusTarget !== null && !currentSlate.contains(nextFocusTarget)) { this.onClickDisengageAll(); } } diff --git a/components/ILIAS/UI/resources/js/MainControls/system_info.js b/components/ILIAS/UI/resources/js/MainControls/system_info.js index d356da31a9bd..dfae4a6f2a38 100755 --- a/components/ILIAS/UI/resources/js/MainControls/system_info.js +++ b/components/ILIAS/UI/resources/js/MainControls/system_info.js @@ -9,31 +9,31 @@ il.UI.maincontrols = il.UI.maincontrols || {}; * decide and init condensed/wide version */ var init = function (id) { - listener(id); - $(window).resize(function () { + const item = $(`#${id}`); + const more_button = item.find('.il-system-info-more'); + more_button.click(() => { + item.toggleClass('full'); + more_button.hide(); + }); + + maybeShowMoreButton(item, more_button); + $(window).resize(() => { if (!calculating) { - listener(id); + maybeShowMoreButton(item); } }); }; - var listener = function (id) { + const maybeShowMoreButton = function (item, more_button) { calculating = true; - let item = $('#' + id); let content = item.find('.il-system-info-content'); let item_height = item.prop('offsetHeight'); let content_height = content.prop('offsetHeight'); - let more_button = item.find('.il-system-info-more'); if (content_height > item_height) { more_button.show(); - more_button.click(function () { - item.toggleClass('full'); - more_button.hide(); - }); } else { more_button.hide(); - more_button.unbind(); } calculating = false; }; diff --git a/components/ILIAS/UI/resources/js/MathJax/mathjax_config.js b/components/ILIAS/UI/resources/js/MathJax/mathjax_config.js new file mode 100644 index 000000000000..0d91735bc907 --- /dev/null +++ b/components/ILIAS/UI/resources/js/MathJax/mathjax_config.js @@ -0,0 +1,47 @@ +/** + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + * + ******************************************************************** */ + +// Configure MathJax on the page +// see https://docs.mathjax.org/en/latest/options/index.html + +// This script can be async because it does not have to synchronize with any other script. +// This will allow it to run as soon as it loads (since it is small, there is little cost to that), +// meaning the script to load MathJax itself will be inserted as soon as possible, +// so that MathJax can begin downloading as early as possible. +// see https://docs.mathjax.org/en/latest/web/configuration.html + +window.MathJax = { + loader: { + load: ['ui/safe'], + }, + options: { + ignoreHtmlClass: 'c-layout__page', // class that marks tags not to search + processHtmlClass: 'c-legacy__content--latex', // class that marks tags that should be searched + }, + tex: { + inlineMath: [ + ['[tex]', '[/tex]'], + // ['\\(', '\\)'] // prevent native mathjax delimiter + ], + + displayMath: [ + // ['\\[', '\\]'] // prevent native mathjax delimiter + ], + }, + svg: { + fontCache: 'global', + }, +}; diff --git a/components/ILIAS/UI/resources/js/Menu/dist/drilldown.js b/components/ILIAS/UI/resources/js/Menu/dist/drilldown.js index d52fadb3146e..a73f7a407257 100644 --- a/components/ILIAS/UI/resources/js/Menu/dist/drilldown.js +++ b/components/ILIAS/UI/resources/js/Menu/dist/drilldown.js @@ -12,4 +12,4 @@ * https://www.ilias.de * https://github.com/ILIAS-eLearning */ -!function(e,t,s,i){"use strict";class l{#e;#t;#s;constructor(e,t,s,i,l){this.#e=t,this.#t=s,this.#s=i,e(document).on(l,(()=>{this.#i()})),this.#s.setFilterHandler((e=>{"Tab"!==e.key&&"Shift"!==e.key&&this.#l(e)})),this.#s.parseLevel(((e,t,s)=>this.#t.addLevel(e,t,s)),((e,t)=>this.#t.buildLeaf(e,t)),(e=>{this.#r(e)})),this.#r(this.#e.read())}#r(e){this.#t.engageLevel(e),this.#n()}#l(e){this.#t.engageLevel(0),this.#t.filter(e),this.#s.setFiltered(this.#t.getFiltered()),e.target.focus()}#i(){this.#t.upLevel(),this.#n()}#n(){const e=this.#t.getCurrent(),t=this.#t.getParent();let s=2;null===e.parent?s=0:"0"===e.parent&&(s=1),this.#s.setEngaged(e.id),this.#e.store(e.id),this.#s.setHeader(e.headerDisplayElement,t.headerDisplayElement),this.#s.setHeaderBacknav(s),this.#s.correctRightColumnPositionAndHeight(e.id)}}class r{#a="level_id";#d;constructor(e){this.#d=e}#h(){return this.#d}read(){return this.#d.items[this.#a]??0}store(e){this.#d.add(this.#a,e),this.#d.store()}}class n{#c={id:null,parent:null,engaged:!1,headerDisplayElement:"",leaves:[]};#o={index:null,text:null,filtered:!1};#E=[];#m(e,t,s,i){const l={...this.#c};return l.id=e,l.parent=s,l.headerDisplayElement=t,l.leaves=i,l}buildLeaf(e,t){const s={...this.#o};return s.index=e,s.text=t,s}addLevel(e,t,s){const i=this.#E.length.toString(),l=this.#m(i,e,t,s);return this.#E[l.id]=l,this.#E[l.id]}engageLevel(e){this.#E.forEach((t=>{const s=t;s.engaged=!1,t.id===e&&(s.engaged=!0)}))}getCurrent(){const e=this.#E.find((e=>e.engaged));return void 0!==e?e:this.#E[0]}getParent(){const e=this.getCurrent();return e.parent?this.#E[e.parent]:{}}upLevel(){const e=this.getCurrent();e.parent&&this.engageLevel(this.#E[e.parent].id)}#u(e){null!==e&&0!==e||(this.#E[e].filtered=!1,null!==this.#E[e].parent&&0!==this.#E[e].parent&&this.#u(this.#E[e].parent))}filter(e){const t=e.target.value.toLowerCase();this.#E.forEach((e=>{e.leaves.forEach((e=>{const s=e;""!==t?!1!==s.text.toLowerCase().includes(t)?s.filtered=!1:s.filtered=!0:s.filtered=!1}))}))}getFiltered(){const e=[];return this.#E.forEach((t=>{const s=t.leaves.filter((e=>e.filtered));if(s.length>0){const i=this.#m(t.id,t.headerDisplayElement,t.parent,[...s]);e.push(i)}})),e}}class a{#g={DRILLDOWN:"c-drilldown",MENU:"c-drilldown__menu",MENU_FILTERED:"c-drilldown--filtered",HEADER_ELEMENT:"c-drilldown__menulevel--trigger",MENU_BRANCH:"c-drilldown__branch",MENU_LEAF:"c-drilldown__leaf",FILTER:"c-drilldown__filter",ACTIVE:"c-drilldown__menulevel--engaged",ACTIVE_ITEM:"c-drilldown__menuitem--engaged",ACTIVE_PARENT:"c-drilldown__menulevel--engagedparent",FILTERED:"c-drilldown__menuitem--filtered",WITH_BACKLINK_ONE_COL:"c-drilldown__header--showbacknav",WITH_BACKLINK_TWO_COL:"c-drilldown__header--showbacknavtwocol",HEADER_TAG:"header",LIST_TAG:"ul",LIST_ELEMENT_TAG:"li",ID_ATTRIBUTE:"data-ddindex"};#L={dd:null,header:null,levels:[]};#p;#v;constructor(e,t,s){this.#p=e,this.#v=t,this.#L.dd=e.getElementById(s),[this.#L.header]=this.#L.dd.getElementsByTagName(this.#g.HEADER_TAG)}#_(){return this.#L.dd.querySelector(`.${this.#g.MENU}`)}setFilterHandler(e){this.#L.header.querySelector(`.${this.#g.FILTER} > input`).addEventListener("keyup",e)}parseLevel(e,t,s){this.#_().querySelectorAll(this.#g.LIST_TAG).forEach((i=>{const l=e(this.#T(i),this.#I(i),this.#f(i,t));this.#A(i,l.id),a.registerHandler(i,s,l.id),this.#L.levels[l.id]=i}))}#A(e,t){e.setAttribute(this.#g.ID_ATTRIBUTE,t)}#T(e){const t=e.previousElementSibling;if(null===t)return null;let s=null;return s=this.#p.createElement("h2"),s.innerText=t.childNodes[0].nodeValue,s}#I(e){return e.parentElement.parentElement.getAttribute(this.#g.ID_ATTRIBUTE)}#f(e,t){const s=e.querySelectorAll(`:scope >.${this.#g.MENU_LEAF}`),i=[];return s.forEach(((e,s)=>{i.push(t(s,e.firstElementChild.innerText))})),i}static registerHandler(e,t,s){const i=e.previousElementSibling;null!==i&&i.addEventListener("click",(()=>{t(s)}))}setEngaged(e){this.#L.dd.querySelector(`.${this.#g.ACTIVE}`)?.classList.remove(`${this.#g.ACTIVE}`),this.#L.dd.querySelector(`.${this.#g.ACTIVE_ITEM}`)?.classList.remove(`${this.#g.ACTIVE_ITEM}`),this.#L.dd.querySelector(`.${this.#g.ACTIVE_PARENT}`)?.classList.remove(`${this.#g.ACTIVE_PARENT}`);const t=this.#L.levels[e];t.classList.add(this.#g.ACTIVE);const s=t.parentElement.parentElement;"UL"===s.nodeName?(t.parentElement.classList.add(this.#g.ACTIVE_ITEM),s.classList.add(this.#g.ACTIVE_PARENT)):t.classList.add(this.#g.ACTIVE_PARENT);this.#L.levels[e].children[0].children[0].focus()}setFiltered(e){const t=this.#L.dd.querySelectorAll(`${this.#g.LIST_TAG}`),s=this.#L.dd.querySelectorAll(`.${this.#g.MENU_LEAF}`),i=e.map((e=>e.id)),l=this.#L.dd.querySelectorAll(`.${this.#g.MENU} > ul > .${this.#g.MENU_BRANCH}`);if(this.#L.levels.forEach((e=>{const t=e;t.style.removeProperty("top"),t.style.removeProperty("height")})),s.forEach((e=>{e.classList.remove(this.#g.FILTERED)})),0===e.length)return this.#L.dd.classList.remove(this.#g.MENU_FILTERED),l.forEach((e=>{const t=e;t.firstElementChild.disabled=!1,t.classList.remove(this.#g.FILTERED)})),void this.correctRightColumnPositionAndHeight("0");this.setEngaged(0),this.#L.dd.classList.add(this.#g.MENU_FILTERED),l.forEach((e=>{const t=e;t.firstElementChild.disabled=!0,t.classList.remove(this.#g.FILTERED)})),i.forEach(((s,i)=>{const[l]=[...t].filter((e=>e.getAttribute(this.#g.ID_ATTRIBUTE)===s)),r=l.querySelectorAll(`:scope >.${this.#g.MENU_LEAF}`);e[i].leaves.forEach((e=>r[e.index].classList.add(this.#g.FILTERED)))})),l.forEach((e=>{if(0===e.querySelectorAll(`.${this.#g.MENU_LEAF}:not(.${this.#g.FILTERED})`).length){e.classList.add(this.#g.FILTERED)}}))}setHeader(e,t){this.#L.header.children[1].replaceWith(this.#p.createElement("div")),null!==e?(this.#L.header.firstElementChild.replaceWith(e),null!==t&&this.#L.header.children[1].replaceWith(t)):this.#L.header.firstElementChild.replaceWith(this.#p.createElement("div"))}setHeaderBacknav(e){this.#L.header.classList.remove(this.#g.WITH_BACKLINK_TWO_COL),this.#L.header.classList.remove(this.#g.WITH_BACKLINK_ONE_COL),0!==e&&(e>1&&this.#L.header.classList.add(this.#g.WITH_BACKLINK_TWO_COL),this.#L.header.classList.add(this.#g.WITH_BACKLINK_ONE_COL))}correctRightColumnPositionAndHeight(e){let t=this.#L.levels[e];const s=this.#L.dd.querySelector(`.${this.#g.MENU}`),i=this.#L.dd.querySelector(`.${this.#g.MENU}`).offsetHeight;if(0!==i)this.#L.levels.forEach((e=>{const t=e;t.style.removeProperty("top"),t.style.removeProperty("height")})),"0"===e&&(t=t.querySelector(`:scope > .${this.#g.MENU_BRANCH} > ul`)),0!==t.offsetHeight&&(t.style.top=`-${t.offsetTop}px`,t.style.height=`${i}px`);else{const t=new this.#v((i=>{i[0].target.offsetHeight>0&&(this.correctRightColumnPositionAndHeight(e),t.unobserve(s))}));t.observe(s)}}}i.UI=i.UI||{},i.UI.menu=i.UI.menu||{},i.UI.menu.drilldown=new class{#y=[];#p;#v;#C;#N;constructor(e,t,s,i){this.#p=e,this.#v=t,this.#C=s,this.#N=i}init(e,t,s){if(void 0!==this.#y[e])throw new Error(`Drilldown with id '${e}' has already been initialized.`);null!==this.#p.getElementById(e)&&(this.#y[e]=new l(this.#C,new r(new this.#N.Utilities.CookieStorage(s)),new n,new a(this.#p,this.#v,e),t))}}(e,t,s,i)}(document,ResizeObserver,$,il); +!function(e,t,s,i){"use strict";class l{#e;#t;#s;constructor(e,t,s,i,l){this.#e=t,this.#t=s,this.#s=i,e(document).on(l,(()=>{this.#i()})),this.#s.setFilterHandler((e=>{"Tab"!==e.key&&"Shift"!==e.key&&this.#l(e)})),this.#s.setResizeHandler((()=>{this.#r()})),this.#s.parseLevel(((e,t,s)=>this.#t.addLevel(e,t,s)),((e,t)=>this.#t.buildLeaf(e,t)),(e=>{this.#n(e)})),this.#n(this.#e.read())}#n(e){this.#t.engageLevel(e),this.#r()}#l(e){this.#t.engageLevel(0),this.#t.filter(e),this.#s.setFiltered(this.#t.getFiltered()),e.target.focus()}#i(){this.#t.upLevel(),this.#r()}#r(){const e=this.#t.getCurrent(),t=this.#t.getParent();let s=2;null===e.parent?s=0:"0"===e.parent&&(s=1),this.#s.setEngaged(e.id),this.#e.store(e.id),this.#s.setHeader(e.headerDisplayElement,t.headerDisplayElement),this.#s.setHeaderBacknav(s),this.#s.correctRightColumnPositionAndHeight(e.id)}}class r{#a="level_id";#d;constructor(e){this.#d=e}#h(){return this.#d}read(){return this.#d.items[this.#a]??0}store(e){this.#d.add(this.#a,e),this.#d.store()}}class n{#c={id:null,parent:null,engaged:!1,headerDisplayElement:"",leaves:[]};#o={index:null,text:null,filtered:!1};#E=[];#m(e,t,s,i){const l={...this.#c};return l.id=e,l.parent=s,l.headerDisplayElement=t,l.leaves=i,l}buildLeaf(e,t){const s={...this.#o};return s.index=e,s.text=t,s}addLevel(e,t,s){const i=this.#E.length.toString(),l=this.#m(i,e,t,s);return this.#E[l.id]=l,this.#E[l.id]}engageLevel(e){this.#E.forEach((t=>{const s=t;s.engaged=!1,t.id===e&&(s.engaged=!0)}))}getCurrent(){const e=this.#E.find((e=>e.engaged));return void 0!==e?e:this.#E[0]}getParent(){const e=this.getCurrent();return e.parent?this.#E[e.parent]:{}}upLevel(){const e=this.getCurrent();e.parent&&this.engageLevel(this.#E[e.parent].id)}#u(e){null!==e&&0!==e||(this.#E[e].filtered=!1,null!==this.#E[e].parent&&0!==this.#E[e].parent&&this.#u(this.#E[e].parent))}filter(e){const t=e.target.value.toLowerCase();this.#E.forEach((e=>{e.leaves.forEach((e=>{const s=e;""!==t?!1!==s.text.toLowerCase().includes(t)?s.filtered=!1:s.filtered=!0:s.filtered=!1}))}))}getFiltered(){const e=[];return this.#E.forEach((t=>{const s=t.leaves.filter((e=>e.filtered));if(s.length>0){const i=this.#m(t.id,t.headerDisplayElement,t.parent,[...s]);e.push(i)}})),e}}class a{#g={DRILLDOWN:"c-drilldown",MENU:"c-drilldown__menu",MENU_FILTERED:"c-drilldown--filtered",HEADER_ELEMENT:"c-drilldown__menulevel--trigger",MENU_BRANCH:"c-drilldown__branch",MENU_LEAF:"c-drilldown__leaf",FILTER:"c-drilldown__filter",ACTIVE:"c-drilldown__menulevel--engaged",ACTIVE_ITEM:"c-drilldown__menuitem--engaged",ACTIVE_PARENT:"c-drilldown__menulevel--engagedparent",FILTERED:"c-drilldown__menuitem--filtered",WITH_BACKLINK_ONE_COL:"c-drilldown__header--showbacknav",WITH_BACKLINK_TWO_COL:"c-drilldown__header--showbacknavtwocol",HEADER_TAG:"header",LIST_TAG:"ul",LIST_ELEMENT_TAG:"li",ID_ATTRIBUTE:"data-ddindex"};#L={dd:null,header:null,levels:[]};#p;#v;constructor(e,t,s){this.#p=e,this.#v=t,this.#L.dd=e.getElementById(s),[this.#L.header]=this.#L.dd.getElementsByTagName(this.#g.HEADER_TAG)}#_(){return this.#L.dd.querySelector(`.${this.#g.MENU}`)}setFilterHandler(e){this.#L.header.querySelector(`.${this.#g.FILTER} > input`).addEventListener("keyup",e)}setResizeHandler(e){this.#p.defaultView.addEventListener("resize",e)}parseLevel(e,t,s){this.#_().querySelectorAll(this.#g.LIST_TAG).forEach((i=>{const l=e(this.#T(i),this.#I(i),this.#f(i,t));this.#A(i,l.id),a.registerClickHandler(i,s,l.id),this.#L.levels[l.id]=i}))}#A(e,t){e.setAttribute(this.#g.ID_ATTRIBUTE,t)}#T(e){const t=e.previousElementSibling;if(null===t)return null;let s=null;return s=this.#p.createElement("h2"),s.innerText=t.childNodes[0].nodeValue,s}#I(e){return e.parentElement.parentElement.getAttribute(this.#g.ID_ATTRIBUTE)}#f(e,t){const s=e.querySelectorAll(`:scope >.${this.#g.MENU_LEAF}`),i=[];return s.forEach(((e,s)=>{i.push(t(s,e.firstElementChild.innerText))})),i}static registerClickHandler(e,t,s){const i=e.previousElementSibling;null!==i&&i.addEventListener("click",(()=>{t(s)}))}setEngaged(e){this.#L.dd.querySelector(`.${this.#g.ACTIVE}`)?.classList.remove(`${this.#g.ACTIVE}`),this.#L.dd.querySelector(`.${this.#g.ACTIVE_ITEM}`)?.classList.remove(`${this.#g.ACTIVE_ITEM}`),this.#L.dd.querySelector(`.${this.#g.ACTIVE_PARENT}`)?.classList.remove(`${this.#g.ACTIVE_PARENT}`);const t=this.#L.levels[e];t.classList.add(this.#g.ACTIVE);const s=t.parentElement.parentElement;"UL"===s.nodeName?(t.parentElement.classList.add(this.#g.ACTIVE_ITEM),s.classList.add(this.#g.ACTIVE_PARENT)):t.classList.add(this.#g.ACTIVE_PARENT);this.#L.levels[e].children[0].children[0].focus()}setFiltered(e){const t=this.#L.dd.querySelectorAll(`${this.#g.LIST_TAG}`),s=this.#L.dd.querySelectorAll(`.${this.#g.MENU_LEAF}`),i=e.map((e=>e.id)),l=this.#L.dd.querySelectorAll(`.${this.#g.MENU} > ul > .${this.#g.MENU_BRANCH}`);if(this.#L.levels.forEach((e=>{const t=e;t.style.removeProperty("top"),t.style.removeProperty("height")})),s.forEach((e=>{e.classList.remove(this.#g.FILTERED)})),0===e.length)return this.#L.dd.classList.remove(this.#g.MENU_FILTERED),l.forEach((e=>{const t=e;t.firstElementChild.disabled=!1,t.classList.remove(this.#g.FILTERED)})),void this.correctRightColumnPositionAndHeight("0");this.setEngaged(0),this.#L.dd.classList.add(this.#g.MENU_FILTERED),l.forEach((e=>{const t=e;t.firstElementChild.disabled=!0,t.classList.remove(this.#g.FILTERED)})),i.forEach(((s,i)=>{const[l]=[...t].filter((e=>e.getAttribute(this.#g.ID_ATTRIBUTE)===s)),r=l.querySelectorAll(`:scope >.${this.#g.MENU_LEAF}`);e[i].leaves.forEach((e=>r[e.index].classList.add(this.#g.FILTERED)))})),l.forEach((e=>{if(0===e.querySelectorAll(`.${this.#g.MENU_LEAF}:not(.${this.#g.FILTERED})`).length){e.classList.add(this.#g.FILTERED)}}))}setHeader(e,t){this.#L.header.children[1].replaceWith(this.#p.createElement("div")),null!==e?(this.#L.header.firstElementChild.replaceWith(e),null!==t&&this.#L.header.children[1].replaceWith(t)):this.#L.header.firstElementChild.replaceWith(this.#p.createElement("div"))}setHeaderBacknav(e){this.#L.header.classList.remove(this.#g.WITH_BACKLINK_TWO_COL),this.#L.header.classList.remove(this.#g.WITH_BACKLINK_ONE_COL),0!==e&&(e>1&&this.#L.header.classList.add(this.#g.WITH_BACKLINK_TWO_COL),this.#L.header.classList.add(this.#g.WITH_BACKLINK_ONE_COL))}correctRightColumnPositionAndHeight(e){let t=this.#L.levels[e];const s=this.#L.dd.querySelector(`.${this.#g.MENU}`),i=this.#L.dd.querySelector(`.${this.#g.MENU}`).offsetHeight;if(0!==i)this.#L.levels.forEach((e=>{const t=e;t.style.removeProperty("top"),t.style.removeProperty("height")})),"0"===e&&(t=t.querySelector(`:scope > .${this.#g.MENU_BRANCH} > ul`)),0!==t.offsetHeight&&(t.style.top=`-${t.offsetTop}px`,t.style.height=`${i}px`);else{const t=new this.#v((i=>{i[0].target.offsetHeight>0&&(this.correctRightColumnPositionAndHeight(e),t.unobserve(s))}));t.observe(s)}}}i.UI=i.UI||{},i.UI.menu=i.UI.menu||{},i.UI.menu.drilldown=new class{#C=[];#p;#v;#y;#N;constructor(e,t,s,i){this.#p=e,this.#v=t,this.#y=s,this.#N=i}init(e,t,s){if(void 0!==this.#C[e])throw new Error(`Drilldown with id '${e}' has already been initialized.`);null!==this.#p.getElementById(e)&&(this.#C[e]=new l(this.#y,new r(new this.#N.Utilities.CookieStorage(s)),new n,new a(this.#p,this.#v,e),t))}}(e,t,s,i)}(document,ResizeObserver,$,il); diff --git a/components/ILIAS/UI/resources/js/Menu/src/drilldown.factory.js b/components/ILIAS/UI/resources/js/Menu/src/drilldown.factory.js index 660f34a90599..a4856d907d44 100755 --- a/components/ILIAS/UI/resources/js/Menu/src/drilldown.factory.js +++ b/components/ILIAS/UI/resources/js/Menu/src/drilldown.factory.js @@ -1,22 +1,23 @@ /** -* This file is part of ILIAS, a powerful learning management system -* published by ILIAS open source e-Learning e.V. -* -* ILIAS is licensed with the GPL-3.0, -* see https://www.gnu.org/licenses/gpl-3.0.en.html -* You should have received a copy of said license along with the -* source code, too. -* -* If this is not the case or you just want to try ILIAS, you'll find -* us at: -* https://www.ilias.de -* https://github.com/ILIAS-eLearning -*/ + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + * + *********************************************************************/ -import Drilldown from './drilldown.main'; -import DrilldownPersistence from './drilldown.persistence'; -import DrilldownModel from './drilldown.model'; -import DrilldownMapping from './drilldown.mapping'; +import Drilldown from './drilldown.main.js'; +import DrilldownPersistence from './drilldown.persistence.js'; +import DrilldownModel from './drilldown.model.js'; +import DrilldownMapping from './drilldown.mapping.js'; export default class DrilldownFactory { /** @@ -75,6 +76,7 @@ export default class DrilldownFactory { this.#instances[drilldownId] = new Drilldown( this.#jQuery, + this.#document, new DrilldownPersistence(new this.#il.Utilities.CookieStorage(persistanceId)), new DrilldownModel(), new DrilldownMapping(this.#document, this.#resizeObserver, drilldownId), diff --git a/components/ILIAS/UI/resources/js/Menu/src/drilldown.js b/components/ILIAS/UI/resources/js/Menu/src/drilldown.js index 77727ed7d509..98e90eed9d03 100755 --- a/components/ILIAS/UI/resources/js/Menu/src/drilldown.js +++ b/components/ILIAS/UI/resources/js/Menu/src/drilldown.js @@ -11,13 +11,14 @@ * us at: * https://www.ilias.de * https://github.com/ILIAS-eLearning - */ + * + *********************************************************************/ import document from 'document'; import ResizeObserver from 'ResizeObserver'; import $ from 'jquery'; import il from 'ilias'; -import DrilldownFactory from './drilldown.factory'; +import DrilldownFactory from './drilldown.factory.js'; il.UI = il.UI || {}; il.UI.menu = il.UI.menu || {}; diff --git a/components/ILIAS/UI/resources/js/Menu/src/drilldown.main.js b/components/ILIAS/UI/resources/js/Menu/src/drilldown.main.js index 3b1014f46b22..a92168f1445a 100755 --- a/components/ILIAS/UI/resources/js/Menu/src/drilldown.main.js +++ b/components/ILIAS/UI/resources/js/Menu/src/drilldown.main.js @@ -32,12 +32,13 @@ export default class Drilldown { /** * @param {jQuery} $ + * @param {Document} document * @param {DrilldownPersistence} persistence * @param {DrilldownModel} model * @param {DrilldownMapping} mapping * @param {string} backSignal */ - constructor($, persistence, model, mapping, backSignal) { + constructor($, document, persistence, model, mapping, backSignal) { this.#persistence = persistence; this.#model = model; this.#mapping = mapping; @@ -50,6 +51,7 @@ export default class Drilldown { } }, ); + this.#mapping.setResizeHandler(() => { this.#apply(); }); this.#mapping.parseLevel( (headerDisplayElement, parent, leaves) => this.#model .addLevel(headerDisplayElement, parent, leaves), diff --git a/components/ILIAS/UI/resources/js/Menu/src/drilldown.mapping.js b/components/ILIAS/UI/resources/js/Menu/src/drilldown.mapping.js index 2cd66dbbeb36..3c9a76312e72 100755 --- a/components/ILIAS/UI/resources/js/Menu/src/drilldown.mapping.js +++ b/components/ILIAS/UI/resources/js/Menu/src/drilldown.mapping.js @@ -1,19 +1,20 @@ /** -* This file is part of ILIAS, a powerful learning management system -* published by ILIAS open source e-Learning e.V. -* -* ILIAS is licensed with the GPL-3.0, -* see https://www.gnu.org/licenses/gpl-3.0.en.html -* You should have received a copy of said license along with the -* source code, too. -* -* If this is not the case or you just want to try ILIAS, you'll find -* us at: -* https://www.ilias.de -* https://github.com/ILIAS-eLearning -*/ + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + * + *********************************************************************/ -export default class DropdownMapping { +export default class DrilldownMapping { /** * @type {object} */ @@ -59,12 +60,12 @@ export default class DropdownMapping { /** * @param {DOMDocument} document * @param {ResizeObserver} resizeObserver - * @param {string} dropdownId + * @param {string} drilldownId */ - constructor(document, resizeObserver, dropdownId) { + constructor(document, resizeObserver, drilldownId) { this.#document = document; this.#resizeObserver = resizeObserver; - this.#elements.dd = document.getElementById(dropdownId); + this.#elements.dd = document.getElementById(drilldownId); [this.#elements.header] = this.#elements.dd.getElementsByTagName(this.#classes.HEADER_TAG); } @@ -83,6 +84,14 @@ export default class DropdownMapping { this.#elements.header.querySelector(`.${this.#classes.FILTER} > input`).addEventListener('keyup', filterHandler); } + /** + * @param {function} filterHandler + * @return {void} + */ + setResizeHandler(resizeHandler) { + this.#document.defaultView.addEventListener('resize', resizeHandler); + } + /** * @param {function} filterHandler * @return {void} @@ -97,7 +106,7 @@ export default class DropdownMapping { this.#getLeavesOfList(sublist, leafBuilder), ); this.#addLevelId(sublist, level.id); - DropdownMapping.registerHandler(sublist, clickHandler, level.id); + DrilldownMapping.registerClickHandler(sublist, clickHandler, level.id); this.#elements.levels[level.id] = sublist; }, ); @@ -163,7 +172,7 @@ export default class DropdownMapping { * @param {string} elementId * @returns {void} */ - static registerHandler(list, handler, elementId) { + static registerClickHandler(list, handler, elementId) { const headerElement = list.previousElementSibling; if (headerElement === null) { return; diff --git a/components/ILIAS/UI/resources/js/Menu/src/drilldown.model.js b/components/ILIAS/UI/resources/js/Menu/src/drilldown.model.js index aefb851e16e9..3392fa6ab8e9 100755 --- a/components/ILIAS/UI/resources/js/Menu/src/drilldown.model.js +++ b/components/ILIAS/UI/resources/js/Menu/src/drilldown.model.js @@ -1,17 +1,18 @@ /** -* This file is part of ILIAS, a powerful learning management system -* published by ILIAS open source e-Learning e.V. -* -* ILIAS is licensed with the GPL-3.0, -* see https://www.gnu.org/licenses/gpl-3.0.en.html -* You should have received a copy of said license along with the -* source code, too. -* -* If this is not the case or you just want to try ILIAS, you'll find -* us at: -* https://www.ilias.de -* https://github.com/ILIAS-eLearning -*/ + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + * + *********************************************************************/ export default class DrilldownModel { /** diff --git a/components/ILIAS/UI/resources/js/Menu/src/drilldown.persistence.js b/components/ILIAS/UI/resources/js/Menu/src/drilldown.persistence.js index 19422209642e..b7efc425ec4a 100755 --- a/components/ILIAS/UI/resources/js/Menu/src/drilldown.persistence.js +++ b/components/ILIAS/UI/resources/js/Menu/src/drilldown.persistence.js @@ -1,17 +1,18 @@ /** -* This file is part of ILIAS, a powerful learning management system -* published by ILIAS open source e-Learning e.V. -* -* ILIAS is licensed with the GPL-3.0, -* see https://www.gnu.org/licenses/gpl-3.0.en.html -* You should have received a copy of said license along with the -* source code, too. -* -* If this is not the case or you just want to try ILIAS, you'll find -* us at: -* https://www.ilias.de -* https://github.com/ILIAS-eLearning -*/ + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + * + *********************************************************************/ export default class DrilldownPersistence { /** diff --git a/components/ILIAS/UI/resources/js/Popover/popover.js b/components/ILIAS/UI/resources/js/Popover/popover.js index 7bbee97ec542..2ff09881558a 100755 --- a/components/ILIAS/UI/resources/js/Popover/popover.js +++ b/components/ILIAS/UI/resources/js/Popover/popover.js @@ -1,109 +1,177 @@ +/** + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + */ + var il = il || {}; il.UI = il.UI || {}; -(function($, UI) { - - UI.popover = (function ($) { - - var defaultOptions = { - // Title of the popover - title: '', - // JQuery selector of the element containing the popover content - url: '', - // How the popover is being triggered: click|hover - trigger: 'click', - // Where the popover is placed: auto|horizontal|vertical - placement: 'auto', - // Allow multiple popovers being opened at the same time - multi: false - }; - - /** - * Internal cache to store the initialized popovers +(function ($, UI) { + UI.popover = (function ($) { + /** + * Tracks the current container to which the scroll listener is bound. + * @type {JQuery|null} */ - var initializedPopovers = {}; + let currentScrollableContainer = null; + /** + * Timeout ID used for debouncing the scroll event. + * @type {number|null} + */ + let repositionTimeout = null; - /** + /** + * Flag indicating whether the scroll listener is currently bound. + * @type {boolean} + */ + let scrollPosRecalcisBound = false; + + /** + * Determine the appropriate scroll container based on viewport width. + * @return {JQuery} + */ + function getScrollableContainer() { + if (window.innerWidth < 768) { + return $('body'); + } + return $(document.querySelector('.il-layout-page-content')); + } + + /** + * Scroll event handler to update the position of open popovers. + * Debounced to prevent excessive calls. + * @return {void} + */ + const updateAfterScroll = function () { + clearTimeout(repositionTimeout); + repositionTimeout = setTimeout(() => { + $('[data-target*="webuiPopover"]').each(function () { + const $triggerer = $(this); + const pop = $triggerer.data('plugin_webuiPopover'); + if (pop && pop._opened) { + pop.displayContent(); + } + }); + }, 30); + }; + + /** + * Sets up a scroll listener on the appropriate container based on screen size. + * Ensures only one container is bound at a time. + * @type {Function} + */ + const scrollPosRecalcHandler = (function () { + return function () { + const newScrollableContainer = getScrollableContainer(); + + // Only rebind if the container has changed + if (scrollPosRecalcisBound && currentScrollableContainer) { + currentScrollableContainer.off('scroll', updateAfterScroll); + scrollPosRecalcisBound = false; + } + + currentScrollableContainer = newScrollableContainer; + + if (!scrollPosRecalcisBound) { + currentScrollableContainer.on('scroll', updateAfterScroll); + scrollPosRecalcisBound = true; + } + }; + }()); + + const defaultOptions = { + title: '', + container: getScrollableContainer(), + url: '', + trigger: 'click', + placement: 'auto', + multi: true, + }; + + const initializedPopovers = {}; + + /** * Show a popover for a triggerer element (the element triggering the show signal) with the given options. - * * @param signalData Object containing all data from the signal * @param options Object with popover options */ - var showFromSignal = function (signalData, options) { - var $triggerer = signalData.triggerer; - if (!$triggerer.length) { - return; - } - var triggererId = $triggerer.attr('id'); - if (signalData.event === 'mouseenter') { - options.trigger = 'hover'; - } - var initialized = show($triggerer, options); - if (initialized === false) { - initializedPopovers[signalData.id] = triggererId; - } - }; - - - /** + const showFromSignal = function (signalData, options) { + const $triggerer = signalData.triggerer; + if (!$triggerer.length) return; + + const triggererId = $triggerer.attr('id'); + if (signalData.event === 'mouseenter') { + options.trigger = 'hover'; + } + + const initialized = show($triggerer, options); + if (initialized === false) { + initializedPopovers[signalData.id] = triggererId; + } + + scrollPosRecalcHandler(); + }; + + /** * Replace the content of the popover showed by the given showSignal with the data returned by the URL * set in the signal options. - * * @param showSignal ID of the show signal for the popover * @param signalData Object containing all data from the replace signal */ - var replaceContentFromSignal = function (showSignal, signalData) { - // Find the ID of the triggerer where this popover belongs to - var triggererId = (showSignal in initializedPopovers) ? initializedPopovers[showSignal] : 0; - if (!triggererId) return; + const replaceContentFromSignal = function (showSignal, signalData) { + const triggererId = (showSignal in initializedPopovers) ? initializedPopovers[showSignal] : 0; + if (!triggererId) return; - var url = signalData.options.url; - var $triggerer = $('#' + triggererId); - var id = $triggerer.attr('data-target'); + const { url } = signalData.options; + const $triggerer = $(`#${triggererId}`); + const id = $triggerer.attr('data-target'); - il.UI.core.replaceContent(id, url, "content"); - }; + il.UI.core.replaceContent(id, url, 'content'); + }; - /** + /** * Show a popover next to the given triggerer element with the provided options - * * @param $triggerer JQuery object acting as triggerer * @param options Object with popover options * @returns {boolean} True if the popover has already been initialized, false otherwise */ - var show = function($triggerer, options) { - - if (WebuiPopovers.isCreated('#' + $triggerer.attr('id'))) { - return true; - } - // webui is moving the content at the end of the document which makes it impossible to use - // popovers in forms without specifying a container here. We search for upper il-popover-container. - // If given we use this element as a container for the popover - var container; - if (container = $('#' + $triggerer.attr('id')).parents(".il-popover-container")[0]) { - options = $.extend({}, {container: container}, options); - - } - options = $.extend({}, {onShow: function($el) { - $el.trigger("il.ui.popover.show");}}, options); - - options = $.extend({}, defaultOptions, options); - // Extend options with data from the signal - $triggerer.webuiPopover(options).webuiPopover('show'); - - - return false; - }; - - /** - * Public interface - */ - return { - showFromSignal: showFromSignal, - replaceContentFromSignal: replaceContentFromSignal, - show: show - }; - - })($); -})($, il.UI); \ No newline at end of file + var show = function ($triggerer, options) { + if (WebuiPopovers.isCreated(`#${$triggerer.attr('id')}`)) { + return true; + } + + let container; + if (container = $(`#${$triggerer.attr('id')}`).parents('.il-popover-container')[0]) { + options = $.extend({}, { container }, options); + } + + options = $.extend({}, { + onShow($el) { + $el.trigger('il.ui.popover.show'); + }, + }, options); + + options = $.extend({}, defaultOptions, options); + + $triggerer.webuiPopover(options).webuiPopover('show'); + + return false; + }; + + return { + showFromSignal, + replaceContentFromSignal, + show, + }; + }($)); +}($, il.UI)); diff --git a/components/ILIAS/UI/resources/js/Table/src/datatable.factory.js b/components/ILIAS/UI/resources/js/Table/src/datatable.factory.js index bd7c956abebb..09ae03437346 100755 --- a/components/ILIAS/UI/resources/js/Table/src/datatable.factory.js +++ b/components/ILIAS/UI/resources/js/Table/src/datatable.factory.js @@ -13,7 +13,7 @@ * https://github.com/ILIAS-eLearning */ -import DataTable from './datatable.class'; +import DataTable from './datatable.class.js'; export default class DataTableFactory { /** diff --git a/components/ILIAS/UI/resources/js/Table/src/presentationtable.factory.js b/components/ILIAS/UI/resources/js/Table/src/presentationtable.factory.js index 59299464622b..d84d6e688d9e 100755 --- a/components/ILIAS/UI/resources/js/Table/src/presentationtable.factory.js +++ b/components/ILIAS/UI/resources/js/Table/src/presentationtable.factory.js @@ -14,7 +14,7 @@ * *********************************************************************/ -import PresentationTable from './presentationtable.class'; +import PresentationTable from './presentationtable.class.js'; export default class PresentationTableFactory { /** diff --git a/components/ILIAS/UI/resources/js/Toast/toast.js b/components/ILIAS/UI/resources/js/Toast/toast.js index b6fe5399a1dd..24a39ee3fe6e 100755 --- a/components/ILIAS/UI/resources/js/Toast/toast.js +++ b/components/ILIAS/UI/resources/js/Toast/toast.js @@ -1,3 +1,19 @@ +/** + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + * + *********************************************************************/ + var il = il || {}; il.UI = il.UI || {}; il.UI.toast = ((UI) => { @@ -5,15 +21,6 @@ il.UI.toast = ((UI) => { let delayTime = 500; let queue = new WeakMap(); - const setToastSettings = (element) => { - if (element.hasAttribute('data-vanish')) { - vanishTime = parseInt(element.dataset.vanish); - } - if (element.hasAttribute('data-delay')) { - delayTime = parseInt(element.dataset.delay); - } - } - const showToast = (element) => { setTimeout(() => {appearToast(element);}, delayTime); } @@ -53,6 +60,5 @@ il.UI.toast = ((UI) => { showToast: showToast, closeToast: closeToast, appearToast: appearToast, - setToastSettings: setToastSettings, } })(il.UI) diff --git a/components/ILIAS/UI/src/Component/Button/Factory.php b/components/ILIAS/UI/src/Component/Button/Factory.php index 5df3a7a2e043..b445fdb3bf2b 100755 --- a/components/ILIAS/UI/src/Component/Button/Factory.php +++ b/components/ILIAS/UI/src/Component/Button/Factory.php @@ -64,9 +64,9 @@ interface Factory * sticky (stay visible on small screens). * accessibility: * 1: > - * Standard buttons MAY define aria-label attribute. Use it in cases - * where a text label is not visible on the screen or when the label - * does not provide enough information about the action. + * Standard buttons MAY define an aria-label attribute. Use it in cases where a text label is not visible + * on the screen, when the button doesn't include a glyph that comes with a fitting aria-label or when the + * label does not provide enough information about the action. * 2: > * Some Buttons can be stateful; when engaged, the state MUST be * reflected in the "aria-pressed"-, respectively the "aria-checked"-attribute. @@ -125,6 +125,10 @@ public function standard(string $label, $action): Standard; * The loading animation rules of the Standard Button MUST be respected. * accessibility: * 1: > + * Primary buttons MAY define an aria-label attribute. Use it in cases where a text label is not visible + * on the screen, when the button doesn't include a glyph that comes with a fitting aria-label or when the + * label does not provide enough information about the action. + * 2: > * Some Buttons can be stateful; when engaged, the state MUST be * reflected in the "aria-pressed"-, respectively the "aria-checked"-attribute. * If the Button is not stateful (which is the default), the diff --git a/components/ILIAS/UI/src/Component/Component.php b/components/ILIAS/UI/src/Component/Component.php index f2110434707e..3019102dd770 100755 --- a/components/ILIAS/UI/src/Component/Component.php +++ b/components/ILIAS/UI/src/Component/Component.php @@ -30,4 +30,12 @@ interface Component * Get the canonical name of the component. */ public function getCanonicalName(): string; + + /** + * The scheme starts at the leaves of the structure and applies the function + * to each leave and moves up the tree recursively. + * @param Closure(Component, array): mixed $fn + */ + public function reduceWith(\Closure $fn): mixed; + } diff --git a/components/ILIAS/UI/src/Component/Input/Field/ColorPicker.php b/components/ILIAS/UI/src/Component/Input/Field/ColorSelect.php similarity index 94% rename from components/ILIAS/UI/src/Component/Input/Field/ColorPicker.php rename to components/ILIAS/UI/src/Component/Input/Field/ColorSelect.php index 9d5debc89a3f..e829581646b1 100755 --- a/components/ILIAS/UI/src/Component/Input/Field/ColorPicker.php +++ b/components/ILIAS/UI/src/Component/Input/Field/ColorSelect.php @@ -25,6 +25,6 @@ /** * @author Patrick Bechtold */ -interface ColorPicker extends FormInput +interface ColorSelect extends FormInput { } diff --git a/components/ILIAS/UI/src/Component/Input/Field/Factory.php b/components/ILIAS/UI/src/Component/Input/Field/Factory.php index 10ed065ba7ef..6b81fb5121af 100755 --- a/components/ILIAS/UI/src/Component/Input/Field/Factory.php +++ b/components/ILIAS/UI/src/Component/Input/Field/Factory.php @@ -50,6 +50,10 @@ interface Factory * 3: > * Text Input MUST NOT be used for letter-only input, an Alphabet Field * is to be used instead. + * 4: > + * If withoutStripTags is set, the consumer MUST make sure the value + * is proberly sanitized before outputing it. + * * interaction: * 1: > * Text Input MUST limit the number of characters, if a certain length @@ -450,6 +454,9 @@ public function textarea(string $label, ?string $byline = null): Textarea; * 2: > * Radios MAY also be used to select between two options * where one is not automatically the inverse of the other + * 3: > + * If withoutStripTags is set, the consumer MUST make sure the value + * is proberly sanitized before outputing it. * wording: * 1: Each option MUST be labeled. * 2: The options' labels MUST state something positive. @@ -711,9 +718,9 @@ public function hidden(): Hidden; * --- * @param string $label * @param string|null $byline - * @return \ILIAS\UI\Component\Input\Field\ColorPicker + * @return \ILIAS\UI\Component\Input\Field\ColorSelect */ - public function colorPicker(string $label, ?string $byline = null): ColorPicker; + public function colorSelect(string $label, ?string $byline = null): ColorSelect; /** * --- diff --git a/components/ILIAS/UI/src/Component/Input/Field/Text.php b/components/ILIAS/UI/src/Component/Input/Field/Text.php index 3d353a8bcef1..f39c25add33d 100755 --- a/components/ILIAS/UI/src/Component/Input/Field/Text.php +++ b/components/ILIAS/UI/src/Component/Input/Field/Text.php @@ -36,4 +36,9 @@ public function withMaxLength(int $max_length): Text; * Gets the max length of the text input */ public function getMaxLength(): ?int; + + /** + * Disable stripping tags from user input + */ + public function withoutStripTags(): Text; } diff --git a/components/ILIAS/UI/src/Component/Input/Field/Textarea.php b/components/ILIAS/UI/src/Component/Input/Field/Textarea.php index ceb34735fbb4..a952bf70d762 100755 --- a/components/ILIAS/UI/src/Component/Input/Field/Textarea.php +++ b/components/ILIAS/UI/src/Component/Input/Field/Textarea.php @@ -53,4 +53,9 @@ public function getMinLimit(); * bool if textarea has max or min number of character limit. */ public function isLimited(): bool; + + /** + * Disable removing tags on user input + */ + public function withoutStripTags(): Textarea; } diff --git a/components/ILIAS/UI/src/Component/Legacy/Factory.php b/components/ILIAS/UI/src/Component/Legacy/Factory.php index 272f69e5caa1..1a97c00a6de4 100755 --- a/components/ILIAS/UI/src/Component/Legacy/Factory.php +++ b/components/ILIAS/UI/src/Component/Legacy/Factory.php @@ -18,7 +18,6 @@ declare(strict_types=1); - namespace ILIAS\UI\Component\Legacy; interface Factory @@ -47,4 +46,30 @@ interface Factory */ public function content(string $content): Content; + /** + * --- + * description: + * purpose: > + * This component is used to wrap an existing ILIAS UI element into a + * UI component AND enable a rendering of LaTeX within. + * + * This is useful if a container of the UI components needs to contain + * content that is not yet implement in the centralized UI components. + * composition: > + * The LatexContent component contains html or any other content as string. + * LaTeX content within must be wrapped by the delimiters [tex] and [/tex] + * + * rules: + * usage: + * 1: > + * This component MUST only be used to ensure backwards compatibility + * with existing UI elements in ILIAS, + * therefore it SHOULD only contain Elements which cannot be generated + * using other UI Components. + * --- + * @param string $content the content of the legacy component + * @return \ILIAS\UI\Component\Legacy\LatexContent + */ + public function latexContent(string $content): LatexContent; + } diff --git a/components/ILIAS/CAS/classes/class.ilAuthFrontendCredentialsCAS.php b/components/ILIAS/UI/src/Component/Legacy/LatexContent.php old mode 100755 new mode 100644 similarity index 78% rename from components/ILIAS/CAS/classes/class.ilAuthFrontendCredentialsCAS.php rename to components/ILIAS/UI/src/Component/Legacy/LatexContent.php index f8075c003954..76193e8fddba --- a/components/ILIAS/CAS/classes/class.ilAuthFrontendCredentialsCAS.php +++ b/components/ILIAS/UI/src/Component/Legacy/LatexContent.php @@ -18,10 +18,14 @@ declare(strict_types=1); +namespace ILIAS\UI\Component\Legacy; + /** - * Auth frontend credentials for CAS auth - * @author Stefan Meyer + * Interface LatexContent + * + * + * @package ILIAS\UI\Component\Legacy */ -class ilAuthFrontendCredentialsCAS extends ilAuthFrontendCredentials +interface LatexContent extends Content { } diff --git a/components/ILIAS/UI/src/Component/Symbol/Glyph/Factory.php b/components/ILIAS/UI/src/Component/Symbol/Glyph/Factory.php index c4d9a7f2ab80..8608f2877229 100755 --- a/components/ILIAS/UI/src/Component/Symbol/Glyph/Factory.php +++ b/components/ILIAS/UI/src/Component/Symbol/Glyph/Factory.php @@ -687,7 +687,6 @@ public function sad(?string $action = null): Glyph; */ public function angry(?string $action = null): Glyph; - /** * --- * description: @@ -1434,7 +1433,6 @@ public function bold(?string $action = null): Glyph; */ public function link(?string $action = null): Glyph; - /** * --- * description: @@ -1562,7 +1560,6 @@ public function preview(?string $action = null): Glyph; */ public function sort(?string $action = null): Glyph; - /** * --- * description: @@ -1585,7 +1582,6 @@ public function sort(?string $action = null): Glyph; */ public function columnSelection(?string $action = null): Glyph; - /** * --- * description: @@ -1612,4 +1608,85 @@ public function columnSelection(?string $action = null): Glyph; * @return \ILIAS\UI\Component\Symbol\Glyph\Glyph */ public function tileView(?string $action = null): Glyph; + + /** + * --- + * description: + * purpose: > + * The Drag Handle Glyph indicates that an element can be dragged by clicking or tapping, then holding and + * moving the mouse or finger. When the hold is released, it's expected to drop the item at the nearest valid + * position. + * The glyph works best when there is a background or border indicating the dimension of the element that is + * draggable. + * composition: > + * The cells of the Ordering Table use this glyph. + * effect: > + * When you click and hold on the glyph, the item it is on can be dragged and dropped. + * rivals: + * No glyph: > + * In some instances the design and context of an element might already sufficiently indicate that it can + * be dragged. However, if an element could be confused with a non-draggable counterpart or is draggable + * only some of the time, you SHOULD use the glyph to indicate when it is draggable or otherwise change the + * appearance to communicate the drag and drop functionality. + * context: + * - The Drag Glyph communicates the drag and drop feature on the Ordering Table cells. + * rules: + * accessibility: + * 1: > + * The aria-label MUST be 'Draggable element'. + * usage: + * 1: The Drag Glyph SHOULD be positioned near the corners of a draggable element. + * --- + * @param string|null $action + * @return \ILIAS\UI\Component\Symbol\Glyph\Glyph + */ + public function dragHandle(?string $action = null): Glyph; + + /** + * --- + * description: + * purpose: > + * The Checked Glyph indicates a positive status (e.g. approved/complete/ok/yes/finished/passed) + * composition: > + * The Checked Glyph uses a checkmark. + * context: + * - The Checked Glyph can be used in combination with the Unchecked Glyph to display binary states. + * rules: + * accessibility: + * 1: > + * The aria-label MUST be 'checked'. + * style: + * 1: > + * The Checked Glyph SHOULD display a checkmark in the geometric focus of a mono-colored symmetric shape + * usage: + * 1: > + * The Checked Glyph SHOULD be used to display a unary state or one option of a binary state. + * --- + * @return \ILIAS\UI\Component\Symbol\Glyph\Glyph + */ + public function checked(): Glyph; + + /** + * --- + * description: + * purpose: > + * The Unchecked Glyph indicates a negative status (e.g. disapproved/blocked/no/failed/rejected) + * composition: > + * The Unchecked Glyph uses a diagonal cross. + * context: + * - The Unchecked Glyph can be used in combination with the Checked Glyph to display binary states. + * rules: + * accessibility: + * 1: > + * The aria-label MUST be 'unchecked'. + * style: + * 1: > + * The Unchecked Glyph SHOULD display a symmetric diagonal cross in the geometric focus of a mono-colored symmetric shape + * usage: + * 1: > + * The Unchecked Glyph SHOULD be used to display a unary state or one option of a binary state. + * --- + * @return \ILIAS\UI\Component\Symbol\Glyph\Glyph + */ + public function unchecked(): Glyph; } diff --git a/components/ILIAS/UI/src/Component/Symbol/Glyph/Glyph.php b/components/ILIAS/UI/src/Component/Symbol/Glyph/Glyph.php index 7ed50d0b81ea..e965297f9334 100755 --- a/components/ILIAS/UI/src/Component/Symbol/Glyph/Glyph.php +++ b/components/ILIAS/UI/src/Component/Symbol/Glyph/Glyph.php @@ -85,6 +85,9 @@ interface Glyph extends Symbol, Clickable public const SORT = "sort"; public const COLUMN_SELECTION = "columnSelection"; public const TILE_VIEW = "tileView"; + public const DRAG_HANDLE = "dragHandle"; + public const CHECKED = "checked"; + public const UNCHECKED = "unchecked"; /** * Get the type of the glyph. diff --git a/components/ILIAS/UI/src/Component/Symbol/Icon/Standard.php b/components/ILIAS/UI/src/Component/Symbol/Icon/Standard.php index 4b39f71ccfa9..68dc66119415 100755 --- a/components/ILIAS/UI/src/Component/Symbol/Icon/Standard.php +++ b/components/ILIAS/UI/src/Component/Symbol/Icon/Standard.php @@ -144,7 +144,8 @@ interface Standard extends Icon public const NOTS = 'nots'; //Notes public const LHTS = 'lhts'; //Learning History public const COMS = 'coms'; //Comments - public const LTIS = 'ltis'; //LTI + public const LTI = 'lti'; //LTI + public const LTIS = 'ltis'; //LTI administration public const CMIS = 'cmis'; //xAPI/cmi5 public const REP = 'rep'; //Repository public const TASK = 'task'; //Task diff --git a/components/ILIAS/UI/src/Component/Table/Factory.php b/components/ILIAS/UI/src/Component/Table/Factory.php index 2c90446ccf38..9925e79627ff 100755 --- a/components/ILIAS/UI/src/Component/Table/Factory.php +++ b/components/ILIAS/UI/src/Component/Table/Factory.php @@ -183,14 +183,15 @@ public function presentation(string $title, array $view_controls, Closure $row_m * initially set to "-1". When focused, this changes to "0". * * --- - * @param string $title - * @param array $columns * @return \ILIAS\UI\Component\Table\Data + * @param DataRetrieval $data_retrieval + * @param string $title + * @param array $columns */ public function data( + DataRetrieval $data_retrieval, string $title, array $columns, - DataRetrieval $data_retrieval ): Data; @@ -294,14 +295,16 @@ public function action(): Action\Factory; * identify and distinguish records. * * --- - * @param string $title - * @param array $columns + * @param OrderingRetrieval $ordering_retrieval + * @param URI $target_url + * @param string $title + * @param array $columns * @return \ILIAS\UI\Component\Table\Ordering */ public function ordering( + OrderingRetrieval $ordering_retrieval, + URI $target_url, string $title, array $columns, - OrderingBinding $retrieval, - URI $target_url ): Ordering; } diff --git a/components/ILIAS/UI/src/Component/Table/OrderingBinding.php b/components/ILIAS/UI/src/Component/Table/OrderingRetrieval.php similarity index 97% rename from components/ILIAS/UI/src/Component/Table/OrderingBinding.php rename to components/ILIAS/UI/src/Component/Table/OrderingRetrieval.php index 1f67c241e031..688c61e0ff66 100644 --- a/components/ILIAS/UI/src/Component/Table/OrderingBinding.php +++ b/components/ILIAS/UI/src/Component/Table/OrderingRetrieval.php @@ -22,7 +22,7 @@ use Generator; -interface OrderingBinding +interface OrderingRetrieval { /** * This is called by the (ordering-)table to retrieve rows; diff --git a/components/ILIAS/UI/src/Component/ViewControl/Factory.php b/components/ILIAS/UI/src/Component/ViewControl/Factory.php index 955ae0f4860c..b15f3e2a9d7b 100755 --- a/components/ILIAS/UI/src/Component/ViewControl/Factory.php +++ b/components/ILIAS/UI/src/Component/ViewControl/Factory.php @@ -46,7 +46,6 @@ interface Factory * accessibility: * 1: The HTML container enclosing the buttons of the Mode View Control MUST cary the role-attribute "group". * 2: The HTML container enclosing the buttons of the Mode View Control MUST set an aria-label describing the element. Eg. "Mode View Control" - * 3: The Buttons of the Mode View Control MUST set an aria-label clearly describing what the button shows if clicked. E.g. "List View", "Month View", ... * --- * @param array $labelled_actions Set of labelled actions (string|string)[]. The label of the action is used as key, the action itself as value. * The first of the actions will be activated by default. diff --git a/components/ILIAS/UI/src/Implementation/Component/Breadcrumbs/Renderer.php b/components/ILIAS/UI/src/Implementation/Component/Breadcrumbs/Renderer.php index 72b5405bd72a..58039e130fc7 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Breadcrumbs/Renderer.php +++ b/components/ILIAS/UI/src/Implementation/Component/Breadcrumbs/Renderer.php @@ -38,11 +38,11 @@ public function render(Component\Component $component, RendererInterface $defaul $tpl = $this->getTemplate("tpl.breadcrumbs.html", true, true); $tpl->setVariable("ARIA_LABEL", $this->txt('breadcrumbs_aria_label')); - - foreach ($component->getItems() as $crumb) { + foreach ($component->getItems() as $key => $crumb) { $tpl->setCurrentBlock("crumbs"); $tpl->setVariable("CRUMB", $default_renderer->render($crumb)); - if (!preg_match('/[a-zA-Z0-9\ ]$/', $crumb->getLabel())) { + if (!preg_match('/[a-zA-Z0-9\ ]$/', $crumb->getLabel()) + && ($key === array_key_last($component->getItems()))) { $tpl->setCurrentBlock("lrmmark"); $tpl->setVariable("LRMMARK", htmlspecialchars_decode("‎", ENT_HTML5)); } diff --git a/components/ILIAS/UI/src/Implementation/Component/Button/Renderer.php b/components/ILIAS/UI/src/Implementation/Component/Button/Renderer.php index b1f51d7d8bfb..13388a37a685 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Button/Renderer.php +++ b/components/ILIAS/UI/src/Implementation/Component/Button/Renderer.php @@ -170,7 +170,6 @@ public function registerResources(ResourceRegistry $registry): void parent::registerResources($registry); $registry->register('assets/js/button.js'); $registry->register("./assets/js/moment-with-locales.min.js"); - $registry->register("./assets/js/bootstrap-datetimepicker.min.js"); } protected function renderClose(Component\Button\Close $component): string diff --git a/components/ILIAS/UI/src/Implementation/Component/Chart/ProgressMeter/Renderer.php b/components/ILIAS/UI/src/Implementation/Component/Chart/ProgressMeter/Renderer.php index cc7868969fa8..184bfbbc56ab 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Chart/ProgressMeter/Renderer.php +++ b/components/ILIAS/UI/src/Implementation/Component/Chart/ProgressMeter/Renderer.php @@ -113,6 +113,8 @@ protected function getDefaultGraphicByComponent( $hasComparison = false ): Template { $main_percentage = $component->getMainValueAsPercent(); + // in some cases, bar needs be adjusted because of a large rounded linecap to visually hit 50% mark exactly + $visual_percentage = $main_percentage; if ($hasComparison) { // multicircle @@ -124,7 +126,7 @@ protected function getDefaultGraphicByComponent( } $tpl->setVariable('COLOR_ONE_CLASS', $color_one_class); // set width for first process bar - $tpl->setVariable('BAR_ONE_WIDTH', $main_percentage); + $tpl->setVariable('BAR_ONE_WIDTH', $visual_percentage); // set second progress bar color class $color_two_class = 'active'; @@ -139,6 +141,11 @@ protected function getDefaultGraphicByComponent( } else { // monocircle $tpl->setCurrentBlock('monocircle'); + // because line endcap is so large, bar value needs to be shorter to visually hit 50% mark + $visual_percentage = $main_percentage - 4; + if ($visual_percentage < 0) { + $visual_percentage = 0; + } // set progress bar color class $color_class = 'no-success'; if ($this->getIsReached($main_percentage, $component->getRequiredAsPercent())) { @@ -146,7 +153,7 @@ protected function getDefaultGraphicByComponent( } $tpl->setVariable('COLOR_ONE_CLASS', $color_class); // set width for process bars - $tpl->setVariable('BAR_ONE_WIDTH', $main_percentage); + $tpl->setVariable('BAR_ONE_WIDTH', $visual_percentage); $tpl->parseCurrentBlock(); } @@ -158,7 +165,11 @@ protected function getDefaultGraphicByComponent( $needle_class = 'no-needle'; if ($component->getRequired() != $component->getMaximum()) { $needle_class = ''; - $tpl->setVariable('ROTATE_ONE', (276 / 100 * $component->getRequiredAsPercent() - 138)); + $needle_value = (270 / 100 * $component->getRequiredAsPercent() - 133); + // because meter is squashed, rotation needs to be gradually modified the further away we get from 0 degrees + // 0 degress is the 50% mark at which this compensation equals 0 + $compensated_needle_value = $needle_value - (7 * (1 - (2 * $main_percentage / 100))); + $tpl->setVariable('ROTATE_ONE', $compensated_needle_value); } $tpl->setVariable('NEEDLE_ONE_CLASS', $needle_class); @@ -201,7 +212,9 @@ protected function modifyVisibleValues(Template $tpl, Component\Component $compo */ protected function getMarkerPos(int $percentage): float { - return round((230 / 100 * ($percentage * 1)) - 115, 2, PHP_ROUND_HALF_UP); + $needle_value = round((230 / 100 * ($percentage * 1)) - 115, 2, PHP_ROUND_HALF_UP); + $compensated_needle_value = $needle_value - (16 * (1 - (2 * $percentage / 100))); + return $compensated_needle_value; } /** diff --git a/components/ILIAS/UI/src/Implementation/Component/ComponentHelper.php b/components/ILIAS/UI/src/Implementation/Component/ComponentHelper.php index f17ba4fe42c9..311139d00f59 100755 --- a/components/ILIAS/UI/src/Implementation/Component/ComponentHelper.php +++ b/components/ILIAS/UI/src/Implementation/Component/ComponentHelper.php @@ -23,6 +23,7 @@ use ILIAS\UI\Component\Signal; use InvalidArgumentException; use Closure; +use ILIAS\UI\Component\Component; /** * Provides common functionality for component implementations. @@ -239,4 +240,28 @@ protected function wrongTypeMessage(string $expected, $value): string return "expected $expected, got $type"; } } + + public function reduceWith(\Closure $fn): mixed + { + $clone = clone $this; + $results = []; + foreach ($clone->getSubComponents() ?? [] as $component) { + $results[] = $component->reduceWith($fn); + } + return $fn($clone, $results); + } + + /** + * Get all components that are contained within this component. Sub components + * could either be contained by construction (like a bulky button gets a glyph + * when it is created) or by internal composition (like a launcher uses a bulky + * button internally). + * + * Defaults to empty array, as many components do not contain any substructure. + */ + protected function getSubComponents(): ?array + { + return null; + } + } diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Container/Container.php b/components/ILIAS/UI/src/Implementation/Component/Input/Container/Container.php index 3b39197bf728..d8c0a8c8d7f4 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Container/Container.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Container/Container.php @@ -149,4 +149,9 @@ protected function setInputGroup(C\Input\Group $input_group): void * since different containers may allow different request methods. */ abstract protected function extractRequestData(ServerRequestInterface $request): InputData; + + public function getSubComponents(): array + { + return $this->getInputs(); + } } diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Field/ColorPicker.php b/components/ILIAS/UI/src/Implementation/Component/Input/Field/ColorSelect.php similarity index 96% rename from components/ILIAS/UI/src/Implementation/Component/Input/Field/ColorPicker.php rename to components/ILIAS/UI/src/Implementation/Component/Input/Field/ColorSelect.php index 9afd9479cab1..e1b48c651d5b 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Field/ColorPicker.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Field/ColorSelect.php @@ -29,7 +29,7 @@ /** * @author Patrick Bechtold */ -class ColorPicker extends FormInput implements C\Input\Field\ColorPicker +class ColorSelect extends FormInput implements C\Input\Field\ColorSelect { /** * Input constructor. diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Factory.php b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Factory.php index 2df65bab3adc..f2cd8318daf7 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Factory.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Factory.php @@ -158,9 +158,9 @@ public function hidden(): Hidden return new Hidden($this->data_factory, $this->refinery); } - public function colorpicker(string $label, ?string $byline = null): ColorPicker + public function colorSelect(string $label, ?string $byline = null): ColorSelect { - return new ColorPicker($this->data_factory, $this->refinery, $label, $byline); + return new ColorSelect($this->data_factory, $this->refinery, $label, $byline); } public function markdown(I\MarkdownRenderer $md_renderer, string $label, ?string $byline = null): Markdown diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Group.php b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Group.php index 91f93e362d70..7ce7d2976a8b 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Group.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Group.php @@ -157,4 +157,9 @@ protected function isClientSideValueOk($value): bool { return $this->_isClientSideValueOk($value); } + + protected function getSubComponents(): ?array + { + return $this->getInputs(); + } } diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Radio.php b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Radio.php index 5ecc096b8461..612ade64b486 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Radio.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Radio.php @@ -51,7 +51,7 @@ class Radio extends FormInput implements C\Input\Field\Radio */ protected function isClientSideValueOk($value): bool { - return ($value === '' || array_key_exists($value, $this->getOptions())); + return ($value === null || array_key_exists($value, $this->getOptions())); } /** @@ -62,8 +62,10 @@ protected function getConstraintForRequirement(): ?Constraint if ($this->requirement_constraint !== null) { return $this->requirement_constraint; } - - return null; + return $this->refinery->logical()->not($this->refinery->null()) + ->withProblemBuilder( + fn($txt, $value) => $txt('required') + ); } /** @@ -105,13 +107,12 @@ public function withInput(InputData $input): self throw new LogicException("Can only collect if input has a name."); } if (!$this->isDisabled()) { - $value = $input->getOr($this->getName(), ""); + $value = $input->getOr($this->getName(), null); $clone = $this->withValue($value); } else { $value = $this->getValue(); $clone = $this; } - $clone->content = $this->applyOperationsTo($value); if ($clone->content->isError()) { return $clone->withError("" . $clone->content->error()); diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Renderer.php b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Renderer.php index ad0ca2d66906..6e283a3479ef 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Renderer.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Renderer.php @@ -146,8 +146,8 @@ public function render(Component\Component $component, RendererInterface $defaul case ($component instanceof F\Hidden): return $this->renderHiddenField($component); - case ($component instanceof F\ColorPicker): - return $this->renderColorPickerField($component, $default_renderer); + case ($component instanceof F\ColorSelect): + return $this->renderColorSelectField($component, $default_renderer); case ($component instanceof F\Rating): return $this->renderRatingField($component, $default_renderer); @@ -387,7 +387,7 @@ protected function renderTagField(F\Tag $component, RendererInterface $default_r if ($value) { $value = array_map( function ($v) { - return ['value' => urlencode($this->convertSpecialCharacters($v)), 'display' => $v]; + return ['value' => urlencode($v), 'display' => $v]; }, $value ); @@ -821,7 +821,7 @@ protected function renderHiddenField(F\Hidden $input): string { $template = $this->getTemplate('tpl.hidden.html', true, true); $this->applyName($input, $template); - $this->applyValue($input, $template); + $this->applyValue($input, $template, $this->escapeSpecialChars()); if ($input->isDisabled()) { $template->setVariable("DISABLED", 'disabled="disabled"'); } @@ -835,7 +835,7 @@ protected function renderHiddenField(F\Hidden $input): string public function registerResources(ResourceRegistry $registry): void { parent::registerResources($registry); - $registry->register('assets/js/tagify.min.js'); + $registry->register('assets/js/tagify.js'); $registry->register('assets/css/tagify.css'); $registry->register('assets/js/tagInput.js'); @@ -980,9 +980,9 @@ protected function prepareDropzoneJsMimeTypes(array $mime_types): string return $mime_type_string; } - protected function renderColorPickerField(F\ColorPicker $component, RendererInterface $default_renderer): string + protected function renderColorSelectField(F\ColorSelect $component, RendererInterface $default_renderer): string { - $tpl = $this->getTemplate("tpl.colorpicker.html", true, true); + $tpl = $this->getTemplate("tpl.color_select.html", true, true); $this->applyName($component, $tpl); $tpl->setVariable('VALUE', $component->getValue()); diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Tag.php b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Tag.php index a9aeb9607343..4131aa5b3e03 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Tag.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Tag.php @@ -75,7 +75,8 @@ protected function addAdditionalTransformations(): void if (count($v) == 1 && $v[0] === '') { return []; } - return array_map("urldecode", $v); + $array = array_map("urldecode", $v); + return array_map('strip_tags', $array); })); } diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Text.php b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Text.php index f9be9a4d9a25..46c1e5a765a0 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Text.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Text.php @@ -24,6 +24,7 @@ use ILIAS\Data\Factory as DataFactory; use ILIAS\Refinery\Constraint; use Closure; +use Generator; /** * This implements the text input. @@ -31,6 +32,7 @@ class Text extends FormInput implements C\Input\Field\Text { private ?int $max_length = null; + private bool $strip_tags_from_input = true; /** * @inheritdoc @@ -42,7 +44,6 @@ public function __construct( ?string $byline ) { parent::__construct($data_factory, $refinery, $label, $byline); - $this->setAdditionalTransformation($refinery->custom()->transformation(fn($v) => strip_tags($v))); } /** @@ -113,4 +114,25 @@ public function isComplex(): bool { return false; } + + /** + * @inheritdoc + */ + public function getOperations(): Generator + { + if ($this->strip_tags_from_input) { + yield $this->refinery->custom()->transformation(fn($v) => strip_tags($v)); + } + yield from parent::getOperations(); + } + + /** + * @inheritdoc + */ + public function withoutStripTags(): Text + { + $clone = clone $this; + $clone->strip_tags_from_input = false; + return $clone; + } } diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Textarea.php b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Textarea.php index a4fe57c84e5f..f3c1fc505192 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Textarea.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Textarea.php @@ -25,6 +25,7 @@ use ILIAS\Data\Factory as DataFactory; use ILIAS\Refinery\Constraint; use Closure; +use Generator; /** * This implements the textarea input. @@ -37,6 +38,8 @@ class Textarea extends FormInput implements C\Input\Field\Textarea protected ?int $min_limit = null; + private bool $strip_tags_from_input = true; + /** * @inheritdoc */ @@ -47,9 +50,6 @@ public function __construct( ?string $byline ) { parent::__construct($data_factory, $refinery, $label, $byline); - $this->setAdditionalTransformation( - $refinery->string()->stripTags() - ); } /** @@ -141,4 +141,25 @@ public function getUpdateOnLoadCode(): Closure }); il.UI.input.onFieldUpdate(event, '$id', $('#$id').val());"; } + + /** + * @inheritdoc + */ + public function getOperations(): Generator + { + if ($this->strip_tags_from_input) { + yield $this->refinery->custom()->transformation(fn($v) => strip_tags($v)); + } + yield from parent::getOperations(); + } + + /** + * @inheritdoc + */ + public function withoutStripTags(): C\Input\Field\Textarea + { + $clone = clone $this; + $clone->strip_tags_from_input = false; + return $clone; + } } diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/ViewControl/Renderer.php b/components/ILIAS/UI/src/Implementation/Component/Input/ViewControl/Renderer.php index 8f5b5766989b..c1f78e9a3005 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/ViewControl/Renderer.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/ViewControl/Renderer.php @@ -273,31 +273,29 @@ protected function renderPagination(Pagination $component, RendererInterface $de } } - $icon_left = $ui_factory->symbol()->glyph()->back(); + $signal = null; if ($current > 0 && count($entries) > 1) { $range = $ranges[$current - 1]; $signal = clone $internal_signal; $signal->addOption('offset', $range->getStart()); $signal->addOption('limit', $limit); - $icon_left = $icon_left ->withOnClick($signal); - } else { - $icon_left = $icon_left->withUnavailableAction(); - $tpl->touchBlock('left_disabled'); } - $tpl->setVariable("LEFT", $default_renderer->render($icon_left)); + $btn_left = $ui_factory->button()->shy('', $signal ?? '#') + ->withSymbol($ui_factory->symbol()->glyph()->back()) + ->withUnavailableAction($signal === null); + $tpl->setVariable("LEFT_ROCKER", $default_renderer->render($btn_left)); - $icon_right = $ui_factory->symbol()->glyph()->next(); + $signal = null; if ($current < count($ranges) - 1) { $range = $ranges[$current + 1]; $signal = clone $internal_signal; $signal->addOption('offset', $range->getStart()); $signal->addOption('limit', $limit); - $icon_right = $icon_right ->withOnClick($signal); - } else { - $icon_right = $icon_right->withUnavailableAction(); - $tpl->touchBlock('right_disabled'); } - $tpl->setVariable("RIGHT", $default_renderer->render($icon_right)); + $btn_right = $ui_factory->button()->shy('', $signal ?? '#') + ->withSymbol($ui_factory->symbol()->glyph()->next()) + ->withUnavailableAction($signal === null); + $tpl->setVariable("RIGHT_ROCKER", $default_renderer->render($btn_right)); } foreach ($component->getLimitOptions() as $option) { diff --git a/components/ILIAS/UI/src/Implementation/Component/Item/Renderer.php b/components/ILIAS/UI/src/Implementation/Component/Item/Renderer.php index 55e77a821857..89516cec270c 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Item/Renderer.php +++ b/components/ILIAS/UI/src/Implementation/Component/Item/Renderer.php @@ -256,7 +256,7 @@ protected function renderNotification(Notification $component, RendererInterface * @var $component Notification */ $component = $component->withAdditionalOnLoadCode( - fn($id) => "il.UI.item.notification.getNotificationItemObject($($id)).registerAggregates($toggleable);" + fn($id) => "il.UI.item.notification.getNotificationItemObject($('#$id')).registerAggregates($toggleable);" ); //Bind id @@ -274,7 +274,7 @@ protected function renderNotification(Notification $component, RendererInterface * @var $close_action Close */ $close_action = $this->getUIFactory()->button()->close()->withAdditionalOnLoadCode( - fn($id) => "il.UI.item.notification.getNotificationItemObject($($id)).registerCloseAction('$url',1);" + fn($id) => "il.UI.item.notification.getNotificationItemObject($('#$id')).registerCloseAction('$url',1);" ); $tpl->setVariable("CLOSE_ACTION", $default_renderer->render($close_action)); } diff --git a/components/ILIAS/UI/src/Implementation/Component/Layout/Page/Renderer.php b/components/ILIAS/UI/src/Implementation/Component/Layout/Page/Renderer.php index 9c4cc1422cb6..37e796a301a7 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Layout/Page/Renderer.php +++ b/components/ILIAS/UI/src/Implementation/Component/Layout/Page/Renderer.php @@ -27,12 +27,11 @@ use ILIAS\UI\Implementation\Render\Template; use ILIAS\UI\Implementation\Render\ResourceRegistry; use iljQueryUtil; -use ilUIFramework; use LogicException; class Renderer extends AbstractComponentRenderer { - public const COOKIE_NAME_SLATES_ENGAGED = 'il_mb_slates'; + public const string COOKIE_NAME_SLATES_ENGAGED = 'il_mb_slates'; /** * @inheritdoc @@ -169,7 +168,6 @@ protected function setHeaderVars(Template $tpl, bool $for_ui_demo = false): Temp $additional_js_files = [ iljQueryUtil::getLocaljQueryPath(), 'assets/js/Basic.js', - ilUIFramework::BOOTSTRAP_JS, './assets/js/jquery.js', './assets/js/jquery-migrate.min.js', ]; diff --git a/components/ILIAS/UI/src/Implementation/Component/Legacy/Factory.php b/components/ILIAS/UI/src/Implementation/Component/Legacy/Factory.php index 6d70d2f55f99..6a9455762a4a 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Legacy/Factory.php +++ b/components/ILIAS/UI/src/Implementation/Component/Legacy/Factory.php @@ -36,4 +36,9 @@ public function content(string $content): Content { return new Content($content, $this->signal_generator); } + + public function latexContent(string $content): LatexContent + { + return new LatexContent($content, $this->signal_generator); + } } diff --git a/components/ILIAS/UI/src/Implementation/Component/Legacy/LatexContent.php b/components/ILIAS/UI/src/Implementation/Component/Legacy/LatexContent.php new file mode 100644 index 000000000000..57d39bd7c5cd --- /dev/null +++ b/components/ILIAS/UI/src/Implementation/Component/Legacy/LatexContent.php @@ -0,0 +1,27 @@ +registerSignals($component); $this->bindJavaScript($component); + + // Wrap LatexContent in a div with a css class that enables the rendering inside + if ($component instanceof Component\Legacy\LatexContent) { + $tpl = $this->getTemplate("tpl.latex_content.html", true, true); + $tpl->setVariable('CONTENT', $component->getContent()); + return $tpl->get(); + } + return $component->getContent(); } @@ -58,4 +67,15 @@ protected function registerSignals(Content $component): Component\JavaScriptBind return $code; }); } + + + /** + * Register additional resources which are needed for the LatexContent component + */ + public function registerResources(ResourceRegistry $registry): void + { + parent::registerResources($registry); + $registry->register('assets/js/mathjax_config.js'); + $registry->register('node_modules/mathjax/es5/tex-chtml-full.js'); + } } diff --git a/components/ILIAS/UI/src/Implementation/Component/MainControls/Renderer.php b/components/ILIAS/UI/src/Implementation/Component/MainControls/Renderer.php index 941a804ea4c5..76d1623ca97f 100755 --- a/components/ILIAS/UI/src/Implementation/Component/MainControls/Renderer.php +++ b/components/ILIAS/UI/src/Implementation/Component/MainControls/Renderer.php @@ -600,7 +600,7 @@ private function permanentLink(string $permanent_url, RendererInterface $rendere return "document.getElementById($id).addEventListener('click', e => il.Footer.permalink.copyText($perm_url) .then(() => il.Footer.permalink.showTooltip(e.target.nextElementSibling, 5000)));"; }; - $button = $this->getUIFactory()->button()->shy($this->txt('copy_perma_link'), '')->withAdditionalOnLoadCode($code); + $button = $this->getUIFactory()->button()->standard($this->txt('copy_perma_link'), '')->withAdditionalOnLoadCode($code); $template->setVariable('PERMANENT', $renderer->render($button)); $template->setVariable('PERMANENT_TOOLTIP', $this->txt('perma_link_copied')); diff --git a/components/ILIAS/UI/src/Implementation/Component/MessageBox/MessageBox.php b/components/ILIAS/UI/src/Implementation/Component/MessageBox/MessageBox.php index e01dd2ffac04..4edd6e01fb35 100755 --- a/components/ILIAS/UI/src/Implementation/Component/MessageBox/MessageBox.php +++ b/components/ILIAS/UI/src/Implementation/Component/MessageBox/MessageBox.php @@ -22,8 +22,9 @@ use ILIAS\UI\Component as C; use ILIAS\UI\Implementation\Component\ComponentHelper; +use ILIAS\UI\Implementation\Component\Prompt\IsPromptContentInternal; -class MessageBox implements C\MessageBox\MessageBox +class MessageBox implements C\MessageBox\MessageBox, IsPromptContentInternal { use ComponentHelper; diff --git a/components/ILIAS/UI/src/Implementation/Component/Prompt/State/Renderer.php b/components/ILIAS/UI/src/Implementation/Component/Prompt/State/Renderer.php index 2a41058c7ed8..d7c22257d2b5 100644 --- a/components/ILIAS/UI/src/Implementation/Component/Prompt/State/Renderer.php +++ b/components/ILIAS/UI/src/Implementation/Component/Prompt/State/Renderer.php @@ -71,7 +71,7 @@ protected function renderState(State $component, RendererInterface $default_rend } $buttons[] = $this->getUIFactory()->button() - ->standard($this->txt('close_prompt'), '') + ->standard($this->txt('close'), '') ->withOnLoadCode( fn($id) => "$('#$id').on('click', (e)=> { let promptId = e.target.closest('dialog').parentNode.id; diff --git a/components/ILIAS/UI/src/Implementation/Component/Symbol/Glyph/Factory.php b/components/ILIAS/UI/src/Implementation/Component/Symbol/Glyph/Factory.php index 2d1ff8486283..ce6025400941 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Symbol/Glyph/Factory.php +++ b/components/ILIAS/UI/src/Implementation/Component/Symbol/Glyph/Factory.php @@ -308,4 +308,19 @@ public function tileView(?string $action = null): Glyph { return new Glyph(G\Glyph::TILE_VIEW, "tile_view", $action); } + + public function dragHandle(?string $action = null): G\Glyph + { + return new Glyph(G\Glyph::DRAG_HANDLE, "drag_handle", $action); + } + + public function checked(): G\Glyph + { + return new Glyph(G\Glyph::CHECKED, "checked"); + } + + public function unchecked(): G\Glyph + { + return new Glyph(G\Glyph::UNCHECKED, "unchecked"); + } } diff --git a/components/ILIAS/UI/src/Implementation/Component/Symbol/Glyph/Glyph.php b/components/ILIAS/UI/src/Implementation/Component/Symbol/Glyph/Glyph.php index b53287fb1b5a..e43d6e4a574c 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Symbol/Glyph/Glyph.php +++ b/components/ILIAS/UI/src/Implementation/Component/Symbol/Glyph/Glyph.php @@ -90,7 +90,10 @@ class Glyph implements C\Symbol\Glyph\Glyph self::PREVIEW, self::SORT, self::COLUMN_SELECTION, - self::TILE_VIEW + self::TILE_VIEW, + self::DRAG_HANDLE, + self::CHECKED, + self::UNCHECKED, ]; private string $type; diff --git a/components/ILIAS/UI/src/Implementation/Component/Symbol/Icon/Standard.php b/components/ILIAS/UI/src/Implementation/Component/Symbol/Icon/Standard.php index 8eeb66ee2a64..8f918585e771 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Symbol/Icon/Standard.php +++ b/components/ILIAS/UI/src/Implementation/Component/Symbol/Icon/Standard.php @@ -139,6 +139,7 @@ class Standard extends Icon implements C\Symbol\Icon\Standard self::NOTS, self::LHTS, self::COMS, + self::LTI, self::LTIS, self::CMIS, self::TASK, diff --git a/components/ILIAS/UI/src/Implementation/Component/Table/Factory.php b/components/ILIAS/UI/src/Implementation/Component/Table/Factory.php index 0e68415f5110..218e3160b3b6 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Table/Factory.php +++ b/components/ILIAS/UI/src/Implementation/Component/Table/Factory.php @@ -49,9 +49,9 @@ public function presentation(string $title, array $view_controls, Closure $row_m } public function data( + T\DataRetrieval $data_retrieval, string $title, array $columns, - T\DataRetrieval $data_retrieval ): Data { return new Data( $this->signal_generator, @@ -77,10 +77,10 @@ public function action(): Action\Factory } public function ordering( + T\OrderingRetrieval $ordering_retrieval, + URI $target_url, string $title, array $columns, - T\OrderingBinding $binding, - URI $target_url ): Ordering { return new Ordering( $this->signal_generator, @@ -89,7 +89,7 @@ public function ordering( $this->ordering_row_builder, $title, $columns, - $binding, + $ordering_retrieval, $target_url, $this->storage ); diff --git a/components/ILIAS/UI/src/Implementation/Component/Table/Ordering.php b/components/ILIAS/UI/src/Implementation/Component/Table/Ordering.php index 229bbf685363..855007dbd3d5 100644 --- a/components/ILIAS/UI/src/Implementation/Component/Table/Ordering.php +++ b/components/ILIAS/UI/src/Implementation/Component/Table/Ordering.php @@ -42,7 +42,7 @@ public function __construct( protected OrderingRowBuilder $row_builder, string $title, array $columns, - protected T\OrderingBinding $binding, + protected T\OrderingRetrieval $binding, protected URI $target_url, \ArrayAccess $storage ) { @@ -65,7 +65,7 @@ public function getRowBuilder(): OrderingRowBuilder ->withVisibleColumns($this->getVisibleColumns()); } - public function getDataBinding(): T\OrderingBinding + public function getDataBinding(): T\OrderingRetrieval { return $this->binding; } diff --git a/components/ILIAS/UI/src/Implementation/Component/Table/Renderer.php b/components/ILIAS/UI/src/Implementation/Component/Table/Renderer.php index 26a31ca2f3a6..7fcc06345547 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Table/Renderer.php +++ b/components/ILIAS/UI/src/Implementation/Component/Table/Renderer.php @@ -217,10 +217,23 @@ public function renderDataTable(Component\Table\Data $component, RendererInterfa $component->getAdditionalParameters() ); + // these column counts and index numbers are meant for aria attributes in the html tpl + $compensate_col_index = 1; // aria-colindex expects counting to start with 1, not 0 + if ($component->hasMultiActions()) { // adds column with checkbox before first data column which is numbered 1. + $compensate_col_index += 1; + } + $compensate_col_count = 0; // count starts with 1, only needs to be compensated for special columns + if ($component->hasMultiActions()) { + $compensate_col_count += 1; + } + if ($component->hasSingleActions()) { // adds column with action dropdown at the very end + $compensate_col_count += 1; + } + $id = $this->bindJavaScript($component); $tpl->setVariable('ID', $id); $tpl->setVariable('TITLE', $component->getTitle()); - $tpl->setVariable('COL_COUNT', (string) $component->getColumnCount()); + $tpl->setVariable('COL_COUNT', (string) $component->getColumnCount() + $compensate_col_count); $tpl->setVariable('VIEW_CONTROLS', $default_renderer->render($view_controls)); $sortation_signal = null; @@ -228,7 +241,7 @@ public function renderDataTable(Component\Table\Data $component, RendererInterfa if (!$rows->valid()) { $this->renderFullWidthDataCell($component, $tpl, $this->txt('ui_table_no_records')); } else { - $this->renderActionsHeader($default_renderer, $component, $tpl); + $this->renderActionsHeader($default_renderer, $component, $tpl, $compensate_col_count); $this->appendTableRows($tpl, $rows, $default_renderer); if ($component->hasMultiActions()) { @@ -239,8 +252,8 @@ public function renderDataTable(Component\Table\Data $component, RendererInterfa $component->getMultiActionSignal(), $modal->getShowSignal() ); - $total_number_of_cols = count($component->getVisibleColumns()) + 2; // + selection column and action dropdown column - $tpl->setVariable('COLUMN_COUNT', (string) $total_number_of_cols); + $multi_action_col_span = count($component->getVisibleColumns()) + $compensate_col_count; + $tpl->setVariable('MULTI_ACTION_SPAN', (string) $multi_action_col_span); $tpl->setVariable('MULTI_ACTION_TRIGGERER', $default_renderer->render($multi_actions_dropdown)); $tpl->setVariable('MULTI_ACTION_ALL_MODAL', $default_renderer->render($modal)); } @@ -255,7 +268,7 @@ public function renderDataTable(Component\Table\Data $component, RendererInterfa } } - $this->renderTableHeader($default_renderer, $component, $tpl, $sortation_signal); + $this->renderTableHeader($default_renderer, $component, $tpl, $sortation_signal, $compensate_col_index); return $tpl->get(); } @@ -263,7 +276,8 @@ protected function renderTableHeader( RendererInterface $default_renderer, Component\Table\Data $component, Template $tpl, - ?Component\Signal $sortation_signal + ?Component\Signal $sortation_signal, + int $compensate_col_index, ): void { $order = $component->getOrder(); $glyph_factory = $this->getUIFactory()->symbol()->glyph(); @@ -276,18 +290,19 @@ protected function renderTableHeader( $col_title = $col->getTitle(); if ($col_id === $sort_col) { if ($sort_direction === Order::ASC) { - $sortation = $this->txt('order_option_generic_ascending'); + $sortation = "ascending"; // aria-sort should not be translated and always be in English $sortation_glyph = $glyph_factory->sortAscending("#"); $param_sort_direction = Order::DESC; } if ($sort_direction === Order::DESC) { - $sortation = $this->txt('order_option_generic_descending'); + $sortation = "descending"; // aria-sort should not be translated and always be in English $sortation_glyph = $glyph_factory->sortDescending("#"); } } $tpl->setCurrentBlock('header_cell'); - $tpl->setVariable('COL_INDEX', (string) $col->getIndex()); + + $tpl->setVariable('COL_INDEX', (string) $col->getIndex() + $compensate_col_index); if ($col->isSortable() && !is_null($sortation_signal)) { $sort_signal = clone $sortation_signal; @@ -312,10 +327,11 @@ protected function renderTableHeader( protected function renderActionsHeader( RendererInterface $default_renderer, Component\Table\Table $component, - Template $tpl + Template $tpl, + int $compensate_col_count, ): void { if ($component->hasSingleActions()) { - $tpl->setVariable('COL_INDEX_ACTION', (string) count($component->getColumns())); + $tpl->setVariable('COL_INDEX_ACTION', (string) $component->getColumnCount() + $compensate_col_count); $tpl->setVariable('COL_TITLE_ACTION', $this->txt('actions')); } @@ -330,6 +346,12 @@ protected function renderActionsHeader( $tpl->setVariable('SELECTION_CONTROL_SELECT', $default_renderer->render($select_all)); $tpl->setVariable('SELECTION_CONTROL_DESELECT', $default_renderer->render($select_none)); } + + if ($component instanceof Component\Table\Ordering) { + if (!$component->isOrderingDisabled() && !$component->hasMultiActions()) { + $tpl->touchBlock('header_rowselection_cell'); + } + } } /** @@ -528,6 +550,7 @@ public function renderOrderingRow(Component\Table\OrderingRow $component, Render $cell_tpl = $this->getTemplate("tpl.orderingcell.html", true, true); $this->fillCells($component, $cell_tpl, $default_renderer); + if ($component->isOrderingDisabled()) { return $cell_tpl->get(); } @@ -543,7 +566,8 @@ public function getNewDedicatedName(string $dedicated_name): string } }; - $input = $this->getUIFactory()->input()->field()->numeric('order') + $numeric_label = $this->txt("ui_table_order"); + $input = $this->getUIFactory()->input()->field()->numeric($numeric_label) ->withDedicatedName($component->getId()) ->withNameFrom($namesource) ->withValue($component->getPosition() * 10); @@ -565,7 +589,6 @@ protected function fillCells( } $cell_tpl->setCurrentBlock('cell'); $cell_tpl->setVariable('COL_TYPE', strtolower($column->getType())); - $cell_tpl->setVariable('COL_INDEX', $column->getIndex()); $cell_content = $row->getCellContent($col_id); if ($cell_content instanceof Component\Component) { $cell_content = $default_renderer->render($cell_content); @@ -585,6 +608,14 @@ protected function fillCells( ); $cell_tpl->setVariable('ACTION_CONTENT', $default_renderer->render($row_actions_dropdown)); } + + if ($row instanceof Component\Table\OrderingRow) { + if (!$row->isOrderingDisabled()) { + $drag_handle = $this->getUIFactory()->symbol()->glyph()->dragHandle(); + $drag_handle = $default_renderer->render($drag_handle); + $cell_tpl->setVariable('DRAG_HANDLE', $drag_handle); + } + } } /** @@ -625,30 +656,50 @@ public function renderOrderingTable(Component\Table\Ordering $component, Rendere ); } + // these column counts and index numbers are meant for aria attributes in the html tpl + $compensate_col_index = 1; // aria-colindex expects counting to start with 1, not 0 + // for column with checkbox and/or drag handle before first data column which is numbered 1. + if ($component->hasMultiActions() || !$component->isOrderingDisabled()) { + $compensate_col_index += 1; // checkbox & drag handle + } + if (!$component->isOrderingDisabled()) { + $compensate_col_index += 1; // Position input + } + + $compensate_col_count = 0; // count starts with 1, only needs to be compensated for special columns + if ($component->hasMultiActions() || !$component->isOrderingDisabled()) { + $compensate_col_count += 1; // checkbox & drag handle + } + if (!$component->isOrderingDisabled()) { + $compensate_col_count += 1; // Position input + } + if ($component->hasSingleActions()) { // adds column with action dropdown at the very end + $compensate_col_count += 1; + } + $tableid = $this->bindJavaScript($component) ?? $this->createId(); - $total_number_of_cols = count($component->getVisibleColumns()); if (!$component->isOrderingDisabled()) { - $total_number_of_cols = $total_number_of_cols + 1; $submit = $this->getUIFactory()->button()->standard($this->txt('sorting_save'), "") ->withOnLoadCode(static fn($id) => "document.getElementById('$id').addEventListener('click', function() {document.querySelector('#$tableid form.c-table-ordering__form').submit();return false;});"); $tpl->setVariable('FORM_BUTTONS', $default_renderer->render($submit)); $tpl->setVariable('POS_INPUT_TITLE', $this->txt('table_posinput_col_title')); + } $tpl->setVariable('ID', $tableid); $tpl->setVariable('TARGET_URL', $component->getTargetURL() ? $component->getTargetURL()->__toString() : '#'); $tpl->setVariable('TITLE', $component->getTitle()); - $tpl->setVariable('COL_COUNT', (string) $component->getColumnCount()); + $tpl->setVariable('COL_COUNT', (string) $component->getColumnCount() + $compensate_col_count); $tpl->setVariable('VIEW_CONTROLS', $default_renderer->render($view_controls)); $columns = $component->getVisibleColumns(); foreach ($columns as $col_id => $col) { $col_title = $col->getTitle(); $tpl->setCurrentBlock('header_cell'); - $tpl->setVariable('COL_INDEX', (string) $col->getIndex()); + $tpl->setVariable('COL_INDEX', (string) $col->getIndex() + $compensate_col_index); $tpl->setVariable('COL_TITLE', $col_title); $tpl->setVariable('COL_TYPE', strtolower($col->getType())); $tpl->parseCurrentBlock(); @@ -662,11 +713,10 @@ function() {document.querySelector('#$tableid form.c-table-ordering__form').subm ->withOrderingDisabled($component->isOrderingDisabled()); } - $this->renderActionsHeader($default_renderer, $component, $tpl); + $this->renderActionsHeader($default_renderer, $component, $tpl, $compensate_col_count); $this->appendTableRows($tpl, $r, $default_renderer); if ($component->hasMultiActions()) { - $total_number_of_cols = $total_number_of_cols + 2; $multi_actions = $component->getMultiActions(); $modal = $this->buildMultiActionsAllObjectsModal($multi_actions, $tableid); $multi_actions_dropdown = $this->buildMultiActionsDropdown( @@ -674,11 +724,15 @@ function() {document.querySelector('#$tableid form.c-table-ordering__form').subm $component->getMultiActionSignal(), $modal->getShowSignal() ); + $tpl->setVariable('MULTI_ACTION_TRIGGERER', $default_renderer->render($multi_actions_dropdown)); $tpl->setVariable('MULTI_ACTION_ALL_MODAL', $default_renderer->render($modal)); } + if ($component->hasMultiActions() || !$component->isOrderingDisabled()) { + $multi_action_col_span = count($component->getVisibleColumns()) + $compensate_col_count; + $tpl->setVariable('MULTI_ACTION_SPAN', (string) $multi_action_col_span); + } - $tpl->setVariable('COLUMN_COUNT', (string) $total_number_of_cols); return $tpl->get(); } diff --git a/components/ILIAS/UI/src/Implementation/Component/Toast/Renderer.php b/components/ILIAS/UI/src/Implementation/Component/Toast/Renderer.php index 233b817a75a8..fc1e0417df4a 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Toast/Renderer.php +++ b/components/ILIAS/UI/src/Implementation/Component/Toast/Renderer.php @@ -57,8 +57,6 @@ protected function renderToast(Component\Toast\Toast $component, RendererInterfa } $tpl->setVariable("TITLE", $title); - $tpl->setVariable("TOAST_DELAY", $component->getDelayTime()); - $tpl->setVariable("TOAST_VANISH", $component->getVanishTime()); $tpl->setVariable("VANISH_ASYNC", $component->getAction()); $desc = htmlentities($component->getDescription()); @@ -80,10 +78,7 @@ protected function renderToast(Component\Toast\Toast $component, RendererInterfa $tpl->setVariable("ICON", $default_renderer->render($component->getIcon())); $tpl->setVariable("CLOSE", $default_renderer->render($this->getUIFactory()->button()->close())); - $component = $component->withAdditionalOnLoadCode(fn($id) => " - il.UI.toast.setToastSettings($id); - il.UI.toast.showToast($id); - "); + $component = $component->withAdditionalOnLoadCode(fn($id) => "il.UI.toast.showToast($id);"); $tpl->setCurrentBlock("id"); $tpl->setVariable('ID', $this->bindJavaScript($component)); diff --git a/components/ILIAS/UI/src/Implementation/Component/Toast/Toast.php b/components/ILIAS/UI/src/Implementation/Component/Toast/Toast.php index b18372bec724..9a2b264eee95 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Toast/Toast.php +++ b/components/ILIAS/UI/src/Implementation/Component/Toast/Toast.php @@ -34,9 +34,6 @@ class Toast implements ComponentInterface\Toast use ComponentHelper; use JavaScriptBindable; - public const DEFAULT_VANISH_TIME = 5000; - public const DEFAULT_DELAY_TIME = 500; - /** * @var string|Shy|Link */ @@ -48,8 +45,6 @@ class Toast implements ComponentInterface\Toast protected string $action = ''; protected SignalGeneratorInterface $signal_generator; protected Signal $signal; - protected int $vanishTime = Toast::DEFAULT_VANISH_TIME; - protected int $delayTime = Toast::DEFAULT_DELAY_TIME; public function __construct($title, Icon $icon, SignalGeneratorInterface $signal_generator) { @@ -126,28 +121,4 @@ public function getShowSignal(): Signal { return $this->signal; } - - public function withVanishTime(int $vanishTime): Toast - { - $new = clone $this; - $new->vanishTime = $vanishTime; - return $new; - } - - public function getVanishTime(): int - { - return $this->vanishTime; - } - - public function withDelayTime(int $delayTime): Toast - { - $new = clone $this; - $new->delayTime = $delayTime; - return $new; - } - - public function getDelayTime(): int - { - return $this->delayTime; - } } diff --git a/components/ILIAS/UI/src/Implementation/Component/ViewControl/Renderer.php b/components/ILIAS/UI/src/Implementation/Component/ViewControl/Renderer.php index b1523a72bb41..42c0d2dce976 100644 --- a/components/ILIAS/UI/src/Implementation/Component/ViewControl/Renderer.php +++ b/components/ILIAS/UI/src/Implementation/Component/ViewControl/Renderer.php @@ -70,9 +70,7 @@ protected function renderMode(Component\ViewControl\Mode $component, RendererInt foreach ($component->getLabelledActions() as $label => $action) { $tpl->setCurrentBlock("view_control"); - //At this point we don't have a specific text for the button aria label. - // component->getAriaLabel gets the main view control aria label. - $button = $f->button()->standard($label, $action)->withAriaLabel($label); + $button = $f->button()->standard($label, $action); if ($activate_first_item) { $button = $button->withEngagedState(true); $activate_first_item = false; @@ -335,9 +333,12 @@ protected function setPaginationBrowseControls( $f = $this->getUIFactory(); + $back_btn = $f->button()->standard("", ""); + $forward_btn = $f->button()->standard("", ""); + if ($component->getTriggeredSignals()) { - $back = $f->symbol()->glyph()->back('')->withOnClick($component->getInternalSignal()); - $forward = $f->symbol()->glyph()->next('')->withOnClick($component->getInternalSignal()); + $back_btn = $back_btn->withOnClick($component->getInternalSignal()); + $forward_btn = $forward_btn->withOnClick($component->getInternalSignal()); } else { $url = $component->getTargetURL() ?? ''; if (strpos($url, '?') === false) { @@ -354,19 +355,25 @@ protected function setPaginationBrowseControls( $url_next = $base . http_build_query($params); } - $back = $f->symbol()->glyph()->back($url_prev); - $forward = $f->symbol()->glyph()->next($url_next); + $back_btn = $f->button()->standard("", $url_prev); + $forward_btn = $f->button()->standard("", $url_next); } if ($component->getCurrentPage() === 0) { - $back = $back->withUnavailableAction(); + $back_btn = $back_btn->withUnavailableAction(); } if ($component->getCurrentPage() >= $component->getNumberOfPages() - 1) { - $forward = $forward->withUnavailableAction(); + $forward_btn = $forward_btn->withUnavailableAction(); } - $tpl->setVariable('PREVIOUS', $default_renderer->render($back)); - $tpl->setVariable('NEXT', $default_renderer->render($forward)); + $back_glyph = $f->symbol()->glyph()->back(); + $forward_glyph = $f->symbol()->glyph()->next(); + + $back_btn = $back_btn->withSymbol($back_glyph); + $forward_btn = $forward_btn->withSymbol($forward_glyph); + + $tpl->setVariable('PREVIOUS', $default_renderer->render($back_btn)); + $tpl->setVariable('NEXT', $default_renderer->render($forward_btn)); } /** diff --git a/components/ILIAS/UI/src/URLBuilder.php b/components/ILIAS/UI/src/URLBuilder.php index 30d1d806a8ea..8016fa91e1a7 100755 --- a/components/ILIAS/UI/src/URLBuilder.php +++ b/components/ILIAS/UI/src/URLBuilder.php @@ -112,7 +112,7 @@ public function withFragment(?string $fragment): self * changed URLBuilder as well as the token for any * subsequent changes to the acquired parameter. * - * @return array + * @return array{0: URLBuilder, 1: URLBuilderToken} * @throws \ilException * @throws \InvalidArgumentException */ diff --git a/components/ILIAS/UI/src/examples/Chart/Bar/Vertical/base.php b/components/ILIAS/UI/src/examples/Chart/Bar/Vertical/base.php index 9f83e1d4995d..53d9750f5904 100755 --- a/components/ILIAS/UI/src/examples/Chart/Bar/Vertical/base.php +++ b/components/ILIAS/UI/src/examples/Chart/Bar/Vertical/base.php @@ -23,7 +23,7 @@ /** * --- * expected output: > - * ILIAS shows a column-chart with an maximum x-value of 80 and four entries + * ILIAS shows a column-chart with an maximum y-value of 80 and four entries * with values 80, 0, 18 and 55. * Each entry is a vertical column with a height according to its value. * --- diff --git a/components/ILIAS/UI/src/examples/Chart/Bar/Vertical/custom.php b/components/ILIAS/UI/src/examples/Chart/Bar/Vertical/custom.php index ba30bba8e7dd..c0d1dfb5713b 100755 --- a/components/ILIAS/UI/src/examples/Chart/Bar/Vertical/custom.php +++ b/components/ILIAS/UI/src/examples/Chart/Bar/Vertical/custom.php @@ -28,7 +28,7 @@ * --- * expected output: > * ILIAS shows a base column-chart but customized. The left is labeled with three coloured rectanlges and captions. - * The y-bar is sectioned in three parts, each part consisting of three columns, + * The x-axis is sectioned in three parts, each part consisting of three columns, * one for each dataset (set 2 of Item 2 has a value of 0, thus not showing a bar). * --- */ diff --git a/components/ILIAS/UI/src/examples/Dropzone/File/Standard/base.php b/components/ILIAS/UI/src/examples/Dropzone/File/Standard/base.php index 347ce18fccb9..695a5f65af4c 100755 --- a/components/ILIAS/UI/src/examples/Dropzone/File/Standard/base.php +++ b/components/ILIAS/UI/src/examples/Dropzone/File/Standard/base.php @@ -29,7 +29,7 @@ * ILIAS shows a white box with a dashed border. You can see a text "Upload files" (displayed as a link) on the left * side and on the right side the text "Drag files in here to upload them". Clicking onto the link or dragging a file * into the box opens a small window with the buttons "Save" and "Cancel". If you dragged a file into the box you can - * see the file in said small window, too. You can upload any number of files. The window will be closed if you click + * see the file in said small window, too. You can upload only one of file. The window will be closed if you click * onto the "Save" button. An upload doesn't happen in this example. If a file got listed after saving your selection * you can remove the file by clicking the "X" on the right side. * --- diff --git a/components/ILIAS/UI/src/examples/Dropzone/File/Wrapper/with_additional_input.php b/components/ILIAS/UI/src/examples/Dropzone/File/Wrapper/with_additional_input.php index 7e3751c2965b..a6961b5eba25 100755 --- a/components/ILIAS/UI/src/examples/Dropzone/File/Wrapper/with_additional_input.php +++ b/components/ILIAS/UI/src/examples/Dropzone/File/Wrapper/with_additional_input.php @@ -27,7 +27,7 @@ * * expected output: > * ILIAS shows a base file wrapper box. If you drag a file into the box a small window opens - * including two buttons named "Save" and "Close" and an additional input field. The upload + * including two buttons named "Save" and "Cancel" and an additional input field. The upload * process works as in the base file wrapper example. * --- */ diff --git a/components/ILIAS/UI/src/examples/Dropzone/File/Wrapper/with_clear_button.php b/components/ILIAS/UI/src/examples/Dropzone/File/Wrapper/with_clear_button.php index 82ea0bcaab54..ed8e224a97ed 100755 --- a/components/ILIAS/UI/src/examples/Dropzone/File/Wrapper/with_clear_button.php +++ b/components/ILIAS/UI/src/examples/Dropzone/File/Wrapper/with_clear_button.php @@ -27,7 +27,7 @@ * * expected output: > * ILIAS shows a base file wrapper. If you drag a file into the box a small window opens - * including three buttons named "Save","Close" and "Clear files!". Clicking the clear button will remove the file. + * including three buttons named "Clear files!", "Save" and "Cancel". Clicking the clear button will remove the file. * The upload process works as in the base file wrapper example. * --- */ diff --git a/components/ILIAS/UI/src/examples/Input/Container/Form/Standard/reduce_with.php b/components/ILIAS/UI/src/examples/Input/Container/Form/Standard/reduce_with.php new file mode 100644 index 000000000000..f5f5240b0ce2 --- /dev/null +++ b/components/ILIAS/UI/src/examples/Input/Container/Form/Standard/reduce_with.php @@ -0,0 +1,62 @@ + + * Example showing reduceWith-"catamorphism" with Form to factor out classes and structure as JSON. + * + * expected output: > + * ILIAS shows a JSON like that: + * { + * "Standard Form Container Input": [ + * { + * "Section Field Input": [ + * { + * "Text Field Input": [] + * } + * ] + * }, + * { + * "Section Field Input": [ + * { + * "Text Field Input": [] + * } + * ] + * }, + * { + * "Text Field Input": [] + * } + * ] + * } + *--- + */ +function reduce_with() +{ + global $DIC; + $ui = $DIC->ui()->factory(); + $renderer = $DIC->ui()->renderer(); + + $text_input = $ui->input()->field() + ->text("Required Input", "User needs to fill this field") + ->withRequired(true); + + $section = $ui->input()->field()->section( + [$text_input], + "Section with required field", + "The Form should show an explaining hint at the bottom" + ); + + $form = $ui->input()->container()->form()->standard("", [$section, $section, $text_input]); + + $array = $form->reduceWith( + fn($c, $res) => [$c->getCanonicalName() => $res] + ); + + return $renderer->render([ + $ui->legacy()->content('
' . print_r(json_encode($array, JSON_PRETTY_PRINT), true) . '
'), + ]); +} diff --git a/components/ILIAS/UI/src/examples/Input/Container/ViewControl/Standard/reduce_with.php b/components/ILIAS/UI/src/examples/Input/Container/ViewControl/Standard/reduce_with.php new file mode 100644 index 000000000000..0f2a15825a4c --- /dev/null +++ b/components/ILIAS/UI/src/examples/Input/Container/ViewControl/Standard/reduce_with.php @@ -0,0 +1,57 @@ + + * ILIAS shows a JSON like that: + * { + * "Standard View Control Container Input": [ + * { + * "Pagination View Control Input": [] + * }, + * { + * "Sortation View Control Input": [] + * }, + * { + * "Field Selection View Control Input": [] + * } + * ] + * } + * --- + */ +function reduce_with() +{ + global $DIC; + $f = $DIC->ui()->factory(); + $r = $DIC->ui()->renderer(); + + $vcs = [ + $f->input()->viewControl()->pagination(), + $f->input()->viewControl()->sortation([ + 'Field 1, ascending' => new Order('field1', 'ASC'), + 'Field 1, descending' => new Order('field1', 'DESC'), + 'Field 2, descending' => new Order('field2', 'ASC') + ]), + $f->input()->viewControl()->fieldSelection([ + 'field1' => 'Feld 1', + 'field2' => 'Feld 2' + ], 'shown columns', 'apply'), + ]; + + $vc_container = $f->input()->container()->viewControl()->standard($vcs); + + $array = $vc_container->reduceWith( + fn($c, $res) => [$c->getCanonicalName() => $res] + ); + + return $r->render([ + $f->legacy()->content('
' . print_r(json_encode($array, JSON_PRETTY_PRINT), true) . '
') + ]); +} diff --git a/components/ILIAS/UI/src/examples/Input/Field/ColorPicker/base.php b/components/ILIAS/UI/src/examples/Input/Field/ColorSelect/base.php similarity index 55% rename from components/ILIAS/UI/src/examples/Input/Field/ColorPicker/base.php rename to components/ILIAS/UI/src/examples/Input/Field/ColorSelect/base.php index 37f829746156..9ae37bd3e8e3 100755 --- a/components/ILIAS/UI/src/examples/Input/Field/ColorPicker/base.php +++ b/components/ILIAS/UI/src/examples/Input/Field/ColorSelect/base.php @@ -1,29 +1,13 @@ - * Base example showing how to plug a colorpicker into a form + * Base example showing how to plug a Colour Select Field into a form. * * expected output: > * ILIAS shows the rendered Component. @@ -38,7 +22,7 @@ function base() $request = $DIC->http()->request(); //Step 1: Define the input field - $color_input = $ui->input()->field()->colorpicker("Color", "click to select a color"); + $color_input = $ui->input()->field()->colorSelect("Color", "click to select a color"); //Step 2: Define the form and attach the field. $form = $ui->input()->container()->form()->standard('#', ['color' => $color_input]); diff --git a/components/ILIAS/UI/src/examples/Input/Field/DateTime/base.php b/components/ILIAS/UI/src/examples/Input/Field/DateTime/base.php index 1436ff1dbd7c..79259fe0edd8 100755 --- a/components/ILIAS/UI/src/examples/Input/Field/DateTime/base.php +++ b/components/ILIAS/UI/src/examples/Input/Field/DateTime/base.php @@ -37,8 +37,9 @@ * - YYYY-MM-DD HH:mm (pre-filled: current date and time in Tokyo) * - YYYY-MM-DD (pre-filled: current date at the current location) * - * A calendar glyph is displayed next to each field, except for the third line: there you can see a clock glyph. - * Clicking the glyphs will display the following options: + * Some browser display a calendar glyph next to each field, except for the third line: there you might see a clock glyph. + * Clicking the glyphs then might display the options listed below, but have in mind that it depends on which browser is used and that we do + * not have a lot of control over their functionality. Therefore some possible issues are not fixable on our side. * * - Line 1: Selection of any date in the future * - Line 2: Selection of a date in the future @@ -49,6 +50,8 @@ * - Line 7: Selection of any date and any time * - Line 8: This field is disabled. No selection possible. * + * If the glyphs are not working you should at least be able to choose your date and time via using your keyboard. + * * Now click "Save". * ILIAS reloads the page and displays the selection in an array. The outputs have to be the same as your selection. * Regarding pure date or time fields: the output might display the current date and time 00:00. diff --git a/components/ILIAS/UI/src/examples/Item/Standard/with_lead_avatar.php b/components/ILIAS/UI/src/examples/Item/Standard/with_lead_avatar.php index 6b9e8a61baf1..fb5a12d585c8 100755 --- a/components/ILIAS/UI/src/examples/Item/Standard/with_lead_avatar.php +++ b/components/ILIAS/UI/src/examples/Item/Standard/with_lead_avatar.php @@ -27,8 +27,8 @@ * * expected output: > * ILIAS shows two very similiar boxes including the following informations: A heading with a dummy text in small writings - * ("Lorem ipsum...") below. Beneath those you can see a fine line and more informations about "Last Update" - * and "Location". Additionally a action menu is displayed in the box on the right top. On the left side a avatar is + * ("Lorem ipsum...") below. Beneath those you can see a fine line and more informations about "Last Login" + * and "Location". Additionally an action menu is displayed in the box on the right top. On the left side an avatar is * displayed. * --- */ diff --git a/components/ILIAS/UI/src/examples/Item/Standard/with_lead_image.php b/components/ILIAS/UI/src/examples/Item/Standard/with_lead_image.php index 8785aa9e54b7..652551430493 100755 --- a/components/ILIAS/UI/src/examples/Item/Standard/with_lead_image.php +++ b/components/ILIAS/UI/src/examples/Item/Standard/with_lead_image.php @@ -24,12 +24,11 @@ * --- * description: > * Example for rendering a standard item with an lead image. - * * expected output: > * ILIAS shows a box including the following informations: A heading with a dummy text in small writings * ("Lorem ipsum...") below. Beneath those you can see a fine line and more informations about "Origin", "Last Update" * and "Location". Additionally a action menu is displayed in the box on the right top. On the left side a ILIAS-Logo - * is displayed. On small screens the logo is rendered above the title. + * is displayed. * --- */ function with_lead_image() diff --git a/components/ILIAS/UI/src/examples/Item/Standard/with_lead_text.php b/components/ILIAS/UI/src/examples/Item/Standard/with_lead_text.php index 14e69acdacb0..5570b10c0066 100755 --- a/components/ILIAS/UI/src/examples/Item/Standard/with_lead_text.php +++ b/components/ILIAS/UI/src/examples/Item/Standard/with_lead_text.php @@ -29,7 +29,7 @@ * ILIAS shows a box including the following informations: A heading with a dummy text in small writings * ("Lorem ipsum...") below. Beneath those you can see a fine line and more informations about "Origin", "Last Update" * and "Location". Additionally a action menu is displayed in the box on the right top. On the left side a period of time - * (11:20-12:40) is displayed. On small screens the period of time will be rendered above the title. + * (11:20-12:40) is displayed. * --- */ function with_lead_text() diff --git a/components/ILIAS/UI/src/examples/Item/Standard/with_shy_properties.php b/components/ILIAS/UI/src/examples/Item/Standard/with_shy_properties.php index 4f20bba8730b..04dd3ba2a18e 100755 --- a/components/ILIAS/UI/src/examples/Item/Standard/with_shy_properties.php +++ b/components/ILIAS/UI/src/examples/Item/Standard/with_shy_properties.php @@ -28,7 +28,7 @@ * expected output: > * ILIAS shows a box including the following informations: A heading with a dummy text in small writings * ("Lorem ipsum...") below. Beneath those you can see a fine line and more informations about "LMS", "Code Repo" - * and "Location". "Code Repo" ("ILIAS") and "LMS" ("GitHub") are rendered as shy buttons and are functioning as links. + * and "Location". Properties of "LMS" ("ILIAS") an "Code Repo" ("GitHub") are rendered as shy buttons and are functioning as links. * --- */ function with_shy_properties() diff --git a/components/ILIAS/UI/src/examples/Layout/Page/Standard/ui.php b/components/ILIAS/UI/src/examples/Layout/Page/Standard/ui.php index da738db00dbd..6c3b7eaeef21 100755 --- a/components/ILIAS/UI/src/examples/Layout/Page/Standard/ui.php +++ b/components/ILIAS/UI/src/examples/Layout/Page/Standard/ui.php @@ -403,8 +403,7 @@ function getDemoEntryPersonalWorkspace(\ILIAS\UI\Factory $f, Renderer $r): \ILIA ->withAdditionalEntry($button->withLabel('Shared Resources')) ->withAdditionalEntry($button->withLabel('Notes')) ->withAdditionalEntry($button->withLabel('News')) - ->withAdditionalEntry($button->withLabel('Background Tasks')) - ->withAdditionalEntry($slate_bookmarks); + ->withAdditionalEntry($button->withLabel('Background Tasks')); } function getDemoEntryAchievements(\ILIAS\UI\Factory $f): \ILIAS\UI\Component\MainControls\Slate\Legacy diff --git a/components/ILIAS/UI/src/examples/Legacy/LatexContent/base.php b/components/ILIAS/UI/src/examples/Legacy/LatexContent/base.php new file mode 100644 index 000000000000..1972b5afd360 --- /dev/null +++ b/components/ILIAS/UI/src/examples/Legacy/LatexContent/base.php @@ -0,0 +1,52 @@ + + * Example for rendering a legacy content with laTeX code. + * The content can be text or HTML. + * LaTeX code within is embedded in the delimiters [tex] and [/tex] + * + * expected output: > + * ILIAS shows the string 'This should be rendered as a formula: ' + * followed by a mathematical function definition with an integral. + * The function definition is rendered graphically. + * The rendering may take a tenth of a second when the page is shown. + * Before that the LaTeX source code is shown. + * A right click with the mouse on the rendered expression will show a popup menu from MathJax. + * Here you can set different display options. + * --- + */ +function base() +{ + //Init Factory and Renderer + global $DIC; + $f = $DIC->ui()->factory(); + $renderer = $DIC->ui()->renderer(); + + //Init Component + $legacy = $f->legacy()->latexContent('This should be rendered as a formula: [tex]f(x)=\int_{-\infty}^x e^{-t^2}dt[/tex]'); + + //Render + return $renderer->render($legacy); +} diff --git a/components/ILIAS/UI/src/examples/Modal/Interruptive/show_modal_on_button_click.php b/components/ILIAS/UI/src/examples/Modal/Interruptive/show_modal_on_button_click.php index d5ada5f6d91b..db7d1661f9cf 100755 --- a/components/ILIAS/UI/src/examples/Modal/Interruptive/show_modal_on_button_click.php +++ b/components/ILIAS/UI/src/examples/Modal/Interruptive/show_modal_on_button_click.php @@ -29,7 +29,6 @@ * Clicking "Show Modal" opens up a modal with some content. * A click onto "Delete" will reload the page and displays a confirmation below the example. * A click onto "Cancel" or Close Glyph will hide the modal. - * Clicking onto the greyed out ILIAS in the background outside of the modal has no effect, Modal remains open. * --- */ function show_modal_on_button_click() @@ -46,13 +45,14 @@ function show_modal_on_button_click() $ctrl->setParameterByClass('ilsystemstyledocumentationgui', 'modal_nr', 1); $form_action = $ctrl->getFormActionByClass('ilsystemstyledocumentationgui'); $icon = $factory->image()->standard('./assets/images/standard/icon_crs.svg', ''); - $modal = $factory->modal()->interruptive('My Title', $message, $form_action) - ->withAffectedItems(array( - $factory->modal()->interruptiveItem()->standard('10', 'Course 1', $icon, 'Some description text'), - $factory->modal()->interruptiveItem()->keyValue('20', 'Item Key', 'item value'), - $factory->modal()->interruptiveItem()->standard('30', 'Course 3', $icon, 'Last but not least, a description'), - $factory->modal()->interruptiveItem()->keyValue('50', 'Second Item Key', 'another item value'), - )); + + $items = [ + $factory->modal()->interruptiveItem()->standard('10', 'Course 1', $icon, 'Some description text'), + $factory->modal()->interruptiveItem()->keyValue('20', 'Item Key', 'item value'), + $factory->modal()->interruptiveItem()->standard('30', 'Course 3', $icon, 'Last but not least, a description'), + $factory->modal()->interruptiveItem()->keyValue('50', 'Second Item Key', 'another item value'), + ]; + $modal = $factory->modal()->interruptive('My Title', $message, $form_action)->withAffectedItems($items); $button = $factory->button()->standard('Show Modal', '') ->withOnClick($modal->getShowSignal()); @@ -60,14 +60,14 @@ function show_modal_on_button_click() // Display POST data of affected items in a panel if ( - $request_wrapper->has('interruptive_items') && + $post_wrapper->has('interruptive_items') && $request_wrapper->retrieve('modal_nr', $refinery->kindlyTo()->string()) === '1' ) { - $panel = $factory->panel()->standard( - 'Affected Items', - $factory->legacy()->content(print_r($post_wrapper->retrieve('interruptive_items', $refinery->kindlyTo()->string()), true)) - ); - $out[] = $panel; + $out[] = $post_wrapper->retrieve('interruptive_items', $refinery->custom()->transformation( + function ($item_keys) use ($factory, $post_wrapper) { + return $factory->panel()->standard('Items deleted', $factory->legacy("Number of items deleted: ".count($item_keys))); + } + )); } return $renderer->render($out); diff --git a/components/ILIAS/UI/src/examples/Modal/LightboxImagePage/show_a_single_image.php b/components/ILIAS/UI/src/examples/Modal/LightboxImagePage/show_a_single_image.php index 755398192285..426d7da8fb88 100755 --- a/components/ILIAS/UI/src/examples/Modal/LightboxImagePage/show_a_single_image.php +++ b/components/ILIAS/UI/src/examples/Modal/LightboxImagePage/show_a_single_image.php @@ -20,6 +20,17 @@ namespace ILIAS\UI\examples\Modal\LightboxImagePage; +/** + * --- + * description: > + * Example for rendering a lightbox image page modal with a single image. + * + * expected output: > + * ILIAS shows a button titled "Show Image". + * A click onto the button greys out ILIAS, opens a modal titled "Mountains", + * an image and a Copyright note above the image. The modal's background is dark with a light font color. + * --- + */ function show_a_single_image() { global $DIC; diff --git a/components/ILIAS/UI/src/examples/Prompt/Standard/parameters.php b/components/ILIAS/UI/src/examples/Prompt/Standard/parameters.php index 96c2229243b0..894c592185b4 100644 --- a/components/ILIAS/UI/src/examples/Prompt/Standard/parameters.php +++ b/components/ILIAS/UI/src/examples/Prompt/Standard/parameters.php @@ -84,6 +84,7 @@ function parameters() fn($id) => "$('#$id').on('click', (e)=> {alert('$id');});" ) ]; + \ilSession::set("full_amount", (string) $idx); $prompt_content = $factory->messageBox()->info('some text') ->withLinks($links) ->withButtons($buttons); @@ -92,7 +93,7 @@ function parameters() case 'promptlink': $back_uri = $url_builder ->withParameter($action_token, "showprompt") - ->withParameter($amount_token, (string) $amount) + ->withParameter($amount_token, (string) \ilSession::get("full_amount")) ->buildURI()->__toString(); $back = $factory->button()->standard('back', '#')->withOnLoadCode( fn($id) => "$('#$id').on('click', (e)=> { diff --git a/components/ILIAS/UI/src/examples/Symbol/Glyph/Checked/checked.php b/components/ILIAS/UI/src/examples/Symbol/Glyph/Checked/checked.php new file mode 100644 index 000000000000..272e21725853 --- /dev/null +++ b/components/ILIAS/UI/src/examples/Symbol/Glyph/Checked/checked.php @@ -0,0 +1,41 @@ + + * Example for rendering a Checked Glyph. + * + * expected output: > + * * Active: + * * ILIAS shows a monochrome heart symbol on a grey background. Moving the cursor above the symbol will darken it's + * * color slightly. Additionally the cursor's form will change and it indicates a linking. + * * + * * Inactive: + * * ILIAS shows the same symbol, but it's greyed out. Moving the cursor will not change the presentation. + * * + * * Highlighted: + * * ILIAS shows the same symbol but it's highlighted particularly. Moving the cursor above the symbol will darken it's + * * color slightly. Additionally the cursor's form will change and it indicates a linking. + * * --- + */ +function checked() +{ + global $DIC; + $f = $DIC->ui()->factory(); + $renderer = $DIC->ui()->renderer(); + + $glyph = $f->symbol()->glyph()->checked(); + + //Showcase the various states of this Glyph + $list = $f->listing()->descriptive([ + "Active" => $glyph, + "Inactive" => $glyph->withUnavailableAction(), + "Highlighted" => $glyph->withHighlight() + ]); + + return $renderer->render($list); +} diff --git a/components/ILIAS/UI/src/examples/Symbol/Glyph/DragHandle/dragHandle.php b/components/ILIAS/UI/src/examples/Symbol/Glyph/DragHandle/dragHandle.php new file mode 100644 index 000000000000..a9aee300dc91 --- /dev/null +++ b/components/ILIAS/UI/src/examples/Symbol/Glyph/DragHandle/dragHandle.php @@ -0,0 +1,41 @@ + + * Example for rendering a Drag Handle Glyph. + * + * expected output: > + * Active: + * ILIAS shows a monochrome symbol on a grey background. If you move your cursor onto the symbol it's + * color darkens a little bit. Additionally, the cursor symbol changes its form and indicates that an element is draggable. + * + * Inactive: + * ILIAS shows the same symbol. But it's greyed out. Moving the cursor above the symbol will not change the presentation. + * + * Highlighted: + * ILIAS shows the same symbol. But it's highlighted particularly. The presentation will darken if you move your cursor + * above the symbol. Additionally, the cursor symbol will change its form and indicates that an element is draggable. + * --- + */ +function dragHandle() +{ + global $DIC; + $f = $DIC->ui()->factory(); + $renderer = $DIC->ui()->renderer(); + + $glyph = $f->symbol()->glyph()->dragHandle("#"); + + //Showcase the various states of this Glyph + $list = $f->listing()->descriptive([ + "Active" => $glyph, + "Inactive" => $glyph->withUnavailableAction(), + "Highlighted" => $glyph->withHighlight() + ]); + + return $renderer->render($list); +} diff --git a/components/ILIAS/UI/src/examples/Symbol/Glyph/Unchecked/unchecked.php b/components/ILIAS/UI/src/examples/Symbol/Glyph/Unchecked/unchecked.php new file mode 100644 index 000000000000..dd5709fdcc32 --- /dev/null +++ b/components/ILIAS/UI/src/examples/Symbol/Glyph/Unchecked/unchecked.php @@ -0,0 +1,41 @@ + + * Example for rendering a Unchecked Glyph. + * + * expected output: > + * * Active: + * * ILIAS shows a monochrome heart symbol on a grey background. Moving the cursor above the symbol will darken it's + * * color slightly. Additionally the cursor's form will change and it indicates a linking. + * * + * * Inactive: + * * ILIAS shows the same symbol, but it's greyed out. Moving the cursor will not change the presentation. + * * + * * Highlighted: + * * ILIAS shows the same symbol but it's highlighted particularly. Moving the cursor above the symbol will darken it's + * * color slightly. Additionally the cursor's form will change and it indicates a linking. + * * --- + */ +function unchecked() +{ + global $DIC; + $f = $DIC->ui()->factory(); + $renderer = $DIC->ui()->renderer(); + + $glyph = $f->symbol()->glyph()->unchecked(); + + //Showcase the various states of this Glyph + $list = $f->listing()->descriptive([ + "Active" => $glyph, + "Inactive" => $glyph->withUnavailableAction(), + "Highlighted" => $glyph->withHighlight() + ]); + + return $renderer->render($list); +} diff --git a/components/ILIAS/UI/src/examples/Table/Action/Multi/base.php b/components/ILIAS/UI/src/examples/Table/Action/Multi/base.php index a38e9788b99c..dcd43244bdaf 100755 --- a/components/ILIAS/UI/src/examples/Table/Action/Multi/base.php +++ b/components/ILIAS/UI/src/examples/Table/Action/Multi/base.php @@ -129,5 +129,5 @@ public function getTotalRowCount( return 6; } }; - return $f->table()->data('a data table with actions', $columns, $data_retrieval); + return $f->table()->data($data_retrieval, 'a data table with actions', $columns); } diff --git a/components/ILIAS/UI/src/examples/Table/Action/Single/base.php b/components/ILIAS/UI/src/examples/Table/Action/Single/base.php index d02f43eac7ac..97f397585dec 100755 --- a/components/ILIAS/UI/src/examples/Table/Action/Single/base.php +++ b/components/ILIAS/UI/src/examples/Table/Action/Single/base.php @@ -127,5 +127,5 @@ public function getTotalRowCount( return 6; } }; - return $f->table()->data('a data table with actions', $columns, $data_retrieval); + return $f->table()->data($data_retrieval, 'a data table with actions', $columns); } diff --git a/components/ILIAS/UI/src/examples/Table/Action/Standard/base.php b/components/ILIAS/UI/src/examples/Table/Action/Standard/base.php index a557cf5f6a7a..b340bb92498d 100755 --- a/components/ILIAS/UI/src/examples/Table/Action/Standard/base.php +++ b/components/ILIAS/UI/src/examples/Table/Action/Standard/base.php @@ -133,5 +133,5 @@ public function getTotalRowCount( return 6; } }; - return $f->table()->data('a data table with actions', $columns, $data_retrieval); + return $f->table()->data($data_retrieval, 'a data table with actions', $columns); } diff --git a/components/ILIAS/UI/src/examples/Table/Column/Boolean/base.php b/components/ILIAS/UI/src/examples/Table/Column/Boolean/base.php index 778dff3e913f..1e4f5ef7d402 100755 --- a/components/ILIAS/UI/src/examples/Table/Column/Boolean/base.php +++ b/components/ILIAS/UI/src/examples/Table/Column/Boolean/base.php @@ -90,7 +90,7 @@ public function getTotalRowCount( } }; - $table = $f->table()->data('Boolean Columns', $columns, $data_retrieval) + $table = $f->table()->data($data_retrieval, 'Boolean Columns', $columns) ->withRequest($DIC->http()->request()); return $r->render($table); } diff --git a/components/ILIAS/UI/src/examples/Table/Column/Date/base.php b/components/ILIAS/UI/src/examples/Table/Column/Date/base.php index 8ae50631e924..7a7f319bb406 100755 --- a/components/ILIAS/UI/src/examples/Table/Column/Date/base.php +++ b/components/ILIAS/UI/src/examples/Table/Column/Date/base.php @@ -69,7 +69,7 @@ public function getTotalRowCount( } }; - $table = $f->table()->data('Date Columns', $columns, $data_retrieval) + $table = $f->table()->data($data_retrieval, 'Date Columns', $columns) ->withRequest($DIC->http()->request()); return $r->render($table); } diff --git a/components/ILIAS/UI/src/examples/Table/Column/EMail/base.php b/components/ILIAS/UI/src/examples/Table/Column/EMail/base.php index 767a490b2af9..b51a7f7622dc 100755 --- a/components/ILIAS/UI/src/examples/Table/Column/EMail/base.php +++ b/components/ILIAS/UI/src/examples/Table/Column/EMail/base.php @@ -69,7 +69,7 @@ public function getTotalRowCount( } }; - $table = $f->table()->data('eMail Columns', $columns, $data_retrieval) + $table = $f->table()->data($data_retrieval, 'eMail Columns', $columns) ->withRequest($DIC->http()->request()); return $r->render($table); } diff --git a/components/ILIAS/UI/src/examples/Table/Column/Link/base.php b/components/ILIAS/UI/src/examples/Table/Column/Link/base.php index 8b227147e553..134f2e1ec291 100755 --- a/components/ILIAS/UI/src/examples/Table/Column/Link/base.php +++ b/components/ILIAS/UI/src/examples/Table/Column/Link/base.php @@ -77,7 +77,7 @@ public function getTotalRowCount( } }; - $table = $f->table()->data('Link Columns', $columns, $data_retrieval) + $table = $f->table()->data($data_retrieval, 'Link Columns', $columns) ->withRequest($DIC->http()->request()); return $r->render($table); } diff --git a/components/ILIAS/UI/src/examples/Table/Column/LinkListing/base.php b/components/ILIAS/UI/src/examples/Table/Column/LinkListing/base.php index b98f49e6085d..c80ff33b3962 100755 --- a/components/ILIAS/UI/src/examples/Table/Column/LinkListing/base.php +++ b/components/ILIAS/UI/src/examples/Table/Column/LinkListing/base.php @@ -78,7 +78,7 @@ public function getTotalRowCount( } }; - $table = $f->table()->data('Link List Columns', $columns, $data_retrieval) + $table = $f->table()->data($data_retrieval, 'Link List Columns', $columns) ->withRequest($DIC->http()->request()); return $r->render($table); } diff --git a/components/ILIAS/UI/src/examples/Table/Column/Number/base.php b/components/ILIAS/UI/src/examples/Table/Column/Number/base.php index 09ca016481cb..ef4637896db0 100755 --- a/components/ILIAS/UI/src/examples/Table/Column/Number/base.php +++ b/components/ILIAS/UI/src/examples/Table/Column/Number/base.php @@ -83,7 +83,7 @@ public function getTotalRowCount( } }; - $table = $f->table()->data('Number Columns', $columns, $data_retrieval) + $table = $f->table()->data($data_retrieval, 'Number Columns', $columns) ->withRequest($DIC->http()->request()); return $r->render($table); } diff --git a/components/ILIAS/UI/src/examples/Table/Column/Status/base.php b/components/ILIAS/UI/src/examples/Table/Column/Status/base.php index 74e4e144b767..0f1bdc7c330d 100755 --- a/components/ILIAS/UI/src/examples/Table/Column/Status/base.php +++ b/components/ILIAS/UI/src/examples/Table/Column/Status/base.php @@ -72,7 +72,7 @@ public function getTotalRowCount( } }; - $table = $f->table()->data('Status Columns', $columns, $data_retrieval) + $table = $f->table()->data($data_retrieval, 'Status Columns', $columns) ->withRequest($DIC->http()->request()); return $r->render($table); } diff --git a/components/ILIAS/UI/src/examples/Table/Column/StatusIcon/base.php b/components/ILIAS/UI/src/examples/Table/Column/StatusIcon/base.php index bddd6a9c2456..49f840b4dba7 100755 --- a/components/ILIAS/UI/src/examples/Table/Column/StatusIcon/base.php +++ b/components/ILIAS/UI/src/examples/Table/Column/StatusIcon/base.php @@ -80,7 +80,7 @@ public function getTotalRowCount( } }; - $table = $f->table()->data('StatusIcons Columns', $columns, $data_retrieval) + $table = $f->table()->data($data_retrieval, 'StatusIcons Columns', $columns) ->withRequest($DIC->http()->request()); return $r->render($table); } diff --git a/components/ILIAS/UI/src/examples/Table/Column/Text/base.php b/components/ILIAS/UI/src/examples/Table/Column/Text/base.php index e5a247a8eef9..de87b1ea8aa5 100755 --- a/components/ILIAS/UI/src/examples/Table/Column/Text/base.php +++ b/components/ILIAS/UI/src/examples/Table/Column/Text/base.php @@ -76,7 +76,7 @@ public function getTotalRowCount( } }; - $table = $f->table()->data('Text Columns', $columns, $data_retrieval) + $table = $f->table()->data($data_retrieval, 'Text Columns', $columns) ->withRequest($DIC->http()->request()); return $r->render($table); } diff --git a/components/ILIAS/UI/src/examples/Table/Column/TimeSpan/base.php b/components/ILIAS/UI/src/examples/Table/Column/TimeSpan/base.php index 464095211241..8aba4c39ad67 100755 --- a/components/ILIAS/UI/src/examples/Table/Column/TimeSpan/base.php +++ b/components/ILIAS/UI/src/examples/Table/Column/TimeSpan/base.php @@ -70,7 +70,7 @@ public function getTotalRowCount( } }; - $table = $f->table()->data('TimeSpan Columns', $columns, $data_retrieval) + $table = $f->table()->data($data_retrieval, 'TimeSpan Columns', $columns) ->withRequest($DIC->http()->request()); return $r->render($table); } diff --git a/components/ILIAS/UI/src/examples/Table/Data/base.php b/components/ILIAS/UI/src/examples/Table/Data/base.php index 877cae4c882c..c8f0916797f3 100755 --- a/components/ILIAS/UI/src/examples/Table/Data/base.php +++ b/components/ILIAS/UI/src/examples/Table/Data/base.php @@ -246,7 +246,7 @@ protected function getRecords(?Range $range = null, ?Order $order = null): array * with an ID for the table, parameters will be stored throughout url changes */ $table = $f->table() - ->data('a data table', $columns, $data_retrieval) + ->data($data_retrieval, 'a data table', $columns) ->withId('example_base') ->withActions($actions) diff --git a/components/ILIAS/UI/src/examples/Table/Data/repo_implementation.php b/components/ILIAS/UI/src/examples/Table/Data/repo_implementation.php index bafed4328118..82bb7615d645 100755 --- a/components/ILIAS/UI/src/examples/Table/Data/repo_implementation.php +++ b/components/ILIAS/UI/src/examples/Table/Data/repo_implementation.php @@ -72,9 +72,9 @@ public function __construct() public function getTableForRepresentation(): \ILIAS\UI\Implementation\Component\Table\Data { return $this->ui_factory->table()->data( + $this, 'a data table from a repository', $this->getColumsForRepresentation(), - $this ); } diff --git a/components/ILIAS/UI/src/examples/Table/Data/without_data.php b/components/ILIAS/UI/src/examples/Table/Data/without_data.php index 36befada6ea4..594e7c23d0f0 100755 --- a/components/ILIAS/UI/src/examples/Table/Data/without_data.php +++ b/components/ILIAS/UI/src/examples/Table/Data/without_data.php @@ -63,6 +63,7 @@ public function getTotalRowCount(?array $filter_data, ?array $additional_paramet }; $table = $factory->table()->data( + $empty_retrieval, 'Empty Data Table', [ 'col1' => $factory->table()->column()->text('Column 1') @@ -70,7 +71,6 @@ public function getTotalRowCount(?array $filter_data, ?array $additional_paramet 'col2' => $factory->table()->column()->number('Column 2') ->withIsSortable(false), ], - $empty_retrieval ); return $renderer->render($table->withRequest($request)); diff --git a/components/ILIAS/UI/src/examples/Table/Ordering/base.php b/components/ILIAS/UI/src/examples/Table/Ordering/base.php index 6c15e62707e3..34d9e508806b 100644 --- a/components/ILIAS/UI/src/examples/Table/Ordering/base.php +++ b/components/ILIAS/UI/src/examples/Table/Ordering/base.php @@ -94,7 +94,7 @@ function base() /** * This is the data binding: retrieve rows and write back the order of records. */ - $data_retrieval = new class ($f, $r) implements I\OrderingBinding { + $data_retrieval = new class ($f, $r) implements I\OrderingRetrieval { protected array $records; public function __construct( @@ -158,7 +158,7 @@ public function setOrder(array $ordered): void }; $target = (new URI((string) $request->getUri()))->withParameter('ordering_example', 1); - $table = $f->table()->ordering('ordering table', $columns, $data_retrieval, $target) + $table = $f->table()->ordering($data_retrieval, $target, 'ordering table', $columns) ->withActions($actions) ->withRequest($request); diff --git a/components/ILIAS/UI/src/examples/Table/Ordering/disabled.php b/components/ILIAS/UI/src/examples/Table/Ordering/disabled.php index fabced6608ef..949aa37a7cf8 100644 --- a/components/ILIAS/UI/src/examples/Table/Ordering/disabled.php +++ b/components/ILIAS/UI/src/examples/Table/Ordering/disabled.php @@ -56,7 +56,7 @@ function disabled() ->withHighlight(true) ]; - $data_retrieval = new class ($f, $r) implements I\OrderingBinding { + $data_retrieval = new class ($f, $r) implements I\OrderingRetrieval { protected array $records; public function __construct( @@ -87,7 +87,7 @@ protected function initRecords(): array * Disable the ordering (e.g. due to missing permissions) */ $target = (new URI((string) $request->getUri())); - $table = $f->table()->ordering('ordering table with disabled ordering', $columns, $data_retrieval, $target) + $table = $f->table()->ordering($data_retrieval, $target, 'ordering table with disabled ordering', $columns) ->withOrderingDisabled(true) ->withRequest($request); diff --git a/components/ILIAS/UI/src/examples/Table/Ordering/external.php b/components/ILIAS/UI/src/examples/Table/Ordering/external.php index 65043be02855..d706eefae306 100644 --- a/components/ILIAS/UI/src/examples/Table/Ordering/external.php +++ b/components/ILIAS/UI/src/examples/Table/Ordering/external.php @@ -64,7 +64,7 @@ function external() ->withHighlight(true) ]; - $data_retrieval = new class ($f, $r) implements I\OrderingBinding { + $data_retrieval = new class ($f, $r) implements I\OrderingRetrieval { protected array $records; public function __construct( @@ -114,7 +114,7 @@ public function setOrder(array $ordered): void * Alter the URL the tables posts its positions to (here: just add a parameter). */ $target = (new URI((string) $request->getUri()))->withParameter('ordering_example', 3); - $table = $f->table()->ordering('sort the letters', $columns, $data_retrieval, $target) + $table = $f->table()->ordering($data_retrieval, $target, 'sort the letters', $columns) ->withRequest($request); /** diff --git a/components/ILIAS/UI/src/templates/default/Input/tpl.colorpicker.html b/components/ILIAS/UI/src/templates/default/Input/tpl.color_select.html similarity index 50% rename from components/ILIAS/UI/src/templates/default/Input/tpl.colorpicker.html rename to components/ILIAS/UI/src/templates/default/Input/tpl.color_select.html index 715d4a06168d..c889a0b9e5db 100755 --- a/components/ILIAS/UI/src/templates/default/Input/tpl.colorpicker.html +++ b/components/ILIAS/UI/src/templates/default/Input/tpl.color_select.html @@ -1 +1 @@ - name="{NAME}" value="{VALUE}" class="c-field-color-picker"/> + name="{NAME}" value="{VALUE}" class="c-field-color-select"/> diff --git a/components/ILIAS/UI/src/templates/default/Input/tpl.viewcontrol_fieldselection.html b/components/ILIAS/UI/src/templates/default/Input/tpl.viewcontrol_fieldselection.html index fa9526d98bff..b15e8b9f3427 100755 --- a/components/ILIAS/UI/src/templates/default/Input/tpl.viewcontrol_fieldselection.html +++ b/components/ILIAS/UI/src/templates/default/Input/tpl.viewcontrol_fieldselection.html @@ -1,12 +1,14 @@
diff --git a/components/ILIAS/UI/src/templates/default/Input/tpl.viewcontrol_pagination.html b/components/ILIAS/UI/src/templates/default/Input/tpl.viewcontrol_pagination.html index 7499fb15e8e6..b340dc1e6a5a 100755 --- a/components/ILIAS/UI/src/templates/default/Input/tpl.viewcontrol_pagination.html +++ b/components/ILIAS/UI/src/templates/default/Input/tpl.viewcontrol_pagination.html @@ -1,26 +1,22 @@ -
- - +
id="{ID}" > - - -
\ No newline at end of file +
diff --git a/components/ILIAS/UI/src/templates/default/Layout/tpl.standardpage.html b/components/ILIAS/UI/src/templates/default/Layout/tpl.standardpage.html index c7429032e2d8..54fbaf406740 100755 --- a/components/ILIAS/UI/src/templates/default/Layout/tpl.standardpage.html +++ b/components/ILIAS/UI/src/templates/default/Layout/tpl.standardpage.html @@ -28,7 +28,7 @@ - + {MODEINFO} diff --git a/components/ILIAS/UI/src/templates/default/Legacy/tpl.latex_content.html b/components/ILIAS/UI/src/templates/default/Legacy/tpl.latex_content.html new file mode 100644 index 000000000000..407c81a7c977 --- /dev/null +++ b/components/ILIAS/UI/src/templates/default/Legacy/tpl.latex_content.html @@ -0,0 +1 @@ +
{CONTENT}
\ No newline at end of file diff --git a/components/ILIAS/UI/src/templates/default/MainControls/tpl.system_info.html b/components/ILIAS/UI/src/templates/default/MainControls/tpl.system_info.html index 4f347de987b2..8bdbbd31b77e 100755 --- a/components/ILIAS/UI/src/templates/default/MainControls/tpl.system_info.html +++ b/components/ILIAS/UI/src/templates/default/MainControls/tpl.system_info.html @@ -1,8 +1,8 @@
- {HEADLINE} - {BODY} +
{HEADLINE}
+
{BODY}
diff --git a/components/ILIAS/UI/src/templates/default/Modal/tpl.lightbox.html b/components/ILIAS/UI/src/templates/default/Modal/tpl.lightbox.html index 4a3800d5923e..0bece8755ccf 100755 --- a/components/ILIAS/UI/src/templates/default/Modal/tpl.lightbox.html +++ b/components/ILIAS/UI/src/templates/default/Modal/tpl.lightbox.html @@ -31,14 +31,14 @@

{TITLE}

- + +
diff --git a/components/ILIAS/UI/src/templates/default/Symbol/tpl.glyph.html b/components/ILIAS/UI/src/templates/default/Symbol/tpl.glyph.html index ab81638299ec..324913696fd8 100755 --- a/components/ILIAS/UI/src/templates/default/Symbol/tpl.glyph.html +++ b/components/ILIAS/UI/src/templates/default/Symbol/tpl.glyph.html @@ -56,6 +56,9 @@ glyphicon-sort glyphicon-columnSelection glyphicon-tileView + glyphicon-dragHandle + glyphicon-checked + glyphicon-unchecked " aria-hidden="true"> diff --git a/components/ILIAS/UI/src/templates/default/Table/tpl.datacell.html b/components/ILIAS/UI/src/templates/default/Table/tpl.datacell.html index e6c247f3c47b..5737c184d495 100755 --- a/components/ILIAS/UI/src/templates/default/Table/tpl.datacell.html +++ b/components/ILIAS/UI/src/templates/default/Table/tpl.datacell.html @@ -1,17 +1,17 @@ -
- - - \ No newline at end of file + diff --git a/components/ILIAS/UI/src/templates/default/Table/tpl.datatable.html b/components/ILIAS/UI/src/templates/default/Table/tpl.datatable.html index 54044fdc6ea3..551248a4d404 100755 --- a/components/ILIAS/UI/src/templates/default/Table/tpl.datatable.html +++ b/components/ILIAS/UI/src/templates/default/Table/tpl.datatable.html @@ -1,25 +1,25 @@
-

{TITLE}

+

{TITLE}

{VIEW_CONTROLS}
-
diff --git a/components/ILIAS/Survey/templates/default/tpl.svy_constraints_row.html b/components/ILIAS/Survey/templates/default/tpl.svy_constraints_row.html index a2c910157811..8107ac2ad383 100755 --- a/components/ILIAS/Survey/templates/default/tpl.svy_constraints_row.html +++ b/components/ILIAS/Survey/templates/default/tpl.svy_constraints_row.html @@ -10,7 +10,7 @@ {COUNTER}. - {ICON_ALT} {TITLE} + {TITLE} ({ICON_ALT}) {CONTENT} diff --git a/components/ILIAS/SurveyQuestionPool/Export/class.SurveyImportParser.php b/components/ILIAS/SurveyQuestionPool/Export/class.SurveyImportParser.php index 81bee25ec50b..314413a53ee6 100755 --- a/components/ILIAS/SurveyQuestionPool/Export/class.SurveyImportParser.php +++ b/components/ILIAS/SurveyQuestionPool/Export/class.SurveyImportParser.php @@ -86,6 +86,7 @@ public function __construct( global $DIC; parent::__construct($a_xml_file); + $this->activequestion = null; $this->spl_id = $a_spl_id; $this->has_error = false; $this->characterbuffer = ""; @@ -130,9 +131,8 @@ public function setSurveyObject(ilObjSurvey $a_svy): void public function setHandlers($a_xml_parser): void { - xml_set_object($a_xml_parser, $this); - xml_set_element_handler($a_xml_parser, 'handlerBeginTag', 'handlerEndTag'); - xml_set_character_data_handler($a_xml_parser, 'handlerCharacterData'); + xml_set_element_handler($a_xml_parser, $this->handlerBeginTag(...), $this->handlerEndTag(...)); + xml_set_character_data_handler($a_xml_parser, $this->handlerCharacterData(...)); } /** @@ -212,10 +212,15 @@ public function handlerBeginTag($a_xml_parser, string $a_name, array $a_attribs) case "online": if ($this->spl_id > 0) { $spl = new ilObjSurveyQuestionPool($this->spl_id, false); - $spl->setOnline($value); + $spl->setOfflineStatus(!$value); $spl->saveToDb(); } break; + case "label": + $spl = new ilObjSurveyQuestionPool($this->spl_id, false); + $spl->setTitle($value); + $spl->saveToDb(); + break; } } break; @@ -285,17 +290,13 @@ public function handlerBeginTag($a_xml_parser, string $a_name, array $a_attribs) break; } if (strlen($type ?? "")) { - if (SurveyQuestion::_includeClass($type)) { - $this->activequestion = new $type(); - - // if no pool is given, question will reference survey - $q_obj_id = $this->spl_id; - if ($this->spl_id < 0) { - $q_obj_id = $this->survey->getId(); - } - - $this->activequestion->setObjId($q_obj_id); + $this->activequestion = new $type(); + // if no pool is given, question will reference survey + $q_obj_id = $this->spl_id; + if ($this->spl_id < 0) { + $q_obj_id = $this->survey->getId(); } + $this->activequestion->setObjId($q_obj_id); } else { $this->activequestion = null; } @@ -381,7 +382,7 @@ public function handlerBeginTag($a_xml_parser, string $a_name, array $a_attribs) break; case "response_text": $this->material = []; - $this->responses[$a_attribs["id"]] = array("type" => "text", "id" => $a_attribs["id"], "columns" => $a_attribs["columns"], "maxlength" => $a_attribs["maxlength"] ?? null, "rows" => $a_attribs["rows"], "label" => $a_attribs["label"] ?? ""); + $this->responses[$a_attribs["id"]] = array("type" => "text", "id" => $a_attribs["id"], "columns" => $a_attribs["columns"] ?? null, "maxlength" => $a_attribs["maxlength"] ?? null, "rows" => $a_attribs["rows"] ?? null, "label" => $a_attribs["label"] ?? ""); $this->response_id = $a_attribs["id"]; break; case "response_num": @@ -597,18 +598,6 @@ public function handlerEndTag($a_xml_parser, string $a_name): void if (strcmp($this->getParent(), "survey") == 0) { foreach ($this->metadata as $key => $value) { switch ($value["label"]) { - case "SCORM": - if (strlen($value["entry"] ?? "")) { - if (is_object($this->survey)) { - $md_sax_parser = new ilMDSaxParser(); - $md_sax_parser->setXMLContent($value["entry"]); - $md_sax_parser->setMDObject($tmp = new ilMD($this->survey->getId(), 0, "svy")); - $md_sax_parser->enableMDParsing(true); - $md_sax_parser->startParsing(); - $this->survey->MDUpdateListener("General"); - } - } - break; case "display_question_titles": if ($value["entry"] == 1) { $this->survey->setShowQuestionTitles(true); @@ -658,25 +647,6 @@ public function handlerEndTag($a_xml_parser, string $a_name): void } } } - if (!$this->spl_exists) { - if (strcmp($this->getParent(), "surveyquestions") == 0) { - foreach ($this->metadata as $key => $value) { - if (strcmp($value["label"], "SCORM") == 0) { - if (strlen($value["entry"] ?? "")) { - if ($this->spl_id > 0) { - $md_sax_parser = new ilMDSaxParser(); - $md_sax_parser->setXMLContent($value["entry"]); - $md_sax_parser->setMDObject($tmp = new ilMD($this->spl_id, 0, "spl")); - $md_sax_parser->enableMDParsing(true); - $md_sax_parser->startParsing(); - $spl = new ilObjSurveyQuestionPool($this->spl_id, false); - $spl->MDUpdateListener("General"); - } - } - } - } - } - } break; case "responses": if (is_object($this->activequestion)) { diff --git a/components/ILIAS/SurveyQuestionPool/Export/class.ilSurveyQuestionPoolExporter.php b/components/ILIAS/SurveyQuestionPool/Export/class.ilSurveyQuestionPoolExporter.php index 86b1afdfe312..7a1f40c3d33c 100755 --- a/components/ILIAS/SurveyQuestionPool/Export/class.ilSurveyQuestionPoolExporter.php +++ b/components/ILIAS/SurveyQuestionPool/Export/class.ilSurveyQuestionPoolExporter.php @@ -34,10 +34,7 @@ public function getXmlRepresentation( ): string { $spl = new ilObjSurveyQuestionPool($a_id, false); $spl->loadFromDb(); - - $spl_exp = new ilSurveyQuestionpoolExport($spl, 'xml'); - $spl_exp->buildExportFile(); - return ""; + return $spl->toXmlForExport(); } public function getXmlExportTailDependencies( @@ -45,7 +42,7 @@ public function getXmlExportTailDependencies( string $a_target_release, array $a_ids ): array { - $deps = []; + $dependencies = []; // service settings $deps[] = [ @@ -54,7 +51,19 @@ public function getXmlExportTailDependencies( "ids" => $a_ids ]; - return $deps; + + $md_ids = []; + foreach ($a_ids as $spl_id) { + $md_ids[] = $spl_id . ":0:spl"; + } + if ($md_ids !== []) { + $dependencies[] = [ + "component" => "components/ILIAS/MetaData", + "entity" => "md", + "ids" => $md_ids + ]; + } + return $dependencies; } public function getValidSchemaVersions(string $a_entity): array diff --git a/components/ILIAS/SurveyQuestionPool/Export/class.ilSurveyQuestionPoolImporter.php b/components/ILIAS/SurveyQuestionPool/Export/class.ilSurveyQuestionPoolImporter.php index a5bad2f44773..c166a3be4411 100755 --- a/components/ILIAS/SurveyQuestionPool/Export/class.ilSurveyQuestionPoolImporter.php +++ b/components/ILIAS/SurveyQuestionPool/Export/class.ilSurveyQuestionPoolImporter.php @@ -31,27 +31,34 @@ public function importXmlRepresentation( // Container import => test object already created if ($new_id = $a_mapping->getMapping('components/ILIAS/Container', 'objs', $a_id)) { $newObj = ilObjectFactory::getInstanceByObjId($new_id, false); - } else { // case ii, non container - // Shouldn't happen - $GLOBALS['ilLog']->write(__METHOD__ . ': Called in non container mode'); - return; + } else { // case ii, non container + $newObj = new ilObjSurveyQuestionPool(); + $new_id = $newObj->create(); } + # Try legacy import $xml_file = $this->getXmlFileName(); - - if (!file_exists($xml_file)) { + if (file_exists($xml_file)) { $GLOBALS['ilLog']->write(__METHOD__ . ': Cannot find xml definition: ' . $xml_file); - return; + // import qti data + $newObj->importObject($xml_file); } - // import qti data - $newObj->importObject($xml_file); + $import = new SurveyImportParser($new_id, "", true); + $import->setXMLContent($a_xml); + $import->startParsing(); $a_mapping->addMapping( "components/ILIAS/SurveyQuestionPool", "spl", $a_id, $newObj->getId() ); + $a_mapping->addMapping( + 'components/ILIAS/MetaData', + 'md', + $a_id . ':0:spl', + $newObj->getId() . ':0:spl' + ); } protected function getXmlFileName(): string diff --git a/components/ILIAS/SurveyQuestionPool/Export/class.ilSurveyQuestionpoolExport.php b/components/ILIAS/SurveyQuestionPool/Export/class.ilSurveyQuestionpoolExport.php index 77cd06f3977d..5745c9c419d5 100755 --- a/components/ILIAS/SurveyQuestionPool/Export/class.ilSurveyQuestionpoolExport.php +++ b/components/ILIAS/SurveyQuestionPool/Export/class.ilSurveyQuestionpoolExport.php @@ -100,6 +100,10 @@ public function buildExportFileXML( $this->export_dir . "/" . $this->subdir . ".zip" ); + // remove created files + unlink($this->export_dir . "/" . $this->subdir . "/" . $this->filename); + rmdir($this->export_dir . "/" . $this->subdir); + $expLog->write(date("[y-m-d H:i:s] ") . "Finished Export"); return $this->export_dir . "/" . $this->subdir . ".zip"; diff --git a/components/ILIAS/SurveyQuestionPool/Questions/class.SurveyQuestion.php b/components/ILIAS/SurveyQuestionPool/Questions/class.SurveyQuestion.php index 60b468cb91f1..7cde96736d31 100755 --- a/components/ILIAS/SurveyQuestionPool/Questions/class.SurveyQuestion.php +++ b/components/ILIAS/SurveyQuestionPool/Questions/class.SurveyQuestion.php @@ -1077,12 +1077,8 @@ public static function _getQuestionTypeName( string $type_tag ): string { global $DIC; - - if (file_exists("./components/ILIAS/SurveyQuestionPool/Questions/class." . $type_tag . ".php")) { - $lng = $DIC->language(); - return $lng->txt($type_tag); - } - return ""; + $lng = $DIC->language(); + return $lng->txt($type_tag); } diff --git a/components/ILIAS/SurveyQuestionPool/Questions/class.ilSurveyQuestionsTableGUI.php b/components/ILIAS/SurveyQuestionPool/Questions/class.ilSurveyQuestionsTableGUI.php index 5ef9ee5e1231..80c918f254d3 100755 --- a/components/ILIAS/SurveyQuestionPool/Questions/class.ilSurveyQuestionsTableGUI.php +++ b/components/ILIAS/SurveyQuestionPool/Questions/class.ilSurveyQuestionsTableGUI.php @@ -94,7 +94,7 @@ public function __construct( $this->addMultiCommand('copy', $this->lng->txt('copy')); $this->addMultiCommand('move', $this->lng->txt('move')); - $this->addMultiCommand('exportQuestion', $this->lng->txt('export')); + #$this->addMultiCommand('exportQuestion', $this->lng->txt('export')); $this->addMultiCommand('deleteQuestions', $this->lng->txt('delete')); if (count($clip_questions) > 0) { diff --git a/components/ILIAS/SurveyQuestionPool/classes/class.ilObjSurveyQuestionPool.php b/components/ILIAS/SurveyQuestionPool/classes/class.ilObjSurveyQuestionPool.php index c87427ea3ad7..10463b8907ea 100755 --- a/components/ILIAS/SurveyQuestionPool/classes/class.ilObjSurveyQuestionPool.php +++ b/components/ILIAS/SurveyQuestionPool/classes/class.ilObjSurveyQuestionPool.php @@ -506,17 +506,6 @@ public function toXML(?array $questions): string ); $a_xml_writer->xmlStartTag("surveyquestions", $attrs); $a_xml_writer->xmlElement("dummy", null, "dummy"); - // add ILIAS specific metadata - $a_xml_writer->xmlStartTag("metadata"); - $a_xml_writer->xmlStartTag("metadatafield"); - $a_xml_writer->xmlElement("fieldlabel", null, "SCORM"); - $md = new ilMD($this->getId(), 0, $this->getType()); - $writer = new ilXmlWriter(); - $md->toXML($writer); - $metadata = $writer->xmlDumpMem(); - $a_xml_writer->xmlElement("fieldentry", null, $metadata); - $a_xml_writer->xmlEndTag("metadatafield"); - $a_xml_writer->xmlEndTag("metadata"); $a_xml_writer->xmlEndTag("surveyquestions"); $a_xml_writer->xmlEndTag("surveyobject"); @@ -536,6 +525,36 @@ public function toXML(?array $questions): string return $xml; } + public function toXmlForExport(): string + { + $questions = $this->getQuestions(); + $a_xml_writer = new ilXmlWriter(); + $attrs = array( + "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance", + "xsi:noNamespaceSchemaLocation" => "https://www.ilias.de/download/xsd/ilias_survey_4_2.xsd" + ); + $a_xml_writer->xmlStartTag("surveyobject", $attrs); + $attrs = array( + "id" => "qpl_" . $this->getId(), + "label" => $this->getTitle() + ); + $a_xml_writer->xmlStartTag("surveyquestions", $attrs); + $a_xml_writer->xmlElement("dummy", null, "dummy"); + $a_xml_writer->xmlEndTag("surveyquestions"); + $a_xml_writer->xmlEndTag("surveyobject"); + $xml = $a_xml_writer->xmlDumpMem(false); + $questionxml = ""; + foreach ($questions as $key => $value) { + $questiontype = $this->getQuestiontype($value); + SurveyQuestion::_includeClass($questiontype); + $question = new $questiontype(); + $question->loadFromDb($value); + $questionxml .= $question->toXML(false); + } + $xml = str_replace("dummy", $questionxml, $xml); + return $xml; + } + public function getQuestions(): array { $ilDB = $this->db; diff --git a/components/ILIAS/SurveyQuestionPool/classes/class.ilObjSurveyQuestionPoolGUI.php b/components/ILIAS/SurveyQuestionPool/classes/class.ilObjSurveyQuestionPoolGUI.php index da81c777ba7b..44c96721e67e 100755 --- a/components/ILIAS/SurveyQuestionPool/classes/class.ilObjSurveyQuestionPoolGUI.php +++ b/components/ILIAS/SurveyQuestionPool/classes/class.ilObjSurveyQuestionPoolGUI.php @@ -16,7 +16,11 @@ * *********************************************************************/ +use ILIAS\Filesystem\Stream\Streams; +use ILIAS\Filesystem\Util\Archive\Archives as ILIASArchives; use ILIAS\SurveyQuestionPool\Editing\EditingGUIRequest; +use ILIAS\Refinery\Factory as RefineryFactory; +use ILIAS\HTTP\Services as HTTPServices; /** * Class ilObjSurveyQuestionPoolGUI @@ -29,6 +33,7 @@ * @ilCtrl_Calls ilObjSurveyQuestionPoolGUI: ilObjectMetaDataGUI, ilPermissionGUI, ilObjectCopyGUI * @ilCtrl_Calls ilObjSurveyQuestionPoolGUI: ilCommonActionDispatcherGUI * @ilCtrl_Calls ilObjSurveyQuestionPoolGUI: ILIAS\SurveyQuestionPool\Settings\SettingsGUI + * @ilCtrl_Calls ilObjSurveyQuestionPoolGUI: ilExportGUI */ class ilObjSurveyQuestionPoolGUI extends ilObjectGUI implements ilCtrlBaseClassInterface { @@ -39,6 +44,8 @@ class ilObjSurveyQuestionPoolGUI extends ilObjectGUI implements ilCtrlBaseClassI protected ilNavigationHistory $nav_history; protected ilHelpGUI $help; protected ilLogger $log; + protected RefineryFactory $refinery; + protected ILIASArchives $archives; public string $defaultscript; public function __construct() @@ -48,7 +55,8 @@ public function __construct() $this->nav_history = $DIC["ilNavigationHistory"]; $this->toolbar = $DIC->toolbar(); $this->help = $DIC["ilHelp"]; - + $this->refinery = $DIC->refinery(); + $this->archives = $DIC->archives(); $this->edit_request = $DIC->surveyQuestionPool() ->internal() ->gui() @@ -76,7 +84,6 @@ public function __construct() public function executeCommand(): void { $ilNavigationHistory = $this->nav_history; - if (!$this->checkPermissionBool("visible") && !$this->checkPermissionBool("read")) { $this->checkPermission("read"); @@ -97,7 +104,9 @@ public function executeCommand(): void $cmd = $this->ctrl->getCmd("questions"); $next_class = $this->ctrl->getNextClass($this); - $this->ctrl->setReturn($this, "questions"); + if ($cmd !== "cancel") { + $this->ctrl->setReturn($this, "questions"); + } $q_type = ""; if ($this->edit_request->getQuestionId() < 1) { $q_type = $this->edit_request->getSelectedQuestionTypes(); @@ -143,6 +152,11 @@ public function executeCommand(): void $this->ctrl->forwardCommand($gui); break; + case strtolower(ilExportGUI::class): + $export = new ilExportGUI($this, $this->object); + $this->ctrl->forwardCommand($export); + break; + case "": $cmd .= "Object"; $this->$cmd(); @@ -226,6 +240,18 @@ public function exportQuestionObject(): void } } + public function exportQuestionExportTabObject(): void + { + $qids = $this->http->wrapper()->query()->retrieve( + "qid", + $this->refinery->custom()->transformation(function (string $value) { + $value = urldecode($value); + return explode(',', $value); + }) + ); + $this->createExportFileObject($qids); + } + /** * Creates a confirmation form to delete questions from the question pool */ @@ -405,11 +431,12 @@ public function questionsObject(): void )->submit()->toToolbar(); $ilToolbar->addSeparator(); - + /* $this->gui->button( $this->lng->txt("import"), "importQuestions" )->submit()->toToolbar(); + */ } $table_gui = new ilSurveyQuestionsTableGUI($this, 'questions', $this->checkPermissionBool('write')); @@ -444,27 +471,8 @@ protected function afterSave(ilObject $new_object): void */ public function exportObject(): void { - $ilToolbar = $this->toolbar; - - $this->tabs_gui->activateTab("export"); - $ilToolbar->addButton( - $this->lng->txt('create_export_file'), - $this->ctrl->getLinkTarget($this, 'createExportFile') - ); - - $table_gui = new ilSurveyQuestionPoolExportTableGUI($this, 'export'); - $export_dir = $this->object->getExportDirectory(); - $export_files = $this->object->getExportFiles($export_dir); - $data = array(); - foreach ($export_files as $exp_file) { - $file_arr = explode("__", $exp_file); - $data[] = array('file' => $exp_file, - 'date' => ilDatePresentation::formatDate(new ilDateTime($file_arr[0], IL_CAL_UNIX)), - 'size' => filesize($export_dir . "/" . $exp_file) - ); - } - $table_gui->setData($data); - $this->tpl->setContent($table_gui->getHTML()); + $export = new ilExportGUI($this, $this->object); + $export->listExportFiles(); } /** @@ -556,29 +564,6 @@ public function deleteExportFileObject(): void $this->ctrl->redirect($this, "export"); } - protected function importFile(string $file_to_import, string $path_to_uploaded_file_in_temp_dir): void - { - $tpl = $this->tpl; - - $newObj = new ilObjSurveyQuestionPool(); - $newObj->setTitle("dummy"); - $newObj->create(true); - $this->putObjectInTree($newObj); - - // import qti data - $newObj->importObject($file_to_import); - - if ($path_to_uploaded_file_in_temp_dir !== '' - && $this->temp_file_system->hasDir($path_to_uploaded_file_in_temp_dir)) { - $this->temp_file_system->deleteDir($path_to_uploaded_file_in_temp_dir); - } - - $this->deleteUploadedImportFile($path_to_uploaded_file_in_temp_dir); - $this->tpl->setOnScreenMessage('success', $this->lng->txt("object_imported"), true); - ilUtil::redirect("ilias.php?ref_id=" . $newObj->getRefId() . - "&baseClass=ilObjSurveyQuestionPoolGUI"); - } - /** * create new question */ @@ -646,6 +631,35 @@ public function infoScreenForward(): void $this->ctrl->forwardCommand($info); } + protected function importFile(string $file_to_import, string $path_to_uploaded_file_in_temp_dir): void + { + $unzip = $this->archives->unzip(Streams::ofResource(fopen($file_to_import, 'r'))); + # If export contains a manifest xml use standard import + if (in_array(basename($file_to_import, ".zip") . DIRECTORY_SEPARATOR . "manifest.xml", iterator_to_array($unzip->getFiles()))) { + parent::importFile($file_to_import, $path_to_uploaded_file_in_temp_dir); + return; + } + $tpl = $this->tpl; + + $newObj = new ilObjSurveyQuestionPool(); + $newObj->setTitle("dummy"); + $newObj->create(); + $this->putObjectInTree($newObj); + + // import qti data + $newObj->importObject($file_to_import); + + if ($path_to_uploaded_file_in_temp_dir !== '' + && $this->temp_file_system->hasDir($path_to_uploaded_file_in_temp_dir)) { + $this->temp_file_system->deleteDir($path_to_uploaded_file_in_temp_dir); + } + + $this->deleteUploadedImportFile($path_to_uploaded_file_in_temp_dir); + $this->tpl->setOnScreenMessage('success', $this->lng->txt("object_imported"), true); + ilUtil::redirect("ilias.php?ref_id=" . $newObj->getRefId() . + "&baseClass=ilObjSurveyQuestionPoolGUI"); + } + protected function addLocatorItems(): void { $ilLocator = $this->locator; diff --git a/components/ILIAS/SurveyQuestionPool/templates/default/tpl.il_svy_out_text.html b/components/ILIAS/SurveyQuestionPool/templates/default/tpl.il_svy_out_text.html index 501887659c3b..b06dea5d03e7 100755 --- a/components/ILIAS/SurveyQuestionPool/templates/default/tpl.il_svy_out_text.html +++ b/components/ILIAS/SurveyQuestionPool/templates/default/tpl.il_svy_out_text.html @@ -6,7 +6,7 @@

{QUESTIONTEXT}{OBLIGATORY_TEXT}

: - + diff --git a/components/ILIAS/SystemCheck/classes/class.ilObjSystemCheckGUI.php b/components/ILIAS/SystemCheck/classes/class.ilObjSystemCheckGUI.php index 2ec65db955df..6d309cb30936 100755 --- a/components/ILIAS/SystemCheck/classes/class.ilObjSystemCheckGUI.php +++ b/components/ILIAS/SystemCheck/classes/class.ilObjSystemCheckGUI.php @@ -29,18 +29,15 @@ class ilObjSystemCheckGUI extends ilObjectGUI { protected const SECTION_MAIN = 'main'; protected const SECTION_GROUP = 'group'; - - protected GlobalHttpState $http; - protected Factory $refinery; + protected \ILIAS\Repository\ExternalGUIService $repo_gui_service; public function __construct($a_data, $a_id, $a_call_by_reference, $a_prepare_output = true) { global $DIC; - $this->http = $DIC->http(); - $this->refinery = $DIC->refinery(); $this->type = 'sysc'; parent::__construct($a_data, $a_id, $a_call_by_reference, $a_prepare_output); $this->lng->loadLanguageModule('sysc'); + $this->repo_gui_service = $DIC->repository()->gui(); } protected function getGrpIdFromRequest(): int @@ -85,7 +82,7 @@ public function executeCommand(): void $read_only = !$this->checkPermissionBool('write'); - $gui = new ilObjectOwnershipManagementGUI(0, $read_only); + $gui = $gui = $this->repo_gui_service->ownershipManagementGUI(0, $read_only); $this->ctrl->forwardCommand($gui); break; diff --git a/components/ILIAS/SystemFolder/classes/class.ilAccessibilitySupportContactsGUI.php b/components/ILIAS/SystemFolder/classes/class.ilAccessibilitySupportContactsGUI.php index 332fbc67eb34..416d9f683ded 100755 --- a/components/ILIAS/SystemFolder/classes/class.ilAccessibilitySupportContactsGUI.php +++ b/components/ILIAS/SystemFolder/classes/class.ilAccessibilitySupportContactsGUI.php @@ -103,7 +103,7 @@ public static function getFooterLink(): string $contacts = ilAccessibilitySupportContacts::getValidSupportContactIds(); if (count($contacts) > 0) { if ($rbac_system->checkAccess("internal_mail", ilMailGlobalServices::getMailObjectRefId())) { - return $ctrl->getLinkTargetByClass("ilaccessibilitysupportcontactsgui", ""); + return ILIAS_HTTP_PATH . "/" . $ctrl->getLinkTargetByClass("ilaccessibilitysupportcontactsgui", ""); } else { $mails = ilLegacyFormElementsUtil::prepareFormOutput( ilAccessibilitySupportContacts::getMailsToAddress() diff --git a/components/ILIAS/SystemFolder/classes/class.ilAccessibilitySupportFooterProvider.php b/components/ILIAS/SystemFolder/classes/class.ilAccessibilitySupportFooterProvider.php new file mode 100644 index 000000000000..1212be2be247 --- /dev/null +++ b/components/ILIAS/SystemFolder/classes/class.ilAccessibilitySupportFooterProvider.php @@ -0,0 +1,77 @@ +lng = $dic->language(); + $this->lng->loadLanguageModule('gsfo'); + } + + public function getGroups(): array + { + return [ + $this->item_factory->group( + $this->id_factory->identifier(ilFooterStandardGroups::ACCESSIBILITY->value), + $this->lng->txt('accessibility') + )->withPosition(10), + ]; + } + + public function getEntries(): array + { + $entries = []; + // Accessibility Items + // accessibility control concept + if (($accessibility_control_url = \ilAccessibilityControlConceptGUI::getFooterLink()) !== '') { + $accessibility_control_title = \ilAccessibilityControlConceptGUI::getFooterText(); + $entries[] = $this->item_factory + ->link( + $this->id_factory->identifier('accessibility_control'), + $accessibility_control_title + ) + ->withAction($accessibility_control_url) + ->withParent($this->id_factory->identifier(ilFooterStandardGroups::ACCESSIBILITY->value)); + } + + // report accessibility issue + if (($accessibility_report_url = \ilAccessibilitySupportContactsGUI::getFooterLink()) !== '') { + $accessibility_report_title = \ilAccessibilitySupportContactsGUI::getFooterText(); + $entries[] = $this->item_factory + ->link( + $this->id_factory->identifier('accessibility_report'), + $accessibility_report_title + ) + ->withAction($accessibility_report_url) + ->withParent($this->id_factory->identifier(ilFooterStandardGroups::ACCESSIBILITY->value)); + } + + return $entries; + } + +} diff --git a/components/ILIAS/ILIASObject/classes/Translation/class.ilObjectTranslationTableGUI.php b/components/ILIAS/SystemFolder/classes/class.ilInstallationHeadingTableGUI.php similarity index 95% rename from components/ILIAS/ILIASObject/classes/Translation/class.ilObjectTranslationTableGUI.php rename to components/ILIAS/SystemFolder/classes/class.ilInstallationHeadingTableGUI.php index eeceb3918e7d..3119fbe0de12 100755 --- a/components/ILIAS/ILIASObject/classes/Translation/class.ilObjectTranslationTableGUI.php +++ b/components/ILIAS/SystemFolder/classes/class.ilInstallationHeadingTableGUI.php @@ -25,7 +25,7 @@ * * @author Alex Killing */ -class ilObjectTranslationTableGUI extends ilTable2GUI +class ilInstallationHeadingTableGUI extends ilTable2GUI { protected ilAccessHandler $access; protected LOMServices $lom_services; @@ -61,7 +61,7 @@ public function __construct( $this->setEnableHeader(true); $this->setFormAction($this->ctrl->getFormAction($parent_obj)); - $this->setRowTemplate("tpl.obj_translation_row.html", "components/ILIAS/ILIASObject"); + $this->setRowTemplate("tpl.installation_heading_table_row.html", "components/ILIAS/SystemFolder"); $this->disable("footer"); $this->setEnableTitle(true); diff --git a/components/ILIAS/SystemFolder/classes/class.ilObjSystemFolderGUI.php b/components/ILIAS/SystemFolder/classes/class.ilObjSystemFolderGUI.php index 22ac7057c490..8c965c6386df 100755 --- a/components/ILIAS/SystemFolder/classes/class.ilObjSystemFolderGUI.php +++ b/components/ILIAS/SystemFolder/classes/class.ilObjSystemFolderGUI.php @@ -32,6 +32,7 @@ */ class ilObjSystemFolderGUI extends ilObjectGUI { + protected \Pimple\Container $dic; protected \ILIAS\Repository\InternalGUIService $gui; protected ilPropertyFormGUI $form; protected \ILIAS\Style\Content\Object\ObjectFacade $content_style_domain; @@ -56,6 +57,7 @@ public function __construct($a_data, $a_id, $a_call_by_reference) { global $DIC; + $this->dic = $DIC; $this->tabs = $DIC->tabs(); $this->access = $DIC->access(); $this->ctrl = $DIC->ctrl(); @@ -100,7 +102,7 @@ public function executeCommand(): void case "ilobjectownershipmanagementgui": $this->setSystemCheckSubTabs("no_owner"); - $gui = new ilObjectOwnershipManagementGUI(0); + $gui = $this->gui->ownership()->ownershipManagementGUI(0); $this->ctrl->forwardCommand($gui); break; @@ -420,8 +422,11 @@ public function getAdminTabs(): void */ public function showPHPInfoObject(): void { - phpinfo(); - exit; + $rbacsystem = $this->rbacsystem; + if ($rbacsystem->checkAccess("read", $this->object->getRefId())) { + phpinfo(); + exit; + } } // @@ -440,9 +445,12 @@ public function setServerInfoSubTabs($a_activate): void $ilCtrl = $this->ctrl; $rbacsystem = $this->rbacsystem; - $ilTabs->addSubTabTarget("installation_status", $ilCtrl->getLinkTarget($this, "showServerInstallationStatus")); + if ($rbacsystem->checkAccess("read", $this->object->getRefId())) { + $ilTabs->addSubTabTarget("installation_status", $ilCtrl->getLinkTarget($this, "showServerInstallationStatus")); + + $ilTabs->addSubTabTarget("server_data", $ilCtrl->getLinkTarget($this, "showServerInfo")); + } - $ilTabs->addSubTabTarget("server_data", $ilCtrl->getLinkTarget($this, "showServerInfo")); if ($rbacsystem->checkAccess("write", $this->object->getRefId())) { $ilTabs->addSubTabTarget("java_server", $ilCtrl->getLinkTarget($this, "showJavaServer")); @@ -467,20 +475,24 @@ public function showServerInfoObject(): void $ilCtrl = $this->ctrl; $ilToolbar = $this->toolbar; - $this->gui->link( - $this->lng->txt("vc_information"), - $this->ctrl->getLinkTarget($this, 'showVcsInformation') - )->toToolbar(); + $rbacsystem = $this->rbacsystem; + if ($rbacsystem->checkAccess("read", $this->object->getRefId())) { + + $this->gui->link( + $this->lng->txt("vc_information"), + $this->ctrl->getLinkTarget($this, 'showVcsInformation') + )->toToolbar(); - $this->initServerInfoForm(); - // TODO: remove sub tabs - // $this->tabs->setTabActive("server"); - $this->setServerInfoSubTabs("server_data"); + $this->initServerInfoForm(); + // TODO: remove sub tabs + // $this->tabs->setTabActive("server"); + $this->setServerInfoSubTabs("server_data"); - $btpl = new ilTemplate("tpl.server_data.html", true, true, "components/ILIAS/SystemFolder"); - $btpl->setVariable("FORM", $this->form->getHTML()); - $btpl->setVariable("PHP_INFO_TARGET", $ilCtrl->getLinkTarget($this, "showPHPInfo")); - $tpl->setContent($btpl->get()); + $btpl = new ilTemplate("tpl.server_data.html", true, true, "components/ILIAS/SystemFolder"); + $btpl->setVariable("FORM", $this->form->getHTML()); + $btpl->setVariable("PHP_INFO_TARGET", $ilCtrl->getLinkTarget($this, "showPHPInfo")); + $tpl->setContent($btpl->get()); + } } /** @@ -587,39 +599,27 @@ public function initServerInfoForm(): void protected function showServerInstallationStatusObject(): void { - $this->setServerInfoSubTabs("installation_status"); - $this->renderServerStatus(); + $rbacsystem = $this->rbacsystem; + if ($rbacsystem->checkAccess("read", $this->object->getRefId())) { + $this->setServerInfoSubTabs("installation_status"); + $this->renderServerStatus(); + } } protected function renderServerStatus(): void { - global $DIC; - $f = $DIC->ui()->factory(); - $r = $DIC->ui()->renderer(); - $refinery = $DIC->refinery(); - - $metric = $this->getServerStatusInfo($refinery); + $f = $this->dic->ui()->factory(); + $r = $this->dic->ui()->renderer(); + $metric = $this->getServerStatusInfo(); $report = $metric->toUIReport($f, $this->lng->txt("installation_status")); $this->tpl->setContent($r->render($report)); } - protected function getServerStatusInfo(ILIAS\Refinery\Factory $refinery): ILIAS\Setup\Metrics\Metric + protected function getServerStatusInfo(): ILIAS\Setup\Metrics\Metric { - $data = new Factory(); - $lng = new ilSetupLanguage('en'); - $interface_finder = new ImplementationOfInterfaceFinder(); - - $agent_finder = new ImplementationOfAgentFinder( - $refinery, - $data, - $lng, - $interface_finder, - [] - ); - + $agent_finder = $this->dic['setup.agentfinder']; $st = new StatusCommand($agent_finder); - return $st->getMetrics($agent_finder->getAgents()); } @@ -807,7 +807,7 @@ public function showHeaderTitleObject( ): void { $tpl = $this->tpl; $this->setGeneralSettingsSubTabs("header_title"); - $table = new ilObjectTranslationTableGUI($this, "showHeaderTitle", false); + $table = new ilInstallationHeadingTableGUI($this, "showHeaderTitle", false); $post = $this->gui->http()->request()->getParsedBody(); if ($a_get_post_values) { $vals = array(); @@ -848,7 +848,6 @@ public function showHeaderTitleObject( */ public function saveHeaderTitlesObject(bool $delete = false) { - global $DIC; $ilCtrl = $this->ctrl; $lng = $this->lng; $rbacsystem = $this->rbacsystem; @@ -859,7 +858,7 @@ public function saveHeaderTitlesObject(bool $delete = false) $this->ctrl->redirectByClass(self::class, "showHeaderTitle"); } - $post = $DIC->http()->request()->getParsedBody(); + $post = $this->dic->http()->request()->getParsedBody(); foreach ($post["title"] as $k => $v) { if ($delete && ($post["check"][$k] ?? false)) { unset($post["title"][$k]); @@ -1264,20 +1263,22 @@ public static function _goto(): void protected function showVcsInformationObject(): void { $vcInfo = []; + $rbacsystem = $this->rbacsystem; + if ($rbacsystem->checkAccess("read", $this->object->getRefId())) { + foreach ([new ilGitInformation()] as $vc) { + $html = $vc->getInformationAsHtml(); + if ($html) { + $vcInfo[] = $html; + } + } - foreach ([new ilGitInformation()] as $vc) { - $html = $vc->getInformationAsHtml(); - if ($html) { - $vcInfo[] = $html; + if ($vcInfo !== []) { + $this->tpl->setOnScreenMessage('info', implode("
", $vcInfo)); + } else { + $this->tpl->setOnScreenMessage('info', $this->lng->txt('vc_information_not_determined')); } - } - if ($vcInfo !== []) { - $this->tpl->setOnScreenMessage('info', implode("
", $vcInfo)); - } else { - $this->tpl->setOnScreenMessage('info', $this->lng->txt('vc_information_not_determined')); + $this->showServerInfoObject(); } - - $this->showServerInfoObject(); } } diff --git a/components/ILIAS/ILIASObject/templates/default/tpl.obj_translation_row.html b/components/ILIAS/SystemFolder/templates/default/tpl.installation_heading_table_row.html similarity index 100% rename from components/ILIAS/ILIASObject/templates/default/tpl.obj_translation_row.html rename to components/ILIAS/SystemFolder/templates/default/tpl.installation_heading_table_row.html diff --git a/components/ILIAS/Tagging/classes/class.ilTaggingGUI.php b/components/ILIAS/Tagging/classes/class.ilTaggingGUI.php index 9c99cb669650..aaa39846102a 100755 --- a/components/ILIAS/Tagging/classes/class.ilTaggingGUI.php +++ b/components/ILIAS/Tagging/classes/class.ilTaggingGUI.php @@ -284,7 +284,7 @@ public static function initJavascript( $tpl->addOnLoadCode("ilTagging.setAjaxUrl('" . $a_ajax_url . "');"); $tpl->addOnLoadCode('ilTagging.setModalTemplate("' . addslashes(json_encode($modal_template["template"])) . '");'); $tpl->addOnLoadCode("ilTagging.setShowSignal('" . $modal_template["show"] . "');"); - $tpl->addOnLoadCode("ilTagging.setCloseSignal('" . $modal_template["close"] . "');"); + $tpl->addOnLoadCode("ilTagging.setHideSignal('" . $modal_template["close"] . "');"); } public static function getModalTemplate(): array diff --git a/components/ILIAS/TermsOfService/classes/class.ilObjTermsOfServiceGUI.php b/components/ILIAS/TermsOfService/classes/class.ilObjTermsOfServiceGUI.php index 8e6148439489..a107eaf4757c 100755 --- a/components/ILIAS/TermsOfService/classes/class.ilObjTermsOfServiceGUI.php +++ b/components/ILIAS/TermsOfService/classes/class.ilObjTermsOfServiceGUI.php @@ -101,6 +101,9 @@ public function executeCommand(): void return; case 'resetNow': $this->resetNow(); return; + case 'documents': + $this->ctrl->redirectByClass([self::class, get_class($this->legal_documents)], 'documents'); + return; default: $this->settings(); return; } @@ -230,6 +233,7 @@ private function resetNow(): void $in = $this->dic->database()->in('usr_id', [ANONYMOUS_USER_ID, SYSTEM_USER_ID], true, 'integer'); $this->dic->database()->manipulate("UPDATE usr_data SET agree_date = NULL WHERE $in"); $this->tos_settings->lastResetDate()->update((new DataFactory())->clock()->system()->now()); + $this->tpl->setOnScreenMessage('success', $this->lng->txt('msg_obj_modified'), true); $this->dic->ctrl()->redirectByClass([self::class, $this->legal_documents::class], 'documents'); } diff --git a/components/ILIAS/Test/classes/Evaluation/class.ilTestEvaluationFactory.php b/components/ILIAS/Test/classes/Evaluation/class.ilTestEvaluationFactory.php index 50d965fadb7c..d79b6bcd4a89 100644 --- a/components/ILIAS/Test/classes/Evaluation/class.ilTestEvaluationFactory.php +++ b/components/ILIAS/Test/classes/Evaluation/class.ilTestEvaluationFactory.php @@ -94,93 +94,157 @@ private function retrieveEvaluationData(array $active_ids): \Generator } } + public function getCorrectionsEvaluationData(): ilTestEvaluationData + { + $participants = []; + $current_user = null; + $current_attempt = null; + + foreach ($this->retrieveEvaluationData($this->getAccessFilteredActiveIds()) as $row) { + $active_id = $row['active_id']; + $pass = $row['pass']; + + if ($current_user !== $active_id) { + $current_user = $active_id; + $current_attempt = null; + $user_eval_data = $this->buildBasicUserEvaluationDataFromDB($row); + } + + if ($current_attempt !== $pass) { + $current_attempt = $pass; + $attempt = $this->buildBasicAttemptEvaluationDataFromDB($row); + } + + $attempt = $this->addQuestionToAttempt($attempt, $row); + $user_eval_data->addPass($pass, $attempt); + $participants[$active_id] = $user_eval_data; + } + return new ilTestEvaluationData($participants); + } + public function getEvaluationData(): ilTestEvaluationData { $eval_data_rows = $this->retrieveEvaluationData($this->getAccessFilteredActiveIds()); - $scoring_settings = $this->test_obj->getPassScoring(); $participants = []; $current_user = null; $current_attempt = null; foreach ($eval_data_rows as $row) { + if ($row['pass'] === null) { + continue; + } + if ($current_user !== $row['active_id']) { $current_user = $row['active_id']; $current_attempt = null; - - $user_eval_data = new ilTestEvaluationUserData($scoring_settings); - - $user_eval_data->setName( - $this->test_obj->buildName($row['usr_id'], $row['firstname'], $row['lastname']) + $user_eval_data = $this->addVisitingTimeToUserEvalData( + $this->buildBasicUserEvaluationDataFromDB($row), + $row['active_id'] ); - - if ($row['login'] !== null) { - $user_eval_data->setLogin($row['login']); - } - if ($row['usr_id'] !== null) { - $user_eval_data->setUserID($row['usr_id']); - } - $user_eval_data->setSubmitted((bool) $row['submitted']); - $user_eval_data->setLastFinishedPass($row['last_finished_pass']); - - - $visiting_time = $this->test_obj->getVisitingTimeOfParticipant($row['active_id']); - $user_eval_data->setFirstVisit($visiting_time["first_access"]); - $user_eval_data->setLastVisit($visiting_time["last_access"]); } if ($row['pass'] !== null && $current_attempt !== $row['pass']) { $current_attempt = $row['pass']; - $attempt = new \ilTestEvaluationPassData(); - $attempt->setPass($row['pass']); - $attempt->setReachedPoints($row['points']); - - if ($row['questioncount'] == 0) { - list($count, $points) = array_values( - $this->test_obj->getQuestionCountAndPointsForPassOfParticipant($row['active_id'], $row['pass']) - ); - $attempt->setMaxPoints($points); - $attempt->setQuestionCount($count); - } else { - $attempt->setMaxPoints($row['maxpoints']); - $attempt->setQuestionCount($row['questioncount']); - } + $attempt = $this->addPointsAndQuestionCountToAttempt( + $this->buildBasicAttemptEvaluationDataFromDB($row), + $row + ); - $attempt->setNrOfAnsweredQuestions($row['answeredquestions']); - $attempt->setWorkingTime($row['workingtime']); - $start_time = $this->getFirstVisitForActiveIdAndAttempt($row['active_id'], $row['pass']); + $start_time = $this->getFirstVisitForActiveIdAndAttempt($row['active_id'], $current_attempt); if ($start_time !== null) { $attempt->setStartTime($start_time); } - $attempt->setExamId((string) $row['exam_id']); - $attempt->setRequestedHintsCount($row['hint_count']); - $attempt->setDeductedHintPoints($row['hint_points']); $attempt->setStatusOfAttempt( $this->buildFinalizedBy($current_attempt, $row['last_finished_pass'], $row['finalized_by']) ); } - if ($row['question_fi'] !== null) { - $attempt->addAnsweredQuestion( - $row["question_fi"], - $row["qpl_maxpoints"], - $row["result_points"], - (bool) $row['answered'], - null, - $row['manual'] - ); - } - - if (isset($attempt)) { - $user_eval_data->addPass($row['pass'], $attempt); - } - + $user_eval_data->addPass($row['pass'], $this->addQuestionToAttempt($attempt, $row)); $participants[$row['active_id']] = $user_eval_data; } - $evaluation_data = $this->addQuestionsToParticipantPasses(new ilTestEvaluationData($participants)); + $evaluation_data = $this->addQuestionsToParticipantPasses( + new ilTestEvaluationData($participants) + ); return $this->addMarksToParticipants($evaluation_data); } + private function buildBasicUserEvaluationDataFromDB(array $row): ilTestEvaluationUserData + { + $user_data = new ilTestEvaluationUserData($this->test_obj->getPassScoring()); + + $user_data->setName( + $this->test_obj->buildName($row['usr_id'], $row['firstname'], $row['lastname']) + ); + + if ($row['login'] !== null) { + $user_data->setLogin($row['login']); + } + if ($row['usr_id'] !== null) { + $user_data->setUserID($row['usr_id']); + } + $user_data->setSubmitted((bool) $row['submitted']); + $user_data->setLastFinishedPass($row['last_finished_pass']); + return $user_data; + } + + private function buildBasicAttemptEvaluationDataFromDB(array $row): ilTestEvaluationPassData + { + $attempt = new \ilTestEvaluationPassData(); + $attempt->setPass($row['pass']); + $attempt->setReachedPoints($row['points']); + $attempt->setNrOfAnsweredQuestions($row['answeredquestions']); + $attempt->setWorkingTime($row['workingtime']); + $attempt->setExamId((string) $row['exam_id']); + return $attempt; + } + + private function addVisitingTimeToUserEvalData( + ilTestEvaluationUserData $user_data, + int $active_id + ): ilTestEvaluationUserData { + $visiting_time = $this->test_obj->getVisitingTimeOfParticipant($active_id); + $user_data->setFirstVisit($visiting_time['first_access']); + $user_data->setLastVisit($visiting_time['last_access']); + return $user_data; + } + + private function addPointsAndQuestionCountToAttempt( + ilTestEvaluationPassData $attempt, + array $row + ): ilTestEvaluationPassData { + if ($row['questioncount'] !== 0) { + $attempt->setMaxPoints($row['maxpoints']); + $attempt->setQuestionCount($row['questioncount']); + return $attempt; + } + + list($count, $points) = array_values( + $this->test_obj->getQuestionCountAndPointsForPassOfParticipant($row['active_id'], $row['pass']) + ); + $attempt->setMaxPoints($points); + $attempt->setQuestionCount($count); + return $attempt; + } + + private function addQuestionToAttempt( + ilTestEvaluationPassData $attempt, + array $row + ): ilTestEvaluationPassData { + if ($row['question_fi'] === null) { + return $attempt; + } + + $attempt->addAnsweredQuestion( + $row["question_fi"], + $row["qpl_maxpoints"], + $row["result_points"], + (bool) $row['answered'], + null, + $row['manual'] + ); + return $attempt; + } private function addQuestionsToParticipantPasses(ilTestEvaluationData $evaluation_data): ilTestEvaluationData { diff --git a/components/ILIAS/Test/classes/Evaluation/class.ilTestEvaluationPassData.php b/components/ILIAS/Test/classes/Evaluation/class.ilTestEvaluationPassData.php index 06b4dd24c4ab..9fe55f6a7f95 100644 --- a/components/ILIAS/Test/classes/Evaluation/class.ilTestEvaluationPassData.php +++ b/components/ILIAS/Test/classes/Evaluation/class.ilTestEvaluationPassData.php @@ -39,8 +39,6 @@ class ilTestEvaluationPassData private ?Mark $mark = null; private int $nrOfAnsweredQuestions; private int $pass; - private ?int $requestedHintsCount = null; - private ?float $deductedHintPoints = null; private string $exam_id = ''; private ?StatusOfAttempt $status_of_attempt = null; @@ -206,26 +204,6 @@ public function getAnsweredQuestionCount(): int return count($this->answeredQuestions); } - public function getRequestedHintsCount(): ?int - { - return $this->requestedHintsCount; - } - - public function setRequestedHintsCount(int $requestedHintsCount): void - { - $this->requestedHintsCount = $requestedHintsCount; - } - - public function getDeductedHintPoints(): ?float - { - return $this->deductedHintPoints; - } - - public function setDeductedHintPoints(float $deductedHintPoints): void - { - $this->deductedHintPoints = $deductedHintPoints; - } - public function getExamId(): string { return $this->exam_id; diff --git a/components/ILIAS/Test/classes/Evaluation/class.ilTestEvaluationUserData.php b/components/ILIAS/Test/classes/Evaluation/class.ilTestEvaluationUserData.php index 28de6c8dc32f..1dfe1af1efec 100644 --- a/components/ILIAS/Test/classes/Evaluation/class.ilTestEvaluationUserData.php +++ b/components/ILIAS/Test/classes/Evaluation/class.ilTestEvaluationUserData.php @@ -388,14 +388,6 @@ public function getScoredPassObject(): ilTestEvaluationPassData return $this->getLastPassObject(); } - /** - * returns the count of hints requested by participant for scored testpass - */ - public function getRequestedHintsCountFromScoredPass(): int - { - return $this->getRequestedHintsCount($this->getScoredPass()); - } - public function getExamIdFromScoredPass(): string { $exam_id = ''; @@ -408,22 +400,6 @@ public function getExamIdFromScoredPass(): string return $exam_id; } - /** - * returns the count of hints requested by participant for given testpass - * - * @throws ilTestException - */ - public function getRequestedHintsCount(int $pass): int - { - if (!isset($this->passes[$pass]) || !($this->passes[$pass] instanceof ilTestEvaluationPassData)) { - throw new ilTestException("invalid pass index given: $pass"); - } - - $requestedHintsCount = $this->passes[$pass]->getRequestedHintsCount(); - - return $requestedHintsCount; - } - /** * returns the object of class ilTestEvaluationPassData * that relates to the the best test pass diff --git a/components/ILIAS/Test/classes/Notifications/class.ilTestManScoringParticipantNotification.php b/components/ILIAS/Test/classes/Notifications/class.ilTestManScoringParticipantNotification.php index ea85aa2c33ed..3e332f894529 100755 --- a/components/ILIAS/Test/classes/Notifications/class.ilTestManScoringParticipantNotification.php +++ b/components/ILIAS/Test/classes/Notifications/class.ilTestManScoringParticipantNotification.php @@ -78,7 +78,7 @@ private function buildBody(): void $feedback = $this->convertFeedbackForMail($feedback); - $this->appendBody($this->getLanguageText('tst_question') . ': ' . $questionGui->getObject()->getTitle()); + $this->appendBody($this->getLanguageText('tst_question') . ': ' . $questionGui->getObject()->getTitleForHTMLOutput()); $this->appendBody("\n"); $this->appendBody($this->getLanguageText('tst_reached_points') . ': ' . $points); $this->appendBody("\n"); diff --git a/components/ILIAS/Test/classes/Tables/class.ilTestExportTableGUI.php b/components/ILIAS/Test/classes/Tables/class.ilTestExportTableGUI.php deleted file mode 100755 index 21695ca6770d..000000000000 --- a/components/ILIAS/Test/classes/Tables/class.ilTestExportTableGUI.php +++ /dev/null @@ -1,96 +0,0 @@ - - * @version $Id$ - * - * @ingroup components\ILIASTest - */ -class ilTestExportTableGUI extends ilExportTableGUI -{ - private \ILIAS\UI\Factory $ui_factory; - private \ILIAS\UI\Renderer $ui_renderer; - - public function __construct(object $a_parent_obj, string $a_parent_cmd, ilObject $a_exp_obj) - { - global $DIC; - $this->ui_factory = $DIC->ui()->factory(); - $this->ui_renderer = $DIC->ui()->renderer(); - - parent::__construct($a_parent_obj, $a_parent_cmd, $a_exp_obj); - } - - protected function formatActionsList(string $type, string $filename): string - { - $this->ctrl->setParameter($this->getParentObject(), 'file', $filename); - $actions[] = $this->ui_factory->link()->standard($this->lng->txt('download'), $this->ctrl->getLinkTarget($this->getParentObject(), 'download')); - $this->ctrl->setParameter($this->getParentObject(), 'file', ''); - $dropdown = $this->ui_factory->dropdown()->standard($actions)->withLabel($this->lng->txt('actions')); - return $this->ui_renderer->render($dropdown); - } - - protected function initMultiCommands(): void - { - $this->addMultiCommand('confirmDeletion', $this->lng->txt('delete')); - } - - /** - * Overwrite method because data is passed from outside - */ - public function getExportFiles(): array - { - return []; - } - - protected function initColumns(): void - { - $this->addColumn($this->lng->txt(''), '', '1', true); - $this->addColumn($this->lng->txt('type'), 'type'); - $this->addColumn($this->lng->txt('file'), 'file'); - $this->addColumn($this->lng->txt('size'), 'size'); - $this->addColumn($this->lng->txt('date'), 'timestamp'); - } - - public function numericOrdering(string $a_field): bool - { - if (in_array($a_field, ['size', 'date'])) { - return true; - } - - return false; - } - - protected function getRowId(array $row): string - { - return $row['file']; - } - - public function resetFormats(): void - { - $this->formats = []; - } - - public function addFormat(string $format) - { - $this->formats[$format] = $format; - } -} diff --git a/components/ILIAS/Test/classes/Tables/class.ilTestPassDetailsOverviewTableGUI.php b/components/ILIAS/Test/classes/Tables/class.ilTestPassDetailsOverviewTableGUI.php index 04c27f7a62de..30693d71746e 100755 --- a/components/ILIAS/Test/classes/Tables/class.ilTestPassDetailsOverviewTableGUI.php +++ b/components/ILIAS/Test/classes/Tables/class.ilTestPassDetailsOverviewTableGUI.php @@ -115,10 +115,6 @@ public function initColumns(): void $this->addColumn($this->lng->txt("tst_maximum_points"), '', ''); $this->addColumn($this->lng->txt("tst_reached_points"), '', ''); - if ($this->getShowHintCount()) { - $this->addColumn($this->lng->txt("tst_question_hints_requested_hint_count_header"), '', ''); - } - $this->addColumn($this->lng->txt("tst_percent_solved"), '', ''); if ($this->getShowSuggestedSolution()) { @@ -165,10 +161,6 @@ public function fillRow(array $a_set): void $this->tpl->setVariable('VALUE_LO_OBJECTIVES', strlen($objectives) ? $objectives : ' '); } - if ($this->getShowHintCount()) { - $this->tpl->setVariable('VALUE_HINT_COUNT', (int) $a_set['requested_hints']); - } - if ($this->getShowSuggestedSolution()) { $this->tpl->setVariable('SOLUTION_HINT', $a_set['solution']); } @@ -255,17 +247,6 @@ private function getAnswerListAnchor($questionId): string return "#detailed_answer_block_act_{$this->getActiveId()}_qst_{$questionId}"; } - public function setShowHintCount($showHintCount): void - { - // Has to be called before column initialization - $this->showHintCount = (bool) $showHintCount; - } - - public function getShowHintCount(): bool - { - return $this->showHintCount; - } - public function setShowSuggestedSolution(bool $showSuggestedSolution): void { $this->showSuggestedSolution = $showSuggestedSolution; diff --git a/components/ILIAS/Test/classes/Tables/class.ilTestPassOverviewTableGUI.php b/components/ILIAS/Test/classes/Tables/class.ilTestPassOverviewTableGUI.php index e9afc45f6018..a346fd5369b0 100755 --- a/components/ILIAS/Test/classes/Tables/class.ilTestPassOverviewTableGUI.php +++ b/components/ILIAS/Test/classes/Tables/class.ilTestPassOverviewTableGUI.php @@ -117,11 +117,6 @@ public function fillRow(array $a_set): void $a_set['num_workedthrough_questions'], $a_set['num_questions_total'] )); - - if ($this->getParentObject()->getObject()->isOfferingQuestionHintsEnabled()) { - $this->tpl->setVariable('VAL_HINTS', $a_set['hints']); - } - $this->tpl->setVariable('VAL_REACHED', $this->buildReachedPointsString( $a_set['reached_points'], $a_set['max_points'] @@ -155,9 +150,6 @@ protected function initColumns(): void if ($this->isResultPresentationEnabled()) { $this->addColumn($this->lng->txt('tst_answered_questions')); - if ($this->getParentObject()->getObject()->isOfferingQuestionHintsEnabled()) { - $this->addColumn($this->lng->txt('tst_question_hints_requested_hint_count_header')); - } $this->addColumn($this->lng->txt('tst_reached_points')); $this->addColumn($this->lng->txt('tst_percent_solved')); } diff --git a/components/ILIAS/Test/classes/Toolbars/class.ilTestNavigationToolbarGUI.php b/components/ILIAS/Test/classes/Toolbars/class.ilTestNavigationToolbarGUI.php index 56a1bae4f32d..8d30ba7509d0 100644 --- a/components/ILIAS/Test/classes/Toolbars/class.ilTestNavigationToolbarGUI.php +++ b/components/ILIAS/Test/classes/Toolbars/class.ilTestNavigationToolbarGUI.php @@ -162,7 +162,7 @@ private function addSuspendTestButton() $button = $this->ui->factory()->button()->standard( $this->lng->txt('cancel_test'), '' - )->withAdditionalOnLoadCode( + )->withUnavailableAction(true)->withAdditionalOnLoadCode( $this->buildCheckNavigationClosure( $this->ctrl->getLinkTarget($this->player_gui, ilTestPlayerCommands::SUSPEND_TEST) ) @@ -175,7 +175,7 @@ private function addPassOverviewButton() $button = $this->ui->factory()->button()->standard( $this->lng->txt('question_summary_btn'), '' - )->withAdditionalOnLoadCode( + )->withUnavailableAction(true)->withAdditionalOnLoadCode( $this->buildCheckNavigationClosure( $this->ctrl->getLinkTarget($this->player_gui, ilTestPlayerCommands::QUESTION_SUMMARY) ) @@ -192,7 +192,7 @@ private function retrieveFinishTestButton(): Button } $button = $this->getStandardOrPrimaryFinishButtonInstance(); - return $button->withAdditionalOnLoadCode( + return $button->withUnavailableAction(true)->withAdditionalOnLoadCode( $this->buildCheckNavigationClosure($target) ); } @@ -209,10 +209,11 @@ private function getStandardOrPrimaryFinishButtonInstance(): Button private function buildCheckNavigationClosure(string $target): Closure { return static function (string $id) use ($target): string { - return "document.getElementById('$id').addEventListener('click', " + return "document.getElementById('{$id}').addEventListener('click', " . '(e) => {' . " il.TestPlayerQuestionEditControl.checkNavigation('{$target}', 'show', e);" - . '});'; + . '}); ' + . "document.getElementById('{$id}').removeAttribute('disabled');"; }; } } diff --git a/components/ILIAS/Test/classes/class.ilAssQuestionPageCommandForwarder.php b/components/ILIAS/Test/classes/class.ilAssQuestionPageCommandForwarder.php index 9c3d49f3ee38..e74d5c7cc79e 100755 --- a/components/ILIAS/Test/classes/class.ilAssQuestionPageCommandForwarder.php +++ b/components/ILIAS/Test/classes/class.ilAssQuestionPageCommandForwarder.php @@ -88,9 +88,9 @@ public function forward(): void $page_gui->setQuestionHTML([$q_gui->getObject()->getId() => $q_gui->getPreview(true)]); $page_gui->setTemplateTargetVar("ADM_CONTENT"); $page_gui->setOutputMode($this->test_obj->evalTotalPersons() == 0 ? "edit" : 'preview'); - $page_gui->setHeader($question->getTitle()); + $page_gui->setHeader($question->getTitleForHTMLOutput()); $page_gui->setPresentationTitle( - $question->getTitle() + $question->getTitleForHTMLOutput() . ' [' . $this->lng->txt('question_id_short') . ': ' . $question->getId() . ']' ); diff --git a/components/ILIAS/Test/classes/class.ilCronFinishUnfinishedTestPasses.php b/components/ILIAS/Test/classes/class.ilCronFinishUnfinishedTestPasses.php index 22398c10dce2..1074b50a671c 100755 --- a/components/ILIAS/Test/classes/class.ilCronFinishUnfinishedTestPasses.php +++ b/components/ILIAS/Test/classes/class.ilCronFinishUnfinishedTestPasses.php @@ -176,7 +176,6 @@ protected function processPasses(): void if (!array_key_exists($test_id, $this->test_ending_times)) { continue; } - if (!$this->finishPassOnEndingTime($test_id, $data['active_id']) && !$this->finishPassOnProcessingTime( $test_id, @@ -267,8 +266,11 @@ protected function finishPassForUser($active_id, $obj_id): void $obj_id ); - $pass_finisher = new ilTestPassFinishTasks($test_session, $obj_id, $this->test_pass_result_repository); - $pass_finisher->performFinishTasks($processLocker, StatusOfAttempt::FINISHED_BY_CRONJOB); + (new ilTestPassFinishTasks( + $test_session, + $test, + $this->test_pass_result_repository + ))->performFinishTasks($processLocker, StatusOfAttempt::FINISHED_BY_CRONJOB); $this->logger->info('Test session with active id (' . $active_id . ') and obj_id (' . $obj_id . ') is now finished.'); } else { $this->logger->info('Test object with id (' . $obj_id . ') does not exist.'); diff --git a/components/ILIAS/Test/classes/class.ilObjTest.php b/components/ILIAS/Test/classes/class.ilObjTest.php index 76c34bd7e16f..ba7caaafe67f 100755 --- a/components/ILIAS/Test/classes/class.ilObjTest.php +++ b/components/ILIAS/Test/classes/class.ilObjTest.php @@ -48,7 +48,6 @@ use ILIAS\TestQuestionPool\Questions\GeneralQuestionPropertiesRepository; use ILIAS\Refinery\Factory as Refinery; use ILIAS\Filesystem\Filesystem; -use ILIAS\Filesystem\Stream\Streams; use ILIAS\MetaData\Services\ServicesInterface as LOMetadata; /** @@ -518,12 +517,12 @@ public function saveToDb(bool $properties_only = false): void } } - $this->storeActivationSettings([ - 'is_activation_limited' => $this->isActivationLimited(), - 'activation_starting_time' => $this->getActivationStartingTime(), - 'activation_ending_time' => $this->getActivationEndingTime(), - 'activation_visibility' => $this->getActivationVisibility() - ]); + $this->storeActivationSettings( + $this->isActivationLimited(), + $this->getActivationStartingTime(), + $this->getActivationEndingTime(), + $this->getActivationVisibility(), + ); if ($properties_only) { return; @@ -1051,7 +1050,7 @@ private function removeQuestionWithResults(int $question_id, TestScoring $scorin $this->user->getId(), TestAdministrationInteractionTypes::QUESTION_REMOVED_IN_CORRECTIONS, [ - AdditionalInformationGenerator::KEY_QUESTION_TITLE => $question->getTitle(), + AdditionalInformationGenerator::KEY_QUESTION_TITLE => $question->getTitleForHTMLOutput(), AdditionalInformationGenerator::KEY_QUESTION_TEXT => $question->getQuestion(), AdditionalInformationGenerator::KEY_QUESTION_ID => $question->getId(), AdditionalInformationGenerator::KEY_QUESTION_TYPE => $question->getQuestionType() @@ -1105,7 +1104,7 @@ public function removeQuestion(int $question_id): void { try { $question = self::_instanciateQuestion($question_id); - $question_title = $question->getTitle(); + $question_title = $question->getTitleForHTMLOutput(); $question->delete($question_id); if ($this->logger->isLoggingEnabled()) { $this->logger->logTestAdministrationInteraction( @@ -1250,8 +1249,6 @@ private function removeTestResultsByActiveIds(array $active_ids): void ilFileUtils::delDir(CLIENT_WEB_DIR . "/assessment/tst_" . $this->getTestId() . "/$active_id"); } } - - ilAssQuestionHintTracking::deleteRequestsByActiveIds($active_ids); } /** @@ -1719,7 +1716,7 @@ public function getActiveIdOfUser($user_id = "", $anonymous_id = ""): ?int [$user_id, $this->test_id, $anonymous_id] ); } else { - if ($this->user->getId() === ANONYMOUS_USER_ID) { + if ((int) $user_id === ANONYMOUS_USER_ID) { return null; } $result = $this->db->queryF( @@ -1813,14 +1810,12 @@ public function getTestResult( $sequence = $test_sequence->getUserSequenceQuestions(); } - $arrResults = []; + $arr_results = []; $query = " SELECT tst_test_result.question_fi, tst_test_result.points reached, - tst_test_result.hint_count requested_hints, - tst_test_result.hint_points hint_points, tst_test_result.answered answered, tst_manual_fb.finalized_evaluation finalized_evaluation @@ -1845,10 +1840,10 @@ public function getTestResult( ); while ($row = $this->db->fetchAssoc($solutionresult)) { - $arrResults[ $row['question_fi'] ] = $row; + $arr_results[ $row['question_fi'] ] = $row; } - $numWorkedThrough = count($arrResults); + $num_worked_through = count($arr_results); $IN_question_ids = $this->db->in('qpl_questions.question_id', $sequence, false, 'integer'); @@ -1871,11 +1866,11 @@ public function getTestResult( $unordered = []; $key = 1; while ($row = $this->db->fetchAssoc($result)) { - if (!isset($arrResults[ $row['question_id'] ])) { + if (!isset($arr_results[ $row['question_id'] ])) { $percentvalue = 0.0; } else { $percentvalue = ( - $row['points'] ? $arrResults[$row['question_id']]['reached'] / $row['points'] : 0 + $row['points'] ? $arr_results[$row['question_id']]['reached'] / $row['points'] : 0 ); } if ($percentvalue < 0) { @@ -1886,17 +1881,15 @@ public function getTestResult( "nr" => "$key", "title" => ilLegacyFormElementsUtil::prepareFormOutput($row['title']), "max" => round($row['points'], 2), - "reached" => round($arrResults[$row['question_id']]['reached'] ?? 0, 2), - 'requested_hints' => $arrResults[$row['question_id']]['requested_hints'] ?? 0, - 'hint_points' => $arrResults[$row['question_id']]['hint_points'] ?? 0, + "reached" => round($arr_results[$row['question_id']]['reached'] ?? 0, 2), "percent" => sprintf("%2.2f ", ($percentvalue) * 100) . "%", "solution" => ($row['has_sug_sol']) ? assQuestion::_getSuggestedSolutionOutput($row['question_id']) : '', "type" => $row["type_tag"], "qid" => $row['question_id'], "original_id" => $row["original_id"], - "workedthrough" => isset($arrResults[$row['question_id']]) ? 1 : 0, - 'answered' => $arrResults[$row['question_id']]['answered'] ?? 0, - 'finalized_evaluation' => $arrResults[$row['question_id']]['finalized_evaluation'] ?? 0, + "workedthrough" => isset($arr_results[$row['question_id']]) ? 1 : 0, + 'answered' => $arr_results[$row['question_id']]['answered'] ?? 0, + 'finalized_evaluation' => $arr_results[$row['question_id']]['finalized_evaluation'] ?? 0, ]; $unordered[ $row['question_id'] ] = $data; @@ -1907,8 +1900,6 @@ public function getTestResult( $pass_max = 0; $pass_reached = 0; - $pass_requested_hints = 0; - $pass_hint_points = 0; $found = []; @@ -1917,8 +1908,6 @@ public function getTestResult( // for question that exists in users qst sequence $pass_max += round($unordered[$qid]['max'], 2); $pass_reached += round($unordered[$qid]['reached'], 2); - $pass_requested_hints += $unordered[$qid]['requested_hints']; - $pass_hint_points += $unordered[$qid]['hint_points']; $found[] = $unordered[$qid]; } @@ -1936,16 +1925,12 @@ public function getTestResult( $found['pass']['total_max_points'] = $pass_max; $found['pass']['total_reached_points'] = $pass_reached; - $found['pass']['total_requested_hints'] = $pass_requested_hints; - $found['pass']['total_hint_points'] = $pass_hint_points; $found['pass']['percent'] = ($pass_max > 0) ? $pass_reached / $pass_max : 0; - $found['pass']['num_workedthrough'] = $numWorkedThrough; + $found['pass']['num_workedthrough'] = $num_worked_through; $found['pass']['num_questions_total'] = $numQuestionsTotal; $found["test"]["total_max_points"] = $results['max_points']; $found["test"]["total_reached_points"] = $results['reached_points']; - $found["test"]["total_requested_hints"] = $results['hint_count']; - $found["test"]["total_hint_points"] = $results['hint_points']; $found["test"]["result_pass"] = $results['pass']; $found['test']['result_tstamp'] = $results['tstamp']; @@ -2891,7 +2876,7 @@ public function getAvailableQuestions($arr_filter, $completeonly = 0): array * Receives parameters from a QTI parser and creates a valid ILIAS test object * @param ilQTIAssessment $assessment */ - public function fromXML(ilQTIAssessment $assessment) + public function fromXML(ilQTIAssessment $assessment, array $mappings): void { if (($importdir = ilSession::get('path_to_container_import_file')) === null) { $importdir = $this->buildImportDirectoryFromImportFile(ilSession::get('path_to_import_file')); @@ -2917,7 +2902,8 @@ public function fromXML(ilQTIAssessment $assessment) $introduction_settings = $this->addIntroductionToSettingsFromImport( $introduction_settings, $this->qtiMaterialToArray($material), - $importdir + $importdir, + $mappings ); } } @@ -2930,7 +2916,8 @@ public function fromXML(ilQTIAssessment $assessment) $this->qtiMaterialToArray( $assessment->getPresentationMaterial()->getFlowMat(0)->getMaterial(0) ), - $importdir + $importdir, + $mappings ); } @@ -2969,57 +2956,54 @@ public function fromXML(ilQTIAssessment $assessment) break; case 'show_introduction': $introduction_settings = $introduction_settings->withIntroductionEnabled((bool) $metadata['entry']); - // no break + break; case "showfinalstatement": case 'show_concluding_remarks': $finishing_settings = $finishing_settings->withConcludingRemarksEnabled((bool) $metadata["entry"]); break; + case 'exam_conditions': + $introduction_settings = $introduction_settings->withExamConditionsCheckboxEnabled($metadata['entry'] === '1'); + break; case "highscore_enabled": $gamification_settings = $gamification_settings->withHighscoreEnabled((bool) $metadata["entry"]); break; - case "highscore_anon": $gamification_settings = $gamification_settings->withHighscoreAnon((bool) $metadata["entry"]); break; - case "highscore_achieved_ts": $gamification_settings = $gamification_settings->withHighscoreAchievedTS((bool) $metadata["entry"]); break; - case "highscore_score": $gamification_settings = $gamification_settings->withHighscoreScore((bool) $metadata["entry"]); break; - case "highscore_percentage": $gamification_settings = $gamification_settings->withHighscorePercentage((bool) $metadata["entry"]); break; - - case "highscore_hints": - $gamification_settings = $gamification_settings->withHighscoreHints((bool) $metadata["entry"]); - break; - case "highscore_wtime": $gamification_settings = $gamification_settings->withHighscoreWTime((bool) $metadata["entry"]); break; - case "highscore_own_table": $gamification_settings = $gamification_settings->withHighscoreOwnTable((bool) $metadata["entry"]); break; - case "highscore_top_table": $gamification_settings = $gamification_settings->withHighscoreTopTable((bool) $metadata["entry"]); break; - case "highscore_top_num": $gamification_settings = $gamification_settings->withHighscoreTopNum((int) $metadata["entry"]); break; case "use_previous_answers": $participant_functionality_settings = $participant_functionality_settings->withUsePreviousAnswerAllowed((bool) $metadata["entry"]); break; + case 'question_list_enabled': + $participant_functionality_settings = $participant_functionality_settings->withQuestionListEnabled((bool) $metadata['entry']); + // no break case "title_output": $question_behaviour_settings = $question_behaviour_settings->withQuestionTitleOutputMode((int) $metadata["entry"]); break; case "question_set_type": + if ($metadata['entry'] === self::QUESTION_SET_TYPE_RANDOM) { + $this->questions = []; + } $general_settings = $general_settings->withQuestionSetType($metadata["entry"]); break; case "anonymity": @@ -3029,7 +3013,7 @@ public function fromXML(ilQTIAssessment $assessment) $result_details_settings = $result_details_settings->withResultsPresentation((int) $metadata["entry"]); break; case "reset_processing_time": - $test_behaviour_settings = $test_behaviour_settings->withResetProcessingTime((bool) $metadata["entry"]); + $test_behaviour_settings = $test_behaviour_settings->withResetProcessingTime($metadata["entry"] === '1'); break; case "answer_feedback_points": $question_behaviour_settings = $question_behaviour_settings->withInstantFeedbackPointsEnabled((bool) $metadata["entry"]); @@ -3186,12 +3170,13 @@ public function fromXML(ilQTIAssessment $assessment) case 'autosave_ival': $question_behaviour_settings = $question_behaviour_settings->withAutosaveInterval((int) $metadata['entry']); break; - case 'offer_question_hints': - $question_behaviour_settings = $question_behaviour_settings->withQuestionHintsEnabled((bool) $metadata['entry']); - break; case 'show_summary': $participant_functionality_settings = $participant_functionality_settings->withQuestionListEnabled(($metadata['entry'] & 1) > 0) ->withUsrPassOverviewMode((int) $metadata['entry']); + + // no break + case 'hide_info_tab': + $additional_settings = $additional_settings->withHideInfoTab($metadata['entry'] === '1'); } if (preg_match("/mark_step_\d+/", $metadata["label"])) { $xmlmark = $metadata["entry"]; @@ -3239,13 +3224,20 @@ public function fromXML(ilQTIAssessment $assessment) private function addIntroductionToSettingsFromImport( SettingsIntroduction $settings, array $material, - string $importdir + string $importdir, + array $mappings ): SettingsIntroduction { $text = $material['text']; $mobs = $material['mobs']; if (str_starts_with($text, '')) { - $text = $this->retrieveMobsFromPageImports($text, $mobs, $importdir); - $text = $this->retrieveFilesFromPageImports($text, $importdir); + $text = $this->replaceMobsInPageImports( + $text, + $mappings['components/ILIAS/MediaObjects']['mob'] ?? [] + ); + $text = $this->replaceFilesInPageImports( + $text, + $mappings['components/ILIAS/File']['file'] ?? [] + ); $page_object = new ilTestPage(); $page_object->setParentId($this->getId()); $page_object->setXMLContent($text); @@ -3265,14 +3257,21 @@ private function addIntroductionToSettingsFromImport( private function addConcludingRemarksToSettingsFromImport( SettingsFinishing $settings, array $material, - string $importdir + string $importdir, + array $mappings ): SettingsFinishing { $file_to_import = ilSession::get('path_to_import_file'); $text = $material['text']; $mobs = $material['mobs']; if (str_starts_with($text, '')) { - $text = $this->retrieveMobsFromPageImports($text, $mobs, $importdir); - $text = $this->retrieveFilesFromPageImports($text, $importdir); + $text = $this->replaceMobsInPageImports( + $text, + $mappings['components/ILIAS/MediaObjects']['mob'] ?? [] + ); + $text = $this->replaceFilesInPageImports( + $text, + $mappings['components/ILIAS/File']['file'] ?? [] + ); $page_object = new ilTestPage(); $page_object->setParentId($this->getId()); $page_object->setXMLContent($text); @@ -3295,33 +3294,27 @@ private function addConcludingRemarksToSettingsFromImport( ); } - private function retrieveMobsFromPageImports(string $text, array $mobs, string $importdir): string + private function replaceMobsInPageImports(string $text, array $mappings): string { - foreach ($mobs as $mob) { - $importfile = $importdir . DIRECTORY_SEPARATOR . $mob['uri']; - if (file_exists($importfile)) { - $media_object = ilObjMediaObject::_saveTempFileAsMediaObject(basename($importfile), $importfile, false); - ilObjMediaObject::_saveUsage($media_object->getId(), 'tst:gp', $this->getId()); - $text = str_replace($mob['mob'], 'il__mob_' . (string) $media_object->getId(), $text); + preg_match_all('/il_(\d+)_mob_(\d+)/', $text, $matches); + foreach ($matches[0] as $index => $match) { + if (empty($mappings[$matches[2][$index]])) { + continue; } + $text = str_replace($match, "il__mob_{$mappings[$matches[2][$index]]}", $text); + ilObjMediaObject::_saveUsage((int) $mappings[$matches[2][$index]], 'tst', $this->getId()); } return $text; } - private function retrieveFilesFromPageImports(string $text, string $importdir): string + private function replaceFilesInPageImports(string $text, array $mappings): string { preg_match_all('/il_(\d+)_file_(\d+)/', $text, $matches); - foreach ($matches[0] as $match) { - $source_dir = $importdir . DIRECTORY_SEPARATOR . 'objects' . DIRECTORY_SEPARATOR . $match; - $files = scandir($source_dir, SCANDIR_SORT_DESCENDING); - if ($files !== false && $files !== [] && is_file($source_dir . '/' . $files[0])) { - $file = fopen($source_dir . '/' . $files[0], 'rb'); - $file_stream = Streams::ofResource($file); - $file_obj = new ilObjFile(); - $file_id = $file_obj->create(); - $file_obj->appendStream($file_stream, $files[0]); - $text = str_replace($match, "il__file_{$file_id}", $text); + foreach ($matches[0] as $index => $match) { + if (empty($mappings[$matches[2][$index]])) { + continue; } + $text = str_replace($match, "il__file_{$mappings[$matches[2][$index]]}", $text); } return $text; } @@ -3403,7 +3396,7 @@ public function toXML(): string $a_xml_writer->xmlStartTag("qtimetadatafield"); $a_xml_writer->xmlElement("fieldlabel", null, "reset_processing_time"); - $a_xml_writer->xmlElement("fieldentry", null, $main_settings->getTestBehaviourSettings()->getResetProcessingTime()); + $a_xml_writer->xmlElement("fieldentry", null, (int) $main_settings->getTestBehaviourSettings()->getResetProcessingTime()); $a_xml_writer->xmlEndTag("qtimetadatafield"); $a_xml_writer->xmlStartTag("qtimetadatafield"); @@ -3444,10 +3437,13 @@ public function toXML(): string if ($this->getScoreSettings()->getResultSummarySettings()->getReportingDate() !== null) { $a_xml_writer->xmlStartTag("qtimetadatafield"); $a_xml_writer->xmlElement("fieldlabel", null, "reporting_date"); - $reporting_date = $this->buildPeriodFromFormatedDateString( - $this->getScoreSettings()->getResultSummarySettings()->getReportingDate()->format('Y-m-d H:i:s') + $a_xml_writer->xmlElement( + "fieldentry", + null, + $this->buildIso8601PeriodForExportCompatibility( + $this->getScoreSettings()->getResultSummarySettings()->getReportingDate(), + ), ); - $a_xml_writer->xmlElement("fieldentry", null, $reporting_date); $a_xml_writer->xmlEndTag("qtimetadatafield"); } @@ -3486,6 +3482,11 @@ public function toXML(): string $a_xml_writer->xmlElement("fieldentry", null, (int) $main_settings->getParticipantFunctionalitySettings()->getUsePreviousAnswerAllowed()); $a_xml_writer->xmlEndTag("qtimetadatafield"); + $a_xml_writer->xmlStartTag('qtimetadatafield'); + $a_xml_writer->xmlElement('fieldlabel', null, 'question_list_enabled'); + $a_xml_writer->xmlElement('fieldentry', null, (int) $main_settings->getParticipantFunctionalitySettings()->getQuestionListEnabled()); + $a_xml_writer->xmlEndTag('qtimetadatafield'); + $a_xml_writer->xmlStartTag("qtimetadatafield"); $a_xml_writer->xmlElement("fieldlabel", null, "title_output"); $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $main_settings->getQuestionBehaviourSettings()->getQuestionTitleOutputMode())); @@ -3562,7 +3563,6 @@ public function toXML(): string 'highscore_achieved_ts' => ['value' => $this->getHighscoreAchievedTS()], 'highscore_score' => ['value' => $this->getHighscoreScore()], 'highscore_percentage' => ['value' => $this->getHighscorePercentage()], - 'highscore_hints' => ['value' => $this->getHighscoreHints()], 'highscore_wtime' => ['value' => $this->getHighscoreWTime()], 'highscore_own_table' => ['value' => $this->getHighscoreOwnTable()], 'highscore_top_table' => ['value' => $this->getHighscoreTopTable()], @@ -3595,6 +3595,11 @@ public function toXML(): string $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", (int) $main_settings->getIntroductionSettings()->getIntroductionEnabled())); $a_xml_writer->xmlEndTag("qtimetadatafield"); + $a_xml_writer->xmlStartTag("qtimetadatafield"); + $a_xml_writer->xmlElement("fieldlabel", null, 'exam_conditions'); + $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", (int) $main_settings->getIntroductionSettings()->getExamConditionsCheckboxEnabled())); + $a_xml_writer->xmlEndTag("qtimetadatafield"); + $a_xml_writer->xmlStartTag("qtimetadatafield"); $a_xml_writer->xmlElement("fieldlabel", null, "show_concluding_remarks"); $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", (int) $main_settings->getFinishingSettings()->getConcludingRemarksEnabled())); @@ -3654,19 +3659,34 @@ public function toXML(): string $a_xml_writer->xmlElement("fieldentry", null, (int) $this->isShowGradingMarkEnabled()); $a_xml_writer->xmlEndTag("qtimetadatafield"); - if ($this->getStartingTime()) { + $a_xml_writer->xmlStartTag('qtimetadatafield'); + $a_xml_writer->xmlElement('fieldlabel', null, 'hide_info_tab'); + $a_xml_writer->xmlElement('fieldentry', null, (int) $this->getMainSettings()->getAdditionalSettings()->getHideInfoTab()); + $a_xml_writer->xmlEndTag("qtimetadatafield"); + + if ($this->getStartingTime() > 0) { $a_xml_writer->xmlStartTag("qtimetadatafield"); $a_xml_writer->xmlElement("fieldlabel", null, "starting_time"); - $backward_compatibility_format = $this->buildIso8601PeriodFromUnixtimeForExportCompatibility($this->getStartingTime()); - $a_xml_writer->xmlElement("fieldentry", null, $backward_compatibility_format); + $a_xml_writer->xmlElement( + "fieldentry", + null, + $this->buildIso8601PeriodForExportCompatibility( + (new DateTimeImmutable())->setTimestamp($this->getStartingTime()), + ), + ); $a_xml_writer->xmlEndTag("qtimetadatafield"); } - if ($this->getEndingTime()) { + if ($this->getEndingTime() > 0) { $a_xml_writer->xmlStartTag("qtimetadatafield"); $a_xml_writer->xmlElement("fieldlabel", null, "ending_time"); - $backward_compatibility_format = $this->buildIso8601PeriodFromUnixtimeForExportCompatibility($this->getEndingTime()); - $a_xml_writer->xmlElement("fieldentry", null, $backward_compatibility_format); + $a_xml_writer->xmlElement( + "fieldentry", + null, + $this->buildIso8601PeriodForExportCompatibility( + (new DateTimeImmutable())->setTimestamp($this->getEndingTime()), + ), + ); $a_xml_writer->xmlEndTag("qtimetadatafield"); } @@ -3700,11 +3720,6 @@ public function toXML(): string $a_xml_writer->xmlElement("fieldentry", null, $main_settings->getQuestionBehaviourSettings()->getAutosaveInterval()); $a_xml_writer->xmlEndTag("qtimetadatafield"); - $a_xml_writer->xmlStartTag("qtimetadatafield"); - $a_xml_writer->xmlElement("fieldlabel", null, "offer_question_hints"); - $a_xml_writer->xmlElement("fieldentry", null, (int) $main_settings->getQuestionBehaviourSettings()->getQuestionHintsEnabled()); - $a_xml_writer->xmlEndTag("qtimetadatafield"); - $a_xml_writer->xmlStartTag("qtimetadatafield"); $a_xml_writer->xmlElement("fieldlabel", null, "instant_feedback_specific"); $a_xml_writer->xmlElement("fieldentry", null, (int) $main_settings->getQuestionBehaviourSettings()->getInstantFeedbackSpecificEnabled()); @@ -3776,17 +3791,9 @@ public function toXML(): string return $xml; } - protected function buildIso8601PeriodFromUnixtimeForExportCompatibility(int $unix_timestamp): string - { - $date_time_unix = new ilDateTime($unix_timestamp, IL_CAL_UNIX); - $date_time = $date_time_unix->get(IL_CAL_DATETIME); - return $this->buildPeriodFromFormatedDateString($date_time); - } - - protected function buildPeriodFromFormatedDateString(string $date_time): string + protected function buildIso8601PeriodForExportCompatibility(DateTimeImmutable $date_time): string { - preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $date_time, $matches); - return sprintf("P%dY%dM%dDT%dH%dM%dS", $matches[1], $matches[2], $matches[3], $matches[4], $matches[5], $matches[6]); + return $date_time->setTimezone(new DateTimeZone('UTC'))->format('\PY\Yn\Mj\D\TG\Hi\Ms\S'); } protected function buildDateTimeImmutableFromPeriod(?string $period): ?DateTimeImmutable @@ -3920,9 +3927,16 @@ public function exportXMLMediaObjects(&$a_xml_writer, $a_inst, $a_target_dir, &$ foreach ($this->mob_ids as $mob_id) { $expLog->write(date("[y-m-d H:i:s] ") . "Media Object " . $mob_id); if (ilObjMediaObject::_exists((int) $mob_id)) { + $target_dir = $a_target_dir . DIRECTORY_SEPARATOR . 'objects' + . DIRECTORY_SEPARATOR . 'il_' . IL_INST_ID . '_mob_' . $mob_id; + ilFileUtils::createDirectory($target_dir); $media_obj = new ilObjMediaObject((int) $mob_id); $media_obj->exportXML($a_xml_writer, (int) $a_inst); - $media_obj->exportFiles($a_target_dir); + foreach ($media_obj->getMediaItems() as $item) { + $stream = $item->getLocationStream(); + file_put_contents($target_dir . DIRECTORY_SEPARATOR . $item->getLocation(), $stream); + $stream->close(); + } unset($media_obj); } } @@ -4953,6 +4967,12 @@ public function isExecutable($test_session, $user_id, $allow_pass_increase = fal "errormessage" => "" ]; + if (!$this->getObjectProperties()->getPropertyIsOnline()->getIsOnline()) { + $result["executable"] = false; + $result["errormessage"] = $this->lng->txt('autosave_failed') . ': ' . $this->lng->txt('offline'); + return $result; + } + if (!$this->startingTimeReached()) { $result["executable"] = false; $result["errormessage"] = sprintf($this->lng->txt("detail_starting_time_not_reached"), ilDatePresentation::formatDate(new ilDateTime($this->getStartingTime(), IL_CAL_UNIX))); @@ -4970,22 +4990,8 @@ public function isExecutable($test_session, $user_id, $allow_pass_increase = fal && $active_id > 0 && ($starting_time = $this->getStartingTimeOfUser($active_id)) !== false && $this->isMaxProcessingTimeReached($starting_time, $active_id)) { - if ($allow_pass_increase - && $this->getResetProcessingTime() - && (($this->getNrOfTries() === 0) - || ($this->getNrOfTries() > (self::_getPass($active_id) + 1)))) { - // a test pass was quitted because the maximum processing time was reached, but the time - // will be resetted for future passes, so if there are more passes allowed, the participant may - // start the test again. - // This code block is only called when $allowPassIncrease is TRUE which only happens when - // the test info page is opened. Otherwise this will lead to unexpected results! - $test_session->increasePass(); - $test_session->setLastSequence(0); - $test_session->saveToDb(); - } else { - $result["executable"] = false; - $result["errormessage"] = $this->lng->txt("detail_max_processing_time_reached"); - } + $result["executable"] = false; + $result["errormessage"] = $this->lng->txt("detail_max_processing_time_reached"); return $result; } @@ -5043,22 +5049,22 @@ public function isNextPassAllowed(ilTestPassesSelector $testPassesSelector, int public function canShowTestResults(ilTestSession $test_session): bool { - $passSelector = new ilTestPassesSelector($this->db, $this); + $pass_selector = new ilTestPassesSelector($this->db, $this); - $passSelector->setActiveId($test_session->getActiveId()); - $passSelector->setLastFinishedPass($test_session->getLastFinishedPass()); + $pass_selector->setActiveId($test_session->getActiveId()); + $pass_selector->setLastFinishedPass($test_session->getLastFinishedPass()); - return $passSelector->hasReportablePasses(); + return $pass_selector->hasReportablePasses(); } public function hasAnyTestResult(ilTestSession $test_session): bool { - $passSelector = new ilTestPassesSelector($this->db, $this); + $pass_selector = new ilTestPassesSelector($this->db, $this); - $passSelector->setActiveId($test_session->getActiveId()); - $passSelector->setLastFinishedPass($test_session->getLastFinishedPass()); + $pass_selector->setActiveId($test_session->getActiveId()); + $pass_selector->setLastFinishedPass($test_session->getLastFinishedPass()); - return $passSelector->hasExistingPasses(); + return $pass_selector->hasExistingPasses(); } /** @@ -5694,7 +5700,6 @@ public function addDefaults($a_name) 'autosave' => (int) $main_settings->getQuestionBehaviourSettings()->getAutosaveEnabled(), 'autosave_ival' => $main_settings->getQuestionBehaviourSettings()->getAutosaveInterval(), 'Shuffle' => (int) $main_settings->getQuestionBehaviourSettings()->getShuffleQuestions(), - 'offer_question_hints' => (int) $main_settings->getQuestionBehaviourSettings()->getQuestionHintsEnabled(), 'AnswerFeedbackPoints' => (int) $main_settings->getQuestionBehaviourSettings()->getInstantFeedbackPointsEnabled(), 'AnswerFeedback' => (int) $main_settings->getQuestionBehaviourSettings()->getInstantFeedbackGenericEnabled(), 'SpecificAnswerFeedback' => (int) $main_settings->getQuestionBehaviourSettings()->getInstantFeedbackSpecificEnabled(), @@ -5737,7 +5742,6 @@ public function addDefaults($a_name) 'highscore_achieved_ts' => $score_settings->getGamificationSettings()->getHighscoreAchievedTS(), 'highscore_score' => $score_settings->getGamificationSettings()->getHighscoreScore(), 'highscore_percentage' => $score_settings->getGamificationSettings()->getHighscorePercentage(), - 'highscore_hints' => $score_settings->getGamificationSettings()->getHighscoreHints(), 'highscore_wtime' => $score_settings->getGamificationSettings()->getHighscoreWTime(), 'highscore_own_table' => $score_settings->getGamificationSettings()->getHighscoreOwnTable(), 'highscore_top_table' => $score_settings->getGamificationSettings()->getHighscoreTopTable(), @@ -5773,6 +5777,12 @@ public function addDefaults($a_name) public function applyDefaults(array $test_defaults): string { $testsettings = unserialize($test_defaults['defaults'], ['allowed_classes' => [DateTimeImmutable::class]]); + $activation_starting_time = is_numeric($testsettings['activation_starting_time'] ?? false) + ? (int) $testsettings['activation_starting_time'] + : null; + $activation_ending_time = is_numeric($testsettings['activation_ending_time'] ?? false) + ? (int) $testsettings['activation_ending_time'] + : null; $unserialized_marks = json_decode($test_defaults['marks'], true); $info = ''; @@ -5793,20 +5803,24 @@ public function applyDefaults(array $test_defaults): string $info = 'old_mark_default_not_applied'; } - $this->storeActivationSettings([ - 'is_activation_limited' => $testsettings['activation_limited'], - 'activation_starting_time' => $testsettings['activation_start_time'], - 'activation_ending_time' => $testsettings['activation_end_time'], - 'activation_visibility' => $testsettings['activation_visibility'] - ]); + + $this->storeActivationSettings( + (bool) ($testsettings['is_activation_limited'] ?? false), + $activation_starting_time, + $activation_ending_time, + (bool) ($testsettings['activation_visibility'] ?? false), + ); $main_settings = $this->getMainSettings(); + + $general_settings = $main_settings->getGeneralSettings() + ->withAnonymity((bool) $testsettings['Anonymity']); + if (isset($testsettings['questionSetType'])) { + $general_settings = $general_settings->withQuestionSetType($testsettings['questionSetType']); + } + $main_settings = $main_settings - ->withGeneralSettings( - $main_settings->getGeneralSettings() - ->withQuestionSetType($testsettings['questionSetType']) - ->withAnonymity((bool) $testsettings['Anonymity']) - ) + ->withGeneralSettings($general_settings) ->withIntroductionSettings( $main_settings->getIntroductionSettings() ->withIntroductionEnabled((bool) $testsettings['IntroEnabled']) @@ -5824,7 +5838,7 @@ public function applyDefaults(array $test_defaults): string ) ->withTestBehaviourSettings( $main_settings->getTestBehaviourSettings() - ->withNumberOfTries($testsettings['NrOfTries']) + ->withNumberOfTries((int) $testsettings['NrOfTries']) ->withBlockAfterPassedEnabled((bool) $testsettings['BlockAfterPassed']) ->withPassWaiting($testsettings['pass_waiting']) ->withKioskMode($testsettings['Kiosk']) @@ -5839,7 +5853,6 @@ public function applyDefaults(array $test_defaults): string ->withAutosaveEnabled((bool) $testsettings['autosave']) ->withAutosaveInterval($testsettings['autosave_ival']) ->withShuffleQuestions((bool) $testsettings['Shuffle']) - ->withQuestionHintsEnabled((bool) $testsettings['offer_question_hints']) ->withInstantFeedbackPointsEnabled((bool) $testsettings['AnswerFeedbackPoints']) ->withInstantFeedbackGenericEnabled((bool) $testsettings['AnswerFeedback']) ->withInstantFeedbackSpecificEnabled((bool) $testsettings['SpecificAnswerFeedback']) @@ -5913,7 +5926,6 @@ public function applyDefaults(array $test_defaults): string ->withHighscoreAchievedTS($testsettings['highscore_achieved_ts']) ->withHighscoreScore((bool) $testsettings['highscore_score']) ->withHighscorePercentage($testsettings['highscore_percentage']) - ->withHighscoreHints((bool) $testsettings['highscore_hints']) ->withHighscoreWTime((bool) $testsettings['highscore_wtime']) ->withHighscoreOwnTable((bool) $testsettings['highscore_own_table']) ->withHighscoreTopTable((bool) $testsettings['highscore_top_table']) @@ -6484,8 +6496,6 @@ public function sendAdvancedNotification(int $active_id): void $this, ExportImportTypes::SCORED_ATTEMPT )->withFilterByActiveId($active_id) - ->withResultsPage() - ->withUserPages() ->write(); $delivered_file_name = 'result_' . $active_id . '.xlsx'; @@ -6665,11 +6675,6 @@ public function isOnline(): bool return $this->online; } - public function isOfferingQuestionHintsEnabled(): bool - { - return $this->getMainSettings()->getQuestionBehaviourSettings()->getQuestionHintsEnabled(); - } - public function setActivationVisibility($a_value) { $this->activation_visibility = (bool) $a_value; @@ -6690,28 +6695,34 @@ public function setActivationLimited($a_value) $this->activation_limited = (bool) $a_value; } - public function storeActivationSettings(array $settings): void - { + public function storeActivationSettings( + ?bool $is_activation_limited = false, + ?int $activation_starting_time = null, + ?int $activation_ending_time = null, + bool $activation_visibility = false, + ): void { if (!$this->ref_id) { return; } $item = new ilObjectActivation(); - if (!$settings['is_activation_limited']) { + $is_activation_limited ??= false; + + if (!$is_activation_limited) { $item->setTimingType(ilObjectActivation::TIMINGS_DEACTIVATED); } else { $item->setTimingType(ilObjectActivation::TIMINGS_ACTIVATION); - $item->setTimingStart($settings['activation_starting_time']); - $item->setTimingEnd($settings['activation_ending_time']); - $item->toggleVisible($settings['activation_visibility']); + $item->setTimingStart($activation_starting_time); + $item->setTimingEnd($activation_ending_time); + $item->toggleVisible($activation_visibility); } $item->update($this->ref_id); - $this->setActivationLimited($settings['is_activation_limited']); - $this->setActivationStartingTime($settings['activation_starting_time']); - $this->setActivationStartingTime($settings['activation_ending_time']); - $this->setActivationVisibility($settings['activation_visibility']); + $this->setActivationLimited($is_activation_limited); + $this->setActivationStartingTime($activation_starting_time); + $this->setActivationStartingTime($activation_ending_time); + $this->setActivationVisibility($activation_visibility); } public function getIntroductionPageId(): int @@ -6806,14 +6817,6 @@ public function getHighscorePercentage(): bool return $this->getScoreSettings()->getGamificationSettings()->getHighscorePercentage(); } - /** - * Gets, if the column with the number of requested hints should be shown. - */ - public function getHighscoreHints(): bool - { - return $this->getScoreSettings()->getGamificationSettings()->getHighscoreHints(); - } - /** * Gets if the column with the workingtime should be shown. */ @@ -7056,7 +7059,6 @@ public function recalculateScores($preserve_manscoring = false) $scoring = new TestScoring($this, $this->user, $this->db, $this->lng); $scoring->setPreserveManualScores($preserve_manscoring); $scoring->recalculateSolutions(); - ilLPStatusWrapper::_updateStatus($this->getId(), $this->user->getId()); } public static function getTestObjIdsWithActiveForUserId($userId): array @@ -7318,8 +7320,11 @@ public function updateTestResultCache(int $active_id, ?ilAssQuestionProcessLocke if ($pass !== null) { $query = ' - SELECT tst_pass_result.* + SELECT tst_pass_result.*, + tst_active.last_finished_pass FROM tst_pass_result + INNER JOIN tst_active + on tst_pass_result.active_fi = tst_active.active_id WHERE active_fi = %s AND pass = %s '; @@ -7340,12 +7345,11 @@ public function updateTestResultCache(int $active_id, ?ilAssQuestionProcessLocke $percentage = ($max <= 0.0 || $reached <= 0.0) ? 0 : ($reached / $max) * 100.0; $mark = $this->getMarkSchema()->getMatchingMark($percentage); - $is_passed = (bool) $mark->getPassed(); - - $hint_count = $test_pass_result_row['hint_count'] ?? 0; - $hint_points = $test_pass_result_row['hint_points'] ?? 0.0; + $is_passed = $test_pass_result_row['last_finished_pass'] !== null + && $pass <= $test_pass_result_row['last_finished_pass'] + && $mark->getPassed(); - $user_test_result_update_callback = function () use ($active_id, $pass, $max, $reached, $is_passed, $hint_count, $hint_points, $mark) { + $user_test_result_update_callback = function () use ($active_id, $pass, $max, $reached, $is_passed, $mark) { $passed_once_before = 0; $query = 'SELECT passed_once FROM tst_result_cache WHERE active_fi = %s'; $res = $this->db->queryF($query, ['integer'], [$active_id]); @@ -7387,9 +7391,7 @@ public function updateTestResultCache(int $active_id, ?ilAssQuestionProcessLocke 'passed_once' => ['integer', $passed_once], 'passed' => ['integer', (int) $is_passed], 'failed' => ['integer', (int) !$is_passed], - 'tstamp' => ['integer', time()], - 'hint_count' => ['integer', $hint_count], - 'hint_points' => ['float', $hint_points] + 'tstamp' => ['integer', time()] ] ); }; @@ -7414,8 +7416,6 @@ public function updateTestPassResults( $result = $this->db->queryF( ' SELECT SUM(points) reachedpoints, - SUM(hint_count) hint_count, - SUM(hint_points) hint_points, COUNT(DISTINCT(question_fi)) answeredquestions FROM tst_test_result WHERE active_fi = %s @@ -7432,12 +7432,6 @@ public function updateTestPassResults( || $row['reachedpoints'] < 0.0) { $row['reachedpoints'] = 0.0; } - if ($row['hint_count'] === null) { - $row['hint_count'] = 0; - } - if ($row['hint_points'] === null) { - $row['hint_points'] = 0.0; - } $exam_identifier = ilObjTest::buildExamId($active_id, $pass, $test_obj_id); @@ -7455,8 +7449,6 @@ public function updateTestPassResults( 'answeredquestions' => ['integer', $row['answeredquestions']], 'workingtime' => ['integer', $time], 'tstamp' => ['integer', time()], - 'hint_count' => ['integer', $row['hint_count']], - 'hint_points' => ['float', $row['hint_points']], 'exam_id' => ['text', $exam_identifier] ] ); @@ -7480,8 +7472,6 @@ public function updateTestPassResults( 'answeredquestions' => $row['answeredquestions'], 'workingtime' => $time, 'tstamp' => time(), - 'hint_count' => $row['hint_count'], - 'hint_points' => $row['hint_points'], 'exam_id' => $exam_identifier ]; } diff --git a/components/ILIAS/Test/classes/class.ilObjTestFolderGUI.php b/components/ILIAS/Test/classes/class.ilObjTestFolderGUI.php index cfbe6c99aaae..3b7a85ee67b6 100755 --- a/components/ILIAS/Test/classes/class.ilObjTestFolderGUI.php +++ b/components/ILIAS/Test/classes/class.ilObjTestFolderGUI.php @@ -22,9 +22,7 @@ use ILIAS\Test\RequestDataCollector; use ILIAS\Test\Logging\TestLogViewer; use ILIAS\Test\Logging\LogTable; - use ILIAS\TestQuestionPool\Questions\GeneralQuestionPropertiesRepository; - use ILIAS\Data\Factory as DataFactory; use ILIAS\UI\URLBuilder; use ILIAS\UI\Component\Input\Container\Form\Form; @@ -143,7 +141,9 @@ private function buildGlobalSettingsForm(): Form public function saveGlobalSettingsObject(): void { if (!$this->access->checkAccess('write', '', $this->getTestFolder()->getRefId())) { + $this->tpl->setOnScreenMessage('failure', $this->lng->txt('no_permission')); $this->showGlobalSettingsObject(); + return; } $form = $this->buildGlobalSettingsForm()->withRequest($this->request); @@ -183,7 +183,9 @@ protected function showLogSettingsObject(?Form $form = null): void protected function saveLogSettingsObject(): void { if (!$this->access->checkAccess('write', '', $this->getTestFolder()->getRefId())) { - $this->ilias->raiseError($this->lng->txt('permission_denied'), $this->ilias->error_obj->WARNING); + $this->tpl->setOnScreenMessage('failure', $this->lng->txt('no_permission')); + $this->showLogSettingsObject(); + return; } $form = $this->buildLogSettingsForm()->withRequest($this->request); diff --git a/components/ILIAS/Test/classes/class.ilObjTestGUI.php b/components/ILIAS/Test/classes/class.ilObjTestGUI.php index 11f6e894a4c3..9b6aa6a208ed 100755 --- a/components/ILIAS/Test/classes/class.ilObjTestGUI.php +++ b/components/ILIAS/Test/classes/class.ilObjTestGUI.php @@ -44,6 +44,7 @@ use ILIAS\Test\Results\Presentation\Factory as ResultsPresentationFactory; use ILIAS\Test\Results\Toplist\TestTopListRepository; use ILIAS\Test\ExportImport\Factory as ExportImportFactory; +use ILIAS\Test\ExportImport\DBRepository as ExportRepository; use ILIAS\TestQuestionPool\Questions\GeneralQuestionPropertiesRepository; use ILIAS\TestQuestionPool\RequestDataCollector as QPLRequestDataCollector; use ILIAS\TestQuestionPool\Import\TestQuestionsImportTrait; @@ -94,7 +95,7 @@ * @ilCtrl_Calls ilObjTestGUI: ILIAS\Test\Settings\MainSettings\SettingsMainGUI, ILIAS\Test\Settings\ScoreReporting\SettingsScoringGUI * @ilCtrl_Calls ilObjTestGUI: ilCommonActionDispatcherGUI * @ilCtrl_Calls ilObjTestGUI: ilTestFixedQuestionSetConfigGUI, ilTestRandomQuestionSetConfigGUI - * @ilCtrl_Calls ilObjTestGUI: ilAssQuestionHintsGUI, ilAssQuestionFeedbackEditingGUI, ilLocalUnitConfigurationGUI, assFormulaQuestionGUI + * @ilCtrl_Calls ilObjTestGUI: ilAssQuestionFeedbackEditingGUI, ilLocalUnitConfigurationGUI, assFormulaQuestionGUI * @ilCtrl_Calls ilObjTestGUI: ilTestPassDetailsOverviewTableGUI * @ilCtrl_Calls ilObjTestGUI: ilTestCorrectionsGUI * @ilCtrl_Calls ilObjTestGUI: ilTestSettingsChangeConfirmationGUI @@ -124,6 +125,7 @@ class ilObjTestGUI extends ilObjectGUI implements ilCtrlBaseClassInterface, ilDe private ilTestPlayerFactory $test_player_factory; private ilTestSessionFactory $test_session_factory; private ExportImportFactory $export_factory; + private ExportRepository $export_repository; private TestQuestionsRepository $test_questions_repository; private GeneralQuestionPropertiesRepository $questionrepository; private TestTopListRepository $toplist_repository; @@ -137,11 +139,8 @@ class ilObjTestGUI extends ilObjectGUI implements ilCtrlBaseClassInterface, ilDe protected ilComponentRepository $component_repository; protected ilComponentFactory $component_factory; protected ilDBInterface $db; - protected UIFactory $ui_factory; - protected UIRenderer $ui_renderer; protected ilUIService $ui_service; private ContentStyle $content_style; - protected HTTPServices $http; protected ilHelpGUI $help; protected GlobalScreen $global_screen; protected ilObjectDataCache $obj_data_cache; @@ -173,10 +172,7 @@ public function __construct() $this->navigation_history = $DIC['ilNavigationHistory']; $this->component_repository = $DIC['component.repository']; $this->component_factory = $DIC['component.factory']; - $this->ui_factory = $DIC['ui.factory']; - $this->ui_renderer = $DIC['ui.renderer']; $this->ui_service = $DIC->uiService(); - $this->http = $DIC['http']; $this->error = $DIC['ilErr']; $this->db = $DIC['ilDB']; $this->help = $DIC['ilHelp']; @@ -187,7 +183,6 @@ public function __construct() $this->archives = $DIC->archives(); $this->type = 'tst'; $this->data_factory = new DataFactory(); - $this->ui_service = $DIC->uiService(); $this->taxonomy = $DIC->taxonomy()->domain(); $this->ui_service = $DIC->uiService(); $this->content_style = $DIC->contentStyle(); @@ -203,6 +198,7 @@ public function __construct() $this->results_data_factory = $local_dic['results.data.factory']; $this->results_presentation_factory = $local_dic['results.presentation.factory']; $this->export_factory = $local_dic['exportimport.factory']; + $this->export_repository = $local_dic['exportimport.repository']; $this->participant_access_filter_factory = $local_dic['participant.access_filter.factory']; $this->test_pass_result_repository = $local_dic['results.data.test_result_repository']; $this->toplist_repository = $local_dic['results.toplist.repository']; @@ -317,13 +313,14 @@ public function executeCommand(): void $export_gui = new ilTestExportGUI( $this, $this->db, - $this->export_factory, $this->obj_data_cache, $this->user, $this->ui_factory, $this->ui_renderer, $this->irss, $this->request, + $this->export_repository, + $this->temp_file_system, $this->participant_access_filter_factory, new ilTestHTMLGenerator(), $selected_files, @@ -449,7 +446,10 @@ public function executeCommand(): void break; case "iltestplayerfixedquestionsetgui": - if ((!$this->access->checkAccess("read", "", $this->testrequest->getRefId()))) { + if ( + $cmd !== 'autosave' && + (!$this->access->checkAccess("read", "", $this->testrequest->getRefId())) + ) { $this->redirectAfterMissingRead(); } $this->trackTestObjectReadEvent(); @@ -462,7 +462,10 @@ public function executeCommand(): void break; case "iltestplayerrandomquestionsetgui": - if ((!$this->access->checkAccess("read", "", $this->testrequest->getRefId()))) { + if ( + $cmd !== 'autosave' && + (!$this->access->checkAccess("read", "", $this->testrequest->getRefId())) + ) { $this->redirectAfterMissingRead(); } $this->trackTestObjectReadEvent(); @@ -507,8 +510,13 @@ public function executeCommand(): void $this->redirectAfterMissingRead(); } + $this->prepareOutput(); + $this->addHeaderAction(); + $this->tabs_manager->activateTab(TabsManager::TAB_ID_LEARNING_PROGRESS); + $test_session = $this->test_session_factory->getSessionByUserId($this->user->getId()); - if (!$this->getTestObject()->canShowTestResults($test_session)) { + if (!$this->test_access->checkOtherParticipantsLearningProgressAccess() + && !$this->getTestObject()->canShowTestResults($test_session)) { $this->tpl->setOnScreenMessage( 'info', $this->lng->txt('tst_res_tab_msg_no_lp_access'), @@ -516,10 +524,6 @@ public function executeCommand(): void break; } - $this->prepareOutput(); - $this->addHeaderAction(); - - $this->tabs_manager->activateTab(TabsManager::TAB_ID_LEARNING_PROGRESS); $new_gui = new ilLearningProgressGUI(ilLearningProgressGUI::LP_CONTEXT_REPOSITORY, $this->getTestObject()->getRefId()); $this->ctrl->forwardCommand($new_gui); @@ -760,10 +764,12 @@ public function executeCommand(): void if (!$this->access->checkAccess('write', '', $this->getTestObject()->getRefId())) { $this->redirectAfterMissingWrite(); } + $this->prepareOutput(); $this->forwardCommandToQuestionPreview($cmd); break; case 'ilassquestionpagegui': if ($cmd === 'finishEditing') { + $this->prepareOutput(); $this->forwardCommandToQuestionPreview(ilAssQuestionPreviewGUI::CMD_SHOW); break; } @@ -828,33 +834,6 @@ public function executeCommand(): void $this->ctrl->forwardCommand($gui); break; - case 'ilassquestionhintsgui': - if (!$this->access->checkAccess('write', '', $this->getTestObject()->getRefId())) { - $this->redirectAfterMissingWrite(); - } - - if ($this->getTestObject()->evalTotalPersons() !== 0) { - $this->tpl->setOnScreenMessage('failure', $this->lng->txt('question_is_part_of_running_test'), true); - $this->forwardCommandToQuestionPreview(ilAssQuestionPreviewGUI::CMD_SHOW); - return; - } - $this->prepareSubGuiOutput(); - - $question_gui = assQuestionGUI::_getQuestionGUI('', $this->fetchAuthoringQuestionIdParameter()); - $question = $question_gui->getObject(); - $question->setObjId($this->getTestObject()->getId()); - $question_gui->setObject($question); - $question_gui->setQuestionTabs(); - - $gui = new ilAssQuestionHintsGUI($question_gui); - - $gui->setEditingEnabled( - $this->access->checkAccess('write', '', $this->getTestObject()->getRefId()) - ); - - $this->ctrl->forwardCommand($gui); - break; - case 'ilassquestionfeedbackeditinggui': if (!$this->access->checkAccess('write', '', $this->getTestObject()->getRefId())) { $this->redirectAfterMissingWrite(); @@ -869,8 +848,11 @@ public function executeCommand(): void $question_gui->setObject($question); $question_gui->setQuestionTabs(); + $this->addQuestionTitleToObjectTitle($question->getTitleForHTMLOutput()); + if ($this->getTestObject()->evalTotalPersons() !== 0) { $this->tpl->setOnScreenMessage('failure', $this->lng->txt('question_is_part_of_running_test'), true); + $this->prepareOutput(); $this->forwardCommandToQuestionPreview(ilAssQuestionPreviewGUI::CMD_SHOW); } $gui = new ilAssQuestionFeedbackEditingGUI( @@ -913,6 +895,14 @@ public function executeCommand(): void if ((!$this->access->checkAccess("write", "", $this->testrequest->getRefId()))) { $this->redirectAfterMissingWrite(); } + + if ($cmd === 'downloadFile') { + $page_id = $this->object->getIntroductionPageId(); + if ($this->testrequest->strVal('page_type') === 'concludingremarkspage') { + $page_id = $this->object->getConcludingRemarksPageId(); + } + $this->ctrl->forwardCommand(new ilTestPageGUI('tst', $page_id)); + } $this->showEditTestPageGUI($cmd); break; @@ -952,6 +942,7 @@ public function executeCommand(): void if (in_array($cmd, ['editQuestion', 'save', 'saveReturn', 'suggestedsolution']) && $this->getTestObject()->evalTotalPersons() !== 0) { $this->tpl->setOnScreenMessage('failure', $this->lng->txt('question_is_part_of_running_test'), true); + $this->prepareOutput(); $this->forwardCommandToQuestionPreview(ilAssQuestionPreviewGUI::CMD_SHOW); return; } @@ -966,8 +957,8 @@ public function executeCommand(): void protected function redirectAfterMissingWrite() { - $this->tpl->setOnScreenMessage('failure', $this->lng->txt("no_permission"), true); - $target_class = get_class($this->getTestObject()) . "GUI"; + $this->tpl->setOnScreenMessage('failure', $this->lng->txt('no_permission'), true); + $target_class = get_class($this->object) . 'GUI'; $this->ctrl->setParameterByClass($target_class, 'ref_id', $this->ref_id); $this->ctrl->redirectByClass($target_class); } @@ -976,7 +967,7 @@ protected function redirectAfterMissingRead(): void { $this->tpl->setOnScreenMessage('failure', sprintf( $this->lng->txt("msg_no_perm_read_item"), - $this->getTestObject()->getTitle() + $this->object->getTitle() ), true); $this->ctrl->setParameterByClass('ilrepositorygui', 'ref_id', ROOT_FOLDER_ID); $this->ctrl->redirectByClass('ilrepositorygui'); @@ -986,11 +977,9 @@ protected function forwardCommandToQuestionPreview( string $cmd, ?assQuestionGUI $question_gui = null ): void { - $this->prepareOutput(); - $nr_of_participants_with_results = $this->getTestObject()->evalTotalPersons(); - $this->ctrl->saveParameter($this, 'q_id'); + $this->ctrl->saveParameterByClass(self::class, 'q_id'); $gui = new ilAssQuestionPreviewGUI( $this->ctrl, $this->rbac_system, @@ -1007,10 +996,6 @@ protected function forwardCommandToQuestionPreview( $this->ref_id ); - if ($this->getTestObject()->isRandomTest() && $nr_of_participants_with_results === 0) { - $gui->setInfoMessage($this->lng->txt('question_is_part_of_running_test')); - } - if ($nr_of_participants_with_results > 0) { $gui->addAdditionalCmd( $this->lng->txt('tst_corrections_qst_form'), @@ -1020,6 +1005,8 @@ protected function forwardCommandToQuestionPreview( $question_gui ??= assQuestion::instantiateQuestionGUI($this->fetchAuthoringQuestionIdParameter()); + $this->addQuestionTitleToObjectTitle($question_gui->getObject()->getTitleForHTMLOutput()); + if (!$this->getTestObject()->isRandomTest() && $nr_of_participants_with_results === 0) { $gui->setPrimaryCmd( $this->lng->txt('edit_question'), @@ -1040,13 +1027,26 @@ protected function forwardCommandToQuestionPreview( $gui->initQuestion($question_gui, $this->getTestObject()->getId()); $gui->initPreviewSettings($this->getTestObject()->getRefId()); $gui->initPreviewSession($this->user->getId(), $this->testrequest->getQuestionId()); - $gui->initHintTracking(); $gui->initStyleSheets(); - $this->tabs_gui->setBackTarget($this->lng->txt('backtocallingtest'), $this->ctrl->getLinkTargetByClass(self::class, self::SHOW_QUESTIONS_CMD)); + $this->ctrl->clearParameterByClass(self::class, 'q_id'); + $this->tabs_gui->setBackTarget( + $this->lng->txt('backtocallingtest'), + $this->ctrl->getLinkTargetByClass(self::class, self::SHOW_QUESTIONS_CMD) + ); + $this->ctrl->saveParameterByClass(self::class, 'q_id'); $gui->{$cmd . 'Cmd'}(); } + private function addQuestionTitleToObjectTitle(string $question_title): void + { + $this->tpl->setTitle( + $this->refinery->encode()->htmlSpecialCharsAsEntities()->transform( + "{$this->getTestObject()->getTitle()}: {$question_title}" + ) + ); + } + protected function forwardCommandToQuestion(string $cmd): void { $this->create_question_mode = true; @@ -1069,6 +1069,8 @@ protected function forwardCommandToQuestion(string $cmd): void $question_gui->setContextAllowsSyncToPool(true); $question_gui->setQuestionTabs(); + $this->addQuestionTitleToObjectTitle($question->getTitleForHTMLOutput()); + $target = strpos($cmd, 'Return') === false ? 'stay' : 'return'; if (in_array($cmd, ['syncQuestion', 'syncQuestionReturn'])) { @@ -1247,18 +1249,6 @@ private function questionsTabGatewayObject() $this->ctrl->redirectByClass('ilObjTestGUI', self::SHOW_QUESTIONS_CMD); } - public function prepareOutput(bool $show_subobjects = true): bool - { - if (!$this->getCreationMode()) { - $settings = ilMemberViewSettings::getInstance(); - if ($settings->isActive() && $settings->getContainer() != $this->getTestObject()->getRefId()) { - $settings->setContainer($this->getTestObject()->getRefId()); - $this->rbac_system->initMemberView(); - } - } - return parent::prepareOutput($show_subobjects); - } - private function showEditTestPageGUI(string $cmd): void { $this->prepareOutput(); @@ -1281,6 +1271,7 @@ private function showEditTestPageGUI(string $cmd): void $this->ctrl->saveParameterByClass(ilTestPageGUI::class, 'page_type'); $gui = new ilTestPageGUI('tst', $page_id); + $this->content_style->gui()->addCss($this->tpl, $this->ref_id); $this->tpl->setContent($this->ctrl->forwardCommand($gui)); $this->tabs_manager->activateTab(TabsManager::TAB_ID_SETTINGS); @@ -1364,7 +1355,8 @@ protected function importFile(string $file_to_import, string $path_to_uploaded_f ilSession::set('path_to_uploaded_file_in_temp_dir', $path_to_uploaded_file_in_temp_dir); if ($qtiParser->getQuestionSetType() !== ilObjTest::QUESTION_SET_TYPE_FIXED - || file_exists($this->buildResultsFilePath($importdir, $subdir))) { + || file_exists($this->buildResultsFilePath($importdir, $subdir)) + || $founditems === []) { $this->importVerifiedFileObject(true); return; } @@ -1473,6 +1465,10 @@ public function getTestObject(): ?ilObjTest public function importVerifiedFileObject( bool $skip_retrieve_selected_questions = false ): void { + if (!$this->checkPermissionBool('create', '', 'tst')) { + $this->tpl->setOnScreenMessage('failure', $this->lng->txt('no_permission'), true); + $this->ctrl->returnToParent($this); + } $file_to_import = ilSession::get('path_to_import_file'); $path_to_uploaded_file_in_temp_dir = ilSession::get('path_to_uploaded_file_in_temp_dir'); list($subdir, $importdir, $xmlfile, $qtifile) = $this->buildImportDirectoriesFromImportFile($file_to_import); @@ -1502,7 +1498,11 @@ public function importVerifiedFileObject( $map = $imp->getMapping(); $map->addMapping('components/ILIAS/Test', 'tst', 'new_id', (string) $new_obj->getId()); - if (is_file($importdir . DIRECTORY_SEPARATOR . "/manifest.xml")) { + /** + * 2025-03-22, sk: This is now only needed for legacy exports as + * now also exports with results do contain a manifest.xml. + */ + if (is_file($importdir . DIRECTORY_SEPARATOR . '/manifest.xml')) { $imp->importObject($new_obj, $file_to_import, basename($file_to_import), 'tst', 'components/ILIAS/Test', true); } else { $test_importer = new ilTestImporter(); @@ -1536,6 +1536,16 @@ public function importVerifiedFileObject( ilSession::clear('path_to_uploaded_file_in_temp_dir'); $this->tpl->setOnScreenMessage('success', $this->lng->txt("object_imported"), true); + + $question_skill_assignments_import_fails = new ilAssQuestionSkillAssignmentImportFails($new_obj->getId()); + if ($question_skill_assignments_import_fails->failedImportsRegistered()) { + $this->tpl->setOnScreenMessage( + 'info', + $question_skill_assignments_import_fails->getFailedImportsMessage($this->lng), + true + ); + } + $this->ctrl->setParameterByClass(ilObjTestGUI::class, 'ref_id', $new_obj->getRefId()); $this->ctrl->redirectByClass(ilObjTestGUI::class); } @@ -1588,6 +1598,8 @@ public function createQuestionPool($name = "dummy", $description = ""): ilObjQue public function createQuestionObject(): void { + $this->protectByWritePermission(); + $this->ctrl->setReturnByClass(self::class, self::SHOW_QUESTIONS_CMD); $form = $this->buildQuestionCreationForm()->withRequest($this->request); @@ -1639,6 +1651,8 @@ public function cancelCreateQuestionObject(): void private function insertQuestionsObject(?array $selected_array = null): void { + $this->protectByWritePermission(); + if (($selected_array ?? $this->testrequest->getQuestionIds()) === []) { $this->tpl->setOnScreenMessage('info', $this->lng->txt('tst_insert_missing_question'), true); $this->ctrl->redirect($this, 'browseForQuestions'); @@ -1652,6 +1666,8 @@ private function insertQuestionsObject(?array $selected_array = null): void public function createQuestionFormObject(?Form $form = null): void { + $this->protectByWritePermission(); + $this->tabs_manager->getQuestionsSubTabs(); $this->tabs_manager->activateSubTab(TabsManager::SUBTAB_ID_QST_LIST_VIEW); @@ -1953,6 +1969,8 @@ public function historyObject(): void public function exportLegacyLogsObject(): void { + $this->protectByWritePermission(); + $csv_output = $this->getTestObject()->getTestLogViewer()->getLegacyLogExportForObjId($this->getTestObject()->getId()); ilUtil::deliverData( @@ -1982,10 +2000,7 @@ public function participantsActionObject(): void */ public function defaultsObject() { - if (!$this->access->checkAccess("write", "", $this->ref_id)) { - $this->tpl->setOnScreenMessage('info', $this->lng->txt("cannot_edit_test"), true); - $this->ctrl->redirectByClass([ilRepositoryGUI::class, self::class, ilInfoScreenGUI::class]); - } + $this->protectByWritePermission(); $this->tabs_manager->activateTab(TabsManager::TAB_ID_SETTINGS); @@ -2003,6 +2018,8 @@ public function defaultsObject() */ public function deleteDefaultsObject() { + $this->protectByWritePermission(); + $defaults_ids = $this->testrequest->retrieveArrayOfIntsFromPost('chb_defaults'); if ($defaults_ids !== null && $defaults_ids !== []) { foreach ($defaults_ids as $test_default_id) { @@ -2019,6 +2036,8 @@ public function deleteDefaultsObject() */ public function confirmedApplyDefaultsObject() { + $this->protectByWritePermission(); + $this->applyDefaultsObject(true); return; } @@ -2028,6 +2047,8 @@ public function confirmedApplyDefaultsObject() */ public function applyDefaultsObject($confirmed = false): void { + $this->protectByWritePermission(); + $defaults_id = $this->testrequest->retrieveArrayOfIntsFromPost('chb_defaults'); if ($defaults_id === []) { $this->tpl->setOnScreenMessage('info', $this->lng->txt('tst_defaults_apply_select_one')); @@ -2118,6 +2139,8 @@ public function applyDefaultsObject($confirmed = false): void */ public function addDefaultsObject(): void { + $this->protectByWritePermission(); + $name = $this->testrequest->strVal('name'); if ($name !== '') { $this->getTestObject()->addDefaults($name); @@ -2146,7 +2169,7 @@ private function infoScreenObject(): void private function forwardToInfoScreen(): void { if (!$this->access->checkAccess('visible', '', $this->ref_id) - && !$this->access->checkAccess('read', '', $this->testrequest->getRefId())) { + && !$this->access->checkAccess('read', '', $this->ref_id)) { $this->redirectAfterMissingRead(); } @@ -2156,7 +2179,7 @@ private function forwardToInfoScreen(): void $this->tabs_manager->activateTab(TabsManager::TAB_ID_INFOSCREEN); - if ($this->access->checkAccess('read', '', $this->testrequest->getRefId())) { + if ($this->access->checkAccess('read', '', $this->ref_id)) { $this->trackTestObjectReadEvent(); } $info = new ilInfoScreenGUI($this); @@ -2330,9 +2353,6 @@ public function addLocatorItems(): void $this->testrequest->getRefId() ); break; - case "create": - case "save": - case "cancel": case "importFile": case "cloneAll": case "importVerifiedFile": @@ -2391,14 +2411,6 @@ public function getTabs(): void $this->tabs_manager->perform(); } - protected function setTitleAndDescription(): void - { - parent::setTitleAndDescription(); - - $icon = ilObject::_getIcon($this->object->getId(), 'big', $this->object->getType()); - $this->tpl->setTitleIcon($icon, $this->lng->txt('obj_' . $this->object->getType())); - } - public static function accessViolationRedirect() { /** @var ILIAS\DI\Container $DIC */ @@ -2534,6 +2546,11 @@ public function copyToQuestionpoolObject() public function createQuestionPoolAndCopyObject() { + if (!$this->access->checkAccess('write', '', $this->object->getRefId()) + || !$this->checkPermissionBool('create', '', 'qpl')) { + $this->redirectAfterMissingWrite(); + } + if ($this->testrequest->raw('title')) { $title = $this->testrequest->raw('title'); } else { @@ -2558,6 +2575,8 @@ public function createQuestionPoolAndCopyObject() */ public function createQuestionpoolTargetObject(string $cmd): void { + $this->protectByWritePermission(); + $this->tabs_manager->getQuestionsSubTabs(); $this->tabs_manager->activateSubTab(TabsManager::SUBTAB_ID_QST_LIST_VIEW); @@ -2685,6 +2704,7 @@ private function getTestScreenGUIInstance(): TestScreenGUI $this->refinery, $this->ctrl, $this->tpl, + $this->content_style, $this->http, $this->tabs_manager, $this->access, @@ -2746,6 +2766,7 @@ protected function getTable(): QuestionsTable { return new QuestionsTable( $this->ui_factory, + $this->refinery, $this->http->request(), $this->getQuestionsTableActions(), $this->lng, diff --git a/components/ILIAS/Test/classes/class.ilObjTestXMLParser.php b/components/ILIAS/Test/classes/class.ilObjTestXMLParser.php index bf3cf193a4f5..fa310992b06f 100755 --- a/components/ILIAS/Test/classes/class.ilObjTestXMLParser.php +++ b/components/ILIAS/Test/classes/class.ilObjTestXMLParser.php @@ -86,9 +86,8 @@ public function setImportMapping(\ilImportMapping $import_mapping): void public function setHandlers($xml_parser): void { - xml_set_object($xml_parser, $this); - xml_set_element_handler($xml_parser, 'handlerBeginTag', 'handlerEndTag'); - xml_set_character_data_handler($xml_parser, 'handlerCharacterData'); + xml_set_element_handler($xml_parser, $this->handlerBeginTag(...), $this->handlerEndTag(...)); + xml_set_character_data_handler($xml_parser, $this->handlerCharacterData(...)); } public function handlerBeginTag( diff --git a/components/ILIAS/Test/classes/class.ilTestAccess.php b/components/ILIAS/Test/classes/class.ilTestAccess.php index 2db6a659aa01..7b5330567b59 100755 --- a/components/ILIAS/Test/classes/class.ilTestAccess.php +++ b/components/ILIAS/Test/classes/class.ilTestAccess.php @@ -88,6 +88,10 @@ public function checkScoreParticipantsAccess(): bool return true; } + if (!$this->getAccess()->checkAccess('read', '', $this->getRefId())) { + return false; + } + if ($this->getAccess()->checkPositionAccess(ilOrgUnitOperation::OP_SCORE_PARTICIPANTS, $this->getRefId())) { return true; } @@ -104,6 +108,10 @@ public function checkManageParticipantsAccess(): bool return true; } + if (!$this->getAccess()->checkAccess('read', '', $this->getRefId())) { + return false; + } + if ($this->getAccess()->checkPositionAccess(ilOrgUnitOperation::OP_MANAGE_PARTICIPANTS, $this->getRefId())) { return true; } @@ -111,9 +119,6 @@ public function checkManageParticipantsAccess(): bool return false; } - /** - * @return bool - */ public function checkParticipantsResultsAccess(): bool { if ($this->getAccess()->checkAccess('write', '', $this->getRefId())) { @@ -124,6 +129,10 @@ public function checkParticipantsResultsAccess(): bool return true; } + if (!$this->getAccess()->checkAccess('read', '', $this->getRefId())) { + return false; + } + if ($this->getAccess()->checkPositionAccess(ilOrgUnitOperation::OP_MANAGE_PARTICIPANTS, $this->getRefId())) { return true; } @@ -135,6 +144,23 @@ public function checkParticipantsResultsAccess(): bool return false; } + public function checkOtherParticipantsLearningProgressAccess(): bool + { + if ($this->getAccess()->checkAccess('write', '', $this->getRefId())) { + return true; + } + + if ($this->getAccess()->checkRbacOrPositionPermissionAccess( + 'read_learning_progress', + ilOrgUnitOperation::OP_READ_LEARNING_PROGRESS, + $this->getRefId() + )) { + return true; + } + + return false; + } + protected function checkAccessForActiveId(Closure $access_filter, int $active_id, int $test_id): bool { $participantData = new ilTestParticipantData($this->db, $this->lng); @@ -214,7 +240,7 @@ private function isParticipantExplicitelyAllowedByIndividualIPRange( } if ($this->isIpTypeOf(FILTER_FLAG_IPV6, $ip, $range_start, $range_end)) { - return !$this->isIpv6Between($ip, $range_start, $range_end); + return $this->isIpv6Between($ip, $range_start, $range_end); } return false; diff --git a/components/ILIAS/Test/classes/class.ilTestArchiveService.php b/components/ILIAS/Test/classes/class.ilTestArchiveService.php index 092f5bda5c27..28182f415f0c 100755 --- a/components/ILIAS/Test/classes/class.ilTestArchiveService.php +++ b/components/ILIAS/Test/classes/class.ilTestArchiveService.php @@ -84,6 +84,7 @@ public function archiveActivesPass(int $active_id, int $pass): void $this->request, $this->obj_cache, $this->participant_access_filter_factory, + $this->test_obj->getTestLogViewer(), $this->test_obj->getId() ); $archiver->setParticipantData($this->getParticipantData()); diff --git a/components/ILIAS/Test/classes/class.ilTestArchiver.php b/components/ILIAS/Test/classes/class.ilTestArchiver.php index 7abbd2241ca0..084ed148dabd 100755 --- a/components/ILIAS/Test/classes/class.ilTestArchiver.php +++ b/components/ILIAS/Test/classes/class.ilTestArchiver.php @@ -19,6 +19,7 @@ declare(strict_types=1); use ILIAS\Test\Results\Presentation\TitlesBuilder as ResultsTitleBuilder; +use ILIAS\Test\Logging\TestLogViewer; use ILIAS\UI\Factory as UIFactory; use ILIAS\UI\Renderer as UIRenderer; use ILIAS\UI\Component\Table\DataRetrieval; @@ -33,32 +34,25 @@ */ class ilTestArchiver { - public const DIR_SEP = '/'; + public const DIR_SEP = DIRECTORY_SEPARATOR; - public const HTML_SUBMISSION_FILENAME = 'test_submission.html'; - public const PASS_MATERIALS_PATH_COMPONENT = 'materials'; - public const QUESTION_PATH_COMPONENT_PREFIX = 'q_'; - - public const TEST_BEST_SOLUTION_PATH_COMPONENT = 'best_solution'; - public const HTML_BEST_SOLUTION_FILENAME = 'best_solution.html'; - public const TEST_MATERIALS_PATH_COMPONENT = 'materials'; + public const EXPORT_DIRECTORY = 'archive_exports'; - private const TEST_RESULT_FILENAME = 'test_result.html'; + private const PASS_MATERIALS_PATH_COMPONENT = 'materials'; + private const QUESTION_PATH_COMPONENT_PREFIX = 'q_'; - public const TEST_OVERVIEW_HTML_FILENAME = 'results_overview_html_v'; - public const TEST_OVERVIEW_HTML_POSTFIX = '.html'; + private const TEST_BEST_SOLUTION_PATH_COMPONENT = 'best_solution'; + private const HTML_BEST_SOLUTION_FILENAME = 'best_solution.html'; + private const TEST_MATERIALS_PATH_COMPONENT = 'materials'; - public const LOG_DTSGROUP_FORMAT = 'D M j G:i:s T Y'; - public const LOG_ADDITION_STRING = ' Adding '; - public const LOG_CREATION_STRING = ' Creating '; - public const LOG_UPDATE_STRING = ' Updating '; - public const LOG_DELETION_STRING = ' Deleting '; + private const TEST_RESULT_FILENAME = 'test_result.html'; - public const TEST_LOG_FILENAME = 'test.log'; - public const DATA_INDEX_FILENAME = 'data_index.csv'; - public const ARCHIVE_LOG = 'archive.log'; + private const LOG_DTSGROUP_FORMAT = 'D M j G:i:s T Y'; + private const LOG_ADDITION_STRING = ' Adding '; - public const EXPORT_DIRECTORY = 'archive_exports'; + private const TEST_LOG_FILENAME = 'test_log.xlsx'; + private const DATA_INDEX_FILENAME = 'data_index.csv'; + private const ARCHIVE_LOG = 'archive.log'; private string $external_directory_path; private string $client_id = CLIENT_ID; @@ -78,6 +72,7 @@ public function __construct( private readonly ServerRequestInterface $request, private readonly ilObjectDataCache $obj_cache, private readonly ilTestParticipantAccessFilterFactory $participant_access_filter_factory, + private readonly TestLogViewer $log_viewer, private readonly int $test_obj_id, private ?int $test_ref_id = null ) { @@ -111,16 +106,16 @@ public function handInParticipantQuestionMaterial( $this->ensurePassDataDirectoryIsAvailable($active_fi, $pass); $pass_question_directory = $this->getPassDataDirectory($active_fi, $pass) - . self::DIR_SEP . self::QUESTION_PATH_COMPONENT_PREFIX . $question_fi; + . DIRECTORY_SEPARATOR . self::QUESTION_PATH_COMPONENT_PREFIX . $question_fi; if (!is_dir($pass_question_directory)) { mkdir($pass_question_directory, 0777, true); } - copy($file_path, $pass_question_directory . self::DIR_SEP . $original_filename); + copy($file_path, $pass_question_directory . DIRECTORY_SEPARATOR . $original_filename); $this->logArchivingProcess( date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING - . $pass_question_directory . self::DIR_SEP . $original_filename + . $pass_question_directory . DIRECTORY_SEPARATOR . $original_filename ); } @@ -132,7 +127,7 @@ public function handInParticipantMisc( ): void { $this->ensureTestArchiveIsAvailable(); $this->ensurePassDataDirectoryIsAvailable($active_fi, $pass); - $new_path = $this->getPassDataDirectory($active_fi, $pass) . self::DIR_SEP . $original_filename; + $new_path = $this->getPassDataDirectory($active_fi, $pass) . DIRECTORY_SEPARATOR . $original_filename; copy($file_path, $new_path); $this->logArchivingProcess(date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING . $new_path); } @@ -141,19 +136,19 @@ public function handInTestBestSolution(string $best_solution): void { $this->ensureTestArchiveIsAvailable(); - $best_solution_path = $this->getTestArchive() . self::DIR_SEP . self::TEST_BEST_SOLUTION_PATH_COMPONENT; + $best_solution_path = $this->getTestArchive() . DIRECTORY_SEPARATOR . self::TEST_BEST_SOLUTION_PATH_COMPONENT; if (!is_dir($best_solution_path)) { mkdir($best_solution_path, 0777, true); } $this->html_generator->generateHTML( $best_solution, - $best_solution_path . self::DIR_SEP . self::HTML_BEST_SOLUTION_FILENAME + $best_solution_path . DIRECTORY_SEPARATOR . self::HTML_BEST_SOLUTION_FILENAME ); $this->logArchivingProcess( date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING - . $best_solution_path . self::DIR_SEP . self::HTML_BEST_SOLUTION_FILENAME + . $best_solution_path . DIRECTORY_SEPARATOR . self::HTML_BEST_SOLUTION_FILENAME ); $this->logArchivingProcess( @@ -174,7 +169,7 @@ public function handInParticipantUploadedResults( $this->ensurePassDataDirectoryIsAvailable($active_fi, $pass); $this->ensurePassMaterialsDirectoryIsAvailable($active_fi, $pass); $pass_material_directory = $this->getPassMaterialsDirectory($active_fi, $pass); - $archive_folder = $pass_material_directory . self::DIR_SEP . $question->question_id . self::DIR_SEP; + $archive_folder = $pass_material_directory . DIRECTORY_SEPARATOR . $question->question_id . DIRECTORY_SEPARATOR; if (!file_exists($archive_folder)) { mkdir($archive_folder, 0777, true); } @@ -205,26 +200,26 @@ public function handInBestSolutionQuestionMaterial( ): void { $this->ensureTestArchiveIsAvailable(); - $best_solution_path = $this->getTestArchive() . self::DIR_SEP . self::TEST_BEST_SOLUTION_PATH_COMPONENT; + $best_solution_path = $this->getTestArchive() . DIRECTORY_SEPARATOR . self::TEST_BEST_SOLUTION_PATH_COMPONENT; if (!is_dir($best_solution_path)) { mkdir($best_solution_path, 0777, true); } - $materials_path = $best_solution_path . self::DIR_SEP . self::TEST_MATERIALS_PATH_COMPONENT; + $materials_path = $best_solution_path . DIRECTORY_SEPARATOR . self::TEST_MATERIALS_PATH_COMPONENT; if (!is_dir($materials_path)) { mkdir($materials_path, 0777, true); } - $question_materials_path = $materials_path . self::DIR_SEP . self::QUESTION_PATH_COMPONENT_PREFIX . $question_fi; + $question_materials_path = $materials_path . DIRECTORY_SEPARATOR . self::QUESTION_PATH_COMPONENT_PREFIX . $question_fi; if (!is_dir($question_materials_path)) { mkdir($question_materials_path, 0777, true); } - copy($file_path, $question_materials_path . self::DIR_SEP . $orginial_filename); + copy($file_path, $question_materials_path . DIRECTORY_SEPARATOR . $orginial_filename); $this->logArchivingProcess( date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING - . $question_materials_path . self::DIR_SEP . $orginial_filename + . $question_materials_path . DIRECTORY_SEPARATOR . $orginial_filename ); } @@ -232,7 +227,7 @@ public function handInTestResult(int $active_fi, int $pass, string $pdf_path): v { $this->ensureTestArchiveIsAvailable(); $this->ensurePassDataDirectoryIsAvailable($active_fi, $pass); - $new_path = $this->getPassDataDirectory($active_fi, $pass) . self::DIR_SEP . self::TEST_RESULT_FILENAME; + $new_path = $this->getPassDataDirectory($active_fi, $pass) . DIRECTORY_SEPARATOR . self::TEST_RESULT_FILENAME; copy($pdf_path, $new_path); $this->logArchivingProcess(date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING . $new_path); } @@ -249,8 +244,8 @@ protected function createArchiveForTest(): void protected function getTestArchive(): string { - $test_archive_directory = $this->external_directory_path . self::DIR_SEP . $this->client_id . self::DIR_SEP . 'tst_data' - . self::DIR_SEP . 'archive' . self::DIR_SEP . 'tst_' . $this->test_obj_id; + $test_archive_directory = $this->external_directory_path . DIRECTORY_SEPARATOR . $this->client_id . DIRECTORY_SEPARATOR . 'tst_data' + . DIRECTORY_SEPARATOR . 'archive' . DIRECTORY_SEPARATOR . 'tst_' . $this->test_obj_id; return $test_archive_directory; } @@ -264,14 +259,11 @@ protected function ensureTestArchiveIsAvailable(): void public function updateTestArchive(): void { - $query = 'SELECT * FROM ass_log WHERE obj_fi = ' . $this->db->quote($this->test_obj_id, 'integer'); - $result = $this->db->query($query); - - $outfile_lines = ''; - while (($row = $this->db->fetchAssoc($result)) !== null) { - $outfile_lines .= "\r\n" . implode("\t", $row); - } - file_put_contents($this->getTestArchive() . self::DIR_SEP . self::TEST_LOG_FILENAME, $outfile_lines); + $this->log_viewer->getLogExportForRefjId( + $this->test_ref_id + )->writeToFile( + $this->getTestArchive() . DIRECTORY_SEPARATOR . self::TEST_LOG_FILENAME + ); // Generate test pass overview $test = new ilObjTest($this->test_obj_id, false); @@ -286,7 +278,7 @@ public function updateTestArchive(): void $array_of_actives[] = $key; } - $filename = realpath($this->getTestArchive()) . self::DIR_SEP . 'participant_attempt_overview.html'; + $filename = realpath($this->getTestArchive()) . DIRECTORY_SEPARATOR . 'participant_attempt_overview.html'; $this->html_generator->generateHTML( $this->createUserResultsForArchive( $test, @@ -315,8 +307,8 @@ protected function createZipExportDirectory(): void public function getZipExportDirectory(): string { - return $this->external_directory_path . self::DIR_SEP . $this->client_id . self::DIR_SEP . 'tst_data' - . self::DIR_SEP . self::EXPORT_DIRECTORY . self::DIR_SEP . 'tst_' . $this->test_obj_id; + return $this->external_directory_path . DIRECTORY_SEPARATOR . $this->client_id . DIRECTORY_SEPARATOR . 'tst_data' + . DIRECTORY_SEPARATOR . self::EXPORT_DIRECTORY . DIRECTORY_SEPARATOR . 'tst_' . $this->test_obj_id; } public function compressTestArchive(): void @@ -325,9 +317,9 @@ public function compressTestArchive(): void $this->ensureZipExportDirectoryExists(); $zip_output_path = $this->getZipExportDirectory(); - $zip_output_filename = 'test_archive_obj_' . $this->test_obj_id . '_' . time() . '_.zip'; + $zip_output_filename = 'test_archive_obj_' . $this->test_obj_id . '_' . time() . '.zip'; - ilFileUtils::zip($this->getTestArchive(), $zip_output_path . self::DIR_SEP . $zip_output_filename, true); + ilFileUtils::zip($this->getTestArchive(), $zip_output_path . DIRECTORY_SEPARATOR . $zip_output_filename, true); return; } @@ -347,7 +339,7 @@ private function buildPassDataDirectory($active_fi, $pass): ?string foreach ($this->archive_data_index as $data_index_entry) { if ($data_index_entry != null && $data_index_entry['identifier'] == $active_fi . '|' . $pass) { array_shift($data_index_entry); - return $this->getTestArchive() . self::DIR_SEP . implode(self::DIR_SEP, $data_index_entry); + return $this->getTestArchive() . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $data_index_entry); } } @@ -364,21 +356,18 @@ protected function getPassDataDirectory(int $active_fi, int $pass): ?string $test_obj = new ilObjTest($this->test_obj_id, false); if ($test_obj->getAnonymity()) { - $firstname = 'anonym'; + $firstname = $this->lng->txt('anonymous'); $lastname = ''; - $matriculation = '0'; + $matriculation = ''; + } elseif ($this->getParticipantData()) { + $usr_data = $this->getParticipantData()->getUserDataByActiveId($active_fi); + $firstname = $usr_data['firstname'] ?? $this->lng->txt('deleted_user'); + $lastname = $usr_data['lastname'] ?? ''; + $matriculation = $usr_data['matriculation'] ?? ''; } else { - if ($this->getParticipantData()) { - $usr_data = $this->getParticipantData()->getUserDataByActiveId($active_fi); - $firstname = $usr_data['firstname']; - $lastname = $usr_data['lastname']; - $matriculation = $usr_data['matriculation']; - } else { - - $firstname = $this->user->getFirstname(); - $lastname = $this->user->getLastname(); - $matriculation = $this->user->getMatriculation(); - } + $firstname = $this->user->getFirstname(); + $lastname = $this->user->getLastname(); + $matriculation = $this->user->getMatriculation(); } $this->appendToArchiveDataIndex( @@ -421,10 +410,9 @@ protected function createPassMaterialsDirectory(int $active_fi, int $pass): stri if ($this->getParticipantData()) { $usrData = $this->getParticipantData()->getUserDataByActiveId($active_fi); $user = new ilObjUser(); - $user->setFirstname($usrData['firstname']); - $user->setLastname($usrData['lastname']); - $user->setMatriculation($usrData['matriculation']); - $user->setFirstname($usrData['firstname']); + $user->setFirstname($usrData['firstname'] ?? $this->lng->txt('deleted_user')); + $user->setLastname($usrData['lastname'] ?? ''); + $user->setMatriculation($usrData['matriculation'] ?? ''); } $this->appendToArchiveDataIndex( @@ -443,7 +431,7 @@ protected function createPassMaterialsDirectory(int $active_fi, int $pass): stri protected function getPassMaterialsDirectory(int $active_fi, int $pass): string { $pass_data_directory = $this->getPassDataDirectory($active_fi, $pass); - return $pass_data_directory . self::DIR_SEP . self::PASS_MATERIALS_PATH_COMPONENT; + return $pass_data_directory . DIRECTORY_SEPARATOR . self::PASS_MATERIALS_PATH_COMPONENT; } protected function ensurePassMaterialsDirectoryIsAvailable(int $active_fi, int $pass): void @@ -459,7 +447,7 @@ protected function readArchiveDataIndex(): array * The Archive Data Index is a csv-file containing the following columns * ||||
| */ - $data_index_file = $this->getTestArchive() . self::DIR_SEP . self::DATA_INDEX_FILENAME; + $data_index_file = $this->getTestArchive() . DIRECTORY_SEPARATOR . self::DATA_INDEX_FILENAME; $contents = []; @@ -503,7 +491,7 @@ protected function appendToArchiveDataIndex( $output_contents .= implode('|', $line_data) . "\n"; } - file_put_contents($this->getTestArchive() . self::DIR_SEP . self::DATA_INDEX_FILENAME, $output_contents); + file_put_contents($this->getTestArchive() . DIRECTORY_SEPARATOR . self::DATA_INDEX_FILENAME, $output_contents); $this->readArchiveDataIndex(); return; } @@ -598,9 +586,9 @@ public function getResultsOfUserOutput( ); $table = $this->ui_factory->table()->data( + $this->getDataRetrievalForAttemptOverviewTable($result_array), $test_result_title_builder->getPassDetailsHeaderLabel($attempt + 1), - $this->getColumnsForAttemptOverviewTable($test_obj->isOfferingQuestionHintsEnabled()), - $this->getDataRetrievalForAttemptOverviewTable($result_array) + $this->getColumnsForAttemptOverviewTable(), )->withRequest($this->request); $template->setVariable( 'PASS_DETAILS', @@ -646,9 +634,8 @@ public function getResultsOfUserOutput( return $template->get(); } - private function getColumnsForAttemptOverviewTable( - bool $show_requested_hints_info - ): array { + private function getColumnsForAttemptOverviewTable(): array + { $cf = $this->ui_factory->table()->column(); $columns = [ 'order' => $cf->number($this->lng->txt('order')), @@ -657,9 +644,6 @@ private function getColumnsForAttemptOverviewTable( 'reachable_points' => $cf->number($this->lng->txt('tst_maximum_points')), 'reached_points' => $cf->number($this->lng->txt('tst_reached_points')) ]; - if ($show_requested_hints_info) { - $columns['hints'] = $cf->number($this->lng->txt('tst_question_hints_requested_hint_count_header')); - } $columns['solved'] = $cf->text($this->lng->txt('tst_percent_solved')); return $columns; } @@ -693,7 +677,6 @@ public function getRows( 'title' => $result['title'], 'reachable_points' => $result['max'], 'reached_points' => $result['reached'], - 'hints' => $result['requested_hints'] ?? 0, 'solved' => $result['percent'] ] ); @@ -711,7 +694,7 @@ public function getTotalRowCount( private function logArchivingProcess(string $message): void { - $archive = $this->getTestArchive() . self::DIR_SEP . self::ARCHIVE_LOG; + $archive = $this->getTestArchive() . DIRECTORY_SEPARATOR . self::ARCHIVE_LOG; if (file_exists($archive)) { $content = file_get_contents($archive) . "\n" . $message; } else { diff --git a/components/ILIAS/Test/classes/class.ilTestCorrectionsGUI.php b/components/ILIAS/Test/classes/class.ilTestCorrectionsGUI.php index 37b208f98296..375148871a1e 100755 --- a/components/ILIAS/Test/classes/class.ilTestCorrectionsGUI.php +++ b/components/ILIAS/Test/classes/class.ilTestCorrectionsGUI.php @@ -81,9 +81,7 @@ protected function showQuestion(?ilPropertyFormGUI $form = null) { $this->setCorrectionTabsContext($this->question_gui, 'question'); - if ($form === null) { - $form = $this->buildQuestionCorrectionForm($this->question_gui); - } + $form ??= $this->buildQuestionCorrectionForm($this->question_gui); $this->populatePageTitleAndDescription($this->question_gui); $this->main_tpl->setContent($form->getHTML()); @@ -131,7 +129,7 @@ protected function confirmManualScoringReset() $confirmation = sprintf( $this->language->txt('tst_corrections_manscore_reset_warning'), $scoring->getNumManualScorings(), - $this->question_gui->getObject()->getTitle(), + $this->question_gui->getObject()->getTitleForHTMLOutput(), $this->question_gui->getObject()->getId() ); @@ -210,7 +208,7 @@ protected function showSolution() ); $page_gui->setQuestionHTML([$question_gui->getObject()->getId() => $solution_html]); - $page_gui->setPresentationTitle($question_gui->getObject()->getTitle()); + $page_gui->setPresentationTitle($question_gui->getObject()->getTitleForHTMLOutput()); $tpl = new ilTemplate('tpl.tst_corrections_solution_presentation.html', true, true, 'components/ILIAS/Test'); $tpl->setVariable('SOLUTION_PRESENTATION', $page_gui->preview()); @@ -235,17 +233,21 @@ protected function showSolution() $this->main_tpl->parseCurrentBlock(); } - protected function showAnswerStatistic() + /** + * @param null|list $participant_results + */ + protected function showAnswerStatistic(?array $participant_results = null): void { - $question_gui = $this->question_gui; - $solutions = $this->getSolutions($question_gui->getObject()); + $solutions = $participant_results + ? $this->getSolutionsByParticipantResults($this->question_gui->getObject(), $participant_results) + : $this->getSolutions($this->question_gui->getObject()); - $this->setCorrectionTabsContext($question_gui, 'answers'); + $this->setCorrectionTabsContext($this->question_gui, 'answers'); $tablesHtml = ''; - foreach ($question_gui->getSubQuestionsIndex() as $subQuestionIndex) { - $table = $question_gui->getAnswerFrequencyTableGUI( + foreach ($this->question_gui->getSubQuestionsIndex() as $subQuestionIndex) { + $table = $this->question_gui->getAnswerFrequencyTableGUI( $this, 'showAnswerStatistic', $solutions, @@ -255,7 +257,7 @@ protected function showAnswerStatistic() $tablesHtml .= $table->getHTML() . $table->getAdditionalHtml(); } - $this->populatePageTitleAndDescription($question_gui); + $this->populatePageTitleAndDescription($this->question_gui); $this->main_tpl->setContent($tablesHtml); } @@ -294,7 +296,8 @@ protected function addAnswer() $this->language ); $scoring->setPreserveManualScores(true); - $scoring->recalculateSolutions(); + $scoring->setQuestionId($question_index); + $participant_results = $scoring->recalculateSolutions(); if ($this->logger->isLoggingEnabled()) { $this->logger->logQuestionAdministrationInteraction( @@ -307,7 +310,7 @@ protected function addAnswer() } $this->main_tpl->setOnScreenMessage('success', $this->language->txt('saved_successfully')); - $this->showAnswerStatistic(); + $this->showAnswerStatistic($participant_results); } protected function addHiddenItemsFromArray(ilConfirmationGUI $gui, $array, $curPath = []) @@ -371,7 +374,7 @@ protected function setCorrectionTabsContext(assQuestionGUI $question_gui, string protected function populatePageTitleAndDescription(assQuestionGUI $question_gui): void { - $this->main_tpl->setTitle($question_gui->getObject()->getTitle()); + $this->main_tpl->setTitle($question_gui->getObject()->getTitleForHTMLOutput()); $this->main_tpl->setDescription($question_gui->outQuestionType()); } @@ -424,6 +427,24 @@ protected function getSolutions(assQuestion $question): array return $solution_rows; } + /** + * @param list $participant_results + */ + private function getSolutionsByParticipantResults(assQuestion $question, array $participant_results): array + { + $solutions = []; + + foreach ($participant_results as $active_id => $result) { + foreach ($result->getPasses() as $pass) { + foreach ($question->getSolutionValues($active_id, $pass->getPass()) as $row) { + $solutions[] = $row; + } + } + } + + return $solutions; + } + protected function getQuestions(): array { diff --git a/components/ILIAS/Test/classes/class.ilTestEvaluationGUI.php b/components/ILIAS/Test/classes/class.ilTestEvaluationGUI.php index 0406cd1131a7..3f236ed41eba 100755 --- a/components/ILIAS/Test/classes/class.ilTestEvaluationGUI.php +++ b/components/ILIAS/Test/classes/class.ilTestEvaluationGUI.php @@ -265,11 +265,6 @@ public function outUserPassDetails(): void $test_result_header_label_builder->initObjectiveOrientedMode(); } - $command_solution_details = ''; - if ($this->object->getShowSolutionListComparison()) { - $command_solution_details = 'outCorrectSolution'; - } - $tpl = new ilTemplate('tpl.il_as_tst_pass_details_overview_participants.html', true, true, 'components/ILIAS/Test'); $this->addPrintButtonToToolbar(); @@ -592,7 +587,7 @@ public function performDeletePass(): void $ilDB = $this->db; - if ($active_fi === 0 || $pass === 0) { + if ($active_fi === 0 || !$this->testrequest->isset('pass')) { $this->ctrl->redirect($this, 'outUserResultsOverview'); } @@ -748,23 +743,6 @@ public function performDeletePass(): void ); } - // qpl_hint_tracking - $ilDB->manipulate( - 'DELETE - FROM qpl_hint_tracking - WHERE qhtr_active_fi = ' . $ilDB->quote($active_fi, 'integer') . ' - AND qhtr_pass = ' . $ilDB->quote($pass, 'integer') - ); - - if ($must_renumber) { - $ilDB->manipulate( - 'UPDATE qpl_hint_tracking - SET qhtr_pass = qhtr_pass - 1 - WHERE qhtr_active_fi = ' . $ilDB->quote($active_fi, 'integer') . ' - AND qhtr_pass > ' . $ilDB->quote($pass, 'integer') - ); - } - // tst_test_rnd_qst -> nothing to do // tst_times @@ -789,66 +767,6 @@ public function performDeletePass(): void $this->redirectToPassDeletionContext($context); } - protected function getFilteredTestResult( - int $active_id, - int $pass, - bool $consider_hidden_questions, - bool $consider_optional_questions = true - ): array { - $component_repository = $this->component_repository; - $ilDB = $this->db; - - $result_data = $this->object->getTestResult($active_id, $pass, false, $consider_hidden_questions); - $question_ids = []; - foreach ($result_data as $result_item_key => $result_item_value) { - if ($result_item_key === 'test' || $result_item_key === 'pass') { - continue; - } - - $question_ids[] = $result_item_value['qid']; - } - - $table_gui = $this->buildPassDetailsOverviewTableGUI($this, 'outUserPassDetails'); - - $question_list = new ilAssQuestionList($ilDB, $this->lng, $this->refinery, $component_repository); - $question_list->setParentObjId($this->object->getId()); - $question_list->setParentObjectType($this->object->getType()); - $question_list->setIncludeQuestionIdsFilter($question_ids); - - foreach ($table_gui->getFilterItems() as $item) { - if (substr($item->getPostVar(), 0, strlen('tax_')) == 'tax_') { - $v = $item->getValue(); - - if (is_array($v) && count($v) && !(int) $v[0]) { - continue; - } - - $tax_id = substr($item->getPostVar(), strlen('tax_')); - $question_list->addTaxonomyFilter($tax_id, $item->getValue(), $this->object->getId(), 'tst'); - } elseif ($item->getValue() !== false) { - $question_list->addFieldFilter($item->getPostVar(), $item->getValue()); - } - } - - $question_list->load(); - - $filtered_test_result = []; - - foreach ($result_data as $result_item_key => $result_item_value) { - if ($result_item_key === 'test' || $result_item_key === 'pass') { - continue; - } - - if (!$question_list->isInList($result_item_value['qid'])) { - continue; - } - - $filtered_test_result[] = $result_item_value; - } - - return $filtered_test_result; - } - protected function redirectBackToParticipantsScreen() { $this->ctrl->redirectByClass(ilTestParticipantsGUI::class); diff --git a/components/ILIAS/Test/classes/class.ilTestExportGUI.php b/components/ILIAS/Test/classes/class.ilTestExportGUI.php index 6c6613a04893..1499d6b973f1 100755 --- a/components/ILIAS/Test/classes/class.ilTestExportGUI.php +++ b/components/ILIAS/Test/classes/class.ilTestExportGUI.php @@ -19,12 +19,12 @@ declare(strict_types=1); use ILIAS\Test\Scoring\Manual\TestScoring; +use ILIAS\Test\ExportImport\DBRepository; +use ILIAS\Test\ExportImport\ResultsExportStakeholder; use ILIAS\UI\Factory as UIFactory; use ILIAS\UI\Renderer as UIRenderer; use ILIAS\ResourceStorage\Services as IRSS; -use ILIAS\UI\Component\Input\Container\Form\Standard as StandardForm; -use ILIAS\DI\UIServices as UIServices; -use ILIAS\Test\ExportImport\Factory as ExportImportFactory; +use ILIAS\Filesystem\Filesystem; use ILIAS\Test\ExportImport\Types as ExportImportTypes; use Psr\Http\Message\ServerRequestInterface; @@ -40,38 +40,73 @@ class ilTestExportGUI extends ilExportGUI public function __construct( ilObjTestGUI $parent_gui, private readonly ilDBInterface $db, - private readonly ExportImportFactory $export_factory, private readonly ilObjectDataCache $obj_cache, private readonly ilObjUser $user, private readonly UIFactory $ui_factory, private readonly UIRenderer $ui_renderer, private readonly IRSS $irss, private readonly ServerRequestInterface $request, + private readonly DBRepository $export_repository, + private readonly Filesystem $temp_file_system, private readonly ilTestParticipantAccessFilterFactory $participant_access_filter_factory, - private readonly ilTestHTMLGenerator $html_generator, - private readonly array $selected_files, + private readonly ilTestHTMLGenerator $html_generator ) { parent::__construct($parent_gui, null); } - /** - * @return ilTestExportTableGUI - */ - protected function buildExportTableGUI(): ilTestExportTableGUI - { - $table = new ilTestExportTableGUI($this, 'listExportFiles', $this->obj); - return $table; - } - /** * Create test export file */ public function createTestExportWithResults() { - $test_exp = $this->export_factory->getExporter($this->obj, ExportImportTypes::XML_WITH_RESULTS); - $test_exp->write(); - $this->tpl->setOnScreenMessage('success', $this->lng->txt('exp_file_created'), true); - $this->ctrl->redirectByClass('iltestexportgui'); + $this->ctrl->setParameterByClass(self::class, 'export_results', 1); + $manager = $this->export_handler->manager()->handler(); + $export_info = $manager->getExportInfoWithObject( + $this->obj, + time() + ); + $element = $manager->createExport( + $this->il_user->getId(), + $export_info, + '' + ); + + $file_name = $element->getIRSSInfo()->getFileName(); + $this->temp_file_system->writeStream( + $file_name, + $this->irss->consume()->stream($element->getIRSSInfo()->getResourceId())->getStream() + ); + $temp_stream = $this->temp_file_system->readStream($file_name); + $rid = $this->irss->manage()->stream( + $temp_stream, + new ResultsExportStakeholder(), + $element->getIRSSInfo()->getFileName() + ); + + $temp_stream->close(); + + $this->temp_file_system->delete($file_name); + + $this->export_repository->store( + $this->obj->getId(), + ExportImportTypes::XML_WITH_RESULTS, + $rid + ); + $this->export_options->getById('expxml')->onDeleteFiles( + $this->context, + $this->export_handler->consumer()->file()->identifier()->collection()->withElement( + $this->export_handler->consumer()->file()->identifier()->handler()->withIdentifier( + $element->getIRSSInfo()->getResourceIdSerialized() + ) + ) + ); + + $this->tpl->setOnScreenMessage( + ilGlobalTemplateInterface::MESSAGE_TYPE_SUCCESS, + $this->lng->txt("exp_file_created"), + true + ); + $this->ctrl->redirect($this, self::CMD_LIST_EXPORT_FILES); } public function createTestArchiveExport() @@ -112,6 +147,7 @@ public function createTestArchiveExport() $this->request, $this->obj_cache, $this->participant_access_filter_factory, + $this->parent_gui->getTestObject()->getTestLogViewer(), $test_id, $test_ref ); @@ -133,183 +169,4 @@ public function createTestArchiveExport() } $this->ctrl->redirectByClass('iltestexportgui'); } - - public function listExportFiles(): void - { - $this->toolbar->setFormAction($this->ctrl->getFormAction($this)); - - if (count($this->getFormats()) > 1) { - foreach ($this->getFormats() as $f) { - $options[$f['key']] = $f['txt']; - } - $si = new ilSelectInputGUI($this->lng->txt('type'), 'format'); - $si->setOptions($options); - $this->toolbar->addInputItem($si, true); - $this->toolbar->addFormButton($this->lng->txt('exp_create_file'), 'createExportFile'); - } else { - $format = $this->getFormats()[0]; - $this->toolbar->addFormButton( - $this->lng->txt('exp_create_file') - . ' (' . $format['txt'] . ')', - 'create_' . $format['key'] - ); - } - - $archiver = new ilTestArchiver( - $this->lng, - $this->db, - $this->user, - $this->ui_factory, - $this->ui_renderer, - $this->irss, - $this->request, - $this->obj_cache, - $this->participant_access_filter_factory, - $this->getParentGUI()->getTestObject()->getId() - ); - $archive_dir = $archiver->getZipExportDirectory(); - $archive_files = []; - - if (file_exists($archive_dir) && is_dir($archive_dir)) { - $archive_files = scandir($archive_dir); - } - - $export_dir = $this->obj->getExportDirectory(); - $export_files = $this->obj->getExportFiles($export_dir); - $data = []; - if (count($export_files) > 0) { - foreach ($export_files as $exp_file) { - $file_arr = explode('__', $exp_file); - if ($file_arr[0] == $exp_file) { - continue; - } - - array_push( - $data, - [ - 'file' => $exp_file, - 'size' => filesize($export_dir . '/' . $exp_file), - 'timestamp' => $file_arr[0], - 'type' => 'ZIP' - ] - ); - } - } - - if (count($archive_files) > 0) { - foreach ($archive_files as $exp_file) { - if ($exp_file == '.' || $exp_file == '..') { - continue; - } - $file_arr = explode('_', $exp_file); - - $data[] = [ - 'file' => $exp_file, - 'size' => filesize($archive_dir . '/' . $exp_file), - 'timestamp' => $file_arr[4], - 'type' => 'ZIP' - ]; - } - } - - $table = $this->buildExportTableGUI(); - $table->setSelectAllCheckbox('file'); - foreach ($this->getCustomColumns() as $c) { - $table->addCustomColumn($c['txt'], $c['obj'], $c['func']); - } - - foreach ($this->getCustomMultiCommands() as $c) { - $table->addCustomMultiCommand($c['txt'], 'multi_' . $c['func']); - } - - $table->resetFormats(); - foreach ($this->formats as $format) { - $table->addFormat($format['key']); - } - - $table->setData($data); - $this->tpl->setOnScreenMessage('info', $this->lng->txt('no_manual_feedback_export_info'), true); - $this->tpl->setContent($table->getHTML()); - } - - public function download(): void - { - if ($this->selected_files === []) { - $this->tpl->setOnScreenMessage('info', $this->lng->txt('no_checkbox'), true); - $this->ctrl->redirect($this, 'listExportFiles'); - } - - if (count($this->selected_files) > 1) { - $this->tpl->setOnScreenMessage('info', $this->lng->txt('select_max_one_item'), true); - $this->ctrl->redirect($this, 'listExportFiles'); - } - - $archiver = new ilTestArchiver( - $this->lng, - $this->db, - $this->user, - $this->ui_factory, - $this->ui_renderer, - $this->irss, - $this->request, - $this->obj_cache, - $this->participant_access_filter_factory, - $this->getParentGUI()->getTestObject()->getId() - ); - $filename = basename($this->selected_files[0]); - $exportFile = $this->obj->getExportDirectory() . '/' . $filename; - $archiveFile = $archiver->getZipExportDirectory() . '/' . $filename; - - if (file_exists($exportFile)) { - ilFileDelivery::deliverFileLegacy($exportFile, $filename); - } - - if (file_exists($archiveFile)) { - ilFileDelivery::deliverFileLegacy($archiveFile, $filename); - } - - $this->ctrl->redirect($this, 'listExportFiles'); - } - - public function delete(): void - { - $archiver = new ilTestArchiver( - $this->lng, - $this->db, - $this->user, - $this->ui_factory, - $this->ui_renderer, - $this->irss, - $this->request, - $this->obj_cache, - $this->participant_access_filter_factory, - $this->getParentGUI()->getTestObject()->getId() - ); - $archiveDir = $archiver->getZipExportDirectory(); - - $export_dir = $this->obj->getExportDirectory(); - foreach ($this->selected_files as $file) { - $file = basename($file); - $dir = substr($file, 0, strlen($file) - 4); - - if (!strlen($file) || !strlen($dir)) { - continue; - } - - $exp_file = $export_dir . '/' . $file; - $arc_file = $archiveDir . '/' . $file; - $exp_dir = $export_dir . '/' . $dir; - if (@is_file($exp_file)) { - unlink($exp_file); - } - if (@is_file($arc_file)) { - unlink($arc_file); - } - if (@is_dir($exp_dir)) { - ilFileUtils::delDir($exp_dir); - } - } - $this->tpl->setOnScreenMessage('success', $this->lng->txt('msg_deleted_export_files'), true); - $this->ctrl->redirect($this, 'listExportFiles'); - } } diff --git a/components/ILIAS/Test/classes/class.ilTestExportOptionARC.php b/components/ILIAS/Test/classes/class.ilTestExportOptionARC.php index 44267fefb8cd..dd46d59d3d9d 100644 --- a/components/ILIAS/Test/classes/class.ilTestExportOptionARC.php +++ b/components/ILIAS/Test/classes/class.ilTestExportOptionARC.php @@ -163,7 +163,7 @@ protected function getDirectory( string $export_object_type ): string { return $this->data_dir - . ilTestArchiver::DIR_SEP . CLIENT_ID . ilTestArchiver::DIR_SEP . 'tst_data' . ilTestArchiver::DIR_SEP + . DIRECTORY_SEPARATOR . CLIENT_ID . DIRECTORY_SEPARATOR . 'tst_data' . DIRECTORY_SEPARATOR . ilTestArchiver::EXPORT_DIRECTORY . DIRECTORY_SEPARATOR . $export_object_type . "_" . $object_id->toInt(); } } diff --git a/components/ILIAS/Test/classes/class.ilTestExportOptionXMLRES.php b/components/ILIAS/Test/classes/class.ilTestExportOptionXMLRES.php index daf5a37b38ca..154460c6622a 100644 --- a/components/ILIAS/Test/classes/class.ilTestExportOptionXMLRES.php +++ b/components/ILIAS/Test/classes/class.ilTestExportOptionXMLRES.php @@ -18,24 +18,35 @@ declare(strict_types=1); +use ILIAS\Test\TestDIC; +use ILIAS\Test\ExportImport\DBRepository; +use ILIAS\Test\ExportImport\ResultsExportStakeholder; +use ILIAS\ResourceStorage\Services as ResourceStorage; +use ILIAS\Data\Factory as DataFactory; use ILIAS\Data\ObjectId; use ILIAS\Data\ReferenceId; -use ILIAS\Export\ExportHandler\Consumer\ExportOption\BasicLegacyHandler as ilBasicLegacyExportOption; +use ILIAS\Export\ExportHandler\Consumer\ExportOption\BasicHandler as ilBasicExportOption; use ILIAS\Export\ExportHandler\I\Consumer\Context\HandlerInterface as ilExportHandlerConsumerContextInterface; use ILIAS\DI\Container; use ILIAS\Export\ExportHandler\I\Info\File\CollectionInterface as ilExportHandlerFileInfoCollectionInterface; use ILIAS\Export\ExportHandler\I\Consumer\File\Identifier\CollectionInterface as ilExportHandlerConsumerFileIdentifierCollectionInterface; use ILIAS\Export\ExportHandler\I\Consumer\File\Identifier\HandlerInterface as ilExportHandlerConsumerFileIdentifierInterface; -class ilTestExportOptionXMLRES extends ilBasicLegacyExportOption +class ilTestExportOptionXMLRES extends ilBasicExportOption { - protected ilLanguage $lng; + public const OPTIONS_ID = 'test_exp_option_xmlres'; + private ilLanguage $lng; + private ResourceStorage $irss; + private DataFactory $data_factory; + private DBRepository $repository; public function init( Container $DIC ): void { - $this->lng = $DIC->language(); - parent::init($DIC); + $this->lng = $DIC['lng']; + $this->irss = $DIC['resource_storage']; + $this->data_factory = new DataFactory(); + $this->repository = TestDIC::dic()['exportimport.repository']; } public function getExportType(): string @@ -45,7 +56,7 @@ public function getExportType(): string public function getExportOptionId(): string { - return 'test_exp_option_xmlres'; + return self::OPTIONS_ID; } public function getSupportedRepositoryObjectTypes(): array @@ -66,20 +77,9 @@ public function onDeleteFiles( ): void { $object_id = new ObjectId($context->exportObject()->getId()); foreach ($file_identifiers as $file_identifier) { - $file = explode(":", $file_identifier->getIdentifier()); - $file[1] = basename($file[1]); - $export_dir = $this->getDirectory($object_id, $context->exportObject()->getType()); - $exp_file = $export_dir . "/" . str_replace("..", "", $file[1]); - $exp_dir = $export_dir . "/" . substr($file[1], 0, strlen($file[1]) - 4); - if (is_file($exp_file)) { - unlink($exp_file); - } - if ( - is_dir($exp_dir) and - count(scandir($exp_dir)) === 2 - ) { - ilFileUtils::delDir($exp_dir); - } + $rid = $this->irss->manage()->find($file_identifier->getIdentifier()); + $this->repository->delete($rid); + $this->irss->manage()->remove($rid, new ResultsExportStakeholder()); } } @@ -89,13 +89,9 @@ public function onDownloadFiles( ): void { $object_id = new ObjectId($context->exportObject()->getId()); foreach ($file_identifiers as $file_identifier) { - $file = explode(":", trim($file_identifier->getIdentifier())); - $export_dir = $this->getDirectory($object_id, $context->exportObject()->getType()); - $file[1] = basename($file[1]); - ilFileDelivery::deliverFileLegacy( - $export_dir . "/" . $file[1], - $file[1] - ); + $this->irss->consume()->download( + $this->irss->manage()->find($file_identifier->getIdentifier()) + )->run(); } } @@ -103,32 +99,27 @@ public function onDownloadWithLink( ReferenceId $reference_id, ilExportHandlerConsumerFileIdentifierInterface $file_identifier ): void { - $object_id = $reference_id->toObjectId(); - $type = ilObject::_lookupType($object_id->toInt()); - $file = explode(":", trim($file_identifier->getIdentifier())); - $export_dir = $this->getDirectory($object_id, $type); - $file[1] = basename($file[1]); - ilFileDelivery::deliverFileLegacy( - $export_dir . "/" . $file[1], - $file[1] - ); + $this->irss->consume()->download($reference_id)->run(); } public function getFiles( ilExportHandlerConsumerContextInterface $context ): ilExportHandlerFileInfoCollectionInterface { - $collection_builder = $context->fileCollectionBuilder(); - $object_id = new ObjectId($context->exportObject()->getId()); - $dir = $this->getDirectory($object_id, $context->exportObject()->getType()); - $file_infos = $this->getExportFiles($dir); - foreach ($file_infos as $file_name => $file_info) { - $collection_builder = $collection_builder->withSPLFileInfo( - new SplFileInfo($dir . DIRECTORY_SEPARATOR . $file_info["file"]), - $object_id, - $this - ); - } - return $collection_builder->collection(); + return $this->buildElements( + $context, + $this->data_factory->objId($context->exportObject()->getId()) + ); + } + + public function getFileSelection( + ilExportHandlerConsumerContextInterface $context, + ilExportHandlerConsumerFileIdentifierCollectionInterface $file_identifiers + ): ilExportHandlerFileInfoCollectionInterface { + return $this->buildElements( + $context, + $this->data_factory->objId($context->exportObject()->getId()), + $file_identifiers->toStringArray() + ); } public function onExportOptionSelected( @@ -137,57 +128,25 @@ public function onExportOptionSelected( $context->exportGUIObject()->createTestExportWithResults(); } - protected function getExportFiles( - string $directory - ): array { - $file = []; - try { - $h_dir = dir($directory); - while ($entry = $h_dir->read()) { - if ( - $entry === "." || - $entry === ".." || - substr($entry, -4) !== ".zip" - ) { - continue; - } - $zip_archive = new ZipArchive(); - $zip_archive->open($directory . DIRECTORY_SEPARATOR . $entry); - $is_result = false; - for ($i = 0; $i < $zip_archive->numFiles; $i++) { - $stat = $zip_archive->statIndex($i); - if (str_contains(basename($stat['name']), "results")) { - $is_result = true; - break; - } - } - if (!$is_result) { - continue; - } - $ts = substr($entry, 0, strpos($entry, "__")); - $file[$entry . $this->getExportType()] = [ - "type" => $this->getExportType(), - "file" => $entry, - "size" => (int) filesize($directory . "/" . $entry), - "timestamp" => (int) $ts - ]; - } - } catch (Exception $e) { - - } - return $file; - } - - protected function getDirectory( + protected function buildElements( + ilExportHandlerConsumerContextInterface $context, ObjectId $object_id, - string $export_object_type - ): string { - $dir = ilExport::_getExportDirectory( - $object_id->toInt(), - "", - $export_object_type - ); - $dir = substr($dir, 0, strlen($dir) - 1); - return $dir; + ?array $file_identifiers = null + ): ilExportHandlerFileInfoCollectionInterface { + if ($file_identifiers === null) { + $file_identifiers = array_map( + static fn(array $v): string => $v['rid'], + $this->repository->getFor($object_id->toInt()) + ); + } + $collection_builder = $context->fileCollectionBuilder(); + foreach ($file_identifiers as $file_identifier) { + $collection_builder = $collection_builder->withResourceIdentifier( + $this->irss->manage()->find($file_identifier), + $object_id, + $this + ); + } + return $collection_builder->collection(); } } diff --git a/components/ILIAS/Test/classes/class.ilTestExporter.php b/components/ILIAS/Test/classes/class.ilTestExporter.php index 56bb4247dde5..3cfc9ff2026d 100755 --- a/components/ILIAS/Test/classes/class.ilTestExporter.php +++ b/components/ILIAS/Test/classes/class.ilTestExporter.php @@ -37,6 +37,7 @@ class ilTestExporter extends ilXmlExporter private readonly ExportImportFactory $export_factory; private readonly TestLogger $logger; private readonly ilTree $tree; + private readonly ilCtrl $ctrl; private readonly ilComponentRepository $component_repository; private readonly GeneralQuestionPropertiesRepository $questionrepository; @@ -49,6 +50,7 @@ public function __construct() $this->logger = $local_dic['logging.logger']; $this->questionrepository = $local_dic['question.general_properties.repository']; $this->tree = $DIC['tree']; + $this->ctrl = $DIC['ilCtrl']; $this->component_repository = $DIC['component.repository']; parent::__construct(); @@ -63,13 +65,59 @@ public function init(): void public function getXmlRepresentation(string $a_entity, string $a_schema_version, string $id): string { + $parameters = $this->ctrl->getParameterArrayByClass(ilTestExportGUI::class); + $export_type = ExportImportTypes::XML; + if (!empty($parameters['export_results'])) { + $export_type = ExportImportTypes::XML_WITH_RESULTS; + $this->ctrl->clearParameterByClass(ilTestExportGUI::class, 'export_results'); + } $tst = new ilObjTest((int) $id, false); $tst->read(); - $zip = $this->export_factory->getExporter($tst, ExportImportTypes::XML) + $zip = $this->export_factory->getExporter($tst, $export_type) + ->withExportDirInfo($this->getAbsoluteExportDirectory()) ->write(); $this->logger->info(__METHOD__ . ': Created zip file ' . $zip); - return ''; // Sagt mjansen + return ''; + } + + public function getXmlExportHeadDependencies(string $entity, string $target_release, array $ids): array + { + if ($entity === 'tst') { + $mobs = []; + $files = []; + foreach ($ids as $id) { + $tst = new ilObjTest((int) $id, false); + $tst->read(); + + $intro_page_id = $tst->getMainSettings()->getIntroductionSettings()->getIntroductionPageId(); + if ($intro_page_id !== null) { + $mobs = array_merge($mobs, ilObjMediaObject::_getMobsOfObject('tst:pg', $intro_page_id)); + $files = array_merge($files, ilObjFile::_getFilesOfObject('tst:pg', $intro_page_id)); + } + + $concluding_remarks_page_id = $tst->getMainSettings()->getFinishingSettings()->getConcludingRemarksPageId(); + if ($concluding_remarks_page_id !== null) { + $mobs = array_merge($mobs, ilObjMediaObject::_getMobsOfObject('tst:pg', $concluding_remarks_page_id)); + $files = array_merge($files, ilObjFile::_getFilesOfObject('tst:pg', $concluding_remarks_page_id)); + } + } + + return [ + [ + 'component' => 'components/ILIAS/MediaObjects', + 'entity' => 'mob', + 'ids' => $mobs + ], + [ + 'component' => 'components/ILIAS/File', + 'entity' => 'file', + 'ids' => $files + ] + ]; + } + + return parent::getXmlExportTailDependencies($entity, $target_release, $ids); } /** @@ -92,7 +140,7 @@ public function getXmlExportTailDependencies(string $a_entity, string $a_target_ } $deps[] = [ - 'component' => 'components/ILIAS/Object', + 'component' => 'components/ILIAS/ILIASObject', 'entity' => 'common', 'ids' => $a_ids ]; diff --git a/components/ILIAS/Test/classes/class.ilTestHTMLGenerator.php b/components/ILIAS/Test/classes/class.ilTestHTMLGenerator.php index d646a11e96ae..2915ed47d0a5 100755 --- a/components/ILIAS/Test/classes/class.ilTestHTMLGenerator.php +++ b/components/ILIAS/Test/classes/class.ilTestHTMLGenerator.php @@ -153,7 +153,7 @@ private function getTemplatePath($a_filename, $module_path = 'components/ILIAS/T { $fname = ''; if (ilStyleDefinition::getCurrentSkin() !== 'default') { - $fname = './Customizing/global/skin/' . + $fname = './Customizing/skin/' . ilStyleDefinition::getCurrentSkin() . '/' . $module_path . basename($a_filename); } diff --git a/components/ILIAS/Test/classes/class.ilTestImporter.php b/components/ILIAS/Test/classes/class.ilTestImporter.php index 73b9b5cd525d..742f932b74db 100755 --- a/components/ILIAS/Test/classes/class.ilTestImporter.php +++ b/components/ILIAS/Test/classes/class.ilTestImporter.php @@ -18,6 +18,7 @@ declare(strict_types=1); +use ILIAS\ResourceStorage\Services as ResourceStorage; use ILIAS\TestQuestionPool\Import\TestQuestionsImportTrait; use ILIAS\Test\TestDIC; use ILIAS\Test\Logging\TestLogger; @@ -39,12 +40,14 @@ class ilTestImporter extends ilXmlImporter private readonly TestLogger $logger; private readonly ilDBInterface $db; + private readonly ResourceStorage $irss; public function __construct() { global $DIC; $this->logger = TestDIC::dic()['logging.logger']; $this->db = $DIC['ilDB']; + $this->irss = $DIC['resource_storage']; parent::__construct(); } @@ -96,7 +99,8 @@ public function importXmlRepresentation( $qtifile, ilQTIParser::IL_MO_PARSE_QTI, $new_obj->getId(), - $selected_questions + $selected_questions, + $a_mapping->getAllMappings() ); $qti_parser->setTestObject($new_obj); $qti_parser->startParsing(); @@ -118,7 +122,7 @@ public function importXmlRepresentation( } if ($results_file_path !== null && file_exists($results_file_path)) { - $results = new ilTestResultsImportParser($results_file_path, $new_obj, $this->db, $this->logger); + $results = new ilTestResultsImportParser($results_file_path, $new_obj, $this->db, $this->logger, $this->irss); $results->setQuestionIdMapping($a_mapping->getMappingsOfEntity('components/ILIAS/Test', 'quest')); $results->setSrcPoolDefIdMapping($a_mapping->getMappingsOfEntity('components/ILIAS/Test', 'rnd_src_pool_def')); $results->startParsing(); @@ -127,9 +131,12 @@ public function importXmlRepresentation( $new_obj->saveToDb(); // this creates test_fi $new_obj->update(); // this saves ilObject data - // import skill assignments - $importedAssignmentList = $this->importQuestionSkillAssignments($a_mapping, $new_obj, $xmlfile); - $this->importSkillLevelThresholds($a_mapping, $importedAssignmentList, $new_obj, $xmlfile); + $this->importSkillLevelThresholds( + $a_mapping, + $this->importQuestionSkillAssignments($a_mapping, $new_obj, $xmlfile), + $new_obj, + $xmlfile + ); $a_mapping->addMapping("components/ILIAS/Test", "tst", (string) $a_id, (string) $new_obj->getId()); $a_mapping->addMapping( diff --git a/components/ILIAS/Test/classes/class.ilTestPassFinishTasks.php b/components/ILIAS/Test/classes/class.ilTestPassFinishTasks.php index da028ccb8886..725bbf249a02 100755 --- a/components/ILIAS/Test/classes/class.ilTestPassFinishTasks.php +++ b/components/ILIAS/Test/classes/class.ilTestPassFinishTasks.php @@ -28,9 +28,9 @@ class ilTestPassFinishTasks { public function __construct( - private ilTestSession $test_session, - private int $obj_id, - private TestResultRepository $test_pass_result_repository + private readonly ilTestSession $test_session, + private readonly ilObjTest $obj_test, + private readonly TestResultRepository $test_pass_result_repository ) { } @@ -65,25 +65,16 @@ public function performFinishTasks(ilTestProcessLocker $process_locker, StatusOf ); }); + $this->obj_test->updateTestResultCache($this->test_session->getActiveId(), null); + $this->updateLearningProgressAfterPassFinishedIsWritten(); } protected function updateLearningProgressAfterPassFinishedIsWritten() { ilLPStatusWrapper::_updateStatus( - $this->obj_id, + $this->obj_test->getId(), ilObjTestAccess::_getParticipantId($this->test_session->getActiveId()) ); } - - protected function getCaller() - { - try { - throw new Exception(); - } catch (Exception $e) { - $trace = $e->getTrace(); - } - - return $trace[3]['class']; - } } diff --git a/components/ILIAS/Test/classes/class.ilTestPlayerAbstractGUI.php b/components/ILIAS/Test/classes/class.ilTestPlayerAbstractGUI.php index 0782b087e3ad..10e0c893f330 100755 --- a/components/ILIAS/Test/classes/class.ilTestPlayerAbstractGUI.php +++ b/components/ILIAS/Test/classes/class.ilTestPlayerAbstractGUI.php @@ -84,13 +84,20 @@ public function __construct(ilObjTest $object) public function executeCommand() { - $this->checkReadAccess(); - $this->tabs->clearTargets(); $cmd = $this->ctrl->getCmd(); $next_class = $this->ctrl->getNextClass($this); + if (($read_access = $this->checkReadAccess()) !== true) { + if ($cmd === 'autosave') { + echo $this->lng->txt('autosave_failed') . ': ' . $read_access; + exit; + } + $this->tpl->setOnScreenMessage('failure', $read_access, true); + $this->ctrl->redirectByClass([ilRepositoryGUI::class, ilObjTestGUI::class, TestScreenGUI::class]); + } + $this->ctrl->saveParameter($this, "sequence"); $this->ctrl->saveParameter($this, "pmode"); $this->ctrl->saveParameter($this, "active_id"); @@ -155,39 +162,6 @@ public function executeCommand() $ret = $this->ctrl->forwardCommand($gui); break; - case 'ilassquestionhintrequestgui': - $this->checkTestExecutable(); - - $question_gui = $this->object->createQuestionGUI( - "", - $this->test_sequence->getQuestionForSequence($this->getCurrentSequenceElement()) - ); - - $questionHintTracking = new ilAssQuestionHintTracking( - $question_gui->getObject()->getId(), - $this->test_session->getActiveId(), - $this->test_session->getPass() - ); - - $gui = new ilAssQuestionHintRequestGUI( - $this, - ilTestPlayerCommands::SHOW_QUESTION, - $question_gui, - $questionHintTracking, - $this->ctrl, - $this->lng, - $this->tpl, - $this->tabs, - $this->global_screen - ); - - // fau: testNav - save the 'answer changed' status for viewing hint requests - $this->setAnswerChangedParameter($this->getAnswerChangedParameter()); - // fau. - $ret = $this->ctrl->forwardCommand($gui); - - break; - case 'iltestpasswordprotectiongui': $this->checkTestExecutable(); @@ -204,7 +178,7 @@ public function executeCommand() break; default: - if (ilTestPlayerCommands::isTestExecutionCommand($cmd)) { + if ($cmd !== 'autosave' && ilTestPlayerCommands::isTestExecutionCommand($cmd)) { $this->checkTestExecutable(); } @@ -229,10 +203,10 @@ public function executeCommand() abstract protected function buildTestPassQuestionList(); abstract protected function populateQuestionOptionalMessage(); - protected function checkReadAccess() + protected function checkReadAccess(): bool|string { if (!$this->rbac_system->checkAccess('read', $this->object->getRefId())) { - $this->ilias->raiseError($this->lng->txt('cannot_execute_test'), $this->ilias->error_obj->MESSAGE); + return $this->lng->txt('cannot_execute_test'); } $participant_access = (new ilTestAccess($this->object->getRefId()))->isParticipantAllowed( @@ -240,9 +214,10 @@ protected function checkReadAccess() $this->user->getId() ); if ($participant_access !== ParticipantAccess::ALLOWED) { - $this->tpl->setOnScreenMessage('failure', $participant_access->getAccessForbiddenMessage($this->lng)); - $this->ctrl->redirectByClass([ilRepositoryGUI::class, ilObjTestGUI::class, TestScreenGUI::class]); + return $participant_access->getAccessForbiddenMessage($this->lng); } + + return true; } protected function checkTestExecutable() @@ -546,14 +521,16 @@ private function buildNextButtonInstance($primaryNext) $target = $this->ctrl->getLinkTarget($this, ilTestPlayerCommands::NEXT_QUESTION); if ($primaryNext) { $button = $this->ui_factory->button()->primary( - $this->lng->txt('next_question') . ' ', + $this->lng->txt('next_question') . $this->ui_renderer->render($this->ui_factory->symbol()->glyph()->next()), '' - )->withOnLoadCode($this->getOnLoadCodeForNavigationButtons($target, ilTestPlayerCommands::NEXT_QUESTION)); + )->withUnavailableAction(true) + ->withOnLoadCode($this->getOnLoadCodeForNavigationButtons($target, ilTestPlayerCommands::NEXT_QUESTION)); } else { $button = $this->ui_factory->button()->standard( - $this->lng->txt('next_question') . ' ', + $this->lng->txt('next_question') . $this->ui_renderer->render($this->ui_factory->symbol()->glyph()->next()), '' - )->withOnLoadCode($this->getOnLoadCodeForNavigationButtons($target, ilTestPlayerCommands::NEXT_QUESTION)); + )->withUnavailableAction(true) + ->withOnLoadCode($this->getOnLoadCodeForNavigationButtons($target, ilTestPlayerCommands::NEXT_QUESTION)); } return $button; } @@ -566,9 +543,10 @@ private function buildPreviousButtonInstance() { $target = $this->ctrl->getLinkTarget($this, ilTestPlayerCommands::PREVIOUS_QUESTION); $button = $this->ui_factory->button()->standard( - ' ' . $this->lng->txt('previous_question'), + $this->ui_renderer->render($this->ui_factory->symbol()->glyph()->back()) . $this->lng->txt('previous_question'), '' - )->withOnLoadCode($this->getOnLoadCodeForNavigationButtons($target, ilTestPlayerCommands::PREVIOUS_QUESTION)); + )->withUnavailableAction(true) + ->withOnLoadCode($this->getOnLoadCodeForNavigationButtons($target, ilTestPlayerCommands::PREVIOUS_QUESTION)); return $button; } @@ -577,7 +555,8 @@ private function getOnLoadCodeForNavigationButtons(string $target, string $cmd): return static function (string $id) use ($target, $cmd): string { return "document.getElementById('{$id}').addEventListener('click', " . "(e) => {il.TestPlayerQuestionEditControl.checkNavigation('{$target}', '{$cmd}', e);}" - . ");"; + . "); " + . "document.getElementById('{$id}').removeAttribute('disabled');"; }; } @@ -834,9 +813,9 @@ public function redirectAfterAutosaveCmd(): void $this->redirectAfterFinish(); } - public function redirectAfterDashboardCmd(): void + public function redirectAfterQuestionListCmd(): void { - $this->performTestPassFinishedTasks(StatusOfAttempt::FINISHED_BY_PARTICIPANT); + $this->performTestPassFinishedTasks(StatusOfAttempt::FINISHED_BY_DURATION); $this->redirectAfterFinish(); } @@ -861,6 +840,15 @@ protected function getCurrentQuestionId(): int */ public function autosaveCmd(): void { + if (!$this->access->checkAccess('read', '', $this->ref_id)) { + echo $this->lng->txt('autosave_failed') . ': ' . $this->lng->txt('msg_no_perm_read_item'); + exit; + } + $test_can_run = $this->object->isExecutable($this->test_session, $this->test_session->getUserId()); + if (!$test_can_run['executable']) { + echo $test_can_run['errormessage']; + exit; + } if ($this->testrequest->getPostKeys() === []) { echo ''; exit; @@ -1002,12 +990,12 @@ public function finishTestCmd() protected function performTestPassFinishedTasks(StatusOfAttempt $status_of_attempt): void { - $finishTasks = new ilTestPassFinishTasks( + (new ilTestPassFinishTasks( $this->test_session, - $this->object->getId(), + $this->object, $this->test_pass_result_repository - ); - $finishTasks->performFinishTasks($this->process_locker, $status_of_attempt); + ))->performFinishTasks($this->process_locker, $status_of_attempt); + $this->object->updateTestResultCache($this->test_session->getActiveId(), null); $this->sendNewPassFinishedNotificationEmailIfActivated( $this->test_session->getActiveId(), @@ -1045,9 +1033,10 @@ protected function afterTestPassFinishedCmd() } // redirect after test - $redirection_mode = $this->object->getRedirectionMode(); - $redirection_url = $this->object->getRedirectionUrl(); - if ($redirection_url !== '' && $redirection_mode !== '0') { + $redirection_url = $this->object->getMainSettings()->getFinishingSettings()->getRedirectionUrl(); + if (!$this->object->canShowTestResults($this->test_session) + && $this->object->getMainSettings()->getFinishingSettings()->getRedirectionMode() !== '0' + && $redirection_url !== '') { if ($this->object->isRedirectModeKiosk()) { if ($this->object->getKioskMode()) { ilUtil::redirect($redirection_url); @@ -1107,6 +1096,8 @@ public function showFinalStatementCmd() $this->object->getTitle() . ' - ' . $this->lng->txt('final_statement') ); + $this->content_style->gui()->addCss($this->tpl, $this->ref_id); + $this->ctrl->setParameterByClass(ilTestPageGUI::class, 'page_type', 'concludingremarkspage'); $this->ctrl->setParameterByClass(static::class, 'skipfinalstatement', 1); $this->tpl->setVariable( $this->getContentBlockName(), @@ -1362,7 +1353,7 @@ protected function showQuestionCmd(): void $this->test_sequence->saveToDb(); } - $isQuestionWorkedThrough = $this->questionrepository->lookupResultRecordExist( + $question_worked_through = $this->questionrepository->lookupResultRecordExist( $this->test_session->getActiveId(), $question_id, $this->test_session->getPass() @@ -1370,10 +1361,16 @@ protected function showQuestionCmd(): void $instant_response = false; if ($this->isParticipantsAnswerFixed($question_id)) { - $presentationMode = ilTestPlayerAbstractGUI::PRESENTATION_MODE_VIEW; - $instant_response = true; + $presentation_mode = ilTestPlayerAbstractGUI::PRESENTATION_MODE_VIEW; + $s = $this->object->getMainSettings()->getQuestionBehaviourSettings(); + if ($s->getInstantFeedbackGenericEnabled() + || $s->getInstantFeedbackPointsEnabled() + || $s->getInstantFeedbackSolutionEnabled() + || $s->getInstantFeedbackSpecificEnabled()) { + $instant_response = true; + } } else { - $presentationMode = ilTestPlayerAbstractGUI::PRESENTATION_MODE_EDIT; + $presentation_mode = ilTestPlayerAbstractGUI::PRESENTATION_MODE_EDIT; if (!$this->object->isInstantFeedbackAnswerFixationEnabled()) { $instant_response = $this->getInstantResponseParameter(); } @@ -1388,41 +1385,44 @@ protected function showQuestionCmd(): void $question_gui->setSequenceNumber($this->test_sequence->getPositionOfSequence($sequence_element)); $question_gui->setQuestionCount($this->test_sequence->getUserQuestionCount()); - $headerBlockBuilder = new ilTestQuestionHeaderBlockBuilder($this->lng); - $headerBlockBuilder->setHeaderMode($this->object->getTitleOutput()); - $headerBlockBuilder->setQuestionTitle($question_gui->getObject()->getTitle()); - $headerBlockBuilder->setQuestionPoints($question_gui->getObject()->getPoints()); - $headerBlockBuilder->setQuestionPosition($this->test_sequence->getPositionOfSequence($sequence_element)); - $headerBlockBuilder->setQuestionCount($this->test_sequence->getUserQuestionCount()); - $headerBlockBuilder->setQuestionPostponed($this->test_sequence->isPostponedQuestion($question_id)); + $header_block_builder = new ilTestQuestionHeaderBlockBuilder($this->lng); + $header_block_builder->setHeaderMode($this->object->getTitleOutput()); + $header_block_builder->setQuestionTitle($question_gui->getObject()->getTitleForHTMLOutput()); + $header_block_builder->setQuestionPoints($question_gui->getObject()->getPoints()); + $header_block_builder->setQuestionPosition($this->test_sequence->getPositionOfSequence($sequence_element)); + $header_block_builder->setQuestionCount($this->test_sequence->getUserQuestionCount()); + $header_block_builder->setQuestionPostponed($this->test_sequence->isPostponedQuestion($question_id)); if ($this->test_session->isObjectiveOriented()) { - $objectivesAdapter = ilLOTestQuestionAdapter::getInstance($this->test_session); - $objectivesAdapter->buildQuestionRelatedObjectiveList($this->test_sequence, $this->question_related_objectives_list); + $objectives_adapter = ilLOTestQuestionAdapter::getInstance($this->test_session); + $objectives_adapter->buildQuestionRelatedObjectiveList($this->test_sequence, $this->question_related_objectives_list); $this->question_related_objectives_list->loadObjectivesTitles(); - $objectivesString = $this->question_related_objectives_list->getQuestionRelatedObjectiveTitles($question_id); - $headerBlockBuilder->setQuestionRelatedObjectives($objectivesString); + $header_block_builder->setQuestionRelatedObjectives( + $this->question_related_objectives_list->getQuestionRelatedObjectiveTitles($question_id) + ); } - $question_gui->setQuestionHeaderBlockBuilder($headerBlockBuilder); + $question_gui->setQuestionHeaderBlockBuilder($header_block_builder); - $this->prepareTestPage($presentationMode, $sequence_element, $question_id); + $this->prepareTestPage($presentation_mode, $sequence_element, $question_id); - $navigationToolbarGUI = $this->getTestNavigationToolbarGUI(); - $navigationToolbarGUI->setFinishTestButtonEnabled(true); + $navigation_toolbar_gui = $this->getTestNavigationToolbarGUI(); + $navigation_toolbar_gui->setFinishTestButtonEnabled(true); - $isNextPrimary = $this->handlePrimaryButton($navigationToolbarGUI, $question_id); + $is_next_primary = $this->handlePrimaryButton($navigation_toolbar_gui, $question_id); $this->ctrl->setParameter($this, 'sequence', $sequence_element); - $this->ctrl->setParameter($this, 'pmode', $presentationMode); - $formAction = $this->ctrl->getFormAction($this, ilTestPlayerCommands::SUBMIT_INTERMEDIATE_SOLUTION); + $this->ctrl->setParameter($this, 'pmode', $presentation_mode); + $form_action = $this->ctrl->getFormAction($this, ilTestPlayerCommands::SUBMIT_INTERMEDIATE_SOLUTION); - switch ($presentationMode) { + switch ($presentation_mode) { case ilTestPlayerAbstractGUI::PRESENTATION_MODE_EDIT: - - // fau: testNav - enable navigation toolbar in edit mode - $navigationToolbarGUI->setDisabledStateEnabled(false); - // fau. - $this->showQuestionEditable($question_gui, $formAction, $isQuestionWorkedThrough, $instant_response); + $navigation_toolbar_gui->setDisabledStateEnabled(false); + $this->showQuestionEditable( + $question_gui, + $form_action, + $question_worked_through, + $instant_response + ); if ($this->ctrl->getCmd() !== self::FINISH_TEST_CMD && $this->logger->isLoggingEnabled() @@ -1438,26 +1438,28 @@ protected function showQuestionCmd(): void ) ); } - break; case ilTestPlayerAbstractGUI::PRESENTATION_MODE_VIEW: - if ($this->test_sequence->isQuestionOptional($question_gui->getObject()->getId())) { $this->populateQuestionOptionalMessage(); } - $this->showQuestionViewable($question_gui, $formAction, $isQuestionWorkedThrough, $instant_response); - + $this->showQuestionViewable( + $question_gui, + $form_action, + $question_worked_through, + $instant_response + ); break; default: throw new ilTestException('no presentation mode given'); } - $navigationToolbarGUI->build(); - $this->populateTestNavigationToolbar($navigationToolbarGUI); - $this->populateQuestionNavigation($sequence_element, $isNextPrimary); + $navigation_toolbar_gui->build(); + $this->populateTestNavigationToolbar($navigation_toolbar_gui); + $this->populateQuestionNavigation($sequence_element, $is_next_primary); if ($instant_response) { $this->populateInstantResponseBlocks( @@ -1777,8 +1779,8 @@ public function outProcessingTime(int $active_id): void . ($processing_time_minutes == 1 ? $this->lng->txt("minute") : $this->lng->txt("minutes")); } if ($processing_time_seconds > 0) { - if (strlen($str_processing_time) > 0) { - $str_processing_time .= " " . $this->lng->txt("and") . " "; + if ($str_processing_time !== '') { + $str_processing_time .= ' ' . $this->lng->txt('and') . ' '; } $str_processing_time .= $processing_time_seconds . " " . ($processing_time_seconds == 1 ? $this->lng->txt("second") : $this->lng->txt("seconds")); } @@ -1850,7 +1852,7 @@ public function outProcessingTime(int $active_id): void $template->setVariable("PTIME_M", $processing_time_minutes); $template->setVariable("PTIME_S", $processing_time_seconds); if ($this->ctrl->getCmd() == 'outQuestionSummary') { - $template->setVariable("REDIRECT_URL", $this->ctrl->getFormAction($this, 'redirectAfterDashboardCmd')); + $template->setVariable("REDIRECT_URL", $this->ctrl->getLinkTargetByClass(static::class, ilTestPlayerCommands::REDIRECT_AFTER_QUESTION_LIST)); } else { $template->setVariable("REDIRECT_URL", ""); } @@ -1879,10 +1881,10 @@ protected function showSideList($current_sequence_element): void $active = 0; foreach ($question_summary_data as $idx => $row) { - $title = ilLegacyFormElementsUtil::prepareFormOutput($row['title']); + $title = htmlspecialchars($row['title'], ENT_QUOTES, null, false); $description = ''; if ($row['description'] !== '') { - $description = ' title="' . htmlspecialchars($row['description']) . '" '; + $description = ' title="' . htmlspecialchars($row['description'], ENT_QUOTES, null, false) . '" '; } if (!$row['disabled']) { @@ -2038,7 +2040,7 @@ public function showListOfAnswers($active_id, $pass = null, $top_data = "", $bot $question_gui = $this->object->createQuestionGUI("", $question); $template = new ilTemplate("tpl.il_as_qpl_question_printview.html", true, true, "components/ILIAS/TestQuestionPool"); $template->setVariable("COUNTER_QUESTION", $counter . ". "); - $template->setVariable("QUESTION_TITLE", $question_gui->getObject()->getTitle()); + $template->setVariable("QUESTION_TITLE", $question_gui->getObject()->getTitleForHTMLOutput()); $show_question_only = ($this->object->getShowSolutionAnswersOnly()) ? true : false; $result_output = $question_gui->getSolutionOutput( @@ -2116,33 +2118,6 @@ public function outUserResultsOverviewCmd() ); } - /** - * Go to requested hint list - */ - protected function showRequestedHintListCmd() - { - // fau: testNav - handle intermediate submit for viewing requested hints - $this->handleIntermediateSubmit(); - // fau. - - $this->ctrl->setParameter($this, 'pmode', self::PRESENTATION_MODE_EDIT); - - $this->ctrl->redirectByClass('ilAssQuestionHintRequestGUI', ilAssQuestionHintRequestGUI::CMD_SHOW_LIST); - } - - /** - * Go to hint request confirmation - */ - protected function confirmHintRequestCmd() - { - // fau: testNav - handle intermediate submit for confirming hint requests - $this->handleIntermediateSubmit(); - // fau. - - $this->ctrl->setParameter($this, 'pmode', self::PRESENTATION_MODE_EDIT); - - $this->ctrl->redirectByClass('ilAssQuestionHintRequestGUI', ilAssQuestionHintRequestGUI::CMD_CONFIRM_REQUEST); - } protected function isFirstQuestionInSequence($sequence_element): bool { @@ -2357,14 +2332,9 @@ protected function initAssessmentSettings() /** * @param ilTestSession $test_session */ - protected function handleSkillTriggering(ilTestSession $test_session) + protected function handleSkillTriggering(ilTestSession $test_session): void { - $questionList = $this->buildTestPassQuestionList(); - $questionList->load(); - - $testResults = $this->object->getTestResult($test_session->getActiveId(), $test_session->getPass(), true); - - $skillEvaluation = new ilTestSkillEvaluation( + $skill_evaluation = new ilTestSkillEvaluation( $this->db, $this->logger, $this->object->getTestId(), @@ -2373,19 +2343,26 @@ protected function handleSkillTriggering(ilTestSession $test_session) $this->skills_service->personal() ); - $skillEvaluation->setUserId($test_session->getUserId()); - $skillEvaluation->setActiveId($test_session->getActiveId()); - $skillEvaluation->setPass($test_session->getPass()); + $skill_evaluation->setUserId($test_session->getUserId()); + $skill_evaluation->setActiveId($test_session->getActiveId()); + $skill_evaluation->setPass($test_session->getPass()); - $skillEvaluation->setNumRequiredBookingsForSkillTriggering( + $skill_evaluation->setNumRequiredBookingsForSkillTriggering( $this->object->getGlobalSettings()->getSkillTriggeringNumberOfAnswers() ); + $question_list = $this->buildTestPassQuestionList(); + $question_list->load(); + $skill_evaluation->init($question_list); + $skill_evaluation->evaluate( + $this->object->getTestResult( + $test_session->getActiveId(), + $test_session->getPass(), + true + ) + ); - $skillEvaluation->init($questionList); - $skillEvaluation->evaluate($testResults); - - $skillEvaluation->handleSkillTriggering(); + $skill_evaluation->handleSkillTriggering(); } protected function showAnswerOptionalQuestionsConfirmation() @@ -2497,22 +2474,6 @@ protected function buildEditableStateQuestionNavigationGUI($question_id): ilTest } } - // hints - if ($this->object->isOfferingQuestionHintsEnabled()) { - $activeId = $this->test_session->getActiveId(); - $pass = $this->test_session->getPass(); - - $questionHintTracking = new ilAssQuestionHintTracking($question_id, $activeId, $pass); - - if ($questionHintTracking->requestsPossible()) { - $navigation_gui->setRequestHintCommand(ilTestPlayerCommands::CONFIRM_HINT_REQUEST); - } - - if ($questionHintTracking->requestsExist()) { - $navigation_gui->setShowHintsCommand(ilTestPlayerCommands::SHOW_REQUESTED_HINTS_LIST); - } - } - if ($this->object->getShowMarker()) { $solved_array = ilObjTest::_getSolvedQuestions($this->test_session->getActiveId(), $question_id); $solved = 0; @@ -2957,6 +2918,7 @@ protected function populateQuestionEditControl(assQuestionGUI $question_gui): vo $state = $question_gui->getObject()->lookupForExistingSolutions($this->test_session->getActiveId(), $this->test_session->getPass()); $config['isAnswered'] = $state['authorized']; $config['isAnswerChanged'] = $state['intermediate'] || $this->getAnswerChangedParameter(); + $config['isAnswerFixed'] = $this->isParticipantsAnswerFixed($question_gui->getObject()->getId()); $config['saveOnTimeReachedUrl'] = str_replace('&', '&', $this->ctrl->getFormAction($this, ilTestPlayerCommands::AUTO_SAVE_ON_TIME_LIMIT)); $config['autosaveUrl'] = ''; diff --git a/components/ILIAS/Test/classes/class.ilTestPlayerCommands.php b/components/ILIAS/Test/classes/class.ilTestPlayerCommands.php index 954ac313f7a9..4634946d92b4 100755 --- a/components/ILIAS/Test/classes/class.ilTestPlayerCommands.php +++ b/components/ILIAS/Test/classes/class.ilTestPlayerCommands.php @@ -57,9 +57,6 @@ class ilTestPlayerCommands public const SKIP_QUESTION = 'skipQuestion'; public const SHOW_INSTANT_RESPONSE = 'showInstantResponse'; - public const CONFIRM_HINT_REQUEST = 'confirmHintRequest'; - public const SHOW_REQUESTED_HINTS_LIST = 'showRequestedHintList'; - public const QUESTION_SUMMARY = 'outQuestionSummary'; public const TOGGLE_SIDE_LIST = 'toggleSideList'; @@ -69,6 +66,7 @@ class ilTestPlayerCommands public const AUTO_SAVE = 'autosave'; public const AUTO_SAVE_ON_TIME_LIMIT = 'autosaveOnTimeLimit'; public const REDIRECT_ON_TIME_LIMIT = 'redirectAfterAutosave'; + public const REDIRECT_AFTER_QUESTION_LIST = 'redirectAfterQuestionList'; public const SUSPEND_TEST = 'suspendTest'; public const FINISH_TEST = 'finishTest'; @@ -82,11 +80,13 @@ class ilTestPlayerCommands * @var array */ private static $nonExecutionCommands = [ -// fau: testNav - declare DETECT_CHANGES as non execution command self::DETECT_CHANGES, -// fau. - self::AUTO_SAVE, self::AUTO_SAVE_ON_TIME_LIMIT, self::REDIRECT_ON_TIME_LIMIT, - self::AFTER_TEST_PASS_FINISHED, self::SHOW_FINAL_STATMENT + self::AUTO_SAVE, + self::AUTO_SAVE_ON_TIME_LIMIT, + self::REDIRECT_ON_TIME_LIMIT, + self::AFTER_TEST_PASS_FINISHED, + self::SHOW_FINAL_STATMENT, + self::REDIRECT_AFTER_QUESTION_LIST ]; /** diff --git a/components/ILIAS/Test/classes/class.ilTestPlayerFixedQuestionSetGUI.php b/components/ILIAS/Test/classes/class.ilTestPlayerFixedQuestionSetGUI.php index 91ca2f6e5cb2..b0b1fcdcb1c4 100755 --- a/components/ILIAS/Test/classes/class.ilTestPlayerFixedQuestionSetGUI.php +++ b/components/ILIAS/Test/classes/class.ilTestPlayerFixedQuestionSetGUI.php @@ -23,7 +23,6 @@ * @package components\ILIAS/Test * @ilCtrl_Calls ilTestPlayerFixedQuestionSetGUI: ilAssGenFeedbackPageGUI * @ilCtrl_Calls ilTestPlayerFixedQuestionSetGUI: ilAssSpecFeedbackPageGUI - * @ilCtrl_Calls ilTestPlayerFixedQuestionSetGUI: ilAssQuestionHintRequestGUI * @ilCtrl_Calls ilTestPlayerFixedQuestionSetGUI: ilAssQuestionPageGUI * @ilCtrl_Calls ilTestPlayerFixedQuestionSetGUI: ilTestSubmissionReviewGUI * @ilCtrl_Calls ilTestPlayerFixedQuestionSetGUI: ilTestPasswordProtectionGUI @@ -36,8 +35,7 @@ protected function buildTestPassQuestionList(): ilAssQuestionList { $question_list = new ilAssQuestionList($this->db, $this->lng, $this->refinery, $this->component_repository); $question_list->setParentObjId($this->object->getId()); - $question_list->setQuestionInstanceTypeFilter(ilAssQuestionList::QUESTION_INSTANCE_TYPE_DUPLICATES); - + $question_list->setQuestionInstanceTypeFilter(null); return $question_list; } diff --git a/components/ILIAS/Test/classes/class.ilTestPlayerRandomQuestionSetGUI.php b/components/ILIAS/Test/classes/class.ilTestPlayerRandomQuestionSetGUI.php index e9a584ddd424..72e0a778e84e 100755 --- a/components/ILIAS/Test/classes/class.ilTestPlayerRandomQuestionSetGUI.php +++ b/components/ILIAS/Test/classes/class.ilTestPlayerRandomQuestionSetGUI.php @@ -22,7 +22,6 @@ * @author Björn Heyser * @ilCtrl_Calls ilTestPlayerRandomQuestionSetGUI: ilAssGenFeedbackPageGUI * @ilCtrl_Calls ilTestPlayerRandomQuestionSetGUI: ilAssSpecFeedbackPageGUI - * @ilCtrl_Calls ilTestPlayerRandomQuestionSetGUI: ilAssQuestionHintRequestGUI * @ilCtrl_Calls ilTestPlayerRandomQuestionSetGUI: ilAssQuestionPageGUI * @ilCtrl_Calls ilTestPlayerRandomQuestionSetGUI: ilTestSubmissionReviewGUI * @ilCtrl_Calls ilTestPlayerRandomQuestionSetGUI: ilTestPasswordProtectionGUI diff --git a/components/ILIAS/Test/classes/class.ilTestQuestionNavigationGUI.php b/components/ILIAS/Test/classes/class.ilTestQuestionNavigationGUI.php index 2401fe399381..b506d40478ba 100755 --- a/components/ILIAS/Test/classes/class.ilTestQuestionNavigationGUI.php +++ b/components/ILIAS/Test/classes/class.ilTestQuestionNavigationGUI.php @@ -45,9 +45,6 @@ class ilTestQuestionNavigationGUI private string $instant_feedback_command = ''; private bool $answer_freezing_enabled = false; private bool $force_instant_response_enabled = false; - private string $request_hint_command = ''; - private string $show_hints_command = ''; - private bool $hint_requests_exist = false; private string $question_mark_link_target = ''; private bool $question_marked = false; private bool $anything_rendered = false; @@ -100,21 +97,6 @@ public function setAnswerFreezingEnabled(bool $answer_freezing_enabled): void $this->answer_freezing_enabled = $answer_freezing_enabled; } - public function setRequestHintCommand(string $request_hint_command): void - { - $this->request_hint_command = $request_hint_command; - } - - public function setShowHintsCommand(string $show_hints_command): void - { - $this->show_hints_command = $show_hints_command; - } - - public function setHintRequestsExist(bool $hint_requests_exist): void - { - $this->hint_requests_exist = $hint_requests_exist; - } - public function setQuestionMarkLinkTarget(string $question_mark_link_target): void { $this->question_mark_link_target = $question_mark_link_target; @@ -216,22 +198,6 @@ public function getHTML(): string ); } - if ($this->request_hint_command) { - $this->renderSubmitButton( - $tpl, - $this->request_hint_command, - $this->getRequestHintButtonLabel() - ); - } - - if ($this->show_hints_command) { - $this->renderSubmitButton( - $tpl, - $this->show_hints_command, - $this->lng->txt('show_requested_question_hints') - ); - } - if ($this->anything_rendered) { $this->parseNavigation($tpl); } @@ -257,15 +223,6 @@ private function getCheckButtonLabel(): string return $this->lng->txt('check'); } - private function getRequestHintButtonLabel(): string - { - if ($this->hint_requests_exist) { - return $this->lng->txt('button_request_next_question_hint'); - } - - return $this->lng->txt('button_request_question_hint'); - } - private function getQuestionMarkActionLabel(): string { if ($this->question_marked) { diff --git a/components/ILIAS/Test/classes/class.ilTestResultsGUI.php b/components/ILIAS/Test/classes/class.ilTestResultsGUI.php index 91fce1a41155..cea9b8df6fd5 100755 --- a/components/ILIAS/Test/classes/class.ilTestResultsGUI.php +++ b/components/ILIAS/Test/classes/class.ilTestResultsGUI.php @@ -161,13 +161,10 @@ public function executeCommand(): void case 'iltestskillevaluationgui': $this->test_tabs->activateSubTab(TabsManager::SUBTAB_ID_SKILL_RESULTS); - $questionList = new ilAssQuestionList($this->db, $this->lng, $this->refinery, $this->component_repository); - $questionList->setParentObjId($this->test_object->getId()); - $questionList->setQuestionInstanceTypeFilter(ilAssQuestionList::QUESTION_INSTANCE_TYPE_DUPLICATES); - $questionList->load(); - - $testSessionFactory = new ilTestSessionFactory($this->test_object, $this->db, $this->user); - $testSession = $testSessionFactory->getSession(); + $question_list = new ilAssQuestionList($this->db, $this->lng, $this->refinery, $this->component_repository); + $question_list->setParentObjId($this->test_object->getId()); + $question_list->setQuestionInstanceTypeFilter(null); + $question_list->load(); $gui = new ilTestSkillEvaluationGUI( $this->test_object, @@ -179,8 +176,14 @@ public function executeCommand(): void $this->skills_service, $this->testrequest ); - $gui->setQuestionList($questionList); - $gui->setTestSession($testSession); + $gui->setQuestionList($question_list); + $gui->setTestSession( + (new ilTestSessionFactory( + $this->test_object, + $this->db, + $this->user + ))->getSession() + ); $gui->setObjectiveOrientedContainer($this->objective_parent); $this->ctrl->forwardCommand($gui); diff --git a/components/ILIAS/Test/classes/class.ilTestResultsImportParser.php b/components/ILIAS/Test/classes/class.ilTestResultsImportParser.php index db8952efccc5..c55ab970e300 100755 --- a/components/ILIAS/Test/classes/class.ilTestResultsImportParser.php +++ b/components/ILIAS/Test/classes/class.ilTestResultsImportParser.php @@ -1,4 +1,5 @@ table = ''; $this->active_id_mapping = []; $this->question_id_mapping = []; - $this->user_criteria_checked = false; $this->src_pool_def_id_mapping = []; + $this->import_directory = dirname($a_xml_file); } /** @@ -86,9 +92,8 @@ public function setSrcPoolDefIdMapping(array $src_pool_def_id_mapping): void */ public function setHandlers($a_xml_parser): void { - xml_set_object($a_xml_parser, $this); - xml_set_element_handler($a_xml_parser, 'handlerBeginTag', 'handlerEndTag'); - xml_set_character_data_handler($a_xml_parser, 'handlerParseCharacterData'); + xml_set_element_handler($a_xml_parser, $this->handlerBeginTag(...), $this->handlerEndTag(...)); + xml_set_character_data_handler($a_xml_parser, $this->handlerParseCharacterData(...)); } /** @@ -228,7 +233,7 @@ public function handlerBeginTag($a_xml_parser, $a_name, $a_attribs): void "solution_id" => ["integer", $next_id], "active_fi" => ["integer", $this->active_id_mapping[$a_attribs['active_fi']]], "question_fi" => ["integer", $this->question_id_mapping[$a_attribs['question_fi']]], - "value1" => ["clob", (strlen($a_attribs['value1'])) ? $a_attribs['value1'] : null], + "value1" => ["clob", $this->importParticipantsUploadedFiles($a_attribs)], "value2" => ["clob", (strlen($a_attribs['value2'])) ? $a_attribs['value2'] : null], "pass" => ["integer", $a_attribs['pass']], "tstamp" => ["integer", $a_attribs['tstamp']] @@ -315,4 +320,40 @@ private function fetchLastStartedPass($attribs): ?int return null; } + + /** + * @param array{value1: ?string, value2: ?string} $a_attribs + * @return ?string + */ + private function importParticipantsUploadedFiles(array $a_attribs): ?string + { + ['value1' => $value1, 'value2' => $value2] = $a_attribs; + + if ($value2 !== 'rid') { + return $value1 !== '' ? $value1 : null; + } + + $resource_directory = "$this->import_directory/objects/resources/$value1"; + $file_name = $this->getFirstFileName($resource_directory); + if (!is_string($file_name)) { + return $value1 !== '' ? $value1 : null; + } + + $file_path = "$resource_directory/$file_name"; + $new_rid = $this->irss->manage()->stream( + new Stream(fopen($file_path, 'rwb')), + new assFileUploadStakeholder(), + basename($file_path), + ); + return $new_rid->serialize(); + } + + private function getFirstFileName(string $resource_directory): mixed + { + $entries = array_filter( + scandir($resource_directory), + static fn(string $entry): bool => is_file("$resource_directory/$entry") && !in_array($entry, ['.', '..']), + ); + return array_shift($entries); + } } diff --git a/components/ILIAS/Test/classes/class.ilTestResultsToXML.php b/components/ILIAS/Test/classes/class.ilTestResultsToXML.php index 33359e54dcb4..9c9453c76e14 100755 --- a/components/ILIAS/Test/classes/class.ilTestResultsToXML.php +++ b/components/ILIAS/Test/classes/class.ilTestResultsToXML.php @@ -18,6 +18,8 @@ declare(strict_types=1); +use ILIAS\ResourceStorage\Services as ResourceStorage; + class ilTestResultsToXML extends ilXmlWriter { private $active_ids; @@ -27,6 +29,8 @@ class ilTestResultsToXML extends ilXmlWriter public function __construct( private int $test_id, private ilDBInterface $db, + private ResourceStorage $irss, + private string $objects_export_directory, private bool $anonymized = false ) { parent::__construct(); @@ -200,20 +204,52 @@ protected function exportTestResults(): void $result = $this->db->query($query); $this->xmlStartTag('tst_test_result', null); while ($row = $this->db->fetchAssoc($result)) { + $active_fi = $row['active_fi']; + $pass = $row['pass'] ?? ''; $attrs = [ 'test_result_id' => $row['test_result_id'], - 'active_fi' => $row['active_fi'], + 'active_fi' => $active_fi, 'question_fi' => $row['question_fi'], 'points' => $row['points'] ?? '', - 'pass' => $row['pass'] ?? '', + 'pass' => $pass, 'manual' => $row['manual'] ?? '', 'tstamp' => $row['tstamp'] ?? '' ]; + + if (($question = assQuestion::instantiateQuestion($row['question_fi'])) instanceof assFileUpload) { + $this->exportParticipantUploadedFiles($question->getUploadedFiles($active_fi, $pass)); + } + $this->xmlElement('row', $attrs); } $this->xmlEndTag('tst_test_result'); } + /** + * @param array{value1: string, value2: string} $uploaded_files + */ + protected function exportParticipantUploadedFiles(array $uploaded_files): void + { + foreach ($uploaded_files as $uploaded_file) { + if ($uploaded_file['value2'] !== 'rid') { + continue; + } + + $rid_string = $uploaded_file['value1']; + $rid = $this->irss->manage()->find($rid_string); + if ($rid === null) { + continue; + } + + $target_dir = "$this->objects_export_directory/resources/$rid_string"; + ilFileUtils::makeDirParents($target_dir); + file_put_contents( + "$target_dir/{$this->irss->manage()->getCurrentRevision($rid)->getTitle()}", + $this->irss->consume()->stream($rid)->getStream(), + ); + } + } + protected function exportTestTimes(): void { $query = 'SELECT * FROM tst_times WHERE ' . $this->db->in('active_fi', $this->active_ids, false, 'integer') . ' ORDER BY active_fi'; diff --git a/components/ILIAS/Test/classes/class.ilTestSequence.php b/components/ILIAS/Test/classes/class.ilTestSequence.php index bad9a9cd5723..dfc4e06bcc74 100755 --- a/components/ILIAS/Test/classes/class.ilTestSequence.php +++ b/components/ILIAS/Test/classes/class.ilTestSequence.php @@ -620,7 +620,7 @@ public function getSequenceSummary(): array $result_array[] = [ 'nr' => $key, - 'title' => $question->getTitle(), + 'title' => $question->getTitleForHTMLOutput(), 'qid' => $question->getId(), 'presented' => $this->isQuestionPresented($question->getId()), 'visited' => $worked_through, diff --git a/components/ILIAS/Test/classes/class.ilTestServiceGUI.php b/components/ILIAS/Test/classes/class.ilTestServiceGUI.php index ee375d3ebc59..1353ee8d9d2a 100755 --- a/components/ILIAS/Test/classes/class.ilTestServiceGUI.php +++ b/components/ILIAS/Test/classes/class.ilTestServiceGUI.php @@ -33,6 +33,7 @@ use ILIAS\Refinery\Factory as Refinery; use ILIAS\HTTP\Wrapper\ArrayBasedRequestWrapper; use ILIAS\Skill\Service\SkillService; +use ILIAS\Style\Content\Service as ContentStyle; /** * Service GUI class for tests. This class is the parent class for all @@ -66,6 +67,7 @@ class ilTestServiceGUI * `ilTestPlayerAbstractGUI::populateIntantResponseModal()`. */ protected ilGlobalTemplateInterface|ilTemplate $tpl; + protected readonly ContentStyle $content_style; protected readonly ilErrorHandling $error; protected ilAccess $access; protected readonly HTTPServices $http; @@ -125,6 +127,7 @@ public function __construct( global $DIC; $this->lng = $DIC['lng']; $this->tpl = $DIC['tpl']; + $this->content_style = $DIC->contentStyle(); $this->error = $DIC['ilErr']; $this->access = $DIC['ilAccess']; $this->http = $DIC['http']; @@ -202,10 +205,6 @@ public function getPassOverviewTableData( $scored_pass = \ilObjTest::_getResultPass($test_session->getActiveId()); - $question_hint_request_register = ilAssQuestionHintTracking::getRequestRequestStatisticDataRegisterByActiveId( - $test_session->getActiveId() - ); - foreach ($passes as $pass) { $row = [ 'scored' => false, @@ -245,24 +244,6 @@ public function getPassOverviewTableData( $considerOptionalQuestions ); - foreach ($result_array as $result_struct_key => $question) { - if ($result_struct_key === 'test' - || $result_struct_key === 'pass' - || $result_array[$result_struct_key]['requested_hints'] !== null) { - continue; - } - - $request_data = $question_hint_request_register->getRequestByTestPassIndexAndQuestionId($pass, $question['qid']); - - if ($request_data === null) { - continue; - } - - $result_array['pass']['total_requested_hints'] += $request_data->getRequestsCount(); - $result_array[$result_struct_key]['requested_hints'] = $request_data->getRequestsCount(); - $result_array[$result_struct_key]['hint_points'] = $request_data->getRequestsPoints(); - } - if (!$result_array['pass']['total_max_points']) { $row['percentage'] = 0; } else { @@ -274,11 +255,6 @@ public function getPassOverviewTableData( $row['scored'] = ($pass == $scored_pass); $row['num_workedthrough_questions'] = $result_array['pass']['num_workedthrough']; $row['num_questions_total'] = $result_array['pass']['num_questions_total']; - - if ($this->object->isOfferingQuestionHintsEnabled()) { - $row['hints'] = $result_array['pass']['total_requested_hints']; - } - $data[] = $row; } @@ -400,7 +376,7 @@ public function getPassListOfAnswers( $template->setVariable("COUNTER_QUESTION", $counter . ". "); $template->setVariable("TXT_QUESTION_ID", $this->lng->txt('question_id_short')); $template->setVariable("QUESTION_ID", $question_gui->getObject()->getId()); - $template->setVariable("QUESTION_TITLE", $this->object->getQuestionTitle($question_gui->getObject()->getTitle())); + $template->setVariable("QUESTION_TITLE", $this->object->getQuestionTitle($question_gui->getObject()->getTitleForHTMLOutput())); if ($objectives_list !== null) { $objectives = $this->lng->txt('tst_res_lo_objectives_header') . ': '; @@ -471,7 +447,6 @@ protected function getPassDetailsOverviewTableGUI( $this->ctrl->setParameter($target_gui, 'pass', $pass); $table_gui = $this->buildPassDetailsOverviewTableGUI($target_gui, $target_cmd); - $table_gui->setShowHintCount($this->object->isOfferingQuestionHintsEnabled()); if ($objectives_list !== null) { $table_gui->setQuestionRelatedObjectivesList($objectives_list); @@ -627,9 +602,9 @@ public function getCorrectSolutionOutput($question_id, $active_id, $pass, ?ilTes // I set both old and new since the old one is set as well in several places. $maxpoints = $question_gui->getObject()->getMaximumPoints(); if ($maxpoints == 1) { - $template->setVariable("QUESTION_TITLE", $this->object->getQuestionTitle($question_gui->getObject()->getTitle()) . " (" . $maxpoints . " " . $this->lng->txt("point") . ")"); + $template->setVariable("QUESTION_TITLE", $this->object->getQuestionTitle($question_gui->getObject()->getTitleForHTMLOutput()) . " (" . $maxpoints . " " . $this->lng->txt("point") . ")"); } else { - $template->setVariable("QUESTION_TITLE", $this->object->getQuestionTitle($question_gui->getObject()->getTitle()) . " (" . $maxpoints . " " . $this->lng->txt("points") . ")"); + $template->setVariable("QUESTION_TITLE", $this->object->getQuestionTitle($question_gui->getObject()->getTitleForHTMLOutput()) . " (" . $maxpoints . " " . $this->lng->txt("points") . ")"); } if ($objectives_list !== null) { $objectives = $this->lng->txt('tst_res_lo_objectives_header') . ': '; @@ -652,15 +627,15 @@ protected function buildPassDetailsOverviewTableGUI( protected function isGradingMessageRequired(): bool { - if ($this->getObjectiveOrientedContainer()->isObjectiveOrientedPresentationRequired()) { + $session = $this->test_session_factory->getSession(); + if ($this->getObjectiveOrientedContainer()->isObjectiveOrientedPresentationRequired() + || $this->object->getScoreSettings()->getScoringSettings()->getPassScoring() === ilObjTest::SCORE_LAST_PASS + && $session->getLastFinishedPass() < $session->getLastStartedPass()) { return false; } - if ($this->object->isShowGradingStatusEnabled()) { - return true; - } - - if ($this->object->isShowGradingMarkEnabled()) { + if ($this->object->isShowGradingStatusEnabled() + || $this->object->isShowGradingMarkEnabled()) { return true; } @@ -687,58 +662,6 @@ protected function buildQuestionRelatedObjectivesList( return $questionRelatedObjectivesList; } - protected function getFilteredTestResult( - int $active_id, - int $pass, - bool $considerHiddenQuestions, - bool $considerOptionalQuestions - ): array { - $ilDB = $this->db; - $component_repository = $this->component_repository; - - $table_gui = $this->buildPassDetailsOverviewTableGUI($this, 'outUserPassDetails'); - - $questionList = new ilAssQuestionList($ilDB, $this->lng, $this->refinery, $component_repository); - - $questionList->setParentObjIdsFilter([$this->object->getId()]); - $questionList->setQuestionInstanceTypeFilter(ilAssQuestionList::QUESTION_INSTANCE_TYPE_DUPLICATES); - - foreach ($table_gui->getFilterItems() as $item) { - if (substr($item->getPostVar(), 0, strlen('tax_')) == 'tax_') { - $v = $item->getValue(); - - if (is_array($v) && count($v) && !(int) $v[0]) { - continue; - } - - $taxId = substr($item->getPostVar(), strlen('tax_')); - $questionList->addTaxonomyFilter($taxId, $item->getValue(), $this->object->getId(), 'tst'); - } elseif ($item->getValue() !== false) { - $questionList->addFieldFilter($item->getPostVar(), $item->getValue()); - } - } - - $questionList->load(); - - $filteredTestResult = []; - - $resultData = $this->object->getTestResult($active_id, $pass, false, $considerHiddenQuestions, $considerOptionalQuestions); - - foreach ($resultData as $resultItemKey => $resultItemValue) { - if ($resultItemKey === 'test' || $resultItemKey === 'pass') { - continue; - } - - if (!$questionList->isInList($resultItemValue['qid'])) { - continue; - } - - $filteredTestResult[] = $resultItemValue; - } - - return $filteredTestResult; - } - protected function populateContent(string $content): void { $this->tpl->setContent($content); diff --git a/components/ILIAS/Test/classes/class.ilTestSkillEvaluation.php b/components/ILIAS/Test/classes/class.ilTestSkillEvaluation.php index 0ac3c1705253..88c5acdfb1e7 100755 --- a/components/ILIAS/Test/classes/class.ilTestSkillEvaluation.php +++ b/components/ILIAS/Test/classes/class.ilTestSkillEvaluation.php @@ -32,17 +32,17 @@ */ class ilTestSkillEvaluation { - private ilAssQuestionSkillAssignmentList $skillQuestionAssignmentList; - private ilTestSkillLevelThresholdList $skillLevelThresholdList; + private ilAssQuestionSkillAssignmentList $skill_question_assignment_list; + private ilTestSkillLevelThresholdList $skill_level_threshold_list; private array $questions = []; - private array $maxPointsByQuestion = []; - private array $reachedPointsByQuestion; - private array $skillPointAccounts; - private array $reachedSkillLevels; - private int $userId; - private int $activeId; + private array $max_points_by_question = []; + private array $reached_points_by_question; + private array $skill_point_accounts; + private array $reached_skill_levels; + private int $user_id; + private int $active_id; private int $pass; - private int $numRequiredBookingsForSkillTriggering; + private int $num_required_bookings_for_skill_triggering; public function __construct( @@ -53,30 +53,30 @@ public function __construct( private SkillProfileService $skill_profile_service, private SkillPersonalService $skill_personal_service ) { - $this->skillQuestionAssignmentList = new ilAssQuestionSkillAssignmentList($this->db); + $this->skill_question_assignment_list = new ilAssQuestionSkillAssignmentList($this->db); - $this->skillLevelThresholdList = new ilTestSkillLevelThresholdList($this->db); - $this->skillLevelThresholdList->setTestId($test_id); + $this->skill_level_threshold_list = new ilTestSkillLevelThresholdList($this->db); + $this->skill_level_threshold_list->setTestId($test_id); } public function getUserId(): int { - return $this->userId; + return $this->user_id; } - public function setUserId($userId) + public function setUserId(int $user_id): void { - $this->userId = $userId; + $this->user_id = $user_id; } public function getActiveId(): int { - return $this->activeId; + return $this->active_id; } - public function setActiveId($activeId) + public function setActiveId(int $active_id): void { - $this->activeId = $activeId; + $this->active_id = $active_id; } public function getPass(): int @@ -84,39 +84,39 @@ public function getPass(): int return $this->pass; } - public function setPass($pass) + public function setPass($pass): void { $this->pass = $pass; } public function getNumRequiredBookingsForSkillTriggering(): int { - return $this->numRequiredBookingsForSkillTriggering; + return $this->num_required_bookings_for_skill_triggering; } - public function setNumRequiredBookingsForSkillTriggering(int $numRequiredBookingsForSkillTriggering): void + public function setNumRequiredBookingsForSkillTriggering(int $num_required_bookings_for_skill_triggering): void { - $this->numRequiredBookingsForSkillTriggering = $numRequiredBookingsForSkillTriggering; + $this->num_required_bookings_for_skill_triggering = $num_required_bookings_for_skill_triggering; } - public function init(ilAssQuestionList $questionList) + public function init(ilAssQuestionList $question_list): void { - $this->skillQuestionAssignmentList->setParentObjId($questionList->getParentObjId()); - $this->skillQuestionAssignmentList->loadFromDb(); + $this->skill_question_assignment_list->setParentObjId($question_list->getParentObjId()); + $this->skill_question_assignment_list->loadFromDb(); - $this->skillLevelThresholdList->loadFromDb(); + $this->skill_level_threshold_list->loadFromDb(); - $this->initTestQuestionData($questionList); + $this->initTestQuestionData($question_list); } /** - * @param array $testResults An array containing the test results for a given user + * @param array $test_results An array containing the test results for a given user */ - public function evaluate(array $testResults): void + public function evaluate(array $test_results): void { $this->reset(); - $this->initTestResultData($testResults); + $this->initTestResultData($test_results); $this->drawUpSkillPointAccounts(); $this->evaluateSkillPointAccounts(); @@ -124,61 +124,56 @@ public function evaluate(array $testResults): void public function getReachedSkillLevels(): array { - return $this->reachedSkillLevels; + return $this->reached_skill_levels; } - private function reset() + private function reset(): void { - $this->reachedPointsByQuestion = []; - $this->skillPointAccounts = []; - $this->reachedSkillLevels = []; + $this->reached_points_by_question = []; + $this->skill_point_accounts = []; + $this->reached_skill_levels = []; } - private function initTestQuestionData(ilAssQuestionList $questionList) + private function initTestQuestionData(ilAssQuestionList $question_list): void { - foreach ($questionList->getQuestionDataArray() as $questionData) { - $this->questions[] = $questionData['question_id']; - - $this->maxPointsByQuestion[ $questionData['question_id'] ] = $questionData['points']; + foreach ($question_list->getQuestionDataArray() as $question_data) { + $this->questions[] = $question_data['question_id']; + $this->max_points_by_question[ $question_data['question_id'] ] = $question_data['points']; } } - /** - * @param array $testResults - */ - private function initTestResultData($testResults) + private function initTestResultData(array $test_results): void { - foreach ($testResults as $key => $result) { + foreach ($test_results as $key => $result) { if ($key === 'pass' || $key === 'test') { // note: key int 0 IS == 'pass' or 'buxtehude' continue; } - $this->reachedPointsByQuestion[ $result['qid'] ] = $result['reached']; + $this->reached_points_by_question[ $result['qid'] ] = $result['reached']; } } - private function drawUpSkillPointAccounts() + private function drawUpSkillPointAccounts(): void { - foreach ($this->questions as $questionId) { - if (!$this->isAnsweredQuestion($questionId)) { + foreach ($this->questions as $question_id) { + if (!$this->isAnsweredQuestion($question_id)) { continue; } - $assignments = $this->skillQuestionAssignmentList->getAssignmentsByQuestionId($questionId); + $assignments = $this->skill_question_assignment_list->getAssignmentsByQuestionId($question_id); foreach ($assignments as $assignment) { if ($assignment->hasEvalModeBySolution()) { - $reachedSkillPoints = $this->determineReachedSkillPointsWithSolutionCompare( + $reached_skill_points = $this->determineReachedSkillPointsWithSolutionCompare( $assignment->getSolutionComparisonExpressionList() ); } else { - $maxTestPoints = $this->maxPointsByQuestion[$questionId]; - $reachedTestPoints = $this->reachedPointsByQuestion[$questionId]; - - $reachedSkillPoints = $this->calculateReachedSkillPointsFromTestPoints( + $max_test_points = $this->max_points_by_question[$question_id]; + $reached_test_points = $this->reached_points_by_question[$question_id]; + $reached_skill_points = $this->calculateReachedSkillPointsFromTestPoints( $assignment->getSkillPoints(), - $maxTestPoints, - $reachedTestPoints + $max_test_points, + $reached_test_points ); } @@ -186,34 +181,34 @@ private function drawUpSkillPointAccounts() $assignment->getSkillBaseId(), $assignment->getSkillTrefId(), $assignment->getMaxSkillPoints(), - $reachedSkillPoints + $reached_skill_points ); } } } - private function isAnsweredQuestion($questionId): bool + private function isAnsweredQuestion(int $question_id): bool { - return isset($this->reachedPointsByQuestion[$questionId]); + return isset($this->reached_points_by_question[$question_id]); } - private function determineReachedSkillPointsWithSolutionCompare(ilAssQuestionSolutionComparisonExpressionList $expressionList): ?int - { - $questionProvider = new ilAssLacQuestionProvider(); - $questionProvider->setQuestionId($expressionList->getQuestionId()); - - foreach ($expressionList->get() as $expression) { - /* @var ilAssQuestionSolutionComparisonExpression $expression */ + private function determineReachedSkillPointsWithSolutionCompare( + ilAssQuestionSolutionComparisonExpressionList $expression_list + ): ?int { + $question_provider = new ilAssLacQuestionProvider(); + $question_provider->setQuestionId($expression_list->getQuestionId()); - $conditionParser = new ilAssLacConditionParser(); - $conditionComposite = $conditionParser->parse($expression->getExpression()); + foreach ($expression_list->get() as $expression) { + $condition_composite = (new ilAssLacConditionParser())->parse( + $expression->getExpression() + ); - $compositeEvaluator = new ilAssLacCompositeEvaluator( - $questionProvider, + $composite_evaluator = new ilAssLacCompositeEvaluator( + $question_provider, $this->getActiveId(), $this->getPass() ); - if ($compositeEvaluator->evaluate($conditionComposite)) { + if ($composite_evaluator->evaluate($condition_composite)) { return $expression->getPoints(); } } @@ -221,65 +216,69 @@ private function determineReachedSkillPointsWithSolutionCompare(ilAssQuestionSol return 0; } - private function calculateReachedSkillPointsFromTestPoints($skillPoints, $maxTestPoints, $reachedTestPoints) - { - if ($reachedTestPoints < 0) { - $reachedTestPoints = 0; + private function calculateReachedSkillPointsFromTestPoints( + int $skill_points, + float $max_test_points, + float $reached_test_points + ): float { + if ($reached_test_points < 0) { + $reached_test_points = 0; } $factor = 0; - if ($maxTestPoints > 0) { - $factor = $reachedTestPoints / $maxTestPoints; + if ($max_test_points > 0) { + $factor = $reached_test_points / $max_test_points; } - return ($skillPoints * $factor); + return ($skill_points * $factor); } - private function bookToSkillPointAccount($skillBaseId, $skillTrefId, $maxSkillPoints, $reachedSkillPoints) - { - $skillKey = $skillBaseId . ':' . $skillTrefId; + private function bookToSkillPointAccount( + int $skill_base_id, + int $skill_tref_id, + int $max_skill_points, + float $reached_skill_points + ): void { + $skill_key = $skill_base_id . ':' . $skill_tref_id; - if (!isset($this->skillPointAccounts[$skillKey])) { - $this->skillPointAccounts[$skillKey] = new ilTestSkillPointAccount(); + if (!isset($this->skill_point_accounts[$skill_key])) { + $this->skill_point_accounts[$skill_key] = new ilTestSkillPointAccount(); } - $this->skillPointAccounts[$skillKey]->addBooking($maxSkillPoints, $reachedSkillPoints); + $this->skill_point_accounts[$skill_key]->addBooking($max_skill_points, $reached_skill_points); } - private function evaluateSkillPointAccounts() + private function evaluateSkillPointAccounts(): void { - foreach ($this->skillPointAccounts as $skillKey => $skillPointAccount) { - if (!$this->doesNumBookingsExceedRequiredBookingsBarrier($skillPointAccount)) { + foreach ($this->skill_point_accounts as $skill_key => $skill_point_account) { + if (!$this->doesNumBookingsExceedRequiredBookingsBarrier($skill_point_account)) { continue; } - list($skillBaseId, $skillTrefId) = explode(':', $skillKey); + list($skill_base_id, $skill_tref_id) = explode(':', $skill_key); - $skill = new ilBasicSkill((int) $skillBaseId); + $skill = new ilBasicSkill((int) $skill_base_id); $levels = $skill->getLevelData(); - $reachedLevelId = null; - + $reached_level_id = null; foreach ($levels as $level) { - $threshold = $this->skillLevelThresholdList->getThreshold($skillBaseId, $skillTrefId, $level['id']); + $threshold = $this->skill_level_threshold_list->getThreshold($skill_base_id, $skill_tref_id, $level['id']); - if (!($threshold instanceof ilTestSkillLevelThreshold) || !$threshold->getThreshold()) { + if (!($threshold instanceof ilTestSkillLevelThreshold) || $threshold->getThreshold() === null) { continue; } - if ($skillPointAccount->getTotalReachedSkillPercent() < $threshold->getThreshold()) { + if ($skill_point_account->getTotalReachedSkillPercent() < $threshold->getThreshold()) { break; } - $reachedLevelId = $level['id']; + $reached_level_id = $level['id']; } - if ($reachedLevelId) { - $this->reachedSkillLevels[] = [ - 'sklBaseId' => $skillBaseId, 'sklTrefId' => $skillTrefId, 'sklLevelId' => $reachedLevelId - ]; - } + $this->reached_skill_levels[] = [ + 'sklBaseId' => $skill_base_id, 'sklTrefId' => $skill_tref_id, 'sklLevelId' => $reached_level_id + ]; } } @@ -288,28 +287,28 @@ private function doesNumBookingsExceedRequiredBookingsBarrier(ilTestSkillPointAc return $skillPointAccount->getNumBookings() >= $this->getNumRequiredBookingsForSkillTriggering(); } - public function handleSkillTriggering() + public function handleSkillTriggering(): void { - foreach ($this->getReachedSkillLevels() as $reachedSkillLevel) { - $this->invokeSkillLevelTrigger((int) $reachedSkillLevel['sklLevelId'], (int) $reachedSkillLevel['sklTrefId']); + foreach ($this->getReachedSkillLevels() as $reached_skill_level) { + $this->invokeSkillLevelTrigger((int) $reached_skill_level['sklLevelId'], (int) $reached_skill_level['sklTrefId']); - if ($reachedSkillLevel['sklTrefId'] > 0) { - $this->skill_personal_service->addPersonalSkill($this->getUserId(), (int) $reachedSkillLevel['sklTrefId']); + if ($reached_skill_level['sklTrefId'] > 0) { + $this->skill_personal_service->addPersonalSkill($this->getUserId(), (int) $reached_skill_level['sklTrefId']); } else { - $this->skill_personal_service->addPersonalSkill($this->getUserId(), (int) $reachedSkillLevel['sklBaseId']); + $this->skill_personal_service->addPersonalSkill($this->getUserId(), (int) $reached_skill_level['sklBaseId']); } } //write profile completion entries if fulfilment status has changed $this->skill_profile_service->writeCompletionEntryForAllProfiles($this->getUserId()); } - private function invokeSkillLevelTrigger(int $skillLevelId, int $skillTrefId) + private function invokeSkillLevelTrigger(int $skill_level_id, int $skill_tref_id): void { ilBasicSkill::writeUserSkillLevelStatus( - $skillLevelId, + $skill_level_id, $this->getUserId(), $this->refId, - $skillTrefId, + $skill_tref_id, ilBasicSkill::ACHIEVED, true, false, @@ -317,89 +316,88 @@ private function invokeSkillLevelTrigger(int $skillLevelId, int $skillTrefId) ); $this->logger->info( - "refId={$this->refId} / usrId={$this->getUserId()} / levelId={$skillLevelId} / trefId={$skillTrefId}" + "refId={$this->refId} / usrId={$this->getUserId()} / levelId={$skill_level_id} / trefId={$skill_tref_id}" ); } public function getSkillsMatchingNumAnswersBarrier(): array { - $skillsMatchingNumAnswersBarrier = []; + $skills_matching_num_answers_barrier = []; - foreach ($this->skillPointAccounts as $skillKey => $skillPointAccount) { + foreach ($this->skill_point_accounts as $skillKey => $skillPointAccount) { if ($this->doesNumBookingsExceedRequiredBookingsBarrier($skillPointAccount)) { list($skillBaseId, $skillTrefId) = explode(':', $skillKey); - $skillsMatchingNumAnswersBarrier[$skillKey] = [ + $skills_matching_num_answers_barrier[$skillKey] = [ 'base_skill_id' => (int) $skillBaseId, 'tref_id' => (int) $skillTrefId ]; } } - return $skillsMatchingNumAnswersBarrier; + return $skills_matching_num_answers_barrier; } public function getSkillsInvolvedByAssignment(): array { - $uniqueSkills = []; + $unique_skills = []; - foreach ($this->skillQuestionAssignmentList->getUniqueAssignedSkills() as $skill) { + foreach ($this->skill_question_assignment_list->getUniqueAssignedSkills() as $skill) { $skillKey = $skill['skill_base_id'] . ':' . $skill['skill_tref_id']; - $uniqueSkills[$skillKey] = [ + $unique_skills[$skillKey] = [ 'base_skill_id' => (int) $skill['skill_base_id'], 'tref_id' => (int) $skill['skill_tref_id'] ]; } - return $uniqueSkills; + return $unique_skills; } - public function isAssignedSkill($skillBaseId, $skillTrefId) + public function isAssignedSkill($skill_base_id, $skill_tref_id): void { - $this->skillQuestionAssignmentList->isAssignedSkill($skillBaseId, $skillTrefId); + $this->skill_question_assignment_list->isAssignedSkill($skill_base_id, $skill_tref_id); } public function getAssignedSkillMatchingSkillProfiles(): array { - $matchingSkillProfiles = []; - - $usersProfiles = $this->skill_profile_service->getProfilesOfUser($this->getUserId()); + $matching_skill_profiles = []; + $users_profiles = $this->skill_profile_service->getProfilesOfUser($this->getUserId()); + foreach ($users_profiles as $profile_data) { + $assigned_skill_levels = $this->skill_profile_service->getSkillLevels($profile_data->getId()); - foreach ($usersProfiles as $profileData) { - $assignedSkillLevels = $this->skill_profile_service->getSkillLevels($profileData->getId()); + foreach ($assigned_skill_levels as $assigned_skill_level) { + $skill_base_id = $assigned_skill_level->getBaseSkillId(); + $skill_tref_id = $assigned_skill_level->getTrefId(); - foreach ($assignedSkillLevels as $assignedSkillLevel) { - $skillBaseId = $assignedSkillLevel->getBaseSkillId(); - $skillTrefId = $assignedSkillLevel->getTrefId(); - - if ($this->skillQuestionAssignmentList->isAssignedSkill($skillBaseId, $skillTrefId)) { - $matchingSkillProfiles[$profileData->getId()] = $profileData->getTitle(); + if ($this->skill_question_assignment_list->isAssignedSkill($skill_base_id, $skill_tref_id)) { + $matching_skill_profiles[$profile_data->getId()] = $profile_data->getTitle(); } } } - return $matchingSkillProfiles; + return $matching_skill_profiles; } - public function noProfileMatchingAssignedSkillExists(array $availableSkillProfiles): bool + public function noProfileMatchingAssignedSkillExists(array $available_skill_profiles): bool { - $noProfileMatchingSkills = $this->skillQuestionAssignmentList->getUniqueAssignedSkills(); + $no_profile_matching_skills = $this->skill_question_assignment_list->getUniqueAssignedSkills(); - foreach ($availableSkillProfiles as $skillProfileId => $skillProfileTitle) { - $profile = $this->skill_profile_service->getProfile($skillProfileId); - $assignedSkillLevels = $this->skill_profile_service->getSkillLevels($profile->getId()); + foreach (array_keys($available_skill_profiles) as $skill_profile_id) { + $assigned_skill_levels = $this->skill_profile_service->getSkillLevels( + $this->skill_profile_service->getProfile($skill_profile_id)->getId() + ); - foreach ($assignedSkillLevels as $assignedSkillLevel) { - $skillBaseId = $assignedSkillLevel->getBaseSkillId(); - $skillTrefId = $assignedSkillLevel->getTrefId(); + foreach ($assigned_skill_levels as $assigned_skill_level) { + $skill_base_id = $assigned_skill_level->getBaseSkillId(); + $skill_tref_id = $assigned_skill_level->getTrefId(); - if ($this->skillQuestionAssignmentList->isAssignedSkill($skillBaseId, $skillTrefId)) { - unset($noProfileMatchingSkills["{$skillBaseId}:{$skillTrefId}"]); + if ($this->skill_question_assignment_list->isAssignedSkill($skill_base_id, $skill_tref_id)) { + unset($no_profile_matching_skills["{$skill_base_id}:{$skill_tref_id}"]); } } } - return $noProfileMatchingSkills !== []; + return $no_profile_matching_skills !== []; } } diff --git a/components/ILIAS/Test/classes/class.ilTestSkillEvaluationGUI.php b/components/ILIAS/Test/classes/class.ilTestSkillEvaluationGUI.php index 68c162a9a2f8..bf9f4d3d1d05 100755 --- a/components/ILIAS/Test/classes/class.ilTestSkillEvaluationGUI.php +++ b/components/ILIAS/Test/classes/class.ilTestSkillEvaluationGUI.php @@ -41,14 +41,14 @@ class ilTestSkillEvaluationGUI public const SKILL_PROFILE_PARAM = 'skill_profile'; public const CMD_SHOW = 'show'; - private ilTestSession $testSession; - private ilTestObjectiveOrientedContainer $objectiveOrientedContainer; - private ilAssQuestionList $questionList; + private ilTestSession $test_session; + private ilTestObjectiveOrientedContainer $objective_oriented_container; + private ilAssQuestionList $question_list; - protected bool $noSkillProfileOptionEnabled = false; - protected array $availableSkillProfiles = []; + protected bool $no_skill_profile_option_enabled = false; + protected array $available_skill_profiles = []; protected array $available_skills = []; - protected ?ilTestPassesSelector $testPassesSelector = null; + protected ?ilTestPassesSelector $test_passes_selector = null; public function __construct( private readonly ilObjTest $test_obj, @@ -62,39 +62,28 @@ public function __construct( ) { } - /** - * @return ilAssQuestionList - */ public function getQuestionList(): ilAssQuestionList { - return $this->questionList; + return $this->question_list; } - /** - * @param ilAssQuestionList $questionList - */ - public function setQuestionList($questionList) + public function setQuestionList(ilAssQuestionList $question_list): void { - $this->questionList = $questionList; + $this->question_list = $question_list; } - /** - * @return ilTestObjectiveOrientedContainer - */ public function getObjectiveOrientedContainer(): ilTestObjectiveOrientedContainer { - return $this->objectiveOrientedContainer; + return $this->objective_oriented_container; } - /** - * @param ilTestObjectiveOrientedContainer $objectiveOrientedContainer - */ - public function setObjectiveOrientedContainer($objectiveOrientedContainer) - { - $this->objectiveOrientedContainer = $objectiveOrientedContainer; + public function setObjectiveOrientedContainer( + ilTestObjectiveOrientedContainer $objective_oriented_container + ): void { + $this->objective_oriented_container = $objective_oriented_container; } - public function executeCommand() + public function executeCommand(): void { $cmd = $this->ctrl->getCmd(self::CMD_SHOW) . 'Cmd'; $this->$cmd(); @@ -102,11 +91,11 @@ public function executeCommand() protected function init(bool $skill_profile_enabled): void { - $this->testPassesSelector = new ilTestPassesSelector($this->db, $this->test_obj); - $this->testPassesSelector->setActiveId($this->testSession->getActiveId()); - $this->testPassesSelector->setLastFinishedPass($this->testSession->getLastFinishedPass()); + $this->test_passes_selector = new ilTestPassesSelector($this->db, $this->test_obj); + $this->test_passes_selector->setActiveId($this->test_session->getActiveId()); + $this->test_passes_selector->setLastFinishedPass($this->test_session->getLastFinishedPass()); - $skillEvaluation = new ilTestSkillEvaluation( + $skill_evaluation = new ilTestSkillEvaluation( $this->db, $this->logger, $this->test_obj->getTestId(), @@ -115,45 +104,45 @@ protected function init(bool $skill_profile_enabled): void $this->skills_service->personal() ); - $skillEvaluation->setUserId($this->getTestSession()->getUserId()); - $skillEvaluation->setActiveId($this->getTestSession()->getActiveId()); + $skill_evaluation->setUserId($this->getTestSession()->getUserId()); + $skill_evaluation->setActiveId($this->getTestSession()->getActiveId()); - $skillEvaluation->setNumRequiredBookingsForSkillTriggering( + $skill_evaluation->setNumRequiredBookingsForSkillTriggering( $this->test_obj->getGlobalSettings()->getSkillTriggeringNumberOfAnswers() ); - $skillEvaluation->init($this->getQuestionList()); + $skill_evaluation->init($this->getQuestionList()); - $availableSkillProfiles = $skillEvaluation->getAssignedSkillMatchingSkillProfiles(); + $available_skill_profiles = $skill_evaluation->getAssignedSkillMatchingSkillProfiles(); $this->setNoSkillProfileOptionEnabled( - $skillEvaluation->noProfileMatchingAssignedSkillExists($availableSkillProfiles) + $skill_evaluation->noProfileMatchingAssignedSkillExists($available_skill_profiles) ); - $this->setAvailableSkillProfiles($availableSkillProfiles); + $this->setAvailableSkillProfiles($available_skill_profiles); // should be reportedPasses - yes - indeed, skill level status will not respect - avoid confuse here - $evaluationPasses = $this->testPassesSelector->getExistingPasses(); + $evaluation_passes = $this->test_passes_selector->getExistingPasses(); - $availableSkills = []; + $available_skills = []; - foreach ($evaluationPasses as $evalPass) { - $testResults = $this->test_obj->getTestResult($this->getTestSession()->getActiveId(), $evalPass, true); + foreach ($evaluation_passes as $eval_pass) { + $test_results = $this->test_obj->getTestResult($this->getTestSession()->getActiveId(), $eval_pass, true); - $skillEvaluation->setPass($evalPass); - $skillEvaluation->evaluate($testResults); + $skill_evaluation->setPass($eval_pass); + $skill_evaluation->evaluate($test_results); if ($skill_profile_enabled && self::INVOLVE_SKILLS_BELOW_NUM_ANSWERS_BARRIER_FOR_GAP_ANALASYS) { - $skills = $skillEvaluation->getSkillsInvolvedByAssignment(); + $skills = $skill_evaluation->getSkillsInvolvedByAssignment(); } else { - $skills = $skillEvaluation->getSkillsMatchingNumAnswersBarrier(); + $skills = $skill_evaluation->getSkillsMatchingNumAnswersBarrier(); } - $availableSkills = array_merge($availableSkills, $skills); + $available_skills = array_merge($available_skills, $skills); } - $this->setAvailableSkills(array_values($availableSkills)); + $this->setAvailableSkills(array_values($available_skills)); } - private function showCmd() + private function showCmd(): void { $skill_profile_selected = $this->testrequest->isset(self::SKILL_PROFILE_PARAM); $selected_skill_profile = $this->testrequest->int(self::SKILL_PROFILE_PARAM); @@ -178,42 +167,43 @@ private function showCmd() ); } - private function buildEvaluationToolbarGUI(int $selectedSkillProfileId): ilTestSkillEvaluationToolbarGUI - { - if (!$this->noSkillProfileOptionEnabled && $selectedSkillProfileId === null) { - $selectedSkillProfileId = key($this->availableSkillProfiles) ?? 0; + private function buildEvaluationToolbarGUI( + int $selected_skill_profile_id + ): ilTestSkillEvaluationToolbarGUI { + if (!$this->no_skill_profile_option_enabled && $selected_skill_profile_id === null) { + $selected_skill_profile_id = key($this->available_skill_profiles) ?? 0; } $gui = new ilTestSkillEvaluationToolbarGUI($this->ctrl, $this->lng); - $gui->setAvailableSkillProfiles($this->availableSkillProfiles); - $gui->setNoSkillProfileOptionEnabled($this->noSkillProfileOptionEnabled); - $gui->setSelectedEvaluationMode($selectedSkillProfileId); + $gui->setAvailableSkillProfiles($this->available_skill_profiles); + $gui->setNoSkillProfileOptionEnabled($this->no_skill_profile_option_enabled); + $gui->setSelectedEvaluationMode($selected_skill_profile_id); $gui->build(); return $gui; } - public function setTestSession(ilTestSession $testSession): void + public function setTestSession(ilTestSession $test_session): void { - $this->testSession = $testSession; + $this->test_session = $test_session; } public function getTestSession(): ilTestSession { - return $this->testSession; + return $this->test_session; } - public function setNoSkillProfileOptionEnabled(bool $noSkillProfileOptionEnabled): void + public function setNoSkillProfileOptionEnabled(bool $no_skill_profile_option_enabled): void { - $this->noSkillProfileOptionEnabled = $noSkillProfileOptionEnabled; + $this->no_skill_profile_option_enabled = $no_skill_profile_option_enabled; } - public function setAvailableSkillProfiles(array $availableSkillProfiles): void + public function setAvailableSkillProfiles(array $available_skill_profiles): void { - $this->availableSkillProfiles = $availableSkillProfiles; + $this->available_skill_profiles = $available_skill_profiles; } - public function setAvailableSkills(array $availableSkills): void + public function setAvailableSkills(array $available_skills): void { - $this->available_skills = $availableSkills; + $this->available_skills = $available_skills; } } diff --git a/components/ILIAS/Test/classes/class.ilTestSkillLevelThresholdImport.php b/components/ILIAS/Test/classes/class.ilTestSkillLevelThresholdImport.php index 8740cc7eb94d..39d86e9d0e0c 100755 --- a/components/ILIAS/Test/classes/class.ilTestSkillLevelThresholdImport.php +++ b/components/ILIAS/Test/classes/class.ilTestSkillLevelThresholdImport.php @@ -26,65 +26,32 @@ */ class ilTestSkillLevelThresholdImport { - /** - * @var integer - */ - protected $importSkillBaseId = null; - /** - * @var integer - */ - protected $importSkillTrefId = null; - - /** - * @var integer - */ - protected $importLevelId = null; - /** - * @var integer - */ - protected $orderIndex = null; - + protected ?int $import_skill_base_id = null; + protected ?int $import_skill_tref_id = null; + protected ?int $import_level_id = null; + protected ?int $order_index = null; protected ?int $threshold = null; + protected ?string $original_level_title = null; + protected ?string $original_level_description = null; - /** - * @var string - */ - protected $originalLevelTitle = null; - /** - * @var string - */ - protected $originalLevelDescription = null; - - /** - * @return int - */ public function getImportSkillBaseId(): ?int { - return $this->importSkillBaseId; + return $this->import_skill_base_id; } - /** - * @param int $importSkillBaseId - */ - public function setImportSkillBaseId($importSkillBaseId) + public function setImportSkillBaseId(int $import_skill_base_id): void { - $this->importSkillBaseId = $importSkillBaseId; + $this->import_skill_base_id = $import_skill_base_id; } - /** - * @return int - */ public function getImportSkillTrefId(): ?int { - return $this->importSkillTrefId; + return $this->import_skill_tref_id; } - /** - * @param int $importSkillTrefId - */ - public function setImportSkillTrefId($importSkillTrefId) + public function setImportSkillTrefId(int $import_skill_tref_id): void { - $this->importSkillTrefId = $importSkillTrefId; + $this->import_skill_tref_id = $import_skill_tref_id; } /** @@ -92,78 +59,51 @@ public function setImportSkillTrefId($importSkillTrefId) */ public function getImportLevelId(): ?int { - return $this->importLevelId; + return $this->import_level_id; } - /** - * @param int $importLevelId - */ - public function setImportLevelId($importLevelId) + public function setImportLevelId(int $import_level_id): void { - $this->importLevelId = $importLevelId; + $this->import_level_id = $import_level_id; } - /** - * @return int - */ public function getOrderIndex(): ?int { - return $this->orderIndex; + return $this->order_index; } - /** - * @param int $orderIndex - */ - public function setOrderIndex($orderIndex) + public function setOrderIndex(int $order_index): void { - $this->orderIndex = $orderIndex; + $this->order_index = $order_index; } - /** - * @return int - */ public function getThreshold(): ?int { return $this->threshold; } - /** - * @param int $threshold - */ - public function setThreshold($threshold) + public function setThreshold(int $threshold): void { - $this->threshold = (int) $threshold; + $this->threshold = $threshold; } - /** - * @return string - */ public function getOriginalLevelTitle(): ?string { - return $this->originalLevelTitle; + return $this->original_level_title; } - /** - * @param string $originalLevelTitle - */ - public function setOriginalLevelTitle($originalLevelTitle) + public function setOriginalLevelTitle(string $original_level_title): void { - $this->originalLevelTitle = $originalLevelTitle; + $this->original_level_title = $original_level_title; } - /** - * @return string - */ public function getOriginalLevelDescription(): ?string { - return $this->originalLevelDescription; + return $this->original_level_description; } - /** - * @param string $originalLevelDescription - */ - public function setOriginalLevelDescription($originalLevelDescription) + public function setOriginalLevelDescription(string $original_level_description): void { - $this->originalLevelDescription = $originalLevelDescription; + $this->original_level_description = $original_level_description; } } diff --git a/components/ILIAS/Test/classes/class.ilTestSkillLevelThresholdImporter.php b/components/ILIAS/Test/classes/class.ilTestSkillLevelThresholdImporter.php index dbe00546a30d..a10f4136d347 100755 --- a/components/ILIAS/Test/classes/class.ilTestSkillLevelThresholdImporter.php +++ b/components/ILIAS/Test/classes/class.ilTestSkillLevelThresholdImporter.php @@ -157,6 +157,10 @@ public function import(): void $mappedLevelId = $this->getLevelIdMapping($importLevelThreshold->getImportLevelId()); + if ($mappedLevelId === null) { + continue(2); + } + $threshold = new ilTestSkillLevelThreshold($this->db); $threshold->setTestId($this->getTargetTestId()); $threshold->setSkillBaseId($skillData['skill_base_id']); @@ -171,10 +175,13 @@ public function import(): void $importedLevelThresholdList->saveToDb(); } - protected function getLevelIdMapping(int $importLevelId): int + protected function getLevelIdMapping(int $importLevelId): ?int { $result = ilBasicSkill::getLevelIdForImportId($this->getImportInstallationId(), $importLevelId); $mostNewLevelData = current($result); + if (!is_array($mostNewLevelData)) { + return null; + } return $mostNewLevelData['level_id']; } } diff --git a/components/ILIAS/Test/classes/class.ilTestSkillLevelThresholdList.php b/components/ILIAS/Test/classes/class.ilTestSkillLevelThresholdList.php index c8ba36296416..1fde8f370a0a 100755 --- a/components/ILIAS/Test/classes/class.ilTestSkillLevelThresholdList.php +++ b/components/ILIAS/Test/classes/class.ilTestSkillLevelThresholdList.php @@ -123,27 +123,25 @@ private function buildSkillLevelThresholdByArray($data): ilTestSkillLevelThresho return $threshold; } - /** - * @param $skillBaseId - * @param $skillTrefId - * @param $skillLevelId - * @return ilTestSkillLevelThreshold - */ - public function getThreshold($skillBaseId, $skillTrefId, $skillLevelId, $forceObject = false): ?ilTestSkillLevelThreshold - { - $skillKey = $skillBaseId . ':' . $skillTrefId; - - if (isset($this->thresholds[$skillKey]) && isset($this->thresholds[$skillKey][$skillLevelId])) { - return $this->thresholds[$skillKey][$skillLevelId]; + public function getThreshold( + $skill_base_id, + $skill_tref_id, + $skill_level_id, + $force_object = false + ): ?ilTestSkillLevelThreshold { + $skillKey = $skill_base_id . ':' . $skill_tref_id; + + if (isset($this->thresholds[$skillKey]) && isset($this->thresholds[$skillKey][$skill_level_id])) { + return $this->thresholds[$skillKey][$skill_level_id]; } - if ($forceObject) { + if ($force_object) { $threshold = new ilTestSkillLevelThreshold($this->db); $threshold->setTestId($this->getTestId()); - $threshold->setSkillBaseId($skillBaseId); - $threshold->setSkillTrefId($skillTrefId); - $threshold->setSkillLevelId($skillLevelId); + $threshold->setSkillBaseId($skill_base_id); + $threshold->setSkillTrefId($skill_tref_id); + $threshold->setSkillLevelId($skill_level_id); return $threshold; } @@ -151,6 +149,13 @@ public function getThreshold($skillBaseId, $skillTrefId, $skillLevelId, $forceOb return null; } + public function getThesholdsOfBaseAndTrefId( + int $skill_base_id, + int $skill_tref_id + ): array { + return $this->thresholds["{$skill_base_id}:{$skill_tref_id}"] ?? []; + } + public function cloneListForTest($testId) { foreach ($this->thresholds as $data) { diff --git a/components/ILIAS/Test/classes/class.ilTestSkillLevelThresholdXmlParser.php b/components/ILIAS/Test/classes/class.ilTestSkillLevelThresholdXmlParser.php index a8b314ebdce2..2d7f1ab684a8 100755 --- a/components/ILIAS/Test/classes/class.ilTestSkillLevelThresholdXmlParser.php +++ b/components/ILIAS/Test/classes/class.ilTestSkillLevelThresholdXmlParser.php @@ -36,11 +36,12 @@ class ilTestSkillLevelThresholdXmlParser extends ilSaxParser protected ?ilTestSkillLevelThresholdImportList $skillLevelThresholdImportList = null; protected ?ilTestSkillLevelThresholdImport $curSkillLevelThreshold = null; - public function __construct() - { + public function __construct( + ?string $path_to_file = '' + ) { global $DIC; $this->db = $DIC['ilDB']; - parent::__construct(); + parent::__construct($path_to_file); } public function isParsingActive(): bool @@ -113,9 +114,8 @@ public function setCurSkillLevelThreshold(?ilTestSkillLevelThresholdImport $curS public function setHandlers($a_xml_parser): void { - xml_set_object($a_xml_parser, $this); - xml_set_element_handler($a_xml_parser, 'handlerBeginTag', 'handlerEndTag'); - xml_set_character_data_handler($a_xml_parser, 'handlerCharacterData'); + xml_set_element_handler($a_xml_parser, $this->handlerBeginTag(...), $this->handlerEndTag(...)); + xml_set_character_data_handler($a_xml_parser, $this->handlerCharacterData(...)); } public function handlerBeginTag($xmlParser, $tagName, $tagAttributes): void @@ -131,8 +131,8 @@ public function handlerBeginTag($xmlParser, $tagName, $tagAttributes): void break; case 'QuestionsAssignedSkill': - $this->setCurSkillBaseId($tagAttributes['BaseId']); - $this->setCurSkillTrefId($tagAttributes['TrefId']); + $this->setCurSkillBaseId((int) $tagAttributes['BaseId']); + $this->setCurSkillTrefId((int) $tagAttributes['TrefId']); break; case 'OriginalLevelDescription': @@ -147,8 +147,8 @@ public function handlerBeginTag($xmlParser, $tagName, $tagAttributes): void $skillLevelThreshold = new ilTestSkillLevelThresholdImport(); $skillLevelThreshold->setImportSkillBaseId($this->getCurSkillBaseId()); $skillLevelThreshold->setImportSkillTrefId($this->getCurSkillTrefId()); - $skillLevelThreshold->setImportLevelId($tagAttributes['Id']); - $skillLevelThreshold->setOrderIndex($tagAttributes['Nr']); + $skillLevelThreshold->setImportLevelId((int) $tagAttributes['Id']); + $skillLevelThreshold->setOrderIndex((int) $tagAttributes['Nr']); $this->setCurSkillLevelThreshold($skillLevelThreshold); break; } @@ -196,7 +196,7 @@ public function handlerEndTag($xmlParser, $tagName): void break; case 'ThresholdPercentage': - $this->getCurSkillLevelThreshold()->setThreshold($this->getCharacterDataBuffer()); + $this->getCurSkillLevelThreshold()->setThreshold((int) $this->getCharacterDataBuffer()); $this->resetCharacterDataBuffer(); break; diff --git a/components/ILIAS/Test/classes/class.ilTestSkillPointAccount.php b/components/ILIAS/Test/classes/class.ilTestSkillPointAccount.php index 17d9c0b02cb1..726454a3907d 100755 --- a/components/ILIAS/Test/classes/class.ilTestSkillPointAccount.php +++ b/components/ILIAS/Test/classes/class.ilTestSkillPointAccount.php @@ -40,10 +40,10 @@ public function __construct() $this->numBookings = 0; } - public function addBooking($maxSkillPoints, $reachedSkillPoints) + public function addBooking($max_skill_points, $reached_skill_points): void { - $this->totalMaxSkillPoints += $maxSkillPoints; - $this->totalReachedSkillPoints += $reachedSkillPoints; + $this->totalMaxSkillPoints += $max_skill_points; + $this->totalReachedSkillPoints += $reached_skill_points; $this->numBookings++; } diff --git a/components/ILIAS/Test/classes/class.ilTestToplistGUI.php b/components/ILIAS/Test/classes/class.ilTestToplistGUI.php index 43f10133a9d7..c8d1b8396060 100755 --- a/components/ILIAS/Test/classes/class.ilTestToplistGUI.php +++ b/components/ILIAS/Test/classes/class.ilTestToplistGUI.php @@ -136,7 +136,7 @@ protected function buildTable(string $title, TopListType $list_type, TopListOrde $order_by ); return $this->ui_factory->table() - ->data($title, $table->getColumns(), $table) + ->data($table, $title, $table->getColumns()) ->withRequest($this->http_state->request()); } diff --git a/components/ILIAS/Test/resources/ilTestPlayerQuestionEditControl.js b/components/ILIAS/Test/resources/ilTestPlayerQuestionEditControl.js index 711ce0901204..665b008ac55c 100644 --- a/components/ILIAS/Test/resources/ilTestPlayerQuestionEditControl.js +++ b/components/ILIAS/Test/resources/ilTestPlayerQuestionEditControl.js @@ -67,6 +67,7 @@ il.TestPlayerQuestionEditControl = new function() { var config = { isAnswered: false, isAnswerChanged: false, + isAnswerFixed: false, saveOnTimeReachedUrl: '', autosaveUrl: '', autosaveInterval: 0, @@ -158,6 +159,11 @@ il.TestPlayerQuestionEditControl = new function() { stickyChanged = true; } + if (config.isAnswered && config.isAnswerChanged && config.isAnswerFixed) { + answerChanged = false; + stickyChanged = false; + } + // adjust the display of status dependent elements refreshAnswerStatusView(); @@ -680,7 +686,6 @@ il.TestPlayerQuestionEditControl = new function() { // get and compare the current form data var newData = $(FORM_SELECTOR).serialize(); if (autoSavedData != newData) { - $.ajax({ type: 'POST', url: url, @@ -706,7 +711,6 @@ il.TestPlayerQuestionEditControl = new function() { * @param responseText */ function autoSaveSuccess(responseText) { - if (typeof responseText !== 'undefined' && responseText != '-IGNORE-') { $('#autosavemessage').text(responseText) .fadeIn(500, function(){ diff --git a/components/ILIAS/Test/src/ExportImport/DBRepository.php b/components/ILIAS/Test/src/ExportImport/DBRepository.php new file mode 100644 index 000000000000..8f1f3f8c558a --- /dev/null +++ b/components/ILIAS/Test/src/ExportImport/DBRepository.php @@ -0,0 +1,79 @@ +db->insert( + self::TST_EXPORT_TABLE, + [ + 'object_id' => [ + \ilDBConstants::T_INTEGER, + $object_id + ], + 'type' => [ + \ilDBConstants::T_TEXT, + $type->value + ], + 'rid' => [ + \ilDBConstants::T_TEXT, + $rid->serialize() + ], + ] + ); + } + + public function delete( + ResourceIdentification $rid + ): void { + $this->db->manipulateF( + 'DELETE FROM ' . self::TST_EXPORT_TABLE . ' WHERE rid = %s', + [\ilDBConstants::T_TEXT], + [$rid->serialize()] + ); + } + + public function getFor( + int $object_id + ): array { + return $this->db->fetchAll( + $this->db->queryF( + 'SELECT * FROM ' . self::TST_EXPORT_TABLE . ' WHERE object_id = %s', + [\ilDBConstants::T_INTEGER], + [$object_id] + ) + ); + } +} diff --git a/components/ILIAS/Test/src/ExportImport/Export.php b/components/ILIAS/Test/src/ExportImport/Export.php index 977143f0b9f2..22b528d4402e 100755 --- a/components/ILIAS/Test/src/ExportImport/Export.php +++ b/components/ILIAS/Test/src/ExportImport/Export.php @@ -24,6 +24,7 @@ use ILIAS\TestQuestionPool\Questions\GeneralQuestionPropertiesRepository; use ILIAS\Language\Language; use ILIAS\FileDelivery\Services as FileDeliveryServices; +use ILIAS\ResourceStorage\Services as ResourceStorage; /** * Export class for tests @@ -61,9 +62,11 @@ public function __construct( protected readonly \ilComponentRepository $component_repository, protected readonly GeneralQuestionPropertiesRepository $questionrepository, protected readonly FileDeliveryServices $file_delivery, - protected readonly \ilObjTest $test_obj + protected readonly \ilObjTest $test_obj, + protected readonly ResourceStorage $irss ) { $this->inst_id = (string) IL_INST_ID; + $this->export_dir = $test_obj->getExportDirectory(); $date = time(); $this->export_dir = $test_obj->getExportDirectory(); @@ -71,7 +74,6 @@ public function __construct( $this->filename = $this->subdir . '.xml'; $this->resultsfile = "{$date}__{$this->inst_id}__results_{$this->test_obj->getId()}.xml"; $this->qti_filename = "{$date}__{$this->inst_id}__qti_{$this->test_obj->getId()}.xml"; - $this->filename = $this->subdir . '.xml'; } abstract protected function initXmlExport(); @@ -91,6 +93,17 @@ public function withResultExportingEnabled(bool $enable): self return $clone; } + public function withExportDirInfo(string $export_dir): self + { + $clone = clone $this; + $clone->subdir = basename($export_dir); + $clone->filename = $clone->subdir . '.xml'; + $path_array = explode('__', $clone->subdir); + $clone->resultsfile = "{$path_array[0]}__{$path_array[1]}__results_{$this->test_obj->getId()}.xml"; + $clone->qti_filename = "{$path_array[0]}__{$path_array[1]}__qti_{$this->test_obj->getId()}.xml"; + return $clone; + } + public function write(): ?string { $this->bench->start('TestExport', 'write'); @@ -152,7 +165,13 @@ public function write(): ?string $this->bench->stop('TestExport', 'write_dumpToFile'); if ($this->isResultExportingEnabled()) { - $resultwriter = new \ilTestResultsToXML($this->test_obj->getTestId(), $this->db, $this->test_obj->getAnonymity()); + $resultwriter = new \ilTestResultsToXML( + $this->test_obj->getTestId(), + $this->db, + $this->irss, + $this->export_dir . "/" . $this->subdir . "/objects", + $this->test_obj->getAnonymity() + ); $resultwriter->setIncludeRandomTestQuestionsEnabled($this->test_obj->isRandomTest()); $this->bench->start('TestExport', 'write_results'); $resultwriter->xmlDumpFile($this->export_dir . '/' . $this->subdir . '/' . $this->resultsfile, false); @@ -178,9 +197,7 @@ public function write(): ?string $exp_log->write(date('[y-m-d H:i:s] ') . 'Finished Export'); $this->bench->stop('TestExport', 'write'); - if (!$this->isResultExportingEnabled()) { - unlink($this->export_dir . '/' . $this->subdir . '.zip'); - } + unlink($this->export_dir . '/' . $this->subdir . '.zip'); return $this->export_dir . '/' . $this->subdir . '.zip'; } @@ -228,16 +245,6 @@ public function exportXHTMLMediaObjects($a_export_dir): void { $mobs = \ilObjMediaObject::_getMobsOfObject('tst:html', $this->test_obj->getId()); - $intro_page_id = $this->test_obj->getMainSettings()->getIntroductionSettings()->getIntroductionPageId(); - if ($intro_page_id !== null) { - $mobs += \ilObjMediaObject::_getMobsOfObject('tst:pg', $intro_page_id); - } - - $concluding_remarks_page_id = $this->test_obj->getMainSettings()->getFinishingSettings()->getConcludingRemarksPageId(); - if ($concluding_remarks_page_id !== null) { - $mobs += \ilObjMediaObject::_getMobsOfObject('tst:pg', $concluding_remarks_page_id); - } - foreach ($mobs as $mob) { if (\ilObjMediaObject::_exists($mob)) { $mob_obj = new \ilObjMediaObject($mob); diff --git a/components/ILIAS/Test/src/ExportImport/Factory.php b/components/ILIAS/Test/src/ExportImport/Factory.php index 0f1518398da8..c9bdc60e7866 100755 --- a/components/ILIAS/Test/src/ExportImport/Factory.php +++ b/components/ILIAS/Test/src/ExportImport/Factory.php @@ -23,6 +23,7 @@ use ILIAS\TestQuestionPool\Questions\GeneralQuestionPropertiesRepository; use ILIAS\Test\Logging\TestLogger; use ILIAS\FileDelivery\Services as FileDeliveryServices; +use ILIAS\ResourceStorage\Services as ResourceStorage; class Factory { @@ -37,7 +38,8 @@ public function __construct( private readonly \ilComponentFactory $component_factory, private readonly FileDeliveryServices $file_delivery, private readonly \ilObjUser $current_user, - private readonly GeneralQuestionPropertiesRepository $questionrepository + private readonly GeneralQuestionPropertiesRepository $questionrepository, + private readonly ResourceStorage $irss ) { } @@ -97,7 +99,8 @@ public function getExporter( $this->component_repository, $this->questionrepository, $this->file_delivery, - $test_obj + $test_obj, + $this->irss ); if ($export_type === Types::XML_WITH_RESULTS) { diff --git a/components/ILIAS/Test/src/ExportImport/ResultsExportExcel.php b/components/ILIAS/Test/src/ExportImport/ResultsExportExcel.php index 6d785a17f6ef..b3a9cf9bd268 100755 --- a/components/ILIAS/Test/src/ExportImport/ResultsExportExcel.php +++ b/components/ILIAS/Test/src/ExportImport/ResultsExportExcel.php @@ -22,6 +22,7 @@ use ILIAS\TestQuestionPool\Questions\GeneralQuestionPropertiesRepository; use ILIAS\Data\DateFormat\DateFormat; +use PhpOffice\PhpSpreadsheet\Cell\DataType; /** * @author Fabian Helfer @@ -46,7 +47,7 @@ public function __construct( private readonly \ilObjTest $test_obj, private readonly GeneralQuestionPropertiesRepository $question_repository, private readonly string $filename = '', - private readonly bool $scoredonly = true, + private readonly bool $scoredonly = true ) { $this->user_date_format = $this->current_user->getDateTimeFormat(); $this->aggregated_data = $test_obj->getAggregatedResultsData(); @@ -83,7 +84,6 @@ public function withUserPages(): self { $usersheet_titles = []; foreach ($this->getCompleteData()->getParticipants() as $active_id => $user_data) { - $active_id = 1; $usersheet_titles = $this->addUserSheet( $usersheet_titles, $user_data->getName(), @@ -461,6 +461,9 @@ private function addUserContent( $answers = implode("\n", $answers); } + $disable_strip_tags_for_answers = $question_obj instanceof \assTextQuestion + && $this->test_obj->getGlobalSettings()->getExportEssayQuestionsAsHtml(); + $correct_answers = $question_obj->getCorrectSolutionForTextOutput($active_id, $test_attempt->getPass()); if (is_array($correct_answers)) { $correct_answers = implode("\n", $correct_answers); @@ -469,7 +472,7 @@ private function addUserContent( $col = 0; $this->worksheet->setCell($current_row, $col++, $question_obj->getTitle()); $this->worksheet->setCell($current_row, $col++, $this->lng->txt($question_obj->getQuestionType())); - $this->worksheet->setCell($current_row, $col++, $answers); + $this->worksheet->setCell($current_row, $col++, $answers, DataType::TYPE_STRING, $disable_strip_tags_for_answers); $this->worksheet->setCell($current_row, $col++, $correct_answers); $this->worksheet->setCell($current_row, $col++, implode(', ', $question_obj->getVariablesAsTextArray($active_id, $test_attempt->getPass()))); $this->worksheet->setCell($current_row, $col++, $test_attempt->getAnsweredQuestionByQuestionId($question_id)['reached'] ?? 0); diff --git a/components/ILIAS/Test/src/ExportImport/ResultsExportStakeholder.php b/components/ILIAS/Test/src/ExportImport/ResultsExportStakeholder.php new file mode 100644 index 000000000000..c0d97a5e3148 --- /dev/null +++ b/components/ILIAS/Test/src/ExportImport/ResultsExportStakeholder.php @@ -0,0 +1,36 @@ +default_owner; + } +} diff --git a/components/ILIAS/Test/src/Logging/AdditionalInformationGenerator.php b/components/ILIAS/Test/src/Logging/AdditionalInformationGenerator.php index 601b79841562..1a957b33bb1b 100644 --- a/components/ILIAS/Test/src/Logging/AdditionalInformationGenerator.php +++ b/components/ILIAS/Test/src/Logging/AdditionalInformationGenerator.php @@ -83,7 +83,6 @@ class AdditionalInformationGenerator public const KEY_TEST_TITLE_PRESENTATION = 'tst_title_output'; public const KEY_TEST_AUTOSAVE_ENABLED = 'autosave'; public const KEY_TEST_SHUFFLE_QUESTIONS = 'tst_shuffle_questions'; - public const KEY_TEST_HINTS_ENABLED = 'tst_setting_offer_hints_label'; public const KEY_TEST_FEEDBACK_ENABLED = 'tst_instant_feedback'; public const KEY_TEST_FEEDBACK_SHOW_POINTS = 'tst_instant_feedback_results'; public const KEY_TEST_FEEDBACK_SHOW_GENERIC = 'tst_instant_feedback_answer_generic'; @@ -131,7 +130,6 @@ class AdditionalInformationGenerator public const KEY_SCORING_HIGHSCORE_SHOW_ACHIEVED_TS = 'tst_highscore_achieved_ts'; public const KEY_SCORING_HIGHSCORE_SHOW_SCORE = 'tst_highscore_score'; public const KEY_SCORING_HIGHSCORE_SHOW_PERCENTAGE = 'tst_highscore_percentage'; - public const KEY_SCORING_HIGHSCORE_SHOW_HINTS = 'tst_highscore_hints'; public const KEY_SCORING_HIGHSCORE_SHOW_WTIME = 'tst_highscore_wtime'; public const KEY_PASS = 'pass'; @@ -378,7 +376,9 @@ private function parseValue( ); case self::KEY_QUESTION_ID: if (is_int($value)) { - return $this->questions_repo->getForQuestionId($value)?->getTitle() ?? $this->lng->txt('deleted'); + return $this->refinery->encode()->htmlSpecialCharsAsEntities()->transform( + $this->questions_repo->getForQuestionId($value)?->getTitle() ?? $this->lng->txt('deleted') + ); } //no break default: diff --git a/components/ILIAS/Test/src/Logging/LogTable.php b/components/ILIAS/Test/src/Logging/LogTable.php index c3316fd2a128..e84940594409 100644 --- a/components/ILIAS/Test/src/Logging/LogTable.php +++ b/components/ILIAS/Test/src/Logging/LogTable.php @@ -24,8 +24,6 @@ use ILIAS\TestQuestionPool\Questions\GeneralQuestionPropertiesRepository; use ILIAS\UI\Factory as UIFactory; use ILIAS\UI\Renderer as UIRenderer; -use ILIAS\Data\Factory as DataFactory; -use ILIAS\Data\DateFormat\DateFormat; use ILIAS\Data\Range; use ILIAS\Data\Order; use ILIAS\UI\Component\Table; @@ -33,7 +31,6 @@ use ILIAS\UI\URLBuilder; use ILIAS\UI\URLBuilderToken; use ILIAS\FileDelivery\Delivery\StreamDelivery; -use ILIAS\Filesystem\Stream\Streams; class LogTable implements Table\DataRetrieval { @@ -79,12 +76,12 @@ class LogTable implements Table\DataRetrieval public function __construct( private readonly TestLoggingRepository $logging_repository, private readonly TestLogger $logger, + private readonly TestLogViewer $log_viewer, private readonly TitleColumnsBuilder $title_builder, private readonly GeneralQuestionPropertiesRepository $question_repo, private readonly \ilUIService $ui_service, private readonly UIFactory $ui_factory, private readonly UIRenderer $ui_renderer, - private readonly DataFactory $data_factory, private readonly \ilLanguage $lng, private \ilGlobalTemplateInterface $tpl, private readonly URLBuilder $url_builder, @@ -100,9 +97,9 @@ public function __construct( public function getTable(): Table\Data { return $this->ui_factory->table()->data( + $this, $this->lng->txt('history'), $this->getColums(), - $this )->withActions($this->getActions()); } @@ -129,7 +126,7 @@ private function initializeFilter(): void $filter_inputs = [ self::FILTER_FIELD_PERIOD => $field_factory->duration($this->lng->txt('cal_period')) ->withUseTime(true) - ->withFormat($this->buildUserDateTimeFormat()) + ->withFormat($this->log_viewer->buildUserDateTimeFormat()) ]; if ($this->ref_id === null) { $filter_inputs[self::FILTER_FIELD_TEST_TITLE] = $field_factory->text($this->lng->txt('test')); @@ -168,7 +165,7 @@ private function getColums(): array $f = $this->ui_factory->table()->column(); $columns = [ - self::COLUMN_DATE_TIME => $f->date($this->lng->txt('date_time'), $this->buildUserDateTimeFormat()), + self::COLUMN_DATE_TIME => $f->date($this->lng->txt('date_time'), $this->log_viewer->buildUserDateTimeFormat()), self::COLUMN_CORRESPONDING_TEST => $f->link($this->lng->txt('test'))->withIsOptional(true, true), self::COLUMN_ADMIN => $f->text($this->lng->txt('author'))->withIsOptional(true, true), self::COLUMN_PARTICIPANT => $f->text($this->lng->txt('tst_participant'))->withIsOptional(true, true) @@ -207,7 +204,7 @@ public function getRows( $environment = [ 'timezone' => new \DateTimeZone($this->current_user->getTimeZone()), - 'date_format' => $this->buildUserDateTimeFormat()->toString() + 'date_format' => $this->log_viewer->buildUserDateTimeFormat()->toString() ]; foreach ($this->logging_repository->getLogs( @@ -275,7 +272,7 @@ public function executeAction( }; } - protected function showAdditionalDetails(string $affected_item): void + private function showAdditionalDetails(string $affected_item): void { $log = $this->logging_repository->getLog($affected_item); if ($log === null) { @@ -284,7 +281,7 @@ protected function showAdditionalDetails(string $affected_item): void $environment = [ 'timezone' => new \DateTimeZone($this->current_user->getTimeZone()), - 'date_format' => $this->buildUserDateTimeFormat()->toString() + 'date_format' => $this->log_viewer->buildUserDateTimeFormat()->toString() ]; echo $this->ui_renderer->renderAsync( @@ -300,7 +297,7 @@ protected function showAdditionalDetails(string $affected_item): void exit; } - protected function showConfirmTestUserInteractionsDeletion(array $affected_items): void + private function showConfirmTestUserInteractionsDeletion(array $affected_items): void { if ($affected_items === []) { $this->showErrorModal($this->lng->txt('no_checkbox')); @@ -319,7 +316,7 @@ protected function showConfirmTestUserInteractionsDeletion(array $affected_items exit; } - protected function deleteTestUserInteractions(array $affected_items): void + private function deleteTestUserInteractions(array $affected_items): void { if ($this->ref_id !== null) { $this->tpl->setOnScreenMessage('failure', $this->lng->txt('log_deletion_not_allowed')); @@ -330,110 +327,53 @@ protected function deleteTestUserInteractions(array $affected_items): void $this->tpl->setOnScreenMessage('success', $this->lng->txt('logs_deleted')); } - protected function exportTestUserInteractions(array $affected_items): void + private function exportTestUserInteractions(array $affected_items): void { if ($affected_items === []) { $this->tpl->setOnScreenMessage('info', $this->lng->txt('no_checkbox')); return; } - $environment = [ - 'timezone' => new \DateTimeZone($this->current_user->getTimeZone()), - 'date_format' => $this->buildUserDateTimeFormat()->toString() - ]; - - if ($affected_items[0] === 'ALL_OBJECTS') { - $this->initializeFilterAndData(); - [ - $from_filter, - $to_filter, - $test_filter, - $admin_filter, - $pax_filter, - $question_filter, - $ip_filter, - $log_entry_type_filter, - $interaction_type_filter - ] = $this->prepareFilterData($this->filter_data); - $interactions = $this->logging_repository->getLogs( - $this->logger->getInteractionTypes(), - $this->ref_id !== null ? [$this->ref_id] : null, - null, - null, - $from_filter, - $to_filter, - $admin_filter, - $pax_filter, - $question_filter, - $ip_filter, - $log_entry_type_filter, - $interaction_type_filter - ); - } else { - $interactions = $this->logging_repository->getLogsByUniqueIdentifiers($affected_items); - } - - $header = $this->getColumHeadingsForExport(); - $content = []; - foreach ($interactions as $interaction) { - $content[] = $interaction->getLogEntryAsExportRow( - $this->lng, - $this->title_builder, - $this->logger->getAdditionalInformationGenerator(), - $environment - ); - } - - $workbook = $this->buildExcelWorkbook($header, $content); - $workbook->sendToClient(date('Y-m-d') . self::EXPORT_FILE_NAME); - - $stream = fopen('php://memory', 'r+'); - fwrite($stream, $csv); - rewind($stream); - $this->stream_delivery->deliver( - Streams::ofResource($stream), - date('Y-m-d') . self::CSV_EXPORT_FILE_NAME - ); - } - - private function getColumHeadingsForExport(): array - { - return [ - $this->lng->txt('date_time'), - $this->lng->txt('test'), - $this->lng->txt('author'), - $this->lng->txt('tst_participant'), - $this->lng->txt('client_ip'), - $this->lng->txt('question'), - $this->lng->txt('log_entry_type'), - $this->lng->txt('interaction_type'), - $this->lng->txt('additional_info') - ]; + $this->log_viewer->buildExcelWorkbookForLogs( + $this->buildLogsFromAffectedItems($affected_items) + )->sendToClient(date('Y-m-d') . self::EXPORT_FILE_NAME); } - private function buildExcelWorkbook(array $header, array $content): \ilExcel + private function buildLogsFromAffectedItems(array $affected_items): \Generator { - $workbook = new \ilExcel(); - $workbook->addSheet($this->lng->txt('history')); - $row = 1; - $column = 0; - foreach ($header as $header_cell) { - $workbook->setCell($row, $column++, $header_cell); - } - $workbook->setBold('A' . $row . ':' . $workbook->getColumnCoord($column - 1) . $row); - $workbook->setColors('A' . $row . ':' . $workbook->getColumnCoord($column - 1) . $row, 'C0C0C0'); - - foreach ($content as $content_row) { - $row++; - $column = 0; - foreach ($content_row as $content_cell) { - $workbook->setCell($row, $column++, $content_cell); - } + if ($affected_items[0] !== 'ALL_OBJECTS') { + return $this->logging_repository->getLogsByUniqueIdentifiers($affected_items); } - return $workbook; + + $this->initializeFilterAndData(); + [ + $from_filter, + $to_filter, + $test_filter, + $admin_filter, + $pax_filter, + $question_filter, + $ip_filter, + $log_entry_type_filter, + $interaction_type_filter + ] = $this->prepareFilterData($this->filter_data); + return $this->logging_repository->getLogs( + $this->logger->getInteractionTypes(), + $this->ref_id !== null ? [$this->ref_id] : null, + null, + null, + $from_filter, + $to_filter, + $admin_filter, + $pax_filter, + $question_filter, + $ip_filter, + $log_entry_type_filter, + $interaction_type_filter + ); } - protected function getActions(): array + private function getActions(): array { $af = $this->ui_factory->table()->action(); $actions = [ @@ -584,19 +524,6 @@ private function showErrorModal(string $message): void exit; } - private function buildUserDateTimeFormat(): DateFormat - { - $user_format = $this->current_user->getDateFormat(); - if ($this->current_user->getTimeFormat() == \ilCalendarSettings::TIME_FORMAT_24) { - return $this->data_factory->dateFormat()->amend( - $this->data_factory->dateFormat()->withTime24($user_format) - )->colon()->seconds()->get(); - } - return $this->data_factory->dateFormat()->amend( - $user_format - )->space()->hours12()->colon()->minutes()->colon()->seconds()->meridiem()->get(); - } - private function extractIdsFromUserQuery(array $response): array { if (!isset($response['set'])) { diff --git a/components/ILIAS/Test/src/Logging/TestLogViewer.php b/components/ILIAS/Test/src/Logging/TestLogViewer.php index 5177c129f524..12e619e64d59 100644 --- a/components/ILIAS/Test/src/Logging/TestLogViewer.php +++ b/components/ILIAS/Test/src/Logging/TestLogViewer.php @@ -22,6 +22,7 @@ use ILIAS\Test\Utilities\TitleColumnsBuilder; use ILIAS\TestQuestionPool\Questions\GeneralQuestionPropertiesRepository; +use ILIAS\Data\DateFormat\DateFormat; use ILIAS\HTTP\Wrapper\RequestWrapper; use ILIAS\UI\Factory as UIFactory; use ILIAS\UI\Renderer as UIRenderer; @@ -64,12 +65,12 @@ public function getLogTable( $log_table = new LogTable( $this->logging_repository, $this->logger, + $this, $this->title_builder, $this->question_repository, $this->ui_service, $this->ui_factory, $this->ui_renderer, - $this->data_factory, $this->lng, $this->tpl, $url_builder, @@ -95,12 +96,12 @@ public function executeLogTableAction( $log_table = new LogTable( $this->logging_repository, $this->logger, + $this, $this->title_builder, $this->question_repository, $this->ui_service, $this->ui_factory, $this->ui_renderer, - $this->data_factory, $this->lng, $this->tpl, $url_builder, @@ -138,6 +139,86 @@ public function executeLogTableAction( $log_table->executeAction($action, $affected_items); } + public function getLogExportForRefjId(int $ref_id): \ilExcel + { + return $this->buildExcelWorkbookForLogs( + $this->logging_repository->getLogs( + $this->logger->getInteractionTypes(), + [$ref_id] + ) + ); + } + + public function buildExcelWorkbookForLogs(\Generator $logs): \ilExcel + { + $workbook = new \ilExcel(); + $workbook->addSheet($this->lng->txt('history')); + + $column = 0; + foreach ($this->getColumHeadingsForExport() as $header_cell) { + $workbook->setCell(1, $column++, $header_cell); + } + $workbook->setBold('A' . 1 . ':' . $workbook->getColumnCoord($column - 1) . 1); + $workbook->setColors('A' . 1 . ':' . $workbook->getColumnCoord($column - 1) . 1, 'C0C0C0'); + + return $this->addRowsFromLogs($logs, $workbook); + } + + private function addRowsFromLogs( + \Generator $logs, + \ilExcel $workbook + ): \ilExcel { + $row = 1; + foreach ($logs as $log) { + $row++; + $column = 0; + foreach ($log->getLogEntryAsExportRow( + $this->lng, + $this->title_builder, + $this->logger->getAdditionalInformationGenerator(), + [ + 'timezone' => new \DateTimeZone($this->current_user->getTimeZone()), + 'date_format' => $this->buildUserDateTimeFormat()->toString() + ] + ) as $cell_content) { + $workbook->setCell( + $row, + $column++, + $cell_content + ); + } + } + return $workbook; + } + + private function getColumHeadingsForExport(): array + { + return [ + $this->lng->txt('date_time'), + $this->lng->txt('test'), + $this->lng->txt('author'), + $this->lng->txt('tst_participant'), + $this->lng->txt('client_ip'), + $this->lng->txt('question'), + $this->lng->txt('log_entry_type'), + $this->lng->txt('interaction_type'), + $this->lng->txt('additional_info') + ]; + } + + public function buildUserDateTimeFormat(): DateFormat + { + $user_format = $this->current_user->getDateFormat(); + if ($this->current_user->getTimeFormat() == \ilCalendarSettings::TIME_FORMAT_24) { + return $this->data_factory->dateFormat()->amend( + $this->data_factory->dateFormat()->withTime24($user_format) + )->colon()->seconds()->get(); + } + return $this->data_factory->dateFormat()->amend( + $user_format + )->space()->hours12()->colon()->minutes()->colon()->seconds()->meridiem()->get(); + } + /* The following functions will be removed with ILIAS 11 */ public function getLegacyLogExportForObjId(?int $obj_id = null): string @@ -176,7 +257,7 @@ public function getLegacyLogExportForObjId(?int $obj_id = null): string return $csvoutput; } - private function buildQuestionTitleForLegacyLog(array $log): string + public function buildQuestionTitleForLegacyLog(array $log): string { if (!$log['question_fi'] && !$log['original_fi']) { return ''; diff --git a/components/ILIAS/Test/src/Participants/ParticipantRepository.php b/components/ILIAS/Test/src/Participants/ParticipantRepository.php index 1a8aa9af9586..37e00e6ef4c4 100755 --- a/components/ILIAS/Test/src/Participants/ParticipantRepository.php +++ b/components/ILIAS/Test/src/Participants/ParticipantRepository.php @@ -289,10 +289,11 @@ private function applyOrder(?Order $order): string $order_by = []; foreach ($order->get() as $subject => $direction) { $order_by[] = match ($subject) { - 'name' => "lastname $direction, firstname $direction", - 'ip_range' => "ip_range_from $direction, ip_range_to $direction", - 'total_attempts' => "tries $direction", - 'extra_time' => "extra_time $direction", + 'name' => "lastname {$direction}, firstname {$direction}", + 'login' => "login {$direction}", + 'ip_range' => "ip_range_from {$direction}, ip_range_to {$direction}", + 'total_attempts' => "tries {$direction}", + 'extra_time' => "extra_time {$direction}", default => null }; } diff --git a/components/ILIAS/Test/src/Participants/ParticipantTable.php b/components/ILIAS/Test/src/Participants/ParticipantTable.php index 5932337c3d03..98520b72356c 100644 --- a/components/ILIAS/Test/src/Participants/ParticipantTable.php +++ b/components/ILIAS/Test/src/Participants/ParticipantTable.php @@ -97,20 +97,22 @@ public function getRows( $current_user_timezone = new \DateTimeZone($this->current_user->getTimeZone()); + /** @var \ILIAS\Test\Participants\Participant $record */ foreach ($this->getViewControlledRecords($filter_data, $range, $order) as $record) { $total_duration = $record->getTotalDuration($processing_time); $status_of_attempt = $record->getAttemptOverviewInformation()?->getStatusOfAttempt() ?? StatusOfAttempt::NOT_YET_STARTED; $row = [ - 'name' => $this->test_object->buildName($record->getUserId(), $record->getLastname(), $record->getFirstname()), + 'name' => $this->test_object->buildName($record->getUserId(), $record->getFirstname(), $record->getLastname()), 'login' => $record->getLogin(), 'matriculation' => $record->getMatriculation(), + 'total_time_on_task' => $record->getAttemptOverviewInformation()?->getHumanReadableTotalTimeOnTask() ?? '', 'status_of_attempt' => $this->lng->txt($status_of_attempt->value), 'id_of_attempt' => $record->getAttemptOverviewInformation()?->getExamId(), 'ip_range' => $record->getClientIpTo() !== '' || $record->getClientIpFrom() !== '' ? sprintf('%s - %s', $record->getClientIpFrom(), $record->getClientIpTo()) : '', - 'total_attempts' => $record->getAttempts(), + 'total_attempts' => $record->getAttemptOverviewInformation()?->getNrOfAttempts() ?? 0, 'extra_time' => $record->getExtraTime() > 0 ? sprintf('%d min', $record->getExtraTime()) : '', 'total_duration' => $total_duration > 0 ? sprintf('%d min', $total_duration / 60) : '', 'remaining_duration' => sprintf('%d min', $record->getRemainingDuration($processing_time, $reset_time_on_new_attempt) / 60), @@ -141,6 +143,9 @@ public function getRows( $record->getAttemptOverviewInformation()?->getNrOfTotalQuestions() ); $row['percent_of_available_points'] = $record->getAttemptOverviewInformation()?->getReachedPointsInPercent(); + } + + if ($status_of_attempt->isFinished()) { $row['test_passed'] = $record->getAttemptOverviewInformation()?->hasPassingMark() ?? false; $row['mark'] = $record->getAttemptOverviewInformation()?->getMark(); } @@ -315,9 +320,9 @@ private function getTableComponent(ServerRequestInterface $request, ?array $filt return $this->ui_factory ->table() ->data( + $this, $this->lng->txt('list_of_participants'), $this->getColumns(), - $this ) ->withId(self::ID) ->withRequest($request) @@ -350,6 +355,8 @@ private function getColumns(): array $this->lng->txt('tst_attempt_started'), $this->current_user->getDateTimeFormat() )->withIsSortable(true), + 'total_time_on_task' => $column_factory->text($this->lng->txt('working_time')) + ->withIsOptional(true, false), 'total_attempts' => $column_factory->number($this->lng->txt('total_attempts')) ->withIsOptional(true, false) ->withIsSortable(true), @@ -428,20 +435,35 @@ private function loadRecords(?array $filter, Order $order): iterable ) ); - $access_filter = $this->participant_access_filter->getManageParticipantsUserFilter($this->test_object->getRefId()); - $filtered_user_ids = $access_filter(array_map( - fn(Participant $participant) => $participant->getUserId(), - $records - )); - $this->records = array_filter( $records, - fn(Participant $participant) => in_array($participant->getUserId(), $filtered_user_ids), + fn(Participant $participant) => in_array( + $participant->getUserId(), + $this->buildAccessFilteredParticipantsList($records) + ) ); return $this->records; } + /** + * + * @param array $records + * @return array + */ + private function buildAccessFilteredParticipantsList(array $records): array + { + $manage_access_filter = $this->participant_access_filter + ->getManageParticipantsUserFilter($this->test_object->getRefId()); + $access_results_access_filter = $this->participant_access_filter + ->getAccessResultsUserFilter($this->test_object->getRefId()); + $participant_ids = array_map( + fn(Participant $participant) => $participant->getUserId(), + $records + ); + return $manage_access_filter($participant_ids) + $access_results_access_filter($participant_ids); + } + /** * @return iterable @@ -451,7 +473,7 @@ private function getViewControlledRecords(?array $filter_data, Range $range, Ord return $this->limitRecords( $this->sortRecords( $this->filterRecords( - $records = $this->results_data_factory->addAttemptOverviewInformationToParticipants( + $this->results_data_factory->addAttemptOverviewInformationToParticipants( $this->results_presentation_settings, $this->test_object, $this->loadRecords($filter_data, $order) diff --git a/components/ILIAS/Test/src/Participants/ParticipantTableActions.php b/components/ILIAS/Test/src/Participants/ParticipantTableActions.php index 7dfd3a83dabd..d639d59b790b 100644 --- a/components/ILIAS/Test/src/Participants/ParticipantTableActions.php +++ b/components/ILIAS/Test/src/Participants/ParticipantTableActions.php @@ -25,7 +25,7 @@ use ILIAS\Language\Language; use ILIAS\Refinery\Factory as Refinery; use ILIAS\UI\Component\Table\DataRow; -use ILIAS\UI\Component\Table\Action\Standard as StandardAction; +use ILIAS\UI\Component\Table\Action\Action; use ILIAS\UI\Component\Modal\Modal; use ILIAS\UI\Factory as UIFactory; use ILIAS\UI\Renderer as UIRenderer; @@ -70,7 +70,7 @@ function (TableAction $action) use ( $row_id_token, $action_token, $action_type_token - ): ?StandardAction { + ): ?Action { if (!$action->isAvailable()) { return null; } diff --git a/components/ILIAS/Test/src/Participants/ParticipantTableFinishTestAction.php b/components/ILIAS/Test/src/Participants/ParticipantTableFinishTestAction.php index c6f8f1fa1da3..799e3e0e9ceb 100644 --- a/components/ILIAS/Test/src/Participants/ParticipantTableFinishTestAction.php +++ b/components/ILIAS/Test/src/Participants/ParticipantTableFinishTestAction.php @@ -27,7 +27,7 @@ use ILIAS\Test\Results\Data\Repository as TestResultRepository; use ILIAS\UI\Factory as UIFactory; use ILIAS\UI\Component\Modal\Modal; -use ILIAS\UI\Component\Table\Action\Action; +use ILIAS\UI\Component\Table\Action\Standard as StandardAction; use ILIAS\UI\URLBuilder; use ILIAS\UI\URLBuilderToken; use Psr\Http\Message\ServerRequestInterface; @@ -64,7 +64,7 @@ public function getTableAction( URLBuilderToken $row_id_token, URLBuilderToken $action_token, URLBuilderToken $action_type_token - ): Action { + ): StandardAction { return $this->ui_factory->table()->action()->standard( $this->lng->txt(self::ACTION_ID), $url_builder @@ -90,11 +90,7 @@ public function getModal( array_map( fn(Participant $participant) => $this->ui_factory->modal()->interruptiveItem()->standard( (string) $participant->getUserId(), - sprintf( - '%s, %s', - $participant->getLastname(), - $participant->getFirstname() - ) + (new \ilObjUser($participant->getUserId()))->getPublicName() ), $selected_participants ) @@ -119,17 +115,17 @@ public function onSubmit( return null; } - if (!$this->test_obj->getResetProcessingTime() && count($selected_participants) > 1) { - foreach ($selected_participants as $participant) { - if ($participant->hasUnfinishedAttempts()) { - $this->tpl->setOnScreenMessage( - \ilGlobalTemplateInterface::MESSAGE_TYPE_FAILURE, - $this->lng->txt('finish_test_more_than_one_selected'), - true - ); - return null; - } - } + if (count($selected_participants) > 1 + && $this->test_obj->getNrOfTries() === 1 + && $this->test_obj->getEnableProcessingTime() + && !$this->test_obj->getResetProcessingTime() + && !$this->haveAllSelectedParticipantsReachedMaxProcessingTime($selected_participants)) { + $this->tpl->setOnScreenMessage( + \ilGlobalTemplateInterface::MESSAGE_TYPE_FAILURE, + $this->lng->txt('finish_pass_for_multiple_users_in_processing_time'), + true + ); + return null; } // This is required here because of late test object binding @@ -141,13 +137,11 @@ public function onSubmit( foreach ($selected_participants as $participant) { $process_locker = $this->process_locker_factory->withContextId($participant->getActiveId())->getLocker(); - - $test_pass_finisher = new \ilTestPassFinishTasks( + (new \ilTestPassFinishTasks( $test_session_factory->getSession($participant->getActiveId()), - $this->test_obj->getId(), + $this->test_obj, $this->test_pass_result_repository - ); - $test_pass_finisher->performFinishTasks($process_locker, StatusOfAttempt::FINISHED_BY_ADMINISTRATOR); + ))->performFinishTasks($process_locker, StatusOfAttempt::FINISHED_BY_ADMINISTRATOR); } $logger = $this->test_obj->getTestLogger(); @@ -191,11 +185,7 @@ private function resolveMessage( if (count($selected_participants) === 1) { return sprintf( $this->lng->txt('finish_test_single'), - sprintf( - '%s, %s', - $selected_participants[0]->getLastname(), - $selected_participants[0]->getFirstname() - ) + (new \ilObjUser($selected_participants[0]->getUserId()))->getPublicName() ); } @@ -206,4 +196,18 @@ public function getSelectionErrorMessage(): ?string { return $this->lng->txt('finish_test_no_valid_participants_selected'); } + + private function haveAllSelectedParticipantsReachedMaxProcessingTime(array $selected_participants): bool + { + foreach ($selected_participants as $participant) { + if (!$participant->hasUnfinishedAttempts() + || !$this->test_obj->isMaxProcessingTimeReached( + $this->test_obj->getStartingTimeOfUser($participant->getActiveId()), + $participant->getActiveId() + )) { + return false; + } + } + return true; + } } diff --git a/components/ILIAS/Test/src/Participants/ParticipantTableIpRangeAction.php b/components/ILIAS/Test/src/Participants/ParticipantTableIpRangeAction.php index cf92a68cdd0f..dd6bb4bdc813 100644 --- a/components/ILIAS/Test/src/Participants/ParticipantTableIpRangeAction.php +++ b/components/ILIAS/Test/src/Participants/ParticipantTableIpRangeAction.php @@ -81,7 +81,7 @@ public function getModal( ); $validate_order = $this->refinery->custom()->constraint( function (?array $vs): bool { - if ($vs === null) { + if ($vs['from'] === '' && $vs['to'] === '') { return true; } return $this->checkIpRangeValidity( diff --git a/components/ILIAS/Test/src/Presentation/TabsManager.php b/components/ILIAS/Test/src/Presentation/TabsManager.php index 9ee2144156f6..cd0944718010 100755 --- a/components/ILIAS/Test/src/Presentation/TabsManager.php +++ b/components/ILIAS/Test/src/Presentation/TabsManager.php @@ -95,6 +95,7 @@ public function activateTab(string $tab_id): void case self::TAB_ID_YOUR_RESULTS: case self::TAB_ID_SETTINGS: case self::TAB_ID_TEST: + case self::TAB_ID_LEARNING_PROGRESS: $this->tabs->activateTab($tab_id); } } @@ -184,6 +185,10 @@ protected function isPermissionsAccessGranted(): bool protected function isLpAccessGranted(): bool { + if (!$this->test_access->getAccess()->checkAccess('read', '', $this->test_object->getRefId())) { + return false; + } + return \ilLearningProgressAccess::checkAccess($this->test_object->getRefId()); } diff --git a/components/ILIAS/Test/src/Presentation/class.TestScreenGUI.php b/components/ILIAS/Test/src/Presentation/class.TestScreenGUI.php index 8e1db3734f68..4a06018e98df 100755 --- a/components/ILIAS/Test/src/Presentation/class.TestScreenGUI.php +++ b/components/ILIAS/Test/src/Presentation/class.TestScreenGUI.php @@ -35,8 +35,7 @@ use ILIAS\HTTP\Services as HTTPServices; use ILIAS\Refinery\Factory as Refinery; use ILIAS\Test\Logging\TestParticipantInteractionTypes; -use ilInfoScreenGUI; -use ilObjTestGUI; +use ILIAS\Style\Content\Service as ContentStyle; /** * Class TestScreenGUI @@ -63,6 +62,7 @@ public function __construct( private readonly Refinery $refinery, private readonly \ilCtrlInterface $ctrl, private readonly \ilGlobalTemplateInterface $tpl, + private readonly ContentStyle $content_style, private readonly HTTPServices $http, private readonly TabsManager $tabs_manager, private readonly \ilAccessHandler $access, @@ -140,10 +140,10 @@ private function handleRenderMessageBox(array $elements): array $message_box_message_elements[] = $this->lng->txt('tst_launcher_status_message_password'); } - if ($test_behaviour_settings->getProcessingTimeEnabled()) { + if ($test_behaviour_settings->getProcessingTimeEnabled() && !$this->isUserOutOfProcessingTime()) { $message_box_message_elements[] = sprintf( $this->lng->txt('tst_time_limit_message'), - $this->object->getProcessingTimeInSeconds($this->test_session->getActiveId()) / 60 + $test_behaviour_settings->getProcessingTimeAsMinutes() ); } @@ -186,6 +186,7 @@ private function handleRenderIntroduction(array $elements): array $this->main_settings->getIntroductionSettings()->getIntroductionEnabled() && !empty($introduction) ) { + $this->content_style->gui()->addCss($this->tpl, $this->ref_id); $elements[] = $this->ui_factory->panel()->standard( $this->lng->txt('tst_introduction'), $this->ui_factory->legacy()->content($introduction), @@ -197,25 +198,16 @@ private function handleRenderIntroduction(array $elements): array private function handleRenderLauncher(array $elements): array { - $launcher = $this->getLauncher(); - $request = $this->http->request(); - $key = 'launcher_id'; - - if (array_key_exists($key, $request->getQueryParams()) && $request->getQueryParams()[$key] === 'exam_modal') { - $launcher = $launcher->withRequest($request); - } - - $elements[] = $launcher; - + $elements[] = $this->getLauncher(); return $elements; } private function getLauncher(): Launcher { - $launcher = $this->ui_factory->launcher(); + $launcher_factory = $this->ui_factory->launcher(); if ($this->object->isStartingTimeEnabled() && !$this->object->startingTimeReached()) { - return $launcher + return $launcher_factory ->inline($this->data_factory->link('', $this->data_factory->uri($this->http->request()->getUri()->__toString()))) ->withButtonLabel(sprintf( $this->lng->txt('detail_starting_time_not_reached'), @@ -225,7 +217,7 @@ private function getLauncher(): Launcher } if ($this->object->isEndingTimeEnabled() && $this->object->endingTimeReached()) { - return $launcher + return $launcher_factory ->inline($this->data_factory->link('', $this->data_factory->uri($this->http->request()->getUri()->__toString()))) ->withButtonLabel(sprintf( $this->lng->txt('detail_ending_time_reached'), @@ -235,7 +227,7 @@ private function getLauncher(): Launcher } if ($this->isUserOutOfProcessingTime()) { - return $launcher + return $launcher_factory ->inline($this->data_factory->link('', $this->data_factory->uri($this->http->request()->getUri()->__toString()))) ->withButtonLabel($this->lng->txt('tst_out_of_time_message'), false) ; @@ -247,21 +239,21 @@ private function getLauncher(): Launcher ); if ($participant_access === ParticipantAccess::NOT_INVITED) { - return $launcher + return $launcher_factory ->inline($this->data_factory->link('', $this->data_factory->uri($this->http->request()->getUri()->__toString()))) ->withButtonLabel($this->lng->txt('tst_exam_not_assigned_participant_disclaimer'), false) ; } if ($participant_access !== ParticipantAccess::ALLOWED) { - return $launcher + return $launcher_factory ->inline($this->data_factory->link('', $this->data_factory->uri($this->http->request()->getUri()->__toString()))) ->withButtonLabel($participant_access->getAccessForbiddenMessage($this->lng), false) ; } - if (!$this->hasUserPassedAlreadyAndCanRetake()) { - return $launcher + if ($this->blockUserAfterHavingPassed()) { + return $launcher_factory ->inline($this->data_factory->link('', $this->data_factory->uri($this->http->request()->getUri()->__toString()))) ->withButtonLabel($this->lng->txt('tst_already_passed_cannot_retake'), false) ; @@ -269,7 +261,7 @@ private function getLauncher(): Launcher $next_pass_allowed_timestamp = 0; if (!$this->object->isNextPassAllowed($this->test_passes_selector, $next_pass_allowed_timestamp)) { - return $launcher + return $launcher_factory ->inline($this->data_factory->link('', $this->data_factory->uri($this->http->request()->getUri()->__toString()))) ->withButtonLabel( sprintf( @@ -281,39 +273,20 @@ private function getLauncher(): Launcher ; } - if ($this->hasAvailablePasses()) { - if ($this->lastPassSuspended()) { - $launcher = $launcher->inline($this->getResumeLauncherLink()); - } - if ($this->newPassCanBeStarted()) { - if ($this->isModalLauncherNeeded()) { - $launcher = $launcher - ->inline($this->getModalLauncherLink()) - ->withInputs( - $this->ui_factory->input()->field()->group($this->getModalLauncherInputs()), - function (Result $result) { - $this->evaluateLauncherModalForm($result); - }, - $this->getModalLauncherMessageBox() - ) - ->withModalSubmitLabel($this->lng->txt('continue')) - ; - } else { - $launcher = $launcher->inline($this->getStartLauncherLink()); - } - } - } else { - $launcher = $launcher + if (!$this->hasAvailablePasses()) { + return $launcher_factory ->inline($this->data_factory->link('', $this->data_factory->uri($this->http->request()->getUri()->__toString()))) - ->withButtonLabel($this->lng->txt('tst_launcher_button_label_passes_limit_reached'), false) - ; + ->withButtonLabel($this->lng->txt('tst_launcher_button_label_passes_limit_reached'), false); } - if ($launcher instanceof LauncherFactory) { - $launcher = $launcher->inline($this->data_factory->link('Test', $this->data_factory->uri($this->http->request()->getUri()->__toString()))); + if ($this->lastPassSuspended()) { + return $launcher_factory->inline($this->getResumeLauncherLink()); } - return $launcher; + if ($this->isModalLauncherNeeded()) { + return $this->buildModalLauncher(); + } + return $launcher_factory->inline($this->getStartLauncherLink()); } private function getResumeLauncherLink(): Link @@ -325,6 +298,26 @@ private function getResumeLauncherLink(): Link return $this->data_factory->link($this->lng->txt('tst_resume_test'), $this->data_factory->uri(ILIAS_HTTP_PATH . '/' . $url)); } + private function buildModalLauncher(): Launcher + { + $launcher = $this->ui_factory->launcher()->inline($this->getModalLauncherLink()) + ->withInputs( + $this->ui_factory->input()->field()->group($this->getModalLauncherInputs()), + function (Result $result) { + $this->evaluateLauncherModalForm($result); + }, + $this->getModalLauncherMessageBox() + )->withModalSubmitLabel($this->lng->txt('continue')); + + $request = $this->http->request(); + $key = 'launcher_id'; + if (array_key_exists($key, $request->getQueryParams()) + && $request->getQueryParams()[$key] === 'exam_modal') { + $launcher = $launcher->withRequest($request); + } + return $launcher; + } + private function getModalLauncherLink(): Link { $uri = $this->data_factory->uri($this->http->request()->getUri()->__toString())->withParameter('launcher_id', 'exam_modal'); @@ -525,13 +518,14 @@ private function isUserOutOfProcessingTime(): bool ); } - private function hasUserPassedAlreadyAndCanRetake(): bool + private function blockUserAfterHavingPassed(): bool { if ($this->main_settings->getTestBehaviourSettings()->getBlockAfterPassedEnabled()) { - return !$this->test_passes_selector->hasTestPassedOnce($this->test_session->getActiveId()); + return $this->test_passes_selector->getLastFinishedPass() >= 0 + && $this->test_passes_selector->hasTestPassedOnce($this->test_session->getActiveId()); } - return true; + return false; } private function hasAvailablePasses(): bool @@ -546,13 +540,6 @@ private function lastPassSuspended(): bool return (count($this->test_passes_selector->getExistingPasses()) - count($this->test_passes_selector->getClosedPasses())) === 1; } - private function newPassCanBeStarted(): bool - { - $nr_of_tries = $this->object->getNrOfTries(); - - return !$this->lastPassSuspended() && ($nr_of_tries === 0 || count($this->test_passes_selector->getExistingPasses()) < $nr_of_tries); - } - private function isModalLauncherNeeded(): bool { return ( diff --git a/components/ILIAS/Test/src/Questions/Presentation/Printer.php b/components/ILIAS/Test/src/Questions/Presentation/Printer.php index 33d799788c7f..6c4bae5eceea 100644 --- a/components/ILIAS/Test/src/Questions/Presentation/Printer.php +++ b/components/ILIAS/Test/src/Questions/Presentation/Printer.php @@ -80,7 +80,7 @@ public function printSelectedQuestions( $question_gui = $this->test_obj->createQuestionGUI('', $question_id); $question_gui->setRenderPurpose(\assQuestionGUI::RENDER_PURPOSE_PREVIEW); - $this->question_header_builder->setQuestionTitle($question_gui->getObject()->getTitle()); + $this->question_header_builder->setQuestionTitle($question_gui->getObject()->getTitleForHTMLOutput()); $this->question_header_builder->setQuestionPoints($question_gui->getObject()->getMaximumPoints()); $this->question_header_builder->setQuestionPosition($counter); $template->setVariable('QUESTION_HEADER', $this->question_header_builder->getHTML()); @@ -133,7 +133,7 @@ public function printAnswers(int $question_id): void $question_gui = $this->test_obj->createQuestionGUI('', $question_id); $question_gui->setRenderPurpose(\assQuestionGUI::RENDER_PURPOSE_PREVIEW); - $template->setVariable('TITLE', $question_gui->getObject()->getTitle()); + $template->setVariable('TITLE', $question_gui->getObject()->getTitleForHTMLOutput()); $template->setVariable('TXT_PRINT_DATE', $this->lng->txt('date')); $template->setVariable( 'VALUE_PRINT_DATE', diff --git a/components/ILIAS/Test/src/Questions/Presentation/QuestionsBrowserFilter.php b/components/ILIAS/Test/src/Questions/Presentation/QuestionsBrowserFilter.php index 5968f0f2058c..da9df304104c 100644 --- a/components/ILIAS/Test/src/Questions/Presentation/QuestionsBrowserFilter.php +++ b/components/ILIAS/Test/src/Questions/Presentation/QuestionsBrowserFilter.php @@ -89,7 +89,6 @@ private function getFields(FieldFactory $input): array 'taxonomy_title' => [$input->text($this->lng->txt('taxonomy_title')), true], 'taxonomy_node_title' => [$input->text($this->lng->txt('taxonomy_node_title')), true], 'feedback' => [$input->select($this->lng->txt('feedback'), $yes_no_all_options), false], - 'hints' => [$input->select($this->lng->txt('hints'), $yes_no_all_options), false] ]; } diff --git a/components/ILIAS/Test/src/Questions/Presentation/QuestionsBrowserTable.php b/components/ILIAS/Test/src/Questions/Presentation/QuestionsBrowserTable.php index 928b2efe4635..5c08639c7da6 100644 --- a/components/ILIAS/Test/src/Questions/Presentation/QuestionsBrowserTable.php +++ b/components/ILIAS/Test/src/Questions/Presentation/QuestionsBrowserTable.php @@ -57,9 +57,9 @@ public function __construct( public function getComponent(ServerRequestInterface $request, ?array $filter): Data { return $this->ui_factory->table()->data( + $this, $this->lng->txt('list_of_questions'), $this->getColumns(), - $this )->withId($this->table_id) ->withActions($this->getActions()) ->withRequest($request) @@ -103,11 +103,6 @@ public function getColumns(): array $iconYes, $iconNo )->withIsOptional(true, false), - 'hints' => $column_factory->boolean( - $this->lng->txt('hints'), - $iconYes, - $iconNo - )->withIsOptional(true, false), 'created' => $column_factory->date( $this->lng->txt('created'), $this->current_user->getDateTimeFormat() diff --git a/components/ILIAS/Test/src/Questions/Presentation/QuestionsOfAttemptTable.php b/components/ILIAS/Test/src/Questions/Presentation/QuestionsOfAttemptTable.php index 3ef3a6852450..dede9020151d 100644 --- a/components/ILIAS/Test/src/Questions/Presentation/QuestionsOfAttemptTable.php +++ b/components/ILIAS/Test/src/Questions/Presentation/QuestionsOfAttemptTable.php @@ -102,9 +102,9 @@ public function buildComponents(): array } $components[] = $this->ui_factory->table()->data( + $this, $this->lng->txt('question_summary'), $this->getColumns(), - $this ) ->withRequest($this->http->request()) ->withId('listofquestions'); diff --git a/components/ILIAS/Test/src/Questions/Presentation/QuestionsTable.php b/components/ILIAS/Test/src/Questions/Presentation/QuestionsTable.php index 2ecfbb078323..9ee8926d9d58 100644 --- a/components/ILIAS/Test/src/Questions/Presentation/QuestionsTable.php +++ b/components/ILIAS/Test/src/Questions/Presentation/QuestionsTable.php @@ -23,20 +23,22 @@ use ILIAS\Test\Utilities\TitleColumnsBuilder; use ILIAS\Test\Questions\Properties\Repository as TestQuestionsRepository; use ILIAS\Test\Questions\Properties\Properties as TestQuestionProperties; +use ILIAS\Refinery\Factory as Refinery; use ILIAS\UI\Factory as UIFactory; use ILIAS\UI\Component\Table\Ordering; -use ILIAS\UI\Component\Table\OrderingBinding; +use ILIAS\UI\Component\Table\OrderingRetrieval; use ILIAS\UI\Component\Table\OrderingRowBuilder; use ILIAS\Language\Language; use Psr\Http\Message\ServerRequestInterface; -class QuestionsTable implements OrderingBinding +class QuestionsTable implements OrderingRetrieval { /** * @param array $data */ public function __construct( private readonly UIFactory $ui_factory, + private readonly Refinery $refinery, private readonly ServerRequestInterface $request, private readonly QuestionsTableActions $table_actions, private readonly Language $lng, @@ -49,15 +51,19 @@ public function __construct( public function getTableComponent(): Ordering { $table = $this->ui_factory->table()->ordering( + $this, + $this->table_actions->getOrderActionUrl(), $this->lng->txt('list_of_questions'), $this->getColumns(), - $this, - $this->table_actions->getOrderActionUrl() ) ->withId((string) $this->test_obj->getId()) ->withActions($this->table_actions->getActions()) ->withRequest($this->request); + if ($this->test_obj->isRandomTest()) { + return $table->withOrderingDisabled(true); + } + return $table; } @@ -69,6 +75,7 @@ public function getRows( $row = $record->getAsQuestionsTableRow( $this->lng, $this->ui_factory, + $this->refinery, $this->table_actions->getQuestionTargetLinkBuilder(), $row_builder, $this->title_builder diff --git a/components/ILIAS/Test/src/Questions/Presentation/QuestionsTableActions.php b/components/ILIAS/Test/src/Questions/Presentation/QuestionsTableActions.php index 45e857d74a4e..46848edced91 100644 --- a/components/ILIAS/Test/src/Questions/Presentation/QuestionsTableActions.php +++ b/components/ILIAS/Test/src/Questions/Presentation/QuestionsTableActions.php @@ -43,7 +43,6 @@ class QuestionsTableActions private const ACTION_EDIT_QUESTION = 'edit_question'; private const ACTION_EDIT_PAGE = 'edit_page'; private const ACTION_FEEDBACK = 'feedback'; - private const ACTION_HINTS = 'hints'; private const ACTION_PRINT_QUESTIONS = 'print_questions'; private const ACTION_PRINT_ANSWERS = 'print_answers'; private const ACTION_DOWNLOAD_FILE_QUESTION_ANSWERS = 'download_files'; @@ -89,7 +88,6 @@ public function setDisabledActions( self::ACTION_ADJUST, $this->is_adjusting_questions_with_results_allowed && !$this->is_in_test_with_results )->withDisabledAction(self::ACTION_FEEDBACK, $disable_default_actions) - ->withDisabledAction(self::ACTION_HINTS, $disable_default_actions) ->withDisabledAction(self::ACTION_PRINT_ANSWERS, !$this->is_in_test_with_results) ->withDisabledAction( self::ACTION_DOWNLOAD_FILE_QUESTION_ANSWERS, @@ -118,7 +116,6 @@ public function getActions(): array self::ACTION_EDIT_QUESTION => $ag('single', 'edit_question', self::ACTION_EDIT_QUESTION), self::ACTION_EDIT_PAGE => $ag('single', 'edit_page', self::ACTION_EDIT_PAGE), self::ACTION_FEEDBACK => $ag('single', 'tst_feedback', self::ACTION_FEEDBACK), - self::ACTION_HINTS => $ag('single', 'tst_question_hints_tab', self::ACTION_HINTS), self::ACTION_PRINT_ANSWERS => $ag('single', 'print_answers', self::ACTION_PRINT_ANSWERS), self::ACTION_DOWNLOAD_FILE_QUESTION_ANSWERS => $ag('single', 'download_all_files', self::ACTION_DOWNLOAD_FILE_QUESTION_ANSWERS), ]; @@ -214,14 +211,6 @@ public function handleCommand( ); return false; - case self::ACTION_HINTS: - $this->redirectWithQuestionParameters( - current($row_ids), - \ilAssQuestionHintsGUI::class, - \ilAssQuestionHintsGUI::CMD_SHOW_LIST - ); - return false; - case self::ACTION_DELETE: echo $this->ui_renderer->renderAsync( $this->getDeleteConfirmation(array_filter($row_ids)) diff --git a/components/ILIAS/Test/src/Questions/Properties/Properties.php b/components/ILIAS/Test/src/Questions/Properties/Properties.php index 42962a0128d5..2a1fe517ede4 100644 --- a/components/ILIAS/Test/src/Questions/Properties/Properties.php +++ b/components/ILIAS/Test/src/Questions/Properties/Properties.php @@ -22,6 +22,7 @@ use ILIAS\Test\Utilities\TitleColumnsBuilder; use ILIAS\TestQuestionPool\Questions\GeneralQuestionProperties; +use ILIAS\Refinery\Factory as Refinery; use ILIAS\UI\Factory as UIFactory; use ILIAS\UI\Component\Table\OrderingRowBuilder; use ILIAS\UI\Component\Table\OrderingRow; @@ -86,6 +87,7 @@ public function withAggregatedResults(PropertyAggregatedResults $aggregated_resu public function getAsQuestionsTableRow( Language $lng, UIFactory $ui_factory, + Refinery $refinery, \Closure $question_target_link_builder, OrderingRowBuilder $row_builder, TitleColumnsBuilder $title_builder @@ -95,7 +97,9 @@ public function getAsQuestionsTableRow( [ 'question_id' => $this->question_id, 'title' => $ui_factory->link()->standard( - $this->question_properties->getTitle(), + $refinery->encode()->htmlSpecialCharsAsEntities()->transform( + $this->question_properties->getTitle() + ), $question_target_link_builder($this->question_id) ), 'description' => $this->question_properties->getDescription(), diff --git a/components/ILIAS/Test/src/Questions/RandomQuestionSetNonAvailablePoolsTable.php b/components/ILIAS/Test/src/Questions/RandomQuestionSetNonAvailablePoolsTable.php index 54578b91d466..ae06dc874d28 100644 --- a/components/ILIAS/Test/src/Questions/RandomQuestionSetNonAvailablePoolsTable.php +++ b/components/ILIAS/Test/src/Questions/RandomQuestionSetNonAvailablePoolsTable.php @@ -84,7 +84,7 @@ protected function getData(Range $range, Order $order): array public function getComponent(): DataTable { return $this->ui_factory->table() - ->data($this->lng->txt('tst_non_avail_pools_table'), $this->getColumns(), $this) + ->data($this, $this->lng->txt('tst_non_avail_pools_table'), $this->getColumns()) ->withRequest($this->request) ->withActions($this->getActions()) ->withId('tst_non_avail_pools_table'); diff --git a/components/ILIAS/Test/src/Questions/RandomQuestionSetSourcePoolDefinitionListTable.php b/components/ILIAS/Test/src/Questions/RandomQuestionSetSourcePoolDefinitionListTable.php index 3de324384dc0..3ae00a36c4b5 100644 --- a/components/ILIAS/Test/src/Questions/RandomQuestionSetSourcePoolDefinitionListTable.php +++ b/components/ILIAS/Test/src/Questions/RandomQuestionSetSourcePoolDefinitionListTable.php @@ -24,7 +24,7 @@ use ILIAS\Data\URI; use ILIAS\Test\Utilities\TitleColumnsBuilder; use ILIAS\Test\RequestDataCollector; -use ILIAS\UI\Component\Table\OrderingBinding; +use ILIAS\UI\Component\Table\OrderingRetrieval; use ILIAS\UI\Component\Table\OrderingRowBuilder; use ILIAS\UI\Component\Table\Ordering as OrderingTable; use ILIAS\UI\Factory as UIFactory; @@ -34,7 +34,7 @@ use ilTestRandomQuestionSetConfigGUI as ConfigGUI; use Psr\Http\Message\ServerRequestInterface; -class RandomQuestionSetSourcePoolDefinitionListTable implements OrderingBinding +class RandomQuestionSetSourcePoolDefinitionListTable implements OrderingRetrieval { private URI $target; private URLBuilder $url_builder; @@ -111,10 +111,10 @@ public function getComponent(): OrderingTable { return $this->ui_factory->table() ->ordering( + $this, + $this->getTarget(ConfigGUI::CMD_SAVE_SRC_POOL_DEF_LIST), $this->lng->txt('tst_src_quest_pool_def_list_table'), $this->getColumns(), - $this, - $this->getTarget(ConfigGUI::CMD_SAVE_SRC_POOL_DEF_LIST) ) ->withActions($this->getActions()) ->withRequest($this->request) diff --git a/components/ILIAS/Test/src/Results/Data/AttemptOverview.php b/components/ILIAS/Test/src/Results/Data/AttemptOverview.php index dc618fd95ea7..a4df081670c4 100644 --- a/components/ILIAS/Test/src/Results/Data/AttemptOverview.php +++ b/components/ILIAS/Test/src/Results/Data/AttemptOverview.php @@ -39,8 +39,8 @@ public function __construct( private readonly ?Mark $mark = null, private readonly int $nr_of_answered_questions = 0, private readonly int $nr_of_questions_in_attempt = 0, - private readonly ?int $requested_hints_count = null, private readonly int $time_on_task = 0, + private readonly int $total_time_on_task = 0, private readonly ?\DateTimeImmutable $attempt_started_date = null, private readonly ?\DateTimeImmutable $last_access = null, private readonly int $nr_of_attempts = 0, @@ -118,6 +118,11 @@ public function getStatusOfAttempt(): StatusOfAttempt return $this->status_of_attempt; } + public function getNrOfAttempts(): int + { + return $this->nr_of_attempts; + } + public function getAsDescriptiveListing( Language $lng, UIFactory $ui_factory, @@ -130,10 +135,6 @@ public function getAsDescriptiveListing( $lng->txt('tst_stat_result_resultsmarks') => $this->mark?->getShortName() ?? '' ]; - if ($this->settings->getShowHints()) { - $items[$lng->txt('tst_question_hints_requested_hint_count_header')] = (string) $this->requested_hints_count; - } - return $ui_factory->listing()->descriptive( $items + [ $lng->txt('tst_stat_result_timeontask') => $this->buildHumanReadableTime($this->time_on_task), @@ -150,6 +151,11 @@ public function getAsDescriptiveListing( ); } + public function getHumanReadableTotalTimeOnTask(): string + { + return $this->buildHumanReadableTime($this->total_time_on_task); + } + private function buildHumanReadableTime(int $time): string { $diff_seconds = $time; diff --git a/components/ILIAS/Test/src/Results/Data/Factory.php b/components/ILIAS/Test/src/Results/Data/Factory.php index 506dbf17a6e5..3925da26a9f7 100644 --- a/components/ILIAS/Test/src/Results/Data/Factory.php +++ b/components/ILIAS/Test/src/Results/Data/Factory.php @@ -117,8 +117,8 @@ public function getAttemptOverviewFor( $attempt_data->getMark(), $attempt_data->getAnsweredQuestionCount(), $attempt_data->getQuestionCount(), - $attempt_data->getRequestedHintsCount(), $attempt_data->getWorkingTime(), + $participant_data->getTimeOnTask(), $attempt_data->getStartTime(), $attempt_data->getLastAccessTime(), $participant_data->getPassCount(), @@ -144,26 +144,21 @@ function (Participant $v) use ($settings, $test_obj, $participants): Participant return $v; } - $last_attempt = $this->getAttemptOverviewFor( + $scored_attempt = $this->getAttemptOverviewFor( $settings, $test_obj, $v->getActiveId(), - $v->getLastStartedAttempt() + null ); - if ($last_attempt !== null - && $last_attempt->getStatusOfAttempt() === StatusOfAttempt::RUNNING - && $last_attempt->getStartedDate() !== null) { - $v = $v->withRunningAttemptStart($last_attempt->getStartedDate()); + if ($scored_attempt !== null + && $scored_attempt->getStatusOfAttempt() === StatusOfAttempt::RUNNING + && $scored_attempt->getStartedDate() !== null) { + $v = $v->withRunningAttemptStart($scored_attempt->getStartedDate()); } return $v->withAttemptOverviewInformation( - $this->getAttemptOverviewFor( - $settings, - $test_obj, - $v->getActiveId(), - null - ) + $scored_attempt ); }, $participants @@ -225,8 +220,6 @@ private function buildAttemptResults( $usr_score = $qresult['reached']; $workedthrough = (bool) $qresult['workedthrough']; $answered = (bool) $qresult['answered']; - $requested_hints = (int) $qresult['requested_hints']; - $question_gui = $test_obj->createQuestionGUI('', $qid); $shuffle_trafo = $this->shuffler->getAnswerShuffleFor($qid, $active_id, $attempt_id); @@ -307,7 +300,6 @@ private function buildAttemptResults( $feedback, $workedthrough, $answered, - $requested_hints, $recapitulation ); } diff --git a/components/ILIAS/Test/src/Results/Data/QuestionResult.php b/components/ILIAS/Test/src/Results/Data/QuestionResult.php index 49d2fb6b848c..8dbbebb86c7f 100755 --- a/components/ILIAS/Test/src/Results/Data/QuestionResult.php +++ b/components/ILIAS/Test/src/Results/Data/QuestionResult.php @@ -37,7 +37,6 @@ public function __construct( private readonly string $feedback, private readonly bool $workedthrough, private readonly bool $answered, - private readonly int $requested_hints, private readonly ?string $content_for_recapitulation ) { } @@ -104,8 +103,4 @@ public function getContentForRecapitulation(): ?string { return $this->content_for_recapitulation; } - public function getNumberOfRequestedHints(): int - { - return $this->requested_hints; - } } diff --git a/components/ILIAS/Test/src/Results/Presentation/AttemptResultsTable.php b/components/ILIAS/Test/src/Results/Presentation/AttemptResultsTable.php index 6ef9b1c62153..7b152661fac0 100644 --- a/components/ILIAS/Test/src/Results/Presentation/AttemptResultsTable.php +++ b/components/ILIAS/Test/src/Results/Presentation/AttemptResultsTable.php @@ -186,7 +186,9 @@ private function getMapping(): \Closure $title = sprintf( '%s [ID: %s]', - $question->getTitle(), + $this->refinery->encode()->htmlSpecialCharsAsEntities()->transform( + $question->getTitle() + ), (string) $question->getId() ); @@ -202,7 +204,6 @@ private function getMapping(): \Closure ]; $stats_fields = $important_fields; - $stats_fields[$lng->txt('tst_question_hints_requested_hint_count_header')] = (string) $question->getNumberOfRequestedHints(); $stats = $ui_factory->listing()->characteristicValue()->text($stats_fields); diff --git a/components/ILIAS/Test/src/Results/Presentation/Factory.php b/components/ILIAS/Test/src/Results/Presentation/Factory.php index dbe3759c2484..4977e77077fc 100644 --- a/components/ILIAS/Test/src/Results/Presentation/Factory.php +++ b/components/ILIAS/Test/src/Results/Presentation/Factory.php @@ -79,7 +79,6 @@ public function getAttemptResultsSettings( $test_obj->getId(), $show_hidden_questions, $show_optional_questions, - $test_obj->getMainSettings()->getQuestionBehaviourSettings()->getQuestionHintsEnabled(), $show_best_solution, $settings_result->getShowSolutionFeedback(), $settings_result->getShowSolutionAnswersOnly(), diff --git a/components/ILIAS/Test/src/Results/Presentation/Settings.php b/components/ILIAS/Test/src/Results/Presentation/Settings.php index 9821d76e4859..bbfb5fe5d709 100755 --- a/components/ILIAS/Test/src/Results/Presentation/Settings.php +++ b/components/ILIAS/Test/src/Results/Presentation/Settings.php @@ -26,7 +26,6 @@ public function __construct( private int $test_obj_id, private bool $show_hidden_questions = false, private bool $show_optional_questions = false, - private bool $show_hints = false, private bool $show_best_solution = true, private bool $show_feedback = true, private bool $question_text_only = false, @@ -49,11 +48,6 @@ public function getShowOptionalQuestions(): bool return $this->show_optional_questions; } - public function getShowHints(): bool - { - return $this->show_hints; - } - public function getShowBestSolution(): bool { return $this->show_best_solution; diff --git a/components/ILIAS/Test/src/Results/Toplist/DataRetrieval.php b/components/ILIAS/Test/src/Results/Toplist/DataRetrieval.php index ad3d70b848fb..f4dc4885b637 100755 --- a/components/ILIAS/Test/src/Results/Toplist/DataRetrieval.php +++ b/components/ILIAS/Test/src/Results/Toplist/DataRetrieval.php @@ -58,7 +58,6 @@ public function getColumns(): array ), 'score' => $column_factory->text($this->lng->txt('toplist_col_score')), 'percentage' => $column_factory->number($this->lng->txt('toplist_col_percentage'))->withUnit('%'), - 'hints' => $column_factory->number($this->lng->txt('toplist_col_hints')), 'workingtime' => $column_factory->text($this->lng->txt('toplist_col_wtime')), ]; @@ -66,7 +65,6 @@ public function getColumns(): array 'achieved' => $this->test_obj->getHighscoreAchievedTS(), 'score' => $this->test_obj->getHighscoreScore(), 'percentage' => $this->test_obj->getHighscorePercentage(), - 'hints' => $this->test_obj->getHighscoreHints(), 'workingtime' => $this->test_obj->getHighscoreWTime() ]; @@ -106,9 +104,6 @@ public function getRows( if (in_array('percentage', $visible_column_ids, true)) { $item['percentage'] = $row['percentage']; } - if (in_array('hints', $visible_column_ids, true)) { - $item['hints'] = $row['hint_count']; - } if (in_array('workingtime', $visible_column_ids, true)) { $item['workingtime'] = $this->formatTime($row['workingtime']); } diff --git a/components/ILIAS/Test/src/Results/Toplist/TestTopListRepository.php b/components/ILIAS/Test/src/Results/Toplist/TestTopListRepository.php index 65482d57d5a2..c762ae0eb364 100755 --- a/components/ILIAS/Test/src/Results/Toplist/TestTopListRepository.php +++ b/components/ILIAS/Test/src/Results/Toplist/TestTopListRepository.php @@ -335,7 +335,6 @@ private function buildEmptyItem(): array 'achieved' => '', 'score' => '', 'percentage' => '', - 'hints' => '', 'time' => '' ]; } diff --git a/components/ILIAS/Test/src/Scoring/Manual/ScoringByQuestionTable.php b/components/ILIAS/Test/src/Scoring/Manual/ScoringByQuestionTable.php index 3b0fe3603c76..6cb2119fa9cd 100644 --- a/components/ILIAS/Test/src/Scoring/Manual/ScoringByQuestionTable.php +++ b/components/ILIAS/Test/src/Scoring/Manual/ScoringByQuestionTable.php @@ -64,12 +64,21 @@ public function getTable( $f = $this->ui_factory->table(); $table = $f->data( + $data_retrieval->withFilterData($ui_service->filter()->getData($filter) ?? []), $title, [ self::COLUMN_NAME => $f->column()->text($this->lng->txt('name'))->withIsSortable(true), self::COLUMN_ATTEMPT => $f->column()->number($this->lng->txt('tst_attempt')), - self::COLUMN_POINTS_REACHED => $f->column()->number($this->lng->txt('tst_reached_points'))->withIsSortable(true), - self::COLUMN_POINTS_AVAILABLE => $f->column()->number($this->lng->txt('tst_maximum_points'))->withIsSortable(true), + self::COLUMN_POINTS_REACHED => $f + ->column() + ->number($this->lng->txt('tst_reached_points')) + ->withDecimals(2) + ->withIsSortable(true), + self::COLUMN_POINTS_AVAILABLE => $f + ->column() + ->number($this->lng->txt('tst_maximum_points')) + ->withDecimals(2) + ->withIsSortable(true), self::COLUMN_FEEDBACK => $f->column()->text($this->lng->txt('tst_feedback')), self::COLUMN_FINALIZED => $f->column()->boolean( $this->lng->txt('finalized_evaluation'), @@ -87,7 +96,6 @@ public function getTable( self::COLUMN_FINALIZED_BY => $f->column()->text($this->lng->txt('finalized_by'))->withIsSortable(true), self::COLUMN_FINALIZED_ON => $f->column()->date($this->lng->txt('finalized_on'), $date_format)->withIsSortable(true) ], - $data_retrieval->withFilterData($ui_service->filter()->getData($filter) ?? []) )->withActions( [ self::ACTION_SCORING => $f->action()->single( diff --git a/components/ILIAS/Test/src/Scoring/Manual/TestScoring.php b/components/ILIAS/Test/src/Scoring/Manual/TestScoring.php index c89f3b26757f..0dedb765d90f 100755 --- a/components/ILIAS/Test/src/Scoring/Manual/TestScoring.php +++ b/components/ILIAS/Test/src/Scoring/Manual/TestScoring.php @@ -45,9 +45,13 @@ class TestScoring { private bool $preserve_manual_scores = false; - private array $recalculated_passes = []; private int $question_id = 0; + /** + * @var array $question_cache + */ + protected array $question_cache = []; + public function __construct( private \ilObjTest $test, private \ilObjUser $scorer, @@ -76,17 +80,23 @@ public function setQuestionId(int $question_id): void $this->question_id = $question_id; } - public function recalculateSolutions(): void + public function recalculateSolutions(): array { - $participants = $this->test->getCompleteEvaluationData()->getParticipants(); - if (is_array($participants)) { - foreach ($participants as $active_id => $userdata) { - if (is_object($userdata) && is_array($userdata->getPasses())) { - $this->recalculatePasses($userdata, $active_id); - } - $this->test->updateTestResultCache($active_id); + $factory = new \ilTestEvaluationFactory($this->db, $this->test); + $participants = $factory->getCorrectionsEvaluationData()->getParticipants(); + + foreach ($participants as $active_id => $userdata) { + if (is_object($userdata) && is_array($userdata->getPasses())) { + $this->recalculatePasses($userdata, $active_id); + \ilLPStatusWrapper::_updateStatus( + $this->test->getId(), + $userdata->getUserID() + ); } } + + return $participants; + } public function recalculateSolution(int $active_id, int $pass): void @@ -111,9 +121,9 @@ public function recalculatePasses(\ilTestEvaluationUserData $userdata, int $acti foreach ($passes as $pass => $passdata) { if (is_object($passdata)) { $this->recalculatePass($passdata, $userdata->getUserID(), $active_id, $pass); - $this->addRecalculatedPassByActive($active_id, $pass); } } + $this->test->updateTestResultCache($active_id); } public function recalculatePass( @@ -121,53 +131,109 @@ public function recalculatePass( int $user_id, int $active_id, int $pass - ) { + ): void { $questions = $passdata->getAnsweredQuestions(); - if (is_array($questions)) { - foreach ($questions as $questiondata) { - if ($this->getQuestionId() && $this->getQuestionId() != $questiondata['id']) { - continue; - } - - $question_gui = $this->test->createQuestionGUI('', $questiondata['id']); - $this->recalculateQuestionScore($question_gui, $user_id, $active_id, $pass, $questiondata); + foreach ($questions as $question_data) { + if (!$this->getQuestionId() || $this->getQuestionId() === $question_data['id']) { + $this->recalculateQuestionScore($user_id, $active_id, $pass, $question_data); } } } - public function recalculateQuestionScore( - \assQuestionGUI $question_gui, + private function recalculateQuestionScore( int $user_id, int $active_id, int $pass, array $questiondata ): void { - $question = $question_gui->getObject(); - $reached = $question->calculateReachedPoints($active_id, $pass); - $actual_reached = $question->adjustReachedPointsByScoringOptions($reached, $active_id); - if ($this->preserve_manual_scores === true && $questiondata['manual'] === 1) { return; } - \assQuestion::setForcePassResultUpdateEnabled(true); - \assQuestion::_setReachedPoints( + $q_id = $questiondata['id']; + if (!isset($this->question_cache[$q_id])) { + $this->question_cache[$q_id] = $this->test->createQuestionGUI('', $q_id)->getObject(); + } + $question = $this->question_cache[$q_id]; + + $old_points = $question->getReachedPoints($active_id, $pass); + $reached = $question->adjustReachedPointsByScoringOptions( + $question->calculateReachedPoints($active_id, $pass), + $active_id, + ); + + $this->updateReachedPoints( + $user_id, $active_id, $questiondata['id'], - $actual_reached, + $old_points, + $reached, $question->getMaximumPoints(), $pass, - false, - true ); - \assQuestion::setForcePassResultUpdateEnabled(false); + } + + /** + * This is an optimized version of \assQuestion::_setReachedPoints that only executes updates in the database if + * necessary. In addition, unlike the original, this method does NOT update the test cache, so this must also be called + * afterward. + */ + public function updateReachedPoints( + int $user_id, + int $active_id, + int $question_id, + float $old_points, + float $points, + float $max_points, + int $pass, + bool $manual_scoring = false + ): void { + // Only update the test results if necessary + $has_changed = $old_points !== $points; + if ($has_changed && $points <= $max_points) { + $this->db->update( + 'tst_test_result', + [ + 'points' => [\ilDBConstants::T_FLOAT, $points], + 'tstamp' => [\ilDBConstants::T_INTEGER, time()], + ], + [ + 'active_fi' => [\ilDBConstants::T_INTEGER, $active_id], + 'question_fi' => [\ilDBConstants::T_INTEGER, $question_id], + 'pass' => [\ilDBConstants::T_INTEGER, $pass] + ] + ); + } + + // Always update the pass result as the maximum points might have changed + $data = $this->test->getQuestionCountAndPointsForPassOfParticipant($active_id, $pass); + $values = [ + 'maxpoints' => [\ilDBConstants::T_FLOAT, $data['points']], + 'tstamp' => [\ilDBConstants::T_INTEGER, time()], + ]; + + if ($has_changed) { + $result = $this->db->queryF( + 'SELECT SUM(points) reachedpoints FROM tst_test_result WHERE active_fi = %s AND pass = %s', + [\ilDBConstants::T_INTEGER, \ilDBConstants::T_INTEGER], + [$active_id, $pass] + ); + $values['points'] = [\ilDBConstants::T_FLOAT, $result->fetchAssoc()['reachedpoints'] ?? 0.0]; + } + + $this->db->update( + 'tst_pass_result', + $values, + ['active_fi' => [\ilDBConstants::T_INTEGER, $active_id], 'pass' => [\ilDBConstants::T_INTEGER, $pass]] + ); + \ilCourseObjectiveResult::_updateObjectiveResult($user_id, $active_id, $question_id); $logger = $this->test->getTestLogger(); if ($logger->isLoggingEnabled()) { $logger->logScoringInteraction( new TestScoringInteraction( $this->test->getRefId(), - $questiondata['id'], + $question_id, $this->scorer->getId(), $user_id, TestScoringInteractionTypes::QUESTION_GRADING_RESET, @@ -187,34 +253,13 @@ public function calculateBestSolutionForTest(): string foreach ($this->test->getAllQuestions() as $question) { /** @var AssQuestionGUI $question_gui */ $question_gui = $this->test->createQuestionGUI("", $question['question_id']); - $solution .= '

' . $question_gui->getObject()->getTitle() . '

'; + $solution .= '

' . $question_gui->getObject()->getTitleForHTMLOutput() . '

'; $solution .= $question_gui->getSolutionOutput(0, null, true, true, false, false, true, false); } return $solution; } - public function resetRecalculatedPassesByActives() - { - $this->recalculated_passes = []; - } - - public function getRecalculatedPassesByActives(): array - { - return $this->recalculated_passes; - } - - public function addRecalculatedPassByActive(int $active_id, int $pass): void - { - if (! array_key_exists($active_id, $this->recalculated_passes) - || !is_array($this->recalculated_passes[$active_id]) - ) { - $this->recalculated_passes[$active_id] = []; - } - - $this->recalculated_passes[$active_id][] = $pass; - } - public function removeAllQuestionResults($question_id) { $query = "DELETE FROM tst_test_result WHERE question_fi = %s"; diff --git a/components/ILIAS/Test/src/Scoring/Manual/TestScoringByParticipantPassesOverviewTableGUI.php b/components/ILIAS/Test/src/Scoring/Manual/TestScoringByParticipantPassesOverviewTableGUI.php index 94aefa694a54..10bd10238d68 100755 --- a/components/ILIAS/Test/src/Scoring/Manual/TestScoringByParticipantPassesOverviewTableGUI.php +++ b/components/ILIAS/Test/src/Scoring/Manual/TestScoringByParticipantPassesOverviewTableGUI.php @@ -56,7 +56,7 @@ private function initColumns(): void $this->addColumn($this->lng->txt("tst_answered_questions"), 'answered_questions', ''); $this->addColumn($this->lng->txt("tst_reached_points"), 'reached_points', ''); $this->addColumn($this->lng->txt("tst_percent_solved"), 'percentage', ''); - $this->addColumn('', '', '1%'); + $this->addColumn($this->lng->txt("actions"), '', '1%'); } private function initOrdering(): void diff --git a/components/ILIAS/Test/src/Scoring/Manual/class.TestScoringByParticipantGUI.php b/components/ILIAS/Test/src/Scoring/Manual/class.TestScoringByParticipantGUI.php index 3801480396e7..0d5458bf28e7 100755 --- a/components/ILIAS/Test/src/Scoring/Manual/class.TestScoringByParticipantGUI.php +++ b/components/ILIAS/Test/src/Scoring/Manual/class.TestScoringByParticipantGUI.php @@ -391,7 +391,10 @@ private function buildManScoringParticipantForm( $form->setTableWidth('100%'); foreach ($question_gui_list as $question_id => $question_gui) { - $question_header = sprintf($this->lng->txt('tst_manscoring_question_section_header'), $question_gui->getObject()->getTitle()); + $question_header = sprintf( + $this->lng->txt('tst_manscoring_question_section_header'), + $question_gui->getObject()->getTitleForHTMLOutput() + ); $question_solution = $question_gui->getSolutionOutput($active_id, $pass, false, false, true, false, false, true); $best_solution = $question_gui->getObject()->getSuggestedSolutionOutput(); @@ -410,14 +413,19 @@ private function buildManScoringParticipantForm( $cust->setHtml($question_solution); $form->addItem($cust); - $text = new \ilTextInputGUI($this->lng->txt('tst_change_points_for_question'), "question__{$question_id}__points"); + $number_input_gui = new \ilNumberInputGUI( + $this->lng->txt('tst_change_points_for_question'), + "question__{$question_id}__points" + ); + $number_input_gui->allowDecimals(true); + if ($initValues) { - $text->setValue((string) \assQuestion::_getReachedPoints($active_id, $question_id, $pass)); + $number_input_gui->setValue((string) \assQuestion::_getReachedPoints($active_id, $question_id, $pass)); } if ($disabled) { - $text->setDisabled($disabled); + $number_input_gui->setDisabled($disabled); } - $form->addItem($text); + $form->addItem($number_input_gui); $nonedit = new \ilNonEditableValueGUI($this->lng->txt('tst_manscoring_input_max_points_for_question'), "question__{$question_id}__maxpoints"); if ($initValues) { diff --git a/components/ILIAS/Test/src/Scoring/Manual/class.TestScoringByQuestionGUI.php b/components/ILIAS/Test/src/Scoring/Manual/class.TestScoringByQuestionGUI.php index 023ec420d239..fd46025396f0 100755 --- a/components/ILIAS/Test/src/Scoring/Manual/class.TestScoringByQuestionGUI.php +++ b/components/ILIAS/Test/src/Scoring/Manual/class.TestScoringByQuestionGUI.php @@ -250,7 +250,7 @@ protected function saveManScoringByQuestion(): void 'success', sprintf( $this->lng->txt('tst_saved_manscoring_by_question_successfully'), - $question_gui->getObject()->getTitle(), + $question_gui->getObject()->getTitleForHTMLOutput(), $attempt + 1 ) ); @@ -294,9 +294,7 @@ private function buildFeedbackModal( ): RoundTripModal { $question_gui = $this->object->createQuestionGUI('', $question_id); - $content = [ - $this->buildSolutionPanel($question_gui, $question_id, $attempt) - ]; + $content = [$this->buildSolutionPanel($question_gui, $active_id, $attempt)]; if ($question_gui instanceof \assTextQuestionGUI && $this->object->getAutosave()) { $content[] = $this->buildAutosavedSolutionPanel($question_gui, $question_id, $attempt); @@ -345,7 +343,7 @@ private function buildSolutionPanel( int $attempt ): StandardPanel { return $this->ui_factory->panel()->standard( - $question_gui->getObject()->getTitle(), + $question_gui->getObject()->getTitleForHTMLOutput(), $this->ui_factory->legacy()->content( $question_gui->getSolutionOutput( $active_id, @@ -485,8 +483,9 @@ private function buildQuestionTitleWithPoints(TestQuestionProperties $test_quest { $question_properties = $test_question_properties->getGeneralQuestionProperties(); $lang_var = $question_properties->getAvailablePoints() === 1.0 ? $this->lng->txt('point') : $this->lng->txt('points'); - return "{$question_properties->getTitle()} ({$question_properties->getAvailablePoints()} {$lang_var}) " - . "[{$this->lng->txt('question_id_short')}: {$question_properties->getQuestionId()}]"; + return "{$this->refinery->encode()->htmlSpecialCharsAsEntities()->transform($question_properties->getTitle())} " + . "({$question_properties->getAvailablePoints()} {$lang_var}) " + . "[{$this->lng->txt('question_id_short')}: {$question_properties->getQuestionId()}]"; } private function initJavascript(): void diff --git a/components/ILIAS/Test/src/Scoring/Marks/MarkSchemaTable.php b/components/ILIAS/Test/src/Scoring/Marks/MarkSchemaTable.php index 8ac58e2a1475..4c75b57b29e5 100644 --- a/components/ILIAS/Test/src/Scoring/Marks/MarkSchemaTable.php +++ b/components/ILIAS/Test/src/Scoring/Marks/MarkSchemaTable.php @@ -50,6 +50,7 @@ public function getTable(): DataTable $f = $this->ui_factory->table(); $table = $f->data( + $this, $this->lng->txt('mark_schema'), [ 'name' => $f->column()->text($this->lng->txt('tst_mark_short_form')), @@ -69,7 +70,6 @@ public function getTable(): DataTable ) ) ], - $this ); if (!$this->marks_editable) { diff --git a/components/ILIAS/Test/src/Settings/GlobalSettings/GlobalTestSettings.php b/components/ILIAS/Test/src/Settings/GlobalSettings/GlobalTestSettings.php index 271c5aa46cd8..09f856802ba8 100755 --- a/components/ILIAS/Test/src/Settings/GlobalSettings/GlobalTestSettings.php +++ b/components/ILIAS/Test/src/Settings/GlobalSettings/GlobalTestSettings.php @@ -244,7 +244,7 @@ private function buildGeneralSettingsInputs( $lng->txt('ass_process_lock') )->withByline($lng->txt('ass_process_lock_desc')) ->withValue($this->process_lock_mode === ProcessLockModes::ASS_PROC_LOCK_MODE_NONE ? null : [$this->process_lock_mode->value]), - 'image_map_line_color' => $ff->colorPicker($lng->txt('imap_line_color')) + 'image_map_line_color' => $ff->colorSelect($lng->txt('imap_line_color')) ->withValue('#' . $this->image_map_line_color), 'user_identifier' => $ff->select( $lng->txt('user_criteria'), diff --git a/components/ILIAS/Test/src/Settings/MainSettings/MainSettingsDatabaseRepository.php b/components/ILIAS/Test/src/Settings/MainSettings/MainSettingsDatabaseRepository.php index c93fa4e2bb2e..64ef89642f1e 100755 --- a/components/ILIAS/Test/src/Settings/MainSettings/MainSettingsDatabaseRepository.php +++ b/components/ILIAS/Test/src/Settings/MainSettings/MainSettingsDatabaseRepository.php @@ -92,7 +92,6 @@ protected function doSelect(string $where_part): MainSettings . 'autosave,' . PHP_EOL . 'autosave_ival,' . PHP_EOL . 'shuffle_questions,' . PHP_EOL - . 'offer_question_hints,' . PHP_EOL . 'answer_feedback_points,' . PHP_EOL . 'answer_feedback,' . PHP_EOL . 'specific_feedback,' . PHP_EOL @@ -176,7 +175,6 @@ protected function doSelect(string $where_part): MainSettings (bool) $row['autosave'], $row['autosave_ival'], (bool) $row['shuffle_questions'], - (bool) $row['offer_question_hints'], (bool) $row['answer_feedback_points'], (bool) $row['answer_feedback'], (bool) $row['specific_feedback'], diff --git a/components/ILIAS/Test/src/Settings/MainSettings/SettingsAccess.php b/components/ILIAS/Test/src/Settings/MainSettings/SettingsAccess.php index 40d72f8f1157..70fb59baee51 100755 --- a/components/ILIAS/Test/src/Settings/MainSettings/SettingsAccess.php +++ b/components/ILIAS/Test/src/Settings/MainSettings/SettingsAccess.php @@ -77,31 +77,15 @@ private function getInputAccessWindow( ?array $environment = null ): Group { $constraint = $refinery->custom()->constraint( - static function (array $vs): bool { - if ($vs['start_time'] === null - || $vs['end_time'] === null) { - return true; - } - return $vs['start_time'] < $vs['end_time']; - }, + static fn (array $vs) => + $vs['start_time'] === null || $vs['end_time'] === null || $vs['start_time'] < $vs['end_time'], $lng->txt('duration_end_must_not_be_earlier_than_start') ); $trafo = $refinery->custom()->transformation( static function (array $vs): array { - if ($vs['start_time'] === null) { - $vs['start_time_enabled'] = false; - } else { - $vs['start_time_enabled'] = true; - $vs['start_time'] = $vs['start_time']; - } - - if ($vs['end_time'] === null) { - $vs['end_time_enabled'] = false; - return $vs; - } - - $vs['end_time_enabled'] = true; + $vs['start_time_enabled'] = $vs['start_time'] !== null; + $vs['end_time_enabled'] = $vs['end_time'] !== null; return $vs; } ); diff --git a/components/ILIAS/Test/src/Settings/MainSettings/SettingsQuestionBehaviour.php b/components/ILIAS/Test/src/Settings/MainSettings/SettingsQuestionBehaviour.php index 482babaffbad..d6dc473ef574 100755 --- a/components/ILIAS/Test/src/Settings/MainSettings/SettingsQuestionBehaviour.php +++ b/components/ILIAS/Test/src/Settings/MainSettings/SettingsQuestionBehaviour.php @@ -22,7 +22,6 @@ use ILIAS\Test\Settings\TestSettings; use ILIAS\Test\Logging\AdditionalInformationGenerator; - use ILIAS\UI\Component\Input\Field\Factory as FieldFactory; use ILIAS\UI\Component\Input\Container\Form\FormInput; use ILIAS\UI\Component\Input\Field\Radio; @@ -46,7 +45,6 @@ public function __construct( protected bool $autosave_enabled, protected int $autosave_interval, protected bool $shuffle_questions, - protected bool $question_hints_enabled, protected bool $instant_feedback_points_enabled, protected bool $instant_feedback_generic_enabled, protected bool $instant_feedback_specific_enabled, @@ -84,14 +82,8 @@ public function toForm( $lng->txt('tst_shuffle_questions_description') )->withValue($this->getShuffleQuestions()); - $inputs['offer_hints'] = $f->checkbox( - $lng->txt('tst_setting_offer_hints_label'), - $lng->txt('tst_setting_offer_hints_info') - )->withValue($this->getQuestionHintsEnabled()); - if ($environment['participant_data_exists']) { $inputs['shuffle_questions'] = $inputs['shuffle_questions']->withDisabled(true); - $inputs['offer_hints'] = $inputs['offer_hints']->withDisabled(true); } $inputs['instant_feedback'] = $this->getInputInstantFeedback($lng, $f, $refinery, $environment); @@ -126,8 +118,11 @@ static function (?array $vs): array { $sub_inputs_autosave['autosave_interval'] = $f->numeric($lng->txt('autosave_ival'), $lng->txt('seconds')) ->withRequired(true) ->withAdditionalTransformation($refinery->int()->isGreaterThan(0)) - ->withValue($this->getAutosaveInterval() / 1000) - ; + ->withValue( + $this->getAutosaveInterval() !== 0 + ? $this->getAutosaveInterval() / 1000 + : 30 + ); $autosave_input = $f->optionalGroup( $sub_inputs_autosave, @@ -346,7 +341,6 @@ public function toStorage(): array 'autosave' => ['integer', (int) $this->getAutosaveEnabled()], 'autosave_ival' => ['integer', $this->getAutosaveInterval()], 'shuffle_questions' => ['integer', (int) $this->getShuffleQuestions()], - 'offer_question_hints' => ['integer', (int) $this->getQuestionHintsEnabled()], 'answer_feedback_points' => ['integer', (int) $this->getInstantFeedbackPointsEnabled()], 'answer_feedback' => ['integer', (int) $this->getInstantFeedbackGenericEnabled()], 'specific_feedback' => ['integer', (int) $this->getInstantFeedbackSpecificEnabled()], @@ -382,8 +376,6 @@ public function toLog(AdditionalInformationGenerator $additional_info): array ? $this->getAutosaveInterval() / 1000 . ' ' . $additional_info->getTagForLangVar('seconds') : $additional_info->getEnabledDisabledTagForBool(false); $log_array[AdditionalInformationGenerator::KEY_TEST_SHUFFLE_QUESTIONS] = $additional_info ->getEnabledDisabledTagForBool($this->getShuffleQuestions()); - $log_array[AdditionalInformationGenerator::KEY_TEST_HINTS_ENABLED] = $additional_info - ->getEnabledDisabledTagForBool($this->getQuestionHintsEnabled()); $log_array[AdditionalInformationGenerator::KEY_TEST_FEEDBACK_ENABLED] = $additional_info ->getEnabledDisabledTagForBool($this->isAnyInstantFeedbackOptionEnabled()); @@ -463,18 +455,6 @@ public function withShuffleQuestions(bool $shuffle_questions): self return $clone; } - public function getQuestionHintsEnabled(): bool - { - return $this->question_hints_enabled; - } - - public function withQuestionHintsEnabled(bool $question_hints_enabled): self - { - $clone = clone $this; - $clone->question_hints_enabled = $question_hints_enabled; - return $clone; - } - public function getInstantFeedbackPointsEnabled(): bool { return $this->instant_feedback_points_enabled; diff --git a/components/ILIAS/Test/src/Settings/MainSettings/class.SettingsMainGUI.php b/components/ILIAS/Test/src/Settings/MainSettings/class.SettingsMainGUI.php index e417313632b7..68f0fd54916b 100755 --- a/components/ILIAS/Test/src/Settings/MainSettings/class.SettingsMainGUI.php +++ b/components/ILIAS/Test/src/Settings/MainSettings/class.SettingsMainGUI.php @@ -21,13 +21,14 @@ namespace ILIAS\Test\Settings\MainSettings; use ILIAS\Test\Settings\TestSettingsGUI; +use ILIAS\Test\Settings\MainSettings\MainSettingsRepository; use ILIAS\Test\Logging\TestLogger; use ILIAS\Test\Logging\TestAdministrationInteractionTypes; use ILIAS\Test\Logging\AdditionalInformationGenerator; use ILIAS\TestQuestionPool\Questions\GeneralQuestionPropertiesRepository; use ILIAS\UI\Factory as UIFactory; use ILIAS\UI\Renderer as UIRenderer; -use ILIAS\Test\Settings\MainSettings\MainSettingsRepository; +use ILIAS\ILIASObject\Properties\Properties as ObjectProperties; use ILIAS\UI\Component\Modal\Interruptive as InterruptiveModal; use ILIAS\UI\Component\Input\Field\Section; use ILIAS\UI\Component\Input\Field\Checkbox; @@ -65,7 +66,7 @@ class SettingsMainGUI extends TestSettingsGUI private const ECS_FUNCTIONALITY_SETTINGS_LABEL = 'ecs_settings'; private const ADDITIONAL_FUNCTIONALITY_SETTINGS_LABEL = 'additional_functionality_settings'; - protected \ilObjectProperties $object_properties; + protected ObjectProperties $object_properties; protected MainSettings $main_settings; protected MainSettingsRepository $main_settings_repository; @@ -604,13 +605,19 @@ private function getValueForActivationLimitedOptionalGroup(): ?array private function saveAvailabilitySettingsSection(array $section): void { - $timebased_availability = $section['timebased_availability']; - if ($this->test_object->participantDataExist()) { - $timebased_availability['is_activation_limited'] = $this->test_object->isActivationLimited(); - $timebased_availability['activation_starting_time'] = $this->test_object->getActivationStartingTime(); - } - - $this->test_object->storeActivationSettings($timebased_availability); + $time_based_availability = $section['timebased_availability']; + + $participant_data_exists = $this->test_object->participantDataExist(); + $this->test_object->storeActivationSettings( + $participant_data_exists + ? $this->test_object->isActivationLimited() + : $time_based_availability['is_activation_limited'], + $participant_data_exists + ? $this->test_object->getActivationStartingTime() + : $time_based_availability['activation_starting_time'], + $time_based_availability['activation_ending_time'], + $time_based_availability['activation_visibility'] + ); $this->test_object->getObjectProperties()->storePropertyIsOnline($section['is_online']); } @@ -701,7 +708,6 @@ private function getQuestionBehaviourSettingsForStorage(array $section): Setting } return $question_behaviour_settings - ->withQuestionHintsEnabled($section['offer_hints']) ->withInstantFeedbackPointsEnabled($section['instant_feedback']['enabled_feedback_types']['instant_feedback_points']) ->withInstantFeedbackGenericEnabled($section['instant_feedback']['enabled_feedback_types']['instant_feedback_generic']) ->withInstantFeedbackSpecificEnabled($section['instant_feedback']['enabled_feedback_types']['instant_feedback_specific']) diff --git a/components/ILIAS/Test/src/Settings/ScoreReporting/ScoreSettingsDatabaseRepository.php b/components/ILIAS/Test/src/Settings/ScoreReporting/ScoreSettingsDatabaseRepository.php index dbe70712af62..c3d94f602a88 100755 --- a/components/ILIAS/Test/src/Settings/ScoreReporting/ScoreSettingsDatabaseRepository.php +++ b/components/ILIAS/Test/src/Settings/ScoreReporting/ScoreSettingsDatabaseRepository.php @@ -57,7 +57,7 @@ protected function doSelect(string $where_part): ScoreSettings . 'examid_in_test_res,' . PHP_EOL . 'results_presentation,' . PHP_EOL . 'exportsettings,' . PHP_EOL - . 'highscore_enabled, highscore_anon, highscore_achieved_ts, highscore_score, highscore_percentage, highscore_hints, highscore_wtime, highscore_own_table, highscore_top_table, highscore_top_num' . PHP_EOL + . 'highscore_enabled, highscore_anon, highscore_achieved_ts, highscore_score, highscore_percentage, highscore_wtime, highscore_own_table, highscore_top_table, highscore_top_num' . PHP_EOL . 'FROM ' . self::TABLE_NAME . PHP_EOL . $where_part; @@ -94,7 +94,6 @@ protected function doSelect(string $where_part): ScoreSettings ->withHighscoreAchievedTS((bool) $row['highscore_achieved_ts']) ->withHighscoreScore((bool) $row['highscore_score']) ->withHighscorePercentage((bool) $row['highscore_percentage']) - ->withHighscoreHints((bool) $row['highscore_hints']) ->withHighscoreWTime((bool) $row['highscore_wtime']) ->withHighscoreOwnTable((bool) $row['highscore_own_table']) ->withHighscoreTopTable((bool) $row['highscore_top_table']) diff --git a/components/ILIAS/Test/src/Settings/ScoreReporting/SettingsGamification.php b/components/ILIAS/Test/src/Settings/ScoreReporting/SettingsGamification.php index 6bf53eb3dec5..514ea11e6199 100755 --- a/components/ILIAS/Test/src/Settings/ScoreReporting/SettingsGamification.php +++ b/components/ILIAS/Test/src/Settings/ScoreReporting/SettingsGamification.php @@ -22,7 +22,6 @@ use ILIAS\Test\Settings\TestSettings; use ILIAS\Test\Logging\AdditionalInformationGenerator; - use ILIAS\UI\Component\Input\Field\Factory as FieldFactory; use ILIAS\UI\Component\Input\Container\Form\FormInput; use ILIAS\Refinery\Factory as Refinery; @@ -38,7 +37,6 @@ class SettingsGamification extends TestSettings protected bool $highscore_achieved_ts = true; protected bool $highscore_score = true; protected bool $highscore_percentage = true; - protected bool $highscore_hints = true; protected bool $highscore_wtime = true; protected bool $highscore_own_table = true; protected bool $highscore_top_table = true; @@ -62,7 +60,7 @@ public function toForm( ->withOption((string) self::HIGHSCORE_SHOW_OWN_TABLE, $lng->txt('tst_highscore_own_table'), $lng->txt('tst_highscore_own_table_description')) ->withOption((string) self::HIGHSCORE_SHOW_TOP_TABLE, $lng->txt('tst_highscore_top_table'), $lng->txt('tst_highscore_top_table_description')) ->withOption((string) self::HIGHSCORE_SHOW_ALL_TABLES, $lng->txt('tst_highscore_all_tables'), $lng->txt('tst_highscore_all_tables_description')) - ->withValue($this->getHighScoreMode() > 0 ? (string) $this->getHighScoreMode() : '') + ->withValue($this->getHighScoreMode() > 0 ? (string) $this->getHighScoreMode() : null) ->withRequired(true) , 'highscore_top_num' => $f->numeric($lng->txt('tst_highscore_top_num'), $lng->txt('tst_highscore_top_num_description')) @@ -84,10 +82,6 @@ public function toForm( $lng->txt('tst_highscore_percentage'), $lng->txt('tst_highscore_percentage_description') )->withValue($this->getHighscorePercentage()), - 'highscore_hints' => $f->checkbox( - $lng->txt('tst_highscore_hints'), - $lng->txt('tst_highscore_hints_description') - )->withValue($this->getHighscoreHints()), 'highscore_wtime' => $f->checkbox( $lng->txt('tst_highscore_wtime'), $lng->txt('tst_highscore_wtime_description') @@ -128,7 +122,6 @@ function ($v) { ->withHighscoreAchievedTS($v['highscore']['highscore_achieved_ts']) ->withHighscoreScore($v['highscore']['highscore_score']) ->withHighscorePercentage($v['highscore']['highscore_percentage']) - ->withHighscoreHints($v['highscore']['highscore_hints']) ->withHighscoreWTime($v['highscore']['highscore_wtime']); } ) @@ -143,7 +136,6 @@ public function toStorage(): array 'highscore_achieved_ts' => ['integer', (int) $this->getHighscoreAchievedTS()], 'highscore_score' => ['integer', (int) $this->getHighscoreScore()], 'highscore_percentage' => ['integer', (int) $this->getHighscorePercentage()], - 'highscore_hints' => ['integer', (int) $this->getHighscoreHints()], 'highscore_wtime' => ['integer', (int) $this->getHighscoreWTime()], 'highscore_own_table' => ['integer', (int) $this->getHighscoreOwnTable()], 'highscore_top_table' => ['integer', (int) $this->getHighscoreTopTable()], @@ -187,8 +179,6 @@ public function toLog(AdditionalInformationGenerator $additional_info): array ->getEnabledDisabledTagForBool($this->getHighscoreScore()), AdditionalInformationGenerator::KEY_SCORING_HIGHSCORE_SHOW_PERCENTAGE => $additional_info ->getEnabledDisabledTagForBool($this->getHighscorePercentage()), - AdditionalInformationGenerator::KEY_SCORING_HIGHSCORE_SHOW_HINTS => $additional_info - ->getEnabledDisabledTagForBool($this->getHighscoreHints()), AdditionalInformationGenerator::KEY_SCORING_HIGHSCORE_SHOW_WTIME => $additional_info ->getEnabledDisabledTagForBool($this->getHighscoreWTime()) ]; @@ -298,17 +288,6 @@ public function withHighscorePercentage(bool $highscore_percentage): self return $clone; } - public function getHighscoreHints(): bool - { - return $this->highscore_hints; - } - public function withHighscoreHints(bool $highscore_hints): self - { - $clone = clone $this; - $clone->highscore_hints = $highscore_hints; - return $clone; - } - public function getHighscoreWTime(): bool { return $this->highscore_wtime; diff --git a/components/ILIAS/Test/src/Settings/ScoreReporting/SettingsResultDetails.php b/components/ILIAS/Test/src/Settings/ScoreReporting/SettingsResultDetails.php index 22c271e955ab..3af547fd1652 100755 --- a/components/ILIAS/Test/src/Settings/ScoreReporting/SettingsResultDetails.php +++ b/components/ILIAS/Test/src/Settings/ScoreReporting/SettingsResultDetails.php @@ -39,7 +39,6 @@ class SettingsResultDetails extends TestSettings public const RESULTPRES_BIT_SOLUTION_LISTCOMPARE = 128; public const RESULTPRES_BIT_SOLUTION_LISTOWNANSWERS = 256; - protected bool $print_bs_with_res = true; protected bool $examid_in_test_res = true; protected int $exportsettings = 0; protected int $results_presentation = 0; diff --git a/components/ILIAS/Test/src/Setup/Test10DBUpdateSteps.php b/components/ILIAS/Test/src/Setup/Test10DBUpdateSteps.php index 792fa177e8c0..15f8527b7195 100644 --- a/components/ILIAS/Test/src/Setup/Test10DBUpdateSteps.php +++ b/components/ILIAS/Test/src/Setup/Test10DBUpdateSteps.php @@ -22,6 +22,7 @@ use ILIAS\Test\Logging\TestLoggingDatabaseRepository; use ILIAS\Test\Certificate\TestPlaceholderValues; +use ILIAS\Test\ExportImport\DBRepository; class Test10DBUpdateSteps implements \ilDatabaseUpdateSteps { @@ -425,4 +426,42 @@ public function step_11(): void ); } } + + public function step_12(): void + { + if (!$this->db->tableExists(DBRepository::TST_EXPORT_TABLE)) { + $this->db->createTable(DBRepository::TST_EXPORT_TABLE, [ + 'object_id' => [ + 'type' => \ilDBConstants::T_INTEGER, + 'length' => 8, + 'notnull' => true + ], + 'type' => [ + 'type' => \ilDBConstants::T_TEXT, + 'length' => 32, + 'notnull' => true + ], + 'rid' => [ + 'type' => \ilDBConstants::T_TEXT, + 'length' => 64 + ] + ]); + $this->db->addPrimaryKey(DBRepository::TST_EXPORT_TABLE, ['rid']); + $this->db->addIndex(DBRepository::TST_EXPORT_TABLE, ['object_id'], 'oid'); + } + } + + public function step_13(): void + { + $this->db->update( + 'rbac_operations', + ['op_order' => [\ilDBConstants::T_INTEGER, 4100]], + ['operation' => [\ilDBConstants::T_TEXT, 'tst_history_read']] + ); + $this->db->update( + 'rbac_operations', + ['op_order' => [\ilDBConstants::T_INTEGER, 4200]], + ['operation' => [\ilDBConstants::T_TEXT, 'tst_results']] + ); + } } diff --git a/components/ILIAS/Test/src/Setup/TestSetupAgent.php b/components/ILIAS/Test/src/Setup/TestSetupAgent.php index b649ac43d441..8e4fb24b0e64 100755 --- a/components/ILIAS/Test/src/Setup/TestSetupAgent.php +++ b/components/ILIAS/Test/src/Setup/TestSetupAgent.php @@ -51,7 +51,10 @@ public function getUpdateObjective(?Config $config = null): Objective 7200, ['tst'] ), - new \ilAccessRBACOperationDeletedObjective('tst', 56) + new \ilAccessRBACOperationDeletedObjective('tst', 56), + new \ilDatabaseUpdateStepsExecutedObjective( + new ilTestNoHintsDBUpdateSteps() + ), ); } @@ -67,7 +70,11 @@ public function getStatusObjective(Storage $storage): Objective new \ilDatabaseUpdateStepsMetricsCollectedObjective( $storage, new Test10DBUpdateSteps() - ) + ), + new \ilDatabaseUpdateStepsMetricsCollectedObjective( + $storage, + new ilTestNoHintsDBUpdateSteps() + ), ); } diff --git a/components/ILIAS/Test/src/Setup/class.ilTestNoHintsDBUpdateSteps.php b/components/ILIAS/Test/src/Setup/class.ilTestNoHintsDBUpdateSteps.php new file mode 100644 index 000000000000..6b0abe21eb49 --- /dev/null +++ b/components/ILIAS/Test/src/Setup/class.ilTestNoHintsDBUpdateSteps.php @@ -0,0 +1,96 @@ +db = $db; + } + + private function dropCols($table, $cols): void + { + foreach ($cols as $col) { + if ($this->db->tableColumnExists($table, $col)) { + $this->db->dropTableColumn($table, $col); + } + } + } + + public function step_1(): void + { + $this->dropCols('tst_tests', [ + 'offer_question_hints', + 'highscore_hints' + ]); + } + + public function step_2(): void + { + $this->dropCols('tst_test_result', [ + 'hint_count', + 'hint_points' + ]); + } + + public function step_3(): void + { + $this->dropCols('tst_pass_result', [ + 'hint_count', + 'hint_points' + ]); + } + + public function step_4(): void + { + $this->dropCols('tst_result_cache', [ + 'hint_count', + 'hint_points' + ]); + } + + public function step_5(): void + { + if ($this->db->tableExists('qpl_hint_tracking')) { + $this->db->dropTable('qpl_hint_tracking'); + } + if ($this->db->tableExists('qpl_hint_tracking_seq')) { + $this->db->dropTable('qpl_hint_tracking_seq'); + } + } + + public function step_6(): void + { + if ($this->db->tableExists('qpl_hints')) { + $this->db->dropTable('qpl_hints'); + } + } + + public function step_7(): void + { + $query = "DELETE FROM page_object WHERE parent_type = 'qht'"; + $this->db->manipulate($query); + } + +} diff --git a/components/ILIAS/Test/src/TestDIC.php b/components/ILIAS/Test/src/TestDIC.php index 0543a55dc927..c239d77772ea 100755 --- a/components/ILIAS/Test/src/TestDIC.php +++ b/components/ILIAS/Test/src/TestDIC.php @@ -36,6 +36,7 @@ use ILIAS\Test\Logging\TestLogViewer; use ILIAS\Test\Logging\Factory as InteractionFactory; use ILIAS\Test\ExportImport\Factory as ExportImportFactory; +use ILIAS\Test\ExportImport\DBRepository as ExportImportRepository; use ILIAS\Test\Questions\Properties\Repository as TestQuestionsRepository; use ILIAS\Test\Questions\Properties\DatabaseRepository as TestQuestionsDatabaseRepository; use ILIAS\Test\Results\Data\Factory as ResultsDataFactory; @@ -73,7 +74,8 @@ protected static function buildDIC(ILIASContainer $DIC): self $DIC['ilAccess'], $DIC['lng'], $DIC['static_url'], - $DIC['ui.factory'] + $DIC['ui.factory'], + $DIC['refinery'] ); $dic['results.data.factory'] = static fn($c): ResultsDataFactory => @@ -180,7 +182,13 @@ protected static function buildDIC(ILIASContainer $DIC): self $DIC['component.factory'], $DIC['file_delivery'], $DIC['ilUser'], - $c['question.general_properties.repository'] + $c['question.general_properties.repository'], + $DIC['resource_storage'], + ); + + $dic['exportimport.repository'] = static fn($c): ExportImportRepository => + new ExportImportRepository( + $DIC['ilDB'] ); $dic['questions.properties.repository'] = static fn($c): TestQuestionsRepository => diff --git a/components/ILIAS/Test/src/Utilities/TitleColumnsBuilder.php b/components/ILIAS/Test/src/Utilities/TitleColumnsBuilder.php index 7ce0946a108d..16825db648ce 100644 --- a/components/ILIAS/Test/src/Utilities/TitleColumnsBuilder.php +++ b/components/ILIAS/Test/src/Utilities/TitleColumnsBuilder.php @@ -23,6 +23,7 @@ use ILIAS\TestQuestionPool\Questions\GeneralQuestionPropertiesRepository; use ILIAS\StaticURL\Services as StaticURLServices; use ILIAS\Data\ReferenceId; +use ILIAS\Refinery\Factory as Refinery; use ILIAS\UI\Factory as UIFactory; use ILIAS\UI\Component\Link\Standard as StandardLink; @@ -34,7 +35,8 @@ public function __construct( private readonly \ilAccessHandler $access, private readonly \ilLanguage $lng, private readonly StaticURLServices $static_url, - private readonly UIFactory $ui_factory + private readonly UIFactory $ui_factory, + private readonly Refinery $refinery ) { } @@ -52,7 +54,9 @@ public function buildQuestionTitleAsLink( } return $this->ui_factory->link()->standard( - $this->properties_repository->getForQuestionId($question_id)?->getTitle(), + $this->refinery->encode()->htmlSpecialCharsAsEntities()->transform( + $this->properties_repository->getForQuestionId($question_id)?->getTitle() + ), $this->static_url->builder()->build( 'tst', new ReferenceId($test_ref_id), @@ -73,7 +77,7 @@ public function buildQuestionTitleAsText( return "{$this->lng->txt('deleted')} ({$this->lng->txt('id')}: {$question_id})"; } - return $question_title; + return $this->refinery->encode()->htmlSpecialCharsAsEntities()->transform($question_title); } public function buildTestTitleAsLink(int $test_ref_id): StandardLink diff --git a/components/ILIAS/Test/templates/default/tpl.il_as_tst_pass_details_overview_participants.html b/components/ILIAS/Test/templates/default/tpl.il_as_tst_pass_details_overview_participants.html index 543289161d41..3706d45fd839 100755 --- a/components/ILIAS/Test/templates/default/tpl.il_as_tst_pass_details_overview_participants.html +++ b/components/ILIAS/Test/templates/default/tpl.il_as_tst_pass_details_overview_participants.html @@ -1,4 +1,3 @@ -

{TEXT_HEADING}

{USER_DATA}
{PASS_FINISH_DATE_LABEL}: {PASS_FINISH_DATE_VALUE}
diff --git a/components/ILIAS/Test/templates/default/tpl.il_as_tst_pass_details_overview_qst_row.html b/components/ILIAS/Test/templates/default/tpl.il_as_tst_pass_details_overview_qst_row.html index 1775e67e656f..b9a0ddadbbd7 100755 --- a/components/ILIAS/Test/templates/default/tpl.il_as_tst_pass_details_overview_qst_row.html +++ b/components/ILIAS/Test/templates/default/tpl.il_as_tst_pass_details_overview_qst_row.html @@ -16,9 +16,6 @@
{VALUE_MAX_POINTS} {VALUE_REACHED_POINTS}{VALUE_HINT_COUNT} {VALUE_PERCENT_SOLVED} {SOLUTION_HINT}{VAL_LO_OBJECTIVES} {VAL_LO_TRY} {VAL_ANSWERED}{VAL_HINTS} {VAL_REACHED} {VAL_PERCENTAGE} {VAL_ACTIONS}{VAL_PERCENTAGE} {VAL_HINTS} {VAL_TIME}
- - - - - {HINT_TEXT} - - {HINT_POINTS} - - {ACTIONS} -
- {HINT_INDEX} - - {HINT_TEXT} - - {HINT_POINTS} -
+ colspan="{COL_SPAN}" tabindex="-1"> +colspan="{COL_SPAN}" tabindex="-1"> {CELL_COL_TITLE}: {CELL_CONTENT} + {ACTION_CONTENT}
+
- - + + - - + - + - + {CELLS} - diff --git a/components/ILIAS/UI/src/templates/default/Table/tpl.orderingcell.html b/components/ILIAS/UI/src/templates/default/Table/tpl.orderingcell.html index 201dfa0b0bde..131de767a736 100644 --- a/components/ILIAS/UI/src/templates/default/Table/tpl.orderingcell.html +++ b/components/ILIAS/UI/src/templates/default/Table/tpl.orderingcell.html @@ -1,23 +1,24 @@ - - - - - \ No newline at end of file + diff --git a/components/ILIAS/UI/src/templates/default/Table/tpl.orderingtable.html b/components/ILIAS/UI/src/templates/default/Table/tpl.orderingtable.html index d517a5e6fd6a..7d1b4a9b234f 100644 --- a/components/ILIAS/UI/src/templates/default/Table/tpl.orderingtable.html +++ b/components/ILIAS/UI/src/templates/default/Table/tpl.orderingtable.html @@ -2,32 +2,37 @@ -

{TITLE}

+

{TITLE}

{VIEW_CONTROLS}
- -
+
{SELECTION_CONTROL_SELECT}
{SELECTION_CONTROL_DESELECT}
aria-sort="{COL_SORTATION}"> + aria-sort="{COL_SORTATION}">
{COL_SORTATION_GLYPH} @@ -30,20 +30,20 @@

{TITLE}

-
{COL_TITLE_ACTION}{COL_TITLE_ACTION}
+
{MULTI_ACTION_TRIGGERER}
{MULTI_ACTION_ALL_MODAL}
- + + {DRAG_HANDLE} + + {ORDER_INPUT} - {CELL_CONTENT} + + {CELL_COL_TITLE}: {CELL_CONTENT} + {ACTION_CONTENT}
+ +
- + - - - + - + - + {CELLS} - + + - + diff --git a/components/ILIAS/UI/src/templates/default/Toast/tpl.toast.html b/components/ILIAS/UI/src/templates/default/Toast/tpl.toast.html index 618eeef140c0..b0913bbf19cf 100755 --- a/components/ILIAS/UI/src/templates/default/Toast/tpl.toast.html +++ b/components/ILIAS/UI/src/templates/default/Toast/tpl.toast.html @@ -1,4 +1,4 @@ -
id="{ID}" data-vanishurl="{VANISH_ASYNC}" data-vanish="{TOAST_VANISH}" data-delay="{TOAST_DELAY}"> +
id="{ID}" data-vanishurl="{VANISH_ASYNC}">
{ICON}
{TITLE}
diff --git a/components/ILIAS/UI/src/templates/default/ViewControl/tpl.pagination.html b/components/ILIAS/UI/src/templates/default/ViewControl/tpl.pagination.html index 90883f13c3be..42511ecb176f 100755 --- a/components/ILIAS/UI/src/templates/default/ViewControl/tpl.pagination.html +++ b/components/ILIAS/UI/src/templates/default/ViewControl/tpl.pagination.html @@ -1,7 +1,7 @@
id="{ID}"> -{PREVIOUS} - {FIRST} - {BUTTON} - {LAST} -{NEXT} -
\ No newline at end of file +{PREVIOUS} +{FIRST} +{BUTTON} +{LAST} +{NEXT} +
diff --git a/components/ILIAS/UI/src/templates/default/ViewControl/tpl.sortation.html b/components/ILIAS/UI/src/templates/default/ViewControl/tpl.sortation.html index d8bf0e2ed9b5..24704370c724 100755 --- a/components/ILIAS/UI/src/templates/default/ViewControl/tpl.sortation.html +++ b/components/ILIAS/UI/src/templates/default/ViewControl/tpl.sortation.html @@ -1,5 +1,5 @@
+ + {DRAG_HINT} +
{SELECTION_CONTROL_SELECT}
{SELECTION_CONTROL_DESELECT}
+
- {POS_INPUT_TITLE} + +
+ {POS_INPUT_TITLE} +
+
{COL_TITLE}
@@ -35,25 +40,24 @@

{TITLE}

-
{COL_TITLE_ACTION}{COL_TITLE_ACTION}
+
-
{MULTI_ACTION_TRIGGERER}
-
{FORM_BUTTONS} @@ -61,6 +65,7 @@

{TITLE}

+
- - - - - + + + + + - +
-
- - -
-
-
Field 2
-
-
- -
-
+
+ + +
+
+
Field 2
+
+
+ +
+
- - EOT; $expected = $this->brutallyTrimHTML($expected); @@ -343,7 +276,7 @@ public function getTotalRowCount( $sortation_signal = null; - $table = $this->getUIFactory()->table()->data('', $columns, $data) + $table = $this->getUIFactory()->table()->data($data, '', $columns) ->withRequest($this->getDummyRequest()); $renderer->p_renderTableHeader($this->getDefaultRenderer(), $table, $tpl, $sortation_signal); $actual = $this->brutallyTrimHTML($tpl->get()); @@ -351,34 +284,34 @@ public function getTotalRowCount(
{VIEW_CONTROLS}
- +
- - - - + + + + - +
-
Field 1
-
-
Field 2
-
+
Field 1
+
+
Field 2
+
-
- -
EOT; $expected = $this->brutallyTrimHTML($expected); @@ -423,13 +356,13 @@ public function getTotalRowCount( $sortation_signal = null; - $table = $this->getUIFactory()->table()->data('', $columns, $data) + $table = $this->getUIFactory()->table()->data($data, '', $columns) ->withActions($actions) ->withRequest($this->getDummyRequest()); $renderer->p_renderActionsHeader($this->getDefaultRenderer(), $table, $tpl); $actual = $this->brutallyTrimHTML($tpl->get()); - $expected = 'actions'; + $expected = 'actions'; $this->assertStringContainsString($expected, $actual); } @@ -461,9 +394,7 @@ public function testDataTableRowBuilder() return [$rb, $columns, $actions]; } - /** - * @depends testDataTableRowBuilder - */ + #[\PHPUnit\Framework\Attributes\Depends('testDataTableRowBuilder')] public function testDataTableDataRowFromBuilder(array $params): I\Table\DataRow { list($rb, $columns, $actions) = $params; @@ -490,34 +421,34 @@ public function testDataTableDataRowFromBuilder(array $params): I\Table\DataRow return $row; } - /** - * @depends testDataTableDataRowFromBuilder - */ + #[\PHPUnit\Framework\Attributes\Depends('testDataTableDataRowFromBuilder')] public function testDataTableRenderStandardRow(I\Table\DataRow $row) { $actual = $this->brutallyTrimHTML($this->getDefaultRenderer()->render($row)); $expected = << - + + +Field 1:v1 - - Field 1:v1 +Field 2:v2 - - Field 2:v2 +Field 3:3 - - Field 3:3 - - + + EOT; $expected = $this->brutallyTrimHTML($expected); $this->assertEquals($expected, $actual); @@ -551,7 +482,7 @@ public function getTotalRowCount(?array $filter_data, ?array $additional_paramet 'f5' => $this->getUIFactory()->table()->column()->text('f5'), ]; - $table = $this->getTableFactory()->data('', $columns, $data) + $table = $this->getTableFactory()->data($data, '', $columns) ->withRequest($this->getDummyRequest()); $html = $this->getDefaultRenderer()->render($table); diff --git a/components/ILIAS/UI/tests/Component/Table/DataTest.php b/components/ILIAS/UI/tests/Component/Table/DataTest.php index 50a015f7e305..87b19af30887 100755 --- a/components/ILIAS/UI/tests/Component/Table/DataTest.php +++ b/components/ILIAS/UI/tests/Component/Table/DataTest.php @@ -72,7 +72,7 @@ public function testDataTableBasicConstruction(): void { $data = $this->getDataRetrieval(); $cols = ['f0' => $this->getTableFactory()->column()->text("col1")]; - $table = $this->getTableFactory()->data('title', $cols, $data); + $table = $this->getTableFactory()->data($data, 'title', $cols); $this->assertInstanceOf(Order::class, $table->getOrder()); $this->assertInstanceOf(Range::class, $table->getRange()); $this->assertInstanceOf(I\Signal::class, $table->getAsyncActionSignal()); @@ -88,7 +88,7 @@ public function testDataTableConstructionWithErrorColumns(): void $this->expectException(\InvalidArgumentException::class); $data = $this->getDataRetrieval(); $cols = ['f0' => "col1"]; - $table = $this->getTableFactory()->data('title', $cols, $data); + $table = $this->getTableFactory()->data($data, 'title', $cols); } public function testDataTableConstructionWithoutColumns(): void @@ -96,7 +96,7 @@ public function testDataTableConstructionWithoutColumns(): void $this->expectException(\InvalidArgumentException::class); $data = $this->getDataRetrieval(); $cols = []; - $table = $this->getTableFactory()->data('title', $cols, $data); + $table = $this->getTableFactory()->data($data, 'title', $cols); } public function testDataTableColumns(): void @@ -106,7 +106,7 @@ public function testDataTableColumns(): void 'f0' => $f->text("col1"), 'f1' => $f->text("col2") ]; - $table = $this->getTableFactory()->data('title', $cols, $this->getDataRetrieval()); + $table = $this->getTableFactory()->data($this->getDataRetrieval(), 'title', $cols); $this->assertEquals(2, $table->getColumnCount()); $check = [ @@ -130,7 +130,7 @@ public function testDataTableActions(): void $f->standard('act0', $builder, $token) ]; $cols = ['f0' => $this->getTableFactory()->column()->text("col1")]; - $table = $this->getTableFactory()->data('title', $cols, $this->getDataRetrieval()) + $table = $this->getTableFactory()->data($this->getDataRetrieval(), 'title', $cols) ->withActions($actions); $this->assertEquals($actions, $table->getAllActions()); @@ -142,7 +142,7 @@ protected function getTable(): I\Table\Data { $data = $this->getDataRetrieval(); $cols = ['f0' => $this->getTableFactory()->column()->text("col1")]; - $table = $this->getTableFactory()->data('title', $cols, $data); + $table = $this->getTableFactory()->data($data, 'title', $cols); return $table; } @@ -194,7 +194,7 @@ public function testDataTableWithSelectedOptionalCols(): void ->withIsOptional(true, false), 'f2' => $this->getTableFactory()->column()->text('') ]; - $table = $this->getTableFactory()->data('title', $cols, $data); + $table = $this->getTableFactory()->data($data, 'title', $cols); $this->assertEquals(3, $table->getColumnCount()); $this->assertEquals(['f0', 'f2'], array_keys($table->getVisibleColumns())); $this->assertEquals(0, $table->getVisibleColumns()['f0']->getIndex()); diff --git a/components/ILIAS/UI/tests/Component/Table/DataViewControlsTest.php b/components/ILIAS/UI/tests/Component/Table/DataViewControlsTest.php index c60859dca470..aa2708ace88c 100755 --- a/components/ILIAS/UI/tests/Component/Table/DataViewControlsTest.php +++ b/components/ILIAS/UI/tests/Component/Table/DataViewControlsTest.php @@ -63,7 +63,7 @@ public function getTotalRowCount( protected function getTable(int $total_count, array $columns): array { $factory = $this->getTableFactory(); - $table = $factory->data('Table', $columns, $this->getDataRetrieval($total_count)); + $table = $factory->data($this->getDataRetrieval($total_count), 'Table', $columns); return $table->applyViewControls([], []); } diff --git a/components/ILIAS/UI/tests/Component/Table/OrderingRendererTest.php b/components/ILIAS/UI/tests/Component/Table/OrderingRendererTest.php new file mode 100644 index 000000000000..358d5933274b --- /dev/null +++ b/components/ILIAS/UI/tests/Component/Table/OrderingRendererTest.php @@ -0,0 +1,127 @@ +getUIFactory(), + $this->getTemplateFactory(), + $this->getLanguage(), + $this->getJavaScriptBinding(), + new ilImagePathResolver(), + new \ILIAS\Data\Factory(), + new \ILIAS\UI\Help\TextRetriever\Echoing(), + $this->getUploadLimitResolver() + ); + } + + public function testOrderingTableRenderTableHeaderWithoutActions() + { + $renderer = $this->getRenderer(); + $f = $this->getColumnFactory(); + $data = new class () implements ILIAS\UI\Component\Table\OrderingRetrieval { + public function getRows( + Component\Table\OrderingRowBuilder $row_builder, + array $visible_column_ids + ): \Generator { + if (false) { + yield; + } + } + }; + $columns = [ + 'f1' => $f->text("Field 1")->withIndex(1), + 'f2' => $f->text("Field 2")->withIndex(2), + 'f3' => $f->number("Field 3")->withIndex(3) + ]; + $uri = new Data\URI('https://localhost'); + $table = $this->getUIFactory()->table()->ordering($data, $uri, '', $columns) + ->withRequest($this->getDummyRequest()); + + $actual = $renderer->renderOrderingTable($table, $this->getDefaultRenderer()); + $expected = <<

+
+
+
+
+ + + + + + + + + + + + + + + +
+
table_posinput_col_title
+
+
Field 1
+
+
Field 2
+
+
Field 3
+
+
+
+
+
+
+ +
+
+
+
+
+ + +EOT; + $this->assertEquals($this->brutallyTrimHTML($expected), $this->brutallyTrimHTML($actual)); + } +} diff --git a/components/ILIAS/UI/tests/Component/Table/TableRendererTestBase.php b/components/ILIAS/UI/tests/Component/Table/TableRendererTestBase.php new file mode 100644 index 000000000000..6b0a2d778651 --- /dev/null +++ b/components/ILIAS/UI/tests/Component/Table/TableRendererTestBase.php @@ -0,0 +1,97 @@ +getLanguage() + ); + } + + protected function getDummyRequest() + { + $request = $this->createMock(ServerRequestInterface::class); + $request + ->method("getUri") + ->willReturn(new \GuzzleHttp\Psr7\Uri('http://localhost:80')); + $request + ->method("getQueryParams") + ->willReturn([]); + return $request; + } + + public function getDataFactory(): Data\Factory + { + return new Data\Factory(); + } + + public function getUIFactory(): NoUIFactory + { + $factory = new class ($this->getTableFactory()) extends NoUIFactory { + public function __construct( + protected Component\Table\Factory $table_factory + ) { + } + public function button(): I\Button\Factory + { + return new I\Button\Factory(); + } + public function dropdown(): I\Dropdown\Factory + { + return new I\Dropdown\Factory(); + } + public function symbol(): I\Symbol\Factory + { + return new I\Symbol\Factory( + new I\Symbol\Icon\Factory(), + new I\Symbol\Glyph\Factory(), + new I\Symbol\Avatar\Factory() + ); + } + public function table(): I\Table\Factory + { + return $this->table_factory; + } + public function divider(): I\Divider\Factory + { + return new I\Divider\Factory(); + } + }; + return $factory; + } +} diff --git a/components/ILIAS/UI/tests/Component/Toast/ToastClientHtmlTest.php b/components/ILIAS/UI/tests/Component/Toast/ToastClientHtmlTest.php index 80acdbe1d695..774e74f1b70d 100755 --- a/components/ILIAS/UI/tests/Component/Toast/ToastClientHtmlTest.php +++ b/components/ILIAS/UI/tests/Component/Toast/ToastClientHtmlTest.php @@ -65,8 +65,6 @@ public function testRenderClientHtml(): void 'Title', $this->getIconFactory()->standard('mail', 'Test') ) - ->withVanishTime(5000) - ->withDelayTime(500) ->withDescription('Description') ->withAction('https://www.ilias.de') ); diff --git a/components/ILIAS/UI/tests/Component/Toast/ToastTest.php b/components/ILIAS/UI/tests/Component/Toast/ToastTest.php index efc3a6740991..75b4c6763d4a 100755 --- a/components/ILIAS/UI/tests/Component/Toast/ToastTest.php +++ b/components/ILIAS/UI/tests/Component/Toast/ToastTest.php @@ -52,23 +52,17 @@ public function testImplementsFactoryInterface(): void $this->assertInstanceOf("ILIAS\\UI\\Component\\Toast\\Container", $f->container()); } - /** - * @dataProvider getToastProvider - */ - public function testToast(string $title, string $description, int $vanish_time, int $delay_time, string $action): void + #[\PHPUnit\Framework\Attributes\DataProvider('getToastProvider')] + public function testToast(string $title, string $description, string $action): void { $toast = $this->getToastFactory()->standard($title, $this->getIconFactory()->standard('', '')) ->withDescription($description) - ->withVanishTime($vanish_time) - ->withDelayTime($delay_time) ->withAction($action) ->withAdditionalLink($this->getLinkFactory()->standard('', '')); $this->assertNotNull($toast); $this->assertEquals($title, $toast->getTitle()); $this->assertEquals($description, $toast->getDescription()); - $this->assertEquals($vanish_time, $toast->getVanishTime()); - $this->assertEquals($delay_time, $toast->getDelayTime()); $this->assertEquals($action, $toast->getAction()); $this->assertCount(1, $toast->getLinks()); $this->assertInstanceOf(Link::class, $toast->getLinks()[0]); @@ -76,10 +70,8 @@ public function testToast(string $title, string $description, int $vanish_time, $this->assertInstanceOf(Icon::class, $toast->getIcon()); } - /** - * @dataProvider getToastProvider - */ - public function testToastContainer(string $title, string $description, int $vanish_time): void + #[\PHPUnit\Framework\Attributes\DataProvider('getToastProvider')] + public function testToastContainer(string $title, string $description): void { $container = $this->getToastFactory()->container()->withAdditionalToast( $this->getToastFactory()->standard('', $this->getIconFactory()->standard('', '')) @@ -94,9 +86,9 @@ public function testToastContainer(string $title, string $description, int $vani public static function getToastProvider(): array { return [ - ['title', 'description', 5000, 500, 'test.php'], - ['', '', -5000, -500, ''], - ['"/>', '"/>', PHP_INT_MAX, PHP_INT_MIN, 'test.php'] + ['title', 'description', 'test.php'], + ['', '', ''], + ['"/>', '"/>', 'test.php'] ]; } } diff --git a/components/ILIAS/UI/tests/Component/Tree/Node/NodeTest.php b/components/ILIAS/UI/tests/Component/Tree/Node/NodeTest.php index dd3d074d66c5..103adb189c50 100755 --- a/components/ILIAS/UI/tests/Component/Tree/Node/NodeTest.php +++ b/components/ILIAS/UI/tests/Component/Tree/Node/NodeTest.php @@ -62,9 +62,7 @@ public function testConstruction(): TestingNode return $node; } - /** - * @depends testConstruction - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstruction')] public function testDefaults(TestingNode $node): void { $this->assertFalse($node->isExpanded()); @@ -72,9 +70,7 @@ public function testDefaults(TestingNode $node): void $this->assertEquals([], $node->getSubnodes()); } - /** - * @depends testConstruction - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstruction')] public function testWithExpanded(TestingNode $node): void { $this->assertTrue( @@ -82,9 +78,7 @@ public function testWithExpanded(TestingNode $node): void ); } - /** - * @depends testConstruction - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstruction')] public function testWithHighlighted(TestingNode $node): void { $this->assertTrue( @@ -92,9 +86,7 @@ public function testWithHighlighted(TestingNode $node): void ); } - /** - * @depends testConstruction - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstruction')] public function testWithOnClick(TestingNode $node): Clickable { $sig_gen = new I\SignalGenerator(); @@ -106,9 +98,7 @@ public function testWithOnClick(TestingNode $node): Clickable return $node; } - /** - * @depends testWithOnClick - */ + #[\PHPUnit\Framework\Attributes\Depends('testWithOnClick')] public function testWithAppendOnClick(Clickable $node): void { $sig_gen = new I\SignalGenerator(); @@ -119,9 +109,7 @@ public function testWithAppendOnClick(Clickable $node): void $this->assertEquals($sig, $check); } - /** - * @depends testWithOnClick - */ + #[\PHPUnit\Framework\Attributes\Depends('testWithOnClick')] public function testWithURI(Clickable $node): void { $uri = new URI('http://google.de:8080'); diff --git a/components/ILIAS/UI/tests/Component/Tree/Node/SimpleNodeTest.php b/components/ILIAS/UI/tests/Component/Tree/Node/SimpleNodeTest.php index 7fc7cd3dc719..4a991eb4514b 100755 --- a/components/ILIAS/UI/tests/Component/Tree/Node/SimpleNodeTest.php +++ b/components/ILIAS/UI/tests/Component/Tree/Node/SimpleNodeTest.php @@ -82,42 +82,32 @@ public function testConstructionWithIconAndDifferentLabels(): C\Tree\Node\Simple return $node; } - /** - * @depends testConstructionWithIconAndDifferentLabels - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstructionWithIconAndDifferentLabels')] public function testGetDifferentLabels(C\Tree\Node\Simple $node): void { $this->assertNotEquals($this->icon->getLabel(), $node->getLabel()); } - /** - * @depends testConstructionWithIcon - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstructionWithIcon')] public function testGetLabel(C\Tree\Node\Simple $node): void { $this->assertEquals("label", $node->getLabel()); } - /** - * @depends testConstructionWithIcon - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstructionWithIcon')] public function testGetIcon(C\Tree\Node\Simple $node): C\Tree\Node\Simple { $this->assertEquals($this->icon, $node->getIcon()); return $node; } - /** - * @depends testConstruction - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstruction')] public function testDefaultAsyncLoading(C\Tree\Node\Simple $node): void { $this->assertFalse($node->getAsyncLoading()); } - /** - * @depends testConstruction - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstruction')] public function testWithAsyncURL(C\Tree\Node\Simple $node): C\Tree\Node\Simple { $url = 'something.de'; @@ -127,9 +117,7 @@ public function testWithAsyncURL(C\Tree\Node\Simple $node): C\Tree\Node\Simple return $node; } - /** - * @depends testConstruction - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstruction')] public function testRendering(C\Tree\Node\Simple $node): void { $r = $this->getDefaultRenderer(); @@ -149,9 +137,7 @@ public function testRendering(C\Tree\Node\Simple $node): void ); } - /** - * @depends testWithAsyncURL - */ + #[\PHPUnit\Framework\Attributes\Depends('testWithAsyncURL')] public function testRenderingWithAsync(C\Tree\Node\Simple $node): void { $r = $this->getDefaultRenderer(); @@ -174,9 +160,7 @@ public function testRenderingWithAsync(C\Tree\Node\Simple $node): void ); } - /** - * @depends testConstructionWithIcon - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstructionWithIcon')] public function testRenderingWithIcon(C\Tree\Node\Simple $node): void { $r = $this->getDefaultRenderer(); @@ -202,9 +186,8 @@ public function testRenderingWithIcon(C\Tree\Node\Simple $node): void * This test is successfull if the icon label differs from the node label. * As a result the alt attribute will get the icon's label as content. * Else the alt attribute will be empty (see testRenderingWithIcon). - * - * @depends testConstructionWithIconAndDifferentLabels */ + #[\PHPUnit\Framework\Attributes\Depends('testConstructionWithIconAndDifferentLabels')] public function testRenderingWithIconAndAltAttribute(C\Tree\Node\Simple $node): void { $r = $this->getDefaultRenderer(); diff --git a/components/ILIAS/UI/tests/Component/Tree/TreeTest.php b/components/ILIAS/UI/tests/Component/Tree/TreeTest.php index c19ec590874d..6cf9614f49ec 100755 --- a/components/ILIAS/UI/tests/Component/Tree/TreeTest.php +++ b/components/ILIAS/UI/tests/Component/Tree/TreeTest.php @@ -73,52 +73,40 @@ public function build( return $tree; } - /** - * @depends testConstruction - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstruction')] public function testGetLabel(TestingTree $tree): void { $this->assertEquals("label", $tree->getLabel()); } - /** - * @depends testConstruction - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstruction')] public function testGetRecursion(TestingTree $tree): void { $this->assertInstanceOf("ILIAS\\UI\\Component\\Tree\\TreeRecursion", $tree->getRecursion()); } - /** - * @depends testConstruction - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstruction')] public function testWithEnvironment(TestingTree $tree): void { $env = ['key1' => 'val1', 'key2' => 2]; $this->assertEquals($env, $tree->withEnvironment($env)->getEnvironment()); } - /** - * @depends testConstruction - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstruction')] public function testWithData(TestingTree $tree): void { $data = ['entry1', 'entry2']; $this->assertEquals($data, $tree->withData($data)->getData()); } - /** - * @depends testConstruction - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstruction')] public function testWithHighlightOnNodeClick(TestingTree $tree): void { $this->assertFalse($tree->getHighlightOnNodeClick()); $this->assertTrue($tree->withHighlightOnNodeClick(true)->getHighlightOnNodeClick()); } - /** - * @depends testConstruction - */ + #[\PHPUnit\Framework\Attributes\Depends('testConstruction')] public function testWithIsSubTree(TestingTree $tree): void { $this->assertFalse($tree->isSubTree()); diff --git a/components/ILIAS/UI/tests/Component/ViewControl/PaginationTest.php b/components/ILIAS/UI/tests/Component/ViewControl/PaginationTest.php index acfe938f77cc..d8ddb57978a6 100755 --- a/components/ILIAS/UI/tests/Component/ViewControl/PaginationTest.php +++ b/components/ILIAS/UI/tests/Component/ViewControl/PaginationTest.php @@ -104,20 +104,14 @@ public function testViewControlPaginationRenderUnlimited(): void //browse-left disabled $expected_html = << - - - - - - - - - - - - - - + + + + EOT; @@ -136,25 +130,22 @@ public function testRenderWithCurrentPage(): void //browse-right disabled $expected_html = << - - - - - - - - - - - - - - + + + + EOT; $html = $this->getDefaultRenderer()->render($p); - $this->assertHTMLEquals($expected_html, $html); + $this->assertHTMLEquals( + $this->brutallyTrimHTML($expected_html), + $this->brutallyTrimHTML($html) + ); } public function testRenderLimited(): void @@ -169,27 +160,21 @@ public function testRenderLimited(): void //boundary-button right $expected_html = << - - - - - - - - - - - - - - - - - + + + + EOT; $html = $this->getDefaultRenderer()->render($p); - $this->assertHTMLEquals($expected_html, $html); + $this->assertEquals( + $this->brutallyTrimHTML($expected_html), + $this->brutallyTrimHTML($html) + ); } public function testRenderLimitedWithCurrentPage(): void @@ -205,31 +190,21 @@ public function testRenderLimitedWithCurrentPage(): void //both boundary-buttons $expected_html = << - - - - - - - - - - - - - - - - - - - - - + + + + EOT; $html = $this->getDefaultRenderer()->render($p); - $this->assertHTMLEquals($expected_html, $html); + $this->assertHTMLEquals( + $this->brutallyTrimHTML($expected_html), + $this->brutallyTrimHTML($html) + ); } public function testRenderLimitedWithCurrentPage2(): void @@ -245,30 +220,23 @@ public function testRenderLimitedWithCurrentPage2(): void //boundary-button left only $expected_html = << - - - - - - - - - - - - - - - - + + + + EOT; $html = $this->getDefaultRenderer()->render($p); - $this->assertHTMLEquals($expected_html, $html); + $this->assertEquals( + $this->brutallyTrimHTML($expected_html), + $this->brutallyTrimHTML($html) + ); } - - public function testRenderDropdown(): void { $p = $this->getFactory()->pagination() @@ -278,30 +246,33 @@ public function testRenderDropdown(): void $expected_html = << - - - - - - - - - - - - - + + + EOT; $html = $this->getDefaultRenderer()->render($p); - $this->assertEquals($this->brutallyTrimHTML($expected_html), $this->brutallyTrimHTML($html)); + $this->assertEquals( + $this->brutallyTrimHTML($expected_html), + $this->brutallyTrimHTML($html) + ); } public function testGetRangeOnNull(): void diff --git a/components/ILIAS/UI/tests/Component/ViewControl/SortationTest.php b/components/ILIAS/UI/tests/Component/ViewControl/SortationTest.php index 3ca462e5b233..c1add000cf90 100755 --- a/components/ILIAS/UI/tests/Component/ViewControl/SortationTest.php +++ b/components/ILIAS/UI/tests/Component/ViewControl/SortationTest.php @@ -86,7 +86,7 @@ public function testRendering(): void