From 9215f75a72cff9e6c8a3029a20f8742f53805399 Mon Sep 17 00:00:00 2001 From: mjansen Date: Tue, 4 Jun 2024 11:19:51 +0200 Subject: [PATCH 001/115] Registration: Stretch request duration See: https://mantis.ilias.de/view.php?id=32037 --- ...ilPrivacySecurityConfigStoredObjective.php | 6 +++ .../class.ilPrivacySecuritySetupAgent.php | 3 +- .../class.ilPrivacySecuritySetupConfig.php | 10 ++++- .../class.ilAccountRegistrationGUI.php | 41 ++++++++++++------- components/ILIAS/setup_/README.md | 4 +- 5 files changed, 47 insertions(+), 17 deletions(-) diff --git a/components/ILIAS/PrivacySecurity/classes/Setup/class.ilPrivacySecurityConfigStoredObjective.php b/components/ILIAS/PrivacySecurity/classes/Setup/class.ilPrivacySecurityConfigStoredObjective.php index 9b0286833615..74175d21329a 100755 --- a/components/ILIAS/PrivacySecurity/classes/Setup/class.ilPrivacySecurityConfigStoredObjective.php +++ b/components/ILIAS/PrivacySecurity/classes/Setup/class.ilPrivacySecurityConfigStoredObjective.php @@ -76,6 +76,12 @@ public function achieve(Setup\Environment $environment): Setup\Environment $settings->delete("account_assistance_duration"); } + if (null !== $this->config->getRegistrationDurationInMs()) { + $settings->set("registration_duration", (string) $this->config->getRegistrationDurationInMs()); + } else { + $settings->delete("registration_duration"); + } + return $environment; } diff --git a/components/ILIAS/PrivacySecurity/classes/Setup/class.ilPrivacySecuritySetupAgent.php b/components/ILIAS/PrivacySecurity/classes/Setup/class.ilPrivacySecuritySetupAgent.php index c6943158473e..17dd879a844e 100755 --- a/components/ILIAS/PrivacySecurity/classes/Setup/class.ilPrivacySecuritySetupAgent.php +++ b/components/ILIAS/PrivacySecurity/classes/Setup/class.ilPrivacySecuritySetupAgent.php @@ -61,7 +61,8 @@ public function getArrayToConfigTransformation(): Refinery\Transformation return new ilPrivacySecuritySetupConfig( (bool) ($data["https_enabled"] ?? false), (isset($data["auth_duration"])) ? (int) $data["auth_duration"] : null, - (isset($data["account_assistance_duration"])) ? (int) $data["account_assistance_duration"] : null + (isset($data["account_assistance_duration"])) ? (int) $data["account_assistance_duration"] : null, + (isset($data["registration_duration"])) ? (int) $data["registration_duration"] : null, ); }); } diff --git a/components/ILIAS/PrivacySecurity/classes/Setup/class.ilPrivacySecuritySetupConfig.php b/components/ILIAS/PrivacySecurity/classes/Setup/class.ilPrivacySecuritySetupConfig.php index 3de27e1fee43..a51230213fc1 100755 --- a/components/ILIAS/PrivacySecurity/classes/Setup/class.ilPrivacySecuritySetupConfig.php +++ b/components/ILIAS/PrivacySecurity/classes/Setup/class.ilPrivacySecuritySetupConfig.php @@ -24,15 +24,18 @@ class ilPrivacySecuritySetupConfig implements Setup\Config protected bool $force_https_on_login; protected ?int $authentication_duration_in_ms; protected ?int $account_assistance_duration_in_ms; + protected ?int $registration_duration_in_ms; public function __construct( bool $force_https_on_login = false, ?int $authentication_duration_in_ms = null, - ?int $account_assistance_duration_in_ms = null + ?int $account_assistance_duration_in_ms = null, + ?int $registration_duration_in_ms = null, ) { $this->force_https_on_login = $force_https_on_login; $this->authentication_duration_in_ms = $authentication_duration_in_ms; $this->account_assistance_duration_in_ms = $account_assistance_duration_in_ms; + $this->registration_duration_in_ms = $registration_duration_in_ms; } public function getForceHttpsOnLogin(): bool @@ -49,4 +52,9 @@ public function getAccountAssistanceDurationInMs(): ?int { return $this->account_assistance_duration_in_ms; } + + public function getRegistrationDurationInMs(): ?int + { + return $this->registration_duration_in_ms; + } } diff --git a/components/ILIAS/Registration/classes/class.ilAccountRegistrationGUI.php b/components/ILIAS/Registration/classes/class.ilAccountRegistrationGUI.php index 6b68707bec72..a173fa42d34c 100755 --- a/components/ILIAS/Registration/classes/class.ilAccountRegistrationGUI.php +++ b/components/ILIAS/Registration/classes/class.ilAccountRegistrationGUI.php @@ -323,25 +323,38 @@ public function saveForm(): ilGlobalTemplateInterface $form_valid = false; } - if ($form_valid) { - if (ilObjUser::_loginExists($login)) { - $login_obj->setAlert($this->lng->txt("login_exists")); - $form_valid = false; - } elseif ((int) $this->settings->get('allow_change_loginname') && - (int) $this->settings->get('reuse_of_loginnames') === 0 && - ilObjUser::_doesLoginnameExistInHistory($login)) { - $login_obj->setAlert($this->lng->txt('login_exists')); - $form_valid = false; + // We should use the HTTP request stretching mechanisms here, according to Mantis #32037 + $username_checked_and_register_callback = function () use (&$form_valid, $login, $login_obj, $valid_role) { + if ($form_valid) { + if (ilObjUser::_loginExists($login)) { + $login_obj->setAlert($this->lng->txt('login_exists')); + $form_valid = false; + } elseif ((int) $this->settings->get('allow_change_loginname') && + (int) $this->settings->get('reuse_of_loginnames') === 0 && + ilObjUser::_doesLoginnameExistInHistory($login)) { + $login_obj->setAlert($this->lng->txt('login_exists')); + $form_valid = false; + } } - } - if (!$form_valid) { - $this->tpl->setOnScreenMessage('failure', $this->lng->txt('form_input_not_valid')); + if ($form_valid) { + $password = $this->createUser($valid_role); + $this->distributeMails($password); + } + }; + + if (($register_duration = $this->settings->get('registration_duration')) !== null) { + $duration = $this->http->durations()->callbackDuration((int) $register_duration); + $duration->stretch($username_checked_and_register_callback); } else { - $password = $this->createUser($valid_role); - $this->distributeMails($password); + $username_checked_and_register_callback(); + } + + if ($form_valid) { return $this->login(); } + + $this->tpl->setOnScreenMessage('failure', $this->lng->txt('form_input_not_valid')); $this->form->setValuesByPost(); return $this->displayForm(); } diff --git a/components/ILIAS/setup_/README.md b/components/ILIAS/setup_/README.md index 1ece4390cc63..a16e68b9e905 100755 --- a/components/ILIAS/setup_/README.md +++ b/components/ILIAS/setup_/README.md @@ -493,12 +493,14 @@ are printed bold**, all other fields might be omitted. A minimal example is "privacysecurity" : { "https_enabled" : true, "auth_duration" : 3000, - "account_assistance_duration" : 3000 + "account_assistance_duration" : 3000, + "registration_duration" : 3000, }, ``` * *https_enabled* (type: boolean) forces https on login page, defaults to `false` * *auth_duration* (type: integer) stretches the auth-duration on logins to the given amount in ms, defaults to `null` * *account_assistance_duration* (type: integer) stretches the password- and username-assistance duration to the given amount in ms, defaults to `null` + * *registration_duration* (type: integer) stretches registration duration to the given amount in ms, defaults to `null` * *webservices* (type: object) ``` "webservices" : { From eb3b400c15dd4986bf3b6f5b41c5ba5e03d58639 Mon Sep 17 00:00:00 2001 From: Alex Killing Date: Thu, 25 Jul 2024 17:09:30 +0200 Subject: [PATCH 002/115] fixed drag/drop --- .../COPage/Editor/js/src/components/page/ui/page-ui.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/components/ILIAS/COPage/Editor/js/src/components/page/ui/page-ui.js b/components/ILIAS/COPage/Editor/js/src/components/page/ui/page-ui.js index 689395020e97..32cd9de5ae59 100755 --- a/components/ILIAS/COPage/Editor/js/src/components/page/ui/page-ui.js +++ b/components/ILIAS/COPage/Editor/js/src/components/page/ui/page-ui.js @@ -96,7 +96,7 @@ export default class PageUI { toolSlate, pageModifier, ) { - this.debug = false; + this.debug = true; this.droparea = "
"; this.add = ""; this.model = {}; @@ -290,6 +290,11 @@ export default class PageUI { }); ul.appendChild(li); } + if (ul.style.display == 'block') { + ul.style.display = ''; + } else { + ul.style.display = 'block'; + } }); }); }); From d0b281ede6cbe2aff11edb60098cd8b6c946d8fd Mon Sep 17 00:00:00 2001 From: iszmais <45942348+iszmais@users.noreply.github.com> Date: Thu, 25 Jul 2024 18:11:19 +0200 Subject: [PATCH 003/115] Remove key assumption and add strict casting (#7859) --- .../classes/class.ilDashboardBlockGUI.php | 18 +++++++++--------- .../classes/class.ilFavouritesDBRepository.php | 2 +- .../classes/class.ilFavouritesListGUI.php | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/components/ILIAS/Dashboard/Block/classes/class.ilDashboardBlockGUI.php b/components/ILIAS/Dashboard/Block/classes/class.ilDashboardBlockGUI.php index b9ed6560f8af..25a1d33c2e76 100755 --- a/components/ILIAS/Dashboard/Block/classes/class.ilDashboardBlockGUI.php +++ b/components/ILIAS/Dashboard/Block/classes/class.ilDashboardBlockGUI.php @@ -47,7 +47,7 @@ abstract class ilDashboardBlockGUI extends ilBlockGUI implements ilDesktopItemHa protected Services $http; private Factory $refinery; protected ilPDSelectedItemsBlockViewSettings $viewSettings; - /** @var array */ + /** @var array */ protected array $data; public function __construct() @@ -224,7 +224,7 @@ public function getHTML(): string } /** - * @param array $a_data + * @param array $a_data */ public function setData(array $a_data): void { @@ -237,7 +237,7 @@ public function setData(array $a_data): void } /** - * @return array + * @return array */ public function getData(): array { @@ -245,7 +245,7 @@ public function getData(): array } /** - * @return array + * @return array */ public function groupItemsByStartDate(): array { @@ -302,7 +302,7 @@ public function groupItemsByStartDate(): array } /** - * @return array + * @return array */ protected function groupItemsByType(): array { @@ -337,7 +337,7 @@ protected function groupItemsByType(): array } /** - * @return array + * @return array */ protected function groupItemsByLocation(): array { @@ -351,7 +351,7 @@ protected function groupItemsByLocation(): array )); $this->object_cache->preloadReferenceCache($parent_ref_ids); - foreach ($data as $key => $item) { + foreach ($data as $item) { $parent_ref = $this->tree->getParentId($item->getRefId()); if ($this->isRootNode($parent_ref)) { $title = $this->getRepositoryTitle(); @@ -484,7 +484,7 @@ protected function returnToContext(): void } /** - * @return array + * @return array */ public function getItemGroups(): array { @@ -598,7 +598,7 @@ public function manage(ReplaceSignal $replace_signal = null): string $item_groups = $this->getItemGroups(); foreach ($item_groups as $key => $item_group) { $group = new ilPDSelectedItemsBlockGroup(); - $group->setLabel($key); + $group->setLabel((string) $key); $items = []; foreach ($item_group as $item) { if ($this->rbacsystem->checkAccess('leave', $item->getRefId())) { diff --git a/components/ILIAS/Dashboard/Favourites/classes/class.ilFavouritesDBRepository.php b/components/ILIAS/Dashboard/Favourites/classes/class.ilFavouritesDBRepository.php index 7357be3b6008..65e932d5d2ac 100644 --- a/components/ILIAS/Dashboard/Favourites/classes/class.ilFavouritesDBRepository.php +++ b/components/ILIAS/Dashboard/Favourites/classes/class.ilFavouritesDBRepository.php @@ -23,7 +23,7 @@ */ class ilFavouritesDBRepository { - /** @var array */ + /** @var array */ public static array $is_desktop_item = []; protected ilDBInterface $db; protected ilTree $tree; diff --git a/components/ILIAS/Dashboard/Favourites/classes/class.ilFavouritesListGUI.php b/components/ILIAS/Dashboard/Favourites/classes/class.ilFavouritesListGUI.php index 66177358f00a..26d7197c6e97 100644 --- a/components/ILIAS/Dashboard/Favourites/classes/class.ilFavouritesListGUI.php +++ b/components/ILIAS/Dashboard/Favourites/classes/class.ilFavouritesListGUI.php @@ -59,7 +59,7 @@ public function render(): string )->withLeadIcon($f->symbol()->icon()->custom(ilObject::_getIcon((int) $item->getObjId()), $item->getTitle())); } if (count($items) > 0) { - $item_groups[] = $f->item()->group($key, $items); + $item_groups[] = $f->item()->group((string) $key, $items); } } if (count($item_groups) > 0) { From fb5b860ed3b9697cd40a6c0faa75be834bf3aca9 Mon Sep 17 00:00:00 2001 From: iszmais <45942348+iszmais@users.noreply.github.com> Date: Thu, 25 Jul 2024 18:42:18 +0200 Subject: [PATCH 004/115] Add ia gui class to dashbaord (#7862) --- components/ILIAS/Dashboard/classes/class.ilDashboardGUI.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/ILIAS/Dashboard/classes/class.ilDashboardGUI.php b/components/ILIAS/Dashboard/classes/class.ilDashboardGUI.php index 558a7e05ba6d..6bbc0a33a469 100755 --- a/components/ILIAS/Dashboard/classes/class.ilDashboardGUI.php +++ b/components/ILIAS/Dashboard/classes/class.ilDashboardGUI.php @@ -31,7 +31,7 @@ * @ilCtrl_Calls ilDashboardGUI: ilGroupUserActionsGUI, ilAchievementsGUI * @ilCtrl_Calls ilDashboardGUI: ilPDMailBlockGUI * @ilCtrl_Calls ilDashboardGUI: ilSelectedItemsBlockGUI, ilDashboardRecommendedContentGUI, ilMembershipBlockGUI, ilDashboardLearningSequenceGUI, ilStudyProgrammeDashboardViewGUI, ilObjStudyProgrammeGUI - * + * @ilCtrl_Calls ilDashboardGUI: ilObjIndividualAssessmentGUI */ class ilDashboardGUI implements ilCtrlBaseClassInterface { From 8f75fc169bebda47ef30956e19dc85f102fff733 Mon Sep 17 00:00:00 2001 From: Thomas Famula Date: Wed, 24 Jul 2024 16:22:35 +0200 Subject: [PATCH 005/115] Fix chart.js registration --- components/ILIAS/UI/UI.php | 2 ++ .../UI/src/Implementation/Component/Chart/Bar/Renderer.php | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/components/ILIAS/UI/UI.php b/components/ILIAS/UI/UI.php index 906f9496113c..2d56345f90a2 100644 --- a/components/ILIAS/UI/UI.php +++ b/components/ILIAS/UI/UI.php @@ -98,6 +98,8 @@ public function init( new Component\Resource\NodeModule("@yaireo/tagify/dist/tagify.min.js"); $contribute[Component\Resource\PublicAsset::class] = fn() => new Component\Resource\NodeModule("@yaireo/tagify/dist/tagify.css"); + $contribute[Component\Resource\PublicAsset::class] = fn() => + new Component\Resource\NodeModule("chart.js/dist/chart.umd.js"); /* those are contributed by MediaObjects $contribute[Component\Resource\PublicAsset::class] = fn() => diff --git a/components/ILIAS/UI/src/Implementation/Component/Chart/Bar/Renderer.php b/components/ILIAS/UI/src/Implementation/Component/Chart/Bar/Renderer.php index a7e08351130b..ddb30715f122 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Chart/Bar/Renderer.php +++ b/components/ILIAS/UI/src/Implementation/Component/Chart/Bar/Renderer.php @@ -416,7 +416,7 @@ protected function getUserData(Bar\Bar $component): array public function registerResources(ResourceRegistry $registry): void { parent::registerResources($registry); - $registry->register('./node_modules/chart.js/dist/chart.min.js'); + $registry->register('assets/js/chart.umd.js'); $registry->register('assets/js/bar.js'); } From 5b751c290fa3e9afab732cd48ab306d7d6c75aed Mon Sep 17 00:00:00 2001 From: Thomas Famula Date: Wed, 24 Jul 2024 16:28:57 +0200 Subject: [PATCH 006/115] Fix copyright --- .../UI/src/Implementation/Component/Chart/Bar/Renderer.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/ILIAS/UI/src/Implementation/Component/Chart/Bar/Renderer.php b/components/ILIAS/UI/src/Implementation/Component/Chart/Bar/Renderer.php index ddb30715f122..d66cdf3eb6a6 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Chart/Bar/Renderer.php +++ b/components/ILIAS/UI/src/Implementation/Component/Chart/Bar/Renderer.php @@ -1,7 +1,5 @@ Date: Fri, 26 Jul 2024 13:31:12 +0200 Subject: [PATCH 007/115] prevent multiple init calls --- components/ILIAS/Container/Content/class.ItemSetManager.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/ILIAS/Container/Content/class.ItemSetManager.php b/components/ILIAS/Container/Content/class.ItemSetManager.php index c5ac0a1fa30a..c3279017ce0d 100755 --- a/components/ILIAS/Container/Content/class.ItemSetManager.php +++ b/components/ILIAS/Container/Content/class.ItemSetManager.php @@ -42,6 +42,7 @@ class ItemSetManager protected array $rendered = []; protected int $mode = self::FLAT; protected ?\ilContainerUserFilter $user_filter = null; + protected bool $initialised = false; /** * @param int $mode self::TREE|self::FLAT|self::SINGLE @@ -81,6 +82,9 @@ public function getHiddenFilesFound(): bool */ protected function init(): void { + if ($this->initialised) { + return; + } $tree = $this->domain->repositoryTree(); if ($this->mode === self::TREE) { $this->raw = $tree->getSubTree($tree->getNodeData($this->parent_ref_id)); @@ -97,6 +101,7 @@ protected function init(): void $this->groupItems(); $this->sortSessions(); $this->preloadAdvancedMDValues(); + $this->initialised = true; } /** From 526aba034fe09c106a825212152c1df88b842c2a Mon Sep 17 00:00:00 2001 From: Maja Boldt Date: Fri, 26 Jul 2024 11:27:36 +0200 Subject: [PATCH 008/115] fix return in method buildScoringInteractionFromId of class ILIAS\Test\Logging\TestLoggingDatabaseRepository --- .../ILIAS/Test/src/Logging/TestLoggingDatabaseRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/ILIAS/Test/src/Logging/TestLoggingDatabaseRepository.php b/components/ILIAS/Test/src/Logging/TestLoggingDatabaseRepository.php index c547c7ecece9..1eea4cf3fec7 100644 --- a/components/ILIAS/Test/src/Logging/TestLoggingDatabaseRepository.php +++ b/components/ILIAS/Test/src/Logging/TestLoggingDatabaseRepository.php @@ -297,7 +297,7 @@ private function buildScoringInteractionFromId(int $id): ?TestScoringInteraction return null; } - return $this->factory->buildParticipantInteractionFromDBValues($this->db->fetchObject($query)); + return $this->factory->buildScoringInteractionFromDBValues($this->db->fetchObject($query)); } private function buildErrorFromId(int $id): ?TestError From 442a6acf6a6f7e43aa037e2088f45c370579f502 Mon Sep 17 00:00:00 2001 From: Fabian Schmid Date: Fri, 26 Jul 2024 14:06:03 +0200 Subject: [PATCH 009/115] [FIX] 0041816: File objects in repository are very slow... --- .../ILIAS/File/classes/Preview/Settings.php | 7 ++- .../File/classes/Preview/SettingsFactory.php | 35 +++++++++++ .../class.ilObjFilePreviewRendererGUI.php | 59 +++++++++++++++++-- 3 files changed, 94 insertions(+), 7 deletions(-) create mode 100644 components/ILIAS/File/classes/Preview/SettingsFactory.php diff --git a/components/ILIAS/File/classes/Preview/Settings.php b/components/ILIAS/File/classes/Preview/Settings.php index cb4734ffb36f..033b1e73c491 100755 --- a/components/ILIAS/File/classes/Preview/Settings.php +++ b/components/ILIAS/File/classes/Preview/Settings.php @@ -46,10 +46,15 @@ class Settings extends ilSetting implements Setting private const F_PREVIEW_IMAGE_SIZE = 'preview_image_size'; private const F_PREVIEW_PERSISTING = 'preview_persisting'; private const F_PREVIEW_IMAGE_QUALITY = 'preview_image_quality'; + /** + * @readonly + */ + private ImagickEngine $imagick; public function __construct() { parent::__construct(self::MODULE_NAME, false); + $this->imagick = new ImagickEngine(); } public function setPersisting(bool $a_value): void @@ -64,7 +69,7 @@ public function isPersisting(): bool public function isPreviewPossible(): bool { - return (new ImagickEngine())->isRunning(); // &&(new GDEngine())->isRunning(); + return $this->imagick->isRunning(); // &&(new GDEngine())->isRunning(); } public function setPreviewEnabled(bool $a_value): void diff --git a/components/ILIAS/File/classes/Preview/SettingsFactory.php b/components/ILIAS/File/classes/Preview/SettingsFactory.php new file mode 100644 index 000000000000..6d2a4dfe38e8 --- /dev/null +++ b/components/ILIAS/File/classes/Preview/SettingsFactory.php @@ -0,0 +1,35 @@ + + */ +class SettingsFactory +{ + private static ?Settings $settings = null; + + public function getSettings(): Settings + { + if (self::$settings === null) { + self::$settings = new Settings(); + } + return self::$settings; + } +} diff --git a/components/ILIAS/File/classes/Preview/class.ilObjFilePreviewRendererGUI.php b/components/ILIAS/File/classes/Preview/class.ilObjFilePreviewRendererGUI.php index b428f0020b97..e7a91324fa18 100755 --- a/components/ILIAS/File/classes/Preview/class.ilObjFilePreviewRendererGUI.php +++ b/components/ILIAS/File/classes/Preview/class.ilObjFilePreviewRendererGUI.php @@ -16,6 +16,10 @@ * *********************************************************************/ +use ILIAS\UI\Factory; +use ILIAS\UI\Renderer; +use ILIAS\ResourceStorage\Services; +use ILIAS\HTTP\Wrapper\WrapperFactory; use ILIAS\Filesystem\Stream\Streams; use ILIAS\ResourceStorage\Flavour\Definition\FlavourDefinition; use ILIAS\ResourceStorage\Flavour\Definition\PagesToExtract; @@ -24,6 +28,7 @@ use ILIAS\components\File\Preview\Settings; use ILIAS\ResourceStorage\Flavour\Definition\CropToSquare; use ILIAS\ResourceStorage\Flavour\Definition\FitToSquare; +use ILIAS\Modules\File\Preview\SettingsFactory; /** * @author Fabian Schmid @@ -33,30 +38,72 @@ class ilObjFilePreviewRendererGUI implements ilCtrlBaseClassInterface public const P_RID = "rid"; public const CMD_GET_ASYNC_MODAL = 'getAsyncModal'; + /** + * @readonly + */ private ilDBInterface $db; - private \ILIAS\UI\Factory $ui_factory; - private \ILIAS\UI\Renderer $ui_renderer; + /** + * @readonly + */ + private Factory $ui_factory; + /** + * @readonly + */ + private Renderer $ui_renderer; + /** + * @readonly + */ private ilCtrlInterface $ctrl; private ?ResourceIdentification $rid = null; - private \ILIAS\ResourceStorage\Services $irss; + /** + * @readonly + */ + private Services $irss; + /** + * @readonly + */ private \ILIAS\HTTP\Services $http; - private \ILIAS\HTTP\Wrapper\WrapperFactory $http_wrapper; + /** + * @readonly + */ + private WrapperFactory $http_wrapper; + /** + * @readonly + */ private \ILIAS\Refinery\Factory $refinery; + /** + * @readonly + */ private FlavourDefinition $flavour_definition; + /** + * @readonly + */ private ilAccessHandler $access; + /** + * @readonly + */ private ilLanguage $language; + /** + * @readonly + */ private int $preview_size; + /** + * @readonly + */ private int $pages_to_extract; private bool $activated = false; private string $file_name = ''; + /** + * @readonly + */ private FlavourDefinition $fallback_flavour_definition; public function __construct( - private ?int $object_id = null + private readonly ?int $object_id = null ) { global $DIC; - $settings = new Settings(); + $settings = (new SettingsFactory())->getSettings(); $this->activated = $settings->isPreviewEnabled(); $this->db = $DIC->database(); From 22b6a9b3246e6706d2860ee46fe90548579bafd4 Mon Sep 17 00:00:00 2001 From: iszmais Date: Thu, 18 Jul 2024 13:53:35 +0200 Subject: [PATCH 010/115] Add primary key to resource table --- .../Setup/DB/class.ilResourceStorageDB80.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/components/ILIAS/ResourceStorage/classes/Setup/DB/class.ilResourceStorageDB80.php b/components/ILIAS/ResourceStorage/classes/Setup/DB/class.ilResourceStorageDB80.php index e0d6c31a5671..3f68b8d2eb75 100755 --- a/components/ILIAS/ResourceStorage/classes/Setup/DB/class.ilResourceStorageDB80.php +++ b/components/ILIAS/ResourceStorage/classes/Setup/DB/class.ilResourceStorageDB80.php @@ -344,4 +344,17 @@ public function step_12(): void 'owner_id', ); } + + public function step_13(): void + { + if ($this->db->indexExistsByFields('il_resource_stkh_u', ['stakeholder_id'])) { + $this->db->dropIndexByFields('il_resource_stkh_u', ['stakeholder_id']); + } + if ($this->db->indexExistsByFields('il_resource_stkh_u', ['rid'])) { + $this->db->dropIndexByFields('il_resource_stkh_u', ['rid']); + } + if (!$this->db->primaryExistsByFields('il_resource_stkh_u', ['rid'])) { + $this->db->addPrimaryKey('il_resource_stkh_u', ['rid']); + } + } } From 62b668f1bbdafe419a98d06371a5ee064e4b3c8f Mon Sep 17 00:00:00 2001 From: iszmais Date: Thu, 18 Jul 2024 13:48:38 +0200 Subject: [PATCH 011/115] fix primary key check for resources --- .../classes/Setup/DB/class.ilResourceStorageDB80.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/ILIAS/ResourceStorage/classes/Setup/DB/class.ilResourceStorageDB80.php b/components/ILIAS/ResourceStorage/classes/Setup/DB/class.ilResourceStorageDB80.php index 3f68b8d2eb75..c203c059f247 100755 --- a/components/ILIAS/ResourceStorage/classes/Setup/DB/class.ilResourceStorageDB80.php +++ b/components/ILIAS/ResourceStorage/classes/Setup/DB/class.ilResourceStorageDB80.php @@ -301,7 +301,7 @@ public function step_9(): void public function step_10(): void { - if (!$this->db->addPrimaryKey('il_resource_rca', ['rcid', 'rid'])) { + if (!$this->db->primaryExistsByFields('il_resource_rca', ['rcid', 'rid'])) { $this->db->addPrimaryKey( 'il_resource_rca', [ @@ -311,7 +311,7 @@ public function step_10(): void ); } - if (!$this->db->indexExistsByFields('il_resource_rc', ['rcid'])) { + if (!$this->db->primaryExistsByFields('il_resource_rc', ['rcid'])) { $this->db->addPrimaryKey( 'il_resource_rc', [ From 61ab6ed1f3e155d61bf0c3005cef1ed7bcea7ad6 Mon Sep 17 00:00:00 2001 From: Tim Schmitz Date: Fri, 26 Jul 2024 16:48:04 +0200 Subject: [PATCH 012/115] Calendar: aria label for month view table (36761) --- components/ILIAS/Calendar/templates/default/tpl.month_view.html | 2 +- .../ILIAS/Calendar/templates/default/tpl.navigation_header.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/ILIAS/Calendar/templates/default/tpl.month_view.html b/components/ILIAS/Calendar/templates/default/tpl.month_view.html index ade0a6487f05..fb5e2a217fe9 100755 --- a/components/ILIAS/Calendar/templates/default/tpl.month_view.html +++ b/components/ILIAS/Calendar/templates/default/tpl.month_view.html @@ -1,5 +1,5 @@ {NAVIGATION} - +
diff --git a/components/ILIAS/Calendar/templates/default/tpl.navigation_header.html b/components/ILIAS/Calendar/templates/default/tpl.navigation_header.html index c6f190dad32a..80564eedd344 100755 --- a/components/ILIAS/Calendar/templates/default/tpl.navigation_header.html +++ b/components/ILIAS/Calendar/templates/default/tpl.navigation_header.html @@ -1 +1 @@ -

{TXT_TITLE}

\ No newline at end of file +

{TXT_TITLE}

\ No newline at end of file From fc494e11cfac9f9d3180e3aa360a96c1017a174c Mon Sep 17 00:00:00 2001 From: Ilja Lukin Date: Mon, 8 Jul 2024 15:46:55 +0200 Subject: [PATCH 013/115] Fix Error when assigning new HisInOne users from ECS with a numeric value as personID https://mantis.ilias.de/view.php?id=41671 --- .../Course/class.ilECSCmsCourseMemberCommandQueueHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/ILIAS/WebServices/ECS/classes/Course/class.ilECSCmsCourseMemberCommandQueueHandler.php b/components/ILIAS/WebServices/ECS/classes/Course/class.ilECSCmsCourseMemberCommandQueueHandler.php index 44c71d1398bd..7722a4c94460 100755 --- a/components/ILIAS/WebServices/ECS/classes/Course/class.ilECSCmsCourseMemberCommandQueueHandler.php +++ b/components/ILIAS/WebServices/ECS/classes/Course/class.ilECSCmsCourseMemberCommandQueueHandler.php @@ -355,7 +355,7 @@ protected function refreshAssignmentStatus(object $course_member, int $obj_id, ? $assignment->setCmsId($course_id); $assignment->setCmsSubId((int) $sub_id); $assignment->setObjId($obj_id); - $assignment->setUid($person_id); + $assignment->setUid((string) $person_id); $assignment->save(); } } From 7647f3d290864e460b785c523b0b85b0d2c2940e Mon Sep 17 00:00:00 2001 From: Tim Schmitz Date: Wed, 27 Mar 2024 17:28:59 +0100 Subject: [PATCH 014/115] MetaData: complete new backend, extend API --- .../xml/SchemaValidation/ilias_md_10_0.xsd | 402 ++++++++ .../ilias_md_4_1.xsd} | 128 +-- components/ILIAS/MetaData/ROADMAP.md | 77 +- .../Full/Services/Inputs/InputFactory.php | 1 - .../Editor/Manipulator/Manipulator.php | 10 +- .../Manipulator/ManipulatorInterface.php | 2 + .../classes/Editor/Services/Services.php | 3 +- .../classes/Editor/class.ilMDEditorGUI.php | 13 +- .../classes/Elements/Base/BaseElement.php | 37 +- .../MetaData/classes/Elements/Element.php | 20 +- .../Elements/Markers/MarkableInterface.php | 7 +- .../MetaData/classes/Elements/NullElement.php | 8 +- .../RessourceID/NullRessourceIDFactory.php | 37 + .../RessourceID/RessourceIDFactory.php | 5 + .../RessourceIDFactoryInterface.php | 2 + .../Scaffolds/NullScaffoldFactory.php | 40 + .../Elements/Scaffolds/ScaffoldFactory.php | 26 +- .../Scaffolds/ScaffoldFactoryInterface.php | 32 + .../Scaffolds/ScaffoldableInterface.php | 6 +- .../class.ilMDServicesException.php | 23 + .../Exceptions/class.ilMDXMLException.php | 23 + .../GlobalScreen/class.ilMDKeywordExposer.php | 2 +- .../classes/Manipulator/Manipulator.php | 17 +- .../Manipulator/ManipulatorInterface.php | 2 - .../classes/Manipulator/NullManipulator.php | 4 - .../Path/NullPathUtilitiesFactory.php | 5 - .../NullScaffoldProvider.php | 14 +- .../ScaffoldProvider}/ScaffoldProvider.php | 60 +- .../ScaffoldProviderInterface.php | 19 +- .../classes/Manipulator/Services/Services.php | 33 +- .../ILIAS/MetaData/classes/Paths/Builder.php | 5 +- .../Navigator/NavigatorFactoryInterface.php | 2 +- .../classes/Paths/Steps/NavigatorBridge.php | 12 +- .../classes/Repository/Dictionary/NullTag.php | 56 ++ .../IdentifierHandler/IdentifierHandler.php | 100 ++ .../IdentifierHandlerInterface.php | 32 + .../Repository/LOMDatabaseRepository.php | 48 +- .../classes/Repository/NullRepository.php | 27 +- .../Repository/RepositoryInterface.php | 41 +- .../Repository/Search/Clauses/Clause.php | 64 ++ .../Search/Clauses/ClauseInterface.php | 35 + .../Repository/Search/Clauses/Factory.php | 80 ++ .../Search/Clauses/FactoryInterface.php | 71 ++ .../Repository/Search/Clauses/Mode.php | 29 + .../Repository/Search/Clauses/NullClause.php | 47 + .../Repository/Search/Clauses/NullFactory.php | 49 + .../Repository/Search/Clauses/Operator.php | 27 + .../Clauses/Properties/BasicProperties.php | 64 ++ .../Properties/BasicPropertiesInterface.php | 35 + .../Clauses/Properties/JoinProperties.php | 60 ++ .../Properties/JoinPropertiesInterface.php | 34 + .../Properties/NullBasicProperties.php | 48 + .../Clauses/Properties/NullJoinProperties.php | 40 + .../Repository/Search/Filters/Factory.php | 32 + .../Search/Filters/FactoryInterface.php | 30 + .../Repository/Search/Filters/Filter.php | 53 ++ .../Search/Filters/FilterInterface.php | 30 + .../Repository/Search/Filters/NullFactory.php | 32 + .../Repository/Search/Filters/NullFilter.php | 39 + .../Repository/Search/Filters/Placeholder.php | 29 + .../classes/Repository/Services/Services.php | 58 +- .../Utilities/DatabaseManipulator.php | 127 ++- .../DatabaseManipulatorInterface.php | 5 + .../Repository/Utilities/DatabaseReader.php | 43 +- .../Utilities/Queries/DatabaseQuerier.php | 22 +- .../Utilities/Queries/DatabaseSearcher.php | 255 +++++ .../Queries/DatabaseSearcherInterface.php | 34 + .../Queries/Paths/DatabasePathsParser.php | 372 ++++++++ .../Paths/DatabasePathsParserFactory.php | 58 ++ .../DatabasePathsParserFactoryInterface.php | 28 + .../Paths/DatabasePathsParserInterface.php | 32 + .../Queries/Paths/NullDatabasePathsParser.php | 41 + .../Paths/NullDatabasePathsParserFactory.php | 31 + .../Utilities/Queries/TableNamesHandler.php | 53 ++ .../classes/Repository/Validation/Cleaner.php | 37 +- .../Validation/CleanerInterface.php | 6 + .../DataHelper/DataHelperInterface.php | 4 +- .../Services/Derivation/Creation/Creator.php | 176 ++++ .../Derivation/Creation/CreatorInterface.php | 35 + .../Derivation/Creation/NullCreator.php | 35 + .../classes/Services/Derivation/Derivator.php | 62 ++ .../Derivation/DerivatorInterface.php | 33 + .../Services/Derivation/NullDerivator.php | 28 + .../Services/Derivation/SourceSelector.php | 71 ++ .../Derivation/SourceSelectorInterface.php | 42 + .../classes/Services/InternalServices.php | 23 +- .../classes/Services/Manipulator/Factory.php | 54 ++ .../Services/Manipulator/FactoryInterface.php | 30 + .../Services/Manipulator/Manipulator.php | 49 +- .../Manipulator/ManipulatorInterface.php | 13 +- .../Services/Manipulator/NullFactory.php | 32 + .../Services/Manipulator/NullManipulator.php | 49 + .../classes/Services/Paths/Builder.php | 26 +- .../Services/Paths/BuilderInterface.php | 8 +- .../classes/Services/Reader/Factory.php | 42 + .../Services/Reader/FactoryInterface.php | 28 + .../classes/Services/Reader/NullFactory.php | 31 + .../classes/Services/Reader/NullReader.php | 41 + .../classes/Services/Search/Searcher.php | 79 ++ .../Services/Search/SearcherInterface.php | 63 ++ .../MetaData/classes/Services/Services.php | 98 +- .../classes/Services/ServicesInterface.php | 37 +- .../XML/Copyright/CopyrightHandler.php | 39 + .../Copyright/CopyrightHandlerInterface.php | 28 + .../XML/Copyright/NullCopyrightHandler.php | 34 + .../XML/Dictionary/DictionaryInterface.php | 32 + .../classes/XML/Dictionary/LOMDictionary.php | 46 + .../XML/Dictionary/LOMDictionaryInitiator.php | 197 ++++ .../classes/XML/Dictionary/NullDictionary.php | 34 + .../classes/XML/Dictionary/NullTag.php | 60 ++ .../classes/XML/Dictionary/SpecialCase.php | 29 + .../MetaData/classes/XML/Dictionary/Tag.php | 81 ++ .../classes/XML/Dictionary/TagFactory.php | 34 + .../XML/Dictionary/TagFactoryInterface.php | 32 + .../classes/XML/Dictionary/TagInterface.php | 36 + .../classes/XML/Reader/NullReader.php | 36 + .../classes/XML/Reader/ReaderInterface.php | 33 + .../classes/XML/Reader/Standard/Legacy.php | 523 ++++++++++ .../classes/XML/Reader/Standard/Standard.php | 53 ++ .../Reader/Standard/StructurallyCoupled.php | 210 ++++ .../classes/XML/Services/Services.php | 98 ++ .../ILIAS/MetaData/classes/XML/Version.php | 27 + .../classes/XML/Writer/Standard/Standard.php | 173 ++++ .../classes/XML/Writer/WriterInterface.php | 34 + .../ILIAS/MetaData/classes/class.ilMD.php | 1 + .../ILIAS/MetaData/classes/class.ilMD2XML.php | 1 + .../MetaData/classes/class.ilMDAnnotation.php | 1 + .../ILIAS/MetaData/classes/class.ilMDBase.php | 1 + .../classes/class.ilMDClassification.php | 1 + .../MetaData/classes/class.ilMDContribute.php | 1 + .../MetaData/classes/class.ilMDCreator.php | 1 + .../classes/class.ilMDDescription.php | 1 + .../classes/class.ilMDEducational.php | 1 + .../MetaData/classes/class.ilMDEntity.php | 1 + .../MetaData/classes/class.ilMDFormat.php | 1 + .../MetaData/classes/class.ilMDGeneral.php | 1 + .../MetaData/classes/class.ilMDIdentifier.php | 1 + .../classes/class.ilMDIdentifier_.php | 1 + .../MetaData/classes/class.ilMDKeyword.php | 1 + .../MetaData/classes/class.ilMDLanguage.php | 1 + .../classes/class.ilMDLanguageElement.php | 1 + .../classes/class.ilMDLanguageItem.php | 1 + .../MetaData/classes/class.ilMDLifecycle.php | 1 + .../MetaData/classes/class.ilMDLocation.php | 1 + .../classes/class.ilMDMetaMetadata.php | 1 + .../classes/class.ilMDOrComposite.php | 1 + .../MetaData/classes/class.ilMDRelation.php | 1 + .../classes/class.ilMDRequirement.php | 1 + .../MetaData/classes/class.ilMDRights.php | 1 + .../MetaData/classes/class.ilMDSaxParser.php | 1 + .../MetaData/classes/class.ilMDTaxon.php | 1 + .../MetaData/classes/class.ilMDTaxonPath.php | 1 + .../MetaData/classes/class.ilMDTechnical.php | 1 + .../classes/class.ilMDTypicalAgeRange.php | 1 + .../MetaData/classes/class.ilMDUtilSelect.php | 1 + .../MetaData/classes/class.ilMDUtils.php | 1 + .../MetaData/classes/class.ilMDXMLCopier.php | 1 + .../classes/class.ilMetaDataExporter.php | 55 +- .../classes/class.ilMetaDataImporter.php | 77 +- components/ILIAS/MetaData/docs/api.md | 410 +++++++- components/ILIAS/MetaData/docs/copyrights.md | 2 +- .../ILIAS/MetaData/docs/enabling_lom.md | 2 +- .../MetaData/docs/identifying_objects.md | 18 +- .../ILIAS/MetaData/docs/lom_structure.md | 21 +- components/ILIAS/MetaData/docs/manipulator.md | 4 +- .../MetaData/tests/Elements/ElementTest.php | 274 +++--- .../Scaffolds/ScaffoldFactoryTest.php | 22 +- .../ILIAS/MetaData/tests/Elements/SetTest.php | 2 +- .../tests/Manipulator/ManipulatorTest.php | 38 +- .../IdentifierHandlerTest.php | 203 ++++ .../ClauseWithPropertiesAndFactoryTest.php | 217 +++++ .../Search/Filters/FilterAndFactoryTest.php | 68 ++ .../Queries/DatabaseSearcherTest.php | 897 ++++++++++++++++++ .../Queries/Paths/DatabasePathsParserTest.php | 670 +++++++++++++ .../Derivation/Creation/CreatorTest.php | 226 +++++ .../Services/Derivation/DerivatorTest.php | 120 +++ .../Derivation/SourceSelectorTest.php | 110 +++ .../Services/Manipulator/ManipulatorTest.php | 78 +- .../tests/Services/Paths/BuilderTest.php | 33 +- .../tests/Services/Search/SearcherTest.php | 183 ++++ .../MetaData/tests/Services/ServicesTest.php | 214 +++++ .../XML/Reader/Standard/StandardTest.php | 92 ++ .../Standard/StructurallyCoupledTest.php | 781 +++++++++++++++ .../XML/Writer/Standard/StandardTest.php | 595 ++++++++++++ 184 files changed, 11471 insertions(+), 543 deletions(-) create mode 100644 components/ILIAS/Export/xml/SchemaValidation/ilias_md_10_0.xsd rename components/ILIAS/Export/xml/{ilias_meta_4_1.xsd => SchemaValidation/ilias_md_4_1.xsd} (82%) create mode 100644 components/ILIAS/MetaData/classes/Elements/RessourceID/NullRessourceIDFactory.php create mode 100644 components/ILIAS/MetaData/classes/Elements/Scaffolds/NullScaffoldFactory.php create mode 100644 components/ILIAS/MetaData/classes/Elements/Scaffolds/ScaffoldFactoryInterface.php create mode 100644 components/ILIAS/MetaData/classes/Exceptions/class.ilMDServicesException.php create mode 100644 components/ILIAS/MetaData/classes/Exceptions/class.ilMDXMLException.php rename components/ILIAS/MetaData/classes/{Repository/Utilities => Manipulator/ScaffoldProvider}/NullScaffoldProvider.php (68%) rename components/ILIAS/MetaData/classes/{Repository/Utilities => Manipulator/ScaffoldProvider}/ScaffoldProvider.php (63%) rename components/ILIAS/MetaData/classes/{Repository/Utilities => Manipulator/ScaffoldProvider}/ScaffoldProviderInterface.php (62%) create mode 100644 components/ILIAS/MetaData/classes/Repository/Dictionary/NullTag.php create mode 100644 components/ILIAS/MetaData/classes/Repository/IdentifierHandler/IdentifierHandler.php create mode 100644 components/ILIAS/MetaData/classes/Repository/IdentifierHandler/IdentifierHandlerInterface.php create mode 100644 components/ILIAS/MetaData/classes/Repository/Search/Clauses/Clause.php create mode 100644 components/ILIAS/MetaData/classes/Repository/Search/Clauses/ClauseInterface.php create mode 100644 components/ILIAS/MetaData/classes/Repository/Search/Clauses/Factory.php create mode 100644 components/ILIAS/MetaData/classes/Repository/Search/Clauses/FactoryInterface.php create mode 100644 components/ILIAS/MetaData/classes/Repository/Search/Clauses/Mode.php create mode 100644 components/ILIAS/MetaData/classes/Repository/Search/Clauses/NullClause.php create mode 100644 components/ILIAS/MetaData/classes/Repository/Search/Clauses/NullFactory.php create mode 100644 components/ILIAS/MetaData/classes/Repository/Search/Clauses/Operator.php create mode 100644 components/ILIAS/MetaData/classes/Repository/Search/Clauses/Properties/BasicProperties.php create mode 100644 components/ILIAS/MetaData/classes/Repository/Search/Clauses/Properties/BasicPropertiesInterface.php create mode 100644 components/ILIAS/MetaData/classes/Repository/Search/Clauses/Properties/JoinProperties.php create mode 100644 components/ILIAS/MetaData/classes/Repository/Search/Clauses/Properties/JoinPropertiesInterface.php create mode 100644 components/ILIAS/MetaData/classes/Repository/Search/Clauses/Properties/NullBasicProperties.php create mode 100644 components/ILIAS/MetaData/classes/Repository/Search/Clauses/Properties/NullJoinProperties.php create mode 100644 components/ILIAS/MetaData/classes/Repository/Search/Filters/Factory.php create mode 100644 components/ILIAS/MetaData/classes/Repository/Search/Filters/FactoryInterface.php create mode 100644 components/ILIAS/MetaData/classes/Repository/Search/Filters/Filter.php create mode 100644 components/ILIAS/MetaData/classes/Repository/Search/Filters/FilterInterface.php create mode 100644 components/ILIAS/MetaData/classes/Repository/Search/Filters/NullFactory.php create mode 100644 components/ILIAS/MetaData/classes/Repository/Search/Filters/NullFilter.php create mode 100644 components/ILIAS/MetaData/classes/Repository/Search/Filters/Placeholder.php create mode 100644 components/ILIAS/MetaData/classes/Repository/Utilities/Queries/DatabaseSearcher.php create mode 100644 components/ILIAS/MetaData/classes/Repository/Utilities/Queries/DatabaseSearcherInterface.php create mode 100644 components/ILIAS/MetaData/classes/Repository/Utilities/Queries/Paths/DatabasePathsParser.php create mode 100644 components/ILIAS/MetaData/classes/Repository/Utilities/Queries/Paths/DatabasePathsParserFactory.php create mode 100644 components/ILIAS/MetaData/classes/Repository/Utilities/Queries/Paths/DatabasePathsParserFactoryInterface.php create mode 100644 components/ILIAS/MetaData/classes/Repository/Utilities/Queries/Paths/DatabasePathsParserInterface.php create mode 100644 components/ILIAS/MetaData/classes/Repository/Utilities/Queries/Paths/NullDatabasePathsParser.php create mode 100644 components/ILIAS/MetaData/classes/Repository/Utilities/Queries/Paths/NullDatabasePathsParserFactory.php create mode 100644 components/ILIAS/MetaData/classes/Repository/Utilities/Queries/TableNamesHandler.php create mode 100644 components/ILIAS/MetaData/classes/Services/Derivation/Creation/Creator.php create mode 100644 components/ILIAS/MetaData/classes/Services/Derivation/Creation/CreatorInterface.php create mode 100644 components/ILIAS/MetaData/classes/Services/Derivation/Creation/NullCreator.php create mode 100644 components/ILIAS/MetaData/classes/Services/Derivation/Derivator.php create mode 100644 components/ILIAS/MetaData/classes/Services/Derivation/DerivatorInterface.php create mode 100644 components/ILIAS/MetaData/classes/Services/Derivation/NullDerivator.php create mode 100644 components/ILIAS/MetaData/classes/Services/Derivation/SourceSelector.php create mode 100644 components/ILIAS/MetaData/classes/Services/Derivation/SourceSelectorInterface.php create mode 100644 components/ILIAS/MetaData/classes/Services/Manipulator/Factory.php create mode 100644 components/ILIAS/MetaData/classes/Services/Manipulator/FactoryInterface.php create mode 100644 components/ILIAS/MetaData/classes/Services/Manipulator/NullFactory.php create mode 100644 components/ILIAS/MetaData/classes/Services/Manipulator/NullManipulator.php create mode 100644 components/ILIAS/MetaData/classes/Services/Reader/Factory.php create mode 100644 components/ILIAS/MetaData/classes/Services/Reader/FactoryInterface.php create mode 100644 components/ILIAS/MetaData/classes/Services/Reader/NullFactory.php create mode 100644 components/ILIAS/MetaData/classes/Services/Reader/NullReader.php create mode 100644 components/ILIAS/MetaData/classes/Services/Search/Searcher.php create mode 100644 components/ILIAS/MetaData/classes/Services/Search/SearcherInterface.php create mode 100644 components/ILIAS/MetaData/classes/XML/Copyright/CopyrightHandler.php create mode 100644 components/ILIAS/MetaData/classes/XML/Copyright/CopyrightHandlerInterface.php create mode 100644 components/ILIAS/MetaData/classes/XML/Copyright/NullCopyrightHandler.php create mode 100644 components/ILIAS/MetaData/classes/XML/Dictionary/DictionaryInterface.php create mode 100644 components/ILIAS/MetaData/classes/XML/Dictionary/LOMDictionary.php create mode 100644 components/ILIAS/MetaData/classes/XML/Dictionary/LOMDictionaryInitiator.php create mode 100644 components/ILIAS/MetaData/classes/XML/Dictionary/NullDictionary.php create mode 100644 components/ILIAS/MetaData/classes/XML/Dictionary/NullTag.php create mode 100644 components/ILIAS/MetaData/classes/XML/Dictionary/SpecialCase.php create mode 100644 components/ILIAS/MetaData/classes/XML/Dictionary/Tag.php create mode 100644 components/ILIAS/MetaData/classes/XML/Dictionary/TagFactory.php create mode 100644 components/ILIAS/MetaData/classes/XML/Dictionary/TagFactoryInterface.php create mode 100644 components/ILIAS/MetaData/classes/XML/Dictionary/TagInterface.php create mode 100644 components/ILIAS/MetaData/classes/XML/Reader/NullReader.php create mode 100644 components/ILIAS/MetaData/classes/XML/Reader/ReaderInterface.php create mode 100644 components/ILIAS/MetaData/classes/XML/Reader/Standard/Legacy.php create mode 100644 components/ILIAS/MetaData/classes/XML/Reader/Standard/Standard.php create mode 100644 components/ILIAS/MetaData/classes/XML/Reader/Standard/StructurallyCoupled.php create mode 100644 components/ILIAS/MetaData/classes/XML/Services/Services.php create mode 100644 components/ILIAS/MetaData/classes/XML/Version.php create mode 100644 components/ILIAS/MetaData/classes/XML/Writer/Standard/Standard.php create mode 100644 components/ILIAS/MetaData/classes/XML/Writer/WriterInterface.php create mode 100644 components/ILIAS/MetaData/tests/Repository/IdentifierHandler/IdentifierHandlerTest.php create mode 100644 components/ILIAS/MetaData/tests/Repository/Search/Clauses/ClauseWithPropertiesAndFactoryTest.php create mode 100644 components/ILIAS/MetaData/tests/Repository/Search/Filters/FilterAndFactoryTest.php create mode 100644 components/ILIAS/MetaData/tests/Repository/Utilities/Queries/DatabaseSearcherTest.php create mode 100644 components/ILIAS/MetaData/tests/Repository/Utilities/Queries/Paths/DatabasePathsParserTest.php create mode 100644 components/ILIAS/MetaData/tests/Services/Derivation/Creation/CreatorTest.php create mode 100644 components/ILIAS/MetaData/tests/Services/Derivation/DerivatorTest.php create mode 100644 components/ILIAS/MetaData/tests/Services/Derivation/SourceSelectorTest.php create mode 100644 components/ILIAS/MetaData/tests/Services/Search/SearcherTest.php create mode 100644 components/ILIAS/MetaData/tests/Services/ServicesTest.php create mode 100644 components/ILIAS/MetaData/tests/XML/Reader/Standard/StandardTest.php create mode 100644 components/ILIAS/MetaData/tests/XML/Reader/Standard/StructurallyCoupledTest.php create mode 100644 components/ILIAS/MetaData/tests/XML/Writer/Standard/StandardTest.php diff --git a/components/ILIAS/Export/xml/SchemaValidation/ilias_md_10_0.xsd b/components/ILIAS/Export/xml/SchemaValidation/ilias_md_10_0.xsd new file mode 100644 index 000000000000..2fc3526dff71 --- /dev/null +++ b/components/ILIAS/Export/xml/SchemaValidation/ilias_md_10_0.xsd @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/ILIAS/Export/xml/ilias_meta_4_1.xsd b/components/ILIAS/Export/xml/SchemaValidation/ilias_md_4_1.xsd similarity index 82% rename from components/ILIAS/Export/xml/ilias_meta_4_1.xsd rename to components/ILIAS/Export/xml/SchemaValidation/ilias_md_4_1.xsd index 4fdda0d4c140..6f0fa545cfa0 100755 --- a/components/ILIAS/Export/xml/ilias_meta_4_1.xsd +++ b/components/ILIAS/Export/xml/SchemaValidation/ilias_md_4_1.xsd @@ -157,15 +157,15 @@ - - - - - - - - - + + + + + + + + + @@ -174,12 +174,12 @@ - - - - - - + + + + + + @@ -199,7 +199,7 @@ - + @@ -207,8 +207,8 @@ - - + + @@ -217,28 +217,28 @@ - + - + - + - + @@ -246,8 +246,8 @@ - - + + @@ -265,7 +265,7 @@ - + @@ -273,8 +273,8 @@ - - + + @@ -318,8 +318,8 @@ - - + + @@ -328,7 +328,7 @@ - + @@ -336,16 +336,16 @@ - - - + + + - - + + - - - + + + @@ -380,7 +380,7 @@ - + @@ -389,7 +389,7 @@ - + @@ -397,9 +397,9 @@ - - - + + + @@ -447,14 +447,14 @@ - + - + @@ -468,10 +468,10 @@ - - - - + + + + @@ -562,7 +562,7 @@ - + @@ -576,7 +576,7 @@ - + @@ -601,7 +601,7 @@ - + @@ -628,8 +628,8 @@ - - + + @@ -646,9 +646,9 @@ - - - + + + @@ -657,9 +657,9 @@ - - - + + + @@ -683,8 +683,8 @@ - - + + @@ -692,14 +692,14 @@ - + - + diff --git a/components/ILIAS/MetaData/ROADMAP.md b/components/ILIAS/MetaData/ROADMAP.md index 711b9bf682b8..906002b0c3fd 100755 --- a/components/ILIAS/MetaData/ROADMAP.md +++ b/components/ILIAS/MetaData/ROADMAP.md @@ -47,17 +47,7 @@ replaced by bespoke iterator classes. ### Make Greater Use of Null Objects `null` as a return type should be replaced by proper null objects. -A good starting point might be `Tags` from `Dictionaries`. - -### Query Smarter - -Currently, every metadata element is queried separately, even if -they are persisted in the same table. This should be optimized. - -Furthermore, the methods constructing the bespoke queries for -the MD elements in ilMDLOMDatabaseDictionary are a bit of -a mess, with a lot of overlap between them. This can be done in -a more elegant way. +A good starting point might be `Tags` from `Dictionaries`. ### Stricter formatting of 'format' and 'entity' @@ -65,6 +55,22 @@ The fields technical>format and the various entities should conform to different standards (e.g. entities should be vcards). This could be supported better in ILIAS, currently any string is valid. +### Allow `INDEX` path filters in search + +It should be investigated, how path filters of type `INDEX` can be +taken into account in the search, to allow for search queries like +'Find objects where the **first** author is Dr. No'. + +These filters make translating the search queries to SQL much more +complex, so the cost might outweigh the use. + +### Allow manipulation of LOM sets during derivation + +The `Derivator` in the API could be expanded to contain methods like +`prepareOmit` and `prepareAddOrChange` to allow changes to the derived +LOM set before it is persisted. The repository would need to take into +account more types of markers/scaffolds in `transferMD`. + ### Vocabularies Allow adding other vocabularies than LOM. This could be implemented @@ -85,6 +91,49 @@ new classes as the new MD editor does. ### Customizable LOM Digest -Allow customizing what elements are part of the LOM Digest in -administration settings. It would also be worth thinking about -how to implement multilinguality in the Digest. \ No newline at end of file +Customizing of LOM Digest could be made possible for plugins, in +order to tailor the screen better to every installations configuration. + +### Clean up Elements Folder + +Currently, the Elements folder does not have its own service, so +creation of the factories contained therein is not centralized. + +For the factories for elements and structure elements, centralization +does not make much sense: those elements only make sense when created +in bulk and corss-referenced (sub- and super-elements), but the +factories only offer creation of single objects. The actual creation of +elements in context is done by higher order infrastructure such as the +repository. Those factories should thus only be created for that specific +part of the infrastructure and not reused. + +Factories for scaffolds, ressource IDs and data on the other hand +can be reused just fine, and should be offered through a service. +Markers are a special case, I'm not sure whether they are needed outside +of the manipulator. + +### Internationalization of LangStrings + +Elements consisting of a string and a language could be allowed to +contain multiple such tuples, such that e.g. translations of the title +can be stored in LOM. + +This would need expansive changes to the database structure, and a new +input element for multilangual text input. + +### Improve Unit Test Coverage + +The following classes are not yet covered by unit tests: + +- everything in `Editor` +- `GlobalScreen/ilMDKeywordExposer` +- `Manipulator/ScaffoldProvider` +- everything in `Paths` +- everything in `Repository` except `Repository/Search`, +`Repository/Utilities/Queries/DatabaseSearcher`, and +`Repository/Utilities/Queries/Paths` +- `Services\InternalServices` (along with all `Services` used by it), +also all methods in `Services\Services` that don't do anything except +lazily instantiate an object +- everything in `Vocabularies` +- `XML/Copyright`, `XML/Dictionary`, and `XML/Reader/Standard/Legacy` diff --git a/components/ILIAS/MetaData/classes/Editor/Full/Services/Inputs/InputFactory.php b/components/ILIAS/MetaData/classes/Editor/Full/Services/Inputs/InputFactory.php index b53906cca9e9..c5f48ee39c6c 100755 --- a/components/ILIAS/MetaData/classes/Editor/Full/Services/Inputs/InputFactory.php +++ b/components/ILIAS/MetaData/classes/Editor/Full/Services/Inputs/InputFactory.php @@ -27,7 +27,6 @@ use ILIAS\MetaData\Editor\Presenter\PresenterInterface; use ILIAS\MetaData\Repository\Dictionary\DictionaryInterface as DatabaseDictionary; use ILIAS\MetaData\Elements\ElementInterface; -use ILIAS\MetaData\Repository\Dictionary\ExpectedParameter; use ILIAS\MetaData\Editor\Full\Services\DataFinder; use ILIAS\MetaData\Paths\FactoryInterface as PathFactory; use ILIAS\MetaData\Vocabularies\VocabulariesInterface; diff --git a/components/ILIAS/MetaData/classes/Editor/Manipulator/Manipulator.php b/components/ILIAS/MetaData/classes/Editor/Manipulator/Manipulator.php index fe5e947fad1b..0e850ff19623 100755 --- a/components/ILIAS/MetaData/classes/Editor/Manipulator/Manipulator.php +++ b/components/ILIAS/MetaData/classes/Editor/Manipulator/Manipulator.php @@ -28,21 +28,25 @@ use ILIAS\MetaData\Paths\PathInterface; use ILIAS\MetaData\Repository\RepositoryInterface; use ilMDPathException; +use ILIAS\MetaData\Manipulator\ScaffoldProvider\ScaffoldProviderInterface; class Manipulator implements ManipulatorInterface { protected BaseManipulator $base_manipulator; protected NavigatorFactoryInterface $navigator_factory; protected RepositoryInterface $repository; + protected ScaffoldProviderInterface $scaffold_provider; public function __construct( BaseManipulator $base_manipulator, NavigatorFactoryInterface $navigator_factory, - RepositoryInterface $repository + RepositoryInterface $repository, + ScaffoldProviderInterface $scaffold_provider ) { $this->base_manipulator = $base_manipulator; $this->navigator_factory = $navigator_factory; $this->repository = $repository; + $this->scaffold_provider = $scaffold_provider; } /** @@ -66,7 +70,7 @@ public function addScaffolds( if (!($element instanceof ScaffoldableInterface)) { continue; } - $element->addScaffoldsToSubElements($this->repository->scaffolds()); + $element->addScaffoldsToSubElements($this->scaffold_provider); $next = array_merge( $next, iterator_to_array($element->getSubElements()) @@ -102,7 +106,7 @@ public function prepareDelete(SetInterface $set, PathInterface $path): SetInterf public function execute(SetInterface $set): void { - $this->base_manipulator->execute($set); + $this->repository->manipulateMD($set); } /** diff --git a/components/ILIAS/MetaData/classes/Editor/Manipulator/ManipulatorInterface.php b/components/ILIAS/MetaData/classes/Editor/Manipulator/ManipulatorInterface.php index 0472a4a694a1..4be4cac2400e 100755 --- a/components/ILIAS/MetaData/classes/Editor/Manipulator/ManipulatorInterface.php +++ b/components/ILIAS/MetaData/classes/Editor/Manipulator/ManipulatorInterface.php @@ -26,6 +26,8 @@ interface ManipulatorInterface extends BaseManipulatorInterface { + public function execute(SetInterface $set): void; + public function addScaffolds( SetInterface $set, PathInterface $path diff --git a/components/ILIAS/MetaData/classes/Editor/Services/Services.php b/components/ILIAS/MetaData/classes/Editor/Services/Services.php index 87c6c4f85ebb..47f96c996a08 100755 --- a/components/ILIAS/MetaData/classes/Editor/Services/Services.php +++ b/components/ILIAS/MetaData/classes/Editor/Services/Services.php @@ -141,7 +141,8 @@ public function manipulator(): Manipulator return $this->manipulator = new Manipulator( $this->manipulator_services->manipulator(), $this->path_services->navigatorFactory(), - $this->repository_services->repository() + $this->repository_services->repository(), + $this->manipulator_services->scaffoldProvider() ); } diff --git a/components/ILIAS/MetaData/classes/Editor/class.ilMDEditorGUI.php b/components/ILIAS/MetaData/classes/Editor/class.ilMDEditorGUI.php index 0ffd45d72b72..eb82e2a38e2c 100755 --- a/components/ILIAS/MetaData/classes/Editor/class.ilMDEditorGUI.php +++ b/components/ILIAS/MetaData/classes/Editor/class.ilMDEditorGUI.php @@ -37,10 +37,10 @@ use ILIAS\MetaData\Editor\Full\Services\Tables\Table; use ILIAS\MetaData\Editor\Digest\DigestInitiator; use ILIAS\MetaData\Editor\Digest\Digest; +use ILIAS\MetaData\XML\Writer\WriterInterface as XMLWriter; /** * @author Stefan Meyer - * @ilCtrl_Calls ilMDEditorGUI: ilFormPropertyDispatchGUI */ class ilMDEditorGUI { @@ -62,6 +62,7 @@ class ilMDEditorGUI protected GlobalScreen $global_screen; protected ilTabsGUI $tabs; protected UIFactory $ui_factory; + protected XMLWriter $xml_writer; protected int $obj_id; protected int $sub_id; @@ -87,6 +88,7 @@ public function __construct(int $obj_id, int $sub_id, string $type) $this->global_screen = $services->dic()->globalScreen(); $this->tabs = $services->dic()->tabs(); $this->ui_factory = $services->dic()->ui()->factory(); + $this->xml_writer = $services->xml()->standardWriter(); $this->obj_id = $obj_id; $this->sub_id = $sub_id === 0 ? $obj_id : $sub_id; @@ -110,12 +112,13 @@ public function executeCommand(): void public function debug(): bool { - $xml_writer = new ilMD2XML($this->obj_id, $this->sub_id, $this->type); - $xml_writer->startExport(); - $button = $this->renderButtonToFullEditor(); - $this->tpl->setContent($button . htmlentities($xml_writer->getXML())); + $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->loadXML($xml->asXML()); + $this->tpl->setContent($button . '
' . htmlentities($dom->saveXML()) . '
'); return true; } diff --git a/components/ILIAS/MetaData/classes/Elements/Base/BaseElement.php b/components/ILIAS/MetaData/classes/Elements/Base/BaseElement.php index 644c2366fa97..684748073a4b 100755 --- a/components/ILIAS/MetaData/classes/Elements/Base/BaseElement.php +++ b/components/ILIAS/MetaData/classes/Elements/Base/BaseElement.php @@ -75,29 +75,28 @@ public function getSubElements(): \Generator yield from $this->sub_elements; } - protected function addSubElement( - BaseElement $sub_element, - string $insert_before = '' - ): void { + protected function addSubElement(BaseElement $sub_element): void + { $sub_element->setSuperElement($this); - if ($insert_before === '') { - $this->sub_elements[] = $sub_element; - return; - } + $this->sub_elements[] = $sub_element; + } - $new_subs = []; - $added = false; - foreach ($this->getSubElements() as $sub) { - if (!$added && $sub->getDefinition()->name() === $insert_before) { - $new_subs[] = $sub_element; - $added = true; - } - $new_subs[] = $sub; + protected function orderSubElements(string ...$names_in_order): void + { + $sub_elements_by_name = []; + foreach ($this->sub_elements as $sub_element) { + $sub_elements_by_name[$sub_element->getDefinition()->name()][] = $sub_element; } - if (!$added) { - $new_subs[] = $sub_element; + + $reordered_sub_elements = []; + foreach ($names_in_order as $name) { + $reordered_sub_elements = array_merge( + $reordered_sub_elements, + $sub_elements_by_name[$name] ?? [] + ); } - $this->sub_elements = $new_subs; + + $this->sub_elements = $reordered_sub_elements; } public function getSuperElement(): ?BaseElement diff --git a/components/ILIAS/MetaData/classes/Elements/Element.php b/components/ILIAS/MetaData/classes/Elements/Element.php index 57f32883da98..070c6d3e0cd5 100755 --- a/components/ILIAS/MetaData/classes/Elements/Element.php +++ b/components/ILIAS/MetaData/classes/Elements/Element.php @@ -29,7 +29,7 @@ use ILIAS\MetaData\Elements\Markers\MarkerInterface; use ILIAS\MetaData\Elements\Data\DataInterface; use ILIAS\MetaData\Elements\Markers\Action; -use ILIAS\MetaData\Repository\Utilities\ScaffoldProviderInterface; +use ILIAS\MetaData\Manipulator\ScaffoldProvider\ScaffoldProviderInterface; class Element extends BaseElement implements ElementInterface { @@ -112,6 +112,14 @@ public function mark( } } + public function unmark(): void + { + $this->setMarker(null); + foreach ($this->getSubElements() as $sub_element) { + $sub_element->unmark(); + } + } + protected function setMarker(?MarkerInterface $marker): void { $this->marker = $marker; @@ -120,11 +128,12 @@ protected function setMarker(?MarkerInterface $marker): void public function addScaffoldsToSubElements( ScaffoldProviderInterface $scaffold_provider ): void { - foreach ($scaffold_provider->getScaffoldsForElement($this) as $insert_before => $scaffold) { + foreach ($scaffold_provider->getScaffoldsForElement($this) as $scaffold) { if ($scaffold->getSubElements()->current() !== null) { throw new \ilMDElementsException('Can only add scaffolds with no sub-elements.'); } - $this->addSubElement($scaffold, $insert_before); + $this->addSubElement($scaffold); + $this->orderSubElements(...$scaffold_provider->getPossibleSubElementNamesForElementInOrder($this)); } } @@ -132,12 +141,13 @@ public function addScaffoldToSubElements( ScaffoldProviderInterface $scaffold_provider, string $name ): ?ElementInterface { - foreach ($scaffold_provider->getScaffoldsForElement($this) as $insert_before => $scaffold) { + foreach ($scaffold_provider->getScaffoldsForElement($this) as $scaffold) { if (strtolower($scaffold->getDefinition()->name()) === strtolower($name)) { if ($scaffold->getSubElements()->current() !== null) { throw new \ilMDElementsException('Can only add scaffolds with no sub-elements.'); } - $this->addSubElement($scaffold, $insert_before); + $this->addSubElement($scaffold); + $this->orderSubElements(...$scaffold_provider->getPossibleSubElementNamesForElementInOrder($this)); return $scaffold; } } diff --git a/components/ILIAS/MetaData/classes/Elements/Markers/MarkableInterface.php b/components/ILIAS/MetaData/classes/Elements/Markers/MarkableInterface.php index 7bec3bc55667..4ed2ab77bd43 100755 --- a/components/ILIAS/MetaData/classes/Elements/Markers/MarkableInterface.php +++ b/components/ILIAS/MetaData/classes/Elements/Markers/MarkableInterface.php @@ -43,5 +43,10 @@ public function mark( MarkerFactoryInterface $factory, Action $action, string $data_value = '' - ); + ): void; + + /** + * Removes markers from this element, and recursively from all sub-elements. + */ + public function unmark(): void; } diff --git a/components/ILIAS/MetaData/classes/Elements/NullElement.php b/components/ILIAS/MetaData/classes/Elements/NullElement.php index 0ada722a492d..f2bf73447dbf 100755 --- a/components/ILIAS/MetaData/classes/Elements/NullElement.php +++ b/components/ILIAS/MetaData/classes/Elements/NullElement.php @@ -27,7 +27,7 @@ use ILIAS\MetaData\Elements\Markers\MarkerFactoryInterface; use ILIAS\MetaData\Elements\Markers\MarkerInterface; use ILIAS\MetaData\Elements\Markers\NullMarker; -use ILIAS\MetaData\Repository\Utilities\ScaffoldProviderInterface; +use ILIAS\MetaData\Manipulator\ScaffoldProvider\ScaffoldProviderInterface; class NullElement extends NullBaseElement implements ElementInterface { @@ -51,7 +51,11 @@ public function getMarker(): ?MarkerInterface return new NullMarker(); } - public function mark(MarkerFactoryInterface $factory, Action $action, string $data_value = '') + public function mark(MarkerFactoryInterface $factory, Action $action, string $data_value = ''): void + { + } + + public function unmark(): void { } diff --git a/components/ILIAS/MetaData/classes/Elements/RessourceID/NullRessourceIDFactory.php b/components/ILIAS/MetaData/classes/Elements/RessourceID/NullRessourceIDFactory.php new file mode 100644 index 000000000000..b5b7cd0d793b --- /dev/null +++ b/components/ILIAS/MetaData/classes/Elements/RessourceID/NullRessourceIDFactory.php @@ -0,0 +1,37 @@ +data_factory = $data_factory; + $this->ressource_id_factory = $ressource_id_factory; } public function scaffold(DefinitionInterface $definition): ElementInterface @@ -43,4 +51,16 @@ public function scaffold(DefinitionInterface $definition): ElementInterface $this->data_factory->null() ); } + + public function set(DefinitionInterface $root_definition): SetInterface + { + return new Set( + $this->ressource_id_factory->null(), + new Element( + NoID::ROOT, + $root_definition, + $this->data_factory->null() + ) + ); + } } diff --git a/components/ILIAS/MetaData/classes/Elements/Scaffolds/ScaffoldFactoryInterface.php b/components/ILIAS/MetaData/classes/Elements/Scaffolds/ScaffoldFactoryInterface.php new file mode 100644 index 000000000000..32fd706551e8 --- /dev/null +++ b/components/ILIAS/MetaData/classes/Elements/Scaffolds/ScaffoldFactoryInterface.php @@ -0,0 +1,32 @@ +repository = $repository; + $this->scaffold_provider = $scaffold_provider; $this->marker_factory = $marker_factory; $this->navigator_factory = $navigator_factory; $this->path_factory = $path_factory; @@ -84,11 +82,6 @@ public function prepareDelete( return $my_set; } - public function execute(SetInterface $set): void - { - $this->repository->manipulateMD($set); - } - /** * @throws ilMDPathException */ @@ -411,7 +404,7 @@ protected function addAndMarkScaffoldByStep( return $element->getSuperElement(); } $scaffold = $element->addScaffoldToSubElements( - $this->repository->scaffolds(), + $this->scaffold_provider, $step->name() ); if (!isset($scaffold)) { diff --git a/components/ILIAS/MetaData/classes/Manipulator/ManipulatorInterface.php b/components/ILIAS/MetaData/classes/Manipulator/ManipulatorInterface.php index afe769dca930..00ebb25714eb 100755 --- a/components/ILIAS/MetaData/classes/Manipulator/ManipulatorInterface.php +++ b/components/ILIAS/MetaData/classes/Manipulator/ManipulatorInterface.php @@ -47,6 +47,4 @@ public function prepareDelete( SetInterface $set, PathInterface $path ): SetInterface; - - public function execute(SetInterface $set): void; } diff --git a/components/ILIAS/MetaData/classes/Manipulator/NullManipulator.php b/components/ILIAS/MetaData/classes/Manipulator/NullManipulator.php index 25d42e3df7c5..18014c38985c 100755 --- a/components/ILIAS/MetaData/classes/Manipulator/NullManipulator.php +++ b/components/ILIAS/MetaData/classes/Manipulator/NullManipulator.php @@ -45,10 +45,6 @@ public function prepareDelete( return new NullSet(); } - public function execute(SetInterface $set): void - { - } - public function prepareCreateOrUpdate( SetInterface $set, PathInterface $path, diff --git a/components/ILIAS/MetaData/classes/Manipulator/Path/NullPathUtilitiesFactory.php b/components/ILIAS/MetaData/classes/Manipulator/Path/NullPathUtilitiesFactory.php index 08e1fde0b08f..af8d6fcf36d6 100755 --- a/components/ILIAS/MetaData/classes/Manipulator/Path/NullPathUtilitiesFactory.php +++ b/components/ILIAS/MetaData/classes/Manipulator/Path/NullPathUtilitiesFactory.php @@ -33,9 +33,4 @@ public function pathConditionsCollection(PathInterface $path): PathConditionsCol { return new NullPathConditionsCollection(); } - - public function navigatorManager(): NavigatorManagerInterface - { - return new NullNavigatorManager(); - } } diff --git a/components/ILIAS/MetaData/classes/Repository/Utilities/NullScaffoldProvider.php b/components/ILIAS/MetaData/classes/Manipulator/ScaffoldProvider/NullScaffoldProvider.php similarity index 68% rename from components/ILIAS/MetaData/classes/Repository/Utilities/NullScaffoldProvider.php rename to components/ILIAS/MetaData/classes/Manipulator/ScaffoldProvider/NullScaffoldProvider.php index db8c357271ea..00a9e2025bc7 100755 --- a/components/ILIAS/MetaData/classes/Repository/Utilities/NullScaffoldProvider.php +++ b/components/ILIAS/MetaData/classes/Manipulator/ScaffoldProvider/NullScaffoldProvider.php @@ -18,9 +18,11 @@ declare(strict_types=1); -namespace ILIAS\MetaData\Repository\Utilities; +namespace ILIAS\MetaData\Manipulator\ScaffoldProvider; use ILIAS\MetaData\Elements\ElementInterface; +use ILIAS\MetaData\Elements\SetInterface; +use ILIAS\MetaData\Elements\NullSet; class NullScaffoldProvider implements ScaffoldProviderInterface { @@ -28,4 +30,14 @@ public function getScaffoldsForElement(ElementInterface $element): \Generator { yield from []; } + + public function getPossibleSubElementNamesForElementInOrder(ElementInterface $element): \Generator + { + yield from []; + } + + public function set(): SetInterface + { + return new NullSet(); + } } diff --git a/components/ILIAS/MetaData/classes/Repository/Utilities/ScaffoldProvider.php b/components/ILIAS/MetaData/classes/Manipulator/ScaffoldProvider/ScaffoldProvider.php similarity index 63% rename from components/ILIAS/MetaData/classes/Repository/Utilities/ScaffoldProvider.php rename to components/ILIAS/MetaData/classes/Manipulator/ScaffoldProvider/ScaffoldProvider.php index 2fed6abaf67a..376c65939956 100755 --- a/components/ILIAS/MetaData/classes/Repository/Utilities/ScaffoldProvider.php +++ b/components/ILIAS/MetaData/classes/Manipulator/ScaffoldProvider/ScaffoldProvider.php @@ -18,23 +18,24 @@ declare(strict_types=1); -namespace ILIAS\MetaData\Repository\Utilities; +namespace ILIAS\MetaData\Manipulator\ScaffoldProvider; use ILIAS\MetaData\Elements\ElementInterface; use ILIAS\MetaData\Paths\FactoryInterface as PathFactoryInterface; use ILIAS\MetaData\Paths\Navigator\NavigatorFactoryInterface; use ILIAS\MetaData\Elements\Structure\StructureSetInterface; -use ILIAS\MetaData\Elements\Scaffolds\ScaffoldFactory; +use ILIAS\MetaData\Elements\Scaffolds\ScaffoldFactoryInterface; +use ILIAS\MetaData\Elements\SetInterface; class ScaffoldProvider implements ScaffoldProviderInterface { - protected ScaffoldFactory $scaffold_factory; + protected ScaffoldFactoryInterface $scaffold_factory; protected PathFactoryInterface $path_factory; protected NavigatorFactoryInterface $navigator_factory; protected StructureSetInterface $structure; public function __construct( - ScaffoldFactory $scaffold_factory, + ScaffoldFactoryInterface $scaffold_factory, PathFactoryInterface $path_factory, NavigatorFactoryInterface $navigator_factory, StructureSetInterface $structure, @@ -51,33 +52,48 @@ public function __construct( public function getScaffoldsForElement( ElementInterface $element ): \Generator { - $navigator = $this->navigator_factory->structureNavigator( - $this->path_factory->toElement($element), - $this->structure->getRoot() - ); - $structure_element = $navigator->elementAtFinalStep(); - $sub_names = []; foreach ($element->getSubElements() as $sub) { $sub_names[] = $sub->getDefinition()->name(); } - $previous_sub = null; - foreach ($structure_element->getSubElements() as $sub) { - $sub = $sub->getDefinition(); + foreach ($this->getPossibleSubElementDefinitionsForElementInOrder($element) as $sub_definition) { if ( - isset($previous_sub) && - (!$previous_sub->unique() || !in_array($previous_sub->name(), $sub_names)) + !$sub_definition->unique() || + !in_array($sub_definition->name(), $sub_names) ) { - yield $sub->name() => $this->scaffold_factory->scaffold($previous_sub); + yield $this->scaffold_factory->scaffold($sub_definition); } - $previous_sub = $sub; } - if ( - isset($previous_sub) && - (!$previous_sub->unique() || !in_array($previous_sub->name(), $sub_names)) - ) { - yield '' => $this->scaffold_factory->scaffold($previous_sub); + } + + /** + * @return string[] + */ + public function getPossibleSubElementNamesForElementInOrder( + ElementInterface $element + ): \Generator { + foreach ($this->getPossibleSubElementDefinitionsForElementInOrder($element) as $sub_definition) { + yield $sub_definition->name(); } } + + protected function getPossibleSubElementDefinitionsForElementInOrder( + ElementInterface $element + ): \Generator { + $navigator = $this->navigator_factory->structureNavigator( + $this->path_factory->toElement($element), + $this->structure->getRoot() + ); + $structure_element = $navigator->elementAtFinalStep(); + + foreach ($structure_element->getSubElements() as $sub) { + yield $sub->getDefinition(); + } + } + + public function set(): SetInterface + { + return $this->scaffold_factory->set($this->structure->getRoot()->getDefinition()); + } } diff --git a/components/ILIAS/MetaData/classes/Repository/Utilities/ScaffoldProviderInterface.php b/components/ILIAS/MetaData/classes/Manipulator/ScaffoldProvider/ScaffoldProviderInterface.php similarity index 62% rename from components/ILIAS/MetaData/classes/Repository/Utilities/ScaffoldProviderInterface.php rename to components/ILIAS/MetaData/classes/Manipulator/ScaffoldProvider/ScaffoldProviderInterface.php index 27fcad5d7664..926c77b45dd0 100755 --- a/components/ILIAS/MetaData/classes/Repository/Utilities/ScaffoldProviderInterface.php +++ b/components/ILIAS/MetaData/classes/Manipulator/ScaffoldProvider/ScaffoldProviderInterface.php @@ -18,9 +18,10 @@ declare(strict_types=1); -namespace ILIAS\MetaData\Repository\Utilities; +namespace ILIAS\MetaData\Manipulator\ScaffoldProvider; use ILIAS\MetaData\Elements\ElementInterface; +use ILIAS\MetaData\Elements\SetInterface; interface ScaffoldProviderInterface { @@ -34,4 +35,20 @@ interface ScaffoldProviderInterface public function getScaffoldsForElement( ElementInterface $element ): \Generator; + + /** + * Returns the names of all possible sub-elements for the + * given element in the order defined by the structure. + * This is needed to bring order the sub-elements of an element + * in the right order after e.g. scaffolds were added. + * @return string[] + */ + public function getPossibleSubElementNamesForElementInOrder( + ElementInterface $element + ): \Generator; + + /** + * Returns an empty LOM set, containing only the root element. + */ + public function set(): SetInterface; } diff --git a/components/ILIAS/MetaData/classes/Manipulator/Services/Services.php b/components/ILIAS/MetaData/classes/Manipulator/Services/Services.php index 3c9cfc98a93c..474621d424fc 100755 --- a/components/ILIAS/MetaData/classes/Manipulator/Services/Services.php +++ b/components/ILIAS/MetaData/classes/Manipulator/Services/Services.php @@ -21,24 +21,31 @@ namespace ILIAS\MetaData\Manipulator\Services; use ILIAS\MetaData\Paths\Services\Services as PathServices; -use ILIAS\MetaData\Repository\Services\Services as RepositoryServices; +use ILIAS\MetaData\Structure\Services\Services as StructureServices; use ILIAS\MetaData\Manipulator\Path\PathUtilitiesFactory; use ILIAS\MetaData\Elements\Markers\MarkerFactory; use ILIAS\MetaData\Manipulator\ManipulatorInterface; use ILIAS\MetaData\Manipulator\Manipulator; +use ILIAS\MetaData\Manipulator\ScaffoldProvider\ScaffoldProviderInterface; +use ILIAS\MetaData\Manipulator\ScaffoldProvider\ScaffoldProvider; +use ILIAS\MetaData\Elements\Scaffolds\ScaffoldFactory; +use ILIAS\MetaData\Elements\Data\DataFactory; +use ILIAS\MetaData\Elements\RessourceID\RessourceIDFactory; class Services { protected ManipulatorInterface $manipulator; + protected ScaffoldProviderInterface $scaffold_provider; + protected PathServices $path_services; - protected RepositoryServices $repository_services; + protected StructureServices $structure_services; public function __construct( PathServices $path_services, - RepositoryServices $repository_services + StructureServices $structure_services ) { $this->path_services = $path_services; - $this->repository_services = $repository_services; + $this->structure_services = $structure_services; } public function manipulator(): ManipulatorInterface @@ -47,7 +54,7 @@ public function manipulator(): ManipulatorInterface return $this->manipulator; } return $this->manipulator = new Manipulator( - $this->repository_services->repository(), + $this->scaffoldProvider(), new MarkerFactory(), $this->path_services->navigatorFactory(), $this->path_services->pathFactory(), @@ -56,4 +63,20 @@ public function manipulator(): ManipulatorInterface ) ); } + + public function scaffoldProvider(): ScaffoldProviderInterface + { + if (isset($this->scaffold_provider)) { + return $this->scaffold_provider; + } + return $this->scaffold_provider = new ScaffoldProvider( + new ScaffoldFactory( + new DataFactory(), + new RessourceIDFactory() + ), + $this->path_services->pathFactory(), + $this->path_services->navigatorFactory(), + $this->structure_services->structure() + ); + } } diff --git a/components/ILIAS/MetaData/classes/Paths/Builder.php b/components/ILIAS/MetaData/classes/Paths/Builder.php index f63c7dda5af7..5a650062e1c6 100755 --- a/components/ILIAS/MetaData/classes/Paths/Builder.php +++ b/components/ILIAS/MetaData/classes/Paths/Builder.php @@ -100,6 +100,9 @@ public function withNextStepFromStep( return $builder; } + /** + * @throws \ilMDPathException + */ public function withAdditionalFilterAtCurrentStep( FilterType $type, string ...$values @@ -123,7 +126,7 @@ public function withAdditionalFilterAtCurrentStep( public function get(): PathInterface { $clone = $this->withCurrentStepSaved(); - $path = new Path( + $path = new Path( $clone->is_relative, $clone->leads_to_one, ...$clone->steps diff --git a/components/ILIAS/MetaData/classes/Paths/Navigator/NavigatorFactoryInterface.php b/components/ILIAS/MetaData/classes/Paths/Navigator/NavigatorFactoryInterface.php index 4201bc5a12c7..d6ac932dc31c 100755 --- a/components/ILIAS/MetaData/classes/Paths/Navigator/NavigatorFactoryInterface.php +++ b/components/ILIAS/MetaData/classes/Paths/Navigator/NavigatorFactoryInterface.php @@ -41,7 +41,7 @@ public function navigator( * Used to navigate on a metadata set structure along the given path. * If the path is relative, navigation starts at the given * element, otherwise it starts at the root of the set the - * element is in. + * element is in. Path filters are ignored during navigation. */ public function structureNavigator( PathInterface $path, diff --git a/components/ILIAS/MetaData/classes/Paths/Steps/NavigatorBridge.php b/components/ILIAS/MetaData/classes/Paths/Steps/NavigatorBridge.php index d1bf0d7c1b43..f510f00f4fd0 100755 --- a/components/ILIAS/MetaData/classes/Paths/Steps/NavigatorBridge.php +++ b/components/ILIAS/MetaData/classes/Paths/Steps/NavigatorBridge.php @@ -26,6 +26,7 @@ use ILIAS\MetaData\Elements\Base\BaseElementInterface; use ILIAS\MetaData\Elements\Markers\MarkableInterface; use ILIAS\MetaData\Elements\Structure\StructureElement; +use ILIAS\MetaData\Elements\Structure\StructureElementInterface; class NavigatorBridge { @@ -117,11 +118,12 @@ protected function filterByMDID( BaseElementInterface ...$elements ): \Generator { foreach ($elements as $element) { - $id = $element->getMDID(); - $id = is_int($id) ? (string) $id : $id->value; - if ($element instanceof StructureElement) { + if ($element instanceof StructureElementInterface) { yield $element; + continue; } + $id = $element->getMDID(); + $id = is_int($id) ? (string) $id : $id->value; if (in_array($id, iterator_to_array($filter->values()), true)) { yield $element; } @@ -148,7 +150,8 @@ protected function filterByIndex( foreach ($elements as $element) { if ( in_array($index, $filter_values, true) || - ($select_last && array_key_last($elements) === $index) + ($select_last && array_key_last($elements) === $index) || + $element instanceof StructureElementInterface ) { yield $element; } @@ -165,6 +168,7 @@ protected function filterByData( ): \Generator { foreach ($elements as $element) { if (!($element instanceof ElementInterface)) { + yield $element; continue; } $data = $element->getData()->value(); diff --git a/components/ILIAS/MetaData/classes/Repository/Dictionary/NullTag.php b/components/ILIAS/MetaData/classes/Repository/Dictionary/NullTag.php new file mode 100644 index 000000000000..46c6a481c752 --- /dev/null +++ b/components/ILIAS/MetaData/classes/Repository/Dictionary/NullTag.php @@ -0,0 +1,56 @@ +manipulator = $manipulator; + $this->path_factory = $path_factory; + } + + public function prepareUpdateOfIdentifier( + SetInterface $set, + RessourceIDInterface $ressource_id + ): SetInterface { + $set = $this->manipulator->prepareCreateOrUpdate( + $set, + $this->getPathToFirstIdentifierEntry(), + $this->generateIdentifierEntry($ressource_id) + ); + $set = $this->manipulator->prepareCreateOrUpdate( + $set, + $this->getPathToFirstIdentifierCatalog(), + $this->generateIdentifierCatalog() + ); + return $set; + } + + protected function generateIdentifierEntry(RessourceIDInterface $ressource_id): string + { + $numeric_id = $ressource_id->subID() !== 0 ? + $ressource_id->subID() : + $ressource_id->objID(); + + return 'il_' . $this->getInstallID() . '_' . $ressource_id->type() . '_' . $numeric_id; + } + + protected function generateIdentifierCatalog(): string + { + return 'ILIAS'; + } + + protected function getPathToFirstIdentifierEntry(): PathInterface + { + return $this->path_factory + ->custom() + ->withNextStep('general') + ->withNextStep('identifier') + ->withAdditionalFilterAtCurrentStep(FilterType::INDEX, '0') + ->withNextStep('entry') + ->get(); + } + + protected function getPathToFirstIdentifierCatalog(): PathInterface + { + return $this->path_factory + ->custom() + ->withNextStep('general') + ->withNextStep('identifier') + ->withAdditionalFilterAtCurrentStep(FilterType::INDEX, '0') + ->withNextStep('catalog') + ->get(); + } + + protected function getInstallID(): string + { + return (string) IL_INST_ID; + } +} diff --git a/components/ILIAS/MetaData/classes/Repository/IdentifierHandler/IdentifierHandlerInterface.php b/components/ILIAS/MetaData/classes/Repository/IdentifierHandler/IdentifierHandlerInterface.php new file mode 100644 index 000000000000..4d8bbc7e96e9 --- /dev/null +++ b/components/ILIAS/MetaData/classes/Repository/IdentifierHandler/IdentifierHandlerInterface.php @@ -0,0 +1,32 @@ +ressource_factory = $ressource_factory; - $this->scaffold_provider = $scaffold_provider; $this->manipulator = $manipulator; $this->reader = $reader; + $this->searcher = $searcher; $this->cleaner = $cleaner; + $this->identifier_handler = $identifier_handler; } public function getMD( @@ -79,11 +84,15 @@ public function getMDOnPath( } /** - * @return ElementInterface[] + * @return RessourceIDInterface[] */ - public function scaffolds(): ScaffoldProviderInterface - { - return $this->scaffold_provider; + public function searchMD( + ClauseInterface $clause, + ?int $limit, + ?int $offset, + FilterInterface ...$filters + ): \Generator { + yield from $this->searcher->search($clause, $limit, $offset, ...$filters); } public function manipulateMD(SetInterface $set): void @@ -92,6 +101,25 @@ public function manipulateMD(SetInterface $set): void $this->manipulator->manipulateMD($set); } + public function transferMD( + SetInterface $from_set, + int $to_obj_id, + int $to_sub_id, + string $to_type, + bool $throw_error_if_invalid + ): void { + $to_ressource_id = $this->ressource_factory->ressourceID($to_obj_id, $to_sub_id, $to_type); + + if ($throw_error_if_invalid) { + $this->cleaner->checkMarkers($from_set); + } else { + $this->cleaner->cleanMarkers($from_set); + } + $from_set = $this->identifier_handler->prepareUpdateOfIdentifier($from_set, $to_ressource_id); + $this->manipulator->deleteAllMD($to_ressource_id); + $this->manipulator->transferMD($from_set, $to_ressource_id); + } + public function deleteAllMD( int $obj_id, int $sub_id, diff --git a/components/ILIAS/MetaData/classes/Repository/NullRepository.php b/components/ILIAS/MetaData/classes/Repository/NullRepository.php index 7b531dca1aed..ab6318f80144 100755 --- a/components/ILIAS/MetaData/classes/Repository/NullRepository.php +++ b/components/ILIAS/MetaData/classes/Repository/NullRepository.php @@ -23,8 +23,9 @@ use ILIAS\MetaData\Elements\NullSet; use ILIAS\MetaData\Elements\SetInterface; use ILIAS\MetaData\Paths\PathInterface; -use ILIAS\MetaData\Repository\Utilities\NullScaffoldProvider; -use ILIAS\MetaData\Repository\Utilities\ScaffoldProviderInterface; +use ILIAS\MetaData\Elements\RessourceID\RessourceIDInterface; +use ILIAS\MetaData\Repository\Search\Clauses\ClauseInterface; +use ILIAS\MetaData\Repository\Search\Filters\FilterInterface; class NullRepository implements RepositoryInterface { @@ -38,15 +39,31 @@ public function getMDOnPath(PathInterface $path, int $obj_id, int $sub_id, strin return new NullSet(); } - public function scaffolds(): ScaffoldProviderInterface - { - return new NullScaffoldProvider(); + /** + * @return RessourceIDInterface[] + */ + public function searchMD( + ClauseInterface $clause, + ?int $limit, + ?int $offset, + FilterInterface ...$filters + ): \Generator { + yield from []; } public function manipulateMD(SetInterface $set): void { } + public function transferMD( + SetInterface $from_set, + int $to_obj_id, + int $to_sub_id, + string $to_type, + bool $throw_error_if_invalid + ): void { + } + public function deleteAllMD(int $obj_id, int $sub_id, string $type): void { } diff --git a/components/ILIAS/MetaData/classes/Repository/RepositoryInterface.php b/components/ILIAS/MetaData/classes/Repository/RepositoryInterface.php index da8e13b6c757..c6a60dedf785 100755 --- a/components/ILIAS/MetaData/classes/Repository/RepositoryInterface.php +++ b/components/ILIAS/MetaData/classes/Repository/RepositoryInterface.php @@ -20,10 +20,11 @@ namespace ILIAS\MetaData\Repository; -use ILIAS\MetaData\Elements\ElementInterface; use ILIAS\MetaData\Elements\SetInterface; use ILIAS\MetaData\Paths\PathInterface; -use ILIAS\MetaData\Repository\Utilities\ScaffoldProviderInterface; +use ILIAS\MetaData\Elements\RessourceID\RessourceIDInterface; +use ILIAS\MetaData\Repository\Search\Clauses\ClauseInterface; +use ILIAS\MetaData\Repository\Search\Filters\FilterInterface; interface RepositoryInterface { @@ -48,7 +49,9 @@ public function getMD( /** * Returns an MD set with only the elements specified on a path, and all nested * subelements of the last elements on the path. - * The path must start from the root element. + * The path must start from the root element. Note that path filters are ignored, + * and if the path contains steps to super elements, it is only followed down to + * the first element that the path returns to. * Note that resulting partial MD sets might not be completely valid, due to * conditions between elements. Be careful when dealing with vocabularies, or * Technical > Requirement > OrComposite. @@ -60,7 +63,18 @@ public function getMDOnPath( string $type ): SetInterface; - public function scaffolds(): ScaffoldProviderInterface; + /** + * Results are always ordered first by obj_id, then sub_id, then type. + * Multiple filters are joined with a logical OR, values within the + * same filter with AND. + * @return RessourceIDInterface[] + */ + public function searchMD( + ClauseInterface $clause, + ?int $limit, + ?int $offset, + FilterInterface ...$filters + ): \Generator; /** * Follows a trail of markers from the root element, @@ -72,6 +86,25 @@ public function scaffolds(): ScaffoldProviderInterface; */ public function manipulateMD(SetInterface $set): void; + /** + * Transfers a metadata set to an object, regardless of its source. Takes + * The data from 'create or update' markers takes priority over the data + * carried by marked elements, but 'delete' markers and unmarked or neutrally + * marked scaffolds are ignored. + * Always deletes whatever metadata already exist at the target. + * + * If $throw_error_if_invalid is set true, an error is thrown if the + * markers on the $from_set are invalid, otherwise the invalid markers + * are replaced by neutral markers. + */ + public function transferMD( + SetInterface $from_set, + int $to_obj_id, + int $to_sub_id, + string $to_type, + bool $throw_error_if_invalid + ): void; + public function deleteAllMD( int $obj_id, int $sub_id, diff --git a/components/ILIAS/MetaData/classes/Repository/Search/Clauses/Clause.php b/components/ILIAS/MetaData/classes/Repository/Search/Clauses/Clause.php new file mode 100644 index 000000000000..27b8ecaea6f4 --- /dev/null +++ b/components/ILIAS/MetaData/classes/Repository/Search/Clauses/Clause.php @@ -0,0 +1,64 @@ +negated = $negated; + $this->join = $join; + $this->join_properties = $join_properties; + $this->basic_properties = $basic_properties; + } + + public function isNegated(): bool + { + return $this->negated; + } + + public function isJoin(): bool + { + return $this->join; + } + + public function joinProperties(): ?JoinPropertiesInterface + { + return $this->join_properties; + } + + public function basicProperties(): ?BasicPropertiesInterface + { + return $this->basic_properties; + } +} diff --git a/components/ILIAS/MetaData/classes/Repository/Search/Clauses/ClauseInterface.php b/components/ILIAS/MetaData/classes/Repository/Search/Clauses/ClauseInterface.php new file mode 100644 index 000000000000..e6a73e9445f8 --- /dev/null +++ b/components/ILIAS/MetaData/classes/Repository/Search/Clauses/ClauseInterface.php @@ -0,0 +1,35 @@ +steps())) === 0) { + throw new \ilMDRepositoryException('Paths in search clauses must not be empty.'); + } + + return new Clause( + false, + false, + null, + new BasicProperties( + $path, + $mode, + $value, + $is_mode_negated + ) + ); + } + + public function getJoinedClauses( + Operator $operator, + ClauseInterface $first_clause, + ClauseInterface $second_clause, + ClauseInterface ...$further_clauses + ): ClauseInterface { + return new Clause( + false, + true, + new JoinProperties( + $operator, + $first_clause, + $second_clause, + ...$further_clauses + ), + null + ); + } + + public function getNegatedClause(ClauseInterface $clause): ClauseInterface + { + return new Clause( + !$clause->isNegated(), + $clause->isJoin(), + $clause->joinProperties(), + $clause->basicProperties() + ); + } +} diff --git a/components/ILIAS/MetaData/classes/Repository/Search/Clauses/FactoryInterface.php b/components/ILIAS/MetaData/classes/Repository/Search/Clauses/FactoryInterface.php new file mode 100644 index 000000000000..eaf09199e560 --- /dev/null +++ b/components/ILIAS/MetaData/classes/Repository/Search/Clauses/FactoryInterface.php @@ -0,0 +1,71 @@ +path = $path; + $this->mode = $mode; + $this->value = $value; + $this->is_mode_negated = $is_mode_negated; + } + + public function path(): PathInterface + { + return $this->path; + } + + public function mode(): Mode + { + return $this->mode; + } + + public function isModeNegated(): bool + { + return $this->is_mode_negated; + } + + public function value(): string + { + return $this->value; + } +} diff --git a/components/ILIAS/MetaData/classes/Repository/Search/Clauses/Properties/BasicPropertiesInterface.php b/components/ILIAS/MetaData/classes/Repository/Search/Clauses/Properties/BasicPropertiesInterface.php new file mode 100644 index 000000000000..fcec6db08a48 --- /dev/null +++ b/components/ILIAS/MetaData/classes/Repository/Search/Clauses/Properties/BasicPropertiesInterface.php @@ -0,0 +1,35 @@ +operator = $operator; + $this->sub_clauses = [ + $first_clause, + $second_clause, + ...$further_clauses + ]; + } + + public function operator(): Operator + { + return $this->operator; + } + + /** + * @return ClauseInterface[] + */ + public function subClauses(): \Generator + { + yield from $this->sub_clauses; + } +} diff --git a/components/ILIAS/MetaData/classes/Repository/Search/Clauses/Properties/JoinPropertiesInterface.php b/components/ILIAS/MetaData/classes/Repository/Search/Clauses/Properties/JoinPropertiesInterface.php new file mode 100644 index 000000000000..73aa26c3ba59 --- /dev/null +++ b/components/ILIAS/MetaData/classes/Repository/Search/Clauses/Properties/JoinPropertiesInterface.php @@ -0,0 +1,34 @@ +obj_id = $obj_id; + $this->sub_id = $sub_id; + $this->type = $type; + } + + public function objID(): int|Placeholder + { + return $this->obj_id; + } + + public function subID(): int|Placeholder + { + return $this->sub_id; + } + + public function type(): string|Placeholder + { + return $this->type; + } +} diff --git a/components/ILIAS/MetaData/classes/Repository/Search/Filters/FilterInterface.php b/components/ILIAS/MetaData/classes/Repository/Search/Filters/FilterInterface.php new file mode 100644 index 000000000000..484e923af60a --- /dev/null +++ b/components/ILIAS/MetaData/classes/Repository/Search/Filters/FilterInterface.php @@ -0,0 +1,30 @@ +dic = $dic; $this->path_services = $path_services; $this->structure_services = $structure_services; $this->vocabularies_services = $vocabularies_services; $this->data_helper_services = $data_helper_services; + $this->manipulator_services = $manipulator_services; } public function constraintDictionary(): ValidationDictionary @@ -114,13 +126,7 @@ public function repository(): RepositoryInterface ); $element_factory = new ElementFactory($data_factory); return $this->repository = new LOMDatabaseRepository( - new RessourceIDFactory(), - new ScaffoldProvider( - new ScaffoldFactory($data_factory), - $this->path_services->pathFactory(), - $this->path_services->navigatorFactory(), - $this->structure_services->structure() - ), + $ressource_id_factory = new RessourceIDFactory(), new DatabaseManipulator( $this->databaseDictionary(), $querier, @@ -132,11 +138,23 @@ public function repository(): RepositoryInterface $this->structure_services->structure(), $this->databaseDictionary(), $this->path_services->navigatorFactory(), + $this->path_services->pathFactory(), $querier, $logger ), + new DatabaseSearcher( + $ressource_id_factory, + new DatabasePathsParserFactory( + $this->dic->database(), + $this->structure_services->structure(), + $this->databaseDictionary(), + $this->path_services->navigatorFactory() + ), + $this->dic->database() + ), new Cleaner( $element_factory, + new MarkerFactory(), $this->structure_services->structure(), new DataValidator( new DataValidatorService( @@ -146,7 +164,27 @@ public function repository(): RepositoryInterface ), $this->constraintDictionary(), $logger + ), + new IdentifierHandler( + $this->manipulator_services->manipulator(), + $this->path_services->pathFactory() ) ); } + + public function SearchClauseFactory(): ClauseFactoryInterface + { + if (isset($this->search_clause_factory)) { + return $this->search_clause_factory; + } + return $this->search_clause_factory = new ClauseFactory(); + } + + public function SearchFilterFactory(): FilterFactoryInterface + { + if (isset($this->search_filter_factory)) { + return $this->search_filter_factory; + } + return $this->search_filter_factory = new FilterFactory(); + } } diff --git a/components/ILIAS/MetaData/classes/Repository/Utilities/DatabaseManipulator.php b/components/ILIAS/MetaData/classes/Repository/Utilities/DatabaseManipulator.php index 89e75cf57f35..8e7a41de7071 100755 --- a/components/ILIAS/MetaData/classes/Repository/Utilities/DatabaseManipulator.php +++ b/components/ILIAS/MetaData/classes/Repository/Utilities/DatabaseManipulator.php @@ -32,6 +32,7 @@ use ILIAS\MetaData\Repository\Utilities\Queries\Assignments\AssignmentFactoryInterface; use ILIAS\MetaData\Repository\Utilities\Queries\Assignments\AssignmentRowInterface; use ILIAS\MetaData\Repository\Utilities\Queries\Assignments\Action; +use ILIAS\MetaData\Elements\NoID; class DatabaseManipulator implements DatabaseManipulatorInterface { @@ -61,19 +62,10 @@ public function manipulateMD( SetInterface $set ): void { foreach ($set->getRoot()->getSubElements() as $sub) { - /** - * Note that the following is necessary here, since the function needs - * to run through fully before using the yielded rows, as the rows are - * filled after being yielded. - */ - $rows = []; - foreach ($this->collectAssignmentsFromElementAndSubElements( + foreach ($this->collectRowsForManipulationFromElementAndSubElements( 0, $sub ) as $row) { - $rows[] = $row; - } - foreach ($rows as $row) { $this->querier->manipulate( $set->getRessourceID(), $row @@ -82,31 +74,45 @@ public function manipulateMD( } } + public function transferMD(SetInterface $from_set, RessourceIDInterface $to_ressource_id): void + { + foreach ($from_set->getRoot()->getSubElements() as $sub) { + foreach ($this->collectRowsForTransferFromElementAndSubElements( + 0, + $sub + ) as $row) { + $this->querier->manipulate( + $to_ressource_id, + $row + ); + } + } + } + /** * @return AssignmentRowInterface[] */ - protected function collectAssignmentsFromElementAndSubElements( + protected function collectRowsForManipulationFromElementAndSubElements( int $depth, ElementInterface $element, AssignmentRowInterface $current_row = null, bool $delete_all = false - ): \Generator { + ): array { if ($depth > 20) { throw new \ilMDStructureException('LOM Structure is nested to deep.'); } + + $collected_rows = []; + $marker = $this->marker($element); if (!isset($marker) && !$delete_all) { - return; + return []; } - $id = $element->getMDID(); + $tag = $this->tag($element); - $table = $tag?->table() ?? ''; - if ($table && $current_row?->table() !== $table) { - yield $current_row = $this->assignment_factory->row( - $table, - is_int($id) ? $id : 0, - $current_row?->id() ?? 0 - ); + if (!is_null($next_row = $this->getNewRowIfNecessary($element->getMDID(), $tag, $current_row))) { + $current_row = $next_row; + $collected_rows[] = $next_row; } $action = $marker?->action(); @@ -116,7 +122,7 @@ protected function collectAssignmentsFromElementAndSubElements( switch ($action) { case MarkerAction::NEUTRAL: if ($element->isScaffold()) { - return; + return []; } break; @@ -142,13 +148,80 @@ protected function collectAssignmentsFromElementAndSubElements( } foreach ($element->getSubElements() as $sub) { - yield from $this->collectAssignmentsFromElementAndSubElements( - $depth + 1, - $sub, - $current_row, - $delete_all + $collected_rows = array_merge( + $collected_rows, + $this->collectRowsForManipulationFromElementAndSubElements( + $depth + 1, + $sub, + $current_row, + $delete_all + ) + ); + } + return $collected_rows; + } + + protected function collectRowsForTransferFromElementAndSubElements( + int $depth, + ElementInterface $element, + AssignmentRowInterface $current_row = null + ): array { + if ($depth > 20) { + throw new \ilMDStructureException('LOM Structure is nested to deep.'); + } + + $collected_rows = []; + $marker = $this->marker($element); + + if ($element->isScaffold() && $marker?->action() !== MarkerAction::CREATE_OR_UPDATE) { + return []; + } + $data_value = !is_null($marker?->dataValue()) ? $marker->dataValue() : $element->getData()->value(); + + $tag = $this->tag($element); + if (!is_null($next_row = $this->getNewRowIfNecessary(NoID::SCAFFOLD, $tag, $current_row))) { + $current_row = $next_row; + $collected_rows[] = $next_row; + } + + if (!is_null($tag)) { + $current_row->addAction($this->assignment_factory->action( + Action::CREATE, + $tag, + $data_value + )); + if (!$current_row->id()) { + $current_row->setId($this->querier->nextID($current_row->table())); + } + } + + foreach ($element->getSubElements() as $sub) { + $collected_rows = array_merge( + $collected_rows, + $this->collectRowsForTransferFromElementAndSubElements( + $depth + 1, + $sub, + $current_row + ) + ); + } + return $collected_rows; + } + + protected function getNewRowIfNecessary( + NoID|int $md_id, + ?TagInterface $tag, + ?AssignmentRowInterface $current_row + ): ?AssignmentRowInterface { + $table = $tag?->table() ?? ''; + if ($table && $current_row?->table() !== $table) { + return $this->assignment_factory->row( + $table, + is_int($md_id) ? $md_id : 0, + $current_row?->id() ?? 0 ); } + return null; } protected function createOrUpdateElement( diff --git a/components/ILIAS/MetaData/classes/Repository/Utilities/DatabaseManipulatorInterface.php b/components/ILIAS/MetaData/classes/Repository/Utilities/DatabaseManipulatorInterface.php index 5e7aaa7dc668..18183b9128d1 100755 --- a/components/ILIAS/MetaData/classes/Repository/Utilities/DatabaseManipulatorInterface.php +++ b/components/ILIAS/MetaData/classes/Repository/Utilities/DatabaseManipulatorInterface.php @@ -27,5 +27,10 @@ interface DatabaseManipulatorInterface { public function manipulateMD(SetInterface $set): void; + /** + * Transfers the set to object, ignores unmarked scaffolds and delete markers. + */ + public function transferMD(SetInterface $from_set, RessourceIDInterface $to_ressource_id): void; + public function deleteAllMD(RessourceIDInterface $ressource_id): void; } diff --git a/components/ILIAS/MetaData/classes/Repository/Utilities/DatabaseReader.php b/components/ILIAS/MetaData/classes/Repository/Utilities/DatabaseReader.php index 14b16369ff52..35c78addf79f 100755 --- a/components/ILIAS/MetaData/classes/Repository/Utilities/DatabaseReader.php +++ b/components/ILIAS/MetaData/classes/Repository/Utilities/DatabaseReader.php @@ -36,6 +36,8 @@ use ILIAS\MetaData\Vocabularies\Dictionary\LOMDictionaryInitiator as LOMVocabInitiator; use ILIAS\MetaData\Repository\Utilities\Queries\DatabaseQuerierInterface; use ILIAS\MetaData\Repository\Utilities\Queries\Results\RowInterface; +use ILIAS\MetaData\Paths\FactoryInterface as PathFactoryInterface; +use ILIAS\MetaData\Paths\Steps\StepToken; class DatabaseReader implements DatabaseReaderInterface { @@ -43,6 +45,7 @@ class DatabaseReader implements DatabaseReaderInterface protected StructureSetInterface $structure; protected DictionaryInterface $dictionary; protected NavigatorFactoryInterface $navigator_factory; + protected PathFactoryInterface $path_factory; protected DatabaseQuerierInterface $querier; protected \ilLogger $logger; @@ -51,6 +54,7 @@ public function __construct( StructureSetInterface $structure, DictionaryInterface $dictionary, NavigatorFactoryInterface $navigator_factory, + PathFactoryInterface $path_factory, DatabaseQuerierInterface $querier, \ilLogger $logger ) { @@ -58,6 +62,7 @@ public function __construct( $this->structure = $structure; $this->dictionary = $dictionary; $this->navigator_factory = $navigator_factory; + $this->path_factory = $path_factory; $this->querier = $querier; $this->logger = $logger; } @@ -79,6 +84,8 @@ public function getMDOnPath( PathInterface $path, RessourceIDInterface $ressource_id ): SetInterface { + $path = $this->shortenPath($path); + $navigator = $this->navigator_factory->structureNavigator( $path, $this->structure->getRoot() @@ -174,7 +181,7 @@ protected function readSubElements( } /** - * @return RowInterface[] + * @return TagInterface[] */ protected function collectTagsFromSameTable( int $depth, @@ -202,6 +209,40 @@ protected function collectTagsFromSameTable( } } + /** + * Cuts off the path at the highest starting point of sub-paths + * created with super steps. + */ + protected function shortenPath(PathInterface $path): PathInterface + { + $depth = 0; + $super_step_depths = []; + foreach ($path->steps() as $step) { + if ($step->name() === StepToken::SUPER) { + $depth--; + $super_step_depths[] = $depth; + continue; + } + $depth++; + } + + if (empty($super_step_depths)) { + return $path; + } + + $cut_off = min($super_step_depths); + $depth = 0; + $path_builder = $this->path_factory->custom(); + foreach ($path->steps() as $step) { + if ($depth === $cut_off) { + break; + } + $path_builder = $path_builder->withNextStepFromStep($step); + $depth++; + } + return $path_builder->get(); + } + protected function definition( StructureElementInterface|StructureNavigatorInterface $struct, ): DefinitionInterface { diff --git a/components/ILIAS/MetaData/classes/Repository/Utilities/Queries/DatabaseQuerier.php b/components/ILIAS/MetaData/classes/Repository/Utilities/Queries/DatabaseQuerier.php index 17c5c6bf772f..c9cb53a8398e 100755 --- a/components/ILIAS/MetaData/classes/Repository/Utilities/Queries/DatabaseQuerier.php +++ b/components/ILIAS/MetaData/classes/Repository/Utilities/Queries/DatabaseQuerier.php @@ -31,6 +31,8 @@ class DatabaseQuerier implements DatabaseQuerierInterface { + use TableNamesHandler; + protected ResultFactoryInterface $data_row_factory; protected \ilDBInterface $db; protected \ilLogger $logger; @@ -45,26 +47,6 @@ public function __construct( $this->logger = $logger; } - protected function checkTable(string $table): void - { - if ( - is_null($this->table($table)) || - is_null($this->IDName($table)) - ) { - throw new \ilMDRepositoryException('Invalid MD table: ' . $table); - } - } - - protected function table(string $table): ?string - { - return LOMDictionaryInitiator::TABLES[$table] ?? null; - } - - protected function IDName(string $table): ?string - { - return LOMDictionaryInitiator::ID_NAME[$table] ?? null; - } - public function manipulate( RessourceIDInterface $ressource_id, AssignmentRowInterface $row diff --git a/components/ILIAS/MetaData/classes/Repository/Utilities/Queries/DatabaseSearcher.php b/components/ILIAS/MetaData/classes/Repository/Utilities/Queries/DatabaseSearcher.php new file mode 100644 index 000000000000..8fc1c3193377 --- /dev/null +++ b/components/ILIAS/MetaData/classes/Repository/Utilities/Queries/DatabaseSearcher.php @@ -0,0 +1,255 @@ +ressource_factory = $ressource_factory; + $this->paths_parser_factory = $paths_parser_factory; + $this->db = $db; + } + + public function search( + ClauseInterface $clause, + ?int $limit, + ?int $offset, + FilterInterface ...$filters + ): \Generator { + $paths_parser = $this->paths_parser_factory->forSearch(); + $where = $this->getClauseForQueryHaving($clause, $paths_parser); + $quoted_table_alias = $this->quoteIdentifier($paths_parser->getTableAliasForFilters()); + + $query = $paths_parser->getSelectForQuery() . ' GROUP BY ' . $quoted_table_alias . '.rbac_id, ' . + $quoted_table_alias . '.obj_id, ' . $quoted_table_alias . '.obj_type HAVING ' . $where . + $this->getFiltersForQueryHaving($quoted_table_alias, ...$filters) . + ' ORDER BY rbac_id, obj_id, obj_type' . $this->getLimitAndOffsetForQuery($limit, $offset); + + foreach ($this->queryDB($query) as $row) { + yield $this->ressource_factory->ressourceID( + (int) $row['rbac_id'], + (int) $row['obj_id'], + (string) $row['obj_type'] + ); + } + } + + protected function getFiltersForQueryHaving( + string $quoted_table_alias, + FilterInterface ...$filters + ): string { + $filter_where = []; + foreach ($filters as $filter) { + $filter_values = []; + if ($val = $this->getFilterValueForCondition($quoted_table_alias, $filter->objID())) { + $filter_values[] = $quoted_table_alias . '.rbac_id = ' . $val; + } + if ($val = $this->getFilterValueForCondition($quoted_table_alias, $filter->subID())) { + $filter_values[] = $quoted_table_alias . '.obj_id = ' . $val; + } + if ($val = $this->getFilterValueForCondition($quoted_table_alias, $filter->type())) { + $filter_values[] = $quoted_table_alias . '.obj_type = ' . $val; + } + if (!empty($filter_values)) { + $filter_where[] = '(' . implode(' AND ', $filter_values) . ')'; + } + } + + if (empty($filter_where)) { + return ''; + } + + return ' AND (' . implode(' OR ', $filter_where) . ')'; + } + + protected function getFilterValueForCondition( + string $quoted_table_alias, + string|int|Placeholder $value + ): string { + if (is_int($value)) { + return $this->quoteInteger($value); + } + if (is_string($value)) { + return $this->quoteText($value); + } + + switch ($value) { + case Placeholder::OBJ_ID: + return $quoted_table_alias . '.rbac_id'; + + case Placeholder::SUB_ID: + return $quoted_table_alias . '.obj_id'; + + case Placeholder::TYPE: + return $quoted_table_alias . '.obj_type'; + + case Placeholder::ANY: + default: + return ''; + } + } + + protected function getLimitAndOffsetForQuery(?int $limit, ?int $offset): string + { + $query_limit = ''; + if (!is_null($limit) || !is_null($offset)) { + $limit = is_null($limit) ? PHP_INT_MAX : $limit; + $query_limit = ' LIMIT ' . $this->quoteInteger($limit); + } + $query_offset = ''; + if (!is_null($offset)) { + $query_offset = ' OFFSET ' . $this->quoteInteger($offset); + } + return $query_limit . $query_offset; + } + + protected function getClauseForQueryHaving( + ClauseInterface $clause, + DatabasePathsParserInterface $paths_parser, + int $depth = 0 + ): string { + if ($depth > 50) { + throw new \ilMDRepositoryException('Search clause is nested to deep.'); + } + + if (!$clause->isJoin()) { + return $this->getBasicClauseForQueryWhere( + $clause->basicProperties(), + $clause->isNegated(), + $paths_parser + ); + } + + $join_props = $clause->joinProperties(); + + $sub_clauses_for_query = []; + foreach ($join_props->subClauses() as $sub_clause) { + $sub_clauses_for_query[] = $this->getClauseForQueryHaving($sub_clause, $paths_parser, $depth + 1); + } + + switch ($join_props->operator()) { + case Operator::AND: + $operator_for_query = 'AND'; + break; + + case Operator::OR: + $operator_for_query = 'OR'; + break; + + default: + throw new \ilMDRepositoryException('Invalid search operator.'); + } + + $negation = ''; + if ($clause->isNegated()) { + $negation = 'NOT '; + } + + return $negation . '(' . implode(' ' . $operator_for_query . ' ', $sub_clauses_for_query) . ')'; + } + + protected function getBasicClauseForQueryWhere( + BasicPropertiesInterface $basic_props, + bool $is_clause_negated, + DatabasePathsParserInterface $paths_parser + ): string { + switch ($basic_props->mode()) { + case Mode::EQUALS: + $comparison = '= ' . + $this->quoteText($basic_props->value()); + break; + + case Mode::CONTAINS: + $comparison = 'LIKE ' . + $this->quoteText('%' . $basic_props->value() . '%'); + break; + + case Mode::STARTS_WITH: + $comparison = 'LIKE ' . + $this->quoteText($basic_props->value() . '%'); + break; + + case Mode::ENDS_WITH: + $comparison = 'LIKE ' . + $this->quoteText('%' . $basic_props->value()); + break; + + default: + throw new \ilMDRepositoryException('Invalid search mode.'); + } + + $mode_negation = ''; + if ($basic_props->isModeNegated()) { + $mode_negation = 'NOT '; + } + $clause_negation = ''; + if ($is_clause_negated) { + $clause_negation = 'NOT '; + } + + return $clause_negation . 'COUNT(CASE WHEN ' . $mode_negation . + $paths_parser->addPathAndGetColumn($basic_props->path()) . ' ' . $comparison . + ' THEN 1 END) > 0'; + } + + protected function queryDB(string $query): \Generator + { + $result = $this->db->query($query); + + while ($row = $this->db->fetchAssoc($result)) { + yield $row; + } + } + + protected function quoteIdentifier(string $identifier): string + { + return $this->db->quoteIdentifier($identifier); + } + + protected function quoteText(string $text): string + { + return $this->db->quote($text, \ilDBConstants::T_TEXT); + } + + protected function quoteInteger(int $integer): string + { + return $this->db->quote($integer, \ilDBConstants::T_INTEGER); + } +} diff --git a/components/ILIAS/MetaData/classes/Repository/Utilities/Queries/DatabaseSearcherInterface.php b/components/ILIAS/MetaData/classes/Repository/Utilities/Queries/DatabaseSearcherInterface.php new file mode 100644 index 000000000000..b89fdfdadcaf --- /dev/null +++ b/components/ILIAS/MetaData/classes/Repository/Utilities/Queries/DatabaseSearcherInterface.php @@ -0,0 +1,34 @@ +db = $db; + $this->structure = $structure; + $this->dictionary = $dictionary; + $this->navigator_factory = $navigator_factory; + } + + /** + * Make sure that you add paths before calling this. + */ + public function getSelectForQuery(): string + { + $from_expression = ''; + if (empty($this->path_joins_by_path)) { + throw new \ilMDRepositoryException('No tables found for search.'); + } elseif (count($this->path_joins_by_path) === 1) { + $from_expression = array_values($this->path_joins_by_path)[0]; + $path = array_keys($this->path_joins_by_path)[0]; + if (isset($this->additional_conditions_by_path[$path])) { + $from_expression .= ' WHERE ' . + implode(' AND ', $this->additional_conditions_by_path[$path]); + } + } else { + $from_expression = 'il_meta_general AS base'; + $path_number = 1; + foreach ($this->path_joins_by_path as $path => $join) { + $condition = $this->getBaseJoinConditionsForTable( + 'base', + 'p' . $path_number . 't1', + ); + if (isset($this->additional_conditions_by_path[$path])) { + $condition .= ' AND ' . + implode(' AND ', $this->additional_conditions_by_path[$path]); + } + $from_expression .= ' LEFT JOIN (' . $join . ') ON ' . $condition; + $path_number++; + } + } + + return 'SELECT p1t1.rbac_id, p1t1.obj_id, p1t1.obj_type FROM ' . $from_expression; + } + + public function addPathAndGetColumn(PathInterface $path): string + { + $path_string = $path->toString(); + if (isset($this->columns_by_path[$path_string])) { + return $this->columns_by_path[$path_string]; + } + + $data_column_name = ''; + + $tables = []; + $conditions = []; + foreach ($this->collectJoinInfos($path, $this->path_number) as $type => $info) { + if ($type === self::JOIN_TABLE && !empty($info)) { + $tables[] = $info; + } + if ($type === self::JOIN_CONDITION && !empty($info)) { + $conditions[] = $info; + } + if ($type === self::COLUMN_NAME && !empty($info)) { + $data_column_name = $info; + } + } + + if (count($tables) === 1 && !empty($conditions)) { + $this->path_joins_by_path[$path_string] = $tables[0]; + /** + * If there is just one table on the path, additional conditions + * e.g. from filters can't be treated as a join condition on the + * path, so it has to be passed one layer up. + */ + $this->additional_conditions_by_path[$path_string] = $conditions; + $this->path_number++; + } elseif (!empty($tables)) { + $join = implode(' JOIN ', $tables); + if (!empty($conditions)) { + $join .= ' ON ' . implode(' AND ', $conditions); + } + $this->path_joins_by_path[$path_string] = $join; + $this->path_number++; + } + + return $this->columns_by_path[$path_string] = $data_column_name; + } + + public function getTableAliasForFilters(): string + { + if (empty($this->path_joins_by_path)) { + throw new \ilMDRepositoryException('No tables found for search.'); + } + return 'p1t1'; + } + + /** + * @return string[], key is either self::JOIN_TABLE, self::JOIN_CONDITION or self::COLUMN_NAME + */ + protected function collectJoinInfos( + PathInterface $path, + int $path_number + ): \Generator { + $navigator = $this->getNavigatorForPath($path); + $table_aliases = []; + $current_tag = null; + $current_table = ''; + $table_number = 1; + + $depth = 0; + while ($navigator->hasNextStep()) { + if ($depth > 20) { + throw new \ilMDStructureException('LOM Structure is nested to deep.'); + } + + $navigator = $navigator->nextStep(); + $current_tag = $this->getTagForCurrentStepOfNavigator($navigator); + + if ($current_tag?->table() && $current_table !== $current_tag?->table()) { + $parent_table = $current_table; + $current_table = $current_tag->table(); + $this->checkTable($current_table); + + /** + * If the step goes back to a previous table, reuse the same + * alias, but if it goes down again to the same table, use a new + * alias (since path filter might mean you're on different + * branches now). + */ + if ($navigator->currentStep()->name() === StepToken::SUPER) { + $alias = $table_aliases[$current_table]; + } else { + $alias = 'p' . $path_number . 't' . $table_number; + $table_aliases[$current_table] = $alias; + $table_number++; + + yield self::JOIN_TABLE => $this->quoteIdentifier($this->table($current_table)) . + ' AS ' . $this->quoteIdentifier($alias); + } + + if (!$current_tag->hasParent()) { + yield self::JOIN_CONDITION => $this->getBaseJoinConditionsForTable( + 'p' . $path_number . 't1', + $alias + ); + } else { + yield self::JOIN_CONDITION => $this->getBaseJoinConditionsForTable( + 'p' . $path_number . 't1', + $alias, + $table_aliases[$parent_table], + $parent_table, + $current_tag->parent() + ); + } + } + + foreach ($navigator->currentStep()->filters() as $filter) { + yield self::JOIN_CONDITION => $res = $this->getJoinConditionFromPathFilter( + $table_aliases[$current_table], + $current_table, + $current_tag?->hasData() ? $current_tag->dataField() : '', + $this->getDataTypeForCurrentStepOfNavigator($navigator) === Type::VOCAB_SOURCE ? + LOMVocabInitiator::SOURCE : + '', + $filter + ); + } + + $depth++; + } + + yield self::COLUMN_NAME => $this->getDataColumn( + $this->quoteIdentifier($table_aliases[$current_table]), + $current_tag?->hasData() ? $current_tag->dataField() : '', + $this->getDataTypeForCurrentStepOfNavigator($navigator) === Type::VOCAB_SOURCE ? + LOMVocabInitiator::SOURCE : + '', + ); + } + + protected function getBaseJoinConditionsForTable( + string $first_table_alias, + string $table_alias, + string $parent_table_alias = null, + string $parent_table = null, + string $parent_type = null + ): string { + $table_alias = $this->quoteIdentifier($table_alias); + $first_table_alias = $this->quoteIdentifier($first_table_alias); + $conditions = []; + + if ($table_alias !== $first_table_alias) { + $conditions[] = $first_table_alias . '.rbac_id = ' . $table_alias . '.rbac_id'; + $conditions[] = $first_table_alias . '.obj_id = ' . $table_alias . '.obj_id'; + $conditions[] = $first_table_alias . '.obj_type = ' . $table_alias . '.obj_type'; + } + + if (!is_null($parent_table_alias) && !is_null($parent_table)) { + $parent_id_column = $parent_table_alias . '.' . + $this->quoteIdentifier($this->IDName($parent_table)); + $conditions[] = $parent_id_column . ' = ' . $table_alias . '.parent_id'; + } + if (!is_null($parent_type)) { + $conditions[] = $this->quoteText($parent_type) . + ' = ' . $table_alias . '.parent_type'; + } + + return implode(' AND ', $conditions); + } + + protected function getJoinConditionFromPathFilter( + string $table_alias, + string $table, + string $data_field, + string $direct_data, + PathFilter $filter + ): string { + $table_alias = $this->quoteIdentifier($table_alias); + $quoted_values = []; + foreach ($filter->values() as $value) { + $quoted_values[] = $filter->type() === FilterType::DATA ? + $this->quoteText($value) : + $this->quoteInteger((int) $value); + } + + if (empty($quoted_values)) { + return ''; + } + + switch ($filter->type()) { + case FilterType::NULL: + return ''; + + case FilterType::MDID: + $column = $table_alias . '.' . $this->quoteIdentifier($this->IDName($table)); + return $column . ' IN (' . implode(', ', $quoted_values) . ')'; + break; + + case FilterType::INDEX: + // not supported + return ''; + + case FilterType::DATA: + $column = $this->getDataColumn($table_alias, $data_field, $direct_data); + return $column . ' IN (' . implode(', ', $quoted_values) . ')'; + break; + + default: + throw new \ilMDRepositoryException('Unknown filter type: ' . $filter->type()->value); + } + } + + /** + * Direct_data is only needed to make vocab sources work until + * controlled vocabularies are implemented. + */ + protected function getDataColumn( + string $quoted_table_alias, + string $data_field, + string $direct_data, + ): string { + $column = $this->quoteText($direct_data); + if ($data_field !== '') { + $column = 'COALESCE(' . $quoted_table_alias . '.' . $this->quoteIdentifier($data_field) . ", '')"; + } + return $column; + } + + protected function getNavigatorForPath(PathInterface $path): StructureNavigatorInterface + { + return $this->navigator_factory->structureNavigator( + $path, + $this->structure->getRoot() + ); + } + + protected function getTagForCurrentStepOfNavigator(StructureNavigatorInterface $navigator): ?TagInterface + { + return $this->dictionary->tagForElement($navigator->element()); + } + + protected function getDataTypeForCurrentStepOfNavigator(StructureNavigatorInterface $navigator): Type + { + return $navigator->element()->getDefinition()->dataType(); + } + + protected function quoteIdentifier(string $identifier): string + { + return $this->db->quoteIdentifier($identifier); + } + + protected function quoteText(string $text): string + { + return $this->db->quote($text, \ilDBConstants::T_TEXT); + } + + protected function quoteInteger(int $integer): string + { + return $this->db->quote($integer, \ilDBConstants::T_INTEGER); + } +} diff --git a/components/ILIAS/MetaData/classes/Repository/Utilities/Queries/Paths/DatabasePathsParserFactory.php b/components/ILIAS/MetaData/classes/Repository/Utilities/Queries/Paths/DatabasePathsParserFactory.php new file mode 100644 index 000000000000..a707cf76448f --- /dev/null +++ b/components/ILIAS/MetaData/classes/Repository/Utilities/Queries/Paths/DatabasePathsParserFactory.php @@ -0,0 +1,58 @@ +db = $db; + $this->structure = $structure; + $this->dictionary = $dictionary; + $this->navigator_factory = $navigator_factory; + } + + public function forSearch(): DatabasePathsParserInterface + { + return new DatabasePathsParser( + $this->db, + $this->structure, + $this->dictionary, + $this->navigator_factory + ); + } +} diff --git a/components/ILIAS/MetaData/classes/Repository/Utilities/Queries/Paths/DatabasePathsParserFactoryInterface.php b/components/ILIAS/MetaData/classes/Repository/Utilities/Queries/Paths/DatabasePathsParserFactoryInterface.php new file mode 100644 index 000000000000..969b4819be4d --- /dev/null +++ b/components/ILIAS/MetaData/classes/Repository/Utilities/Queries/Paths/DatabasePathsParserFactoryInterface.php @@ -0,0 +1,28 @@ +table($table)) || + is_null($this->IDName($table)) + ) { + throw new \ilMDRepositoryException('Invalid MD table: ' . $table); + } + } + + protected function table(string $table): ?string + { + return LOMDictionaryInitiator::TABLES[$table] ?? null; + } + + protected function IDName(string $table): ?string + { + return LOMDictionaryInitiator::ID_NAME[$table] ?? null; + } +} diff --git a/components/ILIAS/MetaData/classes/Repository/Validation/Cleaner.php b/components/ILIAS/MetaData/classes/Repository/Validation/Cleaner.php index 6bd5e88ea855..889ab1c4c824 100755 --- a/components/ILIAS/MetaData/classes/Repository/Validation/Cleaner.php +++ b/components/ILIAS/MetaData/classes/Repository/Validation/Cleaner.php @@ -23,7 +23,7 @@ use ILIAS\MetaData\Elements\Structure\StructureSetInterface; use ILIAS\MetaData\Elements\SetInterface; use ILIAS\MetaData\Repository\Validation\Data\DataValidatorInterface; -use ILIAS\MetaData\Elements\Factory; +use ILIAS\MetaData\Elements\Factory as ElementFactory; use ILIAS\MetaData\Elements\Element; use ILIAS\MetaData\Elements\ElementInterface; use ILIAS\MetaData\Repository\Validation\Dictionary\DictionaryInterface; @@ -33,23 +33,27 @@ use ILIAS\MetaData\Elements\NoID; use ILIAS\MetaData\Elements\Markers\MarkerInterface; use ILIAS\MetaData\Repository\Validation\Dictionary\TagInterface; +use ILIAS\MetaData\Elements\Markers\MarkerFactoryInterface; class Cleaner implements CleanerInterface { - protected Factory $element_factory; + protected ElementFactory $element_factory; + protected MarkerFactoryInterface $marker_factory; protected StructureSetInterface $structure_set; protected DataValidatorInterface $data_validator; protected DictionaryInterface $dictionary; protected \ilLogger $logger; public function __construct( - Factory $element_factory, + ElementFactory $element_factory, + MarkerFactoryInterface $marker_factory, StructureSetInterface $structure_set, DataValidatorInterface $data_validator, DictionaryInterface $dictionary, \ilLogger $logger ) { $this->element_factory = $element_factory; + $this->marker_factory = $marker_factory; $this->structure_set = $structure_set; $this->data_validator = $data_validator; $this->dictionary = $dictionary; @@ -113,13 +117,19 @@ protected function getCleanSubElements( } } + public function cleanMarkers(SetInterface $set): void + { + $this->checkMarkerOnElement($set->getRoot(), true, 0); + } + public function checkMarkers(SetInterface $set): void { - $this->checkMarkerOnElement($set->getRoot(), 0); + $this->checkMarkerOnElement($set->getRoot(), false, 0); } protected function checkMarkerOnElement( ElementInterface $element, + bool $replace_by_neutral, int $depth ): void { if ($depth > 20) { @@ -135,20 +145,22 @@ protected function checkMarkerOnElement( ) { $message = $marker->dataValue() . ' is not valid as ' . $element->getDefinition()->dataType()->value . ' data.'; - $this->throwErrorOrLog($element, $message, true); + $this->throwErrorOrLog($element, $message, !$replace_by_neutral); + $element->mark($this->marker_factory, Action::NEUTRAL); } foreach ($this->dictionary->tagsForElement($element) as $tag) { - $this->checkMarkerAgainstTag($tag, $element, $marker); + $this->checkMarkerAgainstTag($tag, $element, $marker, $replace_by_neutral); } foreach ($element->getSubElements() as $sub) { - $this->checkMarkerOnElement($sub, $depth + 1); + $this->checkMarkerOnElement($sub, $replace_by_neutral, $depth + 1); } } protected function checkMarkerAgainstTag( TagInterface $tag, ElementInterface $element, - MarkerInterface $marker + MarkerInterface $marker, + bool $replace_by_neutral ): void { switch ($tag->restriction()) { case Restriction::PRESET_VALUE: @@ -159,14 +171,16 @@ protected function checkMarkerAgainstTag( $this->throwErrorOrLog( $element, 'can only be created with preset value ' . $tag->value(), - true + !$replace_by_neutral ); + $element->mark($this->marker_factory, Action::NEUTRAL); } break; case Restriction::NOT_DELETABLE: if ($marker->action() === Action::DELETE) { - $this->throwErrorOrLog($element, 'cannot be deleted.', true); + $this->throwErrorOrLog($element, 'cannot be deleted.', !$replace_by_neutral); + $element->mark($this->marker_factory, Action::NEUTRAL); } break; @@ -175,7 +189,8 @@ protected function checkMarkerAgainstTag( $marker->action() === Action::CREATE_OR_UPDATE && $element->getMDID() !== NoID::SCAFFOLD ) { - $this->throwErrorOrLog($element, 'cannot be edited.', true); + $this->throwErrorOrLog($element, 'cannot be edited.', !$replace_by_neutral); + $element->mark($this->marker_factory, Action::NEUTRAL); } break; } diff --git a/components/ILIAS/MetaData/classes/Repository/Validation/CleanerInterface.php b/components/ILIAS/MetaData/classes/Repository/Validation/CleanerInterface.php index 6f44163c5678..b73908969b3f 100755 --- a/components/ILIAS/MetaData/classes/Repository/Validation/CleanerInterface.php +++ b/components/ILIAS/MetaData/classes/Repository/Validation/CleanerInterface.php @@ -37,4 +37,10 @@ public function clean(SetInterface $set): SetInterface; * @throws \ilMDRepositoryException */ public function checkMarkers(SetInterface $set): void; + + /** + * Checks whether the proposed manipulations on the set via markers + * are valid. Replaces the offending markers by neutral ones. + */ + public function cleanMarkers(SetInterface $set): void; } diff --git a/components/ILIAS/MetaData/classes/Services/DataHelper/DataHelperInterface.php b/components/ILIAS/MetaData/classes/Services/DataHelper/DataHelperInterface.php index ca4c44325bdc..c02503386c80 100755 --- a/components/ILIAS/MetaData/classes/Services/DataHelper/DataHelperInterface.php +++ b/components/ILIAS/MetaData/classes/Services/DataHelper/DataHelperInterface.php @@ -41,14 +41,14 @@ public function durationToArray(string $duration): array; /** * Translates strings in the LOM-internal duration format to seconds. * This is only a rough estimate, as LOM-durations do not have a start - * date, so e.g. each month is treated as 30 days. + * date, so e.g. each month is treated as 30 days. */ public function durationToSeconds(string $duration): int; /** * Translates strings in the LOM-internal datetime format to * datetime objects. - * Note that LOM datetimes in ILIAS only consist of a date, and not + * Note that LOM datetimes in ILIAS only consist of a date, without * a time. */ public function datetimeToObject(string $datetime): \DateTimeImmutable; diff --git a/components/ILIAS/MetaData/classes/Services/Derivation/Creation/Creator.php b/components/ILIAS/MetaData/classes/Services/Derivation/Creation/Creator.php new file mode 100644 index 000000000000..e2bd590bbbff --- /dev/null +++ b/components/ILIAS/MetaData/classes/Services/Derivation/Creation/Creator.php @@ -0,0 +1,176 @@ +manipulator = $manipulator; + $this->path_factory = $path_factory; + $this->scaffold_provider = $scaffold_provider; + } + + /** + * @throws \ilMDServicesException if title is empty string + */ + public function createSet( + string $title, + string $description = '', + string $language = '' + ): SetInterface { + $set = $this->scaffold_provider->set(); + + $set = $this->prepareTitle($set, $title, $language); + $set = $this->prepareDescription($set, $description, $language); + $set = $this->prepareLanguage($set, $language); + + return $set; + } + + /** + * @throws \ilMDServicesException if title is empty string + */ + protected function prepareTitle( + SetInterface $set, + string $title, + string $language + ): SetInterface { + if ($title === '') { + throw new \ilMDServicesException('Title cannot be empty.'); + } + + $set = $this->manipulator->prepareCreateOrUpdate( + $set, + $this->getPathToTitleString(), + $title + ); + + if ($language === '') { + return $set; + } + return $this->manipulator->prepareCreateOrUpdate( + $set, + $this->getPathToTitleLanguage(), + $language + ); + } + + protected function prepareDescription( + SetInterface $set, + string $description, + string $language + ): SetInterface { + if ($description === '') { + return $set; + } + $set = $this->manipulator->prepareCreateOrUpdate( + $set, + $this->getPathToDescriptionString(), + $description + ); + + if ($language === '') { + return $set; + } + return $this->manipulator->prepareCreateOrUpdate( + $set, + $this->getPathToDescriptionLanguage(), + $language + ); + } + + protected function prepareLanguage( + SetInterface $set, + string $language + ): SetInterface { + if ($language === '') { + return $set; + } + return $this->manipulator->prepareCreateOrUpdate( + $set, + $this->getPathToLanguage(), + $language + ); + } + + protected function getPathToTitleString(): PathInterface + { + return $this->path_factory + ->custom() + ->withNextStep('general') + ->withNextStep('title') + ->withNextStep('string') + ->get(); + } + + protected function getPathToTitleLanguage(): PathInterface + { + return $this->path_factory + ->custom() + ->withNextStep('general') + ->withNextStep('title') + ->withNextStep('language') + ->get(); + } + + protected function getPathToDescriptionString(): PathInterface + { + return $this->path_factory + ->custom() + ->withNextStep('general') + ->withNextStep('description') + ->withNextStep('string') + ->get(); + } + + protected function getPathToDescriptionLanguage(): PathInterface + { + return $this->path_factory + ->custom() + ->withNextStep('general') + ->withNextStep('description') + ->withNextStep('language') + ->get(); + } + + protected function getPathToLanguage(): PathInterface + { + return $this->path_factory + ->custom() + ->withNextStep('general') + ->withNextStep('language') + ->get(); + } +} diff --git a/components/ILIAS/MetaData/classes/Services/Derivation/Creation/CreatorInterface.php b/components/ILIAS/MetaData/classes/Services/Derivation/Creation/CreatorInterface.php new file mode 100644 index 000000000000..e5f0b1b56a7e --- /dev/null +++ b/components/ILIAS/MetaData/classes/Services/Derivation/Creation/CreatorInterface.php @@ -0,0 +1,35 @@ +from_set = $from_set; + $this->repository = $repository; + } + + /** + * @throws \ilMDServicesException + */ + public function forObject(int $obj_id, int $sub_id, string $type): void + { + if ($sub_id === 0) { + $sub_id = $obj_id; + } + + try { + $this->repository->transferMD( + $this->from_set, + $obj_id, + $sub_id, + $type, + true + ); + } catch (\ilMDRepositoryException $e) { + throw new \ilMDServicesException( + 'Failed to derive LOM set: ' . $e->getMessage() + ); + } + } +} diff --git a/components/ILIAS/MetaData/classes/Services/Derivation/DerivatorInterface.php b/components/ILIAS/MetaData/classes/Services/Derivation/DerivatorInterface.php new file mode 100644 index 000000000000..d1a0f48989e7 --- /dev/null +++ b/components/ILIAS/MetaData/classes/Services/Derivation/DerivatorInterface.php @@ -0,0 +1,33 @@ +repository = $repository; + $this->creator = $creator; + } + + public function fromObject(int $obj_id, int $sub_id, string $type): DerivatorInterface + { + if ($sub_id === 0) { + $sub_id = $obj_id; + } + + return $this->getDerivator( + $this->repository->getMD($obj_id, $sub_id, $type) + ); + } + + /** + * @throws \ilMDServicesException if title is empty string + */ + public function fromBasicProperties( + string $title, + string $description = '', + string $language = '' + ): DerivatorInterface { + return $this->getDerivator( + $this->creator->createSet($title, $description, $language) + ); + } + + protected function getDerivator(SetInterface $from_set): DerivatorInterface + { + return new Derivator( + $from_set, + $this->repository + ); + } +} diff --git a/components/ILIAS/MetaData/classes/Services/Derivation/SourceSelectorInterface.php b/components/ILIAS/MetaData/classes/Services/Derivation/SourceSelectorInterface.php new file mode 100644 index 000000000000..c37040a7d615 --- /dev/null +++ b/components/ILIAS/MetaData/classes/Services/Derivation/SourceSelectorInterface.php @@ -0,0 +1,42 @@ +path_services, $this->structure_services ); + $this->manipulator_services = new ManipulatorServices( + $this->path_services, + $this->structure_services + ); $this->repository_services = new RepositoryServices( $this->dic, $this->path_services, $this->structure_services, $this->vocabularies_services, - $this->data_helper_services - ); - $this->manipulator_services = new ManipulatorServices( - $this->path_services, - $this->repository_services + $this->data_helper_services, + $this->manipulator_services ); $this->editor_services = new EditorServices( $this->dic, @@ -82,6 +85,11 @@ public function __construct(GlobalContainer $dic) $this->copyright_services = new CopyrightServices( $this->dic ); + $this->xml_services = new XMLServices( + $this->path_services, + $this->structure_services, + $this->manipulator_services + ); } public function dic(): GlobalContainer @@ -133,4 +141,9 @@ public function copyright(): CopyrightServices { return $this->copyright_services; } + + public function xml(): XMLServices + { + return $this->xml_services; + } } diff --git a/components/ILIAS/MetaData/classes/Services/Manipulator/Factory.php b/components/ILIAS/MetaData/classes/Services/Manipulator/Factory.php new file mode 100644 index 000000000000..c0b481314050 --- /dev/null +++ b/components/ILIAS/MetaData/classes/Services/Manipulator/Factory.php @@ -0,0 +1,54 @@ +internal_manipulator = $internal_manipulator; + $this->repository = $repository; + } + + public function get( + SetInterface $set + ): ManipulatorInterface { + return new Manipulator( + $this->internal_manipulator, + $this->repository, + $set + ); + } +} diff --git a/components/ILIAS/MetaData/classes/Services/Manipulator/FactoryInterface.php b/components/ILIAS/MetaData/classes/Services/Manipulator/FactoryInterface.php new file mode 100644 index 000000000000..338a27447252 --- /dev/null +++ b/components/ILIAS/MetaData/classes/Services/Manipulator/FactoryInterface.php @@ -0,0 +1,30 @@ +internal_manipulator = $internal_manipulator; + $this->repository = $repository; $this->set = $set; } @@ -41,11 +45,19 @@ public function prepareCreateOrUpdate( PathInterface $path, string ...$values ): ManipulatorInterface { - $set = $this->internal_manipulator->prepareCreateOrUpdate( - $this->set, - $path, - ...$values - ); + try { + $set = $this->internal_manipulator->prepareCreateOrUpdate( + $this->set, + $path, + ...$values + ); + } catch (\ilMDPathException $e) { + throw new \ilMDServicesException( + 'Failed to prepare create or update values ' . implode(', ', $values) . + ' at "' . $path->toString() . '": ' . $e->getMessage() + ); + } + return $this->getCloneWithNewSet($set); } @@ -53,11 +65,19 @@ public function prepareForceCreate( PathInterface $path, string ...$values ): ManipulatorInterface { - $set = $this->internal_manipulator->prepareForceCreate( - $this->set, - $path, - ...$values - ); + try { + $set = $this->internal_manipulator->prepareForceCreate( + $this->set, + $path, + ...$values + ); + } catch (\ilMDPathException $e) { + throw new \ilMDServicesException( + 'Failed to force-create values ' . implode(', ', $values) . + ' at "' . $path->toString() . '": ' . $e->getMessage() + ); + } + return $this->getCloneWithNewSet($set); } @@ -72,7 +92,14 @@ public function prepareDelete(PathInterface $path): ManipulatorInterface public function execute(): void { - $this->internal_manipulator->execute($this->set); + try { + $this->repository->manipulateMD($this->set); + } catch (\ilMDRepositoryException $e) { + throw new \ilMDServicesException( + 'Failed to execute manipulations: ' . $e->getMessage() + ); + } + } protected function getCloneWithNewSet(SetInterface $set): ManipulatorInterface diff --git a/components/ILIAS/MetaData/classes/Services/Manipulator/ManipulatorInterface.php b/components/ILIAS/MetaData/classes/Services/Manipulator/ManipulatorInterface.php index c376910abcaf..571e7028e0c6 100755 --- a/components/ILIAS/MetaData/classes/Services/Manipulator/ManipulatorInterface.php +++ b/components/ILIAS/MetaData/classes/Services/Manipulator/ManipulatorInterface.php @@ -29,7 +29,7 @@ interface ManipulatorInterface * by the path in order. Previous values of these elements are * overwritten, new elements are created if not enough exist. * - * Note that an error is thrown if it is not possible to create + * @throws \ilMDServicesException if it is not possible to create * enough elements to hold all values. Be careful with unique * elements. */ @@ -40,9 +40,9 @@ public function prepareCreateOrUpdate( /** * New elements are set to be created as specified by the path, - * and are filled with the values. + * and filled with the values. * - * Note that an error is thrown if it is not possible to create + * @throws \ilMDServicesException if it is not possible to create * enough elements to hold all values. Be careful with unique * elements. */ @@ -57,9 +57,10 @@ public function prepareForceCreate( public function prepareDelete(PathInterface $path): ManipulatorInterface; /** - * Execute all prepared actions. An error is thrown if - * the LOM set would become invalid, e.g. because of - * invalid data values. + * Execute all prepared actions. + * + * @throws \ilMDServicesException if the LOM set would become invalid, + * e.g. because of invalid data values. */ public function execute(): void; } diff --git a/components/ILIAS/MetaData/classes/Services/Manipulator/NullFactory.php b/components/ILIAS/MetaData/classes/Services/Manipulator/NullFactory.php new file mode 100644 index 000000000000..bdb30bfb8550 --- /dev/null +++ b/components/ILIAS/MetaData/classes/Services/Manipulator/NullFactory.php @@ -0,0 +1,32 @@ +internal_builder = $clone->internal_builder->withAdditionalFilterAtCurrentStep( - $type, - ...$values - ); + try { + $clone->internal_builder = $clone->internal_builder->withAdditionalFilterAtCurrentStep( + $type, + ...$values + ); + } catch (\ilMDPathException $e) { + throw new \ilMDServicesException($e->getMessage()); + } + return $clone; } + /** + * @throws \ilMDServicesException + */ public function get(): PathInterface { - return $this->internal_builder->get(); + try { + return $this->internal_builder->get(); + } catch (\ilMDPathException $e) { + throw new \ilMDServicesException($e->getMessage()); + } + } } diff --git a/components/ILIAS/MetaData/classes/Services/Paths/BuilderInterface.php b/components/ILIAS/MetaData/classes/Services/Paths/BuilderInterface.php index 3d4d7045bf5a..3f9b2e258c7d 100755 --- a/components/ILIAS/MetaData/classes/Services/Paths/BuilderInterface.php +++ b/components/ILIAS/MetaData/classes/Services/Paths/BuilderInterface.php @@ -48,6 +48,8 @@ public function withNextStepToSuperElement(): BuilderInterface; * * Multiple values in the same filter are treated as OR, * multiple filters at the same step are treated as AND. + * + * @throws \ilMDServicesException if there is no step in the path yet */ public function withAdditionalFilterAtCurrentStep( FilterType $type, @@ -55,8 +57,10 @@ public function withAdditionalFilterAtCurrentStep( ): BuilderInterface; /** - * Get the path as constructed. Throws an error if the path - * is invalid, e.g. because the name of a step was misspelled. + * Get the path as constructed. + * + * @throws \ilMDServicesException if the path is invalid, + * e.g. because the name of a step was misspelled. */ public function get(): PathInterface; } diff --git a/components/ILIAS/MetaData/classes/Services/Reader/Factory.php b/components/ILIAS/MetaData/classes/Services/Reader/Factory.php new file mode 100644 index 000000000000..c39919912dbc --- /dev/null +++ b/components/ILIAS/MetaData/classes/Services/Reader/Factory.php @@ -0,0 +1,42 @@ +navigator_factory = $navigator_factory; + } + + public function get(SetInterface $set): ReaderInterface + { + return new Reader( + $this->navigator_factory, + $set + ); + } +} diff --git a/components/ILIAS/MetaData/classes/Services/Reader/FactoryInterface.php b/components/ILIAS/MetaData/classes/Services/Reader/FactoryInterface.php new file mode 100644 index 000000000000..6b1b1480408d --- /dev/null +++ b/components/ILIAS/MetaData/classes/Services/Reader/FactoryInterface.php @@ -0,0 +1,28 @@ +clause_factory = $clause_factory; + $this->filter_factory = $filter_factory; + $this->repository = $repository; + } + + public function getClauseFactory(): ClauseFactory + { + return $this->clause_factory; + } + + public function getFilter( + int|Placeholder $obj_id = Placeholder::ANY, + int|Placeholder $sub_id = Placeholder::ANY, + string|Placeholder $type = Placeholder::ANY + ): FilterInterface { + if ($sub_id === 0) { + $sub_id = Placeholder::OBJ_ID; + } + return $this->filter_factory->get($obj_id, $sub_id, $type); + } + + /** + * @return RessourceIDInterface[] + */ + public function execute( + ClauseInterface $clause, + ?int $limit, + ?int $offset, + FilterInterface ...$filters + ): \Generator { + yield from $this->repository->searchMD( + $clause, + $limit, + $offset, + ...$filters + ); + } +} diff --git a/components/ILIAS/MetaData/classes/Services/Search/SearcherInterface.php b/components/ILIAS/MetaData/classes/Services/Search/SearcherInterface.php new file mode 100644 index 000000000000..223e14234e3a --- /dev/null +++ b/components/ILIAS/MetaData/classes/Services/Search/SearcherInterface.php @@ -0,0 +1,63 @@ +internal_services->repository()->repository(); + $repo = $this->repository(); if (isset($limited_to)) { $set = $repo->getMDOnPath($limited_to, $obj_id, $sub_id, $type); } else { $set = $repo->getMD($obj_id, $sub_id, $type); } - return new Reader( - $this->internal_services->paths()->navigatorFactory(), - $set + return $this->readerFactory()->get($set); + } + + public function search(): SearcherInterface + { + if (isset($this->searcher)) { + return $this->searcher; + } + return $this->searcher = new Searcher( + $this->internal_services->repository()->SearchClauseFactory(), + $this->internal_services->repository()->SearchFilterFactory(), + $this->internal_services->repository()->repository() ); } public function manipulate(int $obj_id, int $sub_id, string $type): ManipulatorInterface { - $repo = $this->internal_services->repository()->repository(); + if ($sub_id === 0) { + $sub_id = $obj_id; + } + + $repo = $this->repository(); $set = $repo->getMD($obj_id, $sub_id, $type); - return new Manipulator( - $this->internal_services->manipulator()->manipulator(), - $set + return $this->manipulatorFactory()->get($set); + } + + public function derive(): SourceSelectorInterface + { + if (isset($this->derivation_source_selector)) { + return $this->derivation_source_selector; + } + return $this->derivation_source_selector = new SourceSelector( + $this->internal_services->repository()->repository(), + new Creator( + $this->internal_services->manipulator()->manipulator(), + $this->internal_services->paths()->pathFactory(), + $this->internal_services->manipulator()->scaffoldProvider() + ) ); } + public function deleteAll(int $obj_id, int $sub_id, string $type): void + { + if ($sub_id === 0) { + $sub_id = $obj_id; + } + + $repo = $this->repository(); + $repo->deleteAllMD($obj_id, $sub_id, $type); + } + public function paths(): PathsInterface { if (isset($this->paths)) { return $this->paths; } - return new Paths( + return $this->paths = new Paths( $this->internal_services->paths()->pathFactory() ); } @@ -90,9 +142,35 @@ public function dataHelper(): DataHelperInterface if (isset($this->data_helper)) { return $this->data_helper; } - return new DataHelper( + return $this->data_helper = new DataHelper( $this->internal_services->dataHelper()->dataHelper(), $this->internal_services->presentation()->data() ); } + + protected function readerFactory(): ReaderFactoryInterface + { + if (isset($this->reader_factory)) { + return $this->reader_factory; + } + return $this->reader_factory = new ReaderFactory( + $this->internal_services->paths()->navigatorFactory() + ); + } + + protected function manipulatorFactory(): ManipulatorFactoryInterface + { + if (isset($this->manipulator_factory)) { + return $this->manipulator_factory; + } + return $this->manipulator_factory = new ManipulatorFactory( + $this->internal_services->manipulator()->manipulator(), + $this->internal_services->repository()->repository() + ); + } + + protected function repository(): RepositoryInterface + { + return $this->internal_services->repository()->repository(); + } } diff --git a/components/ILIAS/MetaData/classes/Services/ServicesInterface.php b/components/ILIAS/MetaData/classes/Services/ServicesInterface.php index 19d75753f232..11863c5e8bfd 100755 --- a/components/ILIAS/MetaData/classes/Services/ServicesInterface.php +++ b/components/ILIAS/MetaData/classes/Services/ServicesInterface.php @@ -25,6 +25,8 @@ use ILIAS\MetaData\Services\Manipulator\ManipulatorInterface; use ILIAS\MetaData\Services\Reader\ReaderInterface; use ILIAS\MetaData\Paths\PathInterface; +use ILIAS\MetaData\Services\Derivation\SourceSelectorInterface; +use ILIAS\MetaData\Services\Search\SearcherInterface; interface ServicesInterface { @@ -40,8 +42,10 @@ interface ServicesInterface * 3. **type:** The type of the object (and not its parent's), e.g. `'crs'` or `'lm'`. * * Optionally, a path can be specified to which the reading is restricted: the reader - * will then only have access to elements on the path, along with all sub-elements - * of the last element of the path. + * will then only have access to elements on the path, along with recursively all + * sub-elements of the last element of the path. + * Note that path filters are ignored, and if the path contains steps to super elements, + * it is only followed down to the first element that the path returns to. */ public function read( int $obj_id, @@ -51,18 +55,29 @@ public function read( ): ReaderInterface; /** - * Get a manipulator, which can manipulate the LOM of an ILIAS object. The object is specified - * with three parameters: - * 1. **obj_id:** The `obj_id` of the object if it is a repository object, else the - * `obj_id` of its parent repository object. If the object does not have - * a fixed parent (e.g. MediaObject), then this parameter is 0. - * 2. **sub_id:** The `obj_id` of the object. If the object is a repository object by - * itself and not a sub-object, then you can set this parameter to 0, but - * we recommend passing the `obj_id` again. - * 3. **type:** The type of the object (and not its parent's), e.g. `'crs'` or `'lm'`. + * Get a searcher, in which you can assemble a search clause and filters, + * and use these to find objects whose LOM matches the search. + */ + public function search(): SearcherInterface; + + /** + * Get a manipulator, which can manipulate the LOM of an ILIAS object. + * See {@see \ILIAS\MetaData\Services\ServicesInterface::read()} for a description of the parameters. */ public function manipulate(int $obj_id, int $sub_id, string $type): ManipulatorInterface; + /** + * Derives LOM from a target, for a source. Encompasses both copying LOM between + * ILIAS objects and creating LOM for an object from some basic properties. + */ + public function derive(): SourceSelectorInterface; + + /** + * Delete all LOM of an ILIAS object. See {@see \ILIAS\MetaData\Services\ServicesInterface::read()} + * for a description of the parameters. + */ + public function deleteAll(int $obj_id, int $sub_id, string $type): void; + /** * Elements in LOM are identified by paths to them from the root. Get a collection of * frequently used paths, as well as a builder to construct custom ones. diff --git a/components/ILIAS/MetaData/classes/XML/Copyright/CopyrightHandler.php b/components/ILIAS/MetaData/classes/XML/Copyright/CopyrightHandler.php new file mode 100644 index 000000000000..13ef7cf28e20 --- /dev/null +++ b/components/ILIAS/MetaData/classes/XML/Copyright/CopyrightHandler.php @@ -0,0 +1,39 @@ +version() === $version) { + return $tag; + } + } + return null; + } +} diff --git a/components/ILIAS/MetaData/classes/XML/Dictionary/LOMDictionaryInitiator.php b/components/ILIAS/MetaData/classes/XML/Dictionary/LOMDictionaryInitiator.php new file mode 100644 index 000000000000..756dcc2ed11f --- /dev/null +++ b/components/ILIAS/MetaData/classes/XML/Dictionary/LOMDictionaryInitiator.php @@ -0,0 +1,197 @@ +tag_factory = $tag_factory; + $this->path_factory = $path_factory; + parent::__construct($path_factory, $structure); + } + + public function get(): DictionaryInterface + { + $this->initDictionary(); + return new LOMDictionary($this->path_factory, ...$this->getTagAssignments()); + } + + protected function initDictionary(): void + { + $structure = $this->getStructure(); + + $this->addTagsToGeneral($structure); + $this->addTagsToLifeCycle($structure); + $this->addTagsToMetaMetadata($structure); + $this->addTagsToTechnical($structure); + $this->addTagsToEducational($structure); + $this->addTagsToRights($structure); + $this->addTagsToRelation($structure); + $this->addTagsToAnnotation($structure); + $this->addTagsToClassification($structure); + } + + protected function addTagsToGeneral(StructureSetInterface $structure): void + { + $root = $structure->getRoot(); + + $general = $root->getSubElement('general'); + $this->addTagsToLangString($general->getSubElement('title')); + $this->addTagsToLangString($general->getSubElement('description')); + $this->addTagsToLangString($general->getSubElement('keyword')); + $this->addTagsToLangString($general->getSubElement('coverage')); + } + + protected function addTagsToLifeCycle(StructureSetInterface $structure): void + { + $root = $structure->getRoot(); + + $lifecycle = $root->getSubElement('lifeCycle'); + $this->addTagsToLangString($lifecycle->getSubElement('version')); + $this->addTagsToLangString( + $lifecycle->getSubElement('contribute') + ->getSubElement('date') + ->getSubElement('description') + ); + } + + protected function addTagsToMetaMetadata(StructureSetInterface $structure): void + { + $root = $structure->getRoot(); + + $metametadata = $root->getSubElement('metaMetadata'); + $this->addTagsToLangString( + $metametadata->getSubElement('contribute') + ->getSubElement('date') + ->getSubElement('description') + ); + } + + protected function addTagsToTechnical(StructureSetInterface $structure): void + { + $root = $structure->getRoot(); + + $technical = $root->getSubElement('technical'); + $this->addTagsToLangString($technical->getSubElement('installationRemarks')); + $this->addTagsToLangString($technical->getSubElement('otherPlatformRequirements')); + $this->addTagsToLangString( + $technical->getSubElement('duration') + ->getSubElement('description') + ); + } + + protected function addTagsToEducational(StructureSetInterface $structure): void + { + $root = $structure->getRoot(); + + $educational = $root->getSubElement('educational'); + $this->addTagsToLangString($educational->getSubElement('typicalAgeRange')); + $this->addTagsToLangString($educational->getSubElement('description')); + $this->addTagsToLangString( + $educational->getSubElement('typicalLearningTime') + ->getSubElement('description') + ); + } + + protected function addTagsToRights(StructureSetInterface $structure): void + { + $root = $structure->getRoot(); + + $rights = $root->getSubElement('rights'); + $description = $rights->getSubElement('description'); + $this->addTagsToLangString($description); + + $tag_10 = $this->tag_factory->tag( + Version::V10_0, + SpecialCase::COPYRIGHT + ); + $tag_4 = $this->tag_factory->tag( + Version::V4_1_0, + SpecialCase::COPYRIGHT + ); + + $description_string = $description->getSubElement('string'); + $this->addTagToElement($tag_10, $description_string); + $this->addTagToElement($tag_4, $description_string); + } + + protected function addTagsToRelation(StructureSetInterface $structure): void + { + $root = $structure->getRoot(); + + $relation = $root->getSubElement('relation'); + $this->addTagsToLangString( + $relation->getSubElement('resource') + ->getSubElement('description') + ); + } + + protected function addTagsToAnnotation(StructureSetInterface $structure): void + { + $root = $structure->getRoot(); + + $annotation = $root->getSubElement('annotation'); + $this->addTagsToLangString( + $annotation->getSubElement('date') + ->getSubElement('description') + ); + $this->addTagsToLangString($annotation->getSubElement('description')); + } + + protected function addTagsToClassification(StructureSetInterface $structure): void + { + $root = $structure->getRoot(); + + $classification = $root->getSubElement('classification'); + $taxon_path = $classification->getSubElement('taxonPath'); + $this->addTagsToLangString($taxon_path->getSubElement('source')); + $this->addTagsToLangString( + $taxon_path->getSubElement('taxon') + ->getSubElement('entry') + ); + $this->addTagsToLangString($classification->getSubElement('description')); + $this->addTagsToLangString($classification->getSubElement('keyword')); + } + + protected function addTagsToLangString(StructureElementInterface $element): void + { + $tag_10 = $this->tag_factory->tag( + Version::V10_0, + SpecialCase::LANGSTRING + ); + + $this->addTagToElement($tag_10, $element); + } +} diff --git a/components/ILIAS/MetaData/classes/XML/Dictionary/NullDictionary.php b/components/ILIAS/MetaData/classes/XML/Dictionary/NullDictionary.php new file mode 100644 index 000000000000..9cfd3d91a0ee --- /dev/null +++ b/components/ILIAS/MetaData/classes/XML/Dictionary/NullDictionary.php @@ -0,0 +1,34 @@ +version = $version; + $this->specialities = $specialities; + parent::__construct(); + } + + public function version(): Version + { + return $this->version; + } + + public function isExportedAsLangString(): bool + { + return in_array( + SpecialCase::LANGSTRING, + $this->specialities + ); + } + + public function isTranslatedAsCopyright(): bool + { + return in_array( + SpecialCase::COPYRIGHT, + $this->specialities + ); + } + + public function isOmitted(): bool + { + return in_array( + SpecialCase::OMITTED, + $this->specialities + ); + } + + public function isExportedAsAttribute(): bool + { + return in_array( + SpecialCase::AS_ATTRIBUTE, + $this->specialities + ); + } +} diff --git a/components/ILIAS/MetaData/classes/XML/Dictionary/TagFactory.php b/components/ILIAS/MetaData/classes/XML/Dictionary/TagFactory.php new file mode 100644 index 000000000000..bd85591581b9 --- /dev/null +++ b/components/ILIAS/MetaData/classes/XML/Dictionary/TagFactory.php @@ -0,0 +1,34 @@ +marker_factory = $marker_factory; + $this->scaffold_provider = $scaffold_provider; + $this->copyright_handler = $copyright_handler; + } + + public function read( + \SimpleXMLElement $xml, + Version $version + ): SetInterface { + $set = $this->scaffold_provider->set(); + + $this->prepareAddingOfGeneral($set, $xml->General); + if (!empty($xml->Lifecycle)) { + $this->prepareAddingOfLifeCycle($set, $xml->Lifecycle); + } + if (!empty($xml->{'Meta-Metadata'})) { + $this->prepareAddingOfMetaMetadata($set, $xml->{'Meta-Metadata'}); + } + if (!empty($xml->Technical)) { + $this->prepareAddingOfTechnical($set, $xml->Technical); + } + if (!empty($xml->Educational)) { + $this->prepareAddingOfEducational($set, $xml->Educational); + } + if (!empty($xml->Rights)) { + $this->prepareAddingOfRights($set, $xml->Rights); + } + foreach ($xml->Relation as $relation_xml) { + $this->prepareAddingOfRelation($set, $relation_xml); + } + foreach ($xml->Annotation as $annotation_xml) { + $this->prepareAddingOfAnnotation($set, $annotation_xml); + } + foreach ($xml->Classification as $classification_xml) { + $this->prepareAddingOfClassification($set, $classification_xml); + } + + return $set; + } + + protected function prepareAddingOfGeneral( + SetInterface $set, + \SimpleXMLElement $xml + ): void { + $general = $this->addScaffoldAndMark($set->getRoot(), 'general'); + + foreach ($xml->Identifier as $identifier_xml) { + $this->prepareAddingOfIdentifier($general, $identifier_xml); + } + + $this->prepareAddingOfLangstring('title', $general, $xml->Title); + + foreach ($xml->Language as $language_xml) { + $this->addScaffoldAndMark($general, 'language', (string) $language_xml->attributes()->Language); + } + + foreach ($xml->Description as $description_xml) { + $this->prepareAddingOfLangstring('description', $general, $description_xml); + } + + foreach ($xml->Keyword as $keyword_xml) { + $this->prepareAddingOfLangstring('keyword', $general, $keyword_xml); + } + + if (!empty($xml->Coverage)) { + $this->prepareAddingOfLangstring('coverage', $general, $xml->Coverage); + } + + $this->prepareAddingOfVocabulary( + 'structure', + (string) $xml->attributes()->Structure, + $general + ); + } + + protected function prepareAddingOfLifeCycle( + SetInterface $set, + \SimpleXMLElement $xml + ): void { + $lifecycle = $this->addScaffoldAndMark($set->getRoot(), 'lifeCycle'); + + $this->prepareAddingOfLangstring('version', $lifecycle, $xml->Version); + + $this->prepareAddingOfVocabulary( + 'status', + (string) $xml->attributes()->status, + $lifecycle + ); + + foreach ($xml->Contribute as $contribute_xml) { + $this->prepareAddingOfContribute($lifecycle, $contribute_xml); + } + } + + protected function prepareAddingOfMetaMetadata( + SetInterface $set, + \SimpleXMLElement $xml + ): void { + $metametadata = $this->addScaffoldAndMark($set->getRoot(), 'metaMetadata'); + + foreach ($xml->Identifier as $identifier_xml) { + $this->prepareAddingOfIdentifier($metametadata, $identifier_xml); + } + + foreach ($xml->Contribute as $contribute_xml) { + $this->prepareAddingOfContribute($metametadata, $contribute_xml); + } + + $this->addScaffoldAndMark($metametadata, 'metadataSchema', 'LOMv1.0'); + + if (!empty($xml->attributes()->Language)) { + $this->addScaffoldAndMark($metametadata, 'language', (string) $xml->attributes()->Language); + } + } + + protected function prepareAddingOfTechnical( + SetInterface $set, + \SimpleXMLElement $xml + ): void { + $technical = $this->addScaffoldAndMark($set->getRoot(), 'technical'); + + foreach ($xml->Format as $format_xml) { + $this->addScaffoldAndMark($technical, 'format', (string) $format_xml); + } + + if (!empty($xml->Size)) { + $this->addScaffoldAndMark($technical, 'size', (string) $xml->Size); + } + + foreach ($xml->Location as $location_xml) { + $this->addScaffoldAndMark($technical, 'location', (string) $location_xml); + } + + foreach ($xml->Requirement as $requirement_xml) { + $this->prepareAddingOfRequirement($technical, $requirement_xml); + } + foreach ($xml->OrComposite as $or_composite_xml) { + foreach ($or_composite_xml->Requirement as $requirement_xml) { + $this->prepareAddingOfRequirement($technical, $requirement_xml); + } + } + + if (!empty($xml->InstallationRemarks)) { + $this->prepareAddingOfLangstring( + 'installationRemarks', + $technical, + $xml->InstallationRemarks + ); + } + + if (!empty($xml->OtherPlatformRequirements)) { + $this->prepareAddingOfLangstring( + 'otherPlatformRequirements', + $technical, + $xml->OtherPlatformRequirements + ); + } + + if (!empty($xml->Duration)) { + $duration = $this->addScaffoldAndMark($technical, 'duration'); + $this->addScaffoldAndMark($duration, 'duration', (string) $xml->Duration); + } + } + + protected function prepareAddingOfEducational( + SetInterface $set, + \SimpleXMLElement $xml + ): void { + $educational = $this->addScaffoldAndMark($set->getRoot(), 'educational'); + + $this->prepareAddingOfVocabulary( + 'interactivityType', + (string) $xml->attributes()->InteractivityType, + $educational + ); + + $this->prepareAddingOfVocabulary( + 'learningResourceType', + (string) $xml->attributes()->LearningResourceType, + $educational + ); + + $this->prepareAddingOfVocabulary( + 'interactivityLevel', + (string) $xml->attributes()->InteractivityLevel, + $educational + ); + + $this->prepareAddingOfVocabulary( + 'semanticDensity', + (string) $xml->attributes()->SemanticDensity, + $educational + ); + + $this->prepareAddingOfVocabulary( + 'intendedEndUserRole', + (string) $xml->attributes()->IntendedEndUserRole, + $educational + ); + + $this->prepareAddingOfVocabulary( + 'context', + (string) $xml->attributes()->Context, + $educational + ); + + foreach ($xml->TypicalAgeRange as $tar_xml) { + $this->prepareAddingOfLangstring('typicalAgeRange', $educational, $tar_xml); + } + + $this->prepareAddingOfVocabulary( + 'difficulty', + (string) $xml->attributes()->Difficulty, + $educational + ); + + if (!empty($xml->TypicalLearningTime)) { + $duration = $this->addScaffoldAndMark($educational, 'typicalLearningTime'); + $this->addScaffoldAndMark($duration, 'duration', (string) $xml->TypicalLearningTime); + } + + foreach ($xml->Description as $description_xml) { + $this->prepareAddingOfLangstring('description', $educational, $description_xml); + } + + foreach ($xml->Language as $language_xml) { + $this->addScaffoldAndMark($educational, 'language', (string) $language_xml->attributes()->Language); + } + } + + protected function prepareAddingOfRights( + SetInterface $set, + \SimpleXMLElement $xml + ): void { + $rights = $this->addScaffoldAndMark($set->getRoot(), 'rights'); + + $this->prepareAddingOfVocabulary( + 'cost', + (string) $xml->attributes()->Cost, + $rights + ); + + $this->prepareAddingOfVocabulary( + 'copyrightAndOtherRestrictions', + (string) $xml->attributes()->CopyrightAndOtherRestrictions, + $rights + ); + + $description_scaffold = $this->addScaffoldAndMark($rights, 'description'); + $this->addScaffoldAndMark( + $description_scaffold, + 'language', + (string) $xml->Description->attributes()->Language + ); + $description_string = $this->copyright_handler->copyrightFromExport((string) $xml->Description); + if ($description_string !== '') { + $this->addScaffoldAndMark($description_scaffold, 'string', $description_string); + } + } + + protected function prepareAddingOfRelation( + SetInterface $set, + \SimpleXMLElement $xml + ): void { + $relation = $this->addScaffoldAndMark($set->getRoot(), 'relation'); + + $this->prepareAddingOfVocabulary( + 'kind', + (string) $xml->attributes()->Kind, + $relation, + true + ); + + $resource = $this->addScaffoldAndMark($relation, 'resource'); + $resource_xml = $xml->Resource; + + foreach ($resource_xml->Identifier_ as $identifier_xml) { + $this->prepareAddingOfIdentifier($resource, $identifier_xml); + } + + foreach ($resource_xml->Description as $description_xml) { + $this->prepareAddingOfLangstring('description', $resource, $description_xml); + } + } + + protected function prepareAddingOfAnnotation( + SetInterface $set, + \SimpleXMLElement $xml + ): void { + $annotation = $this->addScaffoldAndMark($set->getRoot(), 'annotation'); + + $this->addScaffoldAndMark($annotation, 'entity', (string) $xml->Entity); + + $date = $this->addScaffoldAndMark($annotation, 'date'); + $this->addScaffoldAndMark($date, 'dateTime', (string) $xml->Date); + + $this->prepareAddingOfLangstring('description', $annotation, $xml->Description); + } + + protected function prepareAddingOfClassification( + SetInterface $set, + \SimpleXMLElement $xml + ): void { + $classification = $this->addScaffoldAndMark($set->getRoot(), 'classification'); + + $this->prepareAddingOfVocabulary( + 'purpose', + (string) $xml->attributes()->Purpose, + $classification + ); + + foreach ($xml->TaxonPath as $taxon_path_xml) { + $taxon_path = $this->addScaffoldAndMark($classification, 'taxonPath'); + + $this->prepareAddingOfLangstring('source', $taxon_path, $taxon_path_xml->Source); + + foreach ($taxon_path_xml->taxon as $taxon_xml) { + $taxon = $this->addScaffoldAndMark($taxon_path, 'taxon'); + + if (!empty($taxon_xml->attributes()->Id)) { + $this->addScaffoldAndMark($taxon, 'id', (string) $taxon_xml->attributes()->Id); + } + + $this->prepareAddingOfLangstring('entry', $taxon, $taxon_xml); + } + } + + $this->prepareAddingOfLangstring( + 'description', + $classification, + $xml->Description + ); + + foreach ($xml->Keyword as $keyword_xml) { + $this->prepareAddingOfLangstring('keyword', $classification, $keyword_xml); + } + } + + protected function prepareAddingOfRequirement( + ElementInterface $element, + \SimpleXMLElement $xml + ): void { + $scaffold = $this->addScaffoldAndMark($element, 'requirement'); + + foreach ($xml->Type->OperatingSystem as $os_xml) { + $orc_scaffold = $this->addScaffoldAndMark($scaffold, 'orComposite'); + $this->prepareAddingOfVocabulary('type', 'operating system', $orc_scaffold); + + $name = (string) $os_xml->attributes()->Name; + if ($name === 'MacOS') { + $name = 'macos'; + } + $this->prepareAddingOfVocabulary( + 'name', + $name, + $orc_scaffold + ); + + $min_version = (string) ($os_xml->attributes()->MinimumVersion ?? ''); + $max_version = (string) ($os_xml->attributes()->MaximumVersion ?? ''); + if ($min_version !== '') { + $this->addScaffoldAndMark($orc_scaffold, 'minimumVersion', $min_version); + } + if ($max_version !== '') { + $this->addScaffoldAndMark($orc_scaffold, 'maximumVersion', $max_version); + } + } + + foreach ($xml->Type->Browser as $browser_xml) { + $orc_scaffold = $this->addScaffoldAndMark($scaffold, 'orComposite'); + $this->prepareAddingOfVocabulary('type', 'browser', $orc_scaffold); + + $name = (string) $browser_xml->attributes()->Name; + if ($name !== 'Mozilla') { + $this->prepareAddingOfVocabulary( + 'name', + strtolower((string) $browser_xml->attributes()->Name), + $orc_scaffold + ); + } + + $min_version = (string) ($browser_xml->attributes()->MinimumVersion ?? ''); + $max_version = (string) ($browser_xml->attributes()->MaximumVersion ?? ''); + if ($min_version !== '') { + $this->addScaffoldAndMark($orc_scaffold, 'minimumVersion', $min_version); + } + if ($max_version !== '') { + $this->addScaffoldAndMark($orc_scaffold, 'maximumVersion', $max_version); + } + } + } + + protected function prepareAddingOfLangstring( + string $name, + ElementInterface $element, + \SimpleXMLElement $xml + ): void { + $language = (string) $xml->attributes()->Language; + $string = (string) $xml; + + $scaffold = $this->addScaffoldAndMark($element, $name); + $this->addScaffoldAndMark($scaffold, 'language', $language); + if ($string !== '') { + $this->addScaffoldAndMark($scaffold, 'string', $string); + } + } + + protected function prepareAddingOfVocabulary( + string $name, + string $value, + ElementInterface $element, + bool $fill_spaces_in_value = false + ): void { + $value = $this->transformVocabValue($value, $fill_spaces_in_value); + + $scaffold = $this->addScaffoldAndMark($element, $name); + $this->addScaffoldAndMark($scaffold, 'source', 'LOMv1.0'); + $this->addScaffoldAndMark($scaffold, 'value', $value); + } + + protected function prepareAddingOfIdentifier( + ElementInterface $element, + \SimpleXMLElement $xml + ): void { + $catalog = (string) ($xml->attributes()->Catalog ?? ''); + $entry = (string) ($xml->attributes()->Entry ?? ''); + + $scaffold = $this->addScaffoldAndMark($element, 'identifier'); + if ($catalog !== '') { + $this->addScaffoldAndMark($scaffold, 'catalog', $catalog); + } + if ($entry !== '') { + $this->addScaffoldAndMark($scaffold, 'entry', $entry); + } + } + + protected function prepareAddingOfContribute( + ElementInterface $element, + \SimpleXMLElement $xml + ): void { + $role = (string) ($xml->attributes()->Role ?? ''); + $date = (string) $xml->Date; + + $scaffold = $this->addScaffoldAndMark($element, 'contribute'); + $this->prepareAddingOfVocabulary('role', $role, $scaffold); + foreach ($xml->Entity as $entity_xml) { + $this->addScaffoldAndMark($scaffold, 'entity', (string) $entity_xml); + } + if ($date !== '') { + $date_scaffold = $this->addScaffoldAndMark($scaffold, 'date'); + $this->addScaffoldAndMark($date_scaffold, 'dateTime', $date); + } + } + + protected function addScaffoldAndMark( + ElementInterface $to_element, + string $name, + string $value = '' + ): ElementInterface { + $scaffold = $to_element->addScaffoldToSubElements($this->scaffold_provider, $name); + $scaffold->mark($this->marker_factory, Action::CREATE_OR_UPDATE, $value); + return $scaffold; + } + + protected function transformVocabValue(string $value, bool $fill_spaces = false): string + { + $value = $this->camelCaseToSpaces($value); + + if ($fill_spaces) { + $value = str_replace(' ', '', $value); + } + + return $value; + } + + protected function camelCaseToSpaces(string $string): string + { + $string = preg_replace('/(?<=[a-z])(?=[A-Z])/', ' ', $string); + return strtolower($string); + } +} diff --git a/components/ILIAS/MetaData/classes/XML/Reader/Standard/Standard.php b/components/ILIAS/MetaData/classes/XML/Reader/Standard/Standard.php new file mode 100644 index 000000000000..d440ff08a59d --- /dev/null +++ b/components/ILIAS/MetaData/classes/XML/Reader/Standard/Standard.php @@ -0,0 +1,53 @@ +structurally_coupled = $structurally_coupled; + $this->legacy = $legacy; + } + + public function read( + \SimpleXMLElement $xml, + Version $version + ): SetInterface { + switch ($version) { + case Version::V4_1_0: + return $this->legacy->read($xml, $version); + + case Version::V10_0: + default: + return $this->structurally_coupled->read($xml, $version); + } + } +} diff --git a/components/ILIAS/MetaData/classes/XML/Reader/Standard/StructurallyCoupled.php b/components/ILIAS/MetaData/classes/XML/Reader/Standard/StructurallyCoupled.php new file mode 100644 index 000000000000..7fb9b6b5468b --- /dev/null +++ b/components/ILIAS/MetaData/classes/XML/Reader/Standard/StructurallyCoupled.php @@ -0,0 +1,210 @@ +marker_factory = $marker_factory; + $this->scaffold_provider = $scaffold_provider; + $this->dictionary = $dictionary; + $this->copyright_handler = $copyright_handler; + } + + /** + * Assumes that the structure of the xml is identical to the structure of + * LOM in ILIAS, with exceptions defined in the dictionary. + */ + public function read( + \SimpleXMLElement $xml, + Version $version + ): SetInterface { + $set = $this->scaffold_provider->set(); + $root_element = $set->getRoot(); + + if ($xml->getName() !== $root_element->getDefinition()->name()) { + throw new \ilMDXMLException( + $xml->getName() . ' is not the correct root element, should be ' . + $root_element->getDefinition()->name() + ); + } + + $this->prepareAddingSubElementsFromXML( + $version, + $root_element, + $this->dictionary->tagForElement($root_element, $version), + $xml + ); + + return $set; + } + + protected function prepareAddingSubElementsFromXML( + Version $version, + ElementInterface $element, + ?TagInterface $tag, + \SimpleXMLElement $xml, + int $depth = 0 + ): void { + if ($depth > 30) { + throw new \ilMDXMLException('LOM XML is nested too deep.'); + } + + if ($tag?->isExportedAsLangString()) { + $this->prepareAddingLangStringFromXML($version, $element, $xml); + return; + } + + $children_and_attributes = new \AppendIterator(); + if (!empty($children = $xml->children())) { + $children_and_attributes->append($children); + } + if (!empty($attributes = $xml->attributes())) { + $children_and_attributes->append($attributes); + } + /** @var \SimpleXMLElement $child_or_attrib_xml */ + foreach ($children_and_attributes as $child_or_attrib_xml) { + $sub_scaffold = $element->addScaffoldToSubElements( + $this->scaffold_provider, + $child_or_attrib_xml->getName(), + ); + if (is_null($sub_scaffold)) { + continue; + } + + $sub_tag = $this->dictionary->tagForElement($sub_scaffold, $version); + if ($sub_tag?->isOmitted()) { + continue; + } + + $sub_value = $this->parseElementValue( + $sub_scaffold->getDefinition(), + $sub_tag, + (string) $child_or_attrib_xml + ); + $sub_scaffold->mark($this->marker_factory, Action::CREATE_OR_UPDATE, $sub_value); + + $this->prepareAddingSubElementsFromXML( + $version, + $sub_scaffold, + $sub_tag, + $child_or_attrib_xml, + $depth + 1 + ); + } + } + + protected function prepareAddingLangStringFromXML( + Version $version, + ElementInterface $element, + \SimpleXMLElement $xml, + ): void { + $string_xml = $xml->string; + $language_xml = $string_xml->attributes()->language; + + if (!empty($string_xml) && ((string) $string_xml) !== '') { + $string_element = $element->addScaffoldToSubElements( + $this->scaffold_provider, + 'string' + ); + $string_element->mark( + $this->marker_factory, + Action::CREATE_OR_UPDATE, + $this->parseElementValue( + $string_element->getDefinition(), + $this->dictionary->tagForElement($string_element, $version), + (string) $string_xml + ) + ); + } + + if (!empty($language_xml)) { + $language_element = $element->addScaffoldToSubElements( + $this->scaffold_provider, + 'language' + ); + $language_element->mark( + $this->marker_factory, + Action::CREATE_OR_UPDATE, + $this->parseElementValue( + $language_element->getDefinition(), + $this->dictionary->tagForElement($language_element, $version), + (string) $language_xml + ) + ); + } + } + + protected function parseElementValue( + DefinitionInterface $definition, + ?TagInterface $tag, + string $value + ): string { + $value = strip_tags($value); + + if ($tag?->isTranslatedAsCopyright()) { + return $this->copyright_handler->copyrightFromExport($value); + } + + switch ($definition->dataType()) { + case Type::NULL: + return ''; + + case Type::LANG: + if ($value === 'none') { + return 'xx'; + } + return $value; + + case Type::STRING: + case Type::VOCAB_SOURCE: + case Type::VOCAB_VALUE: + case Type::DATETIME: + case Type::NON_NEG_INT: + case Type::DURATION: + default: + return $value; + } + } +} diff --git a/components/ILIAS/MetaData/classes/XML/Services/Services.php b/components/ILIAS/MetaData/classes/XML/Services/Services.php new file mode 100644 index 000000000000..d64c1f5a8c7b --- /dev/null +++ b/components/ILIAS/MetaData/classes/XML/Services/Services.php @@ -0,0 +1,98 @@ +path_services = $path_services; + $this->structure_services = $structure_services; + $this->manipulator_services = $manipulator_services; + } + + public function standardWriter(): WriterInterface + { + if (isset($this->standard_writer)) { + return $this->standard_writer; + } + $dictionary = (new LOMDictionaryInitiator( + new TagFactory(), + $this->path_services->pathFactory(), + $this->structure_services->structure() + ))->get(); + return $this->standard_writer = new StandardWriter( + $dictionary, + new CopyrightHandler() + ); + } + + public function standardReader(): ReaderInterface + { + if (isset($this->standard_reader)) { + return $this->standard_reader; + } + $dictionary = (new LOMDictionaryInitiator( + new TagFactory(), + $this->path_services->pathFactory(), + $this->structure_services->structure() + ))->get(); + $marker_factory = new MarkerFactory(); + $copyright_handler = new CopyrightHandler(); + return $this->standard_reader = new StandardReader( + new StructurallyCoupled( + $marker_factory, + $this->manipulator_services->scaffoldProvider(), + $dictionary, + $copyright_handler + ), + new Legacy( + $marker_factory, + $this->manipulator_services->scaffoldProvider(), + $copyright_handler + ) + ); + } +} diff --git a/components/ILIAS/MetaData/classes/XML/Version.php b/components/ILIAS/MetaData/classes/XML/Version.php new file mode 100644 index 000000000000..2d2118bab798 --- /dev/null +++ b/components/ILIAS/MetaData/classes/XML/Version.php @@ -0,0 +1,27 @@ +dictionary = $dictionary; + $this->copyright_handler = $copyright_handler; + } + + public function write(SetInterface $set): \SimpleXMLElement + { + $root = $set->getRoot(); + $root_name = $root->getDefinition()->name(); + $xml = new \SimpleXMLElement('<' . $root_name . '>'); + + $this->addSubElementsToXML( + $root, + $this->getTagForElement($root), + $xml + ); + + return $xml; + } + + protected function addSubElementsToXML( + ElementInterface $element, + ?TagInterface $tag, + \SimpleXMLElement $xml, + int $depth = 0 + ): void { + if ($depth > 30) { + throw new \ilMDXMLException('LOM set is nested too deep.'); + } + + if ($tag?->isExportedAsLangString()) { + $this->addLangStringToXML($element, $xml); + return; + } + + foreach ($element->getSubElements() as $sub_element) { + $sub_tag = $this->getTagForElement($sub_element); + $sub_name = $sub_element->getDefinition()->name(); + $sub_value = $this->getDataValue($sub_element->getData(), $sub_tag); + + if ($sub_tag?->isOmitted()) { + continue; + } + + if ($sub_tag?->isExportedAsAttribute()) { + $xml->addAttribute($sub_name, (string) $sub_value); + continue; + } + + $child_xml = $xml->addChild($sub_name, $sub_value); + $this->addSubElementsToXML($sub_element, $sub_tag, $child_xml, $depth + 1); + } + } + + protected function addLangStringToXML( + ElementInterface $element, + \SimpleXMLElement $xml + ): void { + $string_element = null; + $language_element = null; + foreach ($element->getSubElements() as $sub_element) { + if ($sub_element->getDefinition()->name() === 'string') { + $string_element = $sub_element; + } elseif ($sub_element->getDefinition()->name() === 'language') { + $language_element = $sub_element; + } + } + + $string_value = ''; + if (!is_null($string_element)) { + $string_value = $this->getDataValue( + $string_element->getData(), + $this->getTagForElement($string_element) + ); + } + $string_xml = $xml->addChild( + 'string', + $string_value + ); + + if (is_null($language_element)) { + return; + } + $language_value = $this->getDataValue( + $language_element->getData(), + $this->getTagForElement($language_element) + ); + $string_xml->addAttribute( + 'language', + $language_value + ); + } + + protected function getDataValue( + DataInterface $data, + ?TagInterface $tag + ): ?string { + if ($tag?->isTranslatedAsCopyright()) { + return $this->copyright_handler->copyrightForExport($data->value()); + } + + switch ($data->type()) { + case Type::NULL: + return null; + + case Type::LANG: + $value = $data->value(); + if ($value === 'xx') { + return 'none'; + } + return $value; + + case Type::STRING: + case Type::VOCAB_SOURCE: + case Type::VOCAB_VALUE: + case Type::DATETIME: + case Type::NON_NEG_INT: + case Type::DURATION: + default: + return $data->value(); + } + } + + protected function getTagForElement(ElementInterface $element): ?TagInterface + { + return $this->dictionary->tagForElement($element, $this->currentVersion()); + } + + protected function currentVersion(): Version + { + return Version::V10_0; + } +} diff --git a/components/ILIAS/MetaData/classes/XML/Writer/WriterInterface.php b/components/ILIAS/MetaData/classes/XML/Writer/WriterInterface.php new file mode 100644 index 000000000000..4e4e73185e05 --- /dev/null +++ b/components/ILIAS/MetaData/classes/XML/Writer/WriterInterface.php @@ -0,0 +1,34 @@ + * @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 { diff --git a/components/ILIAS/MetaData/classes/class.ilMDCreator.php b/components/ILIAS/MetaData/classes/class.ilMDCreator.php index 354c80f4492b..11be25c48e8d 100755 --- a/components/ILIAS/MetaData/classes/class.ilMDCreator.php +++ b/components/ILIAS/MetaData/classes/class.ilMDCreator.php @@ -30,6 +30,7 @@ * @package ilias-core * @author Stefan Meyer * @version $Id$ + * @deprecated will be removed with ILIAS 11, please use the new API (see {@see ../docs/api.md}) */ class ilMDCreator { diff --git a/components/ILIAS/MetaData/classes/class.ilMDDescription.php b/components/ILIAS/MetaData/classes/class.ilMDDescription.php index 962387e0e258..b22960a16190 100755 --- a/components/ILIAS/MetaData/classes/class.ilMDDescription.php +++ b/components/ILIAS/MetaData/classes/class.ilMDDescription.php @@ -22,6 +22,7 @@ * Meta Data class (element description) * @package ilias-core * @version $Id$ + * @deprecated will be removed with ILIAS 11, please use the new API (see {@see ../docs/api.md}) */ class ilMDDescription extends ilMDBase { diff --git a/components/ILIAS/MetaData/classes/class.ilMDEducational.php b/components/ILIAS/MetaData/classes/class.ilMDEducational.php index 992c64149226..57220e662335 100755 --- a/components/ILIAS/MetaData/classes/class.ilMDEducational.php +++ b/components/ILIAS/MetaData/classes/class.ilMDEducational.php @@ -22,6 +22,7 @@ * Meta Data class (element educational) * @package ilias-core * @version $Id$ + * @deprecated will be removed with ILIAS 11, please use the new API (see {@see ../docs/api.md}) */ class ilMDEducational extends ilMDBase { diff --git a/components/ILIAS/MetaData/classes/class.ilMDEntity.php b/components/ILIAS/MetaData/classes/class.ilMDEntity.php index e90c1728c16c..aedb0f35e3c5 100755 --- a/components/ILIAS/MetaData/classes/class.ilMDEntity.php +++ b/components/ILIAS/MetaData/classes/class.ilMDEntity.php @@ -23,6 +23,7 @@ * @author Stefan Meyer * @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 { diff --git a/components/ILIAS/MetaData/classes/class.ilMDFormat.php b/components/ILIAS/MetaData/classes/class.ilMDFormat.php index 5f95c3dc223b..d4dbbe318872 100755 --- a/components/ILIAS/MetaData/classes/class.ilMDFormat.php +++ b/components/ILIAS/MetaData/classes/class.ilMDFormat.php @@ -23,6 +23,7 @@ * @author Stefan Meyer * @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 { diff --git a/components/ILIAS/MetaData/classes/class.ilMDGeneral.php b/components/ILIAS/MetaData/classes/class.ilMDGeneral.php index 97d5a1caf957..bb74bfeaec70 100755 --- a/components/ILIAS/MetaData/classes/class.ilMDGeneral.php +++ b/components/ILIAS/MetaData/classes/class.ilMDGeneral.php @@ -23,6 +23,7 @@ * @author Stefan Meyer * @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 { diff --git a/components/ILIAS/MetaData/classes/class.ilMDIdentifier.php b/components/ILIAS/MetaData/classes/class.ilMDIdentifier.php index 1e2e7d77a102..8f3ed533c6ec 100755 --- a/components/ILIAS/MetaData/classes/class.ilMDIdentifier.php +++ b/components/ILIAS/MetaData/classes/class.ilMDIdentifier.php @@ -22,6 +22,7 @@ * Meta Data class (element identifier) * @package ilias-core * @version $Id$ + * @deprecated will be removed with ILIAS 11, please use the new API (see {@see ../docs/api.md}) */ class ilMDIdentifier extends ilMDBase { diff --git a/components/ILIAS/MetaData/classes/class.ilMDIdentifier_.php b/components/ILIAS/MetaData/classes/class.ilMDIdentifier_.php index a35cad6bc86f..ac0d4a5c79e0 100755 --- a/components/ILIAS/MetaData/classes/class.ilMDIdentifier_.php +++ b/components/ILIAS/MetaData/classes/class.ilMDIdentifier_.php @@ -22,6 +22,7 @@ * Meta Data class (element identifier_) * @package ilias-core * @version $Id$ + * @deprecated will be removed with ILIAS 11, please use the new API (see {@see ../docs/api.md}) */ class ilMDIdentifier_ extends ilMDBase { diff --git a/components/ILIAS/MetaData/classes/class.ilMDKeyword.php b/components/ILIAS/MetaData/classes/class.ilMDKeyword.php index da1d5a69503a..2e4fe94d59b5 100755 --- a/components/ILIAS/MetaData/classes/class.ilMDKeyword.php +++ b/components/ILIAS/MetaData/classes/class.ilMDKeyword.php @@ -22,6 +22,7 @@ * Meta Data class (element keyword) * @package ilias-core * @version $Id$ + * @deprecated will be removed with ILIAS 11, please use the new API (see {@see ../docs/api.md}) */ class ilMDKeyword extends ilMDBase { diff --git a/components/ILIAS/MetaData/classes/class.ilMDLanguage.php b/components/ILIAS/MetaData/classes/class.ilMDLanguage.php index 46ddc9eec1bb..79fabe5434a0 100755 --- a/components/ILIAS/MetaData/classes/class.ilMDLanguage.php +++ b/components/ILIAS/MetaData/classes/class.ilMDLanguage.php @@ -22,6 +22,7 @@ * Meta Data class (element language) * @package ilias-core * @version $Id$ + * @deprecated will be removed with ILIAS 11, please use the new API (see {@see ../docs/api.md}) */ class ilMDLanguage extends ilMDBase { diff --git a/components/ILIAS/MetaData/classes/class.ilMDLanguageElement.php b/components/ILIAS/MetaData/classes/class.ilMDLanguageElement.php index 5fc2facc9648..88dbdbbaac49 100755 --- a/components/ILIAS/MetaData/classes/class.ilMDLanguageElement.php +++ b/components/ILIAS/MetaData/classes/class.ilMDLanguageElement.php @@ -27,6 +27,7 @@ * Meta Data class Language codes and translations * @package ilias-core * @version $Id$ + * @deprecated will be removed with ILIAS 11, please use the new API (see {@see ../docs/api.md}) */ class ilMDLanguageElement { diff --git a/components/ILIAS/MetaData/classes/class.ilMDLanguageItem.php b/components/ILIAS/MetaData/classes/class.ilMDLanguageItem.php index 649e3e12ca1c..14badb0ed6b3 100755 --- a/components/ILIAS/MetaData/classes/class.ilMDLanguageItem.php +++ b/components/ILIAS/MetaData/classes/class.ilMDLanguageItem.php @@ -27,6 +27,7 @@ * Meta Data class Language codes and translations * @package ilias-core * @version $Id$ + * @deprecated will be removed with ILIAS 11, please use the new API (see {@see ../docs/api.md}) */ class ilMDLanguageItem { diff --git a/components/ILIAS/MetaData/classes/class.ilMDLifecycle.php b/components/ILIAS/MetaData/classes/class.ilMDLifecycle.php index 61dd0c169f41..d5a15b1b8c2e 100755 --- a/components/ILIAS/MetaData/classes/class.ilMDLifecycle.php +++ b/components/ILIAS/MetaData/classes/class.ilMDLifecycle.php @@ -23,6 +23,7 @@ * @author Stefan Meyer * @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 { diff --git a/components/ILIAS/MetaData/classes/class.ilMDLocation.php b/components/ILIAS/MetaData/classes/class.ilMDLocation.php index 83608af27843..63260ad8516e 100755 --- a/components/ILIAS/MetaData/classes/class.ilMDLocation.php +++ b/components/ILIAS/MetaData/classes/class.ilMDLocation.php @@ -22,6 +22,7 @@ * Meta Data class (element location) * @package ilias-core * @version $Id$ + * @deprecated will be removed with ILIAS 11, please use the new API (see {@see ../docs/api.md}) */ class ilMDLocation extends ilMDBase { diff --git a/components/ILIAS/MetaData/classes/class.ilMDMetaMetadata.php b/components/ILIAS/MetaData/classes/class.ilMDMetaMetadata.php index 54b08774651e..247e579c3aa5 100755 --- a/components/ILIAS/MetaData/classes/class.ilMDMetaMetadata.php +++ b/components/ILIAS/MetaData/classes/class.ilMDMetaMetadata.php @@ -22,6 +22,7 @@ * Meta Data class (element meta_data) * @package ilias-core * @version $Id$ + * @deprecated will be removed with ILIAS 11, please use the new API (see {@see ../docs/api.md}) */ class ilMDMetaMetadata extends ilMDBase { diff --git a/components/ILIAS/MetaData/classes/class.ilMDOrComposite.php b/components/ILIAS/MetaData/classes/class.ilMDOrComposite.php index a6f5e3929dcf..79464e860f70 100755 --- a/components/ILIAS/MetaData/classes/class.ilMDOrComposite.php +++ b/components/ILIAS/MetaData/classes/class.ilMDOrComposite.php @@ -23,6 +23,7 @@ * Extends MDRequirement * @package ilias-core * @version $Id$ + * @deprecated will be removed with ILIAS 11, please use the new API (see {@see ../docs/api.md}) */ class ilMDOrComposite extends ilMDRequirement { diff --git a/components/ILIAS/MetaData/classes/class.ilMDRelation.php b/components/ILIAS/MetaData/classes/class.ilMDRelation.php index a9dae4137bcc..716f3aeefcc1 100755 --- a/components/ILIAS/MetaData/classes/class.ilMDRelation.php +++ b/components/ILIAS/MetaData/classes/class.ilMDRelation.php @@ -22,6 +22,7 @@ * Meta Data class (element relation) * @package ilias-core * @version $Id$ + * @deprecated will be removed with ILIAS 11, please use the new API (see {@see ../docs/api.md}) */ class ilMDRelation extends ilMDBase { diff --git a/components/ILIAS/MetaData/classes/class.ilMDRequirement.php b/components/ILIAS/MetaData/classes/class.ilMDRequirement.php index d7a712093d22..8c6510ac9462 100755 --- a/components/ILIAS/MetaData/classes/class.ilMDRequirement.php +++ b/components/ILIAS/MetaData/classes/class.ilMDRequirement.php @@ -22,6 +22,7 @@ * Meta Data class (element requirement) * @package ilias-core * @version $Id$ + * @deprecated will be removed with ILIAS 11, please use the new API (see {@see ../docs/api.md}) */ class ilMDRequirement extends ilMDBase { diff --git a/components/ILIAS/MetaData/classes/class.ilMDRights.php b/components/ILIAS/MetaData/classes/class.ilMDRights.php index 72e039c4051a..73b0ac36718b 100755 --- a/components/ILIAS/MetaData/classes/class.ilMDRights.php +++ b/components/ILIAS/MetaData/classes/class.ilMDRights.php @@ -22,6 +22,7 @@ * Meta Data class (element rights) * @package ilias-core * @version $Id$ + * @deprecated will be removed with ILIAS 11, please use the new API (see {@see ../docs/api.md}) */ class ilMDRights extends ilMDBase { diff --git a/components/ILIAS/MetaData/classes/class.ilMDSaxParser.php b/components/ILIAS/MetaData/classes/class.ilMDSaxParser.php index ef42cc75b7c8..c65ae337208e 100755 --- a/components/ILIAS/MetaData/classes/class.ilMDSaxParser.php +++ b/components/ILIAS/MetaData/classes/class.ilMDSaxParser.php @@ -25,6 +25,7 @@ * 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 { diff --git a/components/ILIAS/MetaData/classes/class.ilMDTaxon.php b/components/ILIAS/MetaData/classes/class.ilMDTaxon.php index 4d93556bc322..48b81b6033e1 100755 --- a/components/ILIAS/MetaData/classes/class.ilMDTaxon.php +++ b/components/ILIAS/MetaData/classes/class.ilMDTaxon.php @@ -22,6 +22,7 @@ * Meta Data class (element taxon) * @package ilias-core * @version $Id$ + * @deprecated will be removed with ILIAS 11, please use the new API (see {@see ../docs/api.md}) */ class ilMDTaxon extends ilMDBase { diff --git a/components/ILIAS/MetaData/classes/class.ilMDTaxonPath.php b/components/ILIAS/MetaData/classes/class.ilMDTaxonPath.php index eb08b21548b9..475274190a19 100755 --- a/components/ILIAS/MetaData/classes/class.ilMDTaxonPath.php +++ b/components/ILIAS/MetaData/classes/class.ilMDTaxonPath.php @@ -22,6 +22,7 @@ * Meta Data class (element taxon_path) * @package ilias-core * @version $Id$ + * @deprecated will be removed with ILIAS 11, please use the new API (see {@see ../docs/api.md}) */ class ilMDTaxonPath extends ilMDBase { diff --git a/components/ILIAS/MetaData/classes/class.ilMDTechnical.php b/components/ILIAS/MetaData/classes/class.ilMDTechnical.php index 08742e63d456..d3fac550f1a4 100755 --- a/components/ILIAS/MetaData/classes/class.ilMDTechnical.php +++ b/components/ILIAS/MetaData/classes/class.ilMDTechnical.php @@ -22,6 +22,7 @@ * Meta Data class (element technical) * @package ilias-core * @version $Id$ + * @deprecated will be removed with ILIAS 11, please use the new API (see {@see ../docs/api.md}) */ class ilMDTechnical extends ilMDBase { diff --git a/components/ILIAS/MetaData/classes/class.ilMDTypicalAgeRange.php b/components/ILIAS/MetaData/classes/class.ilMDTypicalAgeRange.php index a1e7093c42ae..b83e8856ad9b 100755 --- a/components/ILIAS/MetaData/classes/class.ilMDTypicalAgeRange.php +++ b/components/ILIAS/MetaData/classes/class.ilMDTypicalAgeRange.php @@ -22,6 +22,7 @@ * Meta Data class (element typicalagerange) * @package ilias-core * @version $Id$ + * @deprecated will be removed with ILIAS 11, please use the new API (see {@see ../docs/api.md}) */ class ilMDTypicalAgeRange extends ilMDBase { diff --git a/components/ILIAS/MetaData/classes/class.ilMDUtilSelect.php b/components/ILIAS/MetaData/classes/class.ilMDUtilSelect.php index 93df324d6d56..094ad5406424 100755 --- a/components/ILIAS/MetaData/classes/class.ilMDUtilSelect.php +++ b/components/ILIAS/MetaData/classes/class.ilMDUtilSelect.php @@ -23,6 +23,7 @@ * @author Stefan Meyer * @package ilias-core * @version $Id$ + * @deprecated will be removed with ILIAS 11, please use the new API (see {@see ../docs/api.md}) */ class ilMDUtilSelect { diff --git a/components/ILIAS/MetaData/classes/class.ilMDUtils.php b/components/ILIAS/MetaData/classes/class.ilMDUtils.php index f3736b2fee17..059f4165ee1d 100755 --- a/components/ILIAS/MetaData/classes/class.ilMDUtils.php +++ b/components/ILIAS/MetaData/classes/class.ilMDUtils.php @@ -23,6 +23,7 @@ * @author Stefan Meyer * @package ilias-core * @version $Id$ + * @deprecated will be removed with ILIAS 11, please use the new API (see {@see ../docs/api.md}) */ class ilMDUtils { diff --git a/components/ILIAS/MetaData/classes/class.ilMDXMLCopier.php b/components/ILIAS/MetaData/classes/class.ilMDXMLCopier.php index de786387614a..13d1a6be425d 100755 --- a/components/ILIAS/MetaData/classes/class.ilMDXMLCopier.php +++ b/components/ILIAS/MetaData/classes/class.ilMDXMLCopier.php @@ -22,6 +22,7 @@ * @package ilias-core * @author Stefan Meyer * @version $Id$ + * @deprecated will be removed with ILIAS 11, LOM should only be exported as a tail dependency */ class ilMDXMLCopier extends ilMDSaxParser { diff --git a/components/ILIAS/MetaData/classes/class.ilMetaDataExporter.php b/components/ILIAS/MetaData/classes/class.ilMetaDataExporter.php index 01cd7f031d3a..17a6d4e98c99 100755 --- a/components/ILIAS/MetaData/classes/class.ilMetaDataExporter.php +++ b/components/ILIAS/MetaData/classes/class.ilMetaDataExporter.php @@ -18,26 +18,41 @@ declare(strict_types=1); -/** - * Exporter class for meta data - * @author Alex Killing - * @version $Id: $ - * @ingroup ServicesMetaData - */ +use ILIAS\MetaData\Services\InternalServices; +use ILIAS\MetaData\XML\Writer\WriterInterface as StandardXMLWriter; +use ILIAS\MetaData\Repository\RepositoryInterface; + class ilMetaDataExporter extends ilXmlExporter { + protected StandardXMLWriter $writer; + protected RepositoryInterface $repository; + public function init(): void { + global $DIC; + + $services = new InternalServices($DIC); + + $this->writer = $services->xml()->standardWriter(); + $this->repository = $services->repository()->repository(); } public function getXmlRepresentation(string $a_entity, string $a_schema_version, string $a_id): string { $id = explode(":", $a_id); - $mdxml = new ilMD2XML((int) $id[0], (int) $id[1], (string) $id[2]); - $mdxml->setExportMode(); - $mdxml->startExport(); - return $mdxml->getXML(); + $obj_id = (int) $id[0]; + $sub_id = (int) $id[1]; + $type = (string) $id[2]; + + if ($sub_id === 0) { + $sub_id = $obj_id; + } + + $md = $this->repository->getMD($obj_id, $sub_id, $type); + $xml = $this->writer->write($md); + + return trim(str_replace('', '', $xml->asXML())); } /** @@ -48,13 +63,19 @@ public function getXmlRepresentation(string $a_entity, string $a_schema_version, */ public function getValidSchemaVersions(string $a_entity): array { - return array( - "4.1.0" => array( - "namespace" => "http://www.ilias.de/Services/MetaData/meta/4_1", - "xsd_file" => "ilias_meta_4_1.xsd", - "min" => "4.1.0", + return [ + "10.0" => [ + "namespace" => "http://www.ilias.de/Services/MetaData/md/10_0", + "xsd_file" => "ilias_md_10_0.xsd", + "min" => "10.0", "max" => "" - ) - ); + ], + "4.1.0" => [ + "namespace" => "http://www.ilias.de/Services/MetaData/md/4_1", + "xsd_file" => "ilias_md_4_1.xsd", + "min" => "4.1.0", + "max" => "9.99" + ] + ]; } } diff --git a/components/ILIAS/MetaData/classes/class.ilMetaDataImporter.php b/components/ILIAS/MetaData/classes/class.ilMetaDataImporter.php index 8a21197df775..87a81cb9337a 100755 --- a/components/ILIAS/MetaData/classes/class.ilMetaDataImporter.php +++ b/components/ILIAS/MetaData/classes/class.ilMetaDataImporter.php @@ -1,16 +1,45 @@ - * @version $Id: $ - * @ingroup components\ILIASMediaPool - */ +use ILIAS\MetaData\Services\InternalServices; +use ILIAS\MetaData\XML\Reader\ReaderInterface as StandardXMLReader; +use ILIAS\MetaData\Repository\RepositoryInterface; +use ILIAS\MetaData\XML\Version; + class ilMetaDataImporter extends ilXmlImporter { + protected StandardXMLReader $reader; + protected RepositoryInterface $repository; + protected ilLogger $logger; + + public function init(): void + { + global $DIC; + + $services = new InternalServices($DIC); + + $this->reader = $services->xml()->standardReader(); + $this->repository = $services->repository()->repository(); + $this->logger = $DIC->logger()->meta(); + } + public function importXmlRepresentation( string $a_entity, string $a_id, @@ -19,10 +48,36 @@ public function importXmlRepresentation( ): void { $new_id = $a_mapping->getMapping("components/ILIAS/MetaData", "md", $a_id); - if (is_string($new_id) && $new_id !== "") { - $id = explode(":", $new_id); - $xml_copier = new ilMDXMLCopier($a_xml, (int) $id[0], (int) $id[1], $id[2]); - $xml_copier->startParsing(); + if (!is_string($new_id) || $new_id === "") { + $this->logger->error( + 'Import of LOM aborted for ' . $new_id . ', ID mapping failed.' + ); + return; + } + + $id = explode(":", $new_id); + + $obj_id = (int) $id[0]; + $sub_id = (int) $id[1]; + $type = (string) $id[2]; + + if ($sub_id === 0) { + $sub_id = $obj_id; + } + + $version = Version::tryFrom($this->getSchemaVersion()); + if (is_null($version)) { + $this->logger->error( + 'Import of LOM aborted for ' . $new_id . + ', invalid schema version: ' . $this->getSchemaVersion() + ); + return; } + + $md = $this->reader->read( + new SimpleXMLElement($a_xml), + $version + ); + $this->repository->transferMD($md, $obj_id, $sub_id, $type, false); } } diff --git a/components/ILIAS/MetaData/docs/api.md b/components/ILIAS/MetaData/docs/api.md index 9631e0bab0b1..220b7f16d1d3 100755 --- a/components/ILIAS/MetaData/docs/api.md +++ b/components/ILIAS/MetaData/docs/api.md @@ -4,13 +4,23 @@ missing or wrong information using the [ILIAS issue tracker](https://mantis.ilias.de) or contribute a fix via [Pull Request](../../../docs/development/contributing.md#pull-request-to-the-repositories). -`Services\Metadata` offers an API with which the [Learning Object Metadata +The `Metadata` component offers an API with which the [Learning Object Metadata (LOM)](lom_structure.md) of ILIAS objects can be read out, processed, and manipulated. It can be obtained from the `DIC` via the method `learningObjectMetadata`. -The API offers four different sub-services. In the following, we will -explain what they offer and how they can be used. +In the following, we will explain what functionality the API offers, +and how it can be used. + +## Contents + +1. [`read`](#read) +2. [`search`](#search) +3. [`manipulate`](#manipulate) +4. [`derive`](#derive) +5. [`deleteAll`](#deleteall) +6. [`paths`](#paths) +7. [`dataHelper`](#datahelper) ## `read` @@ -18,11 +28,17 @@ With `read`, one can read out the LOM of a specific ILIAS object. When calling `read`, the object whose metadata one wants to read out needs to be identified by a triple of IDs as explained [here](identifying_objects.md). -Optionally, one can also specify metadata elements via a [path](#paths). +Optionally, one can in addition specify metadata elements via a [path](#paths). In this case, not the whole metadata set is read out, but only the -elements on the path along with all sub-elements of its last element. +elements on the path. If the path ends at an element that has +sub-elements, reading continues recursively. + +> Beware that when restricting `read` to a path, filters on the path +are ignored. Further, if the path contains steps to super elements, +it is only followed down to the first element that the path returns +to (see [here](#paths) for details). -`read` returns a `Reader` object, which can then be used to access the +`read` returns a `Reader`, which can then be used to access the values of different elements in the (partial) set, selected via [paths](#paths). These values are returned as data objects, containing the actual value as a string, and its data type (see [here](lom_structure.md) for details @@ -31,13 +47,12 @@ is consistent. To further process the values, see the [data helper](#datahelper). Note that the `Reader` returns null data objects for elements not -carrying any data according to the [LOM standard](lom_structure.md), and when -requesting the `firstData` of an element that does not exist at all -in the set of the current ILIAS object. +carrying any data, and when requesting the `firstData` of an element +that does not exist at all in the set of the current ILIAS object. ### Examples -To read out the title of a course with `obj_id` 380, call `read` with +To read out the title of a Course with `obj_id` 380, call `read` with the [appropriate IDs](identifying_objects.md), and then `firstData` with the right [path](#paths). Since this returns a data object, one then has to extract the actual value with `value`: @@ -88,16 +103,268 @@ $title = $reader->firstData($lom->paths()->title())->value(); See [here](#paths) for details on custom paths. +## `search` + +`search` is used to find objects whose LOM matches some user-defined +specifications. When calling `search`, a `Searcher` is +returned. In the `Searcher`, search `Clauses` and `Filters` can be +assembled, and using them a search can be performed. In the search +results, objects are identified by a triple of ID as explained [here](identifying_objects.md). + +`Clauses` can be obtained in the `ClauseFactory` available through the +`Searcher`. Basic `Clauses` consist of a [path](#paths) to a LOM +element, a search `Mode`, and a value to search for. Additionally, the +`Mode` can be negated. Searching with just the basic `Clause` then +finds all objects whose LOM sets have at least one element specified +by the path, and whose value fulfills the condition given by search `Mode` +and value (or does not fulfill the condition, in the case +of the `Mode` being negated). + +The search does take into account path filters, with the exception +of those of type `INDEX`. For more information on paths, see [here](#paths). + +>The search is not built to check the (non-)existence of elements +which do not carry any value. Searches of this nature will not give +accurate or reliable results. + +Multiple `Clauses` can be joined logically into a single `Clause` +using `AND` or `OR` `Operators`. These joined `Clauses` then can further +be joined, such that arbitrarily complicated search criteria can be +assembled from the basic `Clauses`. + +Further, `Clauses` can be negated. Negating a basic `Clause` will then +lead to a search that finds objects with LOM sets that have **no** +elements that fulfill the conditions. Note that this can lead to different +results than negating the `Mode` of the basic `Clause`! Negating joined +`Clauses` will negate the whole assembled logical statement. + +Searches will return a `RessourceID` object for each search result, +and each `RessourceID` identifies an object in ILIAS by a [triple of IDs](identifying_objects.md). + +The `Searcher` can also generate `Filters`. These `Filters` can be passed +to the `execute` method in the `Searcher` in addition to a clause to restrict +the objects the search will return. Each `Filter` object carries the +same [triple of IDs](identifying_objects.md) as is returned by the search. Each ID can +either be a specific value, or a `Placeholder`. Using `Placeholders`, the +filter can be configured to allow either any value for an ID, or only allow +values that match the value of one of the other IDs. Multiple values in +the same filter are joined with a logical AND, and multiple filters in the same +search with a logical OR. + +Finally, a limit and offset can also be applied to the search. Both parameters +can be set independently of each other. The order of results returned by +the search is consistent, they are ordered by their IDs. + +>The search was built to be versatile, and is as such not particulary +well optimized for any specific task. If you have a use case for the +search that performs especially poorly, feel free to report that +in the [ILIAS issue tracker](https://mantis.ilias.de) or contribute a +possible improvement to the search via [Pull Request](../../../docs/development/contributing.md#pull-request-to-the-repositories). + +### Examples + +To find all objects in ILIAS that have a keyword with value 'Great Content', +build an according basic `Clause` and pass it to `Searcher::search` with +limit and offset set to `null`: + +```` +$clause = $lom->search()->getClauseFactory()->getBasicClause( + $lom->paths()->keywords(), + Mode::EQUALS, + 'Great Content' +); + +$results = $lom->search()->execute($clause, null, null); +```` + +By negating the search `Mode` in the basic `Clause` one can find e.g. +all objects in ILIAS that have a keyword that does not start with 'Great': + +```` +$clause = $lom->search()->getClauseFactory()->getBasicClause( + $lom->paths()->keywords(), + Mode::STARTS_WITH, + 'Great', + true +); + +$results = $lom->search()->execute($clause, null, null); +```` + +On the other hand, by negating the whole basic `Clause` one can find +e.g. all objects that have no keyword ending in 'Content'. Note that +because a LOM set can have multiple keywords, this is different to +searching for all objects that have a keyword that does **not** end with +content! If an object has a keyword 'Great Content' and a keyword +'Great Styling', it would be returned in the second search but not in +the first. + +```` +$clause_factory = $lom->search()->getClauseFactory(); +$clause = $clause_factory->getBasicClause( + $lom->paths()->keywords(), + Mode::ENDS_WITH, + 'Content' +); +$clause = $clause_factory->getNegatedClause($clause); + +$results = $lom->search()->execute($clause, null, null); +```` + +Joining `Clauses` allows for more specific searches. The following finds +all objects with a keyword containing 'great', and with an author +'Dr. Doom': + +```` +$clause_factory = $lom->search()->getClauseFactory(); +$keyword_clause = $clause_factory->getBasicClause( + $lom->paths()->keywords(), + Mode::CONTAINS, + 'great' +); +$author_clause = $clause_factory->getBasicClause( + $lom->paths()->authors(), + Mode::EQUALS, + 'Dr. Doom' +); +$clause = $clause_factory->getJoinedClauses( + Operator::AND, + $keyword_clause, + $author_clause +); + +$results = $lom->search()->execute($clause, null, null); +```` + +Joining clauses even further, one can find e.g. all objects with a +keyword containing 'great', and with an author 'Dr. Doom' or +'Dr. House': + +```` +$clause_factory = $lom->search()->getClauseFactory(); +$keyword_clause = $clause_factory->getBasicClause( + $lom->paths()->keywords(), + Mode::CONTAINS, + 'great' +); +$doom_clause = $clause_factory->getBasicClause( + $lom->paths()->authors(), + Mode::EQUALS, + 'Dr. Doom' +); +$house_clause = $clause_factory->getBasicClause( + $lom->paths()->authors(), + Mode::EQUALS, + 'Dr. House' +); +$clause = $clause_factory->getJoinedClauses( + Operator::AND, + $keyword_clause, + $clause_factory->getJoinedClauses( + Operator::OR, + $doom_clause, + $house_clause + ) +); + +$results = $lom->search()->execute($clause, null, null); +```` + +To find specifically all Courses with keyword 'Great Content', `Filters` +can be used to only search for objects which have `'crs'` as the type in +their [triple of IDs](identifying_objects.md). Since the other two parameters do not need +to be restricted, one can set them to `Placeholder::ANY`: + +```` +$clause = $lom->search()->getClauseFactory()->getBasicClause( + $lom->paths()->keywords(), + Mode::EQUALS, + 'Great Content' +); +$filter = $lom->search()->getFilter( + Placeholder::ANY, + Placeholder::ANY, + 'crs' +); + +$results = $lom->search()->execute($clause, null, null, $filter); +```` + +Analogously, one can also search for a specific repository object and +its subobjects. To only find e.g. the Learning Module with `obj_id` 123 +and its chapters and pages: + +```` +$filter = $lom->search()->getFilter( + 123, + Placeholder::ANY, + Placeholder::ANY +); + +$results = $lom->search()->execute($clause, null, null, $filter); +```` + +Multiple filters are joined with a logical OR, so to search in two different +Learning Modules with `obj_id`s 123 and 456 simultaneously: + +```` +$filter_123 = $lom->search()->getFilter( + 123, + Placeholder::ANY, + Placeholder::ANY +); +$filter_456 = $lom->search()->getFilter( + 456, + Placeholder::ANY, + Placeholder::ANY +); + +$results = $lom->search()->execute( + $clause, + null, + null, + $filter_123, + $filter_456 +); +```` + +`Filters` can also be used to only search repository objects, and not +their sub-objects. To this end, use placeholders to set the `sub_id` equal +to the `obj_id`: + +```` +$filter = $lom->search()->getFilter( + Placeholder::ANY, + Placeholder::OBJ_ID, + Placeholder::ANY +); + +$results = $lom->search()->execute($clause, null, null, $filter); +```` + +Alternatively, one can also filter for objects with `sub_id` equal to 0. +See [here](identifying_objects.md) for information on how repository objects are indentified +in the `Metadata` component. + +Lastly, limit and offset can be used to e.g. sequentially search through +objects. The following will first fetch the first five results, then the +next five, and lastly all remaining results: + +```` +$first_five_results = $lom->search()->execute($clause, 5, null); +$next_five_results = $lom->search()->execute($clause, 5, 5); +$remaining_results = $lom->search()->execute($clause, null, 10); +```` + ## `manipulate` With `manipulate`, one can edit an ILIAS object's LOM by deleting -elements, changing their value or adding new ones. +elements, changing their value or adding new elements. -When calling `manipulate`, an object needs to be identified by a triple -of IDs as explained [here](identifying_objects.md). A `Manipulator` -object is returned. +When calling `manipulate`, the object in question needs to be identified +by a triple of IDs as explained [here](identifying_objects.md). A `Manipulator` is returned. -The `Manipulator` offers a few `prepare` methods, with which changes +The `Manipulator` offers a few `prepare` methods, with which the changes one wants to make to the metadata can be collected. Upon calling `execute`, all changes registered to the `Manipulator` are carried out simultaneously. @@ -109,22 +376,22 @@ there are less elements than provided values, new elements will be set to be created according to the path to hold the leftover values.
If one of the provided values is not valid for the data type of the selected elements, or if it is not possible to add enough elements to the -LOM set to fit all values, an error will be thrown (either by this method -or by `execute`). Make sure that you are not trying to give multiple -values to unique elements (see [here](lom_structure.md) for details).
+LOM set to fit all values, an exception will be thrown (either by this method +or when calling `execute`). Make sure that you are not trying to give +multiple values to unique elements (see [here](lom_structure.md) for details).
For further details on how the `Manipulator` works see [here](manipulator.md). - `prepareForceCreate`: This behaves identically to the above, but will always create new elements, and never update existing ones.
The warning given above goes double here; if not enough of the requested -elements can be created, an error will be thrown. We recommend only using +elements can be created, an exception will be thrown. We recommend only using this method over `prepareCreateOrUpdate` when absolutely necessary, and -if at all possible only for non-unique elements. +only for non-unique elements. - `prepareDelete`: All elements selected by a [path](#paths) are set to -be deleted, along with their sub-elements. +be deleted. All their sub-elements are recursively deleted as well. ### Examples -To update the title in the LOM metadata of a course with `obj_id` +To update the title in the LOM metadata of a Course with `obj_id` 380, call `manipulate` with the [appropriate IDs](identifying_objects.md), then `prepareCreateOrUpdate` with the right [path](#paths), and finally `execute`: @@ -136,7 +403,7 @@ $lom->manipulate(380, 380, 'crs') ```` Note that adding a second value to `prepareCreateOrUpdate` would lead -to an error. The manipulator would try to create a second `title` element +to an exception. The manipulator would try to create a second `title` element to hold the additional value, but this is not possible since `title` is unique. @@ -281,6 +548,85 @@ $lom->manipulate(380, 380, 'crs') The custom path ensures, that even when the `structure` element does not already exist, it will be created with the right source. +## `derive` + +`derive` can be used to derive a LOM set for a target from that of a +source. This encompasses copying between ILIAS objects and creating +a LOM set for an object from basic properties, depending on the chosen +type of source and target. + +In the future, XML might be supported as source and target to also allow +import and export of LOM sets via the API. + +When calling `derive`, a `SourceSelector` is returned. There, +either an ILIAS object can be identified as a source by a triple of +IDs as explained [here](identifying_objects.md), or a LOM set can be created from basic +fields. A `Derivator` is returned, where an object can be chosen +analogously as the target. + +When a target is chosen, the `Derivator` reads out the LOM set from the +source, and writes it to the target. Currently, the two use cases +are: + +- **Creation:** When the target is an ILIAS object and title, description +and language are given as the source, a new LOM set is created for the +object containing the given data. Note that description and language are +optional, but title is not. Any previous LOM of the target object +is deleted before copying. +- **Copying:** When both source and target are ILIAS objects, the `Derivator` creates +a LOM set for the target by copying the LOM of the source. Any previous +LOM of the target object is deleted before copying. + +### Examples + +To create a new LOM set for a course with `obj_id` 380, pass its title, +description and language as basic properties, and choose the course as +the target: + +```` +$lom->derive() + ->fromBasicProperties( + 'title', + 'description', + 'en' + ) + ->forObject(380, 380, 'crs'); +```` + +To copy the LOM of a chapter with `obj_id` 2 in a Learning Module with +`obj_id` 325 to the course, choose those objects as target and source +with the appropriate IDs: + +```` +$lom->derive() + ->fromObject(325, 2, 'st') + ->forObject(380, 380, 'crs'); +```` + +## `deleteAll` + +`deleteAll` simply deletes all LOM of an ILIAS object, the object being +by identified by a triple of IDs as explained [here](identifying_objects.md). + +Note that consistency of the LOM set is not checked before deletion, +all occurences of the given object will be scrubbed indiscriminately +from the LOM tables. + +### Examples + +To delete all LOM of a Course with `obj_id` 380, call `deleteAll` with +the [appropriate IDs](identifying_objects.md): + +```` +$lom->deleteAll(380, 380, 'crs'); +```` + +or for a chapter with `obj_id` 2 in a Learning Module with `obj_id` 325: + +```` +$lom->deleteAll(325, 2, 'st'); +```` + ## `paths` Elements in LOM can not to be identified by name alone, but rather by @@ -300,17 +646,17 @@ or one only wants to select an element if it fulfills a certain condition), one can attach one or multiple filters to a step. Filters will be explained in more detail below. -Lastly, steps can also lead to the super-elements (or parent) of -the current elements. This is useful if one only wants to select elements -that contain certain sub-elements. Especially in combination with filters, -this makes paths a powerful tool for working with the `Reader` and -`Manipulator`. See the examples for possible ways to make use of this. +Lastly, steps can also lead to the super-elements of the current elements. +This is useful if one only wants to select elements that contain certain +sub-elements. Especially in combination with filters, this makes paths a +powerful tool for working with e.g. the `Reader` and `Manipulator`. See the +examples for possible ways to make use of this. ### Filters There are three types of filters: -- `'id'`: Filters elements by their ID from the `Services\Metadata` +- `'id'`: Filters elements by their ID from the `Metadata` tables. This is primarily used internally, the API does not expose these IDs. - `'index`: Filters elements by their index in order, starting from 0. @@ -338,8 +684,8 @@ $lom->paths() ```` Note that it does not stop at the element `title`, since that element -consists not only of the `string`, can also contain a `language` sub-element. -Many elements work similarly, often times one needs to go one step +consists not only of the `string`, but can also contain a `language` sub-element. +Many elements work similarly, often one needs to go one step further than one would think to get to the data-carrying element. If in doubt, consult the [LOM Standard](lom_structure.md). @@ -432,7 +778,7 @@ $lom->paths() ## `dataHelper` `dataHelper` is used to transform the data-values of LOM elements from -various LOM-internal formats into more useful forms. +various LOM-internal formats into something more useful. `makePresentable` returns the value of a data-object as something that can be shown to the user: vocabulary values and languages will be diff --git a/components/ILIAS/MetaData/docs/copyrights.md b/components/ILIAS/MetaData/docs/copyrights.md index 538c7c6ff6e0..166c0f492848 100644 --- a/components/ILIAS/MetaData/docs/copyrights.md +++ b/components/ILIAS/MetaData/docs/copyrights.md @@ -1,6 +1,6 @@ # Copyright Administration -ILIAS 9 comes pre-installed with seven Creative Commons licenses as well +ILIAS 10 comes pre-installed with seven Creative Commons licenses as well as 'All rights reserved'. Copyright can be selected for objects that support LOM when 'Enable Copyright Selection' is checked in the 'Copyright'-tab of the Metadata Administration. The copyright of an object can be chosen diff --git a/components/ILIAS/MetaData/docs/enabling_lom.md b/components/ILIAS/MetaData/docs/enabling_lom.md index 28944a377f9e..aa03ba019c23 100755 --- a/components/ILIAS/MetaData/docs/enabling_lom.md +++ b/components/ILIAS/MetaData/docs/enabling_lom.md @@ -52,7 +52,7 @@ array in `ilObjectMetadataGUI::isLOMAvailable`. #### Listening to Changes in LOM -The `ilObjectMetadataGUI` (and `Services\Object` in general) already +The `ilObjectMetadataGUI` (and the `Object` component in general) already takes care of changing the title and description of your object when the corresponding elements in LOM are changed in the editor, see `ilObject::doMDUpdateListener`. If you want to change or extend this diff --git a/components/ILIAS/MetaData/docs/identifying_objects.md b/components/ILIAS/MetaData/docs/identifying_objects.md index c668d2b27567..94c85ad3d7e8 100755 --- a/components/ILIAS/MetaData/docs/identifying_objects.md +++ b/components/ILIAS/MetaData/docs/identifying_objects.md @@ -4,16 +4,16 @@ missing or wrong information using the [ILIAS issue tracker](https://mantis.ilias.de) or contribute a fix via [Pull Request](../../../docs/development/contributing.md#pull-request-to-the-repositories). -In `Services/MetaData`, objects in ILIAS are generally identified by +In the `MetaData` component, objects in ILIAS are generally identified by three parameters: -1. The `obj_id` of the object if it is a repository object, else the +1. **obj_id:** The `obj_id` of the object if it is a repository object, else the `obj_id` of its parent repository object. If the object does not have a fixed parent (e.g. MediaObject), then this parameter is 0. -2. The `obj_id` of the object. If the object is a repository object by +2. **sub_id:** The `obj_id` of the object. If the object is a repository object by itself and not a sub-object, then you can set this parameter to 0, but we recommend passing the `obj_id` again. -3. The type of the object (and not its parent's), e.g. `'crs'` or `'lm'`. +3. **type:** The type of the object (and not its parent's), e.g. `'crs'` or `'lm'`. For example, consider three different objects: @@ -23,8 +23,8 @@ For example, consider three different objects: The corresponding ID-triples would then be -| | 1 | 2 | 3 -|-------------|-----|-----|----- -| Group | 123 | 123 | grp -| Page in LM | 456 | 54 | pg -| MediaObject | 0 | 789 | mob +| | **obj_id** | **sub_id** | **type** +|-------------|------------|------------|---------- +| Group | 123 | 123 | grp +| Page in LM | 456 | 54 | pg +| MediaObject | 0 | 789 | mob diff --git a/components/ILIAS/MetaData/docs/lom_structure.md b/components/ILIAS/MetaData/docs/lom_structure.md index 275c5329eb87..256b57bd85c2 100755 --- a/components/ILIAS/MetaData/docs/lom_structure.md +++ b/components/ILIAS/MetaData/docs/lom_structure.md @@ -5,7 +5,8 @@ missing or wrong information using the [ILIAS issue tracker](https://mantis.ilia or contribute a fix via [Pull Request](../../../docs/development/contributing.md#pull-request-to-the-repositories). Metadata of objects in ILIAS follow the Learning Object Metadata -(LOM) standard. The standard will not be reproduced here in full, +(LOM) standard (for the most part, see [here](#specific-to-ilias) for +diversions). The standard will not be reproduced here in full, this documentation is restricted to providing information useful for working with LOM in ILIAS. @@ -407,5 +408,19 @@ Since the value of the element `general > title > string` is synchronized with the title of the ILIAS object the LOM set belongs to, neither it nor its super-elements can be deleted. -The sub-elements of the first `general > identifier` can neither -be edited nor deleted, along with their super-elements. +The sub-elements of the first `general > identifier` are fixed and can +neither be edited nor deleted, along with their super-elements. The +`catalog` sub-element is always set to `ILIAS` and the `entry` to +`il_{Installation ID}_{Object Type}_{Object ID}`. `Object ID` refers +here to either the `sub_id` if it is non-zero, and otherwise to the +`obj_id` of the object as defined [here](identifying_objects.md). + +The LOM standard allows any elements of type `LangString`, meaning those +with a tupel `string` and `language` as sub-elements, to have arbitrarily +many such tupels. This is currently not implemented in ILIAS. + +Further, some elements denoted above as having data type `string` are +more restrictive in the LOM standard. For example, `technical > format` +should be restricted to MIME types (or the token `non-digital`), and +`contribute > entity` should be restricted to representation of vCards. +Likewise, these restrictions are currently not implemented in ILIAS. diff --git a/components/ILIAS/MetaData/docs/manipulator.md b/components/ILIAS/MetaData/docs/manipulator.md index fbca3309c402..6dd3bc93ac72 100755 --- a/components/ILIAS/MetaData/docs/manipulator.md +++ b/components/ILIAS/MetaData/docs/manipulator.md @@ -8,10 +8,10 @@ In this documentation we describe how exactly the `Manipulator` used in the [API](api.md) manipulates LOM sets via the method `prepareCreateOrUpdate` (and relatedly `prepareForceCreate`). -The main mechanism with which the `Manipulator` decides how the proceed +The main mechanism with which the `Manipulator` decides how to proceed is by checking whether various paths are 'complete' on the to-be-manipulated set, meaning whether there is at least one instance of the element -that the path points to in the set. +that the path points to. Consider `prepareCreateOrUpdate` being called with a path *p* and *n* string values. First, if the path contains any steps to super-elements, diff --git a/components/ILIAS/MetaData/tests/Elements/ElementTest.php b/components/ILIAS/MetaData/tests/Elements/ElementTest.php index 4a23b30ee62b..726f5857c11d 100755 --- a/components/ILIAS/MetaData/tests/Elements/ElementTest.php +++ b/components/ILIAS/MetaData/tests/Elements/ElementTest.php @@ -25,12 +25,15 @@ use ILIAS\MetaData\Elements\Markers\MarkerFactoryInterface; use ILIAS\MetaData\Elements\Markers\Action; use ILIAS\MetaData\Elements\Markers\MarkerInterface; -use ILIAS\MetaData\Repository\Utilities\ScaffoldProviderInterface; +use ILIAS\MetaData\Manipulator\ScaffoldProvider\ScaffoldProviderInterface; use ILIAS\MetaData\Structure\Definitions\DefinitionInterface; use ILIAS\MetaData\Elements\Data\Type; use ILIAS\MetaData\Elements\Data\DataInterface; use ILIAS\MetaData\Elements\Data\NullData; use ILIAS\MetaData\Structure\Definitions\NullDefinition; +use ILIAS\MetaData\Elements\Markers\NullMarkerFactory; +use ILIAS\MetaData\Elements\Markers\NullMarker; +use ILIAS\MetaData\Manipulator\ScaffoldProvider\NullScaffoldProvider; class ElementTest extends TestCase { @@ -48,6 +51,117 @@ public function name(): string }; } + protected function getMarkerFactory(): MarkerFactoryInterface + { + return new class () extends NullMarkerFactory { + public function marker(Action $action, string $data_value = ''): MarkerInterface + { + return new class ($action) extends NullMarker { + protected Action $action; + + public function __construct(Action $action) + { + $this->action = $action; + } + + public function action(): Action + { + return $this->action; + } + + public function dataValue(): string + { + return ''; + } + }; + } + }; + } + + protected function getScaffoldProvider(bool $broken = false): ScaffoldProviderInterface + { + return new class ($broken) extends NullScaffoldProvider { + public function __construct(protected bool $broken) + { + } + + protected function getScaffold(string $name, ElementInterface ...$elements): ElementInterface + { + $definition = new class ($name) implements DefinitionInterface { + protected string $name; + + public function __construct(string $name) + { + $this->name = $name; + } + + public function name(): string + { + return $this->name; + } + + public function unique(): bool + { + return false; + } + + public function dataType(): Type + { + return Type::NULL; + } + }; + + $data = new class () implements DataInterface { + public function type(): Type + { + return Type::STRING; + } + + public function value(): string + { + return 'value'; + } + }; + + return new Element( + NoID::SCAFFOLD, + $definition, + $data, + ...$elements + ); + } + + public function getScaffoldsForElement(ElementInterface $element): \Generator + { + if ($this->broken) { + $sub = $this->getScaffold('name'); + $with_sub = $this->getScaffold('with sub', $sub); + + yield '' => $with_sub; + return; + } + + $first = $this->getScaffold('first'); + $second = $this->getScaffold('second'); + $third = $this->getScaffold('third'); + $fourth = $this->getScaffold('fourth'); + + yield $first; + yield $second; + yield $third; + yield $fourth; + } + + public function getPossibleSubElementNamesForElementInOrder(ElementInterface $element): \Generator + { + yield 'first'; + yield 'second'; + yield 'third'; + yield 'fourth'; + } + }; + } + protected function getElement( int|NoID $id, Element ...$elements @@ -116,7 +230,7 @@ public function testGetMarkerAndIsMarked(): void { $mark_me = $this->getElement(13); $stay_away = $this->getElement(7); - $mark_me->mark(new MockMarkerFactory(), Action::NEUTRAL); + $mark_me->mark($this->getMarkerFactory(), Action::NEUTRAL); $this->assertTrue($mark_me->isMarked()); $this->assertInstanceOf(MarkerInterface::class, $mark_me->getMarker()); @@ -133,7 +247,7 @@ public function testMarkerTrail(): void $el2 = $this->getElement(2); $root = $this->getElement(NoID::ROOT, $el1, $el2); - $el11->mark(new MockMarkerFactory(), Action::CREATE_OR_UPDATE); + $el11->mark($this->getMarkerFactory(), Action::CREATE_OR_UPDATE); $this->assertTrue($el11->isMarked()); $this->assertSame(Action::CREATE_OR_UPDATE, $el11->getMarker()->action()); @@ -149,7 +263,7 @@ public function testMarkerTrail(): void public function testMarkTwice(): void { - $marker_factory = new MockMarkerFactory(); + $marker_factory = $this->getMarkerFactory(); $sub = $this->getElement(11); $el = $this->getElement(1, $sub); @@ -166,7 +280,7 @@ public function testMarkTwice(): void public function testMarkWithScaffolds(): void { - $marker_factory = new MockMarkerFactory(); + $marker_factory = $this->getMarkerFactory(); $sub = $this->getElement(NoID::SCAFFOLD); $el = $this->getElement(NoID::SCAFFOLD, $sub); @@ -182,12 +296,29 @@ public function testMarkWithScaffolds(): void $this->assertSame(Action::NEUTRAL, $el->getMarker()->action()); } + public function testUnmark(): void + { + $el111 = $this->getElement(111); + $el11 = $this->getElement(11, $el111); + $el1 = $this->getElement(1, $el11); + $root = $this->getElement(NoID::ROOT, $el1); + + $el111->mark($this->getMarkerFactory(), Action::CREATE_OR_UPDATE); + $el11->unmark(); + + $this->assertTrue($root->isMarked()); + $this->assertTrue($el1->isMarked()); + $this->assertFalse($el11->isMarked()); + $this->assertFalse($el111->isMarked()); + } + public function testAddScaffolds(): void { $second = $this->getElementWithName(6, 'second'); - $el = $this->getElement(13, $second); + $fourth = $this->getElementWithName(6, 'fourth'); + $el = $this->getElement(13, $second, $fourth); - $el->addScaffoldsToSubElements(new MockScaffoldProvider()); + $el->addScaffoldsToSubElements($this->getScaffoldProvider()); $subs = $el->getSubElements(); $this->assertTrue($subs->current()->isScaffold()); @@ -201,6 +332,11 @@ public function testAddScaffolds(): void $this->assertTrue($subs->current()->isScaffold()); $this->assertSame('third', $subs->current()->getDefinition()->name()); $subs->next(); + $this->assertSame($fourth, $subs->current()); + $subs->next(); + $this->assertTrue($subs->current()->isScaffold()); + $this->assertSame('fourth', $subs->current()->getDefinition()->name()); + $subs->next(); $this->assertNull($subs->current()); } @@ -210,7 +346,7 @@ public function testAddScaffoldByName(): void $third = $this->getElementWithName(17, 'third'); $el = $this->getElement(13, $second, $third); - $el->addScaffoldToSubElements(new MockScaffoldProvider(), 'second'); + $el->addScaffoldToSubElements($this->getScaffoldProvider(), 'second'); $subs = $el->getSubElements(); $this->assertSame($second, $subs->current()); @@ -223,118 +359,40 @@ public function testAddScaffoldByName(): void $this->assertNull($subs->current()); } - public function testAddScaffoldsWithSubElementsException(): void - { - $el = $this->getElement(37); - $this->expectException(\ilMDElementsException::class); - $el->addScaffoldsToSubElements(new MockBrokenScaffoldProvider()); - } - public function testAddScaffoldByNameWithSubElementsException(): void + public function testAddScaffoldByNameWithGap(): void { - $el = $this->getElement(37); - - $this->expectException(\ilMDElementsException::class); - $el->addScaffoldToSubElements(new MockBrokenScaffoldProvider(), 'with sub'); - } -} - -class MockMarkerFactory implements MarkerFactoryInterface -{ - public function marker(Action $action, string $data_value = ''): MarkerInterface - { - return new MockMarker($action); - } -} - -class MockMarker implements MarkerInterface -{ - protected Action $action; - - public function __construct(Action $action) - { - $this->action = $action; - } - - public function action(): Action - { - return $this->action; - } - - public function dataValue(): string - { - return ''; - } -} - -class MockScaffoldProvider implements ScaffoldProviderInterface -{ - protected function getScaffold(string $name, ElementInterface ...$elements): ElementInterface - { - $definition = new class ($name) implements DefinitionInterface { - protected string $name; - - public function __construct(string $name) - { - $this->name = $name; - } - - public function name(): string - { - return $this->name; - } - - public function unique(): bool - { - return false; - } - - public function dataType(): Type - { - return Type::NULL; - } - }; - - $data = new class () implements DataInterface { - public function type(): Type - { - return Type::STRING; - } + $second = $this->getElementWithName(6, 'second'); + $fourth = $this->getElementWithName(17, 'fourth'); + $el = $this->getElement(13, $second, $fourth); - public function value(): string - { - return 'value'; - } - }; + $el->addScaffoldToSubElements($this->getScaffoldProvider(), 'second'); - return new Element( - NoID::SCAFFOLD, - $definition, - $data, - ...$elements - ); + $subs = $el->getSubElements(); + $this->assertSame($second, $subs->current()); + $subs->next(); + $this->assertTrue($subs->current()->isScaffold()); + $this->assertSame('second', $subs->current()->getDefinition()->name()); + $subs->next(); + $this->assertSame($fourth, $subs->current()); + $subs->next(); + $this->assertNull($subs->current()); } - public function getScaffoldsForElement(ElementInterface $element): \Generator + public function testAddScaffoldsWithSubElementsException(): void { - $first = $this->getScaffold('first'); - $second = $this->getScaffold('second'); - $third = $this->getScaffold('third'); + $el = $this->getElement(37); - yield 'second' => $first; - yield 'third' => $second; - yield '' => $third; + $this->expectException(\ilMDElementsException::class); + $el->addScaffoldsToSubElements($this->getScaffoldProvider(true)); } -} -class MockBrokenScaffoldProvider extends MockScaffoldProvider -{ - public function getScaffoldsForElement(ElementInterface $element): \Generator + public function testAddScaffoldByNameWithSubElementsException(): void { - $sub = $this->getScaffold('name'); - $with_sub = $this->getScaffold('with sub', $sub); + $el = $this->getElement(37); - yield '' => $with_sub; + $this->expectException(\ilMDElementsException::class); + $el->addScaffoldToSubElements($this->getScaffoldProvider(true), 'with sub'); } } diff --git a/components/ILIAS/MetaData/tests/Elements/Scaffolds/ScaffoldFactoryTest.php b/components/ILIAS/MetaData/tests/Elements/Scaffolds/ScaffoldFactoryTest.php index 2ff158e7d6f8..a4242a7d6971 100755 --- a/components/ILIAS/MetaData/tests/Elements/Scaffolds/ScaffoldFactoryTest.php +++ b/components/ILIAS/MetaData/tests/Elements/Scaffolds/ScaffoldFactoryTest.php @@ -29,16 +29,36 @@ use ILIAS\MetaData\Elements\NoID; use ILIAS\MetaData\Structure\Definitions\NullDefinition; use ILIAS\MetaData\Elements\Data\NullDataFactory; +use ILIAS\MetaData\Elements\RessourceID\NullRessourceIDFactory; +use ILIAS\MetaData\Elements\SetInterface; +use ILIAS\MetaData\Elements\RessourceID\NullRessourceID; class ScaffoldFactoryTest extends TestCase { public function testCreateScaffold(): void { - $factory = new ScaffoldFactory(new NullDataFactory()); + $factory = new ScaffoldFactory( + new NullDataFactory(), + new NullRessourceIDFactory() + ); $scaffold = $factory->scaffold(new NullDefinition()); $this->assertInstanceOf(ElementInterface::class, $scaffold); $this->assertSame(NoID::SCAFFOLD, $scaffold->getMDID()); $this->assertSame(Type::NULL, $scaffold->getData()->type()); } + + public function testCreateSet(): void + { + $factory = new ScaffoldFactory( + new NullDataFactory(), + new NullRessourceIDFactory() + ); + + $root_definition = new NullDefinition(); + $set = $factory->set($root_definition); + + $this->assertInstanceOf(SetInterface::class, $set); + $this->assertInstanceOf(NullRessourceID::class, $set->getRessourceID()); + } } diff --git a/components/ILIAS/MetaData/tests/Elements/SetTest.php b/components/ILIAS/MetaData/tests/Elements/SetTest.php index 173a9b6ee7cd..4272e72150e0 100755 --- a/components/ILIAS/MetaData/tests/Elements/SetTest.php +++ b/components/ILIAS/MetaData/tests/Elements/SetTest.php @@ -25,7 +25,7 @@ use ILIAS\MetaData\Elements\NoID; use ILIAS\MetaData\Elements\Data\DataInterface; use ILIAS\MetaData\Elements\RessourceID\RessourceIDInterface; -use ILIAS\MetaData\Repository\Utilities\ScaffoldProviderInterface; +use ILIAS\MetaData\Manipulator\ScaffoldProvider\ScaffoldProviderInterface; use ILIAS\MetaData\Elements\Markers\Action; use ILIAS\MetaData\Elements\Markers\MarkerFactoryInterface; use ILIAS\MetaData\Elements\Markers\MarkerInterface; diff --git a/components/ILIAS/MetaData/tests/Manipulator/ManipulatorTest.php b/components/ILIAS/MetaData/tests/Manipulator/ManipulatorTest.php index ba3cd5cb6cea..e56df0629198 100755 --- a/components/ILIAS/MetaData/tests/Manipulator/ManipulatorTest.php +++ b/components/ILIAS/MetaData/tests/Manipulator/ManipulatorTest.php @@ -58,8 +58,8 @@ use ILIAS\MetaData\Paths\Steps\StepInterface; use ILIAS\MetaData\Repository\NullRepository; use ILIAS\MetaData\Repository\RepositoryInterface; -use ILIAS\MetaData\Repository\Utilities\NullScaffoldProvider; -use ILIAS\MetaData\Repository\Utilities\ScaffoldProviderInterface; +use ILIAS\MetaData\Manipulator\ScaffoldProvider\NullScaffoldProvider; +use ILIAS\MetaData\Manipulator\ScaffoldProvider\ScaffoldProviderInterface; use ILIAS\MetaData\Paths\Steps\StepToken; use ILIAS\MetaData\Structure\Definitions\DefinitionInterface; use ILIAS\MetaData\Structure\Definitions\NullDefinition; @@ -87,7 +87,7 @@ public function getRoot(): ElementInterface }; } - public function getScaffoldProviderMock(): ScaffoldProviderInterface + protected function getScaffoldProviderMock(): ScaffoldProviderInterface { return new class () extends NullScaffoldProvider { public function getScaffoldsForElement(ElementInterface $element): \Generator @@ -97,20 +97,6 @@ public function getScaffoldsForElement(ElementInterface $element): \Generator }; } - protected function getRepositoryMock(): RepositoryInterface - { - return new class ($this) extends NullRepository { - public function __construct(protected ManipulatorTest $test) - { - } - - public function scaffolds(): ScaffoldProviderInterface - { - return $this->test->getScaffoldProviderMock(); - } - }; - } - public function getMarkerMock(Action $action, string $data_value = ''): MarkerInterface { return new class ($action, $data_value) extends NullMarker { @@ -812,7 +798,7 @@ protected function myAssertTree(ElementInterface $root, array $expected_root_val public function testPrepareDelete_001(): void { $manipulator = new Manipulator( - new NullRepository(), + new NullScaffoldProvider(), $this->getMarkerFactoryMock(), $this->getNavigatorFactoryMock(), $this->getPathFactoryMock(), @@ -935,7 +921,7 @@ public function testPrepareDelete_001(): void public function testPrepareDelete_002(): void { $manipulator = new Manipulator( - new NullRepository(), + new NullScaffoldProvider(), $this->getMarkerFactoryMock(), $this->getNavigatorFactoryMock(), $this->getPathFactoryMock(), @@ -1034,7 +1020,7 @@ public function testPrepareDelete_002(): void public function testPrepareCreateOrUpdate_001(): void { $manipulator = new Manipulator( - $this->getRepositoryMock(), + $this->getScaffoldProviderMock(), $this->getMarkerFactoryMock(), $this->getNavigatorFactoryMock(), $this->getPathFactoryMock(), @@ -1082,7 +1068,7 @@ public function testPrepareCreateOrUpdate_001(): void public function testPrepareCreateOrUpdate_002(): void { $manipulator = new Manipulator( - $this->getRepositoryMock(), + $this->getScaffoldProviderMock(), $this->getMarkerFactoryMock(), $this->getNavigatorFactoryMock(), $this->getPathFactoryMock(), @@ -1182,7 +1168,7 @@ public function testPrepareCreateOrUpdate_002(): void public function testPrepareCreateOrUpdate_003(): void { $manipulator = new Manipulator( - $this->getRepositoryMock(), + $this->getScaffoldProviderMock(), $this->getMarkerFactoryMock(), $this->getNavigatorFactoryMock(), $this->getPathFactoryMock(), @@ -1305,7 +1291,7 @@ public function testPrepareCreateOrUpdate_003(): void public function testPrepareCreateOrUpdate_004(): void { $manipulator = new Manipulator( - $this->getRepositoryMock(), + $this->getScaffoldProviderMock(), $this->getMarkerFactoryMock(), $this->getNavigatorFactoryMock(), $this->getPathFactoryMock(), @@ -1440,7 +1426,7 @@ public function testPrepareCreateOrUpdate_004(): void public function testPrepareCreateOrUpdate_005(): void { $manipulator = new Manipulator( - $this->getRepositoryMock(), + $this->getScaffoldProviderMock(), $this->getMarkerFactoryMock(), $this->getNavigatorFactoryMock(), $this->getPathFactoryMock(), @@ -1601,7 +1587,7 @@ public function testPrepareCreateOrUpdate_005(): void public function testPrepareForceCreate01(): void { $manipulator = new Manipulator( - $this->getRepositoryMock(), + $this->getScaffoldProviderMock(), $this->getMarkerFactoryMock(), $this->getNavigatorFactoryMock(), $this->getPathFactoryMock(), @@ -1749,7 +1735,7 @@ public function testPrepareForceCreate01(): void public function testPrepareForceCreate02(): void { $manipulator = new Manipulator( - $this->getRepositoryMock(), + $this->getScaffoldProviderMock(), $this->getMarkerFactoryMock(), $this->getNavigatorFactoryMock(), $this->getPathFactoryMock(), diff --git a/components/ILIAS/MetaData/tests/Repository/IdentifierHandler/IdentifierHandlerTest.php b/components/ILIAS/MetaData/tests/Repository/IdentifierHandler/IdentifierHandlerTest.php new file mode 100644 index 000000000000..0d5d481b4bf9 --- /dev/null +++ b/components/ILIAS/MetaData/tests/Repository/IdentifierHandler/IdentifierHandlerTest.php @@ -0,0 +1,203 @@ +obj_id; + } + + public function subID(): int + { + return $this->sub_id; + } + + public function type(): string + { + return $this->type; + } + }; + } + + protected function getIdentifierHandler(): IdentifierHandler + { + $manipulator = new class () extends NullManipulator { + public function prepareCreateOrUpdate( + SetInterface $set, + PathInterface $path, + string ...$values + ): SetInterface { + $set = clone $set; + $set->prepared_changes[] = [ + 'path' => $path->toString(), + 'values' => $values + ]; + return $set; + } + + public function prepareDelete(SetInterface $set, PathInterface $path): SetInterface + { + $set = clone $set; + $set->prepared_changes[] = ['delete should not be prepared!']; + return $set; + } + + public function prepareForceCreate( + SetInterface $set, + PathInterface $path, + string ...$values + ): SetInterface { + $set = clone $set; + $set->prepared_changes[] = ['force create should not be prepared!']; + return $set; + } + }; + + $builder = new class () extends NullPathBuilder { + protected string $path_string = '~start~'; + + public function withNextStep(string $name, bool $add_as_first = false): PathBuilder + { + $builder = clone $this; + if ($add_as_first) { + $name .= '[added as first]'; + } + $builder->path_string .= '%' . $name; + return $builder; + } + + public function withAdditionalFilterAtCurrentStep(FilterType $type, string ...$values): PathBuilder + { + $builder = clone $this; + $builder->path_string .= '{' . $type->value . ':' . implode('><', $values) . '}'; + return $builder; + } + + public function get(): PathInterface + { + return new class ($this->path_string) extends NullPath { + public function __construct(protected string $path_string) + { + } + + public function toString(): string + { + return $this->path_string; + } + }; + } + }; + + $path_factory = new class ($builder) extends NullPathFactory { + public function __construct(protected PathBuilder $builder) + { + } + + public function custom(): PathBuilder + { + return $this->builder; + } + }; + + return new class ($manipulator, $path_factory) extends IdentifierHandler { + protected function getInstallID(): string + { + return 'MockInstID'; + } + }; + } + + public function testPrepareUpdateOfIdentifier(): void + { + $set = $this->getSet(); + $ressource_id = $this->getRessourceID(78, 983, 'TargetType'); + $identifier_handler = $this->getIdentifierHandler(); + + $prepared_set = $identifier_handler->prepareUpdateOfIdentifier($set, $ressource_id); + + $expected_entry_changes = [ + 'path' => '~start~%general%identifier{index:0}%entry', + 'values' => ['il_MockInstID_TargetType_983'] + ]; + $expected_catalog_changes = [ + 'path' => '~start~%general%identifier{index:0}%catalog', + 'values' => ['ILIAS'] + ]; + $prepared_changes = $prepared_set->prepared_changes; + $this->assertCount(2, $prepared_changes); + $this->assertContains($expected_entry_changes, $prepared_changes); + $this->assertContains($expected_catalog_changes, $prepared_changes); + } + + public function testPrepareUpdateOfIdentifierForSubIDZero(): void + { + $set = $this->getSet(); + $ressource_id = $this->getRessourceID(78, 0, 'TargetType'); + $identifier_handler = $this->getIdentifierHandler(); + + $prepared_set = $identifier_handler->prepareUpdateOfIdentifier($set, $ressource_id); + + $expected_entry_changes = [ + 'path' => '~start~%general%identifier{index:0}%entry', + 'values' => ['il_MockInstID_TargetType_78'] + ]; + $expected_catalog_changes = [ + 'path' => '~start~%general%identifier{index:0}%catalog', + 'values' => ['ILIAS'] + ]; + $prepared_changes = $prepared_set->prepared_changes; + $this->assertCount(2, $prepared_changes); + $this->assertContains($expected_entry_changes, $prepared_changes); + $this->assertContains($expected_catalog_changes, $prepared_changes); + } +} diff --git a/components/ILIAS/MetaData/tests/Repository/Search/Clauses/ClauseWithPropertiesAndFactoryTest.php b/components/ILIAS/MetaData/tests/Repository/Search/Clauses/ClauseWithPropertiesAndFactoryTest.php new file mode 100644 index 000000000000..da49bd52350b --- /dev/null +++ b/components/ILIAS/MetaData/tests/Repository/Search/Clauses/ClauseWithPropertiesAndFactoryTest.php @@ -0,0 +1,217 @@ +getBasicClause($this->getNonEmptyPath(), Mode::CONTAINS, 'value'); + + $this->assertFalse($basic_clause->isJoin()); + $this->assertNull($basic_clause->joinProperties()); + $this->assertNotNull($basic_clause->basicProperties()); + } + + public function testGetBasicClauseEmptyPathException(): void + { + $factory = new Factory(); + + $this->expectException(\ilMDRepositoryException::class); + $basic_clause = $factory->getBasicClause(new NullPath(), Mode::CONTAINS, 'value'); + } + + public function testGetBasicClauseNotNegated(): void + { + $factory = new Factory(); + $basic_clause = $factory->getBasicClause($this->getNonEmptyPath(), Mode::CONTAINS, 'value'); + + $this->assertFalse($basic_clause->isNegated()); + } + + public function testBasicClausePath(): void + { + $factory = new Factory(); + $path = $this->getNonEmptyPath(); + $basic_clause = $factory->getBasicClause($path, Mode::CONTAINS, 'value'); + $this->assertSame($path, $basic_clause->basicProperties()->path()); + } + + public function testBasicClauseMode(): void + { + $factory = new Factory(); + $basic_clause = $factory->getBasicClause($this->getNonEmptyPath(), Mode::CONTAINS, 'value'); + $this->assertSame(Mode::CONTAINS, $basic_clause->basicProperties()->mode()); + } + + public function testBasicClauseNegatedModeTrue(): void + { + $factory = new Factory(); + $basic_clause = $factory->getBasicClause( + $this->getNonEmptyPath(), + Mode::CONTAINS, + 'value', + true + ); + $this->assertTrue($basic_clause->basicProperties()->isModeNegated()); + } + + public function testBasicClauseNegatedModeDefaultFalse(): void + { + $factory = new Factory(); + $basic_clause = $factory->getBasicClause( + $this->getNonEmptyPath(), + Mode::CONTAINS, + 'value' + ); + $this->assertFalse($basic_clause->basicProperties()->isModeNegated()); + } + + public function testBasicClauseValue(): void + { + $factory = new Factory(); + $basic_clause = $factory->getBasicClause($this->getNonEmptyPath(), Mode::CONTAINS, 'value'); + $this->assertSame('value', $basic_clause->basicProperties()->value()); + } + + public function testGetNegatedClause(): void + { + $factory = new Factory(); + $join_props = new JoinProperties(Operator::AND, new NullClause(), new NullClause()); + $basic_props = new BasicProperties( + $this->getNonEmptyPath(), + Mode::ENDS_WITH, + 'value', + false + ); + $clause = new Clause(false, true, $join_props, $basic_props); + + $negated = $factory->getNegatedClause($clause); + + $this->assertTrue($negated->isNegated()); + $this->assertTrue($negated->isJoin()); + $this->assertSame($basic_props, $negated->basicProperties()); + $this->assertSame($join_props, $negated->joinProperties()); + } + + public function testNegateNegatedClause(): void + { + $factory = new Factory(); + $join_props = new JoinProperties(Operator::AND, new NullClause(), new NullClause()); + $basic_props = new BasicProperties( + $this->getNonEmptyPath(), + Mode::ENDS_WITH, + 'value', + false + ); + $clause = new Clause(true, true, $join_props, $basic_props); + + $negated = $factory->getNegatedClause($clause); + + $this->assertFalse($negated->isNegated()); + $this->assertTrue($negated->isJoin()); + $this->assertSame($basic_props, $negated->basicProperties()); + $this->assertSame($join_props, $negated->joinProperties()); + } + + public function testGetJoinedClauses(): void + { + $factory = new Factory(); + $clause_1 = new NullClause(); + $clause_2 = new NullClause(); + $joined_clause = $factory->getJoinedClauses(Operator::OR, $clause_1, $clause_2); + + $this->assertTrue($joined_clause->isJoin()); + $this->assertNull($joined_clause->basicProperties()); + $this->assertNotNull($joined_clause->joinProperties()); + } + + public function testGetJoinedClausesNotNegated(): void + { + $factory = new Factory(); + $clause_1 = new NullClause(); + $clause_2 = new NullClause(); + $joined_clause = $factory->getJoinedClauses(Operator::OR, $clause_1, $clause_2); + + $this->assertFalse($joined_clause->isNegated()); + } + + public function testJoinedClauseOperator(): void + { + $factory = new Factory(); + $clause_1 = new NullClause(); + $clause_2 = new NullClause(); + $joined_clause = $factory->getJoinedClauses(Operator::OR, $clause_1, $clause_2); + + $this->assertSame(Operator::OR, $joined_clause->joinProperties()->operator()); + } + + public function testJoinedClauseSubClausesWithTwo(): void + { + $factory = new Factory(); + $clause_1 = new NullClause(); + $clause_2 = new NullClause(); + $joined_clause = $factory->getJoinedClauses(Operator::OR, $clause_1, $clause_2); + + $sub_clauses = iterator_to_array($joined_clause->joinProperties()->subClauses()); + $this->assertSame([$clause_1, $clause_2], $sub_clauses); + } + + public function testJoinedClauseSubClausesWithMoreThanTwo(): void + { + $factory = new Factory(); + $clause_1 = new NullClause(); + $clause_2 = new NullClause(); + $clause_3 = new NullClause(); + $clause_4 = new NullClause(); + $joined_clause = $factory->getJoinedClauses( + Operator::OR, + $clause_1, + $clause_2, + $clause_3, + $clause_4 + ); + + $sub_clauses = iterator_to_array($joined_clause->joinProperties()->subClauses()); + $this->assertSame( + [$clause_1, $clause_2, $clause_3, $clause_4], + $sub_clauses + ); + } +} diff --git a/components/ILIAS/MetaData/tests/Repository/Search/Filters/FilterAndFactoryTest.php b/components/ILIAS/MetaData/tests/Repository/Search/Filters/FilterAndFactoryTest.php new file mode 100644 index 000000000000..6a83e9a067b6 --- /dev/null +++ b/components/ILIAS/MetaData/tests/Repository/Search/Filters/FilterAndFactoryTest.php @@ -0,0 +1,68 @@ +get(23, 5, 'type'); + $this->assertSame(23, $filter->objID()); + } + + public function testObjIDPlaceholder() + { + $factory = new Factory(); + $filter = $factory->get(Placeholder::ANY, 5, 'type'); + $this->assertSame(Placeholder::ANY, $filter->objID()); + } + + public function testSubID() + { + $factory = new Factory(); + $filter = $factory->get(23, 5, 'type'); + $this->assertSame(5, $filter->subID()); + } + + public function testSubIDPlaceholder() + { + $factory = new Factory(); + $filter = $factory->get(245, Placeholder::OBJ_ID, 'type'); + $this->assertSame(Placeholder::OBJ_ID, $filter->subID()); + } + + public function testType() + { + $factory = new Factory(); + $filter = $factory->get(23, 5, 'type'); + $this->assertSame('type', $filter->type()); + } + + public function testTypePlaceholder() + { + $factory = new Factory(); + $filter = $factory->get(23, 5, Placeholder::ANY); + $this->assertSame(Placeholder::ANY, $filter->type()); + } +} diff --git a/components/ILIAS/MetaData/tests/Repository/Utilities/Queries/DatabaseSearcherTest.php b/components/ILIAS/MetaData/tests/Repository/Utilities/Queries/DatabaseSearcherTest.php new file mode 100644 index 000000000000..07083f46aad5 --- /dev/null +++ b/components/ILIAS/MetaData/tests/Repository/Utilities/Queries/DatabaseSearcherTest.php @@ -0,0 +1,897 @@ + 37, 'obj_id' => 55, 'obj_type' => 'type1'], + ['rbac_id' => 123, 'obj_id' => 85, 'obj_type' => 'type2'], + ['rbac_id' => 98, 'obj_id' => 4, 'obj_type' => 'type3'] + ]; + + protected function mockRessourceIDsMatchArrayData( + array $array, + RessourceIDInterface ...$ressource_ids + ): bool { + $data = []; + foreach ($ressource_ids as $ressource_id) { + $data[] = [ + 'rbac_id' => $ressource_id->obj_id, + 'obj_id' => $ressource_id->sub_id, + 'obj_type' => $ressource_id->type + ]; + } + + return $array === $data; + } + + protected function getDatabaseSearcher(array $db_result): DatabaseSearcher + { + $ressource_factory = new class () extends NullRessourceIDFactory { + public function ressourceID(int $obj_id, int $sub_id, string $type): RessourceIDInterface + { + return new class ($obj_id, $sub_id, $type) extends NullRessourceID { + public function __construct( + public int $obj_id, + public int $sub_id, + public string $type + ) { + } + }; + } + }; + + $paths_parser_factory = new class () extends NullDatabasePathsParserFactory { + public function forSearch(): DatabasePathsParserInterface + { + return new class () extends NullDatabasePathsParser { + protected array $paths = []; + + public function addPathAndGetColumn(PathInterface $path): string + { + $path_string = $path->toString(); + $this->paths[] = $path_string; + return $path_string . '_column'; + } + + public function getSelectForQuery(): string + { + if (empty($this->paths)) { + throw new \ilMDRepositoryException('no paths!'); + } + return 'selected paths:[' . implode('~', $this->paths) . ']'; + } + + public function getTableAliasForFilters(): string + { + if (empty($this->paths)) { + throw new \ilMDRepositoryException('no paths!'); + } + return 'base_table'; + } + }; + } + }; + + return new class ( + $ressource_factory, + $paths_parser_factory, + $db_result + ) extends DatabaseSearcher { + public string $exposed_last_query; + + public function __construct( + RessourceIDFactoryInterface $ressource_factory, + DatabasePathsParserFactoryInterface $paths_parser_factory, + protected array $db_result + ) { + $this->ressource_factory = $ressource_factory; + $this->paths_parser_factory = $paths_parser_factory; + } + + protected function queryDB(string $query): \Generator + { + $this->exposed_last_query = $query; + yield from $this->db_result; + } + + protected function quoteIdentifier(string $identifier): string + { + return '~identifier:' . $identifier . '~'; + } + + protected function quoteText(string $text): string + { + return '~text:' . $text . '~'; + } + + protected function quoteInteger(int $integer): string + { + return '~int:' . $integer . '~'; + } + }; + } + + protected function getBasicClause( + bool $negated, + string $path, + Mode $mode, + string $value, + bool $mode_negated + ): ClauseInterface { + return new class ($negated, $path, $mode, $value, $mode_negated) extends NullClause { + public function __construct( + protected bool $negated, + protected string $path, + protected Mode $mode, + protected string $value, + protected bool $mode_negated + ) { + } + + public function isNegated(): bool + { + return $this->negated; + } + + public function isJoin(): bool + { + return false; + } + + public function basicProperties(): ?BasicPropertiesInterface + { + return new class ( + $this->path, + $this->mode, + $this->value, + $this->mode_negated + ) extends NullBasicProperties { + public function __construct( + protected string $path, + protected Mode $mode, + protected string $value, + protected bool $mode_negated + ) { + } + + public function path(): PathInterface + { + return new class ($this->path) extends NullPath { + public function __construct(protected string $path) + { + } + + public function toString(): string + { + return $this->path; + } + }; + } + + public function isModeNegated(): bool + { + return $this->mode_negated; + } + + public function mode(): Mode + { + return $this->mode; + } + + public function value(): string + { + return $this->value; + } + }; + } + + public function joinProperties(): ?JoinPropertiesInterface + { + return null; + } + }; + } + + protected function getJoinedClause( + bool $negated, + Operator $operator, + ClauseInterface ...$clauses + ): ClauseInterface { + return new class ($negated, $operator, $clauses) extends NullClause { + public function __construct( + protected bool $negated, + protected Operator $operator, + protected array $clauses + ) { + } + + public function isNegated(): bool + { + return $this->negated; + } + + public function isJoin(): bool + { + return true; + } + + public function basicProperties(): ?BasicPropertiesInterface + { + return null; + } + + public function joinProperties(): ?JoinPropertiesInterface + { + return new class ($this->operator, $this->clauses) extends NullJoinProperties { + public function __construct( + protected Operator $operator, + protected array $clauses + ) { + } + + public function operator(): Operator + { + return $this->operator; + } + + public function subClauses(): \Generator + { + yield from $this->clauses; + } + }; + } + }; + } + + protected function getFilter( + int|Placeholder $obj_id, + int|Placeholder $sub_id, + string|Placeholder $type + ): FilterInterface { + return new class ($obj_id, $sub_id, $type) extends NullFilter { + public function __construct( + protected int|Placeholder $obj_id, + protected int|Placeholder $sub_id, + protected string|Placeholder $type + ) { + } + + public function objID(): int|Placeholder + { + return $this->obj_id; + } + + public function subID(): int|Placeholder + { + return $this->sub_id; + } + + public function type(): string|Placeholder + { + return $this->type; + } + }; + } + + public function testSearchWithNoResults(): void + { + $searcher = $this->getDatabaseSearcher([]); + $clause = $this->getBasicClause( + false, + 'path', + Mode::EQUALS, + 'value', + false + ); + + $result = $searcher->search($clause, null, null); + $this->assertNull($result->current()); + } + + public function testSearchWithResults(): void + { + $searcher = $this->getDatabaseSearcher(self::RESULT); + $clause = $this->getBasicClause( + false, + 'path', + Mode::EQUALS, + 'value', + false + ); + + $result = $searcher->search($clause, null, null); + $this->assertTrue( + $this->mockRessourceIDsMatchArrayData(self::RESULT, ...$result) + ); + } + + public function testSearchWithBasicClauseModeEquals(): void + { + $searcher = $this->getDatabaseSearcher(self::RESULT); + $clause = $this->getBasicClause( + false, + 'path', + Mode::EQUALS, + 'value', + false + ); + + $result = iterator_to_array($searcher->search($clause, null, null)); + $this->assertSame( + 'selected paths:[path] GROUP BY ~identifier:base_table~.rbac_id, ' . + '~identifier:base_table~.obj_id, ~identifier:base_table~.obj_type ' . + 'HAVING COUNT(CASE WHEN path_column = ~text:value~ THEN 1 END) > 0 ' . + 'ORDER BY rbac_id, obj_id, obj_type', + $searcher->exposed_last_query + ); + } + + public function testSearchWithBasicClauseModeContains(): void + { + $searcher = $this->getDatabaseSearcher(self::RESULT); + $clause = $this->getBasicClause( + false, + 'path', + Mode::CONTAINS, + 'value', + false + ); + + $result = iterator_to_array($searcher->search($clause, null, null)); + $this->assertSame( + 'selected paths:[path] GROUP BY ~identifier:base_table~.rbac_id, ' . + '~identifier:base_table~.obj_id, ~identifier:base_table~.obj_type ' . + 'HAVING COUNT(CASE WHEN path_column LIKE ~text:%value%~ THEN 1 END) > 0 ' . + 'ORDER BY rbac_id, obj_id, obj_type', + $searcher->exposed_last_query + ); + } + + public function testSearchWithBasicClauseModeStartsWith(): void + { + $searcher = $this->getDatabaseSearcher(self::RESULT); + $clause = $this->getBasicClause( + false, + 'path', + Mode::STARTS_WITH, + 'value', + false + ); + + $result = iterator_to_array($searcher->search($clause, null, null)); + $this->assertSame( + 'selected paths:[path] GROUP BY ~identifier:base_table~.rbac_id, ' . + '~identifier:base_table~.obj_id, ~identifier:base_table~.obj_type ' . + 'HAVING COUNT(CASE WHEN path_column LIKE ~text:value%~ THEN 1 END) > 0 ' . + 'ORDER BY rbac_id, obj_id, obj_type', + $searcher->exposed_last_query + ); + } + + public function testSearchWithBasicClauseModeEndsWith(): void + { + $searcher = $this->getDatabaseSearcher(self::RESULT); + $clause = $this->getBasicClause( + false, + 'path', + Mode::ENDS_WITH, + 'value', + false + ); + + $result = iterator_to_array($searcher->search($clause, null, null)); + $this->assertSame( + 'selected paths:[path] GROUP BY ~identifier:base_table~.rbac_id, ' . + '~identifier:base_table~.obj_id, ~identifier:base_table~.obj_type ' . + 'HAVING COUNT(CASE WHEN path_column LIKE ~text:%value~ THEN 1 END) > 0 ' . + 'ORDER BY rbac_id, obj_id, obj_type', + $searcher->exposed_last_query + ); + } + + public function testSearchWithBasicClauseNegatedMode(): void + { + $searcher = $this->getDatabaseSearcher(self::RESULT); + $clause = $this->getBasicClause( + false, + 'path', + Mode::EQUALS, + 'value', + true + ); + + $result = iterator_to_array($searcher->search($clause, null, null)); + $this->assertSame( + 'selected paths:[path] GROUP BY ~identifier:base_table~.rbac_id, ' . + '~identifier:base_table~.obj_id, ~identifier:base_table~.obj_type ' . + 'HAVING COUNT(CASE WHEN NOT path_column = ~text:value~ THEN 1 END) > 0 ' . + 'ORDER BY rbac_id, obj_id, obj_type', + $searcher->exposed_last_query + ); + } + + public function testSearchWithNegatedBasicClause(): void + { + $searcher = $this->getDatabaseSearcher(self::RESULT); + $clause = $this->getBasicClause( + true, + 'path', + Mode::EQUALS, + 'value', + false + ); + + $result = iterator_to_array($searcher->search($clause, null, null)); + $this->assertSame( + 'selected paths:[path] GROUP BY ~identifier:base_table~.rbac_id, ' . + '~identifier:base_table~.obj_id, ~identifier:base_table~.obj_type ' . + 'HAVING NOT COUNT(CASE WHEN path_column = ~text:value~ THEN 1 END) > 0 ' . + 'ORDER BY rbac_id, obj_id, obj_type', + $searcher->exposed_last_query + ); + } + + public function testSearchWithORJoinedClauses(): void + { + $searcher = $this->getDatabaseSearcher(self::RESULT); + $clause1 = $this->getBasicClause( + false, + 'path1', + Mode::EQUALS, + 'value1', + false + ); + $clause2 = $this->getBasicClause( + false, + 'path2', + Mode::STARTS_WITH, + 'value2', + false + ); + $joined_clause = $this->getJoinedClause(false, Operator::OR, $clause1, $clause2); + + $result = iterator_to_array($searcher->search($joined_clause, null, null)); + $this->assertSame( + 'selected paths:[path1~path2] GROUP BY ~identifier:base_table~.rbac_id, ' . + '~identifier:base_table~.obj_id, ~identifier:base_table~.obj_type ' . + 'HAVING (COUNT(CASE WHEN path1_column = ~text:value1~ THEN 1 END) > 0 ' . + 'OR COUNT(CASE WHEN path2_column LIKE ~text:value2%~ THEN 1 END) > 0) ' . + 'ORDER BY rbac_id, obj_id, obj_type', + $searcher->exposed_last_query + ); + } + + public function testSearchWithANDJoinedClauses(): void + { + $searcher = $this->getDatabaseSearcher(self::RESULT); + $clause1 = $this->getBasicClause( + false, + 'path1', + Mode::CONTAINS, + 'value1', + false + ); + $clause2 = $this->getBasicClause( + false, + 'path2', + Mode::STARTS_WITH, + 'value2', + false + ); + $joined_clause = $this->getJoinedClause(false, Operator::AND, $clause1, $clause2); + + $result = iterator_to_array($searcher->search($joined_clause, null, null)); + $this->assertSame( + 'selected paths:[path1~path2] GROUP BY ~identifier:base_table~.rbac_id, ' . + '~identifier:base_table~.obj_id, ~identifier:base_table~.obj_type ' . + 'HAVING (COUNT(CASE WHEN path1_column LIKE ~text:%value1%~ THEN 1 END) > 0 ' . + 'AND COUNT(CASE WHEN path2_column LIKE ~text:value2%~ THEN 1 END) > 0) ' . + 'ORDER BY rbac_id, obj_id, obj_type', + $searcher->exposed_last_query + ); + } + + public function testSearchWithNegatedJoinedClause(): void + { + $searcher = $this->getDatabaseSearcher(self::RESULT); + $clause1 = $this->getBasicClause( + false, + 'path1', + Mode::CONTAINS, + 'value1', + false + ); + $clause2 = $this->getBasicClause( + false, + 'path2', + Mode::EQUALS, + 'value2', + false + ); + $joined_clause = $this->getJoinedClause(true, Operator::AND, $clause1, $clause2); + + $result = iterator_to_array($searcher->search($joined_clause, null, null)); + $this->assertSame( + 'selected paths:[path1~path2] GROUP BY ~identifier:base_table~.rbac_id, ' . + '~identifier:base_table~.obj_id, ~identifier:base_table~.obj_type ' . + 'HAVING NOT (COUNT(CASE WHEN path1_column LIKE ~text:%value1%~ THEN 1 END) > 0 ' . + 'AND COUNT(CASE WHEN path2_column = ~text:value2~ THEN 1 END) > 0) ' . + 'ORDER BY rbac_id, obj_id, obj_type', + $searcher->exposed_last_query + ); + } + + public function testSearchWithNestedJoinedClauses(): void + { + $searcher = $this->getDatabaseSearcher(self::RESULT); + $clause1 = $this->getBasicClause( + false, + 'path1', + Mode::CONTAINS, + 'value1', + false + ); + $clause2 = $this->getBasicClause( + false, + 'path2', + Mode::EQUALS, + 'value2', + false + ); + $clause3 = $this->getBasicClause( + false, + 'path3', + Mode::ENDS_WITH, + 'value3', + false + ); + $joined_clause = $this->getJoinedClause( + false, + Operator::AND, + $clause1, + $this->getJoinedClause( + true, + Operator::OR, + $clause2, + $clause3 + ) + ); + + $result = iterator_to_array($searcher->search($joined_clause, null, null)); + $this->assertSame( + 'selected paths:[path1~path2~path3] GROUP BY ~identifier:base_table~.rbac_id, ' . + '~identifier:base_table~.obj_id, ~identifier:base_table~.obj_type ' . + 'HAVING (COUNT(CASE WHEN path1_column LIKE ~text:%value1%~ THEN 1 END) > 0 ' . + 'AND NOT (COUNT(CASE WHEN path2_column = ~text:value2~ THEN 1 END) > 0 OR ' . + 'COUNT(CASE WHEN path3_column LIKE ~text:%value3~ THEN 1 END) > 0)) ' . + 'ORDER BY rbac_id, obj_id, obj_type', + $searcher->exposed_last_query + ); + } + + public function testSearchWithLimit(): void + { + $searcher = $this->getDatabaseSearcher(self::RESULT); + $clause = $this->getBasicClause( + false, + 'path', + Mode::EQUALS, + 'value', + false + ); + + $result = iterator_to_array($searcher->search($clause, 37, null)); + $this->assertSame( + 'selected paths:[path] GROUP BY ~identifier:base_table~.rbac_id, ' . + '~identifier:base_table~.obj_id, ~identifier:base_table~.obj_type ' . + 'HAVING COUNT(CASE WHEN path_column = ~text:value~ THEN 1 END) > 0 ' . + 'ORDER BY rbac_id, obj_id, obj_type LIMIT ~int:37~', + $searcher->exposed_last_query + ); + } + + public function testSearchWithOffset(): void + { + $searcher = $this->getDatabaseSearcher(self::RESULT); + $clause = $this->getBasicClause( + false, + 'path', + Mode::EQUALS, + 'value', + false + ); + + $result = iterator_to_array($searcher->search($clause, null, 16)); + $this->assertSame( + 'selected paths:[path] GROUP BY ~identifier:base_table~.rbac_id, ' . + '~identifier:base_table~.obj_id, ~identifier:base_table~.obj_type ' . + 'HAVING COUNT(CASE WHEN path_column = ~text:value~ THEN 1 END) > 0 ' . + 'ORDER BY rbac_id, obj_id, obj_type LIMIT ~int:' . PHP_INT_MAX . '~ OFFSET ~int:16~', + $searcher->exposed_last_query + ); + } + + public function testSearchWithLimitAndOffset(): void + { + $searcher = $this->getDatabaseSearcher(self::RESULT); + $clause = $this->getBasicClause( + false, + 'path', + Mode::EQUALS, + 'value', + false + ); + + $result = iterator_to_array($searcher->search($clause, 37, 16)); + $this->assertSame( + 'selected paths:[path] GROUP BY ~identifier:base_table~.rbac_id, ' . + '~identifier:base_table~.obj_id, ~identifier:base_table~.obj_type ' . + 'HAVING COUNT(CASE WHEN path_column = ~text:value~ THEN 1 END) > 0 ' . + 'ORDER BY rbac_id, obj_id, obj_type LIMIT ~int:37~ OFFSET ~int:16~', + $searcher->exposed_last_query + ); + } + + public function testSearchWithEmptyFilter(): void + { + $searcher = $this->getDatabaseSearcher(self::RESULT); + $clause = $this->getBasicClause( + false, + 'path', + Mode::EQUALS, + 'value', + false + ); + $filter = $this->getFilter(Placeholder::ANY, Placeholder::ANY, Placeholder::ANY); + + $result = iterator_to_array($searcher->search($clause, null, null, $filter)); + $this->assertSame( + 'selected paths:[path] GROUP BY ~identifier:base_table~.rbac_id, ' . + '~identifier:base_table~.obj_id, ~identifier:base_table~.obj_type ' . + 'HAVING COUNT(CASE WHEN path_column = ~text:value~ THEN 1 END) > 0 ' . + 'ORDER BY rbac_id, obj_id, obj_type', + $searcher->exposed_last_query + ); + } + + public function testSearchWithSingleValueObjIDFilter(): void + { + $searcher = $this->getDatabaseSearcher(self::RESULT); + $clause = $this->getBasicClause( + false, + 'path', + Mode::EQUALS, + 'value', + false + ); + $filter = $this->getFilter(37, Placeholder::ANY, Placeholder::ANY); + + $result = iterator_to_array($searcher->search($clause, null, null, $filter)); + $this->assertSame( + 'selected paths:[path] GROUP BY ~identifier:base_table~.rbac_id, ' . + '~identifier:base_table~.obj_id, ~identifier:base_table~.obj_type ' . + 'HAVING COUNT(CASE WHEN path_column = ~text:value~ THEN 1 END) > 0 ' . + 'AND ((~identifier:base_table~.rbac_id = ~int:37~)) ' . + 'ORDER BY rbac_id, obj_id, obj_type', + $searcher->exposed_last_query + ); + } + + public function testSearchWithSingleValueSubIDFilter(): void + { + $searcher = $this->getDatabaseSearcher(self::RESULT); + $clause = $this->getBasicClause( + false, + 'path', + Mode::EQUALS, + 'value', + false + ); + $filter = $this->getFilter(Placeholder::ANY, 15, Placeholder::ANY); + + $result = iterator_to_array($searcher->search($clause, null, null, $filter)); + $this->assertSame( + 'selected paths:[path] GROUP BY ~identifier:base_table~.rbac_id, ' . + '~identifier:base_table~.obj_id, ~identifier:base_table~.obj_type ' . + 'HAVING COUNT(CASE WHEN path_column = ~text:value~ THEN 1 END) > 0 ' . + 'AND ((~identifier:base_table~.obj_id = ~int:15~)) ' . + 'ORDER BY rbac_id, obj_id, obj_type', + $searcher->exposed_last_query + ); + } + + public function testSearchWithSingleValueTypeFilter(): void + { + $searcher = $this->getDatabaseSearcher(self::RESULT); + $clause = $this->getBasicClause( + false, + 'path', + Mode::EQUALS, + 'value', + false + ); + $filter = $this->getFilter(Placeholder::ANY, Placeholder::ANY, 'some type'); + + $result = iterator_to_array($searcher->search($clause, null, null, $filter)); + $this->assertSame( + 'selected paths:[path] GROUP BY ~identifier:base_table~.rbac_id, ' . + '~identifier:base_table~.obj_id, ~identifier:base_table~.obj_type ' . + 'HAVING COUNT(CASE WHEN path_column = ~text:value~ THEN 1 END) > 0 ' . + 'AND ((~identifier:base_table~.obj_type = ~text:some type~)) ' . + 'ORDER BY rbac_id, obj_id, obj_type', + $searcher->exposed_last_query + ); + } + + public function testSearchWithMultiValueFilter(): void + { + $searcher = $this->getDatabaseSearcher(self::RESULT); + $clause = $this->getBasicClause( + false, + 'path', + Mode::EQUALS, + 'value', + false + ); + $filter = $this->getFilter(37, 15, 'some type'); + + $result = iterator_to_array($searcher->search($clause, null, null, $filter)); + $this->assertSame( + 'selected paths:[path] GROUP BY ~identifier:base_table~.rbac_id, ' . + '~identifier:base_table~.obj_id, ~identifier:base_table~.obj_type ' . + 'HAVING COUNT(CASE WHEN path_column = ~text:value~ THEN 1 END) > 0 ' . + 'AND ((~identifier:base_table~.rbac_id = ~int:37~ AND ' . + '~identifier:base_table~.obj_id = ~int:15~ AND ' . + '~identifier:base_table~.obj_type = ~text:some type~)) ' . + 'ORDER BY rbac_id, obj_id, obj_type', + $searcher->exposed_last_query + ); + } + + public function testSearchWithMultipleFilters(): void + { + $searcher = $this->getDatabaseSearcher(self::RESULT); + $clause = $this->getBasicClause( + false, + 'path', + Mode::EQUALS, + 'value', + false + ); + $filter1 = $this->getFilter(37, 15, Placeholder::ANY); + $filter2 = $this->getFilter(Placeholder::ANY, 15, 'some type'); + $filter3 = $this->getFilter(37, Placeholder::ANY, 'some type'); + + $result = iterator_to_array($searcher->search( + $clause, + null, + null, + $filter1, + $filter2, + $filter3 + )); + $this->assertSame( + 'selected paths:[path] GROUP BY ~identifier:base_table~.rbac_id, ' . + '~identifier:base_table~.obj_id, ~identifier:base_table~.obj_type ' . + 'HAVING COUNT(CASE WHEN path_column = ~text:value~ THEN 1 END) > 0 ' . + 'AND ((~identifier:base_table~.rbac_id = ~int:37~ AND ~identifier:base_table~.obj_id = ~int:15~) ' . + 'OR (~identifier:base_table~.obj_id = ~int:15~ AND ~identifier:base_table~.obj_type = ~text:some type~) ' . + 'OR (~identifier:base_table~.rbac_id = ~int:37~ AND ~identifier:base_table~.obj_type = ~text:some type~)) ' . + 'ORDER BY rbac_id, obj_id, obj_type', + $searcher->exposed_last_query + ); + } + + public function testSearchWithObjIDPlaceholderFilter(): void + { + $searcher = $this->getDatabaseSearcher(self::RESULT); + $clause = $this->getBasicClause( + false, + 'path', + Mode::EQUALS, + 'value', + false + ); + $filter = $this->getFilter(Placeholder::ANY, Placeholder::OBJ_ID, Placeholder::ANY); + + $result = iterator_to_array($searcher->search($clause, null, null, $filter)); + $this->assertSame( + 'selected paths:[path] GROUP BY ~identifier:base_table~.rbac_id, ' . + '~identifier:base_table~.obj_id, ~identifier:base_table~.obj_type ' . + 'HAVING COUNT(CASE WHEN path_column = ~text:value~ THEN 1 END) > 0 ' . + 'AND ((~identifier:base_table~.obj_id = ~identifier:base_table~.rbac_id)) ' . + 'ORDER BY rbac_id, obj_id, obj_type', + $searcher->exposed_last_query + ); + } + + public function testSearchWithSubIDPlaceholderFilter(): void + { + $searcher = $this->getDatabaseSearcher(self::RESULT); + $clause = $this->getBasicClause( + false, + 'path', + Mode::EQUALS, + 'value', + false + ); + $filter = $this->getFilter(Placeholder::SUB_ID, Placeholder::ANY, Placeholder::ANY); + + $result = iterator_to_array($searcher->search($clause, null, null, $filter)); + $this->assertSame( + 'selected paths:[path] GROUP BY ~identifier:base_table~.rbac_id, ' . + '~identifier:base_table~.obj_id, ~identifier:base_table~.obj_type ' . + 'HAVING COUNT(CASE WHEN path_column = ~text:value~ THEN 1 END) > 0 ' . + 'AND ((~identifier:base_table~.rbac_id = ~identifier:base_table~.obj_id)) ' . + 'ORDER BY rbac_id, obj_id, obj_type', + $searcher->exposed_last_query + ); + } + + public function testSearchWithTypePlaceholderFilter(): void + { + $searcher = $this->getDatabaseSearcher(self::RESULT); + $clause = $this->getBasicClause( + false, + 'path', + Mode::EQUALS, + 'value', + false + ); + $filter = $this->getFilter(Placeholder::TYPE, Placeholder::ANY, Placeholder::ANY); + + $result = iterator_to_array($searcher->search($clause, null, null, $filter)); + $this->assertSame( + 'selected paths:[path] GROUP BY ~identifier:base_table~.rbac_id, ' . + '~identifier:base_table~.obj_id, ~identifier:base_table~.obj_type ' . + 'HAVING COUNT(CASE WHEN path_column = ~text:value~ THEN 1 END) > 0 ' . + 'AND ((~identifier:base_table~.rbac_id = ~identifier:base_table~.obj_type)) ' . + 'ORDER BY rbac_id, obj_id, obj_type', + $searcher->exposed_last_query + ); + } +} diff --git a/components/ILIAS/MetaData/tests/Repository/Utilities/Queries/Paths/DatabasePathsParserTest.php b/components/ILIAS/MetaData/tests/Repository/Utilities/Queries/Paths/DatabasePathsParserTest.php new file mode 100644 index 000000000000..e35a6a9be470 --- /dev/null +++ b/components/ILIAS/MetaData/tests/Repository/Utilities/Queries/Paths/DatabasePathsParserTest.php @@ -0,0 +1,670 @@ +steps = iterator_to_array($path->steps()); + } + + public function hasNextStep(): bool + { + return count($this->steps) > 1; + } + + public function nextStep(): ?StructureNavigatorInterface + { + if (!$this->hasNextStep()) { + return null; + } + $clone = clone $this; + array_shift($clone->steps); + return $clone; + } + + public function currentStep(): ?StepInterface + { + return $this->steps[0]; + } + }; + } + + protected function getTagForCurrentStepOfNavigator( + StructureNavigatorInterface $navigator + ): ?TagInterface { + return $navigator->currentStep()->tag->data_type === DataType::VOCAB_SOURCE ? + null : + $navigator->currentStep()->tag; + } + + protected function getDataTypeForCurrentStepOfNavigator(StructureNavigatorInterface $navigator): DataType + { + return $navigator->currentStep()->tag->data_type; + } + + protected function quoteIdentifier(string $identifier): string + { + return '~identifier:' . $identifier . '~'; + } + + protected function quoteText(string $text): string + { + return '~text:' . $text . '~'; + } + + protected function quoteInteger(int $integer): string + { + return '~int:' . $integer . '~'; + } + + protected function checkTable(string $table): void + { + if ($table === 'WRONG') { + throw new \ilMDRepositoryException('Invalid MD table: ' . $table); + } + } + + protected function table(string $table): ?string + { + return $table === 'WRONG' ? null : $table . '_name'; + } + + protected function IDName(string $table): ?string + { + return $table === 'WRONG' ? null : $table . '_id'; + } + }; + } + + protected function getPath(TagInterface ...$tags): PathInterface + { + array_unshift( + $tags, + $this->getTag('', '', '', 'root'), + ); + + return new class ($tags) extends NullPath { + public function __construct(protected array $tags) + { + } + + public function steps(): \Generator + { + foreach ($this->tags as $tag) { + yield new class ($tag) extends NullStep { + public function __construct(public TagInterface $tag) + { + } + + public function name(): string|StepToken + { + return $this->tag->step_name; + } + + public function filters(): \Generator + { + yield from $this->tag->filters; + } + }; + } + } + + public function toString(): string + { + $string = '@'; + foreach ($this->tags as $tag) { + $step_name = is_string($tag->step_name) ? $tag->step_name : $tag->step_name->value; + $string .= '.' . $step_name; + foreach ($tag->filters as $filter) { + $string .= ':' . $filter->type()->value; + foreach ($filter->values() as $value) { + $string .= '~' . $value; + } + } + } + return $string; + } + }; + } + + /** + * To build mock-paths I start from the tags I want the mock-dictionary + * to return at that step. Kind of backwards, but turned out the most + * convenient here. + */ + protected function getTag( + string $table, + string $parent, + string $data_field, + string|StepToken $step_name, + DataType $data_type = DataType::STRING, + PathFilter ...$filters, + ): TagInterface { + return new class ($table, $parent, $data_field, $step_name, $data_type, $filters) extends NullTag { + public function __construct( + protected string $table, + protected string $parent, + protected string $data_field, + public string|StepToken $step_name, + public DataType $data_type, + public array $filters + ) { + } + + public function table(): string + { + return $this->table; + } + + public function hasParent(): bool + { + return $this->parent !== ''; + } + + public function parent(): string + { + return $this->parent; + } + + public function hasData(): bool + { + return $this->data_field !== ''; + } + + public function dataField(): string + { + return $this->data_field; + } + }; + } + + protected function getPathFilter( + FilterType $type, + string ...$values + ): PathFilter { + return new class ($type, $values) extends NullPathFilter { + public function __construct( + protected FilterType $type, + protected array $values + ) { + } + + public function type(): FilterType + { + return $this->type; + } + + public function values(): \Generator + { + yield from $this->values; + } + }; + } + + public function testGetTableAliasForFilters(): void + { + $parser = $this->getDatabasePathsParser(); + $parser->addPathAndGetColumn( + $this->getPath($this->getTag('table', '', '', 'step')) + ); + + $this->assertSame('p1t1', $parser->getTableAliasForFilters()); + } + + public function testGetTableAliasForFiltersNoPathsException(): void + { + $parser = $this->getDatabasePathsParser(); + + $this->expectException(\ilMDRepositoryException::class); + $parser->getTableAliasForFilters(); + } + + public function testPathAndGetColumnWrongTableException(): void + { + $parser = $this->getDatabasePathsParser(); + + $this->expectException(\ilMDRepositoryException::class); + $parser->addPathAndGetColumn( + $this->getPath($this->getTag('WRONG', '', '', 'step')) + ); + } + + public function testGetSelectForQueryNoPathsException(): void + { + $parser = $this->getDatabasePathsParser(); + + $this->expectException(\ilMDRepositoryException::class); + $parser->getSelectForQuery(); + } + + public function testGetSelectForQueryWithSinglePathAdded(): void + { + $parser = $this->getDatabasePathsParser(); + $data_column = $parser->addPathAndGetColumn($this->getPath( + $this->getTag('table', '', '', 'step1'), + $this->getTag('table', '', 'data', 'step2'), + )); + + $this->assertSame( + "COALESCE(~identifier:p1t1~.~identifier:data~, '')", + $data_column + ); + $this->assertSame( + 'SELECT p1t1.rbac_id, p1t1.obj_id, p1t1.obj_type ' . 'FROM ' . + '~identifier:table_name~ AS ~identifier:p1t1~', + $parser->getSelectForQuery() + ); + } + + public function testGetSelectForQueryWithSinglePathAcrossMultipleTablesAdded(): void + { + $parser = $this->getDatabasePathsParser(); + $data_column = $parser->addPathAndGetColumn($this->getPath( + $this->getTag('table1', '', '', 'step1'), + $this->getTag('table1', '', '', 'step2'), + $this->getTag('table2', 'table1', '', 'step3'), + $this->getTag('table2', 'table1', 'data', 'step4') + )); + + $this->assertSame( + "COALESCE(~identifier:p1t2~.~identifier:data~, '')", + $data_column + ); + $this->assertSame( + 'SELECT p1t1.rbac_id, p1t1.obj_id, p1t1.obj_type ' . 'FROM ' . + '~identifier:table1_name~ AS ~identifier:p1t1~ JOIN ' . + '~identifier:table2_name~ AS ~identifier:p1t2~ ON ' . + '~identifier:p1t1~.rbac_id = ~identifier:p1t2~.rbac_id AND ' . + '~identifier:p1t1~.obj_id = ~identifier:p1t2~.obj_id AND ' . + '~identifier:p1t1~.obj_type = ~identifier:p1t2~.obj_type AND ' . + 'p1t1.~identifier:table1_id~ = ~identifier:p1t2~.parent_id AND ' . + '~text:table1~ = ~identifier:p1t2~.parent_type', + $parser->getSelectForQuery() + ); + } + + public function testGetSelectForQueryWithPathToElementWithoutValueAdded(): void + { + $parser = $this->getDatabasePathsParser(); + $data_column = $parser->addPathAndGetColumn($this->getPath( + $this->getTag('table', '', '', 'step1'), + $this->getTag('table', '', '', 'step2') + )); + + $this->assertSame( + '~text:~', + $data_column + ); + $this->assertSame( + 'SELECT p1t1.rbac_id, p1t1.obj_id, p1t1.obj_type ' . 'FROM ' . + '~identifier:table_name~ AS ~identifier:p1t1~', + $parser->getSelectForQuery() + ); + } + + public function testGetSelectForQueryWithPathToVocabSourceAdded(): void + { + $parser = $this->getDatabasePathsParser(); + $data_column = $parser->addPathAndGetColumn($this->getPath( + $this->getTag('table', '', '', 'step1'), + $this->getTag('', '', '', 'step2', DataType::VOCAB_SOURCE) + )); + + $this->assertSame( + '~text:' . LOMVocabInitiator::SOURCE . '~', + $data_column + ); + $this->assertSame( + 'SELECT p1t1.rbac_id, p1t1.obj_id, p1t1.obj_type ' . 'FROM ' . + '~identifier:table_name~ AS ~identifier:p1t1~', + $parser->getSelectForQuery() + ); + } + + public function testGetSelectForQueryWithSinglePathAddedMultipleTimes(): void + { + $parser = $this->getDatabasePathsParser(); + $data_column_1 = $parser->addPathAndGetColumn($this->getPath( + $this->getTag('table', '', '', 'step1'), + $this->getTag('table', '', 'data', 'step2'), + )); + $data_column_2 = $parser->addPathAndGetColumn($this->getPath( + $this->getTag('table', '', '', 'step1'), + $this->getTag('table', '', 'data', 'step2'), + )); + + $this->assertSame( + "COALESCE(~identifier:p1t1~.~identifier:data~, '')", + $data_column_1 + ); + $this->assertSame( + "COALESCE(~identifier:p1t1~.~identifier:data~, '')", + $data_column_2 + ); + $this->assertSame( + 'SELECT p1t1.rbac_id, p1t1.obj_id, p1t1.obj_type ' . 'FROM ' . + '~identifier:table_name~ AS ~identifier:p1t1~', + $parser->getSelectForQuery() + ); + } + + public function testGetSelectForQueryWithMultiplePathsAdded(): void + { + $parser = $this->getDatabasePathsParser(); + $data_column_1 = $parser->addPathAndGetColumn($this->getPath( + $this->getTag('table1', '', '', 'step1'), + $this->getTag('table1', '', '', 'step2'), + $this->getTag('table2', 'table2', '', 'step3'), + $this->getTag('table2', 'table2', 'data1', 'step4') + )); + $data_column_2 = $parser->addPathAndGetColumn($this->getPath( + $this->getTag('table1', '', '', 'step1'), + $this->getTag('table1', '', 'data2', 'step2'), + )); + + $this->assertSame( + "COALESCE(~identifier:p1t2~.~identifier:data1~, '')", + $data_column_1 + ); + $this->assertSame( + "COALESCE(~identifier:p2t1~.~identifier:data2~, '')", + $data_column_2 + ); + $this->assertSame( + 'SELECT p1t1.rbac_id, p1t1.obj_id, p1t1.obj_type ' . 'FROM ' . + 'il_meta_general AS base LEFT JOIN (' . + '~identifier:table1_name~ AS ~identifier:p1t1~ JOIN ' . + '~identifier:table2_name~ AS ~identifier:p1t2~ ON ' . + '~identifier:p1t1~.rbac_id = ~identifier:p1t2~.rbac_id AND ' . + '~identifier:p1t1~.obj_id = ~identifier:p1t2~.obj_id AND ' . + '~identifier:p1t1~.obj_type = ~identifier:p1t2~.obj_type AND ' . + 'p1t1.~identifier:table1_id~ = ~identifier:p1t2~.parent_id AND ' . + '~text:table2~ = ~identifier:p1t2~.parent_type) ON ' . + '~identifier:base~.rbac_id = ~identifier:p1t1~.rbac_id AND ' . + '~identifier:base~.obj_id = ~identifier:p1t1~.obj_id AND ' . + '~identifier:base~.obj_type = ~identifier:p1t1~.obj_type LEFT JOIN ' . + '(~identifier:table1_name~ AS ~identifier:p2t1~) ON ' . + '~identifier:base~.rbac_id = ~identifier:p2t1~.rbac_id AND ' . + '~identifier:base~.obj_id = ~identifier:p2t1~.obj_id AND ' . + '~identifier:base~.obj_type = ~identifier:p2t1~.obj_type', + $parser->getSelectForQuery() + ); + } + + public function testGetSelectForQueryWithPathWithStepsToSuperElementsAcrossTablesAdded(): void + { + $parser = $this->getDatabasePathsParser(); + $data_column = $parser->addPathAndGetColumn($this->getPath( + $this->getTag('table1', '', '', 'step1'), + $this->getTag('table1', '', '', 'step2'), + $this->getTag('table2', 'table1', '', 'step3'), + $this->getTag('table1', '', 'data1', StepToken::SUPER), + $this->getTag('table2', 'table1', 'data2', 'step5') + )); + + $this->assertSame( + "COALESCE(~identifier:p1t3~.~identifier:data2~, '')", + $data_column + ); + $this->assertSame( + 'SELECT p1t1.rbac_id, p1t1.obj_id, p1t1.obj_type ' . 'FROM ' . + '~identifier:table1_name~ AS ~identifier:p1t1~ JOIN ' . + '~identifier:table2_name~ AS ~identifier:p1t2~ JOIN ' . + '~identifier:table2_name~ AS ~identifier:p1t3~ ON ' . + '~identifier:p1t1~.rbac_id = ~identifier:p1t2~.rbac_id AND ' . + '~identifier:p1t1~.obj_id = ~identifier:p1t2~.obj_id AND ' . + '~identifier:p1t1~.obj_type = ~identifier:p1t2~.obj_type AND ' . + 'p1t1.~identifier:table1_id~ = ~identifier:p1t2~.parent_id AND ' . + '~text:table1~ = ~identifier:p1t2~.parent_type AND ' . + '~identifier:p1t1~.rbac_id = ~identifier:p1t3~.rbac_id AND ' . + '~identifier:p1t1~.obj_id = ~identifier:p1t3~.obj_id AND ' . + '~identifier:p1t1~.obj_type = ~identifier:p1t3~.obj_type AND ' . + 'p1t1.~identifier:table1_id~ = ~identifier:p1t3~.parent_id AND ' . + '~text:table1~ = ~identifier:p1t3~.parent_type', + $parser->getSelectForQuery() + ); + } + + public function testGetSelectForQueryWithPathWithMDIDFilterAdded(): void + { + $parser = $this->getDatabasePathsParser(); + $filter = $this->getPathFilter( + FilterType::MDID, + '13' + ); + $data_column = $parser->addPathAndGetColumn($this->getPath( + $this->getTag('table1', '', '', 'step1'), + $this->getTag('table1', '', '', 'step2', DataType::STRING, $filter), + $this->getTag('table2', 'table1', '', 'step3'), + $this->getTag('table2', 'table1', 'data', 'step4') + )); + + $this->assertSame( + 'SELECT p1t1.rbac_id, p1t1.obj_id, p1t1.obj_type ' . 'FROM ' . + '~identifier:table1_name~ AS ~identifier:p1t1~ JOIN ' . + '~identifier:table2_name~ AS ~identifier:p1t2~ ON ' . + '~identifier:p1t1~.~identifier:table1_id~ IN (~int:13~) AND ' . + '~identifier:p1t1~.rbac_id = ~identifier:p1t2~.rbac_id AND ' . + '~identifier:p1t1~.obj_id = ~identifier:p1t2~.obj_id AND ' . + '~identifier:p1t1~.obj_type = ~identifier:p1t2~.obj_type AND ' . + 'p1t1.~identifier:table1_id~ = ~identifier:p1t2~.parent_id AND ' . + '~text:table1~ = ~identifier:p1t2~.parent_type', + $parser->getSelectForQuery() + ); + } + + public function testGetSelectForQueryWithPathWithDataFilterAdded(): void + { + $parser = $this->getDatabasePathsParser(); + $filter = $this->getPathFilter( + FilterType::DATA, + 'some data' + ); + $data_column = $parser->addPathAndGetColumn($this->getPath( + $this->getTag('table1', '', '', 'step1'), + $this->getTag('table1', '', 'filter_data', 'step2', DataType::STRING, $filter), + $this->getTag('table2', 'table1', '', 'step3'), + $this->getTag('table2', 'table1', 'data', 'step4') + )); + + $this->assertSame( + 'SELECT p1t1.rbac_id, p1t1.obj_id, p1t1.obj_type ' . 'FROM ' . + '~identifier:table1_name~ AS ~identifier:p1t1~ JOIN ' . + '~identifier:table2_name~ AS ~identifier:p1t2~ ON ' . + "COALESCE(~identifier:p1t1~.~identifier:filter_data~, '') IN (~text:some data~) AND " . + '~identifier:p1t1~.rbac_id = ~identifier:p1t2~.rbac_id AND ' . + '~identifier:p1t1~.obj_id = ~identifier:p1t2~.obj_id AND ' . + '~identifier:p1t1~.obj_type = ~identifier:p1t2~.obj_type AND ' . + 'p1t1.~identifier:table1_id~ = ~identifier:p1t2~.parent_id AND ' . + '~text:table1~ = ~identifier:p1t2~.parent_type', + $parser->getSelectForQuery() + ); + } + + public function testGetSelectForQueryWithPathWithIndexFilterAdded(): void + { + $parser = $this->getDatabasePathsParser(); + $filter = $this->getPathFilter( + FilterType::INDEX, + '2' + ); + $data_column = $parser->addPathAndGetColumn($this->getPath( + $this->getTag('table1', '', '', 'step1'), + $this->getTag('table1', '', '', 'step2', DataType::STRING, $filter), + $this->getTag('table2', 'table1', '', 'step3'), + $this->getTag('table2', 'table1', 'data', 'step4') + )); + + $this->assertSame( + 'SELECT p1t1.rbac_id, p1t1.obj_id, p1t1.obj_type ' . 'FROM ' . + '~identifier:table1_name~ AS ~identifier:p1t1~ JOIN ' . + '~identifier:table2_name~ AS ~identifier:p1t2~ ON ' . + '~identifier:p1t1~.rbac_id = ~identifier:p1t2~.rbac_id AND ' . + '~identifier:p1t1~.obj_id = ~identifier:p1t2~.obj_id AND ' . + '~identifier:p1t1~.obj_type = ~identifier:p1t2~.obj_type AND ' . + 'p1t1.~identifier:table1_id~ = ~identifier:p1t2~.parent_id AND ' . + '~text:table1~ = ~identifier:p1t2~.parent_type', + $parser->getSelectForQuery() + ); + } + + public function testGetSelectForQueryWithPathWithMultiValueFilterAdded(): void + { + $parser = $this->getDatabasePathsParser(); + $filter = $this->getPathFilter( + FilterType::DATA, + 'some data', + 'some other data', + 'more' + ); + $data_column = $parser->addPathAndGetColumn($this->getPath( + $this->getTag('table1', '', '', 'step1'), + $this->getTag('table1', '', 'filter_data', 'step2', DataType::STRING, $filter), + $this->getTag('table2', 'table1', '', 'step3'), + $this->getTag('table2', 'table1', 'data', 'step4') + )); + + $this->assertSame( + 'SELECT p1t1.rbac_id, p1t1.obj_id, p1t1.obj_type ' . 'FROM ' . + '~identifier:table1_name~ AS ~identifier:p1t1~ JOIN ' . + '~identifier:table2_name~ AS ~identifier:p1t2~ ON ' . + "COALESCE(~identifier:p1t1~.~identifier:filter_data~, '') " . + 'IN (~text:some data~, ~text:some other data~, ~text:more~) AND ' . + '~identifier:p1t1~.rbac_id = ~identifier:p1t2~.rbac_id AND ' . + '~identifier:p1t1~.obj_id = ~identifier:p1t2~.obj_id AND ' . + '~identifier:p1t1~.obj_type = ~identifier:p1t2~.obj_type AND ' . + 'p1t1.~identifier:table1_id~ = ~identifier:p1t2~.parent_id AND ' . + '~text:table1~ = ~identifier:p1t2~.parent_type', + $parser->getSelectForQuery() + ); + } + + public function testGetSelectForQueryWithPathWithDataFilterOnVocabSourceAdded(): void + { + $parser = $this->getDatabasePathsParser(); + $filter = $this->getPathFilter( + FilterType::DATA, + 'some data' + ); + $data_column = $parser->addPathAndGetColumn($this->getPath( + $this->getTag('table1', '', '', 'step1'), + $this->getTag('table1', '', 'filter_data', 'step2', DataType::VOCAB_SOURCE, $filter), + $this->getTag('table2', 'table1', '', 'step3'), + $this->getTag('table2', 'table1', 'data', 'step4') + )); + + $this->assertSame( + 'SELECT p1t1.rbac_id, p1t1.obj_id, p1t1.obj_type ' . 'FROM ' . + '~identifier:table1_name~ AS ~identifier:p1t1~ JOIN ' . + '~identifier:table2_name~ AS ~identifier:p1t2~ ON ' . + '~text:' . LOMVocabInitiator::SOURCE . '~ IN (~text:some data~) AND ' . + '~identifier:p1t1~.rbac_id = ~identifier:p1t2~.rbac_id AND ' . + '~identifier:p1t1~.obj_id = ~identifier:p1t2~.obj_id AND ' . + '~identifier:p1t1~.obj_type = ~identifier:p1t2~.obj_type AND ' . + 'p1t1.~identifier:table1_id~ = ~identifier:p1t2~.parent_id AND ' . + '~text:table1~ = ~identifier:p1t2~.parent_type', + $parser->getSelectForQuery() + ); + } + + public function testGetSelectForQueryWithPathWithFilterOnOnlyTableAdded(): void + { + $parser = $this->getDatabasePathsParser(); + $filter = $this->getPathFilter( + FilterType::DATA, + 'some data' + ); + $data_column = $parser->addPathAndGetColumn($this->getPath( + $this->getTag('table', '', 'filter_data', 'step1', DataType::STRING, $filter), + $this->getTag('table', '', 'data', 'step2'), + )); + + $this->assertSame( + 'SELECT p1t1.rbac_id, p1t1.obj_id, p1t1.obj_type ' . 'FROM ' . + '~identifier:table_name~ AS ~identifier:p1t1~ WHERE ' . + "COALESCE(~identifier:p1t1~.~identifier:filter_data~, '') IN (~text:some data~)", + $parser->getSelectForQuery() + ); + } + + public function testGetSelectForQueryWithPathWithFilterOnOnlyTableButMultiplePathsAdded(): void + { + $parser = $this->getDatabasePathsParser(); + $filter = $this->getPathFilter( + FilterType::DATA, + 'some data' + ); + $data_column_1 = $parser->addPathAndGetColumn($this->getPath( + $this->getTag('table', '', 'filter_data', 'step1', DataType::STRING, $filter), + $this->getTag('table', '', 'data', 'step2'), + )); + $data_column_2 = $parser->addPathAndGetColumn($this->getPath( + $this->getTag('table1', '', '', 'step1'), + $this->getTag('table1', '', 'data2', 'step2'), + )); + + $this->assertSame( + 'SELECT p1t1.rbac_id, p1t1.obj_id, p1t1.obj_type ' . 'FROM ' . + 'il_meta_general AS base LEFT JOIN (' . + '~identifier:table_name~ AS ~identifier:p1t1~) ON ' . + '~identifier:base~.rbac_id = ~identifier:p1t1~.rbac_id AND ' . + '~identifier:base~.obj_id = ~identifier:p1t1~.obj_id AND ' . + '~identifier:base~.obj_type = ~identifier:p1t1~.obj_type AND ' . + "COALESCE(~identifier:p1t1~.~identifier:filter_data~, '') IN (~text:some data~) LEFT JOIN " . + '(~identifier:table1_name~ AS ~identifier:p2t1~) ON ' . + '~identifier:base~.rbac_id = ~identifier:p2t1~.rbac_id AND ' . + '~identifier:base~.obj_id = ~identifier:p2t1~.obj_id AND ' . + '~identifier:base~.obj_type = ~identifier:p2t1~.obj_type', + $parser->getSelectForQuery() + ); + } +} diff --git a/components/ILIAS/MetaData/tests/Services/Derivation/Creation/CreatorTest.php b/components/ILIAS/MetaData/tests/Services/Derivation/Creation/CreatorTest.php new file mode 100644 index 000000000000..62b36c31a488 --- /dev/null +++ b/components/ILIAS/MetaData/tests/Services/Derivation/Creation/CreatorTest.php @@ -0,0 +1,226 @@ +prepared_changes[] = [ + 'path' => $path->toString(), + 'values' => $values + ]; + return $set; + } + + public function prepareDelete(SetInterface $set, PathInterface $path): SetInterface + { + $set = clone $set; + $set->prepared_changes[] = ['delete should not be prepared!']; + return $set; + } + + public function prepareForceCreate( + SetInterface $set, + PathInterface $path, + string ...$values + ): SetInterface { + $set = clone $set; + $set->prepared_changes[] = ['force create should not be prepared!']; + return $set; + } + }; + + $builder = new class () extends NullPathBuilder { + protected string $path_string = '~start~'; + + public function withNextStep(string $name, bool $add_as_first = false): PathBuilder + { + $builder = clone $this; + if ($add_as_first) { + $name .= '[added as first]'; + } + $builder->path_string .= '%' . $name; + return $builder; + } + + public function withAdditionalFilterAtCurrentStep(FilterType $type, string ...$values): PathBuilder + { + $builder = clone $this; + $builder->path_string .= '{' . $type->value . ':' . implode('><', $values) . '}'; + return $builder; + } + + public function get(): PathInterface + { + return new class ($this->path_string) extends NullPath { + public function __construct(protected string $path_string) + { + } + + public function toString(): string + { + return $this->path_string; + } + }; + } + }; + + $path_factory = new class ($builder) extends NullPathFactory { + public function __construct(protected PathBuilder $builder) + { + } + + public function custom(): PathBuilder + { + return $this->builder; + } + }; + + $scaffold_provider = new class () extends NullScaffoldProvider { + public function set(): SetInterface + { + return new class () extends NullSet { + public array $prepared_changes = []; + }; + } + }; + + return new Creator($manipulator, $path_factory, $scaffold_provider); + } + + public function testCreateSet(): void + { + $creator = $this->getCreator(); + + $set = $creator->createSet('some title'); + + $expected_title_changes = [ + 'path' => '~start~%general%title%string', + 'values' => ['some title'] + ]; + $prepared_changes = $set->prepared_changes; + $this->assertCount(1, $prepared_changes); + $this->assertContains($expected_title_changes, $prepared_changes); + } + + public function testCreateSetWithLanguage(): void + { + $creator = $this->getCreator(); + + $set = $creator->createSet('some title', '', 'tg'); + + $expected_title_changes = [ + 'path' => '~start~%general%title%string', + 'values' => ['some title'] + ]; + $expected_title_lang_changes = [ + 'path' => '~start~%general%title%language', + 'values' => ['tg'] + ]; + $expected_lang_changes = [ + 'path' => '~start~%general%language', + 'values' => ['tg'] + ]; + $prepared_changes = $set->prepared_changes; + $this->assertCount(3, $prepared_changes); + $this->assertContains($expected_title_changes, $prepared_changes); + $this->assertContains($expected_title_lang_changes, $prepared_changes); + $this->assertContains($expected_lang_changes, $prepared_changes); + } + + public function testCreateSetWithDescription(): void + { + $creator = $this->getCreator(); + + $set = $creator->createSet('some title', 'some description'); + + $expected_title_changes = [ + 'path' => '~start~%general%title%string', + 'values' => ['some title'] + ]; + $expected_description_changes = [ + 'path' => '~start~%general%description%string', + 'values' => ['some description'] + ]; + $prepared_changes = $set->prepared_changes; + $this->assertCount(2, $prepared_changes); + $this->assertContains($expected_title_changes, $prepared_changes); + $this->assertContains($expected_description_changes, $prepared_changes); + } + + public function testCreateSetWithDescriptionAndLanguage(): void + { + $creator = $this->getCreator(); + + $set = $creator->createSet('some title', 'some description', 'tg'); + + $expected_title_changes = [ + 'path' => '~start~%general%title%string', + 'values' => ['some title'] + ]; + $expected_title_lang_changes = [ + 'path' => '~start~%general%title%language', + 'values' => ['tg'] + ]; + $expected_lang_changes = [ + 'path' => '~start~%general%language', + 'values' => ['tg'] + ]; + $expected_description_changes = [ + 'path' => '~start~%general%description%string', + 'values' => ['some description'] + ]; + $expected_description_lang_changes = [ + 'path' => '~start~%general%description%language', + 'values' => ['tg'] + ]; + $prepared_changes = $set->prepared_changes; + $this->assertCount(5, $prepared_changes); + $this->assertContains($expected_title_changes, $prepared_changes); + $this->assertContains($expected_title_lang_changes, $prepared_changes); + $this->assertContains($expected_lang_changes, $prepared_changes); + $this->assertContains($expected_description_changes, $prepared_changes); + $this->assertContains($expected_description_lang_changes, $prepared_changes); + } +} diff --git a/components/ILIAS/MetaData/tests/Services/Derivation/DerivatorTest.php b/components/ILIAS/MetaData/tests/Services/Derivation/DerivatorTest.php new file mode 100644 index 000000000000..524847881bd0 --- /dev/null +++ b/components/ILIAS/MetaData/tests/Services/Derivation/DerivatorTest.php @@ -0,0 +1,120 @@ +throw_exception) { + throw new \ilMDRepositoryException('failed'); + } + + $this->transferred_md[] = [ + 'from_set' => $from_set, + 'to_obj_id' => $to_obj_id, + 'to_sub_id' => $to_sub_id, + 'to_type' => $to_type + ]; + $this->error_thrown[] = $throw_error_if_invalid; + } + }; + return new class ($from_set, $repo) extends Derivator { + public function exposeRepository(): RepositoryInterface + { + return $this->repository; + } + }; + + } + + public function testForObject(): void + { + $from_set = new NullSet(); + $derivator = $this->getDerivator($from_set); + $derivator->forObject(78, 5, 'to_type'); + + $this->assertCount(1, $derivator->exposeRepository()->transferred_md); + $this->assertSame( + [ + 'from_set' => $from_set, + 'to_obj_id' => 78, + 'to_sub_id' => 5, + 'to_type' => 'to_type' + ], + $derivator->exposeRepository()->transferred_md[0] + ); + $this->assertCount(1, $derivator->exposeRepository()->error_thrown); + $this->assertTrue($derivator->exposeRepository()->error_thrown[0]); + } + + public function testForObjectWithSubIDZero(): void + { + $from_set = new NullSet(); + $derivator = $this->getDerivator($from_set); + $derivator->forObject(78, 0, 'to_type'); + + $this->assertCount(1, $derivator->exposeRepository()->transferred_md); + $this->assertSame( + [ + 'from_set' => $from_set, + 'to_obj_id' => 78, + 'to_sub_id' => 78, + 'to_type' => 'to_type' + ], + $derivator->exposeRepository()->transferred_md[0] + ); + $this->assertCount(1, $derivator->exposeRepository()->error_thrown); + $this->assertTrue($derivator->exposeRepository()->error_thrown[0]); + } + + public function testForObjectException(): void + { + $from_set = new NullSet(); + $derivator = $this->getDerivator($from_set, true); + + $this->expectException(\ilMDServicesException::class); + $derivator->forObject(78, 0, 'to_type'); + } +} diff --git a/components/ILIAS/MetaData/tests/Services/Derivation/SourceSelectorTest.php b/components/ILIAS/MetaData/tests/Services/Derivation/SourceSelectorTest.php new file mode 100644 index 000000000000..8ba90f3df021 --- /dev/null +++ b/components/ILIAS/MetaData/tests/Services/Derivation/SourceSelectorTest.php @@ -0,0 +1,110 @@ +getSourceSelector(); + $derivator = $source_selector->fromObject(7, 33, 'type'); + + $this->assertSame(7, $derivator->from_set->obj_id); + $this->assertSame(33, $derivator->from_set->sub_id); + $this->assertSame('type', $derivator->from_set->type); + } + + public function testFromObjectWithSubIDZero(): void + { + $source_selector = $this->getSourceSelector(); + $derivator = $source_selector->fromObject(67, 0, 'type'); + + $this->assertSame(67, $derivator->from_set->obj_id); + $this->assertSame(67, $derivator->from_set->sub_id); + $this->assertSame('type', $derivator->from_set->type); + } + + public function testFromBasicProperties(): void + { + $source_selector = $this->getSourceSelector(); + + $derivator = $source_selector->fromBasicProperties( + 'great title', + 'amazing description', + 'best language' + ); + + $this->assertSame('great title', $derivator->from_set->title); + $this->assertSame('amazing description', $derivator->from_set->description); + $this->assertSame('best language', $derivator->from_set->language); + } +} diff --git a/components/ILIAS/MetaData/tests/Services/Manipulator/ManipulatorTest.php b/components/ILIAS/MetaData/tests/Services/Manipulator/ManipulatorTest.php index e58ae530eb2c..4a4622ffff63 100755 --- a/components/ILIAS/MetaData/tests/Services/Manipulator/ManipulatorTest.php +++ b/components/ILIAS/MetaData/tests/Services/Manipulator/ManipulatorTest.php @@ -26,6 +26,8 @@ use ILIAS\MetaData\Manipulator\NullManipulator as NullInternalManipulator; use ILIAS\MetaData\Elements\NullSet; use ILIAS\MetaData\Elements\SetInterface; +use ILIAS\MetaData\Repository\RepositoryInterface; +use ILIAS\MetaData\Repository\NullRepository; class ManipulatorTest extends TestCase { @@ -38,16 +40,24 @@ public function __construct(public string $string) }; } - protected function getManipulator(): Manipulator + protected function getManipulator(bool $throw_exception = false): Manipulator { - $internal_manipulator = new class () extends NullInternalManipulator { + $internal_manipulator = new class ($throw_exception) extends NullInternalManipulator { public array $executed_actions = []; + public function __construct(protected bool $throw_exception) + { + } + public function prepareCreateOrUpdate( SetInterface $set, PathInterface $path, string ...$values ): SetInterface { + if ($this->throw_exception) { + throw new \ilMDPathException('failed'); + } + $cloned_set = clone $set; $cloned_set->actions[] = [ 'action' => 'create or update', @@ -62,6 +72,10 @@ public function prepareForceCreate( PathInterface $path, string ...$values ): SetInterface { + if ($this->throw_exception) { + throw new \ilMDPathException('failed'); + } + $cloned_set = clone $set; $cloned_set->actions[] = [ 'action' => 'force create', @@ -82,23 +96,39 @@ public function prepareDelete( ]; return $cloned_set; } + }; + + $repository = new class ($throw_exception) extends NullRepository { + public array $executed_actions = []; - public function execute(SetInterface $set): void + public function __construct(protected bool $throw_exception) { - $set->executed_actions = $set->actions; + } + + public function manipulateMD(SetInterface $set): void + { + if ($this->throw_exception) { + throw new \ilMDRepositoryException('failed'); + } + + $this->executed_actions[] = $set->actions; } }; $set = new class () extends NullSet { public array $actions = []; - public array $executed_actions = []; }; - return new class ($internal_manipulator, $set) extends Manipulator { + return new class ($internal_manipulator, $repository, $set) extends Manipulator { public function exposeSet(): SetInterface { return $this->set; } + + public function exposeRepository(): RepositoryInterface + { + return $this->repository; + } }; } @@ -139,7 +169,15 @@ public function testPrepareCreateOrUpdate(): void ); } - public function testPrepareForce(): void + public function testPrepareCreateOrUpdateException(): void + { + $manipulator = $this->getManipulator(true); + + $this->expectException(\ilMDServicesException::class); + $manipulator->prepareCreateOrUpdate($this->getPath('path')); + } + + public function testPrepareForceCreate(): void { $exp1 = [ 'action' => 'force create', @@ -176,6 +214,14 @@ public function testPrepareForce(): void ); } + public function testPrepareForceCreateException(): void + { + $manipulator = $this->getManipulator(true); + + $this->expectException(\ilMDServicesException::class); + $manipulator->prepareForceCreate($this->getPath('path')); + } + public function testPrepareDelete(): void { $exp1 = [ @@ -246,19 +292,29 @@ public function testExecute(): void ->prepareDelete($this->getPath($exp3['path'])) ->prepareCreateOrUpdate($this->getPath($exp4['path']), ...$exp4['values']); $manipulator->execute(); - $manipulator5 = $manipulator + $manipulator = $manipulator ->prepareForceCreate($this->getPath($exp5['path']), ...$exp5['values']) ->prepareDelete($this->getPath($exp6['path'])) ->prepareCreateOrUpdate($this->getPath($exp7['path']), ...$exp7['values']); - $manipulator5->execute(); + $manipulator->execute(); + $executed_actions = $manipulator->exposeRepository()->executed_actions; + $this->assertCount(2, $executed_actions); $this->assertSame( [$exp1, $exp2, $exp3, $exp4], - $manipulator->exposeSet()->executed_actions + $executed_actions[0] ); $this->assertSame( [$exp1, $exp2, $exp3, $exp4, $exp5, $exp6, $exp7], - $manipulator5->exposeSet()->executed_actions + $executed_actions[1] ); } + + public function testExecuteException(): void + { + $manipulator = $this->getManipulator(true); + + $this->expectException(\ilMDServicesException::class); + $manipulator->execute(); + } } diff --git a/components/ILIAS/MetaData/tests/Services/Paths/BuilderTest.php b/components/ILIAS/MetaData/tests/Services/Paths/BuilderTest.php index d813604d2b48..6c0d135eb706 100755 --- a/components/ILIAS/MetaData/tests/Services/Paths/BuilderTest.php +++ b/components/ILIAS/MetaData/tests/Services/Paths/BuilderTest.php @@ -30,6 +30,10 @@ class BuilderTest extends TestCase { + /** + * Builder will throw an exception on get if the path contains a step with + * name INVALID. + */ protected function getBuilder(): Builder { $internal_builder = new class ('') extends NullInternalBuilder { @@ -51,12 +55,20 @@ public function withAdditionalFilterAtCurrentStep( FilterType $type, string ...$values ): BuilderInterface { + if ($this->path === '') { + throw new \ilMDPathException('failed'); + } + $filter = '{' . $type->value . ';' . implode(',', $values) . '}'; return new self($this->path . $filter); } public function get(): PathInterface { + if (str_contains($this->path, ':INVALID')) { + throw new \ilMDPathException('failed'); + } + return new class ($this->path) extends NullPath { public function __construct(public string $path_string) { @@ -111,18 +123,27 @@ public function testWithNextStepToSuperElement(): void public function testWithAdditionalFilterAtCurrentStep(): void { $builder = $this->getBuilder(); - $builder1 = $builder->withAdditionalFilterAtCurrentStep(FilterType::DATA, 'v1', 'v2'); + $builder1 = $builder->withNextStep('step') + ->withAdditionalFilterAtCurrentStep(FilterType::DATA, 'v1', 'v2'); $this->assertSame( '', $builder->exposePath() ); $this->assertSame( - '{data;v1,v2}', + ':step{data;v1,v2}', $builder1->exposePath() ); } + public function testWithAdditionalFilterAtCurrentStepEmptyPathException(): void + { + $builder = $this->getBuilder(); + + $this->expectException(\ilMDServicesException::class); + $builder->withAdditionalFilterAtCurrentStep(FilterType::DATA); + } + public function testGet(): void { $builder = $this->getBuilder() @@ -143,4 +164,12 @@ public function testGet(): void $builder3->get()->path_string ); } + + public function testGetException(): void + { + $builder = $this->getBuilder()->withNextStep('INVALID'); + + $this->expectException(\ilMDServicesException::class); + $builder->get(); + } } diff --git a/components/ILIAS/MetaData/tests/Services/Search/SearcherTest.php b/components/ILIAS/MetaData/tests/Services/Search/SearcherTest.php new file mode 100644 index 000000000000..ad4912bf06a3 --- /dev/null +++ b/components/ILIAS/MetaData/tests/Services/Search/SearcherTest.php @@ -0,0 +1,183 @@ +data = [ + 'obj_id' => $obj_id, + 'sub_id' => $sub_id, + 'type' => $type + ]; + } + }; + } + }; + $repository = new class () extends NullRepository { + public function searchMD( + ClauseInterface $clause, + ?int $limit, + ?int $offset, + FilterInterface ...$filters + ): \Generator { + yield 'clause' => $clause; + yield 'limit' => $limit; + yield 'offset' => $offset; + yield 'filters' => $filters; + } + }; + + return new Searcher($clause_factory, $filter_factory, $repository); + } + + public function testGetFilter(): void + { + $searcher = $this->getSearcher(); + + $filter = $searcher->getFilter(56, 98, 'type'); + $this->assertSame( + ['obj_id' => 56, 'sub_id' => 98, 'type' => 'type'], + $filter->data + ); + } + + public function testGetFilterWithSubIDZero(): void + { + $searcher = $this->getSearcher(); + + $filter = $searcher->getFilter(56, 0, 'type'); + $this->assertSame( + ['obj_id' => 56, 'sub_id' => Placeholder::OBJ_ID, 'type' => 'type'], + $filter->data + ); + } + + public function testGetFilterWithObjIDPlaceholder(): void + { + $searcher = $this->getSearcher(); + + $filter = $searcher->getFilter(Placeholder::ANY, 98, 'type'); + $this->assertSame( + ['obj_id' => Placeholder::ANY, 'sub_id' => 98, 'type' => 'type'], + $filter->data + ); + } + + public function testGetFilterWithSubIDPlaceholder(): void + { + $searcher = $this->getSearcher(); + + $filter = $searcher->getFilter(56, Placeholder::ANY, 'type'); + $this->assertSame( + ['obj_id' => 56, 'sub_id' => Placeholder::ANY, 'type' => 'type'], + $filter->data + ); + } + + public function testGetFilterWithTypePlaceholder(): void + { + $searcher = $this->getSearcher(); + + $filter = $searcher->getFilter(56, 98, Placeholder::ANY); + $this->assertSame( + ['obj_id' => 56, 'sub_id' => 98, 'type' => Placeholder::ANY], + $filter->data + ); + } + + public function testExecute(): void + { + $searcher = $this->getSearcher(); + $clause = new NullClause(); + + $results = iterator_to_array($searcher->execute($clause, null, null)); + $this->assertSame( + ['clause' => $clause, 'limit' => null, 'offset' => null, 'filters' => []], + $results + ); + } + + public function testExecuteWithLimit(): void + { + $searcher = $this->getSearcher(); + $clause = new NullClause(); + + $results = iterator_to_array($searcher->execute($clause, 999, null)); + $this->assertSame( + ['clause' => $clause, 'limit' => 999, 'offset' => null, 'filters' => []], + $results + ); + } + + public function testExecuteWithLimitAndOffset(): void + { + $searcher = $this->getSearcher(); + $clause = new NullClause(); + + $results = iterator_to_array($searcher->execute($clause, 999, 333)); + $this->assertSame( + ['clause' => $clause, 'limit' => 999, 'offset' => 333, 'filters' => []], + $results + ); + } + + public function testExecuteWithFilters(): void + { + $searcher = $this->getSearcher(); + $clause = new NullClause(); + $filter_1 = new NullFilter(); + $filter_2 = new NullFilter(); + $filter_3 = new NullFilter(); + + $results = iterator_to_array($searcher->execute($clause, 999, 333, $filter_1, $filter_2, $filter_3)); + $this->assertSame( + ['clause' => $clause, 'limit' => 999, 'offset' => 333, 'filters' => [$filter_1, $filter_2, $filter_3]], + $results + ); + } +} diff --git a/components/ILIAS/MetaData/tests/Services/ServicesTest.php b/components/ILIAS/MetaData/tests/Services/ServicesTest.php new file mode 100644 index 000000000000..3435dad33095 --- /dev/null +++ b/components/ILIAS/MetaData/tests/Services/ServicesTest.php @@ -0,0 +1,214 @@ +repositories[] = new class () extends NullRepository { + public array $deleted_md = []; + + public function getMD(int $obj_id, int $sub_id, string $type): SetInterface + { + return new class ($obj_id, $sub_id, $type) extends NullSet { + public array $data; + + public function __construct(int $obj_id, int $sub_id, string $type) + { + $this->data = [ + 'obj_id' => $obj_id, + 'sub_id' => $sub_id, + 'type' => $type, + ]; + } + }; + } + + public function getMDOnPath( + PathInterface $path, + int $obj_id, + int $sub_id, + string $type + ): SetInterface { + return new class ($path, $obj_id, $sub_id, $type) extends NullSet { + public array $data; + + public function __construct(PathInterface $path, int $obj_id, int $sub_id, string $type) + { + $this->data = [ + 'path' => $path, + 'obj_id' => $obj_id, + 'sub_id' => $sub_id, + 'type' => $type, + ]; + } + }; + } + + public function deleteAllMD(int $obj_id, int $sub_id, string $type): void + { + $this->deleted_md[] = [ + 'obj_id' => $obj_id, + 'sub_id' => $sub_id, + 'type' => $type + ]; + } + }; + } + + protected function readerFactory(): ReaderFactoryInterface + { + return new class () extends NullReaderFactory { + public function get(SetInterface $set): ReaderInterface + { + return new class ($set) extends NullReader { + public function __construct(public SetInterface $set) + { + } + }; + } + }; + } + + protected function manipulatorFactory(): ManipulatorFactoryInterface + { + return new class () extends NullManipulatorFactory { + public function get(SetInterface $set): ManipulatorInterface + { + return new class ($set) extends NullManipulator { + public function __construct(public SetInterface $set) + { + } + }; + } + }; + } + }; + } + + public function testRead(): void + { + $services = $this->getServices(); + $reader = $services->read(5, 17, 'type'); + + $this->assertSame( + ['obj_id' => 5, 'sub_id' => 17, 'type' => 'type'], + $reader->set->data + ); + } + + public function testReadWithPath(): void + { + $services = $this->getServices(); + $path = new NullPath(); + $reader = $services->read(5, 17, 'type', $path); + + $this->assertSame( + ['path' => $path, 'obj_id' => 5, 'sub_id' => 17, 'type' => 'type'], + $reader->set->data + ); + } + + public function testReadWithSubIDZero(): void + { + $services = $this->getServices(); + $reader = $services->read(23, 0, 'type'); + + $this->assertSame( + ['obj_id' => 23, 'sub_id' => 23, 'type' => 'type'], + $reader->set->data + ); + } + + public function testManipulate(): void + { + $services = $this->getServices(); + $manipulator = $services->manipulate(5, 17, 'type'); + + $this->assertSame( + ['obj_id' => 5, 'sub_id' => 17, 'type' => 'type'], + $manipulator->set->data + ); + } + + public function testManipulateWithSubIDZero(): void + { + $services = $this->getServices(); + $manipulator = $services->manipulate(35, 0, 'type'); + + $this->assertSame( + ['obj_id' => 35, 'sub_id' => 35, 'type' => 'type'], + $manipulator->set->data + ); + } + + public function testDeleteAll(): void + { + $services = $this->getServices(); + $services->deleteAll(34, 90, 'type'); + + $this->assertCount(1, $services->repositories); + $this->assertCount(1, $services->repositories[0]->deleted_md); + $this->assertSame( + ['obj_id' => 34, 'sub_id' => 90, 'type' => 'type'], + $services->repositories[0]->deleted_md[0] + ); + } + + public function testDeleteAllWithSubIDZero(): void + { + $services = $this->getServices(); + $services->deleteAll(789, 0, 'type'); + + $this->assertCount(1, $services->repositories); + $this->assertCount(1, $services->repositories[0]->deleted_md); + $this->assertSame( + ['obj_id' => 789, 'sub_id' => 789, 'type' => 'type'], + $services->repositories[0]->deleted_md[0] + ); + } +} diff --git a/components/ILIAS/MetaData/tests/XML/Reader/Standard/StandardTest.php b/components/ILIAS/MetaData/tests/XML/Reader/Standard/StandardTest.php new file mode 100644 index 000000000000..369f8d5bc6d5 --- /dev/null +++ b/components/ILIAS/MetaData/tests/XML/Reader/Standard/StandardTest.php @@ -0,0 +1,92 @@ +exposed = 'generated by StructurallyCoupled in version ' . + $version->value . ' from xml ' . $xml->asXML(); + } + }; + } + }; + + $legacy = new class () extends NullReader { + public function read(\SimpleXMLElement $xml, Version $version): SetInterface + { + return new class ($xml, $version) extends NullSet { + public string $exposed; + + public function __construct(\SimpleXMLElement $xml, Version $version) + { + $this->exposed = 'generated by Legacy in version ' . + $version->value . ' from xml ' . $xml->asXML(); + } + }; + } + }; + + return new Standard($structurally_coupled, $legacy); + } + + public function testReadWithVersion10_0(): void + { + $xml = new SimpleXMLElement('xml'); + $reader = $this->getReader(); + + $set = $reader->read($xml, Version::V10_0); + + $this->assertSame( + 'generated by StructurallyCoupled in version 10.0 from xml ' . $xml->asXML(), + $set->exposed + ); + } + + public function testReadWithVersion4_1_0(): void + { + $xml = new SimpleXMLElement('xml'); + $reader = $this->getReader(); + + $set = $reader->read($xml, Version::V4_1_0); + + $this->assertSame( + 'generated by Legacy in version 4.1.0 from xml ' . $xml->asXML(), + $set->exposed + ); + } +} diff --git a/components/ILIAS/MetaData/tests/XML/Reader/Standard/StructurallyCoupledTest.php b/components/ILIAS/MetaData/tests/XML/Reader/Standard/StructurallyCoupledTest.php new file mode 100644 index 000000000000..9f73b09963c0 --- /dev/null +++ b/components/ILIAS/MetaData/tests/XML/Reader/Standard/StructurallyCoupledTest.php @@ -0,0 +1,781 @@ +exposed_data) extends NullDefinition { + public function __construct(public array $exposed_data) + { + } + + public function name(): string + { + return $this->exposed_data['name']; + } + + public function dataType(): Type + { + return $this->exposed_data['type']; + } + }; + } + + public function addScaffoldToSubElements( + ScaffoldProviderInterface $scaffold_provider, + string $name + ): ?ElementInterface { + if ($name === 'failme') { + return null; + } + + $scaffold = clone $this; + $this->sub_elements[] = $scaffold; + + $scaffold->sub_elements = []; + $scaffold->super_element = $this; + $scaffold->exposed_data = []; + + $info_from_name = explode('-', $name); + + // langstrings need to treated as a special case + if ($name === 'string') { + $type = Type::STRING; + } elseif ($name === 'language') { + $type = Type::LANG; + } else { + $type = Type::from($info_from_name[1]); + } + + $scaffold->exposed_data['name'] = $name; + $scaffold->exposed_data['type'] = $type; + + return $scaffold; + } + + public function mark( + MarkerFactoryInterface $factory, + Action $action, + string $data_value = '' + ): void { + $this->exposed_data['marker_action'] = $action; + $this->exposed_data['marker_value'] = $data_value; + if (isset($this->super_element)) { + $this->super_element->mark($factory, $action); + } + } + + public function exposeData(): array + { + $data = $this->exposed_data; + $data['subs'] = []; + foreach ($this->sub_elements as $sub_element) { + $data['subs'][] = $sub_element->exposeData(); + } + return $data; + } + }; + } + + protected function getReader(): StructurallyCoupled + { + $root = $this->getElement(); + $root->exposed_data = ['name' => 'correctroot', 'type' => Type::NULL]; + + $scaffold_provider = new class ($root) extends NullScaffoldProvider { + public function __construct(protected ElementInterface $root) + { + } + + public function set(): SetInterface + { + return new class ($this->root) extends NullSet { + public function __construct(protected ElementInterface $root) + { + } + + public function getRoot(): ElementInterface + { + return $this->root; + } + }; + } + }; + + $dictionary = new class () extends NullDictionary { + public function tagForElement( + BaseElementInterface $element, + Version $version + ): ?TagInterface { + $info_from_name = explode('-', $element->getDefinition()->name()); + + //throw away name and type + array_shift($info_from_name); + array_shift($info_from_name); + + $langstring = false; + $omitted = false; + foreach ($info_from_name as $info) { + if ($info === SpecialCase::LANGSTRING->value . '.' . $version->value) { + $langstring = true; + } + if ($info === SpecialCase::OMITTED->value . '.' . $version->value) { + $omitted = true; + } + } + + return new class ($langstring, $omitted) extends NullTag { + public function __construct( + protected bool $langstring, + protected bool $omitted + ) { + } + + public function isExportedAsLangString(): bool + { + return $this->langstring; + } + + public function isOmitted(): bool + { + return $this->omitted; + } + }; + } + }; + + $copyright_handler = new class () extends NullCopyrightHandler { + public function copyrightFromExport(string $copyright): string + { + return '~parsed:' . $copyright . '~'; + } + }; + + return new StructurallyCoupled( + new NullMarkerFactory(), + $scaffold_provider, + $dictionary, + $copyright_handler + ); + } + + public function testRead(): void + { + $xml_string = /** @lang text */ << + + val1 + + val2.1 + val2.2 + + + XML; + $xml = new SimpleXMLElement($xml_string); + + $expected_data = [ + 'name' => 'correctroot', + 'type' => Type::NULL, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => '', + 'subs' => [ + [ + 'name' => 'el1-string', + 'type' => Type::STRING, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => 'val1', + 'subs' => [] + ], + [ + 'name' => 'el2-none', + 'type' => Type::NULL, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => '', + 'subs' => [ + [ + 'name' => 'el2.1-non_neg_int', + 'type' => Type::NON_NEG_INT, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => 'val2.1', + 'subs' => [] + ], + [ + 'name' => 'el2.2-duration', + 'type' => Type::DURATION, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => 'val2.2', + 'subs' => [] + ] + ] + ] + ] + ]; + + $reader = $this->getReader(); + $result_set = $reader->read($xml, Version::V10_0); + + $this->assertEquals( + $expected_data, + $result_set->getRoot()->exposeData() + ); + } + + public function testReadWrongStructure(): void + { + $xml_string = /** @lang text */ << + + val1 + + val2.1 + val2.2 + + + XML; + $xml = new SimpleXMLElement($xml_string); + + $expected_data = [ + 'name' => 'correctroot', + 'type' => Type::NULL, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => '', + 'subs' => [ + [ + 'name' => 'el1-string', + 'type' => Type::STRING, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => 'val1', + 'subs' => [] + ], + [ + 'name' => 'el2-none', + 'type' => Type::NULL, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => '', + 'subs' => [ + [ + 'name' => 'el2.2-duration', + 'type' => Type::DURATION, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => 'val2.2', + 'subs' => [] + ] + ] + ] + ] + ]; + + $reader = $this->getReader(); + $result_set = $reader->read($xml, Version::V10_0); + + $this->assertEquals( + $expected_data, + $result_set->getRoot()->exposeData() + ); + } + + public function testReadInvalidRootException(): void + { + $xml_string = /** @lang text */ << + + val1 + + val2.1 + val2.2 + + + XML; + $xml = new SimpleXMLElement($xml_string); + + $reader = $this->getReader(); + + $this->expectException(\ilMDXMLException::class); + $result_set = $reader->read($xml, Version::V10_0); + } + + public function testReadWithLanguageNone(): void + { + $xml_string = /** @lang text */ << + + none + + XML; + $xml = new SimpleXMLElement($xml_string); + + $expected_data = [ + 'name' => 'correctroot', + 'type' => Type::NULL, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => '', + 'subs' => [ + [ + 'name' => 'el-lang', + 'type' => Type::LANG, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => 'xx', + 'subs' => [] + ] + ] + ]; + + $reader = $this->getReader(); + $result_set = $reader->read($xml, Version::V10_0); + + $this->assertEquals( + $expected_data, + $result_set->getRoot()->exposeData() + ); + } + + public function testReadWithLangstring(): void + { + $xml_string = /** @lang text */ << + + + some text + + + XML; + $xml = new SimpleXMLElement($xml_string); + + $expected_data = [ + 'name' => 'correctroot', + 'type' => Type::NULL, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => '', + 'subs' => [ + [ + 'name' => 'el1-none-langstring.10.0', + 'type' => Type::NULL, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => '', + 'subs' => [ + [ + 'name' => 'string', + 'type' => Type::STRING, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => 'some text', + 'subs' => [] + ], + [ + 'name' => 'language', + 'type' => Type::LANG, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => 'br', + 'subs' => [] + ] + ] + ] + ] + ]; + + $reader = $this->getReader(); + $result_set = $reader->read($xml, Version::V10_0); + + $this->assertEquals( + $expected_data, + $result_set->getRoot()->exposeData() + ); + } + + public function testReadWithLangstringInDifferentVersion(): void + { + $xml_string = /** @lang text */ << + + + val1.1 + val1.2 + + + XML; + $xml = new SimpleXMLElement($xml_string); + + $expected_data = [ + 'name' => 'correctroot', + 'type' => Type::NULL, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => '', + 'subs' => [ + [ + 'name' => 'el1-none-langstring.4.1.0', + 'type' => Type::NULL, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => '', + 'subs' => [ + [ + 'name' => 'el1.1-string', + 'type' => Type::STRING, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => 'val1.1', + 'subs' => [] + ], + [ + 'name' => 'el1.2-lang', + 'type' => Type::LANG, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => 'val1.2', + 'subs' => [] + ] + ] + ] + ] + ]; + + $reader = $this->getReader(); + $result_set = $reader->read($xml, Version::V10_0); + + $this->assertEquals( + $expected_data, + $result_set->getRoot()->exposeData() + ); + } + + public function testReadWithLangstringLanguageNone(): void + { + $xml_string = /** @lang text */ << + + + some text + + + XML; + $xml = new SimpleXMLElement($xml_string); + + $expected_data = [ + 'name' => 'correctroot', + 'type' => Type::NULL, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => '', + 'subs' => [ + [ + 'name' => 'el1-none-langstring.10.0', + 'type' => Type::NULL, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => '', + 'subs' => [ + [ + 'name' => 'string', + 'type' => Type::STRING, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => 'some text', + 'subs' => [] + ], + [ + 'name' => 'language', + 'type' => Type::LANG, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => 'xx', + 'subs' => [] + ] + ] + ] + ] + ]; + + $reader = $this->getReader(); + $result_set = $reader->read($xml, Version::V10_0); + + $this->assertEquals( + $expected_data, + $result_set->getRoot()->exposeData() + ); + } + + public function testReadWithLangstringNoLanguage(): void + { + $xml_string = /** @lang text */ << + + + some text + + + XML; + $xml = new SimpleXMLElement($xml_string); + + $expected_data = [ + 'name' => 'correctroot', + 'type' => Type::NULL, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => '', + 'subs' => [ + [ + 'name' => 'el1-none-langstring.10.0', + 'type' => Type::NULL, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => '', + 'subs' => [ + [ + 'name' => 'string', + 'type' => Type::STRING, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => 'some text', + 'subs' => [] + ] + ] + ] + ] + ]; + + $reader = $this->getReader(); + $result_set = $reader->read($xml, Version::V10_0); + + $this->assertEquals( + $expected_data, + $result_set->getRoot()->exposeData() + ); + } + + public function testReadWithLangstringNoString(): void + { + $xml_string = /** @lang text */ << + + + + + + XML; + $xml = new SimpleXMLElement($xml_string); + + $expected_data = [ + 'name' => 'correctroot', + 'type' => Type::NULL, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => '', + 'subs' => [ + [ + 'name' => 'el1-none-langstring.10.0', + 'type' => Type::NULL, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => '', + 'subs' => [ + [ + 'name' => 'language', + 'type' => Type::LANG, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => 'pl', + 'subs' => [] + ] + ] + ] + ] + ]; + + $reader = $this->getReader(); + $result_set = $reader->read($xml, Version::V10_0); + + $this->assertEquals( + $expected_data, + $result_set->getRoot()->exposeData() + ); + } + + public function testReadWithOmittedDataCarryingElement(): void + { + $xml_string = /** @lang text */ << + + val1 + + val2.1 + val2.2 + + + XML; + $xml = new SimpleXMLElement($xml_string); + + $expected_data = [ + 'name' => 'correctroot', + 'type' => Type::NULL, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => '', + 'subs' => [ + [ + 'name' => 'el1-string', + 'type' => Type::STRING, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => 'val1', + 'subs' => [] + ], + [ + 'name' => 'el2-none', + 'type' => Type::NULL, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => '', + 'subs' => [ + [ + 'name' => 'el2.1-non_neg_int-omitted.10.0', + 'type' => Type::NON_NEG_INT, + 'subs' => [] + ], + [ + 'name' => 'el2.2-duration', + 'type' => Type::DURATION, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => 'val2.2', + 'subs' => [] + ] + ] + ] + ] + ]; + + $reader = $this->getReader(); + $result_set = $reader->read($xml, Version::V10_0); + + $this->assertEquals( + $expected_data, + $result_set->getRoot()->exposeData() + ); + } + + public function testReadWithOmittedContainerElement(): void + { + $xml_string = /** @lang text */ << + + + val1.1 + val1.2 + + val2 + + XML; + $xml = new SimpleXMLElement($xml_string); + + $expected_data = [ + 'name' => 'correctroot', + 'type' => Type::NULL, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => '', + 'subs' => [ + [ + 'name' => 'el1-none-omitted.10.0', + 'type' => Type::NULL, + 'subs' => [] + ], + [ + 'name' => 'el2-string', + 'type' => Type::STRING, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => 'val2', + 'subs' => [] + ], + ] + ]; + + $reader = $this->getReader(); + $result_set = $reader->read($xml, Version::V10_0); + + $this->assertEquals( + $expected_data, + $result_set->getRoot()->exposeData() + ); + } + + public function testReadWithOmittedInDifferentVersion(): void + { + $xml_string = /** @lang text */ << + + val1 + + XML; + $xml = new SimpleXMLElement($xml_string); + + $expected_data = [ + 'name' => 'correctroot', + 'type' => Type::NULL, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => '', + 'subs' => [ + [ + 'name' => 'el1-string-omitted.4.1.0', + 'type' => Type::STRING, + 'marker_action' => Action::CREATE_OR_UPDATE, + 'marker_value' => 'val1', + 'subs' => [] + ], + ] + ]; + + $reader = $this->getReader(); + $result_set = $reader->read($xml, Version::V10_0); + + $this->assertEquals( + $expected_data, + $result_set->getRoot()->exposeData() + ); + } +} diff --git a/components/ILIAS/MetaData/tests/XML/Writer/Standard/StandardTest.php b/components/ILIAS/MetaData/tests/XML/Writer/Standard/StandardTest.php new file mode 100644 index 000000000000..f92a7aadcb48 --- /dev/null +++ b/components/ILIAS/MetaData/tests/XML/Writer/Standard/StandardTest.php @@ -0,0 +1,595 @@ +element_as_array['subs'] as $sub_array) { + $sub = clone $this; + $sub->element_as_array = $sub_array; + yield $sub; + } + } + + public function getDefinition(): DefinitionInterface + { + return new class ($this->element_as_array) extends NullDefinition { + public function __construct(protected array $element_as_array) + { + } + + public function name(): string + { + return $this->element_as_array['name']; + } + }; + } + + public function getData(): DataInterface + { + return new class ($this->element_as_array) implements DataInterface { + public function __construct(protected array $element_as_array) + { + } + + public function type(): Type + { + return $this->element_as_array['type']; + } + + public function value(): string + { + return $this->element_as_array['value']; + } + }; + } + }; + $root->element_as_array = $set_as_array; + + return new class ($root) extends NullSet { + public function __construct(protected ElementInterface $root) + { + } + + public function getRoot(): ElementInterface + { + return $this->root; + } + }; + } + + protected function getStandardWriter(): Standard + { + $dictionary = new class () extends NullDictionary { + /** + * What version is selected is not part of unit tests. + */ + public function tagForElement( + BaseElementInterface $element, + Version $version + ): ?TagInterface { + if (!isset($element->element_as_array['specials'])) { + return null; + } + + return new class ($element->element_as_array['specials']) extends NullTag { + public function __construct( + protected array $specials + ) { + } + + public function isExportedAsLangString(): bool + { + return in_array(SpecialCase::LANGSTRING, $this->specials); + } + + public function isTranslatedAsCopyright(): bool + { + return in_array(SpecialCase::COPYRIGHT, $this->specials); + } + + public function isOmitted(): bool + { + return in_array(SpecialCase::OMITTED, $this->specials); + } + + public function isExportedAsAttribute(): bool + { + return in_array(SpecialCase::AS_ATTRIBUTE, $this->specials); + } + }; + } + }; + + $copyright_handler = new class () extends NullCopyrightHandler { + public function copyrightForExport(string $copyright): string + { + return '~parsed:' . $copyright . '~'; + } + }; + + return new Standard($dictionary, $copyright_handler); + } + + public function testWrite(): void + { + $set_array = [ + 'name' => 'el1', + 'type' => Type::NULL, + 'value' => '', + 'subs' => [ + [ + 'name' => 'el1.1', + 'type' => Type::STRING, + 'value' => 'val1.1', + 'subs' => [] + ], + [ + 'name' => 'el1.2', + 'type' => Type::NULL, + 'value' => '', + 'subs' => [ + [ + 'name' => 'el1.2.1', + 'type' => Type::NON_NEG_INT, + 'value' => 'val1.2.1', + 'subs' => [] + ], + [ + 'name' => 'el1.2.2', + 'type' => Type::DURATION, + 'value' => 'val1.2.2', + 'subs' => [] + ] + ] + ] + ] + ]; + + $expected_xml = /** @lang text */ << + + val1.1 + + val1.2.1 + val1.2.2 + + + 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 = [ + 'name' => 'el1', + 'type' => Type::NULL, + 'value' => '', + 'subs' => [ + [ + 'name' => 'el1.1', + 'type' => Type::LANG, + 'value' => 'xx', + 'subs' => [] + ] + ] + ]; + + $expected_xml = /** @lang text */ << + + none + + XML; + + $writer = $this->getStandardWriter(); + $set = $this->getSet($set_array); + $xml = $writer->write($set); + + $this->assertXmlStringEqualsXmlString($expected_xml, $xml->asXML()); + } + + public function testWriteWithLangString(): 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' => 'some text', + 'subs' => [] + ], + [ + 'name' => 'language', + 'type' => Type::LANG, + 'value' => 'br', + 'subs' => [] + ] + ] + ] + ] + ]; + + $expected_xml = /** @lang text */ << + + + some text + + + XML; + + $writer = $this->getStandardWriter(); + $set = $this->getSet($set_array); + $xml = $writer->write($set); + + $this->assertXmlStringEqualsXmlString($expected_xml, $xml->asXML()); + } + + public function testWriteWithLangStringNoLanguage(): 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' => 'some text', + 'subs' => [] + ] + ] + ] + ] + ]; + + $expected_xml = /** @lang text */ << + + + some text + + + XML; + + $writer = $this->getStandardWriter(); + $set = $this->getSet($set_array); + $xml = $writer->write($set); + + $this->assertXmlStringEqualsXmlString($expected_xml, $xml->asXML()); + } + + public function testWriteWithLangStringLanguageNone(): 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' => 'some text', + 'subs' => [] + ], + [ + 'name' => 'language', + 'type' => Type::LANG, + 'value' => 'xx', + 'subs' => [] + ] + ] + ] + ] + ]; + + $expected_xml = /** @lang text */ << + + + some text + + + XML; + + $writer = $this->getStandardWriter(); + $set = $this->getSet($set_array); + $xml = $writer->write($set); + + $this->assertXmlStringEqualsXmlString($expected_xml, $xml->asXML()); + } + + public function testWriteWithLangStringNoString(): void + { + $set_array = [ + 'name' => 'el1', + 'type' => Type::NULL, + 'value' => '', + 'subs' => [ + [ + 'name' => 'el1.1', + 'type' => Type::NULL, + 'value' => '', + 'specials' => [SpecialCase::LANGSTRING], + 'subs' => [ + [ + 'name' => 'language', + 'type' => Type::LANG, + 'value' => 'br', + 'subs' => [] + ] + ] + ] + ] + ]; + + $expected_xml = /** @lang text */ << + + + + + + XML; + + $writer = $this->getStandardWriter(); + $set = $this->getSet($set_array); + $xml = $writer->write($set); + + $this->assertXmlStringEqualsXmlString($expected_xml, $xml->asXML()); + } + + public function testWriteWithCopyright(): void + { + $set_array = [ + 'name' => 'el1', + 'type' => Type::NULL, + 'value' => '', + 'subs' => [ + [ + 'name' => 'cp', + 'type' => Type::STRING, + 'value' => 'some license', + 'specials' => [SpecialCase::COPYRIGHT], + 'subs' => [] + ] + ] + ]; + + $expected_xml = /** @lang text */ << + + ~parsed:some license~ + + XML; + + $writer = $this->getStandardWriter(); + $set = $this->getSet($set_array); + $xml = $writer->write($set); + + $this->assertXmlStringEqualsXmlString($expected_xml, $xml->asXML()); + } + + public function testWriteWithOmittedDataCarryingElement(): void + { + $set_array = [ + 'name' => 'el1', + 'type' => Type::NULL, + 'value' => '', + 'subs' => [ + [ + 'name' => 'el1.1', + 'type' => Type::STRING, + 'value' => 'val1.1', + 'specials' => [SpecialCase::OMITTED], + 'subs' => [] + ] + ] + ]; + + $expected_xml = /** @lang text */ << + + XML; + + $writer = $this->getStandardWriter(); + $set = $this->getSet($set_array); + $xml = $writer->write($set); + + $this->assertXmlStringEqualsXmlString($expected_xml, $xml->asXML()); + } + + public function testWriteWithOmittedContainerElement(): void + { + $set_array = [ + 'name' => 'el1', + 'type' => Type::NULL, + 'value' => '', + 'subs' => [ + [ + 'name' => 'el1.1', + 'type' => Type::NULL, + 'value' => '', + 'specials' => [SpecialCase::OMITTED], + 'subs' => [ + [ + 'name' => 'el1.1.1', + 'type' => Type::STRING, + 'value' => 'val1.1.1', + 'subs' => [] + ], + [ + 'name' => 'el1.1.2', + 'type' => Type::STRING, + 'value' => 'val1.1.2', + 'subs' => [] + ] + ] + ] + ] + ]; + + $expected_xml = /** @lang text */ << + + XML; + + $writer = $this->getStandardWriter(); + $set = $this->getSet($set_array); + $xml = $writer->write($set); + + $this->assertXmlStringEqualsXmlString($expected_xml, $xml->asXML()); + } + + public function testWriteWithExportedAsAttribute(): void + { + $set_array = [ + 'name' => 'el1', + 'type' => Type::NULL, + 'value' => '', + 'subs' => [ + [ + 'name' => 'el1.1', + 'type' => Type::NULL, + 'value' => '', + 'subs' => [ + [ + 'name' => 'el1.1.1', + 'type' => Type::STRING, + 'value' => 'val1.1.1', + 'specials' => [SpecialCase::AS_ATTRIBUTE], + 'subs' => [] + ], + [ + 'name' => 'el1.1.2', + 'type' => Type::STRING, + 'value' => 'val1.1.2', + 'subs' => [] + ] + ] + ] + ] + ]; + + $expected_xml = /** @lang text */ << + + + val1.1.2 + + + XML; + + $writer = $this->getStandardWriter(); + $set = $this->getSet($set_array); + $xml = $writer->write($set); + + $this->assertXmlStringEqualsXmlString($expected_xml, $xml->asXML()); + } + + public function testWriteWithoutDataCarryingElement(): void + { + $set_array = [ + 'name' => 'el1', + 'type' => Type::NULL, + 'value' => '', + 'subs' => [ + [ + 'name' => 'el1.1', + 'type' => Type::NULL, + 'value' => '', + 'subs' => [] + ], + [ + 'name' => 'el1.2', + 'type' => Type::NULL, + 'value' => '', + 'subs' => [] + ] + ] + ]; + + $expected_xml = /** @lang text */ << + + + + + XML; + + $writer = $this->getStandardWriter(); + $set = $this->getSet($set_array); + $xml = $writer->write($set); + + $this->assertXmlStringEqualsXmlString($expected_xml, $xml->asXML()); + } +} From 3614319f8d1c42180af3735c6637bbf56255edd7 Mon Sep 17 00:00:00 2001 From: Tim Schmitz Date: Mon, 29 Jul 2024 16:01:38 +0200 Subject: [PATCH 015/115] Metadata: repair dependencies in xml dictionary --- .../classes/XML/Dictionary/LOMDictionaryInitiator.php | 6 ++++-- components/ILIAS/MetaData/classes/XML/Services/Services.php | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/components/ILIAS/MetaData/classes/XML/Dictionary/LOMDictionaryInitiator.php b/components/ILIAS/MetaData/classes/XML/Dictionary/LOMDictionaryInitiator.php index 756dcc2ed11f..dc5f64243ee2 100644 --- a/components/ILIAS/MetaData/classes/XML/Dictionary/LOMDictionaryInitiator.php +++ b/components/ILIAS/MetaData/classes/XML/Dictionary/LOMDictionaryInitiator.php @@ -26,6 +26,7 @@ use ILIAS\MetaData\Elements\Structure\StructureSetInterface; use ILIAS\MetaData\Paths\FactoryInterface as PathFactoryInterface; use ILIAS\MetaData\Elements\Structure\StructureElementInterface; +use ILIAS\MetaData\Paths\Navigator\NavigatorFactoryInterface; class LOMDictionaryInitiator extends BaseDictionaryInitiator { @@ -35,17 +36,18 @@ class LOMDictionaryInitiator extends BaseDictionaryInitiator public function __construct( TagFactoryInterface $tag_factory, PathFactoryInterface $path_factory, + NavigatorFactoryInterface $navigator_factory, StructureSetInterface $structure ) { $this->tag_factory = $tag_factory; $this->path_factory = $path_factory; - parent::__construct($path_factory, $structure); + parent::__construct($path_factory, $navigator_factory, $structure); } public function get(): DictionaryInterface { $this->initDictionary(); - return new LOMDictionary($this->path_factory, ...$this->getTagAssignments()); + return new LOMDictionary($this->path_factory, $this->navigator_factory, ...$this->getTagAssignments()); } protected function initDictionary(): void diff --git a/components/ILIAS/MetaData/classes/XML/Services/Services.php b/components/ILIAS/MetaData/classes/XML/Services/Services.php index d64c1f5a8c7b..b0516c8b3905 100644 --- a/components/ILIAS/MetaData/classes/XML/Services/Services.php +++ b/components/ILIAS/MetaData/classes/XML/Services/Services.php @@ -61,6 +61,7 @@ public function standardWriter(): WriterInterface $dictionary = (new LOMDictionaryInitiator( new TagFactory(), $this->path_services->pathFactory(), + $this->path_services->navigatorFactory(), $this->structure_services->structure() ))->get(); return $this->standard_writer = new StandardWriter( @@ -77,6 +78,7 @@ public function standardReader(): ReaderInterface $dictionary = (new LOMDictionaryInitiator( new TagFactory(), $this->path_services->pathFactory(), + $this->path_services->navigatorFactory(), $this->structure_services->structure() ))->get(); $marker_factory = new MarkerFactory(); From abc81b6efa8dcf18c886261f8a421d041dd4f6ba Mon Sep 17 00:00:00 2001 From: Tim Schmitz Date: Mon, 29 Jul 2024 16:26:23 +0200 Subject: [PATCH 016/115] Metadata: re-add js --- components/ILIAS/MetaData/MetaData.php | 3 + .../Editor/Digest/ContentAssembler.php | 4 +- .../MetaData/js/ilMetaCopyrightListener.js | 71 ------------------- .../resources/ilMetaCopyrightListener.js | 61 ++++++++++++++++ 4 files changed, 66 insertions(+), 73 deletions(-) delete mode 100755 components/ILIAS/MetaData/js/ilMetaCopyrightListener.js create mode 100755 components/ILIAS/MetaData/resources/ilMetaCopyrightListener.js diff --git a/components/ILIAS/MetaData/MetaData.php b/components/ILIAS/MetaData/MetaData.php index 47da4cda24b6..be26d0e14b2b 100644 --- a/components/ILIAS/MetaData/MetaData.php +++ b/components/ILIAS/MetaData/MetaData.php @@ -36,5 +36,8 @@ public function init( new \ilMDSetupAgent( $pull[\ILIAS\Refinery\Factory::class] ); + + $contribute[Component\Resource\PublicAsset::class] = fn() => + new Component\Resource\ComponentJS($this, 'ilMetaCopyrightListener.js'); } } diff --git a/components/ILIAS/MetaData/classes/Editor/Digest/ContentAssembler.php b/components/ILIAS/MetaData/classes/Editor/Digest/ContentAssembler.php index 347fd685924a..832eebcdb308 100755 --- a/components/ILIAS/MetaData/classes/Editor/Digest/ContentAssembler.php +++ b/components/ILIAS/MetaData/classes/Editor/Digest/ContentAssembler.php @@ -250,7 +250,7 @@ protected function getCopyrightContent( $signal = $modal->getShowSignal(); yield ContentType::MODAL => $modal; - yield ContentType::JS_SOURCE => 'components/ILIAS/MetaData/js/ilMetaCopyrightListener.js'; + yield ContentType::JS_SOURCE => 'assets/js/ilMetaCopyrightListener.js'; yield ContentType::FORM => $this->getCopyrightSection($set, $signal); } @@ -401,7 +401,7 @@ protected function getTypicalLearningTimeSection( $inputs )->withAdditionalTransformation( $this->refinery->custom()->transformation(function ($vs) use ($dh) { - $vs = array_map(fn ($v) => is_null($v) ? $v : (int) $v, $vs); + $vs = array_map(fn($v) => is_null($v) ? $v : (int) $v, $vs); return $dh->durationFromIntegers(...$vs); }) ); diff --git a/components/ILIAS/MetaData/js/ilMetaCopyrightListener.js b/components/ILIAS/MetaData/js/ilMetaCopyrightListener.js deleted file mode 100755 index c487f673d763..000000000000 --- a/components/ILIAS/MetaData/js/ilMetaCopyrightListener.js +++ /dev/null @@ -1,71 +0,0 @@ -il.MetaDataCopyrightListener = { - - modalSignalId: "", - radioGroupId: "", - form: HTMLFormElement, - formButton: HTMLButtonElement, - - confirmed: false, - - - initialValue: "", - - - init: function(modalSignalId, radioGroupId) { - - this.modalSignalId = modalSignalId; - this.radioGroupId = radioGroupId; - this.form = $("input[id^='" + this.radioGroupId + "']")[0].form; - this.formButton = $(":submit", this.form); - - this.initialValue = - $("input[id^='" + this.radioGroupId + "']:checked").val(); - - $(this.form).on( - "submit", - function (event) { - - var current_value = - $("input[id^='" + il.MetaDataCopyrightListener.radioGroupId + "']:checked").val(); - - if(current_value != il.MetaDataCopyrightListener.initialValue) { - - if(!il.MetaDataCopyrightListener.confirmed) { - event.preventDefault(); - il.MetaDataCopyrightListener.triggerModal(event); - } - } - } - ); - }, - - triggerModal: function (event) { - - var buttonName = il.MetaDataCopyrightListener.formButton[0].textContent; - $('.modal-dialog').find('form').find('input').prop('value', buttonName); - $('.modal-dialog').find('form').on( - 'submit', - function (event) { - - $(il.MetaDataCopyrightListener.form).off(); - $(il.MetaDataCopyrightListener.formButton).off(); - il.MetaDataCopyrightListener.confirmed = true; - $(il.MetaDataCopyrightListener.formButton).click(); - return false; - } - ); - - // Show modal - $(document).trigger( - il.MetaDataCopyrightListener.modalSignalId, - { - 'id': this.modalSignalId, - 'event': event, - 'triggerer': this.radioGroupId //previously this was the form id - } - ); - - - - } -}; \ No newline at end of file diff --git a/components/ILIAS/MetaData/resources/ilMetaCopyrightListener.js b/components/ILIAS/MetaData/resources/ilMetaCopyrightListener.js new file mode 100755 index 000000000000..ae4ad354f710 --- /dev/null +++ b/components/ILIAS/MetaData/resources/ilMetaCopyrightListener.js @@ -0,0 +1,61 @@ +/* eslint-env jquery */ +/* eslint-env browser */ +il.MetaDataCopyrightListener = { + + modalSignalId: '', + radioGroupId: '', + form: HTMLFormElement, + formButton: HTMLButtonElement, + + confirmed: false, + + initialValue: '', + + init(modalSignalId, radioGroupId) { + this.modalSignalId = modalSignalId; + this.radioGroupId = radioGroupId; + this.form = $(`input[id^='${this.radioGroupId}']`)[0].form; + this.formButton = $(':submit', this.form); + + this.initialValue = $(`input[id^='${this.radioGroupId}']:checked`).val(); + + $(this.form).on( + 'submit', + (event) => { + const currentValue = $(`input[id^='${il.MetaDataCopyrightListener.radioGroupId}']:checked`).val(); + + if (currentValue !== il.MetaDataCopyrightListener.initialValue) { + if (!il.MetaDataCopyrightListener.confirmed) { + event.preventDefault(); + il.MetaDataCopyrightListener.triggerModal(event); + } + } + }, + ); + }, + + triggerModal(event) { + const buttonName = il.MetaDataCopyrightListener.formButton[0].textContent; + $('.modal-dialog').find('form').find('input').prop('value', buttonName); + $('.modal-dialog').find('form').on( + 'submit', + () => { + $(il.MetaDataCopyrightListener.form).off(); + $(il.MetaDataCopyrightListener.formButton).off(); + il.MetaDataCopyrightListener.confirmed = true; + $(il.MetaDataCopyrightListener.formButton).click(); + return false; + }, + ); + + // Show modal + $(document).trigger( + il.MetaDataCopyrightListener.modalSignalId, + { + id: this.modalSignalId, + event, + triggerer: this.radioGroupId, // previously this was the form id + }, + ); + }, +}; From 908b28d1aefb811da1622a6b31e62b79cce228d3 Mon Sep 17 00:00:00 2001 From: Tim Schmitz Date: Mon, 29 Jul 2024 17:42:31 +0200 Subject: [PATCH 017/115] Metadata: add doc for update info, fix file paths in docs --- components/ILIAS/MetaData/README.md | 1 + components/ILIAS/MetaData/docs/api.md | 4 +- .../ILIAS/MetaData/docs/enabling_lom.md | 2 +- .../MetaData/docs/identifying_objects.md | 2 +- .../ILIAS/MetaData/docs/lom_structure.md | 2 +- components/ILIAS/MetaData/docs/manipulator.md | 2 +- .../ILIAS/MetaData/docs/update_infos.md | 37 +++++++++++++++++++ 7 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 components/ILIAS/MetaData/docs/update_infos.md diff --git a/components/ILIAS/MetaData/README.md b/components/ILIAS/MetaData/README.md index 52b11142e984..4ea7da576d65 100755 --- a/components/ILIAS/MetaData/README.md +++ b/components/ILIAS/MetaData/README.md @@ -3,6 +3,7 @@ ## Business Rules * [Copyright Administration](docs/copyrights.md) +* [Important Changes](docs/update_infos.md) ## Technical Documentation diff --git a/components/ILIAS/MetaData/docs/api.md b/components/ILIAS/MetaData/docs/api.md index 220b7f16d1d3..229d47d9d78f 100755 --- a/components/ILIAS/MetaData/docs/api.md +++ b/components/ILIAS/MetaData/docs/api.md @@ -2,7 +2,7 @@ > This documentation does not warrant completeness or correctness. Please report any missing or wrong information using the [ILIAS issue tracker](https://mantis.ilias.de) -or contribute a fix via [Pull Request](../../../docs/development/contributing.md#pull-request-to-the-repositories). +or contribute a fix via [Pull Request](../../../../docs/development/contributing.md#pull-request-to-the-repositories). The `Metadata` component offers an API with which the [Learning Object Metadata (LOM)](lom_structure.md) of ILIAS objects can be read out, processed, @@ -159,7 +159,7 @@ the search is consistent, they are ordered by their IDs. well optimized for any specific task. If you have a use case for the search that performs especially poorly, feel free to report that in the [ILIAS issue tracker](https://mantis.ilias.de) or contribute a -possible improvement to the search via [Pull Request](../../../docs/development/contributing.md#pull-request-to-the-repositories). +possible improvement to the search via [Pull Request](../../../../docs/development/contributing.md#pull-request-to-the-repositories). ### Examples diff --git a/components/ILIAS/MetaData/docs/enabling_lom.md b/components/ILIAS/MetaData/docs/enabling_lom.md index aa03ba019c23..425a977696dd 100755 --- a/components/ILIAS/MetaData/docs/enabling_lom.md +++ b/components/ILIAS/MetaData/docs/enabling_lom.md @@ -2,7 +2,7 @@ > This documentation does not warrant completeness or correctness. Please report any missing or wrong information using the [ILIAS issue tracker](https://mantis.ilias.de) -or contribute a fix via [Pull Request](../../../docs/development/contributing.md#pull-request-to-the-repositories). +or contribute a fix via [Pull Request](../../../../docs/development/contributing.md#pull-request-to-the-repositories). In this documentation, we will outline briefly how support for Learning Object Metadata (LOM) can be added to an ILIAS object. diff --git a/components/ILIAS/MetaData/docs/identifying_objects.md b/components/ILIAS/MetaData/docs/identifying_objects.md index 94c85ad3d7e8..8d01427ec2a2 100755 --- a/components/ILIAS/MetaData/docs/identifying_objects.md +++ b/components/ILIAS/MetaData/docs/identifying_objects.md @@ -2,7 +2,7 @@ > This documentation does not warrant completeness or correctness. Please report any missing or wrong information using the [ILIAS issue tracker](https://mantis.ilias.de) -or contribute a fix via [Pull Request](../../../docs/development/contributing.md#pull-request-to-the-repositories). +or contribute a fix via [Pull Request](../../../../docs/development/contributing.md#pull-request-to-the-repositories). In the `MetaData` component, objects in ILIAS are generally identified by three parameters: diff --git a/components/ILIAS/MetaData/docs/lom_structure.md b/components/ILIAS/MetaData/docs/lom_structure.md index 256b57bd85c2..bffddccb2ffa 100755 --- a/components/ILIAS/MetaData/docs/lom_structure.md +++ b/components/ILIAS/MetaData/docs/lom_structure.md @@ -2,7 +2,7 @@ > This documentation does not warrant completeness or correctness. Please report any missing or wrong information using the [ILIAS issue tracker](https://mantis.ilias.de) -or contribute a fix via [Pull Request](../../../docs/development/contributing.md#pull-request-to-the-repositories). +or contribute a fix via [Pull Request](../../../../docs/development/contributing.md#pull-request-to-the-repositories). Metadata of objects in ILIAS follow the Learning Object Metadata (LOM) standard (for the most part, see [here](#specific-to-ilias) for diff --git a/components/ILIAS/MetaData/docs/manipulator.md b/components/ILIAS/MetaData/docs/manipulator.md index 6dd3bc93ac72..46345e1ed359 100755 --- a/components/ILIAS/MetaData/docs/manipulator.md +++ b/components/ILIAS/MetaData/docs/manipulator.md @@ -2,7 +2,7 @@ > This documentation does not warrant completeness or correctness. Please report any missing or wrong information using the [ILIAS issue tracker](https://mantis.ilias.de) -or contribute a fix via [Pull Request](../../../docs/development/contributing.md#pull-request-to-the-repositories). +or contribute a fix via [Pull Request](../../../../docs/development/contributing.md#pull-request-to-the-repositories). In this documentation we describe how exactly the `Manipulator` used in the [API](api.md) manipulates LOM sets via the method diff --git a/components/ILIAS/MetaData/docs/update_infos.md b/components/ILIAS/MetaData/docs/update_infos.md new file mode 100644 index 000000000000..75ad2c39572f --- /dev/null +++ b/components/ILIAS/MetaData/docs/update_infos.md @@ -0,0 +1,37 @@ +# Important Changes + +> This documentation does not warrant completeness or correctness. Please report any +missing or wrong information using the [ILIAS issue tracker](https://mantis.ilias.de) +or contribute a fix via [Pull Request](../../../../docs/development/contributing.md#pull-request-to-the-repositories). + +## With ILIAS 9 + +Due to changes in the Metadata component, certain LOM elements will not +be shown in the LOM editor anymore in ILIAS 9, if their value does not +conform to the [LOM standard](lom_structure.md). The following elements +are affected: + +- **All elements of type datetime**: In previous ILIAS versions, any string +could be entered for datetimes, but ILIAS 9 expects values in the form +`YYYY-MM-DDThh:mm:ss.sTZD`. Not all parts of this format need to be present, +`YYYY-MM-DD` and `YYYY` for example are also valid. The LOM editor in +ILIAS 9 will ignore values that do not fit this format.
+Further, for values that fit this format, only the date part is used, +the time is disregarded. +- **'Mozilla' as browser name**: `Mozilla` as `name` under `technical > requirement > orComposite` where +`type` is `browser` will be ignored in the LOM editor in ILIAS 9. + +The affected invalid elements are not deleted. They are still exported and imported, +and in the case of 'Mozilla' also still found via the Advanced Search. + +When trying to read out one such element, the LOM editor will write a +corresponding `info` to the ILIAS log, such that the element can be +corrected manually in the database if necessary. + +## With ILIAS 10 + +In ILIAS 10, the LOM elements mentioned [above](#with-ilias-9) will also +not be exported and imported anymore, and 'Mozilla' will not be found as +a browser name in the Advanced Search. + +Elements with invalid values can still be corrected manually in the database. From 772375b8f83ae91641b28792fded5e8e0a2ff4f1 Mon Sep 17 00:00:00 2001 From: Alex Killing Date: Tue, 30 Jul 2024 11:58:59 +0200 Subject: [PATCH 018/115] 41814: Entering https://help.ilias.de/ gets error --- .../Repository/Administration/class.ilObjRepositorySettings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/ILIAS/Repository/Administration/class.ilObjRepositorySettings.php b/components/ILIAS/Repository/Administration/class.ilObjRepositorySettings.php index c6faebd007f6..cdd9d363652a 100755 --- a/components/ILIAS/Repository/Administration/class.ilObjRepositorySettings.php +++ b/components/ILIAS/Repository/Administration/class.ilObjRepositorySettings.php @@ -120,7 +120,7 @@ public static function getNewItemGroups(): array $title = $row["titles"][$usr_lng] ?? false; if (!$title) { - $title = $row["titles"][$def_lng]; + $title = $row["titles"][$def_lng] ?? false; } if (!$title) { $title = array_shift($row["titles"]); From 58365dd0155846a34e68827ea14211ef0ef2f737 Mon Sep 17 00:00:00 2001 From: Matthias Kunkel Date: Tue, 30 Jul 2024 13:58:11 +0200 Subject: [PATCH 019/115] =?UTF-8?q?Replaced=20some=20usages=20of=20?= =?UTF-8?q?=E2=80=98execute=E2=80=99=20by=20a=20less=20cruel=20term.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lang/ilias_en.lang | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index 1fb5e22cc8cd..b7f6c4a0b642 100755 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -8500,7 +8500,6 @@ dateplaner#:#date_format#:#m/d/Y H:i dateplaner#:#end_date#:#End Date dateplaner#:#err_end_before_start#:#The appointment must not start before the end. dateplaner#:#err_missing_title#:#Please enter an appointment title. -dateplaner#:#execute#:#Apply dateplaner#:#free#:#Free dateplaner#:#group#:#Group dateplaner#:#grp_cal_end#:#Group ends @@ -8973,7 +8972,7 @@ ecs#:#ecs_connection_settings#:#Connection Settings ecs#:#ecs_consent_modal_title#:#Consent for data transfer ecs#:#ecs_consent_reset_confirm_title#:#Reset user consent for this participant ecs#:#ecs_cron_task_scheduler#:#ECS Task Scheduler -ecs#:#ecs_cron_task_scheduler_info#:#ECS tasks will be executed according to the schedule settings. Only possible, if ECS server is configured in administration at 'Extending ILIAS » ECS'. +ecs#:#ecs_cron_task_scheduler_info#:#ECS tasks will be carried out according to the schedule settings. This will only take place if an ECS server has been configured via ‘Administration » Extending ILIAS » ECS’. ecs#:#ecs_crs_alloc#:#Course Allocation ecs#:#ecs_crs_alloc_set#:#Edit Course Allocation ecs#:#ecs_crs_export#:#Course Releases @@ -11454,7 +11453,7 @@ mail#:#mail_notification_membership_section#:#Membership mail#:#mail_notification_subject#:#New mail in your inbox mail#:#mail_notify_orphaned#:#Notification Mail mail#:#mail_notify_orphaned_info#:#If you enter a value here that is larger than or equal to 1, ILIAS will send a notification e-mail to all affected accounts that many days before any internal mails are deleted. Please make sure that external e-mail reception is possible. -mail#:#mail_operation_on_invalid_folder#:#The operation cannot be executed, the folder given in the server request is invalid. Please contact an administrator. +mail#:#mail_operation_on_invalid_folder#:#It was not possible to carry out the requested operation. The folder given in the server request is invalid. Please contact an administrator. mail#:#mail_options_saved#:#Options saved. mail#:#mail_orphaned_mails#:#Delete old or orphaned mails mail#:#mail_orphaned_mails_desc#:#Deletes orphaned mails and mails that are older than the configured threshold value (configurable via the ‘Edit’ link to the right of this cron job). From 550eb8317db4108cea4a0c337cfa210d3b1ab274 Mon Sep 17 00:00:00 2001 From: Fabian Helfer Date: Tue, 30 Jul 2024 13:35:32 +0200 Subject: [PATCH 020/115] Set ilSession in TestImport early --- components/ILIAS/Test/classes/class.ilObjTestGUI.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/ILIAS/Test/classes/class.ilObjTestGUI.php b/components/ILIAS/Test/classes/class.ilObjTestGUI.php index 42ce38d1ac74..3c6b90b20389 100755 --- a/components/ILIAS/Test/classes/class.ilObjTestGUI.php +++ b/components/ILIAS/Test/classes/class.ilObjTestGUI.php @@ -1354,15 +1354,15 @@ protected function importFile(string $file_to_import, string $path_to_uploaded_f return; } + ilSession::set('path_to_import_file', $file_to_import); + 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))) { $this->importVerifiedFileObject(); return; } - ilSession::set('path_to_import_file', $file_to_import); - ilSession::set('path_to_uploaded_file_in_temp_dir', $path_to_uploaded_file_in_temp_dir); - $form = $this->buildImportQuestionsSelectionForm( 'importVerifiedFile', $importdir, From ffad42950e22c7c03e1fd0d32c417cf086468772 Mon Sep 17 00:00:00 2001 From: Matthias Kunkel Date: Tue, 30 Jul 2024 15:01:57 +0200 Subject: [PATCH 021/115] Several improvements of English language file made by Chris Potter --- lang/ilias_en.lang | 90 +++++++++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index b7f6c4a0b642..9d46f1cd2b4d 100755 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -3495,8 +3495,8 @@ common#:#adve_frm_post_settings#:#Forum Posts common#:#adve_general_settings#:#General Settings common#:#adve_survey_settings#:#Survey Settings common#:#all#:#All -common#:#all_global_roles#:#Global roles -common#:#all_local_roles#:#Local roles (all) +common#:#all_global_roles#:#Global Roles +common#:#all_local_roles#:#Local Roles (all) common#:#all_objects#:#All Objects common#:#all_roles#:#All Roles common#:#all_topics#:#All Topics @@ -3699,7 +3699,7 @@ common#:#check_web_resources#:#Check Weblinks common#:#check_web_resources_desc#:#If enabled all Weblinks will be checked if they are active. common#:#checked#:#Checked common#:#checked_files#:#Importable files -common#:#chg_ilias_and_webfolder_password#:#Change Webfolder Password +common#:#chg_ilias_and_webfolder_password#:#Change Web Folder Password common#:#chg_ilias_password#:#Change ILIAS Password common#:#chg_password#:#Change Password common#:#choose_language#:#Choose Your Language @@ -3984,7 +3984,7 @@ common#:#enable_multi_download#:#'Download of multiple objects' enabled common#:#enable_multi_download_info#:#Multiple folders/files can be selected and downloaded as zip archive. common#:#enable_password_assistance#:#Enable Password Assistance common#:#enable_preview#:#Enable Preview -common#:#enable_preview_info#:#Enable this option to display preview images on supported file types. +common#:#enable_preview_info#:#Enable the display of preview images for supported file types. common#:#enable_repository_dnd_upload#:#Enable in Repository common#:#enable_repository_dnd_upload_info#:#Enables that files can be dragged from the computer directly onto an object in the repository to start uploading the files into this object. common#:#enable_sahs_protocol_data#:#Activate Protocol Data @@ -3993,7 +3993,7 @@ common#:#enable_search_engine#:#Open Public Area for Internet Search Engines (e. common#:#enable_trash#:#Enable Trash common#:#enable_trash_info#:#If enabled, deleted Objects are moved into Trash and may be recovered later. When deactivating this option deleted Objects are removed irreversibly from the System! common#:#enable_webdav#:#Enable WebDAV access -common#:#enable_webdav_info#:#Allows WebDAV clients to access the repository as a webfolder. Users can open webfolders using the ‘Open as webfolder’ action in the repository, or by entering the following address in a WebDAV client: %1$s +common#:#enable_webdav_info#:#Allows WebDAV clients to access the repository as a web folder. Users can open web folders using the ‘Open as web folder’ action in the repository, or by entering the following address in a WebDAV client: %1$s common#:#enabled#:#Enabled common#:#enter_in_mb_desc#:#Enter a value in MB. common#:#enter_new_name#:#Please enter a name @@ -4088,9 +4088,9 @@ common#:#field_type#:#Field Type common#:#file#:#File common#:#file_add#:#Upload File common#:#file_add_and_metadata#:#Upload File and Edit Metadata -common#:#file_added#:#File uploaded +common#:#file_added#:#File uploaded. common#:#file_allowed_suffixes#:#Allowed file types: -common#:#file_confirm_delete_all_versions#:#You selected all file versions. This will delete the whole file object. Do you want to continue? +common#:#file_confirm_delete_all_versions#:#You have selected all file versions. Deleting all versions of this file will result in the deletion of the whole file object. Do you wish to continue? common#:#file_confirm_delete_versions#:#Are you sure you want to delete the following file version(s)? common#:#file_created#:#File has been added. common#:#file_edit#:#Edit File Information @@ -4098,18 +4098,18 @@ common#:#file_info#:#File Information common#:#file_is_infected#:#The file is infected by a virus. common#:#file_no_valid_file_type#:#This file type is not allowed. common#:#file_not_found#:#File Not Found -common#:#file_not_found_sec#:#This file cannot be found in ILIAS or has been blocked due to security reasons. +common#:#file_not_found_sec#:#This file cannot be found in ILIAS or has been blocked for security reasons. common#:#file_not_valid#:#File not valid! common#:#file_notice#:#Maximum upload size: common#:#file_objects#:#File Objects -common#:#file_rollback#:#Publish this version -common#:#file_rollback_done#:#File version %s is now the published version. -common#:#file_rollback_select_exact_one#:#Only one file version can be selected to make it the published version. +common#:#file_rollback#:#Publish This Version +common#:#file_rollback_done#:#File version %s has been published, assigned a new version number and can be found at the top of the ‘Versions’ table below. +common#:#file_rollback_select_exact_one#:#Only one file version can be published at any one time. Please select only one file version to publish. common#:#file_some_invalid_file_types_removed#:#Some file types are not allowed and have been removed. common#:#file_suffix_repl#:#File Upload Suffix Replacement common#:#file_suffix_repl_info#:#Enter file types with suffix (separated by comma) that shall be be renamed with a ‘.sec’ when uploaded into webspace to prevent execution of this file. This is done per default for these suffixes: -common#:#file_system_clean_temp_dir_cron#:#Clean temp directory -common#:#file_system_clean_temp_dir_cron_info#:#This job cleans the ILIAS temp-directory of files, which are older than 10 days. This counteracts the accumulation of unused files and therefore prevents an increased use of disk space by the temp directory. +common#:#file_system_clean_temp_dir_cron#:#Clean Temp Directory +common#:#file_system_clean_temp_dir_cron_info#:#This cron job cleans the ILIAS temp-directory of files which are older than 10 days. This prevents the accumulation of unused files which would otherwise lead to the temp directory taking up too much disk space. common#:#file_updated#:#File has been updated. common#:#file_upload_pending#:#Pending file common#:#file_valid#:#File is valid! @@ -4126,12 +4126,12 @@ common#:#filename_extension_missing#:#File name extension missing common#:#filename_hidden_backup_file#:#Hidden backup file common#:#filename_hidden_unix_file#:#Hidden Unix file common#:#filename_interoperability#:#Interoperability -common#:#filename_special_characters#:#Due to the character / this object is not visible in webfolders -common#:#filename_special_filename#:#Objects with names . and .. are not visible in webfolders +common#:#filename_special_characters#:#Due to the character / this object is not visible in web folders +common#:#filename_special_filename#:#Objects with names . and .. are not visible in web folders common#:#filename_visibility#:#Visibility -common#:#filename_windows_empty_extension#:#Due to the character . at the end of the name this object is not visible in Windows webfolders -common#:#filename_windows_special_characters#:#Due to one of the characters \ / : * ? " < > | this object is not visible in Windows webfolders -common#:#filename_windows_webdav_issue#:#Due to the character # this object is not visible in Windows webfolders +common#:#filename_windows_empty_extension#:#Due to the character . at the end of the name this object is not visible in Windows web folders +common#:#filename_windows_special_characters#:#Due to one of the characters \ / : * ? " < > | this object is not visible in Windows web folders +common#:#filename_windows_webdav_issue#:#Due to the character # this object is not visible in Windows web folders common#:#files#:#Files common#:#filesize#:#File Size common#:#filetype#:#File Type @@ -4279,7 +4279,7 @@ common#:#header_action#:#Insert Heading - Click to insert a heading. common#:#header_searchable#:#Searchable common#:#header_title#:#Header Title common#:#header_visible_registration#:#Visible in Registration -common#:#header_zip#:#Upload Multiple Files as Zip-Archive +common#:#header_zip#:#Upload Zip-Archive of Multiple Files common#:#height#:#Height common#:#help#:#Help common#:#hide#:#Hide @@ -4705,7 +4705,7 @@ common#:#month_12_long#:#December common#:#month_12_short#:#Dec common#:#monthly#:#monthly common#:#months#:#Months -common#:#mount_webfolder#:#Open as webfolder +common#:#mount_webfolder#:#Open as web folder common#:#move#:#Move common#:#moveChapter#:#Move common#:#movePage#:#Move @@ -5974,20 +5974,20 @@ common#:#webdav_missing_lang#:#There is no english version of the WebDAV Mount I common#:#webdav_mount_instructions#:#Mount Instructions common#:#webdav_problem_free_container#:#There are no objects that cause problems in this container. common#:#webdav_problem_info_duplicate#:#There is a file with the same title as the info file. -common#:#webdav_pwd_instruction#:#We suggest to create a local password for opening the repository as webfolder.
This password is only required for the webfolder functionality. -common#:#webdav_pwd_instruction_success#:#A new local password has been created. You can now open the repository as webfolder. +common#:#webdav_pwd_instruction#:#We suggest to create a local password for opening the repository as web folder.
This password is only required for the web folder functionality. +common#:#webdav_pwd_instruction_success#:#A new local password has been created. You can now open the repository as web folder. common#:#webdav_sure_delete_documents_s#:#Are you sure you want to delete the Mount Instructions with the following title: common#:#webdav_tbl_docs_head_title#:#Dokument title common#:#webdav_tbl_docs_title#:#List of uploaded WebDAV Mount Instructions common#:#webdav_upload_instructions#:#Upload Instructions common#:#webdav_versioning_info#:#If enabled, already existing files will get a new version instead of beeing overwritten. -common#:#webfolder_dir_info#:#You got here, because your browser can't open webfolders. Read the instructions for opening webfolders. +common#:#webfolder_dir_info#:#You got here, because your browser can't open web folders. Read the instructions for opening web folders. common#:#webfolder_index_of#:#Index of %1$s -common#:#webfolder_instructions#:#Webfolder instructions -common#:#webfolder_instructions_info#:#The webfolder instructions are shown on browsers, which can not open a webfolder directly. You can use HTML code, and the following placeholders: [WEBFOLDER_TITLE], [WEBFOLDER_URI], [WEBFOLDER_URI], [WEBFOLDER_URI_KONQUEROR], [WEBFOLDER_URI_NAUTILUS], [ADMIN_MAIL], [WINDOWS]...[/WINDOWS], [MAC]...[/MAC], [LINUX]...[/LINUX]. Clear the field to get the default instructions. -common#:#webfolder_instructions_text#:#[WINDOWS]

Instructions for connecting with Windows

  1. Open Windows-Explorer (e.g. key combination Windows + E).
  2. Rightclick on "This PC" and select "Map network drive...".
  3. Enter the following address as address (you may copy and paste the URL):

    [WEBFOLDER_URI]

  4. Click "Finish".
  5. Enter your login and password, and select "OK".
  6. You can now access the webfolder from the start menu "Computer".

Your login and password is identical to your local ILIAS login and can be changed in your personal settings.[/WINDOWS] [MAC]

Instructions for Mac OS X

  1. Open the Finder.
  2. Choose the menu 'Goto to > Connect to server...'.
    This opens the window 'connect to server'.
  3. Enter this URL as server URL:
    [WEBFOLDER_URI]
    und click on 'Connect'.
  4. Enter your login and password, and select "OK".

Your login and password is identical to your local ILIAS login and can be changed via "Personal Settings".[/MAC][LINUX]

Instructions for Linux with Konqueror

  1. Open Konqueror Browser.
  2. Enter this URL as server URL:
    [WEBFOLDER_URI_KONQUEROR]
    and press enter.
  3. Enter your login and password, and select "OK".

Instructions for Linux with Nautilus

  1. Open Nautilus Browser.
  2. Enter this URL as server URL:
    [WEBFOLDER_URI_NAUTILUS]
    and press enter.
  3. Enter your login and password, and select "OK".

Your login and password is identical to your local ILIAS login and can be changed in your personal settings.[/LINUX]

Tips & Support

  • These steps need to be done only once. You may access your connection again later.
  • Connect to a higher folder. You can always navigate down the tree, but nut upwards.
  • If your computer can not open the WebDAV Connection, please contact ILIAS Support.
-common#:#webfolder_instructions_titletext#:#Open as webfolder -common#:#webfolder_mount_dir_with#:#Open this page as a Webfolder with Internet Explorer 6, Konqueror, Nautilus, other Browser. +common#:#webfolder_instructions#:#Web folder instructions +common#:#webfolder_instructions_info#:#The web folder instructions are shown on browsers, which can not open a web folder directly. You can use HTML code, and the following placeholders: [WEBFOLDER_TITLE], [WEBFOLDER_URI], [WEBFOLDER_URI], [WEBFOLDER_URI_KONQUEROR], [WEBFOLDER_URI_NAUTILUS], [ADMIN_MAIL], [WINDOWS]...[/WINDOWS], [MAC]...[/MAC], [LINUX]...[/LINUX]. Clear the field to get the default instructions. +common#:#webfolder_instructions_text#:#[WINDOWS]

Instructions for connecting with Windows

  1. Open Windows-Explorer (e.g. key combination Windows + E).
  2. Rightclick on "This PC" and select "Map network drive...".
  3. Enter the following address as address (you may copy and paste the URL):

    [WEBFOLDER_URI]

  4. Click "Finish".
  5. Enter your login and password, and select "OK".
  6. You can now access the web folder from the start menu "Computer".

Your login and password is identical to your local ILIAS login and can be changed in your personal settings.[/WINDOWS] [MAC]

Instructions for Mac OS X

  1. Open the Finder.
  2. Choose the menu 'Goto to > Connect to server...'.
    This opens the window 'connect to server'.
  3. Enter this URL as server URL:
    [WEBFOLDER_URI]
    und click on 'Connect'.
  4. Enter your login and password, and select "OK".

Your login and password is identical to your local ILIAS login and can be changed via "Personal Settings".[/MAC][LINUX]

Instructions for Linux with Konqueror

  1. Open Konqueror Browser.
  2. Enter this URL as server URL:
    [WEBFOLDER_URI_KONQUEROR]
    and press enter.
  3. Enter your login and password, and select "OK".

Instructions for Linux with Nautilus

  1. Open Nautilus Browser.
  2. Enter this URL as server URL:
    [WEBFOLDER_URI_NAUTILUS]
    and press enter.
  3. Enter your login and password, and select "OK".

Your login and password is identical to your local ILIAS login and can be changed in your personal settings.[/LINUX]

Tips & Support

  • These steps need to be done only once. You may access your connection again later.
  • Connect to a higher folder. You can always navigate down the tree, but nut upwards.
  • If your computer can not open the WebDAV Connection, please contact ILIAS Support.
+common#:#webfolder_instructions_titletext#:#Open as web folder +common#:#webfolder_mount_dir_with#:#Open this page as a web folder with Internet Explorer 6, Konqueror, Nautilus, other Browser. common#:#webr#:#Weblink common#:#webr_active#:#Active common#:#webr_add#:#Add Weblink @@ -9734,7 +9734,7 @@ file#:#copyright_custom#:#Custom file#:#copyright_custom_info#:#Choose a custom copyright which will be applied to all unzipped files of this archive. file#:#copyright_inherited#:#Inherited file#:#copyright_inherited_info#:#Apply the copyright of the zip archive to its unzipped files.
Copyright of zip archive: %s. -file#:#could_not_create_file_objs#:#An error occurred while creating the file objects, contact the administrators of the platform. +file#:#could_not_create_file_objs#:#An error occurred while creating your file objects. Please contact the administrators of this platform. file#:#de_activate_icon#:#Activate / Deactivate file#:#download_ascii_filename#:#Allow Only ASCII Characters in Downloaded Filenames file#:#download_ascii_filename_info#:#Downloaded files should only have ASCII-characters in their filename. Deactivate to use all characters. @@ -9746,18 +9746,18 @@ file#:#file_import#:#Import File file#:#file_new_version#:#Create New Version file#:#file_new_version_info#:#Create new file version. Previous versions will not be modified. file#:#file_publish#:#Publish Draft -file#:#file_rollback_rollback_first#:#The version could not be reset because an unpublished draft exists. -file#:#file_rollback_same_version#:#This is already the published version +file#:#file_rollback_rollback_first#:#The selected version could not be published because an unpublished draft exists. +file#:#file_rollback_same_version#:#This is already the published version! file#:#file_unpublish#:#Mark as Draft file#:#file_upload_info_file_with_critical_extension#:#At least one uploaded file contains a critical or unknown file ending. Whenever the file is downloaded, its ending will be changed to ‘.sec’. If necessary, contact your administrator. Filename(s): %s file#:#file_uploaded_by#:#Version Uploaded By file#:#file_version_draft#:#Draft Version -file#:#file_version_draft_info#:#The latest version is in "Draft" status. As long as the version has not been published, no new versions can be created. People with read permission on the file get the latest published version. +file#:#file_version_draft_info#:#The latest version of this file has the status ‘Draft’. As long as this version has not been published, no new versions can be created. People with read permission for the file get the most recent previously published version. file#:#form_icon_creation#:#Create Icon file#:#form_icon_updating#:#Update Icon file#:#general_upload_error_occured#:#An unexpected error occurred during upload. file#:#important_info#:#Important Information -file#:#important_info_byline#:#The information is displayed in the "Info" tab. +file#:#important_info_byline#:#The information will be displayed in the ‘Info’ tab. file#:#input_active#:#Active file#:#input_desc_active#:#Activate this icon. file#:#input_desc_icon#:#Image to be used as the icon for files with the specified suffixes. @@ -9766,7 +9766,7 @@ file#:#input_icon#:#Icon file#:#input_suffixes#:#Suffixes file#:#migrated#:#Status file#:#mime_type#:#MIME Type -file#:#msg_cant_unpublish#:#Action could not be executed +file#:#msg_cant_unpublish#:#File could not be unpublished. file#:#msg_confirm_entry_deletion#:#Are you sure you want to delete the following entry?: file#:#msg_error_active_suffixes_blacklisted#:#One of the selected file extensions is on the global blacklist and cannot therefore be currently used. file#:#msg_error_active_suffixes_conflict#:#Error: It is not possible to have multiple icons activated for the same suffix. Please deactivate either this icon or the other activated icon whose suffixes overlap with those of this icon. @@ -9787,11 +9787,11 @@ file#:#preview_caption#:#Preview %sof %s file#:#preview_image_size_info#:#The preview versions of images will be downscaled or upscaled as appropriate, so that their longest side is the length (in px) entered here. file#:#preview_persisting#:#Persistent Preview Images file#:#preview_persisting_info#:#Generated preview images will be stored by ILIAS and used from then on each time the preview icon for that file is clicked on. If deactivated, previews will be generated anew each time. -file#:#publish_before_delete#:#Version(s) could not be deleted because an unpublished draft exists. +file#:#publish_before_delete#:#It was not possible to delete any of the existing versions because an unpublished draft exists. file#:#replace_file_info#:#All previous file versions will be deleted. file#:#resource_id#:#Resource ID file#:#service_settings#:#Additional Features -file#:#service_settings_saved#:#Saved +file#:#service_settings_saved#:#Changes saved. file#:#set_license_for_all_files#:#Set License for All Files file#:#show_amount_of_downloads#:#Show Number of Downloads file#:#show_amount_of_downloads_info#:#Display the number of times a file object has been downloaded on its 'Info' page. @@ -9799,7 +9799,7 @@ file#:#storage_id#:#Storage ID file#:#suffix_specific_icons#:#Suffix-Specific Icons file#:#suffixes#:#Suffixes file#:#upload_files#:#Upload Files -file#:#upload_files_limit#:#The maximum size of a file is %s. +file#:#upload_files_limit#:#The maximum file size allowed is %s. file#:#upload_files_title#:#Upload Files file#:#upload_info#:#File file#:#upload_info_desc#:#Uploads and versions can be managed in the ‘Versions’ tab. @@ -9819,21 +9819,21 @@ fils#:#file_suffix_default_positive#:#File Suffixes: Positive List (Default List fils#:#file_suffix_default_positive_info#:#Preset default list of accepted file suffixes. fils#:#file_suffix_overall_positive#:#Overall Positive List fils#:#file_suffix_overall_positive_info#:#This is the final list of accepted file suffixes. -fils#:#policy_audience#:#Audience -fils#:#policy_audience_all_users_option_desc#:#Apply policy for all users. -fils#:#policy_audience_global_roles_option_desc#:#Apply policy for users with specific global roles. +fils#:#policy_audience#:#Target Group +fils#:#policy_audience_all_users_option_desc#:#Apply policy to all users. +fils#:#policy_audience_global_roles_option_desc#:#Apply policy to users with specific global roles. fils#:#policy_confirm_deletion#:#Are you sure you want to delete the policy with the following properties?: -fils#:#policy_deletion_failure_not_found#:#Error: Deletion failed due to policy not being found. -fils#:#policy_deletion_successful#:#Deletion of policy was successful. +fils#:#policy_deletion_failure_not_found#:#Error: Deletion failed because policy could not be found. +fils#:#policy_deletion_successful#:#Policy successfully deleted. fils#:#policy_filter#:#Policy Filter fils#:#policy_no_validity_limitation_set#:#Valid indefinitely fils#:#policy_scope#:#Scope fils#:#policy_table_info_no_policies#:#No upload policies have been created yet. -fils#:#policy_title_desc#:#Descriptive title for the policy. +fils#:#policy_title_desc#:#Descriptive title for this policy. fils#:#policy_upload_limit#:#Upload Limit -fils#:#policy_upload_limit_desc#:#Upload limit which is set by this policy in MB. +fils#:#policy_upload_limit_desc#:#Upload limit (in MB) imposed by this policy. fils#:#policy_valid_until#:#Valid Until -fils#:#policy_valid_until_desc#:#Set an optional 'valid until' date after which the policy expires. +fils#:#policy_valid_until_desc#:#Set an optional ‘valid until’ date, after which the policy expires. fils#:#policy_validity#:#Validity fils#:#upload_limits#:#Upload Limits fils#:#upload_policies#:#Upload Policies From c3a3aa1b0f060e0f34048dbed2a085aa687e4e10 Mon Sep 17 00:00:00 2001 From: fneumann Date: Tue, 30 Jul 2024 14:00:21 +0200 Subject: [PATCH 022/115] MathJax: changed byline of polyfill url A polyfill is not needed with current browsers. Added advice to delete the use of polyfill.io. --- lang/ilias_ar.lang | 4 ++-- lang/ilias_bg.lang | 4 ++-- lang/ilias_cs.lang | 4 ++-- lang/ilias_da.lang | 4 ++-- lang/ilias_de.lang | 4 ++-- lang/ilias_el.lang | 4 ++-- lang/ilias_en.lang | 4 ++-- lang/ilias_es.lang | 4 ++-- lang/ilias_et.lang | 4 ++-- lang/ilias_fa.lang | 4 ++-- lang/ilias_fr.lang | 4 ++-- lang/ilias_hr.lang | 4 ++-- lang/ilias_hu.lang | 4 ++-- lang/ilias_it.lang | 4 ++-- lang/ilias_ja.lang | 4 ++-- lang/ilias_ka.lang | 4 ++-- lang/ilias_lt.lang | 4 ++-- lang/ilias_nl.lang | 4 ++-- lang/ilias_pl.lang | 4 ++-- lang/ilias_pt.lang | 4 ++-- lang/ilias_ro.lang | 4 ++-- lang/ilias_ru.lang | 4 ++-- lang/ilias_sk.lang | 4 ++-- lang/ilias_sl.lang | 4 ++-- lang/ilias_sq.lang | 4 ++-- lang/ilias_sr.lang | 4 ++-- lang/ilias_sv.lang | 4 ++-- lang/ilias_tr.lang | 4 ++-- lang/ilias_uk.lang | 4 ++-- lang/ilias_vi.lang | 4 ++-- lang/ilias_zh.lang | 4 ++-- 31 files changed, 62 insertions(+), 62 deletions(-) diff --git a/lang/ilias_ar.lang b/lang/ilias_ar.lang index 0982aec7ad2b..9062a28b4052 100755 --- a/lang/ilias_ar.lang +++ b/lang/ilias_ar.lang @@ -10749,8 +10749,8 @@ mathjax#:#mathjax_polyfill_url#:#Url of a Polyfill###31 03 2023 new variable mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 not needed###31 03 2023 new variable mathjax#:#mathjax_polyfill_url_desc_line2#:#For MathJax 3 e.g. https://polyfill.io/v3/polyfill.min.js?features=es6###31 03 2023 new variable mathjax#:#mathjax_script_url#:#URL of the MathJax Script###31 03 2023 new variable -mathjax#:#mathjax_script_url_desc_line1#:#For MathJax 2 e.g. %s###31 03 2023 new variable -mathjax#:#mathjax_script_url_desc_line2#:#Please activate the safe mode to avoid XSS attacks by MathJax code with javascript. For MathJax 2 it can be activated by adding the 'Safe' parameter to the CDN url. For MathJax 3 you need to include a script that configures it before MathJax is loaded. Use for example %s###31 03 2023 new variable +mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_server_address#:#Server Address###25 10 2016 new variable mathjax#:#mathjax_server_address_info#:#E.g. http://localhost:8003###25 10 2016 new variable mathjax#:#mathjax_server_cache_cleared#:#The MathJax cache was cleared.###25 10 2016 new variable diff --git a/lang/ilias_bg.lang b/lang/ilias_bg.lang index ab7ad5ea9b85..0a689c1570a3 100755 --- a/lang/ilias_bg.lang +++ b/lang/ilias_bg.lang @@ -10822,8 +10822,8 @@ mathjax#:#mathjax_limiter#:#Inline Delimiters###28 09 2012 new variable mathjax#:#mathjax_limiter_info#:#Select the inline delimiters as they are configured in your MathJax installation.###28 09 2012 new variable mathjax#:#mathjax_mathjax#:#MathJax###28 09 2012 new variable mathjax#:#mathjax_polyfill_url#:#Url of a Polyfill###29 07 2022 new variable -mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 not needed###29 07 2022 new variable -mathjax#:#mathjax_polyfill_url_desc_line2#:#For MathJax 3 e.g. https://polyfill.io/v3/polyfill.min.js?features=es6###29 07 2022 new variable +mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_script_url#:#URL of the MathJax Script###29 07 2022 new variable mathjax#:#mathjax_script_url_desc_line1#:#For MathJax 2 e.g. %s###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line2#:#Please activate the safe mode to avoid XSS attacks by MathJax code with javascript. For MathJax 2 it can be activated by adding the 'Safe' parameter to the CDN url. For MathJax 3 you need to include a script that configures it before MathJax is loaded. Use for example %s###31 03 2023 new variable diff --git a/lang/ilias_cs.lang b/lang/ilias_cs.lang index be8713fca147..e12156943925 100755 --- a/lang/ilias_cs.lang +++ b/lang/ilias_cs.lang @@ -10744,8 +10744,8 @@ mathjax#:#mathjax_limiter#:#Oddělovače inline mathjax#:#mathjax_limiter_info#:#Vyberte inline oddělovače, jak jsou nakonfigurovány ve vaší instalaci MathJax. mathjax#:#mathjax_mathjax#:#MathJax mathjax#:#mathjax_polyfill_url#:#Url of a Polyfill###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 not needed###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line2#:#For MathJax 3 e.g. https://polyfill.io/v3/polyfill.min.js?features=es6###31 03 2023 new variable +mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_script_url#:#URL of the MathJax Script###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line1#:#For MathJax 2 e.g. %s###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line2#:#Please activate the safe mode to avoid XSS attacks by MathJax code with javascript. For MathJax 2 it can be activated by adding the 'Safe' parameter to the CDN url. For MathJax 3 you need to include a script that configures it before MathJax is loaded. Use for example %s###31 03 2023 new variable diff --git a/lang/ilias_da.lang b/lang/ilias_da.lang index c8ec0704f2b2..bfc061883e36 100755 --- a/lang/ilias_da.lang +++ b/lang/ilias_da.lang @@ -10746,8 +10746,8 @@ mathjax#:#mathjax_limiter#:#Inline Delimiters###28 09 2012 new variable mathjax#:#mathjax_limiter_info#:#Select the inline delimiters as they are configured in your MathJax installation.###28 09 2012 new variable mathjax#:#mathjax_mathjax#:#MathJax mathjax#:#mathjax_polyfill_url#:#Url of a Polyfill###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 not needed###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line2#:#For MathJax 3 e.g. https://polyfill.io/v3/polyfill.min.js?features=es6###31 03 2023 new variable +mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_script_url#:#URL of the MathJax Script###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line1#:#For MathJax 2 e.g. %s###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line2#:#Please activate the safe mode to avoid XSS attacks by MathJax code with javascript. For MathJax 2 it can be activated by adding the 'Safe' parameter to the CDN url. For MathJax 3 you need to include a script that configures it before MathJax is loaded. Use for example %s###31 03 2023 new variable diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang index 8dffefb80461..3f5f772a2790 100755 --- a/lang/ilias_de.lang +++ b/lang/ilias_de.lang @@ -11610,8 +11610,8 @@ mathjax#:#mathjax_limiter#:#Trennzeichen mathjax#:#mathjax_limiter_info#:#Wählen Sie die Trennzeichen, die ILIAS für das MathJax-Skript produzieren soll mathjax#:#mathjax_mathjax#:#MathJax mathjax#:#mathjax_polyfill_url#:#Url eines Polyfills -mathjax#:#mathjax_polyfill_url_desc_line1#:#Für MathJax 2 nicht benötigt -mathjax#:#mathjax_polyfill_url_desc_line2#:#Für MathJax 3 z.B. https://polyfill.io/v3/polyfill.min.js?features=es6 +mathjax#:#mathjax_polyfill_url_desc_line1#:#Für MathJax 2 und MathJax 3 mit aktuellen Browsern nicht benötigt. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Bitte entfernen Sie die Einbindung von polyfill.io! Siehe https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_script_url#:#URL des MathJax-Skripts mathjax#:#mathjax_script_url_desc_line1#:#Für MathJax 2 z.B. %s mathjax#:#mathjax_script_url_desc_line2#:#Bitte verwenden Sie den Safe Mode, um XSS-Angriffe durch MathJax-Code mit Javascript zu vermeiden. Für MathJax 2 kann er an die CDN-Url als Parameter angefügt werden (s.o). Für MathJax 3 müssen Sie ein Skript einbinden, dass ihn konfiguriert, bevor MathJax geladen wird. Verwenden Sie z.B. %s diff --git a/lang/ilias_el.lang b/lang/ilias_el.lang index b523ca1cb106..513356ed19f7 100755 --- a/lang/ilias_el.lang +++ b/lang/ilias_el.lang @@ -10747,8 +10747,8 @@ mathjax#:#mathjax_limiter#:#Inline Delimiters###28 09 2012 new variable mathjax#:#mathjax_limiter_info#:#Select the inline delimiters as they are configured in your MathJax installation.###28 09 2012 new variable mathjax#:#mathjax_mathjax#:#MathJax###28 09 2012 new variable mathjax#:#mathjax_polyfill_url#:#Url of a Polyfill###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 not needed###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line2#:#For MathJax 3 e.g. https://polyfill.io/v3/polyfill.min.js?features=es6###31 03 2023 new variable +mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_script_url#:#URL of the MathJax Script###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line1#:#For MathJax 2 e.g. %s###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line2#:#Please activate the safe mode to avoid XSS attacks by MathJax code with javascript. For MathJax 2 it can be activated by adding the 'Safe' parameter to the CDN url. For MathJax 3 you need to include a script that configures it before MathJax is loaded. Use for example %s###31 03 2023 new variable diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index 9d46f1cd2b4d..dd9f12d2b899 100755 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -11592,8 +11592,8 @@ mathjax#:#mathjax_limiter#:#Inline Delimiters mathjax#:#mathjax_limiter_info#:#Select the inline delimiters that ILIAS should produce for the MathJax script mathjax#:#mathjax_mathjax#:#MathJax mathjax#:#mathjax_polyfill_url#:#Url of a Polyfill -mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 not needed -mathjax#:#mathjax_polyfill_url_desc_line2#:#For MathJax 3 e.g. https://polyfill.io/v3/polyfill.min.js?features=es6 +mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_script_url#:#URL of the MathJax Script mathjax#:#mathjax_script_url_desc_line1#:#For MathJax 2 e.g. %s mathjax#:#mathjax_script_url_desc_line2#:#Please activate the safe mode to avoid XSS attacks by MathJax code with javascript. For MathJax 2 it can be activated by adding the 'Safe' parameter to the CDN url. For MathJax 3 you need to include a script that configures it before MathJax is loaded. Use for example %s diff --git a/lang/ilias_es.lang b/lang/ilias_es.lang index f27f7a8f0878..09a50dcf8a48 100755 --- a/lang/ilias_es.lang +++ b/lang/ilias_es.lang @@ -10746,8 +10746,8 @@ mathjax#:#mathjax_limiter#:#Delimitadores en línea mathjax#:#mathjax_limiter_info#:#Selecciona los delimitadores en línea tal y como tengas configurado en tu instalación de MathJax mathjax#:#mathjax_mathjax#:#MathJax mathjax#:#mathjax_polyfill_url#:#Url of a Polyfill###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 not needed###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line2#:#For MathJax 3 e.g. https://polyfill.io/v3/polyfill.min.js?features=es6###31 03 2023 new variable +mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_script_url#:#URL of the MathJax Script###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line1#:#For MathJax 2 e.g. %s###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line2#:#Please activate the safe mode to avoid XSS attacks by MathJax code with javascript. For MathJax 2 it can be activated by adding the 'Safe' parameter to the CDN url. For MathJax 3 you need to include a script that configures it before MathJax is loaded. Use for example %s###31 03 2023 new variable diff --git a/lang/ilias_et.lang b/lang/ilias_et.lang index 2ae6fd6c4317..8faa7a41411c 100755 --- a/lang/ilias_et.lang +++ b/lang/ilias_et.lang @@ -10744,8 +10744,8 @@ mathjax#:#mathjax_limiter#:#Inline Delimiters mathjax#:#mathjax_limiter_info#:#Select the inline delimiters as they are configured in your MathJax installation. mathjax#:#mathjax_mathjax#:#MathJax mathjax#:#mathjax_polyfill_url#:#Url of a Polyfill###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 not needed###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line2#:#For MathJax 3 e.g. https://polyfill.io/v3/polyfill.min.js?features=es6###31 03 2023 new variable +mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_script_url#:#URL of the MathJax Script###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line1#:#For MathJax 2 e.g. %s###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line2#:#Please activate the safe mode to avoid XSS attacks by MathJax code with javascript. For MathJax 2 it can be activated by adding the 'Safe' parameter to the CDN url. For MathJax 3 you need to include a script that configures it before MathJax is loaded. Use for example %s###31 03 2023 new variable diff --git a/lang/ilias_fa.lang b/lang/ilias_fa.lang index 00731fb4b82f..5ef657b55303 100755 --- a/lang/ilias_fa.lang +++ b/lang/ilias_fa.lang @@ -10744,8 +10744,8 @@ mathjax#:#mathjax_limiter#:#Inline Delimiters###28 09 2012 new variable mathjax#:#mathjax_limiter_info#:#Select the inline delimiters as they are configured in your MathJax installation.###28 09 2012 new variable mathjax#:#mathjax_mathjax#:#MathJax###28 09 2012 new variable mathjax#:#mathjax_polyfill_url#:#Url of a Polyfill###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 not needed###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line2#:#For MathJax 3 e.g. https://polyfill.io/v3/polyfill.min.js?features=es6###31 03 2023 new variable +mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_script_url#:#URL of the MathJax Script###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line1#:#For MathJax 2 e.g. %s###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line2#:#Please activate the safe mode to avoid XSS attacks by MathJax code with javascript. For MathJax 2 it can be activated by adding the 'Safe' parameter to the CDN url. For MathJax 3 you need to include a script that configures it before MathJax is loaded. Use for example %s###31 03 2023 new variable diff --git a/lang/ilias_fr.lang b/lang/ilias_fr.lang index e7fb047cc5bb..e18291244d60 100755 --- a/lang/ilias_fr.lang +++ b/lang/ilias_fr.lang @@ -11383,8 +11383,8 @@ mathjax#:#mathjax_limiter#:#Séparateurs mathjax#:#mathjax_limiter_info#:#Définissez les séparateurs tels qu'ils sont configurés dans votre installation MathJax. mathjax#:#mathjax_mathjax#:#MathJax mathjax#:#mathjax_polyfill_url#:#Url of a Polyfill###31 10 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 not needed###31 10 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line2#:#For MathJax 3 e.g. https://polyfill.io/v3/polyfill.min.js?features=es6###31 10 2023 new variable +mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_script_url#:#URL of the MathJax Script###31 10 2023 new variable mathjax#:#mathjax_script_url_desc_line1#:#For MathJax 2 e.g. %s###31 10 2023 new variable mathjax#:#mathjax_script_url_desc_line2#:#Please activate the safe mode to avoid XSS attacks by MathJax code with javascript. For MathJax 2 it can be activated by adding the 'Safe' parameter to the CDN url. For MathJax 3 you need to include a script that configures it before MathJax is loaded. Use for example %s###31 10 2023 new variable diff --git a/lang/ilias_hr.lang b/lang/ilias_hr.lang index 364a93ea7ec2..2e447e3c916d 100755 --- a/lang/ilias_hr.lang +++ b/lang/ilias_hr.lang @@ -10744,8 +10744,8 @@ mathjax#:#mathjax_limiter#:#Inline Delimiters mathjax#:#mathjax_limiter_info#:#Select the inline delimiters as they are configured in your MathJax installation. mathjax#:#mathjax_mathjax#:#MathJax mathjax#:#mathjax_polyfill_url#:#Url of a Polyfill###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 not needed###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line2#:#For MathJax 3 e.g. https://polyfill.io/v3/polyfill.min.js?features=es6###31 03 2023 new variable +mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_script_url#:#URL of the MathJax Script###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line1#:#For MathJax 2 e.g. %s###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line2#:#Please activate the safe mode to avoid XSS attacks by MathJax code with javascript. For MathJax 2 it can be activated by adding the 'Safe' parameter to the CDN url. For MathJax 3 you need to include a script that configures it before MathJax is loaded. Use for example %s###31 03 2023 new variable diff --git a/lang/ilias_hu.lang b/lang/ilias_hu.lang index e999770f6427..bc3c613f332f 100755 --- a/lang/ilias_hu.lang +++ b/lang/ilias_hu.lang @@ -10745,8 +10745,8 @@ mathjax#:#mathjax_limiter#:#Beágyazott határolójelek mathjax#:#mathjax_limiter_info#:#Válassza ki a beágyazott határolójeleket, ahogy azok az Ön MathJax installációjában konfiguráltak. mathjax#:#mathjax_mathjax#:#MathJax mathjax#:#mathjax_polyfill_url#:#Polyfill URJ-je -mathjax#:#mathjax_polyfill_url_desc_line1#:#MathJax 2-höz nem szükséges -mathjax#:#mathjax_polyfill_url_desc_line2#:#MathJax 3-hoz például https://polyfill.io/v3/polyfill.min.js?features=es6 +mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_script_url#:#MathJax Script URL-je mathjax#:#mathjax_script_url_desc_line1#:#MathJax 2-höz például %s mathjax#:#mathjax_script_url_desc_line2#:#Kérem, aktiválja a biztonságos módot, hogy elkerülje a MathJax kódon keresztüli javascript-alapú XSS-támadásokat. MathJax 2 alatt ezt a CDN url-hez a 'Safe' paraméter hozzádásával teheti meg. MathJax 3 alatt egy szkriptet kell beletennie, ami ezt beállítja, mielőtt a MathJax betöltődik. Például: %s diff --git a/lang/ilias_it.lang b/lang/ilias_it.lang index 648c6bb1693d..dd45ebef6d3d 100755 --- a/lang/ilias_it.lang +++ b/lang/ilias_it.lang @@ -10744,8 +10744,8 @@ mathjax#:#mathjax_limiter#:#Delimitatori in linea mathjax#:#mathjax_limiter_info#:#Selezionare i delimitatori in linea come sono configurati nell'installazione di MathJax. mathjax#:#mathjax_mathjax#:#MathJax mathjax#:#mathjax_polyfill_url#:#Url of a Polyfill###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 not needed###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line2#:#For MathJax 3 e.g. https://polyfill.io/v3/polyfill.min.js?features=es6###31 03 2023 new variable +mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_script_url#:#URL of the MathJax Script###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line1#:#For MathJax 2 e.g. %s###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line2#:#Please activate the safe mode to avoid XSS attacks by MathJax code with javascript. For MathJax 2 it can be activated by adding the 'Safe' parameter to the CDN url. For MathJax 3 you need to include a script that configures it before MathJax is loaded. Use for example %s###31 03 2023 new variable diff --git a/lang/ilias_ja.lang b/lang/ilias_ja.lang index 498d548b18ef..f008e0cbc129 100755 --- a/lang/ilias_ja.lang +++ b/lang/ilias_ja.lang @@ -10838,8 +10838,8 @@ mathjax#:#mathjax_limiter#:#インライン区切り文字 mathjax#:#mathjax_limiter_info#:#MathJaxを有効にする構成するインライン区切り文字を選択します。 mathjax#:#mathjax_mathjax#:#MathJax mathjax#:#mathjax_polyfill_url#:#多角形のURL -mathjax#:#mathjax_polyfill_url_desc_line1#:#MathJax 2 には不要 -mathjax#:#mathjax_polyfill_url_desc_line2#:#MathJax 3の例 https://polyfill.io/v3/polyfill.min.js?features=es6 +mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_script_url#:#MathJax ScriptのURL mathjax#:#mathjax_script_url_desc_line1#:#MathJax 2 の例 %s mathjax#:#mathjax_script_url_desc_line2#:#javascriptのMathJaxコードでXSS攻撃を回避するためのセーフモード有効にして下さい。MathJax 2用はCDN urlへ'セーフ'パラメータを追加する事で有効化できます。MathJax 3用はMathjaxを読込む前に構成するスクリプトをincludeする必要があります。例の使用 %s diff --git a/lang/ilias_ka.lang b/lang/ilias_ka.lang index 3ccafd7992b2..55b0dfcdc74a 100755 --- a/lang/ilias_ka.lang +++ b/lang/ilias_ka.lang @@ -10744,8 +10744,8 @@ mathjax#:#mathjax_limiter#:#Inline Delimiters mathjax#:#mathjax_limiter_info#:#Select the inline delimiters as they are configured in your MathJax installation. mathjax#:#mathjax_mathjax#:#MathJax mathjax#:#mathjax_polyfill_url#:#Url of a Polyfill###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 not needed###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line2#:#For MathJax 3 e.g. https://polyfill.io/v3/polyfill.min.js?features=es6###31 03 2023 new variable +mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_script_url#:#URL of the MathJax Script###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line1#:#For MathJax 2 e.g. %s###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line2#:#Please activate the safe mode to avoid XSS attacks by MathJax code with javascript. For MathJax 2 it can be activated by adding the 'Safe' parameter to the CDN url. For MathJax 3 you need to include a script that configures it before MathJax is loaded. Use for example %s###31 03 2023 new variable diff --git a/lang/ilias_lt.lang b/lang/ilias_lt.lang index f880035c9ed2..805a0e550b3d 100755 --- a/lang/ilias_lt.lang +++ b/lang/ilias_lt.lang @@ -10746,8 +10746,8 @@ mathjax#:#mathjax_limiter#:#Inline Delimiters###28 09 2012 new variable mathjax#:#mathjax_limiter_info#:#Select the inline delimiters as they are configured in your MathJax installation.###28 09 2012 new variable mathjax#:#mathjax_mathjax#:#MathJax###28 09 2012 new variable mathjax#:#mathjax_polyfill_url#:#Url of a Polyfill###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 not needed###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line2#:#For MathJax 3 e.g. https://polyfill.io/v3/polyfill.min.js?features=es6###31 03 2023 new variable +mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_script_url#:#URL of the MathJax Script###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line1#:#For MathJax 2 e.g. %s###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line2#:#Please activate the safe mode to avoid XSS attacks by MathJax code with javascript. For MathJax 2 it can be activated by adding the 'Safe' parameter to the CDN url. For MathJax 3 you need to include a script that configures it before MathJax is loaded. Use for example %s###31 03 2023 new variable diff --git a/lang/ilias_nl.lang b/lang/ilias_nl.lang index 32ca6dee4bcb..4052b4f02679 100755 --- a/lang/ilias_nl.lang +++ b/lang/ilias_nl.lang @@ -10744,8 +10744,8 @@ mathjax#:#mathjax_limiter#:#Inline Delimiters mathjax#:#mathjax_limiter_info#:#Selecteer de inline delimiters zoals deze zijn ingesteld in je MathJax installatie mathjax#:#mathjax_mathjax#:#MathJax mathjax#:#mathjax_polyfill_url#:#Url of a Polyfill###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 not needed###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line2#:#For MathJax 3 e.g. https://polyfill.io/v3/polyfill.min.js?features=es6###31 03 2023 new variable +mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_script_url#:#URL of the MathJax Script###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line1#:#For MathJax 2 e.g. %s###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line2#:#Please activate the safe mode to avoid XSS attacks by MathJax code with javascript. For MathJax 2 it can be activated by adding the 'Safe' parameter to the CDN url. For MathJax 3 you need to include a script that configures it before MathJax is loaded. Use for example %s###31 03 2023 new variable diff --git a/lang/ilias_pl.lang b/lang/ilias_pl.lang index 0f1ea964a463..7a98dc056088 100755 --- a/lang/ilias_pl.lang +++ b/lang/ilias_pl.lang @@ -10744,8 +10744,8 @@ mathjax#:#mathjax_limiter#:#Separator mathjax#:#mathjax_limiter_info#:#Wybierz te separatory, które są skonfigurowane w MathJax. mathjax#:#mathjax_mathjax#:#MathJax mathjax#:#mathjax_polyfill_url#:#Url of a Polyfill###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 not needed###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line2#:#For MathJax 3 e.g. https://polyfill.io/v3/polyfill.min.js?features=es6###31 03 2023 new variable +mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_script_url#:#URL of the MathJax Script###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line1#:#For MathJax 2 e.g. %s###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line2#:#Please activate the safe mode to avoid XSS attacks by MathJax code with javascript. For MathJax 2 it can be activated by adding the 'Safe' parameter to the CDN url. For MathJax 3 you need to include a script that configures it before MathJax is loaded. Use for example %s###31 03 2023 new variable diff --git a/lang/ilias_pt.lang b/lang/ilias_pt.lang index 9a39481c9f8a..fd860bb06b3f 100755 --- a/lang/ilias_pt.lang +++ b/lang/ilias_pt.lang @@ -10744,8 +10744,8 @@ mathjax#:#mathjax_limiter#:#Delimitadores em linha mathjax#:#mathjax_limiter_info#:#Selecione delimitadores em linha, uma vez que estão configurados na sua instalação MathJax. mathjax#:#mathjax_mathjax#:#MathJax mathjax#:#mathjax_polyfill_url#:#Url of a Polyfill###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 not needed###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line2#:#For MathJax 3 e.g. https://polyfill.io/v3/polyfill.min.js?features=es6###31 03 2023 new variable +mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_script_url#:#URL of the MathJax Script###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line1#:#For MathJax 2 e.g. %s###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line2#:#Please activate the safe mode to avoid XSS attacks by MathJax code with javascript. For MathJax 2 it can be activated by adding the 'Safe' parameter to the CDN url. For MathJax 3 you need to include a script that configures it before MathJax is loaded. Use for example %s###31 03 2023 new variable diff --git a/lang/ilias_ro.lang b/lang/ilias_ro.lang index 745c46445549..5117922968ee 100755 --- a/lang/ilias_ro.lang +++ b/lang/ilias_ro.lang @@ -10746,8 +10746,8 @@ mathjax#:#mathjax_limiter#:#Inline Delimiters###28 09 2012 new variable mathjax#:#mathjax_limiter_info#:#Select the inline delimiters as they are configured in your MathJax installation.###28 09 2012 new variable mathjax#:#mathjax_mathjax#:#MathJax###28 09 2012 new variable mathjax#:#mathjax_polyfill_url#:#Url of a Polyfill###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 not needed###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line2#:#For MathJax 3 e.g. https://polyfill.io/v3/polyfill.min.js?features=es6###31 03 2023 new variable +mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_script_url#:#URL of the MathJax Script###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line1#:#For MathJax 2 e.g. %s###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line2#:#Please activate the safe mode to avoid XSS attacks by MathJax code with javascript. For MathJax 2 it can be activated by adding the 'Safe' parameter to the CDN url. For MathJax 3 you need to include a script that configures it before MathJax is loaded. Use for example %s###31 03 2023 new variable diff --git a/lang/ilias_ru.lang b/lang/ilias_ru.lang index 99b82df8dd87..3b96906757de 100755 --- a/lang/ilias_ru.lang +++ b/lang/ilias_ru.lang @@ -10745,8 +10745,8 @@ mathjax#:#mathjax_limiter#:#Inline Delimiters mathjax#:#mathjax_limiter_info#:#Select the inline delimiters as they are configured in your MathJax installation. mathjax#:#mathjax_mathjax#:#MathJax mathjax#:#mathjax_polyfill_url#:#Url of a Polyfill###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 not needed###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line2#:#For MathJax 3 e.g. https://polyfill.io/v3/polyfill.min.js?features=es6###31 03 2023 new variable +mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_script_url#:#URL of the MathJax Script###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line1#:#For MathJax 2 e.g. %s###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line2#:#Please activate the safe mode to avoid XSS attacks by MathJax code with javascript. For MathJax 2 it can be activated by adding the 'Safe' parameter to the CDN url. For MathJax 3 you need to include a script that configures it before MathJax is loaded. Use for example %s###31 03 2023 new variable diff --git a/lang/ilias_sk.lang b/lang/ilias_sk.lang index c1c1365d18a2..12345cd7eb50 100755 --- a/lang/ilias_sk.lang +++ b/lang/ilias_sk.lang @@ -10744,8 +10744,8 @@ mathjax#:#mathjax_limiter#:#Inline Delimiters###28 09 2012 new variable mathjax#:#mathjax_limiter_info#:#Select the inline delimiters as they are configured in your MathJax installation.###28 09 2012 new variable mathjax#:#mathjax_mathjax#:#MathJax###28 09 2012 new variable mathjax#:#mathjax_polyfill_url#:#Url of a Polyfill###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 not needed###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line2#:#For MathJax 3 e.g. https://polyfill.io/v3/polyfill.min.js?features=es6###31 03 2023 new variable +mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_script_url#:#URL of the MathJax Script###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line1#:#For MathJax 2 e.g. %s###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line2#:#Please activate the safe mode to avoid XSS attacks by MathJax code with javascript. For MathJax 2 it can be activated by adding the 'Safe' parameter to the CDN url. For MathJax 3 you need to include a script that configures it before MathJax is loaded. Use for example %s###31 03 2023 new variable diff --git a/lang/ilias_sl.lang b/lang/ilias_sl.lang index aca7e7716de3..c2235e9f959e 100755 --- a/lang/ilias_sl.lang +++ b/lang/ilias_sl.lang @@ -10835,8 +10835,8 @@ mathjax#:#mathjax_limiter#:#Inline Delimiters mathjax#:#mathjax_limiter_info#:#Select the inline delimiters as they are configured in your MathJax installation. mathjax#:#mathjax_mathjax#:#MathJax mathjax#:#mathjax_polyfill_url#:#Url of a Polyfill###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 not needed###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line2#:#For MathJax 3 e.g. https://polyfill.io/v3/polyfill.min.js?features=es6###31 03 2023 new variable +mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_script_url#:#URL of the MathJax Script###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line1#:#For MathJax 2 e.g. %s###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line2#:#Please activate the safe mode to avoid XSS attacks by MathJax code with javascript. For MathJax 2 it can be activated by adding the 'Safe' parameter to the CDN url. For MathJax 3 you need to include a script that configures it before MathJax is loaded. Use for example %s###31 03 2023 new variable diff --git a/lang/ilias_sq.lang b/lang/ilias_sq.lang index 86c3023d50fb..27b638bb124a 100755 --- a/lang/ilias_sq.lang +++ b/lang/ilias_sq.lang @@ -10746,8 +10746,8 @@ mathjax#:#mathjax_limiter#:#Inline Delimiters###28 09 2012 new variable mathjax#:#mathjax_limiter_info#:#Select the inline delimiters as they are configured in your MathJax installation.###28 09 2012 new variable mathjax#:#mathjax_mathjax#:#MathJax###28 09 2012 new variable mathjax#:#mathjax_polyfill_url#:#Url of a Polyfill###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 not needed###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line2#:#For MathJax 3 e.g. https://polyfill.io/v3/polyfill.min.js?features=es6###31 03 2023 new variable +mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_script_url#:#URL of the MathJax Script###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line1#:#For MathJax 2 e.g. %s###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line2#:#Please activate the safe mode to avoid XSS attacks by MathJax code with javascript. For MathJax 2 it can be activated by adding the 'Safe' parameter to the CDN url. For MathJax 3 you need to include a script that configures it before MathJax is loaded. Use for example %s###31 03 2023 new variable diff --git a/lang/ilias_sr.lang b/lang/ilias_sr.lang index e4d0f17c82ad..1102506d6227 100755 --- a/lang/ilias_sr.lang +++ b/lang/ilias_sr.lang @@ -10746,8 +10746,8 @@ mathjax#:#mathjax_limiter#:#Inline Delimiters###28 09 2012 new variable mathjax#:#mathjax_limiter_info#:#Select the inline delimiters as they are configured in your MathJax installation.###28 09 2012 new variable mathjax#:#mathjax_mathjax#:#MathJax###28 09 2012 new variable mathjax#:#mathjax_polyfill_url#:#Url of a Polyfill###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 not needed###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line2#:#For MathJax 3 e.g. https://polyfill.io/v3/polyfill.min.js?features=es6###31 03 2023 new variable +mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_script_url#:#URL of the MathJax Script###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line1#:#For MathJax 2 e.g. %s###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line2#:#Please activate the safe mode to avoid XSS attacks by MathJax code with javascript. For MathJax 2 it can be activated by adding the 'Safe' parameter to the CDN url. For MathJax 3 you need to include a script that configures it before MathJax is loaded. Use for example %s###31 03 2023 new variable diff --git a/lang/ilias_sv.lang b/lang/ilias_sv.lang index 3863a55a7ca0..9b220452a84c 100644 --- a/lang/ilias_sv.lang +++ b/lang/ilias_sv.lang @@ -11408,8 +11408,8 @@ mathjax#:#mathjax_limiter#:#Separator mathjax#:#mathjax_limiter_info#:#Välj de separatorer som ILIAS ska producera för MathJax-skriptet mathjax#:#mathjax_mathjax#:#MathJax mathjax#:#mathjax_polyfill_url#:#Url för en polyfill -mathjax#:#mathjax_polyfill_url_desc_line1#:#Behövs inte för MathJax 2 -mathjax#:#mathjax_polyfill_url_desc_line2#:#För MathJax 3 i äldre webbläsare, t.ex. https +mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_script_url#:#URL för MathJax-skriptet mathjax#:#mathjax_script_url_desc_line1#:#För MathJax 2 t.ex. %s mathjax#:#mathjax_script_url_desc_line2#:#Använd det säkra läget för att undvika XSS-attacker från MathJax-kod med Javascript. För MathJax 2 kan den läggas till i CDN-url:en som en parameter (se ovan). För MathJax 3 måste du inkludera ett skript som konfigurerar det innan MathJax laddas. Använd %s diff --git a/lang/ilias_tr.lang b/lang/ilias_tr.lang index cc0b3031fd7d..c87407bf0a9e 100755 --- a/lang/ilias_tr.lang +++ b/lang/ilias_tr.lang @@ -10744,8 +10744,8 @@ mathjax#:#mathjax_limiter#:#Inline Ayırıcılar mathjax#:#mathjax_limiter_info#:#Onlar MathJax kurulum yapılandırılmış olarak satır sınırlayıcıları seçin. mathjax#:#mathjax_mathjax#:#MathJax mathjax#:#mathjax_polyfill_url#:#Url of a Polyfill###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 not needed###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line2#:#For MathJax 3 e.g. https://polyfill.io/v3/polyfill.min.js?features=es6###31 03 2023 new variable +mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_script_url#:#URL of the MathJax Script###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line1#:#For MathJax 2 e.g. %s###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line2#:#Please activate the safe mode to avoid XSS attacks by MathJax code with javascript. For MathJax 2 it can be activated by adding the 'Safe' parameter to the CDN url. For MathJax 3 you need to include a script that configures it before MathJax is loaded. Use for example %s###31 03 2023 new variable diff --git a/lang/ilias_uk.lang b/lang/ilias_uk.lang index 6a5080889793..9ef3404556c3 100755 --- a/lang/ilias_uk.lang +++ b/lang/ilias_uk.lang @@ -10746,8 +10746,8 @@ mathjax#:#mathjax_limiter#:#Inline Delimiters###28 09 2012 new variable mathjax#:#mathjax_limiter_info#:#Select the inline delimiters as they are configured in your MathJax installation.###28 09 2012 new variable mathjax#:#mathjax_mathjax#:#MathJax###28 09 2012 new variable mathjax#:#mathjax_polyfill_url#:#Url of a Polyfill###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 not needed###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line2#:#For MathJax 3 e.g. https://polyfill.io/v3/polyfill.min.js?features=es6###31 03 2023 new variable +mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_script_url#:#URL of the MathJax Script###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line1#:#For MathJax 2 e.g. %s###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line2#:#Please activate the safe mode to avoid XSS attacks by MathJax code with javascript. For MathJax 2 it can be activated by adding the 'Safe' parameter to the CDN url. For MathJax 3 you need to include a script that configures it before MathJax is loaded. Use for example %s###31 03 2023 new variable diff --git a/lang/ilias_vi.lang b/lang/ilias_vi.lang index 83948fd30b91..fd0a5cdcdc10 100755 --- a/lang/ilias_vi.lang +++ b/lang/ilias_vi.lang @@ -10748,8 +10748,8 @@ mathjax#:#mathjax_limiter#:#Inline Delimiters###28 09 2012 new variable mathjax#:#mathjax_limiter_info#:#Select the inline delimiters as they are configured in your MathJax installation.###28 09 2012 new variable mathjax#:#mathjax_mathjax#:#MathJax###28 09 2012 new variable mathjax#:#mathjax_polyfill_url#:#Url of a Polyfill###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 not needed###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line2#:#For MathJax 3 e.g. https://polyfill.io/v3/polyfill.min.js?features=es6###31 03 2023 new variable +mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_script_url#:#URL of the MathJax Script###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line1#:#For MathJax 2 e.g. %s###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line2#:#Please activate the safe mode to avoid XSS attacks by MathJax code with javascript. For MathJax 2 it can be activated by adding the 'Safe' parameter to the CDN url. For MathJax 3 you need to include a script that configures it before MathJax is loaded. Use for example %s###31 03 2023 new variable diff --git a/lang/ilias_zh.lang b/lang/ilias_zh.lang index 89b7f0f5659e..8363332755ef 100755 --- a/lang/ilias_zh.lang +++ b/lang/ilias_zh.lang @@ -10743,8 +10743,8 @@ mathjax#:#mathjax_limiter#:#行内分隔符 mathjax#:#mathjax_limiter_info#:#选择行内分隔符,因为他们已配置在您的MathJax安装中。 mathjax#:#mathjax_mathjax#:#MathJax mathjax#:#mathjax_polyfill_url#:#Url of a Polyfill###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 not needed###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line2#:#For MathJax 3 e.g. https://polyfill.io/v3/polyfill.min.js?features=es6###31 03 2023 new variable +mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. +mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack mathjax#:#mathjax_script_url#:#URL of the MathJax Script###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line1#:#For MathJax 2 e.g. %s###31 03 2023 new variable mathjax#:#mathjax_script_url_desc_line2#:#Please activate the safe mode to avoid XSS attacks by MathJax code with javascript. For MathJax 2 it can be activated by adding the 'Safe' parameter to the CDN url. For MathJax 3 you need to include a script that configures it before MathJax is loaded. Use for example %s###31 03 2023 new variable From 31dea454785e63a4ad9493874ccbfb63a8aa6eff Mon Sep 17 00:00:00 2001 From: Fabian Wolf Date: Tue, 30 Jul 2024 15:22:30 +0200 Subject: [PATCH 023/115] Fix lang/ilias_ar.lang duplicate language entries --- lang/ilias_ar.lang | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lang/ilias_ar.lang b/lang/ilias_ar.lang index 9062a28b4052..5f0c1a128ac8 100755 --- a/lang/ilias_ar.lang +++ b/lang/ilias_ar.lang @@ -10746,11 +10746,9 @@ mathjax#:#mathjax_limiter#:#Inline Delimiters mathjax#:#mathjax_limiter_info#:#Select the inline delimiters as they are configured in your MathJax installation. mathjax#:#mathjax_mathjax#:#MathJax mathjax#:#mathjax_polyfill_url#:#Url of a Polyfill###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 not needed###31 03 2023 new variable -mathjax#:#mathjax_polyfill_url_desc_line2#:#For MathJax 3 e.g. https://polyfill.io/v3/polyfill.min.js?features=es6###31 03 2023 new variable -mathjax#:#mathjax_script_url#:#URL of the MathJax Script###31 03 2023 new variable mathjax#:#mathjax_polyfill_url_desc_line1#:#For MathJax 2 and MathJax 3 with current browsers not needed. mathjax#:#mathjax_polyfill_url_desc_line2#:#Please remove any reference to polyfill.io! See https://sansec.io/research/polyfill-supply-chain-attack +mathjax#:#mathjax_script_url#:#URL of the MathJax Script###31 03 2023 new variable mathjax#:#mathjax_server_address#:#Server Address###25 10 2016 new variable mathjax#:#mathjax_server_address_info#:#E.g. http://localhost:8003###25 10 2016 new variable mathjax#:#mathjax_server_cache_cleared#:#The MathJax cache was cleared.###25 10 2016 new variable From 6849d1062e33ea1f1fa397820905a13fc8b713e1 Mon Sep 17 00:00:00 2001 From: Stephan Kergomard Date: Tue, 30 Jul 2024 15:24:02 +0200 Subject: [PATCH 024/115] Object: Fix setImportId-Function See: https://mantis.ilias.de/view.php?id=41819 --- .../CoreProperties/ilObjectCoreProperties.php | 7 ++++++ .../Properties/class.ilObjectProperties.php | 24 +++++++++++++++++++ .../ILIASObject/classes/class.ilObject.php | 1 + .../ILIAS/ILIASObject/classes/ilObjectDIC.php | 8 +++---- 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/components/ILIAS/ILIASObject/classes/Properties/CoreProperties/ilObjectCoreProperties.php b/components/ILIAS/ILIASObject/classes/Properties/CoreProperties/ilObjectCoreProperties.php index f26875db2c42..4725cc90de06 100755 --- a/components/ILIAS/ILIASObject/classes/Properties/CoreProperties/ilObjectCoreProperties.php +++ b/components/ILIAS/ILIASObject/classes/Properties/CoreProperties/ilObjectCoreProperties.php @@ -99,6 +99,13 @@ public function getImportId(): string return $this->import_id ?? ''; } + public function withImportId(string $import_id): self + { + $clone = clone $this; + $clone->import_id = $import_id; + return $clone; + } + public function getPropertyTitleAndDescription(): ilObjectPropertyTitleAndDescription { return $this->property_title_and_description; diff --git a/components/ILIAS/ILIASObject/classes/Properties/class.ilObjectProperties.php b/components/ILIAS/ILIASObject/classes/Properties/class.ilObjectProperties.php index 0213cc0aa47f..712b4320b9cf 100755 --- a/components/ILIAS/ILIASObject/classes/Properties/class.ilObjectProperties.php +++ b/components/ILIAS/ILIASObject/classes/Properties/class.ilObjectProperties.php @@ -40,6 +40,30 @@ public function storeCoreProperties(): void ); } + public function getOwner(): int + { + return $this->core_properties->getOwner(); + } + + public function withOwner(int $owner): self + { + $clone = clone $this; + $clone->core_properties = $this->core_properties->withOwner($owner); + return $clone; + } + + public function getImportId(): string + { + return $this->core_properties->getImportId(); + } + + public function withImportId(string $import_id): self + { + $clone = clone $this; + $clone->core_properties = $this->core_properties->withImportId($import_id); + return $clone; + } + public function getPropertyTitleAndDescription(): ilObjectPropertyTitleAndDescription { return $this->core_properties->getPropertyTitleAndDescription(); diff --git a/components/ILIAS/ILIASObject/classes/class.ilObject.php b/components/ILIAS/ILIASObject/classes/class.ilObject.php index 065c3a3b69d4..46b978557dd5 100755 --- a/components/ILIAS/ILIASObject/classes/class.ilObject.php +++ b/components/ILIAS/ILIASObject/classes/class.ilObject.php @@ -397,6 +397,7 @@ final public function getImportId(): string final public function setImportId(string $import_id): void { + $this->object_properties = $this->getObjectProperties()->withImportId($import_id); $this->import_id = $import_id; } diff --git a/components/ILIAS/ILIASObject/classes/ilObjectDIC.php b/components/ILIAS/ILIASObject/classes/ilObjectDIC.php index e2d11ff23d90..7c75d098309d 100755 --- a/components/ILIAS/ILIASObject/classes/ilObjectDIC.php +++ b/components/ILIAS/ILIASObject/classes/ilObjectDIC.php @@ -48,10 +48,10 @@ public static function dic(): self private function init(ILIASContainer $DIC): void { $this['common_settings'] = fn($c): \ilObjectCommonSettings => new \ilObjectCommonSettings( - $DIC->language(), - $DIC->upload(), - $DIC->resourceStorage(), - $DIC->http(), + $DIC['lng'], + $DIC['upload'], + $DIC['resource_storage'], + $DIC['http'], $c['tile_image_stackholder'], $c['tile_image_flavour'] ); From 69c478043c44d8d88bd697d790f1068f1d2efd58 Mon Sep 17 00:00:00 2001 From: Matthias Kunkel Date: Tue, 30 Jul 2024 16:29:50 +0200 Subject: [PATCH 025/115] Improved language variables for English and German - esp. concerning blogs. Thanks to Chris Potter (cherry picked from commit 3d6bd9190a9f492f1e69c1303966d747d7cb23d5) --- lang/ilias_de.lang | 20 +++--- lang/ilias_en.lang | 151 +++++++++++++++++++++------------------------ 2 files changed, 81 insertions(+), 90 deletions(-) diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang index 3f5f772a2790..6c5e65e75f54 100755 --- a/lang/ilias_de.lang +++ b/lang/ilias_de.lang @@ -2401,7 +2401,7 @@ bibl#:#translate#:#Übersetzen bkm#:#bkm_fold_created#:#Bookmark-Ordner wurde angelegt. blog#:#blog_abstract_image#:#Erstes Bild anzeigen blog#:#blog_abstract_image_height#:#Höhe -blog#:#blog_abstract_image_info#:#Das erste Bild des Beitrags wird auf die hier festgelegte Größe verkleinert. Bilder, die kleiner sind als die festgelegte Größe, werden nicht angezeigt. +blog#:#blog_abstract_image_info#:#Das erste im Beitrag verwendete Bild wird in der Größe so angepasst, dass seine längste Kante der hier eingegebenen Wert entspricht. Dieses Bild wird zusammen mit der Zusammenfassung des Textinhalts des Beitrags angezeigt. Bilder, die kleiner als die Mindestgröße sind, werden nicht angezeigt. blog#:#blog_abstract_image_pixels#:#Px blog#:#blog_abstract_image_width#:#Breite blog#:#blog_abstract_shorten#:#Nur Anfang des Beitrags zeigen @@ -2410,8 +2410,8 @@ blog#:#blog_abstract_shorten_length#:#Maximale Länge blog#:#blog_add#:#Blog anlegen blog#:#blog_add_contributor#:#Mitwirkende hinzufügen blog#:#blog_add_posting#:#Beitrag hinzufügen -blog#:#blog_admin_inactive_info#:#Das Blog-Feature kann in der Administration der „Persönlichen Ressourcen“ aktiviert werden. -blog#:#blog_admin_toggle_info#:#Das Blog-Feature kann in der Administration der „Persönlichen Ressourcen“ deaktiviert werden. +blog#:#blog_admin_inactive_info#:#Das Blog-Feature kann in der Administration unter „Administration » Persönlicher Arbeitsraum » Persönliche Ressourcen“ aktiviert werden. +blog#:#blog_admin_toggle_info#:#Das Blog-Feature kann in der Administration unter „Administration » Persönlicher Arbeitsraum » Persönliche Ressourcen“ deaktiviert werden. blog#:#blog_allow_html#:#HTML/Javascript erlauben blog#:#blog_allow_html_info#:#Es kann HTML oder Javascript in Blog-Einträgen verwendet werden. Dies kann zu Sicherheitsproblemen führen.###Modified as part of gender mainstreaming activities for ILIAS 8 blog#:#blog_approve#:#Beitrag freischalten @@ -2433,7 +2433,7 @@ blog#:#blog_contributors#:#Mitwirkende blog#:#blog_copy#:#Blog kopieren blog#:#blog_download_submission#:#Kopie der Abgabe herunterladen blog#:#blog_draft#:#Entwurf -blog#:#blog_draft_info#:#Der Inhalt dieses Beitrags ist als Entwurf nur für Sie sichtbar. +blog#:#blog_draft_info#:#Die Veröffentlichung dieses Beitrags wurde zurückgenommen. Dieser Beitrag ist jetzt nur für Mitwirkende sichtbar. blog#:#blog_draft_info_contributors#:#Dieser Beitrag ist als Entwurf nur für die Mitwirkenden sichtbar. blog#:#blog_draft_text#:#Unveröffentlichter Beitrag blog#:#blog_edit#:#Blog bearbeiten @@ -2451,7 +2451,7 @@ blog#:#blog_enable_notes#:#Öffentliche Kommentare blog#:#blog_enable_rss#:#RSS aktivieren blog#:#blog_enable_rss_info#:#Der RSS-Feed ist öffentlich und unabhängig von Blog-Freigaben oder Rechten. blog#:#blog_est_reading_time#:#Geschätzte Lesedauer -blog#:#blog_est_reading_time_info#:#In Blogs im Magazin kann eine geschätzte Lesezeit ermittelt und angezeigt werden. +blog#:#blog_est_reading_time_info#:#Eine automatisch geschätzte Lesezeit wird allen Blog-Beiträge im Magazin hinzugefügt. blog#:#blog_exercise_info#:#Dieses Blog ist der Übungseinheit „%s“ in der Übung „%s“ zugeordnet. blog#:#blog_exercise_submitted_info#:#Ihre letzte Einreichung war am %s. Bitte prüfen Sie die Export-Datei: %s blog#:#blog_finalize_blog#:#Blog abschließen und abgeben @@ -2467,18 +2467,18 @@ blog#:#blog_latest_posting#:#Neuester Beitrag blog#:#blog_link#:#Link blog#:#blog_list_more#:#mehr blog#:#blog_list_num_postings#:#Anzahl Einträge in der initialen Übersicht -blog#:#blog_list_num_postings_info#:#Diese Einstellung wird nur verwendet, wenn kein konkreter Monat ausgewählt ist. +blog#:#blog_list_num_postings_info#:#Die maximale Anzahl von Beiträgen, die angezeigt werden, wenn kein konkreter Monat ausgewählt ist. blog#:#blog_nav_mode#:#Monatsweise Navigation blog#:#blog_nav_mode_month_list#:#Aktuelle Monate als Liste blog#:#blog_nav_mode_month_list_info#:#Der Seitenblock zeigt eine Liste der Monate. Die Anzahl angezeigter Monate und Beiträge kann festgelegt werden. blog#:#blog_nav_mode_month_list_num_detail#:#Anzahl Einträge (nach absteigendem Datum sortiert) blog#:#blog_nav_mode_month_list_num_detail_info#:#Ältere Einträge werden in der Monatsansicht gelistet. blog#:#blog_nav_mode_month_list_num_month#:#Gesamtanzahl angezeigter Monate -blog#:#blog_nav_mode_month_list_num_month_info#:#Monate werden standardmäßig nur mit der Anzahl ihrer Beiträge angezeigt. +blog#:#blog_nav_mode_month_list_num_month_info#:#Die Anzahl der Monate, die in dem „Beiträge“ Block gezeigt werde, wird beschränkt. Die angezeigten Monate sind immer die aktuellsten und werden standardmäßig nur mit der Anzahl ihrer Beiträge angezeigt. blog#:#blog_nav_mode_month_list_num_month_with_post#:#Anzahl Monate mit Links zu allen Beiträgen -blog#:#blog_nav_mode_month_list_num_month_with_post_info#:#Für diese Anzahl aktueller Monate werden im Seitenblock Links zu allen Beiträge angezeigt. +blog#:#blog_nav_mode_month_list_num_month_with_post_info#:#Für diese Anzahl aktueller Monate werden im Seitenblock Links zu allen Beiträgen angezeigt. Weitere Monate werden mit einer Zahl angezeigt, die die Gesamtzahl der Beiträge in diesem Monat angibt. blog#:#blog_nav_mode_month_single#:#Alle Monate per Dropdown -blog#:#blog_nav_mode_month_single_info#:#Im Seitenblock bietet ein Dropdown alle Monate zur Auswahl an. Für den gewählten Monat werden alle Beiträge aufgelistet. +blog#:#blog_nav_mode_month_single_info#:#Anstelle einer Liste wird im Seitenblock ein Dropdown-Menü gezeigt, das alle Monate zur Auswahl anbietet. Nur Beiträge der gewählten Monat werden aufgelistet. blog#:#blog_nav_sortorder#:#Reihenfolge der Seitenblöcke blog#:#blog_navigation#:#Beiträge blog#:#blog_needs_approval#:#Beitrag ist noch nicht freigeschaltet @@ -2498,7 +2498,7 @@ blog#:#blog_permanent_link#:#Permalink blog#:#blog_posting#:#Beitrag blog#:#blog_posting_deleted#:#Der Beitrag wurde gelöscht. blog#:#blog_posting_deletion_confirmation#:#Wollen Sie diesen Beitrag wirklich löschen? -blog#:#blog_posting_edit_approval_info#:#Der Beitrag muss vor der Veröffentlichung durch die Blog-Redaktion freigeschaltet werden. +blog#:#blog_posting_edit_approval_info#:#Der Beitrag muss durch die Blog-Redaktion freigeschaltet werden, bevor er sichtbar wird. Die Freischaltung kann nur stattfinden, wenn der Beitrag schon veröffentlicht wurde. blog#:#blog_posting_not_found#:#Der Beitrag ist nicht verfügbar. blog#:#blog_postings#:#Beiträge blog#:#blog_presentation_frame#:#Darstellung diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index dd9f12d2b899..bb71cbb9bfc5 100755 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -2392,9 +2392,9 @@ bibl#:#ris_default_y1#:#Year bibl#:#standard#:#Standard bibl#:#translate#:#Translate bkm#:#bkm_fold_created#:#Bookmark folder has been created. -blog#:#blog_abstract_image#:#Add Image To Abstract +blog#:#blog_abstract_image#:#Add Image to Abstract blog#:#blog_abstract_image_height#:#Height -blog#:#blog_abstract_image_info#:#Images which are smaller will not be used. +blog#:#blog_abstract_image_info#:#The first image used in the post will be resized so that its longest edge matches the size entered here. This image will be displayed along with the abstract of the post’s text content. Images that are smaller than the minimum size will not be used. blog#:#blog_abstract_image_pixels#:#Px blog#:#blog_abstract_image_width#:#Width blog#:#blog_abstract_shorten#:#Shorten Abstract @@ -2402,98 +2402,90 @@ blog#:#blog_abstract_shorten_characters#:#Characters blog#:#blog_abstract_shorten_length#:#Maximum Length blog#:#blog_add#:#Create Blog blog#:#blog_add_contributor#:#Add Contributor -blog#:#blog_add_posting#:#Add Posting -blog#:#blog_admin_inactive_info#:#You can activate the blog feature in the Personal Resources Administration. -blog#:#blog_admin_toggle_info#:#You can deactivate the blog feature completely in the Personal Resources Administration. -blog#:#blog_allow_html#:#Allow HTML/Javascript -blog#:#blog_allow_html_info#:#Enables users to include HTML and/or Javascript in their blog postings. This can lead to security issues. -blog#:#blog_approve#:#Approve Posting +blog#:#blog_add_posting#:#Add Post +blog#:#blog_admin_inactive_info#:#You can activate the blog feature by going to ‘Administration » Personal Workspace » Personal Resources’. +blog#:#blog_admin_toggle_info#:#You can deactivate the blog feature completely by going to ‘Administration » Personal Workspace » Personal Resources’. +blog#:#blog_approve#:#Approve Post blog#:#blog_author#:#Written by blog#:#blog_authors#:#Authors blog#:#blog_back_to_blog_owner#:#Edit Blog blog#:#blog_banner#:#Banner -blog#:#blog_change_notification_body_approve#:#the following blog posting needs approval. -blog#:#blog_change_notification_body_comment#:#the following blog posting has been commented. -blog#:#blog_change_notification_body_new#:#the following blog posting was created. -blog#:#blog_change_notification_body_update#:#the following blog posting has been updated. -blog#:#blog_change_notification_link#:#Link to Blog Posting -blog#:#blog_change_notification_reason#:#You are receiving this e-mail because you activated notifications for the blog mentioned above. -blog#:#blog_change_notification_subject#:#Blog "%s" has been updated +blog#:#blog_change_notification_body_approve#:#The following blog post needs approval. +blog#:#blog_change_notification_body_comment#:#The following blog post has received a comment. +blog#:#blog_change_notification_body_new#:#A new post has been added to the following blog. +blog#:#blog_change_notification_body_update#:#The following blog post has been updated. +blog#:#blog_change_notification_link#:#Link to Blog Post +blog#:#blog_change_notification_reason#:#You have received this e-mail because you have activated notifications for the blog mentioned above. +blog#:#blog_change_notification_subject#:#The blog "%s" has been updated. blog#:#blog_comments#:#Comments -blog#:#blog_confirm_delete_contributors#:#Are you sure you want to delete the following contributors? -blog#:#blog_contribute_other_roles#:#Additionally to Blog-Contributors users with the role %s can write postings. They are not listed in the table below, because they get the respective permission from an encompassing object. -blog#:#blog_contributors#:#Blog-Contributors +blog#:#blog_confirm_delete_contributors#:#Are you sure you want to remove the following contributors? +blog#:#blog_contribute_other_roles#:#In addition to the blog contributors listed below, users with the role(s) %s can also add posts to this blog. They are not listed in the table below, because their respective permission(s) are derived from the object (e.g. course or group) in which this blog is contained. +blog#:#blog_contributors#:#Blog Contributors blog#:#blog_copy#:#Copy Blog blog#:#blog_download_submission#:#Download Submission blog#:#blog_draft#:#Draft -blog#:#blog_draft_info#:#The posting is withdrawn and no longer published. -blog#:#blog_draft_info_contributors#:#This posting draft is only visible for the contributors. +blog#:#blog_draft_info#:#The post has been unpublished and is now only visible to contributors. +blog#:#blog_draft_info_contributors#:#This draft post is currently only visible to the contributors of this blog. blog#:#blog_draft_text#:#Unpublished Post blog#:#blog_edit#:#Edit Blog blog#:#blog_edit_date#:#Edit Date blog#:#blog_edit_date_info#:#Publication status does not depend on this date. blog#:#blog_edit_keywords#:#Edit Keywords -blog#:#blog_edit_posting#:#Edit Posting -blog#:#blog_enable_approval#:#Approve Postings -blog#:#blog_enable_approval_info#:#Postings are published only after being approved by a user with permission ‘Edit Settings’ for this blog. +blog#:#blog_edit_posting#:#Edit Post +blog#:#blog_enable_approval#:#Approve Posts +blog#:#blog_enable_approval_info#:#Posts will only be visible to other users after having being approved by a user with the ‘Edit Settings’ permission for this blog. blog#:#blog_enable_keywords#:#Keywords -blog#:#blog_enable_keywords_info#:#Keywords can be set for postings. Clicking a keyword shows all postings tagged with this keyword. +blog#:#blog_enable_keywords_info#:#Keywords can be added to posts. These keywords will then be displayed in a ‘Keywords’ block in the sidebar, where they can be used to filter the blog posts accordingly. blog#:#blog_enable_nav_authors#:#Authors -blog#:#blog_enable_nav_authors_info#:#List of blog authors is displayed. Clicking a username shows all postings of this author. +blog#:#blog_enable_nav_authors_info#:#Display a list of all blog authors in an ‘Authors’ side block. Clicking on an author’s username will show all posts by this author in the main section of the blog. blog#:#blog_enable_notes#:#Public Comments blog#:#blog_enable_rss#:#Activate RSS blog#:#blog_enable_rss_info#:#The RSS feed is public and independent of blog sharing or user permissions. blog#:#blog_est_reading_time#:#Estimated Reading Time -blog#:#blog_est_reading_time_info#:#For Blogs in the repository reading time can be determined and displayed. -blog#:#blog_exercise_info#:#This blog is part of the assignment "%s" of exercise "%s". +blog#:#blog_est_reading_time_info#:#Add an automatically generated estimated reading time to blog posts in the repository. +blog#:#blog_exercise_info#:#This blog is part of the assignment "%s" of the exercise "%s". blog#:#blog_exercise_submitted_info#:#Your last submission was on %s. Please check the export file: %s -blog#:#blog_finalize_blog#:#Finalize and Submit Blog -blog#:#blog_finalized#:#The Blog has been submitted. +blog#:#blog_finalize_blog#:#Finalise and Submit Blog +blog#:#blog_finalized#:#Your blog has been submitted. blog#:#blog_import#:#Import Blog blog#:#blog_incl_comments#:#including comments blog#:#blog_keyword#:#Keyword blog#:#blog_keyword_enter#:#Write a keyword and press Enter. blog#:#blog_keywords#:#Keywords -blog#:#blog_keywords_other#:#Other Keywords -blog#:#blog_keywords_other_info#:#These keywords were set for other blog postings. -blog#:#blog_latest_posting#:#Latest Posting +blog#:#blog_latest_posting#:#Latest Post blog#:#blog_link#:#Link -blog#:#blog_list_more#:#more -blog#:#blog_list_num_postings#:#Number of postings -blog#:#blog_list_num_postings_info#:#This setting is only used if no specific month is selected. -blog#:#blog_nav_mode#:#Postings +blog#:#blog_list_more#:#More +blog#:#blog_list_num_postings#:#Number of Posts +blog#:#blog_list_num_postings_info#:#The maximum number of posts displayed when viewing the blog with no specific month selected. +blog#:#blog_nav_mode#:#Posts blog#:#blog_nav_mode_month_list#:#Show List of Months -blog#:#blog_nav_mode_month_list_info#:#All months with postings are listed in the ‘Postings’ block. -blog#:#blog_nav_mode_month_list_num_detail#:#Total number of postings -blog#:#blog_nav_mode_month_list_num_detail_info#:#Latest postings are listed by date in reverse chronological order. Older postings are available via their month. -blog#:#blog_nav_mode_month_list_num_month#:#Total Number of shown months -blog#:#blog_nav_mode_month_list_num_month_info#:#Additional months will be displayed with their total number of postings. -blog#:#blog_nav_mode_month_list_num_month_with_post#:#Number of month with listed postings -blog#:#blog_nav_mode_month_list_num_month_with_post_info#:#These months will be displayed with all their postings in the navigation block. +blog#:#blog_nav_mode_month_list_info#:#List months with posts in the ‘Posts’ block. +blog#:#blog_nav_mode_month_list_num_month#:#Total Number of Months Shown +blog#:#blog_nav_mode_month_list_num_month_info#:#Limit the number of months that will be displayed in the ‘Posts’ block. The months displayed will always be the most recent and will be shown with a number indicating how many posts were made in that month as standard. +blog#:#blog_nav_mode_month_list_num_month_with_post#:#Number of Months With Listed Posts +blog#:#blog_nav_mode_month_list_num_month_with_post_info#:#These months will be displayed with the titles of all of their posts in the navigation block. Additional months will be displayed with a number indicating the total number of posts made in that month. blog#:#blog_nav_mode_month_single#:#Show Selected Month -blog#:#blog_nav_mode_month_single_info#:#Only postings of the selected month are listed in the ‘Postings’ block. Month can be changed by drop-down. -blog#:#blog_nav_sortorder#:#Order of Blocks -blog#:#blog_navigation#:#Postings -blog#:#blog_needs_approval#:#Posting is not yet approved +blog#:#blog_nav_mode_month_single_info#:#Instead of a list of months, display a drop-down menu, containing all of the months with posts for this blog. Users can use this menu to select the month they wish to view. Only the posts of the selected month will then be shown. +blog#:#blog_nav_sortorder#:#Block Order +blog#:#blog_navigation#:#Posts +blog#:#blog_needs_approval#:#Post has not yet been approved blog#:#blog_new#:#Create New Blog -blog#:#blog_new_posting_info#:#The posting is published. +blog#:#blog_new_posting_info#:#The post has been published. blog#:#blog_news_posting_authors#:#Contributing authors: %s -blog#:#blog_news_posting_published#:#A blog posting has been published by %s. -blog#:#blog_news_posting_updated#:#A blog posting has been updated by %s. +blog#:#blog_news_posting_published#:#New blog post published by %s. +blog#:#blog_news_posting_updated#:#Blog post updated by %s. blog#:#blog_no_keywords#:#No keywords have been entered yet. -blog#:#blog_notification_activated#:#Notification Activated -blog#:#blog_notification_deactivated#:#Notification Deactivated -blog#:#blog_notification_toggle_off#:#Deactivate Notification +blog#:#blog_notification_activated#:#Notifications Activated +blog#:#blog_notification_deactivated#:#Notifications Deactivated +blog#:#blog_notification_toggle_off#:#Deactivate Notifications blog#:#blog_notification_toggle_on#:#Activate Notification -blog#:#blog_number_users_notes_or_comments#:#Number of users that attached notes or comments to this posting -blog#:#blog_page_type_blp#:#Blog Posting -blog#:#blog_permanent_link#:#Permalink -blog#:#blog_posting#:#Blog Posting -blog#:#blog_posting_deleted#:#Posting has been deleted. -blog#:#blog_posting_deletion_confirmation#:#Are you sure you want to delete the following posting? -blog#:#blog_posting_edit_approval_info#:#Posting remains to be approved by a tutor before being published. -blog#:#blog_posting_not_found#:#This blog posting is not available. -blog#:#blog_postings#:#Postings +blog#:#blog_number_users_notes_or_comments#:#Number of users who have attached notes or comments to this post +blog#:#blog_posting#:#Blog Post +blog#:#blog_posting_deleted#:#Post successfully deleted. +blog#:#blog_posting_deletion_confirmation#:#Are you sure you want to delete the following post? +blog#:#blog_posting_edit_approval_info#:#Post needs to be approved by an editor in order to be visible. The post needs to be published before approval can take place. +blog#:#blog_posting_not_found#:#This blog post is not available. +blog#:#blog_postings#:#Posts blog#:#blog_presentation_frame#:#Blog Presentation blog#:#blog_presentation_overview#:#Initial Overview blog#:#blog_preview#:#Preview @@ -2502,21 +2494,20 @@ blog#:#blog_preview_banner_height#:#Banner Height blog#:#blog_preview_banner_info#:#Default size is 1370x100 pixels. blog#:#blog_preview_banner_width#:#Banner Width blog#:#blog_profile_picture#:#Display Profile Picture -blog#:#blog_profile_picture_repository_info#:#Profile pictures of the authors are only shown on the postings pages, not on overview pages. +blog#:#blog_profile_picture_repository_info#:#Profile pictures of authors are only shown on post pages, not on overview pages. blog#:#blog_properties#:#Blog Properties -blog#:#blog_rename_posting#:#Rename Posting -blog#:#blog_selected_pages#:#Selected Postings +blog#:#blog_rename_posting#:#Rename Post +blog#:#blog_selected_pages#:#Selected posts blog#:#blog_settings#:#Blog Administration -blog#:#blog_settings_navigation#:#Navigation Column -blog#:#blog_show_latest#:#Show latest postings/drafts +blog#:#blog_settings_navigation#:#Navigation Sidebar +blog#:#blog_show_latest#:#Show Latest Posts/Drafts blog#:#blog_show_print_view#:#Show Print View blog#:#blog_starting_page#:#Starting Page -blog#:#blog_style#:#Blog Style -blog#:#blog_task_publishing_draft_title#:#Publishing of Draft "%s" +blog#:#blog_task_publishing_draft_title#:#Publish Draft Blog Post "%s" blog#:#blog_toggle_draft#:#Withdraw Publication -blog#:#blog_toggle_draft_admin#:#Deactivate Blog Posting -blog#:#blog_toggle_final#:#Publish Posting -blog#:#blog_whole_blog#:#Whole Blog +blog#:#blog_toggle_draft_admin#:#Deactivate Blog Post +blog#:#blog_toggle_final#:#Publish Post +blog#:#blog_whole_blog#:#Whole blog book#:#X_reservations_of#:#%s booking(s) of book#:#book_add#:#Add Booking Pool book#:#book_add_object#:#Add Item @@ -5118,7 +5109,7 @@ common#:#obj_wiki_duplicate#:#Copy Wiki common#:#obj_wiks#:#Wiki common#:#obj_wiks_desc#:#Global Wiki Settings common#:#object#:#Object -common#:#object_added#:#Object added +common#:#object_added#:#Object added. common#:#object_copy_in_progress#:#Started Copying. common#:#object_duplicated#:#Object copied common#:#object_id#:#Object-ID @@ -5587,7 +5578,7 @@ common#:#settings#:#Settings common#:#settings_for_all_members#:#Members will be notified automatically common#:#settings_per_users#:#Members are notified automatically. Deactivating notifications can be allowed for each member individually. common#:#settings_presentation_header#:#Presentation -common#:#settings_saved#:#Settings saved +common#:#settings_saved#:#Settings saved. common#:#shib#:#Shibboleth common#:#shib_active#:#Enable Shibboleth Support common#:#shib_city#:#Attribute for city @@ -9216,7 +9207,7 @@ exc#:#exc_assignment_type#:#Type of Submission exc#:#exc_assignment_view#:#Assignment View exc#:#exc_assignments#:#Assignments exc#:#exc_assignments_deleted#:#The assignments have been deleted. -exc#:#exc_blog_created#:#Blog was created. +exc#:#exc_blog_created#:#Blog successfully created. exc#:#exc_blog_returned#:#Assignment Blog exc#:#exc_blog_selected#:#The blog has been assigned. exc#:#exc_chars_remaining#:#Characters remaining: @@ -9231,7 +9222,7 @@ exc#:#exc_conf_del_assignments#:#Do you really want to delete the following assi exc#:#exc_copy#:#Copy Exercise exc#:#exc_copy_zip_error#:#The submission can not be opened. An error occurred when copying the files. exc#:#exc_create_blog#:#Create Blog -exc#:#exc_create_blog_select_info#:#Please select the resource folder to add the blog to. +exc#:#exc_create_blog_select_info#:#Please select the resources folder in which you would like to create your blog. exc#:#exc_create_portfolio#:#Create Portfolio exc#:#exc_create_portfolio_no_template#:#Do not use template exc#:#exc_create_team#:#Create Team @@ -15760,14 +15751,14 @@ style#:#sty_confirm_del_ind_styles#:#Confirm Deletion of Individual Content Styl style#:#sty_confirm_del_ind_styles_desc#:#All learning modules with individual styles will be assigned to style '%s'. This will also delete all individual content styles. Are you sure to continue? style#:#sty_confirm_template_deletion#:#Confirm Template Deletion style#:#sty_copied_please_select_target#:#The style classes have been copied. Please open the target style and click ‘Paste Style Classes’. -style#:#sty_copy_other_stylesheet#:#Copy Style from Local Source +style#:#sty_copy_other_stylesheet#:#Copy Style From Local Source style#:#sty_copy_other_system_style#:#Copy System Style style#:#sty_copy_to#:#to: style#:#sty_create_ind_style#:#Create Individual Style style#:#sty_create_new_class#:#Create new style class -style#:#sty_create_new_stylesheet#:#Create new Style -style#:#sty_create_new_system_style#:#Create new System Style -style#:#sty_create_new_system_sub_style#:#Create new Sub Style +style#:#sty_create_new_stylesheet#:#Create New Style +style#:#sty_create_new_system_style#:#Create New System Style +style#:#sty_create_new_system_sub_style#:#Create New Sub Style style#:#sty_create_pgl#:#Create Page Layout style#:#sty_cursor#:#Cursor style#:#sty_custom#:#Custom From 09a37701dabb5717053ff99286d9bd2f2e6dd180 Mon Sep 17 00:00:00 2001 From: Alex Killing Date: Tue, 30 Jul 2024 16:51:44 +0200 Subject: [PATCH 026/115] 41826: On saving a table, the content of some cells is duplicated --- components/ILIAS/COPage/PC/Blog/class.ilPCBlog.php | 4 +--- .../ILIAS/COPage/PC/Plugged/class.ilPCPlugged.php | 12 ++++++------ .../PC/Table/class.TableCommandActionHandler.php | 1 - .../ILIAS/COPage/PC/Table/class.ilPCDataTable.php | 8 ++------ components/ILIAS/COPage/PC/Table/class.ilPCTable.php | 8 ++------ .../ILIAS/COPage/PC/Table/class.ilPCTableData.php | 8 ++------ 6 files changed, 13 insertions(+), 28 deletions(-) diff --git a/components/ILIAS/COPage/PC/Blog/class.ilPCBlog.php b/components/ILIAS/COPage/PC/Blog/class.ilPCBlog.php index dedac6941107..6bcdeea9c235 100755 --- a/components/ILIAS/COPage/PC/Blog/class.ilPCBlog.php +++ b/components/ILIAS/COPage/PC/Blog/class.ilPCBlog.php @@ -59,9 +59,7 @@ public function setData( // remove all children first $children = $this->getChildNode()->childNodes; if ($children) { - foreach ($children as $child) { - $this->getChildNode()->removeChild($child); - } + $this->dom_util->deleteAllChilds($this->getChildNode()); } if (count($a_posting_ids)) { diff --git a/components/ILIAS/COPage/PC/Plugged/class.ilPCPlugged.php b/components/ILIAS/COPage/PC/Plugged/class.ilPCPlugged.php index 12013df4b3d6..0333866ffc70 100755 --- a/components/ILIAS/COPage/PC/Plugged/class.ilPCPlugged.php +++ b/components/ILIAS/COPage/PC/Plugged/class.ilPCPlugged.php @@ -162,6 +162,7 @@ public static function handleCopiedPluggedContent( DOMDocument $a_domdoc ): void { global $DIC; + $dom_util = $DIC->copage()->internal()->domain()->domUtil(); $component_repository = $DIC['component.repository']; $component_factory = $DIC['component.factory']; @@ -187,9 +188,7 @@ public static function handleCopiedPluggedContent( // and allow it to modify the saved parameters $plugin_obj->onClone($properties, $plugin_version); - foreach ($node->childNodes as $child) { - $node->removeChild($child); - } + $dom_util->deleteAllChilds($node); foreach ($properties as $name => $value) { $child = new DOMElement( 'PluggedProperty', @@ -208,6 +207,9 @@ public static function handleCopiedPluggedContent( public static function afterRepositoryCopy(ilPageObject $page, array $mapping, int $source_ref_id): void { global $DIC; + + $dom_util = $DIC->copage()->internal()->domain()->domUtil(); + $ilPluginAdmin = $DIC['ilPluginAdmin']; /** @var ilComponentFactory $component_factory */ @@ -238,9 +240,7 @@ public static function afterRepositoryCopy(ilPageObject $page, array $mapping, i // and allow it to modify the saved parameters $plugin_obj->afterRepositoryCopy($properties, $mapping, $source_ref_id, $plugin_version); - foreach ($node->childNodes as $child) { - $node->removeChild($child); - } + $dom_util->deleteAllChilds($node); foreach ($properties as $name => $value) { $child = new DOMElement( 'PluggedProperty', diff --git a/components/ILIAS/COPage/PC/Table/class.TableCommandActionHandler.php b/components/ILIAS/COPage/PC/Table/class.TableCommandActionHandler.php index ec2b94a6e108..626e73f3c9ae 100755 --- a/components/ILIAS/COPage/PC/Table/class.TableCommandActionHandler.php +++ b/components/ILIAS/COPage/PC/Table/class.TableCommandActionHandler.php @@ -223,7 +223,6 @@ protected function updateData( $updated = (!is_null($text)); $text = $text["text"]; } - if ($updated) { $text = \ilPCParagraph::_input2xml( $text, diff --git a/components/ILIAS/COPage/PC/Table/class.ilPCDataTable.php b/components/ILIAS/COPage/PC/Table/class.ilPCDataTable.php index f33150f46de4..9d4080d8825e 100755 --- a/components/ILIAS/COPage/PC/Table/class.ilPCDataTable.php +++ b/components/ILIAS/COPage/PC/Table/class.ilPCDataTable.php @@ -50,9 +50,7 @@ public function create( public function makeEmptyCell(DomNode $td_node): void { // delete children of paragraph node - foreach ($td_node->childNodes as $child) { - $td_node->removeChild($child); - } + $this->dom_util->deleteAllChilds($td_node); // create page content and paragraph node here. $pc_node = $this->getNewPageContentNode(); @@ -86,9 +84,7 @@ public function setData(array $a_data) // remove all childs if (empty($error) && !is_null($par_node)) { // delete children of paragraph node - foreach ($par_node->childNodes as $child) { - $par_node->removeChild($child); - } + $this->dom_util->deleteAllChilds($par_node); // copy new content children in paragraph node $nodes = $this->dom_util->path( diff --git a/components/ILIAS/COPage/PC/Table/class.ilPCTable.php b/components/ILIAS/COPage/PC/Table/class.ilPCTable.php index 1f1ed0d7faa4..e5eacfb6fdbc 100755 --- a/components/ILIAS/COPage/PC/Table/class.ilPCTable.php +++ b/components/ILIAS/COPage/PC/Table/class.ilPCTable.php @@ -109,9 +109,7 @@ public function getCellNode(int $i, int $j, bool $create_if_not_exists = false): if (!is_null($td_node)) { // delete children of paragraph node - foreach ($td_node->childNodes as $child) { - $td_node->removeChild($child); - } + $this->dom_util->deleteAllChilds($td_node); // create page content and paragraph node here. $pc_node = $this->getNewPageContentNode(); @@ -409,9 +407,7 @@ public function fixHideAndSpans(): void public function makeEmptyCell(DomNode $td_node): void { // delete children of paragraph node - foreach ($td_node->childNodes as $child) { - $td_node->removeChild($child); - } + $this->dom_util->deleteAllChilds($td_node); } /** diff --git a/components/ILIAS/COPage/PC/Table/class.ilPCTableData.php b/components/ILIAS/COPage/PC/Table/class.ilPCTableData.php index e2558f060092..d48119252aa7 100755 --- a/components/ILIAS/COPage/PC/Table/class.ilPCTableData.php +++ b/components/ILIAS/COPage/PC/Table/class.ilPCTableData.php @@ -85,9 +85,7 @@ public function deleteRowContent( if ($td->hasAttribute("PCID")) { $td->removeAttribute("PCID"); } - foreach ($td->childNodes as $c) { - $td->removeChild($c); - } + $this->dom_util->deleteAllChilds($td); } } @@ -97,9 +95,7 @@ public function deleteRowContent( public function deleteTDContent( DOMNode $a_td_node ): void { - foreach ($a_td_node->childNodes as $child) { - $a_td_node->removeChild($child); - } + $this->dom_util->deleteAllChilds($a_td_node); } public function deleteRow(): void From 69a815d8201f9bf4febbc8620fd3f241f5add838 Mon Sep 17 00:00:00 2001 From: Alexander Killing Date: Wed, 31 Jul 2024 08:30:34 +0200 Subject: [PATCH 027/115] added br on questions and multilinguality --- components/ILIAS/LearningModule/README.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/components/ILIAS/LearningModule/README.md b/components/ILIAS/LearningModule/README.md index f5e21573dabd..6c5d6d29f6cd 100755 --- a/components/ILIAS/LearningModule/README.md +++ b/components/ILIAS/LearningModule/README.md @@ -1,21 +1,26 @@ # Learning Modules -## Business Rules - -### Copy Process +## Copy Process - When pages, chapters or learning modules are copied, the media objects are re-used. This means that all instances will refer to the same shared object. This prevents large media files from to filling up disk space quickly. - Questions will be not shared when pages are copied. Every page copy will get separate copies of a question. -### Reading Time +## Reading Time - The estimated reading time is language independent and always derived from the master language. (https://docu.ilias.de/goto_docu_wiki_wpage_6586_1357.html) -### Auto-Linked Glossaries +## Auto-Linked Glossaries - If glossaries are linked to a learning module, internal links to found terms in the glossary will be created during text editing automatically when text is saved. This can be triggered for the whole content, when linking the glossary to the learning module. Once the links are created they are part of the content like manual created links. Removing a glossary from the auto-link list will not delete the links to the glossary terms in the content. -### Print View +## Print View - Print view selection uses a modal, see https://docu.ilias.de/goto_docu_wiki_wpage_5907_1357.html -- The print view opens in a separate tab, see https://mantis.ilias.de/view.php?id=20653 \ No newline at end of file +- The print view opens in a separate tab, see https://mantis.ilias.de/view.php?id=20653 + +## Multilinguality and Questions + +Avoid mixing these two concepts. The outcome will be most probably not what you desire. + +The learning module component re-uses question from the T&A component. These questions do not support multilinguality yet. This means that using questions on multi-lingual pages are always completely separated instances. The question on the german page "does not know" that it is a copy of the version on the english page. Additionally there is nothing like a language-dependent learning progress in ILIAS. So activating the learning progress based on questions or navigation restrictions will also not work as expected in a multi-lingual learning module. + From eabe2b30dc123adf9ebf552fc5cd3cfad5598fca Mon Sep 17 00:00:00 2001 From: Denis Strassner <66684170+dsstrassner@users.noreply.github.com> Date: Wed, 31 Jul 2024 09:20:16 +0200 Subject: [PATCH 028/115] Docu: Update contributing.md (#7867) * https://github.com/ILIAS-eLearning/ILIAS/pull/7867 * add the new labels: Authorities, Looking for Shepherd * update ToC * correct typos * replace maintainers with authorities --- docs/development/contributing.md | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/docs/development/contributing.md b/docs/development/contributing.md index 121e0f4a8b32..e8cad939a1a4 100755 --- a/docs/development/contributing.md +++ b/docs/development/contributing.md @@ -8,7 +8,8 @@ 1. [How to contribute?](#how-to-contribute) 1. [Pull Request to the Repositories](#pull-request-to-the-repositories) 1. [Rules for Contributors](#rules-for-contributors) - 1. [List of Labels?](#list-of-labels) + 1. [List of Labels](#list-of-labels) + 1. [Looking for Shepherd](#looking-for-shepherd) 1. [Rules for Community Members assigned to PRs](#rules-for-community-members-assigned-to-prs) 1. [Want to Contribute Something else than Commits?](#want-to-contribute-something-else-than-commits) @@ -60,8 +61,8 @@ code please make sure: * that your PR has a description that tells what is changed and why - with a size relative to the changes * that your PRs is minimal - prefer to make two small PRs instead of one big PR -* that you discuss huge PRs with the responsible maintainers in advance - this - will save your time if the maintainers do not agree with your proposed change +* that you discuss huge PRs with the responsible authorities in advance - this + will save your time if the authorities do not agree with your proposed change * that you create commits of self-contained logical units with concise commit messages and no unnecessary whitespace - this will help reviewers to understand what you did @@ -94,21 +95,28 @@ accidentally. #### List of Labels Currently, the following labels are used for Pull-Requests. These labels will -be assigned by the Technical Board or Maintainers: +be assigned by the Technical Board or Authorities: | Label | Description | |-----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| authorities | The label `authorities` has to be assigned to PRs that contain updates to the authorities of a component. | bugfix | PRs with the label `bugfix` propose a solution for a reported bug in the official Bugtracker https://mantis.ilias.de | -| improvement | The label `improvement` is used for PRs which propose a general improvement of code or documentation which is not related to a bug. | | dependencies | The label `dependecies` is used for PRs which propose new or updated dependencies. Please don't forget to also add the label `jour fixe`, when proposing new dependencies.| | documentation | The label `documentation` has to be assigned to PRs adding or updating documentation. | -| roadmap | The label `roadmap` is assigned to PRs that contain strategical or tactical discussions of technical topics regarding the future of a component. | -| php | The label `php` has to be set for PRs changing PHP code. | +| improvement | The label `improvement` is used for PRs which propose a general improvement of code or documentation which is not related to a bug. | | javascript | The label `javascript` has to be set for PRs changing Javascript code. | | jour fixe | PRs which should be discussed during the next Jour Fixe are labeled with this `jour fixe`. Please set this label at least 2 days before the envisaged date of Jour Fixe. | | kitchen sink | All contributions to the Kitchen Sink Project are labeled accordingly. | +| Looking for Shepherd | The label `Looking for Shepherd` has to be set for PRs which changes made for unmaintained components. | +| php | The label `php` has to be set for PRs changing PHP code. | +| roadmap | The label `roadmap` is assigned to PRs that contain strategical or tactical discussions of technical topics regarding the future of a component. | | technical board | This label is given for PRs which will be discussed in a meeting of the Technical Board. The label will be removed after the discussion. | + +#### Looking for Shepherd + +`Looking for Shepherd` is a label in GitHub to mark PRs made for unmaintained components. As there is no developer that gets assigned such bugs due to her/his authority, pull requests with this tag can be reviewed by every ILIAS developer who can commit and decide if it is accepted and merged. We kindly ask every ILIAS developer to look into these PRs regularly and take responsibility for our shared code base. + #### Rules for Community Members assigned to PRs From a87a9644f6754dff122daa19d9ee3c382ffced83 Mon Sep 17 00:00:00 2001 From: Ilja Lukin Date: Thu, 4 Jul 2024 13:46:53 +0200 Subject: [PATCH 029/115] Fix: Error when retrieving a HISinOne course from ECS https://mantis.ilias.de/view.php?id=41655 --- .../ECS/classes/Course/class.ilECSCourseCreationHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/ILIAS/WebServices/ECS/classes/Course/class.ilECSCourseCreationHandler.php b/components/ILIAS/WebServices/ECS/classes/Course/class.ilECSCourseCreationHandler.php index 39be7ae5f3bb..d49eafa6adbe 100755 --- a/components/ILIAS/WebServices/ECS/classes/Course/class.ilECSCourseCreationHandler.php +++ b/components/ILIAS/WebServices/ECS/classes/Course/class.ilECSCourseCreationHandler.php @@ -364,7 +364,7 @@ protected function doSync($a_content_id, $course, $a_parent_obj_id): bool { // Check if course is already created $course_id = $course->lectureID; - $this->course_url->setCmsLectureId($course_id); + $this->course_url->setCmsLectureId((string) $course_id); $obj_id = $this->getImportId((int) $course_id); From 49540f79b95a5ebe4b2fb8dae79829a1a13dc417 Mon Sep 17 00:00:00 2001 From: Ilja Lukin Date: Thu, 4 Jul 2024 14:10:27 +0200 Subject: [PATCH 030/115] Fix: Missing Radio Buttons in ECS Course Allocation https://mantis.ilias.de/view.php?id=41657 --- .../classes/Mapping/class.ilECSMappingSettingsGUI.php | 2 +- .../Mapping/class.ilECSNodeMappingLocalExplorer.php | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/components/ILIAS/WebServices/ECS/classes/Mapping/class.ilECSMappingSettingsGUI.php b/components/ILIAS/WebServices/ECS/classes/Mapping/class.ilECSMappingSettingsGUI.php index 76a025936438..873996cad642 100755 --- a/components/ILIAS/WebServices/ECS/classes/Mapping/class.ilECSMappingSettingsGUI.php +++ b/components/ILIAS/WebServices/ECS/classes/Mapping/class.ilECSMappingSettingsGUI.php @@ -225,7 +225,7 @@ protected function cShowLocalExplorer(): \ilECSNodeMappingLocalExplorer $explorer->setPostVar('lnodes[]'); $lnodes = (array) $_REQUEST['lnodes']; - $checked_node = array_pop($lnodes); + $checked_node = (int) array_pop($lnodes); if ((int) $_REQUEST['lid']) { $checked_node = (int) $_REQUEST['lid']; } diff --git a/components/ILIAS/WebServices/ECS/classes/Mapping/class.ilECSNodeMappingLocalExplorer.php b/components/ILIAS/WebServices/ECS/classes/Mapping/class.ilECSNodeMappingLocalExplorer.php index 27ab61b6b0c3..1f3173176f1a 100755 --- a/components/ILIAS/WebServices/ECS/classes/Mapping/class.ilECSNodeMappingLocalExplorer.php +++ b/components/ILIAS/WebServices/ECS/classes/Mapping/class.ilECSNodeMappingLocalExplorer.php @@ -123,7 +123,7 @@ public function getPostVar(): string return $this->post_var; } - public function buildFormItem($a_node_id, int $a_type): string + public function buildFormItem($a_node_id, string $a_type): string { if (!array_key_exists($a_type, $this->form_items) || !$this->form_items[$a_type]) { return ''; @@ -134,13 +134,13 @@ public function buildFormItem($a_node_id, int $a_type): string return ilLegacyFormElementsUtil::formCheckbox( $this->isItemChecked($a_node_id), $this->post_var, - $a_node_id + (string) $a_node_id ); case self::SEL_TYPE_RADIO: return ilLegacyFormElementsUtil::formRadioButton( $this->isItemChecked($a_node_id), $this->post_var, - $a_node_id, + (string) $a_node_id, "document.getElementById('map').submit(); return false;" ); } @@ -201,7 +201,7 @@ public function formatObject(ilTemplate $tpl, $a_node_id, array $a_option, $a_ob $tpl->parseCurrentBlock(); } - if ($formItem = ($this->buildFormItem((int) $a_node_id, (int) $a_option['type']) !== '')) { + if (($formItem = $this->buildFormItem((int) $a_node_id, (string) $a_option['type'])) !== '') { $tpl->setCurrentBlock('check'); $tpl->setVariable('OBJ_CHECK', $formItem); $tpl->parseCurrentBlock(); @@ -285,7 +285,7 @@ public function formatHeader(ilTemplate $tpl, $a_obj_id, array $a_option): void $tpl->setVariable("TXT_ALT_IMG", $title); $tpl->parseCurrentBlock(); - if (($formItem = $this->buildFormItem((int) $a_obj_id, (int) $a_option['type'])) !== '') { + if (($formItem = $this->buildFormItem((int) $a_obj_id, (string) $a_option['type'])) !== '') { $tpl->setCurrentBlock('check'); $tpl->setVariable('OBJ_CHECK', $formItem); $tpl->parseCurrentBlock(); From 59afe5fd00197bb4fc793a068aabe542b2817716 Mon Sep 17 00:00:00 2001 From: Pascal Seeland Date: Wed, 31 Jul 2024 21:17:52 +0200 Subject: [PATCH 031/115] LDAP: Increase length of password fields See: https://mantis.ilias.de/view.php?id=31280 --- components/ILIAS/LDAP/LDAP.php | 3 +- components/ILIAS/LDAP/classes/Setup/Agent.php | 75 +++++++++++++++++++ .../Setup/LDAPBindPasswordFieldMigration.php | 62 +++++++++++++++ .../LDAP/classes/class.ilLDAPSettingsGUI.php | 4 +- 4 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 components/ILIAS/LDAP/classes/Setup/Agent.php create mode 100644 components/ILIAS/LDAP/classes/Setup/LDAPBindPasswordFieldMigration.php diff --git a/components/ILIAS/LDAP/LDAP.php b/components/ILIAS/LDAP/LDAP.php index 49c14d213b4d..1797fedcdb20 100644 --- a/components/ILIAS/LDAP/LDAP.php +++ b/components/ILIAS/LDAP/LDAP.php @@ -32,6 +32,7 @@ public function init( array | \ArrayAccess &$pull, array | \ArrayAccess &$internal, ): void { - // ... + $contribute[\ILIAS\Setup\Agent::class] = fn() => + new \ILIAS\LDAP\Setup\Agent(); } } diff --git a/components/ILIAS/LDAP/classes/Setup/Agent.php b/components/ILIAS/LDAP/classes/Setup/Agent.php new file mode 100644 index 000000000000..ff4a09c145cc --- /dev/null +++ b/components/ILIAS/LDAP/classes/Setup/Agent.php @@ -0,0 +1,75 @@ +db = $db; + } + + public function step_1(): void + { + if ($this->db->tableColumnExists('ldap_server_settings', 'bind_pass')) { + $this->db->modifyTableColumn( + 'ldap_server_settings', + 'bind_pass', + [ + 'type' => 'text', + 'length' => 100, + 'notnull' => false, + 'default' => null + ] + ); + } + if ($this->db->tableColumnExists('ldap_server_settings', 'role_bind_pass')) { + $this->db->modifyTableColumn( + 'ldap_server_settings', + 'role_bind_pass', + [ + 'type' => 'text', + 'length' => 100, + 'notnull' => false, + 'default' => null + ] + ); + } + } +} diff --git a/components/ILIAS/LDAP/classes/class.ilLDAPSettingsGUI.php b/components/ILIAS/LDAP/classes/class.ilLDAPSettingsGUI.php index d916c33c7fec..72ba272c44f3 100755 --- a/components/ILIAS/LDAP/classes/class.ilLDAPSettingsGUI.php +++ b/components/ILIAS/LDAP/classes/class.ilLDAPSettingsGUI.php @@ -805,7 +805,7 @@ private function initForm(): void $pass = new ilPasswordInputGUI($this->lng->txt('ldap_server_bind_pass'), 'bind_pass'); $pass->setSkipSyntaxCheck(true); $pass->setSize(12); - $pass->setMaxLength(36); + $pass->setMaxLength(100); $user->addSubItem($pass); $binding->addOption($user); $this->form_gui->addItem($binding); @@ -1369,7 +1369,7 @@ public function roleMapping(): void $pass->setPostVar("role_bind_pass"); $pass->setValue($this->server->getRoleBindPassword()); $pass->setSize(12); - $pass->setMaxLength(36); + $pass->setMaxLength(100); $pass->setRetype(false); $binding->addCombinationItem("1", $pass, $this->lng->txt('ldap_role_bind_pass')); From 69b1a607cce58f467003fda98c4734fd5e503423 Mon Sep 17 00:00:00 2001 From: Stephan Kergomard Date: Thu, 1 Aug 2024 14:49:39 +0200 Subject: [PATCH 032/115] RBAC: DIC Should Never Be Set To Array See (maybe): https://mantis.ilias.de/view.php?id=41574 --- ...ccessCustomRBACOperationAddedObjective.php | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/components/ILIAS/AccessControl/classes/Setup/class.ilAccessCustomRBACOperationAddedObjective.php b/components/ILIAS/AccessControl/classes/Setup/class.ilAccessCustomRBACOperationAddedObjective.php index d08b92d6631e..6249a1b48aca 100755 --- a/components/ILIAS/AccessControl/classes/Setup/class.ilAccessCustomRBACOperationAddedObjective.php +++ b/components/ILIAS/AccessControl/classes/Setup/class.ilAccessCustomRBACOperationAddedObjective.php @@ -25,6 +25,8 @@ class ilAccessCustomRBACOperationAddedObjective implements Setup\Objective { + private const NO_DIC_FOUND = 'There is no $DIC.'; + protected string $id; protected string $title; protected string $class; @@ -125,7 +127,7 @@ public function achieve(Environment $environment): Environment $db->insert("rbac_ta", $values); } - $GLOBALS["DIC"] = $dic; + $this->resetDIC($dic); return $environment; } @@ -159,8 +161,7 @@ public function isApplicable(Environment $environment): bool } } - $GLOBALS["DIC"] = $dic; - + $this->resetDIC($dic); return count($this->types) && in_array($this->class, ['create', 'object', 'general']); } @@ -172,17 +173,26 @@ protected function initEnvironment(Setup\Environment $environment) // subcomponents of the various readers to run. This is a memento to the // fact, that dependency injection is something we want. Currently, every // component could just service locate the whole world via the global $DIC. - $DIC = []; - if (isset($GLOBALS["DIC"])) { - $DIC = $GLOBALS["DIC"]; + $DIC = self::NO_DIC_FOUND; + if (array_key_exists('DIC', $GLOBALS)) { + $DIC = $GLOBALS['DIC']; } - $GLOBALS["DIC"] = new DI\Container(); - $GLOBALS["DIC"]["ilDB"] = $db; + $GLOBALS['DIC'] = new DI\Container(); + $GLOBALS['DIC']['ilDB'] = $db; - if (!defined("ILIAS_ABSOLUTE_PATH")) { - define("ILIAS_ABSOLUTE_PATH", dirname(__FILE__, 6)); + if (!defined('ILIAS_ABSOLUTE_PATH')) { + define('ILIAS_ABSOLUTE_PATH', dirname(__FILE__, 6)); } return $DIC; } + + protected function resetDIC($dic): void + { + if ($dic !== self::NO_DIC_FOUND) { + $GLOBALS['DIC'] = $dic; + return; + } + unset($GLOBALS['DIC']); + } } From fe10ca63b0cdb5116e9b70e37af09d2411c184dd Mon Sep 17 00:00:00 2001 From: Stephan Kergomard Date: Fri, 2 Aug 2024 08:01:44 +0200 Subject: [PATCH 033/115] RBAC: Operation Allows Null in DB See: https://mantis.ilias.de/view.php?id=41835 --- .../ILIAS/AccessControl/classes/class.ilRoleXmlExport.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/ILIAS/AccessControl/classes/class.ilRoleXmlExport.php b/components/ILIAS/AccessControl/classes/class.ilRoleXmlExport.php index bc98c0a64df2..a708f097db70 100755 --- a/components/ILIAS/AccessControl/classes/class.ilRoleXmlExport.php +++ b/components/ILIAS/AccessControl/classes/class.ilRoleXmlExport.php @@ -120,7 +120,9 @@ private function writeRole(int $a_role_id, int $a_rolf): void $this->xmlStartTag('operations'); foreach ($this->rbacreview->getAllOperationsOfRole($a_role_id, $a_rolf) as $obj_group => $operations) { foreach ($operations as $ops_id) { - $this->xmlElement('operation', ['group' => $obj_group], trim($this->operations[$ops_id])); + if (isset($this->operations[$ops_id])) { + $this->xmlElement('operation', ['group' => $obj_group], trim($this->operations[$ops_id])); + } } } $this->xmlEndTag('operations'); From 5053413a5e7f845f784ef489adb723514947e0cd Mon Sep 17 00:00:00 2001 From: Stephan Kergomard Date: Fri, 2 Aug 2024 13:24:55 +0200 Subject: [PATCH 034/115] Test: Remove Unneeded Function --- .../ILIAS/Test/src/Logging/LogTable.php | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/components/ILIAS/Test/src/Logging/LogTable.php b/components/ILIAS/Test/src/Logging/LogTable.php index 0334280d39f8..2c0368ccc2cb 100644 --- a/components/ILIAS/Test/src/Logging/LogTable.php +++ b/components/ILIAS/Test/src/Logging/LogTable.php @@ -444,37 +444,6 @@ private function buildInteractionTypesOptionsForFilter(): array return $interaction_options; } - protected function postOrder(array $list, \ILIAS\Data\Order $order): array - { - [$aspect, $direction] = $order->join('', function ($i, $k, $v) { - return [$k, $v]; - }); - usort($list, static function (array $a, array $b) use ($aspect): int { - if (is_numeric($a[$aspect]) || is_bool($a[$aspect])) { - return $a[$aspect] <=> $b[$aspect]; - } - if (is_array($a[$aspect])) { - return $a[$aspect] <=> $b[$aspect]; - } - - $aspect_a = ''; - $aspect_b = ''; - if ($a[$aspect] !== null) { - $aspect_a = $a[$aspect]; - } - if ($b[$aspect] !== null) { - $aspect_b = $b[$aspect]; - } - - return strcmp($aspect_a, $aspect_b); - }); - - if ($direction === $order::DESC) { - $list = array_reverse($list); - } - return $list; - } - private function prepareFilterData(array $filter_array): array { $from_filter = null; From b8ef8bc9a25bd4d42272d933eea4df3d2ce3ca24 Mon Sep 17 00:00:00 2001 From: Alex Killing Date: Fri, 2 Aug 2024 13:43:30 +0200 Subject: [PATCH 035/115] 41841: Editing the Portfilio pages leads to an error --- .../Portfolio/PrintView/class.PortfolioPrintViewProviderGUI.php | 1 + 1 file changed, 1 insertion(+) diff --git a/components/ILIAS/Portfolio/PrintView/class.PortfolioPrintViewProviderGUI.php b/components/ILIAS/Portfolio/PrintView/class.PortfolioPrintViewProviderGUI.php index d736cbc72299..c7b31e418de0 100755 --- a/components/ILIAS/Portfolio/PrintView/class.PortfolioPrintViewProviderGUI.php +++ b/components/ILIAS/Portfolio/PrintView/class.PortfolioPrintViewProviderGUI.php @@ -27,6 +27,7 @@ */ class PortfolioPrintViewProviderGUI extends Export\AbstractPrintViewProvider { + protected bool $include_declaration = false; protected StandardGUIRequest $port_request; protected \ilLanguage $lng; protected \ilObjPortfolio $portfolio; From 359a69dc7a32958a0c777f756f48d629fe4c8e33 Mon Sep 17 00:00:00 2001 From: Tim Schmitz Date: Fri, 2 Aug 2024 13:47:45 +0200 Subject: [PATCH 036/115] Metadata: possible languages in API, simplify search clause factory --- .../Repository/Search/Clauses/Factory.php | 5 +- .../Search/Clauses/FactoryInterface.php | 3 +- .../Repository/Search/Clauses/NullFactory.php | 1 - .../Services/DataHelper/DataHelper.php | 15 ++++++ .../DataHelper/DataHelperInterface.php | 7 +++ .../Services/DataHelper/LabelledValue.php | 47 +++++++++++++++++++ .../DataHelper/LabelledValueInterface.php | 36 ++++++++++++++ .../MetaData/classes/Services/Paths/Paths.php | 10 ++++ .../classes/Services/Paths/PathsInterface.php | 6 +++ components/ILIAS/MetaData/docs/api.md | 7 ++- .../ClauseWithPropertiesAndFactoryTest.php | 10 ++++ .../Services/DataHelper/DataHelperTest.php | 30 ++++++++++-- 12 files changed, 169 insertions(+), 8 deletions(-) create mode 100755 components/ILIAS/MetaData/classes/Services/DataHelper/LabelledValue.php create mode 100755 components/ILIAS/MetaData/classes/Services/DataHelper/LabelledValueInterface.php diff --git a/components/ILIAS/MetaData/classes/Repository/Search/Clauses/Factory.php b/components/ILIAS/MetaData/classes/Repository/Search/Clauses/Factory.php index c1b542686dc1..5769cfbc6f81 100644 --- a/components/ILIAS/MetaData/classes/Repository/Search/Clauses/Factory.php +++ b/components/ILIAS/MetaData/classes/Repository/Search/Clauses/Factory.php @@ -52,16 +52,17 @@ public function getBasicClause( public function getJoinedClauses( Operator $operator, ClauseInterface $first_clause, - ClauseInterface $second_clause, ClauseInterface ...$further_clauses ): ClauseInterface { + if (count($further_clauses) === 0) { + return $first_clause; + } return new Clause( false, true, new JoinProperties( $operator, $first_clause, - $second_clause, ...$further_clauses ), null diff --git a/components/ILIAS/MetaData/classes/Repository/Search/Clauses/FactoryInterface.php b/components/ILIAS/MetaData/classes/Repository/Search/Clauses/FactoryInterface.php index eaf09199e560..613af1be9f84 100644 --- a/components/ILIAS/MetaData/classes/Repository/Search/Clauses/FactoryInterface.php +++ b/components/ILIAS/MetaData/classes/Repository/Search/Clauses/FactoryInterface.php @@ -50,11 +50,12 @@ public function getBasicClause( * "Find all LOM sets that have at least one keyword with * value 'key' and at least one keyword with value 'different key' * and at least one author with value 'name'." + * + * When only a single clause is passed, it is returned as is. */ public function getJoinedClauses( Operator $operator, ClauseInterface $first_clause, - ClauseInterface $second_clause, ClauseInterface ...$further_clauses ): ClauseInterface; diff --git a/components/ILIAS/MetaData/classes/Repository/Search/Clauses/NullFactory.php b/components/ILIAS/MetaData/classes/Repository/Search/Clauses/NullFactory.php index d7d9315c0ea5..cc92cad86f06 100644 --- a/components/ILIAS/MetaData/classes/Repository/Search/Clauses/NullFactory.php +++ b/components/ILIAS/MetaData/classes/Repository/Search/Clauses/NullFactory.php @@ -36,7 +36,6 @@ public function getBasicClause( public function getJoinedClauses( Operator $operator, ClauseInterface $first_clause, - ClauseInterface $second_clause, ClauseInterface ...$further_clauses ): ClauseInterface { return new NullClause(); diff --git a/components/ILIAS/MetaData/classes/Services/DataHelper/DataHelper.php b/components/ILIAS/MetaData/classes/Services/DataHelper/DataHelper.php index e1a63e32b3f6..f0de700e7482 100755 --- a/components/ILIAS/MetaData/classes/Services/DataHelper/DataHelper.php +++ b/components/ILIAS/MetaData/classes/Services/DataHelper/DataHelper.php @@ -95,4 +95,19 @@ public function datetimeFromObject(\DateTimeImmutable $object): string { return $this->internal_helper->datetimeFromObject($object); } + + /** + * @return LabelledValueInterface[] + */ + public function getAllLanguages(): array + { + $languages = []; + foreach ($this->internal_helper->getAllLanguages() as $language) { + $languages[] = new LabelledValue( + $language, + $this->data_presentation->language($language) + ); + } + return $languages; + } } diff --git a/components/ILIAS/MetaData/classes/Services/DataHelper/DataHelperInterface.php b/components/ILIAS/MetaData/classes/Services/DataHelper/DataHelperInterface.php index c02503386c80..c664c61cbe7a 100755 --- a/components/ILIAS/MetaData/classes/Services/DataHelper/DataHelperInterface.php +++ b/components/ILIAS/MetaData/classes/Services/DataHelper/DataHelperInterface.php @@ -72,4 +72,11 @@ public function durationFromIntegers( * Note that LOM in ILIAS ignores the time part of any datetimes. */ public function datetimeFromObject(\DateTimeImmutable $object): string; + + /** + * Returns all languages that can be selected + * in LOM in ILIAS. + * @return LabelledValueInterface[] + */ + public function getAllLanguages(): array; } diff --git a/components/ILIAS/MetaData/classes/Services/DataHelper/LabelledValue.php b/components/ILIAS/MetaData/classes/Services/DataHelper/LabelledValue.php new file mode 100755 index 000000000000..a467955ee9c5 --- /dev/null +++ b/components/ILIAS/MetaData/classes/Services/DataHelper/LabelledValue.php @@ -0,0 +1,47 @@ +value = $value; + $this->label = $label; + } + + public function value(): string + { + return $this->value; + } + + public function presentableLabel(): string + { + return $this->label; + } +} diff --git a/components/ILIAS/MetaData/classes/Services/DataHelper/LabelledValueInterface.php b/components/ILIAS/MetaData/classes/Services/DataHelper/LabelledValueInterface.php new file mode 100755 index 000000000000..5fa8ab48947f --- /dev/null +++ b/components/ILIAS/MetaData/classes/Services/DataHelper/LabelledValueInterface.php @@ -0,0 +1,36 @@ +get(); } + public function firstDescription(): PathInterface + { + return $this->custom() + ->withNextStep('general') + ->withNextStep('description') + ->withAdditionalFilterAtCurrentStep(FilterType::INDEX, '0') + ->withNextStep('string') + ->get(); + } + public function keywords(): PathInterface { return $this->custom() diff --git a/components/ILIAS/MetaData/classes/Services/Paths/PathsInterface.php b/components/ILIAS/MetaData/classes/Services/Paths/PathsInterface.php index c008ad6c2182..675b9b58cfb8 100755 --- a/components/ILIAS/MetaData/classes/Services/Paths/PathsInterface.php +++ b/components/ILIAS/MetaData/classes/Services/Paths/PathsInterface.php @@ -34,6 +34,12 @@ public function title(): PathInterface; */ public function descriptions(): PathInterface; + /** + * Path to general > description > string, restricted to the + * first description. + */ + public function firstDescription(): PathInterface; + /** * Path to general > keyword > string. */ diff --git a/components/ILIAS/MetaData/docs/api.md b/components/ILIAS/MetaData/docs/api.md index 229d47d9d78f..e86272be9d39 100755 --- a/components/ILIAS/MetaData/docs/api.md +++ b/components/ILIAS/MetaData/docs/api.md @@ -795,6 +795,11 @@ months, days, hours, minutes, seconds (in this order). Note that there is a difference between a field not being filled and being filled with 0. In the former case, null is used instead of an integer. -Lastly, `durationToSeconds` transforms a LOM-duration to seconds. +`durationToSeconds` transforms a LOM-duration to seconds. This is only a rough estimate, as LOM-durations do not have a start date, so e.g. each month is treated as 30 days. + +`getAllLanguages` returns all languages that are valid in LOM in ILIAS +(see also [here](lom_structure.md#language-lang)) as pairs of value and +label. The value is what should be actually written into LOM, and the +label can be presented as is to the user. diff --git a/components/ILIAS/MetaData/tests/Repository/Search/Clauses/ClauseWithPropertiesAndFactoryTest.php b/components/ILIAS/MetaData/tests/Repository/Search/Clauses/ClauseWithPropertiesAndFactoryTest.php index da49bd52350b..ac1066348c85 100644 --- a/components/ILIAS/MetaData/tests/Repository/Search/Clauses/ClauseWithPropertiesAndFactoryTest.php +++ b/components/ILIAS/MetaData/tests/Repository/Search/Clauses/ClauseWithPropertiesAndFactoryTest.php @@ -162,6 +162,16 @@ public function testGetJoinedClauses(): void $this->assertNotNull($joined_clause->joinProperties()); } + public function testGetJoinedClausesWithOneClause(): void + { + $factory = new Factory(); + $clause_1 = new NullClause(); + $joined_clause = $factory->getJoinedClauses(Operator::OR, $clause_1); + + $this->assertSame($clause_1, $joined_clause); + } + + public function testGetJoinedClausesNotNegated(): void { $factory = new Factory(); diff --git a/components/ILIAS/MetaData/tests/Services/DataHelper/DataHelperTest.php b/components/ILIAS/MetaData/tests/Services/DataHelper/DataHelperTest.php index 9dcf4d826a4a..19249e5e8909 100755 --- a/components/ILIAS/MetaData/tests/Services/DataHelper/DataHelperTest.php +++ b/components/ILIAS/MetaData/tests/Services/DataHelper/DataHelperTest.php @@ -21,8 +21,7 @@ namespace ILIAS\MetaData\Services\DataHelper; use PHPUnit\Framework\TestCase; -use ILIAS\MetaData\Services\DataHelper\DataHelper; -use ILIAS\MetaData\DataHelper\NullDataHelper; +use ILIAS\MetaData\DataHelper\NullDataHelper as NullInternalDataHelper; use ILIAS\MetaData\Presentation\NullData; use ILIAS\MetaData\Elements\Data\DataInterface as ElementsDataInterface; use ILIAS\MetaData\Elements\Data\NullData as NullElementsData; @@ -45,7 +44,7 @@ public function value(): string protected function getDataHelper(): DataHelper { - $internal_helper = new class () extends NullDataHelper { + $internal_helper = new class () extends NullInternalDataHelper { public function durationToIterator(string $duration): \Generator { foreach (explode(':', $duration) as $v) { @@ -83,6 +82,11 @@ public function datetimeFromObject(\DateTimeImmutable $object): string { return $object->format('Y-m-d'); } + + public function getAllLanguages(): \Generator + { + yield from ['lang1', 'lang2', 'lang3']; + } }; $data_presentation = new class () extends NullData { @@ -90,6 +94,11 @@ public function dataValue(ElementsDataInterface $data): string { return 'presentable ' . $data->value(); } + + public function language(string $language): string + { + return 'translated_' . $language; + } }; return new DataHelper($internal_helper, $data_presentation); @@ -169,4 +178,19 @@ public function testDatetimeFromObject(): void $helper->datetimeFromObject(new \DateTimeImmutable('2013-01-20')) ); } + + public function testGetAllLanguages(): void + { + $helper = $this->getDataHelper(); + + $languages = $helper->getAllLanguages(); + + $this->assertCount(3, $languages); + $this->assertSame('lang1', $languages[0]->value()); + $this->assertSame('translated_lang1', $languages[0]->presentableLabel()); + $this->assertSame('lang2', $languages[1]->value()); + $this->assertSame('translated_lang2', $languages[1]->presentableLabel()); + $this->assertSame('lang3', $languages[2]->value()); + $this->assertSame('translated_lang3', $languages[2]->presentableLabel()); + } } From 992d883266c0e6051b30d417b64606009ed38ec0 Mon Sep 17 00:00:00 2001 From: Alex Killing Date: Fri, 2 Aug 2024 13:54:57 +0200 Subject: [PATCH 037/115] 41825: Import failure for portfolios --- components/ILIAS/Portfolio/Export/class.ilPortfolioImporter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/ILIAS/Portfolio/Export/class.ilPortfolioImporter.php b/components/ILIAS/Portfolio/Export/class.ilPortfolioImporter.php index 59b1a77adc2d..d0d0ed888e3b 100755 --- a/components/ILIAS/Portfolio/Export/class.ilPortfolioImporter.php +++ b/components/ILIAS/Portfolio/Export/class.ilPortfolioImporter.php @@ -52,7 +52,7 @@ public function finalProcessing( ): void { $prttpg_map = $a_mapping->getMappingsOfEntity("components/ILIAS/COPage", "pg"); foreach ($prttpg_map as $prttpg_id) { - $prttpg_id = substr($prttpg_id, 5); + $prttpg_id = (int) substr($prttpg_id, 5); $prtt_id = ilPortfolioTemplatePage::findPortfolioForPage($prttpg_id); ilPortfolioTemplatePage::_writeParentId("prtt", $prttpg_id, $prtt_id); } From 4217ee65b092f703ae9723181f8c3deda0ea9505 Mon Sep 17 00:00:00 2001 From: Alex Killing Date: Fri, 2 Aug 2024 14:30:26 +0200 Subject: [PATCH 038/115] added br on heading formattings in page overview --- components/ILIAS/Wiki/README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/components/ILIAS/Wiki/README.md b/components/ILIAS/Wiki/README.md index 1f2144400113..231d1fbe4efa 100755 --- a/components/ILIAS/Wiki/README.md +++ b/components/ILIAS/Wiki/README.md @@ -1,13 +1,15 @@ # Wiki -## Business Rules - -### Print View +## Print View - The print process uses a modal to select the printed pages, see https://docu.ilias.de/goto_docu_wiki_wpage_6995_1357.html - The print view opens in a separate tab, see https://mantis.ilias.de/view.php?id=20653 -### Notifications +## Notifications - Notifications can be activated for the whole wiki or for single wiki pages. -- After a notification for a page change has been sent, ILIAS does not sent additional notifications for the same page for the next 3 hours (standard waiting time in notification service). \ No newline at end of file +- After a notification for a page change has been sent, ILIAS does not sent additional notifications for the same page for the next 3 hours (standard waiting time in notification service). + +## Page Overview + +- The overview block on top, container all headings, will render all formattings included in the headings to enable things like sub and sup or latex code. \ No newline at end of file From faae7c2fbe812c6dfe9b2b3160cccb8eed5b9737 Mon Sep 17 00:00:00 2001 From: Alex Killing Date: Fri, 2 Aug 2024 15:43:14 +0200 Subject: [PATCH 039/115] 37390: Data not saved when adding internal links --- .../components/paragraph/actions/paragraph-action-types.js | 2 +- .../paragraph/actions/paragraph-editor-action-factory.js | 4 ++++ .../Editor/js/src/components/paragraph/ui/paragraph-ui.js | 6 ++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/components/ILIAS/COPage/Editor/js/src/components/paragraph/actions/paragraph-action-types.js b/components/ILIAS/COPage/Editor/js/src/components/paragraph/actions/paragraph-action-types.js index 06a32e444f24..5f405f262beb 100755 --- a/components/ILIAS/COPage/Editor/js/src/components/paragraph/actions/paragraph-action-types.js +++ b/components/ILIAS/COPage/Editor/js/src/components/paragraph/actions/paragraph-action-types.js @@ -49,6 +49,7 @@ const ACTIONS = { LINK_INTERNAL: 'link.internal', LINK_EXTERNAL: 'link.external', LINK_USER: 'link.user', + LINK_ADDED: 'link.added', SAVE_RETURN: 'save.return', AUTO_SAVE: 'save.auto', AUTO_INSERT_POST: 'post.insert.auto', @@ -56,6 +57,5 @@ const ACTIONS = { SPLIT_PARAGRAPH: 'par.split', MERGE_PREVIOUS: 'merge.previous', SECTION_CLASS: 'sec.class', // section format - }; export default ACTIONS; diff --git a/components/ILIAS/COPage/Editor/js/src/components/paragraph/actions/paragraph-editor-action-factory.js b/components/ILIAS/COPage/Editor/js/src/components/paragraph/actions/paragraph-editor-action-factory.js index f8d261c00d42..8e2715f90d96 100755 --- a/components/ILIAS/COPage/Editor/js/src/components/paragraph/actions/paragraph-editor-action-factory.js +++ b/components/ILIAS/COPage/Editor/js/src/components/paragraph/actions/paragraph-editor-action-factory.js @@ -174,6 +174,10 @@ export default class ParagraphEditorActionFactory { return this.editorActionFactory.action(this.COMPONENT, ACTIONS.LINK_USER); } + linkAdded() { + return this.editorActionFactory.action(this.COMPONENT, ACTIONS.LINK_ADDED); + } + /** * @returns {EditorAction} */ diff --git a/components/ILIAS/COPage/Editor/js/src/components/paragraph/ui/paragraph-ui.js b/components/ILIAS/COPage/Editor/js/src/components/paragraph/ui/paragraph-ui.js index 2fd191dc19b5..66ea4a6a4f64 100755 --- a/components/ILIAS/COPage/Editor/js/src/components/paragraph/ui/paragraph-ui.js +++ b/components/ILIAS/COPage/Editor/js/src/components/paragraph/ui/paragraph-ui.js @@ -294,7 +294,10 @@ export default class ParagraphUI { } addIntLink(b, e, content) { + const dispatch = this.dispatcher; + const action = this.actionFactory; this.addBBCode(b, e, false, content); + dispatch.dispatch(action.paragraph().editor().linkAdded()); } cmdIntLink() { @@ -424,6 +427,9 @@ export default class ParagraphUI { const t = this; il.Wiki.Edit.openLinkDialog(url, this.getSelection(), (stag) => { t.addBBCode(stag, '', true); + const dispatch = t.dispatcher; + const action = t.actionFactory; + dispatch.dispatch(action.paragraph().editor().linkAdded()); }); } From 3e7197fdc7af7de9574e06929bbd26c064814cd3 Mon Sep 17 00:00:00 2001 From: Tim Schmitz Date: Fri, 2 Aug 2024 16:02:29 +0200 Subject: [PATCH 040/115] DidacticTemplate: switch to LOM API --- .../Setting/class.ilDidacticTemplateSettingsGUI.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/components/ILIAS/DidacticTemplate/classes/Setting/class.ilDidacticTemplateSettingsGUI.php b/components/ILIAS/DidacticTemplate/classes/Setting/class.ilDidacticTemplateSettingsGUI.php index 4d67250c0632..0a5351712561 100755 --- a/components/ILIAS/DidacticTemplate/classes/Setting/class.ilDidacticTemplateSettingsGUI.php +++ b/components/ILIAS/DidacticTemplate/classes/Setting/class.ilDidacticTemplateSettingsGUI.php @@ -29,6 +29,7 @@ use ILIAS\Export\ImportStatus\ilCollection as ilImportStatusCollection; use ILIAS\Export\ImportStatus\ilFactory as ilImportStatusFactory; use ILIAS\Export\ImportStatus\StatusType as ImportStatusType; +use ILIAS\MetaData\Services\ServicesInterface as LOMServices; /** * Settings for a single didactic template @@ -55,6 +56,7 @@ class ilDidacticTemplateSettingsGUI private ilGlobalTemplateInterface $tpl; private ilTabsGUI $tabs; private FileUpload $upload; + private LOMServices $lom_services; private int $ref_id; @@ -75,6 +77,7 @@ public function __construct(ilObjectGUI $a_parent_obj) $this->upload = $DIC->upload(); $this->renderer = $DIC->ui()->renderer(); $this->ui_factory = $DIC->ui()->factory(); + $this->lom_services = $DIC->learningObjectMetadata(); } protected function initReferenceFromRequest(): void @@ -485,8 +488,13 @@ protected function initEditTemplate(ilDidacticTemplateSetting $set): ilPropertyF $def = $trans[0]; // default if (count($trans) > 1) { - $languages = ilMDLanguageItem::_getLanguages(); - $title->setInfo($this->lng->txt("language") . ": " . $languages[$def["lang_code"]] . + $language = ''; + foreach ($this->lom_services->dataHelper()->getAllLanguages() as $lom_lang) { + if ($lom_lang->value() === ($def["lang_code"] ?? '')) { + $language = $lom_lang->presentableLabel(); + } + } + $title->setInfo($this->lng->txt("language") . ": " . $language . ' » ' . $this->lng->txt("more_translations") . ''); } From d3e36260c98c3c1f26e2051924f8115b93740828 Mon Sep 17 00:00:00 2001 From: Tim Schmitz <104776863+schmitz-ilias@users.noreply.github.com> Date: Fri, 2 Aug 2024 17:46:42 +0200 Subject: [PATCH 041/115] Repository: switch to LOM API (#7885) --- .../classes/RepositoryOpenGraphExposer.php | 38 +++++++++++-------- .../Service/trait.GlobalDICDomainServices.php | 6 +++ 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/components/ILIAS/Repository/GlobalScreen/classes/RepositoryOpenGraphExposer.php b/components/ILIAS/Repository/GlobalScreen/classes/RepositoryOpenGraphExposer.php index 35738e0d170f..346605c91613 100755 --- a/components/ILIAS/Repository/GlobalScreen/classes/RepositoryOpenGraphExposer.php +++ b/components/ILIAS/Repository/GlobalScreen/classes/RepositoryOpenGraphExposer.php @@ -27,15 +27,25 @@ use ILIAS\GlobalScreen\ScreenContext\ContextRepository; use ILIAS\GlobalScreen\ScreenContext\ScreenContext; use ILIAS\Data\Meta\Html\OpenGraph\Image as OGImage; +use ILIAS\MetaData\Services\ServicesInterface as LOMServices; +use ILIAS\MetaData\Services\Reader\ReaderInterface as LOMReader; /** * @author Thibeau Fuhrer */ final class RepositoryOpenGraphExposer extends AbstractModificationProvider { + private LOMServices $lom_services; + private bool $fetch_tile_image = false; + public function __construct(\ILIAS\DI\Container $dic) + { + $this->lom_services = $dic->learningObjectMetadata(); + parent::__construct($dic); + } + public function isInterestedInContexts(): ContextCollection { // the exposer is interested in any context, BUT the repository context @@ -67,18 +77,14 @@ public function getContentModification(CalledContexts $screen_context_stack): ?C private function exposeObjectOpenGraphMetaData(\ilObject $object): void { $object_translation = \ilObjectTranslation::getInstance($object->getId()); - $general_meta_data = $this->getGeneralObjectMeta($object->getId()); $additional_locale_count = 0; $additional_locales = []; - if (null !== $general_meta_data) { - foreach ($general_meta_data->getLanguageIds() as $language_id) { - $language = $general_meta_data->getLanguage($language_id); - if (null !== $language && $language->getLanguageCode() !== $object_translation->getDefaultLanguage()) { - $additional_locales[] = $language->getLanguageCode(); - $additional_locale_count++; - } + foreach ($this->getLanguageCodesFromLOM($object->getId(), $object->getType()) as $language_code) { + if ($language_code !== $object_translation->getDefaultLanguage()) { + $additional_locales[] = $language_code; + $additional_locale_count++; } } @@ -145,16 +151,16 @@ protected function getDefaultImage(): OGImage ); } - protected function getGeneralObjectMeta(int $object_id): ?\ilMDGeneral + /** + * @return string[] + */ + protected function getLanguageCodesFromLOM(int $object_id, string $object_type): \Generator { - if (0 < ($meta_id = \ilMDGeneral::_getId($object_id, $object_id))) { - $general = new \ilMDGeneral(); - $general->setMetaId($meta_id); - - return $general; + $languages_path = $this->lom_services->paths()->languages(); + $reader = $this->lom_services->read($object_id, 0, $object_type, $languages_path); + foreach ($reader->allData($languages_path) as $lang_data) { + yield $lang_data->value(); } - - return null; } protected function ensureRepoContext(CalledContexts $screen_context_stack): CalledContexts diff --git a/components/ILIAS/Repository/Service/trait.GlobalDICDomainServices.php b/components/ILIAS/Repository/Service/trait.GlobalDICDomainServices.php index 19dcb13aad16..87aa9355d8c2 100755 --- a/components/ILIAS/Repository/Service/trait.GlobalDICDomainServices.php +++ b/components/ILIAS/Repository/Service/trait.GlobalDICDomainServices.php @@ -29,6 +29,7 @@ use ILIAS\Repository\Object\ObjectAdapter; use ILIAS\Repository\Profile\ProfileAdapter; use ILIAS\Repository\Resources\DomainService; +use ILIAS\MetaData\Services\ServicesInterface as LOMServices; trait GlobalDICDomainServices { @@ -121,6 +122,11 @@ public function backgroundTasks(): \ILIAS\BackgroundTasks\BackgroundTaskServices return $this->DIC->backgroundTasks(); } + public function learningObjectMetadata(): LOMServices + { + return $this->DIC->learningObjectMetadata(); + } + public function resources(): DomainService { return new DomainService( From 84088a41a073ab7c077c4bf1fe4377aae87c4373 Mon Sep 17 00:00:00 2001 From: Tim Schmitz <104776863+schmitz-ilias@users.noreply.github.com> Date: Fri, 2 Aug 2024 17:50:21 +0200 Subject: [PATCH 042/115] COPage: switch to LOM API (#7879) --- .../PC/FileList/class.ilPCFileListGUI.php | 7 +++- .../PC/Paragraph/class.ilPCParagraph.php | 38 ++++--------------- .../COPage/PC/Table/class.ilPCTableGUI.php | 7 +++- .../COPage/classes/class.ilPageContentGUI.php | 4 +- .../COPage/classes/class.ilPageObject.php | 4 ++ 5 files changed, 24 insertions(+), 36 deletions(-) diff --git a/components/ILIAS/COPage/PC/FileList/class.ilPCFileListGUI.php b/components/ILIAS/COPage/PC/FileList/class.ilPCFileListGUI.php index fd52a4f283a6..2767b684240e 100755 --- a/components/ILIAS/COPage/PC/FileList/class.ilPCFileListGUI.php +++ b/components/ILIAS/COPage/PC/FileList/class.ilPCFileListGUI.php @@ -305,9 +305,12 @@ public function initEditForm(string $a_mode = "edit"): ilPropertyFormGUI $form->addItem($ti); // language - $lang = ilMDLanguageItem::_getLanguages(); + $languages = []; + foreach ($this->lom_services->dataHelper()->getAllLanguages() as $language) { + $languages[$language->value()] = $language->presentableLabel(); + } $si = new ilSelectInputGUI($lng->txt("language"), "flst_language"); - $si->setOptions($lang); + $si->setOptions($languages); $form->addItem($si); } diff --git a/components/ILIAS/COPage/PC/Paragraph/class.ilPCParagraph.php b/components/ILIAS/COPage/PC/Paragraph/class.ilPCParagraph.php index a6e52b3a0d15..25fc43eb072f 100755 --- a/components/ILIAS/COPage/PC/Paragraph/class.ilPCParagraph.php +++ b/components/ILIAS/COPage/PC/Paragraph/class.ilPCParagraph.php @@ -1933,6 +1933,10 @@ public static function saveMetaKeywords( ilPageObject $a_page, DOMDocument $a_domdoc ): void { + global $DIC; + + $lom_services = $DIC->learningObjectMetadata(); + // not nice, should be set by context per method if ($a_page->getParentType() == "term" || $a_page->getParentType() == "lm") { @@ -1955,37 +1959,9 @@ public static function saveMetaKeywords( $meta_rep_id = $a_page->getParentId(); $meta_id = $a_page->getId(); - $md_obj = new ilMD($meta_rep_id, $meta_id, $meta_type); - $mkeywords = array(); - $lang = ""; - if (is_object($md_section = $md_obj->getGeneral())) { - foreach ($ids = $md_section->getKeywordIds() as $id) { - $md_key = $md_section->getKeyword($id); - $mkeywords[] = strtolower($md_key->getKeyword()); - if ($lang == "") { - $lang = $md_key->getKeywordLanguageCode(); - } - } - if ($lang == "") { - foreach ($ids = $md_section->getLanguageIds() as $id) { - $md_lang = $md_section->getLanguage($id); - if ($lang == "") { - $lang = $md_lang->getLanguageCode(); - } - } - } - foreach ($keywords as $k) { - if (!in_array(strtolower($k), $mkeywords)) { - if (trim($k) != "" && $lang != "") { - $md_key = $md_section->addKeyword(); - $md_key->setKeyword(ilUtil::stripSlashes($k)); - $md_key->setKeywordLanguage(new ilMDLanguageItem($lang)); - $md_key->save(); - } - $mkeywords[] = strtolower($k); - } - } - } + $lom_services->manipulate($meta_rep_id, $meta_id, $meta_type) + ->prepareCreateOrUpdate($lom_services->paths()->keywords(), ...$keywords) + ->execute(); } } diff --git a/components/ILIAS/COPage/PC/Table/class.ilPCTableGUI.php b/components/ILIAS/COPage/PC/Table/class.ilPCTableGUI.php index fe3c253abcfc..9244843c7544 100755 --- a/components/ILIAS/COPage/PC/Table/class.ilPCTableGUI.php +++ b/components/ILIAS/COPage/PC/Table/class.ilPCTableGUI.php @@ -382,9 +382,12 @@ public function initPropertiesForm( } else { $s_lang = $ilUser->getLanguage(); } - $lang = ilMDLanguageItem::_getLanguages(); + $languages = []; + foreach ($this->lom_services->dataHelper()->getAllLanguages() as $language) { + $languages[$language->value()] = $language->presentableLabel(); + } $language = new ilSelectInputGUI($this->lng->txt("language"), "language"); - $language->setOptions($lang); + $language->setOptions($languages); $language->setValue($s_lang); $this->form->addItem($language); diff --git a/components/ILIAS/COPage/classes/class.ilPageContentGUI.php b/components/ILIAS/COPage/classes/class.ilPageContentGUI.php index df1daf9527ad..5f8bad1e0a16 100755 --- a/components/ILIAS/COPage/classes/class.ilPageContentGUI.php +++ b/components/ILIAS/COPage/classes/class.ilPageContentGUI.php @@ -18,7 +18,7 @@ use ILIAS\COPage\PC\EditGUIRequest; use ILIAS\COPage\Editor\EditSessionRepository; - +use ILIAS\MetaData\Services\ServicesInterface as LOMServices; use ILIAS\Style; /** @@ -38,6 +38,7 @@ class ilPageContentGUI public ilGlobalTemplateInterface $tpl; public ilLanguage $lng; public ilCtrl $ctrl; + protected LOMServices $lom_services; public ilPageObject $pg_obj; public string $hier_id = ""; public DOMDocument $dom; @@ -83,6 +84,7 @@ public function __construct( $this->lng = $lng; $this->pg_obj = $a_pg_obj; $this->ctrl = $ilCtrl; + $this->lom_services = $DIC->learningObjectMetadata(); $this->content_obj = $a_content_obj; $service = $DIC->copage()->internal(); $this->request = $service diff --git a/components/ILIAS/COPage/classes/class.ilPageObject.php b/components/ILIAS/COPage/classes/class.ilPageObject.php index f8657451a09e..7548384d36ac 100755 --- a/components/ILIAS/COPage/classes/class.ilPageObject.php +++ b/components/ILIAS/COPage/classes/class.ilPageObject.php @@ -42,6 +42,8 @@ */ +use ILIAS\MetaData\Services\ServicesInterface as LOMServices; + /** * Class ilPageObject * Handles PageObjects of ILIAS Learning Modules (see ILIAS DTD) @@ -66,6 +68,7 @@ abstract class ilPageObject protected ilObjUser $user; protected ilLanguage $lng; protected ilTree $tree; + protected LOMServices $lom_services; protected int $id; public ?DOMDocument $dom = null; public string $xml = ""; @@ -116,6 +119,7 @@ final public function __construct( $this->lng = $DIC->language(); $this->tree = $DIC->repositoryTree(); $this->log = ilLoggerFactory::getLogger('copg'); + $this->lom_services = $DIC->learningObjectMetadata(); $this->reading_time_manager = new ILIAS\COPage\ReadingTime\ReadingTimeManager(); From 54115fbc06dbdf8f58fd36e3fd4ec1dbb8e32639 Mon Sep 17 00:00:00 2001 From: mjansen Date: Wed, 3 Apr 2024 16:37:05 +0200 Subject: [PATCH 043/115] WAC/Init: Provide a possible fix for Mantis 39817 See: https://mantis.ilias.de/view.php?id=39817 --- .../CmiXapi/classes/XapiProxy/DataService.php | 19 ++++---- .../Init/classes/class.ilInitialisation.php | 44 +++++++++++++------ .../classes/class.ilWebAccessChecker.php | 7 ++- 3 files changed, 44 insertions(+), 26 deletions(-) diff --git a/components/ILIAS/CmiXapi/classes/XapiProxy/DataService.php b/components/ILIAS/CmiXapi/classes/XapiProxy/DataService.php index 9931bf5539bd..589eaf155be5 100755 --- a/components/ILIAS/CmiXapi/classes/XapiProxy/DataService.php +++ b/components/ILIAS/CmiXapi/classes/XapiProxy/DataService.php @@ -1,7 +1,5 @@ getLogger('auth') ); @@ -1324,7 +1341,8 @@ protected static function initUser(): void self::initGlobal( "ilUser", new ilObjUser(ANONYMOUS_USER_ID), - "./components/ILIAS/User/classes/class.ilObjUser.php" + "./components/ILIAS/User/classes/class.ilObjUser.php", + true ); $ilias->account = $ilUser; diff --git a/components/ILIAS/WebAccessChecker/classes/class.ilWebAccessChecker.php b/components/ILIAS/WebAccessChecker/classes/class.ilWebAccessChecker.php index a2d1d576a1b2..383343e9e38d 100755 --- a/components/ILIAS/WebAccessChecker/classes/class.ilWebAccessChecker.php +++ b/components/ILIAS/WebAccessChecker/classes/class.ilWebAccessChecker.php @@ -1,4 +1,5 @@ init(); $ilAuthSession->regenerateId(); $ilAuthSession->setUserId(ANONYMOUS_USER_ID); $ilAuthSession->setAuthenticated(false, ANONYMOUS_USER_ID); From 7b2f867dc2a63aa0f54e1adf20bf16504c248a82 Mon Sep 17 00:00:00 2001 From: mjansen Date: Fri, 14 Jun 2024 11:39:38 +0200 Subject: [PATCH 044/115] LDAP: Handle internal PHP errors in `ilLDAPQuery` See: https://mantis.ilias.de/view.php?id=38111 --- .../ILIAS/LDAP/classes/class.ilLDAPQuery.php | 91 ++++++++++++++----- .../ILIAS/LDAP/classes/class.ilLDAPResult.php | 44 +++------ 2 files changed, 84 insertions(+), 51 deletions(-) diff --git a/components/ILIAS/LDAP/classes/class.ilLDAPQuery.php b/components/ILIAS/LDAP/classes/class.ilLDAPQuery.php index e0204b9af8ce..56f12f15ad65 100755 --- a/components/ILIAS/LDAP/classes/class.ilLDAPQuery.php +++ b/components/ILIAS/LDAP/classes/class.ilLDAPQuery.php @@ -43,10 +43,9 @@ class ilLDAPQuery private array $users = []; /** - * LDAP Handle - * @var resource + * @var false|resource|\LDAP\Connection */ - private $lh; + private $lh = false; /** * @throws ilLDAPQueryException @@ -541,29 +540,77 @@ private function readUserData(string $a_name, bool $a_check_dn = false, bool $a_ * IL_SCOPE_SUB => ldap_search * IL_SCOPE_ONE => ldap_list * @param array|null $controls LDAP Control to be passed on the the ldap functions - * @return resource|null + * @return null|false|resource|list<\LDAP\Result>|\LDAP\Result */ - private function queryByScope(int $a_scope, string $a_base_dn, string $a_filter, array $a_attributes, array $controls = null) - { + private function queryByScope( + int $a_scope, + string $a_base_dn, + string $a_filter, + array $a_attributes, + array $controls = null + ) { $a_filter = $a_filter ?: "(objectclass=*)"; - switch ($a_scope) { - case ilLDAPServer::LDAP_SCOPE_SUB: - $res = ldap_search($this->lh, $a_base_dn, $a_filter, $a_attributes, 0, 0, 0, LDAP_DEREF_NEVER, $controls); - break; + set_error_handler(static function (int $severity, string $message, string $file, int $line): void { + throw new ErrorException($message, $severity, $severity, $file, $line); + }); - case ilLDAPServer::LDAP_SCOPE_ONE: - $res = ldap_list($this->lh, $a_base_dn, $a_filter, $a_attributes, 0, 0, 0, LDAP_DEREF_NEVER, $controls); - break; - - case ilLDAPServer::LDAP_SCOPE_BASE: - $res = ldap_read($this->lh, $a_base_dn, $a_filter, $a_attributes, 0, 0, 0, LDAP_DEREF_NEVER, $controls); - break; + $result = null; // We should ensure the similar behaviour of using the @ operator in PHP < 8.x - default: - throw new ilLDAPUndefinedScopeException( - "Undefined LDAP Search Scope: " . $a_scope - ); + try { + switch ($a_scope) { + case ilLDAPServer::LDAP_SCOPE_SUB: + $result = ldap_search( + $this->lh, + $a_base_dn, + $a_filter, + $a_attributes, + 0, + 0, + 0, + LDAP_DEREF_NEVER, + $controls + ); + break; + + case ilLDAPServer::LDAP_SCOPE_ONE: + $result = ldap_list( + $this->lh, + $a_base_dn, + $a_filter, + $a_attributes, + 0, + 0, + 0, + LDAP_DEREF_NEVER, + $controls + ); + break; + + case ilLDAPServer::LDAP_SCOPE_BASE: + $result = ldap_read( + $this->lh, + $a_base_dn, + $a_filter, + $a_attributes, + 0, + 0, + 0, + LDAP_DEREF_NEVER, + $controls + ); + break; + + default: + throw new ilLDAPUndefinedScopeException( + "Undefined LDAP Search Scope: " . $a_scope + ); + } + } catch (ErrorException $e) { + $this->logger->warning($e->getMessage()); + $this->logger->warning($e->getTraceAsString()); + } finally { + restore_error_handler(); } $error = ldap_errno($this->lh); @@ -573,7 +620,7 @@ private function queryByScope(int $a_scope, string $a_base_dn, string $a_filter, $this->logger->warning('Filter: ' . $a_filter); } - return $res ?? null; + return $result; } /** diff --git a/components/ILIAS/LDAP/classes/class.ilLDAPResult.php b/components/ILIAS/LDAP/classes/class.ilLDAPResult.php index 8fff630098c1..aa213f2b634e 100755 --- a/components/ILIAS/LDAP/classes/class.ilLDAPResult.php +++ b/components/ILIAS/LDAP/classes/class.ilLDAPResult.php @@ -26,53 +26,42 @@ class ilLDAPResult { /** - * @var resource + * @var resource|\Ldap\Connection */ private $handle; /** - * @var resource + * @var null|resource|list<\Ldap\Result>|\Ldap\Result */ - private $result; + private $result = null; private array $rows = []; private ?array $last_row; /** - * ilLDAPPagedResult constructor. - * @param resource $a_ldap_handle from ldap_connect() - * @param resource $a_result from ldap_search() + * @param resource|\Ldap\Connection $a_ldap_handle from ldap_connect() + * @param null|false|resource|list<\Ldap\Result>|\Ldap\Result $a_result from ldap_search(), ldap_list() or ldap_read() */ public function __construct($a_ldap_handle, $a_result = null) { $this->handle = $a_ldap_handle; - if ($a_result !== null) { + if ($a_result !== null && $a_result !== false) { $this->result = $a_result; } } /** * Total count of resulted rows - * @return int */ public function numRows(): int { - return is_array($this->rows) ? count($this->rows) : 0; + return count($this->rows); } /** * Resource from ldap_search() - * @return resource - */ - public function getResult() - { - return $this->result; - } - - /** - * Resource from ldap_search() - * @param resource $result + * @param false|resource|list<\Ldap\Result>|\Ldap\Result $result */ public function setResult($result): void { @@ -93,7 +82,7 @@ public function get(): array */ public function getRows(): array { - return is_array($this->rows) ? $this->rows : []; + return $this->rows; } /** @@ -102,8 +91,10 @@ public function getRows(): array */ public function run(): self { - $entries = ldap_get_entries($this->handle, $this->result); - $this->addEntriesToRows($entries); + if ($this->result) { + $entries = ldap_get_entries($this->handle, $this->result); + $this->addEntriesToRows($entries); + } return $this; } @@ -118,8 +109,7 @@ private function addEntriesToRows(array $entries): void return; } - - for ($row_counter = 0; $row_counter < $num;$row_counter++) { + for ($row_counter = 0; $row_counter < $num; $row_counter++) { $data = $this->toSimpleArray($entries[$row_counter]); $this->rows[] = $data; $this->last_row = $data; @@ -129,10 +119,9 @@ private function addEntriesToRows(array $entries): void /** * Transforms results from ldap_get_entries() to a simple format */ - private function toSimpleArray(array $entry): array { - $data = array(); + $data = []; foreach ($entry as $key => $value) { if (is_int($key)) { continue; @@ -159,9 +148,6 @@ private function toSimpleArray(array $entry): array return $data; } - /** - * Destructor - */ public function __destruct() { if ($this->result) { From ee777cea9b5507c8b30b208b3ff193f01089f11a Mon Sep 17 00:00:00 2001 From: Fabian Helfer Date: Tue, 23 Jul 2024 10:20:53 +0200 Subject: [PATCH 045/115] Fix ilMailUserFieldChangeListener not found --- .../ILIAS/Mail/classes/class.ilMailUserFieldChangeListener.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/ILIAS/Mail/classes/class.ilMailUserFieldChangeListener.php b/components/ILIAS/Mail/classes/class.ilMailUserFieldChangeListener.php index 7cc94d12a940..06d5b0f99e1c 100755 --- a/components/ILIAS/Mail/classes/class.ilMailUserFieldChangeListener.php +++ b/components/ILIAS/Mail/classes/class.ilMailUserFieldChangeListener.php @@ -18,7 +18,7 @@ declare(strict_types=1); -namespace ILIAS\components\Mail; +namespace ILIAS\Services\Mail; use ILIAS\components\User\UserFieldAttributesChangeListener; use ILIAS\DI\Container; From 5d5c69188ff113e05677ff8f653c75832ab83fe4 Mon Sep 17 00:00:00 2001 From: Fabian Helfer Date: Fri, 2 Aug 2024 13:38:51 +0200 Subject: [PATCH 046/115] Sprachvariable sort_by_posts (cherry picked from commit 7a75657d2fc9d1f0881f640b4f678e4478297a1c) --- lang/ilias_de.lang | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang index 6c5e65e75f54..5d58f1190ef0 100755 --- a/lang/ilias_de.lang +++ b/lang/ilias_de.lang @@ -10123,7 +10123,7 @@ forum#:#selected_threads_closed#:#Das Thema wurde geschlossen. forum#:#selected_threads_reopened#:#Das Thema wurde wiedereröffnet. forum#:#sort_by_date#:#Nach Datum sortieren forum#:#sort_by_date_desc#:#Das Thema wird in flacher Ansicht präsentiert. Die Beiträge werden in der zeitlichen Reihenfolge der Erstellung gezeigt. -forum#:#sort_by_posts#:#Nach Beiträge sortieren +forum#:#sort_by_posts#:#Nach Beiträgen sortieren forum#:#sort_by_posts_desc#:#Das Thema wird in einer Baumansicht präsentiert. Antworten auf Beiträge werden in der Reihenfolge gezeigt, in der sie sich aufeinander beziehen. forum#:#sorting#:#Sortierung forum#:#sticky#:#'Top-Thema' From 6f0302aab05f82e3fe21687cf9414d4632327649 Mon Sep 17 00:00:00 2001 From: fneumann Date: Wed, 24 Jul 2024 10:50:57 +0200 Subject: [PATCH 047/115] Registration: Fix Unable to access Registration Codes Mantis: https://mantis.ilias.de/view.php?id=41640 (cherry picked from commit b15a21e99d524c729b59308cf80d2746d398f1fc) --- .../Registration/classes/class.ilRegistrationCodesTableGUI.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/ILIAS/Registration/classes/class.ilRegistrationCodesTableGUI.php b/components/ILIAS/Registration/classes/class.ilRegistrationCodesTableGUI.php index 90111b14b430..ba506f944eae 100755 --- a/components/ILIAS/Registration/classes/class.ilRegistrationCodesTableGUI.php +++ b/components/ILIAS/Registration/classes/class.ilRegistrationCodesTableGUI.php @@ -135,7 +135,7 @@ private function getItems(): void } if ($code["role"]) { - $result[$k]["role"] = $this->role_map[$code["role"]]; + $result[$k]["role"] = $this->role_map[$code["role"]] ?? $this->lng->txt('deleted'); } else { $result[$k]["role"] = ""; } From b8e431ca98a091d89684ea22f28ff28f0b159f89 Mon Sep 17 00:00:00 2001 From: Marvin Beym Date: Thu, 18 Jul 2024 11:22:28 +0200 Subject: [PATCH 048/115] Move ilias_copa_5_4.xsd file to correct folder and adapt to current Schema style (cherry picked from commit e32779b97b51d75cb77d70afa5ff9c3714d8285e) --- .../xml/SchemaValidation/ilias_copa_5_4.xsd | 27 +++++++++++++++++ .../ILIAS/Export/xml/ilias_copa_5_4.xsd | 29 ------------------- 2 files changed, 27 insertions(+), 29 deletions(-) create mode 100644 components/ILIAS/Export/xml/SchemaValidation/ilias_copa_5_4.xsd delete mode 100755 components/ILIAS/Export/xml/ilias_copa_5_4.xsd diff --git a/components/ILIAS/Export/xml/SchemaValidation/ilias_copa_5_4.xsd b/components/ILIAS/Export/xml/SchemaValidation/ilias_copa_5_4.xsd new file mode 100644 index 000000000000..d111259a1bce --- /dev/null +++ b/components/ILIAS/Export/xml/SchemaValidation/ilias_copa_5_4.xsd @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/components/ILIAS/Export/xml/ilias_copa_5_4.xsd b/components/ILIAS/Export/xml/ilias_copa_5_4.xsd deleted file mode 100755 index 092507af0f26..000000000000 --- a/components/ILIAS/Export/xml/ilias_copa_5_4.xsd +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From 9d655218e57e17be94055764a42dd9d400515d96 Mon Sep 17 00:00:00 2001 From: Marvin Beym Date: Thu, 18 Jul 2024 11:22:44 +0200 Subject: [PATCH 049/115] Check for style-id set for older imported contentpage (cherry picked from commit 573b6fcfe87585e856a4cb392a4429209ea4b5d0) --- .../ILIAS/ContentPage/classes/class.ilContentPageDataSet.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/ILIAS/ContentPage/classes/class.ilContentPageDataSet.php b/components/ILIAS/ContentPage/classes/class.ilContentPageDataSet.php index 7c63f6ec5c30..e137b8adfa10 100755 --- a/components/ILIAS/ContentPage/classes/class.ilContentPageDataSet.php +++ b/components/ILIAS/ContentPage/classes/class.ilContentPageDataSet.php @@ -132,6 +132,10 @@ public function importRecord( self::$style_map[(int) $a_rec['Style']][] = $newObject->getId(); } + if ($a_rec['style-id'] ?? false) { + self::$style_map[(int) $a_rec['style-id']][] = $newObject->getId(); + } + ilContainer::_writeContainerSetting( $newObject->getId(), ilObjectServiceSettingsGUI::INFO_TAB_VISIBILITY, From 0601670e004bb66d249b43c6dce8c27e1cc46d5d Mon Sep 17 00:00:00 2001 From: Marvin Beym Date: Thu, 18 Jul 2024 11:49:30 +0200 Subject: [PATCH 050/115] Fiy code style (cherry picked from commit b0a3c5e73b08b13c83fde0366c601fd2cf5d3057) --- .../ILIAS/ContentPage/classes/class.ilContentPageDataSet.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/ILIAS/ContentPage/classes/class.ilContentPageDataSet.php b/components/ILIAS/ContentPage/classes/class.ilContentPageDataSet.php index e137b8adfa10..15dacf3b0729 100755 --- a/components/ILIAS/ContentPage/classes/class.ilContentPageDataSet.php +++ b/components/ILIAS/ContentPage/classes/class.ilContentPageDataSet.php @@ -175,7 +175,7 @@ public function getXmlRecord( return $a_set; } - + return parent::getXmlRecord($a_entity, $a_version, $a_set); } } From 54fdf756b3b7f374d09067851c4f3e4c10a264bb Mon Sep 17 00:00:00 2001 From: Vincenzo Padula Date: Mon, 5 Aug 2024 09:44:20 +0200 Subject: [PATCH 051/115] Fix skin path in html exports (#7836) (cherry picked from commit 17ce70f79b82d5734829a08fc2b940f0635f9873) --- .../Style/System/classes/class.ilSystemStyleHTMLExport.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/components/ILIAS/Style/System/classes/class.ilSystemStyleHTMLExport.php b/components/ILIAS/Style/System/classes/class.ilSystemStyleHTMLExport.php index 7baeab2977d1..6948adc5448c 100755 --- a/components/ILIAS/Style/System/classes/class.ilSystemStyleHTMLExport.php +++ b/components/ILIAS/Style/System/classes/class.ilSystemStyleHTMLExport.php @@ -74,10 +74,14 @@ public function addImage(string $a_file, string $a_exp_file_name = ''): void public function export(): void { + $location_stylesheet = ilUtil::getStyleSheetLocation('filesystem'); + + // Fix skin path + $this->style_dir = dirname($this->style_dir, 2) . DIRECTORY_SEPARATOR . dirname($location_stylesheet); + $this->createDirectories(); // export system style sheet - $location_stylesheet = ilUtil::getStyleSheetLocation('filesystem'); $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator(dirname($location_stylesheet), FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST From db456d4474640e9d165f8ef93ac7514514f3d123 Mon Sep 17 00:00:00 2001 From: Timon Amstutz Date: Mon, 5 Aug 2024 11:17:39 +0200 Subject: [PATCH 052/115] Recompile Sass --- templates/default/delos.css | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/templates/default/delos.css b/templates/default/delos.css index 6b23064bf7e5..64be77874a35 100644 --- a/templates/default/delos.css +++ b/templates/default/delos.css @@ -4205,6 +4205,9 @@ hr.il-divider-with-label { .il-filter-input-section .il-input-multiselect { margin: 0.8em; } +.il-filter-input-section .c-input--duration { + margin: 12px; +} .il-filter-field { cursor: text; @@ -4228,7 +4231,7 @@ hr.il-divider-with-label { width: 100%; } -.il-input-duration .form-group.row { +.c-input--duration .form-group.row { width: 50%; float: left; } From 8a6a56c0d4596ed8f38a2c8b2b667a15591b4ba8 Mon Sep 17 00:00:00 2001 From: Timon Amstutz Date: Mon, 5 Aug 2024 11:18:39 +0200 Subject: [PATCH 053/115] See PR #7848 --- .../UI-framework/Divider/_ui-component_divider.scss | 2 +- .../UI-framework/Menu/_ui-component_drilldown.scss | 3 ++- templates/default/delos.css | 5 +++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/templates/default/070-components/UI-framework/Divider/_ui-component_divider.scss b/templates/default/070-components/UI-framework/Divider/_ui-component_divider.scss index 34beb574447a..af5f7ed09b12 100755 --- a/templates/default/070-components/UI-framework/Divider/_ui-component_divider.scss +++ b/templates/default/070-components/UI-framework/Divider/_ui-component_divider.scss @@ -8,7 +8,7 @@ $il-divider-bg: $il-main-bg; $il-divider-color: $il-text-color; $il-divider-color: $il-text-color; -h4.il-divider { +.engaged ~ ul > li > h4.il-divider { padding: $il-padding-small-vertical $il-padding-small-horizontal; margin-bottom: 0px; background-color: $il-divider-bg; diff --git a/templates/default/070-components/UI-framework/Menu/_ui-component_drilldown.scss b/templates/default/070-components/UI-framework/Menu/_ui-component_drilldown.scss index 0fa585413641..cce5f2fd1b1f 100755 --- a/templates/default/070-components/UI-framework/Menu/_ui-component_drilldown.scss +++ b/templates/default/070-components/UI-framework/Menu/_ui-component_drilldown.scss @@ -105,7 +105,8 @@ $c-drilldown-selected-bg: $il-main-dark-bg; .c-drilldown__menulevel--trigger, .btn-bulky, .link-bulky, - hr { + hr, + .il-divider { display: none; } diff --git a/templates/default/delos.css b/templates/default/delos.css index 64be77874a35..9bd815fafa82 100644 --- a/templates/default/delos.css +++ b/templates/default/delos.css @@ -3855,7 +3855,7 @@ button .minimize, button .close { display: flex; } -h4.il-divider { +.engaged ~ ul > li > h4.il-divider { padding: 3px 6px; margin-bottom: 0px; background-color: white; @@ -7115,7 +7115,8 @@ button .minimize, button .close { .c-drilldown .c-drilldown__menu .c-drilldown__menulevel--trigger, .c-drilldown .c-drilldown__menu .btn-bulky, .c-drilldown .c-drilldown__menu .link-bulky, -.c-drilldown .c-drilldown__menu hr { +.c-drilldown .c-drilldown__menu hr, +.c-drilldown .c-drilldown__menu .il-divider { display: none; } .c-drilldown .c-drilldown__menu > ul > li:not(.c-drilldown__menuitem--filtered) ~ .c-drilldown__menu--no-items { From b8c148764223d473017c88e5cd5b7a70443acdf2 Mon Sep 17 00:00:00 2001 From: Luka Stocker <67695434+lukastocker@users.noreply.github.com> Date: Mon, 5 Aug 2024 10:54:33 +0200 Subject: [PATCH 054/115] UI: set min-width to max-content for dropdowns. (#39053) (#7850) * UI: set min-width to max-content for dropdowns. (#39053) https://mantis.ilias.de/view.php?id=39053 * MEM & PRG: remove actions dropdown label from members table. (#39053) https://mantis.ilias.de/view.php?id=39053 (cherry picked from commit 0c7321ce95c715fb68916f0f30a49e7030852b19) --- .../Membership/classes/class.ilParticipantTableGUI.php | 7 +++---- .../classes/class.ilStudyProgrammeMembersTableGUI.php | 2 +- .../UI-framework/Dropdown/_ui-component_dropdown.scss | 1 + templates/default/delos.css | 1 + 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/components/ILIAS/Membership/classes/class.ilParticipantTableGUI.php b/components/ILIAS/Membership/classes/class.ilParticipantTableGUI.php index 6aef4c911053..8c1aeed73407 100755 --- a/components/ILIAS/Membership/classes/class.ilParticipantTableGUI.php +++ b/components/ILIAS/Membership/classes/class.ilParticipantTableGUI.php @@ -1,7 +1,5 @@ uiFactory->dropdown()->standard($dropDownItems) - ->withLabel($this->lng->txt('actions')); + $dropDown = $this->uiFactory->dropdown()->standard($dropDownItems); $this->tpl->setVariable('ACTION_USER', $this->renderer->render($dropDown)); } } diff --git a/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeMembersTableGUI.php b/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeMembersTableGUI.php index ae0d73b6a526..1530c325cf80 100755 --- a/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeMembersTableGUI.php +++ b/components/ILIAS/StudyProgramme/classes/class.ilStudyProgrammeMembersTableGUI.php @@ -303,7 +303,7 @@ protected function buildActionDropDown( $l[] = $this->ui_factory->button()->shy($this->lng->txt("prg_$action"), $target); } return $this->ui_renderer->render( - $this->ui_factory->dropdown()->standard($l)->withLabel($this->lng->txt('actions')) + $this->ui_factory->dropdown()->standard($l) ); } diff --git a/templates/default/070-components/UI-framework/Dropdown/_ui-component_dropdown.scss b/templates/default/070-components/UI-framework/Dropdown/_ui-component_dropdown.scss index abc6baaf00eb..68b1c43472e0 100755 --- a/templates/default/070-components/UI-framework/Dropdown/_ui-component_dropdown.scss +++ b/templates/default/070-components/UI-framework/Dropdown/_ui-component_dropdown.scss @@ -56,6 +56,7 @@ $cursor-disabled: not-allowed !default; .dropdown { position: relative; display: inline-block; // so dropdown can be in one line with a button + min-width: max-content; } // The dropdown menu (ul) diff --git a/templates/default/delos.css b/templates/default/delos.css index 9bd815fafa82..53fba61b0750 100644 --- a/templates/default/delos.css +++ b/templates/default/delos.css @@ -377,6 +377,7 @@ .dropdown { position: relative; display: inline-block; + min-width: max-content; } .dropdown-menu { From 05f16226e9024a59e1996bf3bd80171412435b96 Mon Sep 17 00:00:00 2001 From: Tim Schmitz Date: Fri, 2 Aug 2024 15:39:26 +0200 Subject: [PATCH 055/115] ContentPage: switch to LOM API --- .../classes/class.ilObjContentPageGUI.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/components/ILIAS/ContentPage/classes/class.ilObjContentPageGUI.php b/components/ILIAS/ContentPage/classes/class.ilObjContentPageGUI.php index 7052ebad8ad9..96b2556fe0f9 100755 --- a/components/ILIAS/ContentPage/classes/class.ilObjContentPageGUI.php +++ b/components/ILIAS/ContentPage/classes/class.ilObjContentPageGUI.php @@ -24,6 +24,7 @@ use ILIAS\ContentPage\PageMetrics\Event\PageUpdatedEvent; use ILIAS\DI\Container; use ILIAS\HTTP\GlobalHttpState; +use ILIAS\MetaData\Services\ServicesInterface as LOMServices; /** * Class ilObjContentPageGUI @@ -53,6 +54,7 @@ class ilObjContentPageGUI extends ilObject2GUI implements ilContentPageObjectCon private ilHelpGUI $help; private \ILIAS\DI\UIServices $uiServices; private readonly bool $in_page_editor_style_context; + private LOMServices $lom_services; public function __construct(int $a_id = 0, int $a_id_type = self::REPOSITORY_NODE_ID, int $a_parent_node_id = 0) { @@ -66,6 +68,7 @@ public function __construct(int $a_id = 0, int $a_id_type = self::REPOSITORY_NOD $this->navHistory = $this->dic['ilNavigationHistory']; $this->help = $DIC['ilHelp']; $this->uiServices = $DIC->ui(); + $this->lom_services = $DIC->learningObjectMetadata(); $this->lng->loadLanguageModule('copa'); $this->lng->loadLanguageModule('style'); @@ -603,14 +606,19 @@ protected function initEditCustomForm(ilPropertyFormGUI $a_form): void ); if ($this->getCreationMode() !== true && count($this->object->getObjectTranslation()->getLanguages()) > 1) { - $languages = ilMDLanguageItem::_getLanguages(); + $language = ''; + foreach ($this->lom_services->dataHelper()->getAllLanguages() as $lom_lang) { + if ($lom_lang->value() === $this->object->getObjectTranslation()->getDefaultLanguage()) { + $language = $lom_lang->presentableLabel(); + } + } $a_form->getItemByPostVar('title') ->setInfo( implode( ': ', [ $this->lng->txt('language'), - $languages[$this->object->getObjectTranslation()->getDefaultLanguage()] + $language ] ) ); From 82993d3f12bde0833b8fbf67f9a9495c20149331 Mon Sep 17 00:00:00 2001 From: iszmais Date: Thu, 25 Jul 2024 18:00:22 +0200 Subject: [PATCH 056/115] Prevent calling of undefined constants --- components/ILIAS/File/classes/class.ilObjFileStakeholder.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/ILIAS/File/classes/class.ilObjFileStakeholder.php b/components/ILIAS/File/classes/class.ilObjFileStakeholder.php index d0f6593162a0..7db2fde73b27 100755 --- a/components/ILIAS/File/classes/class.ilObjFileStakeholder.php +++ b/components/ILIAS/File/classes/class.ilObjFileStakeholder.php @@ -1,4 +1,5 @@ current_user = (int) ($DIC->isDependencyAvailable('user') ? $DIC->user()->getId() : ANONYMOUS_USER_ID); + $this->current_user = (int) ($DIC->isDependencyAvailable('user') ? $DIC->user()->getId() : 13); } /** From 5d8b490561603711be86eb0d64391f2c8dfef217 Mon Sep 17 00:00:00 2001 From: Fabian Schmid Date: Wed, 5 Jun 2024 07:22:06 +0200 Subject: [PATCH 057/115] [IMPROVEMENT] simplified stakeholder default owner --- .../class.ilObjBibliographicStakeholder.php | 9 +------ .../class.ilDataCollectionStakeholder.php | 14 +++------- ...class.ilExcInstructionFilesStakeholder.php | 15 +---------- .../class.ilExcSampleSolutionStakeholder.php | 12 +-------- ...lass.ilExcTutorFeedbackFileStakeholder.php | 13 +-------- ...class.ilExcTutorFeedbackZipStakeholder.php | 13 +-------- ....ilExcTutorTeamFeedbackFileStakeholder.php | 13 +-------- .../Icons/class.ilObjFileIconStakeholder.php | 12 +-------- .../classes/class.ilObjFileStakeholder.php | 12 +-------- .../class.ilTemporaryStakeholder.php | 27 ++++++++++++------- .../class.ilForumPostingFileStakeholder.php | 9 ------- .../class.ilObjectTileImageStakeholder.php | 16 +---------- ...lIndiviualAssessmentGradingStakeholder.php | 6 +---- .../class.ilMMStorageStakeholder.php | 9 +------ .../class.ilMDCopyrightImageStakeholder.php | 9 +------ .../Types/ilOrgUnitTypeStakeholder.php | 6 +---- .../AbstractResourceStakeholder.php | 22 +++++++++++++++ ...class.ilSkillProfileStorageStakeHolder.php | 22 +++------------ .../types/ilStudyProgrammeTypeStakeholder.php | 7 +---- .../class.assFileUploadStakeholder.php | 13 +-------- .../class.ilUserProfilePictureStakeholder.php | 16 +---------- .../Handler/WOPIStakeholderWrapper.php | 4 +-- .../Handler/WOPIUnknownStakeholder.php | 6 +---- 23 files changed, 64 insertions(+), 221 deletions(-) diff --git a/components/ILIAS/Bibliographic/classes/class.ilObjBibliographicStakeholder.php b/components/ILIAS/Bibliographic/classes/class.ilObjBibliographicStakeholder.php index e7e19a9bacd5..6c01fca1af4f 100755 --- a/components/ILIAS/Bibliographic/classes/class.ilObjBibliographicStakeholder.php +++ b/components/ILIAS/Bibliographic/classes/class.ilObjBibliographicStakeholder.php @@ -27,10 +27,6 @@ class ilObjBibliographicStakeholder extends AbstractResourceStakeholder { protected ?ilDBInterface $database = null; - public function __construct() - { - } - /** * @inheritDoc */ @@ -39,12 +35,9 @@ public function getId(): string return 'bibl'; } - /** - * @inheritDoc - */ public function getOwnerOfNewResources(): int { - return 6; + return $this->default_owner; } public function getLocationURIForResourceUsage(ResourceIdentification $identification): ?string diff --git a/components/ILIAS/DataCollection/classes/class.ilDataCollectionStakeholder.php b/components/ILIAS/DataCollection/classes/class.ilDataCollectionStakeholder.php index 822ecdbd83e9..36a2692c9b8c 100755 --- a/components/ILIAS/DataCollection/classes/class.ilDataCollectionStakeholder.php +++ b/components/ILIAS/DataCollection/classes/class.ilDataCollectionStakeholder.php @@ -18,18 +18,10 @@ declare(strict_types=1); +use ILIAS\DI\Container; + class ilDataCollectionStakeholder extends \ILIAS\ResourceStorage\Stakeholder\AbstractResourceStakeholder { - private int $owner; - - public function __construct() - { - global $DIC; - $this->owner = $DIC->isDependencyAvailable('user') - ? $DIC->user()->getId() - : (defined('SYSTEM_USER_ID') ? (int) SYSTEM_USER_ID : 6); - } - public function getId(): string { return "dcl_uploads"; @@ -37,6 +29,6 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return $this->owner; + return $this->default_owner; } } diff --git a/components/ILIAS/Exercise/InstructionFile/class.ilExcInstructionFilesStakeholder.php b/components/ILIAS/Exercise/InstructionFile/class.ilExcInstructionFilesStakeholder.php index afedbd5f08a8..d5eaf49a4e97 100755 --- a/components/ILIAS/Exercise/InstructionFile/class.ilExcInstructionFilesStakeholder.php +++ b/components/ILIAS/Exercise/InstructionFile/class.ilExcInstructionFilesStakeholder.php @@ -27,21 +27,8 @@ */ class ilExcInstructionFilesStakeholder extends AbstractResourceStakeholder { - protected int $owner = 6; - private int $current_user; protected ?ilDBInterface $database = null; - /** - * ilObjFileStakeholder constructor. - */ - public function __construct(int $owner = 6) - { - global $DIC; - $this->current_user = (int) ($DIC->isDependencyAvailable('user') - ? $DIC->user()->getId() - : (defined('ANONYMOUS_USER_ID') ? ANONYMOUS_USER_ID : 6)); - $this->owner = $owner; - } public function getId(): string { @@ -50,7 +37,7 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return $this->owner; + return $this->default_owner; } public function canBeAccessedByCurrentUser(ResourceIdentification $identification): bool diff --git a/components/ILIAS/Exercise/SampleSolution/class.ilExcSampleSolutionStakeholder.php b/components/ILIAS/Exercise/SampleSolution/class.ilExcSampleSolutionStakeholder.php index 3e65be82a572..937c660fb3b0 100755 --- a/components/ILIAS/Exercise/SampleSolution/class.ilExcSampleSolutionStakeholder.php +++ b/components/ILIAS/Exercise/SampleSolution/class.ilExcSampleSolutionStakeholder.php @@ -24,18 +24,8 @@ class ilExcSampleSolutionStakeholder extends AbstractResourceStakeholder { - protected int $owner = 6; - private int $current_user; protected ?ilDBInterface $database = null; - public function __construct(int $owner = 6) - { - global $DIC; - $this->current_user = (int) ($DIC->isDependencyAvailable('user') - ? $DIC->user()->getId() - : (defined('ANONYMOUS_USER_ID') ? ANONYMOUS_USER_ID : 6)); - $this->owner = $owner; - } public function getId(): string { @@ -44,7 +34,7 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return $this->owner; + return $this->default_owner; } public function canBeAccessedByCurrentUser(ResourceIdentification $identification): bool diff --git a/components/ILIAS/Exercise/TutorFeedbackFile/class.ilExcTutorFeedbackFileStakeholder.php b/components/ILIAS/Exercise/TutorFeedbackFile/class.ilExcTutorFeedbackFileStakeholder.php index 6d5c31426d47..abb339fc5b08 100755 --- a/components/ILIAS/Exercise/TutorFeedbackFile/class.ilExcTutorFeedbackFileStakeholder.php +++ b/components/ILIAS/Exercise/TutorFeedbackFile/class.ilExcTutorFeedbackFileStakeholder.php @@ -26,19 +26,8 @@ class ilExcTutorFeedbackFileStakeholder extends AbstractResourceStakeholder { - protected int $owner = 6; - private int $current_user; protected ?ilDBInterface $database = null; - public function __construct(int $owner = 6) - { - global $DIC; - $this->current_user = (int) ($DIC->isDependencyAvailable('user') - ? $DIC->user()->getId() - : (defined('ANONYMOUS_USER_ID') ? ANONYMOUS_USER_ID : 6)); - $this->owner = $owner; - } - public function getId(): string { return 'exc_tutor_feedback'; @@ -46,7 +35,7 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return $this->owner; + return $this->default_owner; } public function canBeAccessedByCurrentUser(ResourceIdentification $identification): bool diff --git a/components/ILIAS/Exercise/TutorFeedbackFile/class.ilExcTutorFeedbackZipStakeholder.php b/components/ILIAS/Exercise/TutorFeedbackFile/class.ilExcTutorFeedbackZipStakeholder.php index 5f3b056629ac..86e3121205a1 100755 --- a/components/ILIAS/Exercise/TutorFeedbackFile/class.ilExcTutorFeedbackZipStakeholder.php +++ b/components/ILIAS/Exercise/TutorFeedbackFile/class.ilExcTutorFeedbackZipStakeholder.php @@ -26,19 +26,8 @@ class ilExcTutorFeedbackZipStakeholder extends AbstractResourceStakeholder { - protected int $owner = 6; - private int $current_user; protected ?ilDBInterface $database = null; - public function __construct(int $owner = 6) - { - global $DIC; - $this->current_user = (int) ($DIC->isDependencyAvailable('user') - ? $DIC->user()->getId() - : (defined('ANONYMOUS_USER_ID') ? ANONYMOUS_USER_ID : 6)); - $this->owner = $owner; - } - public function getId(): string { return 'exc_tutor_feedback_zip'; @@ -46,7 +35,7 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return $this->owner; + return $this->default_owner; } public function canBeAccessedByCurrentUser(ResourceIdentification $identification): bool diff --git a/components/ILIAS/Exercise/TutorFeedbackFile/class.ilExcTutorTeamFeedbackFileStakeholder.php b/components/ILIAS/Exercise/TutorFeedbackFile/class.ilExcTutorTeamFeedbackFileStakeholder.php index 162991d984f1..ac2cfd967653 100755 --- a/components/ILIAS/Exercise/TutorFeedbackFile/class.ilExcTutorTeamFeedbackFileStakeholder.php +++ b/components/ILIAS/Exercise/TutorFeedbackFile/class.ilExcTutorTeamFeedbackFileStakeholder.php @@ -26,19 +26,8 @@ class ilExcTutorTeamFeedbackFileStakeholder extends AbstractResourceStakeholder { - protected int $owner = 6; - private int $current_user; protected ?ilDBInterface $database = null; - public function __construct(int $owner = 6) - { - global $DIC; - $this->current_user = (int) ($DIC->isDependencyAvailable('user') - ? $DIC->user()->getId() - : (defined('ANONYMOUS_USER_ID') ? ANONYMOUS_USER_ID : 6)); - $this->owner = $owner; - } - public function getId(): string { return 'exc_tutor_team_feedback'; @@ -46,7 +35,7 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return $this->owner; + return $this->default_owner; } public function canBeAccessedByCurrentUser(ResourceIdentification $identification): bool diff --git a/components/ILIAS/File/classes/Icons/class.ilObjFileIconStakeholder.php b/components/ILIAS/File/classes/Icons/class.ilObjFileIconStakeholder.php index 4bc20a3b75e1..189c3b3731ea 100755 --- a/components/ILIAS/File/classes/Icons/class.ilObjFileIconStakeholder.php +++ b/components/ILIAS/File/classes/Icons/class.ilObjFileIconStakeholder.php @@ -27,16 +27,6 @@ */ class ilObjFileIconStakeholder extends AbstractResourceStakeholder { - /** - * ilObjFileIconStakeholder constructor. - */ - public function __construct(protected int $owner = 6) - { - } - - /** - * @inheritDoc - */ public function getId(): string { return 'file_icon'; @@ -47,6 +37,6 @@ public function getId(): string */ public function getOwnerOfNewResources(): int { - return $this->owner; + return $this->default_owner; } } diff --git a/components/ILIAS/File/classes/class.ilObjFileStakeholder.php b/components/ILIAS/File/classes/class.ilObjFileStakeholder.php index 7db2fde73b27..f2ad8f996a34 100755 --- a/components/ILIAS/File/classes/class.ilObjFileStakeholder.php +++ b/components/ILIAS/File/classes/class.ilObjFileStakeholder.php @@ -25,18 +25,8 @@ */ class ilObjFileStakeholder extends AbstractResourceStakeholder { - private int $current_user; protected ?ilDBInterface $database = null; - /** - * ilObjFileStakeholder constructor. - */ - public function __construct(protected int $owner = 6) - { - global $DIC; - $this->current_user = (int) ($DIC->isDependencyAvailable('user') ? $DIC->user()->getId() : 13); - } - /** * @inheritDoc */ @@ -47,7 +37,7 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return $this->owner; + return $this->default_owner; } public function canBeAccessedByCurrentUser(ResourceIdentification $identification): bool diff --git a/components/ILIAS/FileServices/classes/UploadService/class.ilTemporaryStakeholder.php b/components/ILIAS/FileServices/classes/UploadService/class.ilTemporaryStakeholder.php index 99387e520f3e..11622d1d492b 100755 --- a/components/ILIAS/FileServices/classes/UploadService/class.ilTemporaryStakeholder.php +++ b/components/ILIAS/FileServices/classes/UploadService/class.ilTemporaryStakeholder.php @@ -1,5 +1,21 @@ owner_id = $DIC->user()->getId(); - } - public function getId(): string { return 'irss_temp'; @@ -32,6 +39,6 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return $this->owner_id; + return $this->default_owner; } } diff --git a/components/ILIAS/Forum/classes/class.ilForumPostingFileStakeholder.php b/components/ILIAS/Forum/classes/class.ilForumPostingFileStakeholder.php index be2e62a0ec09..8aca0ae9c58f 100755 --- a/components/ILIAS/Forum/classes/class.ilForumPostingFileStakeholder.php +++ b/components/ILIAS/Forum/classes/class.ilForumPostingFileStakeholder.php @@ -25,15 +25,6 @@ */ class ilForumPostingFileStakeholder extends AbstractResourceStakeholder { - private int $default_owner; - - public function __construct() - { - global $DIC; - $this->default_owner = $DIC->isDependencyAvailable('user') ? $DIC->user()->getId() : 6; - } - - public function getId(): string { return 'frm_post'; diff --git a/components/ILIAS/ILIASObject/classes/Properties/CoreProperties/TileImage/class.ilObjectTileImageStakeholder.php b/components/ILIAS/ILIASObject/classes/Properties/CoreProperties/TileImage/class.ilObjectTileImageStakeholder.php index e317ee6fbed4..351f3849e237 100755 --- a/components/ILIAS/ILIASObject/classes/Properties/CoreProperties/TileImage/class.ilObjectTileImageStakeholder.php +++ b/components/ILIAS/ILIASObject/classes/Properties/CoreProperties/TileImage/class.ilObjectTileImageStakeholder.php @@ -21,27 +21,13 @@ namespace ILIAS\Object\Properties\CoreProperties\TileImage; use ILIAS\ResourceStorage\Stakeholder\AbstractResourceStakeholder; +use ILIAS\DI\Container; /** * @author Fabian Schmid */ class ilObjectTileImageStakeholder extends AbstractResourceStakeholder { - private int $default_owner; - - public function __construct() - { - global $DIC; - $this->default_owner = $DIC->isDependencyAvailable('user') - ? $DIC->user()->getId() - : (defined('SYSTEM_USER_ID') ? (int) SYSTEM_USER_ID : 6); - } - - public function setOwner(int $user_id_of_owner): void - { - $this->default_owner = $user_id_of_owner; - } - public function getId(): string { return 'object_tile_image'; diff --git a/components/ILIAS/IndividualAssessment/classes/FileStorage/class.ilIndiviualAssessmentGradingStakeholder.php b/components/ILIAS/IndividualAssessment/classes/FileStorage/class.ilIndiviualAssessmentGradingStakeholder.php index c646bc4a0b16..9349f60867e3 100644 --- a/components/ILIAS/IndividualAssessment/classes/FileStorage/class.ilIndiviualAssessmentGradingStakeholder.php +++ b/components/ILIAS/IndividualAssessment/classes/FileStorage/class.ilIndiviualAssessmentGradingStakeholder.php @@ -24,10 +24,6 @@ class ilIndividualAssessmentGradingStakeholder extends AbstractResourceStakehold { private const ID = 'IASSGrading'; - public function __construct( - protected int $owner = 6 - ) { - } public function getId(): string { @@ -36,6 +32,6 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return $this->owner; + return $this->default_owner; } } diff --git a/components/ILIAS/MainMenu/classes/Administration/class.ilMMStorageStakeholder.php b/components/ILIAS/MainMenu/classes/Administration/class.ilMMStorageStakeholder.php index c3bb1a6a53c9..476b4467c60b 100755 --- a/components/ILIAS/MainMenu/classes/Administration/class.ilMMStorageStakeholder.php +++ b/components/ILIAS/MainMenu/classes/Administration/class.ilMMStorageStakeholder.php @@ -26,13 +26,6 @@ */ class ilMMStorageStakeholder extends AbstractResourceStakeholder { - public function __construct() - { - } - - /** - * @inheritDoc - */ public function getId(): string { return 'mme'; @@ -40,6 +33,6 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return 6; + return $this->default_owner; } } diff --git a/components/ILIAS/MetaData/classes/Settings/Copyright/class.ilMDCopyrightImageStakeholder.php b/components/ILIAS/MetaData/classes/Settings/Copyright/class.ilMDCopyrightImageStakeholder.php index d54604cde35b..ee301937a813 100755 --- a/components/ILIAS/MetaData/classes/Settings/Copyright/class.ilMDCopyrightImageStakeholder.php +++ b/components/ILIAS/MetaData/classes/Settings/Copyright/class.ilMDCopyrightImageStakeholder.php @@ -22,13 +22,6 @@ class ilMDCopyrightImageStakeholder extends AbstractResourceStakeholder { - protected int $owner = 6; - - public function __construct(int $owner = 6) - { - $this->owner = $owner; - } - public function getId(): string { return 'copyright_image'; @@ -36,6 +29,6 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return $this->owner; + return $this->default_owner; } } diff --git a/components/ILIAS/OrgUnit/classes/Types/ilOrgUnitTypeStakeholder.php b/components/ILIAS/OrgUnit/classes/Types/ilOrgUnitTypeStakeholder.php index 0329cddbac33..19c26e9db991 100644 --- a/components/ILIAS/OrgUnit/classes/Types/ilOrgUnitTypeStakeholder.php +++ b/components/ILIAS/OrgUnit/classes/Types/ilOrgUnitTypeStakeholder.php @@ -25,10 +25,6 @@ class ilOrgUnitTypeStakeholder extends AbstractResourceStakeholder { private const ID = 'PRGType'; - public function __construct( - protected int $owner = 6 - ) { - } public function getId(): string { @@ -37,7 +33,7 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return $this->owner; + return $this->default_owner; } public function getPreprocessors(): array diff --git a/components/ILIAS/ResourceStorage/src/Stakeholder/AbstractResourceStakeholder.php b/components/ILIAS/ResourceStorage/src/Stakeholder/AbstractResourceStakeholder.php index 00919c6adec8..4ab65ad232b8 100755 --- a/components/ILIAS/ResourceStorage/src/Stakeholder/AbstractResourceStakeholder.php +++ b/components/ILIAS/ResourceStorage/src/Stakeholder/AbstractResourceStakeholder.php @@ -21,6 +21,7 @@ namespace ILIAS\ResourceStorage\Stakeholder; use ILIAS\ResourceStorage\Identification\ResourceIdentification; +use ILIAS\DI\Container; /** * @author Fabian Schmid @@ -29,6 +30,27 @@ abstract class AbstractResourceStakeholder implements ResourceStakeholder { private string $provider_name_cache = ''; + protected int $default_owner; + protected int $current_user; + + public function __construct(?int $user_id_of_owner = null) + { + global $DIC; + + if ($user_id_of_owner === null) { + $user_id_of_owner = $DIC instanceof Container && $DIC->isDependencyAvailable('user') + ? $DIC->user()->getId() + : (defined('SYSTEM_USER_ID') ? (int) SYSTEM_USER_ID : 6); + } + + $this->default_owner = $this->current_user = $user_id_of_owner; + } + + public function setOwner(int $user_id_of_owner): void + { + $this->default_owner = $user_id_of_owner; + } + public function getFullyQualifiedClassName(): string { return static::class; diff --git a/components/ILIAS/Skill/Profile/class.ilSkillProfileStorageStakeHolder.php b/components/ILIAS/Skill/Profile/class.ilSkillProfileStorageStakeHolder.php index 806e8aa723d0..1b676a44be12 100755 --- a/components/ILIAS/Skill/Profile/class.ilSkillProfileStorageStakeHolder.php +++ b/components/ILIAS/Skill/Profile/class.ilSkillProfileStorageStakeHolder.php @@ -1,7 +1,5 @@ owner = $owner; - } - - /** - * @inheritDoc - */ public function getId(): string { return 'skl_prof'; } - /** - * @inheritDoc - */ public function getOwnerOfNewResources(): int { - return $this->owner; + return $this->default_owner; } } diff --git a/components/ILIAS/StudyProgramme/classes/types/ilStudyProgrammeTypeStakeholder.php b/components/ILIAS/StudyProgramme/classes/types/ilStudyProgrammeTypeStakeholder.php index 4bec6aa35b16..cee66dd9ead7 100644 --- a/components/ILIAS/StudyProgramme/classes/types/ilStudyProgrammeTypeStakeholder.php +++ b/components/ILIAS/StudyProgramme/classes/types/ilStudyProgrammeTypeStakeholder.php @@ -25,11 +25,6 @@ class ilStudyProgrammeTypeStakeholder extends AbstractResourceStakeholder { private const ID = 'PRGType'; - public function __construct( - protected int $owner = 6 - ) { - } - public function getId(): string { return self::ID; @@ -37,7 +32,7 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return $this->owner; + return $this->default_owner; } public function getPreprocessors(): array diff --git a/components/ILIAS/TestQuestionPool/classes/class.assFileUploadStakeholder.php b/components/ILIAS/TestQuestionPool/classes/class.assFileUploadStakeholder.php index aeb962772214..a634cc4ea909 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assFileUploadStakeholder.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assFileUploadStakeholder.php @@ -23,17 +23,6 @@ */ class assFileUploadStakeholder extends AbstractResourceStakeholder { - private int $current_user; - - public function __construct() - { - global $DIC; - $anonymous = defined( - 'ANONYMOUS_USER_ID' - ) ? ANONYMOUS_USER_ID : 13; - $this->current_user = (int) ($DIC->isDependencyAvailable('user') ? $DIC->user()->getId() : $anonymous); - } - public function getId(): string { return 'qpl_file_upload'; @@ -41,7 +30,7 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return $this->current_user; + return $this->default_owner; } } diff --git a/components/ILIAS/User/classes/Avatar/class.ilUserProfilePictureStakeholder.php b/components/ILIAS/User/classes/Avatar/class.ilUserProfilePictureStakeholder.php index 088d113d9820..b6743f6f4147 100755 --- a/components/ILIAS/User/classes/Avatar/class.ilUserProfilePictureStakeholder.php +++ b/components/ILIAS/User/classes/Avatar/class.ilUserProfilePictureStakeholder.php @@ -19,27 +19,13 @@ declare(strict_types=1); use ILIAS\ResourceStorage\Stakeholder\AbstractResourceStakeholder; +use ILIAS\DI\Container; /** * @author Fabian Schmid */ class ilUserProfilePictureStakeholder extends AbstractResourceStakeholder { - private int $default_owner; - - public function __construct() - { - global $DIC; - $this->default_owner = $DIC->isDependencyAvailable('user') - ? $DIC->user()->getId() - : (defined('SYSTEM_USER_ID') ? (int) SYSTEM_USER_ID : 6); - } - - public function setOwner(int $user_id_of_owner): void - { - $this->default_owner = $user_id_of_owner; - } - public function getId(): string { return 'usr_picture'; diff --git a/components/ILIAS/WOPI/classes/Handler/WOPIStakeholderWrapper.php b/components/ILIAS/WOPI/classes/Handler/WOPIStakeholderWrapper.php index 9731ad553a34..27ca74e57ff3 100644 --- a/components/ILIAS/WOPI/classes/Handler/WOPIStakeholderWrapper.php +++ b/components/ILIAS/WOPI/classes/Handler/WOPIStakeholderWrapper.php @@ -29,13 +29,11 @@ class WOPIStakeholderWrapper extends AbstractResourceStakeholder private ?int $user_id = null; private ?ResourceStakeholder $stakeholder = null; - public function __construct() - { - } public function init(ResourceStakeholder $stakeholder, int $user_id): void { $this->user_id = $user_id; + $this->setOwner($user_id); $this->stakeholder = $stakeholder; } diff --git a/components/ILIAS/WOPI/classes/Handler/WOPIUnknownStakeholder.php b/components/ILIAS/WOPI/classes/Handler/WOPIUnknownStakeholder.php index 06191e1374eb..acef7841eb1f 100644 --- a/components/ILIAS/WOPI/classes/Handler/WOPIUnknownStakeholder.php +++ b/components/ILIAS/WOPI/classes/Handler/WOPIUnknownStakeholder.php @@ -24,10 +24,6 @@ class WOPIUnknownStakeholder extends AbstractResourceStakeholder { - public function __construct(protected int $owner = 6) - { - } - public function getId(): string { return 'wopi_unknown'; @@ -35,7 +31,7 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return $this->owner; + return $this->default_owner; } } From 255794aad3eb6062854ce826a4152c2e2cd431ea Mon Sep 17 00:00:00 2001 From: Fabian Schmid Date: Mon, 5 Aug 2024 13:20:52 +0200 Subject: [PATCH 058/115] [FIX] missing declare statement --- components/ILIAS/File/classes/Preview/SettingsFactory.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/ILIAS/File/classes/Preview/SettingsFactory.php b/components/ILIAS/File/classes/Preview/SettingsFactory.php index 6d2a4dfe38e8..648d64a6b19e 100644 --- a/components/ILIAS/File/classes/Preview/SettingsFactory.php +++ b/components/ILIAS/File/classes/Preview/SettingsFactory.php @@ -18,6 +18,8 @@ namespace ILIAS\Modules\File\Preview; +use ILIAS\components\File\Preview\Settings; + /** * @author Fabian Schmid */ From b6d3f1c2909733ffdbd700e092ddcd5ab6f247f7 Mon Sep 17 00:00:00 2001 From: Tim Schmitz Date: Fri, 2 Aug 2024 17:14:54 +0200 Subject: [PATCH 059/115] Multilingualism: switch to LOM API --- .../classes/class.ilMultilingualismGUI.php | 11 +++++++++-- .../classes/class.ilMultilingualismTableGUI.php | 11 +++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/components/ILIAS/Multilingualism/classes/class.ilMultilingualismGUI.php b/components/ILIAS/Multilingualism/classes/class.ilMultilingualismGUI.php index 3cf1237a3166..e5880cff56e3 100755 --- a/components/ILIAS/Multilingualism/classes/class.ilMultilingualismGUI.php +++ b/components/ILIAS/Multilingualism/classes/class.ilMultilingualismGUI.php @@ -16,6 +16,8 @@ * *********************************************************************/ +use ILIAS\MetaData\Services\ServicesInterface as LOMServices; + /** * GUI class for object translation handling. * @author Alexander Killing @@ -29,6 +31,7 @@ class ilMultilingualismGUI protected ilGlobalTemplateInterface $tpl; protected ilToolbarGUI $toolbar; protected ilObjUser $user; + protected LOMServices $lom_services; protected ilMultilingualism $obj_trans; protected bool $title_descr_only = true; protected string $start_title = ""; @@ -46,6 +49,7 @@ public function __construct( $this->lng->loadLanguageModule('obj'); $this->ctrl = $DIC->ctrl(); $this->tpl = $DIC->ui()->mainTemplate(); + $this->lom_services = $DIC->learningObjectMetadata(); $this->obj_trans = ilMultilingualism::getInstance($a_obj_id, $a_type); $this->request = new \ILIAS\Multilingualism\StandardGUIRequest( @@ -233,9 +237,13 @@ public function getMultiLangForm(bool $a_add = false): ilPropertyFormGUI $form = new ilPropertyFormGUI(); + $options = []; + foreach ($this->lom_services->dataHelper()->getAllLanguages() as $language) { + $options[$language->value()] = $language->presentableLabel(); + } + // master language if (!$a_add) { - $options = ilMDLanguageItem::_getLanguages(); $si = new ilSelectInputGUI($lng->txt("obj_master_lang"), "master_lang"); $si->setOptions($options); $si->setValue($ilUser->getLanguage()); @@ -244,7 +252,6 @@ public function getMultiLangForm(bool $a_add = false): ilPropertyFormGUI // additional languages if ($a_add) { - $options = ilMDLanguageItem::_getLanguages(); $options = array("" => $lng->txt("please_select")) + $options; $si = new ilSelectInputGUI($lng->txt("obj_additional_langs"), "additional_langs"); $si->setOptions($options); diff --git a/components/ILIAS/Multilingualism/classes/class.ilMultilingualismTableGUI.php b/components/ILIAS/Multilingualism/classes/class.ilMultilingualismTableGUI.php index 2c5be0b5c295..d8ca4a148a12 100755 --- a/components/ILIAS/Multilingualism/classes/class.ilMultilingualismTableGUI.php +++ b/components/ILIAS/Multilingualism/classes/class.ilMultilingualismTableGUI.php @@ -16,6 +16,8 @@ * *********************************************************************/ +use ILIAS\MetaData\Services\ServicesInterface as LOMServices; + /** * TableGUI class for title/description translations * @@ -28,6 +30,7 @@ class ilMultilingualismTableGUI extends ilTable2GUI protected string $base_cmd; protected bool $incl_desc; protected ilAccessHandler $access; + protected LOMServices $lom_services; public function __construct( object $a_parent_obj, @@ -42,6 +45,7 @@ public function __construct( $this->lng = $DIC->language(); $this->access = $DIC->access(); $ilCtrl = $DIC->ctrl(); + $this->lom_services = $DIC->learningObjectMetadata(); parent::__construct($a_parent_obj, $a_parent_cmd); $this->incl_desc = $a_incl_desc; @@ -61,7 +65,7 @@ public function __construct( $this->setEnableHeader(true); $this->setFormAction($ilCtrl->getFormAction($a_parent_obj)); - $this->setRowTemplate("tpl.obj_translation2_row.html", "components/ILIAS/Object"); + $this->setRowTemplate("tpl.obj_translation2_row.html", "components/ILIAS/ILIASObject"); $this->disable("footer"); $this->setEnableTitle(true); @@ -112,7 +116,10 @@ protected function fillRow(array $a_set): void $this->tpl->setVariable("NR", $this->nr); // lang selection - $languages = ilMDLanguageItem::_getLanguages(); + $languages = []; + foreach ($this->lom_services->dataHelper()->getAllLanguages() as $language) { + $languages[$language->value()] = $language->presentableLabel(); + } $this->tpl->setVariable( "LANG_SELECT", ilLegacyFormElementsUtil::formSelect( From 26b9854eb654602955768243494ddfdc27fbdc88 Mon Sep 17 00:00:00 2001 From: Fabian Schmid Date: Mon, 5 Aug 2024 13:41:23 +0200 Subject: [PATCH 060/115] Revert "[IMPROVEMENT] simplified stakeholder default owner" This reverts commit 5d8b490561603711be86eb0d64391f2c8dfef217. --- .../class.ilObjBibliographicStakeholder.php | 9 ++++++- .../class.ilDataCollectionStakeholder.php | 14 +++++++--- ...class.ilExcInstructionFilesStakeholder.php | 15 ++++++++++- .../class.ilExcSampleSolutionStakeholder.php | 12 ++++++++- ...lass.ilExcTutorFeedbackFileStakeholder.php | 13 ++++++++- ...class.ilExcTutorFeedbackZipStakeholder.php | 13 ++++++++- ....ilExcTutorTeamFeedbackFileStakeholder.php | 13 ++++++++- .../Icons/class.ilObjFileIconStakeholder.php | 12 ++++++++- .../classes/class.ilObjFileStakeholder.php | 12 ++++++++- .../class.ilTemporaryStakeholder.php | 27 +++++++------------ .../class.ilForumPostingFileStakeholder.php | 9 +++++++ .../class.ilObjectTileImageStakeholder.php | 16 ++++++++++- ...lIndiviualAssessmentGradingStakeholder.php | 6 ++++- .../class.ilMMStorageStakeholder.php | 9 ++++++- .../class.ilMDCopyrightImageStakeholder.php | 9 ++++++- .../Types/ilOrgUnitTypeStakeholder.php | 6 ++++- .../AbstractResourceStakeholder.php | 22 --------------- ...class.ilSkillProfileStorageStakeHolder.php | 22 ++++++++++++--- .../types/ilStudyProgrammeTypeStakeholder.php | 7 ++++- .../class.assFileUploadStakeholder.php | 13 ++++++++- .../class.ilUserProfilePictureStakeholder.php | 16 ++++++++++- .../Handler/WOPIStakeholderWrapper.php | 4 ++- .../Handler/WOPIUnknownStakeholder.php | 6 ++++- 23 files changed, 221 insertions(+), 64 deletions(-) diff --git a/components/ILIAS/Bibliographic/classes/class.ilObjBibliographicStakeholder.php b/components/ILIAS/Bibliographic/classes/class.ilObjBibliographicStakeholder.php index 6c01fca1af4f..e7e19a9bacd5 100755 --- a/components/ILIAS/Bibliographic/classes/class.ilObjBibliographicStakeholder.php +++ b/components/ILIAS/Bibliographic/classes/class.ilObjBibliographicStakeholder.php @@ -27,6 +27,10 @@ class ilObjBibliographicStakeholder extends AbstractResourceStakeholder { protected ?ilDBInterface $database = null; + public function __construct() + { + } + /** * @inheritDoc */ @@ -35,9 +39,12 @@ public function getId(): string return 'bibl'; } + /** + * @inheritDoc + */ public function getOwnerOfNewResources(): int { - return $this->default_owner; + return 6; } public function getLocationURIForResourceUsage(ResourceIdentification $identification): ?string diff --git a/components/ILIAS/DataCollection/classes/class.ilDataCollectionStakeholder.php b/components/ILIAS/DataCollection/classes/class.ilDataCollectionStakeholder.php index 36a2692c9b8c..822ecdbd83e9 100755 --- a/components/ILIAS/DataCollection/classes/class.ilDataCollectionStakeholder.php +++ b/components/ILIAS/DataCollection/classes/class.ilDataCollectionStakeholder.php @@ -18,10 +18,18 @@ declare(strict_types=1); -use ILIAS\DI\Container; - class ilDataCollectionStakeholder extends \ILIAS\ResourceStorage\Stakeholder\AbstractResourceStakeholder { + private int $owner; + + public function __construct() + { + global $DIC; + $this->owner = $DIC->isDependencyAvailable('user') + ? $DIC->user()->getId() + : (defined('SYSTEM_USER_ID') ? (int) SYSTEM_USER_ID : 6); + } + public function getId(): string { return "dcl_uploads"; @@ -29,6 +37,6 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return $this->default_owner; + return $this->owner; } } diff --git a/components/ILIAS/Exercise/InstructionFile/class.ilExcInstructionFilesStakeholder.php b/components/ILIAS/Exercise/InstructionFile/class.ilExcInstructionFilesStakeholder.php index d5eaf49a4e97..afedbd5f08a8 100755 --- a/components/ILIAS/Exercise/InstructionFile/class.ilExcInstructionFilesStakeholder.php +++ b/components/ILIAS/Exercise/InstructionFile/class.ilExcInstructionFilesStakeholder.php @@ -27,8 +27,21 @@ */ class ilExcInstructionFilesStakeholder extends AbstractResourceStakeholder { + protected int $owner = 6; + private int $current_user; protected ?ilDBInterface $database = null; + /** + * ilObjFileStakeholder constructor. + */ + public function __construct(int $owner = 6) + { + global $DIC; + $this->current_user = (int) ($DIC->isDependencyAvailable('user') + ? $DIC->user()->getId() + : (defined('ANONYMOUS_USER_ID') ? ANONYMOUS_USER_ID : 6)); + $this->owner = $owner; + } public function getId(): string { @@ -37,7 +50,7 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return $this->default_owner; + return $this->owner; } public function canBeAccessedByCurrentUser(ResourceIdentification $identification): bool diff --git a/components/ILIAS/Exercise/SampleSolution/class.ilExcSampleSolutionStakeholder.php b/components/ILIAS/Exercise/SampleSolution/class.ilExcSampleSolutionStakeholder.php index 937c660fb3b0..3e65be82a572 100755 --- a/components/ILIAS/Exercise/SampleSolution/class.ilExcSampleSolutionStakeholder.php +++ b/components/ILIAS/Exercise/SampleSolution/class.ilExcSampleSolutionStakeholder.php @@ -24,8 +24,18 @@ class ilExcSampleSolutionStakeholder extends AbstractResourceStakeholder { + protected int $owner = 6; + private int $current_user; protected ?ilDBInterface $database = null; + public function __construct(int $owner = 6) + { + global $DIC; + $this->current_user = (int) ($DIC->isDependencyAvailable('user') + ? $DIC->user()->getId() + : (defined('ANONYMOUS_USER_ID') ? ANONYMOUS_USER_ID : 6)); + $this->owner = $owner; + } public function getId(): string { @@ -34,7 +44,7 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return $this->default_owner; + return $this->owner; } public function canBeAccessedByCurrentUser(ResourceIdentification $identification): bool diff --git a/components/ILIAS/Exercise/TutorFeedbackFile/class.ilExcTutorFeedbackFileStakeholder.php b/components/ILIAS/Exercise/TutorFeedbackFile/class.ilExcTutorFeedbackFileStakeholder.php index abb339fc5b08..6d5c31426d47 100755 --- a/components/ILIAS/Exercise/TutorFeedbackFile/class.ilExcTutorFeedbackFileStakeholder.php +++ b/components/ILIAS/Exercise/TutorFeedbackFile/class.ilExcTutorFeedbackFileStakeholder.php @@ -26,8 +26,19 @@ class ilExcTutorFeedbackFileStakeholder extends AbstractResourceStakeholder { + protected int $owner = 6; + private int $current_user; protected ?ilDBInterface $database = null; + public function __construct(int $owner = 6) + { + global $DIC; + $this->current_user = (int) ($DIC->isDependencyAvailable('user') + ? $DIC->user()->getId() + : (defined('ANONYMOUS_USER_ID') ? ANONYMOUS_USER_ID : 6)); + $this->owner = $owner; + } + public function getId(): string { return 'exc_tutor_feedback'; @@ -35,7 +46,7 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return $this->default_owner; + return $this->owner; } public function canBeAccessedByCurrentUser(ResourceIdentification $identification): bool diff --git a/components/ILIAS/Exercise/TutorFeedbackFile/class.ilExcTutorFeedbackZipStakeholder.php b/components/ILIAS/Exercise/TutorFeedbackFile/class.ilExcTutorFeedbackZipStakeholder.php index 86e3121205a1..5f3b056629ac 100755 --- a/components/ILIAS/Exercise/TutorFeedbackFile/class.ilExcTutorFeedbackZipStakeholder.php +++ b/components/ILIAS/Exercise/TutorFeedbackFile/class.ilExcTutorFeedbackZipStakeholder.php @@ -26,8 +26,19 @@ class ilExcTutorFeedbackZipStakeholder extends AbstractResourceStakeholder { + protected int $owner = 6; + private int $current_user; protected ?ilDBInterface $database = null; + public function __construct(int $owner = 6) + { + global $DIC; + $this->current_user = (int) ($DIC->isDependencyAvailable('user') + ? $DIC->user()->getId() + : (defined('ANONYMOUS_USER_ID') ? ANONYMOUS_USER_ID : 6)); + $this->owner = $owner; + } + public function getId(): string { return 'exc_tutor_feedback_zip'; @@ -35,7 +46,7 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return $this->default_owner; + return $this->owner; } public function canBeAccessedByCurrentUser(ResourceIdentification $identification): bool diff --git a/components/ILIAS/Exercise/TutorFeedbackFile/class.ilExcTutorTeamFeedbackFileStakeholder.php b/components/ILIAS/Exercise/TutorFeedbackFile/class.ilExcTutorTeamFeedbackFileStakeholder.php index ac2cfd967653..162991d984f1 100755 --- a/components/ILIAS/Exercise/TutorFeedbackFile/class.ilExcTutorTeamFeedbackFileStakeholder.php +++ b/components/ILIAS/Exercise/TutorFeedbackFile/class.ilExcTutorTeamFeedbackFileStakeholder.php @@ -26,8 +26,19 @@ class ilExcTutorTeamFeedbackFileStakeholder extends AbstractResourceStakeholder { + protected int $owner = 6; + private int $current_user; protected ?ilDBInterface $database = null; + public function __construct(int $owner = 6) + { + global $DIC; + $this->current_user = (int) ($DIC->isDependencyAvailable('user') + ? $DIC->user()->getId() + : (defined('ANONYMOUS_USER_ID') ? ANONYMOUS_USER_ID : 6)); + $this->owner = $owner; + } + public function getId(): string { return 'exc_tutor_team_feedback'; @@ -35,7 +46,7 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return $this->default_owner; + return $this->owner; } public function canBeAccessedByCurrentUser(ResourceIdentification $identification): bool diff --git a/components/ILIAS/File/classes/Icons/class.ilObjFileIconStakeholder.php b/components/ILIAS/File/classes/Icons/class.ilObjFileIconStakeholder.php index 189c3b3731ea..4bc20a3b75e1 100755 --- a/components/ILIAS/File/classes/Icons/class.ilObjFileIconStakeholder.php +++ b/components/ILIAS/File/classes/Icons/class.ilObjFileIconStakeholder.php @@ -27,6 +27,16 @@ */ class ilObjFileIconStakeholder extends AbstractResourceStakeholder { + /** + * ilObjFileIconStakeholder constructor. + */ + public function __construct(protected int $owner = 6) + { + } + + /** + * @inheritDoc + */ public function getId(): string { return 'file_icon'; @@ -37,6 +47,6 @@ public function getId(): string */ public function getOwnerOfNewResources(): int { - return $this->default_owner; + return $this->owner; } } diff --git a/components/ILIAS/File/classes/class.ilObjFileStakeholder.php b/components/ILIAS/File/classes/class.ilObjFileStakeholder.php index f2ad8f996a34..7db2fde73b27 100755 --- a/components/ILIAS/File/classes/class.ilObjFileStakeholder.php +++ b/components/ILIAS/File/classes/class.ilObjFileStakeholder.php @@ -25,8 +25,18 @@ */ class ilObjFileStakeholder extends AbstractResourceStakeholder { + private int $current_user; protected ?ilDBInterface $database = null; + /** + * ilObjFileStakeholder constructor. + */ + public function __construct(protected int $owner = 6) + { + global $DIC; + $this->current_user = (int) ($DIC->isDependencyAvailable('user') ? $DIC->user()->getId() : 13); + } + /** * @inheritDoc */ @@ -37,7 +47,7 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return $this->default_owner; + return $this->owner; } public function canBeAccessedByCurrentUser(ResourceIdentification $identification): bool diff --git a/components/ILIAS/FileServices/classes/UploadService/class.ilTemporaryStakeholder.php b/components/ILIAS/FileServices/classes/UploadService/class.ilTemporaryStakeholder.php index 11622d1d492b..99387e520f3e 100755 --- a/components/ILIAS/FileServices/classes/UploadService/class.ilTemporaryStakeholder.php +++ b/components/ILIAS/FileServices/classes/UploadService/class.ilTemporaryStakeholder.php @@ -1,21 +1,5 @@ owner_id = $DIC->user()->getId(); + } + public function getId(): string { return 'irss_temp'; @@ -39,6 +32,6 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return $this->default_owner; + return $this->owner_id; } } diff --git a/components/ILIAS/Forum/classes/class.ilForumPostingFileStakeholder.php b/components/ILIAS/Forum/classes/class.ilForumPostingFileStakeholder.php index 8aca0ae9c58f..be2e62a0ec09 100755 --- a/components/ILIAS/Forum/classes/class.ilForumPostingFileStakeholder.php +++ b/components/ILIAS/Forum/classes/class.ilForumPostingFileStakeholder.php @@ -25,6 +25,15 @@ */ class ilForumPostingFileStakeholder extends AbstractResourceStakeholder { + private int $default_owner; + + public function __construct() + { + global $DIC; + $this->default_owner = $DIC->isDependencyAvailable('user') ? $DIC->user()->getId() : 6; + } + + public function getId(): string { return 'frm_post'; diff --git a/components/ILIAS/ILIASObject/classes/Properties/CoreProperties/TileImage/class.ilObjectTileImageStakeholder.php b/components/ILIAS/ILIASObject/classes/Properties/CoreProperties/TileImage/class.ilObjectTileImageStakeholder.php index 351f3849e237..e317ee6fbed4 100755 --- a/components/ILIAS/ILIASObject/classes/Properties/CoreProperties/TileImage/class.ilObjectTileImageStakeholder.php +++ b/components/ILIAS/ILIASObject/classes/Properties/CoreProperties/TileImage/class.ilObjectTileImageStakeholder.php @@ -21,13 +21,27 @@ namespace ILIAS\Object\Properties\CoreProperties\TileImage; use ILIAS\ResourceStorage\Stakeholder\AbstractResourceStakeholder; -use ILIAS\DI\Container; /** * @author Fabian Schmid */ class ilObjectTileImageStakeholder extends AbstractResourceStakeholder { + private int $default_owner; + + public function __construct() + { + global $DIC; + $this->default_owner = $DIC->isDependencyAvailable('user') + ? $DIC->user()->getId() + : (defined('SYSTEM_USER_ID') ? (int) SYSTEM_USER_ID : 6); + } + + public function setOwner(int $user_id_of_owner): void + { + $this->default_owner = $user_id_of_owner; + } + public function getId(): string { return 'object_tile_image'; diff --git a/components/ILIAS/IndividualAssessment/classes/FileStorage/class.ilIndiviualAssessmentGradingStakeholder.php b/components/ILIAS/IndividualAssessment/classes/FileStorage/class.ilIndiviualAssessmentGradingStakeholder.php index 9349f60867e3..c646bc4a0b16 100644 --- a/components/ILIAS/IndividualAssessment/classes/FileStorage/class.ilIndiviualAssessmentGradingStakeholder.php +++ b/components/ILIAS/IndividualAssessment/classes/FileStorage/class.ilIndiviualAssessmentGradingStakeholder.php @@ -24,6 +24,10 @@ class ilIndividualAssessmentGradingStakeholder extends AbstractResourceStakehold { private const ID = 'IASSGrading'; + public function __construct( + protected int $owner = 6 + ) { + } public function getId(): string { @@ -32,6 +36,6 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return $this->default_owner; + return $this->owner; } } diff --git a/components/ILIAS/MainMenu/classes/Administration/class.ilMMStorageStakeholder.php b/components/ILIAS/MainMenu/classes/Administration/class.ilMMStorageStakeholder.php index 476b4467c60b..c3bb1a6a53c9 100755 --- a/components/ILIAS/MainMenu/classes/Administration/class.ilMMStorageStakeholder.php +++ b/components/ILIAS/MainMenu/classes/Administration/class.ilMMStorageStakeholder.php @@ -26,6 +26,13 @@ */ class ilMMStorageStakeholder extends AbstractResourceStakeholder { + public function __construct() + { + } + + /** + * @inheritDoc + */ public function getId(): string { return 'mme'; @@ -33,6 +40,6 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return $this->default_owner; + return 6; } } diff --git a/components/ILIAS/MetaData/classes/Settings/Copyright/class.ilMDCopyrightImageStakeholder.php b/components/ILIAS/MetaData/classes/Settings/Copyright/class.ilMDCopyrightImageStakeholder.php index ee301937a813..d54604cde35b 100755 --- a/components/ILIAS/MetaData/classes/Settings/Copyright/class.ilMDCopyrightImageStakeholder.php +++ b/components/ILIAS/MetaData/classes/Settings/Copyright/class.ilMDCopyrightImageStakeholder.php @@ -22,6 +22,13 @@ class ilMDCopyrightImageStakeholder extends AbstractResourceStakeholder { + protected int $owner = 6; + + public function __construct(int $owner = 6) + { + $this->owner = $owner; + } + public function getId(): string { return 'copyright_image'; @@ -29,6 +36,6 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return $this->default_owner; + return $this->owner; } } diff --git a/components/ILIAS/OrgUnit/classes/Types/ilOrgUnitTypeStakeholder.php b/components/ILIAS/OrgUnit/classes/Types/ilOrgUnitTypeStakeholder.php index 19c26e9db991..0329cddbac33 100644 --- a/components/ILIAS/OrgUnit/classes/Types/ilOrgUnitTypeStakeholder.php +++ b/components/ILIAS/OrgUnit/classes/Types/ilOrgUnitTypeStakeholder.php @@ -25,6 +25,10 @@ class ilOrgUnitTypeStakeholder extends AbstractResourceStakeholder { private const ID = 'PRGType'; + public function __construct( + protected int $owner = 6 + ) { + } public function getId(): string { @@ -33,7 +37,7 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return $this->default_owner; + return $this->owner; } public function getPreprocessors(): array diff --git a/components/ILIAS/ResourceStorage/src/Stakeholder/AbstractResourceStakeholder.php b/components/ILIAS/ResourceStorage/src/Stakeholder/AbstractResourceStakeholder.php index 4ab65ad232b8..00919c6adec8 100755 --- a/components/ILIAS/ResourceStorage/src/Stakeholder/AbstractResourceStakeholder.php +++ b/components/ILIAS/ResourceStorage/src/Stakeholder/AbstractResourceStakeholder.php @@ -21,7 +21,6 @@ namespace ILIAS\ResourceStorage\Stakeholder; use ILIAS\ResourceStorage\Identification\ResourceIdentification; -use ILIAS\DI\Container; /** * @author Fabian Schmid @@ -30,27 +29,6 @@ abstract class AbstractResourceStakeholder implements ResourceStakeholder { private string $provider_name_cache = ''; - protected int $default_owner; - protected int $current_user; - - public function __construct(?int $user_id_of_owner = null) - { - global $DIC; - - if ($user_id_of_owner === null) { - $user_id_of_owner = $DIC instanceof Container && $DIC->isDependencyAvailable('user') - ? $DIC->user()->getId() - : (defined('SYSTEM_USER_ID') ? (int) SYSTEM_USER_ID : 6); - } - - $this->default_owner = $this->current_user = $user_id_of_owner; - } - - public function setOwner(int $user_id_of_owner): void - { - $this->default_owner = $user_id_of_owner; - } - public function getFullyQualifiedClassName(): string { return static::class; diff --git a/components/ILIAS/Skill/Profile/class.ilSkillProfileStorageStakeHolder.php b/components/ILIAS/Skill/Profile/class.ilSkillProfileStorageStakeHolder.php index 1b676a44be12..806e8aa723d0 100755 --- a/components/ILIAS/Skill/Profile/class.ilSkillProfileStorageStakeHolder.php +++ b/components/ILIAS/Skill/Profile/class.ilSkillProfileStorageStakeHolder.php @@ -1,5 +1,7 @@ owner = $owner; + } + + /** + * @inheritDoc + */ public function getId(): string { return 'skl_prof'; } + /** + * @inheritDoc + */ public function getOwnerOfNewResources(): int { - return $this->default_owner; + return $this->owner; } } diff --git a/components/ILIAS/StudyProgramme/classes/types/ilStudyProgrammeTypeStakeholder.php b/components/ILIAS/StudyProgramme/classes/types/ilStudyProgrammeTypeStakeholder.php index cee66dd9ead7..4bec6aa35b16 100644 --- a/components/ILIAS/StudyProgramme/classes/types/ilStudyProgrammeTypeStakeholder.php +++ b/components/ILIAS/StudyProgramme/classes/types/ilStudyProgrammeTypeStakeholder.php @@ -25,6 +25,11 @@ class ilStudyProgrammeTypeStakeholder extends AbstractResourceStakeholder { private const ID = 'PRGType'; + public function __construct( + protected int $owner = 6 + ) { + } + public function getId(): string { return self::ID; @@ -32,7 +37,7 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return $this->default_owner; + return $this->owner; } public function getPreprocessors(): array diff --git a/components/ILIAS/TestQuestionPool/classes/class.assFileUploadStakeholder.php b/components/ILIAS/TestQuestionPool/classes/class.assFileUploadStakeholder.php index a634cc4ea909..aeb962772214 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assFileUploadStakeholder.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assFileUploadStakeholder.php @@ -23,6 +23,17 @@ */ class assFileUploadStakeholder extends AbstractResourceStakeholder { + private int $current_user; + + public function __construct() + { + global $DIC; + $anonymous = defined( + 'ANONYMOUS_USER_ID' + ) ? ANONYMOUS_USER_ID : 13; + $this->current_user = (int) ($DIC->isDependencyAvailable('user') ? $DIC->user()->getId() : $anonymous); + } + public function getId(): string { return 'qpl_file_upload'; @@ -30,7 +41,7 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return $this->default_owner; + return $this->current_user; } } diff --git a/components/ILIAS/User/classes/Avatar/class.ilUserProfilePictureStakeholder.php b/components/ILIAS/User/classes/Avatar/class.ilUserProfilePictureStakeholder.php index b6743f6f4147..088d113d9820 100755 --- a/components/ILIAS/User/classes/Avatar/class.ilUserProfilePictureStakeholder.php +++ b/components/ILIAS/User/classes/Avatar/class.ilUserProfilePictureStakeholder.php @@ -19,13 +19,27 @@ declare(strict_types=1); use ILIAS\ResourceStorage\Stakeholder\AbstractResourceStakeholder; -use ILIAS\DI\Container; /** * @author Fabian Schmid */ class ilUserProfilePictureStakeholder extends AbstractResourceStakeholder { + private int $default_owner; + + public function __construct() + { + global $DIC; + $this->default_owner = $DIC->isDependencyAvailable('user') + ? $DIC->user()->getId() + : (defined('SYSTEM_USER_ID') ? (int) SYSTEM_USER_ID : 6); + } + + public function setOwner(int $user_id_of_owner): void + { + $this->default_owner = $user_id_of_owner; + } + public function getId(): string { return 'usr_picture'; diff --git a/components/ILIAS/WOPI/classes/Handler/WOPIStakeholderWrapper.php b/components/ILIAS/WOPI/classes/Handler/WOPIStakeholderWrapper.php index 27ca74e57ff3..9731ad553a34 100644 --- a/components/ILIAS/WOPI/classes/Handler/WOPIStakeholderWrapper.php +++ b/components/ILIAS/WOPI/classes/Handler/WOPIStakeholderWrapper.php @@ -29,11 +29,13 @@ class WOPIStakeholderWrapper extends AbstractResourceStakeholder private ?int $user_id = null; private ?ResourceStakeholder $stakeholder = null; + public function __construct() + { + } public function init(ResourceStakeholder $stakeholder, int $user_id): void { $this->user_id = $user_id; - $this->setOwner($user_id); $this->stakeholder = $stakeholder; } diff --git a/components/ILIAS/WOPI/classes/Handler/WOPIUnknownStakeholder.php b/components/ILIAS/WOPI/classes/Handler/WOPIUnknownStakeholder.php index acef7841eb1f..06191e1374eb 100644 --- a/components/ILIAS/WOPI/classes/Handler/WOPIUnknownStakeholder.php +++ b/components/ILIAS/WOPI/classes/Handler/WOPIUnknownStakeholder.php @@ -24,6 +24,10 @@ class WOPIUnknownStakeholder extends AbstractResourceStakeholder { + public function __construct(protected int $owner = 6) + { + } + public function getId(): string { return 'wopi_unknown'; @@ -31,7 +35,7 @@ public function getId(): string public function getOwnerOfNewResources(): int { - return $this->default_owner; + return $this->owner; } } From ee2ee4335e4279578222b64cc42773127b94126c Mon Sep 17 00:00:00 2001 From: mjansen Date: Mon, 5 Aug 2024 13:56:24 +0200 Subject: [PATCH 061/115] Chatroom: Fix blank page on installation status page See: https://mantis.ilias.de/view.php?id=41839 (cherry picked from commit f68d9c9030099f0ae523da9857ec7386de3ed6ca) --- ...ss.ilChatroomMetricsCollectedObjective.php | 113 +++++++++--------- 1 file changed, 58 insertions(+), 55 deletions(-) diff --git a/components/ILIAS/Chatroom/classes/Setup/class.ilChatroomMetricsCollectedObjective.php b/components/ILIAS/Chatroom/classes/Setup/class.ilChatroomMetricsCollectedObjective.php index 6b4398e8b9fa..bd08849f5ce6 100755 --- a/components/ILIAS/Chatroom/classes/Setup/class.ilChatroomMetricsCollectedObjective.php +++ b/components/ILIAS/Chatroom/classes/Setup/class.ilChatroomMetricsCollectedObjective.php @@ -28,7 +28,7 @@ protected function getTentativePreconditions(Setup\Environment $environment): ar return [ new ilIniFilesLoadedObjective(), new ilDatabaseInitializedObjective(), - new ilFileSystemComponentDataDirectoryCreatedObjective("chatroom") + new ilFileSystemComponentDataDirectoryCreatedObjective('chatroom') ]; } @@ -40,10 +40,10 @@ protected function collectFrom(Setup\Environment $environment, Setup\Metrics\Sto // sub components of the various readers to run. This is a memento to the // fact, that dependency injection is something we want. Currently, every // component could just service locate the whole world via the global $DIC. - $DIC = $GLOBALS["DIC"]; - $GLOBALS["DIC"] = new DI\Container(); - $GLOBALS["DIC"]["ilDB"] = $db; - $GLOBALS["DIC"]["ilBench"] = null; + $DIC = $GLOBALS['DIC']; + $GLOBALS['DIC'] = new DI\Container(); + $GLOBALS['DIC']['ilDB'] = $db; + $GLOBALS['DIC']['ilBench'] = null; $chatAdministrations = ilObject::_getObjectsByType('chta'); $chatAdministration = current($chatAdministrations); @@ -53,123 +53,123 @@ protected function collectFrom(Setup\Environment $environment, Setup\Metrics\Sto if (count($settings) > 0) { $storage->storeConfigText( - "address", - $settings['address'] ?? "", - "IP-Address/FQN of Chat Server." + 'address', + $settings['address'] ?? '', + 'IP-Address/FQN of Chat Server.' ); $storage->storeConfigText( - "port", - (string) ($settings['port'] ?? ""), - "Port of the chat server." + 'port', + (string) ($settings['port'] ?? ''), + 'Port of the chat server.' ); $storage->storeConfigText( - "sub_directory", - $settings['sub_directory'] ?? "", - "http(s)://[IP/Domain]/[SUB_DIRECTORY]" + 'sub_directory', + $settings['sub_directory'] ?? '', + 'http(s)://[IP/Domain]/[SUB_DIRECTORY]' ); $storage->storeConfigText( - "protocol", - $settings['protocol'] ?? "", - "Protocol used for connection (http/https)." + 'protocol', + $settings['protocol'] ?? '', + 'Protocol used for connection (http/https).' ); - if ($settings['protocol'] === 'https') { + if (isset($settings['protocol']) && $settings['protocol'] === 'https') { $cert = new Setup\Metrics\Metric( Setup\Metrics\Metric::STABILITY_CONFIG, Setup\Metrics\Metric::TYPE_TEXT, - $settings['cert'] ?? "" + $settings['cert'] ?? '' ); $key = new Setup\Metrics\Metric( Setup\Metrics\Metric::STABILITY_CONFIG, Setup\Metrics\Metric::TYPE_TEXT, - $settings['key'] ?? "" + $settings['key'] ?? '' ); $dhparam = new Setup\Metrics\Metric( Setup\Metrics\Metric::STABILITY_CONFIG, Setup\Metrics\Metric::TYPE_TEXT, - $settings['dhparam'] ?? "" + $settings['dhparam'] ?? '' ); $https = new Setup\Metrics\Metric( Setup\Metrics\Metric::STABILITY_CONFIG, Setup\Metrics\Metric::TYPE_COLLECTION, [ - "cert" => $cert, - "key" => $key, - "dhparam" => $dhparam, + 'cert' => $cert, + 'key' => $key, + 'dhparam' => $dhparam, ], - "Holds parameters for https." + 'Holds parameters for https.' ); - $storage->store("https", $https); + $storage->store('https', $https); } $storage->storeConfigText( - "log", + 'log', (string) ($settings['log'] ?? ''), "Absolute server path to the chat server's log file." ); $storage->storeConfigText( - "log_level", - $settings['log_level'] ?? "", - "Possible values are emerg, alert, crit error, warning, notice, info, debug, silly." + 'log_level', + $settings['log_level'] ?? '', + 'Possible values are emerg, alert, crit error, warning, notice, info, debug, silly.' ); $storage->storeConfigText( - "error_log", - $settings['error_log'] ?? "", + 'error_log', + $settings['error_log'] ?? '', "Absolute server path to the chat server's error log file." ); - if ($settings['ilias_proxy']) { + if (isset($settings['ilias_proxy']) && $settings['ilias_proxy']) { $ilias_url = new Setup\Metrics\Metric( Setup\Metrics\Metric::STABILITY_CONFIG, Setup\Metrics\Metric::TYPE_TEXT, - $settings['ilias_url'] ?? "" + $settings['ilias_url'] ?? '' ); $ilias_proxy = new Setup\Metrics\Metric( Setup\Metrics\Metric::STABILITY_CONFIG, Setup\Metrics\Metric::TYPE_COLLECTION, [ - "ilias_url" => $ilias_url + 'ilias_url' => $ilias_url ], - "Holds proxy url if ILIAS proxy is enabled." + 'Holds proxy url if ILIAS proxy is enabled.' ); - $storage->store("ilias_proxy", $ilias_proxy); + $storage->store('ilias_proxy', $ilias_proxy); } else { $storage->storeConfigBool( - "ilias_proxy", + 'ilias_proxy', false, - "Holds proxy url if ILIAS proxy is enabled." + 'Holds proxy url if ILIAS proxy is enabled.' ); } - if ($settings['client_proxy']) { + if (isset($settings['client_proxy']) && $settings['client_proxy']) { $client_url = new Setup\Metrics\Metric( Setup\Metrics\Metric::STABILITY_CONFIG, Setup\Metrics\Metric::TYPE_TEXT, - $settings['client_url'] ?? "" + $settings['client_url'] ?? '' ); $client_proxy = new Setup\Metrics\Metric( Setup\Metrics\Metric::STABILITY_CONFIG, Setup\Metrics\Metric::TYPE_COLLECTION, [ - "client_url" => $client_url + 'client_url' => $client_url ], - "Holds proxy url if client proxy is enabled." + 'Holds proxy url if client proxy is enabled.' ); - $storage->store("client_proxy", $client_proxy); + $storage->store('client_proxy', $client_proxy); } else { $storage->storeConfigBool( - "client_proxy", + 'client_proxy', false, - "Holds proxy url if client proxy is enabled." + 'Holds proxy url if client proxy is enabled.' ); } - if ($settings['deletion_mode']) { + if (isset($settings['deletion_mode']) && $settings['deletion_mode']) { $deletion_unit = new Setup\Metrics\Metric( Setup\Metrics\Metric::STABILITY_CONFIG, Setup\Metrics\Metric::TYPE_TEXT, - $settings['deletion_unit'] ?? "" + $settings['deletion_unit'] ?? '' ); $deletion_value = new Setup\Metrics\Metric( Setup\Metrics\Metric::STABILITY_CONFIG, @@ -179,22 +179,25 @@ protected function collectFrom(Setup\Environment $environment, Setup\Metrics\Sto $deletion_time = new Setup\Metrics\Metric( Setup\Metrics\Metric::STABILITY_CONFIG, Setup\Metrics\Metric::TYPE_TEXT, - $settings['deletion_time'] ?? "" + $settings['deletion_time'] ?? '' ); $deletion_mode = new Setup\Metrics\Metric( Setup\Metrics\Metric::STABILITY_CONFIG, Setup\Metrics\Metric::TYPE_COLLECTION, [ - "deletion_unit" => $deletion_unit, - "deletion_value" => $deletion_value, - "deletion_time" => $deletion_time, + 'deletion_unit' => $deletion_unit, + 'deletion_value' => $deletion_value, + 'deletion_time' => $deletion_time, ], - "Holds information about deletion process." + 'Holds information about deletion process.' + ); + $storage->store( + 'deletion_mode', + $deletion_mode ); - $storage->store("deletion_mode", $deletion_mode); } } - $GLOBALS["DIC"] = $DIC; + $GLOBALS['DIC'] = $DIC; } } From bf6e26531b869ea3f02e8b22523f0a561acc2c68 Mon Sep 17 00:00:00 2001 From: Marvin Beym <79150442+mBeym@users.noreply.github.com> Date: Mon, 5 Aug 2024 14:14:25 +0200 Subject: [PATCH 062/115] =?UTF-8?q?Abandon=20Session=20Mode=20=C2=ABLoad-D?= =?UTF-8?q?ependent=20Session=20Settings=C2=BB=20(#7525)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Feature Wiki: https://docu.ilias.de/goto_docu_wiki_wpage_8263_1357.html --- ...ependantSessionDatabaseUpdateObjective.php | 70 +++++++ .../classes/class.ilSession.php | 73 +------ .../classes/class.ilSessionControl.php | 187 +----------------- .../classes/class.ilSessionReminder.php | 13 +- .../classes/class.ilSessionStatistics.php | 111 +---------- .../classes/class.ilSessionStatisticsGUI.php | 54 +---- components/ILIAS/Authentication/service.xml | 1 - .../tpl.session_statistics_center.html | 2 - .../default/tpl.session_statistics_left.html | 10 - .../Authentication/tests/ilSessionTest.php | 4 - .../ILIAS/LDAP/tests/ilLDAPServerTest.php | 6 - .../classes/class.ilSCORM13PlayerGUI.php | 2 +- .../SCORM/class.ilObjSCORMInitData.php | 2 +- .../User/classes/class.ilObjUserFolderGUI.php | 159 ++------------- lang/ilias_de.lang | 19 +- lang/ilias_en.lang | 17 -- 16 files changed, 95 insertions(+), 635 deletions(-) create mode 100644 components/ILIAS/Authentication/classes/Setup/AbandonLoadDependantSessionDatabaseUpdateObjective.php diff --git a/components/ILIAS/Authentication/classes/Setup/AbandonLoadDependantSessionDatabaseUpdateObjective.php b/components/ILIAS/Authentication/classes/Setup/AbandonLoadDependantSessionDatabaseUpdateObjective.php new file mode 100644 index 000000000000..68bee1e819df --- /dev/null +++ b/components/ILIAS/Authentication/classes/Setup/AbandonLoadDependantSessionDatabaseUpdateObjective.php @@ -0,0 +1,70 @@ +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/class.ilSession.php b/components/ILIAS/Authentication/classes/class.ilSession.php index ac280f7b422d..44af1fde53d1 100755 --- a/components/ILIAS/Authentication/classes/class.ilSession.php +++ b/components/ILIAS/Authentication/classes/class.ilSession.php @@ -25,24 +25,6 @@ */ class ilSession { - /** - * - * Constant for fixed dession handling - * - * @var integer - * - */ - public const SESSION_HANDLING_FIXED = 0; - - /** - * - * Constant for load dependend session handling - * - * @var integer - * - */ - public const SESSION_HANDLING_LOAD_DEPENDENT = 1; - /** * Constant for reason of session destroy * @@ -50,9 +32,6 @@ class ilSession */ public const SESSION_CLOSE_USER = 1; // manual logout public const SESSION_CLOSE_EXPIRE = 2; // has expired - public const SESSION_CLOSE_FIRST = 3; // kicked by session control (first abidencer) - public const SESSION_CLOSE_IDLE = 4; // kickey by session control (ilde time) - public const SESSION_CLOSE_LIMIT = 5; // kicked by session control (limit reached) 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 @@ -376,73 +355,31 @@ public static function _duplicate(string $a_session_id): string } /** - * * Returns the expiration timestamp in seconds - * - * @param boolean If passed, the value for fixed session is returned - * @return integer The expiration timestamp in seconds - * @static - * */ - public static function getExpireValue(bool $fixedMode = false): int + public static function getExpireValue(): int { - global $DIC; - - if ($fixedMode) { - // fixed session - return time() + self::getIdleValue($fixedMode); - } - - /** @var ilSetting $ilSetting */ - $ilSetting = $DIC['ilSetting']; - if ($ilSetting->get('session_handling_type', (string) self::SESSION_HANDLING_FIXED) === (string) self::SESSION_HANDLING_FIXED) { - return time() + self::getIdleValue($fixedMode); - } - - if ($ilSetting->get('session_handling_type', (string) self::SESSION_HANDLING_FIXED) === (string) self::SESSION_HANDLING_LOAD_DEPENDENT) { - // load dependent session settings - $max_idle = (int) ($ilSetting->get('session_max_idle') ?? ilSessionControl::DEFAULT_MAX_IDLE); - return time() + $max_idle * 60; - } - return time() + ilSessionControl::DEFAULT_MAX_IDLE * 60; + return time() + self::getIdleValue(); } /** - * * Returns the idle time in seconds - * - * @param boolean If passed, the value for fixed session is returned - * @return integer The idle time in seconds */ - public static function getIdleValue(bool $fixedMode = false): int + public static function getIdleValue(): int { global $DIC; - $ilSetting = $DIC['ilSetting']; $ilClientIniFile = $DIC['ilClientIniFile']; - if ($fixedMode || $ilSetting->get('session_handling_type', (string) self::SESSION_HANDLING_FIXED) === (string) self::SESSION_HANDLING_FIXED) { - // fixed session - return (int) $ilClientIniFile->readVariable('session', 'expire'); - } - - if ($ilSetting->get('session_handling_type', (string) self::SESSION_HANDLING_FIXED) === (string) self::SESSION_HANDLING_LOAD_DEPENDENT) { - // load dependent session settings - return ((int) $ilSetting->get('session_max_idle', (string) (ilSessionControl::DEFAULT_MAX_IDLE))) * 60; - } - return ilSessionControl::DEFAULT_MAX_IDLE * 60; + return (int) $ilClientIniFile->readVariable('session', 'expire'); } /** - * * Returns the session expiration value - * - * @return integer The expiration value in seconds - * */ public static function getSessionExpireValue(): int { - return self::getIdleValue(true); + return self::getIdleValue(); } /** diff --git a/components/ILIAS/Authentication/classes/class.ilSessionControl.php b/components/ILIAS/Authentication/classes/class.ilSessionControl.php index 4610d869bc27..812a88850c7b 100755 --- a/components/ILIAS/Authentication/classes/class.ilSessionControl.php +++ b/components/ILIAS/Authentication/classes/class.ilSessionControl.php @@ -27,10 +27,7 @@ class ilSessionControl * default value for settings that have not * been defined in setup or administration yet */ - public const DEFAULT_MAX_COUNT = 0; public const DEFAULT_MIN_IDLE = 15; - public const DEFAULT_MAX_IDLE = 30; - public const DEFAULT_MAX_IDLE_AFTER_FIRST_REQUEST = 1; public const DEFAULT_ALLOW_CLIENT_MAINTENANCE = 1; /** @@ -39,12 +36,7 @@ class ilSessionControl * @var array $setting_fields */ private static array $setting_fields = array( - 'session_max_count', - 'session_min_idle', - 'session_max_idle', - 'session_max_idle_after_first_request', 'session_allow_client_maintenance', - 'session_handling_type' ); /** @@ -117,17 +109,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 . ")"); - // do not handle login event in fixed duration mode - if ((int) $ilSetting->get('session_handling_type', (string) ilSession::SESSION_HANDLING_FIXED) !== ilSession::SESSION_HANDLING_LOAD_DEPENDENT) { - return true; - } - - if (in_array($type, self::$session_types_controlled, true)) { - //TODO rework this, as it did return value of a void method call - self::checkCurrentSessionIsAllowed($auth_session, $user_id); - return true; - } - return false; + return true; } /** @@ -135,100 +117,6 @@ public static function handleLoginEvent(string $a_login, ilAuthSession $auth_ses */ public static function handleLogoutEvent(): void { - global $DIC; - - $ilSetting = $DIC['ilSetting']; - - // do not handle logout event in fixed duration mode - if ((int) $ilSetting->get('session_handling_type', '0') !== 1) { - return; - } - - ilSession::set('SessionType', self::SESSION_TYPE_UNKNOWN); - self::debug(__METHOD__ . " --> reset sessions type to (" . ilSession::get('SessionType') . ")"); - - // session_destroy() is called in auth, so raw data will be updated - - self::removeSessionCookie(); - } - - /** - * checks wether the current session exhaust the limit of sessions - * when limit is reached it deletes "firstRequestAbidencer" and checks again - * when limit is still reached it deletes "oneMinIdleSession" and checks again - * when limit is still reached the current session will be logged out - */ - private static function checkCurrentSessionIsAllowed(ilAuthSession $auth, int $a_user_id): void - { - global $DIC; - - $ilSetting = $DIC['ilSetting']; - - $max_sessions = (int) $ilSetting->get('session_max_count', (string) self::DEFAULT_MAX_COUNT); - - if ($max_sessions > 0) { - // get total number of sessions - $num_sessions = self::getExistingSessionCount(self::$session_types_controlled); - - self::debug(__METHOD__ . "--> total existing sessions (" . $num_sessions . ")"); - - if (($num_sessions + 1) > $max_sessions) { - self::debug(__METHOD__ . ' --> limit for session pool reached, but try kicking some first request abidencer'); - - self::kickFirstRequestAbidencer(self::$session_types_controlled); - - // get total number of sessions again - $num_sessions = self::getExistingSessionCount(self::$session_types_controlled); - - if (($num_sessions + 1) > $max_sessions) { - self::debug(__METHOD__ . ' --> limit for session pool still reached so try kick one min idle session'); - - self::kickOneMinIdleSession(self::$session_types_controlled); - - // get total number of sessions again - $num_sessions = self::getExistingSessionCount(self::$session_types_controlled); - - if (($num_sessions + 1) > $max_sessions) { - self::debug(__METHOD__ . ' --> limit for session pool still reached so logout session (' . session_id() . ') and trigger event'); - - ilSession::setClosingContext(ilSession::SESSION_CLOSE_LIMIT); - - // as the session is opened and closed in one request, there - // is no proper session yet and we have to do this ourselves - ilSessionStatistics::createRawEntry( - session_id(), - ilSession::get(self::SESSION_TYPE_KEY), - time(), - $a_user_id - ); - - $auth->logout(); - - // Trigger reachedSessionPoolLimit Event - $ilAppEventHandler = $DIC['ilAppEventHandler']; - $ilAppEventHandler->raise( - 'components/ILIAS/Authentication', - 'reachedSessionPoolLimit', - array() - ); - - // auth won't do this, we need to close session properly - // already done in new implementation - // session_destroy(); - - ilUtil::redirect('login.php?reached_session_limit=true'); - } else { - self::debug(__METHOD__ . ' --> limit of session pool not reached anymore after kicking one min idle session'); - } - } else { - self::debug(__METHOD__ . ' --> limit of session pool not reached anymore after kicking some first request abidencer'); - } - } else { - self::debug(__METHOD__ . ' --> limit for session pool not reached yet'); - } - } else { - self::debug(__METHOD__ . ' --> limit for session pool not set so check is bypassed'); - } } /** @@ -250,79 +138,6 @@ public static function getExistingSessionCount(array $a_types): int return (int) $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)->num_sessions; } - /** - * if sessions exist that relates to given session types - * and idled longer than min idle parameter, this method - * deletes one of these sessions - */ - private static function kickOneMinIdleSession(array $a_types): void - { - global $DIC; - - $ilDB = $DIC['ilDB']; - $ilSetting = $DIC['ilSetting']; - - $ts = time(); - $min_idle = (int) $ilSetting->get('session_min_idle', (string) self::DEFAULT_MIN_IDLE) * 60; - $max_idle = (int) $ilSetting->get('session_max_idle', (string) self::DEFAULT_MAX_IDLE) * 60; - - $query = "SELECT session_id,expires FROM usr_session WHERE expires >= %s " . - "AND (expires - %s) < (%s - %s) " . - "AND " . $ilDB->in('type', $a_types, false, 'integer') . " ORDER BY expires"; - - $res = $ilDB->queryF( - $query, - array('integer', 'integer', 'integer', 'integer'), - array($ts, $ts, $max_idle, $min_idle) - ); - - if ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - ilSession::_destroy($row->session_id, ilSession::SESSION_CLOSE_IDLE, $row->expires); - - self::debug(__METHOD__ . ' --> successfully deleted one min idle session'); - - return; - } - self::debug(__METHOD__ . ' --> no min idle session available for deletion'); - } - - /** - * kicks sessions of users that abidence after login - * so people could not login and go for coffe break ;-) - */ - private static function kickFirstRequestAbidencer(array $a_types): void - { - global $DIC; - - $ilDB = $DIC['ilDB']; - $ilSetting = $DIC['ilSetting']; - - $max_idle_after_first_request = (int) $ilSetting->get('session_max_idle_after_first_request') * 60; - - if ((int) $max_idle_after_first_request === 0) { - return; - } - - $query = "SELECT session_id,expires FROM usr_session WHERE " . - "(ctime - createtime) < %s " . - "AND (%s - createtime) > %s " . - "AND " . $ilDB->in('type', $a_types, false, 'integer'); - - $res = $ilDB->queryF( - $query, - array('integer', 'integer', 'integer'), - array($max_idle_after_first_request, time(), $max_idle_after_first_request) - ); - - $session_ids = array(); - while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { - $session_ids[$row->session_id] = $row->expires; - } - ilSession::_destroy($session_ids, ilSession::SESSION_CLOSE_FIRST, true); - - self::debug(__METHOD__ . ' --> Finished kicking first request abidencer'); - } - /** * checks if session exists for given id * and if it is still valid diff --git a/components/ILIAS/Authentication/classes/class.ilSessionReminder.php b/components/ILIAS/Authentication/classes/class.ilSessionReminder.php index 1283ef693f7a..3718995491c9 100755 --- a/components/ILIAS/Authentication/classes/class.ilSessionReminder.php +++ b/components/ILIAS/Authentication/classes/class.ilSessionReminder.php @@ -60,16 +60,7 @@ public static function isGloballyActivated(): bool $ilSetting = $DIC['ilSetting']; - $isSessionReminderEnabled = (bool) $ilSetting->get('session_reminder_enabled', null); - $sessionHandlingMode = (int) $ilSetting->get( - 'session_handling_type', - (string) ilSession::SESSION_HANDLING_FIXED - ); - - return ( - $isSessionReminderEnabled && - $sessionHandlingMode === ilSession::SESSION_HANDLING_FIXED - ); + return (bool) $ilSetting->get('session_reminder_enabled'); } public function __construct(ilObjUser $user, ClockInterface $clock) @@ -89,7 +80,7 @@ private function init(): void )) * 60 ); - $this->setExpirationTime(ilSession::getIdleValue(true) + $this->clock->now()->getTimestamp()); + $this->setExpirationTime(ilSession::getIdleValue() + $this->clock->now()->getTimestamp()); $this->setCurrentTime($this->clock->now()->getTimestamp()); $this->calculateSecondsUntilExpiration(); diff --git a/components/ILIAS/Authentication/classes/class.ilSessionStatistics.php b/components/ILIAS/Authentication/classes/class.ilSessionStatistics.php index c1cd92316973..9e03b6e78084 100755 --- a/components/ILIAS/Authentication/classes/class.ilSessionStatistics.php +++ b/components/ILIAS/Authentication/classes/class.ilSessionStatistics.php @@ -281,9 +281,6 @@ public static function aggregateRawHelper(int $a_begin, int $a_end): void // "relevant" closing types $separate_closed = array(ilSession::SESSION_CLOSE_USER, ilSession::SESSION_CLOSE_EXPIRE, - ilSession::SESSION_CLOSE_IDLE, - ilSession::SESSION_CLOSE_FIRST, - ilSession::SESSION_CLOSE_LIMIT, ilSession::SESSION_CLOSE_LOGIN); // gather/process data (build event timeline) @@ -368,11 +365,6 @@ public static function aggregateRawHelper(int $a_begin, int $a_end): void } unset($events); - - // do we (really) need a log here? - // $max_sessions = (int)$ilSetting->get("session_max_count", ilSessionControl::DEFAULT_MAX_COUNT); - $max_sessions = self::getLimitForSlot($a_begin); - // save aggregated data $fields = array( "active_min" => array("integer", $active_min), @@ -382,12 +374,8 @@ public static function aggregateRawHelper(int $a_begin, int $a_end): void "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_idle" => array("integer", (int) ($closed_counter[ilSession::SESSION_CLOSE_IDLE] ?? 0)), - "closed_idle_first" => array("integer", (int) ($closed_counter[ilSession::SESSION_CLOSE_FIRST] ?? 0)), - "closed_limit" => array("integer", (int) ($closed_counter[ilSession::SESSION_CLOSE_LIMIT] ?? 0)), "closed_login" => array("integer", (int) ($closed_counter[ilSession::SESSION_CLOSE_LOGIN] ?? 0)), "closed_misc" => array("integer", (int) ($closed_counter[0] ?? 0)), - "max_sessions" => array("integer", $max_sessions) ); $ilDB->update( "usr_session_stats", @@ -415,51 +403,6 @@ protected static function deleteAggregatedRaw($a_now): void " WHERE start_time <= " . $ilDB->quote($cut, "integer")); } - /** - * Get latest slot during which sessions were maxed out - */ - public static function getLastMaxedOut(): int - { - global $DIC; - - $ilDB = $DIC['ilDB']; - - $sql = "SELECT max(slot_end) latest FROM usr_session_stats" . - " WHERE active_max >= max_sessions" . - " AND max_sessions > " . $ilDB->quote(0, "integer"); - $res = $ilDB->query($sql); - $row = $ilDB->fetchAssoc($res); - if ($row["latest"]) { - return (int) $row["latest"]; - } - return 0; - } - - /** - * Get maxed out duration in given timeframe - * - * @return ?int seconds - */ - public static function getMaxedOutDuration(int $a_from, int $a_to): ?int - { - global $DIC; - - $ilDB = $DIC['ilDB']; - - $sql = "SELECT SUM(slot_end-slot_begin) dur FROM usr_session_stats" . - " WHERE active_max >= max_sessions" . - " AND max_sessions > " . $ilDB->quote(0, "integer") . - " AND slot_end > " . $ilDB->quote($a_from, "integer") . - " AND slot_begin < " . $ilDB->quote($a_to, "integer"); - $res = $ilDB->query($sql); - $row = $ilDB->fetchAssoc($res); - if ($row["dur"]) { - return (int) $row["dur"]; - } - //TODO check if return null as timestamp causes issues - return null; - } - /** * Get session counters by type (opened, closed) */ @@ -470,8 +413,7 @@ 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_idle) closed_idle," . - " SUM(closed_idle_first) closed_idle_first, SUM(closed_limit) closed_limit," . + " 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") . @@ -490,8 +432,7 @@ 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," . - " max_sessions" . + $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") . @@ -526,52 +467,4 @@ public static function getLastAggregation(): ?int //TODO check if return null as timestamp causes issues return null; } - - /** - * Get max session setting for given timestamp - * - */ - public static function getLimitForSlot(int $a_timestamp): int - { - global $DIC; - - $ilDB = $DIC['ilDB']; - $ilSetting = $DIC['ilSetting']; - - $ilDB->setLimit(1); - $sql = "SELECT maxval FROM usr_session_log" . - " WHERE tstamp <= " . $ilDB->quote($a_timestamp, "integer") . - " ORDER BY tstamp DESC"; - $res = $ilDB->query($sql); - $val = $ilDB->fetchAssoc($res); - if (isset($val["maxval"]) && $val["maxval"]) { - return (int) $val["maxval"]; - } - - return (int) $ilSetting->get("session_max_count", (string) ilSessionControl::DEFAULT_MAX_COUNT); - } - - /** - * Log max session setting - */ - public static function updateLimitLog(int $a_new_value): void - { - global $DIC; - - $ilDB = $DIC['ilDB']; - $ilSetting = $DIC['ilSetting']; - $ilUser = $DIC['ilUser']; - - $new_value = $a_new_value; - $old_value = (int) $ilSetting->get("session_max_count", (string) ilSessionControl::DEFAULT_MAX_COUNT); - - if ($new_value !== $old_value) { - $fields = array( - "tstamp" => array("timestamp", time()), - "maxval" => array("integer", $new_value), - "user_id" => array("integer", $ilUser->getId()) - ); - $ilDB->insert("usr_session_log", $fields); - } - } } diff --git a/components/ILIAS/Authentication/classes/class.ilSessionStatisticsGUI.php b/components/ILIAS/Authentication/classes/class.ilSessionStatisticsGUI.php index f1b6e2f0de9c..9b294b71f033 100755 --- a/components/ILIAS/Authentication/classes/class.ilSessionStatisticsGUI.php +++ b/components/ILIAS/Authentication/classes/class.ilSessionStatisticsGUI.php @@ -469,15 +469,6 @@ protected function renderCurrentBasics(): string $active = ilSessionControl::getExistingSessionCount(ilSessionControl::$session_types_controlled); - $control_active = ((int) $this->settings->get('session_handling_type', "0") === 1); - if ($control_active) { - $control_max_sessions = (int) $this->settings->get('session_max_count', (string) ilSessionControl::DEFAULT_MAX_COUNT); - $control_min_idle = (int) $this->settings->get('session_min_idle', (string) ilSessionControl::DEFAULT_MIN_IDLE); - $control_max_idle = (int) $this->settings->get('session_max_idle', (string) ilSessionControl::DEFAULT_MAX_IDLE); - $control_max_idle_first = (int) $this->settings->get('session_max_idle_after_first_request', (string) ilSessionControl::DEFAULT_MAX_IDLE_AFTER_FIRST_REQUEST); - } - - $last_maxed_out = new ilDateTime(ilSessionStatistics::getLastMaxedOut(), IL_CAL_UNIX); $last_aggr = new ilDateTime(ilSessionStatistics::getLastAggregation(), IL_CAL_UNIX); @@ -491,32 +482,6 @@ protected function renderCurrentBasics(): string $left->setVariable("CAPTION_LAST_AGGR", $this->lng->txt("trac_last_aggregation")); $left->setVariable("VALUE_LAST_AGGR", ilDatePresentation::formatDate($last_aggr)); - $left->setVariable("CAPTION_LAST_MAX", $this->lng->txt("trac_last_maxed_out_sessions")); - $left->setVariable("VALUE_LAST_MAX", ilDatePresentation::formatDate($last_maxed_out)); - - $left->setVariable("CAPTION_SESSION_CONTROL", $this->lng->txt("sess_load_dependent_session_handling")); - if (!$control_active) { - $left->setVariable("VALUE_SESSION_CONTROL", $this->lng->txt("no")); - } else { - $left->setVariable("VALUE_SESSION_CONTROL", $this->lng->txt("yes")); - - $left->setCurrentBlock("control_details"); - - $left->setVariable("CAPTION_SESSION_CONTROL_LIMIT", $this->lng->txt("session_max_count")); - $left->setVariable("VALUE_SESSION_CONTROL_LIMIT", $control_max_sessions); - - $left->setVariable("CAPTION_SESSION_CONTROL_IDLE_MIN", $this->lng->txt("session_min_idle")); - $left->setVariable("VALUE_SESSION_CONTROL_IDLE_MIN", $control_min_idle); - - $left->setVariable("CAPTION_SESSION_CONTROL_IDLE_MAX", $this->lng->txt("session_max_idle")); - $left->setVariable("VALUE_SESSION_CONTROL_IDLE_MAX", $control_max_idle); - - $left->setVariable("CAPTION_SESSION_CONTROL_IDLE_FIRST", $this->lng->txt("session_max_idle_after_first_request")); - $left->setVariable("VALUE_SESSION_CONTROL_IDLE_FIRST", $control_max_idle_first); - - $left->parseCurrentBlock(); - } - // sync button if ($this->access->checkAccess("write", "", $this->ref_id)) { $left->setVariable("URL_SYNC", $this->ilCtrl->getFormAction($this, "adminSync")); @@ -531,11 +496,9 @@ protected function buildData(int $a_time_from, int $a_time_to, string $a_title): { // basic data - time related - $maxed_out_duration = round(ilSessionStatistics::getMaxedOutDuration($a_time_from, $a_time_to) / 60); $counters = ilSessionStatistics::getNumberOfSessionsByType($a_time_from, $a_time_to); $opened = (int) $counters["opened"]; - $closed_limit = (int) $counters["closed_limit"]; - unset($counters["opened"], $counters["closed_limit"]); + unset($counters["opened"]); // build center column @@ -549,8 +512,6 @@ protected function buildData(int $a_time_from, int $a_time_to, string $a_title): new ilDateTime($a_time_to, IL_CAL_UNIX) ) . ")"; - $data["maxed_out_time"] = array($this->lng->txt("trac_maxed_out_time"), $maxed_out_duration); - $data["maxed_out_counter"] = array($this->lng->txt("trac_maxed_out_counter"), $closed_limit); $data["opened"] = array($this->lng->txt("trac_sessions_opened"), $opened); $data["closed"] = array($this->lng->txt("trac_sessions_closed"), array_sum($counters)); foreach ($counters as $type => $counter) { @@ -629,12 +590,6 @@ protected function getChart(array $a_data, string $a_title, int $a_scale = self: $colors[] = $colors_map[$measure]; } - if ($a_scale === self::SCALE_DAY || $a_scale === self::SCALE_WEEK) { - $max_line = $chart->getDataInstance(ilChartGrid::DATA_LINES); - $max_line->setLabel($this->lng->txt("session_max_count")); - $colors[] = "#cc0000"; - } - $chart->setColors($colors); $chart_data = $this->adaptDataToScale($a_scale, $a_data); @@ -689,18 +644,11 @@ protected function getChart(array $a_data, string $a_title, int $a_scale = self: $value = (int) $item["active_" . $measure]; $act_line[$measure]->addPoint($date, $value); } - - if (isset($max_line)) { - $max_line->addPoint($date, (int) $item["max_sessions"]); - } } foreach ($act_line as $line) { $chart->addData($line); } - if (isset($max_line)) { - $chart->addData($max_line); - } $chart->setTicks($labels, null, true); diff --git a/components/ILIAS/Authentication/service.xml b/components/ILIAS/Authentication/service.xml index c757feaed5f3..bb5723582b3e 100755 --- a/components/ILIAS/Authentication/service.xml +++ b/components/ILIAS/Authentication/service.xml @@ -14,7 +14,6 @@ - diff --git a/components/ILIAS/Authentication/templates/default/tpl.session_statistics_center.html b/components/ILIAS/Authentication/templates/default/tpl.session_statistics_center.html index d57c192851be..44d0c3c96caf 100755 --- a/components/ILIAS/Authentication/templates/default/tpl.session_statistics_center.html +++ b/components/ILIAS/Authentication/templates/default/tpl.session_statistics_center.html @@ -12,6 +12,4 @@

{TIMEFRAME}

  • {CAPTION_CLOSED_DETAILS}: {VALUE_CLOSED_DETAILS}
  • -

    {CAPTION_MAXED_OUT_COUNTER}: {VALUE_MAXED_OUT_COUNTER}

    -

    {CAPTION_MAXED_OUT_TIME}: {VALUE_MAXED_OUT_TIME}

    \ No newline at end of file diff --git a/components/ILIAS/Authentication/templates/default/tpl.session_statistics_left.html b/components/ILIAS/Authentication/templates/default/tpl.session_statistics_left.html index 4e16aaf17ac3..b2719a5475c3 100755 --- a/components/ILIAS/Authentication/templates/default/tpl.session_statistics_left.html +++ b/components/ILIAS/Authentication/templates/default/tpl.session_statistics_left.html @@ -2,16 +2,6 @@

    {CAPTION_CURRENT}: {VALUE_CURRENT}

    {CAPTION_LAST_AGGR}: {VALUE_LAST_AGGR}

    -

    {CAPTION_LAST_MAX}: {VALUE_LAST_MAX}

    -

    {CAPTION_SESSION_CONTROL}: {VALUE_SESSION_CONTROL}

    - -
      -
    • {CAPTION_SESSION_CONTROL_LIMIT}: {VALUE_SESSION_CONTROL_LIMIT}
    • -
    • {CAPTION_SESSION_CONTROL_IDLE_MIN}: {VALUE_SESSION_CONTROL_IDLE_MIN}
    • -
    • {CAPTION_SESSION_CONTROL_IDLE_MAX}: {VALUE_SESSION_CONTROL_IDLE_MAX}
    • -
    • {CAPTION_SESSION_CONTROL_IDLE_FIRST}: {VALUE_SESSION_CONTROL_IDLE_FIRST}
    • -
    -