diff --git a/components/ILIAS/Administration/GlobalScreen/classes/AdministrationMainBarProvider.php b/components/ILIAS/Administration/GlobalScreen/classes/AdministrationMainBarProvider.php index 3f9baaa54ae2..660af1b3f27f 100755 --- a/components/ILIAS/Administration/GlobalScreen/classes/AdministrationMainBarProvider.php +++ b/components/ILIAS/Administration/GlobalScreen/classes/AdministrationMainBarProvider.php @@ -225,7 +225,7 @@ private function getGroups(): array // admin menu layout $layout = array( "maintenance" => - array("adma", "serv", "cron", "bnmk", "lngf", "hlps", "wfe", 'fils', 'logs', 'sysc', "recf", "root"), + array("adma", "serv", "cron", "bnmk", "lngf", "hlps", "wfe", 'fils', 'qsts', 'logs', 'sysc', "recf", "root"), "layout_and_navigation" => array("mme", "gsfo", "dshs", "stys", "adve", "stus"), "repository_and_objects" => diff --git a/components/ILIAS/BackgroundTasks_/classes/Jobs/class.ilCollectFilesJob.php b/components/ILIAS/BackgroundTasks_/classes/Jobs/class.ilCollectFilesJob.php index 3dae62a64972..140b8fced1f6 100755 --- a/components/ILIAS/BackgroundTasks_/classes/Jobs/class.ilCollectFilesJob.php +++ b/components/ILIAS/BackgroundTasks_/classes/Jobs/class.ilCollectFilesJob.php @@ -97,21 +97,37 @@ public function run(array $input, Observer $observer): Value foreach ($object_ref_ids as $object_ref_id) { $object = ilObjectFactory::getInstanceByRefId($object_ref_id); + if (!$object instanceof ilObject) { + continue; + } + $object_type = $object->getType(); $object_name = $object->getTitle(); - $object_temp_dir = ""; // empty as content will be added in recurseFolder and getFileDirs + $object_temp_dir = ''; // empty as content will be added in recurseFolder and getFileDirs - if ($object_type === "fold" || $object_type === "crs") { + if (in_array($object_type, ['fold', 'crs', 'grp'], true)) { $num_recursions = 0; - $files_from_folder = $this->recurseFolder($object_ref_id, $object_name, $object_temp_dir, $num_recursions, $initiated_by_folder_action); + $files_from_folder = $this->recurseFolder( + $object_ref_id, + $object_name, + $object_temp_dir, + $num_recursions, + $initiated_by_folder_action + ); $files = array_merge($files, $files_from_folder); - } elseif (($object_type === "file") && (($file_dirs = $this->getFileDirs( - $object_ref_id, - $object_name, - $object_temp_dir - )) !== false)) { - $files[] = $file_dirs; + continue; } + + if ($object_type !== 'file') { + continue; + } + + $file_dirs = $this->getFileDirs($object_ref_id, $object_name, $object_temp_dir); + if (!is_array($file_dirs)) { + continue; + } + + $files[] = $file_dirs; } $this->logger->debug('Collected files:'); $this->logger->dump($files); diff --git a/components/ILIAS/Blog/Contributor/ContributorGUI.php b/components/ILIAS/Blog/Contributor/ContributorGUI.php new file mode 100644 index 000000000000..c2a277e0581e --- /dev/null +++ b/components/ILIAS/Blog/Contributor/ContributorGUI.php @@ -0,0 +1,262 @@ +gui->ctrl(); + $next_class = $ctrl->getNextClass($this); + $cmd = $ctrl->getCmd("contributors"); + + switch ($next_class) { + case strtolower(\ilRepositorySearchGUI::class): + $rep_search = new \ilRepositorySearchGUI(); + $rep_search->setTitle($this->domain->lng()->txt("blog_add_contributor")); + $rep_search->setCallback($this, 'addContributor', $this->blog->getAllLocalRoles($this->node_id)); + $ctrl->setReturn($this, 'contributors'); + $ctrl->forwardCommand($rep_search); + break; + + default: + if (method_exists($this, $cmd)) { + $this->$cmd(); + } + break; + } + } + + public function contributors(): void + { + $ilTabs = $this->gui->tabs(); + $ilToolbar = $this->gui->toolbar(); + $ilCtrl = $this->gui->ctrl(); + $lng = $this->domain->lng(); + $tpl = $this->gui->ui()->mainTemplate(); + + $ilTabs->activateTab("contributors"); + + $local_roles = $this->blog->getAllLocalRoles($this->node_id); + + // add member + ilRepositorySearchGUI::fillAutoCompleteToolbar( + $this, + $ilToolbar, + array( + 'auto_complete_name' => $lng->txt('user'), + 'submit_name' => $lng->txt('add'), + 'add_search' => true, + 'add_from_container' => $this->node_id, + 'user_type' => $local_roles + ), + true + ); + + $other_roles = $this->blog->getRolesWithContributeOrRedact($this->node_id); + if ($other_roles) { + $tpl->setOnScreenMessage('info', sprintf($lng->txt("blog_contribute_other_roles"), implode(", ", $other_roles))); + } + + $table = $this->gui->contributor()->contributorTableBuilder( + $this->blog->getAllLocalRoles($this->node_id), + $this, + "contributors" + )->getTable(); + + if ($table->handleCommand()) { + return; + } + + $tpl->setContent($table->render()); + } + + /** + * Autocomplete submit + */ + public function addUserFromAutoComplete(): void + { + $lng = $this->domain->lng(); + $req = $this->gui->standardRequest(); + + $user_login = $req->getUserLogin(); + $user_type = $req->getUserType(); + + if (trim($user_login) === '') { + $this->gui->ui()->mainTemplate()->setOnScreenMessage('failure', $lng->txt('msg_no_search_string')); + $this->contributors(); + return; + } + $users = explode(',', $user_login); + + $user_ids = array(); + foreach ($users as $user) { + $user_id = ilObjUser::_lookupId($user); + + if (!$user_id) { + $this->gui->ui()->mainTemplate()->setOnScreenMessage('failure', $lng->txt('user_not_known')); + $this->contributors(); + return; + } + + $user_ids[] = (int) $user_id; + } + + $this->addContributor($user_ids, $user_type); + } + + /** + * Centralized method to add contributors + */ + public function addContributor( + array $a_user_ids = array(), + ?string $a_user_type = null + ): void { + $ilCtrl = $this->gui->ctrl(); + $lng = $this->domain->lng(); + $rbacreview = $this->domain->rbac()->review(); + $rbacadmin = $this->domain->rbac()->admin(); + $a_user_type = (int) $a_user_type; + + if (empty($a_user_ids)) { + $a_user_ids = $this->gui->standardRequest()->getIds(); + } + + if (!count($a_user_ids) || !$a_user_type) { + $this->gui->ui()->mainTemplate()->setOnScreenMessage('failure', $lng->txt("no_checkbox")); + $this->contributors(); + return; + } + + // get contributor role + $local_roles = array_keys($this->blog->getAllLocalRoles($this->node_id)); + if (!in_array($a_user_type, $local_roles)) { + $this->gui->ui()->mainTemplate()->setOnScreenMessage('failure', $lng->txt("missing_perm")); + $this->contributors(); + return; + } + + foreach ($a_user_ids as $user_id) { + $user_id = (int) $user_id; + $a_user_type = (int) $a_user_type; + if (!$rbacreview->isAssigned($user_id, $a_user_type)) { + $rbacadmin->assignUser($a_user_type, $user_id); + } + } + + $this->gui->ui()->mainTemplate()->setOnScreenMessage('success', $lng->txt("settings_saved"), true); + $ilCtrl->redirect($this, "contributors"); + } + + /** + * Used in ContributorTableBuilder + */ + public function confirmRemoveContributor(array $ids = []): void + { + if (empty($ids)) { + $ids = $this->gui->standardRequest()->getIds(); + } + if (count($ids) === 0) { + $this->gui->ui()->mainTemplate()->setOnScreenMessage('failure', $this->domain->lng()->txt("select_one"), true); + $this->gui->ctrl()->redirect($this, "contributors"); + } + + $confirm = new ilConfirmationGUI(); + $confirm->setHeaderText($this->domain->lng()->txt('blog_confirm_delete_contributors')); + $confirm->setFormAction($this->gui->ctrl()->getFormAction($this, 'removeContributor')); + $confirm->setConfirm($this->domain->lng()->txt('delete'), 'removeContributor'); + $confirm->setCancel($this->domain->lng()->txt('cancel'), 'contributors'); + + foreach ($ids as $user_id) { + $confirm->addItem( + 'id[]', + (string) $user_id, + \ilUserUtil::getNamePresentation($user_id, false, false, "", true) + ); + } + + $this->gui->ui()->mainTemplate()->setContent($confirm->getHTML()); + } + + public function removeContributor(): void + { + $ilCtrl = $this->gui->ctrl(); + $lng = $this->domain->lng(); + $rbacadmin = $this->domain->rbac()->admin(); + + $ids = $this->gui->standardRequest()->getIds(); + + if (count($ids) === 0) { + $this->gui->ui()->mainTemplate()->setOnScreenMessage('failure', $lng->txt("select_one"), true); + $ilCtrl->redirect($this, "contributors"); + } + + // get contributor role + $local_roles = array_keys($this->blog->getAllLocalRoles($this->node_id)); + if (!$local_roles) { + $this->gui->ui()->mainTemplate()->setOnScreenMessage('failure', $lng->txt("missing_perm")); + $this->contributors(); + return; + } + + foreach ($ids as $user_id) { + foreach ($local_roles as $role_id) { + $rbacadmin->deassignUser($role_id, $user_id); + } + } + + $this->gui->ui()->mainTemplate()->setOnScreenMessage('success', $lng->txt("settings_saved"), true); + $this->gui->ctrl()->redirect($this, "contributors"); + } + + /** + * Used in ContributorTableBuilder + */ + public function addContributorContainerAction(array $ids = []): void + { + if (empty($ids)) { + $ids = $this->gui->standardRequest()->getIds(); + } + + // This would typically add contributors from a container + // For now, redirecting back to contributors as this seems to be a placeholder action + $this->gui->ctrl()->redirect($this, "contributors"); + } +} diff --git a/components/ILIAS/Blog/Contributor/Service/class.GUIService.php b/components/ILIAS/Blog/Contributor/Service/class.GUIService.php index ef8de2f00f64..8007e5d6d2d8 100755 --- a/components/ILIAS/Blog/Contributor/Service/class.GUIService.php +++ b/components/ILIAS/Blog/Contributor/Service/class.GUIService.php @@ -57,4 +57,15 @@ public function contributorTableBuilder( $parent_cmd ); } + + public function contributorGUI(int $node_id, \ilObjBlog $blog): ContributorGUI + { + return new ContributorGUI( + $this->data_service, + $this->domain_service, + $this->gui, + $node_id, + $blog + ); + } } diff --git a/components/ILIAS/Blog/Export/BlogHtmlExport.php b/components/ILIAS/Blog/Export/BlogHtmlExport.php index b94f698f5158..e23f6447c2da 100755 --- a/components/ILIAS/Blog/Export/BlogHtmlExport.php +++ b/components/ILIAS/Blog/Export/BlogHtmlExport.php @@ -56,11 +56,13 @@ public function __construct( $this->blog_gui = $blog_gui; /** @var \ilObjBlog $blog */ $blog = $blog_gui->getObject(); + $this->blog = $blog; + + $blog_service = $DIC->blog()->internal(); + $this->collector = $DIC->export()->domain()->html()->collector($blog->getId()); $this->collector->init(); - $this->blog = $blog; - //$this->export_dir = $exp_dir; $this->sub_dir = $sub_dir; $this->target_dir = $exp_dir . "/" . $sub_dir; @@ -86,7 +88,7 @@ public function __construct( } else { $this->content_style_domain = $cs->domain()->styleForObjId($this->blog->getId()); } - $this->posting_manager = $DIC->blog()->internal()->domain()->posting(); + $this->posting_manager = $blog_service->domain()->posting(); } protected function init(): void { @@ -339,8 +341,6 @@ public function buildExportLink( protected function getInitialisedTemplate( string $a_back_url = "" ): \ilGlobalPageTemplate { - global $DIC; - $this->export_util->resetGlobalScreen(); $location_stylesheet = \ilUtil::getStyleSheetLocation(); @@ -350,13 +350,16 @@ protected function getInitialisedTemplate( ); \ilPCQuestion::resetInitialState(); - $tabs = $DIC->tabs(); + $tabs = $this->tabs; $tabs->clearTargets(); $tabs->clearSubTabs(); if ($a_back_url) { $tabs->setBackTarget($this->lng->txt("back"), $a_back_url); } - $tpl = new \ilGlobalPageTemplate($DIC->globalScreen(), $DIC->ui(), $DIC->http()); + + /** @var \ILIAS\DI\Container $DIC */ + global $DIC; + $tpl = new \ilGlobalPageTemplate($this->global_screen, $DIC->ui(), $DIC->http()); $this->co_page_html_export->getPreparedMainTemplate($tpl); diff --git a/components/ILIAS/Blog/Export/class.ilBlogDataSet.php b/components/ILIAS/Blog/Export/class.ilBlogDataSet.php index b3aa993583a5..b8f8e9195cbe 100755 --- a/components/ILIAS/Blog/Export/class.ilBlogDataSet.php +++ b/components/ILIAS/Blog/Export/class.ilBlogDataSet.php @@ -44,13 +44,15 @@ public function __construct() { global $DIC; parent::__construct(); + + $blog_service = $DIC->blog()->internal(); + $this->content_style_domain = $DIC ->contentStyle() ->domain(); $this->notes = $DIC->notes(); - $this->reading_time = $DIC->blog()->internal()->domain()->readingTime(); - $this->blog_settings = $DIC->blog()->internal()->domain()->blogSettings(); - $this->service = $DIC->blog()->internal(); + $this->reading_time = $blog_service->domain()->readingTime(); + $this->blog_settings = $blog_service->domain()->blogSettings(); } public function getSupportedVersions(): array @@ -285,7 +287,7 @@ public function readData( // keywords foreach ($this->data as $idx => $item) { - $blog_id = ilBlogPosting::lookupBlogId($item["Id"]); + $blog_id = $this->service->domain()->posting()->lookupBlogId((int) $item["Id"]); $keywords = $this->service->domain()->posting()->getKeywords((int) $blog_id, (int) $item["Id"]); if ($keywords) { foreach ($keywords as $kidx => $keyword) { diff --git a/components/ILIAS/Blog/Export/class.ilBlogExporter.php b/components/ILIAS/Blog/Export/class.ilBlogExporter.php index ca20e3be90f0..4132a223fb5f 100755 --- a/components/ILIAS/Blog/Export/class.ilBlogExporter.php +++ b/components/ILIAS/Blog/Export/class.ilBlogExporter.php @@ -28,16 +28,23 @@ class ilBlogExporter extends ilXmlExporter protected ilBlogDataSet $ds; protected \ILIAS\Style\Content\DomainService $content_style_domain; - public function init(): void + public function __construct() { global $DIC; + parent::__construct(); + $blog_service = $DIC->blog()->internal(); - $this->ds = new ilBlogDataSet(); - $this->ds->setDSPrefix("ds"); $this->content_style_domain = $DIC ->contentStyle() ->domain(); - $this->posting_manager = $DIC->blog()->internal()->domain()->posting(); + $this->posting_manager = $blog_service->domain()->posting(); + } + + + public function init(): void + { + $this->ds = new ilBlogDataSet(); + $this->ds->setDSPrefix("ds"); } public function getXmlExportTailDependencies( diff --git a/components/ILIAS/Blog/Export/class.ilBlogImporter.php b/components/ILIAS/Blog/Export/class.ilBlogImporter.php index 4481de258d39..d0c21290b127 100755 --- a/components/ILIAS/Blog/Export/class.ilBlogImporter.php +++ b/components/ILIAS/Blog/Export/class.ilBlogImporter.php @@ -25,19 +25,23 @@ */ class ilBlogImporter extends ilXmlImporter { + protected \ILIAS\Blog\InternalService $blog_service; protected ilBlogDataSet $ds; protected \ILIAS\Style\Content\DomainService $content_style_domain; - public function init(): void + public function __construct() { global $DIC; - - $this->ds = new ilBlogDataSet(); - $this->ds->setDSPrefix("ds"); $this->content_style_domain = $DIC ->contentStyle() ->domain(); + $this->blog_service = $DIC->blog()->internal(); + } + public function init(): void + { + $this->ds = new ilBlogDataSet(); + $this->ds->setDSPrefix("ds"); $cop_config = $this->getImport()->getConfig("components/ILIAS/COPage"); $cop_config->setUpdateIfExists(true); } @@ -64,7 +68,7 @@ public function finalProcessing( $blp_map = $a_mapping->getMappingsOfEntity("components/ILIAS/COPage", "pg"); foreach ($blp_map as $blp_id) { $blp_id = (int) substr($blp_id, 4); - $blog_id = ilBlogPosting::lookupBlogId($blp_id); + $blog_id = $this->blog_service->domain()->posting()->lookupBlogId($blp_id); ilBlogPosting::_writeParentId("blp", $blp_id, (int) $blog_id); } diff --git a/components/ILIAS/Blog/Navigation/AuthorBlockGUI.php b/components/ILIAS/Blog/Navigation/AuthorBlockGUI.php new file mode 100644 index 000000000000..6a88ce305357 --- /dev/null +++ b/components/ILIAS/Blog/Navigation/AuthorBlockGUI.php @@ -0,0 +1,122 @@ +domain = $domain; + $this->gui = $gui; + } + + /** + * @param Posting[][] $items + */ + public function render( + array $items, + string $list_cmd = "render", + bool $show_inactive = false + ): string { + $ctrl = $this->gui->ctrl(); + $lng = $this->domain->lng(); + + $authors = array(); + foreach ($items as $month => $month_items) { + foreach ($month_items as $item) { + $item_id = $item->getId(); + if (($show_inactive || \ilBlogPosting::_lookupActive($item_id, "blp"))) { + $author_id = $item->getAuthor(); + if ($author_id) { + $authors[] = $author_id; + } + foreach (\ilPageObject::getPageContributors("blp", $item_id) as $editor) { + $editor_id = (int) $editor["user_id"]; + if ($editor_id !== $author_id) { + $authors[] = $editor_id; + } + } + } + } + } + + $authors = array_unique($authors); + + // filter out deleted users + $authors = array_filter($authors, function ($id) { + return \ilObject::_lookupType($id) == "usr"; + }); + + if (count($authors) > 1) { + $list = array(); + foreach ($authors as $user_id) { + if ($user_id) { + $ctrl->setParameterByClass(\ilObjBlogGUI::class, "ath", (string) $user_id); + $url = $ctrl->getLinkTargetByClass(\ilObjBlogGUI::class, $list_cmd); + $ctrl->setParameterByClass(\ilObjBlogGUI::class, "ath", ""); + + $base_name = \ilUserUtil::getNamePresentation($user_id); + if (str_starts_with($base_name, "[")) { + $name = \ilUserUtil::getNamePresentation($user_id, true); + $sort = $name; + } else { + $name = \ilUserUtil::getNamePresentation( + $user_id, + true, + false, + "", + false, + true, + false + ); + $name_arr = \ilObjUser::_lookupName($user_id); + $sort = $name_arr["lastname"] . " " . $name_arr["firstname"]; + } + + $idx = trim(strip_tags((string) $sort)) . "///" . $user_id; + $list[$idx] = array($name, $url); + } + } + ksort($list); + + $wtpl = new \ilTemplate("tpl.blog_list_navigation_authors.html", true, true, "components/ILIAS/Blog"); + + $wtpl->setCurrentBlock("author"); + foreach ($list as $author) { + $wtpl->setVariable("TXT_AUTHOR", $author[0]); + $wtpl->setVariable("URL_AUTHOR", $author[1]); + $wtpl->parseCurrentBlock(); + } + + return $wtpl->get(); + } + return ""; + } +} diff --git a/components/ILIAS/Blog/Navigation/KeywordBlockGUI.php b/components/ILIAS/Blog/Navigation/KeywordBlockGUI.php new file mode 100644 index 000000000000..7a177ae023da --- /dev/null +++ b/components/ILIAS/Blog/Navigation/KeywordBlockGUI.php @@ -0,0 +1,138 @@ +domain = $domain; + $this->gui = $gui; + } + + /** + * @param Posting[][] $items + */ + public function render( + array $items, + string $list_cmd = "render", + bool $show_inactive = false, + string $link_template = "", + int $blpg = 0 + ): string { + $ctrl = $this->gui->ctrl(); + $lng = $this->domain->lng(); + + $keywords = $this->getKeywords($items, $show_inactive, $blpg); + if ($keywords) { + $wtpl = new \ilTemplate("tpl.blog_list_navigation_keywords.html", true, true, "components/ILIAS/Blog"); + + $max = max($keywords); + + $wtpl->setCurrentBlock("keyword"); + foreach ($keywords as $keyword => $counter) { + if (!$link_template) { + $ctrl->setParameterByClass(\ilObjBlogGUI::class, "kwd", urlencode((string) $keyword)); + $url = $ctrl->getLinkTargetByClass(\ilObjBlogGUI::class, $list_cmd); + $ctrl->setParameterByClass(\ilObjBlogGUI::class, "kwd", ""); + } else { + $url = $this->buildExportLink($link_template, "keyword", (string) $keyword); + } + + $wtpl->setVariable("TXT_KEYWORD", (string) $keyword); + $wtpl->setVariable("CLASS_KEYWORD", \ilTagging::getRelevanceClass((int) $counter, (int) $max)); + $wtpl->setVariable("URL_KEYWORD", $url); + $wtpl->parseCurrentBlock(); + } + + return $wtpl->get(); + } + return ""; + } + + /** + * @param Posting[][] $items + */ + protected function getKeywords( + array $items, + bool $show_inactive, + ?int $posting_id = null + ): array { + $keywords = array(); + $posting_manager = $this->domain->posting(); + $obj_id = \ilObject::_lookupObjId($this->gui->standardRequest()->getRefId()); + + if ($posting_id) { + foreach ($posting_manager->getKeywords($obj_id, $posting_id) as $keyword) { + if (isset($keywords[$keyword])) { + $keywords[$keyword]++; + } else { + $keywords[$keyword] = 1; + } + } + } else { + foreach ($items as $month => $month_items) { + foreach ($month_items as $item) { + $item_id = $item->getId(); + if ($show_inactive || \ilBlogPosting::_lookupActive($item_id, "blp")) { + foreach ($posting_manager->getKeywords($obj_id, $item_id) as $keyword) { + if (isset($keywords[$keyword])) { + $keywords[$keyword]++; + } else { + $keywords[$keyword] = 1; + } + } + } + } + } + } + + $tmp = array(); + foreach ($keywords as $keyword => $counter) { + $tmp[] = array("keyword" => $keyword, "counter" => $counter); + } + $tmp = \ilArrayUtil::sortArray($tmp, "keyword", "ASC"); + + $keywords = array(); + foreach ($tmp as $item) { + $keywords[(string) $item["keyword"]] = $item["counter"]; + } + return $keywords; + } + + protected function buildExportLink( + string $template, + string $type, + string $id + ): string { + $blog_export = new \ILIAS\Blog\Export\BlogHtmlExport($this->gui->standardRequest()->getRefId()); + return $blog_export->buildExportLink($template, $type, $id, []); + } +} diff --git a/components/ILIAS/Blog/Navigation/MonthBlockGUI.php b/components/ILIAS/Blog/Navigation/MonthBlockGUI.php new file mode 100644 index 000000000000..1c948d4031b7 --- /dev/null +++ b/components/ILIAS/Blog/Navigation/MonthBlockGUI.php @@ -0,0 +1,253 @@ +domain = $domain; + $this->gui = $gui; + } + + /** + * @param Posting[][] $items + */ + public function render( + array $items, + string $list_cmd = "render", + string $posting_cmd = "preview", + ?string $link_template = null, + bool $show_inactive = false, + int $blpg = 0 + ): string { + $ctrl = $this->gui->ctrl(); + $lng = $this->domain->lng(); + $settings = $this->domain->blogSettings()->getByObjId( + \ilObject::_lookupObjId($this->gui->standardRequest()->getRefId()) + ); + + // gather page active status + foreach ($items as $month => $postings) { + foreach (array_keys($postings) as $id) { + $active = \ilBlogPosting::_lookupActive($id, "blp"); + if (!$show_inactive && !$active) { + unset($items[$month][$id]); + } + } + if (!count($items[$month])) { + unset($items[$month]); + } + } + + // list month (incl. postings) + if ($settings->getNavMode() === \ilObjBlog::NAV_MODE_LIST || $link_template) { + $max_months = $settings->getNavModeListMonths(); + + $wtpl = new \ilTemplate("tpl.blog_list_navigation_by_date.html", true, true, "components/ILIAS/Blog"); + + $ctrl->setParameterByClass(\ilObjBlogGUI::class, "blpg", ""); + + $counter = $mon_counter = $last_year = 0; + foreach ($items as $month => $postings) { + if (!$link_template && $max_months && $mon_counter >= $max_months) { + break; + } + + $add_year = false; + $year = substr((string) $month, 0, 4); + if (!$last_year || $year != $last_year) { + $add_year = true; + $last_year = $year; + } + + $mon_counter++; + + $month_name = \ilCalendarUtil::_numericMonthToString((int) substr((string) $month, 5)); + if (!$link_template) { + $ctrl->setParameterByClass(\ilObjBlogGUI::class, "bmn", $month); + $month_url = $ctrl->getLinkTargetByClass(\ilObjBlogGUI::class, $list_cmd); + } else { + $month_url = $this->buildExportLink($link_template, "list", (string) $month); + } + + if ($mon_counter <= $settings->getNavModeListMonthsWithPostings()) { + if ($add_year) { + $wtpl->setCurrentBlock("navigation_year_details"); + $wtpl->setVariable("YEAR", $year); + $wtpl->parseCurrentBlock(); + } + + foreach ($postings as $id => $posting) { + $counter++; + $caption = $posting->getTitle(); + + if (!$link_template) { + $ctrl->setParameterByClass(\ilBlogPostingGUI::class, "bmn", $month); + $ctrl->setParameterByClass(\ilBlogPostingGUI::class, "blpg", (string) $id); + $url = $ctrl->getLinkTargetByClass(\ilBlogPostingGUI::class, $posting_cmd); + } else { + $url = $this->buildExportLink($link_template, "posting", (string) $id); + } + + if (!$posting->isActive()) { + $wtpl->setVariable("NAV_ITEM_DRAFT", $lng->txt("blog_draft")); + } elseif ($settings->getApproval() && !$posting->isApproved()) { + $wtpl->setVariable("NAV_ITEM_APPROVAL", $lng->txt("blog_needs_approval")); + } + + $wtpl->setCurrentBlock("navigation_item"); + $wtpl->setVariable("NAV_ITEM_URL", $url); + $wtpl->setVariable("NAV_ITEM_CAPTION", $caption); + $wtpl->parseCurrentBlock(); + } + + $wtpl->setCurrentBlock("navigation_month_details"); + $wtpl->setVariable("NAV_MONTH", $month_name); + $wtpl->setVariable("URL_MONTH", $month_url); + $wtpl->parseCurrentBlock(); + } + // summarized month + else { + if ($add_year) { + $wtpl->setCurrentBlock("navigation_year"); + $wtpl->setVariable("YEAR", $year); + $wtpl->parseCurrentBlock(); + } + + $wtpl->setCurrentBlock("navigation_month"); + $wtpl->setVariable("MONTH_NAME", $month_name); + $wtpl->setVariable("URL_MONTH", $month_url); + $wtpl->setVariable("MONTH_COUNT", (string) count($postings)); + $wtpl->parseCurrentBlock(); + } + } + if (!$link_template) { + $ctrl->setParameterByClass(\ilObjBlogGUI::class, "bmn", null); + $url = $ctrl->getLinkTargetByClass(\ilObjBlogGUI::class, $list_cmd); + } else { + $url = "index.html"; + } + + $wtpl->setVariable( + "STARTING_PAGE", + $this->gui->ui()->renderer()->render( + $this->gui->ui()->factory()->link()->standard( + $lng->txt("blog_starting_page"), + $url + ) + ) + ); + $ctrl->setParameterByClass(\ilObjBlogGUI::class, "bmn", $this->gui->standardRequest()->getMonth()); + $ctrl->setParameterByClass(\ilBlogPostingGUI::class, "bmn", ""); + return $wtpl->get(); + } + // single month + else { + $wtpl = new \ilTemplate("tpl.blog_list_navigation_month.html", true, true, "components/ILIAS/Blog"); + + $ctrl->setParameterByClass(\ilObjBlogGUI::class, "blpg", ""); + + $month_options = array(); + foreach ($items as $month => $postings) { + $month_name = $this->gui->presentation()->util()->getMonthPresentation((string) $month); + + $month_options[(string) $month] = $month_name; + + if ($month == $this->gui->standardRequest()->getMonth()) { + if (!$link_template) { + $ctrl->setParameterByClass(\ilObjBlogGUI::class, "bmn", (string) $month); + $month_url = $ctrl->getLinkTargetByClass(\ilObjBlogGUI::class, $list_cmd); + } else { + $month_url = $this->buildExportLink($link_template, "list", (string) $month); + } + + foreach ($postings as $id => $posting) { + $caption = $posting->getTitle(); + + if (!$link_template) { + $ctrl->setParameterByClass(\ilBlogPostingGUI::class, "bmn", (string) $month); + $ctrl->setParameterByClass(\ilBlogPostingGUI::class, "blpg", (string) $id); + $url = $ctrl->getLinkTargetByClass(\ilBlogPostingGUI::class, $posting_cmd); + } else { + $url = $this->buildExportLink($link_template, "posting", (string) $id); + } + + if (!$posting->isActive()) { + $wtpl->setVariable("NAV_ITEM_DRAFT", $lng->txt("blog_draft")); + } elseif ($settings->getApproval() && !$posting->isApproved()) { + $wtpl->setVariable("NAV_ITEM_APPROVAL", $lng->txt("blog_needs_approval")); + } + + $wtpl->setCurrentBlock("navigation_item"); + $wtpl->setVariable("NAV_ITEM_URL", $url); + $wtpl->setVariable("NAV_ITEM_CAPTION", $caption); + $wtpl->parseCurrentBlock(); + } + + $wtpl->setCurrentBlock("navigation_month_details"); + if ($blpg > 0) { + $wtpl->setVariable("NAV_MONTH", $month_name); + $wtpl->setVariable("URL_MONTH", $month_url); + } + $wtpl->parseCurrentBlock(); + } + } + + if ($blpg === 0) { + $wtpl->setCurrentBlock("option_bl"); + foreach ($month_options as $value => $caption) { + $wtpl->setVariable("OPTION_VALUE", $value); + $wtpl->setVariable("OPTION_CAPTION", $caption); + if ($value == $this->gui->standardRequest()->getMonth()) { + $wtpl->setVariable("OPTION_SEL", ' selected="selected"'); + } + $wtpl->parseCurrentBlock(); + } + + $wtpl->setVariable("FORM_ACTION", $ctrl->getFormActionByClass(\ilObjBlogGUI::class, $list_cmd)); + } + } + $ctrl->setParameterByClass(\ilObjBlogGUI::class, "bmn", $this->gui->standardRequest()->getMonth()); + $ctrl->setParameterByClass(\ilBlogPostingGUI::class, "bmn", ""); + return $wtpl->get(); + } + + protected function buildExportLink( + string $template, + string $type, + string $id + ): string { + $blog_export = new \ILIAS\Blog\Export\BlogHtmlExport($this->gui->standardRequest()->getRefId()); + // Note: this might need adjustment since the original used $this->getKeywords(false) + // For now we assume keywords are not needed for these links or handled elsewhere + return $blog_export->buildExportLink($template, $type, $id, []); + } +} diff --git a/components/ILIAS/Blog/Navigation/Service/class.GUIService.php b/components/ILIAS/Blog/Navigation/Service/class.GUIService.php index e89279158f08..d420f56023c6 100755 --- a/components/ILIAS/Blog/Navigation/Service/class.GUIService.php +++ b/components/ILIAS/Blog/Navigation/Service/class.GUIService.php @@ -43,4 +43,28 @@ public function toolbarNavigationRenderer( $this->gui ); } + + public function monthBlock(): MonthBlockGUI + { + return new MonthBlockGUI( + $this->domain, + $this->gui + ); + } + + public function authorBlock(): AuthorBlockGUI + { + return new AuthorBlockGUI( + $this->domain, + $this->gui + ); + } + + public function keywordBlock(): KeywordBlockGUI + { + return new KeywordBlockGUI( + $this->domain, + $this->gui + ); + } } diff --git a/components/ILIAS/Blog/News/class.NewsManager.php b/components/ILIAS/Blog/News/class.NewsManager.php index b6b863237105..59227a090de8 100644 --- a/components/ILIAS/Blog/News/class.NewsManager.php +++ b/components/ILIAS/Blog/News/class.NewsManager.php @@ -29,11 +29,15 @@ */ class NewsManager { + protected \ILIAS\Blog\Posting\Service\GUIService $posting_gui; + public function __construct( protected InternalDataService $data, protected InternalRepoService $repo, - protected InternalDomainService $domain + protected InternalDomainService $domain, + \ILIAS\Blog\InternalGUIService $gui ) { + $this->posting_gui = $gui->posting(); } /** @@ -110,7 +114,7 @@ public function handle(\ilBlogPosting $page, bool $update = false): void $news_item->setContentTextIsLangVar(false); $news_item->setContent($content); - $snippet = \ilBlogPostingGUI::getSnippet($page->getId()); + $snippet = $this->posting_gui->getSnippet($page->getId()); $news_item->setContentLong($snippet); if (!$news_item->getId()) { diff --git a/components/ILIAS/Blog/PermanentLink/StaticUrlHandler.php b/components/ILIAS/Blog/PermanentLink/StaticUrlHandler.php index f2e3ea3b9f5c..2a0261e113f7 100644 --- a/components/ILIAS/Blog/PermanentLink/StaticUrlHandler.php +++ b/components/ILIAS/Blog/PermanentLink/StaticUrlHandler.php @@ -29,6 +29,15 @@ class StaticURLHandler extends BaseHandler implements Handler { + protected \ILIAS\Blog\InternalService $blog_service; + + public function __construct() + { + global $DIC; + $this->blog_service = $DIC->blog()->internal(); + parent::__construct(); + } + public function getNamespace(): string { return 'blog'; @@ -36,12 +45,11 @@ public function getNamespace(): string public function handle(Request $request, Context $context, Factory $response_factory): Response { - global $DIC; - - $ctrl = $DIC->ctrl(); - $access = $DIC->access(); + $blog_service = $this->blog_service; + $ctrl = $blog_service->gui()->ctrl(); + $access = $blog_service->domain()->access(); $uri = null; - $blog_domain = $DIC->blog()->internal()->domain(); + $blog_domain = $blog_service->domain(); $id = $request->getReferenceId()?->toInt() ?? 0; $additional_params = $request->getAdditionalParameters() ?? []; diff --git a/components/ILIAS/Blog/Posting/Service/class.GUIService.php b/components/ILIAS/Blog/Posting/Service/class.GUIService.php new file mode 100644 index 000000000000..8e90ca73064a --- /dev/null +++ b/components/ILIAS/Blog/Posting/Service/class.GUIService.php @@ -0,0 +1,83 @@ +postingGUI(0, null, $a_id); + return $bpgui->getSnippet( + $a_truncate, + $a_truncate_length, + $a_truncate_sign, + $a_include_picture, + $a_picture_width, + $a_picture_height, + $a_export_directory + ); + } +} diff --git a/components/ILIAS/Blog/Posting/class.ilBlogPosting.php b/components/ILIAS/Blog/Posting/class.ilBlogPosting.php index 9e50d67ba854..e30778d3d707 100755 --- a/components/ILIAS/Blog/Posting/class.ilBlogPosting.php +++ b/components/ILIAS/Blog/Posting/class.ilBlogPosting.php @@ -28,6 +28,7 @@ */ class ilBlogPosting extends ilPageObject { + protected \ILIAS\Blog\Posting\Service\GUIService $posting_gui; protected string $title = ""; protected ?ilDateTime $created = null; protected int $blog_node_id = 0; @@ -45,6 +46,8 @@ public function afterConstructor(): void $this->internal_data = $DIC->blog()->internal()->data(); $this->posting_manager = $DIC->blog()->internal()->domain()->posting(); $this->blog_domain = $DIC->blog()->internal()->domain(); + $this->posting_gui = $DIC->blog()->internal()->gui()->posting(); + } public function getParentType(): string @@ -254,27 +257,6 @@ public function unpublish(): void ); } - public static function lookupBlogId( - int $a_posting_id - ): ?int { - global $DIC; - return $DIC->blog()->internal()->domain()->posting()->lookupBlogId($a_posting_id); - } - - /** - * Checks whether a posting exists - */ - public static function exists( - int $a_blog_id, - int $a_posting_id - ): bool { - global $DIC; - return $DIC->blog()->internal()->domain()->posting()->exists( - $a_blog_id, - $a_posting_id - ); - } - /** * Set blog node id (needed for notification) */ @@ -288,7 +270,7 @@ public function setBlogNodeId( public function getNotificationAbstract(): string { - $snippet = ilBlogPostingGUI::getSnippet($this->getId(), true); + $snippet = $this->posting_gui->getSnippet($this->getId(), true); // making things more readable $snippet = str_replace(array('
', '
', '

', ''), "\n", $snippet); diff --git a/components/ILIAS/Blog/Posting/class.ilBlogPostingGUI.php b/components/ILIAS/Blog/Posting/class.ilBlogPostingGUI.php index 967bf110d875..af3fc1ae46a3 100755 --- a/components/ILIAS/Blog/Posting/class.ilBlogPostingGUI.php +++ b/components/ILIAS/Blog/Posting/class.ilBlogPostingGUI.php @@ -44,6 +44,7 @@ class ilBlogPostingGUI extends ilPageObjectGUI protected ilLocatorGUI $locator; protected ilSetting $settings; protected LOMServices $lom_services; + protected ilToolbarGUI $toolbar; protected int $node_id; protected PostingManager $posting_manager; protected ?object $access_handler = null; @@ -114,6 +115,7 @@ public function __construct( $this->notes = $DIC->notes(); $this->profile_gui = $DIC->blog()->internal()->gui()->profile(); $this->posting_manager = $DIC->blog()->internal()->domain()->posting(); + $this->toolbar = $DIC->toolbar(); } public function executeCommand(): string @@ -179,12 +181,11 @@ protected function checkAccess(string $a_cmd): bool public function preview( ?string $a_mode = null ): string { - global $DIC; $ilCtrl = $this->ctrl; $tpl = $this->tpl; $ilSetting = $this->settings; - $toolbar = $DIC->toolbar(); + $toolbar = $this->toolbar; $append = ""; $this->getBlogPosting()->increaseViewCnt(); @@ -628,9 +629,7 @@ public function activatePage(bool $a_to_list = false): void */ public function editKeywords(): void { - global $DIC; - - $renderer = $DIC->ui()->renderer(); + $renderer = $this->blog_gui->ui()->renderer(); $ilTabs = $this->tabs; $tpl = $this->tpl; @@ -652,9 +651,7 @@ public function editKeywords(): void */ protected function initKeywordsForm(): \ILIAS\UI\Component\Input\Container\Form\Standard { - global $DIC; - - $ui_factory = $DIC->ui()->factory(); + $ui_factory = $this->blog_gui->ui()->factory(); $keywords = $this->posting_manager->getKeywords( $this->getBlogPosting()->getBlogId(), @@ -681,7 +678,7 @@ protected function initKeywordsForm(): \ILIAS\UI\Component\Input\Container\Form\ $input_tag = $input_tag->withValue($keywords); } - $DIC->ctrl()->setParameter( + $this->ctrl->setParameter( $this, 'tags', 'tags_processing' @@ -689,7 +686,7 @@ protected function initKeywordsForm(): \ILIAS\UI\Component\Input\Container\Form\ $section = $ui_factory->input()->field()->section([$input_tag], $this->lng->txt("blog_edit_keywords"), ""); - $form_action = $DIC->ctrl()->getFormAction($this, "saveKeywordsForm"); + $form_action = $this->ctrl->getFormAction($this, "saveKeywordsForm"); return $ui_factory->input()->container()->form()->standard($form_action, ["tags" => $section]); } @@ -707,9 +704,7 @@ protected function getParentObjId(): int public function saveKeywordsForm(): void { - global $DIC; - - $request = $DIC->http()->request(); + $request = $this->blog_gui->http()->request(); $form = $this->initKeywordsForm(); if ($request->getMethod() === "POST" @@ -735,8 +730,7 @@ public function saveKeywordsForm(): void /** * Get first text paragraph of page */ - public static function getSnippet( - int $a_id, + public function getSnippet( bool $a_truncate = false, int $a_truncate_length = 500, string $a_truncate_sign = "...", @@ -745,25 +739,23 @@ public static function getSnippet( int $a_picture_height = 144, ?string $a_export_directory = null ): string { - $bpgui = new self(0, null, $a_id); - // scan the full page for media objects $img = ""; if ($a_include_picture) { - $img = $bpgui->getFirstMediaObjectAsTag($a_picture_width, $a_picture_height, $a_export_directory); + $img = $this->getFirstMediaObjectAsTag($a_picture_width, $a_picture_height, $a_export_directory); } - $bpgui->setRawPageContent(true); - $bpgui->setAbstractOnly(true); + $this->setRawPageContent(true); + $this->setAbstractOnly(true); // #8627: export won't work - should we set offline mode? - $bpgui->setFileDownloadLink("."); - $bpgui->setFullscreenLink("."); - $bpgui->setSourcecodeDownloadScript("."); - $bpgui->setProfileBackUrl("."); + $this->setFileDownloadLink("."); + $this->setFullscreenLink("."); + $this->setSourcecodeDownloadScript("."); + $this->setProfileBackUrl("."); // render without title - $page = $bpgui->showPage(); + $page = $this->showPage(); if ($a_truncate) { $page = ilPageObject::truncateHTML($page, $a_truncate_length, $a_truncate_sign); @@ -808,7 +800,7 @@ protected function getFirstMediaObjectAsTag( // see ilCOPageHTMLExport::exportHTMLMOB() $mob_dir = "./mobs/mm_" . $mob_obj->getId(); } - $mob_res = self::parseImage( + $mob_res = $this->parseImage( $mob_size["width"], $mob_size["height"], $a_width, @@ -831,7 +823,7 @@ protected function getFirstMediaObjectAsTag( return ""; } - protected static function parseImage( + protected function parseImage( int $src_width, int $src_height, int $tgt_width, diff --git a/components/ILIAS/Blog/RSS/RSSGUI.php b/components/ILIAS/Blog/RSS/RSSGUI.php index 8ed3896f0d09..bd54c3a326e5 100644 --- a/components/ILIAS/Blog/RSS/RSSGUI.php +++ b/components/ILIAS/Blog/RSS/RSSGUI.php @@ -1,6 +1,20 @@

"); + $snippet = strip_tags($this->gui->posting()->getSnippet($id), "

"); $snippet = str_replace("&", "&", $snippet); $snippet = ""; diff --git a/components/ILIAS/Blog/Service/class.InternalDomainService.php b/components/ILIAS/Blog/Service/class.InternalDomainService.php index 55b2141439c3..8aa784d42057 100755 --- a/components/ILIAS/Blog/Service/class.InternalDomainService.php +++ b/components/ILIAS/Blog/Service/class.InternalDomainService.php @@ -119,7 +119,8 @@ public function news(): NewsManager return self::$instance["news"] ??= new NewsManager( $this->data, $this->repo, - $this + $this, + $this->dic->blog()->internal()->gui() ); } diff --git a/components/ILIAS/Blog/Service/class.InternalGUIService.php b/components/ILIAS/Blog/Service/class.InternalGUIService.php index 3d616e8ef350..ddc5461168db 100755 --- a/components/ILIAS/Blog/Service/class.InternalGUIService.php +++ b/components/ILIAS/Blog/Service/class.InternalGUIService.php @@ -25,6 +25,7 @@ use ILIAS\PermanentLink\PermanentLinkManager; use ILIAS\Blog\ReadingTime\GUIService; use ILIAS\Blog\RSS\RSSGUI; +use ILIAS\Blog\Posting\Service\GUIService as PostingGUIService; class InternalGUIService { @@ -42,10 +43,11 @@ public function __construct( public function navigation(): Navigation\GUIService { - return new Navigation\GUIService( - $this->domain_service, - $this - ); + return self::$instance["navigation"] ?? + self::$instance["navigation"] = new Navigation\GUIService( + $this->domain_service, + $this + ); } public function presentation(): Presentation\GUIService @@ -66,11 +68,12 @@ public function standardRequest(): StandardGUIRequest public function contributor(): Contributor\GUIService { - return new Contributor\GUIService( - $this->data_service, - $this->domain_service, - $this - ); + return self::$instance["contributor"] ?? + self::$instance["contributor"] = new Contributor\GUIService( + $this->data_service, + $this->domain_service, + $this + ); } public function exercise(): Exercise\GUIService @@ -114,6 +117,15 @@ public function readingTime(): GUIService ); } + public function posting(): PostingGUIService + { + return self::$instance["posting"] ??= new PostingGUIService( + $this->data_service, + $this->domain_service, + $this + ); + } + public function rss(): RSSGUI { return self::$instance["rss"] ??= new RSSGUI( diff --git a/components/ILIAS/Blog/Tasks/classes/class.ilBlogDraftsDerivedTaskProvider.php b/components/ILIAS/Blog/Tasks/classes/class.ilBlogDraftsDerivedTaskProvider.php index 800be9b2ff13..49f0c0a3c3aa 100755 --- a/components/ILIAS/Blog/Tasks/classes/class.ilBlogDraftsDerivedTaskProvider.php +++ b/components/ILIAS/Blog/Tasks/classes/class.ilBlogDraftsDerivedTaskProvider.php @@ -37,8 +37,9 @@ public function __construct( ) { global $DIC; - $this->gui = $DIC->blog()->internal()->gui(); - $this->domain = $DIC->blog()->internal()->domain(); + $service = $DIC->blog()->internal(); + $this->gui = $service->gui(); + $this->domain = $service->domain(); $this->taskService = $taskService; $this->accessHandler = $accessHandler; $this->lng = $lng; diff --git a/components/ILIAS/Blog/classes/class.ilBlogNewsRendererGUI.php b/components/ILIAS/Blog/classes/class.ilBlogNewsRendererGUI.php index 3275e896e556..32dfa9f6ddc2 100755 --- a/components/ILIAS/Blog/classes/class.ilBlogNewsRendererGUI.php +++ b/components/ILIAS/Blog/classes/class.ilBlogNewsRendererGUI.php @@ -18,17 +18,21 @@ declare(strict_types=1); -/** - * Blog news renderer - * @author Alexander Killing - */ class ilBlogNewsRendererGUI extends ilNewsDefaultRendererGUI { - public function getObjectLink(): string + protected \ILIAS\Blog\InternalGUIService $blog_gui; + + public function __construct() { global $DIC; + parent::__construct(); + $service = $DIC->blog()->internal(); + $this->blog_gui = $service->gui(); + } - $pl = $DIC->blog()->internal()->gui()->permanentLink($this->getNewsRefId()); + public function getObjectLink(): string + { + $pl = $this->blog_gui->permanentLink($this->getNewsRefId()); $n = $this->getNewsItem(); $posting_id = 0; diff --git a/components/ILIAS/Blog/classes/class.ilObjBlog.php b/components/ILIAS/Blog/classes/class.ilObjBlog.php index 27da54a1f0b0..90092326f992 100755 --- a/components/ILIAS/Blog/classes/class.ilObjBlog.php +++ b/components/ILIAS/Blog/classes/class.ilObjBlog.php @@ -44,8 +44,9 @@ public function __construct( ) { global $DIC; + $service = $DIC->blog()->internal(); $this->notes_service = $DIC->notes(); - $this->settings_manager = $DIC->blog()->internal()->domain()->blogSettings(); + $this->settings_manager = $service->domain()->blogSettings(); parent::__construct($a_id, $a_reference); $this->rbac_review = $DIC->rbac()->review(); @@ -57,7 +58,7 @@ public function __construct( if ($this->getId() > 0) { $this->blog_settings = $this->settings_manager->getByObjId($this->getId()); } - $this->posting_manager = $DIC->blog()->internal()->domain()->posting(); + $this->posting_manager = $service->domain()->posting(); } protected function initType(): void diff --git a/components/ILIAS/Blog/classes/class.ilObjBlogGUI.php b/components/ILIAS/Blog/classes/class.ilObjBlogGUI.php index 08e5be07df37..11e4381f6043 100755 --- a/components/ILIAS/Blog/classes/class.ilObjBlogGUI.php +++ b/components/ILIAS/Blog/classes/class.ilObjBlogGUI.php @@ -30,15 +30,17 @@ use ILIAS\Blog\Settings\Settings; use ILIAS\Blog\ReadingTime\ReadingTimeManager; use ILIAS\Blog\Posting\PostingManager; +use ILIAS\Blog\Contributor\ContributorGUI; /** * @ilCtrl_Calls ilObjBlogGUI: ilBlogPostingGUI, ilWorkspaceAccessGUI * @ilCtrl_Calls ilObjBlogGUI: ilInfoScreenGUI, ilNoteGUI, ilCommonActionDispatcherGUI - * @ilCtrl_Calls ilObjBlogGUI: ilPermissionGUI, ilObjectCopyGUI, ilRepositorySearchGUI + * @ilCtrl_Calls ilObjBlogGUI: ilPermissionGUI, ilObjectCopyGUI * @ilCtrl_Calls ilObjBlogGUI: ilExportGUI, ilObjectContentStyleSettingsGUI, ilBlogExerciseGUI, ilObjNotificationSettingsGUI * @ilCtrl_Calls ilObjBlogGUI: ilObjectMetaDataGUI * @ilCtrl_Calls ilObjBlogGUI: ILIAS\Blog\Settings\SettingsGUI * @ilCtrl_Calls ilObjBlogGUI: ILIAS\Blog\Settings\BlockSettingsGUI + * @ilCtrl_Calls ilObjBlogGUI: ILIAS\Blog\Contributor\ContributorGUI */ class ilObjBlogGUI extends ilObject2GUI implements ilDesktopItemHandling { @@ -103,6 +105,7 @@ public function __construct( $this->rbac_review = $domain->rbac()->review(); $this->rbacadmin = $domain->rbac()->admin(); $this->lng = $domain->lng(); + $this->posting_manager = $domain->posting(); $gui = $service->gui(); $this->gui = $gui; @@ -134,12 +137,10 @@ public function __construct( $blog_page = $this->blog_request->getBlogPage(); if ($blog_page > 0 && - ilBlogPosting::lookupBlogId($blog_page) !== $this->object->getId()) { + $this->posting_manager->lookupBlogId($blog_page) !== $this->object->getId()) { throw new ilException("Posting ID does not match blog."); } - $this->posting_manager = $DIC->blog()->internal()->domain()->posting(); - $blog_id = 0; if ($this->object) { // gather postings by month @@ -296,7 +297,7 @@ protected function setTabs(): void $this->tabs_gui->addTab( "contributors", $lng->txt("blog_contributors"), - $this->ctrl->getLinkTarget($this, "contributors") + $this->ctrl->getLinkTargetByClass(ContributorGUI::class, "contributors") ); } @@ -509,18 +510,18 @@ public function executeCommand(): void $this->ctrl->forwardCommand($cp); break; - case 'ilrepositorysearchgui': + case strtolower(\ilRepositorySearchGUI::class): + $this->checkPermission("write"); $this->prepareOutput(); $ilTabs->activateTab("contributors"); - $rep_search = new ilRepositorySearchGUI(); - $rep_search->setTitle($this->lng->txt("blog_add_contributor")); - $rep_search->setCallback($this, 'addContributor', $this->object->getAllLocalRoles($this->node_id)); - $this->ctrl->setReturn($this, 'contributors'); - $this->ctrl->forwardCommand($rep_search); + $this->ctrl->forwardCommand($this->gui->contributor()->contributorGUI($this->node_id, $this->object)); break; case 'ilexportgui': - $this->showExportGUI(); + $this->prepareOutput(); + $this->tabs->activateTab("export"); + $exp_gui = new ilExportGUI($this); + $this->ctrl->forwardCommand($exp_gui); break; case "ilobjectcontentstylesettingsgui": @@ -582,6 +583,17 @@ public function executeCommand(): void $this->ctrl->forwardCommand($gui); break; + case strtolower(ContributorGUI::class): + $this->checkPermission("write"); + $this->prepareOutput(); + $ilTabs->activateTab("contributors"); + $gui = $this->gui->contributor()->contributorGUI( + $this->node_id, + $this->object + ); + $this->ctrl->forwardCommand($gui); + break; + case strtolower(\ILIAS\Blog\Settings\BlockSettingsGUI::class): $this->checkPermission("write"); $this->prepareOutput(); @@ -612,14 +624,6 @@ public function executeCommand(): void } } - protected function showExportGUI(): void - { - $this->prepareOutput(); - $this->tabs->activateTab("export"); - $exp_gui = new ilExportGUI($this); - $this->ctrl->forwardCommand($exp_gui); - } - protected function createExportFileWithComments(): void { $this->buildExportFile(true); @@ -1255,7 +1259,7 @@ public function renderList( $wtpl->parseCurrentBlock(); } - $snippet = ilBlogPostingGUI::getSnippet( + $snippet = $this->gui->posting()->getSnippet( $item_id, $this->blog_settings->getAbstractShorten(), $this->blog_settings->getAbstractShortenLength(), @@ -1371,339 +1375,6 @@ protected function buildExportLink( } - /** - * Build navigation by date block - */ - protected function renderNavigationByDate( - array $a_items, - string $a_list_cmd = "render", - string $a_posting_cmd = "preview", - ?string $a_link_template = null, - bool $a_show_inactive = false, - int $a_blpg = 0 - ): string { - $ilCtrl = $this->ctrl; - - $blpg = ($a_blpg > 0) - ? $a_blpg - : $this->blpg; - - - // gather page active status - foreach ($a_items as $month => $postings) { - foreach (array_keys($postings) as $id) { - $active = ilBlogPosting::_lookupActive($id, "blp"); - if (!$a_show_inactive && !$active) { - unset($a_items[$month][$id]); - } - } - if (!count($a_items[$month])) { - unset($a_items[$month]); - } - } - - // list month (incl. postings) - if ($this->blog_settings->getNavMode() === ilObjBlog::NAV_MODE_LIST || $a_link_template) { - $max_months = $this->blog_settings->getNavModeListMonths(); - - $wtpl = new ilTemplate("tpl.blog_list_navigation_by_date.html", true, true, "components/ILIAS/Blog"); - - $ilCtrl->setParameter($this, "blpg", ""); - - $counter = $mon_counter = $last_year = 0; - foreach ($a_items as $month => $postings) { - if (!$a_link_template && $max_months && $mon_counter >= $max_months) { - break; - } - - $add_year = false; - $year = substr($month, 0, 4); - if (!$last_year || $year != $last_year) { - // #13562 - $add_year = true; - $last_year = $year; - } - - $mon_counter++; - - $month_name = ilCalendarUtil::_numericMonthToString((int) substr($month, 5)); - if (!$a_link_template) { - $ilCtrl->setParameter($this, "bmn", $month); - $month_url = $ilCtrl->getLinkTarget($this, $a_list_cmd); - } else { - $month_url = $this->buildExportLink($a_link_template, "list", (string) $month); - } - - // list postings for month - //if($counter < $max_detail_postings) - if ($mon_counter <= $this->blog_settings->getNavModeListMonthsWithPostings()) { - if ($add_year) { - $wtpl->setCurrentBlock("navigation_year_details"); - $wtpl->setVariable("YEAR", $year); - $wtpl->parseCurrentBlock(); - } - - foreach ($postings as $id => $posting) { - //if($max_detail_postings && $counter >= $max_detail_postings) - //{ - // break; - //} - - $counter++; - - $caption = /* ilDatePresentation::formatDate($posting["created"], IL_CAL_DATETIME). - ", ".*/ $posting->getTitle(); - - if (!$a_link_template) { - $ilCtrl->setParameterByClass("ilblogpostinggui", "bmn", $month); - $ilCtrl->setParameterByClass("ilblogpostinggui", "blpg", $id); - $url = $ilCtrl->getLinkTargetByClass("ilblogpostinggui", $a_posting_cmd); - } else { - $url = $this->buildExportLink($a_link_template, "posting", (string) $id); - } - - if (!$posting->isActive()) { - $wtpl->setVariable("NAV_ITEM_DRAFT", $this->lng->txt("blog_draft")); - } elseif ($this->blog_settings->getApproval() && !$posting->isApproved()) { - $wtpl->setVariable("NAV_ITEM_APPROVAL", $this->lng->txt("blog_needs_approval")); - } - - $wtpl->setCurrentBlock("navigation_item"); - $wtpl->setVariable("NAV_ITEM_URL", $url); - $wtpl->setVariable("NAV_ITEM_CAPTION", $caption); - $wtpl->parseCurrentBlock(); - } - - $wtpl->setCurrentBlock("navigation_month_details"); - $wtpl->setVariable("NAV_MONTH", $month_name); - $wtpl->setVariable("URL_MONTH", $month_url); - } - // summarized month - else { - if ($add_year) { - $wtpl->setCurrentBlock("navigation_year"); - $wtpl->setVariable("YEAR", $year); - $wtpl->parseCurrentBlock(); - } - - $wtpl->setCurrentBlock("navigation_month"); - $wtpl->setVariable("MONTH_NAME", $month_name); - $wtpl->setVariable("URL_MONTH", $month_url); - $wtpl->setVariable("MONTH_COUNT", count($postings)); - } - $wtpl->parseCurrentBlock(); - } - if (!$a_link_template) { - $this->ctrl->setParameterByClass(self::class, "bmn", null); - $url = $this->ctrl->getLinkTargetByClass(self::class, $a_list_cmd); - } else { - $url = "index.html"; - } - - $wtpl->setVariable( - "STARTING_PAGE", - $this->ui->renderer()->render( - $this->ui->factory()->link()->standard( - $this->lng->txt("blog_starting_page"), - $url - ) - ) - ); - } - // single month - else { - $wtpl = new ilTemplate("tpl.blog_list_navigation_month.html", true, true, "components/ILIAS/Blog"); - - $ilCtrl->setParameter($this, "blpg", ""); - - $month_options = array(); - foreach ($a_items as $month => $postings) { - $month_name = $this->gui->presentation()->util()->getMonthPresentation($month); - - $month_options[$month] = $month_name; - - if ($month == $this->month) { - if (!$a_link_template) { - $ilCtrl->setParameter($this, "bmn", $month); - $month_url = $ilCtrl->getLinkTarget($this, $a_list_cmd); - } else { - $month_url = $this->buildExportLink($a_link_template, "list", (string) $month); - } - - foreach ($postings as $id => $posting) { - $caption = /* ilDatePresentation::formatDate($posting["created"], IL_CAL_DATETIME). - ", ".*/ $posting["title"]; - - if (!$a_link_template) { - $ilCtrl->setParameterByClass("ilblogpostinggui", "bmn", $month); - $ilCtrl->setParameterByClass("ilblogpostinggui", "blpg", $id); - $url = $ilCtrl->getLinkTargetByClass("ilblogpostinggui", $a_posting_cmd); - } else { - $url = $this->buildExportLink($a_link_template, "posting", (string) $id); - } - - if (!$posting->isActive()) { - $wtpl->setVariable("NAV_ITEM_DRAFT", $this->lng->txt("blog_draft")); - } elseif ($this->blog_settings->getApproval() && !$posting["approved"]) { - $wtpl->setVariable("NAV_ITEM_APPROVAL", $this->lng->txt("blog_needs_approval")); - } - - $wtpl->setCurrentBlock("navigation_item"); - $wtpl->setVariable("NAV_ITEM_URL", $url); - $wtpl->setVariable("NAV_ITEM_CAPTION", $caption); - $wtpl->parseCurrentBlock(); - } - - $wtpl->setCurrentBlock("navigation_month_details"); - if ($blpg > 0) { - $wtpl->setVariable("NAV_MONTH", $month_name); - $wtpl->setVariable("URL_MONTH", $month_url); - } - $wtpl->parseCurrentBlock(); - } - } - - if ($blpg === 0) { - $wtpl->setCurrentBlock("option_bl"); - foreach ($month_options as $value => $caption) { - $wtpl->setVariable("OPTION_VALUE", $value); - $wtpl->setVariable("OPTION_CAPTION", $caption); - if ($value == $this->month) { - $wtpl->setVariable("OPTION_SEL", ' selected="selected"'); - } - $wtpl->parseCurrentBlock(); - } - - $wtpl->setVariable("FORM_ACTION", $ilCtrl->getFormAction($this, $a_list_cmd)); - } - } - $ilCtrl->setParameter($this, "bmn", $this->month); - $ilCtrl->setParameterByClass("ilblogpostinggui", "bmn", ""); - return $wtpl->get(); - } - - /** - * Build navigation by keywords block - */ - protected function renderNavigationByKeywords( - string $a_list_cmd = "render", - bool $a_show_inactive = false, - string $a_link_template = "", - int $a_blpg = 0 - ): string { - $ilCtrl = $this->ctrl; - - $blpg = ($a_blpg > 0) - ? $a_blpg - : $this->blpg; - - $keywords = $this->getKeywords($a_show_inactive, $blpg); - if ($keywords) { - $wtpl = new ilTemplate("tpl.blog_list_navigation_keywords.html", true, true, "components/ILIAS/Blog"); - - $max = max($keywords); - - $wtpl->setCurrentBlock("keyword"); - foreach ($keywords as $keyword => $counter) { - if (!$a_link_template) { - $ilCtrl->setParameter($this, "kwd", urlencode((string) $keyword)); // #15885 - $url = $ilCtrl->getLinkTarget($this, $a_list_cmd); - $ilCtrl->setParameter($this, "kwd", ""); - } else { - $url = $this->buildExportLink($a_link_template, "keyword", (string) $keyword); - } - - $wtpl->setVariable("TXT_KEYWORD", $keyword); - $wtpl->setVariable("CLASS_KEYWORD", ilTagging::getRelevanceClass($counter, $max)); - $wtpl->setVariable("URL_KEYWORD", $url); - $wtpl->parseCurrentBlock(); - } - - return $wtpl->get(); - } - return ""; - } - - protected function renderNavigationByAuthors( - array $a_items, - string $a_list_cmd = "render", - bool $a_show_inactive = false - ): string { - $ilCtrl = $this->ctrl; - - $authors = array(); - foreach ($a_items as $month => $items) { - foreach ($items as $item) { - /** @var \ILIAS\Blog\Posting\Posting $item */ - $item_id = $item->getId(); - if (($a_show_inactive || ilBlogPosting::_lookupActive($item_id, "blp"))) { - $author_id = $item->getAuthor(); - if ($author_id) { - $authors[] = $author_id; - } - foreach (\ilPageObject::getPageContributors("blp", $item_id) as $editor) { - $editor_id = (int) $editor["user_id"]; - if ($editor_id !== $author_id) { - $authors[] = $editor_id; - } - } - } - } - } - - $authors = array_unique($authors); - - // filter out deleted users - $authors = array_filter($authors, function ($id) { - return ilObject::_lookupType($id) == "usr"; - }); - - if (count($authors) > 1) { - $list = array(); - foreach ($authors as $user_id) { - if ($user_id) { - $ilCtrl->setParameter($this, "ath", $user_id); - $url = $ilCtrl->getLinkTarget($this, $a_list_cmd); - $ilCtrl->setParameter($this, "ath", ""); - - $base_name = ilUserUtil::getNamePresentation($user_id); - if (str_starts_with($base_name, "[")) { - $name = ilUserUtil::getNamePresentation($user_id, true); - $sort = $name; - } else { - $name = ilUserUtil::getNamePresentation( - $user_id, - true, - false, - "", - false, - true, - false - ); - $name_arr = ilObjUser::_lookupName($user_id); - $sort = $name_arr["lastname"] . " " . $name_arr["firstname"]; - } - - $idx = trim(strip_tags($sort)) . "///" . $user_id; // #10934 - $list[$idx] = array($name, $url); - } - } - ksort($list); - - $wtpl = new ilTemplate("tpl.blog_list_navigation_authors.html", true, true, "components/ILIAS/Blog"); - - $wtpl->setCurrentBlock("author"); - foreach ($list as $author) { - $wtpl->setVariable("TXT_AUTHOR", $author[0]); - $wtpl->setVariable("URL_AUTHOR", $author[1]); - $wtpl->parseCurrentBlock(); - } - - return $wtpl->get(); - } - return ""; - } - /** * Toolbar navigation */ @@ -1756,7 +1427,14 @@ public function renderNavigation( if (count($a_items)) { $blocks[$order["navigation"] ?? 0] = array( $this->lng->txt("blog_navigation"), - $this->renderNavigationByDate($a_items, $a_list_cmd, $a_posting_cmd, $a_link_template, $a_show_inactive, $a_blpg) + $this->gui->navigation()->monthBlock()->render( + $a_items, + $a_list_cmd, + $a_posting_cmd, + $a_link_template, + $a_show_inactive, + $a_blpg + ) ); } @@ -1767,7 +1445,13 @@ public function renderNavigation( $a_list_cmd !== "preview" && $a_list_cmd !== "gethtml" && !$a_link_template); - $keywords = $this->renderNavigationByKeywords($a_list_cmd, $a_show_inactive, (string) $a_link_template, $a_blpg); + $keywords = $this->gui->navigation()->keywordBlock()->render( + $a_items, + $a_list_cmd, + $a_show_inactive, + (string) $a_link_template, + $a_blpg + ); if ($keywords || $may_edit_keywords) { if (!$keywords) { $keywords = $this->lng->txt("blog_no_keywords"); @@ -1788,7 +1472,11 @@ public function renderNavigation( // authors if ($this->id_type === self::REPOSITORY_NODE_ID && $this->blog_settings->getAuthors()) { - $authors = $this->renderNavigationByAuthors($a_items, $a_list_cmd, $a_show_inactive); + $authors = $this->gui->navigation()->authorBlock()->render( + $a_items, + $a_list_cmd, + $a_show_inactive + ); if ($authors) { $blocks[$order["authors"] ?? 1] = array($this->lng->txt("blog_authors"), $authors); } @@ -1811,10 +1499,8 @@ public function renderNavigation( } if (count($blocks)) { - global $DIC; - - $ui_factory = $DIC->ui()->factory(); - $ui_renderer = $DIC->ui()->renderer(); + $ui_factory = $this->ui->factory(); + $ui_renderer = $this->ui->renderer(); ksort($blocks); foreach ($blocks as $block) { @@ -2130,209 +1816,6 @@ public function approve(): void } - // - // contributors - // - - public function contributors(): void - { - $ilTabs = $this->tabs; - $ilToolbar = $this->toolbar; - $ilCtrl = $this->ctrl; - $lng = $this->lng; - $tpl = $this->tpl; - - if (!$this->checkPermissionBool("write")) { - return; - } - - $ilTabs->activateTab("contributors"); - - $local_roles = $this->object->getAllLocalRoles($this->node_id); - - // add member - ilRepositorySearchGUI::fillAutoCompleteToolbar( - $this, - $ilToolbar, - array( - 'auto_complete_name' => $lng->txt('user'), - 'submit_name' => $lng->txt('add'), - 'add_search' => true, - 'add_from_container' => $this->node_id, - 'user_type' => $local_roles - ), - true - ); - - $other_roles = $this->object->getRolesWithContributeOrRedact($this->node_id); - if ($other_roles) { - $this->tpl->setOnScreenMessage('info', sprintf($lng->txt("blog_contribute_other_roles"), implode(", ", $other_roles))); - } - - $table = $this->gui->contributor()->contributorTableBuilder( - $this->object->getAllLocalRoles($this->node_id), - $this, - "contributors" - )->getTable(); - - if ($table->handleCommand()) { - return; - } - - $tpl->setContent($table->render()); - } - - /** - * Autocomplete submit - */ - public function addUserFromAutoComplete(): void - { - $lng = $this->lng; - - $user_login = $this->blog_request->getUserLogin(); - $user_type = $this->blog_request->getUserType(); - - if (trim($user_login) === '') { - $this->tpl->setOnScreenMessage('failure', $lng->txt('msg_no_search_string')); - $this->contributors(); - return; - } - $users = explode(',', $user_login); - - $user_ids = array(); - foreach ($users as $user) { - $user_id = ilObjUser::_lookupId($user); - - if (!$user_id) { - $this->tpl->setOnScreenMessage('failure', $lng->txt('user_not_known')); - $this->contributors(); - return; - } - - $user_ids[] = (int) $user_id; - } - - $this->addContributor($user_ids, $user_type); - } - - /** - * Centralized method to add contributors - */ - public function addContributor( - array $a_user_ids = array(), - ?string $a_user_type = null - ): void { - $ilCtrl = $this->ctrl; - $lng = $this->lng; - $rbacreview = $this->rbac_review; - $rbacadmin = $this->rbacadmin; - $a_user_type = (int) $a_user_type; - - if (!$this->checkPermissionBool("write")) { - return; - } - - if (!count($a_user_ids) || !$a_user_type) { - $this->tpl->setOnScreenMessage('failure', $lng->txt("no_checkbox")); - $this->contributors(); - return; - } - - // get contributor role - $local_roles = array_keys($this->object->getAllLocalRoles($this->node_id)); - if (!in_array($a_user_type, $local_roles)) { - $this->tpl->setOnScreenMessage('failure', $lng->txt("missing_perm")); - $this->contributors(); - return; - } - - foreach ($a_user_ids as $user_id) { - $user_id = (int) $user_id; - $a_user_type = (int) $a_user_type; - if (!$rbacreview->isAssigned($user_id, $a_user_type)) { - $rbacadmin->assignUser($a_user_type, $user_id); - } - } - - $this->tpl->setOnScreenMessage('success', $lng->txt("settings_saved"), true); - $ilCtrl->redirect($this, "contributors"); - } - - /** - * Used in ContributorTableBuilder - */ - public function confirmRemoveContributor(array $ids = []): void - { - if (empty($ids)) { - $ids = $this->blog_request->getIds(); - } - if (count($ids) === 0) { - $this->tpl->setOnScreenMessage('failure', $this->lng->txt("select_one"), true); - $this->ctrl->redirect($this, "contributors"); - } - - $confirm = new ilConfirmationGUI(); - $confirm->setHeaderText($this->lng->txt('blog_confirm_delete_contributors')); - $confirm->setFormAction($this->ctrl->getFormAction($this, 'removeContributor')); - $confirm->setConfirm($this->lng->txt('delete'), 'removeContributor'); - $confirm->setCancel($this->lng->txt('cancel'), 'contributors'); - - foreach ($ids as $user_id) { - $confirm->addItem( - 'id[]', - (string) $user_id, - $this->profile_gui->getNamePresentation($user_id, false, "", true) - ); - } - - $this->tpl->setContent($confirm->getHTML()); - } - - public function removeContributor(): void - { - $ilCtrl = $this->ctrl; - $lng = $this->lng; - $rbacadmin = $this->rbacadmin; - - $ids = $this->blog_request->getIds(); - - if (count($ids) === 0) { - $this->tpl->setOnScreenMessage('failure', $lng->txt("select_one"), true); - $ilCtrl->redirect($this, "contributors"); - } - - // get contributor role - $local_roles = array_keys($this->object->getAllLocalRoles($this->node_id)); - if (!$local_roles) { - $this->tpl->setOnScreenMessage('failure', $lng->txt("missing_perm")); - $this->contributors(); - return; - } - - foreach ($ids as $user_id) { - foreach ($local_roles as $role_id) { - $rbacadmin->deassignUser($role_id, $user_id); - } - } - - $this->tpl->setOnScreenMessage('success', $lng->txt("settings_saved"), true); - $this->ctrl->redirect($this, "contributors"); - } - - /** - * Used in ContributorTableBuilder - */ - public function addContributorContainerAction(array $ids = []): void - { - if (empty($ids)) { - $ids = $this->blog_request->getIds(); - } - - // This would typically add contributors from a container - // For now, redirecting back to contributors as this seems to be a placeholder action - $this->ctrl->redirect($this, "contributors"); - } - public function deactivateAdmin(): void { if ($this->checkPermissionBool("write") && $this->apid > 0) { diff --git a/components/ILIAS/Blog/classes/class.ilObjBlogListGUI.php b/components/ILIAS/Blog/classes/class.ilObjBlogListGUI.php index 6212950298f3..d0437edddf09 100755 --- a/components/ILIAS/Blog/classes/class.ilObjBlogListGUI.php +++ b/components/ILIAS/Blog/classes/class.ilObjBlogListGUI.php @@ -27,6 +27,14 @@ class ilObjBlogListGUI extends ilObjectListGUI { private ?Modal $comment_modal = null; + protected \ILIAS\Blog\InternalService $blog_service; + + public function __construct(int $context = self::CONTEXT_REPOSITORY) + { + global $DIC; + parent::__construct($context); + $this->blog_service = $DIC->blog()->internal(); + } public function init(): void { @@ -69,12 +77,10 @@ public function insertCommand( string $cmd = "", string $onclick = "" ): void { - global $DIC; + $blog_service = $this->blog_service; $tpl = $this->ui->mainTemplate(); - $export_possible = $DIC->blog() - ->internal() - ->domain() + $export_possible = $blog_service->domain() ->export() ->isCommentsExportPossible($this->obj_id); if ($cmd === "export" diff --git a/components/ILIAS/BookingManager/BookingProcess/class.ProcessUtilGUI.php b/components/ILIAS/BookingManager/BookingProcess/class.ProcessUtilGUI.php index 584281ab09f1..1ddab4e7782f 100755 --- a/components/ILIAS/BookingManager/BookingProcess/class.ProcessUtilGUI.php +++ b/components/ILIAS/BookingManager/BookingProcess/class.ProcessUtilGUI.php @@ -37,6 +37,8 @@ class ProcessUtilGUI protected InternalGUIService $gui; protected InternalDomainService $domain; protected object $parent_gui; + protected \ilLanguage $lng; + protected \ilGlobalTemplateInterface $tpl; public function __construct( InternalDomainService $domain_service, @@ -53,6 +55,8 @@ public function __construct( $this->ctrl = $this->gui->ctrl(); $this->request = $this->gui->standardRequest(); $this->objects_manager = $domain_service->objects($pool->getId()); + $this->lng = $domain_service->lng(); + $this->tpl = $this->gui->mainTemplate(); } // Back to parent @@ -126,38 +130,50 @@ protected function checkPermission(string $a_perm) : void public function handleBookingSuccess( int $a_obj_id, string $post_info_cmd, - ?array $a_rsv_ids = null + ?array $a_rsv_ids = null, + ?string $message = null ): void { - $this->log->debug("handleBookingSuccess"); - $main_tpl = $this->gui->mainTemplate(); - $ctrl = $this->gui->ctrl(); - $lng = $this->domain->lng(); - $request = $this->gui->standardRequest(); - - $main_tpl->setOnScreenMessage('success', $lng->txt('book_reservation_confirmed'), true); + $this->log->debug('handleBookingSuccess'); + $this->tpl->setOnScreenMessage( + 'success', + $message ?: $this->lng->txt('book_reservation_confirmed'), + true + ); + + $this->redirectToReturnCmdIfAsynchronous(); + $this->redirectToBookingInformationIfAvailable($a_obj_id, $post_info_cmd, $a_rsv_ids); + $this->redirectToRender(); + } - // show post booking information? + public function redirectToBookingInformationIfAvailable(int $a_obj_id, string $post_info_cmd, ?array $a_rsv_ids = null): void + { $obj = new \ilBookingObject($a_obj_id); - $pfile = $this->objects_manager->getBookingInfoFilename($a_obj_id); - $ptext = $obj->getPostText(); + if (trim($obj->getPostText()) === '' && $this->objects_manager->getBookingInfoFilename($a_obj_id) === '') { + return; + } - if (trim($ptext) || $pfile) { - if (count($a_rsv_ids)) { - $ctrl->setParameter( - $this->parent_gui, - 'rsv_ids', - implode(";", $a_rsv_ids) - ); - } - $ctrl->redirect($this->parent_gui, $post_info_cmd); - } else { - if ($ctrl->isAsynch()) { - $this->gui->send(""); - } else { - $this->back(); - } + $a_rsv_ids ??= []; + if ($a_rsv_ids !== []) { + $this->ctrl->setParameter($this->parent_gui, 'rsv_ids', implode(';', $a_rsv_ids)); } + $this->ctrl->redirect($this->parent_gui, $post_info_cmd); + } + + private function redirectToReturnCmdIfAsynchronous(): void + { + if (!$this->ctrl->isAsynch()) { + return; + } + + $this->gui->send( + "" + ); + } + + private function redirectToRender(): void + { + $this->ctrl->saveParameterByClass(\ilObjBookingPoolGUI::class, 'ref_id'); + $this->ctrl->redirectByClass(\ilObjBookingPoolGUI::class, 'render'); } /** diff --git a/components/ILIAS/BookingManager/BookingProcess/class.ilBookingProcessWithScheduleGUI.php b/components/ILIAS/BookingManager/BookingProcess/class.ilBookingProcessWithScheduleGUI.php index 79fe039b102f..696d42e5edc0 100755 --- a/components/ILIAS/BookingManager/BookingProcess/class.ilBookingProcessWithScheduleGUI.php +++ b/components/ILIAS/BookingManager/BookingProcess/class.ilBookingProcessWithScheduleGUI.php @@ -514,12 +514,13 @@ protected function bookAvailableItems(?int $recurrence = null, ?ilDateTime $unti $until, $message ); - if (count($booked) > 0) { - $this->util_gui->handleBookingSuccess($obj_id, "displayPostInfo", $booked); - } else { - $this->tpl->setOnScreenMessage('failure', $this->lng->txt('book_reservation_failed'), true); - $this->util_gui->back(); + if ($booked !== []) { + $this->util_gui->handleBookingSuccess($obj_id, 'displayPostInfo', $booked); + return; } + + $this->tpl->setOnScreenMessage('failure', $this->lng->txt('book_reservation_failed'), true); + $this->util_gui->back(); } protected function getBookAvailableTarget( diff --git a/components/ILIAS/BookingManager/BookingProcess/class.ilBookingProcessWithoutScheduleGUI.php b/components/ILIAS/BookingManager/BookingProcess/class.ilBookingProcessWithoutScheduleGUI.php index 384cb89f4dbc..1922cdee8e80 100755 --- a/components/ILIAS/BookingManager/BookingProcess/class.ilBookingProcessWithoutScheduleGUI.php +++ b/components/ILIAS/BookingManager/BookingProcess/class.ilBookingProcessWithoutScheduleGUI.php @@ -285,12 +285,12 @@ public function saveMultipleBookings(): void { $participants = $this->book_request->getParticipants(); $object_id = $this->book_request->getObjectId(); - if (count($participants) > 0 && $object_id > 0) { + if ($participants !== [] && $object_id > 0) { $this->book_obj_id = $object_id; } else { $this->util_gui->back(); } - $rsv_ids = array(); + $rsv_ids = []; foreach ($participants as $id) { if (!$this->access->canManageReservationForUser($this->pool->getRefId(), $id)) { $this->showNoPermission(); @@ -305,24 +305,29 @@ public function saveMultipleBookings(): void ); } - if (count($rsv_ids)) { - $this->tpl->setOnScreenMessage('success', "booking_multiple_succesfully"); - } else { - $this->tpl->setOnScreenMessage('failure', $this->lng->txt('book_reservation_failed_overbooked'), true); + if ($rsv_ids !== []) { + $this->util_gui->handleBookingSuccess( + $object_id, + 'displayPostInfo', + $rsv_ids, + $this->lng->txt('booking_multiple_succesfully') + ); + return; } + + $this->tpl->setOnScreenMessage('failure', $this->lng->txt('book_reservation_failed_overbooked'), true); $this->util_gui->back(); } - // // Step 2: Confirmation // // Book object - either of type or specific - for given dates - public function confirmedBooking($message = ""): bool + public function confirmedBooking($message = ''): void { $success = false; - $rsv_ids = array(); + $rsv_ids = []; if ($this->book_obj_id > 0) { $object_id = $this->book_obj_id; @@ -349,12 +354,12 @@ public function confirmedBooking($message = ""): bool } if ($success) { - $this->util_gui->handleBookingSuccess($success, "displayPostInfo", $rsv_ids); - } else { - $this->tpl->setOnScreenMessage('failure', $this->lng->txt('book_reservation_failed'), true); - $this->ctrl->redirect($this, 'book'); + $this->util_gui->handleBookingSuccess($success, 'displayPostInfo', $rsv_ids); + return; } - return true; + + $this->tpl->setOnScreenMessage('failure', $this->lng->txt('book_reservation_failed'), true); + $this->ctrl->redirect($this, 'book'); } public function displayPostInfo(): void diff --git a/components/ILIAS/BookingManager/BookingService/class.ilBookingGatewayGUI.php b/components/ILIAS/BookingManager/BookingService/class.ilBookingGatewayGUI.php index c6ad0f1f3255..ee95ae9ed325 100755 --- a/components/ILIAS/BookingManager/BookingService/class.ilBookingGatewayGUI.php +++ b/components/ILIAS/BookingManager/BookingService/class.ilBookingGatewayGUI.php @@ -174,7 +174,11 @@ public function executeCommand(): void case "ilbookingreservationsgui": $this->showPoolSelector("ilbookingreservationsgui"); $this->setSubTabs("reservations"); - $res_gui = new ilBookingReservationsGUI($this->pool, $this->help, $this->obj_id); + $res_gui = new ilBookingReservationsGUI( + $this->pool, + $this->help, + ilObject::_lookupType($this->obj_id) !== 'crs' ? $this->obj_id : null + ); $this->ctrl->forwardCommand($res_gui); break; diff --git a/components/ILIAS/BookingManager/Objects/ObjectEvent.php b/components/ILIAS/BookingManager/Objects/ObjectEvent.php new file mode 100644 index 000000000000..f51b65947bf1 --- /dev/null +++ b/components/ILIAS/BookingManager/Objects/ObjectEvent.php @@ -0,0 +1,48 @@ +getId(); + } catch (ilObjectTypeMismatchException) { + continue; + } + + foreach (ilBookingObject::getList($pool_id) as $booking_object) { + $booking_object_id = $booking_object['booking_object_id'] ?? null; + if ($booking_object_id === null) { + continue; + } + + (new ilBookingObject($booking_object_id))->deleteReservationsAndCalEntries($booking_object_id); + } + } + } +} diff --git a/components/ILIAS/BookingManager/Objects/class.ilBookBookingInfoStakeholder.php b/components/ILIAS/BookingManager/Objects/class.ilBookBookingInfoStakeholder.php index 54413bf58529..bae67fbdfaf3 100755 --- a/components/ILIAS/BookingManager/Objects/class.ilBookBookingInfoStakeholder.php +++ b/components/ILIAS/BookingManager/Objects/class.ilBookBookingInfoStakeholder.php @@ -33,6 +33,11 @@ public function getOwnerOfNewResources(): int return $this->default_owner; } + public function getConsumerNameForPresentation(): string + { + return parent::getConsumerNameForPresentation() . '/BookingInfo'; + } + public function canBeAccessedByCurrentUser(ResourceIdentification $identification): bool { global $DIC; diff --git a/components/ILIAS/BookingManager/Objects/class.ilBookObjectInfoStakeholder.php b/components/ILIAS/BookingManager/Objects/class.ilBookObjectInfoStakeholder.php index bcc4b6fac7d3..3b4efb35302f 100755 --- a/components/ILIAS/BookingManager/Objects/class.ilBookObjectInfoStakeholder.php +++ b/components/ILIAS/BookingManager/Objects/class.ilBookObjectInfoStakeholder.php @@ -34,6 +34,11 @@ public function getOwnerOfNewResources(): int return $this->default_owner; } + public function getConsumerNameForPresentation(): string + { + return parent::getConsumerNameForPresentation() . '/ObjectInfo'; + } + public function canBeAccessedByCurrentUser(ResourceIdentification $identification): bool { global $DIC; diff --git a/components/ILIAS/BookingManager/Reservations/class.ilBookingReservationsGUI.php b/components/ILIAS/BookingManager/Reservations/class.ilBookingReservationsGUI.php index a633795ca9cb..adacb94570a2 100755 --- a/components/ILIAS/BookingManager/Reservations/class.ilBookingReservationsGUI.php +++ b/components/ILIAS/BookingManager/Reservations/class.ilBookingReservationsGUI.php @@ -40,7 +40,7 @@ class ilBookingReservationsGUI protected array $raw_post_data; protected StandardGUIRequest $book_request; protected ilBookingHelpAdapter $help; - protected int $context_obj_id; + protected ?int $context_obj_id; protected ilCtrl $ctrl; protected ilGlobalTemplateInterface $tpl; protected ilLanguage $lng; @@ -53,7 +53,7 @@ class ilBookingReservationsGUI protected int $booked_user; protected ilUIService $ui_service; - public function __construct(ilObjBookingPool $pool, ilBookingHelpAdapter $help, int $context_obj_id = 0) + public function __construct(ilObjBookingPool $pool, ilBookingHelpAdapter $help, ?int $context_obj_id = null) { global $DIC; @@ -202,7 +202,7 @@ public function log(): void $this->access->canManageAllReservations($this->ref_id) || $this->pool->hasPublicLog(), $this->ui_service->filter()->getData($bookings_table->getFilter()) ?? [], null, - $this->context_obj_id > 0 ? [$this->context_obj_id] : null + $this->context_obj_id !== null ? [$this->context_obj_id] : null ); $reservations_table->getExportMode() > 0 && $reservations_table->exportData($reservations_table->getExportMode(), true); } diff --git a/components/ILIAS/BookingManager/Service/class.InternalDomainService.php b/components/ILIAS/BookingManager/Service/class.InternalDomainService.php index 2b59b3ad4a11..ab2fb7defe18 100755 --- a/components/ILIAS/BookingManager/Service/class.InternalDomainService.php +++ b/components/ILIAS/BookingManager/Service/class.InternalDomainService.php @@ -20,6 +20,7 @@ namespace ILIAS\BookingManager; +use ILIAS\BookingManager\Objects\ObjectEvent; use ILIAS\DI\Container; use ILIAS\Repository\GlobalDICDomainServices; use ILIAS\BookingManager\BookingProcess\BookingProcessManager; @@ -128,6 +129,11 @@ public function userEvent(): UserEvent return self::$instances["user_event"] ??= new UserEvent($this); } + public function objectEvent(): ObjectEvent + { + return new ObjectEvent(); + } + public function bookingSettings(): SettingsManager { return self::$instances["settings"] ??= new SettingsManager( diff --git a/components/ILIAS/BookingManager/classes/class.ilBookingManagerAppEventListener.php b/components/ILIAS/BookingManager/classes/class.ilBookingManagerAppEventListener.php index de3c29c33729..39344fef5569 100644 --- a/components/ILIAS/BookingManager/classes/class.ilBookingManagerAppEventListener.php +++ b/components/ILIAS/BookingManager/classes/class.ilBookingManagerAppEventListener.php @@ -42,6 +42,14 @@ public static function handleEvent( break; } break; + case "components/ILIAS/ILIASObject": + switch ($a_event) { + case "toTrash": + case "delete": + $DIC->bookingManager()->internal()->domain()->objectEvent()->handleDeletion([$a_parameter["ref_id"]]); + break; + } + break; } } } diff --git a/components/ILIAS/BookingManager/module.xml b/components/ILIAS/BookingManager/module.xml index a5f775ba7db2..a981b186622d 100755 --- a/components/ILIAS/BookingManager/module.xml +++ b/components/ILIAS/BookingManager/module.xml @@ -5,7 +5,7 @@ cat crs @@ -17,6 +17,7 @@ + diff --git a/components/ILIAS/COPage/Page/class.PageCommandActionHandler.php b/components/ILIAS/COPage/Page/class.PageCommandActionHandler.php index 018d7fdc46a1..feb1801a63ca 100755 --- a/components/ILIAS/COPage/Page/class.PageCommandActionHandler.php +++ b/components/ILIAS/COPage/Page/class.PageCommandActionHandler.php @@ -184,7 +184,7 @@ protected function dragDropCommand(array $body): Server\Response $source = explode(":", $source); $target = explode(":", $target); - $updated = $page->moveContentAfter($source[0], $target[0], $source[1], $target[1]); + $updated = $page->moveContentAfter($source[0], $target[0], $source[1] ?? '', $target[1] ?? ''); return $this->sendPage($updated); } diff --git a/components/ILIAS/COPage/classes/class.ilPageObject.php b/components/ILIAS/COPage/classes/class.ilPageObject.php index 9bdd0fb89a41..92a0d77988ce 100755 --- a/components/ILIAS/COPage/classes/class.ilPageObject.php +++ b/components/ILIAS/COPage/classes/class.ilPageObject.php @@ -75,7 +75,7 @@ abstract class ilPageObject public string $xml = ""; public string $encoding = ""; public DomNode $node; - public string $cur_dtd = "ilias_pg_9.dtd"; + public string $cur_dtd = "ilias_pg_12.dtd"; public bool $contains_int_link = false; public bool $needs_parsing = false; public string $parent_type = ""; diff --git a/components/ILIAS/COPage/classes/class.ilPageObjectGUI.php b/components/ILIAS/COPage/classes/class.ilPageObjectGUI.php index e933fc001306..61f69d26a913 100755 --- a/components/ILIAS/COPage/classes/class.ilPageObjectGUI.php +++ b/components/ILIAS/COPage/classes/class.ilPageObjectGUI.php @@ -930,6 +930,7 @@ public function executeCommand(): string // load required lang mods $this->lng->loadLanguageModule("assessment"); + $this->lng->loadLanguageModule('qsts'); // set context tabs $questionGUI = assQuestionGUI::_getQuestionGUI( diff --git a/components/ILIAS/COPage/classes/class.ilQuestionExporter.php b/components/ILIAS/COPage/classes/class.ilQuestionExporter.php index ec9c2baf51b0..62c5505f4f2a 100755 --- a/components/ILIAS/COPage/classes/class.ilQuestionExporter.php +++ b/components/ILIAS/COPage/classes/class.ilQuestionExporter.php @@ -62,6 +62,7 @@ public function __construct(bool $a_preview_mode = false) $this->lng = $lng; $this->lng->loadLanguageModule('assessment'); + $this->lng->loadLanguageModule('qsts'); $this->inst_id = IL_INST_ID; diff --git a/components/ILIAS/COPage/xsl/page.xsl b/components/ILIAS/COPage/xsl/page.xsl index 5d368b5851b4..c69afb7f61bf 100755 --- a/components/ILIAS/COPage/xsl/page.xsl +++ b/components/ILIAS/COPage/xsl/page.xsl @@ -3721,6 +3721,13 @@ + + [[[LEGACY_ANSWER_FORM_TEXT_]]] + + + [[[ANSWER_FORM_]]] + + diff --git a/components/ILIAS/Course/classes/Objectives/class.ilCourseObjectiveQuestionAssignmentTableGUI.php b/components/ILIAS/Course/classes/Objectives/class.ilCourseObjectiveQuestionAssignmentTableGUI.php index 2a1900f9bd6a..a4e433952c4b 100755 --- a/components/ILIAS/Course/classes/Objectives/class.ilCourseObjectiveQuestionAssignmentTableGUI.php +++ b/components/ILIAS/Course/classes/Objectives/class.ilCourseObjectiveQuestionAssignmentTableGUI.php @@ -92,7 +92,7 @@ protected function fillRow(array $a_set): void if ($sub_data['qst_txt']) { $txt = $sub_data['qst_txt']; if ($sub_data['qst_points']) { - $this->lng->loadLanguageModule('assessment'); + $this->lng->loadLanguageModule('qsts'); $txt .= (' (' . $sub_data['qst_points'] . ' ' . $this->lng->txt('points') . ')'); } diff --git a/components/ILIAS/Export/xml/ilias_pg_12.dtd b/components/ILIAS/Export/xml/ilias_pg_12.dtd new file mode 100755 index 000000000000..64e6145fe231 --- /dev/null +++ b/components/ILIAS/Export/xml/ilias_pg_12.dtd @@ -0,0 +1,572 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/ILIAS/Glossary/Term/class.ilGlossaryTerm.php b/components/ILIAS/Glossary/Term/class.ilGlossaryTerm.php index bd85e6f407da..411e3ab9a0a1 100755 --- a/components/ILIAS/Glossary/Term/class.ilGlossaryTerm.php +++ b/components/ILIAS/Glossary/Term/class.ilGlossaryTerm.php @@ -642,9 +642,12 @@ public static function _copyTerm( int $a_term_id, int $a_glossary_id ): int { - $old_term = new ilGlossaryTerm($a_term_id); - // copy the term + try { + $old_term = new ilGlossaryTerm($a_term_id); + } catch (Exception $e) { + return 0; + } $new_term = new ilGlossaryTerm(); $new_term->setTerm($old_term->getTerm()); $new_term->setLanguage($old_term->getLanguage()); diff --git a/components/ILIAS/Glossary/classes/class.ilObjGlossary.php b/components/ILIAS/Glossary/classes/class.ilObjGlossary.php index 8a1fb18b603c..e836f7c79a41 100755 --- a/components/ILIAS/Glossary/classes/class.ilObjGlossary.php +++ b/components/ILIAS/Glossary/classes/class.ilObjGlossary.php @@ -590,6 +590,9 @@ public function cloneObject(int $target_id, int $copy_id = 0, bool $omit_tree = $term_mappings = array(); foreach (ilGlossaryTerm::getTermList([$this->getRefId()]) as $term) { $new_term_id = ilGlossaryTerm::_copyTerm($term["id"], $new_obj->getId()); + if ($new_term_id === 0) { + continue; + } $term_mappings[$term["id"]] = $new_term_id; // copy tax node assignments diff --git a/components/ILIAS/HTTP/src/StatusCode.php b/components/ILIAS/HTTP/src/StatusCode.php index cdac27a2a57f..0d46f3a347a5 100755 --- a/components/ILIAS/HTTP/src/StatusCode.php +++ b/components/ILIAS/HTTP/src/StatusCode.php @@ -71,6 +71,7 @@ interface StatusCode public const HTTP_UNSUPPORTED_MEDIA_TYPE = 415; public const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416; public const HTTP_EXPECTATION_FAILED = 417; + public const HTTP_UNPROCESSABLE_ENTITY = 422; public const HTTP_TOO_MANY_REQUESTS = 429; // [Server Error 5xx] diff --git a/components/ILIAS/ItemGroup/classes/Setup/class.Agent.php b/components/ILIAS/ItemGroup/classes/Setup/class.Agent.php index 40a3967dc68d..2c3737c26f70 100755 --- a/components/ILIAS/ItemGroup/classes/Setup/class.Agent.php +++ b/components/ILIAS/ItemGroup/classes/Setup/class.Agent.php @@ -29,4 +29,11 @@ public function getUpdateObjective(?Setup\Config $config = null): Setup\Objectiv { return new \ilDatabaseUpdateStepsExecutedObjective(new ilItemGroupDBUpdateSteps()); } + + public function getMigrations(): array + { + return [ + new ilItemGroupDisplayMigration() + ]; + } } diff --git a/components/ILIAS/ItemGroup/classes/Setup/class.ilItemGroupDisplayMigration.php b/components/ILIAS/ItemGroup/classes/Setup/class.ilItemGroupDisplayMigration.php new file mode 100644 index 000000000000..f22fabe7cf1d --- /dev/null +++ b/components/ILIAS/ItemGroup/classes/Setup/class.ilItemGroupDisplayMigration.php @@ -0,0 +1,123 @@ +db = $environment->getResource(Environment::RESOURCE_DATABASE); + } + + public function step(Environment $environment): void + { + $result = $this->db->queryF( + 'SELECT id, hide_title, behaviour FROM itgr_data WHERE hide_title <> %s AND behaviour <> %s LIMIT 1', + [ilDBConstants::T_INTEGER, ilDBConstants::T_INTEGER], + [self::MIGRATED_MARKER, self::MIGRATED_MARKER] + ); + + $row = $this->db->fetchAssoc($result); + if ($row === null) { + return; + } + + [$display, $toggleable_initially] = $this->mapLegacyValues((int) $row['hide_title'], (int) $row['behaviour']); + + $this->db->update( + 'itgr_data', + [ + 'display' => [ilDBConstants::T_TEXT, $display], + 'toggleable_initially' => [ilDBConstants::T_TEXT, $toggleable_initially], + 'hide_title' => [ilDBConstants::T_INTEGER, self::MIGRATED_MARKER], + 'behaviour' => [ilDBConstants::T_INTEGER, self::MIGRATED_MARKER], + ], + [ + 'id' => [ilDBConstants::T_INTEGER, (int) $row['id']], + ] + ); + } + + public function getRemainingAmountOfSteps(): int + { + $result = $this->db->queryF( + 'SELECT COUNT(id) AS cnt FROM itgr_data WHERE hide_title <> %s AND behaviour <> %s', + [ilDBConstants::T_INTEGER, ilDBConstants::T_INTEGER], + [self::MIGRATED_MARKER, self::MIGRATED_MARKER] + ); + + return (int) ($this->db->fetchObject($result)?->cnt ?? 0); + } + + /** + * @return array{0: string, 1: string} + */ + private function mapLegacyValues(int $hide_title, int $behaviour): array + { + return match (true) { + $hide_title === 1 => [ + ilItemGroupAR::DISPLAY_WITHOUT_TITLE, + ilItemGroupAR::DISPLAY_WITH_TITLE_AND_TOGGLEABLE_INITIALLY_OPEN, + ], + $behaviour === ilItemGroupBehaviour::EXPANDABLE_CLOSED => [ + ilItemGroupAR::DISPLAY_WITH_TITLE_AND_TOGGLEABLE, + ilItemGroupAR::DISPLAY_WITH_TITLE_AND_TOGGLEABLE_INITIALLY_CLOSED, + ], + $behaviour === ilItemGroupBehaviour::EXPANDABLE_OPEN => [ + ilItemGroupAR::DISPLAY_WITH_TITLE_AND_TOGGLEABLE, + ilItemGroupAR::DISPLAY_WITH_TITLE_AND_TOGGLEABLE_INITIALLY_OPEN, + ], + default => [ + ilItemGroupAR::DISPLAY_WITH_TITLE, + ilItemGroupAR::DISPLAY_WITH_TITLE_AND_TOGGLEABLE_INITIALLY_OPEN, + ] + }; + } +} diff --git a/components/ILIAS/News/classes/class.ilNewsForContextBlockGUI.php b/components/ILIAS/News/classes/class.ilNewsForContextBlockGUI.php index 2d698667fb70..da2d994301f9 100755 --- a/components/ILIAS/News/classes/class.ilNewsForContextBlockGUI.php +++ b/components/ILIAS/News/classes/class.ilNewsForContextBlockGUI.php @@ -70,6 +70,7 @@ public function __construct() $this->help = $DIC["ilHelp"]; $this->settings = $DIC->settings(); $this->tabs = $DIC->tabs(); + $this->logger = $DIC->logger()->news(); $locator = $DIC->news()->internal(); $this->std_request = $locator->gui()->standardRequest(); diff --git a/components/ILIAS/Questions/Legacy/Administration/ViewConfiguration.php b/components/ILIAS/Questions/Legacy/Administration/ViewConfiguration.php new file mode 100755 index 000000000000..13992e69967f --- /dev/null +++ b/components/ILIAS/Questions/Legacy/Administration/ViewConfiguration.php @@ -0,0 +1,217 @@ +url_builder, + $this->configuration_token + ] = $url_builder->acquireParameter( + self::PARAMETER_NAMESPACE, + self::CONFIGURATION_TOKEN_STRING + ); + + $this->current_configuration = $query_wrapper->retrieve( + $this->configuration_token->getName(), + $this->refinery->custom()->transformation( + function (mixed $values): array { + $current_configuration = [ + DefaultView::getIdentifier() + ]; + if (!is_array($values)) { + return $current_configuration; + } + + $available_capabilities = [ + AsyncView::getIdentifier(), + DefaultView::getIdentifier(), + TextFeedback::getIdentifier(), + SuggestedLearningContent::getIdentifier(), + MarkingAllowingPartialPoints::getIdentifier() + ]; + + return array_reduce( + $values, + function (array $c, string $v) use ($available_capabilities): array { + if ($v === AsyncView::getIdentifier() + || $v === DefaultView::getIdentifier()) { + $c = array_filter( + $c, + fn(string $v): bool => $v !== DefaultView::getIdentifier() + && $v !== AsyncView::getIdentifier() + ); + } + + if (in_array($v, $available_capabilities) + && !in_array($v, $c)) { + $c[] = $v; + } + + return $c; + }, + $current_configuration + ); + } + ) + ); + } + + public function isAsync(): bool + { + return in_array( + AsyncView::getIdentifier(), + $this->current_configuration + ); + } + + public function getCurrentConfiguration(): array + { + return $this->current_configuration; + } + + public function getURLBuilderWithPreservedConfigurationParameter( + ?URLBuilder $url_builder = null + ): URLBuilder { + if ($url_builder !== null) { + [ + $url_builder, + $configuration_token + ] = $url_builder->acquireParameter( + self::PARAMETER_NAMESPACE, + self::CONFIGURATION_TOKEN_STRING + ); + } else { + $url_builder = $this->url_builder; + $configuration_token = $this->configuration_token; + } + + return $url_builder->withParameter( + $configuration_token, + $this->current_configuration + ); + } + + public function initializeToolbar(): void + { + $configuration_without_view = array_filter( + $this->current_configuration, + fn(string $v): bool => $v !== DefaultView::getIdentifier() && $v !== AsyncView::getIdentifier() + ); + $this->toolbar->addComponent( + $this->ui_factory->viewControl()->mode( + [ + $this->lng->txt('default_view') => $this->url_builder->withParameter( + $this->configuration_token, + [...$configuration_without_view, DefaultView::getIdentifier()] + )->buildURI()->__toString(), + $this->lng->txt('async_view') => $this->url_builder->withParameter( + $this->configuration_token, + [...$configuration_without_view, AsyncView::getIdentifier()] + )->buildURI()->__toString() + ], + $this->lng->txt('select_view') + )->withActive( + in_array(DefaultView::getIdentifier(), $this->current_configuration) + ? $this->lng->txt('default_view') + : $this->lng->txt('async_view') + ), + ); + + $this->toolbar->addComponent( + $this->buildToolbarButton( + MarkingAllowingPartialPoints::getIdentifier() + ) + ); + + $this->toolbar->addComponent( + $this->buildToolbarButton( + TextFeedback::getIdentifier() + ) + ); + + $this->toolbar->addComponent( + $this->buildToolbarButton( + SuggestedLearningContent::getIdentifier() + ) + ); + } + + private function buildToolbarButton( + string $identifier + ): StandardButton { + $capability_activated = in_array($identifier, $this->current_configuration); + $filtered_configuration = array_filter( + $this->current_configuration, + fn(string $v): bool => $v !== $identifier + ); + + return $this->ui_factory->button()->standard( + $this->buildButtonLabel($identifier, $capability_activated), + $this->url_builder->withParameter( + $this->configuration_token, + $capability_activated + ? $filtered_configuration + : [...$filtered_configuration, $identifier] + )->buildURI()->__toString() + ); + } + + private function buildButtonLabel( + string $identifier, + bool $enabled + ): string { + $identifier_lng_string = strtolower($identifier); + + return $enabled + ? $this->lng->txt("disable_{$identifier_lng_string}") + : $this->lng->txt("enable_{$identifier_lng_string}"); + } +} diff --git a/components/ILIAS/Questions/Legacy/Administration/class.ilObjQuestionPreviewGUI.php b/components/ILIAS/Questions/Legacy/Administration/class.ilObjQuestionPreviewGUI.php new file mode 100755 index 000000000000..a2ab66f9c24d --- /dev/null +++ b/components/ILIAS/Questions/Legacy/Administration/class.ilObjQuestionPreviewGUI.php @@ -0,0 +1,526 @@ +ctrl = $DIC['ilCtrl']; + $this->lng = $DIC['lng']; + $this->tpl = $DIC['tpl']; + $this->current_user = $DIC['user']->getLoggedInUser(); + $this->tabs_gui = $DIC['ilTabs']; + $this->toolbar = $DIC['ilToolbar']; + $this->http = $DIC['http']; + $this->ui_factory = $DIC['ui.factory']; + $this->ui_renderer = $DIC['ui.renderer']; + $this->ui_service = $DIC->uiService(); + $this->refinery = $DIC['refinery']; + $this->data_factory = new DataFactory(); + $this->uuid_factory = new UuidFactory(); + + $this->temp_attempt_repository = new AttemptRepository( + $DIC['ilDB'], + $this->uuid_factory + ); + + [ + $url_builder, + $this->action_token, + $this->row_id_token + ] = $this->getUrlBuilder()->acquireParameters( + self::PARAMETER_NAMENSPACE, + self::ACTION_TOKEN_STRING, + self::ROW_ID_TOKEN_STRING + ); + + $this->view_configuration = new ViewConfiguration( + $this->lng, + $this->refinery, + $this->ui_factory, + $this->http->wrapper()->query(), + $url_builder, + $DIC['ilToolbar'] + ); + + $this->url_builder = $this->view_configuration + ->getURLBuilderWithPreservedConfigurationParameter(); + + /** + * sk, 2026.05.06: This is done this way as this is a completely + * temporary class. It should be made as simple as possible to get rid + * of it. + */ + $local_dic = LocalDIC::dic(); + $this->repository = $local_dic[Repository::class]; + $this->answer_form_factory = $local_dic[AnswerFormFactory::class]; + $this->participant_view = $local_dic[PublicInterface::class] + ->getParticipantView( + $this->view_configuration->getCurrentConfiguration(), + $this->object_id + ); + } + + public function executeCommand(): void + { + $cmd = $this->ctrl->getCmd(); + + $table_action = $this->http->wrapper()->query()->retrieve( + $this->action_token->getName(), + $this->refinery->byTrying([ + $this->refinery->kindlyTo()->string(), + $this->refinery->always(null) + ]) + ); + + if ($table_action !== null) { + $cmd = $table_action; + } + + if ($cmd === null || $cmd === '') { + $cmd = self::CMD_DEFAULT; + } + $cmd .= 'Cmd'; + $this->$cmd(); + } + + private function showCmd(): void + { + $this->view_configuration->initializeToolbar(); + + $filter = $this->buildFilter( + $this->ctrl->getLinkTargetByClass( + $this->getClassPath() + ) + ); + + $this->tpl->setContent( + $this->ui_renderer->render([ + $filter, + $this->ui_factory->table()->data( + $this, + $this->lng->txt('questions'), + [ + OverviewTableColumns::Title->value + => $this->ui_factory->table()->column()->link( + $this->lng->txt('title') + ), + OverviewTableColumns::AnswerFormTypes->value + => $this->ui_factory->table()->column()->text( + $this->lng->txt('contained_answer_form_types') + )->withIsOptional(true, true) + ->withIsSortable(false), + ], + )->withRange(new Range(0, 20)) + ->withFilter( + $this->ui_service->filter()->getData($filter) + )->withRequest($this->http->request()) + ]) + ); + } + + private function showQuestionCmd(): void + { + $is_async = $this->view_configuration->isAsync(); + $question_id = $this->retrieveQuestionIdFromQuery(); + $attempt = $this->temp_attempt_repository->get( + $this->current_user->getId() + ); + + if ($question_id === null) { + $this->tpl->setOnScreenMessage( + GlobalTemplate::MESSAGE_TYPE_FAILURE, + $this->lng->txt('invalid') + ); + $this->showCmd(); + } + + $this->tabs_gui->clearTargets(); + $this->tabs_gui->setBackTarget( + $this->lng->txt('back'), + $this->view_configuration->getURLBuilderWithPreservedConfigurationParameter( + $this->getUrlBuilder() + )->buildURI()->__toString() + ); + + $this->toolbar->addComponent( + $this->ui_factory->button()->standard( + $this->lng->txt('delete_responses'), + $this->view_configuration->getURLBuilderWithPreservedConfigurationParameter( + $this->getUrlBuilderWithPreservedQuestionParameter( + $question_id, + self::CMD_DELETE_RESPONSES + ) + )->buildURI()->__toString() + ) + ); + + $view = $this->participant_view->getQuestionView( + $question_id, + $attempt?->getAttemptId(), + true, + false, + $is_async ? true : false, + $is_async ? true : false + ); + + if ($attempt === null) { + $this->temp_attempt_repository->storeNew( + $this->current_user->getId(), + $view->getAttemptId() + ); + } + + $content = [ + $this->ui_factory->panel()->standard( + $this->lng->txt('question'), + $is_async + ? $view->getUI() + : $this->ui_factory->legacy()->content( + $this->buildQuestionForm($view, $question_id) + ) + ) + ]; + + if (!$is_async && $attempt?->isQuestionSolved($question_id) ?? false) { + $content[] = $this->ui_factory->panel()->standard( + $this->lng->txt('feedback'), + $this->participant_view->getQuestionView( + $question_id, + $attempt->getAttemptId(), + false, + true, + true, + true + )->getUI() + ); + } + + $this->tpl->setContent( + $this->ui_renderer->render( + $content + ) + ); + } + + private function respondCmd(): void + { + $question_id = $this->retrieveQuestionIdFromQuery(); + $attempt = $this->temp_attempt_repository->get( + $this->current_user->getId() + ); + + if ($question_id === null || $attempt === null) { + $this->tpl->setOnScreenMessage( + GlobalTemplate::MESSAGE_TYPE_FAILURE, + $this->lng->txt('invalid') + ); + $this->showCmd(); + } + + $this->participant_view->persistResponse( + $question_id, + $attempt->getAttemptId() + ); + + $this->temp_attempt_repository->storeSolved( + $attempt->withAdditionalSolvedQuestion($question_id) + ); + + $this->ctrl->redirectToURL( + $this->view_configuration->getURLBuilderWithPreservedConfigurationParameter( + $this->getUrlBuilderWithPreservedQuestionParameter( + $question_id, + self::CMD_SHOW_QUESTION + ) + )->buildURI()->__toString() + ); + } + + private function deleteResponsesCmd(): void + { + $question_id = $this->retrieveQuestionIdFromQuery(); + $attempt = $this->temp_attempt_repository->get( + $this->current_user->getId() + ); + + if ($question_id === null || $attempt === null) { + $this->tpl->setOnScreenMessage( + GlobalTemplate::MESSAGE_TYPE_FAILURE, + $this->lng->txt('invalid') + ); + $this->showCmd(); + } + + $this->participant_view->deleteResponsesFor( + $attempt->getAttemptId(), + $question_id + ); + + $this->temp_attempt_repository->storeSolved( + $attempt->withQuestionRemovedFromSolved($question_id) + ); + + $this->ctrl->redirectToURL( + $this->view_configuration->getURLBuilderWithPreservedConfigurationParameter( + $this->getUrlBuilderWithPreservedQuestionParameter( + $question_id, + self::CMD_SHOW_QUESTION + ) + )->buildURI()->__toString() + ); + } + + #[\Override] + public function getRows( + DataRowBuilder $row_builder, + array $visible_column_ids, + Range $range, + Order $order, + mixed $additional_viewcontrol_data, + mixed $filter_data, + mixed $additional_parameters + ): \Generator { + foreach ($this->repository->getQuestionDataOnlyForAllQuestions( + $range, + $order, + $filter_data + ) as $question) { + yield $row_builder->buildDataRow( + $question->getid()->toString(), + [ + OverviewTableColumns::Title->value => $this->ui_factory->link()->standard( + $question->getTitle(), + $this->view_configuration + ->getURLBuilderWithPreservedConfigurationParameter() + ->withParameter($this->action_token, self::CMD_SHOW_QUESTION) + ->withParameter($this->row_id_token, $question->getid()->toString()) + ->buildURI() + ->__toString() + ), + OverviewTableColumns::AnswerFormTypes->value => implode( + '
', + $question->getListOfContainedAnswerFormTypeLabels($this->lng) + ) + ] + ); + } + } + + #[\Override] + public function getTotalRowCount( + mixed $additional_viewcontrol_data, + mixed $filter_data, + mixed $additional_parameters + ): ?int { + return $this->repository->getQuestionsCount(); + } + + private function buildFilter( + string $action + ): Filter { + $filter_inputs = OverviewTableColumns::getFilterInputs( + $this->lng, + $this->ui_factory->input()->field(), + $this->answer_form_factory->getAnswerFormTypesArrayForSelect( + $this->lng + ) + ); + + $active = array_fill(0, count($filter_inputs), true); + + $filter = $this->ui_service->filter()->standard( + 'question_table_filter_id', + $action, + $filter_inputs, + $active, + true, + true + ); + + $request = $this->http->request(); + return $request->getMethod() === 'POST' + ? $filter->withRequest($request) + : $filter; + } + + private function retrieveQuestionIdFromQuery(): ?Uuid + { + return $this->http->wrapper()->query()->retrieve( + $this->row_id_token->getName(), + $this->refinery->byTrying([ + $this->refinery->custom()->transformation( + function (string $v): Uuid { + try { + return $this->uuid_factory->fromString($v); + } catch (Throwable $e) { + throw new ConstraintViolationException( + sprintf('The value could not be transformed into a Uuid'), + 'not_valid' + ); + } + } + ), + $this->refinery->always(null) + ]) + ); + } + + private function getUrlBuilder( + ?string $cmd = null + ): URLBuilder { + return new URLBuilder( + $this->data_factory->uri( + ILIAS_HTTP_PATH . '/' . $this->ctrl->getLinkTargetByClass( + $this->getClassPath(), + $cmd + ) + ) + ); + } + + private function getUrlBuilderWithPreservedQuestionParameter( + Uuid $question_id, + string $cmd + ): URLBuilder { + [$url_builder, $row_id_parameter] = $this->getUrlBuilder($cmd) + ->acquireParameter( + self::PARAMETER_NAMENSPACE, + self::ROW_ID_TOKEN_STRING + ); + + return $url_builder->withParameter( + $row_id_parameter, + $question_id->toString() + ); + } + + private function buildQuestionForm( + QuestionParticipantView $view, + Uuid $question_id + ): string { + $tpl = new \ilTemplate( + 'tpl.qsts_preview_presentation_interactive.html', + true, + true, + 'components/ILIAS/Questions' + ); + + $tpl->setVariable( + self::TEMPLATE_VARIABLE_FORM_ACTION, + $this->view_configuration->getURLBuilderWithPreservedConfigurationParameter( + $this->getUrlBuilderWithPreservedQuestionParameter( + $question_id, + self::CMD_RESPOND + ) + )->buildURI() + ->__toString() + ); + + $tpl->setVariable( + self::TEMPLATE_VARIABLE_QUESTION_OUTPUT, + $this->ui_renderer->render($view->getUI()) + ); + + $tpl->setVariable( + self::TEMPLATE_VARIABLE_SUBMIT_BUTTON_LABEL, + $this->lng->txt('send') + ); + + return $tpl->get(); + } + + private function getClassPath(): array + { + return [ilObjQuestionsGUI::class, ilObjQuestionPreviewGUI::class]; + } +} diff --git a/components/ILIAS/Questions/Legacy/Administration/class.ilObjQuestions.php b/components/ILIAS/Questions/Legacy/Administration/class.ilObjQuestions.php new file mode 100755 index 000000000000..b2cb613898d0 --- /dev/null +++ b/components/ILIAS/Questions/Legacy/Administration/class.ilObjQuestions.php @@ -0,0 +1,30 @@ +type = 'qsts'; + parent::__construct($id, $referenced); + } +} diff --git a/components/ILIAS/Questions/Legacy/Administration/class.ilObjQuestionsAccess.php b/components/ILIAS/Questions/Legacy/Administration/class.ilObjQuestionsAccess.php new file mode 100755 index 000000000000..4ea07f76b5d0 --- /dev/null +++ b/components/ILIAS/Questions/Legacy/Administration/class.ilObjQuestionsAccess.php @@ -0,0 +1,27 @@ + + * @ingroup components\ILIASTest + */ +class ilObjQuestionsAccess extends ilObjectAccess +{ +} diff --git a/components/ILIAS/Questions/Legacy/Administration/class.ilObjQuestionsGUI.php b/components/ILIAS/Questions/Legacy/Administration/class.ilObjQuestionsGUI.php new file mode 100755 index 000000000000..65ffe901b138 --- /dev/null +++ b/components/ILIAS/Questions/Legacy/Administration/class.ilObjQuestionsGUI.php @@ -0,0 +1,234 @@ +help = $DIC['ilHelp']; + $this->data_factory = new DataFactory(); + + $this->type = 'qsts'; + + parent::__construct($a_data, $a_id, $a_call_by_reference, false); + + $local_dic = LocalDIC::dic(); + $this->edit_view = $local_dic[PublicInterface::class] + ->getEditView( + [ + DefaultView::getIdentifier(), + TextFeedback::getIdentifier(), + SuggestedLearningContent::getIdentifier(), + MarkingAllowingPartialPoints::getIdentifier() + ], + $this->object->getId() + ); + $this->configuration_repository = $local_dic[ConfigurationRepository::class]; + + $this->lng->loadLanguageModule('assessment'); + $this->lng->loadLanguageModule('qsts'); + + if (!$this->access->checkAccess('read', '', $this->object->getRefId())) { + $this->ilias->raiseError($this->lng->txt("msg_no_perm_read_assf"), $this->ilias->error_obj->WARNING); + } + } + + public function executeCommand(): void + { + $next_class = $this->ctrl->getNextClass($this); + $cmd = $this->ctrl->getCmd(); + $this->prepareOutput(); + + switch ($next_class) { + case strtolower(ilPermissionGUI::class): + $this->tabs_gui->activateTab(self::TAB_IDENTIFIER_PERMISSIONS); + $this->ctrl->forwardCommand(new \ilPermissionGUI($this)); + break; + + case strtolower(QstsQuestionPageGUI::class): + $this->edit_view->forwardPageCmds( + $this->buildEditQuestionsBaseUri(), + $this->ref_id + ); + break; + + case strtolower(ConfigurationGUI::class): + $this->tabs_gui->activateTab(self::TAB_IDENTIFIER_SETTINGS); + $this->ctrl->forwardCommand( + new ConfigurationGUI( + $this->ctrl, + $this->http, + $this->lng, + $this->refinery, + $this->tpl, + $this->ui_factory, + $this->ui_renderer, + $this->configuration_repository + ) + ); + break; + + case strtolower(GlobalConfigurationGUI::class): + $this->tabs_gui->activateTab(self::TAB_IDENTIFIER_UNITS); + $this->ctrl->forwardCommand( + new GlobalConfigurationGUI( + $this->units_repository, + $this->lng, + $this->ctrl, + $this->rbac_system, + $this->tpl, + $this->toolbar, + $this->tabs_gui, + $this->help + ) + ); + break; + + case strtolower(ilObjQuestionPreviewGUI::class): + $this->addSubTabs(); + $this->tabs_gui->activateTab(self::TAB_IDENTIFIER_QUESTIONS); + $this->tabs_gui->activateSubTab(self::SUB_TAB_IDENTIFIER_PREVIEW_QUESTIONS); + $this->ctrl->forwardCommand( + new ilObjQuestionPreviewGUI( + $this->object->getId() + ) + ); + + break; + default: + $this->addSubTabs(); + if ($cmd === null || $cmd === '' || $cmd === 'view') { + $cmd = 'editQuestions'; + } + $cmd .= 'Cmd'; + $this->$cmd(); + break; + } + } + + private function editQuestionsCmd(): void + { + $this->tabs_gui->activateTab(self::TAB_IDENTIFIER_QUESTIONS); + $this->tabs_gui->activateSubTab(self::SUB_TAB_IDENTIFIER_EDIT_QUESTIONS); + + $this->tpl->setContent( + $this->ui_renderer->render( + $this->edit_view->getUI( + $this->buildEditQuestionsBaseUri(), + $this->object->getRefId() + ) + ) + ); + } + + public function getAdminTabs(): void + { + $this->addTabs(); + } + + protected function addTabs(): void + { + if ($this->rbac_system->checkAccess('read', $this->object->getRefId())) { + $this->tabs_gui->addTab( + self::TAB_IDENTIFIER_QUESTIONS, + $this->lng->txt(self::TAB_IDENTIFIER_QUESTIONS), + $this->ctrl->getLinkTargetByClass(self::class, 'editQuestions') + ); + + $this->tabs_gui->addTab( + self::TAB_IDENTIFIER_SETTINGS, + $this->lng->txt(self::TAB_IDENTIFIER_SETTINGS), + $this->ctrl->getLinkTargetByClass(ConfigurationGUI::class, ''), + ); + } + + if ($this->rbac_system->checkAccess('edit_permission', $this->object->getRefId())) { + $this->tabs_gui->addTab( + self::TAB_IDENTIFIER_PERMISSIONS, + $this->lng->txt(self::TAB_IDENTIFIER_PERMISSIONS), + $this->ctrl->getLinkTargetByClass([self::class, ilPermissionGUI::class], 'perm'), + ); + } + } + + private function addSubTabs(): void + { + if (!$this->rbac_system->checkAccess('read', $this->object->getRefId())) { + return; + } + + $this->tabs_gui->addSubTab( + self::SUB_TAB_IDENTIFIER_EDIT_QUESTIONS, + $this->lng->txt('edit'), + $this->ctrl->getLinkTargetByClass(self::class, 'editQuestions') + ); + + $this->tabs_gui->addSubTab( + self::SUB_TAB_IDENTIFIER_PREVIEW_QUESTIONS, + $this->lng->txt('preview'), + $this->ctrl->getLinkTargetByClass([self::class, ilObjQuestionPreviewGUI::class]) + ); + } + + private function buildEditQuestionsBaseUri(): URI + { + return $this->data_factory->uri( + ILIAS_HTTP_PATH . '/' . $this->ctrl->getFormActionByClass(self::class, 'editQuestions') + ); + } +} diff --git a/components/ILIAS/Questions/Legacy/LocalDIC.php b/components/ILIAS/Questions/Legacy/LocalDIC.php new file mode 100755 index 000000000000..52836d70a14d --- /dev/null +++ b/components/ILIAS/Questions/Legacy/LocalDIC.php @@ -0,0 +1,294 @@ + new DataFactory(); + $dic[UuidFactory::class] = static fn($c): UuidFactory => new UuidFactory(); + $dic[MustacheEngine::class] = static fn($c): MustacheEngine + => new MustacheEngine(['escape' => static fn($v) => $v]); + $dic[\ilFileServicesFilenameSanitizer::class] = static fn($c): \ilFileServicesFilenameSanitizer + => new \ilFileServicesFilenameSanitizer( + $DIC->fileServiceSettings() + ); + + $dic[PublicInterface::class] = static fn($c): PublicInterface + => new DefaultPublicInterface( + $DIC['lng'], + $DIC['user']->getLoggedInUser(), + $DIC['refinery'], + $DIC['ui.factory'], + $DIC['ui.renderer'], + $DIC['global_screen'], + $DIC['tpl'], + $DIC->contentStyle(), + $DIC['ilCtrl'], + $DIC['http'], + $DIC['ilTabs'], + $DIC->uiService(), + $c[UuidFactory::class], + $c[ConfigurationRepository::class], + $c[AnswerFormFactory::class], + $c[QuestionsRepository::class], + $c[AttemptRepository::class], + $c[LayoutFactory::class], + $c[Capabilities\Factory::class] + ); + + $dic[ConfigurationRepository::class] = static fn($c): ConfigurationRepository + => new ConfigurationRepository( + $DIC['ilSetting'], + $DIC['user']->getSettings()->getSettingByDefinitionClass( + CreateMode::class + ), + new \ilSetting('questions') + ); + $dic[PersistenceFactory::class] = static fn($c): PersistenceFactory + => new PersistenceFactory(); + $dic[QuestionTableDefinitions::class] = static fn($c): QuestionTableDefinitions + => new QuestionTableDefinitions( + $c[PersistenceFactory::class] + ); + $dic[AnswerFormGenericTableDefinitions::class] = static fn($c): AnswerFormGenericTableDefinitions + => new AnswerFormGenericTableDefinitions( + $c[PersistenceFactory::class] + ); + $dic[DatabaseStatementBuilder::class] = static fn($c): DatabaseStatementBuilder + => new DatabaseStatementBuilder( + $c[PersistenceFactory::class], + $c[QuestionTableDefinitions::class], + $c[AnswerFormGenericTableDefinitions::class] + ); + $dic[AttemptTableDefinitions::class] = static fn($c): AttemptTableDefinitions + => new AttemptTableDefinitions( + $c[PersistenceFactory::class] + ); + $dic[AttemptRepository::class] = static fn($c): AttemptRepository + => new AttemptRepository( + $DIC['ilDB'], + $DIC['refinery'], + $c[UuidFactory::class], + $c[PersistenceFactory::class], + $c[AttemptTableDefinitions::class] + ); + $dic[Capabilities\Factory::class] = static fn($c): Capabilities\Factory + => new Capabilities\Factory([ + new Capabilities\TextFeedback\Capability( + $c[DataFactory::class]->text(), + new Capabilities\TextFeedback\Repository( + $DIC['ilDB'], + $DIC['refinery'], + $c[UuidFactory::class], + $c[DataFactory::class]->text(), + $c[PersistenceFactory::class], + new Capabilities\TextFeedback\TableDefinitions( + $c[PersistenceFactory::class] + ) + ) + ), + new Capabilities\SuggestedLearningContent\Capability( + $DIC['ilCtrl'], + $DIC['rbacsystem'], + $DIC['tree'], + $DIC['static_url'], + $DIC['user']->getLoggedInUser(), + new Capabilities\SuggestedLearningContent\Repository( + $DIC['ilDB'], + $DIC['refinery'], + $DIC['resource_storage'], + $c[PersistenceFactory::class], + new Capabilities\SuggestedLearningContent\TableDefinitions( + $c[PersistenceFactory::class] + ) + ) + ), + new Capabilities\MarkingAllowingPartialPoints\Capability(), + new Capabilities\DefaultView\Capability(), + new Capabilities\AsyncView\Capability() + ]); + $dic[AnswerFormFactory::class] = static fn($c): AnswerFormFactory + => new AnswerFormFactory( + $c[UuidFactory::class], + [ + $c[Cloze\Definition::class] + ] + ); + $dic[QuestionsRepository::class] = static fn($c): QuestionsRepository + => new QuestionsRepository( + $DIC['ilDB'], + $DIC['refinery'], + $c[UuidFactory::class], + $c[DatabaseStatementBuilder::class], + $c[PersistenceFactory::class], + $c[QuestionTableDefinitions::class], + $c[AnswerFormGenericTableDefinitions::class], + $c[AnswerFormFactory::class] + ); + $dic[LayoutFactory::class] = static fn($c): LayoutFactory => + new LayoutFactory( + $DIC['ui.factory'], + $DIC['http'], + $DIC['lng'], + $DIC['resource_storage'], + $DIC['filesystem']->temp(), + $DIC['upload'], + $c[\ilFileServicesFilenameSanitizer::class] + ); + $dic[Cloze\Properties\ClozeText\Factory::class] = static fn($c): Cloze\Properties\ClozeText\Factory + => new Cloze\Properties\ClozeText\Factory( + $DIC['refinery'], + $c[MustacheEngine::class], + $c[DataFactory::class]->text() + ); + $dic[Cloze\Properties\Gaps\AnswerOptions\Factory::class] = static fn($c): Cloze\Properties\Gaps\AnswerOptions\Factory + => new Cloze\Properties\Gaps\AnswerOptions\Factory( + $c[UuidFactory::class], + $DIC['refinery'] + ); + $dic[Cloze\Properties\Gaps\Factory::class] = static fn($c): Cloze\Properties\Gaps\Factory + => new Cloze\Properties\Gaps\Factory( + $DIC['refinery'], + $c[UuidFactory::class], + $c[Cloze\Properties\Gaps\AnswerOptions\Factory::class], + [ + new Cloze\Properties\Gaps\Text( + $DIC['lng'], + $DIC['refinery'] + ), + new Cloze\Properties\Gaps\Numeric( + $DIC['lng'], + $DIC['refinery'] + ), + new Cloze\Properties\Gaps\Select( + $DIC['lng'], + $DIC['refinery'] + ), + new Cloze\Properties\Gaps\LongMenu( + $DIC['lng'], + $DIC['refinery'], + $DIC['tpl'] + ) + ] + ); + $dic[Cloze\TableDefinitions::class] = static fn($c): Cloze\TableDefinitions + => new Cloze\TableDefinitions( + $c[PersistenceFactory::class], + new TableSubNameSpace( + 'ILIAS', + 'cloze' + ) + ); + $dic[Cloze\Properties\Factory::class] = static fn($c): Cloze\Properties\Factory + => new Cloze\Properties\Factory( + $c[PersistenceFactory::class], + $c[Cloze\Properties\ClozeText\Factory::class], + $c[Cloze\Properties\Gaps\Factory::class], + $c[Cloze\Properties\Combinations\Factory::class] + ); + $dic[Cloze\Response\Factory::class] = static fn($c): Cloze\Response\Factory + => new Cloze\Response\Factory( + $c[UuidFactory::class], + $c[PersistenceFactory::class] + ); + $dic[Cloze\Properties\Gaps\Edit::class] = static fn($c): Cloze\Properties\Gaps\Edit + => new Cloze\Properties\Gaps\Edit( + $DIC['upload'], + $c[Cloze\Properties\Factory::class], + $c[Cloze\Properties\Gaps\Factory::class] + ); + $dic[Cloze\Views\Edit::class] = static fn($c): Cloze\Views\Edit + => new Cloze\Views\Edit( + $c[Cloze\Properties\Factory::class], + $c[Cloze\Properties\ClozeText\Factory::class], + $c[Cloze\Properties\Gaps\Edit::class] + ); + $dic[Cloze\Definition::class] = static fn($c): Cloze\Definition => new Cloze\Definition( + $c[Cloze\TableDefinitions::class], + [ + new Cloze\Capabilities\TextFeedback( + $c[UuidFactory::class], + $c[DataFactory::class]->text() + ), + new Cloze\Capabilities\MarkingAllowingPartialPoints( + $c[Cloze\Properties\Factory::class], + $c[Cloze\Response\Factory::class] + ), + new Cloze\Capabilities\DefaultView( + $c[MustacheEngine::class], + $c[Cloze\Response\Factory::class] + ), + new Cloze\Capabilities\AsyncView( + $DIC['ui.renderer'], + $c[MustacheEngine::class], + $c[Cloze\Response\Factory::class] + ) + ], + $c[Cloze\Properties\Factory::class], + $c[Cloze\Response\Factory::class], + $c[Cloze\Views\Edit::class] + ); + $dic[Cloze\Properties\Combinations\Factory::class] = static fn($c): Cloze\Properties\Combinations\Factory + => new Cloze\Properties\Combinations\Factory( + $c[UuidFactory::class], + $c[PersistenceFactory::class], + $c[Cloze\TableDefinitions::class] + ); + + return $dic; + } +} diff --git a/components/ILIAS/Questions/Legacy/PageEditor/QstsQuestionPage.php b/components/ILIAS/Questions/Legacy/PageEditor/QstsQuestionPage.php new file mode 100644 index 000000000000..c8abce9710bb --- /dev/null +++ b/components/ILIAS/Questions/Legacy/PageEditor/QstsQuestionPage.php @@ -0,0 +1,191 @@ +uuid_factory = new UuidFactory(); + } + + #[\Override] + public function getParentType(): string + { + return 'qsts'; + } + + public function getEditView(): Edit + { + return $this->edit_view; + } + + public function setEditView( + Edit $edit_view + ): void { + $this->edit_view = $edit_view; + } + + public function getQuestion(): Question + { + return $this->question; + } + + public function setQuestion( + Question $question + ): void { + $this->question = $question; + } + + public function getParticipantView(): Viewable + { + return $this->participant_view; + } + + public function setParticipantView( + Viewable $participant_view + ): void { + $this->participant_view = $participant_view; + } + + public function getAttemptData(): ?Attempt + { + return $this->attempt_data; + } + + public function setAttemptData( + ?Attempt $attempt_data + ): void { + $this->attempt_data = $attempt_data; + } + + public function getRequiredCapabilities(): ?RequiredCapabilities + { + return $this->required_capabilites; + } + + public function setRequiredCapabilities( + RequiredCapabilities $required_capabilities + ): void { + $this->required_capabilites = $required_capabilities; + } + + public function getViewConfiguration(): ?ViewConfiguration + { + return $this->view_configuration; + } + + public function setViewConfiguration( + ViewConfiguration $view_configuration + ): void { + $this->view_configuration = $view_configuration; + } + + #[\Override] + public function pasteContents( + string $page_content_id, + bool $a_self_ass = false + ): array|bool { + $answer_form_element = $this->page_manager + ->content($this->getDomDoc()) + ->getContentDomNode(...explode(':', $page_content_id)) + ?->getElementsByTagName(ilPCAnswerForm::ANSWER_FORM_ELEMENT_TAG); + + if ($answer_form_element?->length > 0) { + foreach ($answer_form_element->getIterator() as $node) { + self::$answer_form_mapping[$node->getAttribute(ilPCAnswerForm::ANSWER_FORM_ID_ATTRIBUTE)] + = $this->uuid_factory->uuid4AsString(); + } + } + + return parent::pasteContents($page_content_id, $a_self_ass); + } + + #[\Override] + public function cutContents( + array $page_content_ids + ): array|bool { + return parent::cutContents( + array_filter( + $page_content_ids, + fn(string $v): bool => $this->page_manager + ->content($this->getDomDoc()) + ->getContentDomNode(...explode(':', $v)) + ?->getElementsByTagName('AnswerForm') + ?->length < 1 + ) + ); + } + + /** + * 2026-06-30, sk:This is awfull, but as there are statics used in the copy + * process this is the only way I found to do this. If somebody has a better + * idea... + * + * @return array|null + */ + public static function getAnswerFormMapping(): array + { + return self::$answer_form_mapping; + } + + public static function setAnswerFormMapping( + array $answer_form_mapping + ): void { + self::$answer_form_mapping = $answer_form_mapping; + } + + public function addQuestionText( + string $text + ): void { + $this->buildDom(); + + $lng = $this->user->getLanguage(); + if ($lng === '') { + $lng = 'de'; + } + + $page_element = new \ilPCParagraph($this); + $page_element->create($this, 'pg'); + $page_element->setLanguage($lng); + $page_element->setText($text); + + $this->update(); + } +} diff --git a/components/ILIAS/Questions/Legacy/PageEditor/QstsQuestionPageConfig.php b/components/ILIAS/Questions/Legacy/PageEditor/QstsQuestionPageConfig.php new file mode 100644 index 000000000000..5a671e51ae45 --- /dev/null +++ b/components/ILIAS/Questions/Legacy/PageEditor/QstsQuestionPageConfig.php @@ -0,0 +1,31 @@ +setEnablePCType('Tabs', true); + $this->setEnablePCType('AnswerForm', true); + $this->setEnablePCType('LegacyAnswerFormText', true); + $this->setEnableInternalLinks(false); + $this->setEnablePageToc(true); + } +} diff --git a/components/ILIAS/Questions/Legacy/PageEditor/class.QstsQuestionPageGUI.php b/components/ILIAS/Questions/Legacy/PageEditor/class.QstsQuestionPageGUI.php new file mode 100644 index 000000000000..31746c812b8a --- /dev/null +++ b/components/ILIAS/Questions/Legacy/PageEditor/class.QstsQuestionPageGUI.php @@ -0,0 +1,92 @@ +getPageId()); + + $this->setEnabledPageFocus(false); + + $this->obj->setQuestion($question); + $this->obj->setParentId($obj_id); + $this->obj->setRequiredCapabilities($required_capabilities); + $this->obj->setViewConfiguration($view_configuration); + } + + #[\Override] + public function finishEditing(): void + { + $this->ctrl->redirectToURL($this->return_uri->__toString()); + } + + public function withEditView( + Edit $edit_view + ): self { + $clone = clone $this; + $clone->obj->setEditView($edit_view); + return $clone; + } + + public function withParticipantView( + Viewable $participant_view + ): self { + $clone = clone $this; + $clone->obj->setParticipantView($participant_view); + return $clone; + } + + public function withAttemptData( + ?Attempt $attempt + ): self { + $clone = clone $this; + $clone->obj->setAttemptData($attempt); + return $clone; + } + + public function withReturnUri( + URI $return_uri + ): self { + $clone = clone $this; + $clone->return_uri = $return_uri; + return $clone; + } +} diff --git a/components/ILIAS/Questions/Legacy/PageEditor/class.ilPCAnswerForm.php b/components/ILIAS/Questions/Legacy/PageEditor/class.ilPCAnswerForm.php new file mode 100644 index 000000000000..70ed2a3181b1 --- /dev/null +++ b/components/ILIAS/Questions/Legacy/PageEditor/class.ilPCAnswerForm.php @@ -0,0 +1,233 @@ +setType('answf'); + } + + #[\Override] + public static function getLangVars(): array + { + return ['ed_insert_pcqst', 'empty_question', 'pc_qst']; + } + + #[\Override] + public function modifyPageContentPostXsl( + string $output, + string $mode, + bool $abstract_only = false + ): string { + if ($this->pg_obj::class !== QstsQuestionPage::class) { + return $output; + } + + global $DIC; + $global_tpl = $DIC['tpl']; + $lng = $DIC['lng']; + $ui_factory = $DIC['ui.factory']; + $ui_renderer = $DIC['ui.renderer']; + $refinery = $DIC['refinery']; + + return mb_ereg_replace_callback( + self::ANSWER_FORM_PLACEHOLDER, + fn(array $matches): string => $this->renderAnswerForm( + $global_tpl, + $lng, + $ui_factory, + $ui_renderer, + $refinery, + $this->pg_obj->getQuestion()->getAnswerFormPropertiesByIdString($matches[1]), + $this->pg_obj->getAttemptData() + ), + $output + ); + } + + #[\Override] + public static function afterPageUpdate( + ilPageObject $page, + DOMDocument $domdoc, + string $xml, + bool $creation + ): void { + if ($page::class !== QstsQuestionPage::class || $creation) { + return; + } + + global $DIC; + $dom_util = $DIC->copage()->internal()->domain()->domUtil(); + /** @var Repository $question_repository */ + $question_repository = LocalDIC::dic()[Repository::class]; + + $uuid_factory = new UuidFactory(); + + /** @var \ILIAS\Questions\Question\Question $question */ + $question = $page->getQuestion(); + + $answer_form_mapping = \QstsQuestionPage::getAnswerFormMapping(); + \QstsQuestionPage::setAnswerFormMapping([]); + + $answer_forms = []; + foreach ($dom_util->path($domdoc, '//AnswerForm') as $node) { + $answer_form_id = $node->getAttribute(self::ANSWER_FORM_ID_ATTRIBUTE); + $answer_form = $question->getAnswerFormPropertiesByIdString($answer_form_id); + + if ($answer_form === null && in_array($answer_form_id, $answer_form_mapping)) { + $old_answer_form_id = array_search($answer_form_id, $answer_form_mapping); + $old_answer_form = $question->getAnswerFormPropertiesByIdString( + $old_answer_form_id + ); + + if ($old_answer_form === null) { + $old_answer_form_uuid = $uuid_factory->fromString($answer_form_id); + $old_answer_form = $question_repository->getQuestionForAnswerFormId( + $old_answer_form_uuid + )->getAnswerFormPropertiesByIdString( + $old_answer_form_uuid + ); + } + + $question = $question->withAnswerFormProperties( + $old_answer_form->clone( + $uuid_factory, + [ + 'new_question_id' => $question->getId(), + 'answer_form_id' => $uuid_factory->fromString( + $answer_form_id + ) + ] + ) + ); + } + + $answer_forms[] = $answer_form_id; + } + + $page->setQuestion($question->withoutDeletedAnswerForms($answer_forms)); + + $question_repository->update( + [$page->getQuestion()] + ); + } + + #[\Override] + public static function handleCopiedContent( + DOMDocument $domdoc, + bool $self_ass = true, + bool $clone_mobs = false, + int $new_parent_id = 0, + int $obj_copy_id = 0 + ): void { + global $DIC; + $dom_util = $DIC->copage()->internal()->domain()->domUtil(); + + $answer_form_mapping = \QstsQuestionPage::getAnswerFormMapping(); + if ($answer_form_mapping === []) { + return; + } + + foreach ($dom_util->path($domdoc, '//AnswerForm') as $node) { + $old_answer_form_id = $node->getAttribute(self::ANSWER_FORM_ID_ATTRIBUTE); + if (isset($answer_form_mapping[$old_answer_form_id])) { + $node->setAttribute( + self::ANSWER_FORM_ID_ATTRIBUTE, + $answer_form_mapping[$old_answer_form_id] + ); + } + } + } + + public function create( + Uuid $answer_form_id + ): void { + $this->createInitialChildNode( + $this->hier_id, + '', + self::ANSWER_FORM_ELEMENT_TAG, + [self::ANSWER_FORM_ID_ATTRIBUTE => $answer_form_id->toString()] + ); + } + + public function getAnswerFormIdStringFromAttribute(): string + { + return $this->getChildNode()->attributes + ->getNamedItem(self::ANSWER_FORM_ID_ATTRIBUTE)->nodeValue; + } + + private function renderAnswerForm( + GlobalTemplate $global_tpl, + Language $lng, + UIFactory $ui_factory, + UIRenderer $ui_renderer, + Refinery $refinery, + ?AnswerFormProperties $answer_form_properties, + ?Attempt $attempt_data + ): string { + if ($answer_form_properties === null) { + return $lng->txt('broken_answer_form'); + } + + /** @var RequiredCapabilities $required_capabilities */ + $required_capabilities = $this->pg_obj->getRequiredCapabilities(); + + $answer_form_response = $attempt_data?->getResponseForQuestion( + $answer_form_properties->getQuestionId() + )?->getAnswerFormResponse( + $answer_form_properties->getAnswerFormId() + ); + + return $ui_renderer->render( + $required_capabilities + ->getParticipantViewProvider() + ->getParticipantView($answer_form_properties) + ->show( + $global_tpl, + $lng, + $refinery, + $ui_factory, + $required_capabilities, + $this->pg_obj->getViewConfiguration(), + $answer_form_properties, + $attempt_data, + $answer_form_response + ) + ); + } +} diff --git a/components/ILIAS/Questions/Legacy/PageEditor/class.ilPCAnswerFormGUI.php b/components/ILIAS/Questions/Legacy/PageEditor/class.ilPCAnswerFormGUI.php new file mode 100644 index 000000000000..4edb44721193 --- /dev/null +++ b/components/ILIAS/Questions/Legacy/PageEditor/class.ilPCAnswerFormGUI.php @@ -0,0 +1,95 @@ +tabs = $DIC['ilTabs']; + $this->ui_renderer = $DIC['ui.renderer']; + $this->data_factory = new DataFactory(); + + $this->edit_view = $pg_obj->getEditView(); + + parent::__construct($pg_obj, $content_obj, $hier_id, $pc_id); + } + + public function executeCommand() + { + $cmd = $this->ctrl->getCmd() . 'Cmd'; + $this->$cmd(); + } + + public function insertCmd(): void + { + $content_obj = new ilPCAnswerForm($this->pg_obj); + $content_obj->setHierId($this->hier_id); + $this->tpl->setContent( + $this->ui_renderer->render( + $this->edit_view->getCreateAnswerForm( + $this->data_factory->uri( + ILIAS_HTTP_PATH . '/' . $this->ctrl->getFormActionByClass(self::class, 'insert') + ), + $this->pg_obj->getQuestion(), + $content_obj + ) + ) + ); + } + + public function editCmd(): void + { + /** @var \ILIAS\Questions\Question\Question $question */ + $question = $this->pg_obj->getQuestion(); + $answer_form_properties = $question->getAnswerFormPropertiesByIdString( + $this->getContentObject()->getAnswerFormIdStringFromAttribute() + ); + + $this->tpl->setContent( + $this->ui_renderer->render( + $this->edit_view->getEditAnswerForm( + $this->data_factory->uri( + ILIAS_HTTP_PATH . '/' . $this->ctrl->getFormActionByClass(self::class, 'edit') + ), + $question, + $answer_form_properties, + $answer_form_properties->getDefinition() + ) + ) + ); + } +} diff --git a/components/ILIAS/Questions/Legacy/PageEditor/class.ilPCLegacyAnswerFormText.php b/components/ILIAS/Questions/Legacy/PageEditor/class.ilPCLegacyAnswerFormText.php new file mode 100644 index 000000000000..a5fdabc3cbfb --- /dev/null +++ b/components/ILIAS/Questions/Legacy/PageEditor/class.ilPCLegacyAnswerFormText.php @@ -0,0 +1,61 @@ +setType('laft'); + } + + #[\Override] + public function modifyPageContentPostXsl( + string $output, + string $mode, + bool $abstract_only = false + ): string { + if ($this->pg_obj::class !== QstsQuestionPage::class) { + return $output; + } + + return mb_ereg_replace_callback( + self::TEXT_PLACEHOLDER, + static fn(array $matches): string => \ilRTE::_replaceMediaObjectImageSrc( + base64_decode($matches[1]) + ), + $output + ); + } + + public function create( + string $legacy_answer_form_text + ): void { + $this->createInitialChildNode( + $this->hier_id, + '', + self::ELEMENT_TAG, + [self::TEXT_ATTRIBUTE => $legacy_answer_form_text] + ); + } +} diff --git a/components/ILIAS/Questions/Legacy/PageEditor/class.ilPCLegacyAnswerFormTextGUI.php b/components/ILIAS/Questions/Legacy/PageEditor/class.ilPCLegacyAnswerFormTextGUI.php new file mode 100644 index 000000000000..ba24c634cdc7 --- /dev/null +++ b/components/ILIAS/Questions/Legacy/PageEditor/class.ilPCLegacyAnswerFormTextGUI.php @@ -0,0 +1,33 @@ +tpl->setOnScreenMessage(MessageBox::FAILURE, $this->lng->txt('legacy_text_cannot_be_edited'), true); + $this->ctrl->redirectByClass(\QstsQuestionPageGUI::class, 'edit'); + } +} diff --git a/components/ILIAS/Questions/Legacy/Temp/Attempt.php b/components/ILIAS/Questions/Legacy/Temp/Attempt.php new file mode 100644 index 000000000000..0c705fd68274 --- /dev/null +++ b/components/ILIAS/Questions/Legacy/Temp/Attempt.php @@ -0,0 +1,74 @@ +attempt_id; + } + + public function isQuestionSolved( + Uuid $question_id + ): bool { + return in_array($question_id->toString(), $this->solved_questions); + } + + public function getSolvedQuestionsForStorage(): string + { + return implode(',', $this->solved_questions); + } + + public function withAdditionalSolvedQuestion( + Uuid $question_id + ): self { + if ($this->isQuestionSolved($question_id)) { + return $this; + } + + $clone = clone $this; + $clone->solved_questions[] = $question_id->toString(); + return $clone; + } + + public function withQuestionRemovedFromSolved( + Uuid $question_id + ): self { + if (!$this->isQuestionSolved($question_id)) { + return $this; + } + + $clone = clone $this; + unset( + $clone->solved_questions[array_search($question_id->toString(), $clone->solved_questions)] + ); + return $clone; + } +} diff --git a/components/ILIAS/Questions/Legacy/Temp/AttemptRepository.php b/components/ILIAS/Questions/Legacy/Temp/AttemptRepository.php new file mode 100644 index 000000000000..31a59b4fe920 --- /dev/null +++ b/components/ILIAS/Questions/Legacy/Temp/AttemptRepository.php @@ -0,0 +1,86 @@ +db->fetchObject( + $this->db->queryF( + 'SELECT attempt_id, solved_questions from ' . self::TABLE_NAME . ' WHERE user_id = %s', + [FieldDefinition::T_INTEGER], + [$user_id] + ) + ); + + if ($value === null) { + return null; + } + + return new Attempt( + $this->uuid_factory->fromString($value->attempt_id), + $value->solved_questions === '' + ? [] + : explode(',', $value->solved_questions) + ); + } + + public function storeNew( + int $user_id, + Uuid $attempt_id + ): void { + $this->db->insert( + self::TABLE_NAME, + [ + 'user_id' => [FieldDefinition::T_INTEGER, $user_id], + 'attempt_id' => [FieldDefinition::T_TEXT, $attempt_id->toString()] + ] + ); + } + + public function storeSolved( + Attempt $attempt + ): void { + $this->db->update( + self::TABLE_NAME, + [ + 'solved_questions' => [FieldDefinition::T_TEXT, $attempt->getSolvedQuestionsForStorage()] + ], + [ + 'attempt_id' => [FieldDefinition::T_TEXT, $attempt->getAttemptId()->toString()] + ] + ); + } +} diff --git a/components/ILIAS/Questions/Questions.php b/components/ILIAS/Questions/Questions.php new file mode 100644 index 000000000000..01c8fae14235 --- /dev/null +++ b/components/ILIAS/Questions/Questions.php @@ -0,0 +1,133 @@ + new PersistenceFactory(); + $internal[\EvalMath::class] = static fn() => new \EvalMath(); + + $contribute[AgentInterface::class] = static fn() => + new Agent( + $internal[PersistenceFactory::class], + new TableNameBuilder( + QuestionRepository::COMPONENT_NAMESPACE, + null + ), + $seek[AnswerFormMigration::class], + $seek[CapabilityMigration::class] + ); + $contribute[AnswerFormMigration::class] = static fn() + => new MigrationCloze( + new ClozeTableDefinitions( + $internal[PersistenceFactory::class], + new TableSubNameSpace( + 'ILIAS', + 'cloze' + ) + ), + $internal[\EvalMath::class] + ); + $contribute[AnswerFormMigration::class] = static fn() + => new MigrationLongMenu( + new ClozeTableDefinitions( + $internal[PersistenceFactory::class], + new TableSubNameSpace( + 'ILIAS', + 'cloze' + ) + ) + ); + $contribute[AnswerFormMigration::class] = static fn() + => new MigrationNumeric( + new ClozeTableDefinitions( + $internal[PersistenceFactory::class], + new TableSubNameSpace( + 'ILIAS', + 'cloze' + ) + ), + $internal[\EvalMath::class] + ); + $contribute[AnswerFormMigration::class] = static fn() + => new MigrationTextSubset( + new ClozeTableDefinitions( + $internal[PersistenceFactory::class], + new TableSubNameSpace( + 'ILIAS', + 'cloze' + ) + ) + ); + + $contribute[CapabilityMigration::class] = static fn() + => new TextFeedbackMigration( + new TextFeedbackTableDefinitions( + $internal[PersistenceFactory::class] + ) + ); + $contribute[CapabilityMigration::class] = static fn() + => new SuggestedLearningContentMigration( + new SuggestedLearningContentTableDefinitions( + $internal[PersistenceFactory::class] + ) + ); + + $contribute[Component\Resource\PublicAsset::class] = fn() + => new Component\Resource\ComponentJS( + $this, + 'js/dist/questions.js' + ); + $contribute[User\Settings\UserSettings::class] = fn() + => new Questions\UserSettings\Settings(); + } +} diff --git a/components/ILIAS/Questions/README.md b/components/ILIAS/Questions/README.md new file mode 100644 index 000000000000..6b2a3b9fda2b --- /dev/null +++ b/components/ILIAS/Questions/README.md @@ -0,0 +1 @@ +# Questions diff --git a/components/ILIAS/Questions/maintenance.json b/components/ILIAS/Questions/maintenance.json new file mode 100644 index 000000000000..462ad3856cc3 --- /dev/null +++ b/components/ILIAS/Questions/maintenance.json @@ -0,0 +1,14 @@ +{ + "maintenance_model": "Classic", + "first_maintainer": "dstrassner(48931)", + "second_maintainer": "mbecker(27266)", + "implicit_maintainers": [], + "coordinator": [ + "" + ], + "tester": "SIG EA", + "testcase_writer": "Fabian(27631)", + "path": "Modules/Test", + "belong_to_component": "Test & Assessment", + "used_in_components": [] +} \ No newline at end of file diff --git a/components/ILIAS/Questions/resources/js/dist/questions.js b/components/ILIAS/Questions/resources/js/dist/questions.js new file mode 100644 index 000000000000..ca6fc283a510 --- /dev/null +++ b/components/ILIAS/Questions/resources/js/dist/questions.js @@ -0,0 +1,15 @@ +/** + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + */ +!function(e){"use strict";function t(e,t,i){return"Enter"===i.key&&"LI"===i.target.nodeName?(i.stopImmediatePropagation(),i.preventDefault(),void n(i)):"ArrowDown"===i.key?(i.stopImmediatePropagation(),i.preventDefault(),"UL"===i.target.nextElementSibling?.nodeName&&i.target.nextElementSibling.firstElementChild.focus(),void("LI"===i.target.nodeName&&null!==i.target.nextElementSibling&&i.target.nextElementSibling.focus())):"ArrowUp"===i.key&&"LI"===i.target.nodeName?(i.stopImmediatePropagation(),i.preventDefault(),void(null===i.target.previousElementSibling?i.target.parentElement.previousElementSibling.focus():i.target.previousElementSibling.focus())):void function(e,t,i){"UL"===i.target.nextElementSibling?.nodeName&&i.target.nextElementSibling.remove();if("Tab"===i.key||i.target.value.lengthe.toLowerCase().includes(i.target.value.toLowerCase()));if(0===o.length)return;const r=document.createElement("ul");o.forEach(e=>{const t=document.createElement("li");t.tabIndex=0,t.textContent=e,r.appendChild(t)}),r.addEventListener("click",n),r.addEventListener("keyup",n),i.target.parentNode.appendChild(r)}(e,t,i)}function n(e){"keydown"===e.type&&"Enter"!==e.key||(e.target.parentNode.previousElementSibling.value=e.target.textContent,e.target.parentNode.previousElementSibling.focus(),e.target.parentNode.remove())}const i="gap_type",o="lower_limit",r="upper_limit",s="response";function a(e,t){const n=parseFloat(t);return Number.isNaN(e[r])||Number.isNaN(parseFloat(e[r]))?["o"]:n<=e[r]?["o","b"]:void 0===e[r]&&n===e[o]||n>=e[o]?["i"]:["o","a"]}function c(e,t){return"numeric"===e[i]&&"i"===a(e[s],t)[0]||"numeric"!==e[i]&&t===e[s].given_response}function l(e,t){return""===t?["no_response"]:"numeric"===e[i]?a(e,t):c(e,t)?["best_response"]:["other_response"]}var u={isBestResponse(e,t){if(Object.keys(e).length!==Object.keys(t).length)return!1;const n=Object.keys(t);for(let i=0;ie[t],e.target.ownerDocument.defaultView);return o[n].apply(o,i)}function g(e){const t=atob(e),n=new Uint8Array(t.length);for(let e=0;ee.length>t.length?e.length:t.length)),e.addEventListener("keyup",e=>t(n,i,e)))},e.questions.cloze.specificTextFeedback=e.questions.specificTextFeedback||{},e.questions.cloze.specificTextFeedback=u,e.questions.textFeedback=e.questions.textFeedback||{},e.questions.textFeedback.retrieve=function(e,t,n,i,o){const{panelTitle:r}=n;delete n.panelTitle;const{specificFeedbackEndPoint:s}=n;delete n.specificFeedbackEndpoint;const a=p(e,s,"isBestResponse",[i,o])?"best":"other";let c="";return Object.hasOwn(n,a)&&(c=n[a]),t.replace("-panel-title-to-replace-",r).replace("-panel-content-to-replace-",c+p(e,s,"retrieveSpecificFeedback",[n,i,o]))},e.questions.suggestedLearningContent=e.questions.suggestedLearningContent||{},e.questions.suggestedLearningContent.retrieve=function(e,t,n){return n},e.questions.asyncView=e.questions.asyncView||{},e.questions.asyncView.showFeedback=function(e,t,n,i,o,...r){const s=g(t),a=JSON.parse(g(i));let c="";void 0!==o&&(c+=g(o));const l=e.target.previousElementSibling.querySelectorAll("input, select, textarea"),u=Array.from(l).reduce((e,t)=>(e[t.name]=t.value,u),{}),p=JSON.parse(g(n));r.forEach((t,n)=>{c+=t(e,s,p[n],a,u)}),e.target.nextElementSibling.innerHTML=c}}(il); diff --git a/components/ILIAS/Questions/resources/js/src/AsyncView/showFeedback.js b/components/ILIAS/Questions/resources/js/src/AsyncView/showFeedback.js new file mode 100644 index 000000000000..475345790574 --- /dev/null +++ b/components/ILIAS/Questions/resources/js/src/AsyncView/showFeedback.js @@ -0,0 +1,77 @@ +/** + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + * + */ + +/** + * @param {String} base64 + * @returns {string} + */ +function base64toUtf8(base64) { + const binaryString = atob(base64); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i += 1) { + bytes[i] = binaryString.charCodeAt(i); + } + const decoder = new TextDecoder(); + return decoder.decode(bytes); +} + +/** + * @param {Event} event + * @param {string} panel Standard panel scafold rendered in php. + * @param {Object} feedbackDataArray Object containing all possible feedbacks + * @param {Object} bestResponse + * @param {Object} response + * @param {...string} Endpoints the retrieve feedbacks of different feedback providers + */ +export default function showFeedback( + event, + panel, + feedbackDataArray, + bestResponse, + bestResponseOutput, + ...feedbackCallbacks +) { + const decodedPanel = base64toUtf8(panel); + const decodedBestResponse = JSON.parse( + base64toUtf8( + bestResponse, + ), + ); + + let feedback = ''; + + if (typeof bestResponseOutput !== 'undefined') { + feedback += base64toUtf8(bestResponseOutput); + } + const inputs = event.target.previousElementSibling.querySelectorAll('input, select, textarea'); + const response = Array.from(inputs).reduce( + (resp, input) => { + resp[input.name] = input.value; + return response; + }, + {}, + ); + + const decodedFeedbackData = JSON.parse(base64toUtf8(feedbackDataArray)); + + feedbackCallbacks.forEach( + (v, i) => { + feedback += v(event, decodedPanel, decodedFeedbackData[i], decodedBestResponse, response); + }, + ); + + event.target.nextElementSibling.innerHTML = feedback; +} diff --git a/components/ILIAS/Questions/resources/js/src/Cloze/feedback.js b/components/ILIAS/Questions/resources/js/src/Cloze/feedback.js new file mode 100644 index 000000000000..47def6716b46 --- /dev/null +++ b/components/ILIAS/Questions/resources/js/src/Cloze/feedback.js @@ -0,0 +1,140 @@ +/** + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + * + */ + +const keyGapType = 'gap_type'; +const keyLowerLimit = 'lower_limit'; +const keyUpperLimit = 'upper_limit'; +const keyResponseObject = 'response'; +const keyGivenResponse = 'given_response'; +const keyNoResponse = 'no_response'; +const keyBestResponse = 'best_response'; +const keyOtherResponse = 'other_response'; +const keyInRange = 'i'; +const keyOutOfRange = 'o'; +const keyBelowRange = 'b'; +const keyAboveRange = 'a'; + +/** + * @param {Object} bestResponse + * @param {string} response + * @return {Array} All feedback keys triggered by given response. + */ +function getKeysForNumericValue(bestResponse, response) { + const responseAsFloat = parseFloat(response); + + if (Number.isNaN(bestResponse[keyUpperLimit]) + || Number.isNaN(parseFloat(bestResponse[keyUpperLimit]))) { + return [keyOutOfRange]; + } + + if (responseAsFloat <= bestResponse[keyUpperLimit]) { + return [keyOutOfRange, keyBelowRange]; + } + + if ((typeof bestResponse[keyUpperLimit] === 'undefined' + && responseAsFloat === bestResponse[keyLowerLimit]) + || responseAsFloat >= bestResponse[keyLowerLimit]) { + return [keyInRange]; + } + + return [keyOutOfRange, keyAboveRange]; +} + +/** + * @param {Object} bestResponse + * @param {string} response + * @return {bool} + */ +function isResponseForInputBest(bestResponse, response) { + if ((bestResponse[keyGapType] === 'numeric' + && getKeysForNumericValue(bestResponse[keyResponseObject], response)[0] === keyInRange) + || (bestResponse[keyGapType] !== 'numeric' + && response === bestResponse[keyResponseObject][keyGivenResponse])) { + return true; + } + + return false; +} + +/** + * @param {Object} bestResponse + * @param {string} response + * @return {Array} All feedback keys triggered by given response. + */ +function getKeysForOtherAnswerValue(bestResponse, response) { + if (response === '') { + return [keyNoResponse]; + } + + if (bestResponse[keyGapType] === 'numeric') { + return getKeysForNumericValue(bestResponse, response); + } + + return isResponseForInputBest(bestResponse, response) + ? [keyBestResponse] + : [keyOtherResponse]; +} + +export default { + /** + * @param {Object} bestResponse + * @param {Object} response + */ + isBestResponse(bestResponse, response) { + if (Object.keys(bestResponse).length !== Object.keys(response).length) { + return false; + } + + const keys = Object.keys(response); + for (let i = 0; i < keys.length; i += 1) { + if (!isResponseForInputBest(bestResponse[keys[i]], response[keys[i]])) { + return false; + } + } + return true; + }, + /** + * @param {Object} data + * @param {Object} bestResponse + * @param {Object} response + */ + retrieveSpecificFeedback(data, bestResponse, response) { + let feedbacks = ''; + + const responseKeys = Object.keys(response); + for (let i = 0; i < responseKeys.length; i += 1) { + const property = responseKeys[i]; + if (Object.hasOwn(data, property)) { + if (Object.hasOwn(data[property], response[property])) { + feedbacks += data[property][response[property]]; + } + + const keys = getKeysForOtherAnswerValue( + bestResponse[property], + response[property], + ); + + for (let j = 0; j < keys.length; j += 1) { + if (Object.hasOwn(data[property], keys[i])) { + feedbacks += data[property][keys[j]]; + } + } + } + } + + return feedbacks; + }, +}; diff --git a/components/ILIAS/Questions/resources/js/src/Cloze/longmenuAutocomplete.js b/components/ILIAS/Questions/resources/js/src/Cloze/longmenuAutocomplete.js new file mode 100644 index 000000000000..71b2a0846c4f --- /dev/null +++ b/components/ILIAS/Questions/resources/js/src/Cloze/longmenuAutocomplete.js @@ -0,0 +1,127 @@ +/** + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + * + */ + +/** + * @param {number} autocompleteLength + * @param {array} answerOptions + * @param {Event} e + */ +function keyHandler(autocompleteLength, answerOptions, e) { + if (e.key === 'Enter' && e.target.nodeName === 'LI') { + e.stopImmediatePropagation(); + e.preventDefault(); + onSelectHandler(e); + return; + } + + if (e.key === 'ArrowDown') { + e.stopImmediatePropagation(); + e.preventDefault(); + if (e.target.nextElementSibling?.nodeName === 'UL') { + e.target.nextElementSibling.firstElementChild.focus(); + } + + if (e.target.nodeName === 'LI' && e.target.nextElementSibling !== null) { + e.target.nextElementSibling.focus(); + } + return; + } + + if (e.key === 'ArrowUp' && e.target.nodeName === 'LI') { + e.stopImmediatePropagation(); + e.preventDefault(); + if (e.target.previousElementSibling === null) { + e.target.parentElement.previousElementSibling.focus(); + } else { + e.target.previousElementSibling.focus(); + } + return; + } + + onChangeHandler(autocompleteLength, answerOptions, e); +} + +/** + * @param {number} autocompleteLength + * @param {array} answerOptions + * @param {Event} e + */ +function onChangeHandler(autocompleteLength, answerOptions, e) { + if (e.target.nextElementSibling?.nodeName === 'UL') { + e.target.nextElementSibling.remove(); + } + + if (e.key === 'Tab' || e.target.value.length < autocompleteLength) { + return; + } + + const matchingAnswers = answerOptions.filter( + (answer) => answer.toLowerCase().includes(e.target.value.toLowerCase()), + ); + + if (matchingAnswers.length === 0) { + return; + } + + const list = document.createElement('ul'); + matchingAnswers.forEach((answer) => { + const listElement = document.createElement('li'); + listElement.tabIndex = 0; + listElement.textContent = answer; + list.appendChild(listElement); + }); + list.addEventListener('click', onSelectHandler); + list.addEventListener('keyup', onSelectHandler); + e.target.parentNode.appendChild(list); +} + +function onSelectHandler(e) { + if (e.type === 'keydown' && e.key !== 'Enter') { + return; + } + e.target.parentNode.previousElementSibling.value = e.target.textContent; + e.target.parentNode.previousElementSibling.focus(); + e.target.parentNode.remove(); +} + +/** + * @param {HTMLElement} input + * @param {number} autocompleteLength + * @param {array} answerOptions + */ +export default function longmenuAutocomplete( + input, + autocompleteLength, + answerOptions, +) { + if (input.nodeName === 'INPUT') { + input.setAttribute( + 'size', + answerOptions.reduce( + (a, b) => { + if (a.length > b.length) { + return a.length; + } + return b.length; + }, + ), + ); + input.addEventListener( + 'keyup', + (e) => keyHandler(autocompleteLength, answerOptions, e), + ); + } +} diff --git a/components/ILIAS/Questions/resources/js/src/SuggestedLearningContent/retrieve.js b/components/ILIAS/Questions/resources/js/src/SuggestedLearningContent/retrieve.js new file mode 100644 index 000000000000..60318c3fe529 --- /dev/null +++ b/components/ILIAS/Questions/resources/js/src/SuggestedLearningContent/retrieve.js @@ -0,0 +1,25 @@ +/** + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + * + */ + +/** + * @param {Event} event + * @param {string} panel Standard panel scafold rendered in php. + * @param {Object} data Object containing all possible feedbacks + * @returns {string} + */ +export default function retrieve(event, panel, data) { + return data; +} diff --git a/components/ILIAS/Questions/resources/js/src/TextFeedback/retrieve.js b/components/ILIAS/Questions/resources/js/src/TextFeedback/retrieve.js new file mode 100644 index 000000000000..e5c90af3105c --- /dev/null +++ b/components/ILIAS/Questions/resources/js/src/TextFeedback/retrieve.js @@ -0,0 +1,81 @@ +/** + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + * + */ + +const placeholderPanelTitle = '-panel-title-to-replace-'; +const placeholderPanelContent = '-panel-content-to-replace-'; + +const keyBestResponse = 'best'; +const keyOtherResponse = 'other'; + +/** + * This function is used to call a namespaced function that has been handed in + * the form of two Strings. This is needed to be able to initialize most of the + * js in a file, but still define proper endpoints in php without some funky + * template parsing of js files. + * + * @param {Event} event + * @param {string} namespace + * @param {string} func + * @param {Array} args + * @returns {string} + */ +function executeSpecificEndPointFunction(event, namespace, func, args) { + const context = namespace.split('.').reduce( + (c, v) => c[v], + event.target.ownerDocument.defaultView, + ); + return context[func].apply(context, args); +} + +/** + * @param {Event} event + * @param {string} panel Standard panel scafold rendered in php. + * @param {Object} data Object containing all possible feedbacks + * @param {Object} bestResponse + * @param {Object} response + * @returns {string} + */ +export default function retrieve(event, panel, data, bestResponse, response) { + const { panelTitle } = data; + delete data.panelTitle; + const { specificFeedbackEndPoint } = data; + delete data.specificFeedbackEndpoint; + + const key = executeSpecificEndPointFunction( + event, + specificFeedbackEndPoint, + 'isBestResponse', + [bestResponse, response], + ) ? keyBestResponse : keyOtherResponse; + + let genericFeedback = ''; + if (Object.hasOwn(data, key)) { + genericFeedback = data[key]; + } + + return panel.replace( + placeholderPanelTitle, + panelTitle, + ).replace( + placeholderPanelContent, + genericFeedback + executeSpecificEndPointFunction( + event, + specificFeedbackEndPoint, + 'retrieveSpecificFeedback', + [data, bestResponse, response], + ), + ); +} diff --git a/components/ILIAS/Questions/resources/js/src/questions.js b/components/ILIAS/Questions/resources/js/src/questions.js new file mode 100644 index 000000000000..43194811908d --- /dev/null +++ b/components/ILIAS/Questions/resources/js/src/questions.js @@ -0,0 +1,34 @@ +/** + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + * + */ + +import il from 'ilias'; +import longmenuAutocomplete from './Cloze/longmenuAutocomplete.js'; +import clozeSpecificTextFeedback from './Cloze/feedback.js'; +import textFeedbackRetrieve from './TextFeedback/retrieve.js'; +import suggestedLearningContentRetrieve from './SuggestedLearningContent/retrieve.js'; +import asyncViewShowFeedback from './AsyncView/showFeedback.js'; + +il.questions = il.questions || {}; +il.questions.cloze = il.questions.cloze || {}; +il.questions.cloze.initLongmenuGap = longmenuAutocomplete; +il.questions.cloze.specificTextFeedback = il.questions.specificTextFeedback || {}; +il.questions.cloze.specificTextFeedback = clozeSpecificTextFeedback; +il.questions.textFeedback = il.questions.textFeedback || {}; +il.questions.textFeedback.retrieve = textFeedbackRetrieve; +il.questions.suggestedLearningContent = il.questions.suggestedLearningContent || {}; +il.questions.suggestedLearningContent.retrieve = suggestedLearningContentRetrieve; +il.questions.asyncView = il.questions.asyncView || {}; +il.questions.asyncView.showFeedback = asyncViewShowFeedback; diff --git a/components/ILIAS/Questions/resources/js/src/rollup.config.js b/components/ILIAS/Questions/resources/js/src/rollup.config.js new file mode 100644 index 000000000000..8ffc0a1b98f6 --- /dev/null +++ b/components/ILIAS/Questions/resources/js/src/rollup.config.js @@ -0,0 +1,27 @@ +import terser from '@rollup/plugin-terser'; +import copyright from '../../../../../../scripts/Copyright-Checker/copyright.js'; +import preserveCopyright from '../../../../../../scripts/Copyright-Checker/preserveCopyright.js'; + +export default { + external: [ + 'document', + 'ilias', + ], + input: './questions.js', + output: { + file: '../dist/questions.js', + format: 'iife', + banner: copyright, + globals: { + document: 'document', + ilias: 'il', + }, + plugins: [ + terser({ + format: { + comments: preserveCopyright, + }, + }), + ], + }, +}; diff --git a/components/ILIAS/Questions/service.xml b/components/ILIAS/Questions/service.xml new file mode 100644 index 000000000000..2f7a463b3da2 --- /dev/null +++ b/components/ILIAS/Questions/service.xml @@ -0,0 +1,21 @@ + + + + + adm + + + + + + + + + + + + + + + diff --git a/components/ILIAS/Questions/src/Administration/ConfigurationRepository.php b/components/ILIAS/Questions/src/Administration/ConfigurationRepository.php new file mode 100755 index 000000000000..fa9e33a3ee4e --- /dev/null +++ b/components/ILIAS/Questions/src/Administration/ConfigurationRepository.php @@ -0,0 +1,85 @@ +user_setting_create_mode->isChangeableByUser(); + } + + public function getGlobalCreateMode(): CreateModes + { + return CreateModes::tryFrom( + $this->questions_settings->get( + self::SETTINGS_KEY_CREATE_MODE, + '' + ) + ) ?? CreateModes::getDefaultMode(); + } + + public function isCreateModeSimple( + DefaultEnvironment $environment + ): bool { + return $this->isCreateModeChangeableByUser() && $environment->isCreateModeSimple() + || $this->getGlobalCreateMode() === CreateModes::Simple; + } + + public function persistCreateMode( + CreateModes $create_mode + ): void { + $this->questions_settings->set( + self::SETTINGS_KEY_CREATE_MODE, + $create_mode->value + ); + } + + public function getInputForCreateMode( + FieldFactory $field_factory, + Language $lng, + Refinery $refinery + ): Input { + return $this->user_setting_create_mode->getInput( + $field_factory, + $lng, + $refinery, + $this->common_settings + ); + } +} diff --git a/components/ILIAS/Questions/src/Administration/class.ConfigurationGUI.php b/components/ILIAS/Questions/src/Administration/class.ConfigurationGUI.php new file mode 100755 index 000000000000..74ff8631a861 --- /dev/null +++ b/components/ILIAS/Questions/src/Administration/class.ConfigurationGUI.php @@ -0,0 +1,115 @@ +ctrl->getCmd(self::CMD_DEFAULT) . 'Cmd'; + $this->$cmd(); + } + + private function viewCmd( + ?StandardForm $form = null + ): void { + $this->tpl->setContent( + $this->ui_renderer->render( + $form ?? $this->buildSettingsForm() + ) + ); + } + + private function saveCmd(): void + { + $form = $this->buildSettingsForm()->withRequest($this->http->request()); + $data = $form->getData(); + if ($data === null) { + $this->viewCmd($form); + } + + $this->repository->persistCreateMode( + $data['default_user_settings']['create_mode'] + ); + + $this->ctrl->redirectByClass( + [ + \ilAdministrationGUI::class, + \ilObjQuestionsGUI::class, + self::class + ] + ); + } + + private function buildSettingsForm(): StandardForm + { + return $this->ui_factory->input()->container()->form()->standard( + $this->ctrl->getLinkTargetByClass( + [ + \ilAdministrationGUI::class, + \ilObjQuestionsGUI::class, + self::class + ], + self::CMD_SAVE + ), + [ + 'default_user_settings' => $this->ui_factory->input()->field()->section( + [ + 'create_mode' => $this->repository->getInputForCreateMode( + $this->ui_factory->input()->field(), + $this->lng, + $this->refinery + )->withAdditionalTransformation( + $this->refinery->custom()->transformation( + static fn(string $v): CreateModes => CreateModes::tryFrom($v) + ?? CreateModes::getDefaultMode() + ) + ) + ], + $this->lng->txt('default_user_settings') + ) + ] + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/AsyncView/AsyncView.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/AsyncView/AsyncView.php new file mode 100644 index 000000000000..4269aba6cb26 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/AsyncView/AsyncView.php @@ -0,0 +1,256 @@ +setVariable( + self::TEMPLATE_VARIABLE_ANSWER_FORM, + $this->getAnswerFormPresentation( + $lng, + $ui_factory, + $view_configuration, + $answer_form_properties, + $attempt_data, + $response_data + ) + ); + + if (!$view_configuration->isInteractive()) { + return $ui_factory->legacy()->content($tpl->get()); + } + + $global_tpl->addJavaScript(self::JAVASCRIPT_BASE_FILE); + + foreach ($this->getJavascriptFiles() as $file) { + $global_tpl->addJavaScript($file); + } + + $feedback_array = $this->buildFeedbackArray( + $lng, + $refinery, + $ui_factory, + $required_capabilities, + $view_configuration, + $answer_form_properties, + $attempt_data + ); + + $tpl->setVariable( + self::TEMPLATE_VARIABLE_CHECK_BUTTON, + $this->ui_renderer->render( + $ui_factory->button()->standard( + $lng->txt('check'), + '' + )->withAdditionalOnLoadCode( + fn(string $id) => "document.querySelector(`#{$id}`).addEventListener(" + . '"click", (e) => il.questions.asyncView.showFeedback(e, "' + . base64_encode( + $this->buildFeedbackPanelWithPlaceholders($ui_factory) + ) . '", "' . base64_encode(json_encode($feedback_array['feedback_data'])) . '", "' + . ($feedback_array[self::KEY_BEST_RESPONSE] === null + ? '' + : base64_encode(json_encode($feedback_array[self::KEY_BEST_RESPONSE]))) . '", ' + . (array_key_exists(self::KEY_BEST_RESPONSE_OUTPUT, $feedback_array) + ? '"' . base64_encode($feedback_array[self::KEY_BEST_RESPONSE_OUTPUT]) . '"' + : 'undefined') + . ', ' + . implode(', ', $feedback_array['feedback_callbacks']) + . '));' + ) + ) + ); + + return $ui_factory->legacy()->content($tpl->get()); + } + + private function buildFeedbackArray( + Language $lng, + Refinery $refinery, + UIFactory $ui_factory, + RequiredCapabilities $required_capabilities, + ViewConfiguration $view_configuration, + AnswerFormProperties $answer_form_properties, + ?AdditionalAttemptData $attempt_data, + ): array { + $best_response = $required_capabilities + ->getMarking($answer_form_properties) + ->getBestResponse($answer_form_properties); + $feedbacks = [ + self::KEY_BEST_RESPONSE => $best_response->toClientSideRepresentation() + ]; + + if ($view_configuration->showBestResponse()) { + $feedbacks[self::KEY_BEST_RESPONSE_OUTPUT] = $this->ui_renderer->render( + $ui_factory->panel()->standard( + $lng->txt('best_response'), + $ui_factory->legacy()->content( + $this->getAnswerFormPresentation( + $lng, + $ui_factory, + $view_configuration->withShowBestResponse(true), + $answer_form_properties, + $attempt_data, + $best_response + ) + ) + ) + ); + } + + if (!$view_configuration->showFeedback()) { + return $feedbacks; + } + + return $this->addFeedbacksFromProvidersToFeedbacks( + $lng, + $refinery, + $ui_factory, + $required_capabilities, + $answer_form_properties, + $feedbacks + ); + } + + private function addFeedbacksFromProvidersToFeedbacks( + Language $lng, + Refinery $refinery, + UIFactory $ui_factory, + RequiredCapabilities $required_capabilities, + AnswerFormProperties $answer_form_properties, + array $feedbacks + ): array { + $feedbacks['feedback_callbacks'] = []; + $feedbacks['feedback_data'] = []; + + return array_reduce( + $required_capabilities->getRequiredFeedbackProviders(), + function ( + array $c, + FeedbackProvider $v + ) use ( + $lng, + $refinery, + $ui_factory, + $required_capabilities, + $answer_form_properties + ): array { + $feedback = $v->getFeedback($answer_form_properties); + $feedback_data = $feedback->getAllFeedbacksForClientSideCode( + $lng, + $refinery, + $ui_factory, + $this->ui_renderer, + $required_capabilities, + $answer_form_properties + ); + + if ($feedback_data !== []) { + $c['feedback_callbacks'][] = $feedback->getFeedbackClientSideEndPoint(); + $c['feedback_data'][] = $feedback_data; + } + + return $c; + }, + $feedbacks + ); + } + + private function buildFeedbackPanelWithPlaceholders( + UIFactory $ui_factory + ): string { + return $this->ui_renderer->render( + $ui_factory->panel()->standard( + self::PLACEHOLDER_PANEL_TITLE, + $ui_factory->legacy()->content( + self::PLACEHOLDER_PANEL_CONTENT + ) + ) + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/AsyncView/Capability.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/AsyncView/Capability.php new file mode 100644 index 000000000000..d4ee1aacf344 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/AsyncView/Capability.php @@ -0,0 +1,76 @@ +getDefinition() + ->hasCapability( + self::getIdentifier() + ); + } + + #[\Override] + public function getParticipantView( + Properties $answer_form_properties + ): Participant { + return $answer_form_properties + ->getDefinition() + ->getCapability(self::getIdentifier()); + } + + #[\Override] + public function onAnswerFormClone( + UuidFactory $uuid_factory, + Properties $old_answer_form_properties, + Properties $new_answer_form_properties + ): void { + } + + #[\Override] + public function onAnswerFormUpdate( + Properties $answer_form_properties + ): void { + } + + #[\Override] + public function onAnswerFormDelete( + Properties $answer_form_properties + ): void { + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/Capability.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/Capability.php new file mode 100644 index 000000000000..00c6a767a56b --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/Capability.php @@ -0,0 +1,47 @@ +getDefinition() + ->hasCapability( + self::getIdentifier() + ); + } + + #[\Override] + public function getParticipantView( + Properties $answer_form_properties + ): Participant { + return $answer_form_properties + ->getDefinition() + ->getCapability(self::getIdentifier()); + } + + #[\Override] + public function onAnswerFormClone( + UuidFactory $uuid_factory, + Properties $old_answer_form_properties, + Properties $new_answer_form_properties + ): void { + } + + #[\Override] + public function onAnswerFormUpdate( + Properties $answer_form_properties + ): void { + } + + #[\Override] + public function onAnswerFormDelete( + Properties $answer_form_properties + ): void { + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/DefaultView/DefaultView.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/DefaultView/DefaultView.php new file mode 100644 index 000000000000..46f5feccb28b --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/DefaultView/DefaultView.php @@ -0,0 +1,201 @@ +buildMainContent( + $global_tpl, + $lng, + $ui_factory, + $required_capabilities, + $view_configuration, + $properties, + $attempt_data, + $response_data + ); + + if (!$view_configuration->showFeedback()) { + return $main_content; + } + + return $this->addFeedbackSubPanelsToContent( + $lng, + $refinery, + $ui_factory, + $required_capabilities, + $properties, + $response_data, + [ + $ui_factory->panel()->standard( + $this->buildMainContentLabel( + $lng, + $view_configuration->showBestResponse() + ), + $main_content + ) + ] + ); + } + + private function buildMainContent( + GlobalTemplate $global_tpl, + Language $lng, + UIFactory $ui_factory, + RequiredCapabilities $required_capabilities, + ViewConfiguration $view_configuration, + ?AnswerFormProperties $answer_form_properties, + ?AdditionalAttemptData $additional_attempt_data, + ?AnswerFormResponse $answer_form_response + ): array|Component { + if ($view_configuration->showBestResponse()) { + $best_response = $required_capabilities->getMarking( + $answer_form_properties + )?->getBestResponse( + $answer_form_properties + ); + + return $this->getAnswerFormPresentation( + $lng, + $ui_factory, + $view_configuration->withShowBestResponse(true), + $answer_form_properties, + $additional_attempt_data, + $best_response + ); + } + + $global_tpl->addJavaScript(self::JAVASCRIPT_BASE_FILE); + + foreach ($this->getJavascriptFiles() as $file) { + $global_tpl->addJavaScript($file); + } + + return $this->getAnswerFormPresentation( + $lng, + $ui_factory, + $view_configuration, + $answer_form_properties, + $additional_attempt_data, + $answer_form_response + ); + } + + private function addFeedbackSubPanelsToContent( + Language $lng, + Refinery $refinery, + UIFactory $ui_factory, + RequiredCapabilities $required_capabilities, + AnswerFormProperties $answer_form_properties, + AnswerFormResponse $answer_form_response, + array $content + ): array { + return array_reduce( + $required_capabilities->getRequiredFeedbackProviders(), + function ( + array $c, + FeedbackProvider $v + ) use ( + $lng, + $refinery, + $ui_factory, + $answer_form_properties, + $answer_form_response, + $required_capabilities + ): array { + $output = $v->getFeedback( + $answer_form_properties + )->getParticipantOutput( + $lng, + $refinery, + $ui_factory, + $answer_form_properties, + $answer_form_response, + $required_capabilities + ); + + if ($output === null) { + return $c; + } + + $c[] = $output->getUI(); + return $c; + }, + $content + ); + } + + private function buildMainContentLabel( + Language $lng, + bool $show_best_response + ): string { + if ($show_best_response) { + return $lng->txt('best_response'); + } + + return $lng->txt('question'); + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/Definitions/ActionWithTab.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/Definitions/ActionWithTab.php new file mode 100644 index 000000000000..ed30d1b0ee66 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/Definitions/ActionWithTab.php @@ -0,0 +1,78 @@ +do->__invoke($environment); + } + + public function getIdentifier(): string + { + return self::PREFIX . '_' . md5($this->capability::class); + } + + public function addTab( + Environment $environment, + \ilTabsGUI $tabs_gui + ): void { + $action = $this->getIdentifier(); + $tabs_gui->addTab( + $action, + $environment->getLanguage()->txt($this->lang_var), + $environment->withActionParameter($action) + ->getUrlBuilder() + ->buildURI() + ->__toString() + ); + } + + public function activateTab( + \ilTabsGUI $tabs_gui + ): void { + $tabs_gui->activateTab( + $this->getIdentifier() + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/Definitions/AdditionalFormStepAction.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/Definitions/AdditionalFormStepAction.php new file mode 100644 index 000000000000..fe46ced4a7f1 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/Definitions/AdditionalFormStepAction.php @@ -0,0 +1,222 @@ +getSubAction(); + $environment_with_preserved_parameters = $environment + ->withPreservedTableRowIdsParameter() + ->withPreservedFormStartSubActionParameter(); + return match ($sub_action) { + self::SUB_ACTION_SAVE => $this->forwardToNextForm( + $environment_with_preserved_parameters + ), + self::SUB_ACTION_BACK => $this->backToPreviousForm( + $environment_with_preserved_parameters + ), + default => $this->buildFormWithCarry( + $environment_with_preserved_parameters + ) + }; + } + + public function getIdentifier(): string + { + return self::PREFIX . '_' . md5($this->capability::class); + } + + private function toPrevious( + DefaultEnvironment $envrionment + ): Async|EditForm { + if ($this->previous instanceof AnswerFormEditView) { + return $this->previous + ->backToLastEditCommand($envrionment->withActionParameter('')) + ->withIsFinalStep(false); + } + + return $this->previous->getAnswerFormEditAdditionalStep()->do( + $envrionment + ); + } + + public function withPrevious( + Capability|AnswerFormEditView $previous + ): self { + $clone = clone $this; + $clone->previous = $previous; + return $clone; + } + + private function toNext( + DefaultEnvironment $environment + ): Async|EditForm|Properties { + if ($this->next === null) { + return $environment->getAnswerFormProperties(); + } + + return $this->next->getAnswerFormEditAdditionalStep()->do( + $environment + ); + } + + public function withNext( + ?Capability $next + ): self { + $clone = clone $this; + $clone->next = $next; + return $clone; + } + + public function getAsTableAction( + DefaultEnvironment $environment + ): TableAction { + return $environment->getUIFactory()->table()->action()->standard( + $environment->getLanguage()->txt($this->lang_var), + $environment->withActionParameter( + $this->getIdentifier() + )->getUrlBuilder(), + $environment->getTableRowIdToken() + ); + } + + private function buildFormWithCarry( + DefaultEnvironment $environment + ): EditForm { + $properties = $environment->getAnswerFormProperties(); + + $inputs_builder_for_points = $this->retrieve_inputs_builder->__invoke( + $environment + )->withCarry( + $properties->toCarry() + ); + + $inputs_builder_for_points->persistCarry(); + + return $this->buildForm( + $environment->withAnswerFormProperties($properties), + $inputs_builder_for_points + ); + } + + private function buildForm( + DefaultEnvironment $environment, + InputsBuilderSession $inputs_builder + ): EditForm { + $properties = $environment->getAnswerFormProperties(); + return $environment->getPresentationFactory()->getEditForm( + $inputs_builder, + $environment->withSubActionParameter( + self::SUB_ACTION_SAVE + )->getUrlBuilder(), + $this->previous === null + ? null + : $environment->withSubActionParameter( + self::SUB_ACTION_BACK + )->getUrlBuilder() + )->withIsFinalStep(true) + ->withContentBeforeForm( + $properties->getClozeText()->buildPanelForEditing( + $environment->getUIFactory(), + $environment->getLanguage(), + $properties->getGaps(), + $properties->getLegacyClozeText() + ) + ); + } + + private function forwardToNextForm( + DefaultEnvironment $environment + ): EditForm|Properties { + $processed_form = $this->processForm($environment); + if ($processed_form instanceof EditForm) { + return $processed_form; + } + + return $this->toNext( + $environment->withAnswerFormProperties($processed_form) + ); + } + + private function backToPreviousForm( + DefaultEnvironment $environment + ): EditForm { + $processed_form = $this->processForm($environment); + if ($processed_form instanceof EditForm) { + return $processed_form; + } + + return $this->toPrevious( + $environment->withAnswerFormProperties($processed_form) + ); + } + + private function processForm( + DefaultEnvironment $environment + ): EditForm|Properties { + $inputs_builder = $this->retrieve_inputs_builder->__invoke($environment); + $form = $this->buildForm( + $environment, + $inputs_builder + )->withRequest($environment->getHttpServices()->request()); + + $data = $form->getData(); + if ($data === null) { + $inputs_builder->persistCarry(); + return $form; + } + + return $data; + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/Definitions/AdditionalStepProvider.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/Definitions/AdditionalStepProvider.php new file mode 100644 index 000000000000..319fbcc00ddf --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/Definitions/AdditionalStepProvider.php @@ -0,0 +1,26 @@ + An array of all possible feedbacks + * as FeedbackViews structured in a way that makes it possible to retrieve + * the correct array corresponding to the answer of the user in the callback + * provided in getClientSideFeedbackEndPoint. The component takes care of + * transporting the information to javascript. + */ + public function getAllFeedbacksForClientSideCode( + Language $lng, + Refinery $refinery, + UIFactory $ui_factory, + UIRenderer $ui_renderer, + RequiredCapabilities $required_capabilities, + Properties $properties + ): array; +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/Definitions/FeedbackProvider.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/Definitions/FeedbackProvider.php new file mode 100644 index 000000000000..2d44472aef47 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/Definitions/FeedbackProvider.php @@ -0,0 +1,30 @@ +feedback; + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/Definitions/Marking.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/Definitions/Marking.php new file mode 100644 index 000000000000..192187709fed --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/Definitions/Marking.php @@ -0,0 +1,36 @@ + $available_capabilities + */ + private readonly array $available_capabilities; + + /** + * @param list $available_capabilities + */ + public function __construct( + array $available_capabilities + ) { + $this->available_capabilities = array_reduce( + $available_capabilities, + function (array $c, Capability $v): array { + $c[$v->getIdentifier()] = $v; + return $c; + }, + [] + ); + } + + /** + * + * @param list> $capability_identifiers + */ + public function get( + array $capability_identifiers + ): RequiredCapabilities { + $required_capabilities = []; + $participant_view_providers = []; + $marking_providers = []; + $required_feedback_providers = []; + $required_actions_with_tab = []; + $required_form_step_actions = []; + $required_page_migration_providers = []; + + foreach ($capability_identifiers as $capability_identifier) { + if (!isset($this->available_capabilities[$capability_identifier])) { + throw new \InvalidArgumentException( + "The capability {$capability_identifier} does not exist." + ); + } + + $capability = $this->available_capabilities[$capability_identifier]; + $required_capabilities[$capability_identifier] = $capability; + + if ($capability instanceof ParticipantViewProvider) { + $participant_view_providers[] = $capability; + } + + if ($capability instanceof MarkingProvider) { + $marking_providers[] = $capability; + } + + if ($capability instanceof FeedbackProvider) { + $required_feedback_providers[] = $capability; + } + + if ($capability instanceof AdditionalTabProvider) { + $action_with_tab = $capability->getAnswerFormEditAdditionalTab(); + $required_actions_with_tab[$action_with_tab->getIdentifier()] = $action_with_tab; + } + + if ($capability instanceof AdditionalStepProvider) { + $form_step_action = $capability->getAnswerFormEditAdditionalStep(); + $required_form_step_actions[$form_step_action->getIdentifier()] = $form_step_action; + } + + if ($capability instanceof PageMigrationProvider) { + $required_page_migration_providers[] = $capability; + } + } + + if (count($participant_view_providers) !== 1 || count($marking_providers) > 1) { + throw new \InvalidArgumentException( + 'You have to provide exactly one Capability providing a Participant View' + . 'and one Capability providing Marking at most.' + ); + } + + return new RequiredCapabilities( + $required_capabilities, + $participant_view_providers[0], + $required_feedback_providers, + $required_actions_with_tab, + $required_form_step_actions, + $required_page_migration_providers, + $marking_providers[0] ?? null + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/MarkingAllowingPartialPoints/Capability.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/MarkingAllowingPartialPoints/Capability.php new file mode 100644 index 000000000000..308201d116f8 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/MarkingAllowingPartialPoints/Capability.php @@ -0,0 +1,98 @@ +getTypeGenericProperties() + ->getDefinition() + ->hasCapability( + self::getIdentifier() + ) + && $answer_form_properties + ->getTypeGenericProperties() + ->getAvailablePoints() !== null; + } + + #[\Override] + public function getAnswerFormEditAdditionalStep(): AdditionalFormStepAction + { + return new AdditionalFormStepAction( + $this, + 'edit_available_points', + fn(Environment $v): InputsBuilderSession + => $v->getAnswerFormProperties() + ->getDefinition() + ->getCapability(self::getIdentifier()) + ->getEditFormInputsBuilder($v) + ); + } + + #[\Override] + public function getMarking( + Properties $answer_form_properties + ): Marking { + return $answer_form_properties + ->getDefinition() + ->getCapability(self::getIdentifier()); + } + + #[\Override] + public function onAnswerFormClone( + UuidFactory $uuid_factory, + Properties $old_answer_form_properties, + Properties $new_answer_form_properties + ): void { + } + + #[\Override] + public function onAnswerFormUpdate( + Properties $answer_form_properties + ): void { + } + + #[\Override] + public function onAnswerFormDelete( + Properties $answer_form_properties + ): void { + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/MarkingAllowingPartialPoints/MarkingAllowingPartialPoints.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/MarkingAllowingPartialPoints/MarkingAllowingPartialPoints.php new file mode 100644 index 000000000000..5b470a3116ee --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/MarkingAllowingPartialPoints/MarkingAllowingPartialPoints.php @@ -0,0 +1,39 @@ + $capability_class_names + * @param array $required_feedback_views + * @param array $required_actions_with_tab + * @param array $required_step_actions + * @param array $required_step_actions + */ + public function __construct( + private readonly array $capabilities, + private ParticipantViewProvider $participant_view_provider, + private array $required_feedback_providers, + private array $required_actions_with_tab, + private array $required_form_step_actions, + private array $required_page_migration_providers, + private ?MarkingProvider $marking_provider + ) { + + } + + /** + * @param list $answer_form_properties + */ + public function areAllCapabilitiesSupportedByAnswerForms( + array $answer_form_properties + ): bool { + foreach ($answer_form_properties as $property) { + foreach ($this->capabilities as $capability) { + if (!$capability->isAvailableFor($property)) { + return false; + } + } + } + + return true; + } + + public function getParticipantViewProvider(): ParticipantViewProvider + { + return $this->participant_view_provider; + } + + /** + * @return array + */ + public function getRequiredFeedbackProviders(): array + { + return $this->required_feedback_providers; + } + + /** + * @return array + */ + public function getRequiredFormStepActions(): array + { + return $this->required_form_step_actions; + } + + /** + * @return array + */ + public function getRequiredPageMigrationProviders(): array + { + return $this->required_page_migration_providers; + } + + public function isMarkingRequired(): bool + { + return $this->marking_provider !== null; + } + + public function getMarking( + AnswerFormProperties $answer_form_properties + ): ?Marking { + return $this->marking_provider?->getMarking($answer_form_properties); + } + + public function isCapabilityRequired( + string $identifier + ): bool { + return array_key_exists($identifier, $this->capabilities); + } + + public function onAnswerFormClone( + UuidFactory $uuid_factory, + AnswerFormProperties $old_properties, + AnswerFormProperties $new_properties + ): void { + foreach ($this->capabilities as $capability) { + $capability->onAnswerFormClone( + $uuid_factory, + $old_properties, + $new_properties + ); + } + } + + public function onAnswerFormUpdate( + AnswerFormProperties $properties + ): void { + foreach ($this->capabilities as $capability) { + $capability->onAnswerFormUpdate($properties); + } + } + + public function edit( + \ilTabsGUI $tabs_gui, + DefaultEnvironment $environment, + AnswerFormEditView $edit_view, + string $action_from_get + ): Async|Viewable|AnswerFormProperties|null { + $environment->setEditAnswerFormTabs( + $this->required_actions_with_tab + ); + + $tab_action = $this->required_actions_with_tab[$action_from_get] ?? null; + if ($tab_action !== null) { + $tab_action->activateTab($tabs_gui); + + return $tab_action->do( + $environment->withActionParameter($action_from_get) + ); + } + + $step_action = $this->retrieveNextFormStepFromActionIdentifier( + $edit_view, + $action_from_get + ); + + if ($step_action === null) { + return null; + } + + $environment->setEditAnswerFormBackTarget(); + return $step_action->do( + $environment->withActionParameter($action_from_get) + ); + } + + public function additionalAnswerFormStepsRequired(): bool + { + return $this->required_form_step_actions !== []; + } + + public function doFirstFormStepAction( + DefaultEnvironment $environment, + AnswerFormEditView $edit_view + ): EditForm|AnswerFormProperties { + if ($this->required_form_step_actions === []) { + return $environment->getAnswerFormProperties(); + } + + $keys = array_keys($this->required_form_step_actions); + + return $this->required_form_step_actions[$keys[0]] + ->withPrevious( + $edit_view + )->withNext( + isset($keys[1]) + ? $this->required_form_step_actions[$keys[1]] + : null + )->do( + $environment->withActionParameter($keys[0]) + ); + } + + private function retrieveNextFormStepFromActionIdentifier( + AnswerFormEditView $edit_view, + string $action_identifier + ): ?AdditionalFormStepAction { + $action = $this->required_form_step_actions[$action_identifier] ?? null; + if ($action === null) { + return null; + } + + $keys = array_keys($this->required_form_step_actions); + $current_index = array_search($action_identifier, $keys); + + $next_index = $current_index + 1; + if (isset($keys[$next_index])) { + $action = $action->withNext( + $this->required_form_step_actions[$next_index] + ); + } + + return $action->withPrevious( + $current_index > 0 + ? $this->required_form_step_actions[$keys[$current_index - 1]] + : $edit_view + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/Capability.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/Capability.php new file mode 100644 index 000000000000..3c6fd632099c --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/Capability.php @@ -0,0 +1,143 @@ +buildDoEditActionClosure() + ); + } + + #[\Override] + public function getFeedback( + Properties $answer_form_properties + ): ?Feedback { + return new Feedback( + $this->ctrl, + $this->static_url, + $this->repository + ); + } + + #[\Override] + public function onAnswerFormClone( + UuidFactory $uuid_factory, + Properties $old_answer_form_properties, + Properties $new_answer_form_properties + ): void { + $suggested_learning_content = $this->repository->getFor( + $old_answer_form_properties->getAnswerFormId() + ); + $this->repository->store( + $suggested_learning_content->clone( + $uuid_factory, + ['answer_form_id' => $new_answer_form_properties->getAnswerFormId()] + ) + ); + } + + #[\Override] + public function onAnswerFormUpdate( + Properties $answer_form_properties + ): void { + } + + #[\Override] + public function onAnswerFormDelete( + Properties $answer_form_properties + ): void { + $this->repository->delete( + $answer_form_properties->getAnswerFormId() + ); + } + + private function buildDoEditActionClosure(): \Closure + { + return function ( + Environment $environment + ): Async|Viewable { + $sub_action = $environment->getSubAction(); + $overview = $this->buildOverview($environment); + if ($sub_action === '') { + return $overview; + } + + return $overview->doAction( + $sub_action + ); + }; + } + + private function buildOverview( + Environment $environment + ): Overview { + return new Overview( + $this->ctrl, + $this->rbac_system, + $this->tree, + $this->current_user, + $this->static_url, + $environment, + $this->repository + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/Content.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/Content.php new file mode 100644 index 000000000000..805fc7ecf949 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/Content.php @@ -0,0 +1,205 @@ +target_ref_id = $decoded_content[self::KEY_TARGET_REF_ID] ?? null; + $this->sub_object_id = $decoded_content[self::KEY_SUB_OBJECT_ID] ?? null; + $this->rid = $this->irss->manage()->find( + $decoded_content[self::KEY_RID] ?? '' + ); + $this->file_title = $decoded_content[self::KEY_FILE_TITLE] ?? ''; + + } + + public function getAnswerFormId(): Uuid + { + return $this->answer_form_id; + } + + public function getType(): Types + { + return $this->type; + } + + public function withType( + Types $type + ): self { + $clone = clone $this; + $clone->type = $type; + return $clone; + } + + public function getTargetRefId(): ?int + { + return $this->target_ref_id; + } + + public function withTargetRefId( + int $target_ref_id + ): self { + $clone = clone $this; + $clone->target_ref_id = $target_ref_id; + return $clone; + } + + public function withSubObjectId( + int $sub_object_id + ): self { + $clone = clone $this; + $clone->sub_object_id = $sub_object_id; + return $clone; + } + + public function withFileInfo( + array $file_info + ): self { + $clone = clone $this; + $clone->rid = $this->irss->manage()->find($file_info[self::KEY_RID][0]); + $clone->file_title = $file_info[self::KEY_FILE_TITLE]; + return $clone; + } + + public function getContentForPresentation( + Language $lng, + \ilCtrl $ctrl, + StaticURLServices $static_url, + UIFactory $ui_factory + ): ?StandardLink { + return $this->type->present( + $lng, + $ctrl, + $static_url, + $this->irss, + $ui_factory, + $this->file_title, + $this->rid, + $this->target_ref_id, + $this->sub_object_id + ); + } + + public function getListing( + \ilCtrl $ctrl, + StaticURLServices $static_url, + Environment $environment + ): array { + $lng = $environment->getLanguage(); + + return [ + $lng->txt('type') => $this->type->getTranslatedOptionName($lng), + $lng->txt('content') => $this + ->getContentForPresentation( + $environment->getLanguage(), + $ctrl, + $static_url, + $environment->getUIFactory() + ) ?? '' + ]; + } + + #[\Override] + public function clone( + UuidFactory $uuid_factory, + array $environment = [] + ): static { + $clone = clone $this; + $clone->answer_form_id = $environment['answer_form_id']; + return $clone; + } + + public function toStorage( + PersistenceFactory $persistence_factory + ): array { + if ($this->type === null) { + throw new \UnexpectedValueException( + 'You cannot save a Suggested Learnign Content without a Type!' + ); + } + + return [ + $persistence_factory->value( + \ilDBConstants::T_TEXT, + $this->answer_form_id->toString() + ), + $persistence_factory->value( + \ilDBConstants::T_TEXT, + $this->type->value + ), + $persistence_factory->value( + \ilDBConstants::T_TEXT, + json_encode($this->buildContentArray()) + ) + ]; + } + + private function buildContentArray(): array + { + $array = []; + if ($this->target_ref_id !== null) { + $array[self::KEY_TARGET_REF_ID] = $this->target_ref_id; + } + + if ($this->sub_object_id !== null) { + $array[self::KEY_SUB_OBJECT_ID] = $this->sub_object_id; + } + + if ($this->rid !== null) { + $array[self::KEY_RID] = $this->rid->serialize(); + } + + if ($this->file_title !== '') { + $array[self::KEY_FILE_TITLE] = $this->file_title; + } + + return $array; + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/Feedback.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/Feedback.php new file mode 100644 index 000000000000..c25a21052ae3 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/Feedback.php @@ -0,0 +1,112 @@ +buildFeedbackView( + $lng, + $properties, + $ui_factory + ); + } + + #[\Override] + public function getFeedbackClientSideEndPoint(): string + { + return 'il.questions.suggestedLearningContent.retrieve'; + } + + #[\Override] + public function getAllFeedbacksForClientSideCode( + Language $lng, + Refinery $refinery, + UIFactory $ui_factory, + UIRenderer $ui_renderer, + RequiredCapabilities $required_capabilities, + Properties $properties + ): array { + $feedback_view = $this->buildFeedbackView( + $lng, + $properties, + $ui_factory + ); + return $feedback_view === null + ? [] + : [ + $ui_renderer->render( + $feedback_view->getUI() + ) + ]; + } + + private function buildFeedbackView( + Language $lng, + Properties $properties, + UIFactory $ui_factory + ): ?FeedbackView { + $content = $this->repository->getFor( + $properties->getAnswerFormId() + )->getContentForPresentation( + $lng, + $this->ctrl, + $this->static_url, + $ui_factory + ); + if ($content === null) { + return null; + } + + return new FeedbackView( + $ui_factory->panel()->standard( + $lng->txt('suggested_learning_content'), + $content + ) + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/Migration.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/Migration.php new file mode 100644 index 000000000000..8137cd0f410e --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/Migration.php @@ -0,0 +1,269 @@ +old_repository === null) { + $this->old_repository = new SuggestedSolutionsDatabaseRepository( + $migration_insert->getDb() + ); + $this->irss_migration_helper = new \ilResourceStorageMigrationHelper( + new Stakeholder(), + $environment + ); + } + + $old_suggested_learning_content = $this->old_repository->selectFor( + $migration_insert->getOldQuestionId() + )[0] ?? null; + + if ($old_suggested_learning_content === null) { + return $migration_insert; + } + + $insert = $this->buildInsertFromOldSuggestedSolution( + $migration_insert, + $old_suggested_learning_content + ); + + if ($insert === null) { + return $migration_insert; + } + + return $migration_insert->withAdditionalInsert($insert); + } + + private function buildInsertFromOldSuggestedSolution( + MigrationInsert $migration_insert, + SuggestedSolution $old_values + ): ?Insert { + $type = $this->buildNewTypeFromOld($old_values->getType()); + $content = $this->buildContentStringFromOldSuggesteSolution( + $migration_insert->getDb(), + $type, + $old_values + ); + + if ($content === null) { + return null; + } + + $pf = $migration_insert->getPersistenceFactory(); + return $pf->insert( + $this->table_definitions->getColumns( + $migration_insert->getTableNameBuilder(null), + TableTypes::SuggestedLearningContent + ), + [ + $pf->value( + FieldDefinition::T_TEXT, + $migration_insert->getAnswerFormId()->toString() + ), + $pf->value( + FieldDefinition::T_TEXT, + $type->value + ), + $pf->value( + FieldDefinition::T_TEXT, + json_encode($content) + ), + ] + ); + + } + + private function buildNewTypeFromOld( + string $old_type + ): Types { + return match($old_type) { + SuggestedSolution::TYPE_FILE => Types::File, + SuggestedSolution::TYPE_LM => Types::LearningModule, + SuggestedSolution::TYPE_LM_PAGE => Types::LearningModulePage, + SuggestedSolution::TYPE_LM_CHAPTER => Types::LearningModuleChapter, + SuggestedSolution::TYPE_GLOSARY_TERM => Types::GlossaryTerm + }; + } + + private function buildContentStringFromOldSuggesteSolution( + \ilDBInterface $db, + Types $type, + SuggestedSolution $old_values + ): ?array { + $old_parent_and_owner = $this->fetchQuestionParentAndOwnerFromDB( + $db, + $old_values->getQuestionId() + ); + if ($old_values instanceof SuggestedSolutionFile) { + $rid = $this->irss_migration_helper->movePathToStorage( + $this->buildFilePath( + $old_parent_and_owner->obj_fi, + $old_values + ), + $this->determineNewOwner( + $old_parent_and_owner->owner + ) + ); + + if ($rid === null) { + return null; + } + + return [ + Content::KEY_FILE_TITLE => $old_values->getTitle() === $old_values->getFilename() + ? '' + : $old_values->getTitle(), + Content::KEY_RID => $rid->serialize() + ]; + } + + $id = $this->parseInternalLink( + $old_values->getInternalLink() + ); + + if ($id === null) { + return null; + } + + if ($type === Types::LearningModule) { + return [ + Content::KEY_TARGET_REF_ID => $id + ]; + } + + $target_ref_id = $this->fetchTargetRefIdFromDB( + $db, + $type, + $id + ); + + if ($target_ref_id === null) { + return null; + } + + return [ + Content::KEY_TARGET_REF_ID => $target_ref_id, + Content::KEY_SUB_OBJECT_ID => $id + ]; + } + + private function fetchQuestionParentAndOwnerFromDB( + \ilDBInterface $db, + int $old_question_id + ): \stdClass { + return $db->fetchObject( + $db->queryF( + 'SELECT obj_fi, owner FROM qpl_questions WHERE question_id = %s', + [FieldDefinition::T_INTEGER], + [$old_question_id] + ) + ); + } + + private function fetchTargetRefIdFromDB( + \ilDBInterface $db, + Types $type, + int $sub_object_id + ): ?int { + if ($type === Types::LearningModulePage + || $type === Types::LearningModuleChapter) { + return $db->fetchObject( + $db->queryF( + 'SELECT object_reference.ref_id FROM lm_data' . PHP_EOL + . 'INNER JOIN object_reference' . PHP_EOL + . 'ON lm_data.lm_id = object_reference.obj_id' . PHP_EOL + . 'WHERE lm_data.obj_id = %s', + [FieldDefinition::T_INTEGER], + [$sub_object_id] + ) + )?->lm_id; + } + + return $db->fetchObject( + $db->queryF( + 'SELECT object_reference.ref_id FROM glossary_term' . PHP_EOL + . 'INNER JOIN object_reference' . PHP_EOL + . 'ON glossary_term.glo_id = object_reference.obj_id' . PHP_EOL + . 'WHERE glossary_term.id = %s', + [FieldDefinition::T_INTEGER], + [$sub_object_id] + ) + )?->glo_id ?? null; + } + + private function determineNewOwner( + int $owner + ): int { + if ($owner > 0) { + return $owner; + } + + return $this->irss_migration_helper + ->getStakeholder() + ->getOwnerOfNewResources(); + } + + private function buildFilePath( + int $parent_id, + SuggestedSolutionFile $old_values + ): string { + return \ilFileUtils::removeTrailingPathSeparators(CLIENT_WEB_DIR) + . "/assessment/{$parent_id}/{$old_values->getQuestionId()}" + . "/solution/{$old_values->getFilename()}"; + } + + private function parseInternalLink( + string $link + ): ?int { + if (preg_match("/il__\w+_(\d+)/", $link, $matches) !== 1) { + return null; + } + + return (int) $matches[1]; + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/NodeRetrieval.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/NodeRetrieval.php new file mode 100644 index 000000000000..2311c9f4eed6 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/NodeRetrieval.php @@ -0,0 +1,277 @@ + + */ + private readonly array $requested_types; + + public function __construct( + private readonly \ilRbacSystem $rbac_system, + private readonly \ilTree $tree, + private readonly Environment $environment, + private readonly string $requested_type + ) { + [$this->url_builder, $this->node_parameter_token] = $environment + ->withSubActionParameter(self::SUB_ACTION_RETRIEVE_NODES) + ->getUrlBuilder() + ->acquireParameter( + self::PARAMETER_NAMESPACE, + self::PARAMETER_ID_STRING_NODE + ); + + $this->requested_types = [ + ...self::CONTAINER_CONTENT_TYPES, + $requested_type + ]; + } + + #[\Override] + public function getNodes( + NodeFactory $node_factory, + IconFactory $icon_factory, + array $sync_node_id_whitelist = [], + ?string $parent_id = null + ): \Generator { + if ($parent_id === null) { + $parent_id = ROOT_FOLDER_ID; + } + + yield from $this->buildFilteredNodes( + $node_factory, + $icon_factory, + $this->tree->getChildsByTypeFilter( + $parent_id, + $this->requested_types + ) + ); + } + + #[\Override] + public function getNodesAsLeaf( + NodeFactory $node_factory, + IconFactory $icon_factory, + array $node_ids, + ): \Generator { + foreach ($node_ids as $node_id) { + $node = $this->tree->getNodeData((int) $node_id); + yield $node_factory->leaf( + explode('.', $node['path'] ?? ''), + $node['title'] ?? $this->environment->getLanguage()->txt('invalid') + ); + } + } + + #[\Override] + public function can( + string $sub_action + ): bool { + return $sub_action === self::SUB_ACTION_RETRIEVE_NODES + && $this->environment->getHttpServices()->wrapper()->query()->has( + $this->node_parameter_token->getName() + ); + } + + #[\Override] + public function do( + string $action + ): Async { + $node_id = $this->retrieveNodeIdFromQuery(); + + $response = ''; + if ($node_id !== null) { + $response = iterator_to_array( + $this->buildFilteredNodes( + $this->environment->getUIFactory()->input()->field()->node(), + $this->environment->getUIFactory()->symbol()->icon(), + $this->tree->getChildsByTypeFilter( + $node_id, + $this->requested_types + ) + ) + ); + } + + return $this->environment->getPresentationFactory()->getAsync($response); + } + + public function buildValidNodeConstraint(): Constraint + { + return $this->environment->getRefinery()->custom()->constraint( + function (array $v): bool { + if (!isset($v[0])) { + return false; + } + $data = $this->tree->getNodeData((int) $v[0]); + return $data['type'] === $this->requested_type; + }, + function (\Closure $txt, array $v): string { + if (!isset($v[0])) { + return $txt('required'); + } + $data = $this->tree->getNodeData((int) $v[0]); + return sprintf( + $txt('invalid_node_selected'), + $txt("obj_{$data['type']}") + ); + } + ); + } + + private function buildFilteredNodes( + NodeFactory $node_factory, + IconFactory $icon_factory, + array $nodes + ): \Generator { + foreach ($nodes as $node) { + $is_visible = $this->rbac_system->checkAccess('visible', $node['ref_id']); + $is_container = in_array($node['type'], self::CONTAINER_CONTENT_TYPES); + if (!$is_container && !$is_visible) { + continue; + } + + if (!$is_container) { + yield $node_factory->leaf( + explode('.', $node['path']), + $node['title'], + $this->getIconForNodeArray( + $icon_factory, + $node + ) + ); + continue; + } + + $is_readable = $this->rbac_system->checkAccess('read', $node['ref_id']); + if ($is_visible && $is_readable && $node['parent'] === ROOT_FOLDER_ID) { + yield $node_factory->branch( + explode('.', $node['path']), + $node['title'], + $this->getIconForNodeArray( + $icon_factory, + $node + ), + ...$this->buildFilteredNodes( + $node_factory, + $icon_factory, + $this->tree->getChildsByTypeFilter( + $node['ref_id'], + $this->requested_types + ) + ) + ); + continue; + } + + if ($is_visible && $is_readable) { + yield $node_factory->async( + $this->getAsyncNodeRenderUrl($node['ref_id']), + explode('.', $node['path']), + $node['title'], + $this->getIconForNodeArray( + $icon_factory, + $node + ) + ); + continue; + } + + if ($is_readable) { + yield from $this->buildFilteredNodes( + $node_factory, + $icon_factory, + $this->tree->getChildsByTypeFilter( + $node['ref_id'], + $this->requested_types + ) + ); + } + } + } + + private function getAsyncNodeRenderUrl( + int $node_id + ): URI { + return $this->url_builder->withParameter( + $this->node_parameter_token, + (string) $node_id + )->buildURI(); + } + + private function retrieveNodeIdFromQuery(): ?int + { + $refinery = $this->environment->getRefinery(); + + return $this->environment + ->getHttpServices() + ->wrapper() + ->query() + ->retrieve( + $this->node_parameter_token->getName(), + $refinery->byTrying([ + $refinery->kindlyTo()->int(), + $refinery->always(null) + ]) + ); + } + + private function getIconForNodeArray( + IconFactory $icon_factory, + array $node + ): StandardIcon { + return $icon_factory->standard( + $node['type'], + $this->environment->getLanguage()->txt( + "obj_{$node['type']}" + ) + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/Overview.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/Overview.php new file mode 100644 index 000000000000..75206e73a8c0 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/Overview.php @@ -0,0 +1,361 @@ +environment->getUIFactory()->listing()->descriptive( + $this->repository->getFor( + $this->environment->getAnswerFormId() + )->getListing( + $this->ctrl, + $this->static_url, + $this->environment + ) + ) + ]; + + if ($this->environment->getEditability() === Editability::Full) { + $prompt = $this->buildPrompt(); + $content[] = $prompt; + $content[] = $this->environment->getUIFactory()->button()->standard( + $this->environment->getLanguage()->txt('edit'), + $prompt->getShowSignal() + ); + } + + return $this->environment->getUIFactory()->panel()->standard( + $this->environment->getLanguage()->txt('suggested_learning_content'), + $content + ); + } + + public function doAction( + string $action + ): Async { + return match($action) { + self::SUB_ACTION_SELECT_TYPE => $this->buildPromptShowAsync( + $this->buildSelectTypeForm() + ), + self::SUB_ACTION_SELECT_CONTENT => $this->processSelectTypeForm(), + self::SUB_ACTION_SAVE_CONTENT => $this->processSelectContentForm(), + self::SUB_ACTION_SAVE_SUB_CONTENT => $this->processSelectSubContentForm(), + default => $this->forwardActionToActors($action) + }; + } + + private function buildPrompt(): Prompt + { + return $this->environment->getUIFactory()->prompt()->standard( + $this->environment->withSubActionParameter( + self::SUB_ACTION_SELECT_TYPE + )->getUrlBuilder()->buildURI() + ); + } + + private function forwardActionToActors( + string $action + ): Async { + $node_retrieval = new NodeRetrieval( + $this->rbac_system, + $this->tree, + $this->environment, + $this->retrieveReferencedObjectTypeFromCarry() + ); + + if ($node_retrieval->can($action)) { + return $node_retrieval->do($action); + } + + $upload_handler = $this->environment->getPresentationFactory()->getUploadHandler( + $this->environment, + new Stakeholder( + $this->current_user->getId() + ) + ); + + if ($upload_handler->can($action)) { + return $upload_handler->do($action); + } + + throw new \InvalidArgumentException( + "No actor found that can '{$action}'" + ); + } + + private function buildSelectTypeForm(): StandardForm + { + $uf = $this->environment->getUIFactory(); + + return $uf->input()->container()->form()->standard( + $this->environment->withSubActionParameter( + self::SUB_ACTION_SELECT_CONTENT + )->getUrlBuilder()->buildURI()->__toString(), + [ + 'type' => $this->buildTypeSelect() + ] + )->withSubmitLabel( + $this->environment->getLanguage()->txt('next') + ); + } + + private function processSelectTypeForm(): Async + { + $form = $this->buildSelectTypeForm()->withRequest( + $this->environment->getHttpServices()->request() + ); + $data = $form->getData(); + if ($data === null) { + return $this->buildPromptShowAsync($form); + ; + } + + if ($data['type'] === Types::None) { + $this->repository->delete( + $this->environment->getAnswerFormId() + ); + return $this->buildRedirectToOverviewAsync(); + } + + $inputs_builder = $this->buildInputsBuilderSelectContentForm() + ->withCarry($data['type']->value); + $inputs_builder->persistCarry(); + + return $this->buildPromptShowAsync( + $this->buildSelectContentForm($inputs_builder) + ); + } + + private function buildSelectContentForm( + InputsBuilderSession $inputs_builder + ): StandardForm { + $uf = $this->environment->getUIFactory(); + + $form = $uf->input()->container()->form()->standard( + $this->environment->withSubActionParameter( + self::SUB_ACTION_SAVE_CONTENT + )->getUrlBuilder()->buildURI()->__toString(), + [ + 'content' => $inputs_builder->getInputs() + ] + ); + + $type = $inputs_builder->retrieveCarry( + $this->environment->getRefinery()->custom()->transformation( + fn(string $v): Types => Types::tryFrom($v) + ) + ); + if ($type->hasSelectContentSubForm()) { + return $form->withSubmitLabel( + $this->environment->getLanguage()->txt('next') + ); + } + return $form; + } + + private function processSelectContentForm(): Async + { + $select_content_inputs_builder = $this->buildInputsBuilderSelectContentForm(); + $form = $this->buildSelectContentForm( + $select_content_inputs_builder + )->withRequest( + $this->environment->getHttpServices()->request() + ); + $data = $form->getData(); + if ($data === null) { + $select_content_inputs_builder->persistCarry(); + return $this->buildPromptShowAsync($form); + } + + if (!$data['content']->getType()->hasSelectContentSubForm()) { + $this->repository->store($data['content']); + return $this->buildRedirectToOverviewAsync(); + } + + $select_sub_content_inputs_builder = $this + ->buildInputsBuilderSelectSubContentForm() + ->withCarry( + json_encode([ + 'type' => $data['content']->getType()->value, + 'target_ref_id' => $data['content']->getTargetRefId() + ]) + ); + $select_sub_content_inputs_builder->persistCarry(); + + return $this->buildPromptShowAsync( + $this->buildSelectSubContentForm($select_sub_content_inputs_builder) + ); + } + + private function buildSelectSubContentForm( + InputsBuilderSession $inputs_builder + ): StandardForm { + $uf = $this->environment->getUIFactory(); + + $form = $uf->input()->container()->form()->standard( + $this->environment->withSubActionParameter( + self::SUB_ACTION_SAVE_SUB_CONTENT + )->getUrlBuilder()->buildURI()->__toString(), + [ + 'content' => $inputs_builder->getInputs() + ] + ); + return $form; + } + + private function processSelectSubContentForm(): Async + { + $inputs_builder = $this->buildInputsBuilderSelectSubContentForm(); + $form = $this->buildSelectSubContentForm( + $inputs_builder + )->withRequest( + $this->environment->getHttpServices()->request() + ); + $data = $form->getData(); + if ($data === null) { + $inputs_builder->persistCarry(); + return $this->buildPromptShowAsync($form); + } + + $this->repository->store($data['content']); + return $this->buildRedirectToOverviewAsync(); + } + + private function buildTypeSelect(): Select + { + return $this->environment->getUIFactory()->input()->field()->select( + $this->environment->getLanguage()->txt('type'), + Types::buildOptionsList( + $this->environment->getLanguage() + ) + )->withAdditionalTransformation( + $this->environment->getRefinery()->custom()->transformation( + fn(string $v): Types => Types::tryFrom($v) ?? Types::None + ) + )->withRequired(true); + } + + private function buildInputsBuilderSelectContentForm(): InputsBuilderSession + { + return $this->environment->getPresentationFactory() + ->getSessionBasedInputsBuilder( + $this->environment->getRefinery()->custom()->transformation( + fn(string $v): Section + => Types::tryFrom($v)->buildContentInput( + $this->repository, + $this->rbac_system, + $this->tree, + $this->environment, + $this->current_user->getId() + ) + ) + ); + } + + private function buildInputsBuilderSelectSubContentForm(): InputsBuilderSession + { + return $this->environment->getPresentationFactory() + ->getSessionBasedInputsBuilder( + $this->environment->getRefinery()->custom()->transformation( + function (string $v): Section { + $v_array = json_decode($v, true); + return Types::tryFrom($v_array['type'])->buildSubContentInput( + $this->repository, + $this->rbac_system, + $this->environment, + $v_array['target_ref_id'] + ); + } + ) + ); + } + + private function buildPromptShowAsync( + StandardForm $form + ): Async { + $lng = $this->environment->getLanguage(); + + return $this->environment->getPresentationFactory()->getAsync( + $this->environment->getUIFactory()->prompt()->state()->show( + $form + )->withTitle( + $lng->txt('edit_suggested_learning_content') + ) + ); + } + + private function buildRedirectToOverviewAsync(): Async + { + return $this->environment->getPresentationFactory()->getAsync( + $this->environment->getUIFactory()->prompt()->state()->redirect( + $this->environment + ->withDefaultSubAction() + ->getUrlBuilder() + ->buildURI() + ) + ); + } + + private function retrieveReferencedObjectTypeFromCarry(): string + { + $inputs_builder = $this->buildInputsBuilderSelectContentForm(); + $inputs_builder->persistCarry(); + + return $inputs_builder->retrieveCarry( + $this->environment->getRefinery()->custom()->transformation( + fn(string $v): string => Types::tryFrom($v) + ->getReferencedObjectType() + ) + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/Repository.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/Repository.php new file mode 100644 index 000000000000..79571ff0d84c --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/Repository.php @@ -0,0 +1,169 @@ +table_names_builder = new TableNameBuilder( + QuestionRepository::COMPONENT_NAMESPACE, + null + ); + } + + public function getNew( + Uuid $answer_form_id, + Types $type + ): Content { + return new Content( + $this->irss, + $answer_form_id, + $type, + '' + ); + } + + public function getFor( + Uuid $answer_form_id, + ): Content { + $database_values = $this->buildQuery( + $answer_form_id + )->getRecords()->current(); + + if ($database_values === null) { + return $this->getNew( + $answer_form_id, + Types::None + ); + } + + return $database_values->retrieveCurrentRecord( + $this->persistence_factory->table( + $this->table_names_builder, + TableTypes::SuggestedLearningContent + ), + $this->refinery->custom()->transformation( + fn(array $vs): Content => new Content( + $this->irss, + $answer_form_id, + Types::tryFrom($vs[0]['type']), + $vs[0]['content'] + ) + ) + ); + } + + public function store( + Content $content + ): void { + (new Manipulate( + $this->db, + QuestionRepository::COMPONENT_NAMESPACE + ))->withAdditionalStatement( + $this->persistence_factory->replace( + $this->table_definitions->getColumns( + $this->table_names_builder, + TableTypes::SuggestedLearningContent + ), + $content->toStorage( + $this->persistence_factory + ) + ) + )->run(); + } + + public function delete( + Uuid $answer_form_id + ): void { + (new Manipulate( + $this->db, + QuestionRepository::COMPONENT_NAMESPACE + ))->withAdditionalStatement( + $this->persistence_factory->delete( + $this->persistence_factory->table( + $this->table_names_builder, + TableTypes::SuggestedLearningContent + ), + [ + $this->persistence_factory->where( + $this->table_definitions->getIdColumn( + $this->table_names_builder, + TableTypes::SuggestedLearningContent + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $answer_form_id->toString() + ) + ) + ] + ) + )->run(); + } + + private function buildQuery( + Uuid $answer_form_id + ): Query { + return $this->table_definitions->completeQuery( + new Query( + $this->db, + $this->refinery, + QuestionRepository::COMPONENT_NAMESPACE, + $this->persistence_factory->table( + $this->table_names_builder, + TableTypes::SuggestedLearningContent + ) + ), + $this->table_definitions->getIdColumn( + $this->table_names_builder, + TableTypes::SuggestedLearningContent + ) + )->withAdditionalWhere( + $this->persistence_factory->where( + $this->table_definitions->getIdColumn( + $this->table_names_builder, + TableTypes::SuggestedLearningContent + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $answer_form_id->toString() + ) + ) + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/Stakeholder.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/Stakeholder.php new file mode 100644 index 000000000000..2c5c79794ba8 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/Stakeholder.php @@ -0,0 +1,39 @@ + + */ +class Stakeholder extends AbstractResourceStakeholder +{ + public function getId(): string + { + return 'questions_suggested_learning_content'; + } + + public function getOwnerOfNewResources(): int + { + return $this->default_owner; + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/TableDefinitions.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/TableDefinitions.php new file mode 100644 index 000000000000..001213a858bd --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/TableDefinitions.php @@ -0,0 +1,100 @@ +persistence_factory->table( + $table_names_builder, + $table_type + ); + return array_map( + fn(string $v): Column => $this->persistence_factory->column( + $table, + $v + ), + self::SUGGESTED_LEARNING_CONTENT_TABLE_COLUMNS + ); + } + + public function getIdColumn( + TableNameBuilder $table_names_builder, + TableTypesInterface $table_type + ): Column { + $table = $this->persistence_factory->table( + $table_names_builder, + $table_type + ); + + return $this->persistence_factory->column( + $table, + self::SUGGESTED_LEARNING_CONTENT_TABLE_ID_COLUMN + ); + } + + public function completeQuery( + Query $query, + ?Column $base_table_id_column + ): Query { + $table_names_builder = $query->getTableNameBuilder(null); + + return $query->withAdditionalSelect( + $this->persistence_factory->select( + $this->getColumns( + $table_names_builder, + TableTypes::SuggestedLearningContent + ) + ) + )->withAdditionalOrder( + $this->persistence_factory->order( + $base_table_id_column + ) + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/TableTypes.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/TableTypes.php new file mode 100644 index 000000000000..dd42118ae452 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/TableTypes.php @@ -0,0 +1,28 @@ + $lng->txt('glossary_term'), + default => $lng->txt($this->value) + }; + } + + public static function buildOptionsList( + Language $lng + ): array { + return array_reduce( + self::cases(), + function (array $c, Types $v) use ($lng): array { + if ($v === Types::None) { + return $c; + } + $c[$v->value] = $v->getTranslatedOptionName($lng); + return $c; + }, + [] + ); + } + + public function present( + Language $lng, + \ilCtrl $ctrl, + StaticURLServices $static_url, + IRSS $irss, + UIFactory $ui_factory, + string $file_title, + ?ResourceIdentification $rid, + ?int $target_ref_id, + ?int $sub_object_id + ): ?StandardLink { + return match($this) { + self::File => $ui_factory->link()->standard( + $file_title === '' + ? $irss->manage()->getResource($rid)->getCurrentRevision()->getTitle() + : $file_title, + $irss->consume()->src($rid)->getSrc(true) + ), + self::LearningModule => $this->buildLinkToLearningModule( + $ctrl, + $lng, + $ui_factory->link(), + $target_ref_id + ), + self::LearningModulePage, + self::LearningModuleChapter, + self::GlossaryTerm => $this->buildLinkToSubObject( + $lng, + $ui_factory->link(), + $static_url, + $target_ref_id, + $sub_object_id + ), + self::None => null + }; + } + + public function getReferencedObjectType(): string + { + return match ($this) { + self::LearningModule, + self::LearningModuleChapter, + self::LearningModulePage => 'lm', + self::GlossaryTerm => 'glo', + default => '' + }; + } + + public function hasSelectContentSubForm(): bool + { + return $this !== self::LearningModule && $this !== self::File; + } + + public function buildContentInput( + Repository $repository, + \ilRbacSystem $rbac_system, + \ilTree $tree, + Environment $environment, + int $current_user_id + ): Section { + return match($this) { + self::File => $this->buildUploadFileInput( + $repository, + $environment, + $current_user_id + ), + default => $this->buildSelectObjectInput( + $repository, + $rbac_system, + $tree, + $environment, + $this->getReferencedObjectType() + ) + }; + } + + public function buildSubContentInput( + Repository $repository, + \ilRbacSystem $rbac_system, + Environment $environment, + int $target_ref_id + ): Section { + if (!$this->hasSelectContentSubForm()) { + throw new InvalidArgumentException( + 'This type of SuggestedLearningContent does not provide a SubContentInput.' + ); + } + + $ff = $environment->getUIFactory()->input()->field(); + $lng = $environment->getLanguage(); + + return $ff->section( + [ + 'sub_object' => $ff->select( + $this->getTranslatedOptionName($lng), + $this->buildSubContentOptions( + $rbac_system, + $target_ref_id + ) + )->withRequired(true) + ], + $lng->txt('select_target') + )->withAdditionalTransformation( + $environment->getRefinery()->custom()->transformation( + fn(array $vs): Content => $repository->getNew( + $environment->getAnswerFormId(), + $this + )->withTargetRefId($target_ref_id) + ->withSubObjectId((int) $vs['sub_object']) + ) + ); + } + + private function buildLinkToLearningModule( + \ilCtrl $ctrl, + Language $lng, + LinkFactory $link_factory, + int $target_ref_id + ): StandardLink { + $ctrl->setParameterByClass( + \ilLMPresentationGUI::class, + 'ref_id', + $target_ref_id + ); + + $link = $link_factory->standard( + "{$this->getTranslatedOptionName($lng)}: {$this->lookupObjectTitle($target_ref_id)}", + $ctrl->getLinkTargetByClass( + [ + \ilLMPresentationGUI::class + ], + ) + ); + $ctrl->clearParameterByClass( + \ilLMPresentationGUI::class, + 'ref_id' + ); + return $link; + } + + private function buildLinkToSubObject( + Language $lng, + LinkFactory $link_factory, + StaticURLServices $static_url, + int $target_ref_id, + int $sub_object_id + ): StandardLink { + $sub_object_title = match($this) { + self::GlossaryTerm => ( + new \ilGlossaryTerm($sub_object_id) + )->getTerm(), + default => \ilLMObject::_lookupTitle( + $sub_object_id + ) + }; + + return $link_factory->standard( + "{$this->getTranslatedOptionName($lng)}: {$this->lookupObjectTitle($target_ref_id)} - {$sub_object_title}", + $static_url->builder()->build( + $this->value, + null, + [$sub_object_id] + )->__toString() + ); + } + + private function lookupObjectTitle( + int $target_ref_id + ): string { + return \ilObject::_lookupTitle( + \ilObject::_lookupObjId($target_ref_id) + ); + } + + private function buildUploadFileInput( + Repository $repository, + Environment $environment, + int $current_user_id + ): Section { + $ff = $environment->getUIFactory()->input()->field(); + $lng = $environment->getLanguage(); + + return $ff->section( + [ + Content::KEY_FILE_TITLE => $ff->text( + $lng->txt('title'), + $lng->txt('if_no_title_then_filename') + ), + Content::KEY_RID => $ff->file( + $environment->getPresentationFactory()->getUploadHandler( + $environment, + new Stakeholder($current_user_id) + ), + $lng->txt('file') + )->withRequired(true) + ], + $lng->txt('upload_file') + )->withAdditionalTransformation( + $environment->getRefinery()->custom()->transformation( + fn(array $vs): Content => $repository->getNew( + $environment->getAnswerFormId(), + $this + )->withFileInfo($vs) + ) + ); + } + + private function buildSelectObjectInput( + Repository $repository, + \ilRbacSystem $rbac_system, + \ilTree $tree, + Environment $environment, + string $type + ): Section { + $ff = $environment->getUIFactory()->input()->field(); + $lng = $environment->getLanguage(); + + $node_retrieval = new NodeRetrieval( + $rbac_system, + $tree, + $environment, + $type + ); + + return $ff->section( + [ + 'object' => $ff->treeSelect( + $node_retrieval, + $lng->txt('select') + )->withAdditionalTransformation( + $node_retrieval->buildValidNodeConstraint() + ) + ], + $this->getTranslatedOptionName($lng) + )->withAdditionalTransformation( + $environment->getRefinery()->custom()->transformation( + fn(array $vs): Content => $repository->getNew( + $environment->getAnswerFormId(), + $this + )->withTargetRefId((int) $vs['object'][0]) + ) + ); + } + + private function buildSubContentOptions( + \ilRbacSystem $rbac_system, + int $target_ref_id + ): array { + if (!$rbac_system->checkAccess('read', $target_ref_id)) { + return []; + } + + return match ($this) { + self::GlossaryTerm => array_reduce( + (new \ilObjGlossary($target_ref_id))->getTermList(), + function (array $c, array $v): array { + $c[$v['id']] = $v['term']; + return $c; + }, + [] + ), + self::LearningModulePage, + self::LearningModuleChapter => array_reduce( + \ilLMObject::getObjectList( + \ilObject::_lookupObjectId($target_ref_id), + $this === self::LearningModuleChapter + ? 'st' + : 'pg' + ), + function (array $c, array $v): array { + $c[$v['obj_id']] = $v['title']; + return $c; + }, + [] + ) + }; + + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/TextFeedback/Capability.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/TextFeedback/Capability.php new file mode 100644 index 000000000000..ca02e412dae1 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/TextFeedback/Capability.php @@ -0,0 +1,233 @@ +getTypeGenericProperties() + ->getDefinition() + ->hasCapability( + self::getIdentifier() + ); + } + + #[\Override] + public function getAnswerFormEditAdditionalTab(): ActionWithTab + { + return new ActionWithTab( + $this, + 'feedback', + $this->buildDoEditActionClosure() + ); + } + + #[\Override] + public function getFeedback( + Properties $answer_form_properties + ): ?Feedback { + return $this->retrieveMigratedFeedback( + $answer_form_properties->getAnswerFormId(), + $answer_form_properties + ->getTypeGenericProperties() + ->getDefinition() + ->getCapability( + self::getIdentifier() + ) + ); + } + + #[\Override] + public function runPageMigration(): void + { + $this->repository->migratePageFeedback(); + } + + #[\Override] + public function onAnswerFormClone( + UuidFactory $uuid_factory, + Properties $old_answer_form_properties, + Properties $new_answer_form_properties + ): void { + $this->repository->store( + $new_answer_form_properties->getAnswerFormId(), + $this->repository->getFor( + $old_answer_form_properties->getAnswerFormId(), + $old_answer_form_properties + ->getTypeGenericProperties() + ->getDefinition() + ->getCapability(self::getIdentifier()) + )->onAnswerFormClone( + $uuid_factory, + $old_answer_form_properties, + $new_answer_form_properties + ) + ); + } + + #[\Override] + public function onAnswerFormUpdate( + Properties $answer_form_properties + ): void { + $this->repository->store( + $answer_form_properties->getAnswerFormId(), + $this->repository->getFor( + $answer_form_properties->getAnswerFormId(), + $answer_form_properties + ->getTypeGenericProperties() + ->getDefinition() + ->getCapability(self::getIdentifier()) + )->onAnswerFormUpdate( + $answer_form_properties + ) + ); + } + + public function onAnswerFormDelete( + Properties $answer_form_properties + ): void { + $this->repository->delete( + $answer_form_properties->getAnswerFormId() + ); + } + + private function buildDoEditActionClosure(): \Closure + { + return function ( + Environment $environment + ): Async|Viewable { + $sub_action = $environment->getSubAction(); + return match ($sub_action) { + '' => $this->buildOverview($environment), + self::SUB_ACTION_INSERT_LEGACY_TEXTS => $this->buildOverviewWithLegacyTexts( + $environment + ), + self::SUB_ACTION_SAVE => $this->save($environment), + default => $this->buildOverview($environment)->doAction( + $this->repository, + $sub_action + ) + }; + }; + } + + private function buildOverview( + Environment $environment + ): Overview { + return new Overview( + $environment, + $this->text_factory, + $this->retrieveMigratedFeedback( + $environment->getAnswerFormId(), + $environment + ->getAnswerFormProperties() + ->getTypeGenericProperties() + ->getDefinition() + ->getCapability(self::getIdentifier()) + ), + $environment->withSubActionParameter( + self::SUB_ACTION_SAVE + )->getUrlBuilder(), + $environment->withSubActionParameter( + self::SUB_ACTION_INSERT_LEGACY_TEXTS + )->getUrlBuilder() + ); + } + + private function buildOverviewWithLegacyTexts( + Environment $environment + ): Overview { + return $this->buildOverview($environment) + ->withLegacyTextsAsValues(true); + } + + private function save( + Environment $environment + ): Overview { + $feedback = $this->buildOverview($environment)->processForm(); + if ($feedback instanceof Overview) { + return $feedback; + } + + $this->repository->store( + $environment->getAnswerFormId(), + $feedback + ); + + return $environment->redirectTo( + $environment->withDefaultSubAction()->getUrlBuilder() + ); + } + + private function retrieveMigratedFeedback( + Uuid $answer_form_id, + TextFeedback $feedback + ): TextFeedback { + $feedback_with_data = $this->repository->getFor( + $answer_form_id, + $feedback + ); + + if ($feedback_with_data->requiresPageMigration()) { + $feedback_with_data = $feedback_with_data->withMigratedPageFeedbacks(); + $this->repository->store( + $answer_form_id, + $feedback_with_data + ); + } + + return $feedback_with_data; + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/TextFeedback/Migration.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/TextFeedback/Migration.php new file mode 100644 index 000000000000..36282fb85ed8 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/TextFeedback/Migration.php @@ -0,0 +1,393 @@ +buildGenericFeedbackInsert( + $migration_insert->getPersistenceFactory(), + $migration_insert + ); + if ($generic_feedback_insert !== null) { + $migration_insert = $migration_insert + ->withAdditionalInsert( + $generic_feedback_insert + ); + } + + $specific_feedback_insert = $this->buildSpecificFeedbackInsert( + $migration_insert->getPersistenceFactory(), + $answer_form_migration, + $migration_insert + ); + if ($specific_feedback_insert !== null) { + $migration_insert = $migration_insert + ->withAdditionalInsert( + $specific_feedback_insert + ); + } + + return $migration_insert; + } + + private function fetchGenericFeedbackDBValues( + \ilDBInterface $db, + int $old_question_id + ): \Generator { + $query = $db->queryF( + 'SELECT * FROM qpl_fb_generic WHERE question_fi = %s ORDER BY question_fi', + [FieldDefinition::T_INTEGER], + [$old_question_id] + ); + + yield from $this->fetchFeedbacksForQuestions($db, $query); + } + + private function fetchSpecificFeedbackDBValues( + \ilDBInterface $db, + int $old_question_id + ): \Generator { + $query = $db->queryF( + 'SELECT * FROM qpl_fb_specific WHERE question_fi = %s ORDER BY question_fi', + [FieldDefinition::T_INTEGER], + [$old_question_id] + ); + + yield from $this->fetchFeedbacksForQuestions($db, $query); + } + + private function fetchFeedbacksForQuestions( + \ilDBInterface $db, + \ilDBStatement $query + ): \Generator { + $feedbacks_for_question = null; + while (($row = $db->fetchAssoc($query)) !== null) { + if ($feedbacks_for_question === null) { + $feedbacks_for_question = [$row]; + continue; + } + + if ($feedbacks_for_question[0]['question_fi'] === $row['question_fi']) { + $feedbacks_for_question[] = $row; + continue; + } + + yield $feedbacks_for_question; + $feedbacks_for_question = [$row]; + } + + if ($feedbacks_for_question !== null) { + yield $feedbacks_for_question; + } + } + + private function buildGenericFeedbackInsert( + PersistenceFactory $persistence_factory, + MigrationInsert $migration_insert + ): ?Insert { + $insert = null; + foreach ($this->fetchGenericFeedbackDBValues( + $migration_insert->getDb(), + $migration_insert->getOldQuestionId() + ) as $feedback_for_question) { + $insert = $this->addGenericFeedbackToInsert( + $persistence_factory, + $migration_insert, + $insert, + $feedback_for_question + ); + } + + return $insert; + } + + private function addGenericFeedbackToInsert( + PersistenceFactory $persistence_factory, + MigrationInsert $migration_insert, + ?Insert $insert, + array $feedback_for_question + ): Insert { + $values = $this->buildNewGenericFeedbackValuesFromOld( + $persistence_factory, + $migration_insert, + $feedback_for_question + ); + + if ($insert === null) { + return $persistence_factory->insert( + $this->table_definitions->getColumns( + $migration_insert->getTableNameBuilder(null), + TableTypes::FeedbackGeneric + ), + $values + ); + } + + return $insert->withAdditionalValues($values); + } + + private function buildNewGenericFeedbackValuesFromOld( + PersistenceFactory $persistence_factory, + MigrationInsert $migration_insert, + array $feedback_for_question + ): array { + $page_editor_used = $migration_insert->wasIliasPageEditorUsedForAdditionalTexts(); + + $feedback_best_response = ''; + $feedback_other_response = ''; + foreach ($feedback_for_question as $feedback_row) { + $feedback_text = $this->generateTextValueForFeedback( + $migration_insert->getDb(), + $page_editor_used, + $feedback_row['feedback_id'], + $feedback_row['feedback'] + ); + if ($feedback_row['correctness'] === '1') { + $feedback_best_response = $feedback_text; + continue; + } + $feedback_other_response = $feedback_text; + } + + return [ + $persistence_factory->value( + FieldDefinition::T_TEXT, + $migration_insert->getAnswerFormId()->toString() + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + '' + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $feedback_best_response, + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + '' + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $feedback_other_response + ) + ]; + } + + private function buildSpecificFeedbackInsert( + PersistenceFactory $persistence_factory, + AnswerFormMigration $answer_form_migration, + MigrationInsert $migration_insert + ): ?Insert { + $insert = null; + foreach ($this->fetchSpecificFeedbackDBValues( + $migration_insert->getDb(), + $migration_insert->getOldQuestionId() + ) as $feedback_for_question) { + $insert = $this->addSpecificFeedbackToInsert( + $persistence_factory, + $answer_form_migration, + $migration_insert, + $insert, + $feedback_for_question + ); + } + + return $insert; + } + + private function addSpecificFeedbackToInsert( + PersistenceFactory $persistence_factory, + AnswerFormMigration $answer_form_migration, + MigrationInsert $migration_insert, + ?Insert $insert, + array $feedback_for_question + ): ?Insert { + $values = $this->buildNewSpecificFeedbackValuesFromOld( + $persistence_factory, + $answer_form_migration, + $migration_insert, + $feedback_for_question + ); + + if ($values === []) { + return $insert; + } + + if ($insert === null) { + $insert = $persistence_factory->insert( + $this->table_definitions->getColumns( + $migration_insert->getTableNameBuilder(null), + TableTypes::FeedbackSpecific + ), + array_shift($values) + ); + } + + return array_reduce( + $values, + fn(Insert $c, array $v): Insert + => $c->withAdditionalValues($v), + $insert + ); + } + + private function buildNewSpecificFeedbackValuesFromOld( + PersistenceFactory $persistence_factory, + AnswerFormMigration $answer_form_migration, + MigrationInsert $migration_insert, + array $feedback_for_question + ): array { + $conditions = array_reduce( + $feedback_for_question, + function ( + array $c, + array $v + ) use ( + $answer_form_migration, + $migration_insert + ): array { + $parent_id = $answer_form_migration->getNewAnswerInputIdForOld( + $v['question'] + ); + + $conditions = $answer_form_migration->getConditionsForFeedbackFromOldValues( + $v['answer'], + $v['question'] + ); + + if ($parent_id === null || $conditions === null) { + return $c; + } + + if (!array_key_exists($parent_id->toString(), $c)) { + $c[$parent_id->toString()] = []; + } + + /* + * sk, 2026-06-09: We got rid of the option to set a value and a + * range on numeric gaps. We now need to make sure that we do not + * have feedback for both. If there is feedback for both only the + * first one is kept. I do not see a better solution. + */ + if ($conditions[0] === Range::InRange->value + && array_filter( + $c[$parent_id->toString()], + fn(array $v): bool => $v['conditions'][0] === Range::InRange->value + ) !== []) { + return $c; + } + + $c[$parent_id->toString()][] = [ + 'conditions' => $conditions, + 'text' => $this->generateTextValueForFeedback( + $migration_insert->getDb(), + $migration_insert->wasIliasPageEditorUsedForAdditionalTexts(), + $v['feedback_id'], + $v['feedback'] + ) + ]; + + return $c; + }, + [] + ); + + $inserts = []; + foreach ($conditions as $parent_id => $definitions) { + foreach ($definitions as $definition) { + foreach ($definition['conditions'] as $condition) { + $inserts[] = [ + $persistence_factory->value( + FieldDefinition::T_TEXT, + $migration_insert->getUuid()->toString() + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $migration_insert->getAnswerFormId()->toString() + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $parent_id + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $condition + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + '' + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->sanitizeLegacyText( + $migration_insert->getDb(), + $definition['text'], + $migration_insert->wasIliasPageEditorUsedForAdditionalTexts() + ) + ), + ]; + } + } + } + + return $inserts; + } + + private function generateTextValueForFeedback( + \ilDBInterface $db, + bool $page_editor_used, + int $feedback_id, + string $feedback_text + ): string { + if ($page_editor_used) { + return "####{$feedback_id}####"; + } + + return $this->sanitizeLegacyText( + $db, + $feedback_text, + $page_editor_used + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/TextFeedback/Overview.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/TextFeedback/Overview.php new file mode 100644 index 000000000000..3d6dd44f9351 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/TextFeedback/Overview.php @@ -0,0 +1,228 @@ +specific_feedback_table = $feedback->getSpecificFeedbackTable( + $environment + ); + } + + #[\Override] + public function getUI(): array + { + $ui_factory = $this->environment->getUIFactory(); + $lng = $this->environment->getLanguage(); + + $content = []; + + if ($this->feedback->hasLegacyTexts() + && !$this->set_legacy_texts_as_values) { + $content[] = $ui_factory->messageBox()->info( + $lng->txt('insert_legacy_texts_info') + )->withButtons([ + $ui_factory->button()->standard( + $lng->txt('insert_legacy_texts'), + $this->insert_legacy_texts_uri->buildURI()->__toString() + ) + ]); + } + + $form = $this->form ?? $this->buildForm(); + if ($form !== null) { + $content[] = $form; + } + + if ($this->specific_feedback_table !== null) { + $modal = $this->specific_feedback_table->getCreateModal( + $this->environment + ); + $content[] = $ui_factory->button()->standard( + $lng->txt('create_feedback'), + $modal->getShowSignal() + ); + $content[] = $modal; + $content[] = $this->specific_feedback_table->getTable( + $this->environment, + $this->feedback + )->withTitle( + $lng->txt('specific_feedback') + ); + } + + if ($this->modal !== null) { + $content[] = $this->modal; + } + return $content; + } + + public function withLegacyTextsAsValues( + bool $replace_with_legacy_texts + ): self { + $clone = clone $this; + $clone->set_legacy_texts_as_values = $replace_with_legacy_texts; + return $clone; + } + + public function processForm(): TextFeedback|StandardForm + { + $this->form = $this->buildForm()?->withRequest( + $this->environment->getHttpServices()->request() + ); + $data = $this->form?->getData(); + + return $data === null + ? $this + : $data['feedback']; + } + + public function doAction( + Repository $repository, + string $action + ): Async|self { + $result = $this->specific_feedback_table->doAction( + $this->environment, + $this->feedback, + $action + ); + + if ($result instanceof Async) { + return $result; + } + + $clone = clone $this; + if ($result instanceof RoundTripModal) { + $clone->modal = $result->withOnLoad($result->getShowSignal()); + return $clone; + } + + $repository->store($this->environment->getAnswerFormId(), $result); + $clone->feedback = $result; + $clone->specific_feedback_table = $result->getSpecificFeedbackTable( + $this->environment + ); + return $clone; + } + + private function buildForm(): ?StandardForm + { + $if = $this->environment->getUIFactory()->input(); + $lng = $this->environment->getLanguage(); + + $inputs = $this->buildGenericFormInputs(); + + $additional_inputs = $this->feedback->getAdditionalInputs( + $lng, + $this->environment->getUIFactory(), + $this->set_legacy_texts_as_values + ); + + if ($additional_inputs !== null) { + $inputs['specific_feedback'] = $if->field()->section( + $additional_inputs, + $lng->txt('edit_specific_feedback') + ); + } + + if ($inputs === []) { + return null; + } + + return $if->container()->form()->standard( + $this->save_uri->buildURI()->__toString(), + [ + 'feedback' => $if->field()->group( + $inputs + )->withAdditionalTransformation( + $this->environment->getRefinery()->custom()->transformation( + fn(array $vs): TextFeedback => ($vs['specific_feedback'] ?? $this->feedback) + ->withFeedbackBestResponse( + $this->text_factory->markdown($vs['generic_feedback']['max_points']) + )->withFeedbackOtherResponse( + $this->text_factory->markdown($vs['generic_feedback']['not_max_points']) + ) + ) + ) + ] + ); + } + + private function buildGenericFormInputs(): array + { + if (!$this->environment->isMarkingRequired()) { + return []; + } + + $if = $this->environment->getUIFactory()->input(); + $lng = $this->environment->getLanguage(); + + return [ + 'generic_feedback' => $if->field()->section( + [ + 'max_points' => $if->field()->markdown( + new \ilUIMarkdownPreviewGUI(), + Types::BestResponse->getTranslatedOptionName($lng) + )->withValue( + $this->set_legacy_texts_as_values + ? $this->environment->getRefinery()->string()->stripTags()->transform( + $this->feedback->getFeedbackBestResponseLegacy() + ) : $this->feedback->getFeedbackBestResponse() + ?->getRawRepresentation() ?? '' + ), + 'not_max_points' => $if->field()->markdown( + new \ilUIMarkdownPreviewGUI(), + Types::OtherResponse->getTranslatedOptionName($lng) + )->withValue( + $this->set_legacy_texts_as_values + ? $this->environment->getRefinery()->string()->stripTags()->transform( + $this->feedback->getFeedbackOtherResponseLegacy() + ) : $this->feedback->getFeedbackOtherResponse() + ?->getRawRepresentation() ?? '' + ), + ], + $lng->txt('edit_generic_feedback') + ) + ]; + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/TextFeedback/OverviewTable.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/TextFeedback/OverviewTable.php new file mode 100644 index 000000000000..e0fb1a05607a --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/TextFeedback/OverviewTable.php @@ -0,0 +1,44 @@ +feedback_table_names_builder = new TableNameBuilder( + QuestionRepository::COMPONENT_NAMESPACE, + null + ); + } + + public function getFor( + Uuid $answer_form_id, + TextFeedback $feedback + ): TextFeedback { + $database_values = $this + ->buildQuery() + ->withAdditionalWhere( + $this->persistence_factory->where( + $this->feedback_table_definitions->getIdColumn( + $this->feedback_table_names_builder, + TableTypes::FeedbackGeneric + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $answer_form_id->toString() + ) + ) + )->withGroupBy( + $this->feedback_table_definitions->getIdColumn( + $this->feedback_table_names_builder, + TableTypes::FeedbackGeneric + ) + )->getRecords()->current(); + + if ($database_values === null) { + return $feedback; + } + + $feedback_with_generic_values = $database_values->retrieveCurrentRecord( + $this->persistence_factory->table( + $this->feedback_table_names_builder, + TableTypes::FeedbackGeneric + ), + $this->refinery->custom()->transformation( + fn(array $vs): TextFeedback => $feedback->withGenericFeedbackFromDatabase( + $this->text_factory, + $vs + ) + ) + ); + + return $feedback_with_generic_values->withSpecificFeedbackFromDatabase( + $this->uuid_factory, + $this->text_factory, + $database_values->retrieveCurrentRecord( + $this->persistence_factory->table( + $this->feedback_table_names_builder, + TableTypes::FeedbackSpecific + ), + $this->refinery->identity() + ) + ); + } + + public function store( + Uuid $answer_form_id, + TextFeedback $feedback + ): void { + $feedback->toStorage( + $this->persistence_factory, + $this->feedback_table_definitions, + $this->feedback_table_names_builder, + $answer_form_id, + new Manipulate( + $this->db, + QuestionRepository::COMPONENT_NAMESPACE + ) + )->run(); + } + + public function delete( + Uuid $answer_form_id + ): void { + (new Manipulate( + $this->db, + QuestionRepository::COMPONENT_NAMESPACE + ))->withAdditionalStatement( + $this->persistence_factory->delete( + $this->persistence_factory->table( + $this->feedback_table_names_builder, + TableTypes::FeedbackGeneric + ), + [ + $this->persistence_factory->where( + $this->feedback_table_definitions->getForeignKeyColumn( + $this->feedback_table_names_builder, + TableTypes::FeedbackGeneric + ) + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $answer_form_id->toString() + ) + ] + ) + )->withAdditionalStatement( + $this->persistence_factory->delete( + $this->persistence_factory->table( + $this->feedback_table_names_builder, + TableTypes::FeedbackSpecific + ), + [ + $this->persistence_factory->where( + $this->feedback_table_definitions->getForeignKeyColumn( + $this->feedback_table_names_builder, + TableTypes::FeedbackSpecific + ) + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $answer_form_id->toString() + ) + ] + ) + )->run(); + } + + private function buildQuery(): Query + { + return $this->feedback_table_definitions->completeQuery( + new Query( + $this->db, + $this->refinery, + QuestionRepository::COMPONENT_NAMESPACE, + $this->persistence_factory->table( + $this->feedback_table_names_builder, + TableTypes::FeedbackGeneric + ) + ), + $this->feedback_table_definitions->getIdColumn( + $this->feedback_table_names_builder, + TableTypes::FeedbackGeneric + ) + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/TextFeedback/SpecificTextFeedback.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/TextFeedback/SpecificTextFeedback.php new file mode 100644 index 000000000000..dc3b12acb48f --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/TextFeedback/SpecificTextFeedback.php @@ -0,0 +1,150 @@ +id; + } + + public function getParentId(): Uuid + { + return $this->parent_id; + } + + public function getCondition(): string + { + return $this->condition; + } + + public function withCondition( + string $condition + ): self { + $clone = clone $this; + $clone->condition = $condition; + return $clone; + } + + public function getFeedbackText(): Markdown + { + return $this->feedback_text; + } + + public function withFeedbackText( + Markdown $text + ): self { + $clone = clone $this; + $clone->feedback_text = $text; + return $clone; + } + + public function getFeedbackTextForPresentation( + Refinery $refinery + ): string { + if ($this->feedback_text !== null) { + return $refinery->string()->markdown()->toHTML()->transform( + $this->feedback_text->getRawRepresentation() + ); + } + + return $this->feedback_legacy; + } + + public function toStorage( + PersistenceFactory $persistence_factory + ): array { + if ($this->feedback_text === null) { + throw new \UnexpectedValueException( + 'You cannot save a SpecificTextFeedback without a feedback text!' + ); + } + + return [ + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->id->toString() + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->answer_form_id->toString() + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->parent_id->toString() + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->condition + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->feedback_text->getRawRepresentation() + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->feedback_legacy + ) + ]; + } + + public function requiresPageMigration(): bool + { + return preg_match('/####\d+####/', $this->feedback_legacy) === 1; + } + + public function withMigratedPageFeedbacks(): self + { + $clone = clone $this; + $feedback = trim($clone->feedback_legacy, '#'); + if (!is_numeric($feedback)) { + return $clone; + } + + $feedback_page = (new \ilAssSpecFeedbackPageGUI( + (int) $feedback + ))->presentation(); + + $clone->feedback_legacy = ''; + if (strip_tags($feedback_page) !== '') { + $clone->feedback_legacy = $feedback_page; + } + + return $clone; + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/TextFeedback/TableDefinitions.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/TextFeedback/TableDefinitions.php new file mode 100644 index 000000000000..10453c206f7c --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/TextFeedback/TableDefinitions.php @@ -0,0 +1,162 @@ +persistence_factory->table( + $table_names_builder, + $table_type + ); + + return array_map( + fn(string $v): Column => $this->persistence_factory->column( + $table, + $v + ), + match($table_type) { + TableTypes::FeedbackGeneric => self::FEEDBACK_GENERIC_TABLE_COLUMNS, + TableTypes::FeedbackSpecific => self::FEEDBACK_SPECIFIC_TABLE_COLUMNS + } + ); + } + + public function getIdColumn( + TableNameBuilder $table_names_builder, + TableTypesInterface $table_type + ): Column { + $table = $this->persistence_factory->table( + $table_names_builder, + $table_type + ); + + return match($table_type) { + TableTypes::FeedbackGeneric => $this->persistence_factory->column( + $table, + self::FEEDBACK_GENERIC_TABLE_ID_COLUMN + ), + TableTypes::FeedbackSpecific => $this->persistence_factory->column( + $table, + self::FEEDBACK_SPECIFIC_TABLE_ID_COLUMN + ) + }; + } + + public function getForeignKeyColumn( + TableNameBuilder $table_names_builder, + TableTypesInterface $table_type + ): Column { + $table = $this->persistence_factory->table( + $table_names_builder, + $table_type + ); + + return match($table_type) { + TableTypes::FeedbackGeneric => $this->persistence_factory->column( + $table, + self::FEEDBACK_GENERIC_TABLE_FOREIGN_KEY_COLUMN + ), + TableTypes::FeedbackSpecific => $this->persistence_factory->column( + $table, + self::FEEDBACK_SPECIFIC_TABLE_FOREIGN_KEY_COLUMN + ) + }; + } + + public function completeQuery( + Query $query, + ?Column $base_table_id_column + ): Query { + $table_names_builder = $query->getTableNameBuilder(null); + + return $query->withAdditionalSelect( + $this->persistence_factory->select( + $this->getColumns( + $table_names_builder, + TableTypes::FeedbackGeneric + ) + ) + )->withAdditionalSelect( + $this->persistence_factory->select( + $this->getColumns( + $table_names_builder, + TableTypes::FeedbackSpecific + ) + ) + )->withAdditionalJoin( + $this->persistence_factory->join( + $base_table_id_column, + $this->getForeignKeyColumn( + $table_names_builder, + TableTypes::FeedbackSpecific + ), + JoinType::Left + ) + )->withAdditionalOrder( + $this->persistence_factory->order( + $base_table_id_column + ) + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/TextFeedback/TableTypes.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/TextFeedback/TableTypes.php new file mode 100644 index 000000000000..2d9b54ebf5da --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/TextFeedback/TableTypes.php @@ -0,0 +1,29 @@ + + */ + private array $specific_feedbacks = []; + + abstract public function getAdditionalInputs( + Language $lng, + UIFactory $ui_factory, + bool $set_legacy_texts_as_values + ): ?array; + + abstract public function getSpecificFeedbackTable( + Environment $environment + ): ?OverviewTable; + + abstract public function onAnswerFormClone( + UuidFactory $uuid_factory, + Properties $old_answer_form_properties, + Properties $new_answer_form_properties + ): static; + + abstract public function onAnswerFormUpdate( + Properties $answer_form_properties + ): static; + + abstract protected function specificFeedbackInputsHaveLegacyTexts(): bool; + + /** + * @return list + */ + abstract protected function getSpecificFeedbackParticipantOutput( + UIFactory $ui_factory, + Refinery $refinery, + Properties $properties, + ?Response $response + ): array; + + /** + * The endpoint must point to a javascript namespace containing the functions + * `retrieveSpecificFeedback(data, bestResponse, response)` where `data` is + * the array produced by `getSpecificFeedbackForClientSideCode()`, `bestResponse` + * it the array produced by `Reponse::toClientSideRepresentation()` and `response` + * is an object containing all the inputs and their values as set in the + * interface. + */ + abstract protected function getSpecificFeedbackClientSideEndPoint(): string; + + abstract protected function getSpecificFeedbackForClientSideCode( + Language $lng, + Refinery $refinery, + UIFactory $ui_factory, + Properties $properties + ): array; + + #[\Override] + final public static function getCapabilityIdentifier(): string + { + return Capability::getIdentifier(); + } + + public function withGenericFeedbackFromDatabase( + TextFactory $text_factory, + ?array $database_data + ): static { + if ($database_data === null) { + return $this; + } + $clone = clone $this; + if ($database_data[0]['feedback_best_response'] !== '') { + $clone->feedback_best_response = $text_factory->markdown( + $database_data[0]['feedback_best_response'] + ); + } + $clone->feedback_best_response_legacy = $database_data[0]['feedback_best_response_legacy']; + + if ($database_data[0]['feedback_other_response'] !== '') { + $clone->feedback_other_response = $text_factory->markdown( + $database_data[0]['feedback_other_response'] + ); + } + $clone->feedback_other_response_legacy = $database_data[0]['feedback_other_response_legacy']; + return $clone; + } + + public function withSpecificFeedbackFromDatabase( + UuidFactory $uuid_factory, + TextFactory $text_factory, + ?array $database_data + ): static { + if ($database_data === null + || $database_data === []) { + return $this; + } + + $clone = clone $this; + foreach ($database_data as $feedback_data) { + $feedback = new SpecificTextFeedback( + $uuid_factory->fromString($feedback_data['id']), + $uuid_factory->fromString($feedback_data['answer_form_id']), + $uuid_factory->fromString($feedback_data['parent_id']), + $feedback_data['condition'], + $text_factory->markdown($feedback_data['feedback']), + $feedback_data['feedback_legacy'] + ); + + $clone->specific_feedbacks[$feedback_data['id']] = $feedback; + } + + return $clone; + } + + public function getFeedbackBestResponse(): ?Markdown + { + return $this->feedback_best_response; + } + + public function withFeedbackBestResponse( + Markdown $feedback_best_response + ): static { + $clone = clone $this; + $clone->feedback_best_response = $feedback_best_response; + return $clone; + } + + public function getFeedbackBestResponseLegacy(): string + { + return $this->feedback_best_response_legacy; + } + + public function getFeedbackOtherResponse(): ?Markdown + { + return $this->feedback_other_response; + } + + public function withFeedbackOtherResponse( + Markdown $feedback_other_response + ): static { + $clone = clone $this; + $clone->feedback_other_response = $feedback_other_response; + return $clone; + } + + public function getFeedbackOtherResponseLegacy(): string + { + return $this->feedback_other_response_legacy; + } + + public function hasLegacyTexts(): bool + { + return $this->feedback_best_response_legacy !== '' + || $this->feedback_other_response_legacy !== '' + || $this->specificFeedbackInputsHaveLegacyTexts(); + } + + public function getSpecificFeedbackForId( + Uuid $id + ): ?SpecificTextFeedback { + return $this->specific_feedbacks[$id->toString()] ?? null; + } + + public function getSpecificFeedbackForConditionOrNew( + UuidFactory $uuid_factory, + Uuid $answer_form_id, + Uuid $parent_id, + string $condition + ): SpecificTextFeedback { + $feedback = array_filter( + $this->specific_feedbacks, + fn(SpecificTextFeedback $v): bool => $v->getParentId()->toString() === $parent_id->toString() + && $v->getCondition() === $condition + ); + + return $feedback !== [] + ? current($feedback) + : new SpecificTextFeedback( + $uuid_factory->uuid4(), + $answer_form_id, + $parent_id, + $condition + ); + } + + /** + * @var array + */ + public function getSpecificFeedbacks(): array + { + return $this->specific_feedbacks; + } + + public function withSpecificFeedback( + SpecificTextFeedback $specific_feedback + ): static { + $clone = clone $this; + $clone->specific_feedbacks[$specific_feedback->getId()->toString()] = $specific_feedback; + return $clone; + } + + public function withoutSpecificFeedback( + SpecificTextFeedback $specific_feedback + ): static { + $clone = clone $this; + unset($clone->specific_feedbacks[$specific_feedback->getId()->toString()]); + return $clone; + } + + #[\Override] + public function getParticipantOutput( + Language $lng, + Refinery $refinery, + UIFactory $ui_factory, + Properties $properties, + ?Response $response, + RequiredCapabilities $required_capabilities + ): ?FeedbackView { + if ($response === null) { + return []; + } + + $generic_feedback = []; + if ($required_capabilities->isMarkingRequired()) { + $generic_feedback[] = $response->isBest() + ? $this->getRenderedFeedbackBestResponse( + $lng, + $refinery, + $ui_factory + ) : $this->getRenderedFeedbackOtherResponse( + $lng, + $refinery, + $ui_factory + ); + } + + $feedback = [ + ...$generic_feedback, + ...$this->getSpecificFeedbackParticipantOutput( + $ui_factory, + $refinery, + $properties, + $response + ) + ]; + + if ($feedback === []) { + return null; + } + + return new FeedbackView( + $ui_factory->panel()->standard( + $lng->txt('feedback'), + $feedback + ) + ); + } + + #[\Override] + public function getFeedbackClientSideEndPoint(): string + { + return 'il.questions.textFeedback.retrieve'; + } + + #[\Override] + public function getAllFeedbacksForClientSideCode( + Language $lng, + Refinery $refinery, + UIFactory $ui_factory, + UIRenderer $ui_renderer, + RequiredCapabilities $required_capabilities, + Properties $properties + ): array { + $feedbacks = []; + if ($required_capabilities->isMarkingRequired()) { + $feedbacks[self::KEY_BEST_RESPONSE] = $ui_renderer->render( + $this->getRenderedFeedbackBestResponse( + $lng, + $refinery, + $ui_factory + ) + ); + + $feedbacks[self::KEY_OTHER_RESPONSE] = $ui_renderer->render( + $this->getRenderedFeedbackOtherResponse( + $lng, + $refinery, + $ui_factory + ) + ); + } + + return [ + self::KEY_PANEL_TITEL => $lng->txt('feedback'), + self::KEY_SPECIFIC_FEEDBACK_END_POINT => $this->getSpecificFeedbackClientSideEndPoint(), + ...$feedbacks, + ...$this->getSpecificFeedbackForClientSideCode( + $lng, + $refinery, + $ui_factory, + $properties + ) + ]; + } + + public function requiresPageMigration(): bool + { + if (preg_match('/####\d+####/', $this->feedback_best_response_legacy) === 1 + || preg_match('/####\d+####/', $this->feedback_other_response_legacy) === 1) { + return true; + } + + foreach ($this->specific_feedbacks as $feedback) { + if ($feedback->requiresPageMigration()) { + return true; + } + } + + return false; + } + + public function withMigratedPageFeedbacks(): self + { + $clone = clone $this; + $feedback_best = trim($clone->feedback_best_response_legacy, '#'); + if (is_numeric($feedback_best)) { + $clone->feedback_best_response_legacy = $this->migrateGenericFeedbackPage( + (int) $feedback_best + ); + } + + $feedback_other = trim($clone->feedback_other_response_legacy, '#'); + if (is_numeric($feedback_other)) { + $clone->feedback_other_response_legacy = $this->migrateGenericFeedbackPage( + (int) $feedback_other + ); + } + + $clone->specific_feedbacks = array_map( + fn(SpecificTextFeedback $v): SpecificTextFeedback + => $v->withMigratedPageFeedbacks(), + $clone->specific_feedbacks + ); + + return $clone; + } + + public function toStorage( + PersistenceFactory $persistence_factory, + TableDefinitions $feedback_table_definitions, + TableNameBuilder $feedback_table_names_builder, + Uuid $answer_form_id, + Manipulate $manipulate + ): Manipulate { + $manipulate_with_feedback = $manipulate + ->withAdditionalStatement( + $this->buildReplaceForGenericFeedback( + $persistence_factory, + $feedback_table_definitions, + $feedback_table_names_builder, + $answer_form_id + ) + )->withAdditionalStatement( + $this->buildDeleteForSpecificFeedback( + $persistence_factory, + $feedback_table_definitions, + $feedback_table_names_builder, + $answer_form_id + ) + ); + + $specific_feedback_replace = $this->buildReplaceForSpecificFeedback( + $persistence_factory, + $feedback_table_definitions, + $feedback_table_names_builder + ); + + if ($specific_feedback_replace === null) { + return $manipulate_with_feedback; + } + + return $manipulate_with_feedback->withAdditionalStatement( + $specific_feedback_replace + ); + } + + private function buildReplaceForGenericFeedback( + PersistenceFactory $persistence_factory, + TableDefinitions $feedback_table_definitions, + TableNameBuilder $feedback_table_names_builder, + Uuid $answer_form_id + ): Replace { + return $persistence_factory->replace( + $feedback_table_definitions->getColumns( + $feedback_table_names_builder, + TableTypes::FeedbackGeneric + ), + [ + $persistence_factory->value( + FieldDefinition::T_TEXT, + $answer_form_id->toString() + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->feedback_best_response?->getRawRepresentation() ?? '' + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->feedback_best_response_legacy + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->feedback_other_response?->getRawRepresentation() ?? '' + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->feedback_other_response_legacy + ) + ] + ); + } + + private function buildDeleteForSpecificFeedback( + PersistenceFactory $persistence_factory, + TableDefinitions $feedback_table_definitions, + TableNameBuilder $feedback_table_names_builder, + Uuid $answer_form_id + ): Delete { + return $persistence_factory->delete( + $persistence_factory->table( + $feedback_table_names_builder, + TableTypes::FeedbackSpecific + ), + [ + $persistence_factory->where( + $feedback_table_definitions->getForeignKeyColumn( + $feedback_table_names_builder, + TableTypes::FeedbackSpecific + ), + new Value( + FieldDefinition::T_TEXT, + $answer_form_id->toString() + ) + ) + ] + ); + } + + private function buildReplaceForSpecificFeedback( + PersistenceFactory $persistence_factory, + TableDefinitions $feedback_table_definitions, + TableNameBuilder $feedback_table_names_builder + ): ?Replace { + return array_reduce( + $this->specific_feedbacks, + fn(?Replace $c, SpecificTextFeedback $v) => $c === null + ? $persistence_factory->replace( + $feedback_table_definitions->getColumns( + $feedback_table_names_builder, + TableTypes::FeedbackSpecific + ), + $v->toStorage($persistence_factory) + ) : $c->withAdditionalValues( + $v->toStorage($persistence_factory) + ) + ); + } + + private function getRenderedFeedbackBestResponse( + Language $lng, + Refinery $refinery, + UIFactory $ui_factory + ): Component { + if ($this->feedback_best_response === null) { + return $ui_factory->messageBox()->success( + $this->feedback_best_response_legacy === '' + ? $lng->txt('best_response_given') + : $this->feedback_best_response_legacy + ); + } + + $rendered_markdown = $refinery->string()->markdown()->toHTML() + ->transform($this->feedback_best_response->getRawRepresentation()); + + return $ui_factory->messageBox()->success( + $rendered_markdown === '' + ? $lng->txt('best_response_given') + : $rendered_markdown + ); + } + + private function getRenderedFeedbackOtherResponse( + Language $lng, + Refinery $refinery, + UIFactory $ui_factory + ): Component { + if ($this->feedback_other_response === null) { + return $ui_factory->messageBox()->info( + $this->feedback_other_response_legacy === '' + ? $lng->txt('other_response_given') + : $this->feedback_other_response_legacy + ); + } + + $rendered_markdown = $refinery->string()->markdown()->toHTML() + ->transform($this->feedback_other_response->getRawRepresentation()); + + return $ui_factory->messageBox()->info( + $rendered_markdown === '' + ? $lng->txt('other_response_given') + : $rendered_markdown + ); + } + + private function migrateGenericFeedbackPage( + int $page_id + ): string { + $feedback_page = (new \ilAssGenFeedbackPageGUI( + $page_id + ))->presentation(); + + if (strip_tags($feedback_page) === '') { + return ''; + } + + return $feedback_page; + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/TextFeedback/Types.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/TextFeedback/Types.php new file mode 100644 index 000000000000..c50fcd2e072b --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/TextFeedback/Types.php @@ -0,0 +1,36 @@ +txt($this->value); + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/TypeSpecification.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/TypeSpecification.php new file mode 100644 index 000000000000..f6da8d139020 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/TypeSpecification.php @@ -0,0 +1,26 @@ + $available_answer_form_types + */ + private readonly array $available_answer_form_types; + + /** + * @param array<\ILIAS\Questions\AnswerForm\Definition> $available_answer_form_types + */ + public function __construct( + private readonly UuidFactory $uuid_factory, + array $available_answer_form_types + ) { + $this->available_answer_form_types = array_reduce( + $available_answer_form_types, + function (array $c, Definition $v) { + $c[$this->getHashedClass($v::class)] = $v; + return $c; + }, + [] + ); + } + + /** + * + * @return array + */ + public function getAvailableDefinitions(): array + { + return array_values($this->available_answer_form_types); + } + + public function getDefinitionForClass( + string $class + ): Definition { + $definition = $this->available_answer_form_types[$this->getHashedClass($class)] ?? null; + if ($definition === null) { + throw new InvalidArgumentException('This type of answer form does not exist.'); + } + return $definition; + } + + /** + * @return array + */ + public function getAnswerFormTypesArrayForSelect( + Language $lng + ): array { + return array_reduce( + $this->available_answer_form_types, + function (array $c, Definition $v) use ($lng): array { + $c[$this->getHashedClass($v::class)] = $v->getLabel($lng); + return $c; + }, + [] + ); + } + + public function getHashedClass(string $class): string + { + return md5($class); + } + + public function getTypeDefinitionFromSelectValue( + string $value + ): Definition { + $type_definition = $this->available_answer_form_types[$value] ?? null; + if ($type_definition === null) { + throw new InvalidArgumentException('This type of answer form does not exist.'); + } + return $type_definition; + } + + public function getDefaultTypeGenericProperties( + Uuid $question_id, + Definition $type_definition, + ?Uuid $answer_form_id = null + ): TypeGenericProperties { + return new TypeGenericProperties( + $answer_form_id ?? $this->uuid_factory->uuid4(), + $question_id, + $type_definition + ); + } + + public function buildTypeGenericPropertiesFromDatabase( + array $db_values + ): TypeGenericProperties { + return new TypeGenericProperties( + $this->uuid_factory->fromString($db_values['id']), + $this->uuid_factory->fromString($db_values['question_id']), + $this->getDefinitionForClass($db_values['type']), + $db_values['available_points'], + $db_values['image_size'], + $db_values['shuffle_answer_options'] === 1, + $db_values['additional_text'], + $db_values['additional_text_legacy'] + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Migration/Migration.php b/components/ILIAS/Questions/src/AnswerForm/Migration/Migration.php new file mode 100644 index 000000000000..b85b7bd314ec --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Migration/Migration.php @@ -0,0 +1,49 @@ +db; + } + + public function getIO(): IOWrapper + { + return $this->io; + } + + public function getUuid(): Uuid + { + return $this->uuid_factory->uuid4(); + } + + public function getPersistenceFactory(): PersistenceFactory + { + return $this->persistence_factory; + } + + public function getTableNameBuilder( + ?TableSubNameSpace $table_sub_name_space + ): TableNameBuilder { + return new TableNameBuilder( + $this->component_name_space, + $table_sub_name_space + ); + } + + public function getOldQuestionId(): int + { + return $this->old_question_id; + } + + public function getAnswerFormId(): Uuid + { + return $this->answer_form_id; + } + + public function wasIliasPageEditorUsedForAdditionalTexts(): bool + { + return $this->ilias_page_editor_used_for_additional_texts; + } + + public function withAvailablePoints( + float $available_points + ): self { + $clone = clone $this; + $clone->available_points = $available_points; + return $clone; + } + + public function withImageSize( + int $image_size + ): self { + $clone = clone $this; + $clone->image_size = $image_size; + return $clone; + } + + public function withShuffleAnswerOptions( + bool $shuffle_answer_options + ): self { + $clone = clone $this; + $clone->shuffle_answer_options = $shuffle_answer_options; + return $clone; + } + + public function withAdditionalText( + string $additional_text + ): self { + $clone = clone $this; + $clone->additional_text = $additional_text; + return $clone; + } + + public function withAdditionalTextLegacy( + string $additional_text_legacy + ): self { + $clone = clone $this; + $clone->additional_text_legacy = $additional_text_legacy; + return $clone; + } + + public function withAdditionalInsert( + Insert $insert + ): self { + $clone = clone $this; + $clone->inserts[] = $insert; + return $clone; + } + + public function run(): void + { + $this->inserts[] = $this->buildCoreAnswerFormInsertStatement(); + $atom_query = $this->db->buildAtomQuery(); + + $manipulates = []; + $locked_tables = []; + foreach ($this->inserts as $statement) { + $table_to_lock = $statement->getTableToLock(); + if (!in_array($table_to_lock, $locked_tables)) { + $atom_query->addTableLock($table_to_lock); + $locked_tables[] = $table_to_lock; + } + $manipulates[] = $statement->toManipulateString($this->db); + } + $atom_query->addQueryCallable( + function (\ilDBInterface $db) use ($manipulates): void { + foreach ($manipulates as $manipulate) { + $db->manipulate($manipulate); + } + } + ); + $atom_query->run(); + } + + private function buildCoreAnswerFormInsertStatement(): Insert + { + return $this->persistence_factory->insert( + $this->answer_form_generic_table_definitions->getColumns( + $this->getTableNameBuilder(null), + AnswerFormGenericTableTypes::AnswerForms + ), + [ + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $this->answer_form_id->toString() + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $this->definition_class + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $this->new_question_id->toString() + ), + $this->persistence_factory->value( + FieldDefinition::T_FLOAT, + $this->available_points + ), + $this->persistence_factory->value( + FieldDefinition::T_INTEGER, + $this->image_size + ), + $this->persistence_factory->value( + FieldDefinition::T_INTEGER, + $this->shuffle_answer_options === null + ? null + : ($this->shuffle_answer_options ? 1 : 0) + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $this->additional_text + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $this->additional_text_legacy + ) + ] + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Migration/MigrationPurifier.php b/components/ILIAS/Questions/src/AnswerForm/Migration/MigrationPurifier.php new file mode 100755 index 000000000000..caca7c763c08 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Migration/MigrationPurifier.php @@ -0,0 +1,117 @@ +set('HTML.DefinitionID', 'migration'); + $config->set('HTML.DefinitionRev', 2); + $config->set('Cache.SerializerPath', sys_get_temp_dir()); + $config->set('HTML.Doctype', 'XHTML 1.0 Strict'); + $config->set('HTML.AllowedElements', $this->getAllowedElements()); + $config->set('HTML.ForbiddenAttributes', 'div@style'); + $config->autoFinalize = false; + $config->set( + 'URI.AllowedSchemes', + [ + ...$config->get('URI.AllowedSchemes'), + 'data' => true + ] + ); + $config->autoFinalize = true; + if (($def = $config->maybeGetRawHTMLDefinition()) !== null) { + $def->addAttribute('img', 'data-id', 'Number'); + $def->addAttribute('a', 'target', 'Enum#_blank,_self,_target,_top'); + } + + return $config; + } + + private function getAllowedElements(): array + { + return $this->removeUnsupportedElements( + $this->makeElementListTinyMceCompliant( + $this->getElementsUsedForAdvancedEditing() + ) + ); + } + + private function getElementsUsedForAdvancedEditing(): array + { + $tags = $this->db->fetchObject( + $this->db->query( + 'SELECT value FROM settings' . PHP_EOL + . 'WHERE module = "advanced_editing"' . PHP_EOL + . 'AND keyword = "advanced_editing_used_html_tags_assessment"' + ) + )?->value ?? ''; + + if ($tags === '') { + return self::DEFAULT_TAGS; + } + + return unserialize($tags, ['allowed_classes' => false]); + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Migration/SanitizeLegacyText.php b/components/ILIAS/Questions/src/AnswerForm/Migration/SanitizeLegacyText.php new file mode 100644 index 000000000000..ea64832261d8 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Migration/SanitizeLegacyText.php @@ -0,0 +1,60 @@ +purifiery === null) { + $this->purifiery = new MigrationPurifier($db); + $this->rte_used = $this->fetchIsRteUsedFromDb($db); + } + $cleaned_text = $this->purifiery->purify($text); + + if ($ilias_page_editor_text || !$this->rte_used) { + $cleaned_text = nl2br($cleaned_text); + } + + return \ilLegacyFormElementsUtil::prepareTextareaOutput( + $cleaned_text, + true + ); + } + + private function fetchIsRteUsedFromDb( + \ilDBInterface $db + ): bool { + return $db->fetchObject( + $db->query( + 'SELECT value FROM settings' . PHP_EOL + . 'WHERE module = "advanced_editing"' . PHP_EOL + . 'AND keyword = "advanced_editing_javascript_editor"' + ) + )?->value === 'tinymce'; + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Persistence/AnswerFormGenericTableDefinitions.php b/components/ILIAS/Questions/src/AnswerForm/Persistence/AnswerFormGenericTableDefinitions.php new file mode 100644 index 000000000000..94e0fa025042 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Persistence/AnswerFormGenericTableDefinitions.php @@ -0,0 +1,128 @@ + $this->persistence_factory->column( + $this->persistence_factory->table( + $table_names_builder, + $table_type + ), + $v + ), + array_values( + array_filter( + self::ANSWER_FORM_TABLE_COLUMNS, + fn(string $v) => !in_array($v, $columns_to_skip) + ) + ) + ); + } + + public function getIdColumn( + TableNameBuilder $table_names_builder, + TableTypes $table_type + ): Column { + return $this->persistence_factory->column( + $this->persistence_factory->table( + $table_names_builder, + $table_type + ), + self::ANSWER_FORM_TABLE_ID_COLUMN + ); + } + + public function getForeignKeyColumn( + TableNameBuilder $table_names_builder, + TableTypes $table_type + ): Column { + return $this->persistence_factory->column( + $this->persistence_factory->table( + $table_names_builder, + $table_type + ), + self::ANSWER_FORM_TABLE_FOREIGN_KEY_COLUMN + ); + } + + public function completeQuestionQuery( + Query $query, + ?Column $base_table_id_column + ): Query { + $table_names_builder = $query->getTableNameBuilder(null); + + return $query->withAdditionalSelect( + $this->persistence_factory->select( + $this->getColumns( + $table_names_builder, + AnswerFormGenericTableTypes::AnswerForms + ) + ) + )->withAdditionalJoin( + $this->persistence_factory->join( + $base_table_id_column, + $this->getForeignKeyColumn( + $table_names_builder, + AnswerFormGenericTableTypes::AnswerForms + ), + JoinType::Left + ) + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Persistence/AnswerFormGenericTableTypes.php b/components/ILIAS/Questions/src/AnswerForm/Persistence/AnswerFormGenericTableTypes.php new file mode 100644 index 000000000000..0e713c3128af --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Persistence/AnswerFormGenericTableTypes.php @@ -0,0 +1,28 @@ +answer_form_id; + } + + public function getQuestionId(): Uuid + { + return $this->question_id; + } + + public function getDefinition(): ?Definition + { + return $this->definition; + } + + public function getAvailablePoints(): ?float + { + return $this->available_points; + } + + public function getImageSize(): ?int + { + return $this->image_size; + } + + public function getShuffleAnswerOptions(): ?bool + { + return $this->shuffle_answer_options; + } + + public function getAdditionalText(): string + { + return $this->additional_text; + } + + public function getAdditionalTextLegacy(): string + { + return $this->additional_text_legacy; + } + + public function toStorage( + PersistenceFactory $persistence_factory, + AnswerFormGenericTableDefinitions $answer_form_generic_definitions, + ManipulationType $manipulation_type, + Manipulate $manipulate + ): Manipulate { + if ($this->definition === null) { + throw new \UnexpectedValueException( + 'You cannot save a Answer Form without a Type!' + ); + } + + $table_names_builder = $manipulate->getTableNameBuilder(null); + + return $manipulate->withAdditionalStatement( + $manipulation_type === ManipulationType::Create + ? $this->buildInsertStatement( + $persistence_factory, + $answer_form_generic_definitions, + $table_names_builder + ) : $this->buildUpdateStatement( + $persistence_factory, + $answer_form_generic_definitions, + $table_names_builder + ) + ); + } + + public function toDelete( + PersistenceFactory $persistence_factory, + AnswerFormGenericTableDefinitions $answer_form_generic_table_definitions, + Manipulate $manipulate + ): Manipulate { + $table_names_builder = $manipulate->getTableNameBuilder( + $answer_form_generic_table_definitions->getTableSubNameSpace() + ); + $table_type = AnswerFormGenericTableTypes::AnswerForms; + + return $manipulate->withAdditionalStatement( + $persistence_factory->delete( + $persistence_factory->table( + $table_names_builder, + $table_type + ), + [ + $persistence_factory->where( + $answer_form_generic_table_definitions->getIdColumn( + $table_names_builder, + $table_type + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->answer_form_id->toString() + ) + ) + ] + ) + ); + } + + private function buildInsertStatement( + PersistenceFactory $persistence_factory, + AnswerFormGenericTableDefinitions $answer_form_generic_table_definitions, + TableNameBuilder $table_names_builder + ): Insert { + return $persistence_factory->insert( + $answer_form_generic_table_definitions->getColumns( + $table_names_builder, + AnswerFormGenericTableTypes::AnswerForms + ), + [ + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->answer_form_id->toString() + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->definition::class + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->question_id->toString() + ), + $persistence_factory->value( + FieldDefinition::T_FLOAT, + $this->available_points + ), + $persistence_factory->value( + FieldDefinition::T_INTEGER, + $this->image_size + ), + $persistence_factory->value( + FieldDefinition::T_INTEGER, + $this->getShuffleAnswerOptionsForStorage() + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->additional_text + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->additional_text_legacy + ) + ] + ); + } + + private function buildUpdateStatement( + PersistenceFactory $persistence_factory, + AnswerFormGenericTableDefinitions $answer_form_generic_table_definitions, + TableNameBuilder $table_names_builder + ): Update { + $table_type = AnswerFormGenericTableTypes::AnswerForms; + + return $persistence_factory->update( + $answer_form_generic_table_definitions->getColumns( + $table_names_builder, + $table_type, + [ + 'id', + 'type', + 'question_id', + 'additional_text_legacy' + ] + ), + [ + $persistence_factory->value( + FieldDefinition::T_FLOAT, + $this->available_points + ), + $persistence_factory->value( + FieldDefinition::T_INTEGER, + $this->image_size + ), + $persistence_factory->value( + FieldDefinition::T_INTEGER, + $this->getShuffleAnswerOptionsForStorage() + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->additional_text + ) + + ], + [ + $persistence_factory->where( + $answer_form_generic_table_definitions->getIdColumn( + $table_names_builder, + $table_type + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->answer_form_id->toString() + ) + ) + ] + ); + } + + private function getShuffleAnswerOptionsForStorage(): ?int + { + if ($this->shuffle_answer_options === null) { + return null; + } + + return $this->shuffle_answer_options ? 1 : 0; + } +} diff --git a/components/ILIAS/Questions/src/AnswerForm/Views/Edit.php b/components/ILIAS/Questions/src/AnswerForm/Views/Edit.php new file mode 100644 index 000000000000..b54a01818687 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerForm/Views/Edit.php @@ -0,0 +1,48 @@ +|Component + */ + public function show( + GlobalTemplate $global_template, + Language $lng, + Refinery $refinery, + UIFactory $ui_factory, + RequiredCapabilities $required_capabilities, + ViewConfiguration $view_configuration, + Properties $properties, + ?AdditionalAttemptData $attempt_data, + ?Response $response_data + ): array|Component; + + public function retrieveResponse( + Uuid $response_id, + Properties $properties, + RequestWrapper $post_wrapper + ): Response; +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Capabilities/AsyncView.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Capabilities/AsyncView.php new file mode 100644 index 000000000000..c40fde5b4da4 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Capabilities/AsyncView.php @@ -0,0 +1,84 @@ +mustache_engine->render( + $properties->getClozeTextForPresentation(), + $properties->getGaps()->getPlaceholderArray( + $lng, + $view_configuration, + $additional_attempt_data, + $response_data + ) + ); + } + + #[\Override] + public function retrieveResponse( + Uuid $response_id, + Properties $properties, + RequestWrapper $post_wrapper + ): Response { + return $this->response_factory->fromPost( + $response_id, + $properties, + $post_wrapper + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Capabilities/DefaultView.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Capabilities/DefaultView.php new file mode 100644 index 000000000000..f046c904b5c3 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Capabilities/DefaultView.php @@ -0,0 +1,84 @@ +legacy()->content( + $this->mustache_engine->render( + $properties->getClozeTextForPresentation(), + $properties->getGaps()->getPlaceholderArray( + $lng, + $view_configuration, + $additional_attempt_data, + $response_data + ) + ) + ); + } + + #[\Override] + public function retrieveResponse( + Uuid $response_id, + Properties $properties, + RequestWrapper $post_wrapper + ): Response { + return $this->response_factory->fromPost( + $response_id, + $properties, + $post_wrapper + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Capabilities/MarkingAllowingPartialPoints.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Capabilities/MarkingAllowingPartialPoints.php new file mode 100644 index 000000000000..ed3bf963c056 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Capabilities/MarkingAllowingPartialPoints.php @@ -0,0 +1,79 @@ +calculateAwardedPoints($properties); + } + + #[\Override] + public function getBestResponse( + Properties $properties + ): Response { + return $this->response_factory->getBestResponse($properties); + } + + #[\Override] + public function getEditFormInputsBuilder( + Environment $environment, + ): InputsBuilderSession { + return $environment->getPresentationFactory()->getSessionBasedInputsBuilder( + $environment->getRefinery()->custom()->transformation( + function (?string $carry) use ($environment): Section { + $properties_from_carry = $this->properties_factory + ->fromCarry( + $environment->getAnswerFormProperties(), + $carry + ); + return $properties_from_carry->getGaps() + ->buildPointInputs( + $environment->getLanguage(), + $environment->getUIFactory(), + $properties_from_carry, + $environment->isInCreationContext(), + $environment->getTableRowIds() + ); + } + ) + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Capabilities/TextFeedback.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Capabilities/TextFeedback.php new file mode 100644 index 000000000000..d87840761547 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Capabilities/TextFeedback.php @@ -0,0 +1,303 @@ +uuid_factory, + $this->text_factory, + new TextFeedbackOverviewDataRetrieval( + $environment->getLanguage(), + $environment->getRefinery(), + $this->uuid_factory, + $environment->getAnswerFormProperties(), + $this + ) + ); + } + + #[\Override] + public function onAnswerFormClone( + UuidFactory $uuid_factory, + Properties $old_answer_form_properties, + Properties $new_answer_form_properties + ): static { + /** @var Gaps $old_gaps */ + $old_gaps = $old_answer_form_properties->getGaps(); + /** @var Gaps $new_gaps */ + $new_gaps = $new_answer_form_properties->getGaps(); + + return array_reduce( + $this->getSpecificFeedbacks(), + function ( + TextFeedback $c, + SpecificTextFeedback $v + ) use ($uuid_factory, $old_gaps, $new_gaps): self { + $old_gap = $old_gaps->getGapById($v->getParentId()); + try { + $answer_option_position = $old_gap + ->getAnswerOptions() + ->getAnswerOptionById( + $uuid_factory->fromString($v->getCondition()) + )->getPosition(); + $new_answer_option_id = $new_gaps + ->getGapByPosition($old_gap->getPosition()) + ->getAnswerOptions() + ->getAnswerOptionForPositionOrNew($answer_option_position) + ->getAnswerOptionId(); + + return $c->withoutSpecificFeedback($v) + ->withSpecificFeedback( + $v->withCondition( + $new_answer_option_id->toString() + ) + ); + } catch (InvalidUuidStringException $e) { + return $c; + } + }, + clone $this + ); + } + + #[\Override] + public function onAnswerFormUpdate( + Properties $answer_form_properties + ): static { + /** @var Gaps $gaps */ + $gaps = $answer_form_properties->getGaps(); + + return array_reduce( + $this->getSpecificFeedbacks(), + function ( + TextFeedback $c, + SpecificTextFeedback $v + ) use ($gaps): self { + $gap = $gaps->getGapById($v->getParentId()); + if ($gap === null + || !$gap->getType()->isValidFeedbackCondition( + $this->uuid_factory, + $gap, + $v->getCondition() + ) + ) { + return $c->withoutSpecificFeedback($v); + } + + return $c; + }, + clone $this + ); + } + + #[\Override] + public function specificFeedbackInputsHaveLegacyTexts(): bool + { + return false; + } + + #[\Override] + protected function getSpecificFeedbackParticipantOutput( + UIFactory $ui_factory, + Refinery $refinery, + Properties $properties, + ?Response $response + ): array { + $specific_feedbacks = $this->getSpecificFeedbacks(); + if ($response === null || $specific_feedbacks === []) { + return []; + } + + /** @var Gaps $gaps */ + $gaps = $properties->getGaps(); + + return array_reduce( + $this->orderFeedbacksByAnswerInputId($specific_feedbacks), + function ( + array $c, + array $v + ) use ( + $ui_factory, + $refinery, + $gaps, + $response + ): array { + $gap_id = $v[0]->getParentId(); + + /** @var ?AnswerInputResponse $answer_for_gap */ + $answer_for_gap = $response?->getResponseForInput($gap_id); + + if ($answer_for_gap === null) { + return $this->addFeedbackForNoSelectionToParticipantOutput( + $ui_factory, + $refinery, + $v, + $c + ); + } + + $gap = $gaps->getGapById($gap_id); + $output = $gap->getType()->getSpecificFeedbackParticipantOutput( + $ui_factory, + $gap, + $v, + $answer_for_gap + ); + + if ($output !== null) { + $c[] = $output; + } + + return $c; + }, + [] + ); + } + + #[\Override] + protected function getSpecificFeedbackClientSideEndPoint(): string + { + return 'il.questions.cloze.specificTextFeedback'; + } + + #[\Override] + protected function getSpecificFeedbackForClientSideCode( + Language $lng, + Refinery $refinery, + UIFactory $ui_factory, + Properties $properties + ): array { + $specific_feedbacks = $this->getSpecificFeedbacks(); + if ($specific_feedbacks === []) { + return []; + } + + return array_map( + fn(array $v): array => array_reduce( + $v, + function (array $c, SpecificTextFeedback $v) use ($refinery): array { + $c[$v->getCondition()] = $v->getFeedbackTextForPresentation( + $refinery + ); + return $c; + }, + [] + ), + $this->orderFeedbacksByAnswerInputId($specific_feedbacks) + ); + } + + public function buildSpecificFeedbackOverviewTableArray(): array + { + return array_reduce( + $this->getSpecificFeedbacks(), + function (array $c, SpecificTextFeedback $v): array { + $gap_id = $v->getParentId()->toString(); + $key = md5($v->getFeedbackText()->getRawRepresentation()); + if (!array_key_exists($gap_id, $c)) { + $c[$gap_id] = []; + } + if (!array_key_exists($key, $c[$gap_id])) { + $c[$gap_id][$key] = []; + } + + $c[$gap_id][$key][] = $v; + return $c; + }, + [] + ); + } + + private function orderFeedbacksByAnswerInputId( + array $specific_feedbacks + ): array { + return array_reduce( + $specific_feedbacks, + function (array $c, SpecificTextFeedback $v): array { + $parent_id = $v->getParentId()->toString(); + if (!array_key_exists($parent_id, $c)) { + $c[$parent_id] = []; + } + + $c[$parent_id][] = $v; + return $c; + }, + [] + ); + } + + private function addFeedbackForNoSelectionToParticipantOutput( + UIFactory $ui_factory, + Refinery $refinery, + array $specific_feedbacks, + array $participant_output + ): array { + $feedback_nothing_selected = array_filter( + $specific_feedbacks, + fn(SpecificTextFeedback $v): bool => TextFeedbackTypes::tryFrom($v->getCondition()) === TextFeedbackTypes::NoResponse + ); + + if ($feedback_nothing_selected !== []) { + $participant_output[] = $ui_factory->legacy()->content( + $feedback_nothing_selected[0]->getFeedbackTextForPresentation( + $refinery + ) + ); + } + + return $participant_output; + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Capabilities/TextFeedbackOverviewDataRetrieval.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Capabilities/TextFeedbackOverviewDataRetrieval.php new file mode 100644 index 000000000000..af35a05e3964 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Capabilities/TextFeedbackOverviewDataRetrieval.php @@ -0,0 +1,135 @@ + + * } + */ + private readonly array $feedback; + + public function __construct( + private readonly Language $lng, + private readonly Refinery $refinery, + private readonly UuidFactory $uuid_factory, + private readonly Properties $answer_form_properties, + TextFeedback $feedback + ) { + $this->feedback = $feedback->buildSpecificFeedbackOverviewTableArray(); + } + + #[\Override] + public function getRows( + DataRowBuilder $row_builder, + array $visible_column_ids, + Range $range, + Order $order, + mixed $additional_viewcontrol_data, + mixed $filter_data, + mixed $additional_parameters + ): \Generator { + foreach ($this->feedback as $gap_feedbacks) { + yield from $this->buildColumnValues( + $row_builder, + $gap_feedbacks + ); + } + } + + #[\Override] + public function getTotalRowCount( + mixed $additional_viewcontrol_data, + mixed $filter_data, + mixed $additional_parameters + ): ?int { + return count($this->feedback); + } + + public function getSpecificFeedbacksForRowId( + string $table_row_id + ): array { + $ids = explode('_', $table_row_id); + $feedbacks_for_gap = array_reduce( + $this->feedback[array_shift($ids)], + fn(array $c, array $v): array => [...$c, ...$v], + [] + ); + return array_filter( + $feedbacks_for_gap, + fn(SpecificTextFeedback $v): bool => in_array($v->getId(), $ids) + ); + } + + private function buildColumnValues( + DataRowBuilder $row_builder, + array $gap_feedbacks + ): \Generator { + foreach ($gap_feedbacks as $feedbacks) { + yield $this->buildTableRow( + $row_builder, + $feedbacks + ); + } + } + + private function buildTableRow( + DataRowBuilder $row_builder, + array $feedbacks + ): DataRow { + $gap_id = $feedbacks[0]->getParentId(); + $gap = $this->answer_form_properties->getGaps()->getGapById($gap_id); + + $keys = [$gap_id->toString()]; + $answer_options = []; + foreach ($feedbacks as $feedback) { + $keys[] = $feedback->getId()->toString(); + $answer_options[] = $gap->getType()->getLabelForValue( + $this->uuid_factory, + $gap, + $feedback->getCondition() + ); + } + + return $row_builder->buildDataRow( + implode('_', $keys), + [ + 'gap' => $gap->buildShortenedGapRepresentation(), + 'answer_options' => implode('
', $answer_options), + 'feedback' => $feedbacks[0] + ->getFeedbackTextForPresentation($this->refinery) + ] + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Capabilities/TextFeedbackOverviewTable.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Capabilities/TextFeedbackOverviewTable.php new file mode 100644 index 000000000000..da5478a2b0da --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Capabilities/TextFeedbackOverviewTable.php @@ -0,0 +1,499 @@ +getUIFactory(); + $lng = $environment->getLanguage(); + + return $ui_factory->modal()->roundtrip( + $lng->txt('create_feedback'), + null, + [ + 'gap' => $environment->getAnswerFormProperties()->getGaps() + ->buildGapsSelect( + $lng->txt('select_gap'), + $ui_factory->input()->field() + )->withAdditionalTransformation( + $environment->getRefinery()->custom()->transformation( + fn(string $v): Uuid => $this->uuid_factory->fromString($v) + ) + )->withRequired(true) + ], + $environment->withSubActionParameter( + self::SUB_ACTION_ENTER_FEEDBACK + )->getUrlBuilder()->buildURI()->__toString() + )->withSubmitLabel($lng->txt('next')); + } + + #[\Override] + public function getTable( + Environment $environment, + TextFeedback $feedback + ): DataTable { + return $environment->getUIFactory()->table()->data( + $this->data_retrieval, + '', + $this->getColumns( + $environment->getUIFactory()->table()->column(), + $environment->getLanguage() + ) + )->withActions( + $this->getActions( + $environment + ) + )->withRequest($environment->getHttpServices()->request()); + } + + #[\Override] + public function doAction( + Environment $environment, + TextFeedback $feedback, + string $action + ): Async|RoundTripModal|TextFeedback { + return match($action) { + self::SUB_ACTION_ENTER_FEEDBACK => $this->processSelectGapModal( + $environment, + $feedback + ), + self::SUB_ACTION_EDIT_FEEDBACK => $this->editFeedback( + $environment->withPreservedTableRowIdsParameter(), + $feedback + ), + self::SUB_ACTION_SAVE_FEEDBACK => $this->processEnterFeedbackModal( + $environment, + $feedback + ), + self::SUB_ACTION_CONFIRM_DELETE_FEEDBACK => $this->confirmDeleteFeedback( + $environment + ), + self::SUB_ACTION_DELETE_FEEDBACK => $this->deleteFeedback( + $environment, + $feedback + ) + }; + } + + private function processSelectGapModal( + Environment $environment, + TextFeedback $feedback + ): RoundTripModal { + $create_modal = $this->getCreateModal($environment) + ->withRequest($environment->getHttpServices()->request()); + $data = $create_modal->getData(); + if ($data === null) { + return $create_modal; + } + + $inputs_builder = $this->buildEnterFeedbackInputBuilder( + $environment, + $feedback, + $data['gap'] + ); + + $enter_feedback_modal = $this->buildEnterFeedbackModal( + $environment, + $inputs_builder + ); + + $inputs_builder + ->withCarry( + $data['gap']->toString() + )->persistCarry(); + + return $enter_feedback_modal; + } + + private function buildEnterFeedbackModal( + Environment $environment, + InputsBuilderSession $inputs_builder + ): RoundTripModal { + return $environment->getUIFactory()->modal()->roundtrip( + $environment->getLanguage()->txt('edit_feedback'), + null, + [ + 'feedback' => $inputs_builder->getInputs() + ], + $environment->withSubActionParameter( + self::SUB_ACTION_SAVE_FEEDBACK + )->getUrlBuilder()->buildURI()->__toString() + ); + } + + private function processEnterFeedbackModal( + Environment $environment, + TextFeedback $feedback + ): RoundTripModal|TextFeedback { + $specific_feedbacks = $this->getSpecifcFeedbacksFromQuery( + $environment + ); + + $gap_id = null; + if (isset($specific_feedbacks[0])) { + $gap_id = $specific_feedbacks[0]->getParentId(); + $environment = $environment->withPreservedTableRowIdsParameter(); + } + + $inputs_builder = $this->buildEnterFeedbackInputBuilder( + $environment, + $feedback, + $gap_id, + array_map( + fn(SpecificTextFeedback $v): string => $v->getCondition(), + $specific_feedbacks + ) + ); + + $modal = $this->buildEnterFeedbackModal( + $environment, + $inputs_builder + )->withRequest($environment->getHttpServices()->request()); + + $data = $modal->getData(); + if ($data === null) { + $inputs_builder->persistCarry(); + return $modal; + } + + return $data['feedback']; + } + + private function editFeedback( + Environment $environment, + TextFeedback $feedback + ): Async|TextFeedback { + $specific_feedbacks = $this->getSpecifcFeedbacksFromQuery( + $environment + ); + + return $environment->getPresentationFactory()->getAsync( + $this->buildEnterFeedbackModal( + $environment, + $this->buildEnterFeedbackInputBuilder( + $environment, + $feedback, + $specific_feedbacks[0]->getParentId(), + array_map( + fn(SpecificTextFeedback $v): string => $v->getCondition(), + $specific_feedbacks + ), + $specific_feedbacks[0]->getFeedbackText()->getRawRepresentation() + ) + ) + ); + } + + private function confirmDeleteFeedback( + Environment $environment + ): Async { + $pf = $environment->getPresentationFactory(); + $uf = $environment->getUIFactory(); + $lng = $environment->getLanguage(); + $selected_feedbacks = $this->data_retrieval->getSpecificFeedbacksForRowId( + $environment->getTableRowIds()[0] + ); + + if ($selected_feedbacks === []) { + return $pf->getAsync( + $uf->messageBox()->failure( + $lng->txt('ui_field_option_filter_no_selection') + ) + ); + } + + return $pf->getAsync( + $uf->modal()->interruptive( + $lng->txt('confirm'), + $lng->txt('confirm_delete_feedback'), + $environment + ->withSubActionParameter(self::SUB_ACTION_DELETE_FEEDBACK) + ->getUrlBuilder() + ->buildURI() + ->__toString() + )->withAffectedItems([ + $uf->modal()->interruptiveItem()->standard( + $environment->getTableRowIds()[0], + $selected_feedbacks[0]->getFeedbackText() + ?->getRawRepresentation() ?? '' + ) + ]) + ); + } + + private function deleteFeedback( + Environment $environment, + TextFeedback $feedback + ): RoundTripModal|TextFeedback { + $uf = $environment->getUIFactory(); + $lng = $environment->getLanguage(); + $selected_feedbacks = $environment->getHttpServices()->wrapper()->post() + ->retrieve( + 'interruptive_items', + $environment->getRefinery()->custom()->transformation( + fn(array $vs): array => $this->data_retrieval + ->getSpecificFeedbacksForRowId($vs[0]) + ) + ); + + if ($selected_feedbacks === []) { + return $uf->modal()->roundtrip( + $lng->txt('error'), + $uf->messageBox()->failure( + $lng->txt('ui_field_option_filter_no_selection') + ) + ); + } + + return array_reduce( + $selected_feedbacks, + fn(TextFeedback $c, SpecificTextFeedback $v): TextFeedback => $c->withoutSpecificFeedback($v), + $feedback + ); + } + + private function buildEnterFeedbackInputBuilder( + Environment $environment, + TextFeedback $feedback, + ?Uuid $selected_gap = null, + array $selected_conditions = [], + string $feedback_text = '' + ): InputsBuilderSession { + return $environment->getPresentationFactory()->getSessionBasedInputsBuilder( + $environment->getRefinery()->custom()->transformation( + function ( + ?string $v + ) use ( + $environment, + $feedback, + $selected_gap, + $selected_conditions, + $feedback_text + ): Section { + $gap_id = $selected_gap + ?? $this->uuid_factory->fromString($v); + + return $this->buildSpecificFeedbackSection( + $environment->getUIFactory(), + $environment->getRefinery(), + $environment->getLanguage(), + $environment->getAnswerFormProperties() + ->getGaps() + ->getGapById($gap_id), + $environment->isMarkingRequired(), + $feedback->getSpecificFeedbacks(), + $selected_conditions, + $feedback_text + )->withAdditionalTransformation( + $this->buildSpecificFeedbackTransformation( + $environment->getRefinery(), + $feedback, + $environment->getAnswerFormId(), + $gap_id, + $selected_conditions + ) + ); + } + ) + ); + } + + private function buildSpecificFeedbackSection( + UIFactory $ui_factory, + Refinery $refinery, + Language $lng, + Gap $gap, + bool $is_marking_required, + array $existing_specific_feedbacks, + array $selected_conditions, + string $feedback_text + ): Section { + $answer_options = $gap->getType()->getFeedbackSelectValues( + $gap, + $is_marking_required + ); + + foreach ($existing_specific_feedbacks as $feedback) { + if (!in_array($feedback->getCondition(), $selected_conditions)) { + unset($answer_options[$feedback->getCondition()]); + } + } + + return $ui_factory->input()->field()->section( + [ + 'answer_options' => $ui_factory->input()->field()->multiSelect( + $lng->txt('answer_options'), + $answer_options + )->withHasOptionFilter(true) + ->withRequired(true) + ->withValue($selected_conditions), + 'feedback' => $ui_factory->input()->field()->markdown( + new \ilUIMarkdownPreviewGUI(), + $lng->txt('feedback') + )->withAdditionalTransformation( + $refinery->custom()->transformation( + fn(string $v): Markdown => $this->text_factory->markdown($v) + ) + )->withRequired(true) + ->withValue($feedback_text) + ], + $lng->txt('feedback') + ); + } + + private function buildSpecificFeedbackTransformation( + Refinery $refinery, + TextFeedback $feedback, + Uuid $answer_form_id, + Uuid $gap_id, + array $selected_conditions + ): CustomTransformation { + return $refinery->custom()->transformation( + function ( + array $vs + ) use ( + $feedback, + $answer_form_id, + $gap_id, + $selected_conditions + ): TextFeedback { + $feedback_with_removed_specific_feedbacks = array_reduce( + array_diff($selected_conditions, $vs['answer_options']), + fn(TextFeedback $c, string $v): TextFeedback => $c->withoutSpecificFeedback( + $c->getSpecificFeedbackForConditionOrNew( + $this->uuid_factory, + $answer_form_id, + $gap_id, + $v + ) + ), + $feedback + ); + + return array_reduce( + $vs['answer_options'], + fn(TextFeedback $c, string $v): TextFeedback => $c->withSpecificFeedback( + $c->getSpecificFeedbackForConditionOrNew( + $this->uuid_factory, + $answer_form_id, + $gap_id, + $v + )->withFeedbackText($vs['feedback']) + ), + $feedback_with_removed_specific_feedbacks + ); + } + ); + } + + private function getSpecifcFeedbacksFromQuery( + Environment $environment + ): array { + $selected_table_row_ids = $environment->getTableRowIds(); + + if ($selected_table_row_ids === []) { + return []; + } + + return $this->data_retrieval->getSpecificFeedbacksForRowId( + $selected_table_row_ids[0] + ); + } + + private function getColumns( + ColumnFactory $column_factory, + Language $lng + ): array { + return [ + 'gap' => $column_factory->text( + $lng->txt('gap') + )->withIsSortable(false), + 'answer_options' => $column_factory->text( + $lng->txt('answer_options') + )->withIsSortable(false), + 'feedback' => $column_factory->text( + $lng->txt('feedback') + )->withIsSortable(false) + ]; + } + + private function getActions( + Environment $environment + ): array { + $af = $environment->getUIFactory()->table()->action(); + $lng = $environment->getLanguage(); + + return [ + 'edit' => $af->single( + $lng->txt('edit'), + $environment->withSubActionParameter( + self::SUB_ACTION_EDIT_FEEDBACK + )->getUrlBuilder(), + $environment->getTableRowIdToken() + )->withAsync(true), + 'delete' => $af->single( + $lng->txt('delete'), + $environment->withSubActionParameter( + self::SUB_ACTION_CONFIRM_DELETE_FEEDBACK + )->getUrlBuilder(), + $environment->getTableRowIdToken() + )->withAsync(true), + ]; + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Definition.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Definition.php new file mode 100644 index 000000000000..c9460bd3b1dd --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Definition.php @@ -0,0 +1,142 @@ + $available_capabilities + */ + private readonly array $available_capabilities; + + /** + * @param list $available_capabilities + */ + public function __construct( + private readonly TableDefinitions $table_definitions, + array $available_capabilities, + private readonly PropertiesFactory $properties_factory, + private readonly ResponseFactory $response_factory, + private readonly Edit $edit_view + ) { + $this->available_capabilities = array_reduce( + $available_capabilities, + function (array $c, TypeSpecification $v): array { + $c[$v::getCapabilityIdentifier()] = $v; + return $c; + }, + [] + ); + } + + #[\Override] + public function getLabel( + Language $lng + ): string { + return $lng->txt('cloze'); + } + + #[\Override] + public function buildProperties( + TypeGenericProperties $type_generic_data, + ?Query $query + ): Properties { + return $this->properties_factory->fromData( + $type_generic_data, + $query + ); + } + + #[\Override] + public function buildResponse( + Uuid $response_id, + AnswerFormProperties $answer_form_properties, + ?Query $query + ): Response { + return $this->response_factory->fromQuery( + $response_id, + $answer_form_properties, + $query + ); + } + + #[\Override] + public function buildResponseFromPreviewData( + Uuid $response_id, + AnswerFormProperties $answer_form_properties, + array $preview_data + ): Response { + return $this->response_factory->fromPreviewData( + $response_id, + $answer_form_properties, + $preview_data + ); + } + + #[\Override] + public function getTableDefinitions(): TableDefinitions + { + return $this->table_definitions; + } + + #[\Override] + public function hasCapability( + string $capability_identifier + ): bool { + return array_key_exists($capability_identifier, $this->available_capabilities); + } + + #[\Override] + public function getCapability( + string $capability_identifier + ): ?TypeSpecification { + return $this->available_capabilities[$capability_identifier] ?? null; + } + + #[\Override] + public function getEditView(): Edit + { + return $this->edit_view; + } + + #[\Override] + public function initializeAttemptData( + Attempt $attempt, + AnswerFormProperties $answer_form_properties + ): Attempt { + return $answer_form_properties->getGaps()->initializeAttemptData($attempt); + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Layout/OverviewTable.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Layout/OverviewTable.php new file mode 100644 index 000000000000..731f85f2b8ea --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Layout/OverviewTable.php @@ -0,0 +1,137 @@ +environment->getUIFactory()->table()->data( + $this, + $this->environment->getLanguage()->txt('gaps'), + $this->getColums() + )->withActions($this->getActions()) + ->withRequest( + $this->environment->getHttpServices()->request() + ); + } + + #[\Override] + public function getRows( + DataRowBuilder $row_builder, + array $visible_column_ids, + Range $range, + Order $order, + mixed $additional_viewcontrol_data, + mixed $filter_data, + mixed $additional_parameters + ): \Generator { + yield from $this->environment->getAnswerFormProperties()->getGaps() + ->toTableRows( + $row_builder, + $this->environment->getLanguage(), + $this->environment->isMarkingRequired() + ); + } + + #[\Override] + public function getTotalRowCount( + mixed $additional_viewcontrol_data, + mixed $filter_data, + mixed $additional_parameters + ): ?int { + return $this->environment->getAnswerFormProperties() + ->getGaps()->getNumberOfGaps(); + } + + private function getColums(): array + { + $f = $this->environment->getUIFactory()->table()->column(); + + $columns = [ + 'gap' => $f->text( + $this->environment->getLanguage()->txt('title') + )->withIsSortable(false), + 'type' => $f->text( + $this->environment->getLanguage()->txt('gap_type') + )->withIsSortable(false) + ]; + + if ($this->environment->isMarkingRequired()) { + $columns = [ + ...$columns, + 'answers_options_awarding_points' => $f->text( + $this->environment->getLanguage()->txt('answer_options_awarding_points') + )->withIsSortable(false), + 'available_points' => $f->number( + $this->environment->getLanguage()->txt('available_points') + )->withDecimals(4) + ->withIsSortable(false) + ]; + } + + return $columns; + } + + private function getActions(): array + { + $taf = $this->environment->getUIFactory()->table()->action(); + return array_reduce( + $this->environment->getAnswerFormTableActionsForRequiredCapabilities(), + function (array $c, TableAction $v): array { + $c[] = $v; + return $c; + }, + [ + 'edit_gaps' => $taf->standard( + $this->environment->getLanguage()->txt('edit_gaps'), + $this->environment + ->withSubActionParameter(EditGaps::SUB_ACTION_JUMP_TO_SET_GAP_TYPES) + ->withFormStartSubActionParameter(EditGaps::SUB_ACTION_JUMP_TO_SET_GAP_TYPES) + ->getUrlBuilder(), + $this->environment->getTableRowIdToken() + ), + 'edit_answer_options' => $taf->standard( + $this->environment->getLanguage()->txt('edit_answer_options'), + $this->environment + ->withSubActionParameter(EditGaps::SUB_ACTION_JUMP_TO_SET_ANSWER_OPTIONS) + ->withFormStartSubActionParameter(EditGaps::SUB_ACTION_JUMP_TO_SET_ANSWER_OPTIONS) + ->getUrlBuilder(), + $this->environment->getTableRowIdToken() + ) + ] + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Migration/BasicMigrationFunctions.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Migration/BasicMigrationFunctions.php new file mode 100644 index 000000000000..274a57f2dff5 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Migration/BasicMigrationFunctions.php @@ -0,0 +1,281 @@ +answer_inputs_mapping === null) { + return null; + } + + return $this->answer_inputs_mapping[$id] ?? null; + } + + private function buildGapInsertStatement( + TableDefinitions $table_definitions, + PersistenceFactory $persistence_factory, + TableNameBuilder $table_names_builder, + ?Insert $gaps_insert, + Uuid $answer_input_id, + Uuid $answer_form_id, + int $position, + string $gap_type, + ?int $max_chars, + ?float $step_size, + ?TextMatchingOptions $matching_options, + ?int $min_autocomplete, + ?int $shuffle + ): Insert { + $this->answer_inputs_mapping[$position] = $answer_input_id; + + if ($gaps_insert === null) { + return $persistence_factory->insert( + $table_definitions->getColumns( + $table_names_builder, + AnswerFormSpecificTableTypes::AnswerInputs + ), + $this->buildGapValuesForInsert( + $persistence_factory, + $answer_input_id, + $answer_form_id, + $position, + $gap_type, + $max_chars, + $step_size, + $matching_options, + $min_autocomplete, + $shuffle + ) + ); + } + + return $gaps_insert->withAdditionalValues( + $this->buildGapValuesForInsert( + $persistence_factory, + $answer_input_id, + $answer_form_id, + $position, + $gap_type, + $max_chars, + $step_size, + $matching_options, + $min_autocomplete, + $shuffle + ) + ); + } + + private function buildGapValuesForInsert( + PersistenceFactory $persistence_factory, + Uuid $answer_input_id, + Uuid $answer_form_id, + int $position, + string $gap_type, + ?int $max_chars, + ?float $step_size, + ?TextMatchingOptions $matching_options, + ?int $min_autocomplete, + ?int $shuffle + ): array { + return [ + $persistence_factory->value(FieldDefinition::T_TEXT, $answer_input_id->toString()), + $persistence_factory->value(FieldDefinition::T_TEXT, $answer_form_id->toString()), + $persistence_factory->value(FieldDefinition::T_INTEGER, $position), + $persistence_factory->value(FieldDefinition::T_TEXT, $gap_type), + $persistence_factory->value(FieldDefinition::T_INTEGER, $max_chars), + $persistence_factory->value(FieldDefinition::T_FLOAT, $step_size), + $persistence_factory->value(FieldDefinition::T_INTEGER, $matching_options?->value), + $persistence_factory->value(FieldDefinition::T_INTEGER, $min_autocomplete), + $persistence_factory->value(FieldDefinition::T_INTEGER, $shuffle) + ]; + } + + private function buildAnswerOptionInsertStatement( + TableDefinitions $table_definitions, + PersistenceFactory $persistence_factory, + TableNameBuilder $table_names_builder, + ?Insert $options_insert, + Uuid $answer_option_id, + Uuid $answer_input_id, + int $position, + string $text_value, + float $points, + ?float $lower_limit, + ?float $upper_limit + ): Insert { + if ($options_insert === null) { + return $persistence_factory->insert( + $table_definitions->getColumns( + $table_names_builder, + AnswerFormSpecificTableTypes::AnswerOptions + ), + $this->buildOptionValuesForInsert( + $persistence_factory, + $answer_option_id, + $answer_input_id, + $position, + $text_value, + $points, + $lower_limit, + $upper_limit + ) + ); + } + + return $options_insert->withAdditionalValues( + $this->buildOptionValuesForInsert( + $persistence_factory, + $answer_option_id, + $answer_input_id, + $position, + $text_value, + $points, + $lower_limit, + $upper_limit + ) + ); + } + + private function buildOptionValuesForInsert( + PersistenceFactory $persistence_factory, + Uuid $answer_option_id, + Uuid $answer_input_id, + int $position, + string $text_value, + float $points, + ?float $lower_limit, + ?float $upper_limit + ): array { + return [ + $persistence_factory->value(FieldDefinition::T_TEXT, $answer_option_id->toString()), + $persistence_factory->value(FieldDefinition::T_TEXT, $answer_input_id->toString()), + $persistence_factory->value(FieldDefinition::T_INTEGER, $position), + $persistence_factory->value(FieldDefinition::T_TEXT, $text_value), + $persistence_factory->value(FieldDefinition::T_FLOAT, $points), + $persistence_factory->value(FieldDefinition::T_FLOAT, $lower_limit), + $persistence_factory->value( + FieldDefinition::T_FLOAT, + $lower_limit !== $upper_limit + ? $upper_limit + : null + ) + ]; + } + + private function buildAnswerFormInsertStatement( + TableDefinitions $table_definitions, + PersistenceFactory $persistence_factory, + TableNameBuilder $table_names_builder, + Uuid $answer_form_id, + ScoringIdentical $scoring_identical, + int $combinations_enabled + ): Insert { + return $persistence_factory->insert( + $table_definitions->getColumns( + $table_names_builder, + AnswerFormSpecificTableTypes::TypeSpecificAnswerForms + ), + [ + $persistence_factory->value(FieldDefinition::T_TEXT, $answer_form_id->toString()), + $persistence_factory->value(FieldDefinition::T_TEXT, $scoring_identical->value), + $persistence_factory->value(FieldDefinition::T_INTEGER, $combinations_enabled) + ] + ); + } + + private function buildScoringIdenticalFromOld( + int $scoring_identical + ): ScoringIdentical { + if ($scoring_identical === '1') { + return ScoringIdentical::ScoreAll; + } + + return ScoringIdentical::OnlyScoreDistinct; + } + + private function buildNewTextRatingFromOld( + string $old_text_rating + ): TextMatchingOptions { + return match($old_text_rating) { + \assClozeGap::TEXTGAP_RATING_CASEINSENSITIVE => TextMatchingOptions::CaseInsensitive, + \assClozeGap::TEXTGAP_RATING_CASESENSITIVE => TextMatchingOptions::CaseSensitive, + \assClozeGap::TEXTGAP_RATING_LEVENSHTEIN1 => TextMatchingOptions::Levenstein1, + \assClozeGap::TEXTGAP_RATING_LEVENSHTEIN2 => TextMatchingOptions::Levenstein2, + \assClozeGap::TEXTGAP_RATING_LEVENSHTEIN3 => TextMatchingOptions::Levenstein3, + \assClozeGap::TEXTGAP_RATING_LEVENSHTEIN4 => TextMatchingOptions::Levenstein4, + \assClozeGap::TEXTGAP_RATING_LEVENSHTEIN5 => TextMatchingOptions::Levenstein5 + }; + } + + private function replaceGapsAndSantizeLegacyClozeText( + \ilDBInterface $db, + string $gap_replace_regex, + string $text, + array $gaps_mapping, + bool $ilias_page_editor_text + ): string { + ksort($gaps_mapping); + + return mb_ereg_replace_callback( + $gap_replace_regex, + function (array $matches) use (&$gaps_mapping): string { + return '{{' . Gap::GAP_PLACEHOLDER_NAME . '_' . array_shift($gaps_mapping) . '}}'; + }, + $this->sanitizeLegacyText( + $db, + $text, + $ilias_page_editor_text + ) + ); + } + + private function limitToFloat( + \EvalMath $math, + string $limit + ): float { + return (float) $math->e($limit); + } + + private function getHtmlQuestionContentPurifier(): \ilHtmlPurifierInterface + { + return \ilHtmlPurifierFactory::getInstanceByType('qpl_usersolution'); + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Migration/MigrationCloze.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Migration/MigrationCloze.php new file mode 100644 index 000000000000..7ab1ed084c22 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Migration/MigrationCloze.php @@ -0,0 +1,402 @@ +fetchDBValues( + $migration_insert->getDb(), + $migration_insert->getOldQuestionId() + ) as $db_row) { + $answer_form_id = $migration_insert->getAnswerFormId(); + if (!isset($answer_input_mapping[$db_row->gap_id])) { + $answer_input_mapping[$db_row->gap_id] = $migration_insert->getUuid(); + $answer_options_mapping[$db_row->gap_id] = []; + $this->answer_options_mapping_for_feedback[$db_row->gap_id] = [ + 'is_numeric' => $db_row->cloze_type == \assClozeGap::TYPE_NUMERIC, + 'is_feedback_per_gap' => $db_row->feedback_mode === \ilAssClozeTestFeedback::FB_MODE_GAP_QUESTION, + 'answer_options' => [] + ]; + $gaps_insert = $this->buildGapInsertStatement( + $this->table_definitions, + $migration_insert->getPersistenceFactory(), + $migration_insert->getTableNameBuilder( + $this->table_definitions->getTableSubNameSpace() + ), + $gaps_insert, + $answer_input_mapping[$db_row->gap_id], + $answer_form_id, + $db_row->gap_id, + $this->buildNewGapTypeIdentifierFromOld((int) $db_row->cloze_type), + null, + null, + $this->buildNewTextRatingFromOld($db_row->textgap_rating), + null, + $db_row->shuffle === '1' ? 1 : 0 + ); + } + + $answer_option_id = $migration_insert->getUuid(); + $answer_options_mapping[$db_row->gap_id][$db_row->answertext] = [ + 'is_numeric' => $db_row->cloze_type == \assClozeGap::TYPE_NUMERIC, + 'answer_option_id' => $answer_option_id + ]; + $this->answer_options_mapping_for_feedback[$db_row->gap_id]['answer_options'][$db_row->aorder] = $answer_option_id; + + if (!isset($available_points[$answer_option_id->toString()]) + || $available_points[$answer_option_id->toString()] < $db_row->points) { + $available_points[$answer_option_id->toString()] = $db_row->points; + } + + $answer_options_insert = $this->buildAnswerOptionInsertStatement( + $this->table_definitions, + $migration_insert->getPersistenceFactory(), + $migration_insert->getTableNameBuilder( + $this->table_definitions->getTableSubNameSpace() + ), + $answer_options_insert, + $answer_option_id, + $answer_input_mapping[$db_row->gap_id], + $db_row->aorder, + $db_row->answertext, + $db_row->points, + $this->limitToFloat($this->math, $db_row->lowerlimit), + $this->limitToFloat($this->math, $db_row->upperlimit) + ); + } + + if (!isset($db_row)) { + return null; + } + + if ($db_row->combinations_enabled) { + $migration_insert = $this->addCombinationInsertStatements( + $migration_insert->getPersistenceFactory(), + $migration_insert, + $answer_input_mapping, + $answer_options_mapping + ); + } + + return $migration_insert + ->withAdditionalInsert( + $this->buildAnswerFormInsertStatement( + $this->table_definitions, + $migration_insert->getPersistenceFactory(), + $migration_insert->getTableNameBuilder( + $this->table_definitions->getTableSubNameSpace() + ), + $answer_form_id, + $this->buildScoringIdenticalFromOld((int) $db_row->identical_scoring), + $db_row->combinations_enabled + ) + )->withAdditionalInsert( + $gaps_insert + )->withAdditionalInsert( + $answer_options_insert + )->withAdditionalTextLegacy( + $this->replaceGapsAndSantizeLegacyClozeText( + $migration_insert->getDb(), + '\[gap\].+?\[\/gap\]', + $db_row->cloze_text, + $answer_input_mapping, + $migration_insert->wasIliasPageEditorUsedForAdditionalTexts() + ) + )->withAvailablePoints( + array_reduce( + $available_points, + fn(float $c, float $v): float => $c + $v, + 0.0 + ) + ); + } + + #[\Override] + public function getConditionsForFeedbackFromOldValues( + int $answer, + int $question + ): ?array { + $gap = $this->answer_options_mapping_for_feedback[$question]; + + if ($gap['is_feedback_per_gap'] && $answer !== -10 + || !$gap['is_feedback_per_gap'] && $answer === -10) { + return null; + } + + if ($answer === -10) { + return array_map( + fn(Uuid $v): string => $v->toString(), + $gap['answer_options'] + ); + } + + if ($answer === -1) { + return [Types::NoResponse->value]; + } + + if ($answer === -2) { + return [Types::OtherResponse->value]; + } + + if ($gap['is_numeric']) { + return [$this->buildRangeValue(true, $answer)->value]; + } + + $answer_option_id = array_filter( + $gap['answer_options'], + fn(string $v): bool => $v == $answer, + ARRAY_FILTER_USE_KEY + ); + + if ($answer_option_id !== []) { + return [array_shift($answer_option_id)->toString()]; + } + + return null; + } + + private function fetchDBValues( + \ilDBInterface $db, + int $old_question_id + ): \Generator { + $query = $db->query( + 'SELECT *, EXISTS (' . PHP_EOL + . 'SELECT gap_fi FROM qpl_a_cloze_combi_res' . PHP_EOL + . 'WHERE question_fi = a.question_fi' . PHP_EOL + . ') combinations_enabled' . PHP_EOL + . 'FROM qpl_qst_cloze q' . PHP_EOL + . 'JOIN qpl_a_cloze a ON q.question_fi = a.question_fi' . PHP_EOL + . "WHERE q.question_fi = {$db->quote($old_question_id)}" . PHP_EOL + . 'ORDER BY a.gap_id, a.aorder' + ); + + while (($row = $db->fetchObject($query)) !== null) { + yield $row; + } + } + + private function fetchCombinationsDBValues( + \ilDBInterface $db, + int $old_question_id + ): \Generator { + $query = $db->query( + 'SELECT combination_id, gap_fi, answer, points, row_id FROM qpl_a_cloze_combi_res' . PHP_EOL + . "WHERE question_fi = {$db->quote($old_question_id)}" . PHP_EOL + . 'ORDER BY combination_id, row_id' + ); + + while (($row = $db->fetchObject($query)) !== null) { + yield $row; + } + } + + private function addCombinationInsertStatements( + PersistenceFactory $persistence_factory, + MigrationInsert $migration_insert, + array $answer_input_mapping, + array $answer_options_mapping + ): MigrationInsert { + $combination_mapping = []; + $combinations_insert = null; + $combinations_to_answer_options_insert = null; + foreach ($this->fetchCombinationsDBValues( + $migration_insert->getDb(), + $migration_insert->getOldQuestionId() + ) as $db_row) { + if ($db_row->answer !== 'out_of_bound' + && !isset($answer_options_mapping[$db_row->gap_fi][$db_row->answer])) { + continue; + } + + $answer_option = $db_row->answer === 'out_of_bound' + ? reset($answer_options_mapping[$db_row->gap_fi]) + : $answer_options_mapping[$db_row->gap_fi][$db_row->answer]; + + if (!isset($combination_mapping[$db_row->combination_id . $db_row->row_id])) { + $combination_mapping[$db_row->combination_id . $db_row->row_id] = $migration_insert->getUuid(); + $combinations_insert = $this->buildCombinationsInsert( + $persistence_factory, + $migration_insert->getTableNameBuilder( + $this->table_definitions->getTableSubNameSpace() + ), + $combinations_insert, + $combination_mapping[$db_row->combination_id . $db_row->row_id], + $migration_insert->getAnswerFormId(), + $db_row->points + ); + } + + $combinations_to_answer_options_insert = $this->buildCombinationsToAnswerOptionsInsert( + $persistence_factory, + $migration_insert->getTableNameBuilder( + $this->table_definitions->getTableSubNameSpace() + ), + $combinations_to_answer_options_insert, + $combination_mapping[$db_row->combination_id . $db_row->row_id], + $answer_input_mapping[$db_row->gap_fi], + $answer_option['answer_option_id'], + $this->buildRangeValue($answer_option['is_numeric'], $db_row->answer) + ); + } + + return $migration_insert->withAdditionalInsert($combinations_insert) + ->withAdditionalInsert($combinations_to_answer_options_insert); + } + + private function buildCombinationsInsert( + PersistenceFactory $persistence_factory, + TableNameBuilder $table_names_builder, + ?Insert $combinations_insert, + Uuid $combination_id, + Uuid $answer_form_id, + float $points + ): Insert { + $values = [ + $persistence_factory->value(FieldDefinition::T_TEXT, $combination_id->toString()), + $persistence_factory->value(FieldDefinition::T_TEXT, $answer_form_id->toString()), + $persistence_factory->value(FieldDefinition::T_FLOAT, $points), + ]; + + if ($combinations_insert === null) { + return $persistence_factory->insert( + $this->table_definitions->getColumns( + $table_names_builder, + AnswerFormSpecificTableTypes::Additional, + $this->table_definitions->getCombinationsTableIdentifier() + ), + $values + ); + } + + return $combinations_insert->withAdditionalValues($values); + } + + private function buildCombinationsToAnswerOptionsInsert( + PersistenceFactory $persistence_factory, + TableNameBuilder $table_names_builder, + ?Insert $combinations_to_answer_options_insert, + Uuid $combination_id, + Uuid $gap_id, + Uuid $answer_option_id, + ?Range $in_range + ): Insert { + $values = [ + $persistence_factory->value(FieldDefinition::T_TEXT, $combination_id->toString()), + $persistence_factory->value(FieldDefinition::T_TEXT, $gap_id->toString()), + $persistence_factory->value(FieldDefinition::T_TEXT, $answer_option_id->toString()), + $persistence_factory->value(FieldDefinition::T_TEXT, $in_range?->value) + ]; + + if ($combinations_to_answer_options_insert === null) { + return $persistence_factory->insert( + $this->table_definitions->getColumns( + $table_names_builder, + AnswerFormSpecificTableTypes::Additional, + $this->table_definitions->getCombinationToAnswerOptionsTableIdentifier() + ), + $values + ); + } + + return $combinations_to_answer_options_insert->withAdditionalValues($values); + } + + private function buildNewGapTypeIdentifierFromOld( + int $old_gap_type + ): string { + return match($old_gap_type) { + \assClozeGap::TYPE_TEXT => 'text', + \assClozeGap::TYPE_SELECT => 'select', + \assClozeGap::TYPE_NUMERIC => 'numeric' + }; + } + + private function buildRangeValue( + bool $is_numeric, + string|int $value + ): ?Range { + if ($is_numeric === false) { + return null; + } + + if ($value === 'out_of_bounds') { + return Range::OutOfRange; + } + + if ($value === \ilAssClozeTestFeedback::FB_NUMERIC_GAP_TOO_LOW_INDEX) { + return Range::BelowRange; + } + + if ($value === \ilAssClozeTestFeedback::FB_NUMERIC_GAP_TOO_HIGH_INDEX) { + return Range::AboveRange; + } + + return Range::InRange; + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Migration/MigrationLongMenu.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Migration/MigrationLongMenu.php new file mode 100644 index 000000000000..8fe8575e950e --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Migration/MigrationLongMenu.php @@ -0,0 +1,228 @@ +getAnswerFormId(); + + $answer_input_mapping = []; + $gaps_insert = null; + $answer_options_to_insert = []; + $available_points = []; + + foreach ($this->fetchDBValues( + $migration_insert->getDb(), + $migration_insert->getOldQuestionId() + ) as $db_row) { + if (!isset($answer_input_mapping[$db_row->gap_number])) { + $answer_input_id = $migration_insert->getUuid(); + $answer_input_mapping[$db_row->gap_number] = $answer_input_id; + + $gaps_insert = $this->buildGapInsertStatement( + $this->table_definitions, + $migration_insert->getPersistenceFactory(), + $migration_insert->getTableNameBuilder( + $this->table_definitions->getTableSubNameSpace() + ), + $gaps_insert, + $answer_input_id, + $answer_form_id, + $db_row->gap_number, + $this->buildNewGapTypeIdentifierFromOld($db_row->type), + null, + null, + null, + $db_row->min_auto_complete, + $db_row->shuffle_answers === '1' ? 1 : 0 + ); + + $answers_from_file = $this->loadAnswersFromFile( + $environment->getResource(Environment::RESOURCE_ILIAS_INI), + $environment->getResource(Environment::RESOURCE_CLIENT_ID), + $migration_insert->getOldQuestionId(), + $db_row->gap_number + ); + + $answer_options_to_insert[$answer_input_id->toString()] = array_map( + fn(int $v) => [ + 'answer_option_id' => $migration_insert->getUuid(), + 'answer_input_id' => $answer_input_id, + 'position' => $v, + 'text' => trim($answers_from_file[$v]), + 'points' => 0.0 + ], + array_keys($answers_from_file) + ); + } + + $answer_input_id_string = $answer_input_mapping[$db_row->gap_number]->toString(); + $position = array_find_key( + $answer_options_to_insert[$answer_input_id_string], + fn(array $v, int $k): bool => trim($v['text']) === trim($db_row->answer_text) + ); + + if ($position !== null) { + $answer_options_to_insert[$answer_input_id_string][$position]['points'] = $db_row->points; + $available_points[$answer_input_id_string] = $db_row->points; + } + } + + if (!isset($db_row)) { + return null; + } + + $answer_options_insert = null; + foreach (array_reduce( + $answer_options_to_insert, + fn(array $c, array $v): array => [...$c, ...$v], + [] + ) as $answer) { + $answer_options_insert = $this->buildAnswerOptionInsertStatement( + $this->table_definitions, + $migration_insert->getPersistenceFactory(), + $migration_insert->getTableNameBuilder( + $this->table_definitions->getTableSubNameSpace() + ), + $answer_options_insert, + $answer['answer_option_id'], + $answer['answer_input_id'], + $answer['position'], + $answer['text'], + $answer['points'], + null, + null + ); + } + + return $migration_insert + ->withAdditionalInsert( + $this->buildAnswerFormInsertStatement( + $this->table_definitions, + $migration_insert->getPersistenceFactory(), + $migration_insert->getTableNameBuilder( + $this->table_definitions->getTableSubNameSpace() + ), + $answer_form_id, + $this->buildScoringIdenticalFromOld($db_row->identical_scoring), + 0 + ) + )->withAdditionalTextLegacy( + $this->replaceGapsAndSantizeLegacyClozeText( + $migration_insert->getDb(), + '\[Longmenu \d+\]', + $db_row->long_menu_text, + $answer_input_mapping, + $migration_insert->wasIliasPageEditorUsedForAdditionalTexts() + ) + )->withAdditionalInsert($gaps_insert) + ->withAdditionalInsert($answer_options_insert) + ->withAvailablePoints( + array_reduce( + $available_points, + fn(float $c, float $v): float => $c + $v, + 0.0 + ) + ); + } + + #[\Override] + public function getConditionsForFeedbackFromOldValues( + int $answer, + int $question + ): null { + return null; + } + + private function fetchDBValues( + \ilDBInterface $db, + int $old_question_id + ): \Generator { + $query = $db->query( + 'SELECT * FROM qpl_qst_lome q' . PHP_EOL + . 'JOIN qpl_a_lome a ON q.question_fi = a.question_fi' . PHP_EOL + . "WHERE q.question_fi = {$db->quote($old_question_id)}" . PHP_EOL + . 'ORDER BY a.gap_number, a.position' + ); + + while (($row = $db->fetchObject($query)) !== null) { + yield $row; + } + } + + private function loadAnswersFromFile( + \ilIniFile $ini, + string $client_id, + int $old_question_id, + int $gap_id + ): array { + $file = "{$ini->readVariable('clients', 'datadir')}/{$client_id}/assessment/longMenuQuestion/{$old_question_id}/{$gap_id}.txt"; + + if (!file_exists($file)) { + return []; + } + + return explode( + "\n", + file_get_contents($file) + ); + } + + private function buildNewGapTypeIdentifierFromOld( + int $old_gap_type + ): string { + return match($old_gap_type) { + \assLongMenu::ANSWER_TYPE_TEXT_VAL => 'long_menu', + \assLongMenu::ANSWER_TYPE_SELECT_VAL => 'select' + }; + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Migration/MigrationNumeric.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Migration/MigrationNumeric.php new file mode 100644 index 000000000000..6649cc95aff8 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Migration/MigrationNumeric.php @@ -0,0 +1,142 @@ +math->suppress_errors = true; + } + + #[\Override] + public function getOldQuestionTypeIdentifier(): string + { + return 'assNumeric'; + } + + #[\Override] + public function getDefinitionClass(): string + { + return Definition::class; + } + + #[\Override] + public function completeMigrationInsert( + Environment $environment, + MigrationInsert $migration_insert + ): ?MigrationInsert { + $db_row = $this->fetchDBValues( + $migration_insert->getDb(), + $migration_insert->getOldQuestionId() + )?->current(); + + if ($db_row === null) { + return null; + } + + $answer_form_id = $migration_insert->getAnswerFormId(); + $gap_id = $migration_insert->getUuid(); + return $migration_insert->withAdditionalInsert( + $this->buildGapInsertStatement( + $this->table_definitions, + $migration_insert->getPersistenceFactory(), + $migration_insert->getTableNameBuilder( + $this->table_definitions->getTableSubNameSpace() + ), + null, + $gap_id, + $answer_form_id, + 0, + 'numeric', + null, + 0.0001, + null, + null, + null + ) + )->withAdditionalInsert( + $this->buildAnswerOptionInsertStatement( + $this->table_definitions, + $migration_insert->getPersistenceFactory(), + $migration_insert->getTableNameBuilder( + $this->table_definitions->getTableSubNameSpace() + ), + null, + $migration_insert->getUuid(), + $gap_id, + 0, + '', + $db_row->points, + $this->limitToFloat($this->math, $db_row->lowerlimit), + $this->limitToFloat($this->math, $db_row->upperlimit) + ) + )->withAdditionalInsert( + $this->buildAnswerFormInsertStatement( + $this->table_definitions, + $migration_insert->getPersistenceFactory(), + $migration_insert->getTableNameBuilder( + $this->table_definitions->getTableSubNameSpace() + ), + $answer_form_id, + ScoringIdentical::ScoreAll, + 0 + ) + )->withAdditionalText( + '{{' . Gap::GAP_PLACEHOLDER_NAME . '_' . $gap_id->toString() . '}}' + )->withAvailablePoints($db_row->points); + } + + #[\Override] + public function getConditionsForFeedbackFromOldValues( + int $answer, + int $question + ): null { + return null; + } + + private function fetchDBValues( + \ilDBInterface $db, + int $old_question_id + ): \Generator { + $query = $db->query( + 'SELECT points, lowerlimit, upperlimit FROM qpl_num_range' . PHP_EOL + . "WHERE question_fi = {$db->quote($old_question_id)}" + ); + + while (($row = $db->fetchObject($query)) !== null) { + yield $row; + } + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Migration/MigrationTextSubset.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Migration/MigrationTextSubset.php new file mode 100644 index 000000000000..27e0a58182b9 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Migration/MigrationTextSubset.php @@ -0,0 +1,185 @@ +getAnswerFormId(); + $answer_options_insert = null; + $gaps = []; + $available_points = []; + + foreach ($this->fetchDBValues( + $migration_insert->getDb(), + $migration_insert->getOldQuestionId() + ) as $db_row) { + if ($gaps === []) { + $gaps_insert = null; + for ($i = 0; $i < $db_row->correctanswers; $i++) { + $gap_id = $migration_insert->getUuid(); + $gaps[] = $gap_id; + + $gaps_insert = $this->buildGapInsertStatement( + $this->table_definitions, + $migration_insert->getPersistenceFactory(), + $migration_insert->getTableNameBuilder( + $this->table_definitions->getTableSubNameSpace() + ), + $gaps_insert, + $gap_id, + $answer_form_id, + $i, + 'text', + null, + null, + $this->buildNewTextRatingFromOld($db_row->textgap_rating), + null, + 0 + ); + } + + $migration_insert = $migration_insert->withAdditionalInsert($gaps_insert); + } + + $available_points[] = $db_row->points; + + foreach ($gaps as $gap_id) { + $answer_options_insert = $this->buildAnswerOptionInsertStatement( + $this->table_definitions, + $migration_insert->getPersistenceFactory(), + $migration_insert->getTableNameBuilder( + $this->table_definitions->getTableSubNameSpace() + ), + $answer_options_insert, + $migration_insert->getUuid(), + $gap_id, + $db_row->aorder, + $db_row->answertext, + $db_row->points, + null, + null + ); + } + } + + if (!isset($db_row)) { + return null; + } + + rsort($available_points); + + return $migration_insert + ->withAdditionalInsert( + $this->buildAnswerFormInsertStatement( + $this->table_definitions, + $migration_insert->getPersistenceFactory(), + $migration_insert->getTableNameBuilder( + $this->table_definitions->getTableSubNameSpace() + ), + $answer_form_id, + ScoringIdentical::OnlyScoreDistinct, + 0 + ) + )->withAdditionalInsert( + $answer_options_insert + )->withAdditionalText( + $this->buildAdditionalTextFromGapsArray($gaps) + )->withAvailablePoints( + array_sum( + array_slice( + $available_points, + 0, + count($gaps) + ) + ) + ); + } + + #[\Override] + public function getConditionsForFeedbackFromOldValues( + int $answer, + int $question + ): null { + return null; + } + + private function fetchDBValues( + \ilDBInterface $db, + int $old_question_id + ): \Generator { + $query = $db->query( + 'SELECT * FROM qpl_qst_textsubset q' . PHP_EOL + . 'JOIN qpl_a_textsubset a ON q.question_fi = a.question_fi' . PHP_EOL + . "WHERE q.question_fi = {$db->quote($old_question_id)}" . PHP_EOL + ); + + while (($row = $db->fetchObject($query)) !== null) { + yield $row; + } + } + + private function buildAdditionalTextFromGapsArray( + array $gaps + ): string { + $text_array = []; + foreach ($gaps as $index => $gap) { + $position = $index + 1; + $text_array[] = "{$position}. {{GAP_{$gap->toString()}}}"; + } + + return implode( + "\n\n", + $text_array + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/ClozeText/Factory.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/ClozeText/Factory.php new file mode 100644 index 000000000000..b24c6ab3de23 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/ClozeText/Factory.php @@ -0,0 +1,46 @@ +refinery, + $this->mustache_engine, + $this->text_factory, + $this->text_factory->markdown($text) + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/ClozeText/Text.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/ClozeText/Text.php new file mode 100644 index 000000000000..2bab1bf870a2 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/ClozeText/Text.php @@ -0,0 +1,202 @@ +markdown( + new \ilUIMarkdownPreviewGUI(), + $lng->txt('cloze_text') + )->withMustacheVariables([ + Gap::GAP_PLACEHOLDER_NAME => $lng->txt('insert_gap') + ])->withAdditionalTransformation( + $this->refinery->custom()->transformation( + fn(string $v): self => $cloze_text_factory->buildFromTextString($v) + ) + )->withAdditionalTransformation( + $this->refinery->custom()->constraint( + fn(self $v): bool => !$is_required && $v->getRawRepresentation() === '' + || $v->hasAtLeastOneGap(), + $lng->txt('no_gaps') + ) + )->withRequired($is_required) + ->withValue($this->cloze_text->getRawRepresentation()); + } + + public function getRawRepresentation(): string + { + return $this->cloze_text->getRawRepresentation(); + } + + public function getRenderedMarkdownForParticipantPresentation(): string + { + return $this->refinery->string()->markdown()->toHTML()->transform( + $this->cloze_text->getRawRepresentation() + ); + } + + public function buildPanelForEditing( + UIFactory $ui_factory, + Language $lng, + Gaps $gaps, + string $legacy_cloze_text + ): StandardPanel { + return $ui_factory->panel()->standard( + $lng->txt('cloze_text'), + $ui_factory->legacy()->latexContent( + $this->getRenderedMarkdownForEditingPresentation( + $gaps, + $legacy_cloze_text + ) + ) + ); + } + + public function getRenderedMarkdownForEditingPresentation( + Gaps $gaps, + string $legacy_cloze_text + ): string { + $text = $this->cloze_text->getRawRepresentation() === '' + ? $legacy_cloze_text + : $this->refinery->string()->markdown()->toHTML()->transform( + $this->cloze_text->getRawRepresentation() + ); + + return $this->mustache_engine->render( + $text, + $gaps->getPlaceholderArrayForEditFormPanel() + ); + } + + public function updateGapsFromMarkdown( + Uuid $answer_form_id, + Gaps $pre_existing_gaps + ): Gaps { + if ($this->cloze_text->getRawRepresentation() === '') { + return $pre_existing_gaps->withResetGaps(); + } + + $position = 0; + return array_reduce( + $this->mustache_engine->getTokenizer()->scan($this->cloze_text->getRawRepresentation()), + function (Gaps $c, array $v) use ($answer_form_id, $pre_existing_gaps, &$position): Gaps { + if ($v['type'] !== '_v' + || !str_starts_with($v['name'], Gap::GAP_PLACEHOLDER_NAME)) { + return $c; + } + + if ($v['name'] === Gap::GAP_PLACEHOLDER_NAME) { + return $c->withNewGap($answer_form_id, $position++); + } + + $gap = $pre_existing_gaps->getGapByTagName($v['name']); + if ($gap !== null) { + return $c->withGap( + $gap->withPosition( + $position++ + ) + ); + } + + return $c->withAdditionalGapFromTagName( + $answer_form_id, + $v['name'], + $position++ + ); + }, + $pre_existing_gaps->withResetGaps() + ); + } + + public function withUpdateMarkdownAfterCloning( + \Closure $replace_gaps + ): self { + $clone = clone $this; + $clone->cloze_text = $this->text_factory->markdown( + $replace_gaps($clone->cloze_text->getRawRepresentation()) + ); + return $clone; + } + + public function withIdsOfNewGapsInClozeText( + array $new_gaps + ): self { + if ($new_gaps === []) { + return $this; + } + + $clone = clone $this; + $clone->cloze_text = $this->text_factory->markdown( + mb_ereg_replace_callback( + '{{' . Gap::GAP_PLACEHOLDER_NAME . '}}', + function (array $matches) use (&$new_gaps): string { + return array_shift($new_gaps)->getGapPlaceholder(); + }, + $this->cloze_text->getRawRepresentation() + ) + ); + return $clone; + } + + private function hasAtLeastOneGap(): bool + { + if ($this->cloze_text->getRawRepresentation() === '') { + return false; + } + + foreach ($this->mustache_engine->getTokenizer() + ->scan($this->cloze_text->getRawRepresentation()) as $token) { + if ($token['type'] === '_v' + && str_starts_with($token['name'], Gap::GAP_PLACEHOLDER_NAME)) { + return true; + } + } + + return false; + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Combinations/Combination.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Combinations/Combination.php new file mode 100644 index 000000000000..9c0d9656bfb9 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Combinations/Combination.php @@ -0,0 +1,353 @@ + $matching_values + */ + public function __construct( + private Uuid $id, + private readonly ?float $available_points, + private array $matching_values = [] + ) { + uasort( + $this->matching_values, + fn(MatchingValue $a, MatchingValue $b): int => $a->getGap()->getPosition() <=> $b->getGap()->getPosition() + ); + } + + public function getId(): Uuid + { + return $this->id; + } + + public function getAvailablePoints(): ?float + { + return $this->available_points; + } + + public function getValuePresentation( + Language $lng + ): string { + return implode( + '
', + array_map( + fn(MatchingValue $v): string => $v->buildPresentationString($lng), + $this->matching_values + ) + ); + } + + public function containsAnswerOptionsExactly( + array $vs + ): bool { + return array_diff( + $vs, + array_map( + fn(MatchingValue $v): string => $v->getAnswerOption()->getAnswerOptionId()->toString(), + $this->matching_values + ) + ) === []; + } + + #[\Override] + public function clone( + UuidFactory $uuid_factory, + array $environment = [] + ): static { + $environment['combination_id'] = $uuid_factory->uuid4(); + $clone = clone $this; + $clone->id = $environment['combination_id']; + $clone->matching_values = array_map( + fn(MatchingValue $v): MatchingValue => $v->clone( + $uuid_factory, + $environment + ), + $clone->matching_values + ); + return $clone; + } + + public function toStorage( + Uuid $answer_form_id, + PersistenceFactory $persistence_factory, + TableDefinitions $table_definitions, + TableNameBuilder $table_names_builder, + Manipulate $manipulate + ): Manipulate { + if ($this->matching_values === []) { + throw new \UnexpectedValueException( + 'A Combination without MatchingValues cannot be stored.' + ); + } + + return array_reduce( + $this->matching_values, + fn(Manipulate $c, MatchingValue $v): Manipulate => $c->withAdditionalStatement( + $v->toStorage( + $table_definitions, + $persistence_factory, + $table_names_builder + ) + ), + $manipulate->withAdditionalStatement( + $this->buildReplace( + $table_definitions, + $persistence_factory, + $table_names_builder, + $answer_form_id + ) + ) + ); + } + + public function toDelete( + PersistenceFactory $persistence_factory, + TableDefinitions $table_definitions, + TableNameBuilder $table_names_builder, + Manipulate $manipulate + ): Manipulate { + return $manipulate->withAdditionalStatement( + $this->buildDelete( + $table_definitions, + $persistence_factory, + $table_names_builder + ) + )->withAdditionalStatement( + $this->buildDeleteForLinkedValues( + $table_definitions, + $persistence_factory, + $table_names_builder + ) + ); + } + + private function buildReplace( + TableDefinitions $table_definitions, + PersistenceFactory $persistence_factory, + TableNameBuilder $table_names_builder, + Uuid $answer_form_id + ): Replace { + return $persistence_factory->replace( + $table_definitions->getColumns( + $table_names_builder, + AnswerFormSpecificTableTypes::Additional, + $table_definitions->getCombinationsTableIdentifier() + ), + [ + $persistence_factory->value(FieldDefinition::T_TEXT, $this->id->toString()), + $persistence_factory->value(FieldDefinition::T_TEXT, $answer_form_id->toString()), + $persistence_factory->value(FieldDefinition::T_FLOAT, $this->available_points) + ] + ); + } + + private function buildDelete( + TableDefinitions $table_definitions, + PersistenceFactory $persistence_factory, + TableNameBuilder $table_names_builder + ): Delete { + $table_definition = AnswerFormSpecificTableTypes::Additional; + return $persistence_factory->delete( + $persistence_factory->table( + $table_names_builder, + $table_definition, + $table_definitions->getCombinationsTableIdentifier() + ), + [ + $persistence_factory->where( + $table_definitions->getIdColumn( + $table_names_builder, + $table_definition, + $table_definitions->getCombinationsTableIdentifier() + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->id->toString() + ) + ) + ] + ); + } + + private function buildDeleteForLinkedValues( + TableDefinitions $table_definitions, + PersistenceFactory $persistence_factory, + TableNameBuilder $table_names_builder + ): Delete { + $table_definition = AnswerFormSpecificTableTypes::Additional; + return $persistence_factory->delete( + $persistence_factory->table( + $table_names_builder, + $table_definition, + $table_definitions->getCombinationToAnswerOptionsTableIdentifier() + ), + [ + $persistence_factory->where( + $table_definitions->getIdColumn( + $table_names_builder, + $table_definition, + $table_definitions->getCombinationToAnswerOptionsTableIdentifier() + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->id->toString() + ) + ) + ] + ); + } + + public function toTableRow( + Language $lng, + DataRowBuilder $data_row_builder + ): DataRow { + return $data_row_builder->buildDataRow( + $this->id->toString(), + [ + 'gaps' => $this->buildGapsString(), + 'values' => $this->getValuePresentation($lng), + 'available_points' => $this->getAvailablePoints() + ] + ); + } + + public function buildGapsString(): string + { + return implode( + '
', + array_map( + fn(MatchingValue $v): string => $v->getGap()->buildShortenedGapRepresentation(), + $this->matching_values + ) + ); + } + + public function buildPointsInputs( + FieldFactory $field_factory, + Refinery $refinery, + Language $lng, + Factory $combinations_factory, + Properties $properties + ): Section { + return $field_factory->section( + [ + 'values' => $this->buildValuesInputs( + $field_factory, + $properties + ), + 'points' => $field_factory->numeric( + $lng->txt('points') + )->withStepSize(0.01) + ->withRequired(true) + ->withValue($this->getAvailablePoints()) + ], + $lng->txt('values') + )->withAdditionalTransformation( + $refinery->custom()->constraint( + fn(array $vs): bool => !$properties->getCombinations() + ->hasMatchingCombinationForAnswerOptionIds($vs['values']), + $lng->txt('gap_combination_already_exists') + ) + )->withAdditionalTransformation( + $refinery->custom()->transformation( + fn(array $v): Properties => $properties->withCombinations( + $properties->getCombinations()->withAdditionalCombination( + $combinations_factory->buildCombination( + $this->id, + $v['points'], + $combinations_factory->buildMatchingValuesFromForm( + $properties, + $this->id, + $v['values'] + ) + ) + ) + ) + ) + ); + } + + public function buildCarryString(): string + { + return json_encode([ + $this->id->toString() => array_map( + fn(MatchingValue $v) => $v->getGap()->getAnswerInputId()->toString(), + $this->matching_values + ) + ]); + } + + private function buildValuesInputs( + FieldFactory $field_factory, + Properties $properties + ): Group { + return $field_factory->group( + array_reduce( + $this->matching_values, + function ( + array $c, + MatchingValue $v + ) use ( + $field_factory, + $properties + ): array { + $gap_id = $v->getGap()->getAnswerInputId(); + $gap = $properties->getGaps()->getGapById($gap_id); + $c[$gap_id->toString()] = $field_factory->select( + $gap->buildShortenedGapName(), + $gap->getType()->getCombinationsSelectValues($gap) + )->withRequired(true) + ->withValue( + $v->getInRange()?->value ?? $v->getAnswerOption() + ?->getAnswerOptionId()->toString() + ); + return $c; + }, + [] + ) + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Combinations/Combinations.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Combinations/Combinations.php new file mode 100644 index 000000000000..9eee90c60fbb --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Combinations/Combinations.php @@ -0,0 +1,187 @@ +combinations = array_reduce( + $combinations, + function (array $c, Combination $v): array { + $c[$v->getId()->toString()] = $v; + return $c; + }, + [] + ); + } + + public function areCombinationsEnabled(): bool + { + return $this->enabled; + } + + public function withCombinationsEnabled( + bool $combinations_enabled + ): self { + $clone = clone $this; + $clone->enabled = $combinations_enabled; + return $clone; + } + + public function getCombinationById( + string $id + ): ?Combination { + return $this->combinations[$id] ?? null; + } + + public function withAdditionalCombination( + Combination $combination + ): self { + $clone = clone $this; + $clone->combinations[$combination->getId()->toString()] = $combination; + return $clone; + } + + public function withoutCombination( + string $id + ): self { + $clone = clone $this; + $clone->deleted_combinations[] = $clone->combinations[$id]; + unset($clone->combinations[$id]); + return $clone; + } + + public function hasMatchingCombinationForAnswerOptionIds( + array $vs + ): bool { + foreach ($this->combinations as $combination) { + if ($combination->containsAnswerOptionsExactly($vs)) { + return true; + } + } + return false; + } + + public function getEditView(): Edit + { + return new Edit( + $this->combinations_factory + ); + } + + #[\Override] + public function clone( + UuidFactory $uuid_factory, + array $environment = [] + ): static { + $clone = clone $this; + $clone->answer_form_id = $environment['answer_form_id']; + $clone->combinations = array_map( + fn(Combination $v): Combination => $v->clone( + $uuid_factory, + $environment + ), + $this->combinations + ); + return + $clone; + } + + public function toStorage( + Manipulate $manipulate, + TableDefinitions $table_definitions, + TableNameBuilder $table_names_builder + ): Manipulate { + return array_reduce( + $this->combinations, + fn(Manipulate $c, Combination $v): Manipulate => $v->toStorage( + $this->answer_form_id, + $this->persistence_factory, + $table_definitions, + $table_names_builder, + $c + ), + array_reduce( + $this->deleted_combinations, + fn(Manipulate $c, Combination $v): Manipulate => $v->toDelete( + $this->persistence_factory, + $table_definitions, + $table_names_builder, + $manipulate + ), + $manipulate + ) + ); + } + + public function toDelete( + Manipulate $manipulate, + TableDefinitions $table_definitions, + TableNameBuilder $table_names_builder + ): Manipulate { + return array_reduce( + $this->combinations, + fn(Manipulate $c, Combination $v): Manipulate => $c->withAdditionalStatement( + $v->toDelete( + $this->persistence_factory, + $table_definitions, + $table_names_builder, + $manipulate + ) + ), + $manipulate + ); + } + + public function toTableRows( + Language $lng, + DataRowBuilder $row_builder, + ): \Generator { + foreach ($this->combinations as $combination) { + yield $combination->toTableRow($lng, $row_builder); + } + } + + public function getNumberOfCombinations(): int + { + return count($this->combinations); + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Combinations/Edit.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Combinations/Edit.php new file mode 100644 index 000000000000..fb7117472686 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Combinations/Edit.php @@ -0,0 +1,83 @@ +addEditAnswerFormSubTab( + self::SUB_ACTION_EDIT_COMBINATIONS_OVERVIEW, + self::LANG_VAR_EDIT_COMBINATIONS + ); + } + + public function show( + Environment $environment + ): Async|Viewable|Properties|null { + if (!$environment->isMarkingRequired()) { + return null; + } + + $environment->addEditAnswerFormSubTab( + self::SUB_ACTION_EDIT_COMBINATIONS_OVERVIEW, + self::LANG_VAR_EDIT_COMBINATIONS + ); + + $environment->activateEditAnswerFormSubTab( + self::SUB_ACTION_EDIT_COMBINATIONS_OVERVIEW + ); + + $combinations_overview = $this->buildOverview($environment); + + $sub_action = $environment->getSubAction(); + if ($sub_action === self::SUB_ACTION_EDIT_COMBINATIONS_OVERVIEW + || $sub_action === '') { + return $combinations_overview; + } + + return $combinations_overview->doAction(); + } + + private function buildOverview( + Environment $environment + ): Overview { + return new Overview( + $environment, + $this->combinations_factory + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Combinations/Factory.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Combinations/Factory.php new file mode 100644 index 000000000000..d6f13e896c44 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Combinations/Factory.php @@ -0,0 +1,307 @@ +persistence_factory, + $type_generic_properties->getAnswerFormId(), + $combinations_enabled, + !$combinations_enabled || $gaps === null || $query === null + ? [] + : $this->retrieveCombinationsFromQuery( + $type_generic_properties + ->getDefinition() + ->getTableDefinitions() + ->getTableSubNameSpace(), + $this->retrieveMatchingValuesFromQuery( + $gaps, + $query + ), + $query + ) + ); + } + + /** + * @param array $gap_ids + */ + public function buildNewCombination( + Gaps $gaps, + array $gap_ids, + ): Combination { + $combination_id = $this->uuid_factory->uuid4(); + return $this->buildCombination( + $combination_id, + null, + array_map( + fn(string $v): MatchingValue => new MatchingValue( + $combination_id, + $gaps->getGapById( + $this->uuid_factory->fromString($v) + ) + ), + $gap_ids + ) + ); + } + + /** + * @param array<\ILIAS\Questions\AnswerFormTypes\Cloze\Properties\Combinations\MatchingValue> $matching_values + */ + public function buildCombination( + Uuid $combination_id, + ?float $points, + array $matching_values + ): Combination { + return new Combination( + $combination_id, + $points, + $matching_values + ); + } + + /** + * @param array $values_array + */ + public function buildMatchingValuesFromForm( + Properties $properties, + Uuid $combination_id, + array $values_array + ): array { + return array_reduce( + array_keys($values_array), + function (array $c, string $v) use ( + $properties, + $values_array, + $combination_id + ): array { + $gap = $properties->getGaps()->getGapById( + $this->uuid_factory->fromString($v) + ); + + $range = Range::tryFrom($values_array[$v]); + $answer_option = $this->retrieveAnswerOptionForFormValues( + $gap, + $values_array[$v], + $range + ); + + if ($answer_option === null) { + return $c; + } + + $c[] = new MatchingValue( + $combination_id, + $gap, + $answer_option, + $range + ); + return $c; + }, + [] + ); + } + + public function buildCombinationFromCarryValue( + string $carry, + Properties $properties + ): Combination { + $values_array = json_decode($carry, true); + $combination_id = $this->uuid_factory->fromString( + array_key_first($values_array) + ); + + return new Combination( + $combination_id, + null, + array_map( + fn(string $v): MatchingValue => new MatchingValue( + $combination_id, + $properties->getGaps()->getGapById( + $this->uuid_factory->fromString($v) + ) + ), + $values_array[$combination_id->toString()] + ) + ); + } + + private function retrieveCombinationsFromQuery( + TableSubNameSpace $table_sub_name_space, + array $matching_values, + Query $query + ): array { + return $query->retrieveCurrentRecord( + $this->persistence_factory->table( + $query->getTableNameBuilder( + $table_sub_name_space + ), + AnswerFormSpecificTableTypes::Additional, + $this->table_definitions->getCombinationsTableIdentifier() + ), + $query->getRefinery()->custom()->transformation( + fn(array $vs): array => $this->buildCombinationsFromQuery( + array_filter( + $vs, + fn(array $v): bool => $v['answer_form_id'] !== null + ), + $matching_values + ) + ) + ); + } + + private function buildCombinationsFromQuery( + array $values, + array $matching_values + ): array { + if ($values === []) { + return []; + } + + return array_reduce( + $values, + function (array $c, array $v) use ($matching_values): array { + if (array_key_exists($v['id'], $c)) { + return $c; + } + + $c[$v['id']] = new Combination( + $this->uuid_factory->fromString($v['id']), + $v['points'], + isset($matching_values[$v['id']]) + ? array_values($matching_values[$v['id']]) + : null + ); + + return $c; + }, + [] + ); + } + + private function retrieveMatchingValuesFromQuery( + Gaps $gaps, + Query $query + ): array { + return $query->retrieveCurrentRecord( + $this->persistence_factory->table( + $query->getTableNameBuilder( + $this->table_definitions->getTableSubNameSpace(), + ), + AnswerFormSpecificTableTypes::Additional, + $this->table_definitions->getCombinationToAnswerOptionsTableIdentifier() + ), + $query->getRefinery()->custom()->transformation( + function (array $vs) use ($gaps): array { + return $this->buildMatchingValuesArray( + $gaps, + $vs + ); + } + ) + ); + } + + private function retrieveAnswerOptionForFormValues( + Gap $gap, + string $value, + ?Range $range + ): AnswerOption { + if ($range === null) { + return $gap->getAnswerOptions() + ->getAnswerOptionById( + $this->uuid_factory->fromString($value) + ); + } + + $answer_options_awarding_points = $gap + ->getAnswerOptions() + ->getAnswerOptionsAwardingPoints(); + + return array_shift($answer_options_awarding_points); + } + + private function buildMatchingValuesArray( + Gaps $gaps, + array $values + ): array { + return array_reduce( + $values, + function (array $c, array $v) use ($gaps): array { + if (isset($c[$v['combination_id']][$v['gap_id']])) { + return $c; + } + + if (!array_key_exists($v['combination_id'], $c)) { + $c[$v['combination_id']] = []; + } + + $gap = $gaps->getGapById( + $this->uuid_factory->fromString($v['gap_id']) + ); + + $c[$v['combination_id']][$v['gap_id']] = new MatchingValue( + $this->uuid_factory->fromString($v['combination_id']), + $gap, + $gap->getAnswerOptions() + ->getAnswerOptionById( + $this->uuid_factory->fromString($v['answer_option_id']) + ), + Range::tryFrom($v['in_range'] ?? '') + ); + + return $c; + }, + [] + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Combinations/MatchingValue.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Combinations/MatchingValue.php new file mode 100644 index 000000000000..519c536430a2 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Combinations/MatchingValue.php @@ -0,0 +1,134 @@ +gap; + } + + public function getAnswerOption(): ?AnswerOption + { + return $this->answer_option; + } + + public function getInRange(): ?Range + { + return $this->in_range; + } + + public function buildPresentationString( + Language $lng + ): string { + if ($this->answer_option === null) { + return ''; + } + + if ($this->in_range !== null) { + return $this->in_range->getLabel($lng); + } + + $value = $this->answer_option->getTextValue(); + if (strlen($value) < 11) { + return $value; + } + + return mb_substr($value, 0, 10) . '...'; + } + + public function clone( + UuidFactory $uuid_factory, + array $environment = [] + ): static { + $clone = clone $this; + $clone->combination_id = $environment['combination_id']; + $clone->gap = $environment['gaps']->getGapByPosition( + $this->gap->getPosition() + ); + return $clone; + } + + public function toStorage( + TableDefinitions $table_definitions, + PersistenceFactory $persistence_factory, + TableNameBuilder $table_names_builder + ): Replace { + if ($this->answer_option === null) { + throw new \UnexpectedValueException( + 'A MatchingValue without AnswerOption cannot be stored.' + ); + } + + $table_type = AnswerFormSpecificTableTypes::Additional; + return $persistence_factory->replace( + $table_definitions->getColumns( + $table_names_builder, + $table_type, + $table_definitions->getCombinationToAnswerOptionsTableIdentifier() + ), + [ + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->combination_id->toString() + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->gap->getAnswerInputId()->toString() + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->answer_option?->getAnswerOptionId()->toString() + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->in_range?->value + ) + ] + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Combinations/Overview.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Combinations/Overview.php new file mode 100644 index 000000000000..11b269ce6bce --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Combinations/Overview.php @@ -0,0 +1,347 @@ +buildSetCombinationGapsModal(); + $content = [ + $this->environment->getUIFactory()->button()->standard( + $this->environment->getLanguage()->txt('add_gap_combination'), + $modal->getShowSignal() + ), + $modal, + $this->buildTable() + ]; + if ($this->modal !== null) { + $content[] = $this->modal; + } + return $content; + } + + #[\Override] + public function getRows( + DataRowBuilder $row_builder, + array $visible_column_ids, + Range $range, + Order $order, + mixed $additional_viewcontrol_data, + mixed $filter_data, + mixed $additional_parameters + ): \Generator { + yield from $this->environment->getAnswerFormProperties() + ->getCombinations()->toTableRows( + $this->environment->getLanguage(), + $row_builder + ); + } + + #[\Override] + public function getTotalRowCount( + mixed $additional_viewcontrol_data, + mixed $filter_data, + mixed $additional_parameters + ): ?int { + return $this->environment->getAnswerFormProperties() + ->getCombinations()->getNumberOfCombinations(); + } + + public function doAction(): Async|self|Properties + { + return match ($this->environment->getSubAction()) { + self::SUB_ACTION_SET_COMBINATION_VALUES => $this->processSetCombinationGapsModal(), + self::SUB_ACTION_DELETE_COMBINATION => $this->deleteCombination(), + self::SUB_ACTION_SAVE => $this->processSetCombinationValues(), + default => $this->buildAction() + }; + } + + private function buildTable(): DataTable + { + return $this->environment->getUIFactory()->table()->data( + $this, + $this->environment->getLanguage()->txt('gap_combinations'), + $this->getColumns() + )->withActions($this->getActions()) + ->withRequest($this->environment->getHttpServices()->request()); + } + + private function getColumns(): array + { + $cf = $this->environment->getUIFactory()->table()->column(); + return [ + 'gaps' => $cf->text($this->environment->getLanguage()->txt('gaps')), + 'values' => $cf->text($this->environment->getLanguage()->txt('values')), + 'available_points' => $cf->number($this->environment->getLanguage()->txt('points'))->withDecimals(2) + ]; + } + + private function getActions(): array + { + $af = $this->environment->getUIFactory()->table()->action(); + return [ + $af->single( + $this->environment->getLanguage()->txt('edit'), + $this->environment + ->withSubActionParameter(self::SUB_ACTION_JUMP_TO_SET_COMBINATION_VALUES) + ->getUrlBuilder(), + $this->environment->getTableRowIdToken() + )->withAsync(true), + $af->single( + $this->environment->getLanguage()->txt('delete'), + $this->environment + ->withSubActionParameter(self::SUB_ACTION_CONFIRM_DELETE_COMBINATION) + ->getUrlBuilder(), + $this->environment->getTableRowIdToken() + )->withAsync(true) + ]; + } + + private function buildAction(): Async + { + $affected_item = $this->environment->getAnswerFormProperties() + ->getCombinations()->getCombinationById( + $this->environment->getTableRowIds()[0] + ); + + if ($affected_item === null) { + return $this->buildNoItemsSelectedAsync(); + } + + return $this->environment->getPresentationFactory()->getAsync( + match ($this->environment->getSubAction()) { + self::SUB_ACTION_JUMP_TO_SET_COMBINATION_VALUES => + $this->buildSetCombinationValuesModal( + $this->buildInputsBuilder($affected_item) + ), + self::SUB_ACTION_CONFIRM_DELETE_COMBINATION => + $this->confirmDeleteCombination($affected_item) + } + ); + } + + private function buildNoItemsSelectedAsync(): Async + { + return new Async( + $this->environment->getHttpServices(), + $this->environment->getUIFactory()->messageBox() + ->failure('no_combination_selected') + ); + } + + private function buildSetCombinationGapsModal(): RoundTripModal + { + $properties = $this->environment->getAnswerFormProperties(); + $gaps = $properties->getGaps(); + return $this->environment->getUIFactory()->modal()->roundtrip( + $this->environment->getLanguage()->txt('add_gap_combination'), + $properties->getClozeText()->buildPanelForEditing( + $this->environment->getUIFactory(), + $this->environment->getLanguage(), + $gaps, + $properties->getLegacyClozeText() + ), + [ + 'combination' => $gaps->buildGapsMultiSelect( + $this->environment->getLanguage()->txt('select_gaps_for_combination'), + $this->environment->getUIFactory()->input()->field() + )->withRequired(true) + ->withAdditionalTransformation( + $this->environment->getRefinery()->custom()->constraint( + fn(array $v): bool => count($v) > 1, + $this->environment->getLanguage()->txt('combination_needs_more_than_one') + ) + )->withAdditionalTransformation( + $this->environment->getRefinery()->custom()->transformation( + fn(array $v): Combination => $this->combinations_factory + ->buildNewCombination($gaps, $v) + ) + ) + ], + $this->environment + ->withSubActionParameter(self::SUB_ACTION_SET_COMBINATION_VALUES) + ->getUrlBuilder() + ->buildURI() + ->__toString() + )->withSubmitLabel($this->environment->getLanguage()->txt('next')); + } + + private function processSetCombinationGapsModal(): self + { + $clone = clone $this; + + $set_gaps_modal = $clone->buildSetCombinationGapsModal() + ->withRequest($clone->environment->getHttpServices()->request()); + $data = $set_gaps_modal->getData(); + + if ($data === null) { + $clone->modal = $set_gaps_modal->withOnLoad($set_gaps_modal->getShowSignal()); + return $clone; + } + + $set_values_modal = $clone->buildSetCombinationValuesModal( + $this->buildInputsBuilder($data['combination']) + ); + $clone->modal = $set_values_modal->withOnLoad($set_values_modal->getShowSignal()); + return $clone; + } + + private function buildSetCombinationValuesModal( + InputsBuilder $inputs_builder + ): RoundTripModal { + $properties = $this->environment->getAnswerFormProperties(); + $gaps = $properties->getGaps(); + + return $this->environment->getUIFactory()->modal()->roundtrip( + $this->environment->getLanguage()->txt('edit'), + $properties->getClozeText()->buildPanelForEditing( + $this->environment->getUIFactory(), + $this->environment->getLanguage(), + $gaps, + $properties->getLegacyClozeText() + ), + [ + 'values_awarding_points' => $inputs_builder->getInputs() + ], + $this->environment->withSubActionParameter(self::SUB_ACTION_SAVE) + ->getUrlBuilder() + ->buildURI() + ->__toString() + ); + } + + private function processSetCombinationValues(): self|Properties + { + $inputs_builder = $this->buildInputsBuilder(null); + $set_values_modal = $this->buildSetCombinationValuesModal($inputs_builder) + ->withRequest($this->environment->getHttpServices()->request()); + $data = $set_values_modal->getData(); + if ($data === null) { + $this->modal = $set_values_modal->withOnLoad($set_values_modal->getShowSignal()); + $inputs_builder->persistCarry(); + return $this; + } + + return $data['values_awarding_points']; + } + + private function confirmDeleteCombination( + Combination $affected_item + ): InterruptiveModal { + return $this->environment->getUIFactory()->modal()->interruptive( + $this->environment->getLanguage()->txt('confirm'), + $this->environment->getLanguage()->txt('delete_combination'), + $this->environment->withSubActionParameter( + self::SUB_ACTION_DELETE_COMBINATION + )->getUrlBuilder() + ->withParameter( + $this->environment->getTableRowIdToken(), + [$affected_item->getId()->toString()] + )->buildURI()->__toString() + ); + } + + private function deleteCombination(): Properties + { + $combination_identifier = $this->environment->getTableRowIds(); + if ($combination_identifier === []) { + return $this->environment->getAnswerFormProperties(); + } + + /** @var \ILIAS\Questions\AnswerFormTypes\Cloze\Properties\Properties $answer_form_properties */ + $answer_form_properties = $this->environment->getAnswerFormProperties(); + + return $answer_form_properties->withCombinations( + $answer_form_properties->getCombinations()->withoutCombination( + $combination_identifier[0] + ) + ); + } + + private function buildInputsBuilder( + ?Combination $combination, + ): InputsBuilderSession { + $builder = $this->environment->getPresentationFactory()->getSessionBasedInputsBuilder( + $this->environment->getRefinery()->custom()->transformation( + function (?string $v) use ($combination): ?Section { + $properties = $this->environment->getAnswerFormProperties(); + if ($combination === null) { + $combination = $this->combinations_factory + ->buildCombinationFromCarryValue( + $v, + $properties + ); + } + + return $combination?->buildPointsInputs( + $this->environment->getUIFactory()->input()->field(), + $this->environment->getRefinery(), + $this->environment->getLanguage(), + $this->combinations_factory, + $properties + ); + } + ) + ); + + if ($combination === null) { + return $builder; + } + + $builder_with_string = $builder->withCarry($combination->buildCarryString()); + $builder_with_string->persistCarry(); + return $builder_with_string; + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Definitions/ScoringIdentical.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Definitions/ScoringIdentical.php new file mode 100644 index 000000000000..5a1903432530 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Definitions/ScoringIdentical.php @@ -0,0 +1,68 @@ +txt($this->value); + } + + public static function buildInput( + Language $lng, + FieldFactory $ff, + Refinery $refinery, + self $default_value + ): Select { + return $ff->select( + $lng->txt('scoring_of_identical_responses'), + self::buildOptionsList($lng) + )->withRequired(true) + ->withAdditionalTransformation( + $refinery->custom()->transformation( + fn(string $v): self => self::tryFrom($v) ?? $default_value + ) + ); + } + + private static function buildOptionsList( + Language $lng + ): array { + return array_reduce( + self::cases(), + function (array $c, self $v) use ($lng): array { + $c[$v->value] = $lng->txt($v->value); + return $c; + }, + [] + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Factory.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Factory.php new file mode 100644 index 000000000000..0d22027a12f0 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Factory.php @@ -0,0 +1,185 @@ +getAnswerFormId(), + $type_generic_properties->getQuestionId(), + $type_generic_properties->getDefinition(), + $type_generic_properties->getAvailablePoints(), + $this->cloze_text_factory->buildFromTextString( + $type_generic_properties->getAdditionalText() + ), + $type_generic_properties->getAdditionalTextLegacy(), + ScoringIdentical::ScoreAll, + $this->gaps_factory->getEmptyGapsObject( + $type_generic_properties->getAnswerFormId() + ), + $this->combinations_factory->getCombinations( + $type_generic_properties, + false + ) + ); + } + + [ + 'scoring_identical_responses' => $scoring_identical_responses, + 'combinations_enabled' => $combinations_enabled + ] = $query->retrieveCurrentRecord( + $this->persistence_factory->table( + $query->getTableNameBuilder( + $type_generic_properties + ->getDefinition() + ->getTableDefinitions() + ->getTableSubNameSpace() + ), + AnswerFormSpecificTableTypes::TypeSpecificAnswerForms + ), + $query->getRefinery()->custom()->transformation( + function (array $vs) use ($type_generic_properties): array { + $values = $this->retrieveFirstMatchingRowFromDBRecords( + $type_generic_properties->getAnswerFormId()->toString(), + $vs + ); + return [ + 'scoring_identical_responses' => ScoringIdentical::tryFrom($values['scoring_identical_responses']), + 'combinations_enabled' => $values['combinations_enabled'] === 1 + ]; + } + ) + ); + + $gaps = $this->gaps_factory->fromDatabase( + $this->persistence_factory, + $type_generic_properties + ->getDefinition() + ->getTableDefinitions() + ->getTableSubNameSpace(), + $type_generic_properties->getAnswerFormId(), + $query + ); + + return new Properties( + $type_generic_properties->getAnswerFormId(), + $type_generic_properties->getQuestionId(), + $type_generic_properties->getDefinition(), + $type_generic_properties->getAvailablePoints(), + $this->cloze_text_factory->buildFromTextString( + $type_generic_properties->getAdditionalText() + ), + $type_generic_properties->getAdditionalTextLegacy(), + $scoring_identical_responses, + $gaps, + $this->combinations_factory->getCombinations( + $type_generic_properties, + $combinations_enabled, + $gaps, + $query + ) + ); + } + + public function fromBasicEditingForm( + Properties $properties, + ClozeText $cloze_text, + ScoringIdentical $scoring_of_identical_responses, + bool $combinations_enabled + ): Properties { + $updated_properties = $properties + ->withScoringOfIdenticalResponses($scoring_of_identical_responses) + ->withCombinations( + $properties->getCombinations()->withCombinationsEnabled( + $combinations_enabled + ) + ); + + if ($updated_properties->getLegacyClozeText() !== '' + && $cloze_text->getRawRepresentation() === '') { + return $updated_properties; + } + + $updated_gaps = $cloze_text->updateGapsFromMarkdown( + $properties->getAnswerFormId(), + $properties->getGaps() + ); + + return $updated_properties + ->withClozeText( + $cloze_text->withIdsOfNewGapsInClozeText( + $updated_gaps->getIncompleteGaps() + ) + )->withGaps($updated_gaps); + } + + public function fromCarry( + Properties $properties, + ?string $carry + ): Properties { + if ($carry === null + || !is_array( + ($carry_array = json_decode($carry, true)) + )) { + return $properties; + } + + return $properties->withValuesFromCarry( + $this->cloze_text_factory, + $carry_array + ); + } + + private function retrieveFirstMatchingRowFromDBRecords( + string $answer_form_id, + array $vs + ): ?array { + foreach ($vs as $row) { + if ($row['answer_form_id'] === $answer_form_id) { + return $row; + } + } + return null; + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/AnswerOptions/AnswerOption.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/AnswerOptions/AnswerOption.php new file mode 100644 index 000000000000..85de10e513cd --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/AnswerOptions/AnswerOption.php @@ -0,0 +1,204 @@ +answer_option_id; + } + + public function getPosition(): ?int + { + return $this->position; + } + + public function withPosition( + int $position + ): self { + $clone = clone $this; + $clone->position = $position; + return $clone; + } + + public function getTextValue(): string + { + return $this->text_value; + } + + public function withTextValue( + string $text_value + ): self { + $clone = clone $this; + $clone->text_value = $text_value; + return $clone; + } + + public function getLowerLimit(): ?float + { + return $this->lower_limit; + } + + public function withLowerLimit( + float $lower_limit + ): self { + $clone = clone $this; + $clone->lower_limit = $lower_limit; + return $clone; + } + + public function getUpperLimit(): ?float + { + return $this->upper_limit; + } + + public function withUpperLimit( + ?float $upper_limit + ): self { + $clone = clone $this; + $clone->upper_limit = $upper_limit; + return $clone; + } + + public function getAvailablePoints(): ?float + { + return $this->available_points; + } + + public function withAvailablePoints( + ?float $available_points + ): self { + $clone = clone $this; + $clone->available_points = $available_points; + return $clone; + } + + public function toCarry(): array + { + $values = [ + self::FORM_KEY_ID => $this->getAnswerOptionId()->toString(), + self::FORM_KEY_POSITION => (string) $this->getPosition(), + self::FORM_KEY_TEXT_VALUE => $this->getTextValue() + ]; + + if ($this->getLowerLimit() !== null) { + $values[self::FORM_KEY_LOWER_LIMIT] = (string) $this->getLowerLimit(); + } + + if ($this->getUpperLimit() !== null) { + $values[self::FORM_KEY_UPPER_LIMIT] = (string) $this->getUpperLimit(); + } + + if ($this->getAvailablePoints() !== null) { + $values[self::FORM_KEY_AVAILABLE_POINTS] = (string) $this->getAvailablePoints(); + } + + return $values; + } + + public function clone( + UuidFactory $uuid_factory, + array $environment = [] + ): static { + $clone = clone $this; + $clone->answer_input_id = $environment['answer_input_id']; + $clone->answer_option_id = $uuid_factory->uuid4(); + return $clone; + } + + public function buildReplace( + PersistenceFactory $persistence_factory, + ?Replace $replace, + array $columns + ): Replace { + if ($replace === null) { + return $persistence_factory->replace( + $columns, + $this->buildValuesForGapReplace($persistence_factory) + ); + } + + return $replace->withAdditionalValues( + $this->buildValuesForGapReplace($persistence_factory) + ); + } + + private function buildValuesForGapReplace( + PersistenceFactory $persistence_factory + ): array { + return [ + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->answer_option_id->toString() + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->answer_input_id->toString() + ), + $persistence_factory->value( + FieldDefinition::T_INTEGER, + $this->position + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->text_value + ), + $persistence_factory->value( + FieldDefinition::T_FLOAT, + $this->available_points + ), + $persistence_factory->value( + FieldDefinition::T_FLOAT, + $this->lower_limit + ), + $persistence_factory->value( + FieldDefinition::T_FLOAT, + $this->upper_limit + ) + ]; + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/AnswerOptions/AnswerOptions.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/AnswerOptions/AnswerOptions.php new file mode 100644 index 000000000000..ea7be6a34b1e --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/AnswerOptions/AnswerOptions.php @@ -0,0 +1,390 @@ +answer_options_awarding_points = $this->buildAnswerOptionsAwardingPointsFromAnswerOptions($answer_options); + } + + public function getMaxAvailablePoints(): float + { + return array_reduce( + $this->answer_options_awarding_points, + function (?float $c, AnswerOption $v): ?float { + if ($v->getAvailablePoints() === null) { + return $c; + } + + if ($c === null) { + return $v->getAvailablePoints(); + } + + return max($c, $v->getAvailablePoints()); + } + ); + } + + public function isIncomplete(): bool + { + return $this->is_incomplete + || $this->answer_options === [] + || $this->answer_options_awarding_points === []; + } + + public function withIsIncomplete( + bool $is_incomplete + ): self { + $clone = clone $this; + $clone->is_incomplete = $is_incomplete; + return $clone; + } + + public function getAnswerOptionById( + Uuid $answer_option_id + ): ?AnswerOption { + $answer_option_id_string = $answer_option_id->toString(); + return array_find( + $this->answer_options, + function (AnswerOption $v) use ($answer_option_id_string): bool { + $id_string = $v->getAnswerOptionId()->toString(); + return $id_string === $answer_option_id_string; + } + ); + } + + public function getAnswerOptionByTextValue( + string $text_value + ): ?AnswerOption { + return array_find( + $this->answer_options, + fn(AnswerOption $v): bool => $v->getTextValue() === $text_value + ); + } + + public function getAnswerOptionForPositionOrNew( + int $position + ): AnswerOption { + return $this->answer_options[$position] + ?? $this->factory->getDefaultAnswerOptionForPosition( + $this->answer_input_id, + $position + ); + } + + public function getBestAnswerOption(): ?AnswerOption + { + return array_reduce( + $this->answer_options_awarding_points, + fn(?AnswerOption $c, AnswerOption $v): ?AnswerOption + => $v->getAvailablePoints() > $c?->getAvailablePoints() + ? $v + : $c + ); + } + + public function getTagsArrayFromAnswerOptions(): array + { + return array_reduce( + $this->answer_options, + function (array $c, AnswerOption $v): array { + if ($v->getTextValue() === '') { + return $c; + } + $c[] = $v->getTextValue(); + return $c; + }, + [] + ); + } + + public function getAnswerOptionsAwardingPoints(): array + { + return $this->answer_options_awarding_points; + } + + public function withAnswerOptionsAwardingPoints( + array $options + ): self { + $clone = clone $this; + $clone->answer_options_awarding_points = array_reduce( + $options, + function (array $c, string $v): array { + $answer_option = $this->retrieveAnswerOptionByTextValue($v); + if ($answer_option !== null) { + $c[] = $answer_option; + } + return $c; + }, + [] + ); + return $clone; + } + + public function withAnswerOptions( + array $answer_options + ): self { + $clone = clone $this; + $clone->answer_options = $answer_options; + return $clone; + } + + public function withAnswerOptionsFromTags( + array $tags + ): self { + $clone = clone $this; + $position = 0; + $clone->answer_options = array_map( + function (string $v) use (&$position): AnswerOption { + return $this->buildAnswerOptionFromTag( + $position++, + $v + ); + }, + $tags + ); + return $clone; + } + + public function withAnswerOptionsWithAddedPointsFromForm( + Refinery $refinery, + array $values_from_form + ): self { + $clone = clone $this; + $clone->answer_options = array_map( + function (AnswerOption $v) use ($refinery, $values_from_form): AnswerOption { + $answer_option_id = $v->getAnswerOptionId()->toString(); + if (array_key_exists($answer_option_id, $values_from_form)) { + return $v->withAvailablePoints( + $refinery->byTrying([ + $refinery->kindlyTo()->float(), + $refinery->always(null) + ])->transform($values_from_form[$answer_option_id]) + ); + } + + return $v; + }, + $clone->answer_options + ); + $clone->answer_options_awarding_points = $clone + ->buildAnswerOptionsAwardingPointsFromAnswerOptions($clone->answer_options); + return $clone; + } + + public function buildArrayForSelectInput( + Transformation $shuffle_transformation + ): array { + return array_reduce( + $shuffle_transformation->transform($this->answer_options), + function (array $c, AnswerOption $v): array { + $c[$v->getAnswerOptionId()->toString()] = $v->getTextValue(); + return $c; + }, + [] + ); + } + + public function toCarry(): array + { + return [ + self::KEY_IS_INCOMPLETE => $this->is_incomplete ? 1 : 0, + self::KEY_ANSWER_OPIONS => array_map( + fn(AnswerOption $v): array => $v->toCarry(), + $this->answer_options + ), + self::KEY_ANSWER_OPTIONS_AWARDING_POINTS => array_map( + fn(AnswerOption $v): string => $v->getAnswerOptionId()->toString(), + $this->answer_options_awarding_points + ) + ]; + } + + public function withValuesFromCarry( + array $carry + ): self { + $clone = clone $this; + $clone->is_incomplete = $carry[self::KEY_IS_INCOMPLETE] === 1; + $clone->answer_options = array_map( + fn(array $vs): AnswerOption => $this->factory->buildAnswerOption( + $vs[AnswerOption::FORM_KEY_ID], + $this->answer_input_id, + (int) $vs[AnswerOption::FORM_KEY_POSITION], + $vs[AnswerOption::FORM_KEY_TEXT_VALUE], + $vs[AnswerOption::FORM_KEY_LOWER_LIMIT] ?? null, + $vs[AnswerOption::FORM_KEY_UPPER_LIMIT] ?? null, + $vs[AnswerOption::FORM_KEY_AVAILABLE_POINTS] ?? null + ), + $carry[self::KEY_ANSWER_OPIONS] ?? [] + ); + + $clone->answer_options_awarding_points = array_filter( + $clone->answer_options, + fn(AnswerOption $v): bool => in_array( + $v->getAnswerOptionId()->toString(), + $carry[self::KEY_ANSWER_OPTIONS_AWARDING_POINTS] ?? [] + ) + ); + return $clone; + } + + public function getEditPointsInputs( + FieldFactory $ff, + \Closure $build_label, + ?array $answer_options_awarding_points = null + ): array { + return array_reduce( + $answer_options_awarding_points ?? $this->answer_options, + function (array $c, AnswerOption $v) use ($ff, $build_label): array { + $c[$v->getAnswerOptionId()->toString()] = $ff->numeric($build_label($v)) + ->withStepSize(0.01) + ->withValue($v->getAvailablePoints()); + return $c; + }, + [] + ); + } + + #[\Override] + public function clone( + UuidFactory $uuid_factory, + array $environment = [] + ): static { + $clone = clone $this; + $clone->answer_input_id = $environment['answer_input_id']; + $clone->answer_options = array_map( + fn(AnswerOption $v): AnswerOption => $v->clone( + $uuid_factory, + $environment + ), + $this->answer_options + ); + return $clone; + } + + public function buildReplace( + ?Replace $replace, + TableDefinitions $table_definitions, + PersistenceFactory $persistence_factory, + TableNameBuilder $table_names_builder + ): Replace { + return array_reduce( + $this->answer_options, + fn(?Replace $c, AnswerOption $v): Replace => $v->buildReplace( + $persistence_factory, + $c, + $table_definitions->getColumns( + $table_names_builder, + AnswerFormSpecificTableTypes::AnswerOptions + ) + ), + $replace + ); + } + + public function buildDelete( + TableDefinitions $table_definitions, + PersistenceFactory $persistence_factory, + TableNameBuilder $table_names_builder + ): Delete { + $table_type = AnswerFormSpecificTableTypes::AnswerOptions; + + return $persistence_factory->delete( + $persistence_factory->table( + $table_names_builder, + $table_type + ), + [ + $persistence_factory->where( + $table_definitions->getForeignKeyColumn( + $table_names_builder, + $table_type + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->answer_input_id->toString() + ) + ) + ] + ); + } + + private function buildAnswerOptionsAwardingPointsFromAnswerOptions( + array $answer_options + ): array { + return array_filter( + $answer_options, + fn(AnswerOption $v): bool => $v->getAvailablePoints() > 0.0 + ); + } + + private function buildAnswerOptionFromTag( + int $position, + string $text_value + ): AnswerOption { + $answer_option = $this->retrieveAnswerOptionByTextValue($text_value) + ?? $this->factory->getDefaultAnswerOptionForPosition( + $this->answer_input_id, + $position + ); + + return $answer_option + ->withPosition($position) + ->withTextValue($text_value); + } + + private function retrieveAnswerOptionByTextValue( + string $value + ): ?AnswerOption { + $filtered_array = array_filter( + $this->answer_options, + fn(AnswerOption $v): bool => $v->getTextValue() === $value + ); + return array_shift($filtered_array) ?? null; + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/AnswerOptions/Factory.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/AnswerOptions/Factory.php new file mode 100644 index 000000000000..c87b3a82beac --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/AnswerOptions/Factory.php @@ -0,0 +1,147 @@ +uuid_factory->uuid4(), + $answer_input_id, + $position + ); + } + + public function buildAnswerOption( + string $answer_option_id, + Uuid $answer_input_id, + int $position, + string $text_value, + ?string $lower_limit, + ?string $upper_limit, + ?string $points + ): AnswerOption { + return new AnswerOption( + $this->uuid_factory->fromString($answer_option_id), + $answer_input_id, + $position, + $text_value, + $this->convertToFloatOrNull($lower_limit), + $this->convertToFloatOrNull($upper_limit), + $this->convertToFloatOrNull($points) + ); + } + + public function fromDatabase( + PersistenceFactory $persistence_factory, + TableSubNameSpace $table_name_specifier, + Query $query + ): array { + return $query->retrieveCurrentRecord( + $persistence_factory->table( + $query->getTableNameBuilder( + $table_name_specifier + ), + AnswerFormSpecificTableTypes::AnswerOptions + ), + $query->getRefinery()->custom()->transformation( + function (array $vs): array { + if ($vs === []) { + return []; + } + + $previous_answer_input_id = null; + $return_array = []; + $answer_options = []; + foreach ($vs as $v) { + if (array_key_exists($v['id'], $answer_options)) { + continue; + } + + if ($previous_answer_input_id !== null + && $v['answer_input_id'] !== $previous_answer_input_id) { + $return_array[$previous_answer_input_id] = new AnswerOptions( + $this, + $this->uuid_factory->fromString($previous_answer_input_id), + $answer_options + ); + $answer_options = []; + } + $previous_answer_input_id = $v['answer_input_id']; + $answer_options[$v['id']] = new AnswerOption( + $this->uuid_factory->fromString($v['id']), + $this->uuid_factory->fromString($v['answer_input_id']), + $v['position'], + $v['text_value'], + $v['lower_limit'], + $v['upper_limit'], + $v['points'] + ); + } + + $return_array[$v['answer_input_id']] = new AnswerOptions( + $this, + $this->uuid_factory->fromString($v['answer_input_id']), + $answer_options + ); + + return $return_array; + } + ) + ); + } + + private function convertToFloatOrNull( + ?string $value + ): ?float { + return $this->refinery->byTrying([ + $this->refinery->kindlyTo()->float(), + $this->refinery->always(null) + ])->transform($value); + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/AnswerOptions/Upload.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/AnswerOptions/Upload.php new file mode 100755 index 000000000000..4f36758c8459 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/AnswerOptions/Upload.php @@ -0,0 +1,211 @@ +environment + ->withSubActionParameter(self::SUB_ACTION_UPLOAD) + ->getUrlBuilder() + ->buildURI() + ->__toString(); + } + + #[\Override] + public function getFileRemovalURL(): string + { + return $this->environment + ->withSubActionParameter(self::SUB_ACTION_REMOVE) + ->getUrlBuilder() + ->buildURI() + ->__toString(); + } + + #[\Override] + public function getExistingFileInfoURL(): string + { + return $this->environment + ->withSubActionParameter(self::SUB_ACTION_INFO) + ->getUrlBuilder() + ->buildURI() + ->__toString(); + } + + #[\Override] + public function getInfoForExistingFiles( + array $file_ids + ): array { + return array_map( + fn($file_id): FileInfoResult => $this->getInfoResult($file_id), + $file_ids + ); + } + + #[\Override] + public function getInfoResult( + string $identifier + ): ?FileInfoResult { + return new BasicFileInfoResult( + $this->getFileIdentifierParameterName(), + $identifier, + 'unknown', + 0, + 'unknown' + ); + } + + #[\Override] + public function supportsChunkedUploads(): bool + { + return false; + } + + #[\Override] + public function can( + string $sub_action + ): bool { + $has_file_identifier = $this->hasFileIdentifier(); + + return $sub_action === self::SUB_ACTION_UPLOAD + || $sub_action === self::SUB_ACTION_REMOVE && $has_file_identifier + || $sub_action === self::SUB_ACTION_INFO && $has_file_identifier; + } + + #[\Override] + public function do( + string $action + ): Async { + $response = match($action) { + self::SUB_ACTION_UPLOAD => $this->upload(), + self::SUB_ACTION_REMOVE => $this->remove(), + self::SUB_ACTION_INFO => $this->info(), + default => '' + }; + + return $this->environment->getPresentationFactory()->getAsync($response); + } + + private function upload(): string + { + $this->upload->process(); + + $result_array = $this->upload->getResults(); + $result = end($result_array); + + if (!($result instanceof UploadResult) || !$result->isOK()) { + return new BasicHandlerResult( + $this->getFileIdentifierParameterName(), + HandlerResult::STATUS_FAILED, + '', + $result->getStatus()->getMessage() + ); + } + + $content = base64_encode(file_get_contents($result->getPath())); + unlink($result->getPath()); + + return json_encode( + new BasicHandlerResult( + $this->getFileIdentifierParameterName(), + HandlerResult::STATUS_OK, + $content, + 'file upload OK' + ) + ); + } + + private function remove(): string + { + return json_encode( + new BasicHandlerResult( + $this->getFileIdentifierParameterName(), + HandlerResult::STATUS_OK, + $this->retrieveFileIdentifier(), + 'We just don\'t do anything here.' + ) + ); + } + + private function info(): string + { + return json_encode( + $this->getInfoResult( + $this->retrieveFileIdentifier() + ) + ); + } + + private function hasFileIdentifier(): bool + { + return $this->environment + ->getHttpServices() + ->wrapper() + ->query() + ->has($this->getFileIdentifierParameterName()); + } + + private function retrieveFileIdentifier(): string + { + if (!$this->hasFileIdentifier()) { + return ''; + } + + return $this->environment + ->getHttpServices() + ->wrapper() + ->query() + ->retrieve( + $this->getFileIdentifierParameterName(), + $this->environment->getRefinery()->kindlyTo()->string() + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/Edit.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/Edit.php new file mode 100644 index 000000000000..97a6243b7859 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/Edit.php @@ -0,0 +1,385 @@ +file_upload, + $environment + ); + if ($upload_handler->can($sub_action)) { + return $upload_handler->do($sub_action); + } + + return match ($sub_action) { + self::SUB_ACTION_SET_GAP_TYPES, + self::SUB_ACTION_JUMP_TO_SET_GAP_TYPES + => $this->buildGapTypesFormWithCarry( + $environment, + $environment->getAnswerFormProperties(), + $sub_action + ), + self::SUB_ACTION_BACK_TO_EDIT_BASIC_PROPERTIES + => $this->backToEditBasicProperties( + $environment, + $sub_action + ), + self::SUB_ACTION_BACK_TO_SET_GAP_TYPES + => $this->backToGapTypesForm( + $environment, + $sub_action + ), + self::SUB_ACTION_SET_ANSWER_OPTIONS + => $this->forwardToAnswerOptionsForm( + $environment, + $sub_action + ), + self::SUB_ACTION_BACK_TO_SET_ANSWER_OPTIONS, + self::SUB_ACTION_JUMP_TO_SET_ANSWER_OPTIONS + => $this->buildAnswerOptionsFormWithCarry( + $environment, + $environment->getAnswerFormProperties(), + $sub_action + ), + self::SUB_ACTION_PROCESS_SET_ANSWER_OPTIONS + => $this->processAnswerOptionsForm( + $environment, + $sub_action + ) + }; + } + + private function backToEditBasicProperties( + Environment $environment, + string $sub_action + ): EditForm|string { + $processed_form = $this->processGapTypesForm( + $environment, + $sub_action + ); + if ($processed_form instanceof EditForm) { + return $processed_form; + } + + return $processed_form->toCarry(); + } + + private function backToGapTypesForm( + Environment $environment, + string $sub_action + ): EditForm { + $processed_form = $this->processAnswerOptionsForm( + $environment, + $sub_action + ); + if ($processed_form instanceof EditForm) { + return $processed_form; + } + + return $this->buildGapTypesFormWithCarry( + $environment, + $processed_form, + $sub_action + ); + } + + private function buildGapTypesFormWithCarry( + Environment $environment, + Properties $properties, + string $sub_action + ): EditForm { + $inputs_builder = $this->buildInputsBuilderForTypesForm( + $environment + )->withCarry( + $properties->toCarry() + ); + + $inputs_builder->persistCarry(); + + return $this->buildGapTypesForm( + $environment, + $inputs_builder, + $sub_action + ); + } + + private function buildGapTypesForm( + Environment $environment, + InputsBuilderSession $inputs_builder, + string $sub_action + ): EditForm { + /** @var \ILIAS\Questions\AnswerFormTypes\Cloze\Properties\Properties $properties */ + $properties = $environment->getAnswerFormProperties(); + + $inputs_builder->persistCarry(); + + return $environment->getPresentationFactory()->getEditForm( + $inputs_builder, + $this->buildPostTarget( + $environment, + self::SUB_ACTION_SET_ANSWER_OPTIONS + ), + $sub_action === self::SUB_ACTION_JUMP_TO_SET_GAP_TYPES + || $environment->getFormStartSubAction() === self::SUB_ACTION_JUMP_TO_SET_GAP_TYPES + ? null + : $this->buildPostTarget( + $environment, + self::SUB_ACTION_BACK_TO_EDIT_BASIC_PROPERTIES + ) + )->withContentBeforeForm( + $properties->getClozeText()->buildPanelForEditing( + $environment->getUIFactory(), + $environment->getLanguage(), + $properties->getGaps(), + $properties->getLegacyClozeText() + ) + ); + } + + private function processGapTypesForm( + Environment $environment, + string $sub_action + ): EditForm|Properties { + $inputs_builder_for_types = $this->buildInputsBuilderForTypesForm( + $environment + ); + + $properties = $inputs_builder_for_types->retrieveCarry( + $this->buildRetrievePropertiesTransformation($environment) + ); + + $form = $this->buildGapTypesForm( + $environment->withAnswerFormProperties($properties), + $inputs_builder_for_types, + $sub_action + )->withRequest($environment->getHttpServices()->request()); + + $data = $form->getData(); + if ($data === null) { + $inputs_builder_for_types->persistCarry(); + return $form; + } + + return $data; + } + + private function buildInputsBuilderForTypesForm( + Environment $environment + ): InputsBuilderSession { + return $environment->getPresentationFactory()->getSessionBasedInputsBuilder( + $environment->getRefinery()->custom()->transformation( + function (?string $carry) use ($environment): Section { + $properties_from_carry = $this->properties_factory + ->fromCarry( + $environment->getAnswerFormProperties(), + $carry + ); + return $properties_from_carry->getGaps()->buildGapsTypeInputs( + $environment->getLanguage(), + $environment->getUIFactory()->input()->field(), + $this->gap_factory->getAvailableGapTypesOptionsArray( + $environment->getLanguage() + ), + $properties_from_carry, + $environment->isInCreationContext(), + $environment->getTableRowIds() + ); + } + ) + ); + } + + private function forwardToAnswerOptionsForm( + Environment $environment, + string $sub_action + ): EditForm { + $processed_form = $this->processGapTypesForm( + $environment, + $sub_action + ); + if ($processed_form instanceof EditForm) { + return $processed_form; + } + + return $this->buildAnswerOptionsFormWithCarry( + $environment, + $processed_form, + $sub_action + ); + } + + private function buildAnswerOptionsFormWithCarry( + Environment $environment, + Properties $properties, + string $sub_action + ): EditForm { + $inputs_builder = $this->buildInputsBuilderForAnswerOptionsForm( + $environment, + $properties, + )->withCarry( + $properties->toCarry() + ); + + $inputs_builder->persistCarry(); + + return $this->buildAnswerOptionsForm( + $environment->withAnswerFormProperties($properties), + $inputs_builder, + $sub_action + ); + } + + private function buildAnswerOptionsForm( + Environment $environment, + InputsBuilderSession $inputs_builder, + string $sub_action + ): EditForm { + $properties = $environment->getAnswerFormProperties(); + return $environment->getPresentationFactory()->getEditForm( + $inputs_builder, + $this->buildPostTarget( + $environment, + self::SUB_ACTION_PROCESS_SET_ANSWER_OPTIONS + ), + $sub_action === self::SUB_ACTION_JUMP_TO_SET_ANSWER_OPTIONS + || $environment->getFormStartSubAction() === self::SUB_ACTION_JUMP_TO_SET_ANSWER_OPTIONS + ? null + : $this->buildPostTarget( + $environment, + self::SUB_ACTION_BACK_TO_SET_GAP_TYPES + ) + )->withIsFinalStep(true) + ->withContentBeforeForm( + $properties->getClozeText()->buildPanelForEditing( + $environment->getUIFactory(), + $environment->getLanguage(), + $properties->getGaps(), + $properties->getLegacyClozeText() + ) + ); + } + + private function processAnswerOptionsForm( + Environment $environment, + string $sub_action + ): EditForm|Properties { + $inputs_builder_for_options = $this->buildInputsBuilderForAnswerOptionsForm( + $environment, + $environment->getAnswerFormProperties() + ); + + $form = $this->buildAnswerOptionsForm( + $environment, + $inputs_builder_for_options, + $sub_action + )->withRequest($environment->getHttpServices()->request()); + + $data = $form->getData(); + if ($data === null) { + $inputs_builder_for_options->persistCarry(); + return $form; + } + + return $data; + } + + private function buildInputsBuilderForAnswerOptionsForm( + Environment $environment, + Properties $properties + ): InputsBuilderSession { + return $environment->getPresentationFactory()->getSessionBasedInputsBuilder( + $environment->getRefinery()->custom()->transformation( + function (?string $carry) use ( + $environment, + $properties + ): Section { + $properties_from_carry = $this->properties_factory + ->fromCarry( + $properties, + $carry + ); + return $properties_from_carry->getGaps() + ->buildAnswerOptionsInputs( + $this->file_upload, + $environment, + $properties_from_carry + ); + } + ) + ); + } + + private function buildPostTarget( + Environment $environment, + string $next_step + ): URLBuilder { + return $environment + ->withSubActionParameter($next_step) + ->getUrlBuilder(); + } + + private function buildRetrievePropertiesTransformation( + Environment $environment + ): CustomTransformation { + return $environment->getRefinery()->custom()->transformation( + fn(?string $carry): Properties => $this->properties_factory + ->fromCarry( + $environment->getAnswerFormProperties(), + $carry + ) + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/Factory.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/Factory.php new file mode 100644 index 000000000000..d0147e99db3d --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/Factory.php @@ -0,0 +1,162 @@ +available_gap_types[$type->getIdentifier()] = $type; + } + } + + public function getAvailableGapTypes(): array + { + return $this->available_gap_types; + } + + public function getAvailableGapTypesOptionsArray( + Language $lng + ): array { + return array_map( + fn(Type $v) => $lng->txt("{$v->getIdentifier()}_gap"), + $this->available_gap_types + ); + } + + public function getNewGap( + Uuid $answer_form_id, + int $position, + string $id = '' + ): Gap { + $answer_input_id = $id !== '' + ? $this->uuid_factory->fromString($id) + : $this->uuid_factory->uuid4(); + + return new Gap( + $answer_input_id, + $answer_form_id, + $position, + $this->answer_options_factory->getDefaultAnswerOptions($answer_input_id) + ); + } + + public function getEmptyGapsObject( + Uuid $answer_form_id, + ): Gaps { + return new Gaps( + $this->refinery, + $this, + $answer_form_id, + [] + ); + } + + public function getGapTypeByIdentifier( + string $identifier + ): Type { + if (!array_key_exists($identifier, $this->available_gap_types)) { + throw new \InvalidArgumentException('Gap type does not exist.'); + } + return $this->available_gap_types[$identifier]; + } + + public function fromDatabase( + PersistenceFactory $persistence_factory, + TableSubNameSpace $table_name_specifier, + Uuid $answer_form_id, + Query $query + ): Gaps { + $answer_options = $this->answer_options_factory->fromDatabase( + $persistence_factory, + $table_name_specifier, + $query + ); + + return $query->retrieveCurrentRecord( + $persistence_factory->table( + $query->getTableNameBuilder( + $table_name_specifier + ), + AnswerFormSpecificTableTypes::AnswerInputs + ), + $query->getRefinery()->custom()->transformation( + function (array $vs) use ($answer_form_id, $answer_options): Gaps { + $previous_answer_input_id = null; + $gaps = []; + foreach ($vs as $v) { + if ($v['answer_form_id'] !== $answer_form_id->toString() + || $previous_answer_input_id === $v['id']) { + continue; + } + $previous_answer_input_id = $v['id']; + $gaps[] = $this->buildGapFromDBValues($v, $answer_options); + } + return new Gaps( + $this->refinery, + $this, + $answer_form_id, + $gaps + ); + } + ) + ); + } + + private function buildGapFromDBValues( + array $values, + array $answer_options + ): Gap { + $answer_input_uuid = $this->uuid_factory->fromString($values['id']); + return new Gap( + $answer_input_uuid, + $this->uuid_factory->fromString($values['answer_form_id']), + $values['position'], + $answer_options[$values['id']], + $this->getGapTypeByIdentifier($values['gap_type']), + $values['max_chars'], + $values['step_size'], + $values['text_matching_method'] === null + ? null + : TextMatchingOptions::tryFrom($values['text_matching_method']), + $values['min_autocomplete'], + $values['shuffle_answer_options'] === 1 + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/Gap.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/Gap.php new file mode 100644 index 000000000000..1cf35f5f059c --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/Gap.php @@ -0,0 +1,447 @@ + $answer_options + */ + public function __construct( + private Uuid $answer_input_id, + private Uuid $answer_form_id, + private int $position, + private AnswerOptions $answer_options, + private ?Type $type = null, + private ?int $max_chars = null, + private ?float $step_size = null, + private ?TextMatchingOptions $text_matching_method = null, + private ?int $min_autocomplete = null, + private ?bool $shuffle_answer_options = null + ) { + } + + public function getAnswerInputId(): Uuid + { + return $this->answer_input_id; + } + + public function getPosition(): int + { + return $this->position; + } + + public function withPosition( + int $position + ): self { + $clone = clone $this; + $clone->position = $position; + return $clone; + } + + public function isUndefined(): bool + { + return $this->type === null; + } + + public function getType(): ?Type + { + return $this->type; + } + + public function withType( + Type $type + ): self { + $clone = clone $this; + $clone->type = $type; + return $clone; + } + + public function getMaxChars(): ?int + { + return $this->max_chars; + } + + public function withMaxChars( + ?int $max_chars + ): self { + $clone = clone $this; + $clone->max_chars = $max_chars; + return $clone; + } + + public function getStepSize(): ?float + { + return $this->step_size; + } + + public function withStepSize( + float $step_size + ): self { + $clone = clone $this; + $clone->step_size = $step_size; + return $clone; + } + + public function getTextMatchingMethod(): ?TextMatchingOptions + { + return $this->text_matching_method; + } + + public function withTextMatchingMethod( + TextMatchingOptions $matching_method + ): self { + $clone = clone $this; + $clone->text_matching_method = $matching_method; + return $clone; + } + + public function getMinAutocomplete(): ?int + { + return $this->min_autocomplete; + } + + public function withMinAutocomplete( + int $min_autocomplete + ): self { + $clone = clone $this; + $clone->min_autocomplete = $min_autocomplete; + return $clone; + } + + public function getShuffleAnswerOptions(): ?bool + { + return $this->shuffle_answer_options; + } + + public function withShuffleAnswerOptions( + bool $shuffle_answer_options + ): self { + $clone = clone $this; + $clone->shuffle_answer_options = $shuffle_answer_options; + return $clone; + } + + public function getAnswerOptions(): AnswerOptions + { + return $this->answer_options; + } + + public function withAnswerOptions( + AnswerOptions $answer_options + ): self { + $clone = clone $this; + $clone->answer_options = $answer_options; + return $clone; + } + + public function toCarry(): array + { + $inputs = [ + self::KEY_TYPE => $this->type?->getIdentifier() ?? '', + self::KEY_POSITION => $this->position, + self::KEY_ANSWER_OPTIONS => $this->answer_options->toCarry() + ]; + + if ($this->max_chars !== null) { + $inputs[self::KEY_MAX_CHARS] = (string) $this->getMaxChars(); + } + + if ($this->step_size !== null) { + $inputs[self::KEY_STEP_SIZE] = (string) $this->getStepSize(); + } + + if ($this->text_matching_method !== null) { + $inputs[self::KEY_TEXT_MATCHING_METHOD] = $this->getTextMatchingMethod()->value; + } + + if ($this->min_autocomplete !== null) { + $inputs[self::KEY_MIN_AUTOCOMPLETE] = (string) $this->getMinAutocomplete(); + } + + if ($this->shuffle_answer_options !== null) { + $inputs[self::KEY_SHUFFLE_ANSWER_OPTIONS] = $this->getShuffleAnswerOptions() ? '1' : '0'; + } + + return $inputs; + } + + public function withValuesFromCarry( + Refinery $refinery, + Factory $gaps_factory, + array $carry + ): self { + if ($carry === null) { + return $this; + } + + $clone = clone $this; + $clone->type = $carry[self::KEY_TYPE] === '' + ? $this->type + : $gaps_factory->getGapTypeByIdentifier($carry[self::KEY_TYPE]); + $clone->position = $carry[self::KEY_POSITION]; + + $clone->max_chars = $refinery->byTrying([ + $refinery->kindlyTo()->int(), + $refinery->always($this->getMaxChars()) + ])->transform($carry[self::KEY_MAX_CHARS] ?? null); + + $clone->step_size = $refinery->byTrying([ + $refinery->kindlyTo()->float(), + $refinery->always($this->getStepSize()) + ])->transform($carry[self::KEY_STEP_SIZE] ?? null); + + $clone->text_matching_method = is_string($carry[self::KEY_TEXT_MATCHING_METHOD] ?? null) + ? TextMatchingOptions::tryFrom($carry[self::KEY_TEXT_MATCHING_METHOD]) + : $this->getTextMatchingMethod(); + + $clone->min_autocomplete = $refinery->byTrying([ + $refinery->kindlyTo()->int(), + $refinery->always($this->getMinAutocomplete()) + ])->transform($carry[self::KEY_MIN_AUTOCOMPLETE] ?? null); + + $clone->shuffle_answer_options = $refinery->byTrying([ + $refinery->kindlyTo()->bool(), + $refinery->always($this->getShuffleAnswerOptions()) + ])->transform($carry[self::KEY_SHUFFLE_ANSWER_OPTIONS] ?? null); + + $clone->answer_options = $this->answer_options + ->withValuesFromCarry($carry[self::KEY_ANSWER_OPTIONS]); + + return $clone; + } + + public function buildReplace( + ?Replace $replace, + TableDefinitions $table_definitions, + PersistenceFactory $persistence_factory, + TableNameBuilder $table_names_builder + ): Replace { + if ($this->type === null) { + throw new \UnexpectedValueException( + 'A Gap without Type cannot be stored.' + ); + } + + $table_type = AnswerFormSpecificTableTypes::AnswerInputs; + + if ($replace === null) { + return $persistence_factory->replace( + $table_definitions->getColumns( + $table_names_builder, + $table_type + ), + $this->buildValuesForGapReplace($persistence_factory) + ); + } + + return $replace->withAdditionalValues( + $this->buildValuesForGapReplace($persistence_factory) + ); + } + + public function getGapPlaceholder(): string + { + return "{{{$this->buildGapPlaceholderNameWithId()}}}"; + } + + public function buildShortenedGapName(): string + { + return self::GAP_PLACEHOLDER_NAME . '_' . $this->getShortenedAnswerInputId(); + } + + public function buildShortenedGapRepresentation(): string + { + return "[{$this->buildShortenedGapName()}]"; + } + + public function buildGapPlaceholderNameWithId(): string + { + return self::GAP_PLACEHOLDER_NAME . '_' . $this->getAnswerInputId()->toString(); + } + + public function buildParticipantViewLegacyInput( + Language $lng, + Refinery $refinery, + ?AdditionalAttemptData $attempt_data, + ?Response $response_data + ): string { + return $this->type->getParticipantViewLegacyInput( + $this, + $attempt_data, + $response_data + ); + } + + public function getEditAnswerOptionsSection( + FileUpload $file_upload, + Environment $environment + ): Section { + $section = $environment->getUIFactory()->input()->field()->section( + $this->type->getEditAnswerOptionsInputs( + $file_upload, + $environment, + $this + ), + "{$this->buildShortenedGapName()} ({$environment->getLanguage()->txt("{$this->type->getIdentifier()}_gap")})" + ); + + $edit_section_constraint = $this->type->getEditAnswerOptionsSectionConstraint(); + if ($edit_section_constraint !== null) { + $section = $section->withAdditionalTransformation($edit_section_constraint); + } + + + return $section->withAdditionalTransformation( + $this->type->getBuildGapTransformation( + $this + ) + ); + } + + public function getEditPointsSection( + Language $lng, + UIFactory $ui_factory + ): Section { + $section = $ui_factory->input()->field()->section( + $this->type->getEditPointsInputs( + $ui_factory, + $this->getAnswerOptions() + ), + "{$this->buildShortenedGapName()} ({$lng->txt("{$this->type->getIdentifier()}_gap")})" + ); + + $edit_section_constraint = $this->type->getEditPointsSectionConstraint(); + if ($edit_section_constraint !== null) { + $section = $section->withAdditionalTransformation($edit_section_constraint); + } + + + return $section->withAdditionalTransformation( + $this->type->getAddPointsTransformation($this) + ); + } + + #[\Override] + public function clone( + UuidFactory $uuid_factory, + array $environment = [] + ): static { + $environment['answer_input_id'] = $uuid_factory->uuid4(); + $clone = clone $this; + $clone->answer_input_id = $environment['answer_input_id']; + $clone->answer_form_id = $environment['answer_form_id']; + $clone->answer_options = $clone->answer_options->clone( + $uuid_factory, + $environment + ); + return $clone; + } + + public function toTableRow( + DataRowBuilder $row_builder, + Language $lng + ): DataRow { + $total_points = 0; + $answer_options_list = ''; + foreach ($this->answer_options->getAnswerOptionsAwardingPoints() as $option) { + $total_points += $option->getAvailablePoints(); + + $gap_text = $option->getTextValue(); + if ($gap_text === '') { + $gap_text = $option->getLowerlimit(); + } + + $answer_options_list .= "{$gap_text} ({$option->getAvailablePoints()})
"; + } + + return $row_builder->buildDataRow( + $this->answer_input_id->toString(), + [ + 'gap' => $this->buildShortenedGapName(), + 'type' => $lng->txt("{$this->type->getIdentifier()}_gap"), + 'answers_options_awarding_points' => $answer_options_list, + 'available_points' => $total_points + ] + ); + } + + private function buildValuesForGapReplace( + PersistenceFactory $persistence_factory + ): array { + return [ + $persistence_factory->value(FieldDefinition::T_TEXT, $this->answer_input_id->toString()), + $persistence_factory->value(FieldDefinition::T_TEXT, $this->answer_form_id->toString()), + $persistence_factory->value(FieldDefinition::T_INTEGER, $this->position), + $persistence_factory->value(FieldDefinition::T_TEXT, $this->type->getIdentifier()), + $persistence_factory->value(FieldDefinition::T_INTEGER, $this->max_chars), + $persistence_factory->value(FieldDefinition::T_FLOAT, $this->step_size), + $persistence_factory->value(FieldDefinition::T_INTEGER, $this->text_matching_method?->value), + $persistence_factory->value(FieldDefinition::T_INTEGER, $this->min_autocomplete), + $persistence_factory->value( + FieldDefinition::T_INTEGER, + $this->shuffle_answer_options === null + ? null + : ($this->shuffle_answer_options ? 1 : 0) + ) + + ]; + } + + private function getShortenedAnswerInputId(): string + { + return mb_substr($this->answer_input_id->toString(), 0, 4); + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/Gaps.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/Gaps.php new file mode 100644 index 000000000000..d6429956a733 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/Gaps.php @@ -0,0 +1,781 @@ + + */ + private array $gaps; + + public function __construct( + private readonly Refinery $refinery, + private readonly Factory $factory, + private Uuid $answer_form_id, + array $gaps + ) { + uasort( + $gaps, + fn(Gap $a, Gap $b): int => $a->getPosition() <=> $b->getPosition() + ); + + $this->gaps = array_reduce( + $gaps, + function (array $c, Gap $v): array { + $c[$v->getAnswerInputId()->toString()] = $v; + return $c; + }, + [] + ); + } + + public function getTotalAvailablePoints(): ?float + { + return array_reduce( + $this->gaps, + function (?float $c, Gap $v): ?float { + $points_from_options = $v->getAnswerOptions()->getMaxAvailablePoints(); + + if ($points_from_options === null) { + return $c; + } + + if ($c === null) { + return $points_from_options; + } + + return $c + $points_from_options; + } + ); + } + + public function getGapById( + Uuid $gap_id + ): ?Gap { + return $this->gaps[$gap_id->toString()] ?? null; + } + + public function getGapByTagName( + string $tag_name + ): ?Gap { + return $this->gaps[$this->extractIdFromTagName($tag_name)] ?? null; + } + + public function getGapByPosition( + int $position + ): ?Gap { + foreach ($this->gaps as $gap) { + if ($gap->getPosition() === $position) { + return $gap; + } + } + return null; + } + + public function getNumberOfGaps( + ): int { + return count($this->gaps); + } + + public function hasAtLeastOneGap(): bool + { + return $this->gaps !== []; + } + + public function withGap( + Gap $gap + ): self { + $clone = clone $this; + $clone->gaps[$gap->getAnswerInputId()->toString()] = $gap; + return $clone; + } + + public function withNewGap( + Uuid $answer_form_id, + int $position + ): self { + $new_gap = $this->factory->getNewGap($answer_form_id, $position); + $clone = clone $this; + $clone->gaps[$new_gap->getAnswerInputId()->toString()] = $new_gap; + + return $clone; + } + + public function withAdditionalGapFromTagName( + Uuid $answer_form_id, + string $tag_name, + int $position + ): self { + $answer_input_id = $this->extractIdFromTagName($tag_name); + $clone = clone $this; + $clone->gaps[$answer_input_id] = $this->factory->getNewGap( + $answer_form_id, + $position, + $answer_input_id + ); + return $clone; + } + + public function withResetGaps(): self + { + if ($this->gaps === []) { + return $this; + } + + $clone = clone $this; + $clone->gaps = []; + return $clone; + } + + public function getIncompleteGaps(): array + { + return array_filter( + $this->gaps, + fn(Gap $v): bool => $v->getAnswerOptions()->isIncomplete() + ); + } + + public function withMarkedIncompleteGaps(): self + { + $clone = clone $this; + $clone->gaps = array_map( + fn(Gap $v): Gap => $v->getAnswerOptions()->isIncomplete() + ? $v->withAnswerOptions( + $v->getAnswerOptions()->withIsIncomplete(true) + ) : $v, + $clone->gaps + ); + return $clone; + } + + public function getRemovedGaps( + self $old_gaps + ): array { + return array_diff_key($old_gaps->gaps, $this->gaps); + } + + public function getAddedGaps( + self $old_gaps + ): array { + return array_diff_key($this->gaps, $old_gaps->gaps); + } + + public function getPlaceholderArray( + Language $lng, + ViewConfiguration $view_configuration, + ?AdditionalAttemptData $additional_attempt_data, + ?Response $response_data + ): array { + return array_reduce( + $this->gaps, + function ( + array $c, + Gap $v + ) use ( + $lng, + $view_configuration, + $additional_attempt_data, + $response_data + ): array { + $c[$v->buildGapPlaceholderNameWithId($v)] = $view_configuration->isInteractive() + ? $v->buildParticipantViewLegacyInput( + $lng, + $this->refinery, + $additional_attempt_data, + $response_data + ) : $this->buildStaticGapReplacement( + $lng, + $view_configuration->showBestResponse(), + $response_data, + $v + ); + return $c; + }, + [] + ); + } + + public function getPlaceholderArrayForEditFormPanel(): array + { + return array_reduce( + $this->gaps, + function (array $c, Gap $v): array { + $c[$v->buildGapPlaceholderNameWithId($v)] = $v->buildShortenedGapRepresentation(); + return $c; + }, + [] + ); + } + + public function buildGapsTypeInputs( + Language $lng, + FieldFactory $ff, + array $available_gap_types, + Properties $properties, + bool $is_in_creation_context, + array $selected_gaps + ): Section { + return $ff->section( + array_reduce( + $this->retrieveGapsForInputs( + $is_in_creation_context, + $selected_gaps + ), + function (array $c, Gap $v) use ($ff, $available_gap_types): array { + $c[$v->getAnswerInputId()->toString()] = $ff->select( + $v->buildShortenedGapName(), + $available_gap_types + )->withRequired(true) + ->withValue($v->getType()?->getIdentifier()); + return $c; + }, + [] + ), + $lng->txt('select_gap_types') + )->withAdditionalTransformation( + $this->refinery->custom()->transformation( + fn(array $vs): Properties => $properties->withGaps( + array_reduce( + array_keys($vs), + fn(self $c, string $v): self => $c->withGap( + $c->gaps[$v]->withType( + $this->factory->getGapTypeByIdentifier($vs[$v]) + ) + ), + $this + ) + ) + ) + ); + } + + public function buildAnswerOptionsInputs( + FileUpload $file_upload, + Environment $environment, + Properties $properties, + ): Section { + + return $environment->getUIFactory()->input()->field()->section( + array_reduce( + $this->retrieveGapsForInputs( + $environment->isInCreationContext(), + $environment->getTableRowIds() + ), + function (array $c, Gap $v) use ($environment, $file_upload): array { + $c[$v->getAnswerInputId()->toString()] = $v->getEditAnswerOptionsSection( + $file_upload, + $environment + ); + return $c; + }, + [] + ), + $environment->getLanguage()->txt('edit_answer_options') + )->withAdditionalTransformation( + $this->refinery->custom()->transformation( + fn(array $vs): Properties => $properties->withGaps( + array_reduce( + array_keys($vs), + fn(self $c, string $v): self => $c->withGap($vs[$v]), + $this + ) + ) + ) + ); + } + + public function buildPointInputs( + Language $lng, + UIFactory $ui_factory, + Properties $properties, + bool $is_in_creation_context, + array $selected_gaps + ): Section { + return $ui_factory->input()->field()->section( + array_reduce( + $this->retrieveGapsForInputs( + $is_in_creation_context, + $selected_gaps + ), + function (array $c, Gap $v) use ($lng, $ui_factory): array { + $c[$v->getAnswerInputId()->toString()] = $v->getEditPointsSection( + $lng, + $ui_factory + ); + return $c; + }, + [] + ), + $lng->txt('edit_points') + )->withAdditionalTransformation( + $this->refinery->custom()->transformation( + fn(array $vs): Properties => $properties->withGaps( + array_reduce( + array_keys($vs), + fn(self $c, string $v): self => $c->withGap($vs[$v]), + $this + ) + ) + ) + ); + } + + public function buildGapsSelect( + string $label, + FieldFactory $ff + ): Select { + return $ff->select( + $label, + $this->buildOptionsArray() + ); + } + + public function buildGapsMultiSelect( + string $label, + FieldFactory $ff + ): MultiSelect { + return $ff->multiSelect( + $label, + $this->buildOptionsArray() + ); + } + + public function toCarry(): array + { + return array_reduce( + $this->gaps, + function (array $c, Gap $v): array { + $c[$v->getAnswerInputId()->toString()] = $v->toCarry(); + return $c; + }, + [] + ); + } + + public function withValuesFromCarry( + array $carry + ): self { + $clone = clone $this; + foreach ($carry as $answer_input_id => $gap_definition) { + if (!isset($clone->gaps[$answer_input_id])) { + $clone->gaps[$answer_input_id] = $this->factory->getNewGap( + $this->answer_form_id, + 0, + $answer_input_id + ); + } + + $clone->gaps[$answer_input_id] = $clone->gaps[$answer_input_id] + ->withValuesFromCarry( + $this->refinery, + $this->factory, + $gap_definition + ); + } + + return $clone; + } + + /** + * + * @return list + */ + public function getBestResponses(): array + { + return array_filter( + array_map( + fn(Gap $v): AnswerInputResponse => $v->getType()->getBestResponse( + $v + ), + $this->gaps + ) + ); + } + + public function initializeAttemptData( + Attempt $attempt + ): Attempt { + return array_reduce( + $this->gaps, + fn(Attempt $c, Gap $v): Attempt => $v->getShuffleAnswerOptions() + ? $c->withAdditionalData( + $v->getAnswerInputId(), + $this->refinery->kindlyTo()->string()->transform( + (new RandomSeed())->createSeed() + ) + ) : $c, + $attempt + ); + } + + /** + * + * @return list + */ + public function retrieveResponsesFromPost( + RequestWrapper $post_wrapper, + UuidFactory $uuid_factory + ): array { + return array_map( + fn(Gap $v): AnswerInputResponse => $v->getType() + ->retrieveResponseFromPost( + $post_wrapper, + $uuid_factory, + $v + ), + $this->gaps + ); + } + + /** + * + * @return list + */ + public function retrieveResponsesFromPreviewData( + UuidFactory $uuid_factory, + array $preview_data + ): array { + return array_reduce( + $this->gaps, + function ( + array $c, + Gap $v + ) use ( + $uuid_factory, + $preview_data + ): array { + $response_object = $v->getType()->retrieveResponseFromPreviewData( + $uuid_factory, + $v, + $preview_data[$v->getAnswerInputId()->toString()] ?? [] + ); + + if ($response_object !== null) { + $c[$v->getAnswerInputId()->toString()] = $response_object; + } + + return $c; + }, + [] + ); + } + + public function clone( + UuidFactory $uuid_factory, + array $environment = [] + ): static { + $clone = clone $this; + $clone->answer_form_id = $environment['answer_form_id']; + $clone->gaps = array_map( + fn(Gap $v): Gap => $v->clone($uuid_factory, $environment), + $this->gaps + ); + return $clone; + } + + public function toTableRows( + DataRowBuilder $row_builder, + Language $lng + ): \Generator { + foreach ($this->orderGapsByPosition($this->gaps) as $gap) { + yield $gap->toTableRow( + $row_builder, + $lng + ); + } + } + + public function toStorage( + Manipulate $manipulate, + PersistenceFactory $persistence_factory, + TableDefinitions $table_definitions, + TableNameBuilder $table_names_builder + ): Manipulate { + [ + 'gaps' => $replace_for_gaps, + 'answer_options' => $replace_for_answer_options + ] = array_reduce( + $this->gaps, + fn(array $c, Gap $v): array => [ + 'gaps' => $v->buildReplace( + $c['gaps'], + $table_definitions, + $persistence_factory, + $table_names_builder + ), + 'answer_options' => $v->getAnswerOptions()->buildReplace( + $c['answer_options'], + $table_definitions, + $persistence_factory, + $table_names_builder + ) + ], + [ + 'gaps' => null, + 'answer_options' => null + ] + ); + + return $manipulate->withAdditionalStatement( + $this->buildDeleteForRemovedGaps( + $table_definitions, + $persistence_factory, + $table_names_builder + ) + )->withAdditionalStatement($replace_for_gaps) + ->withAdditionalStatement($replace_for_answer_options); + } + + public function toDelete( + Manipulate $manipulate, + PersistenceFactory $persistence_factory, + TableDefinitions $table_definitions, + TableNameBuilder $table_names_builder + ): Manipulate { + return array_reduce( + $this->gaps, + fn(Manipulate $c, Gap $v): Manipulate => $c->withAdditionalStatement( + $v->getAnswerOptions()->buildDelete( + $table_definitions, + $persistence_factory, + $table_names_builder + ) + ), + $manipulate->withAdditionalStatement( + $this->buildDeleteForDeletionOfAnswerForm( + $table_definitions, + $persistence_factory, + $table_names_builder + ) + ) + ); + } + + private function buildDeleteForRemovedGaps( + TableDefinitions $table_definitions, + PersistenceFactory $persistence_factory, + TableNameBuilder $table_names_builder + ): Delete { + $table_type = AnswerFormSpecificTableTypes::AnswerInputs; + return $persistence_factory->delete( + $persistence_factory->table( + $table_names_builder, + $table_type + ), + [ + $persistence_factory->where( + $table_definitions->getForeignKeyColumn( + $table_names_builder, + $table_type + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->answer_form_id->toString() + ) + ), + $persistence_factory->where( + $table_definitions->getIdColumn( + $table_names_builder, + $table_type + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + array_map( + fn(Gap $v): string => $v->getAnswerInputId()->toString(), + $this->gaps + ) + ), + Operator::In, + Junctor::Conjunction, + true + ) + ] + ); + } + + private function buildDeleteForDeletionOfAnswerForm( + TableDefinitions $table_definitions, + PersistenceFactory $persistence_factory, + TableNameBuilder $table_names_builder + ): Delete { + $table_type = AnswerFormSpecificTableTypes::AnswerInputs; + + return $persistence_factory->delete( + $persistence_factory->table( + $table_names_builder, + $table_type + ), + [ + $persistence_factory->where( + $table_definitions->getForeignKeyColumn( + $table_names_builder, + $table_type + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->answer_form_id->toString() + ), + ) + ] + ); + } + + private function orderGapsByPosition( + array $gaps + ): array { + usort( + $gaps, + fn(Gap $a, Gap $b) => $a->getPosition() <=> $b->getPosition() + ); + + return $gaps; + } + + private function extractIdFromTagName( + string $tag_name + ): string { + return mb_substr($tag_name, mb_strlen(Gap::GAP_PLACEHOLDER_NAME) + 1); + } + + private function buildOptionsArray(): array + { + return array_reduce( + $this->gaps, + function (array $c, Gap $v): array { + $c[$v->getAnswerInputId()->toString()] = $v->buildShortenedGapName(); + return $c; + }, + [] + ); + } + + private function buildStaticGapReplacement( + Language $lng, + bool $show_best_response, + ?Response $response_data, + Gap $gap + ): string { + $static_gap_template = new \ilTemplate( + 'tpl.cloze_gap_static.html', + true, + true, + 'components/ILIAS/Questions' + ); + $static_gap_template->setVariable( + 'SOLUTION_VALUE', + $this->retrieveStaticGapReplacementValue( + $lng, + $show_best_response, + $response_data, + $gap + ) + ); + return $static_gap_template->get(); + } + + private function retrieveStaticGapReplacementValue( + Language $lng, + bool $show_best_response, + ?Response $response_data, + Gap $gap + ): string { + $empty_gap_text = $lng->txt( + $show_best_response + ? 'no_best_response_available' + : 'no_response_given' + ); + + if ($response_data === null) { + return $empty_gap_text; + } + + $response = $response_data->getResponseForInput( + $gap->getAnswerInputId() + ); + + if ($response === null) { + return $empty_gap_text; + } + + if ($response instanceof Uuid) { + return $gap->getAnswerOptions()->getAnswerOptionById($response)->getTextValue(); + } + + return $response; + } + + private function retrieveGapsForInputs( + bool $is_in_creation_context, + array $selected_gaps + ): array { + if ($is_in_creation_context) { + return $this->gaps; + } + + if ($selected_gaps === []) { + return $this->getIncompleteGaps(); + } + + return $this->filterGapsBySelected($selected_gaps); + } + + private function filterGapsBySelected( + array $selected_gaps + ): array { + return array_filter( + $this->gaps, + fn(string $k): bool => in_array($k, $selected_gaps), + ARRAY_FILTER_USE_KEY + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/LongMenu.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/LongMenu.php new file mode 100644 index 000000000000..55b4980079e3 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/LongMenu.php @@ -0,0 +1,281 @@ +getAnswerInputId()->toString(); + + $gaptemplate = new \ilTemplate( + 'tpl.cloze_gap_longmenu.html', + true, + true, + 'components/ILIAS/Questions' + ); + + $gaptemplate->setVariable( + 'GAP_NAME', + $gap_name + ); + + $response = $response_data?->getResponseForInput($gap->getAnswerInputId()); + if ($response !== null) { + $gaptemplate->setVariable( + 'VALUE', + htmlentities( + $response instanceof Uuid + ? $gap->getAnswerOptions() + ->getAnswerOptionById($response) + ->getTextValue() + : $response + ) + ); + } + + $this->global_tpl->addOnLoadCode('il.questions.cloze.initLongmenuGap(' + . "document.querySelector('input[name=\"{$gap_name}\"]'), " + . "{$gap->getMinAutocomplete()}, " + . json_encode( + array_values( + $gap->getAnswerOptions()->buildArrayForSelectInput( + $this->refinery->random()->dontShuffle() + ) + ) + ) . ')'); + return $gaptemplate->get(); + } + + #[\Override] + public function getEditAnswerOptionsInputs( + FileUpload $file_upload, + Environment $environment, + Gap $gap + ): array { + $ff = $environment->getUIFactory()->input()->field(); + return [ + 'answer_options' => $ff->tag( + $environment->getLanguage()->txt('answer_options'), + [] + )->withValue($gap->getAnswerOptions()->getTagsArrayFromAnswerOptions()), + 'upload_answer_options' => $ff->file( + new Upload( + $file_upload, + $environment + ), + $environment->getLanguage()->txt('upload_answer_options'), + $environment->getLanguage()->txt('upload_answer_options_info') + )->withAcceptedMimeTypes(self::ACCEPTED_MIME_TYPES), + 'min_autocomplete' => $ff->numeric( + $environment->getLanguage()->txt('min_auto_complete') + )->withRequired(true) + ->withValue($gap->getMinAutocomplete() ?? self::DEFAULT_MIN_AUTOCOMPLETE), + 'options_awarding_points' => $ff->tag( + $environment->getLanguage()->txt('answer_options'), + $gap->getAnswerOptions()->getTagsArrayFromAnswerOptions() + ) + ->withRequired(true) + ->withValue( + array_values( + array_map( + fn(AnswerOption $v): string => $v->getTextValue(), + $gap->getAnswerOptions()->getAnswerOptionsAwardingPoints() + ) + ) + ) + ]; + } + + public function getEditAnswerOptionsSectionConstraint(): ?Constraint + { + return $this->refinery->custom()->constraint( + function (array $vs): bool { + $values = [ + ...$vs['answer_options'], + ...$this->retrieveAnswerOptionsArrayFromUpload( + $vs['upload_answer_options'] + ) + ]; + + return $values !== [] && array_filter( + $vs['options_awarding_points'], + fn(string $v): bool => !in_array($v, $values) + ) === []; + }, + $this->lng->txt('error') + ); + } + + public function getEditPointsInputs( + UIFactory $ui_factory, + AnswerOptions $answer_options + ): array { + return $answer_options->getEditPointsInputs( + $ui_factory->input()->field(), + fn(AnswerOption $v): string => $v->getTextValue(), + $answer_options->getAnswerOptionsAwardingPoints() + ); + } + + #[\Override] + public function getEditPointsSectionConstraint(): ?Constraint + { + return $this->refinery->custom()->constraint( + function (array $vs): bool { + foreach ($vs as $v) { + if ($v > 0.0) { + return true; + } + } + return false; + }, + $this->lng->txt('at_least_one_gap_positiv_points') + ); + } + + #[\Override] + public function getBuildGapTransformation( + Gap $gap + ): Transformation { + return $this->refinery->custom()->transformation( + fn(array $vs): Gap => $gap + ->withMinAutocomplete($vs['min_autocomplete']) + ->withAnswerOptions( + $gap->getAnswerOptions()->withAnswerOptionsFromTags( + [ + ...$vs['answer_options'], + ...$this->retrieveAnswerOptionsArrayFromUpload( + $vs['upload_answer_options'] + ) + ] + )->withAnswerOptionsAwardingPoints($vs['options_awarding_points']) + ) + ); + } + + #[\Override] + public function retrieveResponseFromPost( + RequestWrapper $post_wrapper, + UuidFactory $uuid_factory, + Gap $gap + ): AnswerInputResponse { + $response_value = $this->retrieveResponseValueFromPost( + $post_wrapper, + $uuid_factory, + $gap + ); + + $response_is_uuid = $response_value instanceof Uuid; + + return new AnswerInputResponse( + $gap, + $response_is_uuid + ? $response_value + : null, + $response_is_uuid + ? '' + : $response_value + ); + } + + private function retrieveAnswerOptionsArrayFromUpload( + ?array $upload_value + ): array { + if ($upload_value === null + || ($decoded_value = base64_decode($upload_value[0] ?? '')) === '') { + return []; + } + + return array_filter( + mb_split('\R', $decoded_value) + ); + } + + private function retrieveResponseValueFromPost( + RequestWrapper $post_wrapper, + UuidFactory $uuid_factory, + Gap $gap + ): Uuid|string { + return $post_wrapper->retrieve( + $gap->getAnswerInputId()->toString(), + $this->refinery->byTrying([ + $this->refinery->custom()->transformation( + function (?string $v) use ($gap): Uuid|string { + if ($v === null) { + return ''; + } + + $answer_option = $gap->getAnswerOptions() + ->getAnswerOptionByTextValue($v); + + if ($answer_option === null) { + return $v; + } + + return $answer_option?->getAnswerOptionId(); + } + ), + $this->refinery->always('') + ]) + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/Numeric.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/Numeric.php new file mode 100644 index 000000000000..d00df6dc1a2b --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/Numeric.php @@ -0,0 +1,382 @@ +setVariable( + 'GAP_NAME', + $gap->getAnswerInputId()->toString() + ); + + $response = $response_data?->getResponseForInput($gap->getAnswerInputId()); + if ($response !== null) { + $gaptemplate->setVariable( + 'VALUE_GAP', + ' value="' . \ilLegacyFormElementsUtil::prepareFormOutput( + $response + ) . '"' + ); + } + + return $gaptemplate->get(); + } + + #[\Override] + public function getEditAnswerOptionsInputs( + FileUpload $file_upload, + Environment $environment, + Gap $gap + ): array { + $answer_option = $gap->getAnswerOptions()->getAnswerOptionForPositionOrNew(0); + + $ff = $environment->getUIFactory()->input()->field(); + return [ + self::KEY_LOWER_LIMIT => $ff->numeric($environment->getLanguage()->txt('range_lower_limit')) + ->withStepSize($gap->getStepSize() ?? self::DEFAULT_SUB_ACTION_SIZE) + ->withRequired(true) + ->withValue($answer_option->getLowerLimit()), + self::KEY_UPPER_LIMIT => $ff->numeric($environment->getLanguage()->txt('range_upper_limit')) + ->withStepSize($gap->getStepSize() ?? self::DEFAULT_SUB_ACTION_SIZE) + ->withValue($answer_option->getUpperLimit()), + self::KEY_STEP_SIZE => $ff->numeric($environment->getLanguage()->txt('step_size')) + ->withStepSize(0.000001) + ->withRequired(true) + ->withValue($gap->getStepSize() ?? self::DEFAULT_SUB_ACTION_SIZE) + ]; + } + + #[\Override] + public function getEditAnswerOptionsSectionConstraint(): ?Constraint + { + return $this->refinery->custom()->constraint( + fn(array $vs): bool => $vs[self::KEY_UPPER_LIMIT] === null + || $vs[self::KEY_UPPER_LIMIT] >= $vs[self::KEY_LOWER_LIMIT] + && $vs[self::KEY_UPPER_LIMIT] >= $vs[self::KEY_LOWER_LIMIT], + $this->lng->txt('upper_limit_bigger_than_lower') + ); + } + + #[\Override] + public function getEditPointsInputs( + UIFactory $ui_factory, + AnswerOptions $answer_options + ): array { + $inputs = $answer_options->getEditPointsInputs( + $ui_factory->input()->field(), + function (AnswerOption $v): string { + if ($v->getUpperLimit() === null) { + return sprintf( + $this->lng->txt('equal'), + $v->getLowerLimit() + ); + } + + return sprintf( + $this->lng->txt('between'), + $v->getLowerLimit(), + $v->getUpperLimit() + ); + } + ); + return array_map( + fn(NumericInput $v): NumericInput => $v->withRequired(true), + $inputs + ); + } + + #[\Override] + public function getEditPointsSectionConstraint(): ?Constraint + { + return null; + } + + #[\Override] + public function getBuildGapTransformation( + Gap $gap + ): Transformation { + return $this->refinery->custom()->transformation( + fn(array $vs): Gap => $gap->withAnswerOptions( + $gap->getAnswerOptions()->withAnswerOptions([ + $gap->getAnswerOptions()->getAnswerOptionForPositionOrNew(0) + ->withLowerLimit($vs['lower_limit']) + ->withUpperLimit($vs['upper_limit']) + ]) + )->withStepSize($vs['step_size']) + ); + } + + #[\Override] + public function getCombinationsSelectValues( + Gap $gap + ): array { + return [ + Range::InRange->value => Range::InRange->getLabel($this->lng), + Range::OutOfRange->value => Range::OutOfRange->getLabel($this->lng) + ]; + } + + #[\Override] + public function getFeedbackSelectValues( + Gap $gap, + bool $is_marking_required + ): array { + return $this->getCombinationsSelectValues($gap); + } + + #[\Override] + public function isValidFeedbackCondition( + UuidFactory $uuid_factory, + Gap $gap, + string $condition + ): bool { + return Range::tryFrom($condition) !== null; + } + + #[\Override] + public function getLabelForValue( + UuidFactory $uuid_factory, + Gap $gap, + string $value + ): string { + return Range::tryFrom($value)?->getLabel($this->lng) ?? ''; + } + + #[\Override] + public function retrieveResponseFromPost( + RequestWrapper $post_wrapper, + UuidFactory $uuid_factory, + Gap $gap + ): AnswerInputResponse { + return new AnswerInputResponse( + $gap, + null, + $post_wrapper->retrieve( + $gap->getAnswerInputId()->toString(), + $this->refinery->byTrying([ + $this->refinery->in()->series([ + $this->refinery->kindlyTo()->float(), + $this->refinery->kindlyTo()->string() + ]), + $this->refinery->always('') + ]) + ) + ); + } + + #[\Override] + public function isBestResponse( + Gap $gap, + AnswerInputResponse $response + ): bool { + /** @var ?AnswerOption $answer_option */ + $answer_options_awarding_points = $gap->getAnswerOptions() + ->getAnswerOptionsAwardingPoints(); + + $answer_option = $answer_options_awarding_points === null + ? null + : array_shift($answer_options_awarding_points); + + if ($answer_option === null) { + return false; + } + + return $this->getRangeValueFromResponseValue( + $answer_option, + $response->getResponse() + ) === Range::InRange; + } + + #[\Override] + public function calculateAwardedPointsForResponse( + Gap $gap, + Uuid|string|null $response + ): float { + if ($response === null) { + return 0.0; + } + + $answer_option = $this->getAnswerOptionAwardingPoints($gap); + + if ($answer_option === null) { + return 0.0; + } + + if ($this->getRangeValueFromResponseValue( + $answer_option, + $response + ) === Range::InRange) { + return $answer_option->getAvailablePoints(); + } + + return 0.0; + } + + #[\Override] + public function buildClientSideRepresentationOfResponse( + Gap $gap, + AnswerInputResponse $response + ): array { + $answer_option = $this->getAnswerOptionAwardingPoints($gap); + + return [ + self::KEY_RESPONSE => $response->getResponse(), + self::KEY_LOWER_LIMIT => $answer_option->getLowerLimit(), + self::KEY_UPPER_LIMIT => $answer_option->getUpperLimit() + ]; + } + + #[\Override] + public function getSpecificFeedbackParticipantOutput( + UIFactory $ui_factory, + Gap $gap, + array $specific_feedbacks, + Uuid|string $answer_input_response + ): ?Component { + $specific_feedbacks_by_condition = array_reduce( + $specific_feedbacks, + function (array $c, SpecificTextFeedback $v): array { + if (!array_key_exists($v->getCondition(), $c)) { + $c[$v->getCondition()] = []; + } + + $c[$v->getCondition()] = $v->getFeedbackTextForPresentation($this->refinery); + + return $c; + }, + [] + ); + + $range_value = $this->getRangeValueFromResponseValue( + $this->getAnswerOptionAwardingPoints($gap), + $answer_input_response + ); + + if (isset($specific_feedbacks_by_condition[$range_value->value])) { + return $ui_factory->legacy()->content( + $specific_feedbacks_by_condition[$range_value->value] + ); + } + + if (($range_value === Range::BelowRange + || $range_value === Range::BelowRange) + && isset($specific_feedbacks_by_condition[Range::OutOfRange->value])) { + return $ui_factory->legacy()->content( + $specific_feedbacks_by_condition[Range::OutOfRange->value] + ); + } + + return null; + } + + #[\Override] + protected function retrieveResponseTextFromAnswerOption( + AnswerOption $answer_option + ): string { + $trafo = $this->refinery->kindlyTo()->string(); + $lower_limit_string = $trafo->transform( + $answer_option->getLowerLimit() + ); + $upper_limit = $answer_option->getUpperLimit(); + return $upper_limit === null + ? $lower_limit_string + : "{$lower_limit_string} - {$trafo->transform($upper_limit)}"; + } + + private function getAnswerOptionAwardingPoints( + Gap $gap + ): AnswerOption { + /** @var ?AnswerOption $answer_option */ + $answer_options_awarding_points = $gap->getAnswerOptions() + ->getAnswerOptionsAwardingPoints(); + + return $answer_options_awarding_points === null + ? null + : array_shift($answer_options_awarding_points); + } + + private function getRangeValueFromResponseValue( + AnswerOption $answer_option, + string $response + ): Range { + $response_as_float = $this->refinery->kindlyTo()->float()->transform( + $response + ); + + $upper_limit = $answer_option->getUpperLimit(); + $lower_limit = $answer_option->getLowerLimit(); + + if ($response < $lower_limit) { + return Range::BelowRange; + } + + if ($upper_limit === null + && $response_as_float === $lower_limit + || $response_as_float <= $upper_limit) { + return Range::InRange; + } + + return Range::AboveRange; + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/Select.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/Select.php new file mode 100644 index 000000000000..8e0dfb2a98ed --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/Select.php @@ -0,0 +1,247 @@ +getResponseForInput( + $gap->getAnswerInputId() + )?->toString(); + + foreach ($gap->getAnswerOptions()->buildArrayForSelectInput( + $this->buildShuffler( + $gap, + $additional_attempt_data + ) + ) as $answer_option_id => $answer_option) { + $gaptemplate->setCurrentBlock('select_gap_option'); + $gaptemplate->setVariable( + 'SELECT_GAP_VALUE', + $answer_option_id + ); + $gaptemplate->setVariable( + 'SELECT_GAP_TEXT', + \ilLegacyFormElementsUtil::prepareFormOutput($answer_option) + ); + + if ($answer_option_id === $selected_answer_option) { + $gaptemplate->setVariable( + 'SELECT_GAP_SELECTED', + ' selected="selected"' + ); + } + + $gaptemplate->parseCurrentBlock(); + } + + $gaptemplate->setVariable( + 'PLEASE_SELECT', + $this->lng->txt('please_select') + ); + + $gaptemplate->setVariable( + 'GAP_NAME', + $gap->getAnswerInputId()->toString() + ); + + return $gaptemplate->get(); + } + + #[\Override] + public function getEditAnswerOptionsInputs( + FileUpload $file_upload, + Environment $environment, + Gap $gap + ): array { + $ff = $environment->getUIFactory()->input()->field(); + return [ + 'answer_options' => $ff->tag( + $environment->getLanguage()->txt('answer_options'), + [] + )->withRequired(true) + ->withValue($gap->getAnswerOptions()->getTagsArrayFromAnswerOptions()), + 'shuffle_answer_options' => $ff->checkbox( + $environment->getLanguage()->txt('shuffle_answers') + )->withValue($gap?->getShuffleAnswerOptions() ?? self::DEFAULT_SHUFFLE_ANSWER_OPTIONS) + ]; + } + + #[\Override] + public function getEditAnswerOptionsSectionConstraint(): ?Constraint + { + return null; + } + + #[\Override] + public function getEditPointsInputs( + UIFactory $ui_factory, + AnswerOptions $answer_options + ): array { + return $answer_options->getEditPointsInputs( + $ui_factory->input()->field(), + fn(AnswerOption $v): string => $v->getTextValue() + ); + } + + #[\Override] + public function getEditPointsSectionConstraint(): ?Constraint + { + return $this->refinery->custom()->constraint( + function (array $vs): bool { + foreach ($vs as $v) { + if ($v > 0.0) { + return true; + } + } + return false; + }, + $this->lng->txt('at_least_one_gap_positiv_points') + ); + } + + #[\Override] + public function getBuildGapTransformation( + Gap $gap + ): Transformation { + return $this->refinery->custom()->transformation( + fn(array $vs): Gap => $gap->withAnswerOptions( + $gap->getAnswerOptions()->withAnswerOptionsFromTags( + $vs['answer_options'] + ) + )->withShuffleAnswerOptions($vs['shuffle_answer_options']) + ); + } + + #[\Override] + public function retrieveResponseFromPost( + RequestWrapper $post_wrapper, + UuidFactory $uuid_factory, + Gap $gap + ): AnswerInputResponse { + return new AnswerInputResponse( + $gap, + $post_wrapper->retrieve( + $gap->getAnswerInputId()->toString(), + $this->refinery->byTrying([ + $this->refinery->custom()->transformation( + function (?string $v) use ($uuid_factory, $gap): ?Uuid { + if ($v === null) { + return null; + } + + try { + $answer_option_id = $uuid_factory->fromString($v); + } catch (\Exception $e) { + return null; + } + + if ($gap->getAnswerOptions()->getAnswerOptionById( + $answer_option_id + ) === null) { + return null; + } + + return $answer_option_id; + } + ), + $this->refinery->always(null) + ]) + ), + '' + ); + } + + private function buildShuffler( + Gap $gap, + ?AdditionalAttemptData $additional_attempt_data + ): Transformation { + if (!$gap->getShuffleAnswerOptions()) { + return $this->refinery->random()->dontShuffle(); + } + + return $this->refinery->random()->shuffleArray( + $this->buildSeed( + $gap, + $additional_attempt_data + ) + ); + } + + private function buildSeed( + Gap $gap, + ?AdditionalAttemptData $additional_attempt_data + ): GivenSeed { + if ($additional_attempt_data === null) { + return new RandomSeed(); + } + + $given_seed = $additional_attempt_data->getAdditionalDataFor( + $gap->getAnswerInputId() + ); + + if (is_numeric($given_seed)) { + return new GivenSeed( + $this->refinery->kindlyTo()->int()->transform( + $given_seed + ) + ); + } + + return new RandomSeed(); + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/Text.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/Text.php new file mode 100644 index 000000000000..aa79fc0a8b59 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/Text.php @@ -0,0 +1,275 @@ +getMaxChars(); + if ($gap_size > 0) { + $gaptemplate->setCurrentBlock('size_and_maxlength'); + $gaptemplate->setVariable('TEXT_GAP_SIZE', $gap_size); + $gaptemplate->parseCurrentBlock(); + } + $gaptemplate->setVariable( + 'GAP_NAME', + $gap->getAnswerInputId()->toString() + ); + + $response = $response_data?->getResponseForInput($gap->getAnswerInputId()); + if ($response !== null) { + $gaptemplate->setVariable( + 'VALUE_GAP', + ' value="' . \ilLegacyFormElementsUtil::prepareFormOutput( + $response + ) . '"' + ); + } + + return $gaptemplate->get(); + } + + #[\Override] + public function getEditAnswerOptionsInputs( + FileUpload $file_upload, + Environment $environment, + Gap $gap + ): array { + $ff = $environment->getUIFactory()->input()->field(); + return [ + 'answer_options' => $ff->tag( + $environment->getLanguage()->txt('answer_options'), + [] + )->withRequired(true) + ->withValue($gap->getAnswerOptions()->getTagsArrayFromAnswerOptions()), + 'matching_method' => $ff->select( + $environment->getLanguage()->txt('text_matching_method'), + TextMatchingOptions::buildOptionsList($environment->getLanguage()) + )->withRequired(true) + ->withValue($gap->getTextMatchingMethod()?->value ?? self::DEFAULT_TECT_MATCHING_METHOD->value), + 'max_chars' => $ff->numeric( + $environment->getLanguage()->txt('max_characters'), + )->withValue($gap->getMaxChars()) + ]; + } + + #[\Override] + public function getEditAnswerOptionsSectionConstraint(): ?Constraint + { + return $this->refinery->custom()->constraint( + fn(array $vs): bool => array_filter( + $vs['answer_options'], + fn(string $v): bool => in_array($v, $vs['answer_options']) + ) !== [], + $this->lng->txt('answer_options_must_be_unique') + ); + } + + #[\Override] + public function getEditPointsInputs( + UIFactory $ui_factory, + AnswerOptions $answer_options + ): array { + return $answer_options->getEditPointsInputs( + $ui_factory->input()->field(), + fn(AnswerOption $v): string => $v->getTextValue() + ); + } + + #[\Override] + public function getEditPointsSectionConstraint(): ?Constraint + { + return $this->refinery->custom()->constraint( + function (array $vs): bool { + foreach ($vs as $v) { + if ($v > 0.0) { + return true; + } + } + return false; + }, + $this->lng->txt('at_least_one_gap_positiv_points') + ); + } + + #[\Override] + public function getBuildGapTransformation( + Gap $gap + ): Transformation { + return $this->refinery->custom()->transformation( + fn(array $vs): Gap => $gap->withMaxChars($vs['max_chars']) + ->withTextMatchingMethod( + TextMatchingOptions::tryFrom($vs['matching_method']) + ?? self::DEFAULT_TECT_MATCHING_METHOD + )->withAnswerOptions( + $gap->getAnswerOptions()->withAnswerOptionsFromTags( + $vs['answer_options'] + ) + ) + ); + } + + #[\Override] + public function retrieveResponseFromPost( + RequestWrapper $post_wrapper, + UuidFactory $uuid_factory, + Gap $gap + ): AnswerInputResponse { + return new AnswerInputResponse( + $gap, + null, + $post_wrapper->retrieve( + $gap->getAnswerInputId()->toString(), + $this->refinery->byTrying([ + $this->refinery->kindlyTo()->string(), + $this->refinery->always('') + ]) + ) + ); + } + + #[\Override] + public function isBestResponse( + Gap $gap, + AnswerInputResponse $response + ): bool { + return $this->getBestResponse( + $gap + )?->getResponse() === $response->getResponse(); + } + + #[\Override] + public function calculateAwardedPointsForResponse( + Gap $gap, + Uuid|string|null $response + ): float { + + $answer_option = array_filter( + $gap->getAnswerOptions()->getAnswerOptionsAwardingPoints(), + fn(AnswerOption $v): bool => $response === $v->getTextValue() + ); + + if ($answer_option === []) { + return 0.0; + } + + return array_shift($answer_option)->getAvailablePoints(); + } + + #[\Override] + public function buildClientSideRepresentationOfResponse( + Gap $gap, + AnswerInputResponse $response + ): array { + return [ + self::KEY_RESPONSE => $response->getResponse() + ]; + } + + #[\Override] + public function getSpecificFeedbackParticipantOutput( + UIFactory $ui_factory, + Gap $gap, + array $specific_feedbacks, + Uuid|string $answer_input_response + ): ?Component { + $specific_feedbacks_by_condition = array_reduce( + $specific_feedbacks, + function (array $c, SpecificTextFeedback $v): array { + $c[$v->getCondition()] = $v->getFeedbackText(); + return $c; + }, + [] + ); + + if ($answer_input_response instanceof Uuid) { + return $this->getSpecificFeedbackParticipantOutputForAnswerOption( + $ui_factory, + $specific_feedbacks_by_condition[$answer_input_response->toString()] ?? null + ); + } + + if ($this->getBestResponse($gap) === null) { + return null; + } + + if ($this->getBestResponse($gap)->getResponse() === $answer_input_response) { + return isset($specific_feedbacks_by_condition[TextFeedbackTypes::BestResponse->value]) + ? $ui_factory->legacy()->content( + $this->refinery->string()->markdown()->toHTML()->transform( + $specific_feedbacks_by_condition[TextFeedbackTypes::BestResponse->value]->getRawRepresentation() + ) + ) : null; + } + + return isset($specific_feedbacks_by_condition[TextFeedbackTypes::OtherResponse->value]) + ? $ui_factory->legacy()->content( + $this->refinery->string()->markdown()->toHTML()->transform( + $specific_feedbacks_by_condition[TextFeedbackTypes::OtherResponse->value]->getRawRepresentation() + ) + ) : null; + } + + #[\Override] + protected function retrieveResponseTextFromAnswerOption( + AnswerOption $answer_option + ): string { + return $answer_option->getTextValue(); + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/Type.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/Type.php new file mode 100644 index 000000000000..ee4905f35631 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Gaps/Type.php @@ -0,0 +1,308 @@ +getAnswerOptions()->buildArrayForSelectInput( + $this->refinery->random()->dontShuffle() + ); + } + + public function getFeedbackSelectValues( + Gap $gap, + bool $is_marking_required + ): array { + $basic_select_values = $this->getCombinationsSelectValues( + $gap + ); + if (!$is_marking_required) { + return $basic_select_values; + } + return [ + ...[ + TextFeedbackTypes::BestResponse->value => TextFeedbackTypes::BestResponse + ->getTranslatedOptionName($this->lng), + TextFeedbackTypes::OtherResponse->value => TextFeedbackTypes::OtherResponse + ->getTranslatedOptionName($this->lng), + TextFeedbackTypes::NoResponse->value => TextFeedbackTypes::NoResponse + ->getTranslatedOptionName($this->lng) + ], + ...$basic_select_values + ]; + } + + public function isValidFeedbackCondition( + UuidFactory $uuid_factory, + Gap $gap, + string $condition + ): bool { + if (TextFeedbackTypes::tryFrom($condition) !== null + || Range::tryFrom($condition) !== null) { + return true; + } + + try { + return $gap->getAnswerOptions()->getAnswerOptionById( + $uuid_factory->fromString($condition) + ) !== null; + } catch (InvalidUuidStringException $e) { + return false; + } + } + + public function getLabelForValue( + UuidFactory $uuid_factory, + Gap $gap, + string $value + ): string { + $from_feedback = TextFeedbackTypes::tryFrom($value); + if ($from_feedback !== null) { + return $from_feedback->getTranslatedOptionName($this->lng); + } + + return $gap->getAnswerOptions()->getAnswerOptionById( + $uuid_factory->fromString($value) + )->getTextValue(); + } + + public function getAddPointsTransformation( + Gap $gap + ): Transformation { + return $this->refinery->custom()->transformation( + fn(array $vs): Gap => $gap->withAnswerOptions( + $gap->getAnswerOptions() + ->withAnswerOptionsWithAddedPointsFromForm($this->refinery, $vs) + ) + ); + } + + public function getBestResponse( + Gap $gap + ): ?AnswerInputResponse { + $best_answer_option = $gap->getAnswerOptions()->getBestAnswerOption(); + if ($best_answer_option === null) { + return null; + } + + $text = $this->retrieveResponseTextFromAnswerOption( + $best_answer_option + ); + if ($text !== '') { + $best_answer_option = null; + } + + return new AnswerInputResponse( + $gap, + $best_answer_option?->getAnswerOptionId(), + $text + ); + } + + public function isBestResponse( + Gap $gap, + AnswerInputResponse $response + ): bool { + $response_value = $response->getResponse(); + + if (!($response_value instanceof Uuid)) { + return false; + } + + return $this->getBestResponse( + $gap + )->getResponse()?->compareTo( + $response_value + ) === 0; + } + + public function calculateAwardedPointsForResponse( + Gap $gap, + Uuid|string|null $response + ): float { + if (!($response instanceof Uuid)) { + return 0.0; + } + + $answer_option = $gap->getAnswerOptions()->getAnswerOptionById($response); + + if ($answer_option === null) { + return 0.0; + } + + return $answer_option->getAvailablePoints() ?? 0.0; + } + + public function buildClientSideRepresentationOfResponse( + Gap $gap, + AnswerInputResponse $response + ): array { + return [ + self::KEY_RESPONSE => $response->getResponse()->toString() + ]; + } + + public function getSpecificFeedbackParticipantOutput( + UIFactory $ui_factory, + Gap $gap, + array $specific_feedbacks, + Uuid|string $answer_input_response + ): ?Component { + $specific_feedbacks_by_condition = array_reduce( + $specific_feedbacks, + function (array $c, SpecificTextFeedback $v): array { + if (!array_key_exists($v->getCondition(), $c)) { + $c[$v->getCondition()] = []; + } + + $c[$v->getCondition()] = $v->getFeedbackTextForPresentation($this->refinery); + + return $c; + }, + [] + ); + + if ($answer_input_response instanceof Uuid) { + return $this->getSpecificFeedbackParticipantOutputForAnswerOption( + $ui_factory, + $specific_feedbacks_by_condition[$answer_input_response->toString()] ?? null + ); + } + + if ($this->getBestResponse($gap) === null) { + return null; + } + + if ($this->getBestResponse($gap)->getResponse() === $answer_input_response) { + return isset($specific_feedbacks_by_condition[TextFeedbackTypes::BestResponse->value]) + ? $ui_factory->legacy()->content( + $specific_feedbacks_by_condition[TextFeedbackTypes::BestResponse->value] + ) : null; + } + + return isset($specific_feedbacks_by_condition[TextFeedbackTypes::OtherResponse->value]) + ? $ui_factory->legacy()->content( + $specific_feedbacks_by_condition[TextFeedbackTypes::OtherResponse] + ) : null; + } + + public function retrieveResponseFromPreviewData( + UuidFactory $uuid_factory, + Gap $gap, + array $preview_data + ): ?AnswerInputResponse { + if ($preview_data === []) { + return null; + } + + return new AnswerInputResponse( + $gap, + isset($preview_data[AnswerInputResponse::KEY_SELECTED_ANSWER_OPTION]) + ? $uuid_factory->fromString($preview_data[AnswerInputResponse::KEY_SELECTED_ANSWER_OPTION]) + : null, + $preview_data[AnswerInputResponse::KEY_TEXT] ?? '' + ); + } + + protected function retrieveResponseTextFromAnswerOption( + AnswerOption $answer_option + ): string { + return ''; + } + + private function getSpecificFeedbackParticipantOutputForAnswerOption( + UIFactory $ui_factory, + ?string $specific_feedback + ): ?Component { + if ($specific_feedback === null) { + return null; + } + + return $ui_factory->legacy()->content($specific_feedback); + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Properties.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Properties.php new file mode 100644 index 000000000000..3813ed31d417 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Properties/Properties.php @@ -0,0 +1,524 @@ + $gaps + */ + public function __construct( + private Uuid $answer_form_id, + private Uuid $question_id, + private readonly Definition $definition, + private readonly ?float $available_points, + private Text $cloze_text, + private string $legacy_cloze_text, + private ScoringIdentical $scoring_identical, + private Gaps $gaps, + private Combinations $combinations + ) { + } + + #[\Override] + public function getAnswerFormId(): Uuid + { + return $this->answer_form_id; + } + + #[\Override] + public function getQuestionId(): Uuid + { + return $this->question_id; + } + + #[\Override] + public function getDefinition(): ?Definition + { + return $this->definition; + } + + #[\Override] + public function getTypeGenericProperties(): TypeGenericProperties + { + return new TypeGenericProperties( + $this->answer_form_id, + $this->question_id, + $this->definition, + $this->buildAvailablePointsForGenericProperties(), + null, + null, + $this->cloze_text->getRawRepresentation(), + $this->legacy_cloze_text + ); + } + + public function getClozeText(): Text + { + return $this->cloze_text; + } + + public function withClozeText( + Text $cloze_text + ): self { + $clone = clone $this; + $clone->cloze_text = $cloze_text; + return $clone; + } + + public function getLegacyClozeText(): string + { + return $this->legacy_cloze_text; + } + + public function getClozeTextForPresentation(): string + { + return $this->cloze_text->getRawRepresentation() === '' + ? \ilRTE::_replaceMediaObjectImageSrc($this->legacy_cloze_text, 0) + : $this->cloze_text->getRenderedMarkdownForParticipantPresentation(); + } + + public function getScoringOfIdenticalResponses(): ScoringIdentical + { + return $this->scoring_identical; + } + + public function withScoringOfIdenticalResponses( + ScoringIdentical $scoring_identical + ): self { + $clone = clone $this; + $clone->scoring_identical = $scoring_identical; + return $clone; + } + + public function getCombinations(): Combinations + { + return $this->combinations; + } + + public function withCombinations( + Combinations $combinations + ): self { + $clone = clone $this; + $clone->combinations = $combinations; + $clone->updated_combinations = true; + return $clone; + } + + public function getGaps(): Gaps + { + return $this->gaps; + } + + public function withGaps( + Gaps $gaps + ): self { + $clone = clone $this; + $clone->gaps = $gaps; + $clone->updated_gaps = true; + return $clone; + } + + #[\Override] + public function getBasicPropertiesForListing( + Environment $environment + ): array { + $lng = $environment->getLanguage(); + + $listing = [ + $lng->txt('cloze_text') => $this->cloze_text + ->getRenderedMarkdownForEditingPresentation( + $this->gaps, + $this->getLegacyClozeText() + ) + ]; + + if ($environment->isMarkingRequired()) { + $listing[$lng->txt('scoring_of_identical_responses')] = $this + ->scoring_identical + ->getTranslatedOptionName($lng); + } + + return [ + ...$listing, + $lng->txt('gap_combinations') => $this->combinations->areCombinationsEnabled() + ? $lng->txt('enabled') + : $lng->txt('disabled') + ]; + } + + #[\Override] + public function completeEditOverview( + Environment $environment, + EditOverview $edit_overview + ): EditOverview { + return $edit_overview->withTable( + new OverviewTable( + $environment + )->getTable() + ); + } + + public function buildBasicEditingInputs( + Environment $environment, + Factory $properties_factory, + ClozeTextFactory $cloze_text_factory, + bool $add_legacy_cloze_text_to_input + ): Section { + $lng = $environment->getLanguage(); + $ff = $environment->getUIFactory()->input()->field(); + $refinery = $environment->getRefinery(); + + $cloze_text_input = $this->cloze_text->getInput( + $lng, + $ff, + $cloze_text_factory, + $this->legacy_cloze_text === '' + || $this->legacy_cloze_text !== '' + && $this->cloze_text->getRawRepresentation() !== '' + ); + + if ($add_legacy_cloze_text_to_input) { + $cloze_text_input = $cloze_text_input->withValue( + strip_tags($this->legacy_cloze_text) + ); + } + + $inputs = [ + self::KEY_CLOZE_TEXT => $cloze_text_input, + self::KEY_SCORING_IDENTICAL => ScoringIdentical::buildInput( + $lng, + $ff, + $refinery, + $this->scoring_identical + )->withValue($this->getScoringOfIdenticalResponses()->value) + ]; + + if ($environment->isMarkingRequired()) { + $inputs[self::KEY_ENABLE_COMBINATIONS] = $ff->checkbox( + $lng->txt('cloze_enable_gap_combinations') + )->withValue($this->combinations->areCombinationsEnabled()); + } + + return $ff->section( + $inputs, + $lng->txt('edit_basic_answer_form_properties') + )->withAdditionalTransformation( + $refinery->custom()->transformation( + fn(array $vs): self => $properties_factory->fromBasicEditingForm( + $this, + $vs[self::KEY_CLOZE_TEXT], + $vs[self::KEY_SCORING_IDENTICAL], + $vs[self::KEY_ENABLE_COMBINATIONS] ?? false + ) + ) + ); + } + + public function toCarry(): string + { + return json_encode([ + self::KEY_CLOZE_TEXT => $this->cloze_text->getRawRepresentation(), + self::KEY_GAPS => $this->gaps->toCarry(), + self::KEY_SCORING_IDENTICAL => $this->scoring_identical->value, + self::KEY_ENABLE_COMBINATIONS => $this->combinations + ->areCombinationsEnabled() ? 1 : 0 + ]); + } + + public function withValuesFromCarry( + ClozeTextFactory $cloze_text_factory, + array $carry + ): self { + $clone = clone $this; + + $clone->cloze_text = $cloze_text_factory->buildFromTextString( + $carry[self::KEY_CLOZE_TEXT] + ); + $clone->gaps = $this->getGaps()->withValuesFromCarry( + $carry[self::KEY_GAPS] + ); + $clone->scoring_identical = ScoringIdentical::tryFrom( + $carry[self::KEY_SCORING_IDENTICAL] + ); + $clone->combinations = $this->combinations->withCombinationsEnabled( + $carry[self::KEY_ENABLE_COMBINATIONS] === 1 + ); + return $clone; + } + + #[\Override] + public function clone( + UuidFactory $uuid_factory, + array $environment = [] + ): static { + $clone = clone $this; + $clone->answer_form_id = $environment['answer_form_id']; + $clone->question_id = $environment['new_question_id']; + $environment['gaps'] = $clone->gaps->clone($uuid_factory, $environment); + + $update_text_closure = $this->buildReplaceClonedGapsInTextsClosure( + $environment['gaps'] + ); + $clone->cloze_text = $clone->cloze_text->withUpdateMarkdownAfterCloning( + $update_text_closure + ); + $clone->legacy_cloze_text = $update_text_closure($this->legacy_cloze_text); + + $clone->gaps = $environment['gaps']; + $clone->combinations->clone($uuid_factory, $environment); + return $clone; + } + + #[\Override] + public function toStorage( + PersistenceFactory $persistence_factory, + ManipulationType $manipulation_type, + Manipulate $manipulate + ): Manipulate { + $table_definitions = $this->definition->getTableDefinitions(); + + $table_names_builder = $manipulate->getTableNameBuilder( + $table_definitions->getTableSubNameSpace() + ); + + $answer_form_statement = $manipulation_type === ManipulationType::Create + ? $this->buildInsertAnswerFormStatement( + $table_definitions, + $persistence_factory, + $table_names_builder + ) : $this->buildUpdateAnswerFormStatement( + $table_definitions, + $persistence_factory, + $table_names_builder + ); + + return $this->gaps->toStorage( + $this->addReplaceCombinationsStatements( + $manipulate, + $table_definitions, + $table_names_builder + )->withAdditionalStatement( + $answer_form_statement + ), + $persistence_factory, + $table_definitions, + $table_names_builder + ); + } + + #[\Override] + public function toDelete( + PersistenceFactory $persistence_factory, + Manipulate $manipulate + ): Manipulate { + $table_definitions = $this->definition->getTableDefinitions(); + $table_names_builder = $manipulate->getTableNameBuilder( + $table_definitions->getTableSubNameSpace() + ); + + return $this->gaps->toDelete( + $manipulate->withAdditionalStatement( + $this->buildDeleteAnswerFormStatement( + $table_definitions, + $persistence_factory, + $table_names_builder + ) + ), + $persistence_factory, + $table_definitions, + $table_names_builder + ); + } + + private function buildInsertAnswerFormStatement( + TableDefinitions $table_definitions, + PersistenceFactory $persistence_factory, + TableNameBuilder $table_names_builder + ): Insert { + return $persistence_factory->insert( + $table_definitions->getColumns( + $table_names_builder, + AnswerFormSpecificTableTypes::TypeSpecificAnswerForms + ), + [ + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->answer_form_id->toString() + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->scoring_identical->value + ), + $persistence_factory->value( + FieldDefinition::T_INTEGER, + $this->combinations->areCombinationsEnabled() ? 1 : 0 + ) + ] + ); + } + + private function buildUpdateAnswerFormStatement( + TableDefinitions $table_definitions, + PersistenceFactory $persistence_factory, + TableNameBuilder $table_names_builder + ): Update { + $table_type = AnswerFormSpecificTableTypes::TypeSpecificAnswerForms; + return $persistence_factory->update( + $table_definitions->getColumns( + $table_names_builder, + $table_type, + '', + ['answer_form_id'] + ), + [ + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->scoring_identical->value + ), + $persistence_factory->value( + FieldDefinition::T_INTEGER, + $this->combinations->areCombinationsEnabled() ? 1 : 0 + ) + ], + [ + $persistence_factory->where( + $table_definitions->getIdColumn( + $table_names_builder, + $table_type + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->answer_form_id->toString() + ) + ) + ] + ); + } + + private function addReplaceCombinationsStatements( + Manipulate $manipulate, + TableDefinitions $table_definitions, + TableNameBuilder $table_names_builder + ): Manipulate { + if (!$this->combinations->areCombinationsEnabled() + || !$this->updated_combinations) { + return $manipulate; + } + + return $this->combinations->toStorage( + $manipulate, + $table_definitions, + $table_names_builder + ); + } + + private function buildDeleteAnswerFormStatement( + TableDefinitions $table_definitions, + PersistenceFactory $persistence_factory, + TableNameBuilder $table_names_builder + ): Delete { + $table_type = AnswerFormSpecificTableTypes::TypeSpecificAnswerForms; + + return $persistence_factory->delete( + $persistence_factory->table( + $table_names_builder, + $table_type + ), + [ + $persistence_factory->where( + $table_definitions->getForeignKeyColumn( + $table_names_builder, + $table_type + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->answer_form_id->toString() + ) + ) + ] + ); + } + + private function buildAvailablePointsForGenericProperties(): ?float + { + if (!$this->updated_gaps && !$this->updated_combinations) { + return $this->available_points; + } + + if (!$this->combinations->areCombinationsEnabled()) { + return $this->gaps->getTotalAvailablePoints(); + } + + return 1.1; + } + + private function buildReplaceClonedGapsInTextsClosure( + Gaps $new_gaps + ): \Closure { + return function ($text) use ($new_gaps): string { + $position = 0; + return mb_ereg_replace_callback( + '{{' . Gap::GAP_PLACEHOLDER_NAME + . '_[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}}}', + function ($matches) use ($new_gaps, &$position): string { + return '{{' . Gap::GAP_PLACEHOLDER_NAME + . "_{$new_gaps->getGapByPosition($position++)->getAnswerInputId()->toString()}}}"; + }, + $text + ); + }; + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Response/AnswerForm.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Response/AnswerForm.php new file mode 100644 index 000000000000..c7b90e807d3c --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Response/AnswerForm.php @@ -0,0 +1,193 @@ + $answer_input_responses + */ + private readonly array $answer_input_responses; + + /** + * @param array $answer_input_responses + */ + public function __construct( + private readonly TableDefinitions $table_definitions, + private readonly Uuid $response_id, + private readonly Uuid $answer_form_id, + array $answer_input_responses + ) { + $this->answer_input_responses = array_reduce( + $answer_input_responses, + function (array $c, AnswerInput $v): array { + $c[$v->getAnswerInputId()->toString()] = $v; + return $c; + }, + [] + ); + } + + #[\Override] + public function getAnswerFormId(): Uuid + { + return $this->answer_form_id; + } + + #[\Override] + public function isBest(): bool + { + foreach ($this->answer_input_responses as $response) { + if (!$response->isBest()) { + return false; + } + } + + + return true; + } + + #[\Override] + public function toPreviewStorage(): array + { + return array_map( + fn(AnswerInput $v): array => $v->toPreviewStorage(), + $this->answer_input_responses + ); + } + + #[\Override] + public function toStorage( + PersistenceFactory $persistence_factory, + ManipulationType $manipulation_type, + Manipulate $manipulate + ): Manipulate { + return $manipulate->withAdditionalStatement( + array_reduce( + $this->answer_input_responses, + fn(?Insert $c, AnswerInput $v): Insert => $v->toStorage( + $this->table_definitions, + $manipulate->getTableNameBuilder( + $this->table_definitions->getTableSubNameSpace() + ), + $persistence_factory, + $c, + $this->response_id + ) + ) + ); + } + + #[\Override] + public function toDelete( + PersistenceFactory $persistence_factory, + Manipulate $manipulate + ): Manipulate { + return $manipulate->withAdditionalStatement( + $persistence_factory->delete( + $persistence_factory->table( + $manipulate->getTableNameBuilder( + $this->table_definitions->getTableSubNameSpace() + ), + AnswerFormSpecificTableTypes::Responses + ), + [ + $persistence_factory->where( + $this->table_definitions->getIdColumn( + $manipulate->getTableNameBuilder( + $this->table_definitions->getTableSubNameSpace() + ), + AnswerFormSpecificTableTypes::Responses + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->response_id->toString() + ) + ) + ] + ) + ); + } + + #[\Override] + public function toClientSideRepresentation(): array + { + return array_reduce( + $this->answer_input_responses, + function (array $c, AnswerInput $v): array { + $c[$v->getAnswerInputId()->toString()] = $v->toClientSideRepresentation(); + return $c; + }, + [] + ); + } + + public function getResponseForInput( + Uuid $answer_input_id + ): Uuid|string|null { + if (isset($this->answer_input_responses[$answer_input_id->toString()])) { + return $this->answer_input_responses[$answer_input_id->toString()]->getResponse(); + } + + return null; + } + + public function calculateAwardedPoints( + Properties $answer_form_properties + ): float { + return array_reduce( + $this->answer_input_responses, + function (?float $c, AnswerInput $v) use ($answer_form_properties): ?float { + $gap = $answer_form_properties + ->getGaps() + ->getGapById( + $v->getAnswerInputId() + ); + + if ($gap === null) { + return 0.0; + } + + $awarded_points = $gap->getType()->calculateAwardedPointsForResponse( + $gap, + $v->getResponse() + ); + + if ($c === null) { + return $awarded_points; + } + + return $c + $awarded_points; + } + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Response/AnswerInput.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Response/AnswerInput.php new file mode 100644 index 000000000000..8d0b0471fcb8 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Response/AnswerInput.php @@ -0,0 +1,135 @@ +gap->getAnswerInputId(); + } + + public function getResponse(): Uuid|string|null + { + if ($this->selected_answer_option !== null) { + return $this->selected_answer_option; + } + + if ($this->text !== '') { + return $this->text; + } + + return null; + } + + public function isBest(): bool + { + return $this->gap->getType()->isBestResponse($this->gap, $this); + } + + public function toClientSideRepresentation(): array + { + return [ + self::KEY_GAP_TYPE => $this->gap->getType()->getIdentifier(), + self::KEY_RESPONSE => $this->gap->getType()->buildClientSideRepresentationOfResponse( + $this->gap, + $this + ) + ]; + } + + public function toPreviewStorage(): array + { + return [ + self::KEY_SELECTED_ANSWER_OPTION => $this->selected_answer_option?->toString(), + self::KEY_TEXT => $this->text + ]; + } + + public function toStorage( + TableDefinitions $table_definitions, + TableNameBuilder $table_names_builder, + PersistenceFactory $persistence_factory, + ?Insert $insert, + Uuid $id + ): Insert { + if ($insert === null) { + return $persistence_factory->insert( + $table_definitions->getColumns( + $table_names_builder, + AnswerFormSpecificTableTypes::Responses + ), + $this->buildValuesArrayForStorage($persistence_factory, $id) + ); + } + + return $insert->withAdditionalValues( + $this->buildValuesArrayForStorage($persistence_factory, $id) + ); + } + + private function buildValuesArrayForStorage( + PersistenceFactory $persistence_factory, + Uuid $id + ): array { + return [ + $persistence_factory->value( + FieldDefinition::T_TEXT, + $id->toString() + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->gap->getAnswerInputId()->toString() + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->selected_answer_option?->toString() + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->text + ) + ]; + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Response/Factory.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Response/Factory.php new file mode 100644 index 000000000000..c5d46e33f2b5 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Response/Factory.php @@ -0,0 +1,158 @@ +getDefinition() + ->getTableDefinitions(); + + if ($query === null) { + return new AnswerForm( + $table_definitions, + $response_id, + $answer_form_properties->getAnswerFormId() + ); + } + + return $query->retrieveCurrentRecord( + $this->persistence_factory->table( + $query->getTableNameBuilder( + $table_definitions->getTableSubNameSpace() + ), + AnswerFormSpecificTableTypes::Responses + ), + $query->getRefinery()->custom()->transformation( + fn (array $vs): AnswerForm => new AnswerForm( + $table_definitions, + $response_id, + $answer_form_properties->getAnswerFormId(), + $this->retrieveAnswerInputResponsesFromValues( + $answer_form_properties, + $vs + ) + ) + ) + ); + } + + public function fromPost( + Uuid $response_id, + Properties $answer_form_properties, + RequestWrapper $post_wrapper + ): AnswerForm { + return new AnswerForm( + $answer_form_properties + ->getDefinition() + ->getTableDefinitions(), + $response_id, + $answer_form_properties->getAnswerFormId(), + $answer_form_properties->getGaps()->retrieveResponsesFromPost( + $post_wrapper, + $this->uuid_factory + ), + ); + } + + public function fromPreviewData( + Uuid $response_id, + Properties $answer_form_properties, + array $preview_data + ): AnswerForm { + return new AnswerForm( + $answer_form_properties + ->getDefinition() + ->getTableDefinitions(), + $response_id, + $answer_form_properties->getAnswerFormId(), + $answer_form_properties->getGaps()->retrieveResponsesFromPreviewData( + $this->uuid_factory, + $preview_data + ), + ); + } + + public function getBestResponse( + Properties $answer_form_properties, + ): AnswerForm { + return new AnswerForm( + $answer_form_properties + ->getDefinition() + ->getTableDefinitions(), + $this->uuid_factory->uuid4(), + $answer_form_properties->getAnswerFormId(), + $answer_form_properties->getGaps()->getBestResponses(), + ); + } + + private function retrieveAnswerInputResponsesFromValues( + Properties $answer_form_properties, + array $values + ): array { + return array_reduce( + $values, + function (array $c, array $vs) use ($answer_form_properties): array { + $answer_input_id = $this->uuid_factory + ->fromString($vs['answer_input_id']); + + $gap = $answer_form_properties->getGaps()->getGapById( + $answer_input_id + ); + + if ($gap === null) { + return $c; + } + + $c[] = new AnswerInput( + $gap, + $vs['selected_answer_option'] === null + ? null + : $this->uuid_factory + ->fromString($vs['selected_answer_option']), + $vs['text'] + ); + + return $c; + }, + [] + ); + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/TableDefinitions.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/TableDefinitions.php new file mode 100644 index 000000000000..298f5cdcdd21 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/TableDefinitions.php @@ -0,0 +1,382 @@ +table_name_specifiers; + } + + public function getColumns( + TableNameBuilder $table_names_builder, + TableTypes $table_type, + string $sub_table_identifier = '', + array $columns_to_skip = [] + ): array { + $table = $this->persistence_factory->table( + $table_names_builder, + $table_type, + $sub_table_identifier + ); + $column_identifiers = match($table_type) { + AnswerFormSpecificTableTypes::TypeSpecificAnswerForms => self::ANSWER_FORM_TABLE_COLUMNS, + AnswerFormSpecificTableTypes::AnswerInputs => self::ANSWER_INPUTS_TABLE_COLUMNS, + AnswerFormSpecificTableTypes::AnswerOptions => self::ANSWER_OPTIONS_TABLE_COLUMNS, + AnswerFormSpecificTableTypes::Responses => self::RESPONSE_TABLE_COLUMNS, + AnswerFormSpecificTableTypes::Additional => match($sub_table_identifier) { + self::COMBINATION_TABLE_IDENTIFIER => self::COMBINATION_TABLE_COLUMNS, + self::COMBINATION_TO_ANSWER_OPTIONS_TABLE_IDENTIFIER => self::COMBINATION_TO_ANSWER_OPTIONS_TABLE_COLUMNS + } + }; + + return array_map( + fn(string $v): Column => $this->persistence_factory->column( + $table, + $v + ), + array_values( + array_filter( + $column_identifiers, + fn(string $v) => !in_array($v, $columns_to_skip) + ) + ) + ); + } + + public function getIdColumn( + TableNameBuilder $table_names_builder, + TableTypes $table_type, + string $sub_table_identifier = '' + ): Column { + $table = $this->persistence_factory->table( + $table_names_builder, + $table_type, + $sub_table_identifier + ); + + if ($table_type === AnswerFormSpecificTableTypes::TypeSpecificAnswerForms) { + return $this->persistence_factory->column( + $table, + self::ANSWER_FORM_TABLE_ID_COLUMN + ); + } + + if ($table_type === AnswerFormSpecificTableTypes::Responses) { + return $this->persistence_factory->column( + $table, + self::RESPONSE_TABLE_ID_COLUMN + ); + } + + if ($sub_table_identifier === self::COMBINATION_TO_ANSWER_OPTIONS_TABLE_IDENTIFIER) { + return $this->persistence_factory->column( + $table, + self::COMBINATION_TO_ANSWER_OPTIONS_TABLE_ID_COLUMN + ); + } + + return $this->persistence_factory->column( + $table, + self::ID_COLUMN + ); + } + + public function getForeignKeyColumn( + TableNameBuilder $table_names_builder, + TableTypes $table_type, + string $sub_table_identifier = '' + ): Column { + $table = $this->persistence_factory->table( + $table_names_builder, + $table_type, + $sub_table_identifier + ); + + return match($table_type) { + AnswerFormSpecificTableTypes::TypeSpecificAnswerForms => $this->persistence_factory->column( + $table, + self::ANSWER_FORM_TABLE_FOREIGN_KEY_COLUMN + ), + AnswerFormSpecificTableTypes::AnswerInputs => $this->persistence_factory->column( + $table, + self::ANSWER_INPUTS_TABLE_FOREIGN_KEY_COLUMN + ), + AnswerFormSpecificTableTypes::AnswerOptions => $this->persistence_factory->column( + $table, + self::ANSWER_OPTIONS_TABLE_FOREIGN_KEY_COLUMN + ), + AnswerFormSpecificTableTypes::Responses => $this->persistence_factory->column( + $table, + self::RESPONSE_TABLE_FOREIGN_KEY_COLUMN + ), + AnswerFormSpecificTableTypes::Additional => $this->persistence_factory->column( + $table, + $sub_table_identifier === self::COMBINATION_TABLE_IDENTIFIER + ? self::COMBINATION_TABLE_FOREIGN_KEY_COLUMN + : self::COMBINATION_TO_ANSWER_OPTIONS_TABLE_FOREIGN_KEY_COLUMN + ) + }; + } + + #[\Override] + public function completeQuestionQuery( + Query $query, + ?Column $answer_form_id_column + ): Query { + $table_names_builder = $query->getTableNameBuilder( + $this->getTableSubNameSpace() + ); + + $combinations_id_column = $this->getIdColumn( + $table_names_builder, + AnswerFormSpecificTableTypes::Additional, + self::COMBINATION_TABLE_IDENTIFIER + ); + + return $query->withAdditionalJoin( + $this->persistence_factory->join( + $answer_form_id_column, + $this->getForeignKeyColumn( + $table_names_builder, + AnswerFormSpecificTableTypes::TypeSpecificAnswerForms + ), + JoinType::Left + ) + )->withAdditionalSelect( + $this->persistence_factory->select( + $this->getColumns( + $table_names_builder, + AnswerFormSpecificTableTypes::TypeSpecificAnswerForms + ) + ) + )->withAdditionalJoin( + $this->persistence_factory->join( + $answer_form_id_column, + $this->getForeignKeyColumn( + $table_names_builder, + AnswerFormSpecificTableTypes::AnswerInputs + ), + JoinType::Left + ) + )->withAdditionalSelect( + $this->persistence_factory->select( + $this->getColumns( + $table_names_builder, + AnswerFormSpecificTableTypes::AnswerInputs + ) + ) + )->withAdditionalJoin( + $this->persistence_factory->join( + $this->getIdColumn( + $table_names_builder, + AnswerFormSpecificTableTypes::AnswerInputs + ), + $this->getForeignKeyColumn( + $table_names_builder, + AnswerFormSpecificTableTypes::AnswerOptions + ), + JoinType::Left + ) + )->withAdditionalOrder( + $this->persistence_factory->order( + $this->getIdColumn( + $table_names_builder, + AnswerFormSpecificTableTypes::AnswerInputs + ) + ) + )->withAdditionalSelect( + $this->persistence_factory->select( + $this->getColumns( + $table_names_builder, + AnswerFormSpecificTableTypes::AnswerOptions + ) + ) + )->withAdditionalOrder( + $this->persistence_factory->order( + $this->persistence_factory->column( + $this->persistence_factory->table( + $table_names_builder, + AnswerFormSpecificTableTypes::AnswerOptions + ), + self::ANSWER_OPTIONS_TABLE_ADDITIONAL_ORDER_COLUMN + ) + ) + )->withAdditionalJoin( + $this->persistence_factory->join( + $answer_form_id_column, + $this->getForeignKeyColumn( + $table_names_builder, + AnswerFormSpecificTableTypes::Additional, + self::COMBINATION_TABLE_IDENTIFIER + ), + JoinType::Left + ) + )->withAdditionalSelect( + $this->persistence_factory->select( + $this->getColumns( + $table_names_builder, + AnswerFormSpecificTableTypes::Additional, + self::COMBINATION_TABLE_IDENTIFIER + ) + ) + )->withAdditionalJoin( + $this->persistence_factory->join( + $combinations_id_column, + $this->getForeignKeyColumn( + $table_names_builder, + AnswerFormSpecificTableTypes::Additional, + self::COMBINATION_TO_ANSWER_OPTIONS_TABLE_IDENTIFIER + ), + JoinType::Left + ) + )->withAdditionalSelect( + $this->persistence_factory->select( + $this->getColumns( + $table_names_builder, + AnswerFormSpecificTableTypes::Additional, + self::COMBINATION_TO_ANSWER_OPTIONS_TABLE_IDENTIFIER + ) + ) + )->withAdditionalOrder( + $this->persistence_factory->order( + $combinations_id_column + ) + ); + } + + #[\Override] + public function completeResponseQuery( + Query $query, + ?Column $base_table_id_column + ): Query { + $table_names_builder = $query->getTableNameBuilder( + $this->getTableSubNameSpace() + ); + + return $query->withAdditionalSelect( + $this->persistence_factory->select( + $this->getColumns( + $table_names_builder, + AnswerFormSpecificTableTypes::Responses + ) + ) + )->withAdditionalJoin( + $this->persistence_factory->join( + $base_table_id_column, + $this->getForeignKeyColumn( + $table_names_builder, + AnswerFormSpecificTableTypes::Responses + ), + JoinType::Left + ) + ); + } + + public function getCombinationsTableIdentifier(): string + { + return self::COMBINATION_TABLE_IDENTIFIER; + } + + public function getCombinationToAnswerOptionsTableIdentifier(): string + { + return self::COMBINATION_TO_ANSWER_OPTIONS_TABLE_IDENTIFIER; + } +} diff --git a/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Views/Edit.php b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Views/Edit.php new file mode 100644 index 000000000000..582affb3d234 --- /dev/null +++ b/components/ILIAS/Questions/src/AnswerFormTypes/Cloze/Views/Edit.php @@ -0,0 +1,339 @@ +getSubAction(); + + return match($sub_action) { + '' => $this->startEditing($environment), + self::SUB_ACTION_PROCESS_BASIC_PROPERTIES => $this->processBasicEditingForm( + $environment + ), + default => $this->forwardCmdToEditGaps( + $environment + ) + }; + } + + #[\Override] + public function edit( + Environment $environment + ): EditOverview|EditForm|Async|ForImmediateStorage|Properties { + $sub_action = $environment->getSubAction(); + + $combinations = $environment->getAnswerFormProperties()->getCombinations(); + if ($environment->isMarkingRequired() + && $combinations->areCombinationsEnabled()) { + $combinations->getEditView()->addCombinationsSubTab($environment); + } + + if ($sub_action === '') { + return $environment->getAnswerFormProperties()->completeEditOverview( + $environment, + $environment->getPresentationFactory()->getEditOverview( + $environment, + $environment->withSubActionParameter(self::SUB_ACTION_EDIT_BASIC_PROPERTIES) + ->withFormStartSubActionParameter(self::SUB_ACTION_EDIT_BASIC_PROPERTIES) + ->getUrlBuilder() + ) + ); + } + + $environment->setEditAnswerFormBackTarget(); + + return match ($sub_action) { + self::SUB_ACTION_EDIT_BASIC_PROPERTIES => $this->startEditing($environment), + self::SUB_ACTION_ADD_LEGACY_TEXT_BASIC_PROPERTIES => + $this->addLegacyTextToBasicProperties($environment), + self::SUB_ACTION_CONFIRMED_GAP_REMOVAL, + self::SUB_ACTION_PROCESS_BASIC_PROPERTIES => $this->processBasicEditingForm( + $environment->withPreservedTableRowIdsParameter() + ), + default => $this->forwardCmdToEditGaps( + $environment + ->withPreservedTableRowIdsParameter() + ->withPreservedFormStartSubActionParameter() + ) + }; + } + + #[\Override] + public function other( + Environment $environment + ): Async|Viewable|Properties { + $from_other = $environment + ->getAnswerFormProperties() + ->getCombinations()->getEditView()->show($environment); + + if ($from_other === null) { + return $this->edit( + $environment->withDefaultSubAction() + ); + } + + return $from_other; + } + + #[\Override] + public function backToLastEditCommand( + Environment $environment + ): EditForm { + return $this->edit_gaps->do( + $environment, + EditGaps::SUB_ACTION_BACK_TO_SET_ANSWER_OPTIONS + ); + } + + private function startEditing( + Environment $environment + ): EditForm { + $input_builder = $this->buildInputsBuilderForBasicInputs( + $environment, + false + ); + $input_builder->resetCarry(); + + return $this->buildBasicEditingForm( + $environment, + $input_builder, + false + ); + } + + private function forwardCmdToEditGaps( + Environment $environment + ): EditForm|Async|Properties { + $processed_form = $this->edit_gaps->do( + $environment, + $environment->getSubAction() + ); + if (is_string($processed_form)) { + $inputs_builder = $this->buildInputsBuilderForBasicInputs( + $environment, + false, + $processed_form + ); + + $inputs_builder->persistCarry(); + + return $this->buildBasicEditingForm( + $environment, + $inputs_builder, + false + ); + } + + return $processed_form; + } + + private function buildBasicEditingForm( + Environment $environment, + InputsBuilderSession $inputs_builder, + bool $add_legacy_cloze_text_to_input + ): EditForm { + $editing_form = $this->buildEditFormForBasicInputs( + $environment, + $inputs_builder + ); + + /** @var \ILIAS\Questions\AnswerFormTypes\Cloze\Properties\Properties $properties */ + $properties = $environment->getAnswerFormProperties(); + if (!$add_legacy_cloze_text_to_input + && $properties->getLegacyClozeText() !== '' + && $properties->getClozeText()->getRawRepresentation() === '') { + return $editing_form->withInsertLegacyTextsButton( + $environment->withSubActionParameter( + self::SUB_ACTION_ADD_LEGACY_TEXT_BASIC_PROPERTIES + )->getUrlBuilder() + ); + } + + return $editing_form; + } + + private function addLegacyTextToBasicProperties( + Environment $environment + ): EditForm { + $inputs_builder = $this->buildInputsBuilderForBasicInputs( + $environment, + true + ); + + $inputs_builder->persistCarry(); + + return $this->buildBasicEditingForm( + $environment, + $inputs_builder, + true + ); + } + + private function processBasicEditingForm( + Environment $environment + ): EditForm|Async|ForImmediateStorage|Properties { + $inputs_builder = $this->buildInputsBuilderForBasicInputs( + $environment, + false, + ); + + $form = $this->buildBasicEditingForm( + $environment, + $inputs_builder, + false + )->withRequest($environment->getHttpServices()->request()); + + /** @var \ILIAS\Questions\AnswerFormTypes\Cloze\Properties\Properties $data */ + $data = $form->getData(); + if ($data === null) { + $inputs_builder->persistCarry(); + return $form; + } + + $new_gaps = $data->getGaps(); + $old_gaps = $environment->getAnswerFormProperties()->getGaps(); + + if ($environment->getSubAction() !== self::SUB_ACTION_CONFIRMED_GAP_REMOVAL) { + $removed_gaps = $new_gaps->getRemovedGaps($old_gaps); + if ($removed_gaps !== []) { + return $form->withConfirmation( + $this->buildRemovedGapsConfirmation( + $environment, + $removed_gaps + ) + ); + } + } + + if ($new_gaps->getAddedGaps($old_gaps) === []) { + return new ForImmediateStorage($data); + } + + return $this->edit_gaps->do( + $environment->withAnswerFormProperties( + $data->withGaps( + $data->getGaps()->withMarkedIncompleteGaps() + ) + )->withPreservedFormStartSubActionParameter() + ); + } + + private function buildEditFormForBasicInputs( + Environment $environment, + InputsBuilderSession $inputs_builder + ): EditForm { + return $environment->getPresentationFactory()->getEditForm( + $inputs_builder, + $environment + ->withSubActionParameter(self::SUB_ACTION_PROCESS_BASIC_PROPERTIES) + ->withPreservedFormStartSubActionParameter() + ->getUrlBuilder(), + null + ); + } + + private function buildInputsBuilderForBasicInputs( + Environment $environment, + bool $add_legacy_cloze_text_to_input, + ?string $carry = null + ): InputsBuilderSession { + $inputs_builder = $environment->getPresentationFactory() + ->getSessionBasedInputsBuilder( + $environment->getRefinery()->custom()->transformation( + fn(?string $carry): Section => $this->properties_factory + ->fromCarry( + $environment->getAnswerFormProperties(), + $carry + )->buildBasicEditingInputs( + $environment, + $this->properties_factory, + $this->cloze_text_factory, + $add_legacy_cloze_text_to_input + ) + ) + ); + + if ($carry === null) { + return $inputs_builder; + } + + return $inputs_builder->withCarry($carry); + } + + /** + * @param array<\ILIAS\Questions\AnswerFormTypes\Cloze\Properties\Gaps\Gap> $removed_gaps + */ + private function buildRemovedGapsConfirmation( + Environment $environment, + array $removed_gaps + ): InterruptiveModal { + return $environment->getUIFactory()->modal()->interruptive( + $environment->getLanguage()->txt('confirm'), + $environment->getLanguage()->txt('confirm_remove_gaps'), + $environment->withSubActionParameter( + self::SUB_ACTION_CONFIRMED_GAP_REMOVAL + )->getUrlBuilder()->buildURI()->__toString() + )->withAffectedItems( + array_map( + fn(Gap $v): InterruptiveItem => $environment->getUIFactory() + ->modal()->interruptiveItem()->standard( + $v->getAnswerInputId()->toString(), + $v->buildShortenedGapName() + ), + $removed_gaps + ) + ); + } +} diff --git a/components/ILIAS/Questions/src/Attempt/AdditionalAttemptData.php b/components/ILIAS/Questions/src/Attempt/AdditionalAttemptData.php new file mode 100644 index 000000000000..ff7fbbea58a9 --- /dev/null +++ b/components/ILIAS/Questions/src/Attempt/AdditionalAttemptData.php @@ -0,0 +1,30 @@ + + */ + private array $responses = []; + + public function __construct( + private readonly Uuid $identifier, + private readonly int $shuffle_questions_seed, + private array $additional_data = [] + ) { + } + + public function getId(): Uuid + { + return $this->identifier; + } + + public function getShuffleQuestionsSeed(): GivenSeed + { + return new GivenSeed($this->shuffle_questions_seed); + } + + public function getAdditionalDataFor( + Uuid $parent_id + ): ?string { + if (!isset($this->additional_data[$parent_id->toString()])) { + return null; + } + return $this->additional_data[$parent_id->toString()]; + } + + public function withAdditionalData( + Uuid $parent_id, + string $data + ): self { + if (isset($this->additional_data[$parent_id->toString()])) { + throw new InvalidArgumentException( + 'This is a storage for data that stays constant accross the test run.' + . 'Data cannot be changed one it is set.' + ); + } + + $clone = clone $this; + $clone->additional_data[$parent_id->toString()] = $data; + return $clone; + } + + public function getResponseForQuestion( + Uuid $question_id + ): ?Response { + return $this->responses[$question_id->toString()] ?? null; + } + + public function withResponse( + Response $response + ): self { + $clone = clone $this; + $clone->responses[$response->getQuestionId()->toString()] = $response; + return $clone; + } + + public function basicDataToStorage( + PersistenceFactory $persistence_factory, + TableDefinitions $table_definitions, + TableNameBuilder $table_names_builder + ): Insert { + return $persistence_factory->insert( + $table_definitions->getColumns( + $table_names_builder, + TableTypes::AttemptData + ), + [ + $persistence_factory->value( + \ilDBConstants::T_TEXT, + $this->identifier->toString() + ), + $persistence_factory->value( + \ilDBConstants::T_INTEGER, + $this->shuffle_questions_seed + ) + ] + ); + } + + public function additionalDataToStorage( + PersistenceFactory $persistence_factory, + TableDefinitions $table_definitions, + TableNameBuilder $table_names_builder + ): ?Insert { + return array_reduce( + array_keys($this->additional_data), + fn(?Insert $c, string $v): Insert + => $this->buildAdditionalDataInsert( + $persistence_factory, + $table_definitions, + $table_names_builder, + $c, + $v + ) + ); + } + + public function toPreviewStorage(): string + { + return json_encode( + [ + self::KEY_ADDITONAL_DATA => $this->additional_data, + self::KEY_RESPONSES => array_map( + fn(Response $v): array => $v->toPreviewStorage(), + $this->responses + ) + ] + ); + } + + private function buildAdditionalDataInsert( + PersistenceFactory $persistence_factory, + TableDefinitions $table_definitions, + TableNameBuilder $table_names_builder, + ?Insert $insert, + string $parent_id + ): Insert { + if ($insert === null) { + return $persistence_factory->insert( + $table_definitions->getColumns( + $table_names_builder, + TableTypes::AdditionalAttemptData + ), + $this->buildAdditionalDataValuesArray( + $persistence_factory, + $parent_id + ) + ); + } + + return $insert->withAdditionalValues( + $this->buildAdditionalDataValuesArray( + $persistence_factory, + $parent_id + ) + ); + } + + private function buildAdditionalDataValuesArray( + PersistenceFactory $persistence_factory, + string $parent_id + ): array { + return [ + $persistence_factory->value( + \ilDBConstants::T_TEXT, + $this->identifier->toString() + ), + $persistence_factory->value( + \ilDBConstants::T_TEXT, + $parent_id + ), + $persistence_factory->value( + \ilDBConstants::T_TEXT, + $this->additional_data[$parent_id] + ) + ]; + } +} diff --git a/components/ILIAS/Questions/src/Attempt/Repository.php b/components/ILIAS/Questions/src/Attempt/Repository.php new file mode 100644 index 000000000000..52333a04ec99 --- /dev/null +++ b/components/ILIAS/Questions/src/Attempt/Repository.php @@ -0,0 +1,432 @@ +table_names_builder = new TableNameBuilder( + QuestionRepository::COMPONENT_NAMESPACE, + null + ); + } + + /** + * @param array $questions + * @throws InvalidArgumentException + */ + public function getAttemptFor( + ?Uuid $attempt_id, + array $questions + ): Attempt { + if ($attempt_id === null) { + $attempt = $this->getNewAttempt($questions); + $this->storeAttempt($attempt); + return $attempt; + } + + $base_table_id_column = $this->table_definitions->getIdColumn( + $this->table_names_builder, + TableTypes::Responses + ); + + $database_values = array_reduce( + $questions, + fn(Query $c, Question $v): Query => $v->completeResponseQuery( + $c, + $base_table_id_column + ), + $this->buildQuery($attempt_id) + )->getRecords() + ->current(); + + if ($database_values === null) { + throw new \InvalidArgumentException('No Attempt With Given Identifier'); + } + + return array_reduce( + $questions, + fn(Attempt $c, Question $v): Attempt => $c->withResponse( + $this->retrieveCurrentResponseFromQuery( + $v, + $c->getId(), + $database_values + ) + ), + $this->retrieveAttemptFromQuery( + $attempt_id, + $database_values, + $this->retriveAdditionalDataFromQuery( + $database_values + ) + ) + ); + } + + public function getNewResponseFor( + Uuid $question_id, + Uuid $attempt_id + ): Response { + return new Response( + $this->uuid_factory->uuid4(), + $question_id, + $attempt_id, + new \DateTimeImmutable('@' . time()) + ); + } + + public function storeResponse( + Response $response + ): void { + $response->toStorage( + $this->persistence_factory, + $this->table_definitions, + $this->table_names_builder, + ManipulationType::Create, + new Manipulate( + $this->db, + QuestionRepository::COMPONENT_NAMESPACE + ) + )->run(); + } + + public function deleteResponsesFor( + Uuid $attempt_id, + Question $question + ): void { + $manipulate = new Manipulate( + $this->db, + QuestionRepository::COMPONENT_NAMESPACE + ); + foreach ($this->getAllResponsesFor($attempt_id, $question) as $response) { + $manipulate = $response->toDelete( + $this->persistence_factory, + $this->table_definitions, + $this->table_names_builder, + $manipulate + ); + } + + $manipulate->run(); + } + + public function getAttemptFromPreviewData( + Question $question, + string $preview_data + ): ?Attempt { + $data_array = json_decode($preview_data, true); + + if (!is_array($data_array) || $data_array === []) { + return $this->getNewAttempt([$question]); + } + + $response_id = $this->uuid_factory->uuid4(); + + return array_reduce( + $data_array[Attempt::KEY_RESPONSES] ?? [], + fn(Attempt $c, array $v): Attempt => $c->withResponse( + new Response( + $response_id, + $question->getId(), + $this->uuid_factory->uuid4(), + new \DateTimeImmutable( + 'now', + new \DateTimeZone('UTC') + ), + $v[Response::KEY_POINTS] ?? 0.0, + $question->retrieveAnswerFormResponsesFromPreviewData( + $response_id, + $v[Response::KEY_RESPONSES] ?? [] + ) + ) + ), + new Attempt( + $this->uuid_factory->uuid4(), + 0, + $data_array[Attempt::KEY_ADDITONAL_DATA] ?? [] + ) + ); + } + + private function storeAttempt( + Attempt $attempt + ): void { + $manipulate = (new Manipulate( + $this->db, + QuestionRepository::COMPONENT_NAMESPACE + ))->withAdditionalStatement( + $attempt->basicDataToStorage( + $this->persistence_factory, + $this->table_definitions, + $this->table_names_builder + ) + ); + + $additional_data_statement = $attempt->additionalDataToStorage( + $this->persistence_factory, + $this->table_definitions, + $this->table_names_builder + ); + + if ($additional_data_statement !== null) { + $manipulate = $manipulate->withAdditionalStatement( + $additional_data_statement + ); + } + + $manipulate->run(); + } + + /** + * @param array $questions + */ + private function getNewAttempt( + array $questions + ): Attempt { + $attempt = new Attempt( + $this->uuid_factory->uuid4(), + (new RandomSeed())->createSeed() + ); + + return array_reduce( + $questions, + fn(Attempt $c, Question $v) => $v->initializeAttemptData($c), + $attempt + ); + } + + private function getAllResponsesFor( + Uuid $attempt_id, + Question $question + ): \Generator { + $base_table_id_column = $this->table_definitions->getIdColumn( + $this->table_names_builder, + TableTypes::Responses + ); + + $database_values = $question->completeResponseQuery( + $this->buildQuery($attempt_id), + $base_table_id_column + )->getRecords() + ->current(); + + if ($database_values === null) { + throw new \InvalidArgumentException('No Attempt With Given Identifier'); + } + + return $this->retrieveAllResponsesFromQuery( + $question, + $attempt_id, + $database_values + ); + } + + private function buildQuery( + Uuid $attempt_id + ): Query { + $attempt_data_id_column = $this->table_definitions->getIdColumn( + $this->table_names_builder, + TableTypes::AttemptData + ); + return $this->table_definitions->completeAttemptQuery( + new Query( + $this->db, + $this->refinery, + QuestionRepository::COMPONENT_NAMESPACE, + $this->persistence_factory->table( + $this->table_names_builder, + TableTypes::AttemptData + ) + ), + $attempt_data_id_column + )->withAdditionalWhere( + $this->persistence_factory->where( + $attempt_data_id_column, + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $attempt_id->toString() + ) + ) + )->withGroupBy( + $attempt_data_id_column + ); + } + + private function retriveAdditionalDataFromQuery( + Query $query + ): array { + return $query->retrieveCurrentRecord( + $this->persistence_factory->table( + $this->table_names_builder, + TableTypes::AdditionalAttemptData + ), + $this->refinery->custom()->transformation( + function (array $vs): array { + return array_reduce( + $vs, + function (array $c, array $v): array { + $c[$v['parent_id']] = $v['data']; + return $c; + }, + [] + ); + } + ) + ); + } + + private function retrieveAttemptFromQuery( + Uuid $attempt_id, + Query $query, + array $additional_seeds + ): Attempt { + return $query->retrieveCurrentRecord( + $this->persistence_factory->table( + $this->table_names_builder, + TableTypes::AttemptData + ), + $this->refinery->custom()->transformation( + fn(array $vs): Attempt => new Attempt( + $attempt_id, + $vs[0]['shuffler_seed'], + $additional_seeds + ) + ) + ); + } + + private function retrieveCurrentResponseFromQuery( + Question $question, + Uuid $attempt_id, + Query $query + ): Response { + return $query->retrieveCurrentRecord( + $this->persistence_factory->table( + $this->table_names_builder, + TableTypes::Responses + ), + $this->refinery->custom()->transformation( + function ( + array $vs + ) use ( + $question, + $attempt_id, + $query + ): Response { + $question_id_string = $question->getId()->toString(); + + $last_record = array_reduce( + $vs, + fn(array $c, array $v): array + => $v['question_id'] === $question_id_string ? $v : $c, + [] + ); + + if ($last_record === []) { + return $this->getNewResponseFor( + $question->getId(), + $attempt_id + ); + } + + $response_id = $this->uuid_factory + ->fromString($last_record['id']); + + return new Response( + $response_id, + $question->getId(), + $attempt_id, + new \DateTimeImmutable("@{$last_record['create_timestamp']}"), + $last_record['awarded_points'], + $question->retrieveAnswerFormResponsesFromQuery( + $response_id, + $query + ) + ); + } + ) + ); + } + + private function retrieveAllResponsesFromQuery( + Question $question, + Uuid $attempt_id, + Query $query + ): \Generator { + return $query->retrieveCurrentRecord( + $this->persistence_factory->table( + $this->table_names_builder, + TableTypes::Responses + ), + $this->refinery->custom()->transformation( + function ( + array $vs + ) use ( + $question, + $attempt_id, + $query + ): \Generator { + if ($vs === []) { + return; + } + + foreach ($vs as $v) { + $response_id = $this->uuid_factory + ->fromString($v['id']); + + yield new Response( + $response_id, + $question->getId(), + $attempt_id, + new \DateTimeImmutable("@{$v['create_timestamp']}"), + $v['awarded_points'], + $question->retrieveAnswerFormResponsesFromQuery( + $response_id, + $query + ) + ); + } + } + ) + ); + } +} diff --git a/components/ILIAS/Questions/src/Attempt/Response.php b/components/ILIAS/Questions/src/Attempt/Response.php new file mode 100644 index 000000000000..2eb7acd5f895 --- /dev/null +++ b/components/ILIAS/Questions/src/Attempt/Response.php @@ -0,0 +1,204 @@ + $answer_form_responses + */ + private array $answer_form_responses; + + /** + * @param array $answer_form_responses + */ + public function __construct( + private readonly Uuid $id, + private readonly Uuid $question_id, + private readonly Uuid $attempt_id, + private readonly \DateTimeImmutable $create_date, + private ?float $awarded_points = null, + array $answer_form_responses = [] + ) { + $this->answer_form_responses = array_reduce( + $answer_form_responses, + function (array $c, AnswerFormResponse $v): array { + $c[$v->getAnswerFormId()->toString()] = $v; + return $c; + }, + [] + ); + } + + public function getId(): Uuid + { + return $this->id; + } + + public function getQuestionId(): Uuid + { + return $this->question_id; + } + + public function getAwardedPoints(): ?float + { + return $this->awarded_points; + } + + public function withAwardedPoints( + float $awarded_points + ): self { + $clone = clone $this; + $clone->awarded_points = $awarded_points; + return $clone; + } + + public function getCreateDate(): \DateTimeImmutable + { + return $this->create_date; + } + + public function getAnswerFormResponse( + Uuid $answer_form_id + ): ?AnswerFormResponse { + return $this->answer_form_responses[$answer_form_id->toString()] ?? null; + } + + public function withAnswerFormResponse( + AnswerFormResponse $response + ): self { + $clone = clone $this; + $clone->answer_form_responses[$response->getAnswerFormId()->toString()] = $response; + return $clone; + } + + public function toPreviewStorage(): array + { + return [ + self::KEY_POINTS => $this->awarded_points, + self::KEY_RESPONSES => array_map( + fn(AnswerFormResponse $v): array => $v->toPreviewStorage(), + $this->answer_form_responses + ) + ]; + } + + public function toStorage( + PersistenceFactory $persistence_factory, + TableDefinitions $table_definitions, + TableNameBuilder $table_names_builder, + ManipulationType $manipulation_type, + Manipulate $manipulate + ): Manipulate { + return array_reduce( + $this->answer_form_responses, + fn(Manipulate $c, AnswerFormResponse $v): Manipulate + => $v->toStorage( + $persistence_factory, + $manipulation_type, + $c + ), + $manipulate->withAdditionalStatement( + $persistence_factory->insert( + $table_definitions->getColumns( + $table_names_builder, + TableTypes::Responses + ), + $this->buildValuesArrayForStorage($persistence_factory) + ) + ) + ); + } + + public function toDelete( + PersistenceFactory $persistence_factory, + TableDefinitions $table_definitions, + TableNameBuilder $table_names_builder, + Manipulate $manipulate + ): Manipulate { + return array_reduce( + $this->answer_form_responses, + fn(Manipulate $c, AnswerFormResponse $v): Manipulate + => $v->toDelete( + $persistence_factory, + $c + ), + $manipulate->withAdditionalStatement( + $persistence_factory->delete( + $persistence_factory->table( + $table_names_builder, + TableTypes::Responses + ), + [ + $persistence_factory->where( + $table_definitions->getIdColumn( + $table_names_builder, + TableTypes::Responses + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->id->toString() + ) + ) + ] + ) + ) + ); + } + + private function buildValuesArrayForStorage( + PersistenceFactory $persistence_factory + ): array { + return [ + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->id->toString() + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->attempt_id->toString() + ), + $persistence_factory->value( + FieldDefinition::T_TEXT, + $this->question_id->toString() + ), + $persistence_factory->value( + FieldDefinition::T_FLOAT, + $this->awarded_points + ), + $persistence_factory->value( + FieldDefinition::T_INTEGER, + $this->create_date->getTimestamp() + ) + ]; + } +} diff --git a/components/ILIAS/Questions/src/Attempt/TableDefinitions.php b/components/ILIAS/Questions/src/Attempt/TableDefinitions.php new file mode 100644 index 000000000000..f60fa886fa7e --- /dev/null +++ b/components/ILIAS/Questions/src/Attempt/TableDefinitions.php @@ -0,0 +1,203 @@ +persistence_factory->table( + $table_names_builder, + $table_type + ); + + return array_map( + fn(string $v): Column => $this->persistence_factory->column( + $table, + $v + ), + match($table_type) { + TableTypes::AttemptData => self::ATTEMPT_DATA_TABLE_COLUMNS, + TableTypes::AdditionalAttemptData => self::ADDITIONAL_ATTEMPT_DATA_TABLE_COLUMNS, + TableTypes::Responses => self::RESPONSES_TABLE_COLUMNS + } + ); + } + + public function getIdColumn( + TableNameBuilder $table_names_builder, + TableTypesInterface $table_type + ): ?Column { + return match($table_type) { + TableTypes::AttemptData, + TableTypes::Responses => $this->persistence_factory->column( + $this->persistence_factory->table( + $table_names_builder, + $table_type + ), + self::ID_COLUMN + ), + default => null + }; + } + + public function getForeignKeyColumn( + TableNameBuilder $table_names_builder, + TableTypesInterface $table_type + ): ?Column { + $table = $this->persistence_factory->table( + $table_names_builder, + $table_type + ); + + return match($table_type) { + TableTypes::AdditionalAttemptData => $this->persistence_factory->column( + $table, + self::ADDITIONAL_ATTEMPT_DATA_TABLE_FOREIGN_KEY_COLUMN + ), + TableTypes::Responses => $this->persistence_factory->column( + $table, + self::RESPONSES_TABLE_PRIMARY_FOREIGN_KEY_COLUMN + ), + default => null + }; + } + + public function completeAttemptQuery( + Query $query, + ?Column $base_table_id_column + ): Query { + $table_names_builder = $query->getTableNameBuilder(null); + + return $query->withAdditionalSelect( + $this->persistence_factory->select( + $this->getColumns( + $table_names_builder, + TableTypes::AttemptData + ) + ) + )->withAdditionalSelect( + $this->persistence_factory->select( + $this->getColumns( + $table_names_builder, + TableTypes::AdditionalAttemptData + ) + ) + )->withAdditionalJoin( + $this->persistence_factory->join( + $base_table_id_column, + $this->getForeignKeyColumn( + $table_names_builder, + TableTypes::AdditionalAttemptData + ), + JoinType::Left + ) + )->withAdditionalSelect( + $this->persistence_factory->select( + $this->getColumns( + $table_names_builder, + TableTypes::Responses + ) + ) + )->withAdditionalJoin( + $this->persistence_factory->join( + $base_table_id_column, + $this->getForeignKeyColumn( + $table_names_builder, + TableTypes::Responses + ), + JoinType::Left + ) + )->withAdditionalOrder( + $this->persistence_factory->order( + $base_table_id_column + ) + )->withAdditionalOrder( + $this->persistence_factory->order( + $this->persistence_factory->column( + $this->persistence_factory->table( + $table_names_builder, + TableTypes::Responses + ), + self::RESPONSES_TABLE_SECONDARY_FOREIGN_KEY_COLUMN + ) + ) + )->withAdditionalOrder( + $this->persistence_factory->order( + $this->persistence_factory->column( + $this->persistence_factory->table( + $table_names_builder, + TableTypes::Responses + ), + self::RESPONSES_TABLE_ADDITIONAL_ORDERING_COLUMN + ), + OrderDirection::Asc + ) + ); + } +} diff --git a/components/ILIAS/Questions/src/Attempt/TableTypes.php b/components/ILIAS/Questions/src/Attempt/TableTypes.php new file mode 100644 index 000000000000..968ad7c793ef --- /dev/null +++ b/components/ILIAS/Questions/src/Attempt/TableTypes.php @@ -0,0 +1,30 @@ +lng, + $this->refinery, + $this->ui_factory, + $this->http, + $this->questions_repository, + $this->attempt_repository, + $this->capabilities_factory, + $required_capabilities_class_names, + $owner_obj_id + ); + } + + #[\Override] + public function getEditView( + array $required_capabilities_class_names, + int $owner_obj_id + ): Edit { + return new Edit( + $this->lng, + $this->current_user, + $this->refinery, + $this->ui_factory, + $this->ui_renderer, + $this->global_screen, + $this->global_tpl, + $this->content_style, + $this->ctrl, + $this->http, + $this->tabs_gui, + $this->ui_services, + $this->uuid_factory, + $this->configuration_repository, + $this->answer_form_factory, + $this->questions_repository, + $this->attempt_repository, + $this->layout_factory, + $this->capabilities_factory, + $required_capabilities_class_names, + $owner_obj_id + ); + } +} diff --git a/components/ILIAS/Questions/src/Definitions/Clonable.php b/components/ILIAS/Questions/src/Definitions/Clonable.php new file mode 100644 index 000000000000..1fde6df69cdd --- /dev/null +++ b/components/ILIAS/Questions/src/Definitions/Clonable.php @@ -0,0 +1,31 @@ + $lng->txt('in_range'), + self::OutOfRange => $lng->txt('out_of_range'), + self::BelowRange => $lng->txt('below_range'), + self::AboveRange => $lng->txt('above_range') + }; + } +} diff --git a/components/ILIAS/Questions/src/Definitions/TextMatchingOptions.php b/components/ILIAS/Questions/src/Definitions/TextMatchingOptions.php new file mode 100644 index 000000000000..c4c76919e765 --- /dev/null +++ b/components/ILIAS/Questions/src/Definitions/TextMatchingOptions.php @@ -0,0 +1,56 @@ + $lng->txt("cloze_textgap_{$this->value}"), + default => sprintf($lng->txt('cloze_textgap_levenshtein_of'), $this->value) + }; + } + + public static function buildOptionsList( + Language $lng + ): array { + return array_reduce( + self::cases(), + function (array $c, self $v) use ($lng): array { + $c[$v->value] = $v->getLabel($lng); + return $c; + }, + [] + ); + } +} diff --git a/components/ILIAS/Questions/src/Persistence/Column.php b/components/ILIAS/Questions/src/Persistence/Column.php new file mode 100644 index 000000000000..63a1aa110f31 --- /dev/null +++ b/components/ILIAS/Questions/src/Persistence/Column.php @@ -0,0 +1,50 @@ +table->getName(); + } + + public function getColumnAlias(): string + { + return "{$this->table->getName()}_{$this->identifier}"; + } + + public function getColumnString(): string + { + return "{$this->table->getName()}.{$this->identifier}"; + } + + public function getAliasedColumnString(): string + { + return "{$this->table->getName()}.{$this->identifier} {$this->getColumnAlias()}"; + } +} diff --git a/components/ILIAS/Questions/src/Persistence/Delete.php b/components/ILIAS/Questions/src/Persistence/Delete.php new file mode 100644 index 000000000000..5bf813a4ccae --- /dev/null +++ b/components/ILIAS/Questions/src/Persistence/Delete.php @@ -0,0 +1,73 @@ + $where + */ + public function __construct( + private readonly Table $table, + private readonly array $where + ) { + } + + public function getTableToLock(): string + { + return $this->table->getName(); + } + + public function toManipulateString( + \ilDBInterface $db + ): string { + return "DELETE FROM {$this->table->getName()}" . PHP_EOL + . $this->buildWhereString($db); + } + + private function buildWhereString( + \ilDBInterface $db + ): string { + $values = []; + return sprintf( + array_reduce( + $this->where, + function (?string $c, Where $v) use ($db, &$values): string { + $quoted_value = $v->getRight()->getQuotedValue($db); + if (is_array($quoted_value)) { + $values = [ + ...$values, + ...array_values($quoted_value) + ]; + } else { + $values[] = $quoted_value; + } + + if ($c === null) { + return "WHERE {$v->toSql()}" . PHP_EOL; + } + + return "{$c}{$v->getLogicalOperator()->value} {$v->toSql()}" . PHP_EOL; + } + ) ?? '', + ...$values + ); + } +} diff --git a/components/ILIAS/Questions/src/Persistence/Factory.php b/components/ILIAS/Questions/src/Persistence/Factory.php new file mode 100644 index 000000000000..f2cf8d805a4b --- /dev/null +++ b/components/ILIAS/Questions/src/Persistence/Factory.php @@ -0,0 +1,125 @@ + $columns + * @param array<\ILIAS\Questions\Persistence\Value> $values + */ + public function insert( + array $columns, + array $values + ): Insert { + return new Insert($columns, $values); + } + + /** + * @param array<\ILIAS\Questions\Persistence\Column> $columns + * @param array<\ILIAS\Questions\Persistence\Value> $values + */ + public function replace( + array $columns, + array $values + ): Replace { + return new Replace($columns, $values); + } + + /** + * @param array<\ILIAS\Questions\Persistence\Column> $columns + * @param array<\ILIAS\Questions\Persistence\Value> $values + * @param array<\ILIAS\Questions\Persistence\Where> $where + */ + public function update( + array $columns, + array $values, + array $where + ): Update { + return new Update($columns, $values, $where); + } + + /** + * @param array<\ILIAS\Questions\Persistence\Where> $where + */ + public function delete( + Table $table, + array $where + ): Delete { + return new Delete($table, $where); + } + + public function table( + TableNameBuilder $table_names_builder, + TableTypes $table, + string $sub_table_identifier = '' + ): Table { + return new Table($table_names_builder, $table, $sub_table_identifier); + } + + public function column( + Table $table, + string $identifier + ): Column { + return new Column($table, $identifier); + } + + /** + * @param array<\ILIAS\Questions\Persistence\Column> $columns + */ + public function select( + array $columns + ): Select { + return new Select($columns); + } + + public function join( + Column $left, + Column $right, + JoinType $type = JoinType::Inner + ): Join { + return new Join($left, $right, $type); + } + + public function where( + Column $left, + Value $right, + Operator $comparison = Operator::Equal, + Junctor $junctor = Junctor::Conjunction, + bool $negate = false + ): Where { + return new Where($left, $right, $comparison, $junctor, $negate); + } + + public function order( + Column $column, + OrderDirection $direction = OrderDirection::Asc + ): Order { + return new Order($column, $direction); + } + + public function value( + string $type, + null|string|int|float|array $value + ): Value { + return new Value($type, $value); + } +} diff --git a/components/ILIAS/Questions/src/Persistence/Insert.php b/components/ILIAS/Questions/src/Persistence/Insert.php new file mode 100644 index 000000000000..bccf22ee793d --- /dev/null +++ b/components/ILIAS/Questions/src/Persistence/Insert.php @@ -0,0 +1,114 @@ + $columns + * @param array<\ILIAS\Questions\Persistence\Value> $values + */ + public function __construct( + protected readonly array $columns, + array $values + ) { + if ($columns === [] || count($columns) !== count($values)) { + throw new \InvalidArgumentException( + "There MUST be at least one Column and the same amount of Values as there are Columns." + ); + } + + $table_name = $columns[0]->getTableName(); + foreach ($columns as $column) { + if ($column->getTableName() !== $table_name) { + throw new \InvalidArgumentException( + "All Columns MUST belong to the same Table." + ); + } + } + + $this->value_sets[] = $values; + } + + /** + * @param array<\ILIAS\Questions\Persistence\Value> $values + */ + public function withAdditionalValues( + array $values + ): self { + if (count($values) !== count($this->columns)) { + throw new \InvalidArgumentException( + "There MUST be the same amount of Values as there are Columns." + ); + } + + $clone = clone $this; + $clone->value_sets[] = $values; + return $clone; + } + + public function getTableToLock(): string + { + return $this->columns[0]->getTableName(); + } + + public function toManipulateString( + \ilDBInterface $db + ): string { + return "INSERT INTO {$this->columns[0]->getTableName()}" . PHP_EOL + . $this->buildColumnsString() . PHP_EOL + . $this->buildValuesString($db); + } + + protected function buildColumnsString(): string + { + return '(' + . implode( + ', ', + array_map( + fn(Column $v): string => $v->getColumnString(), + $this->columns + ) + ) . ')'; + } + + protected function buildValuesString( + \ilDBInterface $db + ): string { + $return = []; + foreach ($this->value_sets as $values) { + $return[] = '(' . implode( + ', ', + array_map( + fn(Value $v): string => $v->getQuotedValue($db), + $values + ) + ) . ')'; + } + + return 'VALUES ' . implode( + ',' . PHP_EOL, + $return + ); + } +} diff --git a/components/ILIAS/Questions/src/Persistence/Join.php b/components/ILIAS/Questions/src/Persistence/Join.php new file mode 100644 index 000000000000..63e13042ff96 --- /dev/null +++ b/components/ILIAS/Questions/src/Persistence/Join.php @@ -0,0 +1,37 @@ +type->value} JOIN {$this->new->getTableName()} " + . "ON {$this->existing->getColumnString()} = {$this->new->getColumnString()}"; + } +} diff --git a/components/ILIAS/Questions/src/Persistence/JoinType.php b/components/ILIAS/Questions/src/Persistence/JoinType.php new file mode 100644 index 000000000000..3a7d11732407 --- /dev/null +++ b/components/ILIAS/Questions/src/Persistence/JoinType.php @@ -0,0 +1,27 @@ +component_name_space, + $table_sub_name_space + ); + } + + public function withAdditionalStatement( + Insert|Update|Replace|Delete $statement + ): self { + $clone = clone $this; + $clone->statements[] = $statement; + return $clone; + } + + public function run(): void + { + if ($this->statements === []) { + return; + } + + $atom_query = $this->db->buildAtomQuery(); + + $manipulates = []; + $locked_tables = []; + foreach ($this->statements as $statement) { + $table_to_lock = $statement->getTableToLock(); + if (!in_array($table_to_lock, $locked_tables)) { + $atom_query->addTableLock($table_to_lock); + $locked_tables[] = $table_to_lock; + } + $manipulates[] = $statement->toManipulateString($this->db); + } + $atom_query->addQueryCallable( + function (\ilDBInterface $db) use ($manipulates): void { + foreach ($manipulates as $manipulate) { + $db->manipulate($manipulate); + } + } + ); + $atom_query->run(); + } +} diff --git a/components/ILIAS/Questions/src/Persistence/ManipulationType.php b/components/ILIAS/Questions/src/Persistence/ManipulationType.php new file mode 100644 index 000000000000..8bca6030c258 --- /dev/null +++ b/components/ILIAS/Questions/src/Persistence/ManipulationType.php @@ -0,0 +1,29 @@ +'; + case Greater = '>'; + case Less = '<'; + case GreaterOrEqual = '>='; + case LessOrEqual = '<='; + case In = 'IN'; + case Like = 'LIKE'; + case Between = 'BETWEEN'; + + public function toSql( + Column $left, + int $nr_of_values + ): string { + $placeholders = '%s'; + for ($i = 1; $i < $nr_of_values; $i++) { + $placeholders .= ', %s'; + } + + return match($this) { + self::In => "{$left->getColumnString()} {$this->value} ({$placeholders})", + self::Between => "{$left->getColumnString()} {$this->value} %s AND %s", + default => "{$left->getColumnString()} {$this->value} %s" + }; + } +} diff --git a/components/ILIAS/Questions/src/Persistence/Order.php b/components/ILIAS/Questions/src/Persistence/Order.php new file mode 100644 index 000000000000..b7d73975448e --- /dev/null +++ b/components/ILIAS/Questions/src/Persistence/Order.php @@ -0,0 +1,35 @@ +column->getColumnString()} {$this->direction->value}"; + } +} diff --git a/components/ILIAS/Questions/src/Persistence/OrderDirection.php b/components/ILIAS/Questions/src/Persistence/OrderDirection.php new file mode 100644 index 000000000000..072b3a6acd97 --- /dev/null +++ b/components/ILIAS/Questions/src/Persistence/OrderDirection.php @@ -0,0 +1,27 @@ +component_name_space, + $table_sub_name_space + ); + } + + public function getRefinery(): Refinery + { + return $this->refinery; + } + + public function withAdditionalSelect( + Select $select + ): self { + $clone = clone $this; + $clone->select[] = $select; + return $clone; + } + + public function withAdditionalJoin( + Join $join + ): self { + $clone = clone $this; + $clone->joins[] = $join; + return $clone; + } + + public function withAdditionalWhere( + Where $where + ): self { + $clone = clone $this; + $clone->where[] = $where; + return $clone; + } + + public function withAdditionalOrder( + Order $order + ): self { + $clone = clone $this; + $clone->order[] = $order; + return $clone; + } + + public function withRange( + Range $range + ): self { + $clone = clone $this; + $clone->range = $range; + return $clone; + } + + public function withGroupBy( + Column $group_by + ): self { + $clone = clone $this; + $clone->group_by = $group_by; + return $clone; + } + + public function getRecords(): \Generator + { + $result = $this->toSql(); + + $this->current_record = [$this->db->fetchAssoc($result)]; + if ($this->current_record[0] === null) { + return null; + } + + if ($this->group_by === null) { + yield $this; + yield from $this->getRecordsUngrouped($result); + return; + } + + yield from $this->getRecordsGrouped( + $result, + $this->group_by->getColumnAlias() + ); + } + + public function retrieveCurrentRecord( + Table $table, + Transformation $transformation + ): mixed { + $table_name = $table->getName(); + $filtered_record = []; + foreach ($this->current_record as $data_set) { + $filtered_dataset = $this->filterDataSetByTable($table_name, $data_set); + if (array_filter($filtered_dataset) !== []) { + $filtered_record[] = $filtered_dataset; + } + } + + return $transformation->transform($filtered_record); + } + + private function toSql(): \ilDBStatement + { + return $this->db->queryF( + 'SELECT ' . implode( + ', ', + array_reduce( + $this->select, + static fn(array $c, Select $v): array => [...$c, ...$v->toColumnsArray()], + [] + ) + ) . " FROM {$this->base_table->getName()}" + . array_reduce( + $this->joins, + static fn(string $c, Join $v): string => $c . PHP_EOL . $v->toSql(), + '' + ) . PHP_EOL + . $this->buildWhereString() + . 'ORDER BY ' . implode( + ', ', + array_reduce( + $this->order, + static function (array $c, Order $v): array { + $c[] = $v->toSql(); + return $c; + }, + [] + ) + ) . PHP_EOL + . ($this->range !== null ? "LIMIT {$this->range->getStart()}, {$this->range->getLength()}" : ''), + $this->binding_types, + $this->binding_values + ); + } + + private function buildWhereString(): string + { + return array_reduce( + $this->where, + function (?string $c, Where $v): string { + $this->addValueToBinding($v->getRight()); + if ($c === null) { + return "WHERE {$v->toSql()}" . PHP_EOL; + } + + return "{$c}{$v->getLogicalOperator()->value} {$v->toSql()}" . PHP_EOL; + } + ) ?? ''; + } + + private function addValueToBinding( + Value $value + ): void { + if (!is_array($value->getValue())) { + $this->binding_types[] = $value->getType(); + $this->binding_values[] = $value->getValue(); + return; + } + + foreach ($value->getValue() as $v) { + $this->binding_types[] = $value->getType(); + $this->binding_values[] = $v; + } + } + + private function getRecordsGrouped( + \ilDBStatement $result, + string $group_by + ): \Generator { + while (($db_record = $this->db->fetchAssoc($result)) !== null) { + if ($db_record[$group_by] === $this->current_record[0][$group_by]) { + $this->current_record[] = $db_record; + continue; + } + yield $this; + $this->current_record = [$db_record]; + } + yield $this; + } + + private function getRecordsUngrouped( + \ilDBStatement $result + ): \Generator { + while (($db_record = $this->db->fetchAssoc($result)) !== null) { + $this->current_record[0] = $db_record; + yield $this; + } + } + + private function filterDataSetByTable( + string $table_name, + array $data_set + ): array { + return array_reduce( + array_keys($data_set), + function (array $c, string $v) use ($table_name, $data_set): array { + if (str_starts_with($v, $table_name)) { + $c[mb_substr($v, mb_strlen($table_name) + 1)] = $data_set[$v]; + } + return $c; + }, + [] + ); + } +} diff --git a/components/ILIAS/Questions/src/Persistence/Replace.php b/components/ILIAS/Questions/src/Persistence/Replace.php new file mode 100644 index 000000000000..9b34db9f6d8c --- /dev/null +++ b/components/ILIAS/Questions/src/Persistence/Replace.php @@ -0,0 +1,33 @@ +columns[0]->getTableName()}" . PHP_EOL + . $this->buildColumnsString() . PHP_EOL + . $this->buildValuesString($db); + } +} diff --git a/components/ILIAS/Questions/src/Persistence/Select.php b/components/ILIAS/Questions/src/Persistence/Select.php new file mode 100644 index 000000000000..ef4c4ad50258 --- /dev/null +++ b/components/ILIAS/Questions/src/Persistence/Select.php @@ -0,0 +1,40 @@ + $columns + */ +class Select +{ + public function __construct( + private readonly array $columns + ) { + } + + public function toColumnsArray(): array + { + return array_map( + fn(Column $v): string => $v->getAliasedColumnString(), + $this->columns + ); + } +} diff --git a/components/ILIAS/Questions/src/Persistence/Storable.php b/components/ILIAS/Questions/src/Persistence/Storable.php new file mode 100644 index 000000000000..261687414e9d --- /dev/null +++ b/components/ILIAS/Questions/src/Persistence/Storable.php @@ -0,0 +1,35 @@ +table_names_builder->getTableNameFor( + $this->table, + $this->sub_table_identifier + ); + } +} diff --git a/components/ILIAS/Questions/src/Persistence/TableNameBuilder.php b/components/ILIAS/Questions/src/Persistence/TableNameBuilder.php new file mode 100644 index 000000000000..e5cd818baa10 --- /dev/null +++ b/components/ILIAS/Questions/src/Persistence/TableNameBuilder.php @@ -0,0 +1,63 @@ +value === '' && $specifier === '') { + throw \InvalidArgumentException( + 'Identifier cannot be empty if Type->value is empty.' + ); + } + + $base_name = "{$this->component_name_space}"; + if ($table->value !== '') { + $base_name .= "_{$table->value}"; + } + + $additions = ''; + + if ($this->table_sub_name_space !== null) { + $additions = $this->table_sub_name_space->get(); + } + + if ($specifier !== '') { + $additions .= "_{$specifier}"; + } + + if ($additions === '') { + return $base_name; + } + + return "{$base_name}_{$additions}"; + } +} diff --git a/components/ILIAS/Questions/src/Persistence/TableSubNameSpace.php b/components/ILIAS/Questions/src/Persistence/TableSubNameSpace.php new file mode 100644 index 000000000000..fee67a8902f3 --- /dev/null +++ b/components/ILIAS/Questions/src/Persistence/TableSubNameSpace.php @@ -0,0 +1,51 @@ + 6 + || mb_strlen($sub_name_space) > 8) { + throw new \InvalidArgumentException( + '$vendor cannot be empty or longer than 6, ' + . '$sub_name_space cannot be empty or longer than 8 characters.' + ); + } + } + + public function get(): string + { + if ($this->vendor === 'ILIAS') { + return $this->sub_name_space; + } + return "{$this->vendor}_{$this->sub_name_space}"; + } +} diff --git a/components/ILIAS/Questions/src/Persistence/TableTypes.php b/components/ILIAS/Questions/src/Persistence/TableTypes.php new file mode 100644 index 000000000000..09a938572931 --- /dev/null +++ b/components/ILIAS/Questions/src/Persistence/TableTypes.php @@ -0,0 +1,25 @@ + $columns + * @param array<\ILIAS\Questions\Persistence\Value> $values + * @param array<\ILIAS\Questions\Persistence\Where> $where + */ + public function __construct( + private readonly array $columns, + private readonly array $values, + private readonly array $where + ) { + if ($columns === [] || count($columns) !== count($values)) { + throw new \InvalidArgumentException( + "There MUST be at least one Column and the same amount of Values as there are Columns." + ); + } + + $table_name = $columns[0]->getTableName(); + foreach ($columns as $column) { + if ($column->getTableName() !== $table_name) { + throw new \InvalidArgumentException( + "All Columns MUST belong to the same Table." + ); + } + } + } + + public function getTableToLock(): string + { + return $this->columns[0]->getTableName(); + } + + public function toManipulateString( + \ilDBInterface $db + ): string { + return "UPDATE {$this->columns[0]->getTableName()}" . PHP_EOL + . $this->buildSetterString($db) . PHP_EOL + . $this->buildWhereString($db); + } + + private function buildSetterString( + \ilDBInterface $db + ): string { + return trim( + array_reduce( + array_keys($this->columns), + fn(string $c, int $v): string => $c + . "{$this->columns[$v]->getColumnString()} = {$this->values[$v]->getQuotedValue($db)},", + 'SET ' + ), + ',' + ); + } + + private function buildWhereString( + \ilDBInterface $db + ): string { + $values = []; + return sprintf( + array_reduce( + $this->where, + function (?string $c, Where $v) use ($db, &$values): string { + $quoted_value = $v->getRight()->getQuotedValue($db); + if (is_array($quoted_value)) { + $values = [ + ...$values, + ...array_values($quoted_value) + ]; + } else { + $values[] = $quoted_value; + } + + if ($c === null) { + return "WHERE {$v->toSql()}" . PHP_EOL; + } + + return "{$c}{$v->getLogicalOperator()->value} {$v->toSql()}" . PHP_EOL; + } + ) ?? '', + ...$values + ); + } +} diff --git a/components/ILIAS/Questions/src/Persistence/Value.php b/components/ILIAS/Questions/src/Persistence/Value.php new file mode 100644 index 000000000000..3ed7a8107435 --- /dev/null +++ b/components/ILIAS/Questions/src/Persistence/Value.php @@ -0,0 +1,68 @@ +type; + } + + public function getValue(): string|int|array + { + return $this->value; + } + + public function getQuotedValue( + \ilDBInterface $db + ): array|string { + if (!is_array($this->value)) { + return $db->quote( + $this->value, + $this->type + ); + } + + return array_map( + fn(mixed $v): string => $db->quote($v, $this->type), + $this->value + ); + } + + public function getNumberOfElements(): int + { + if (is_array($this->value)) { + return count($this->value); + } + + return 1; + } +} diff --git a/components/ILIAS/Questions/src/Persistence/Where.php b/components/ILIAS/Questions/src/Persistence/Where.php new file mode 100644 index 000000000000..c0714bb73224 --- /dev/null +++ b/components/ILIAS/Questions/src/Persistence/Where.php @@ -0,0 +1,52 @@ +negate ? 'NOT ' : '') + . $this->comparison->toSql( + $this->left, + $this->right->getNumberOfElements() + ); + } + + public function getRight(): Value + { + return $this->right; + } + + public function getLogicalOperator(): Junctor + { + return $this->junctor; + } +} diff --git a/components/ILIAS/Questions/src/Presentation/Definitions/Actor.php b/components/ILIAS/Questions/src/Presentation/Definitions/Actor.php new file mode 100644 index 000000000000..b77e2ee1b97e --- /dev/null +++ b/components/ILIAS/Questions/src/Presentation/Definitions/Actor.php @@ -0,0 +1,34 @@ +acquireURLBuilderAndParameters($base_uri); + } + + #[\Override] + public function getHttpServices(): HTTPServices + { + return $this->http; + } + + #[\Override] + public function getLanguage(): Language + { + return $this->lng; + } + + #[\Override] + public function getRefinery(): Refinery + { + return $this->refinery; + } + + #[\Override] + public function getUIFactory(): UIFactory + { + return $this->presentation_factory->getUIFactory(); + } + + #[\Override] + public function setEditAnswerFormBackTarget(): void + { + $this->tabs_gui->clearTargets(); + $this->tabs_gui->setBackTarget( + $this->lng->txt('cancel'), + $this->buildEditAnswerFormBackUrl()->buildURI()->__toString() + ); + } + + #[\Override] + public function addEditAnswerFormSubTab( + string $sub_action, + string $language_variable + ): void { + $this->tabs_gui->addSubTab( + $sub_action, + $this->lng->txt($language_variable), + $this->withSubActionParameter($sub_action) + ->withActionParameter(Edit::ACTION_OTHER_ANSWER_FORM) + ->getUrlBuilder() + ->buildURI() + ->__toString() + ); + } + + #[\Override] + public function activateEditAnswerFormSubTab( + string $sub_action + ): void { + $this->tabs_gui->activateSubTab($sub_action); + } + + #[\Override] + public function getPresentationFactory(): Factory + { + return $this->presentation_factory; + } + + #[\Override] + public function getUrlBuilder(): URLBuilder + { + return $this->url_builder; + } + + #[\Override] + public function withSubActionParameter( + string $sub_action + ): self { + $clone = clone $this; + if ($clone->sub_action_token === null) { + [ + $clone->url_builder, + $clone->sub_action_token + ] = $this->url_builder->acquireParameter( + self::QUERY_PARAMETER_NAME_SPACE, + self::TOKEN_STRING_SUB_ACTION + ); + } + $clone->url_builder = $clone->url_builder + ->withParameter($clone->sub_action_token, $sub_action); + return $clone; + } + + #[\Override] + public function withDefaultSubAction(): self + { + $clone = clone $this; + $clone->default_sub_action = true; + return $clone; + } + + #[\Override] + public function getSubAction(): string + { + if ($this->default_sub_action) { + return ''; + } + + $sub_action_token = $this->sub_action_token; + if ($sub_action_token === null) { + [,$sub_action_token] = $this->url_builder->acquireParameter( + self::QUERY_PARAMETER_NAME_SPACE, + self::TOKEN_STRING_SUB_ACTION + ); + } + + return $this->retrieveStringValueForToken( + $sub_action_token, + self::TOKEN_STRING_SUB_ACTION + ); + } + + #[\Override] + public function getEditability(): Editability + { + return $this->editability; + } + + #[\Override] + public function isMarkingRequired(): bool + { + return $this->required_capabilities->isMarkingRequired(); + } + + #[\Override] + public function getAnswerFormTableActionsForRequiredCapabilities(): array + { + return array_map( + fn(AdditionalFormStepAction $v): TableAction => $v->getAsTableAction( + $this->withActionParameter($v->getIdentifier()) + ), + $this->required_capabilities->getRequiredFormStepActions() + ); + } + + #[\Override] + public function isInCreationContext(): bool + { + return $this->is_in_creation_context; + } + + #[\Override] + public function getAnswerFormId(): ?Uuid + { + if ($this->answer_form_properties !== null) { + return $this->answer_form_properties->getAnswerFormId(); + } + + $answer_form_id_token = $this->answer_form_id_token; + if ($answer_form_id_token === null) { + [,$answer_form_id_token] = $this->url_builder->acquireParameter( + self::QUERY_PARAMETER_NAME_SPACE, + self::TOKEN_STRING_ANSWER_FORM_ID + ); + } + return $this->http->wrapper()->query()->retrieve( + $answer_form_id_token->getName(), + $this->refinery->byTrying([ + $this->refinery->custom()->transformation( + $this->buildRetrieveUuidClosure() + ), + $this->refinery->always(null) + ]) + ); + } + + #[\Override] + public function getAnswerFormProperties(): ?Properties + { + return $this->answer_form_properties; + } + + #[\Override] + public function withAnswerFormProperties( + Properties $properties + ): self { + $clone = clone $this; + $clone->answer_form_properties = $properties; + return $clone; + } + + #[\Override] + public function getTableRowIdToken(): URLBuilderToken + { + return $this->table_row_token; + } + + /** + * @return list + */ + #[\Override] + public function getTableRowIds(): array + { + if ($this->table_row_ids === null) { + $this->table_row_ids = $this->http->wrapper()->query()->retrieve( + $this->table_row_token->getName(), + $this->refinery->byTrying([ + $this->refinery->kindlyTo()->listOf( + $this->refinery->custom()->transformation( + fn($v): string => $v !== '' + ? $this->refinery->kindlyTo()->string()->transform($v) + : throw new \UnexpectedValueException() + ) + ), + $this->refinery->always([]) + ]) + ); + } + + return $this->table_row_ids; + } + + #[\Override] + public function withPreservedTableRowIdsParameter(): self + { + $clone = clone $this; + $clone->table_row_ids = $clone->getTableRowIds(); + $clone->url_builder = $this->url_builder + ->withParameter($this->table_row_token, $clone->table_row_ids); + return $clone; + } + + #[\Override] + public function redirectTo(URLBuilder $target): void + { + $this->ctrl->redirectToURL( + $target->buildURI()->__toString() + ); + } + + public function withIsInCreationContext( + bool $is_in_creation_context + ): self { + $clone = clone $this; + $clone->is_in_creation_context = $is_in_creation_context; + return $clone; + } + + public function getParentObjId(): int + { + return $this->parent_obj_id; + } + + public function getAction(): string + { + $action_token = $this->action_token; + if ($action_token === null) { + [,$action_token] = $this->url_builder->acquireParameter( + self::QUERY_PARAMETER_NAME_SPACE, + self::TOKEN_STRING_ACTION + ); + } + return $this->retrieveStringValueForToken($action_token); + } + + public function withActionParameter( + string $action + ): self { + $clone = clone $this; + if ($clone->action_token === null) { + [ + $clone->url_builder, + $clone->action_token + ] = $this->url_builder->acquireParameter( + self::QUERY_PARAMETER_NAME_SPACE, + self::TOKEN_STRING_ACTION + ); + } + $clone->url_builder = $clone->url_builder + ->withParameter($clone->action_token, $action); + return $clone; + } + + public function withQuestionIdParameter( + Uuid $question_id + ): self { + $clone = clone $this; + $clone->url_builder = $this->url_builder + ->withParameter($this->question_id_token, $question_id->toString()); + return $clone; + } + + public function withAnswerFormTypeHashParameter( + string $type_hash + ): self { + $clone = clone $this; + if ($clone->type_hash_token === null) { + [ + $clone->url_builder, + $clone->type_hash_token + ] = $this->url_builder->acquireParameter( + self::QUERY_PARAMETER_NAME_SPACE, + self::TOKEN_STRING_TYPE_HASH + ); + } + + $clone->url_builder = $clone->url_builder + ->withParameter($clone->type_hash_token, $type_hash); + return $clone; + } + + public function withAnswerFormIdParameter( + Uuid $answer_form_id + ): self { + $clone = clone $this; + if ($clone->answer_form_id_token === null) { + [ + $clone->url_builder, + $clone->answer_form_id_token + ] = $this->url_builder->acquireParameter( + self::QUERY_PARAMETER_NAME_SPACE, + self::TOKEN_STRING_ANSWER_FORM_ID + ); + } + $clone->url_builder = $clone->url_builder->withParameter( + $clone->answer_form_id_token, + $answer_form_id->toString() + ); + return $clone; + } + + public function withCreateModeParameter(): self + { + $clone = clone $this; + if ($clone->create_mode_token === null) { + [ + $clone->url_builder, + $clone->create_mode_token + ] = $this->url_builder->acquireParameter( + self::QUERY_PARAMETER_NAME_SPACE, + self::TOKEN_STRING_CREATE_MODE + ); + } + + $clone->url_builder = $clone->url_builder + ->withParameter($clone->create_mode_token, '1'); + return $clone; + } + + #[\Override] + public function withFormStartSubActionParameter( + string $sub_action + ): self { + $clone = clone $this; + if ($clone->form_start_sub_action_token === null) { + [ + $clone->url_builder, + $clone->form_start_sub_action_token + ] = $this->url_builder->acquireParameter( + self::QUERY_PARAMETER_NAME_SPACE, + self::TOKEN_STRING_FORM_START_SUB_ACTION + ); + } + + $clone->url_builder = $clone->url_builder + ->withParameter( + $clone->form_start_sub_action_token, + $sub_action + ); + return $clone; + } + + #[\Override] + public function withPreservedFormStartSubActionParameter(): self + { + return $this->withFormStartSubActionParameter( + $this->getFormStartSubAction() + ); + } + + public function getQuestionId(): ?Uuid + { + return $this->http->wrapper()->query()->retrieve( + $this->question_id_token->getName(), + $this->refinery->byTrying([ + $this->refinery->custom()->transformation( + $this->buildRetrieveUuidClosure() + ), + $this->refinery->always(null) + ]) + ); + } + + /** + * This function will either return the QuestionIds from the $_GET parameter + * for row ids OR from an InterruptiveItems $_POST value. + * @return array<\ILIAS\Data\UUID\Uuid>|string|null + */ + public function getQuestionIds(): array|string|null + { + return $this->http->wrapper()->query()->retrieve( + $this->table_row_token->getName(), + $this->refinery->byTrying([ + $this->refinery->custom()->transformation( + fn($v): string => $v === ['ALL_OBJECTS'] + ? 'ALL_OBJECTS' + : throw new \UnexpectedValueException() + ), + $this->refinery->kindlyTo()->listOf( + $this->refinery->custom()->transformation( + $this->buildRetrieveUuidClosure() + ) + ), + $this->refinery->always(null) + ]) + ) ?? $this->http->wrapper()->post()->retrieve( + self::INTERRUPTIVE_ITEMS_KEY, + $this->refinery->kindlyTo()->listOf( + $this->refinery->custom()->transformation( + $this->buildRetrieveUuidClosure() + ) + ), + $this->refinery->always(null) + ); + } + + public function getTypeClassHash(): string + { + $type_hash_token = $this->type_hash_token; + if ($type_hash_token === null) { + [,$type_hash_token] = $this->url_builder->acquireParameter( + self::QUERY_PARAMETER_NAME_SPACE, + self::TOKEN_STRING_TYPE_HASH + ); + } + return $this->retrieveStringValueForToken($type_hash_token); + } + + public function getFormStartSubAction(): string + { + $form_start_command_token = $this->form_start_sub_action_token; + if ($form_start_command_token === null) { + [,$form_start_command_token] = $this->url_builder->acquireParameter( + self::QUERY_PARAMETER_NAME_SPACE, + self::TOKEN_STRING_FORM_START_SUB_ACTION + ); + } + return $this->retrieveStringValueForToken($form_start_command_token); + } + + public function isCreateModeSimple(): bool + { + $create_mode_token = $this->create_mode_token; + if ($create_mode_token === null) { + [, $create_mode_token] = $this->url_builder->acquireParameter( + self::QUERY_PARAMETER_NAME_SPACE, + self::TOKEN_STRING_CREATE_MODE + ); + } + + return $this->http->wrapper()->query()->has( + $create_mode_token->getName() + ); + } + + /** + * + * @param array<\ILIAS\Questions\AnswerForm\Capabilities\Action> $additional_actions + */ + public function setEditAnswerFormTabs( + array $additional_actions + ): void { + $this->tabs_gui->addTab( + self::TAB_ID_ANSWER_FORM, + $this->lng->txt('answer_form'), + $this->withDefaultSubAction()->getUrlBuilder()->buildURI()->__toString() + ); + + foreach ($additional_actions as $action) { + $action->addTab( + $this, + $this->tabs_gui, + $this->lng + ); + } + + $this->tabs_gui->addSubTab( + self::TAB_ID_ANSWER_FORM, + $this->lng->txt('overview'), + $this->withDefaultSubAction()->getUrlBuilder()->buildURI()->__toString() + ); + + $this->tabs_gui->activateTab(self::TAB_ID_ANSWER_FORM); + $this->tabs_gui->activateSubTab(self::TAB_ID_ANSWER_FORM); + } + + public function preserveParametersForPageEditorCmds(): void + { + $this->setQuestionIdParamterForPageEditorCmds($this->getQuestionId()); + } + + public function setParamtersForSimpleCreateCmd( + Uuid $question_id + ): void { + $this->setQuestionIdParamterForPageEditorCmds($question_id); + + $this->ctrl->setParameterByClass( + \QstsQuestionPageGUI::class, + self::PARAMETER_STRING_HIER_ID, + '1' + ); + + [, $create_mode_token] = $this->url_builder->acquireParameter( + self::QUERY_PARAMETER_NAME_SPACE, + self::TOKEN_STRING_CREATE_MODE + ); + + $this->ctrl->setParameterByClass( + \QstsQuestionPageGUI::class, + $create_mode_token->getName(), + '1' + ); + } + + public function isCreateAndNewAction(): bool + { + return $this->http->wrapper()->query()->has( + $this->buildURLBuilderTokenForCreateAndNew()->getName() + ); + } + + public function buildURLBuilderTokenForCreateAndNew(): URLBuilderToken + { + return new URLBuilderToken( + self::QUERY_PARAMETER_NAME_SPACE, + self::TOKEN_STRING_CREATE_AND_NEW + ); + } + + private function setQuestionIdParamterForPageEditorCmds( + Uuid $question_id + ): void { + $this->ctrl->setParameterByClass( + \QstsQuestionPageGUI::class, + $this->question_id_token->getName(), + $question_id->toString() + ); + } + + private function buildEditAnswerFormBackUrl(): URLBuilder + { + if (!$this->is_in_creation_context) { + return $this->withDefaultSubAction()->getUrlBuilder(); + } + + if (!$this->isCreateModeSimple()) { + return new URLBuilder( + new URI( + ILIAS_HTTP_PATH . '/' . $this->ctrl->getLinkTargetByClass( + \QstsQuestionPageGUI::class, + 'edit' + ) + ) + ); + } + + return $this->withActionParameter( + Edit::ACTION_DELETE_QUESTIONS + )->withSubActionParameter( + Edit::ACTION_DELETE_QUESTIONS + )->getUrlBuilder()->withParameter( + $this->table_row_token, + [$this->getQuestionId()->toString()] + ); + } + + private function acquireURLBuilderAndParameters( + URI $base_uri + ): void { + [ + $this->url_builder, + $this->table_row_token, + $this->question_id_token + ] = (new URLBuilder($base_uri)) + ->acquireParameters( + self::QUERY_PARAMETER_NAME_SPACE, + self::TOKEN_STRING_TABLE_ROW_ID, + self::TOKEN_STRING_QUESTION_ID + ); + } + + private function retrieveStringValueForToken( + URLBuilderToken $token + ): string { + return $this->http->wrapper()->query()->retrieve( + $token->getName(), + $this->buildStringTrafo() + ); + } + + private function buildStringTrafo(): Transformation + { + return $this->refinery->byTrying([ + $this->refinery->kindlyTo()->string(), + $this->refinery->always('') + ]); + } + + private function buildRetrieveUuidClosure(): \Closure + { + return fn($v): Uuid => is_string($v) + ? $this->uuid_factory->fromString($v) + : throw new \UnexpectedValueException(); + } +} diff --git a/components/ILIAS/Questions/src/Presentation/Definitions/Editability.php b/components/ILIAS/Questions/src/Presentation/Definitions/Editability.php new file mode 100644 index 000000000000..f7ca340fb0d8 --- /dev/null +++ b/components/ILIAS/Questions/src/Presentation/Definitions/Editability.php @@ -0,0 +1,28 @@ +storable; + } +} diff --git a/components/ILIAS/Questions/src/Presentation/Definitions/HasClientSideRepresentation.php b/components/ILIAS/Questions/src/Presentation/Definitions/HasClientSideRepresentation.php new file mode 100644 index 000000000000..23044ae7c3fd --- /dev/null +++ b/components/ILIAS/Questions/src/Presentation/Definitions/HasClientSideRepresentation.php @@ -0,0 +1,26 @@ +value + => $column_factory->link($lng->txt('title')), + OverviewTableColumns::AnswerFormTypes->value + => $column_factory->text( + $lng->txt('contained_answer_form_types') + )->withIsOptional(true, true) + ->withIsSortable(false), + ]; + } + + public static function getFilterInputs( + Language $lng, + FieldFactory $field_factory, + array $answer_form_types_array_for_select + ): array { + return [ + self::Title->value => $field_factory->text( + $lng->txt('title') + ), + self::AnswerFormTypes->value => $field_factory->multiSelect( + $lng->txt('contains_answer_form_types'), + $answer_form_types_array_for_select + )->withRequired(true), + ]; + } + + public function getDatabaseColumn( + PersistenceFactory $persistence_factory, + TableNameBuilder $table_names_builder + ): ?Column { + return $persistence_factory->column( + $persistence_factory->table( + $table_names_builder, + match($this) { + self::Title => TableTypes::Questions, + self::AnswerFormTypes => AnswerFormGenericTableTypes::AnswerForms + } + ), + $this->value + ); + } + + public function transformFilterValue( + AnswerFormFactory $answer_form_factory, + mixed $value + ): mixed { + return match($this) { + self::AnswerFormTypes => array_map( + fn(string $v): string => $answer_form_factory + ->getTypeDefinitionFromSelectValue($v)::class, + $value + ), + default => $value + }; + } +} diff --git a/components/ILIAS/Questions/src/Presentation/Definitions/ViewConfiguration.php b/components/ILIAS/Questions/src/Presentation/Definitions/ViewConfiguration.php new file mode 100644 index 000000000000..787c44159bc4 --- /dev/null +++ b/components/ILIAS/Questions/src/Presentation/Definitions/ViewConfiguration.php @@ -0,0 +1,65 @@ +interactive; + } + + public function showMarks(): bool + { + return $this->show_marks; + } + + public function showBestResponse(): bool + { + return $this->show_best_response; + } + + public function withShowBestResponse(): self + { + $clone = clone $this; + $clone->interactive = false; + $clone->show_best_response = true; + return $clone; + } + + public function showFeedback(): bool + { + return $this->show_feedback; + } + + public function getViewMode(): ViewMode + { + return $this->view_mode; + } +} diff --git a/components/ILIAS/Questions/src/Presentation/Layout/Async.php b/components/ILIAS/Questions/src/Presentation/Layout/Async.php new file mode 100644 index 000000000000..b61cd8c18b9e --- /dev/null +++ b/components/ILIAS/Questions/src/Presentation/Layout/Async.php @@ -0,0 +1,56 @@ +content) + ? $this->content + : $ui_renderer->renderAsync($this->content); + + $this->http->saveResponse( + $this->http->response()->withBody( + Streams::ofString( + $rendered_content + ) + ) + ); + $this->http->sendResponse(); + $this->http->close(); + } +} diff --git a/components/ILIAS/Questions/src/Presentation/Layout/EditForm.php b/components/ILIAS/Questions/src/Presentation/Layout/EditForm.php new file mode 100644 index 000000000000..efe26faeb70a --- /dev/null +++ b/components/ILIAS/Questions/src/Presentation/Layout/EditForm.php @@ -0,0 +1,208 @@ +form = $this->buildForm( + $inputs, + $default_form_action, + $back_form_action + ); + } + + #[\Override] + public function getUI(): array + { + $content = []; + + if ($this->content_before_form !== null) { + $content[] = $this->content_before_form; + } + + if ($this->confirmation !== null) { + $content[] = $this->confirmation->withOnLoad( + $this->confirmation->getShowSignal() + )->withAdditionalOnLoadCode( + function ($id) { + return "var button = {$id}.querySelector('input[type=\"submit\"]'); " + . "button.addEventListener('click', (e) => {e.preventDefault();" + . 'const form = button.closest("dialog").nextElementSibling;' + . "form.action = '{$this->confirmation->getFormAction()}';" + . 'form.submit();});'; + } + ); + } + + if ($this->insert_legacy_text_button !== null) { + $content[] = $this->insert_legacy_text_button; + } + + $content[] = $this->is_final_step + ? $this->form + : $this->form->withSubmitLabel( + $this->lng->txt('next') + ); + + if ($this->content_after_form !== null) { + $content[] = $this->content_after_form; + } + + return $content; + } + + public function isFinalStep(): bool + { + return $this->is_final_step; + } + + public function withIsFinalStep( + bool $is_final_step + ): self { + $clone = clone $this; + $clone->is_final_step = $is_final_step; + return $clone; + } + + public function withContentBeforeForm( + StandardPanel $content + ): self { + $clone = clone $this; + $clone->content_before_form = $content; + return $clone; + } + + public function withContentAfterForm( + StandardPanel $content + ): self { + $clone = clone $this; + $clone->content_after_form = $content; + return $clone; + } + + public function withConfirmation( + InterruptiveModal $confirmation_modal + ): self { + $clone = clone $this; + $clone->confirmation = $confirmation_modal; + return $clone; + } + + public function withInsertLegacyTextsButton( + URLBuilder $target_builder + ): self { + $clone = clone $this; + $clone->insert_legacy_text_button = $this->ui_factory->messageBox()->info( + $this->lng->txt('insert_legacy_texts_info') + )->withButtons([ + $this->ui_factory->button()->standard( + $this->lng->txt('insert_legacy_texts'), + $target_builder->buildURI()->__toString() + ) + ]); + return $clone; + } + + public function withRequest( + ServerRequestInterface $request + ): self { + $clone = clone $this; + $clone->form = $clone->form->withRequest($request); + return $clone; + } + + public function getData(): mixed + { + $data = $this->form->getData(); + return $data[self::MAIN_SECTION_NAME] ?? null; + } + + public function withAdditionalAction( + URLBuilderToken $parameter_token, + string $parameter_value, + string $label + ): self { + $clone = clone $this; + $clone->form = $this->form->withAdditionalFormAction( + $this->default_form_action->buildURI()->withParameter( + $parameter_token->getName(), + $parameter_value + )->__toString(), + $label + ); + return $clone; + } + + private function buildForm( + Input|InputsBuilder $inputs, + URLBuilder $default_form_action, + ?URLBuilder $back_form_action + ): StandardForm { + if ($inputs instanceof InputsBuilder) { + $inputs = $inputs->getInputs(); + } + $form = $this->ui_factory->input()->container()->form()->standard( + $default_form_action->buildURI()->__toString(), + [ + self::MAIN_SECTION_NAME => $inputs + ] + ); + + if ($back_form_action === null) { + return $form; + } + + return $form->withAdditionalFormAction( + $back_form_action->buildURI()->__toString(), + $this->lng->txt('previous') + ); + } +} diff --git a/components/ILIAS/Questions/src/Presentation/Layout/EditOverview.php b/components/ILIAS/Questions/src/Presentation/Layout/EditOverview.php new file mode 100644 index 000000000000..003eed35deab --- /dev/null +++ b/components/ILIAS/Questions/src/Presentation/Layout/EditOverview.php @@ -0,0 +1,120 @@ + $buttons + */ + private array $buttons = []; + private DataTable|OrderingTable|null $table = null; + + public function __construct( + private readonly Environment $environment, + private readonly URLBuilder $target_to_edit_basic_answer_form_properties + ) { + } + + #[\Override] + public function getUI(): array + { + $ui = [ + $this->buildBasicAnswerFormPanel(), + + ]; + + if ($this->view_control !== null) { + $ui[] = $this->view_control; + } + + $ui_with_buttons = [ + ...$ui, + ...$this->buttons + ]; + + if ($this->table !== null) { + $ui_with_buttons[] = $this->table; + } + + return $ui_with_buttons; + } + + public function withViewControl( + ViewControlMode $view_control + ): self { + $clone = clone $this; + $clone->view_control = $view_control; + return $clone; + } + + public function withAdditionalButton( + StandardButton $button + ): self { + $clone = clone $this; + $clone->buttons[] = $button; + return $clone; + } + + public function withTable( + DataTable|OrderingTable $table + ): self { + $clone = clone $this; + $clone->table = $table; + return $clone; + } + + private function buildBasicAnswerFormPanel(): StandardPanel + { + $content = [ + $this->environment->getUIFactory()->listing()->descriptive( + $this->environment->getAnswerFormProperties() + ->getBasicPropertiesForListing( + $this->environment + ) + ) + ]; + + if ($this->environment->getEditability() === Editability::Full) { + $content[] = $this->environment->getUIFactory()->button()->standard( + $this->environment->getLanguage()->txt('edit_basic_answer_form_properties'), + $this->target_to_edit_basic_answer_form_properties + ->buildURI() + ->__toString() + ); + } + + return $this->environment->getUIFactory()->panel()->standard( + $this->environment->getLanguage()->txt('basic_answer_form_properties'), + $content + ); + } +} diff --git a/components/ILIAS/Questions/src/Presentation/Layout/Factory.php b/components/ILIAS/Questions/src/Presentation/Layout/Factory.php new file mode 100644 index 000000000000..aead15450bb7 --- /dev/null +++ b/components/ILIAS/Questions/src/Presentation/Layout/Factory.php @@ -0,0 +1,114 @@ +ui_factory, + $this->lng, + $main_section_inputs, + $default_form_action, + $back_form_action + ); + } + + public function getAsync( + InterruptiveModal|RoundTripModal|MessageBox|State|array|string $content + ): Async { + return new Async( + $this->http, + $content + ); + } + + public function getSessionBasedInputsBuilder( + Transformation $to_inputs + ): InputsBuilderSession { + return new InputsBuilderSession( + $to_inputs + ); + } + + public function getUploadHandler( + Environment $environment, + ResourceStakeholder $stakeholder + ): UploadHandler { + return new UploadHandler( + $this->irss, + $this->temp_filesystem, + $this->upload, + $this->sanitizer, + $stakeholder, + $environment + ); + } + + public function getUIFactory(): UIFactory + { + return $this->ui_factory; + } +} diff --git a/components/ILIAS/Questions/src/Presentation/Layout/GlobalScreen/LayoutProvider.php b/components/ILIAS/Questions/src/Presentation/Layout/GlobalScreen/LayoutProvider.php new file mode 100755 index 000000000000..590231b8a9c0 --- /dev/null +++ b/components/ILIAS/Questions/src/Presentation/Layout/GlobalScreen/LayoutProvider.php @@ -0,0 +1,165 @@ +context_collection->main(); + } + + protected function isModeEnabled( + CalledContexts $called_contexts + ): bool { + return $called_contexts->current()->getAdditionalData() + ->is(self::MODE_ENABLED, true); + } + + #[\Override] + public function getBreadCrumbsModification( + CalledContexts $called_contexts + ): ?BreadCrumbsModification { + if (!$this->isModeEnabled($called_contexts)) { + return null; + } + + return $this->globalScreen()->layout()->factory()->breadcrumbs() + ->withModification( + function (?Breadcrumbs $current): ?Breadcrumbs { + return null; + } + )->withPriority(self::MODIFICATION_PRIORITY); + } + + #[\Override] + public function getMainBarModification( + CalledContexts $called_contexts + ): ?MainBarModification { + $mainbar = $this->globalScreen()->layout()->factory()->mainbar(); + + if (!$this->isModeEnabled($called_contexts)) { + return null; + } + + return $mainbar + ->withModification( + $this->buildMainBarModification($called_contexts) + )->withPriority(self::MODIFICATION_PRIORITY); + } + + #[\Override] + public function getMetaBarModification( + CalledContexts $called_contexts + ): ?MetaBarModification { + if (!$this->isModeEnabled($called_contexts)) { + return null; + } + + return $this->globalScreen()->layout()->factory()->metabar() + ->withModification( + function (?MetaBar $current): ?MetaBar { + return null; + } + )->withPriority(self::MODIFICATION_PRIORITY); + } + + #[\Override] + public function getPageBuilderDecorator( + CalledContexts $called_contexts + ): ?PageBuilderModification { + if (!$this->isModeEnabled($called_contexts)) { + return null; + } + + $mode_info = $this->dic['ui.factory']->mainControls()->modeInfo( + $this->dic->language()->txt('edit_questions'), + $called_contexts->current()->getAdditionalData()->get(self::URL_CLOSE_MODE_INFO) + ); + + return $this->factory->page() + ->withLowPriority() + ->withModification( + static function (PagePartProvider $parts) use ($mode_info): Page { + $p = new StandardPageBuilder(); + $page = $p->build($parts); + return $page->withModeInfo($mode_info); + } + ); + } + + private function buildMainbarModification( + CalledContexts $called_contexts + ): \Closure { + return function (?MainBar $mainbar) use ($called_contexts): ?MainBar { + if ($mainbar === null) { + return null; + } + + $tools = $mainbar->getToolEntries(); + $new_mainbar = array_reduce( + array_keys($tools), + static fn(MainBar $c, string $v): MainBar => $c->withAdditionalToolEntry($v, $tools[$v]), + $mainbar + ->withClearedEntries() + ->withAdditionalEntry( + 'create_question', + $this->dic['ui.factory']->button()->bulky( + $this->dic['ui.factory']->symbol()->icon()->standard('', '')->withAbbreviation('CQ'), + $this->dic['lng']->txt('create_question'), + $called_contexts->current()->getAdditionalData()->get(self::URL_CREATE_QUESTION)->__toString() + ) + ) + ); + + if ($called_contexts->current()->getAdditionalData()->exists(self::QUESTIONLIST_ENTRY)) { + return $new_mainbar->withAdditionalEntry( + 'question_list', + $called_contexts->current()->getAdditionalData()->get(self::QUESTIONLIST_ENTRY) + ); + } + + return $new_mainbar; + }; + } +} diff --git a/components/ILIAS/Questions/src/Presentation/Layout/QuestionsTable.php b/components/ILIAS/Questions/src/Presentation/Layout/QuestionsTable.php new file mode 100644 index 000000000000..c3bfbec58915 --- /dev/null +++ b/components/ILIAS/Questions/src/Presentation/Layout/QuestionsTable.php @@ -0,0 +1,189 @@ +getLanguage()->loadLanguageModule('qpl'); + } + + #[\Override] + public function getUI(): array + { + + $content = []; + + if ($this->create_question_button !== null) { + $toolbar = new \ilToolbarGUI(); + $toolbar->addComponent( + $this->create_question_button + ); + $content = [ + $this->environment->getUIFactory()->legacy()->content( + $toolbar->getHTML() + ) + ]; + } + + $content[] = $this->buildTable(); + + return $content; + } + + public function withCreateQuestionButton( + PrimaryButton $create_question_button + ): self { + $clone = clone $this; + $clone->create_question_button = $create_question_button; + return $clone; + } + + #[\Override] + public function getRows( + DataRowBuilder $row_builder, + array $visible_column_ids, + Range $range, + Order $order, + mixed $additional_viewcontrol_data, + mixed $filter_data, + mixed $additional_parameters + ): \Generator { + $environment_with_action = $this->environment->withActionParameter( + Edit::ACTION_EDIT_QUESTION + ); + foreach ($this->questions_repository->getQuestionDataOnlyForAllQuestions( + $range, + $order, + $filter_data + ) as $question) { + $table_row = $question->toTableRow( + $row_builder, + $environment_with_action, + $this->required_capabilities + ); + + if ($table_row !== null) { + yield $table_row; + } + } + } + + #[\Override] + public function getTotalRowCount( + mixed $additional_viewcontrol_data, + mixed $filter_data, + mixed $additional_parameters + ): ?int { + return $this->questions_repository->getQuestionsCount(); + } + + private function buildTable(): array + { + $filter = $this->buildFilter( + $this->environment->getUrlBuilder()->buildURI()->__toString() + ); + + return [ + $filter, + $this->environment->getUIFactory()->table()->data( + $this, + $this->environment->getLanguage()->txt('questions'), + OverviewTableColumns::getTableColums( + $this->environment->getLanguage(), + $this->environment->getUIFactory()->table()->column() + ), + )->withActions( + $this->getActions() + )->withRange(new Range(0, 20)) + ->withFilter( + $this->ui_service->filter()->getData($filter) + )->withRequest($this->environment->getHttpServices()->request()) + ]; + } + + private function buildFilter( + string $action + ): Filter { + $filter_inputs = OverviewTableColumns::getFilterInputs( + $this->environment->getLanguage(), + $this->environment->getUIFactory()->input()->field(), + $this->answer_form_factory->getAnswerFormTypesArrayForSelect( + $this->environment->getLanguage() + ) + ); + + $active = array_fill(0, count($filter_inputs), true); + + $filter = $this->ui_service->filter()->standard( + 'question_table_filter_id', + $action, + $filter_inputs, + $active, + true, + true + ); + + $request = $this->environment->getHttpServices()->request(); + return $request->getMethod() === 'POST' + ? $filter->withRequest($request) + : $filter; + } + + private function getActions(): array + { + return [ + 'clone' => $this->environment->getUIFactory()->table()->action()->standard( + $this->environment->getLanguage()->txt('copy'), + $this->environment->withActionParameter(Edit::ACTION_CLONE_QUESTION) + ->getUrlBuilder(), + $this->environment->getTableRowIdToken() + ), + 'delete' => $this->environment->getUIFactory()->table()->action()->standard( + $this->environment->getLanguage()->txt('delete'), + $this->environment->withActionParameter(Edit::ACTION_DELETE_QUESTIONS) + ->getUrlBuilder(), + $this->environment->getTableRowIdToken() + )->withAsync(true) + ]; + } +} diff --git a/components/ILIAS/Questions/src/Presentation/Layout/Tools/InputsBuilder.php b/components/ILIAS/Questions/src/Presentation/Layout/Tools/InputsBuilder.php new file mode 100644 index 000000000000..65adcb355ab4 --- /dev/null +++ b/components/ILIAS/Questions/src/Presentation/Layout/Tools/InputsBuilder.php @@ -0,0 +1,28 @@ +carry = $carry; + return $clone; + } + + public function persistCarry(): void + { + if ($this->carry === null) { + $this->loadCarryFromSessionAndClear(); + } + \ilSession::set(self::STORAGE_KEY, $this->carry); + } + + public function resetCarry(): void + { + \ilSession::clear(self::STORAGE_KEY); + } + + public function retrieveCarry( + Transformation $transformation + ): mixed { + if ($this->carry === null) { + $this->loadCarryFromSessionAndClear(); + } + + return $transformation->transform($this->carry); + } + + public function getInputs(): Section + { + if ($this->carry === null) { + $this->loadCarryFromSessionAndClear(); + } + return $this->to_inputs->transform($this->carry); + } + + private function loadCarryFromSessionAndClear(): void + { + $this->carry = \ilSession::get(self::STORAGE_KEY); + \ilSession::clear(self::STORAGE_KEY); + } +} diff --git a/components/ILIAS/Questions/src/Presentation/Layout/Tools/UploadHandler.php b/components/ILIAS/Questions/src/Presentation/Layout/Tools/UploadHandler.php new file mode 100644 index 000000000000..7aa22a75af85 --- /dev/null +++ b/components/ILIAS/Questions/src/Presentation/Layout/Tools/UploadHandler.php @@ -0,0 +1,364 @@ +environment + ->withSubActionParameter(self::SUB_ACTION_UPLOAD) + ->getUrlBuilder() + ->buildURI() + ->__toString(); + } + + #[\Override] + public function getFileRemovalURL(): string + { + return $this->environment + ->withSubActionParameter(self::SUB_ACTION_REMOVE) + ->getUrlBuilder() + ->buildURI() + ->__toString(); + } + + #[\Override] + public function getExistingFileInfoURL(): string + { + return $this->environment + ->withSubActionParameter(self::SUB_ACTION_INFO) + ->getUrlBuilder() + ->buildURI() + ->__toString(); + } + + #[\Override] + public function getInfoForExistingFiles( + array $file_ids + ): array { + return array_map( + fn($file_id): FileInfoResult => $this->getInfoResult($file_id), + $file_ids + ); + } + + #[\Override] + public function getInfoResult( + string $identifier + ): ?FileInfoResult { + $title = $mime = 'unknown'; + $size = 0; + $id = $this->irss->manage()->find($identifier); + if ($id !== null) { + $revision = $this->irss->manage()->getCurrentRevision($id)->getInformation(); + $title = $revision->getTitle(); + $size = $revision->getSize(); + $mime = $revision->getMimeType(); + } + + return new BasicFileInfoResult( + $this->getFileIdentifierParameterName(), + $identifier, + $title, + $size, + $mime + ); + } + + #[\Override] + public function supportsChunkedUploads(): bool + { + return true; + } + + #[\Override] + public function can( + string $sub_action + ): bool { + $has_file_identifier = $this->hasFileIdentifier(); + + return $sub_action === self::SUB_ACTION_UPLOAD + || $sub_action === self::SUB_ACTION_REMOVE && $has_file_identifier + || $sub_action === self::SUB_ACTION_INFO && $has_file_identifier; + } + + #[\Override] + public function do( + string $action + ): Async { + $response = match($action) { + self::SUB_ACTION_UPLOAD => $this->upload(), + self::SUB_ACTION_REMOVE => $this->remove(), + self::SUB_ACTION_INFO => $this->info(), + default => '' + }; + + return $this->environment->getPresentationFactory()->getAsync($response); + } + + private function upload(): string + { + try { + $this->readChunkedInformation(); + return json_encode($this->getUploadResult()); + } catch (\Throwable $t) { + return json_encode( + new BasicHandlerResult( + $this->getFileIdentifierParameterName(), + BasicHandlerResult::STATUS_FAILED, + '', + $t->getMessage() + ) + ); + } + } + + private function remove(): string + { + return json_encode( + $this->getRemoveResult( + $this->retrieveFileIdentifier() + ) + ); + } + + private function info(): string + { + return json_encode( + $this->getInfoResult( + $this->retrieveFileIdentifier() + ) + ); + } + + private function readChunkedInformation(): void + { + $body = $this->environment->getHttpServices()->request()->getParsedBody(); + $this->chunk_id = $body['dzuuid'] ?? null; + $this->amount_of_chunks = (int) ($body['dztotalchunkcount'] ?? 0); + $this->chunk_index = (int) ($body['dzchunkindex'] ?? 0); + $this->chunk_total_size = (int) ($body['dztotalfilesize'] ?? 0); + $this->is_chunked = ($this->chunk_id !== null && $this->amount_of_chunks > 0); + } + + private function getUploadResult(): HandlerResult + { + $this->upload->process(); + + $result_array = $this->upload->getResults(); + $result = end($result_array); + + $identifier = ''; + $status = HandlerResult::STATUS_FAILED; + $message = $this->environment->getLanguage()->txt( + 'msg_info_blacklisted' + ); + if ($result instanceof UploadResult && $result->isOK()) { + if ($this->is_chunked) { + return $this->processChunckedUpload($result); + } + + $identifier = $this->irss->manage()->upload( + $result, + $this->stakeholder + )->serialize(); + $status = HandlerResult::STATUS_OK; + $message = "file upload OK"; + } + + return new BasicHandlerResult( + $this->getFileIdentifierParameterName(), + $status, + $identifier, + $message + ); + } + + private function getRemoveResult( + string $identifier + ): HandlerResult { + $status = HandlerResult::STATUS_OK; + $message = "file with identifier '{$identifier}' doesn't exist, nothing to do."; + + $id = $this->irss->manage()->find($identifier); + if ($id !== null) { + $this->irss->manage()->remove( + $id, + $this->stakeholder + ); + $status = HandlerResult::STATUS_OK; + $message = "file removal OK"; + } + + return new BasicHandlerResult( + $this->getFileIdentifierParameterName(), + $status, + $identifier, + $message + ); + } + + private function processChunckedUpload( + UploadResult $result + ): HandlerResult { + $temp_path = $this->sanitizer->sanitize( + "{$this->chunk_id}/{$result->getName()}" + ); + + try { + $this->writeChunkedTempFile( + $result, + $temp_path + ); + } catch (\Throwable $t) { + return new BasicHandlerResult( + $this->getFileIdentifierParameterName(), + HandlerResult::STATUS_FAILED, + '', + $t->getMessage() + ); + } + + if (($this->chunk_index + 1) === $this->amount_of_chunks) { + return $this->storeChunkedUpload( + $result, + $temp_path + ); + } + + return new BasicHandlerResult( + $this->getFileIdentifierParameterName(), + HandlerResult::STATUS_PARTIAL, + '', + 'chunk upload OK' + ); + } + + private function writeChunkedTempFile( + UploadResult $result, + string $temp_path + ): void { + if ($this->temp_filesystem->has($temp_path)) { + fwrite( + fopen( + $this->temp_filesystem->readStream($temp_path) + ->getMetadata()['uri'], + 'ab' + ), + file_get_contents( + $result->getPath() + ) + ); + return; + } + + $this->temp_filesystem->write( + $temp_path, + file_get_contents( + $result->getPath() + ) + ); + } + + private function storeChunkedUpload( + UploadResult $result, + string $temp_path + ): HandlerResult { + $id = $this->irss->manage()->stream( + $this->temp_filesystem->readStream($temp_path), + $this->stakeholder, + $result->getName() + ); + + return new BasicHandlerResult( + $this->getFileIdentifierParameterName(), + HandlerResult::STATUS_OK, + $id->serialize(), + 'file upload OK' + ); + } + + private function hasFileIdentifier(): bool + { + return $this->environment + ->getHttpServices() + ->wrapper() + ->query() + ->has($this->getFileIdentifierParameterName()); + } + + private function retrieveFileIdentifier(): string + { + if (!$this->hasFileIdentifier()) { + return ''; + } + + return $this->environment + ->getHttpServices() + ->wrapper() + ->query() + ->retrieve( + $this->getFileIdentifierParameterName(), + $this->environment->getRefinery()->kindlyTo()->string() + ); + } +} diff --git a/components/ILIAS/Questions/src/Presentation/Layout/Viewable.php b/components/ILIAS/Questions/src/Presentation/Layout/Viewable.php new file mode 100644 index 000000000000..a2cf6f879584 --- /dev/null +++ b/components/ILIAS/Questions/src/Presentation/Layout/Viewable.php @@ -0,0 +1,32 @@ + + */ + public function getUI(): array|Component; +} diff --git a/components/ILIAS/Questions/src/Presentation/Views/Edit.php b/components/ILIAS/Questions/src/Presentation/Views/Edit.php new file mode 100644 index 000000000000..d1de2a0fea59 --- /dev/null +++ b/components/ILIAS/Questions/src/Presentation/Views/Edit.php @@ -0,0 +1,909 @@ +> $capability_identifiers + */ + public function __construct( + private readonly Language $lng, + private readonly \ilObjUser $current_user, + private readonly Refinery $refinery, + private readonly UIFactory $ui_factory, + private readonly UIRenderer $ui_renderer, + private readonly GlobalScreen $global_screen, + private readonly GlobalTemplate $global_tpl, + private readonly ContentStyle $content_style, + private readonly \ilCtrl $ctrl, + private readonly HTTP $http, + private readonly \ilTabsGUI $tabs_gui, + private readonly \ilUIService $ui_services, + private readonly UuidFactory $uuid_factory, + private readonly ConfigurationRepository $configuration_repository, + private readonly AnswerFormFactory $answer_form_factory, + private readonly Repository $questions_repository, + private readonly AttemptRepository $attempt_repository, + private readonly LayoutFactory $layout_factory, + private readonly CapabilitiesFactory $capabilities_factory, + array $capability_identifiers, + private readonly int $owner_object_id + ) { + $this->required_capabilities = $this->capabilities_factory->get( + $capability_identifiers + ); + } + + public function withEditable( + Editability $editability + ): self { + $clone = clone $this; + $clone->editability = $editability; + return $clone; + } + + public function withOrderingEnabled( + bool $enable + ): self { + $clone = clone $this; + $clone->ordering_enabled = $enable; + return $clone; + } + + public function getUI( + URI $base_uri, + int $ref_id + ): array|Component { + $this->content_style->gui()->addCss( + $this->global_tpl, + $ref_id + ); + + $environment = $this->buildEnvironment($base_uri); + + $view = match($environment->getAction()) { + self::ACTION_CREATE_QUESTION => $this->createQuestion( + $environment->withIsInCreationContext(true) + ), + self::ACTION_EDIT_QUESTION => $this->editQuestion($environment), + self::ACTION_CLONE_QUESTION => $this->cloneQuestion($environment), + self::ACTION_DELETE_QUESTIONS => $this->deleteQuestions($environment), + default => $this->showTable($environment) + }; + + if ($view instanceof Async) { + $view->render($this->ui_renderer); + } + + return $view->getUI(); + } + + public function forwardPageCmds( + URI $base_uri, + int $ref_id + ): void { + $environment = $this->buildEnvironment($base_uri); + + if ($this->ctrl->getCmd() === 'insert' + && $environment->getAction() === self::ACTION_DELETE_QUESTIONS) { + $this->deleteQuestions($environment); + return; + } + + $this->initializeEditMode($environment); + $environment->preserveParametersForPageEditorCmds(); + + $this->content_style->gui()->addCss( + $this->global_tpl, + $ref_id + ); + + $this->global_tpl->setContent( + $this->ctrl->forwardCommand( + new \QstsQuestionPageGUI( + $this->questions_repository->getForQuestionId( + $environment->getQuestionId() + ), + $this->owner_object_id, + $this->required_capabilities, + new ViewConfiguration( + true, + false, + false, + false + ) + )->withEditView( + $this + )->withReturnURI( + $environment + ->withActionParameter(self::ACTION_EDIT_QUESTION) + ->withQuestionIdParameter($environment->getQuestionId()) + ->getUrlBuilder() + ->buildURI() + ) + ) + ); + } + + public function getCreateAnswerForm( + URI $base_uri, + Question $question, + \ilPCAnswerForm $content_object + ): array|Component { + $environment = $this->buildEnvironment($base_uri) + ->withIsInCreationContext(true) + ->withQuestionIdParameter($question->getId()); + + $environment->setEditAnswerFormBackTarget(); + + if ($this->configuration_repository->isCreateModeSimple($environment)) { + $environment = $environment->withCreateModeParameter(); + } + + $answer_form_type_class_hash = $environment->getTypeClassHash(); + + if ($answer_form_type_class_hash !== '') { + $type_definition = $this->answer_form_factory + ->getTypeDefinitionFromSelectValue($answer_form_type_class_hash); + + return $this->forwardCreateAnswerFormCmd( + $environment->withAnswerFormProperties( + $type_definition->buildProperties( + $this->answer_form_factory->getDefaultTypeGenericProperties( + $question->getId(), + $type_definition, + $environment->getAnswerFormId(), + ), + null + ) + )->withAnswerFormTypeHashParameter($answer_form_type_class_hash), + $question, + $content_object, + $type_definition->getEditView() + )->getUI(); + } + + return match($environment->getAction()) { + self::ACTION_CREATE_ANSWER_FORM => $this->processCreateAnswerForm( + $environment, + $question, + $content_object + )->getUI(), + default => $this->buildCreateAnswerForm($environment)->getUI() + }; + } + + public function getEditAnswerForm( + URI $base_uri, + Question $question, + AnswerFormProperties $answer_form_properties, + Definition $type_definition + ): array|Component { + $environment = $this->buildEnvironment($base_uri) + ->withAnswerFormProperties($answer_form_properties) + ->withQuestionIdParameter($question->getId()); + + $action = $environment->getAction(); + $edit_view = $type_definition->getEditView(); + + $from_capabilites = $this->required_capabilities->edit( + $this->tabs_gui, + $environment, + $edit_view, + $action + ); + + if ($from_capabilites instanceof AnswerFormProperties) { + $this->updateAnswerFormAndRedirect( + $environment, + $question, + $from_capabilites + ); + } + + if ($from_capabilites instanceof Async) { + $from_capabilites->render($this->ui_renderer); + } + + if ($from_capabilites instanceof Viewable) { + return $from_capabilites->getUI(); + } + + if ($action === self::ACTION_OTHER_ANSWER_FORM) { + return $this->processOtherAnswerFormAction( + $environment->withActionParameter(self::ACTION_OTHER_ANSWER_FORM), + $question, + $edit_view + )->getUI(); + } + + $from_edit_view = $edit_view->edit($environment); + if ($from_edit_view instanceof EditForm + && $this->required_capabilities->additionalAnswerFormStepsRequired()) { + return $from_edit_view->withIsFinalStep(false)->getUI(); + } + + if ($from_edit_view instanceof Async) { + $from_edit_view->render($this->ui_renderer); + } + + if ($from_edit_view instanceof Viewable) { + return $from_edit_view->getUI(); + } + + if ($from_edit_view instanceof ForImmediateStorage) { + $this->updateAnswerFormAndRedirect( + $environment, + $question, + $from_edit_view->unpack() + ); + } + + $return_form_step_actions = $this->required_capabilities + ->doFirstFormStepAction( + $environment->withAnswerFormProperties($from_edit_view), + $edit_view + ); + + if ($return_form_step_actions instanceof Async) { + $return_form_step_actions->render($this->ui_renderer); + } + + if ($return_form_step_actions instanceof Viewable) { + return $return_form_step_actions->getUI(); + } + + $this->updateAnswerFormAndRedirect( + $environment, + $question, + $from_edit_view + ); + } + + private function createQuestion( + DefaultEnvironment $environment + ): EditForm { + $this->initializeEditMode($environment); + $this->tabs_gui->setBackTarget( + $this->lng->txt('cancel'), + $environment->withDefaultSubAction()->getUrlBuilder() + ->buildURI() + ->__toString() + ); + + $create = $this->questions_repository->getNew( + $environment->getParentObjId() + )->getEditView( + $this->current_user, + $this->ctrl, + $this->http->wrapper()->post(), + $this->ui_renderer, + $this->uuid_factory, + $this->configuration_repository, + $this->attempt_repository, + $this->required_capabilities + )->create( + $environment->withActionParameter(self::ACTION_CREATE_QUESTION) + ); + + if ($create instanceof EditForm) { + return $create; + } + + $this->questions_repository->create([$create]); + $this->ctrl->redirectToURL( + $this->buildAfterQuestionCreationRedirectUri( + $environment, + $create->getCreateMode(), + $create->getId() + ) + ); + + } + + private function editQuestion( + DefaultEnvironment $environment + ): EditForm { + $this->initializeEditMode($environment); + $this->tabs_gui->setBackTarget( + $this->lng->txt('back'), + $environment->withDefaultSubAction()->getUrlBuilder()->buildURI()->__toString() + ); + + $question_id = $environment->getQuestionId(); + $question = $this->questions_repository->getForQuestionId($question_id); + $environment_with_question_parameter = $environment + ->withQuestionIdParameter($question_id); + + $edit = $question->getEditView( + $this->current_user, + $this->ctrl, + $this->http->wrapper()->post(), + $this->ui_renderer, + $this->uuid_factory, + $this->configuration_repository, + $this->attempt_repository, + $this->required_capabilities + )->edit( + $environment_with_question_parameter + ->withActionParameter(self::ACTION_EDIT_QUESTION) + ); + + if ($edit instanceof EditForm) { + return $edit; + } + + $this->questions_repository->update([$edit]); + return $this->buildEditStartView( + $environment_with_question_parameter + ->withDefaultSubAction() + ->withActionParameter(self::ACTION_EDIT_QUESTION), + $edit + ); + } + + private function cloneQuestion( + DefaultEnvironment $environment + ): QuestionsTable { + $question_to_clone = $environment->getQuestionIds(); + + if (!isset($question_to_clone[0])) { + return $this->showTable($environment); + } + + $question = $this->questions_repository->getForQuestionId( + $question_to_clone[0] + ); + $this->questions_repository->create( + [ + $question->clone( + $this->uuid_factory, + [ + 'parent_obj_id' => $environment->getParentObjId(), + 'new_question_page_id' => $this->questions_repository + ->getNextAvailableQuestionPageId(), + 'required_capabilities' => $this->required_capabilities + ] + )->withParentObjId($environment->getParentObjId()) + ] + ); + + $this->ctrl->redirectToURL( + $environment->getUrlBuilder()->buildURI()->__toString() + ); + } + + private function deleteQuestions( + DefaultEnvironment $environment + ): Async { + $question_ids = $environment->getQuestionIds(); + + if ($question_ids === null) { + return $environment->getPresentationFactory()->getAsync( + $this->ui_factory->messageBox()->failure( + $this->lng->txt('msg_no_questions_selected') + ) + ); + } + + if ($environment->getSubAction() === self::ACTION_DELETE_QUESTIONS) { + $this->deleteSelectedQuestions($question_ids); + $this->ctrl->redirectToURL( + $environment->getUrlBuilder()->buildURI()->__toString() + ); + } + + return $environment->getPresentationFactory()->getAsync( + $this->ui_factory->modal()->interruptive( + $this->lng->txt('confirm'), + $this->lng->txt('confirm_delete_questions'), + $environment->withActionParameter( + self::ACTION_DELETE_QUESTIONS + )->withSubActionParameter( + self::ACTION_DELETE_QUESTIONS + )->getUrlBuilder()->buildURI()->__toString() + )->withAffectedItems( + $this->buildAffectedItems($question_ids) + ) + ); + } + + private function showTable( + DefaultEnvironment $environment + ): QuestionsTable { + $this->questions_repository->migrateQuestionPages(); + + return new QuestionsTable( + $this->ui_services, + $this->answer_form_factory, + $this->questions_repository, + $environment, + $this->required_capabilities + )->withCreateQuestionButton( + $this->ui_factory->button()->primary( + $this->lng->txt('create'), + $environment->withActionParameter(self::ACTION_CREATE_QUESTION) + ->getUrlBuilder() + ->buildURI() + ->__toString() + ) + ); + } + + private function processCreateAnswerForm( + DefaultEnvironment $environment, + Question $question, + \ilPCAnswerForm $content_object + ): EditForm { + $form = $this->buildCreateAnswerForm($environment) + ->withRequest($this->http->request()); + + $data = $form->getData(); + if ($data === null) { + return $form; + } + + if ($this->configuration_repository->isCreateModeSimple($environment)) { + $question_page = new \QstsQuestionPage($question->getPageId()); + $question_page->setQuestion($question); + $question_page->addQuestionText($data['question_text']); + } + + $type_definition = $data['type']; + return $this->forwardCreateAnswerFormCmd( + $environment->withAnswerFormProperties( + $type_definition->buildProperties( + $this->answer_form_factory->getDefaultTypeGenericProperties( + $question->getId(), + $type_definition + ), + null + ) + )->withAnswerFormTypeHashParameter( + $this->answer_form_factory->getHashedClass($type_definition::class) + ), + $question, + $content_object, + $type_definition->getEditView() + ); + } + + private function forwardCreateAnswerFormCmd( + DefaultEnvironment $environment, + Question $question, + \ilPCAnswerForm $content_object, + AnswerFormEditView $answer_form_edit_view + ): ?EditForm { + $action = $environment->getAction(); + + $from_capabilites = $this->required_capabilities->edit( + $this->tabs_gui, + $environment, + $answer_form_edit_view, + $action + ); + + if ($from_capabilites instanceof EditForm) { + return $this->addSaveAndNewToAnswerFormCreateIfNeeded( + $environment, + $from_capabilites + ); + } + + if ($from_capabilites instanceof AnswerFormProperties) { + $this->createAnswerFormAndRedirect( + $environment, + $question->withAnswerFormProperties($from_capabilites), + $content_object + ); + } + + $from_edit_view = $answer_form_edit_view->create( + $environment->withAnswerFormIdParameter( + $environment->getAnswerFormId() + ) + ); + + if ($from_edit_view instanceof EditForm) { + return $this->addSaveAndNewToAnswerFormCreateIfNeeded( + $environment, + $this->required_capabilities->additionalAnswerFormStepsRequired() + ? $from_edit_view->withIsFinalStep(false) + : $from_edit_view + ); + } + + $from_capabilities_first_step = $this->required_capabilities + ->doFirstFormStepAction( + $environment->withAnswerFormProperties($from_edit_view), + $answer_form_edit_view + ); + + if ($from_capabilities_first_step instanceof EditForm) { + return $this->addSaveAndNewToAnswerFormCreateIfNeeded( + $environment, + $from_capabilities_first_step + ); + } + + $this->createAnswerFormAndRedirect( + $environment, + $question->withAnswerFormProperties($from_capabilities_first_step), + $content_object + ); + } + + private function processOtherAnswerFormAction( + Environment $environment, + Question $question, + AnswerFormEditView $edit_view + ): Viewable { + $from_edit_view = $edit_view->other($environment); + + if ($from_edit_view instanceof Viewable) { + return $from_edit_view; + } + + if ($from_edit_view instanceof Async) { + $from_edit_view->render($this->ui_renderer); + } + + $this->updateAnswerFormAndRedirect( + $environment, + $question, + $from_edit_view + ); + } + + private function updateAnswerFormAndRedirect( + Environment $environment, + Question $question, + AnswerFormProperties $properties + ): never { + $this->questions_repository->update( + [$question->withAnswerFormProperties($properties)] + ); + + $this->required_capabilities->onAnswerFormUpdate($properties); + + $this->ctrl->redirectToURL( + $environment->getUrlBuilder()->buildURI()->__toString() + ); + } + + private function createAnswerFormAndRedirect( + Environment $environment, + Question $question, + \ilPCAnswerForm $content_object + ): never { + $this->questions_repository->update( + [$question] + ); + + $content_object->create( + $environment->getAnswerFormProperties()->getAnswerFormId() + ); + $content_object->getPage()->update(); + + $this->ctrl->redirectToURL( + $this->buildAfterAnswerFormCreationRedirectUri($environment) + ); + + } + + private function initializeEditMode( + DefaultEnvironment $environment + ): void { + $this->tabs_gui->clearTargets(); + + $this->global_screen->tool()->context()->current()->addAdditionalData( + LayoutProvider::MODE_ENABLED, + true + ); + $this->global_screen->tool()->context()->current()->addAdditionalData( + LayoutProvider::QUESTIONLIST_ENTRY, + $this->buildQuestionListSlate($environment) + ); + $this->global_screen->tool()->context()->current()->addAdditionalData( + LayoutProvider::URL_CLOSE_MODE_INFO, + $environment->getUrlBuilder()->buildURI() + ); + $this->global_screen->tool()->context()->current()->addAdditionalData( + LayoutProvider::URL_CREATE_QUESTION, + $environment + ->withActionParameter(self::ACTION_CREATE_QUESTION) + ->getUrlBuilder() + ->buildURI() + ); + } + + private function buildQuestionListSlate( + DefaultEnvironment $environment + ): LegacySlate { + return $this->ui_factory->mainControls()->slate()->legacy( + $this->lng->txt('questionlist'), + $this->ui_factory->symbol()->icon()->standard('', '')->withAbbreviation('QL'), + $this->ui_factory->legacy()->content( + $this->ui_renderer->render( + $this->ui_factory->panel()->secondary()->listing( + $this->lng->txt('questionlist'), + [ + $this->buildItemGroupForQuestionListSlate($environment) + ] + ) + ) + ) + ); + } + + private function buildItemGroupForQuestionListSlate( + DefaultEnvironment $environment + ): ItemGroup { + return $this->ui_factory->item()->group( + '', + $this->builEditLinksForQuestionListSlate($environment) + ); + } + + private function builEditLinksForQuestionListSlate( + Environment $environment + ): array { + $links = []; + foreach ($this->questions_repository->getQuestionDataOnlyForAllQuestions() as $question) { + $links[] = $this->ui_factory->item()->standard( + $question->toEditLink( + $this->ui_factory->link(), + $environment->withActionParameter(self::ACTION_EDIT_QUESTION) + ) + ); + } + return $links; + } + + private function buildEditStartView( + DefaultEnvironment $environment, + Question $question + ): EditForm { + return $question->getEditView( + $this->current_user, + $this->ctrl, + $this->http->wrapper()->post(), + $this->ui_renderer, + $this->uuid_factory, + $this->configuration_repository, + $this->attempt_repository, + $this->required_capabilities + )->edit( + $environment + ); + } + + private function buildCreateAnswerForm( + DefaultEnvironment $environment + ): EditForm { + $if = $this->ui_factory->input(); + + $inputs = []; + if ($this->configuration_repository->isCreateModeSimple($environment)) { + $inputs['question_text'] = $if->field()->textarea( + $this->lng->txt('question_text') + )->withRequired(true); + } + + return $environment->getPresentationFactory()->getEditForm( + $if->field()->section( + $inputs + [ + 'type' => $if->field()->select( + $this->lng->txt('select_answer_form_type'), + $this->answer_form_factory + ->getAnswerFormTypesArrayForSelect($this->lng) + )->withRequired(true) + ->withAdditionalTransformation( + $this->refinery->custom()->transformation( + fn(string $v): ?Definition => $this->answer_form_factory + ->getTypeDefinitionFromSelectValue($v) + ) + ) + ], + $this->lng->txt('create_answer_form') + ), + $environment->withActionParameter( + self::ACTION_CREATE_ANSWER_FORM + )->getUrlBuilder(), + null + ); + } + + /** + * + * @param string|array<\ILIAS\Data\UUID\Uuid> $question_ids + * @return array<\ILIAS\UI\Component\Modal\InterruptiveItem\Standard> + */ + private function buildAffectedItems( + string|array $question_ids + ): array { + $questions = $question_ids === 'ALL_OBJECTS' + ? $this->questions_repository->getQuestionDataOnlyForAllQuestions() + : $this->questions_repository->getQuestionDataOnlyForQuestionIds($question_ids); + $affected_items = []; + foreach ($questions as $question) { + $affected_items[] = $this->ui_factory->modal()->interruptiveItem()->standard( + $question->getId()->toString(), + $question->getTitle() + ); + } + return $affected_items; + } + + private function deleteSelectedQuestions( + array $question_ids + ): void { + $questions_to_delete = []; + foreach ($this->questions_repository->getForQuestionIds($question_ids) as $question) { + if (count($questions_to_delete) < 100) { + $questions_to_delete[] = $question; + continue; + } + + $this->questions_repository->delete($questions_to_delete); + $questions_to_delete = []; + } + + $this->questions_repository->delete($questions_to_delete); + } + + private function addSaveAndNewToAnswerFormCreateIfNeeded( + Environment $environment, + EditForm $edit_form + ): EditForm { + if ($edit_form->isFinalStep() && $environment->isCreateModeSimple()) { + $edit_form = $edit_form->withAdditionalAction( + $environment->buildURLBuilderTokenForCreateAndNew(), + '1', + $this->lng->txt('save_and_new') + ); + } + + return $edit_form; + } + + private function buildEnvironment( + URI $base_uri, + ): DefaultEnvironment { + return new DefaultEnvironment( + $this->ctrl, + $this->http, + $this->refinery, + $this->lng, + $this->tabs_gui, + $this->uuid_factory, + $this->layout_factory, + $this->editability, + $this->required_capabilities, + $this->owner_object_id, + $base_uri + ); + } + + private function buildAfterQuestionCreationRedirectUri( + DefaultEnvironment $environment, + CreateModes $create_mode, + Uuid $question_uuid + ): string { + if ($create_mode !== CreateModes::Simple) { + return $environment + ->withDefaultSubAction() + ->withActionParameter(self::ACTION_EDIT_QUESTION) + ->withQuestionIdParameter($question_uuid) + ->getUrlBuilder() + ->buildURI() + ->__toString(); + } + + $environment->setParamtersForSimpleCreateCmd($question_uuid); + + return $this->ctrl->getLinkTargetByClass( + [ + \QstsQuestionPageGUI::class, + \ilPageEditorGUI::class, + \ilPCAnswerFormGUI::class + ], + 'insert' + ); + } + + private function buildAfterAnswerFormCreationRedirectUri( + DefaultEnvironment $environment, + ): string { + if (!$this->configuration_repository->isCreateModeSimple($environment)) { + return $this->ctrl->getLinkTargetByClass( + \QstsQuestionPageGUI::class, + 'edit' + ); + } + + $additonal_data = $this->global_screen + ->tool() + ->context() + ->current() + ->getAdditionalData(); + + if ($environment->isCreateAndNewAction()) { + return $additonal_data + ->get(LayoutProvider::URL_CREATE_QUESTION) + ->__toString(); + } + + return $additonal_data + ->get(LayoutProvider::URL_CLOSE_MODE_INFO) + ->__toString(); + } +} diff --git a/components/ILIAS/Questions/src/Presentation/Views/Participant.php b/components/ILIAS/Questions/src/Presentation/Views/Participant.php new file mode 100644 index 000000000000..5e4c8eeda37f --- /dev/null +++ b/components/ILIAS/Questions/src/Presentation/Views/Participant.php @@ -0,0 +1,173 @@ +> $capability_identifiers + */ + public function __construct( + private readonly Language $lng, + private readonly Refinery $refinery, + private readonly UIFactory $ui_factory, + private readonly HTTP $http, + private readonly QuestionRepository $question_repository, + private readonly AttemptRepository $attempt_repository, + private readonly CapabilitiesFactory $capabilities_factory, + array $capability_identifiers, + private readonly int $owner_object_id + ) { + $this->required_capabilities = $this->capabilities_factory->get( + $capability_identifiers + ); + } + + public function getPresentationIdentifier(): Uuid + { + return $this->presentation_identifier; + } + + public function withShuffleQuestionOrder( + bool $shuffle_question_order + ): self { + $clone = clone $this; + $clone->shuffle_question_order = $shuffle_question_order; + return $clone; + } + + public function getQuestionView( + Uuid $question_id, + ?Uuid $attempt_id, + bool $interactive, + bool $show_marks, + bool $show_best_response, + bool $show_feedback + ): QuestionParticipantView { + $question = $this->question_repository->getForQuestionId( + $question_id + ); + + $marking_required = $this->required_capabilities->isMarkingRequired(); + + if ($attempt_id === null + || !isset($this->attempt_cache[$attempt_id->toString()][$question_id->toString()])) { + $attempt = $this->attempt_repository->getAttemptFor( + $attempt_id, + [$question] + ); + $attempt_id = $attempt->getId(); + $this->attempt_cache[$attempt_id->toString()][$question_id->toString()] = $attempt; + } + + return $question->getParticipantView( + $this->lng, + $this->refinery, + $this->ui_factory, + $this->required_capabilities, + $this->attempt_cache[$attempt_id->toString()][$question_id->toString()], + $interactive, + $show_marks && $marking_required, + $show_best_response && $marking_required, + $show_feedback && $this->required_capabilities->isCapabilityRequired( + TextFeedback::getIdentifier() + ) + ); + } + + public function persistResponse( + Uuid $question_id, + Uuid $attempt_id + ): void { + $question = $this->question_repository->getForQuestionId( + $question_id + ); + + $attempt_data = $this->attempt_repository->getAttemptFor( + $attempt_id, + [$question] + ); + + if ($attempt_data === null) { + throw new \UnexpectedValueException( + 'The provided attempt identifier is invalid. Response cannot be persisted.' + ); + } + + $response = $this->attempt_repository->getNewResponseFor( + $question_id, + $attempt_id + ); + + $response_with_values_from_post = array_reduce( + $question->retrieveAnswerFormResponsesFromPost( + $this->required_capabilities, + $this->http->wrapper()->post(), + $response->getId() + ), + fn(Response $c, AnswerFormResponse $v): Response + => $c->withAnswerFormResponse($v), + $response + ); + + if ($this->required_capabilities->isMarkingRequired()) { + $response_with_values_from_post = $question->addAwardedPointsToResponse( + $response_with_values_from_post + ); + } + + $this->attempt_repository->storeResponse( + $response_with_values_from_post + ); + } + + public function deleteResponsesFor( + Uuid $attempt_id, + Uuid $question_id + ): void { + $this->attempt_repository->deleteResponsesFor( + $attempt_id, + $this->question_repository->getForQuestionId( + $question_id + ) + ); + } +} diff --git a/components/ILIAS/Questions/src/PublicInterface.php b/components/ILIAS/Questions/src/PublicInterface.php new file mode 100644 index 000000000000..78902c6bc939 --- /dev/null +++ b/components/ILIAS/Questions/src/PublicInterface.php @@ -0,0 +1,46 @@ +> $required_capabilities_class_names + */ + public function getParticipantView( + array $required_capabilities_class_names, + int $owner_obj_id + ): Participant; + + /** + * + * @param list> $required_capabilities_class_names + */ + public function getEditView( + array $required_capabilities_class_names, + int $owner_obj_id + ): Edit; +} diff --git a/components/ILIAS/Questions/src/Question/Definitions/Lifecycle.php b/components/ILIAS/Questions/src/Question/Definitions/Lifecycle.php new file mode 100644 index 000000000000..43184f62a9f7 --- /dev/null +++ b/components/ILIAS/Questions/src/Question/Definitions/Lifecycle.php @@ -0,0 +1,31 @@ +addAnswerFormStatementsToManipulate( + $manipulate + ->withAdditionalStatement( + $this->buildInsertLinkingStatement( + $manipulate->getTableNameBuilder(null), + $question_id, + $parent_obj_id, + $position + ) + )->withAdditionalStatement( + $this->buildInsertQuestionStatement( + $manipulate->getTableNameBuilder(null), + $question_id, + $page_id, + $title, + $author, + $lifecycle, + $remarks, + $original_id + ) + ), + $answer_forms + ); + } + + public function addUpdateStatementsToManipulation( + Manipulate $manipulate, + bool $self_updated, + bool $linking_information_updated, + bool $page_id_updated, + Uuid $question_id, + ?int $page_id, + string $title, + string $author, + Lifecycle $lifecycle, + string $remarks, + ?Uuid $original_id, + int $parent_obj_id, + ?int $position, + array $new_answer_forms, + array $updated_answer_forms, + array $deleted_answer_forms + ): Manipulate { + $table_names_builder = $manipulate->getTableNameBuilder(null); + + if ($linking_information_updated) { + $manipulate = $manipulate + ->withAdditionalStatement( + $this->buildUpdateLinkingStatement( + $table_names_builder, + $question_id, + $parent_obj_id, + $position + ) + ); + } + + if ($self_updated) { + $manipulate = $manipulate->withAdditionalStatement( + $this->buildUpdateQuestionStatement( + $table_names_builder, + $question_id, + $title, + $author, + $lifecycle, + $remarks, + $original_id + ) + ); + } + + if ($page_id_updated) { + $manipulate = $manipulate->withAdditionalStatement( + $this->buildUpdatePageIdStatement( + $table_names_builder, + $question_id, + $page_id + ) + ); + } + + if ($deleted_answer_forms !== []) { + $manipulate = $this->addDeleteAnswerFormsStatementsToManipulate( + $manipulate, + $deleted_answer_forms + ); + } + + return $this->addAnswerFormStatementsToManipulate( + $manipulate, + $new_answer_forms, + $updated_answer_forms + ); + } + + public function addDeleteAnswerFormsStatementsToManipulate( + Manipulate $manipulate, + array $answer_forms_to_delete + ): Manipulate { + return array_reduce( + $answer_forms_to_delete, + fn(Manipulate $c, AnswerFormProperties $v): Manipulate => $v->toDelete( + $this->persistence_factory, + $v->getTypeGenericProperties()->toDelete( + $this->persistence_factory, + $this->answer_form_generic_table_definitions, + $c, + ) + ), + $manipulate + ); + } + + public function buildDeleteQuestionStatement( + TableNameBuilder $table_names_builder, + Uuid $question_id + ): Delete { + $table_type = TableTypes::Questions; + return $this->persistence_factory->delete( + $this->persistence_factory->table( + $table_names_builder, + $table_type + ), + [ + $this->persistence_factory->where( + $this->question_tables_definitions->getIdColumn( + $table_names_builder, + $table_type + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $question_id->toString() + ) + ) + ] + ); + } + + public function buildDeleteLinkingStatement( + TableNameBuilder $table_names_builder, + Uuid $question_id + ): Delete { + $table_type = TableTypes::Linking; + return $this->persistence_factory->delete( + $this->persistence_factory->table( + $table_names_builder, + $table_type + ), + [ + $this->persistence_factory->where( + $this->question_tables_definitions->getIdColumn( + $table_names_builder, + $table_type + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $question_id->toString() + ) + ) + ] + ); + } + + /** + * @todo skergomard, 2026-01-86: This we only need while the migrations exist, after + * this MUST go! + */ + public function buildDeleteMigrationStatement( + TableNameBuilder $table_names_builder, + Uuid $question_id + ): Delete { + $table_type = TableTypes::MigrationsTable; + return $this->persistence_factory->delete( + $this->persistence_factory->table( + $table_names_builder, + $table_type + ), + [ + $this->persistence_factory->where( + $this->question_tables_definitions->getIdColumn( + $table_names_builder, + $table_type + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $question_id->toString() + ) + ) + ] + ); + } + + private function addAnswerFormStatementsToManipulate( + Manipulate $manipulate, + array $new_answer_forms, + array $updated_answer_forms = [] + ): Manipulate { + $manipulate_with_new_answer_form_statements = array_reduce( + $new_answer_forms, + fn(Manipulate $c, AnswerFormProperties $v): Manipulate => + $this->addAnswerFormStatementToManipulate( + $v, + ManipulationType::Create, + $c + ), + $manipulate + ); + + return array_reduce( + $updated_answer_forms, + fn(Manipulate $c, AnswerFormProperties $v): Manipulate => + $this->addAnswerFormStatementToManipulate( + $v, + ManipulationType::Update, + $c + ), + $manipulate_with_new_answer_form_statements + ); + } + + private function addAnswerFormStatementToManipulate( + AnswerFormProperties $answer_form_properties, + ManipulationType $manipulation_type, + Manipulate $manipulate + ): Manipulate { + $manipulate_with_generic_properties = $answer_form_properties->getTypeGenericProperties() + ->toStorage( + $this->persistence_factory, + $this->answer_form_generic_table_definitions, + $manipulation_type, + $manipulate + ); + + return $answer_form_properties->toStorage( + $this->persistence_factory, + $manipulation_type, + $manipulate_with_generic_properties + ); + } + + private function buildInsertLinkingStatement( + TableNameBuilder $table_names_builder, + Uuid $question_id, + int $parent_obj_id, + ?int $position + ): Insert { + return $this->persistence_factory->insert( + $this->question_tables_definitions->getColumns( + $table_names_builder, + TableTypes::Linking + ), + [ + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $question_id->toString() + ), + $this->persistence_factory->value( + FieldDefinition::T_INTEGER, + $parent_obj_id + ), + $this->persistence_factory->value( + FieldDefinition::T_INTEGER, + $position + ) + ] + ); + } + + private function buildInsertQuestionStatement( + TableNameBuilder $table_names_builder, + Uuid $question_id, + ?int $page_id, + string $title, + string $author, + Lifecycle $lifecycle, + string $remarks, + ?Uuid $original_id + ): Insert { + return $this->persistence_factory->insert( + $this->question_tables_definitions->getColumns( + $table_names_builder, + TableTypes::Questions + ), + [ + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $question_id->toString() + ), + $this->persistence_factory->value( + FieldDefinition::T_INTEGER, + $page_id + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $title + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $author + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $lifecycle->value + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $remarks + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $original_id?->toString() + ), + $this->persistence_factory->value( + FieldDefinition::T_INTEGER, + time() + ), + $this->persistence_factory->value( + FieldDefinition::T_INTEGER, + time() + ) + ] + ); + } + + private function buildUpdateLinkingStatement( + TableNameBuilder $table_names_builder, + Uuid $question_id, + int $parent_obj_id, + ?int $position + ): Update { + $table_type = TableTypes::Linking; + return $this->persistence_factory->update( + $this->question_tables_definitions->getColumns( + $table_names_builder, + $table_type, + [TableTypes::LINKING_TABLE_ID_COLUMN] + ), + [ + $this->persistence_factory->value( + FieldDefinition::T_INTEGER, + $parent_obj_id + ), + $this->persistence_factory->value( + FieldDefinition::T_INTEGER, + $position + ) + ], + [ + $this->persistence_factory->where( + $this->question_tables_definitions->getIdColumn( + $table_names_builder, + $table_type + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $question_id->toString() + ) + ) + ] + ); + } + + private function buildUpdateQuestionStatement( + TableNameBuilder $table_names_builder, + Uuid $question_id, + string $title, + string $author, + Lifecycle $lifecycle, + string $remarks, + ?Uuid $original_id + ): Update { + $table_type = TableTypes::Questions; + return $this->persistence_factory->update( + $this->question_tables_definitions->getColumns( + $table_names_builder, + $table_type, + [ + 'id', + 'page_id', + 'created' + ] + ), + [ + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $title + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $author + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $lifecycle->value + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $remarks + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $original_id?->toString() + ), + $this->persistence_factory->value( + FieldDefinition::T_INTEGER, + time() + ) + ], + [ + $this->persistence_factory->where( + $this->question_tables_definitions->getIdColumn( + $table_names_builder, + $table_type + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $question_id->toString() + ) + ) + ] + ); + } + + /** + * @todo skergomard, 2026-01-26: This we only need while the migrations exist, after + * this a question MUST never change the page assigned to it after its creation! + */ + private function buildUpdatePageIdStatement( + TableNameBuilder $table_names_builder, + Uuid $question_id, + ?int $page_id + ): Update { + $table_type = TableTypes::Questions; + return $this->persistence_factory->update( + [ + $this->persistence_factory->column( + $this->persistence_factory->table( + $table_names_builder, + $table_type + ), + 'page_id' + ), + $this->persistence_factory->column( + $this->persistence_factory->table( + $table_names_builder, + $table_type + ), + 'last_update' + ) + ], + [ + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $page_id + ), + $this->persistence_factory->value( + FieldDefinition::T_INTEGER, + time() + ) + ], + [ + $this->persistence_factory->where( + $this->question_tables_definitions->getIdColumn( + $table_names_builder, + $table_type + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $question_id->toString() + ) + ) + ] + ); + } +} diff --git a/components/ILIAS/Questions/src/Question/Persistence/Repository.php b/components/ILIAS/Questions/src/Question/Persistence/Repository.php new file mode 100644 index 000000000000..ab7f8c41d34d --- /dev/null +++ b/components/ILIAS/Questions/src/Question/Persistence/Repository.php @@ -0,0 +1,629 @@ +core_table_names_builder = new TableNameBuilder( + self::COMPONENT_NAMESPACE, + null + ); + } + + public function getNew( + int $parent_obj_id + ): Question { + return new Question( + $this->buildAvailableUuid(), + $parent_obj_id + ); + } + + /** + * @return \Generator<\ILIAS\Questions\Question\QuestionImplementation> + */ + public function getQuestionDataOnlyForAllQuestions( + ?DataRange $range = null, + ?DataOrder $order = null, + array $filter_data = [] + ): \Generator { + $questions_query = $this->addFilterToQuery( + $this->buildQuestionsQuery( + $this->buildMainOrder($order) + ), + $filter_data + ); + + if ($range !== null) { + $questions_query = $questions_query->withRange($range); + } + + + + foreach ($questions_query->withGroupBy( + $this->buildGroupByColumn() + )->getRecords() as $query_with_record) { + yield $this->retrieveQuestionFromQuery( + $query_with_record, + $this->retrieveAnswerFormsFromQuery($query_with_record, true) + ); + } + } + + public function getQuestionsCount(): int + { + $id_column = $this->question_table_definitions->getIdColumn( + $this->core_table_names_builder, + TableTypes::Questions + ); + return $this->db->fetchObject( + $this->db->query( + "SELECT count({$id_column->getColumnString()}) cnt FROM {$id_column->getTableName()}", + ) + )->cnt; + } + + /** + * @return \Generator<\ILIAS\Questions\Question\QuestionImplementation> + */ + public function getQuestionDataOnlyForQuestionIds( + array $question_ids + ): \Generator { + foreach ($this->buildQuestionsQuery()->withAdditionalWhere( + $this->persistence_factory->where( + $this->question_table_definitions->getIdColumn( + $this->core_table_names_builder, + TableTypes::Questions + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + array_map( + fn(Uuid $v): string => $v->toString(), + $question_ids + ) + ), + Operator::In + ) + )->withGroupBy( + $this->buildGroupByColumn() + )->getRecords() as $query_with_record) { + yield $this->retrieveQuestionFromQuery( + $query_with_record, + $this->retrieveAnswerFormsFromQuery($query_with_record, true) + ); + } + } + + public function getForQuestionId( + Uuid $question_id + ): ?Question { + return $this->getForBaseQuery( + $this->buildQuestionsQuery()->withAdditionalWhere( + $this->persistence_factory->where( + $this->question_table_definitions->getIdColumn( + $this->core_table_names_builder, + TableTypes::Questions + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $question_id->toString() + ), + Operator::Equal + ) + ), + [$question_id] + )->current(); + } + + /** + * + * @param list<\ILIAS\Data\Uuid> $question_ids + * @return \Generator<\ILIAS\Questions\Question\QuestionImplementation> + */ + public function getForQuestionIds( + array $question_ids + ): \Generator { + yield from $this->getForBaseQuery( + $this->buildQuestionsQuery()->withAdditionalWhere( + $this->persistence_factory->where( + $this->question_table_definitions->getIdColumn( + $this->core_table_names_builder, + TableTypes::Questions + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + array_map( + fn(Uuid $v): string => $v->toString(), + $question_ids + ) + ), + Operator::In + ) + ), + $question_ids + ); + } + + /** + * @param array<\ILIAS\Questions\Question\QuestionImplementation> $questions + */ + public function create( + array $questions + ): void { + $this->store( + array_map( + fn(Question $v): Question => $v->getPageId() === null + ? $v->withPageId( + $this->buildQuestionPage( + $v->getParentObjId() + ) + ) : $v, + $questions + ), + ManipulationType::Create, + $this->buildManipulate() + ); + } + + /** + * @param array<\ILIAS\Questions\Question\QuestionImplementation> $questions + */ + public function update( + array $questions + ): void { + $this->store( + $questions, + ManipulationType::Update, + $this->buildManipulate() + ); + } + + public function delete( + array $questions + ): void { + array_reduce( + $questions, + fn(Manipulate $c, Question $v): Manipulate + => $v->toDelete( + $this->database_statement_builder, + $c + ), + $this->buildManipulate() + )->run(); + + foreach ($questions as $question) { + (new \QstsQuestionPage($question->getPageId()))->delete(); + } + } + + public function getQuestionForAnswerFormId( + Uuid $answer_form_id + ): ?Question { + $question_id = $this->db->fetchObject( + $this->db->query( + "SELECT question_id FROM qsts_answer_forms" . PHP_EOL + . "WHERE id = {$this->db->quote($answer_form_id->toString(), FieldDefinition::T_TEXT)}" + ) + )?->question_id; + + if ($question_id === null) { + return null; + } + + return $this->getForQuestionId( + $this->uuid_factory->fromString($question_id) + ); + } + + public function migrateQuestionPages(): void + { + $questions_table_name = $this->core_table_names_builder + ->getTableNameFor(TableTypes::Questions); + $migration_table_name = $this->core_table_names_builder + ->getTableNameFor(TableTypes::MigrationsTable); + + $query = $this->db->query( + "SELECT new_question_id, old_question_id, question_id" . PHP_EOL + . "FROM {$questions_table_name}" . PHP_EOL + . "INNER JOIN {$migration_table_name} ON id = new_question_id" . PHP_EOL + . 'LEFT OUTER JOIN qpl_questions ON old_question_id = question_id' . PHP_EOL + . "WHERE page_id = 0" + ); + while (($row = $this->db->fetchObject($query)) !== null) { + $question = $this->getForQuestionId( + $this->uuid_factory->fromString($row->new_question_id) + ); + if ($row->question_id === null) { + $this->delete([$question]); + continue; + } + $this->migrateQuestionPage( + $question, + $row->old_question_id + ); + } + } + + public function getNextAvailableQuestionPageId(): int + { + + $last_id = $this->db->fetchObject( + $this->db->query( + 'SELECT MAX(page_id) AS last FROM page_object ' + . 'WHERE parent_type = "qsts"' + ) + )->last; + if ($last_id === null) { + return 1; + } + + return $last_id + 1; + } + + /** + * @param array<\ILIAS\Data\Uuid> $question_ids + * @return \Generator<\ILIAS\Questions\Question\QuestionImplementation> + */ + private function getForBaseQuery( + Query $query, + array $question_ids + ): \Generator { + $query_with_answer_forms = array_reduce( + $this->getAnswerFormTypesForQuestionIds($question_ids), + fn(Query $c, AnswerFormDefinition $v) => $v->getTableDefinitions()->completeQuestionQuery( + $c, + $this->answer_form_generic_table_definitions->getIdColumn( + $this->core_table_names_builder, + AnswerFormGenericTableTypes::AnswerForms + ) + ), + $query + ); + + foreach ($query_with_answer_forms->withGroupBy( + $this->buildGroupByColumn() + )->getRecords() as $query_with_record) { + yield $this->retrieveQuestionFromQuery( + $query_with_record, + $this->retrieveAnswerFormsFromQuery($query_with_record) + ); + } + } + + /** + * $param array<\ILIAS\Questions\AnswerForms\Properties> $answer_forms + */ + private function retrieveQuestionFromQuery( + Query $query, + array $answer_forms + ): Question { + $linking_info = $query->retrieveCurrentRecord( + $this->persistence_factory->table( + $this->core_table_names_builder, + TableTypes::Linking + ), + $this->refinery->identity() + ); + + return $query->retrieveCurrentRecord( + $this->persistence_factory->table( + $this->core_table_names_builder, + TableTypes::Questions, + ), + $this->refinery->custom()->transformation( + fn(array $vs): Question => new Question( + $this->uuid_factory->fromString($vs[0]['id']), + $linking_info[0]['obj_id'], + $linking_info[0]['position'], + $vs[0]['page_id'], + $vs[0]['title'], + $vs[0]['author'], + Lifecycle::from($vs[0]['lifecycle']), + $vs[0]['remarks'], + $vs[0]['original_id'] === null + ? null + : $this->uuid_factory->fromString($vs[0]['original_id']), + new \DateTimeImmutable('@' . $vs[0]['last_update'], new \DateTimeZone('UTC')), + new \DateTimeImmutable('@' . $vs[0]['created'], new \DateTimeZone('UTC')), + $answer_forms + ) + ) + ); + } + + /** + * @return array<\ILIAS\Questions\AnswerForms\Properties> + */ + private function retrieveAnswerFormsFromQuery( + Query $query, + bool $only_generic_data = false + ): array { + return $query->retrieveCurrentRecord( + $this->persistence_factory->table( + $this->core_table_names_builder, + AnswerFormGenericTableTypes::AnswerForms + ), + $this->refinery->custom()->transformation( + function (array $vs) use ($query, $only_generic_data): array { + if (count($vs) === 1 && $vs[0]['type'] === null) { + return []; + } + + $answer_forms = []; + $previous_answer_form_id = null; + foreach ($vs as $data_set) { + if ($data_set['id'] === $previous_answer_form_id) { + continue; + } + $previous_answer_form_id = $data_set['id']; + $definition = $this->answer_form_factory + ->getDefinitionForClass($data_set['type']); + $answer_forms[] = $definition->buildProperties( + $this->answer_form_factory->buildTypeGenericPropertiesFromDatabase($data_set), + $only_generic_data ? null : $query + ); + } + return $answer_forms; + } + ) + ); + } + + /** + * @param array<\ILIAS\Data\Uuid> $question_ids + * @return array<\ILIAS\Questions\AnswerForm\Definition> + */ + private function getAnswerFormTypesForQuestionIds( + array $question_ids + ): array { + $table_name = $this->core_table_names_builder + ->getTableNameFor(AnswerFormGenericTableTypes::AnswerForms); + + $query = $this->db->query( + "SELECT DISTINCT type FROM {$table_name}" . PHP_EOL + . "WHERE {$this->db->in( + 'question_id', + $question_ids, + false, + FieldDefinition::T_TEXT + )}" + ); + $answer_form_types = []; + while (($type_class = $this->db->fetchObject($query)?->type) !== null) { + $answer_form_types[] = $this->answer_form_factory->getDefinitionForClass($type_class); + } + return $answer_form_types; + } + + /** + * @param array<\ILIAS\Questions\Question\QuestionImplementation> $questions + */ + private function store( + array $questions, + ManipulationType $manipulation_type, + Manipulate $manipulate + ): void { + array_reduce( + $questions, + fn(Manipulate $c, Question $v): Manipulate => $v->toStorage( + $this->database_statement_builder, + $manipulation_type, + $c + ), + $manipulate + )->run(); + } + + private function buildQuestionsQuery( + ?Order $main_sort_order = null + ): Query { + $base_query = new Query( + $this->db, + $this->refinery, + self::COMPONENT_NAMESPACE, + $this->persistence_factory->table( + $this->core_table_names_builder, + TableTypes::Linking + ) + ); + + if ($main_sort_order !== null) { + $base_query = $base_query->withAdditionalOrder($main_sort_order); + } + + return $this->answer_form_generic_table_definitions->completeQuestionQuery( + $this->question_table_definitions->completeLoadQuestionQuery($base_query), + $this->question_table_definitions->getIdColumn( + $this->core_table_names_builder, + TableTypes::Questions + ) + ); + } + + private function addFilterToQuery( + Query $question_query, + array $filter_data + ): Query { + foreach ($filter_data as $key => $value) { + if ($value === null || $value === '' || $value === []) { + continue; + } + + $column_definition = OverviewTableColumns::tryFrom($key); + + $column = $column_definition?->getDatabaseColumn( + $this->persistence_factory, + $this->core_table_names_builder, + $this->answer_form_factory + ); + if ($column === null) { + continue; + } + + $operator = Operator::Equal; + if (is_string($value)) { + $operator = Operator::Like; + $value = "%{$value}%"; + } + + $question_query = $question_query->withAdditionalWhere( + $this->persistence_factory->where( + $column, + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $column_definition->transformFilterValue( + $this->answer_form_factory, + $value + ) + ), + $operator + ) + ); + } + + return $question_query; + } + + private function buildManipulate(): Manipulate + { + return new Manipulate( + $this->db, + self::COMPONENT_NAMESPACE + ); + } + + private function buildMainOrder( + ?DataOrder $order + ): ?Order { + if ($order === null) { + return null; + } + + $order_array = $order->get(); + + return $this->persistence_factory->order( + OverviewTableColumns::tryFrom( + array_key_first($order_array) + )->getDatabaseColumn( + $this->persistence_factory, + $this->core_table_names_builder + ), + OrderDirection::tryFrom( + array_shift($order_array) + ) + ); + } + + private function buildGroupByColumn(): Column + { + return $this->question_table_definitions->getIdColumn( + $this->core_table_names_builder, + TableTypes::Questions + ); + } + + private function buildAvailableUuid(): Uuid + { + do { + $uuid = $this->uuid_factory->uuid4(); + if ($this->checkAvailabilityOfId($uuid)) { + return $uuid; + } + } while (true); + } + + private function checkAvailabilityOfId( + Uuid $uuid + ): bool { + $table_name = $this->core_table_names_builder + ->getTableNameFor(TableTypes::Questions); + + return $this->db->fetchObject( + $this->db->query( + "SELECT COUNT(*) as cnt FROM {$table_name}" . PHP_EOL + . "WHERE id='{$uuid->toString()}'" + ) + )->cnt === 0; + } + + private function buildQuestionPage( + int $parent_obj_id + ): int { + $page = new \QstsQuestionPage(); + $page->setId($this->getNextAvailableQuestionPageId()); + $page->setParentId($parent_obj_id); + $page->createFromXML(); + return $page->getId(); + } + + private function migrateQuestionPage( + Question $question, + int $old_question_id + ): Question { + $new_page_id = $this->getNextAvailableQuestionPageId(); + $old_qsts_page = new \ilAssQuestionPage($old_question_id); + $old_qsts_page->setQuestion($question); + $old_qsts_page->copyToAnswerForm($new_page_id, $question); + + $new_question = $question->withPageId($new_page_id); + + $this->update([$new_question]); + + return $new_question; + } +} diff --git a/components/ILIAS/Questions/src/Question/Persistence/TableDefinitions.php b/components/ILIAS/Questions/src/Question/Persistence/TableDefinitions.php new file mode 100644 index 000000000000..41e4c1e1a7cc --- /dev/null +++ b/components/ILIAS/Questions/src/Question/Persistence/TableDefinitions.php @@ -0,0 +1,170 @@ +persistence_factory->table( + $table_names_builder, + $table_type + ); + + return array_map( + fn(string $v): Column => $this->persistence_factory->column( + $table, + $v + ), + array_values( + array_filter( + match($table_type) { + TableTypes::Questions => self::QUESTION_TABLE_COLUMNS, + TableTypes::Linking => self::LINKING_TABLE_COLUMNS, + TableTypes::MigrationsTable => self::MIGRATIONS_TABLE_COLUMNS + }, + fn(string $v) => !in_array($v, $columns_to_skip) + ) + ) + ); + } + + public function getIdColumn( + TableNameBuilder $table_names_builder, + TableTypesInterface $table_type + ): Column { + $table = $this->persistence_factory->table( + $table_names_builder, + $table_type + ); + + return match($table_type) { + TableTypes::Questions => $this->persistence_factory->column( + $table, + self::QUESTION_TABLE_ID_COLUMN + ), + TableTypes::Linking => $this->persistence_factory->column( + $table, + self::LINKING_TABLE_ID_COLUMN + ), + TableTypes::MigrationsTable => $this->persistence_factory->column( + $table, + self::MIGRATIONS_TABLE_ID_COLUMN + ) + }; + } + + public function completeLoadQuestionQuery( + Query $query + ): Query { + $table_names_builder = $query->getTableNameBuilder(null); + $questions_id_column = $this->getIdColumn( + $table_names_builder, + TableTypes::Questions + ); + + return $query->withAdditionalSelect( + $this->persistence_factory->select( + $this->getColumns( + $table_names_builder, + TableTypes::Linking + ) + ) + )->withAdditionalSelect( + $this->persistence_factory->select( + $this->getColumns( + $table_names_builder, + TableTypes::Questions + ) + ) + )->withAdditionalJoin( + $this->persistence_factory->join( + $this->getIdColumn( + $table_names_builder, + TableTypes::Linking + ), + $questions_id_column, + JoinType::Inner + ) + )->withAdditionalOrder( + $this->persistence_factory->order( + $questions_id_column + ) + )->withAdditionalOrder( + $this->persistence_factory->order( + $this->getIdColumn( + $table_names_builder, + TableTypes::Linking + ) + ) + ); + } +} diff --git a/components/ILIAS/Questions/src/Question/Persistence/TableTypes.php b/components/ILIAS/Questions/src/Question/Persistence/TableTypes.php new file mode 100644 index 000000000000..49229b969564 --- /dev/null +++ b/components/ILIAS/Questions/src/Question/Persistence/TableTypes.php @@ -0,0 +1,31 @@ +answer_forms = array_reduce( + $answer_forms, + function (array $c, AnswerFormProperties $v): array { + $c[$v->getAnswerFormId()->toString()] = $v; + return $c; + }, + [] + ); + } + + public function getId(): ?Uuid + { + return $this->id; + } + + public function getParentObjId(): int + { + return $this->parent_obj_id; + } + + public function withParentObjId( + int $parent_obj_id + ): self { + $clone = clone $this; + $clone->parent_obj_id = $parent_obj_id; + $clone->linking_information_updated = true; + return $clone; + } + + public function withPosition( + int $position + ): self { + $clone = clone $this; + $clone->position = $position; + $clone->linking_information_updated = true; + return $clone; + } + + public function getPageId(): ?int + { + return $this->page_id; + } + + public function withPageId( + int $page_id + ): self { + $clone = clone $this; + $clone->page_id = $page_id; + $clone->page_id_updated = true; + return $clone; + } + + public function getTitle(): string + { + return $this->title; + } + + public function withTitle( + string $title + ): self { + $clone = clone $this; + $clone->title = $title; + $clone->self_updated = true; + return $clone; + } + + public function getAuthor(): string + { + return $this->author; + } + + public function withAuthor( + string $author + ): self { + $clone = clone $this; + $clone->author = $author; + $clone->self_updated = true; + return $clone; + } + + public function getLifecycle(): Lifecycle + { + return $this->lifecycle; + } + + public function withLifecycle( + Lifecycle $lifecycle + ): self { + $clone = clone $this; + $clone->lifecycle = $lifecycle; + $clone->self_updated = true; + return $clone; + } + + public function getRemarks(): string + { + return $this->remarks; + } + + public function withRemarks( + string $remarks + ): self { + $clone = clone $this; + $clone->remarks = $remarks; + $clone->self_updated = true; + return $clone; + } + + public function getOriginalId(): ?Uuid + { + return $this->original_id; + } + + public function withOriginalId( + Uuid $original_id + ): self { + $clone = clone $this; + $clone->original_id = $original_id; + $clone->self_updated = true; + return $clone; + } + + public function getLastUpdate(): ?\DateTimeImmutable + { + return $this->last_update; + } + + public function getCreated(): ?\DateTimeImmutable + { + return $this->created; + } + + #[\Override] + public function clone( + UuidFactory $uuid_factory, + array $environment = [] + ): static { + $clone = clone $this; + $clone->id = $uuid_factory->uuid4(); + $clone->parent_obj_id = $environment['parent_obj_id']; + $clone->page_id = $environment['new_question_page_id']; + $mapping = []; + $clone->answer_forms = array_map( + function ( + AnswerFormProperties $v + ) use ( + $uuid_factory, + $environment, + $clone, + &$mapping + ): AnswerFormProperties { + $old_answer_form_id = $v->getAnswerFormId()->toString(); + + $cloned_answer_form = $v->clone( + $uuid_factory, + [ + 'new_question_id' => $clone->id, + 'answer_form_id' => $uuid_factory->uuid4() + ] + ); + + $mapping[$old_answer_form_id] = $cloned_answer_form + ->getAnswerFormId()->toString(); + + $environment['required_capabilities']->onAnswerFormClone( + $uuid_factory, + $v, + $cloned_answer_form + ); + + return $cloned_answer_form; + }, + $this->answer_forms + ); + \QstsQuestionPage::setAnswerFormMapping($mapping); + $question_page = new \QstsQuestionPage($this->page_id); + $question_page->copy($clone->page_id); + return $clone; + } + + /** + * @todo skergomard, 2026-06-08: This we only need while the migrations exist, after + * this a question MUST never change the page assigned to it after its creation! + */ + public function getFirstAnswerFormIdForMigration(): Uuid + { + return reset($this->answer_forms)->getAnswerFormId(); + } + + public function getAnswerFormPropertiesByIdString( + string $form_id + ): ?AnswerFormProperties { + return $this->answer_forms[$form_id] ?? null; + } + + public function withAnswerFormProperties( + AnswerFormProperties $answer_form + ): self { + $clone = clone $this; + if (isset($clone->answer_forms[$answer_form->getAnswerFormId()->toString()])) { + $clone->updated_answer_forms[] = $answer_form; + } else { + $clone->new_answer_forms[] = $answer_form; + } + $clone->answer_forms[$answer_form->getAnswerFormId()->toString()] = $answer_form; + return $clone; + } + + public function withoutDeletedAnswerForms( + array $found_answer_form_ids + ): self { + $clone = clone $this; + foreach (array_keys($this->answer_forms) as $answer_form_id) { + if (!in_array($answer_form_id, $found_answer_form_ids)) { + $clone->deleted_answer_forms[] = $clone->answer_forms[$answer_form_id]; + unset($clone->answer_forms[$answer_form_id]); + } + } + + return $clone; + } + + /** + * Checks whether the question is a clone of another question or not + */ + public function isClone(): bool + { + return $this->original_id !== null; + } + + public function getCreateMode(): ?CreateModes + { + return $this->create_mode; + } + + public function withCreateMode( + CreateModes $create_mode + ): self { + $clone = clone $this; + $clone->create_mode = $create_mode; + return $clone; + } + + public function getListOfContainedAnswerFormTypeLabels( + Language $lng + ): array { + return array_map( + fn(AnswerFormDefinition $v): string => $v->getLabel($lng), + $this->getListOfContainedAnswerFormTypes() + ); + } + + public function getEditView( + \ilObjUser $current_user, + \ilCtrl $ctrl, + RequestWrapper $post_wrapper, + UIRenderer $ui_renderer, + UuidFactory $uuid_factory, + ConfigurationRepository $configuration_repository, + AttemptRepository $attempt_repository, + RequiredCapabilities $required_capabilities + ): Views\Edit { + if (!$required_capabilities->areAllCapabilitiesSupportedByAnswerForms($this->answer_forms)) { + throw new \UnexpectedValueException( + "The Question does not support all required Capabilities." + ); + } + + return new Views\Edit( + $current_user, + $ctrl, + $post_wrapper, + $ui_renderer, + $uuid_factory, + $configuration_repository, + $attempt_repository, + $required_capabilities, + $this + ); + } + + public function getParticipantView( + Language $lng, + Refinery $refinery, + UIFactory $ui_factory, + RequiredCapabilities $required_capabilities, + ?Attempt $attempt_data, + bool $interactive = true, + bool $show_marks = false, + bool $show_best_response = false, + bool $show_feedback = false + ): Views\Participant { + if (!$required_capabilities->areAllCapabilitiesSupportedByAnswerForms($this->answer_forms)) { + throw new \UnexpectedValueException( + "The Question does not support all required Capabilities." + ); + } + + return new Views\Participant( + $lng, + $refinery, + $ui_factory, + $required_capabilities, + $this, + $attempt_data, + new ViewConfiguration( + $interactive, + $show_marks, + $show_best_response, + $show_feedback + ) + ); + } + + public function initializeAttemptData( + Attempt $attempt + ): Attempt { + return array_reduce( + $this->answer_forms, + fn(Attempt $c, AnswerFormProperties $v): Attempt + => $v->getDefinition()->initializeAttemptData($c, $v), + $attempt + ); + } + + public function toEditLink( + LinkFactory $link_factory, + DefaultEnvironment $environment + ): StandardLink { + return $link_factory->standard( + $this->title, + $environment->withQuestionIdParameter($this->id) + ->getUrlBuilder() + ->buildURI() + ->__toString() + ); + } + + public function toTableRow( + DataRowBuilder $row_builder, + DefaultEnvironment $environment, + RequiredCapabilities $required_capabilities + ): ?DataRow { + if (!$required_capabilities->areAllCapabilitiesSupportedByAnswerForms($this->answer_forms)) { + return null; + } + + return $row_builder->buildDataRow( + $this->id->toString(), + [ + OverviewTableColumns::Title->value => $environment + ->getUIFactory()->link()->standard( + $this->title, + $environment->withQuestionIdParameter( + $this->id + )->getUrlBuilder() + ->buildURI() + ->__toString() + ), + OverviewTableColumns::AnswerFormTypes->value => implode( + '
', + $this->getListOfContainedAnswerFormTypeLabels( + $environment->getLanguage() + ) + ) + ] + ); + } + + public function toStorage( + DatabaseStatementBuilder $database_statement_builder, + ManipulationType $manipulation_type, + Manipulate $manipulate + ): Manipulate { + return $manipulation_type === ManipulationType::Create + ? $database_statement_builder->addInsertStatementsToManipulation( + $manipulate, + $this->id, + $this->page_id, + $this->title, + $this->author, + $this->lifecycle, + $this->remarks, + $this->original_id, + $this->parent_obj_id, + $this->position, + $this->answer_forms + ) : $database_statement_builder->addUpdateStatementsToManipulation( + $manipulate, + $this->self_updated, + $this->linking_information_updated, + $this->page_id_updated, + $this->id, + $this->page_id, + $this->title, + $this->author, + $this->lifecycle, + $this->remarks, + $this->original_id, + $this->parent_obj_id, + $this->position, + $this->new_answer_forms, + $this->updated_answer_forms, + $this->deleted_answer_forms + ); + } + + public function toDelete( + DatabaseStatementBuilder $database_statement_builder, + Manipulate $manipulate + ): Manipulate { + $table_names_builder = $manipulate->getTableNameBuilder(null); + + return $database_statement_builder->addDeleteAnswerFormsStatementsToManipulate( + $manipulate->withAdditionalStatement( + $database_statement_builder->buildDeleteQuestionStatement( + $table_names_builder, + $this->id + ) + )->withAdditionalStatement( + $database_statement_builder->buildDeleteLinkingStatement( + $table_names_builder, + $this->id + ) + )->withAdditionalStatement( + $database_statement_builder->buildDeleteMigrationStatement( + $table_names_builder, + $this->id + ) + ), + $this->answer_forms + ); + } + + public function completeResponseQuery( + Query $query, + Column $base_table_id_column + ): Query { + return array_reduce( + $this->getListOfContainedAnswerFormTypes(), + fn(Query $c, AnswerFormDefinition $v): Query => $v + ->getTableDefinitions() + ->completeResponseQuery( + $c, + $base_table_id_column + ), + $query + ); + } + + public function retrieveAnswerFormResponsesFromQuery( + Uuid $response_id, + Query $query + ): array { + return array_map( + fn(AnswerFormProperties $v): AnswerFormResponse => $v + ->getDefinition() + ->buildResponse( + $response_id, + $v, + $query + ), + $this->answer_forms + ); + } + + public function retrieveAnswerFormResponsesFromPost( + RequiredCapabilities $required_capabilities, + RequestWrapper $post_wrapper, + Uuid $response_id + ): array { + return array_map( + fn(AnswerFormProperties $v): AnswerFormResponse => $required_capabilities + ->getParticipantViewProvider() + ->getParticipantView($v) + ->retrieveResponse( + $response_id, + $v, + $post_wrapper + ), + $this->answer_forms + ); + } + + public function retrieveAnswerFormResponsesFromPreviewData( + Uuid $response_id, + array $preview_data + ): array { + return array_map( + fn(AnswerFormProperties $v): AnswerFormResponse => $v + ->getDefinition() + ->buildResponseFromPreviewData( + $response_id, + $v, + $preview_data[$v->getAnswerFormId()->toString()] ?? [] + ), + $this->answer_forms + ); + } + + public function addAwardedPointsToResponse( + Response $response + ): Response { + return $response->withAwardedPoints( + array_reduce( + $this->answer_forms, + function (?float $c, AnswerFormProperties $v) use ($response): ?float { + /** @var Marking $marking */ + $marking = $v->getDefinition()->getCapability( + MarkingAllowingPartialPoints::getIdentifier() + ); + + if ($marking === null) { + return $c; + } + + $points_from_form = $marking->calculateAwardedPoints( + $v, + $response->getAnswerFormResponse($v->getAnswerFormId()) + ); + + if ($points_from_form === null) { + return $c; + } + + if ($c === null) { + return $points_from_form; + } + + return $c + $points_from_form; + } + ) + ); + } + + private function getListOfContainedAnswerFormTypes(): array + { + return array_reduce( + $this->answer_forms, + function ( + array $c, + AnswerFormProperties $v + ): array { + $definition = $v->getDefinition(); + if (!array_key_exists($definition::class, $c)) { + $c[$definition::class] = $definition; + } + return $c; + }, + [] + ); + } +} diff --git a/components/ILIAS/Questions/src/Question/Views/Edit.php b/components/ILIAS/Questions/src/Question/Views/Edit.php new file mode 100644 index 000000000000..25582f7ee9ec --- /dev/null +++ b/components/ILIAS/Questions/src/Question/Views/Edit.php @@ -0,0 +1,429 @@ +getSubAction()) { + self::CMD_SAVE_QUESTION => $this->processBasicPropertiesCreateForm( + $environment + ), + default => $this->buildBasicPropertiesCreateForm($environment) + }; + } + + public function edit( + DefaultEnvironment $environment, + ): EditForm|Question { + return match ($environment->getSubAction()) { + self::CMD_SAVE_QUESTION => $this->processBasicPropertiesEditingForm( + $environment + ), + self::CMD_SEND_RESPONSE => $this->sendResponse( + $environment + ), + self::CMD_RESET_PREVIEW => $this->resetPreview( + $environment + ), + default => $this->buildBasicPropertiesEditingForm($environment) + ->withContentAfterForm( + $this->buildPreviewPanel($environment) + ) + }; + } + + private function buildBasicPropertiesCreateForm( + DefaultEnvironment $environment + ): EditForm { + $ff = $environment->getUIFactory()->input()->field(); + + $inputs = [ + 'question' => $ff->group( + $this->buildBasicPropertiesInputs( + $environment->getUIFactory()->input()->field(), + $environment->getLanguage() + ) + )->withAdditionalTransformation( + $this->buildAddBasicPropertiesToQuestionTrafo( + $environment->getRefinery() + ) + )->withValue( + $this->buildBasicPropertiesBasicValuesArray() + ) + ]; + + if ($this->configuration_repository->isCreateModeChangeableByUser()) { + $inputs['create_mode'] = $this->configuration_repository->getInputForCreateMode( + $ff, + $environment->getLanguage(), + $environment->getRefinery() + )->withAdditionalTransformation( + $environment->getRefinery()->custom()->transformation( + fn(string $v): CreateModes => CreateModes::tryFrom($v) + ?? $this->configuration_repository->getGlobalCreateMode() + ) + ); + } + + return $environment->getPresentationFactory()->getEditForm( + $ff->section( + $inputs, + $environment->getLanguage()->txt('edit_basic_question_properties') + ), + $environment + ->withSubActionParameter(self::CMD_SAVE_QUESTION) + ->getUrlBuilder(), + null + ); + } + + private function processBasicPropertiesCreateForm( + DefaultEnvironment $environment + ): EditForm|Question { + $form = $this->buildBasicPropertiesCreateForm( + $environment + )->withRequest($environment->getHttpServices()->request()); + + $data = $form->getData(); + return $data === null + ? $form + : $data['question']->withCreateMode($data['create_mode']); + } + + private function buildBasicPropertiesEditingForm( + DefaultEnvironment $environment + ): EditForm { + return $environment->getPresentationFactory()->getEditForm( + $environment->getUIFactory()->input()->field()->section( + $this->buildBasicPropertiesInputs( + $environment->getUIFactory()->input()->field(), + $environment->getLanguage() + ), + $environment->getLanguage()->txt('edit_basic_question_properties') + )->withAdditionalTransformation( + $this->buildAddBasicPropertiesToQuestionTrafo( + $environment->getRefinery() + ) + )->withValue( + $this->buildBasicPropertiesBasicValuesArray() + ), + $environment + ->withSubActionParameter(self::CMD_SAVE_QUESTION) + ->getUrlBuilder(), + null + )->withIsFinalStep(true); + } + + private function processBasicPropertiesEditingForm( + DefaultEnvironment $environment + ): EditForm|Question { + $form = $this->buildBasicPropertiesEditingForm( + $environment + )->withRequest($environment->getHttpServices()->request()); + + $data = $form->getData(); + return $data === null + ? $form + : $data; + } + + private function buildBasicPropertiesInputs( + FieldFactory $field_factory, + Language $lng + ): array { + return [ + 'title' => $field_factory->text($lng->txt('title')) + ->withRequired(true), + 'author' => $field_factory->text($lng->txt('author')), + 'lifecycle' => $field_factory->select( + $lng->txt('qst_lifecycle'), + array_reduce( + Lifecycle::cases(), + function (array $c, Lifecycle $v) use ($lng): array { + $c[$v->value] = $lng->txt("qst_lifecycle_{$v->value}"); + return $c; + }, + [] + ) + )->withRequired(true), + 'remarks' => $field_factory->textarea($lng->txt('qst_remarks')) + ]; + } + + private function buildBasicPropertiesBasicValuesArray(): array + { + return [ + 'title' => $this->question->getTitle(), + 'author' => $this->question->getAuthor() !== '' + ? $this->question->getAuthor() + : $this->current_user->getFullname(), + 'lifecycle' => $this->question->getLifecycle()->value, + 'remarks' => $this->question->getRemarks() + ]; + } + + private function buildAddBasicPropertiesToQuestionTrafo( + Refinery $refinery + ): Transformation { + return $refinery->custom()->transformation( + function (array $vs): Question { + $question = $this->question + ->withTitle($vs['title']) + ->withAuthor($vs['author']) + ->withRemarks($vs['remarks']); + + $lifecycle = Lifecycle::tryFrom($vs['lifecycle']); + if ($lifecycle !== null) { + return $question->withLifecycle($lifecycle); + } + + return $question; + } + ); + } + + private function buildPreviewPanel( + DefaultEnvironment $environment + ): StandardPanel { + $environment->preserveParametersForPageEditorCmds(); + + $session_key = $this->buildPreviewSessionKey(); + + $attempt_data = $this->attempt_repository->getAttemptFromPreviewData( + $this->question, + \ilSession::get($session_key) ?? '' + ); + + if (!\ilSession::has($session_key)) { + \ilSession::set( + $session_key, + $attempt_data->toPreviewStorage() + ); + } + + return $environment->getUIFactory()->panel()->standard( + $environment->getLanguage()->txt('preview'), + [ + $environment->getUIFactory()->button()->primary( + $environment->getLanguage()->txt('edit'), + $this->ctrl->getLinkTargetByClass(\QstsQuestionPageGUI::class, 'edit') + ), + $environment->getUIFactory()->button()->standard( + $environment->getLanguage()->txt('reset_preview'), + $environment + ->withSubActionParameter(self::CMD_RESET_PREVIEW) + ->getUrlBuilder() + ->buildURI() + ->__toString() + ), + ...$this->buildPreviewPanelQuestionContent( + $environment, + $attempt_data + ) + ] + ); + } + + private function buildPreviewPanelQuestionContent( + DefaultEnvironment $environment, + Attempt $attempt_data + ): array { + $content = [ + $environment->getUIFactory()->legacy()->content( + $this->buildQuestionForm( + $environment, + $attempt_data + ) + ) + ]; + + if ($attempt_data->getResponseForQuestion($this->question->getId()) !== null) { + $content[] = $environment->getUIFactory()->divider()->horizontal(); + $content[] = $environment->getUIFactory()->panel()->standard( + $environment->getLanguage()->txt('feedback'), + $this->question->getParticipantView( + $environment->getLanguage(), + $environment->getRefinery(), + $environment->getUIFactory(), + $this->required_capabilities, + $attempt_data, + false, + true, + true, + true + )->getUI() + ); + } + + return $content; + } + + private function buildQuestionForm( + DefaultEnvironment $environment, + Attempt $attempt_data + ): string { + $tpl = new \ilTemplate( + 'tpl.qsts_preview_presentation_interactive.html', + true, + true, + 'components/ILIAS/Questions' + ); + + $tpl->setVariable( + self::TEMPLATE_VARIABLE_FORM_ACTION, + $environment + ->withSubActionParameter(self::CMD_SEND_RESPONSE) + ->getUrlBuilder() + ->buildURI() + ->__toString() + ); + + $tpl->setVariable( + self::TEMPLATE_VARIABLE_QUESTION_OUTPUT, + $this->ui_renderer->render( + $this->question->getParticipantView( + $environment->getLanguage(), + $environment->getRefinery(), + $environment->getUIFactory(), + $this->required_capabilities, + $attempt_data + )->getUI() + ) + ); + + $tpl->setVariable( + self::TEMPLATE_VARIABLE_SUBMIT_BUTTON_LABEL, + $environment->getLanguage()->txt('send') + ); + + return $tpl->get(); + } + + private function sendResponse( + DefaultEnvironment $environment + ) { + $session_key = $this->buildPreviewSessionKey(); + $attempt = $this->attempt_repository->getAttemptFromPreviewData( + $this->question, + \ilSession::get($session_key) ?? '' + ); + + \ilSession::set( + $session_key, + $attempt->withResponse( + $this->retrieveResponse() + )->toPreviewStorage() + ); + + return $this->buildBasicPropertiesEditingForm($environment) + ->withContentAfterForm( + $this->buildPreviewPanel($environment) + ); + } + + private function resetPreview( + DefaultEnvironment $environment + ): EditForm { + \ilSession::clear($this->buildPreviewSessionKey()); + return $this->buildBasicPropertiesEditingForm($environment) + ->withContentAfterForm( + $this->buildPreviewPanel($environment) + ); + } + + private function retrieveResponse(): Response + { + $response = $this->attempt_repository->getNewResponseFor( + $this->question->getId(), + $this->uuid_factory->uuid4() + ); + + $response_with_data = array_reduce( + $this->question->retrieveAnswerFormResponsesFromPost( + $this->required_capabilities, + $this->post_wrapper, + $response->getId() + ), + fn(Response $c, AnswerFormResponse $v): Response + => $c->withAnswerFormResponse($v), + $response + ); + + if ($this->required_capabilities->isMarkingRequired()) { + return $this->question->addAwardedPointsToResponse($response_with_data); + } + + return $response_with_data; + } + + private function buildPreviewSessionKey(): string + { + return self::SESSION_VAR_RESPONSE_DATA + . "_{$this->question->getId()->toString()}"; + } +} diff --git a/components/ILIAS/Questions/src/Question/Views/Participant.php b/components/ILIAS/Questions/src/Question/Views/Participant.php new file mode 100644 index 000000000000..40385e1e1f26 --- /dev/null +++ b/components/ILIAS/Questions/src/Question/Views/Participant.php @@ -0,0 +1,82 @@ +attempt_data?->getId(); + } + + #[\Override] + public function getUI(): array + { + $question_page = new \QstsQuestionPageGUI( + $this->question, + $this->question->getParentObjId(), + $this->required_capabilities, + $this->view_configuration + )->withAttemptData($this->attempt_data); + $question_page->setPresentationTitle($this->question->getTitle()); + + + if ($this->view_configuration->showMarks()) { + $content[] = $this->ui_factory->listing()->characteristicValue()->text( + [ + $this->lng->txt('awarded_points') => $this->refinery->kindlyTo()->string()->transform( + $this->attempt_data?->getResponseForQuestion( + $this->question->getId() + )?->getAwardedPoints() ?? 0 + ) + ] + ); + $content[] = $this->ui_factory->divider()->horizontal(); + } + + $content[] = $this->ui_factory->legacy()->content( + $question_page->presentation() + ); + + return $content; + } +} diff --git a/components/ILIAS/Questions/src/Setup/Agent.php b/components/ILIAS/Questions/src/Setup/Agent.php new file mode 100644 index 000000000000..266e1c1071f1 --- /dev/null +++ b/components/ILIAS/Questions/src/Setup/Agent.php @@ -0,0 +1,166 @@ + $answer_form_migrations + * @param array<\ILIAS\Questions\AnswerForm\Capabilities\Migration> $capability_migrations + */ + public function __construct( + private readonly PersistenceFactory $persistence_factory, + private readonly TableNameBuilder $question_table_names_builder, + private readonly array $answer_form_migrations, + private readonly array $capability_migrations + ) { + } + + public function hasConfig(): bool + { + return false; + } + + public function getArrayToConfigTransformation(): Transformation + { + throw new LogicException(self::class . ' has no Config.'); + } + + #[\Override] + public function getUpdateObjective( + ?Config $config = null + ): Objective { + return new ObjectiveCollection( + 'Database is updated for ILIAS\Questions', + false, + new \ilDatabaseUpdateStepsExecutedObjective( + new OverarchingQuestionTables( + $this->question_table_names_builder + ) + ), + new \ilDatabaseUpdateStepsExecutedObjective( + new ClozeQuestionTables( + new SetupTableNameBuilder( + new TableSubNameSpace( + 'ILIAS', + 'cloze' + ) + ), + new ClozeTableDefinitions( + $this->persistence_factory, + new TableSubNameSpace( + 'ILIAS', + 'cloze' + ) + ) + ) + ), + new \ilDatabaseUpdateStepsExecutedObjective( + new TempTables() + ), + new \ilTreeAdminNodeAddedObjective( + 'qsts', + 'Questions' + ) + ); + } + + #[\Override] + public function getStatusObjective( + Storage $storage + ): Objective { + return new ObjectiveCollection( + 'ILIAS\Questions', + true, + new \ilDatabaseUpdateStepsMetricsCollectedObjective( + $storage, + new OverarchingQuestionTables( + $this->question_table_names_builder + ) + ), + new \ilDatabaseUpdateStepsMetricsCollectedObjective( + $storage, + new ClozeQuestionTables( + new SetupTableNameBuilder( + new TableSubNameSpaceCore('cloze') + ) + ) + ) + ); + } + + #[\Override] + public function getMigrations(): array + { + return [ + new QuestionsMigration( + $this->persistence_factory, + $this->question_table_names_builder, + new QuestionTableDefinitions( + $this->persistence_factory + ), + new AnswerFormGenericTableDefinitions( + $this->persistence_factory + ), + $this->answer_form_migrations, + $this->capability_migrations + ) + ]; + } + + #[\Override] + public function getBuildObjective(): Objective + { + return new NullObjective(); + } + + #[\Override] + public function getInstallObjective( + ?Config $config = null + ): Objective { + return new NullObjective(); + } + + + #[\Override] + public function getNamedObjectives( + ?Config $config = null + ): array { + return new NullObjective(); + } +} diff --git a/components/ILIAS/Questions/src/Setup/ClozeQuestionTables.php b/components/ILIAS/Questions/src/Setup/ClozeQuestionTables.php new file mode 100644 index 000000000000..ab3f17b0db7c --- /dev/null +++ b/components/ILIAS/Questions/src/Setup/ClozeQuestionTables.php @@ -0,0 +1,300 @@ +db = $db; + } + + public function step_1(): void + { + $table_name = $this->table_names_builder->getTableNameFor( + AnswerFormSpecificTableTypes::TypeSpecificAnswerForms + ); + if (!$this->db->tableExists($table_name)) { + $this->db->createTable($table_name, [ + 'answer_form_id' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => true + ], + 'scoring_identical_responses' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 32, + 'notnull' => true + ], + 'combinations_enabled' => [ + 'type' => FieldDefinition::T_INTEGER, + 'length' => 1, + 'notnull' => true + ] + ]); + } + + if (!$this->db->primaryExistsByFields($table_name, ['answer_form_id'])) { + $this->db->addPrimaryKey($table_name, ['answer_form_id']); + } + } + + public function step_2(): void + { + $table_name = $this->table_names_builder->getTableNameFor( + AnswerFormSpecificTableTypes::AnswerInputs + ); + if (!$this->db->tableExists($table_name)) { + $this->db->createTable($table_name, [ + 'id' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => true + ], + 'answer_form_id' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => true + ], + 'position' => [ + 'type' => FieldDefinition::T_INTEGER, + 'length' => 2, + 'notnull' => true + ], + 'gap_type' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 32, + 'notnull' => true + ], + 'max_chars' => [ + 'type' => FieldDefinition::T_INTEGER, + 'length' => 2, + 'notnull' => false + ], + 'step_size' => [ + 'type' => FieldDefinition::T_FLOAT, + 'notnull' => false + ], + 'text_matching_method' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 32, + 'notnull' => false + ], + 'min_autocomplete' => [ + 'type' => FieldDefinition::T_INTEGER, + 'length' => 2, + 'notnull' => false + ], + 'shuffle_answer_options' => [ + 'type' => FieldDefinition::T_INTEGER, + 'length' => 1, + 'notnull' => false + ] + ]); + } + + if (!$this->db->primaryExistsByFields($table_name, ['id'])) { + $this->db->addPrimaryKey($table_name, ['id']); + } + + if (!$this->db->indexExistsByFields($table_name, ['answer_form_id'])) { + $this->db->addIndex($table_name, ['answer_form_id'], 'af'); + } + } + + public function step_3(): void + { + $table_name = $this->table_names_builder->getTableNameFor( + AnswerFormSpecificTableTypes::AnswerOptions + ); + if (!$this->db->tableExists($table_name)) { + $this->db->createTable($table_name, [ + 'id' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => true + ], + 'answer_input_id' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => true + ], + 'position' => [ + 'type' => FieldDefinition::T_INTEGER, + 'length' => 2, + 'notnull' => true + ], + 'text_value' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 4000, + 'notnull' => false + ], + 'lower_limit' => [ + 'type' => FieldDefinition::T_FLOAT, + 'notnull' => false + ], + 'upper_limit' => [ + 'type' => FieldDefinition::T_FLOAT, + 'notnull' => false + ], + 'points' => [ + 'type' => FieldDefinition::T_FLOAT, + 'notnull' => false + ] + ]); + } + + if (!$this->db->primaryExistsByFields($table_name, ['id'])) { + $this->db->addPrimaryKey($table_name, ['id']); + } + + if (!$this->db->indexExistsByFields($table_name, ['answer_input_id'])) { + $this->db->addIndex($table_name, ['answer_input_id'], 'ai'); + } + } + + public function step_4(): void + { + $table_name = $this->table_names_builder->getTableNameFor( + AnswerFormSpecificTableTypes::Additional, + $this->table_definitions->getCombinationsTableIdentifier() + ); + if (!$this->db->tableExists($table_name)) { + $this->db->createTable($table_name, [ + 'id' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => true + ], + 'answer_form_id' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => true + ], + 'points' => [ + 'type' => FieldDefinition::T_FLOAT, + 'notnull' => false + ] + ]); + } + + if (!$this->db->primaryExistsByFields($table_name, ['id'])) { + $this->db->addPrimaryKey($table_name, ['id']); + } + + if (!$this->db->indexExistsByFields($table_name, ['answer_form_id'])) { + $this->db->addIndex($table_name, ['answer_form_id'], 'ai'); + } + } + + public function step_5(): void + { + $table_name = $this->table_names_builder->getTableNameFor( + AnswerFormSpecificTableTypes::Additional, + $this->table_definitions->getCombinationToAnswerOptionsTableIdentifier() + ); + if (!$this->db->tableExists($table_name)) { + $this->db->createTable($table_name, [ + 'combination_id' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => true + ], + 'gap_id' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => true + ], + 'answer_option_id' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => true + ], + 'in_range' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 16, + 'notnull' => false + ] + ]); + } + + if (!$this->db->primaryExistsByFields($table_name, ['combination_id', 'gap_id'])) { + $this->db->addPrimaryKey($table_name, ['combination_id', 'gap_id']); + } + + if (!$this->db->indexExistsByFields($table_name, ['combination_id'])) { + $this->db->addIndex($table_name, ['combination_id'], 'ci'); + } + } + + public function step_6(): void + { + $table_name = $this->table_names_builder->getTableNameFor( + AnswerFormSpecificTableTypes::Responses + ); + if (!$this->db->tableExists($table_name)) { + $this->db->createTable($table_name, [ + 'response_id' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => true + ], + 'answer_input_id' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => true + ], + 'selected_answer_option' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => false + ], + 'text' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 4000, + 'notnull' => true + ] + ]); + } + + if (!$this->db->primaryExistsByFields($table_name, ['response_id', 'answer_input_id'])) { + $this->db->addPrimaryKey($table_name, ['response_id', 'answer_input_id']); + } + + if (!$this->db->indexExistsByFields($table_name, ['response_id'])) { + $this->db->addIndex($table_name, ['response_id'], 'r'); + } + } +} diff --git a/components/ILIAS/Questions/src/Setup/OverarchingQuestionTables.php b/components/ILIAS/Questions/src/Setup/OverarchingQuestionTables.php new file mode 100644 index 000000000000..82ce74a091e2 --- /dev/null +++ b/components/ILIAS/Questions/src/Setup/OverarchingQuestionTables.php @@ -0,0 +1,465 @@ +db = $db; + } + + public function step_1(): void + { + $table_name = $this->basic_table_names_builder->getTableNameFor( + QuestionTableTypes::Questions + ); + if (!$this->db->tableExists($table_name)) { + $this->db->createTable($table_name, [ + 'id' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => true + ], + 'page_id' => [ + 'type' => FieldDefinition::T_INTEGER, + 'length' => 4, + 'notnull' => true + ], + 'title' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 512, + 'notnull' => true + ], + 'author' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 512, + 'notnull' => false + ], + 'lifecycle' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 16, + 'notnull' => true + ], + 'remarks' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 4000, + 'notnull' => false + ], + 'original_id' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => false + ], + 'last_update' => [ + 'type' => FieldDefinition::T_INTEGER, + 'length' => 8, + 'notnull' => true + ], + 'created' => [ + 'type' => FieldDefinition::T_INTEGER, + 'length' => 8, + 'notnull' => true + ], + ]); + } + + if (!$this->db->primaryExistsByFields($table_name, ['id'])) { + $this->db->addPrimaryKey($table_name, ['id']); + } + } + + public function step_2(): void + { + $table_name = $this->basic_table_names_builder->getTableNameFor( + AnswerFormGenericTableTypes::AnswerForms + ); + if (!$this->db->tableExists($table_name)) { + $this->db->createTable($table_name, [ + 'id' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => true + ], + 'type' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 4000, + 'notnull' => true + ], + 'question_id' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => true + ], + 'available_points' => [ + 'type' => FieldDefinition::T_FLOAT, + 'notnull' => false + ], + 'image_size' => [ + 'type' => FieldDefinition::T_INTEGER, + 'length' => 2, + 'notnull' => false + ], + 'shuffle_answer_options' => [ + 'type' => FieldDefinition::T_INTEGER, + 'length' => 1, + 'notnull' => false + ], + 'additional_text' => [ + 'type' => FieldDefinition::T_CLOB, + 'notnull' => true + ], + 'additional_text_legacy' => [ + 'type' => FieldDefinition::T_CLOB, + 'notnull' => true + ] + ]); + } + + if (!$this->db->primaryExistsByFields($table_name, ['id'])) { + $this->db->addPrimaryKey($table_name, ['id']); + } + + if (!$this->db->indexExistsByFields($table_name, ['question_id'])) { + $this->db->addIndex($table_name, ['question_id'], 'q'); + } + } + + public function step_3(): void + { + $table_name = $this->basic_table_names_builder->getTableNameFor( + QuestionTableTypes::Responses + ); + if (!$this->db->tableExists($table_name)) { + $this->db->createTable($table_name, [ + 'id' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => true + ], + 'attempt_id' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => true + ], + 'question_id' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => true + ], + 'create_timestamp' => [ + 'type' => FieldDefinition::T_INTEGER, + 'length' => 8, + 'notnull' => true + ], + 'awarded_points' => [ + 'type' => FieldDefinition::T_FLOAT, + 'notnull' => false + ] + ]); + } + + if (!$this->db->primaryExistsByFields($table_name, ['id'])) { + $this->db->addPrimaryKey($table_name, ['id']); + } + + if (!$this->db->indexExistsByFields($table_name, ['question_id'])) { + $this->db->addIndex($table_name, ['question_id'], 'q'); + } + } + + public function step_4(): void + { + $table_name = $this->basic_table_names_builder->getTableNameFor( + QuestionTableTypes::Linking + ); + if (!$this->db->tableExists($table_name)) { + $this->db->createTable($table_name, [ + 'question_id' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => true + ], + 'obj_id' => [ + 'type' => FieldDefinition::T_INTEGER, + 'length' => 4, + 'notnull' => true + ], + 'position' => [ + 'type' => FieldDefinition::T_INTEGER, + 'length' => 2, + 'notnull' => false + ] + ]); + } + + if (!$this->db->primaryExistsByFields($table_name, ['question_id'])) { + $this->db->addPrimaryKey($table_name, ['question_id']); + } + + if (!$this->db->indexExistsByFields($table_name, ['obj_id'])) { + $this->db->addIndex($table_name, ['obj_id'], 'o'); + } + } + + public function step_5(): void + { + $table_name = $this->basic_table_names_builder->getTableNameFor( + QuestionTableTypes::MigrationsTable + ); + if (!$this->db->tableExists($table_name)) { + $this->db->createTable($table_name, [ + 'new_question_id' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => true + ], + 'old_question_id' => [ + 'type' => FieldDefinition::T_INTEGER, + 'length' => 4, + 'notnull' => false + ], + 'success' => [ + 'type' => FieldDefinition::T_INTEGER, + 'length' => 1, + 'notnull' => true + ] + ]); + } + + if (!$this->db->primaryExistsByFields( + $table_name, + ['old_question_id'] + )) { + $this->db->addPrimaryKey( + $table_name, + ['old_question_id'] + ); + } + } + + public function step_6(): void + { + $table_name = $this->basic_table_names_builder->getTableNameFor( + TextFeedbackTableTypes::FeedbackGeneric + ); + if (!$this->db->tableExists($table_name)) { + $this->db->createTable($table_name, [ + 'answer_form_id' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => true + ], + 'feedback_best_response' => [ + 'type' => FieldDefinition::T_CLOB, + 'notnull' => true + ], + 'feedback_best_response_legacy' => [ + 'type' => FieldDefinition::T_CLOB, + 'notnull' => true + ], + 'feedback_other_response' => [ + 'type' => FieldDefinition::T_CLOB, + 'notnull' => true + ], + 'feedback_other_response_legacy' => [ + 'type' => FieldDefinition::T_CLOB, + 'notnull' => true + ] + ]); + } + + if (!$this->db->primaryExistsByFields( + $table_name, + ['answer_form_id'] + )) { + $this->db->addPrimaryKey( + $table_name, + ['answer_form_id'] + ); + } + } + + public function step_7(): void + { + $table_name = $this->basic_table_names_builder->getTableNameFor( + TextFeedbackTableTypes::FeedbackSpecific + ); + if (!$this->db->tableExists($table_name)) { + $this->db->createTable($table_name, [ + 'id' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => true + ], + 'answer_form_id' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => true + ], + 'parent_id' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => true + ], + 'condition' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => false + ], + 'feedback' => [ + 'type' => FieldDefinition::T_CLOB, + 'notnull' => true + ], + 'feedback_legacy' => [ + 'type' => FieldDefinition::T_CLOB, + 'notnull' => true + ] + ]); + } + + if (!$this->db->primaryExistsByFields( + $table_name, + ['id'] + )) { + $this->db->addPrimaryKey( + $table_name, + ['id'], + ); + } + + $this->db->manipulate( + "CREATE UNIQUE INDEX IF NOT EXISTS apc_idx ON {$table_name}" . PHP_EOL + . '(`answer_form_id`, `parent_id`, `condition`)' + ); + } + + public function step_8(): void + { + $table_name = $this->basic_table_names_builder->getTableNameFor( + SuggestedContentTableTypes::SuggestedLearningContent + ); + if (!$this->db->tableExists($table_name)) { + $this->db->createTable($table_name, [ + 'answer_form_id' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => true + ], + 'type' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 32, + 'notnull' => false + ], + 'content' => [ + 'type' => FieldDefinition::T_CLOB, + 'notnull' => true + ] + ]); + } + + if (!$this->db->primaryExistsByFields( + $table_name, + ['answer_form_id'] + )) { + $this->db->addPrimaryKey( + $table_name, + ['answer_form_id'], + ); + } + } + + public function step_9(): void + { + $table_name = $this->basic_table_names_builder->getTableNameFor( + AttemptTableTypes::AttemptData + ); + if (!$this->db->tableExists($table_name)) { + $this->db->createTable($table_name, [ + 'id' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => true + ], + 'shuffler_seed' => [ + 'type' => FieldDefinition::T_INTEGER, + 'length' => 8, + 'notnull' => true + ], + ]); + } + + if (!$this->db->primaryExistsByFields($table_name, ['id'])) { + $this->db->addPrimaryKey($table_name, ['id']); + } + } + + public function step_10(): void + { + $table_name = $this->basic_table_names_builder->getTableNameFor( + AttemptTableTypes::AdditionalAttemptData + ); + if (!$this->db->tableExists($table_name)) { + $this->db->createTable($table_name, [ + 'attempt_id' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => true + ], + 'parent_id' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => true + ], + 'data' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 4000, + 'notnull' => true + ], + ]); + } + + if (!$this->db->primaryExistsByFields($table_name, ['attempt_id', 'parent_id'])) { + $this->db->addPrimaryKey($table_name, ['attempt_id', 'parent_id']); + } + + if (!$this->db->indexExistsByFields($table_name, ['attempt_id'])) { + $this->db->addIndex($table_name, ['attempt_id'], 'aid'); + } + } +} diff --git a/components/ILIAS/Questions/src/Setup/QuestionsMigration.php b/components/ILIAS/Questions/src/Setup/QuestionsMigration.php new file mode 100644 index 000000000000..afc5b0741b8a --- /dev/null +++ b/components/ILIAS/Questions/src/Setup/QuestionsMigration.php @@ -0,0 +1,460 @@ + $answer_form_migrations + * @param array<\ILIAS\Questions\AnswerForm\Capabilities\Migration> $capability_migrations + */ + public function __construct( + private readonly PersistenceFactory $persistence_factory, + private readonly TableNameBuilder $question_table_names_builder, + private readonly QuestionTableDefinitions $question_table_definitions, + private readonly AnswerFormGenericTableDefinitions $answer_form_generic_table_definitions, + array $answer_form_migrations, + private readonly array $capability_migrations + ) { + $this->answer_form_migrations = array_reduce( + $answer_form_migrations, + function (array $c, AnswerFormMigration $v): array { + $c[$v->getOldQuestionTypeIdentifier()] = $v; + return $c; + }, + [] + ); + } + + #[\Override] + public function getLabel(): string + { + return 'Migrate questions to Questions Component.'; + } + + #[\Override] + public function getDefaultAmountOfStepsPerRun(): int + { + return 100; + } + + #[\Override] + public function getPreconditions(Environment $environment): array + { + return [new \ilDatabaseInitializedObjective()]; + } + + #[\Override] + public function prepare(Environment $environment): void + { + $this->db = $environment->getResource(Setup\Environment::RESOURCE_DATABASE); + $this->io = $environment->getResource(Setup\Environment::RESOURCE_ADMIN_INTERACTION); + $this->uuid_factory = new UuidFactory(); + } + + #[\Override] + public function step( + Environment $environment + ): void { + $db_values = $this->fetchValidRecord(); + + if ($db_values === null) { + return; + } + + if ($db_values->obj_fi === 0) { + $db_values->obj_fi = $this->getObjIdFromLearningModulMapping($db_values->question_id); + } + + if ($db_values->obj_fi === null) { + $this->io->error( + "The question with the id {$db_values->question_id} could not be " + . "migrated as it doesn't belong to any object." + ); + return; + } + + /** @var \ILIAS\Questions\AnswerForm\Migration\Migration $answer_form_migration */ + $answer_form_migration = $this->answer_form_migrations[$db_values->type_tag]; + + $new_question_id = $this->uuid_factory->uuid4(); + + $migration_insert = $answer_form_migration->completeMigrationInsert( + $environment, + $this->buildMigrationInsert( + $answer_form_migration, + [ + $this->buildInsertLinkingStatement( + $new_question_id, + $db_values->obj_fi, + $db_values->sequence + ), + $this->buildInsertQuestionStatement( + $new_question_id, + $db_values->title, + $db_values->author, + Lifecycle::tryFrom($db_values->lifecycle), + $db_values->description, + $db_values->original_id, + $db_values->created + ), + $this->buildInsertMigrationStatement( + $db_values->question_id, + $new_question_id + ) + ], + $new_question_id, + $db_values + ) + ); + + if ($migration_insert === null) { + $this->db->manipulate( + $this->buildInsertMigrationStatement( + $db_values->question_id, + null + )->toManipulateString($this->db) + ); + $this->io->inform( + "{$db_values->question_id} could not be migrated due to missing question data." + ); + return; + } + + array_reduce( + $this->capability_migrations, + fn(AnswerFormMigrationInsert $c, CapabilityMigration $v): AnswerFormMigrationInsert + => $v->completeMigrationInsert( + $environment, + $answer_form_migration, + $c + ), + $migration_insert + )->run(); + $this->io->inform("{$new_question_id->toString()} successfully migrated."); + } + + #[\Override] + public function getRemainingAmountOfSteps(): int + { + $migration_table_name = $this->question_table_names_builder + ->getTableNameFor(QuestionTableTypes::MigrationsTable); + + $query = $this->db->query( + 'SELECT COUNT(question_id) cnt FROM ' . self::OLD_QUESTIONS_TABLE . ' q' . PHP_EOL + . 'JOIN ' . self::OLD_QUESTION_TYPE_TABLE . ' t ON q.question_type_fi = t.question_type_id' . PHP_EOL + . "LEFT JOIN {$migration_table_name}" + . ' m ON q.question_id = m.old_question_id' . PHP_EOL + . 'WHERE t.type_tag IN (' + . implode( + ', ', + array_map( + fn(AnswerFormMigration $v): string => "'{$v->getOldQuestionTypeIdentifier()}'", + $this->answer_form_migrations + ) + ) . ')' . PHP_EOL + . 'AND q.complete = 1' . PHP_EOL + . 'AND m.old_question_id IS NULL' + ); + return $this->db->fetchObject( + $query + )->cnt; + } + + private function fetchValidRecord(): ?\stdClass + { + $migration_table_name = $this->question_table_names_builder + ->getTableNameFor(QuestionTableTypes::MigrationsTable); + + $query = $this->db->query( + 'SELECT q.*, t.type_tag, s.sequence FROM ' . self::OLD_QUESTIONS_TABLE . ' q' . PHP_EOL + . 'JOIN ' . self::OLD_QUESTION_TYPE_TABLE . ' t ON q.question_type_fi = t.question_type_id' . PHP_EOL + . "LEFT JOIN {$migration_table_name}" + . ' m ON q.question_id = m.old_question_id' . PHP_EOL + . 'LEFT JOIN ' . self::TEST_QUESTIONS_SEQUENCE_TABLE . ' s ON q.question_id = s.question_fi' . PHP_EOL + . 'WHERE t.type_tag IN (' + . implode( + ', ', + array_map( + fn(AnswerFormMigration $v): string => "'{$v->getOldQuestionTypeIdentifier()}'", + $this->answer_form_migrations + ) + ) . ')' . PHP_EOL + . 'AND q.complete = 1' . PHP_EOL + . 'AND m.old_question_id IS NULL' + ); + + do { + $db_values = $this->db->fetchObject($query); + if ($db_values === null) { + return null; + } + } while (!$this->areDbValuesValid($db_values)); + + $db_values->original_id = $this->cleanupAndMigrateOriginalId($db_values->original_id); + return $db_values; + } + + private function areDbValuesValid( + \stdClass $db_values + ): bool { + if ($db_values->original_id === null) { + return true; + } + + if ($this->allready_migrated_questions === null) { + $this->loadAlreadyMigratedQuestions(); + } + + if (isset($this->allready_migrated_questions[$db_values->original_id])) { + return true; + } + + return false; + } + + private function cleanupAndMigrateOriginalId( + ?int $original_id + ): ?Uuid { + if ($original_id === null + || in_array($original_id, $this->allready_migrated_questions_in_qpls)) { + return null; + } + return $this->uuid_factory->fromString( + $this->allready_migrated_questions[$original_id] + ); + } + + private function loadAlreadyMigratedQuestions(): void + { + $migration_table_name = $this->question_table_names_builder + ->getTableNameFor(QuestionTableTypes::MigrationsTable); + $linking_table_name = $this->question_table_names_builder + ->getTableNameFor(QuestionTableTypes::Linking); + + $query = $this->db->query( + "SELECT m.*, o.type FROM {$migration_table_name} m" . PHP_EOL + . "JOIN {$linking_table_name} l" . PHP_EOL + . 'ON m.new_question_id = l.question_id' . PHP_EOL + . 'JOIN object_data o ON l.obj_id = o.obj_id' . PHP_EOL + ); + + $this->allready_migrated_questions = []; + $this->allready_migrated_questions_in_qpls = []; + while (($row = $this->db->fetchObject($query)) !== null) { + $this->allready_migrated_questions[$row->old_question_id] = $row->new_question_id; + if ($row->type === 'qpl') { + $this->allready_migrated_questions_in_qpls[] = $row->new_question_id; + } + } + } + + private function getObjIdFromLearningModulMapping( + int $question_id + ): ?int { + if ($this->question_to_learning_module_mapping === null) { + $this->loadQuestionsToLearningModuleMapping(); + } + + return $this->question_to_learning_module_mapping[$question_id] ?? null; + } + + private function loadQuestionsToLearningModuleMapping(): void + { + + $query = $this->db->query( + 'SELECT question_id, obj_id FROM page_question pq' . PHP_EOL + . 'JOIN page_object po ON pq.page_id = po.page_id' . PHP_EOL + . 'AND pq.page_parent_type = po.parent_type' . PHP_EOL + . 'JOIN object_data o ON po.parent_id = o.obj_id' . PHP_EOL + . 'WHERE page_parent_type = "lm"' + ); + + $this->question_to_learning_module_mapping = []; + while (($row = $this->db->fetchObject($query)) !== null) { + $this->question_to_learning_module_mapping[$row->question_id] = $row->obj_id; + } + } + + private function buildInsertLinkingStatement( + Uuid $new_question_id, + int $obj_id, + ?int $position + ): Insert { + return $this->persistence_factory->insert( + $this->question_table_definitions->getColumns( + $this->question_table_names_builder, + QuestionTableTypes::Linking + ), + [ + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $new_question_id->toString() + ), + $this->persistence_factory->value( + FieldDefinition::T_INTEGER, + $obj_id + ), + $this->persistence_factory->value( + FieldDefinition::T_INTEGER, + $position + ) + ] + ); + } + + private function buildInsertQuestionStatement( + Uuid $id, + string $title, + string $author, + Lifecycle $lifecycle, + string $remarks, + ?Uuid $original_id, + int $create_date + ): Insert { + return $this->persistence_factory->insert( + $this->question_table_definitions->getColumns( + $this->question_table_names_builder, + QuestionTableTypes::Questions + ), + [ + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $id->toString() + ), + $this->persistence_factory->value( + FieldDefinition::T_INTEGER, + 0 + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $title + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $author + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $lifecycle->value + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $remarks + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $original_id?->toString() + ), + $this->persistence_factory->value( + FieldDefinition::T_INTEGER, + time() + ), + $this->persistence_factory->value( + FieldDefinition::T_INTEGER, + $create_date + ) + ] + ); + } + + private function buildInsertMigrationStatement( + int $old_question_id, + ?Uuid $new_question_id + ): Insert { + return $this->persistence_factory->insert( + $this->question_table_definitions->getColumns( + $this->question_table_names_builder, + QuestionTableTypes::MigrationsTable + ), + [ + $this->persistence_factory->value( + FieldDefinition::T_INTEGER, + $old_question_id + ), + $this->persistence_factory->value( + FieldDefinition::T_TEXT, + $new_question_id?->toString() + ), + $this->persistence_factory->value( + FieldDefinition::T_INTEGER, + $new_question_id === null + ? '0' + : '1' + ) + ] + ); + } + + private function buildMigrationInsert( + AnswerFormMigration $answer_form_migration, + array $question_inserts, + Uuid $new_question_id, + \stdClass $db_values + ): AnswerFormMigrationInsert { + return new AnswerFormMigrationInsert( + $this->db, + $this->io, + $this->uuid_factory, + $this->persistence_factory, + $this->answer_form_generic_table_definitions, + QuestionRepository::COMPONENT_NAMESPACE, + $question_inserts, + $db_values->question_id, + $new_question_id, + $this->uuid_factory->uuid4(), + $answer_form_migration->getDefinitionClass(), + $db_values->add_cont_edit_mode === \assQuestion::ADDITIONAL_CONTENT_EDITING_MODE_IPE + ); + } +} diff --git a/components/ILIAS/Questions/src/Setup/SetupTableNameBuilder.php b/components/ILIAS/Questions/src/Setup/SetupTableNameBuilder.php new file mode 100644 index 000000000000..e1d97883d442 --- /dev/null +++ b/components/ILIAS/Questions/src/Setup/SetupTableNameBuilder.php @@ -0,0 +1,35 @@ +db = $db; + } + + public function step_1(): void + { + $table_name = AttemptRepository::TABLE_NAME; + + if (!$this->db->tableExists($table_name)) { + $this->db->createTable($table_name, [ + 'user_id' => [ + 'type' => FieldDefinition::T_INTEGER, + 'length' => 4, + 'notnull' => true + ], + 'attempt_id' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 64, + 'notnull' => true + ], + 'solved_questions' => [ + 'type' => FieldDefinition::T_TEXT, + 'length' => 4000, + 'notnull' => true, + 'default' => '' + ] + ]); + } + + if (!$this->db->primaryExistsByFields($table_name, ['user_id'])) { + $this->db->addPrimaryKey($table_name, ['user_id']); + } + } +} diff --git a/components/ILIAS/Questions/src/UserSettings/CreateMode.php b/components/ILIAS/Questions/src/UserSettings/CreateMode.php new file mode 100644 index 000000000000..e44c5266b3b6 --- /dev/null +++ b/components/ILIAS/Questions/src/UserSettings/CreateMode.php @@ -0,0 +1,180 @@ +settings = new \ilSetting('questions'); + } + #[\Override] + public function getIdentifier(): string + { + return 'question_create_mode'; + } + + #[\Override] + public function isAvailable(): bool + { + return true; + } + + #[\Override] + public function getLabel(Language $lng): string + { + return $lng->txt('question_create_mode'); + } + + #[\Override] + public function getSettingsPage(): AvailablePages + { + return AvailablePages::MainSettings; + } + + #[\Override] + public function getSection(): AvailableSections + { + return AvailableSections::Additional; + } + + #[\Override] + public function getInput( + FieldFactory $field_factory, + Language $lng, + Refinery $refinery, + \ilSetting $settings, + ?\ilObjUser $user = null + ): Input { + $lng->loadLanguageModule('questions'); + return array_reduce( + CreateModes::cases(), + fn(Radio $c, CreateModes $v): Radio => $c->withOption( + $v->value, + $v->getLabelForInput($lng), + $v->getBylineForInput($lng) + ), + $field_factory->radio( + $lng->txt('question_create_mode') + ) + )->withValue( + $user !== null + ? $this->retrieveValueFromUser($user) + : $this->settings->get( + 'default_create_mode', + CreateModes::getDefaultMode()->value + ) + ); + } + + #[\Override] + public function getLegacyInput( + Language $lng, + \ilSetting $settings, + ?\ilObjUser $user = null + ): \ilFormPropertyGUI { + $lng->loadLanguageModule('questions'); + $input = array_reduce( + CreateModes::cases(), + function ( + \ilRadioGroupInputGUI $c, + CreateModes $v + ) use ($lng): \ilRadioGroupInputGUI { + $c->addOption( + new \ilRadioOption( + $v->getLabelForInput($lng), + $v->value, + $v->getBylineForInput($lng) + ) + ); + return $c; + }, + new \ilRadioGroupInputGUI($lng->txt('question_create_mode')) + ); + + $input->setValue( + $user !== null + ? $this->retrieveValueFromUser($user) + : $this->settings->get( + 'default_create_mode', + CreateModes::getDefaultMode()->value + ) + ); + return $input; + } + + #[\Override] + public function getDefaultValueForDisplay( + Language $lng, + \ilSetting $settings + ): string { + return CreateModes::getDefaultMode()->getLabelForInput($lng); + } + + #[\Override] + public function hasUserPersonalizedSetting( + \ilSetting $settings, + \ilObjUser $user + ): bool { + return $this->retrieveValueFromUser($user) + !== $this->settings->get( + 'default_create_mode', + CreateModes::getDefaultMode()->value + ); + } + + #[\Override] + public function persistUserInput( + \ilObjUser $user, + mixed $input + ): \ilObjUser { + $user->setPref( + 'question_create_mode', + $input !== null + ? $input + : $this->settings->get( + 'default_create_mode', + CreateModes::getDefaultMode()->value + ) + ); + return $user; + } + + #[\Override] + public function retrieveValueFromUser(\ilObjUser $user): string + { + return $user->getPref('question_create_mode') + ?? $this->settings->get( + 'default_create_mode', + CreateModes::getDefaultMode()->value + ); + } +} diff --git a/components/ILIAS/Questions/src/UserSettings/CreateModes.php b/components/ILIAS/Questions/src/UserSettings/CreateModes.php new file mode 100644 index 000000000000..ee6e8c97169f --- /dev/null +++ b/components/ILIAS/Questions/src/UserSettings/CreateModes.php @@ -0,0 +1,46 @@ +txt("create_mode_{$this->value}"); + } + + public function getBylineForInput( + Language $lng + ): string { + return $lng->txt("byline_create_mode_{$this->value}"); + } + + public static function getDefaultMode(): self + { + return self::Simple; + } +} diff --git a/components/ILIAS/Questions/src/UserSettings/Settings.php b/components/ILIAS/Questions/src/UserSettings/Settings.php new file mode 100644 index 000000000000..959f42df91b3 --- /dev/null +++ b/components/ILIAS/Questions/src/UserSettings/Settings.php @@ -0,0 +1,34 @@ + + + {ICON_OK} + \ No newline at end of file diff --git a/components/ILIAS/Questions/templates/default/tpl.cloze_gap_numeric.html b/components/ILIAS/Questions/templates/default/tpl.cloze_gap_numeric.html new file mode 100755 index 000000000000..26110f460c00 --- /dev/null +++ b/components/ILIAS/Questions/templates/default/tpl.cloze_gap_numeric.html @@ -0,0 +1,2 @@ + size="{TEXT_GAP_SIZE}" maxlength="{TEXT_GAP_SIZE}" /> + diff --git a/components/ILIAS/Questions/templates/default/tpl.cloze_gap_select.html b/components/ILIAS/Questions/templates/default/tpl.cloze_gap_select.html new file mode 100755 index 000000000000..fd86a5c57dfb --- /dev/null +++ b/components/ILIAS/Questions/templates/default/tpl.cloze_gap_select.html @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/components/ILIAS/Questions/templates/default/tpl.cloze_gap_static.html b/components/ILIAS/Questions/templates/default/tpl.cloze_gap_static.html new file mode 100755 index 000000000000..e5cf7503b46f --- /dev/null +++ b/components/ILIAS/Questions/templates/default/tpl.cloze_gap_static.html @@ -0,0 +1 @@ +{SOLUTION_VALUE} \ No newline at end of file diff --git a/components/ILIAS/Questions/templates/default/tpl.cloze_gap_text.html b/components/ILIAS/Questions/templates/default/tpl.cloze_gap_text.html new file mode 100755 index 000000000000..bf213ad6472d --- /dev/null +++ b/components/ILIAS/Questions/templates/default/tpl.cloze_gap_text.html @@ -0,0 +1 @@ + size="{TEXT_GAP_SIZE}" maxlength="{TEXT_GAP_SIZE}"/> \ No newline at end of file diff --git a/components/ILIAS/Questions/templates/default/tpl.qsts_async.html b/components/ILIAS/Questions/templates/default/tpl.qsts_async.html new file mode 100644 index 000000000000..2df34a01b580 --- /dev/null +++ b/components/ILIAS/Questions/templates/default/tpl.qsts_async.html @@ -0,0 +1,7 @@ +
+
+ {ANSWER_FORM} +
+ {CHECK_BUTTON} + +
\ No newline at end of file diff --git a/components/ILIAS/Questions/templates/default/tpl.qsts_preview_presentation_interactive.html b/components/ILIAS/Questions/templates/default/tpl.qsts_preview_presentation_interactive.html new file mode 100644 index 000000000000..96a915b096ec --- /dev/null +++ b/components/ILIAS/Questions/templates/default/tpl.qsts_preview_presentation_interactive.html @@ -0,0 +1,8 @@ +
+
+
+ {QUESTION_OUTPUT} +
+ +
+
\ No newline at end of file diff --git a/components/ILIAS/SurveyQuestionPool/classes/class.ilObjSurveyQuestionPoolGUI.php b/components/ILIAS/SurveyQuestionPool/classes/class.ilObjSurveyQuestionPoolGUI.php index 44c96721e67e..ba5836229de9 100755 --- a/components/ILIAS/SurveyQuestionPool/classes/class.ilObjSurveyQuestionPoolGUI.php +++ b/components/ILIAS/SurveyQuestionPool/classes/class.ilObjSurveyQuestionPoolGUI.php @@ -268,7 +268,7 @@ public function deleteQuestionsObject(): void } $cgui = new ilConfirmationGUI(); - $cgui->setHeaderText($this->lng->txt("qpl_confirm_delete_questions")); + $cgui->setHeaderText($this->lng->txt("confirm_delete_questions")); $cgui->setFormAction($this->ctrl->getFormAction($this)); $cgui->setCancel($this->lng->txt("cancel"), "cancelDeleteQuestions"); diff --git a/components/ILIAS/Test/classes/Notifications/class.ilTestManScoringParticipantNotification.php b/components/ILIAS/Test/classes/Notifications/class.ilTestManScoringParticipantNotification.php index 3e332f894529..78cce6992b8a 100755 --- a/components/ILIAS/Test/classes/Notifications/class.ilTestManScoringParticipantNotification.php +++ b/components/ILIAS/Test/classes/Notifications/class.ilTestManScoringParticipantNotification.php @@ -29,6 +29,7 @@ public function __construct($userId, $testRefId) $this->initLanguage($this->getRecipient()); $this->getLanguage()->loadLanguageModule('assessment'); + $this->getLanguage()->loadLanguageModule('qsts'); $this->initMail(); } diff --git a/components/ILIAS/Test/classes/Screen/class.ilTestPlayerLayoutProvider.php b/components/ILIAS/Test/classes/Screen/class.ilTestPlayerLayoutProvider.php index 55cc8b484af7..0b41e55cb29e 100755 --- a/components/ILIAS/Test/classes/Screen/class.ilTestPlayerLayoutProvider.php +++ b/components/ILIAS/Test/classes/Screen/class.ilTestPlayerLayoutProvider.php @@ -88,7 +88,7 @@ public function getMainBarModification(CalledContexts $called_contexts): ?MainBa $question_listing = $f->legacy()->content($r->render($question_listing)); - $label = $lng->txt('mainbar_button_label_questionlist'); + $label = $lng->txt('questionlist'); $entry = $f->maincontrols()->slate()->legacy( $label, $f->symbol()->icon()->standard('tst', $label), diff --git a/components/ILIAS/Test/classes/Screen/ilTestPlayerToolProvider.php b/components/ILIAS/Test/classes/Screen/ilTestPlayerToolProvider.php index a0cb5bf56989..f739551acbb6 100644 --- a/components/ILIAS/Test/classes/Screen/ilTestPlayerToolProvider.php +++ b/components/ILIAS/Test/classes/Screen/ilTestPlayerToolProvider.php @@ -48,7 +48,7 @@ public function getToolsForContextStack(CalledContexts $called_contexts): array $this->factory->tool( $this->identification_provider->contextAwareIdentifier('tst_qst_list') )->withSymbol($ui->factory()->symbol()->icon()->standard('tst', $lng->txt('more'))) - ->withTitle($lng->txt('mainbar_button_label_questionlist')) + ->withTitle($lng->txt('questionlist')) ->withContent( $ui->factory()->legacy()->content( $ui->renderer()->render( diff --git a/components/ILIAS/Test/classes/class.ilObjTest.php b/components/ILIAS/Test/classes/class.ilObjTest.php index e324175b584e..b075d81b97b6 100755 --- a/components/ILIAS/Test/classes/class.ilObjTest.php +++ b/components/ILIAS/Test/classes/class.ilObjTest.php @@ -193,7 +193,8 @@ public function __construct(int $id = 0, bool $a_call_by_reference = true) parent::__construct($id, $a_call_by_reference); - $this->lng->loadLanguageModule("assessment"); + $this->lng->loadLanguageModule('assessment'); + $this->lng->loadLanguageModule('qsts'); $this->score_settings = null; $this->question_set_config_factory = new ilTestQuestionSetConfigFactory( diff --git a/components/ILIAS/Test/classes/class.ilObjTestAccess.php b/components/ILIAS/Test/classes/class.ilObjTestAccess.php index 434fced9b3b5..682c856ff660 100755 --- a/components/ILIAS/Test/classes/class.ilObjTestAccess.php +++ b/components/ILIAS/Test/classes/class.ilObjTestAccess.php @@ -186,6 +186,7 @@ public static function _getCommands(): array { global $DIC; $DIC->language()->loadLanguageModule('assessment'); + $DIC->language()->loadLanguageModule('qsts'); return [ [ diff --git a/components/ILIAS/Test/classes/class.ilObjTestFolderGUI.php b/components/ILIAS/Test/classes/class.ilObjTestFolderGUI.php index ccdb44413a7b..31874d33fca9 100755 --- a/components/ILIAS/Test/classes/class.ilObjTestFolderGUI.php +++ b/components/ILIAS/Test/classes/class.ilObjTestFolderGUI.php @@ -62,6 +62,7 @@ public function __construct( } $this->lng->loadLanguageModule('assessment'); + $this->lng->loadLanguageModule('qsts'); } private function getTestFolder(): ilObjTestFolder diff --git a/components/ILIAS/Test/classes/class.ilObjTestGUI.php b/components/ILIAS/Test/classes/class.ilObjTestGUI.php index ceebd6d9f0d7..dadb8082d5fa 100755 --- a/components/ILIAS/Test/classes/class.ilObjTestGUI.php +++ b/components/ILIAS/Test/classes/class.ilObjTestGUI.php @@ -1737,7 +1737,7 @@ private function buildQuestionCreationForm(): Form $inputs['pool_selection'] = $this->buildInputPoolSelection(); $section = [ - $this->ui_factory->input()->field()->section($inputs, $this->lng->txt('ass_create_question')) + $this->ui_factory->input()->field()->section($inputs, $this->lng->txt('create_question')) ]; $form = $this->ui_factory->input()->container()->form()->standard( @@ -1928,7 +1928,7 @@ private function setupToolBarAndMessage(bool $has_started_test_runs): void return; } - $this->toolbar->addButton($this->lng->txt('ass_create_question'), $this->ctrl->getLinkTarget($this, 'createQuestionForm')); + $this->toolbar->addButton($this->lng->txt('create_question'), $this->ctrl->getLinkTarget($this, 'createQuestionForm')); $this->toolbar->addSeparator(); $this->populateQuestionBrowserToolbarButtons($this->toolbar); } diff --git a/components/ILIAS/Test/classes/class.ilTestFixedQuestionSetConfig.php b/components/ILIAS/Test/classes/class.ilTestFixedQuestionSetConfig.php index 2f429f9fcb02..92db879f27f7 100755 --- a/components/ILIAS/Test/classes/class.ilTestFixedQuestionSetConfig.php +++ b/components/ILIAS/Test/classes/class.ilTestFixedQuestionSetConfig.php @@ -71,12 +71,19 @@ public function cloneQuestionSetRelatedData(ilObjTest $clone_test_obj): void foreach ($this->test_obj->questions as $key => $question_id) { $question_orig = assQuestion::instantiateQuestion($question_id); - $clone_test_obj->questions[$key] = $question_orig->duplicate(true, '', '', -1, $clone_test_obj->getId()); - - $original_id = $this->questionrepository->getForQuestionId($question_id)->getOriginalId(); - - $question_clone = assQuestion::instantiateQuestion($clone_test_obj->questions[$key]); - $question_clone->saveToDb($original_id); + $clone_test_obj->questions[$key] = $question_orig->duplicate( + false, + '', + '', + -1, + $clone_test_obj->getId() + ); + $question_clone = assQuestion::instantiateQuestion( + $clone_test_obj->questions[$key] + ); + $question_clone->updateOriginalId( + $question_orig->getOriginalId() + ); // Save the mapping of old question id <-> new question id // This will be used in class.ilObjCourse::cloneDependencies to copy learning objectives diff --git a/components/ILIAS/Test/classes/class.ilTestPlayerAbstractGUI.php b/components/ILIAS/Test/classes/class.ilTestPlayerAbstractGUI.php index 194abaa4eb24..c5389da89071 100755 --- a/components/ILIAS/Test/classes/class.ilTestPlayerAbstractGUI.php +++ b/components/ILIAS/Test/classes/class.ilTestPlayerAbstractGUI.php @@ -1853,7 +1853,7 @@ protected function showSideList($current_sequence_element): void } $question_listing = $this->ui_factory->listing()->workflow()->linear( - $this->lng->txt('mainbar_button_label_questionlist'), + $this->lng->txt('questionlist'), $questions )->withActive($active); diff --git a/components/ILIAS/Test/src/Logging/AdditionalInformationGenerator.php b/components/ILIAS/Test/src/Logging/AdditionalInformationGenerator.php index e466d9f7cbc9..a31d8210dfbf 100644 --- a/components/ILIAS/Test/src/Logging/AdditionalInformationGenerator.php +++ b/components/ILIAS/Test/src/Logging/AdditionalInformationGenerator.php @@ -263,6 +263,7 @@ public function __construct( private readonly GeneralQuestionPropertiesRepository $questions_repo ) { $lng->loadLanguageModule('assessment'); + $lng->loadLanguageModule('qsts'); $lng->loadLanguageModule('crs'); $this->tags = $this->buildTags(); } diff --git a/components/ILIAS/Test/src/Participants/Participant.php b/components/ILIAS/Test/src/Participants/Participant.php index 21bf4258b985..d51eaf4f4b93 100644 --- a/components/ILIAS/Test/src/Participants/Participant.php +++ b/components/ILIAS/Test/src/Participants/Participant.php @@ -253,12 +253,16 @@ public function getDisplayName(Language $language, bool $anonymous_test = false) return $language->txt('anonymous'); } + if ($this->login === '' && $this->firstname === '' && $this->lastname === '') { + return $language->txt('user_deleted'); + } + $display_name = ''; - if ($this->firstname) { + if ($this->firstname !== '') { $display_name .= $this->firstname . ' '; } - if ($this->lastname) { + if ($this->lastname !== '') { $display_name .= $this->lastname; } diff --git a/components/ILIAS/Test/src/Questions/Presentation/QuestionsOfAttemptTable.php b/components/ILIAS/Test/src/Questions/Presentation/QuestionsOfAttemptTable.php index 7ba58cbdaf2a..de061939dc99 100644 --- a/components/ILIAS/Test/src/Questions/Presentation/QuestionsOfAttemptTable.php +++ b/components/ILIAS/Test/src/Questions/Presentation/QuestionsOfAttemptTable.php @@ -139,7 +139,6 @@ protected function getColumns(): array $icon_factory = $this->ui_factory->symbol()->icon(); $icon_checked = $icon_factory->custom('assets/images/standard/icon_checked.svg', $this->lng->txt('yes')); $icon_unchecked = $icon_factory->custom('assets/images/standard/icon_unchecked.svg', $this->lng->txt('no')); - $icon_marked = $icon_factory->custom('assets/images/object/marked.svg', $this->lng->txt('tst_question_marked')); $columns = [ 'order' => $column_factory->number($this->lng->txt('tst_qst_order')), @@ -148,7 +147,7 @@ protected function getColumns(): array 'postponed' => $column_factory->boolean(ucfirst($this->lng->txt('postponed')), $this->lng->txt('yes'), ''), 'points' => $column_factory->number($this->lng->txt('tst_maximum_points'))->withUnit($this->lng->txt('points_short')), 'answered' => $column_factory->boolean($this->lng->txt('answered'), $icon_checked, $icon_unchecked), - 'marked' => $column_factory->boolean($this->lng->txt('tst_question_marker'), $icon_marked, ''), + 'marked' => $column_factory->boolean($this->lng->txt('tst_question_marker'), $icon_checked, $icon_unchecked), ]; $optional_columns = [ diff --git a/components/ILIAS/TestQuestionPool/classes/class.assKprimChoice.php b/components/ILIAS/TestQuestionPool/classes/class.assKprimChoice.php index c8356c52e5b6..f881f4064b8f 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assKprimChoice.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assKprimChoice.php @@ -710,6 +710,7 @@ protected function lmMigrateQuestionTypeSpecificContent(ilAssSelfAssessmentMigra public function toJSON(): string { $this->lng->loadLanguageModule('assessment'); + $this->lng->loadLanguageModule('qsts'); $result = []; $result['id'] = $this->getId(); diff --git a/components/ILIAS/TestQuestionPool/classes/class.assMatchingQuestion.php b/components/ILIAS/TestQuestionPool/classes/class.assMatchingQuestion.php index fa5c34243f11..85e14711684a 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assMatchingQuestion.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assMatchingQuestion.php @@ -1176,6 +1176,7 @@ public function toJSON(): string $result['mobs'] = $mobs; $this->lng->loadLanguageModule('assessment'); + $this->lng->loadLanguageModule('qsts'); $result['reset_button_label'] = $this->lng->txt("reset_terms"); return json_encode($result); diff --git a/components/ILIAS/TestQuestionPool/classes/class.assQuestion.php b/components/ILIAS/TestQuestionPool/classes/class.assQuestion.php index 8a8cf44b2daf..5fe7dd438d73 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assQuestion.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assQuestion.php @@ -816,20 +816,14 @@ public function deleteAnswers(int $question_id): void public function deleteAdditionalTableData(int $question_id): void { - $additional_table_name = $this->getAdditionalTableName(); + $table_name = $this->getAdditionalTableName(); - if (!is_array($additional_table_name)) { - $additional_table_name = [$additional_table_name]; - } - - foreach ($additional_table_name as $table) { - if (strlen($table)) { - $this->db->manipulateF( - "DELETE FROM $table WHERE question_fi = %s", - ['integer'], - [$question_id] - ); - } + if (strlen($table_name)) { + $this->db->manipulateF( + "DELETE FROM $table_name WHERE question_fi = %s", + [ilDBConstants::T_INTEGER], + [$question_id] + ); } } @@ -1228,8 +1222,27 @@ public function saveQuestionDataToDb(?int $original_id = null): void ]); } + /** + * + * @deprecated This is a momentary helper function to update the original id, + * when cloning a test. Do not use anywhere else. 2026-06-25, sk + */ + public function updateOriginalId(?int $original_id = null): void + { + $this->original_id = $original_id; + $this->db->update( + 'qpl_questions', + [ + 'original_id' => [ilDBConstants::T_INTEGER, $original_id], + ], + [ + 'question_id' => [ilDBConstants::T_INTEGER, $this->getId()] + ] + ); + } + public function duplicate( - bool $for_test = true, + bool $set_original_id = true, string $title = '', string $author = '', int $owner = -1, @@ -1256,7 +1269,7 @@ public function duplicate( if ($owner) { $clone->setOwner($owner); } - if ($for_test) { + if ($set_original_id) { $clone->saveToDb($this->id); } else { $clone->saveToDb(); diff --git a/components/ILIAS/TestQuestionPool/classes/class.assQuestionGUI.php b/components/ILIAS/TestQuestionPool/classes/class.assQuestionGUI.php index 8b484bfac5f4..dbdf43b26ee0 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.assQuestionGUI.php +++ b/components/ILIAS/TestQuestionPool/classes/class.assQuestionGUI.php @@ -1259,7 +1259,7 @@ public function suggestedsolution(bool $save = false): void $formchange = new ilPropertyFormGUI(); $formchange->setFormAction($this->ctrl->getFormAction($this)); - $title = $solution ? $this->lng->txt('changeSuggestedSolution') : $this->lng->txt('addSuggestedSolution'); + $title = $solution ? $this->lng->txt('changeSuggestedSolution') : $this->lng->txt('suggested_learning_content'); $formchange->setTitle($title); $formchange->setMultipart(false); $formchange->setTableWidth('100%'); diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionPage.php b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionPage.php index ba7b5c1ef0c2..80b2a2a72bb1 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionPage.php +++ b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionPage.php @@ -16,17 +16,15 @@ * *********************************************************************/ -/** - * Question page object - * - * @author Alex Killing - * - * @version $Id$ - * - * @ingroup components\ILIASTestQuestionPool - */ +declare(strict_types=1); + +use ILIAS\Questions\Question\Question; +use ILIAS\Data\UUID\Uuid; + class ilAssQuestionPage extends ilPageObject { + private readonly Question $question; + /** * Get parent type * @return string parent type @@ -35,4 +33,92 @@ public function getParentType(): string { return "qpl"; } + + public function setQuestion( + Question $question + ): void { + $this->question = $question; + } + + public function copyToAnswerForm( + int $new_id, + Question $question + ): void { + $this->buildDom(); + $this->migrateQuestionElementToAnswerForm(); + + $new_page_object = new QstsQuestionPage(); + $new_page_object->setParentId($this->getParentId()); + $new_page_object->setId($new_id); + $new_page_object->setXMLContent($this->copyXMLContent(false, $this->getParentId())); + $new_page_object->setActive($this->getActive()); + $new_page_object->setActivationStart($this->getActivationStart()); + $new_page_object->setActivationEnd($this->getActivationEnd()); + $new_page_object->setQuestion($question); + $new_page_object->create(false); + } + + private function migrateQuestionElementToAnswerForm(): void + { + global $DIC; + $DIC->copage() + ->internal() + ->domain() + ->domUtil() + ->path($this->getDomDoc(), '//Question') + ->item(0)->parentNode->replaceWith( + $this->buildLegacyAnswerFormTextNode(), + $this->buildAnswerFormNode( + $this->question->getFirstAnswerFormIdForMigration() + ) + ); + $this->xml = $this->getXMLFromDom(); + } + + private function buildLegacyAnswerFormTextNode(): DOMNode + { + $legacy_answer_form_text_node = new ilPCLegacyAnswerFormText($this); + $legacy_answer_form_text_node->createPageContentNode(); + $legacy_answer_form_text_node->writePCId($this->generatePCId()); + $legacy_answer_form_text_node->create( + $this->retrieveLegacyPageElementContent() + ); + + return $legacy_answer_form_text_node->getDomNode(); + } + + private function buildAnswerFormNode( + Uuid $answer_form_id + ): DOMNode { + $answer_form_node = new ilPCAnswerForm($this); + $answer_form_node->createPageContentNode(); + $answer_form_node->writePCId($this->generatePCId()); + $answer_form_node->create($answer_form_id); + + return $answer_form_node->getDomNode(); + } + + private function retrieveLegacyPageElementContent(): string + { + $question_info = $this->db->fetchObject( + $this->db->query( + "SELECT add_cont_edit_mode, question_text FROM qpl_questions WHERE question_id = {$this->id}" + ) + ); + + $purified_content = ilHtmlPurifierFactory::getInstanceByType('qpl_usersolution') + ->purify($question_info->question_text); + + if ($question_info->add_cont_edit_mode === assQuestion::ADDITIONAL_CONTENT_EDITING_MODE_IPE + || !(new ilSetting('advanced_editing'))->get('advanced_editing_javascript_editor') === 'tinymce') { + $purified_content = nl2br($purified_content); + } + return base64_encode( + ilLegacyFormElementsUtil::prepareTextareaOutput( + $purified_content, + true, + true + ) + ); + } } diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionPreviewGUI.php b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionPreviewGUI.php index 1b74c1325ff5..c08225138906 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionPreviewGUI.php +++ b/components/ILIAS/TestQuestionPool/classes/class.ilAssQuestionPreviewGUI.php @@ -373,7 +373,7 @@ private function populateToolbar(): void $this->toolbar->addComponent( $this->ui_factory->button()->standard( - $this->lng->txt('qpl_reset_preview'), + $this->lng->txt('reset_preview'), $this->ctrl->getLinkTargetByClass(ilAssQuestionPreviewGUI::class, self::CMD_RESET) ) ); diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilObjQuestionPoolGUI.php b/components/ILIAS/TestQuestionPool/classes/class.ilObjQuestionPoolGUI.php index c1e066ff7ed8..cd4181f2e853 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.ilObjQuestionPoolGUI.php +++ b/components/ILIAS/TestQuestionPool/classes/class.ilObjQuestionPoolGUI.php @@ -136,6 +136,7 @@ public function __construct() $this->ctrl->saveParameterByClass('ilobjquestionpoolgui', 'consumer_context'); $this->lng->loadLanguageModule('assessment'); + $this->lng->loadLanguageModule('qsts'); $here_uri = $this->data_factory->uri($this->request->getUri()->__toString()); $url_builder = new URLBuilder($here_uri); @@ -371,15 +372,6 @@ public function executeCommand(): void $question_gui->setObject($question); $question_gui->setQuestionTabs(); - if ($this->questionrepository->isInActiveTest($question_gui->getObject()->getObjId())) { - $this->tpl->setOnScreenMessage( - 'failure', - $this->lng->txt('question_is_part_of_running_test'), - true - ); - $this->ctrl->redirectByClass('ilAssQuestionPreviewGUI', ilAssQuestionPreviewGUI::CMD_SHOW); - } - $this->help->setScreenIdComponent('qpl'); if ($this->object->getType() == 'qpl' && $write_access) { @@ -973,7 +965,7 @@ public function confirmDeleteQuestions(array $ids): void $this->ctrl->redirect($this, self::DEFAULT_CMD); } - $this->tpl->setOnScreenMessage('question', $this->lng->txt('qpl_confirm_delete_questions')); + $this->tpl->setOnScreenMessage('question', $this->lng->txt('confirm_delete_questions')); $deleteable_questions = $this->object->getDeleteableQuestionDetails($questionIdsToDelete); $table_gui = new ilQuestionBrowserTableGUI($this, self::DEFAULT_CMD, (($rbacsystem->checkAccess('write', $this->request_data_collector->getRefId()) ? true : false)), true); $table_gui->setShowRowsSelector(false); @@ -1001,7 +993,7 @@ public function deleteQuestionsObject(): void $this->ctrl->redirect($this, self::DEFAULT_CMD); } - $this->tpl->setOnScreenMessage('question', $this->lng->txt('qpl_confirm_delete_questions')); + $this->tpl->setOnScreenMessage('question', $this->lng->txt('confirm_delete_questions')); $deleteable_questions = &$this->object->getDeleteableQuestionDetails($questionIdsToDelete); $table_gui = new ilQuestionBrowserTableGUI( $this, @@ -1100,7 +1092,7 @@ public function questionsObject(?RoundTripModal $import_questions_modal = null): $out = []; if ($this->rbac_system->checkAccess('write', $this->request_data_collector->getRefId())) { $btn = $this->ui_factory->button()->primary( - $this->lng->txt('ass_create_question'), + $this->lng->txt('create_question'), $this->ctrl->getLinkTargetByClass([ilRepositoryGUI::class, self::class], 'createQuestionForm') ); $this->toolbar->addComponent($btn); @@ -1163,7 +1155,7 @@ private function buildQuestionCreationForm(): Form $inputs['editing_type'] = $this->buildInputEditingType(); $section = [ - $this->ui_factory->input()->field()->section($inputs, $this->lng->txt('ass_create_question')) + $this->ui_factory->input()->field()->section($inputs, $this->lng->txt('create_question')) ]; $form = $this->ui_factory->input()->container()->form()->standard( diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilQuestionEditGUI.php b/components/ILIAS/TestQuestionPool/classes/class.ilQuestionEditGUI.php index d6c7af521379..37ef4c2aa168 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.ilQuestionEditGUI.php +++ b/components/ILIAS/TestQuestionPool/classes/class.ilQuestionEditGUI.php @@ -85,6 +85,7 @@ public function __construct() $this->setQuestionId($this->request->getQuestionId()); $this->setQuestionType($this->request->raw('q_type')); $this->lng->loadLanguageModule('assessment'); + $this->lng->loadLanguageModule('qsts'); $this->ctrl->saveParameter($this, ['qpool_ref_id', 'qpool_obj_id', 'q_id', 'q_type']); diff --git a/components/ILIAS/TestQuestionPool/classes/class.ilUnitConfigurationGUI.php b/components/ILIAS/TestQuestionPool/classes/class.ilUnitConfigurationGUI.php index fac027de50c8..913bf774ffb3 100755 --- a/components/ILIAS/TestQuestionPool/classes/class.ilUnitConfigurationGUI.php +++ b/components/ILIAS/TestQuestionPool/classes/class.ilUnitConfigurationGUI.php @@ -47,6 +47,7 @@ public function __construct( $this->request = $local_dic['request_data_collector']; $this->lng->loadLanguageModule('assessment'); + $this->lng->loadLanguageModule('qsts'); } abstract protected function getDefaultCommand(): string; diff --git a/components/ILIAS/TestQuestionPool/classes/forms/class.ilAssLongmenuCorrectionsInputGUI.php b/components/ILIAS/TestQuestionPool/classes/forms/class.ilAssLongmenuCorrectionsInputGUI.php index 5dd73db6c22d..c841e2df7e3c 100755 --- a/components/ILIAS/TestQuestionPool/classes/forms/class.ilAssLongmenuCorrectionsInputGUI.php +++ b/components/ILIAS/TestQuestionPool/classes/forms/class.ilAssLongmenuCorrectionsInputGUI.php @@ -55,7 +55,7 @@ public function insert(ilTemplate $a_tpl): void $this->answer_options_modal = $this->ui->factory()->modal()->lightbox( $this->ui->factory()->modal()->lightboxTextPage( $inp->render(), - $this->lng->txt('answer_options') + "{$this->lng->txt('answer_options')}:" ) ); @@ -72,7 +72,7 @@ public function insert(ilTemplate $a_tpl): void ) ) ); - $tpl->setVariable('TXT_ANSWERS', $this->lng->txt('answer_options')); + $tpl->setVariable('TXT_ANSWERS', "{$this->lng->txt('answer_options')}:"); $tpl->setVariable('TXT_CORRECT_ANSWERS', $this->lng->txt('correct_answers') . ':'); $tpl->setVariable('POSTVAR', $this->getPostVar()); diff --git a/components/ILIAS/TestQuestionPool/tests/assQuestionTest.php b/components/ILIAS/TestQuestionPool/tests/assQuestionTest.php index 96e8a43ff484..99ecf3210d54 100644 --- a/components/ILIAS/TestQuestionPool/tests/assQuestionTest.php +++ b/components/ILIAS/TestQuestionPool/tests/assQuestionTest.php @@ -69,7 +69,7 @@ public function getQuestionType(): string return ''; } - public function duplicate(bool $for_test = true, string $title = "", string $author = "", int $owner = -1, $testObjId = null): int + public function duplicate(bool $set_original_id = true, string $title = "", string $author = "", int $owner = -1, $testObjId = null): int { return 0; } diff --git a/components/ILIAS/UI/resources/images/standard/icon_qsts.svg b/components/ILIAS/UI/resources/images/standard/icon_qsts.svg new file mode 100755 index 000000000000..a47443e9b27c --- /dev/null +++ b/components/ILIAS/UI/resources/images/standard/icon_qsts.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + diff --git a/components/ILIAS/UI/src/Component/Symbol/Icon/Standard.php b/components/ILIAS/UI/src/Component/Symbol/Icon/Standard.php index f27efd55ac11..c405aacc3452 100755 --- a/components/ILIAS/UI/src/Component/Symbol/Icon/Standard.php +++ b/components/ILIAS/UI/src/Component/Symbol/Icon/Standard.php @@ -184,7 +184,7 @@ interface Standard extends Icon public const GCON = 'gcon'; //Group Conversaion public const FILS = 'fils'; //File System Service public const TALA = 'tala'; //Employee Talk Template Admin - public const QST = 'ques'; //Question + public const QSTS = 'qsts'; //Question Component public const GSFO = 'gsfo'; //Footer Administration public const STUS = 'stus'; //Shortlink public const ADMA = 'adma'; //Administration - General Settings diff --git a/components/ILIAS/UI/src/Implementation/Component/Symbol/Icon/Standard.php b/components/ILIAS/UI/src/Implementation/Component/Symbol/Icon/Standard.php index ae2788bb5693..134cb392eba1 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Symbol/Icon/Standard.php +++ b/components/ILIAS/UI/src/Implementation/Component/Symbol/Icon/Standard.php @@ -152,7 +152,7 @@ class Standard extends Icon implements C\Symbol\Icon\Standard self::CON, self::FILS, self::TALA, - self::QST, + self::QSTS, self::STUS, self::GSFO, self::ADMA, diff --git a/lang/ilias_ar.lang b/lang/ilias_ar.lang index e1c0c51f7ca6..6eb2c4442280 100644 --- a/lang/ilias_ar.lang +++ b/lang/ilias_ar.lang @@ -422,7 +422,6 @@ adve#:#adve_use_tiny_mce#:#Enable TinyMCE for WYSIWYG Editing assessment#:#activate_logging#:#تفعيل تسجيل الاختبار والتقييم assessment#:#activate_manual_scoring#:#Enable Manual Scoring###28 10 2024 new variable assessment#:#activate_manual_scoring_desc#:#Enables manual Scoring for all question types.###28 10 2024 new variable -assessment#:#addSuggestedSolution#:#اضافة حل مقترح assessment#:#add_answers#:#Add Antworten ###23 12 2015 new variable assessment#:#add_circle#:#Add circle area assessment#:#add_gap#:#Add Gap Text @@ -447,7 +446,6 @@ assessment#:#answer_is_not_correct_but_positive#:#You've got points for your sol assessment#:#answer_is_right#:#Your solution is correct assessment#:#answer_is_wrong#:#Your solution is wrong assessment#:#answer_of#:#Answer of###07 02 2020 new variable -assessment#:#answer_options#:#Answer Options: ###23 12 2015 new variable assessment#:#answer_question#:#Answer Question###30 08 2015 new variable assessment#:#answer_text#:#Answer Text assessment#:#answer_types#:#Answer Types @@ -501,7 +499,6 @@ assessment#:#ass_completion_by_submission#:#Completed by Submission assessment#:#ass_completion_by_submission_info#:#If enabled, the submission of at least one file causes the completion of this question by granting the maximum score for this question. The score could be manually changed later. Switching this setting does not effect already submitted solutions. assessment#:#ass_create_export_file_with_results#:#Create Test Export File (incl. Participant Results)###25 10 2016 new variable assessment#:#ass_create_export_test_archive#:#Create Test Archive File###24 10 2013 new variable -assessment#:#ass_create_question#:#Create Question###24 10 2013 new variable assessment#:#ass_imap_hint#:#Hint to be shown as Tooltip###25 10 2016 new variable assessment#:#ass_imap_map_file_not_readable#:#The uploaded image map could not be read.###25 10 2016 new variable assessment#:#ass_imap_no_map_found#:#Could not find any form in the uploaded image map.###25 10 2016 new variable @@ -571,10 +568,6 @@ assessment#:#cloze_answer_text_info#:#Spaces preceding or following the answer t assessment#:#cloze_fixed_textlength#:#Text Field Length assessment#:#cloze_fixed_textlength_description#:#If you enter a value greater than 0, all text and numeric gap text fields will be created with the fixed length of this value. assessment#:#cloze_gap_size_info#:#If you enter a value greater than 0, this gap text field will be created with the fixed length of this value. If you do not enter a value the gap text fiel will be created with the value of the global fixed length.###26 09 2014 new variable -assessment#:#cloze_text#:#Cloze Text -assessment#:#cloze_textgap_case_insensitive#:#Case Insensitive -assessment#:#cloze_textgap_case_sensitive#:#Case Sensitive -assessment#:#cloze_textgap_levenshtein_of#:#Levenshtein Distance of %s assessment#:#code#:#Code assessment#:#codebase#:#Codebase assessment#:#concatenation#:#Concatenation @@ -757,9 +750,7 @@ assessment#:#fq_formula_desc#:#You may enter predefined variables ($v1 to $vn), assessment#:#fq_no_restriction_info#:#Both decimals and fractions are accepted as input.###26 03 2014 new variable assessment#:#fq_precision_info#:#Enter the number of desired decimal places.###26 03 2014 new variable assessment#:#fq_question_desc#:#You can define variables by inserting $v1, $v2 ... $vn, results by inserting $r1, $r2 .... $rn at the desired position in the question text. Click on the button "Parse Question" to create editing forms for variables and results.###06 11 2013 new variable -assessment#:#gap#:#Gap assessment#:#gap_combination#:#Gap Combination###26 09 2014 new variable -assessment#:#gaps#:#Gaps###29 10 2025 new variable assessment#:#glossary_term#:#Glossary Term assessment#:#goto_first_question#:#Show First Question###29 10 2025 new variable assessment#:#grading_mark_msg#:#Your resulting mark is: "[mark]"###26 09 2014 new variable @@ -777,7 +768,6 @@ assessment#:#info_answer_type_change#:#The question already contains images. You assessment#:#info_text_upload#:#Choose an answer file to upload###30 08 2015 new variable assessment#:#insert_after#:#Insert After assessment#:#insert_before#:#Insert Before -assessment#:#insert_gap#:#Insert Gap###24 10 2013 new variable assessment#:#interaction_type#:#Interaction Type###26 08 2024 new variable assessment#:#internal_links#:#Internal Links assessment#:#intprecision#:#Divisible By###24 10 2013 new variable @@ -889,7 +879,6 @@ assessment#:#logs_wrong_test_password_provided#:#Participant entered wrong test assessment#:#longmenu#:#Longmenu###10 11 2018 new variable assessment#:#longmenu_answeroptions_differ#:#This question does not work correctly, as there are not the same amount of gaps in the text as in the correction options.###26 08 2024 new variable assessment#:#longmenu_text#:#Long Menu Text###30 08 2015 new variable -assessment#:#mainbar_button_label_questionlist#:#Questionlist###26 08 2024 new variable assessment#:#maintenance#:#Maintenance assessment#:#manscoring#:#Manual Scoring assessment#:#manscoring_done#:#Scored Participants @@ -916,7 +905,6 @@ assessment#:#maximum_nr_of_tries_reached#:#You have reached the maximum number o assessment#:#maximum_points#:#Maxium Available Points assessment#:#maxsize#:#Maximum file upload size assessment#:#maxsize_info#:#Enter the maximum size in bytes that should be allowed for file uploads. If you leave this field empty, the maximum size of this installation will be chosen instead. -assessment#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable assessment#:#min_ip_label#:#Lowest IP With Access###26 08 2024 new variable assessment#:#min_percentage_ne_0#:#You must define a minimum percentage of 0 percent! The mark schema wasn't saved. assessment#:#misc#:#Misc Options###24 10 2013 new variable @@ -925,7 +913,6 @@ assessment#:#mode_onebyone#:#One by One###29 10 2025 new variable assessment#:#mode_question#:#Question oriented###29 10 2025 new variable assessment#:#mode_user#:#Participant oriented###29 10 2025 new variable assessment#:#msg_circle_added#:#Circle added -assessment#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable assessment#:#msg_number_of_terms_too_low#:#The number of terms must be greater or equal to the number of definitions. assessment#:#msg_poly_added#:#Polygon added assessment#:#msg_questions_moved#:#Question(s) moved @@ -984,7 +971,6 @@ assessment#:#order#:#Order###26 08 2024 new variable assessment#:#ordering_answer_sequence_info#:#The answer sequence you define here will be taken as the correct solution sequence. assessment#:#ordertext#:#Ordering Text assessment#:#ordertext_info#:#Please enter the text that should be ordered horizontally. The ordering text will be separated by the whitespace signs in the text. If you need a different separation, you may use the separator %s to separate your text units. -assessment#:#out_of_range#:#Out of range###27 01 2015 new variable assessment#:#output#:#Output assessment#:#output_mode#:#Output Mode assessment#:#parseQuestion#:#Parse Question###24 10 2013 new variable @@ -1053,7 +1039,6 @@ assessment#:#qpl_bulk_save_add#:#Add###28 10 2024 new variable assessment#:#qpl_bulk_save_overwrite#:#Overwrite###28 10 2024 new variable assessment#:#qpl_bulkedit_success#:#Modifications saved.###28 10 2024 new variable assessment#:#qpl_cancel_skill_assigns_update#:#Cancel###30 08 2015 new variable -assessment#:#qpl_confirm_delete_questions#:#هل انت متأكد من أنك تريد حذف السؤال \الاسئلة التالي\ة؟ assessment#:#qpl_copy_insert_clipboard#:#The selected question(s) are copied to the clipboard assessment#:#qpl_copy_select_none#:#Please check at least one question to copy it to the clipboard assessment#:#qpl_delete_rbac_error#:#You have no rights to delete this question! @@ -1106,7 +1091,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#Competence###30 08 2015 new variable assessment#:#qpl_qst_skl_usg_sklpnt_col#:#Total Sum of Competence-Points per Competence###30 08 2015 new variable assessment#:#qpl_question_is_in_use#:#The question you are about to edit exists in %s test(s). If you change this question, you will NOT change the question(s) in the test(s), because the system creates a copy of a question when it is inserted in a test! assessment#:#qpl_questions_deleted#:#Question(s) deleted. -assessment#:#qpl_reset_preview#:#Reset Preview###26 09 2014 new variable assessment#:#qpl_save_skill_assigns_update#:#Save Competence Assignments###30 08 2015 new variable assessment#:#qpl_settings_availability#:#Availability###26 08 2024 new variable assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#When enabled, possibly created taxonomies are shown for filtering.###24 10 2013 new variable @@ -1136,14 +1120,6 @@ assessment#:#qst_essay_chars_remaining#:#Remaining characters:###07 02 2020 new assessment#:#qst_essay_wordcounter_enabled#:#Count Words###07 02 2020 new variable assessment#:#qst_essay_wordcounter_enabled_info#:#The entered words are counted. The number of written words is shown to the participants below the text input field.###07 02 2020 new variable assessment#:#qst_essay_written_words#:#Number of entered words:###07 02 2020 new variable -assessment#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable -assessment#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable -assessment#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable -assessment#:#qst_lifecycle_final#:#Final###07 02 2020 new variable -assessment#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable -assessment#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable -assessment#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable -assessment#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable assessment#:#qst_nested_nested_answers_off#:#No indents, just order###29 07 2022 new variable assessment#:#qst_nested_nested_answers_on#:#Use indents in anwers###29 07 2022 new variable assessment#:#qst_nr_of_tries#:#Number of tries @@ -1167,17 +1143,14 @@ assessment#:#question_title#:#Question Title assessment#:#question_type#:#Question Type assessment#:#questionpool_not_entered#:#Please enter a name for a question pool! assessment#:#questionpool_not_selected#:#Please select a question pool!###07 02 2020 new variable -assessment#:#questions#:#Questions###26 08 2024 new variable assessment#:#questions_from#:#questions from assessment#:#questions_per_page_view#:#Page View assessment#:#random_accept_sample#:#Accept Sample assessment#:#random_another_sample#:#Get another Sample assessment#:#random_selection#:#Random Selection assessment#:#range#:#Range -assessment#:#range_lower_limit#:#Lower Bound assessment#:#range_max#:#Range (Maximum)###24 10 2013 new variable assessment#:#range_min#:#Range (Minimum)###24 10 2013 new variable -assessment#:#range_upper_limit#:#Upper Bound assessment#:#rated_sign#:#Sign###24 10 2013 new variable assessment#:#rated_unit#:#Unit###24 10 2013 new variable assessment#:#rated_value#:#Value###24 10 2013 new variable @@ -1253,7 +1226,6 @@ assessment#:#search_roles#:#Search Roles assessment#:#search_term#:#مصطلح البحث assessment#:#select_at_least_one_feedback_type_and_trigger#:#Please Select at least one type of feedback and a trigger.###26 08 2024 new variable assessment#:#select_at_least_one_lock_answer_type#:#Please select at least one type of answer lock.###29 10 2025 new variable -assessment#:#select_gap#:#Select Gap assessment#:#select_max_one_item#:#الرجاء اختيار عنصر واحد فقط assessment#:#select_one_user#:#Please select at least one user. assessment#:#select_question#:#Select a Question###28 10 2024 new variable @@ -1280,7 +1252,6 @@ assessment#:#show_old_introduction#:#Show old introduction###26 08 2024 new vari assessment#:#show_pass_overview#:#Show Marked Pass Overview assessment#:#show_results#:#Show Results###28 10 2024 new variable assessment#:#show_user_answers#:#Show User's Marked Answers -assessment#:#shuffle_answers#:#Shuffle Answers assessment#:#skip_question#:#Do not Answer and Next###08 10 2015 new variable assessment#:#solution#:#Solution###28 10 2024 new variable assessment#:#solutionText#:#Text @@ -13971,6 +13942,35 @@ qpl#:#qpl_page_type_qfbg#:#General Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qfbs#:#Special Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qht#:#Hint###29 07 2022 new variable qpl#:#qpl_page_type_qpl#:#Question Page###29 07 2022 new variable +qsts#:#answer_options#:#Answer Options ###23 12 2015 new variable +qsts#:#cloze_text#:#Cloze Text +qsts#:#cloze_textgapcase_insensitive#:#Case Insensitive +qsts#:#cloze_textgapcase_sensitive#:#Case Sensitive +qsts#:#cloze_textgaplevenshtein_of#:#Levenshtein Distance of %s +qsts#:#confirm_delete_questions#:#هل انت متأكد من أنك تريد حذف السؤال \الاسئلة التالي\ة؟ +qsts#:#create_question#:#Create Question###24 10 2013 new variable +qsts#:#gap#:#Gap +qsts#:#gaps#:#Gaps###29 10 2025 new variable +qsts#:#insert_gap#:#Insert Gap###24 10 2013 new variable +qsts#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable +qsts#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable +qsts#:#out_of_range#:#Out of range###27 01 2015 new variable +qsts#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable +qsts#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable +qsts#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable +qsts#:#qst_lifecycle_final#:#Final###07 02 2020 new variable +qsts#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable +qsts#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable +qsts#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable +qsts#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable +qsts#:#questionlist#:#Questionlist###26 08 2024 new variable +qsts#:#questions#:#Questions###26 08 2024 new variable +qsts#:#range_lower_limit#:#Lower Bound +qsts#:#range_upper_limit#:#Upper Bound +qsts#:#reset_preview#:#Reset Preview###26 09 2014 new variable +qsts#:#select_gap#:#Select Gap +qsts#:#shuffle_answers#:#Shuffle Answers +qsts#:#suggested_learning_content#:#اضافة حل مقترح rating#:#rat_not_rated_yet#:#Not Rated Yet rating#:#rat_nr_ratings#:#%s Ratings rating#:#rat_one_rating#:#One Rating @@ -16620,7 +16620,6 @@ survey#:#questionblock#:#Question Block survey#:#questionblock_inserted#:#Question Block inserted survey#:#questionblocks#:#Question Blocks survey#:#questionblocks_inserted#:#Question Blocks inserted -survey#:#questions#:#Questions survey#:#questions_inserted#:#Question(s) inserted! survey#:#questions_removed#:#Question(s) and/or question block(s) removed! survey#:#questiontype#:#Question Type @@ -16921,6 +16920,7 @@ survey#:#svy_please_select_unused_codes#:#Please select at least one unused code survey#:#svy_print_hide_labels#:#Hide labels###31 08 2017 new variable survey#:#svy_print_show_labels#:#Show labels###31 08 2017 new variable survey#:#svy_privacy_info#:#Privacy###29 07 2022 new variable +survey#:#svy_questions#:#Questions survey#:#svy_rater#:#Rater###29 07 2022 new variable survey#:#svy_rater_see_app_info#:#The names of appraisees will be presented to raters to enable them evaluating the questions.###29 07 2022 new variable survey#:#svy_reminder_mail_template#:#Mail Template###25 10 2016 new variable diff --git a/lang/ilias_bg.lang b/lang/ilias_bg.lang index ec20f122d166..ec545e65cf90 100644 --- a/lang/ilias_bg.lang +++ b/lang/ilias_bg.lang @@ -422,7 +422,6 @@ adve#:#adve_use_tiny_mce#:#Enable TinyMCE for WYSIWYG Editing###26 08 2024 new v assessment#:#activate_logging#:#Активиране записването на теста и оценяването assessment#:#activate_manual_scoring#:#Enable Manual Scoring###28 10 2024 new variable assessment#:#activate_manual_scoring_desc#:#Enables manual Scoring for all question types.###28 10 2024 new variable -assessment#:#addSuggestedSolution#:#Add suggested solution###24 02 2009 new variable assessment#:#add_answers#:#Add Antworten ###23 12 2015 new variable assessment#:#add_circle#:#Add circle area###24 07 2009 new variable assessment#:#add_gap#:#Добавяне на текст в празнина @@ -447,7 +446,6 @@ assessment#:#answer_is_not_correct_but_positive#:#You've got points for your sol assessment#:#answer_is_right#:#Вашето решение е правилно assessment#:#answer_is_wrong#:#Вашето решение е грешно assessment#:#answer_of#:#Answer of###07 02 2020 new variable -assessment#:#answer_options#:#Answer Options: ###23 12 2015 new variable assessment#:#answer_question#:#Answer Question###30 08 2015 new variable assessment#:#answer_text#:#Текст за отговора assessment#:#answer_types#:#Answer Types###24 07 2009 new variable @@ -501,7 +499,6 @@ assessment#:#ass_completion_by_submission#:#Completed by Submission###12 06 2011 assessment#:#ass_completion_by_submission_info#:#If enabled, the submission of at least one file causes the completion of this question by granting the maximum score for this question. The score could be manually changed later. Switching this setting does not effect already submitted solutions.###12 06 2011 new variable assessment#:#ass_create_export_file_with_results#:#Create Test Export File (incl. Participant Results)###25 10 2016 new variable assessment#:#ass_create_export_test_archive#:#Create Test Archive File###24 10 2013 new variable -assessment#:#ass_create_question#:#Create Question###24 10 2013 new variable assessment#:#ass_imap_hint#:#Hint to be shown as Tooltip###25 10 2016 new variable assessment#:#ass_imap_map_file_not_readable#:#The uploaded image map could not be read.###25 10 2016 new variable assessment#:#ass_imap_no_map_found#:#Could not find any form in the uploaded image map.###25 10 2016 new variable @@ -571,10 +568,6 @@ assessment#:#cloze_answer_text_info#:#Spaces preceding or following the answer t assessment#:#cloze_fixed_textlength#:#Text Field Length###02 04 2007 new variable assessment#:#cloze_fixed_textlength_description#:#If you enter a value greater than 0, all text and numeric gap text fields will be created with the fixed length of this value.###02 04 2007 new variable assessment#:#cloze_gap_size_info#:#If you enter a value greater than 0, this gap text field will be created with the fixed length of this value. If you do not enter a value the gap text fiel will be created with the value of the global fixed length.###26 09 2014 new variable -assessment#:#cloze_text#:#Затваряне на текста -assessment#:#cloze_textgap_case_insensitive#:#Case insensitive###03 Nov 2005 new variable -assessment#:#cloze_textgap_case_sensitive#:#Case sensitive###03 Nov 2005 new variable -assessment#:#cloze_textgap_levenshtein_of#:#Levenshtein distance of %s###03 Nov 2005 new variable assessment#:#code#:#Код assessment#:#codebase#:#Codebase###25 02 2007 new variable assessment#:#concatenation#:#Concatenation###06 09 2006 new variable @@ -757,9 +750,7 @@ assessment#:#fq_formula_desc#:#You may enter predefined variables ($v1 to $vn), assessment#:#fq_no_restriction_info#:#Both decimals and fractions are accepted as input.###26 03 2014 new variable assessment#:#fq_precision_info#:#Enter the number of desired decimal places.###26 03 2014 new variable assessment#:#fq_question_desc#:#You can define variables by inserting $v1, $v2 ... $vn, results by inserting $r1, $r2 .... $rn at the desired position in the question text. Click on the button "Parse Question" to create editing forms for variables and results.###06 11 2013 new variable -assessment#:#gap#:#Празнина assessment#:#gap_combination#:#Gap Combination###26 09 2014 new variable -assessment#:#gaps#:#Gaps###29 10 2025 new variable assessment#:#glossary_term#:#Термин от речника assessment#:#goto_first_question#:#Show First Question###29 10 2025 new variable assessment#:#grading_mark_msg#:#Your resulting mark is: "[mark]"###26 09 2014 new variable @@ -777,7 +768,6 @@ assessment#:#info_answer_type_change#:#The question already contains images. You assessment#:#info_text_upload#:#Choose an answer file to upload###30 08 2015 new variable assessment#:#insert_after#:#Вмъкване след assessment#:#insert_before#:#Вмъкване преди -assessment#:#insert_gap#:#Insert Gap###24 10 2013 new variable assessment#:#interaction_type#:#Interaction Type###26 08 2024 new variable assessment#:#internal_links#:#Вътрешни линкове assessment#:#intprecision#:#Divisible By###24 10 2013 new variable @@ -889,7 +879,6 @@ assessment#:#logs_wrong_test_password_provided#:#Participant entered wrong test assessment#:#longmenu#:#Longmenu###10 11 2018 new variable assessment#:#longmenu_answeroptions_differ#:#This question does not work correctly, as there are not the same amount of gaps in the text as in the correction options.###26 08 2024 new variable assessment#:#longmenu_text#:#Long Menu Text###30 08 2015 new variable -assessment#:#mainbar_button_label_questionlist#:#Questionlist###26 08 2024 new variable assessment#:#maintenance#:#Поддръжка assessment#:#manscoring#:#Manual Scoring###25 02 2007 new variable assessment#:#manscoring_done#:#Scored Participants###24 02 2009 new variable @@ -916,7 +905,6 @@ assessment#:#maximum_nr_of_tries_reached#:#Достигнали сте макс assessment#:#maximum_points#:#Maxium Available Points###25 02 2007 new variable assessment#:#maxsize#:#Maximum file upload size ###24 02 2009 new variable assessment#:#maxsize_info#:#Enter the maximum size in bytes that should be allowed for file uploads. If you leave this field empty, the maximum size of this installation will be chosen instead.###24 02 2009 new variable -assessment#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable assessment#:#min_ip_label#:#Lowest IP With Access###26 08 2024 new variable assessment#:#min_percentage_ne_0#:#Трябва да дефинирате минимален резултат от 0 процента! Схемата с оценките не е съхранена. assessment#:#misc#:#Misc Options###24 10 2013 new variable @@ -925,7 +913,6 @@ assessment#:#mode_onebyone#:#One by One###29 10 2025 new variable assessment#:#mode_question#:#Question oriented###29 10 2025 new variable assessment#:#mode_user#:#Participant oriented###29 10 2025 new variable assessment#:#msg_circle_added#:#Circle added###24 07 2009 new variable -assessment#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable assessment#:#msg_number_of_terms_too_low#:#The number of terms must be greater or equal to the number of definitions.###12 08 2009 new variable assessment#:#msg_poly_added#:#Polygon added###24 07 2009 new variable assessment#:#msg_questions_moved#:#Question(s) moved###06 08 2009 new variable @@ -984,7 +971,6 @@ assessment#:#order#:#Order###26 08 2024 new variable assessment#:#ordering_answer_sequence_info#:#The answer sequence you define here will be taken as the correct solution sequence.###10 09 2010 new variable assessment#:#ordertext#:#Ordering Text###24 02 2009 new variable assessment#:#ordertext_info#:#Please enter the text that should be ordered horizontally. The ordering text will be separated by the whitespace signs in the text. If you need a different separation, you may use the separator %s to separate your text units.###24 02 2009 new variable -assessment#:#out_of_range#:#Out of range###27 01 2015 new variable assessment#:#output#:#Output###25 02 2007 new variable assessment#:#output_mode#:#Output Mode###25 02 2007 new variable assessment#:#parseQuestion#:#Parse Question###24 10 2013 new variable @@ -1053,7 +1039,6 @@ assessment#:#qpl_bulk_save_add#:#Add###28 10 2024 new variable assessment#:#qpl_bulk_save_overwrite#:#Overwrite###28 10 2024 new variable assessment#:#qpl_bulkedit_success#:#Modifications saved.###28 10 2024 new variable assessment#:#qpl_cancel_skill_assigns_update#:#Cancel###30 08 2015 new variable -assessment#:#qpl_confirm_delete_questions#:#Сигурни ли сте, че желаете да изтриете следният въпрос(и)? Ако изтриете заключен въпрос, резултатите от всички тестове, съдържащи заключения въпрос, също ще бъдат изтрити. assessment#:#qpl_copy_insert_clipboard#:#The selected question(s) are copied to the clipboard###03 Nov 2005 new variable assessment#:#qpl_copy_select_none#:#Please check at least one question to copy it to the clipboard###03 Nov 2005 new variable assessment#:#qpl_delete_rbac_error#:#Нямате права, за да изтриете този въпрос! @@ -1106,7 +1091,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#Competence###30 08 2015 new variable assessment#:#qpl_qst_skl_usg_sklpnt_col#:#Total Sum of Competence-Points per Competence###30 08 2015 new variable assessment#:#qpl_question_is_in_use#:#Въпросът, който желаете да редактирате, съществува в %s тест(а). Ако промените този въпрос, НЯМА да се получи промяна във въпроса(ите) в теста(овете), защото системата създава копие на въпроса, когато той бъде вмъкнат в тест! assessment#:#qpl_questions_deleted#:#Въпросът(ите) изтрит(и). -assessment#:#qpl_reset_preview#:#Reset Preview###26 09 2014 new variable assessment#:#qpl_save_skill_assigns_update#:#Save Competence Assignments###30 08 2015 new variable assessment#:#qpl_settings_availability#:#Availability###26 08 2024 new variable assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#When enabled, possibly created taxonomies are shown for filtering.###24 10 2013 new variable @@ -1136,14 +1120,6 @@ assessment#:#qst_essay_chars_remaining#:#Remaining characters:###07 02 2020 new assessment#:#qst_essay_wordcounter_enabled#:#Count Words###07 02 2020 new variable assessment#:#qst_essay_wordcounter_enabled_info#:#The entered words are counted. The number of written words is shown to the participants below the text input field.###07 02 2020 new variable assessment#:#qst_essay_written_words#:#Number of entered words:###07 02 2020 new variable -assessment#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable -assessment#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable -assessment#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable -assessment#:#qst_lifecycle_final#:#Final###07 02 2020 new variable -assessment#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable -assessment#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable -assessment#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable -assessment#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable assessment#:#qst_nested_nested_answers_off#:#No indents, just order###29 07 2022 new variable assessment#:#qst_nested_nested_answers_on#:#Use indents in anwers###29 07 2022 new variable assessment#:#qst_nr_of_tries#:#Number of tries###15 05 2009 new variable @@ -1167,17 +1143,14 @@ assessment#:#question_title#:#Заглавие на въпроса assessment#:#question_type#:#Тип на въпроса assessment#:#questionpool_not_entered#:#Моля, въведете име за набора от въпроси! assessment#:#questionpool_not_selected#:#Please select a question pool!###07 02 2020 new variable -assessment#:#questions#:#Questions###26 08 2024 new variable assessment#:#questions_from#:#въпроси от assessment#:#questions_per_page_view#:#Page View###12 06 2011 new variable assessment#:#random_accept_sample#:#Приемане на примера assessment#:#random_another_sample#:#Извличане на друг пример assessment#:#random_selection#:#Случаен подбор assessment#:#range#:#Range###25 02 2007 new variable -assessment#:#range_lower_limit#:#Lower Bound###25 02 2007 new variable assessment#:#range_max#:#Range (Maximum)###24 10 2013 new variable assessment#:#range_min#:#Range (Minimum)###24 10 2013 new variable -assessment#:#range_upper_limit#:#Upper Bound###25 02 2007 new variable assessment#:#rated_sign#:#Sign###24 10 2013 new variable assessment#:#rated_unit#:#Unit###24 10 2013 new variable assessment#:#rated_value#:#Value###24 10 2013 new variable @@ -1253,7 +1226,6 @@ assessment#:#search_roles#:#Search Roles###06 09 2006 new variable assessment#:#search_term#:#Search Term###06 09 2006 new variable assessment#:#select_at_least_one_feedback_type_and_trigger#:#Please Select at least one type of feedback and a trigger.###26 08 2024 new variable assessment#:#select_at_least_one_lock_answer_type#:#Please select at least one type of answer lock.###29 10 2025 new variable -assessment#:#select_gap#:#Изберете празнина assessment#:#select_max_one_item#:#Моля, изберете само едно assessment#:#select_one_user#:#Please select at least one user###23 Dec 2005 new variable assessment#:#select_question#:#Select a Question###28 10 2024 new variable @@ -1280,7 +1252,6 @@ assessment#:#show_old_introduction#:#Show old introduction###26 08 2024 new vari assessment#:#show_pass_overview#:#Show Marked Pass Overview###31 05 2007 new variable assessment#:#show_results#:#Show Results###28 10 2024 new variable assessment#:#show_user_answers#:#Show User's Marked Anwers###31 05 2007 new variable -assessment#:#shuffle_answers#:#Разбъркване на отговорите assessment#:#skip_question#:#Do not Answer and Next###08 10 2015 new variable assessment#:#solution#:#Solution###28 10 2024 new variable assessment#:#solutionText#:#Text###24 02 2009 new variable @@ -13971,6 +13942,35 @@ qpl#:#qpl_page_type_qfbg#:#General Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qfbs#:#Special Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qht#:#Hint###29 07 2022 new variable qpl#:#qpl_page_type_qpl#:#Question Page###29 07 2022 new variable +qsts#:#answer_options#:#Answer Options ###23 12 2015 new variable +qsts#:#cloze_text#:#Затваряне на текста +qsts#:#cloze_textgapcase_insensitive#:#Case insensitive###03 Nov 2005 new variable +qsts#:#cloze_textgapcase_sensitive#:#Case sensitive###03 Nov 2005 new variable +qsts#:#cloze_textgaplevenshtein_of#:#Levenshtein distance of %s###03 Nov 2005 new variable +qsts#:#confirm_delete_questions#:#Сигурни ли сте, че желаете да изтриете следният въпрос(и)? Ако изтриете заключен въпрос, резултатите от всички тестове, съдържащи заключения въпрос, също ще бъдат изтрити. +qsts#:#create_question#:#Create Question###24 10 2013 new variable +qsts#:#gap#:#Празнина +qsts#:#gaps#:#Gaps###29 10 2025 new variable +qsts#:#insert_gap#:#Insert Gap###24 10 2013 new variable +qsts#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable +qsts#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable +qsts#:#out_of_range#:#Out of range###27 01 2015 new variable +qsts#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable +qsts#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable +qsts#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable +qsts#:#qst_lifecycle_final#:#Final###07 02 2020 new variable +qsts#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable +qsts#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable +qsts#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable +qsts#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable +qsts#:#questionlist#:#Questionlist###26 08 2024 new variable +qsts#:#questions#:#Questions###26 08 2024 new variable +qsts#:#range_lower_limit#:#Lower Bound###25 02 2007 new variable +qsts#:#range_upper_limit#:#Upper Bound###25 02 2007 new variable +qsts#:#reset_preview#:#Reset Preview###26 09 2014 new variable +qsts#:#select_gap#:#Изберете празнина +qsts#:#shuffle_answers#:#Разбъркване на отговорите +qsts#:#suggested_learning_content#:#Add suggested solution###24 02 2009 new variable rating#:#rat_not_rated_yet#:#Not Rated Yet###12 06 2011 new variable rating#:#rat_nr_ratings#:#%s Ratings###12 06 2011 new variable rating#:#rat_one_rating#:#One Rating###12 06 2011 new variable @@ -16620,7 +16620,6 @@ survey#:#questionblock#:#Блок въпроси survey#:#questionblock_inserted#:#Question Block inserted###06 08 2009 new variable survey#:#questionblocks#:#Блокове въпроси survey#:#questionblocks_inserted#:#Question Blocks inserted###06 08 2009 new variable -survey#:#questions#:#Въпроси survey#:#questions_inserted#:#Въпросът(ите) вмъкнат(и)! survey#:#questions_removed#:#Въпросът(ите) и/или блокът(овете) въпроси премахнат(и)! survey#:#questiontype#:#Тип въпрос @@ -16921,6 +16920,7 @@ survey#:#svy_please_select_unused_codes#:#Please select at least one unused code survey#:#svy_print_hide_labels#:#Hide labels###31 08 2017 new variable survey#:#svy_print_show_labels#:#Show labels###31 08 2017 new variable survey#:#svy_privacy_info#:#Privacy###29 07 2022 new variable +survey#:#svy_questions#:#Въпроси survey#:#svy_rater#:#Rater###29 07 2022 new variable survey#:#svy_rater_see_app_info#:#The names of appraisees will be presented to raters to enable them evaluating the questions.###29 07 2022 new variable survey#:#svy_reminder_mail_template#:#Mail Template###25 10 2016 new variable diff --git a/lang/ilias_cs.lang b/lang/ilias_cs.lang index 0cd6fb6a5092..468be202fd77 100644 --- a/lang/ilias_cs.lang +++ b/lang/ilias_cs.lang @@ -422,7 +422,6 @@ adve#:#adve_use_tiny_mce#:#Zapnout TinyMCE pro editaci WYSIWYG assessment#:#activate_logging#:#Aktivovat záznam Test a Hodnocení assessment#:#activate_manual_scoring#:#Enable Manual Scoring###28 10 2024 new variable assessment#:#activate_manual_scoring_desc#:#Enables manual Scoring for all question types.###28 10 2024 new variable -assessment#:#addSuggestedSolution#:#Přidat doporučené řešení assessment#:#add_answers#:#Přidat odpovědi assessment#:#add_circle#:#Přidat kruhovou plochu assessment#:#add_gap#:#Přidat textovou mezeru @@ -447,7 +446,6 @@ assessment#:#answer_is_not_correct_but_positive#:#Obdržel/a jste body za Vaše assessment#:#answer_is_right#:#Vaše řešení je správné assessment#:#answer_is_wrong#:#Vaše řešení je chybné assessment#:#answer_of#:#Answer of###07 02 2020 new variable -assessment#:#answer_options#:#Možnosti odpovědi: assessment#:#answer_question#:#Odpovědět na otázku assessment#:#answer_text#:#Text odpovědi assessment#:#answer_types#:#Typy odpovědi @@ -501,7 +499,6 @@ assessment#:#ass_completion_by_submission#:#Splnit odevzdáním assessment#:#ass_completion_by_submission_info#:#Je-li zapnuto, odevzdání alespoň jednoho souboru způsobí splnění dané otázky s přidělením maximálního počtu bodů této otázky. Tento počet bodů může být později změněn manuálně. Změna tohoto nastavení nemá vliv na již odevzdaná řešení. assessment#:#ass_create_export_file_with_results#:#Vytvořit exportní soubor testu (vč. výsledků účastníků) assessment#:#ass_create_export_test_archive#:#Vytvořit archivní soubor testu -assessment#:#ass_create_question#:#Vytvořit otázku assessment#:#ass_imap_hint#:#Nápověda zobrazena jako nápovědná bublina assessment#:#ass_imap_map_file_not_readable#:#Nahranou obrazovou mapu nelze načíst. assessment#:#ass_imap_no_map_found#:#V nahrané obrazové mapě nelze najít žádný formulář. @@ -571,10 +568,6 @@ assessment#:#cloze_answer_text_info#:#Spaces preceding or following the answer t assessment#:#cloze_fixed_textlength#:#Délka textového pole assessment#:#cloze_fixed_textlength_description#:#Pokud vložíte hodnotu větší než 0, všechna textová pole textových i numerických mezer budou vytvářeny s pevnou délkou rovnou této hodnotě. assessment#:#cloze_gap_size_info#:#Pokud zadáte hodnotu větší než 0, bude toto textové pole mezery vytvořeno s pevnou délkou této hodnoty. Pokud nezadáte hodnotu, bude textové pole mezery vytvořeno s hodnotou globální pevné délky. -assessment#:#cloze_text#:#Doplňovaný text -assessment#:#cloze_textgap_case_insensitive#:#Nerozlišuje velká/malá písmena -assessment#:#cloze_textgap_case_sensitive#:#Rozlišuje velká/malá písmena -assessment#:#cloze_textgap_levenshtein_of#:#Vzdálenost Levenshtein %s assessment#:#code#:#Kód assessment#:#codebase#:#Databáze kódu assessment#:#concatenation#:#Sřetězení @@ -757,9 +750,7 @@ assessment#:#fq_formula_desc#:#Můžete zadat předem definované proměnné ($v assessment#:#fq_no_restriction_info#:#Desetinná místa i zlomky jsou přijaty jako vstup. assessment#:#fq_precision_info#:#Zadejte počet požadovaných desetinných míst. assessment#:#fq_question_desc#:#Můžete určit proměnné vložením $v1, $v2 ... $vn, výsledky vložením $r1, $r2 .... $rn na požadované místo v textu otázky. Klepněte na tlačítko "Rozbor otázky" k vytvoření ediovaního formuláře pro proměnné a výsledky. -assessment#:#gap#:#Mezera assessment#:#gap_combination#:#Kombinace mezer -assessment#:#gaps#:#Gaps###29 10 2025 new variable assessment#:#glossary_term#:#Pojem glosáře assessment#:#goto_first_question#:#Show First Question###29 10 2025 new variable assessment#:#grading_mark_msg#:#Vaše výsledná známka je: "[mark]" @@ -777,7 +768,6 @@ assessment#:#info_answer_type_change#:#Tato otázka již obsahuje obrázky. Nem assessment#:#info_text_upload#:#Vyberte soubor odpovědí, který chcete nahrát assessment#:#insert_after#:#Vložit za assessment#:#insert_before#:#Vložit před -assessment#:#insert_gap#:#Vložit rezervované místo assessment#:#interaction_type#:#Interaction Type###26 08 2024 new variable assessment#:#internal_links#:#Interní odkazy assessment#:#intprecision#:#Dělitelné @@ -889,7 +879,6 @@ assessment#:#logs_wrong_test_password_provided#:#Účastník zadal nesprávné h assessment#:#longmenu#:#Dlouhé menu assessment#:#longmenu_answeroptions_differ#:#This question does not work correctly, as there are not the same amount of gaps in the text as in the correction options.###26 08 2024 new variable assessment#:#longmenu_text#:#Text dlouhého menu -assessment#:#mainbar_button_label_questionlist#:#Questionlist###26 08 2024 new variable assessment#:#maintenance#:#Údržba assessment#:#manscoring#:#Manuální hodnocení assessment#:#manscoring_done#:#Vyhodnocení účastníci @@ -916,7 +905,6 @@ assessment#:#maximum_nr_of_tries_reached#:#Dosáhl/a jste maximální počet pok assessment#:#maximum_points#:#Maxium dostupných bodů assessment#:#maxsize#:#Maximální velkost ukládaného souboru assessment#:#maxsize_info#:#Vložit maximální velikost v bytech, která je povolena pro ukládané soubory. Pokud necháte toto pole prázdné, bude namísto něj nastavena maximální velikost této instalace. -assessment#:#min_auto_complete#:#Automatické dokončování assessment#:#min_ip_label#:#Lowest IP With Access###26 08 2024 new variable assessment#:#min_percentage_ne_0#:#Musíte definovat minimální procento od specifikace 0 procent! Schéma známkování nebylo uloženo. assessment#:#misc#:#Různé možnosti @@ -925,7 +913,6 @@ assessment#:#mode_onebyone#:#One by One###29 10 2025 new variable assessment#:#mode_question#:#Question oriented###29 10 2025 new variable assessment#:#mode_user#:#Participant oriented###29 10 2025 new variable assessment#:#msg_circle_added#:#Kruh přidán -assessment#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable assessment#:#msg_number_of_terms_too_low#:#Počet výrazů musí být větší, nebo roven počtu definicí. assessment#:#msg_poly_added#:#Polygon přidán assessment#:#msg_questions_moved#:#Otázky přesunuty @@ -984,7 +971,6 @@ assessment#:#order#:#Order###26 08 2024 new variable assessment#:#ordering_answer_sequence_info#:#Sekvence odpovědi Vámi zde definovaná bude považována za sekvenci správného řešení. assessment#:#ordertext#:#Řazení textu assessment#:#ordertext_info#:#Vložte prosím text, který má být řazen horizontálně. Řazený text bude oddělen pomocí značek mezer v textu. Pokud potřebujete jiné oddělení, můžete použít separátor %s k oddělení Vašich textových jednotek. -assessment#:#out_of_range#:#Mimo rozsah assessment#:#output#:#Výstup assessment#:#output_mode#:#Výstupní mód assessment#:#parseQuestion#:#Rozbor otázky @@ -1053,7 +1039,6 @@ assessment#:#qpl_bulk_save_add#:#Add###28 10 2024 new variable assessment#:#qpl_bulk_save_overwrite#:#Overwrite###28 10 2024 new variable assessment#:#qpl_bulkedit_success#:#Modifications saved.###28 10 2024 new variable assessment#:#qpl_cancel_skill_assigns_update#:#Zrušit -assessment#:#qpl_confirm_delete_questions#:#Jste si jist/a, že chcete smazat následující otázky? assessment#:#qpl_copy_insert_clipboard#:#Vybrané otázky jsou zkopírovány do schránky assessment#:#qpl_copy_select_none#:#Označte alespoň jednu otázku ke zkopírování do schránky assessment#:#qpl_delete_rbac_error#:#Nemáte oprávnění smazat tuto otázku! @@ -1106,7 +1091,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#Způsobilost assessment#:#qpl_qst_skl_usg_sklpnt_col#:#Celkový součet bodů způsobilosti podle způsobilosti assessment#:#qpl_question_is_in_use#:#Otázka, kterou upravujete, existuje v %s testech. Pokud tuto otázku změníte, NEZMĚNÍTE otázku(y) v testu(ech), protože systém vytváří kopii otázky, která je vložena do testu! assessment#:#qpl_questions_deleted#:#Otázky smazány. -assessment#:#qpl_reset_preview#:#Obnovit náhled assessment#:#qpl_save_skill_assigns_update#:#Uložit přiřazení způsobilosti assessment#:#qpl_settings_availability#:#Availability###26 08 2024 new variable assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#Pokud je zapnuto, pro filtrování jsou zobrazeny možné vytvořené taxonomie. @@ -1136,14 +1120,6 @@ assessment#:#qst_essay_chars_remaining#:#Remaining characters:###07 02 2020 new assessment#:#qst_essay_wordcounter_enabled#:#Count Words###07 02 2020 new variable assessment#:#qst_essay_wordcounter_enabled_info#:#The entered words are counted. The number of written words is shown to the participants below the text input field.###07 02 2020 new variable assessment#:#qst_essay_written_words#:#Number of entered words:###07 02 2020 new variable -assessment#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable -assessment#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable -assessment#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable -assessment#:#qst_lifecycle_final#:#Final###07 02 2020 new variable -assessment#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable -assessment#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable -assessment#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable -assessment#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable assessment#:#qst_nested_nested_answers_off#:#No indents, just order###29 07 2022 new variable assessment#:#qst_nested_nested_answers_on#:#Use indents in anwers###29 07 2022 new variable assessment#:#qst_nr_of_tries#:#Počet pokusů @@ -1167,17 +1143,14 @@ assessment#:#question_title#:#Název otázky assessment#:#question_type#:#Typ otázky assessment#:#questionpool_not_entered#:#Vložte prosím název zásobníku otázek! assessment#:#questionpool_not_selected#:#Please select a question pool!###07 02 2020 new variable -assessment#:#questions#:#Questions###26 08 2024 new variable assessment#:#questions_from#:#otázky z assessment#:#questions_per_page_view#:#Zobrazení stránky assessment#:#random_accept_sample#:#Akceptovat příklad assessment#:#random_another_sample#:#Vybrat jiný příklad assessment#:#random_selection#:#Náhodný výběr assessment#:#range#:#Rozmezí -assessment#:#range_lower_limit#:#Spodní hranice assessment#:#range_max#:#Rozmezí (Maximum) assessment#:#range_min#:#Rozmezí (Minimum) -assessment#:#range_upper_limit#:#Horní hranice assessment#:#rated_sign#:#Podpis assessment#:#rated_unit#:#Jednotka assessment#:#rated_value#:#Hodnota @@ -1253,7 +1226,6 @@ assessment#:#search_roles#:#Vyhledat role assessment#:#search_term#:#Vyhledat termín assessment#:#select_at_least_one_feedback_type_and_trigger#:#Please Select at least one type of feedback and a trigger.###26 08 2024 new variable assessment#:#select_at_least_one_lock_answer_type#:#Please select at least one type of answer lock.###29 10 2025 new variable -assessment#:#select_gap#:#Vybrat mezeru assessment#:#select_max_one_item#:#Vyberte prosím pouze jednu položku assessment#:#select_one_user#:#Vyberte prosím alespoň jednoho uživatele assessment#:#select_question#:#Select a Question###28 10 2024 new variable @@ -1280,7 +1252,6 @@ assessment#:#show_old_introduction#:#Show old introduction###26 08 2024 new vari assessment#:#show_pass_overview#:#Zobrazit přehled označených průchodů assessment#:#show_results#:#Show Results###28 10 2024 new variable assessment#:#show_user_answers#:#Zobrazit označené odpovědi uživatelů -assessment#:#shuffle_answers#:#Zamíchat odpovědi assessment#:#skip_question#:#Neodpovídat a Další assessment#:#solution#:#Solution###28 10 2024 new variable assessment#:#solutionText#:#Text @@ -13971,6 +13942,35 @@ qpl#:#qpl_page_type_qfbg#:#General Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qfbs#:#Special Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qht#:#Hint###29 07 2022 new variable qpl#:#qpl_page_type_qpl#:#Question Page###29 07 2022 new variable +qsts#:#answer_options#:#Možnosti odpovědi +qsts#:#cloze_text#:#Doplňovaný text +qsts#:#cloze_textgapcase_insensitive#:#Nerozlišuje velká/malá písmena +qsts#:#cloze_textgapcase_sensitive#:#Rozlišuje velká/malá písmena +qsts#:#cloze_textgaplevenshtein_of#:#Vzdálenost Levenshtein %s +qsts#:#confirm_delete_questions#:#Jste si jist/a, že chcete smazat následující otázky? +qsts#:#create_question#:#Vytvořit otázku +qsts#:#gap#:#Mezera +qsts#:#gaps#:#Gaps###29 10 2025 new variable +qsts#:#insert_gap#:#Vložit rezervované místo +qsts#:#min_auto_complete#:#Automatické dokončování +qsts#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable +qsts#:#out_of_range#:#Mimo rozsah +qsts#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable +qsts#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable +qsts#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable +qsts#:#qst_lifecycle_final#:#Final###07 02 2020 new variable +qsts#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable +qsts#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable +qsts#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable +qsts#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable +qsts#:#questionlist#:#Questionlist###26 08 2024 new variable +qsts#:#questions#:#Questions###26 08 2024 new variable +qsts#:#range_lower_limit#:#Spodní hranice +qsts#:#range_upper_limit#:#Horní hranice +qsts#:#reset_preview#:#Obnovit náhled +qsts#:#select_gap#:#Vybrat mezeru +qsts#:#shuffle_answers#:#Zamíchat odpovědi +qsts#:#suggested_learning_content#:#Přidat doporučené řešení rating#:#rat_not_rated_yet#:#Dosud nehodnoceno rating#:#rat_nr_ratings#:#%s Hodnocení rating#:#rat_one_rating#:#Jedno hodnocení @@ -16620,7 +16620,6 @@ survey#:#questionblock#:#Blok otázek survey#:#questionblock_inserted#:#Blok otázek vložen survey#:#questionblocks#:#Bloky otázek survey#:#questionblocks_inserted#:#Bloky otázek vloženy -survey#:#questions#:#Otázky survey#:#questions_inserted#:#Otázky vloženy survey#:#questions_removed#:#Otázky a/nebo bloky otázek odstraněny! survey#:#questiontype#:#Typ otázky @@ -16921,6 +16920,7 @@ survey#:#svy_please_select_unused_codes#:#Please select at least one unused code survey#:#svy_print_hide_labels#:#Skrýt štítky survey#:#svy_print_show_labels#:#Zobrazit štítky survey#:#svy_privacy_info#:#Privacy###29 07 2022 new variable +survey#:#svy_questions#:#Otázky survey#:#svy_rater#:#Rater###29 07 2022 new variable survey#:#svy_rater_see_app_info#:#The names of appraisees will be presented to raters to enable them evaluating the questions.###29 07 2022 new variable survey#:#svy_reminder_mail_template#:#Šablona pošty diff --git a/lang/ilias_da.lang b/lang/ilias_da.lang index 4a6807934292..bdff6e7db8e7 100644 --- a/lang/ilias_da.lang +++ b/lang/ilias_da.lang @@ -422,7 +422,6 @@ adve#:#adve_use_tiny_mce#:#Enable TinyMCE for WYSIWYG Editing###01 10 2011 new v assessment#:#activate_logging#:#Aktiver logging for Test & Assessment assessment#:#activate_manual_scoring#:#Enable Manual Scoring###28 10 2024 new variable assessment#:#activate_manual_scoring_desc#:#Enables manual Scoring for all question types.###28 10 2024 new variable -assessment#:#addSuggestedSolution#:#Opret løsningsforslag assessment#:#add_answers#:#Add Antworten ###23 12 2015 new variable assessment#:#add_circle#:#Lav cirkelområde assessment#:#add_gap#:#Tilføj hul tekst @@ -447,7 +446,6 @@ assessment#:#answer_is_not_correct_but_positive#:#Du har fået point for din lø assessment#:#answer_is_right#:#Din løsning er rigtig assessment#:#answer_is_wrong#:#Din løsning er forkert assessment#:#answer_of#:#Answer of###07 02 2020 new variable -assessment#:#answer_options#:#Answer Options: ###23 12 2015 new variable assessment#:#answer_question#:#Answer Question###30 08 2015 new variable assessment#:#answer_text#:#Besvar tekst assessment#:#answer_types#:#Svartype @@ -501,7 +499,6 @@ assessment#:#ass_completion_by_submission#:#Completed by Submission###12 06 2011 assessment#:#ass_completion_by_submission_info#:#If enabled, the submission of at least one file causes the completion of this question by granting the maximum score for this question. The score could be manually changed later. Switching this setting does not effect already submitted solutions.###12 06 2011 new variable assessment#:#ass_create_export_file_with_results#:#Create Test Export File (incl. Participant Results)###25 10 2016 new variable assessment#:#ass_create_export_test_archive#:#Create Test Archive File###16 09 2013 new variable -assessment#:#ass_create_question#:#Create Question###16 09 2013 new variable assessment#:#ass_imap_hint#:#Hint to be shown as Tooltip###25 10 2016 new variable assessment#:#ass_imap_map_file_not_readable#:#The uploaded image map could not be read.###25 10 2016 new variable assessment#:#ass_imap_no_map_found#:#Could not find any form in the uploaded image map.###25 10 2016 new variable @@ -571,10 +568,6 @@ assessment#:#cloze_answer_text_info#:#Spaces preceding or following the answer t assessment#:#cloze_fixed_textlength#:#Længde på tekstfelt assessment#:#cloze_fixed_textlength_description#:#Hvis du indtaster en værdi der er større end 0 bliver alle numeriske og tekst huller den størrelse. assessment#:#cloze_gap_size_info#:#If you enter a value greater than 0, this gap text field will be created with the fixed length of this value. If you do not enter a value the gap text fiel will be created with the value of the global fixed length.###26 09 2014 new variable -assessment#:#cloze_text#:#Luk tekst -assessment#:#cloze_textgap_case_insensitive#:#Ingen forskel på store og små bogstaver -assessment#:#cloze_textgap_case_sensitive#:#Forskel på store og små bogstaver -assessment#:#cloze_textgap_levenshtein_of#:#Levenshtein afstand på %s assessment#:#code#:#Kode assessment#:#codebase#:#Codebase assessment#:#concatenation#:#Konkatnering @@ -757,9 +750,7 @@ assessment#:#fq_formula_desc#:#You may enter predefined variables ($v1 to $vn), assessment#:#fq_no_restriction_info#:#Both decimals and fractions are accepted as input.###26 03 2014 new variable assessment#:#fq_precision_info#:#Enter the number of desired decimal places.###26 03 2014 new variable assessment#:#fq_question_desc#:#You can define variables by inserting $v1, $v2 ... $vn, results by inserting $r1, $r2 .... $rn at the desired position in the question text. Click on the button "Parse Question" to create editing forms for variables and results.###06 11 2013 new variable -assessment#:#gap#:#Hul assessment#:#gap_combination#:#Gap Combination###26 09 2014 new variable -assessment#:#gaps#:#Gaps###29 10 2025 new variable assessment#:#glossary_term#:#Ordbogsforklaring assessment#:#goto_first_question#:#Show First Question###29 10 2025 new variable assessment#:#grading_mark_msg#:#Your resulting mark is: "[mark]"###26 09 2014 new variable @@ -777,7 +768,6 @@ assessment#:#info_answer_type_change#:#The question already contains images. You assessment#:#info_text_upload#:#Choose an answer file to upload###30 08 2015 new variable assessment#:#insert_after#:#Indsæt efter assessment#:#insert_before#:#Indsæt før -assessment#:#insert_gap#:#Insert Gap###11 10 2013 new variable assessment#:#interaction_type#:#Interaction Type###26 08 2024 new variable assessment#:#internal_links#:#Interne links assessment#:#intprecision#:#Divisible By###16 09 2013 new variable @@ -889,7 +879,6 @@ assessment#:#logs_wrong_test_password_provided#:#Participant entered wrong test assessment#:#longmenu#:#Longmenu###10 11 2018 new variable assessment#:#longmenu_answeroptions_differ#:#This question does not work correctly, as there are not the same amount of gaps in the text as in the correction options.###26 08 2024 new variable assessment#:#longmenu_text#:#Long Menu Text###30 08 2015 new variable -assessment#:#mainbar_button_label_questionlist#:#Questionlist###26 08 2024 new variable assessment#:#maintenance#:#Vedligeholdelse assessment#:#manscoring#:#Manuelle karakterer assessment#:#manscoring_done#:#Scored Participants###24 02 2009 new variable @@ -916,7 +905,6 @@ assessment#:#maximum_nr_of_tries_reached#:#Du har nået maximum antallet af fors assessment#:#maximum_points#:#Maksimalt opnåelige point assessment#:#maxsize#:#Maksimal størrelse på uploadede filer assessment#:#maxsize_info#:#Enter the maximum size in bytes that should be allowed for file uploads. If you leave this field empty, the maximum size of this installation will be chosen instead.###24 02 2009 new variable -assessment#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable assessment#:#min_ip_label#:#Lowest IP With Access###26 08 2024 new variable assessment#:#min_percentage_ne_0#:#Du skal angive et minimum procentdel af 0 procent! Karakterskemaet er ikke gemt! assessment#:#misc#:#Misc Options###16 09 2013 new variable @@ -925,7 +913,6 @@ assessment#:#mode_onebyone#:#One by One###29 10 2025 new variable assessment#:#mode_question#:#Question oriented###29 10 2025 new variable assessment#:#mode_user#:#Participant oriented###29 10 2025 new variable assessment#:#msg_circle_added#:#Cirkel tilføjet -assessment#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable assessment#:#msg_number_of_terms_too_low#:#The number of terms must be greater or equal to the number of definitions.###12 08 2009 new variable assessment#:#msg_poly_added#:#Polygon tilføjet assessment#:#msg_questions_moved#:#Spørgsmål flyttet @@ -984,7 +971,6 @@ assessment#:#order#:#Order###26 08 2024 new variable assessment#:#ordering_answer_sequence_info#:#Rækkefølgen ovenfor skal være den korrekte besvarelse. assessment#:#ordertext#:#Ordering Text###24 02 2009 new variable assessment#:#ordertext_info#:#Please enter the text that should be ordered horizontally. The ordering text will be separated by the whitespace signs in the text. If you need a different separation, you may use the separator %s to separate your text units.###24 02 2009 new variable -assessment#:#out_of_range#:#Out of range###27 01 2015 new variable assessment#:#output#:#Output###25 10 2006 new variable assessment#:#output_mode#:#Output mode assessment#:#parseQuestion#:#Parse Question###16 09 2013 new variable @@ -1053,7 +1039,6 @@ assessment#:#qpl_bulk_save_add#:#Add###28 10 2024 new variable assessment#:#qpl_bulk_save_overwrite#:#Overwrite###28 10 2024 new variable assessment#:#qpl_bulkedit_success#:#Modifications saved.###28 10 2024 new variable assessment#:#qpl_cancel_skill_assigns_update#:#Cancel###30 08 2015 new variable -assessment#:#qpl_confirm_delete_questions#:#Er du sikker på du vil slette følgende spørgsmål? Hvis du sletter låste spørgsmål vil resultater af alle test der bruger et låst spørgsmål også blive slettet. assessment#:#qpl_copy_insert_clipboard#:#The selected question(s) are copied to the clipboard assessment#:#qpl_copy_select_none#:#Please check at least one question to copy it to the clipboard assessment#:#qpl_delete_rbac_error#:#Du har ikke rettigheder til at slette dette spørgsmål! @@ -1106,7 +1091,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#Competence###30 08 2015 new variable assessment#:#qpl_qst_skl_usg_sklpnt_col#:#Total Sum of Competence-Points per Competence###30 08 2015 new variable assessment#:#qpl_question_is_in_use#:#Spørgsmålet du er ved at ændre findes i %s test. Hvis du ændrer spørgsmålet vil det ikke ændres i disse tests, fordi der laves en kopi af spørgsmålet når det tilføjes. assessment#:#qpl_questions_deleted#:#Spørgsmål slettet. -assessment#:#qpl_reset_preview#:#Reset Preview###26 09 2014 new variable assessment#:#qpl_save_skill_assigns_update#:#Save Competence Assignments###30 08 2015 new variable assessment#:#qpl_settings_availability#:#Availability###26 08 2024 new variable assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#When enabled, possibly created taxonomies are shown for filtering.###16 09 2013 new variable @@ -1136,14 +1120,6 @@ assessment#:#qst_essay_chars_remaining#:#Remaining characters:###07 02 2020 new assessment#:#qst_essay_wordcounter_enabled#:#Count Words###07 02 2020 new variable assessment#:#qst_essay_wordcounter_enabled_info#:#The entered words are counted. The number of written words is shown to the participants below the text input field.###07 02 2020 new variable assessment#:#qst_essay_written_words#:#Number of entered words:###07 02 2020 new variable -assessment#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable -assessment#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable -assessment#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable -assessment#:#qst_lifecycle_final#:#Final###07 02 2020 new variable -assessment#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable -assessment#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable -assessment#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable -assessment#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable assessment#:#qst_nested_nested_answers_off#:#No indents, just order###29 07 2022 new variable assessment#:#qst_nested_nested_answers_on#:#Use indents in anwers###29 07 2022 new variable assessment#:#qst_nr_of_tries#:#Antal forsøg @@ -1167,17 +1143,14 @@ assessment#:#question_title#:#Titel assessment#:#question_type#:#Spørgsmåls type assessment#:#questionpool_not_entered#:#Indtast venligst et navn for spørgsmålsgruppe! assessment#:#questionpool_not_selected#:#Please select a question pool!###07 02 2020 new variable -assessment#:#questions#:#Questions###26 08 2024 new variable assessment#:#questions_from#:#spørgsmål fra### assessment#:#questions_per_page_view#:#Sidevisning assessment#:#random_accept_sample#:#Accepter prøve assessment#:#random_another_sample#:#Få en anden prøve assessment#:#random_selection#:#Tilfældigt valg assessment#:#range#:#Range -assessment#:#range_lower_limit#:#Lower limit assessment#:#range_max#:#Range (Maximum)###16 09 2013 new variable assessment#:#range_min#:#Range (Minimum)###16 09 2013 new variable -assessment#:#range_upper_limit#:#Upper limit assessment#:#rated_sign#:#Sign###16 09 2013 new variable assessment#:#rated_unit#:#Unit###16 09 2013 new variable assessment#:#rated_value#:#Value###16 09 2013 new variable @@ -1253,7 +1226,6 @@ assessment#:#search_roles#:#Søg roller assessment#:#search_term#:#Søge kriterier assessment#:#select_at_least_one_feedback_type_and_trigger#:#Please Select at least one type of feedback and a trigger.###26 08 2024 new variable assessment#:#select_at_least_one_lock_answer_type#:#Please select at least one type of answer lock.###29 10 2025 new variable -assessment#:#select_gap#:#Vælg hul assessment#:#select_max_one_item#:#Vælg venligst kun en ting assessment#:#select_one_user#:#Vælg mindst en bruger assessment#:#select_question#:#Select a Question###28 10 2024 new variable @@ -1280,7 +1252,6 @@ assessment#:#show_old_introduction#:#Show old introduction###26 08 2024 new vari assessment#:#show_pass_overview#:#Vis overblik for de markerede besvarelser assessment#:#show_results#:#Show Results###28 10 2024 new variable assessment#:#show_user_answers#:#Vis de markerede besvarelser -assessment#:#shuffle_answers#:#Bland svar assessment#:#skip_question#:#Do not Answer and Next###08 10 2015 new variable assessment#:#solution#:#Solution###28 10 2024 new variable assessment#:#solutionText#:#Tekst @@ -13971,6 +13942,35 @@ qpl#:#qpl_page_type_qfbg#:#General Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qfbs#:#Special Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qht#:#Hint###29 07 2022 new variable qpl#:#qpl_page_type_qpl#:#Question Page###29 07 2022 new variable +qsts#:#answer_options#:#Answer Options ###23 12 2015 new variable +qsts#:#cloze_text#:#Luk tekst +qsts#:#cloze_textgapcase_insensitive#:#Ingen forskel på store og små bogstaver +qsts#:#cloze_textgapcase_sensitive#:#Forskel på store og små bogstaver +qsts#:#cloze_textgaplevenshtein_of#:#Levenshtein afstand på %s +qsts#:#confirm_delete_questions#:#Er du sikker på du vil slette følgende spørgsmål? Hvis du sletter låste spørgsmål vil resultater af alle test der bruger et låst spørgsmål også blive slettet. +qsts#:#create_question#:#Create Question###16 09 2013 new variable +qsts#:#gap#:#Hul +qsts#:#gaps#:#Gaps###29 10 2025 new variable +qsts#:#insert_gap#:#Insert Gap###11 10 2013 new variable +qsts#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable +qsts#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable +qsts#:#out_of_range#:#Out of range###27 01 2015 new variable +qsts#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable +qsts#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable +qsts#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable +qsts#:#qst_lifecycle_final#:#Final###07 02 2020 new variable +qsts#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable +qsts#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable +qsts#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable +qsts#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable +qsts#:#questionlist#:#Questionlist###26 08 2024 new variable +qsts#:#questions#:#Questions###26 08 2024 new variable +qsts#:#range_lower_limit#:#Lower limit +qsts#:#range_upper_limit#:#Upper limit +qsts#:#reset_preview#:#Reset Preview###26 09 2014 new variable +qsts#:#select_gap#:#Vælg hul +qsts#:#shuffle_answers#:#Bland svar +qsts#:#suggested_learning_content#:#Opret løsningsforslag rating#:#rat_not_rated_yet#:#Not Rated Yet###12 06 2011 new variable rating#:#rat_nr_ratings#:#%s Ratings###12 06 2011 new variable rating#:#rat_one_rating#:#One Rating###12 06 2011 new variable @@ -16620,7 +16620,6 @@ survey#:#questionblock#:#Spørgsmåls blok survey#:#questionblock_inserted#:#Blok indsat survey#:#questionblocks#:#Spørgsmåls blok survey#:#questionblocks_inserted#:#Spørgsmålsblokke indsat -survey#:#questions#:#Spørgsmål survey#:#questions_inserted#:#Spørgsmål indsat! survey#:#questions_removed#:#Spørgsmål og/eller spørgsmålsblok fjernet! survey#:#questiontype#:#Spørgsmåls type @@ -16921,6 +16920,7 @@ survey#:#svy_please_select_unused_codes#:#Please select at least one unused code survey#:#svy_print_hide_labels#:#Hide labels###31 08 2017 new variable survey#:#svy_print_show_labels#:#Show labels###31 08 2017 new variable survey#:#svy_privacy_info#:#Privacy###29 07 2022 new variable +survey#:#svy_questions#:#Spørgsmål survey#:#svy_rater#:#Rater###29 07 2022 new variable survey#:#svy_rater_see_app_info#:#The names of appraisees will be presented to raters to enable them evaluating the questions.###29 07 2022 new variable survey#:#svy_reminder_mail_template#:#Mail Template###25 10 2016 new variable diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang index 476a103a5fe0..459e1c929b60 100644 --- a/lang/ilias_de.lang +++ b/lang/ilias_de.lang @@ -422,7 +422,6 @@ adve#:#adve_use_tiny_mce#:#TinyMCE-Editor für WYSIWYG-Bearbeitung aktivieren assessment#:#activate_logging#:#Protokollieren des Test und Assessments aktivieren assessment#:#activate_manual_scoring#:#Bewertung aktivieren assessment#:#activate_manual_scoring_desc#:#Aktiviert die Bewertung für alle Fragentypen -assessment#:#addSuggestedSolution#:#Inhalte zur Wiederholung assessment#:#add_answers#:#Antworten hinzufügen assessment#:#add_circle#:#Kreis hinzufügen assessment#:#add_gap#:#Lückentext hinzufügen @@ -447,7 +446,6 @@ assessment#:#answer_is_not_correct_but_positive#:#Sie haben Punkte für Ihre Lö assessment#:#answer_is_right#:#Ihre Lösung ist korrekt. assessment#:#answer_is_wrong#:#Ihre Lösung ist falsch. assessment#:#answer_of#:#Antwort von -assessment#:#answer_options#:#Antwort-Optionen: assessment#:#answer_question#:#Frage beantworten assessment#:#answer_text#:#Antwort-Text assessment#:#answer_types#:#Antwort-Editor @@ -501,7 +499,6 @@ assessment#:#ass_completion_by_submission#:#Bestehen durch Abgabe assessment#:#ass_completion_by_submission_info#:#Falls aktiviert, führt die Abgabe einer Lösungsdatei zur Vergabe der Maximalpunktzahl für diese Frage. Die Bewertung kann jederzeit manuell angepasst werden. Das Ändern dieser Einstellung hat keine nachträglichen Auswirkungen auf bereits eingereichte Lösungen. assessment#:#ass_create_export_file_with_results#:#inkl. Teilnehmerergebnisse assessment#:#ass_create_export_test_archive#:#als Archivdatei -assessment#:#ass_create_question#:#Frage erstellen assessment#:#ass_imap_hint#:#Hinweis (angezeigt als Tooltipp) assessment#:#ass_imap_map_file_not_readable#:#Die hochgeladene Imagemap kann nicht gelesen werden. assessment#:#ass_imap_no_map_found#:#In der hochgeladenen Imagemap konnte keine unterstützte Form gefunden werden. @@ -571,10 +568,6 @@ assessment#:#cloze_answer_text_info#:#Vorangestellte oder nachfolgende Leerzeich assessment#:#cloze_fixed_textlength#:#Länge des Textfeldes assessment#:#cloze_fixed_textlength_description#:#Wenn Sie hier einen Wert eintragen, werden Textlücken, welche keinen eigenen Wert für eine maximale Länge definieren, sowie numerische Lücken mit dieser Länge erzeugt, so dass es nicht möglich ist eine größere Anzahl an Zeichen einzugeben. Für numerische Lücken ist darüber hinaus zu beachten, dass das Dezimaltrennzeichen dabei mit gezählt wird. assessment#:#cloze_gap_size_info#:#Ist ein Wert größer 0 eingetragen, wird diese Lücke mit der hier angegebenen Länge erzeugt. Ist kein Wert angegeben, wird diese Lücke mit der global angegebenen Textfeldlänge erzeugt. -assessment#:#cloze_text#:#Lückentextfrage -assessment#:#cloze_textgap_case_insensitive#:#Zwischen Groß- und Kleinschreibung wird nicht unterschieden -assessment#:#cloze_textgap_case_sensitive#:#Groß- und Kleinschreibung beachten -assessment#:#cloze_textgap_levenshtein_of#:#Levenshtein-Abstand von %s assessment#:#code#:#Code assessment#:#codebase#:#Codebase assessment#:#concatenation#:#Verknüpfung @@ -757,9 +750,7 @@ assessment#:#fq_formula_desc#:#Erlaubt ist die Verwendung von bereits definierte assessment#:#fq_no_restriction_info#:#Sowohl Dezimalzahlen als auch Brüche werden als Eingabe akzeptiert. assessment#:#fq_precision_info#:#Geben Sie hier die Anzahl der gewünschten Nachkommastellen an. assessment#:#fq_question_desc#:#Sie definieren Variablen durch die Angabe von $v1, $v2 ... $vn, Ergebnisfelder mit $r1, $r2 .... $rn an den gewünschten Positionen im Text. Klicken Sie dann auf die Schaltfläche „Frage analysieren“, um Bearbeitungsformulare für alle Variablen und Ergebnisse zu erzeugen. -assessment#:#gap#:#Lücke assessment#:#gap_combination#:#Lückentext-Kombination -assessment#:#gaps#:#Lücken assessment#:#glossary_term#:#Glossarbegriff assessment#:#goto_first_question#:#Zur ersten Frage assessment#:#grading_mark_msg#:#Sie haben die Note "[mark]" erzielt. @@ -777,7 +768,6 @@ assessment#:#info_answer_type_change#:#Die Frage beinhaltet bereits Bilder. Der assessment#:#info_text_upload#:#Wählen Sie eine Textdatei (UTF-8) mit Antworten zum Hochladen aus. assessment#:#insert_after#:#Einfügen hinter assessment#:#insert_before#:#Einfügen vor -assessment#:#insert_gap#:#Lücke einfügen assessment#:#interaction_type#:#Interaktion assessment#:#internal_links#:#Interne Verweise assessment#:#intprecision#:#Teilbar durch @@ -889,7 +879,6 @@ assessment#:#logs_wrong_test_password_provided#:#Teilnehmer hat falsches Testpas assessment#:#longmenu#:#Longmenu assessment#:#longmenu_answeroptions_differ#:#Diese Frage funktioniert nicht richtig, denn sie hat nicht die gleiche Anzahl Lücken im Text wie in den Antwortoptionen. assessment#:#longmenu_text#:#„Long Menu“-Text -assessment#:#mainbar_button_label_questionlist#:#Fragenliste assessment#:#maintenance#:#Wartung assessment#:#manscoring#:#Bewertung assessment#:#manscoring_done#:#Bereits bewertete Teilnehmer @@ -916,7 +905,6 @@ assessment#:#maximum_nr_of_tries_reached#:#Sie haben die maximale Anzahl von Tes assessment#:#maximum_points#:#Maximal erreichbare Punktezahl assessment#:#maxsize#:#Maximale Dateigröße assessment#:#maxsize_info#:#Geben Sie die maximale Größe in Bytes an, die eine hochgeladene Datei haben darf. Wenn Sie das Feld leer lassen, wird die Einstellung des zugrunde liegenden Systems verwendet. -assessment#:#min_auto_complete#:#Autovervollständigung assessment#:#min_ip_label#:#Kleinste IP mit Zugriff assessment#:#min_percentage_ne_0#:#Das Notenschema muss mindestens eine Stufe mit einem minimalen Prozentsatz von 0 Prozent enthalten. assessment#:#misc#:#Verschiedene Optionen @@ -925,7 +913,6 @@ assessment#:#mode_onebyone#:#Nacheinander assessment#:#mode_question#:#Nach Frage assessment#:#mode_user#:#Nach Teilnehmer assessment#:#msg_circle_added#:#Kreis hinzugefügt -assessment#:#msg_no_questions_selected#:#Keine Fragen ausgewählt. assessment#:#msg_number_of_terms_too_low#:#Die Anzahl der Terme muss größer oder gleich der Anzahl der Definitionen sein. assessment#:#msg_poly_added#:#Polygon hinzugefügt assessment#:#msg_questions_moved#:#Fragen verschoben @@ -984,7 +971,6 @@ assessment#:#order#:#Sortierung assessment#:#ordering_answer_sequence_info#:#Die hier definierte Reihenfolge der Antworten wird als korrekte Lösungsreihenfolge verwendet. assessment#:#ordertext#:#Anzuordnender Text assessment#:#ordertext_info#:#Bitte geben Sie den Text in der Reihenfolge ein, in der er horizontal angeordnet werden soll. Die einzelnen Bestandteile werden durch Leerzeichen getrennt. Wenn Sie eine abweichende Trennung benötigen, verwenden Sie bitte den Trenner %s anstelle der Leerzeichen. -assessment#:#out_of_range#:#Ausserhalb des Bereichs assessment#:#output#:#Ausgabe assessment#:#output_mode#:#Ausgabemodus assessment#:#parseQuestion#:#Frage analysieren @@ -1053,7 +1039,6 @@ assessment#:#qpl_bulk_save_add#:#Hinzufügen assessment#:#qpl_bulk_save_overwrite#:#Überschreiben assessment#:#qpl_bulkedit_success#:#Änderungen gespeichert. assessment#:#qpl_cancel_skill_assigns_update#:#Abbrechen -assessment#:#qpl_confirm_delete_questions#:#Sind Sie sicher, dass Sie die folgenden Fragen entfernen wollen? assessment#:#qpl_copy_insert_clipboard#:#Die ausgewählten Fragen wurden in die Zwischenablage kopiert assessment#:#qpl_copy_select_none#:#Bitte wählen Sie mindestens eine Frage aus, um diese in die Zwischenablage zu kopieren! assessment#:#qpl_delete_rbac_error#:#Sie haben keine Berechtigung, diese Frage zu entfernen! @@ -1106,7 +1091,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#Kompetenz assessment#:#qpl_qst_skl_usg_sklpnt_col#:#Summe aller Kompetenzpunkte je Kompetenz assessment#:#qpl_question_is_in_use#:#Die Frage, die Sie jetzt bearbeiten möchten, existiert bereits in %s Test(s). Wenn Sie diese Frage jetzt verändern, so hat das KEINE Auswirkungen auf bereits in Tests eingebundene Fragen, da das System automatisch eine Kopie der Frage anlegt, wenn diese in einen Test eingebunden wird! assessment#:#qpl_questions_deleted#:#Fragen gelöscht -assessment#:#qpl_reset_preview#:#Vorschau zurücksetzen assessment#:#qpl_save_skill_assigns_update#:#Kompetenzzuweisung speichern assessment#:#qpl_settings_availability#:#Verfügbarkeit assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#Vorhandene Taxonomien können zum Filtern der Fragen genutzt werden. @@ -1136,14 +1120,6 @@ assessment#:#qst_essay_chars_remaining#:#Noch übrige Zeichen: assessment#:#qst_essay_wordcounter_enabled#:#Wörter zählen assessment#:#qst_essay_wordcounter_enabled_info#:#Die eingegebenen Wörter werden gezählt. Die Anzahl der Wörter wird den Teilnehmern unterhalb des Texteingabefeldes angezeigt. assessment#:#qst_essay_written_words#:#Anzahl der eingegebenen Wörter: -assessment#:#qst_lifecycle#:#Lebenszyklus -assessment#:#qst_lifecycle_draft#:#Entwurf -assessment#:#qst_lifecycle_filter_all#:#Alle Lebenszyklen -assessment#:#qst_lifecycle_final#:#Endgültig -assessment#:#qst_lifecycle_outdated#:#Veraltet -assessment#:#qst_lifecycle_rejected#:#Abgelehnt -assessment#:#qst_lifecycle_review#:#Überarbeitung notwendig -assessment#:#qst_lifecycle_sharable#:#Verteilbar assessment#:#qst_nested_nested_answers_off#:#Ohne Einrückung assessment#:#qst_nested_nested_answers_on#:#Mit Einrückung assessment#:#qst_nr_of_tries#:#Anzahl der Versuche @@ -1168,17 +1144,14 @@ assessment#:#question_type#:#Fragetyp assessment#:#questionlist_cannot_be_altered#:#Die Fragenliste kann nicht verändert werden da der Test Teilnehmerdatensätze enthält. assessment#:#questionpool_not_entered#:#Bitte geben Sie einen Namen für den Fragenpool an! assessment#:#questionpool_not_selected#:#Bitte wählen Sie einen Fragenpool aus! -assessment#:#questions#:#Fragen assessment#:#questions_from#:#Fragen aus assessment#:#questions_per_page_view#:#Seitenansicht assessment#:#random_accept_sample#:#Zusammenstellung akzeptieren assessment#:#random_another_sample#:#Neue Zusammenstellung assessment#:#random_selection#:#Zufällige Auswahl assessment#:#range#:#Bereich -assessment#:#range_lower_limit#:#Untere Schranke assessment#:#range_max#:#Bereich (Maximum) assessment#:#range_min#:#Bereich (Minimum) -assessment#:#range_upper_limit#:#Obere Schranke assessment#:#rated_sign#:#Vorzeichen assessment#:#rated_unit#:#Einheit assessment#:#rated_value#:#Wert @@ -1254,7 +1227,6 @@ assessment#:#search_roles#:#nach Rollen assessment#:#search_term#:#Suchbegriff assessment#:#select_at_least_one_feedback_type_and_trigger#:#Bitte mindestens eine Art der Rückmeldung und einen Auslöser auswählen. assessment#:#select_at_least_one_lock_answer_type#:#Bitte mindestens eine Art Antworten festzuschreiben auswählen. -assessment#:#select_gap#:#Auswahl-Lücke assessment#:#select_max_one_item#:#Bitte wählen Sie nur ein Objekt aus! assessment#:#select_one_user#:#Bitte wählen Sie mindestens einen Benutzer aus! assessment#:#select_question#:#Frage auswählen @@ -1281,7 +1253,6 @@ assessment#:#show_old_introduction#:#Alten Einleitungstext anzeigen assessment#:#show_pass_overview#:#Ergebnisübersicht (bewerteter Testdurchlauf) assessment#:#show_results#:#Testergebnisse anzeigen assessment#:#show_user_answers#:#Antworten (bewerteter Testdurchlauf) -assessment#:#shuffle_answers#:#Antworten mischen assessment#:#skip_question#:#Nicht antworten und weiter assessment#:#solution#:#Lösungen assessment#:#solutionText#:#Text @@ -5107,6 +5078,8 @@ common#:#obj_ps_desc#:#Globale Einstellungen für Datenschutz und Sicherheit common#:#obj_qpl#:#Fragenpool für Tests common#:#obj_qpl_duplicate#:#Fragenpool für Tests kopieren common#:#obj_qpl_select#:#-- Bitte wählen Sie einen Fragenpool für Tests aus -- +common#:#obj_qsts#:#Fragen +common#:#obj_qsts_desc#:#Globale Einstellungen für Fragen common#:#obj_rcat#:#ECS-Kategorie common#:#obj_rcrs#:#ECS-Kurs common#:#obj_recf#:#Wiederhergestellte Objekte @@ -14132,6 +14105,113 @@ qpl#:#qpl_page_type_qfbg#:#Generelles Feedback qpl#:#qpl_page_type_qfbs#:#Spezielles Feedback qpl#:#qpl_page_type_qht#:#Hinweis qpl#:#qpl_page_type_qpl#:#Fragenseite +qsts#:#above_range#:#Oberhalb des Bereichs +qsts#:#add_gap_combination#:#Lückentext-Kombination hinzufügen +qsts#:#answer_form#:#Antwortformular +qsts#:#answer_options#:#Antwort-Optionen +qsts#:#answer_options_awarding_points#:#Punktevergebende Antwort-Optionen +qsts#:#answer_options_must_be_unique#:#Antwort-Optionen müssen einzigartig sein. +qsts#:#async_view#:#Asynchrone Ansicht +qsts#:#at_least_one_gap_positiv_points#:#Mindestens eine Lücke muss Punkte geben. +qsts#:#available_points#:#Maximal erreichbare Punkte +qsts#:#awarded_points#:#Erhaltene Punkte +qsts#:#basic_answer_form_properties#:#Grundlegende Eigenschaften des Antwortformulars +qsts#:#below_range#:#Unterhalb des Bereichs +qsts#:#best_response#:#Beste Antwort +qsts#:#best_response_given#:#Dies ist die bestmögliche Antwort. +qsts#:#between#:#Zwischen %s und %s +qsts#:#broken_answer_form#:#Ungültiges Antwortformular +qsts#:#byline_create_mode_full#:#Der Seiteneditor wird verwendet. Der Fragentext kann Formatierungen und mehrere Antwortformulare enthalten. +qsts#:#byline_create_mode_simple#:#Es wird nur ein Textfeld zur Eingabe des Fragentexts angezeigt. Der Fragetext kann keine Formatierungen enthalten und nur ein Antwortformular kann erstellt werden. Beim Abschluss des Erstellprozesses wird ein Knopf angeboten, der es erlaubt beim Speichern gleich die nächste Frage zu erstellen. +qsts#:#cloze#:#Lückentext +qsts#:#cloze_enable_gap_combinations#:#Enable Gap Combination +qsts#:#cloze_text#:#Lückentextfrage +qsts#:#cloze_textgap_case_insensitive#:#Zwischen Groß- und Kleinschreibung wird nicht unterschieden +qsts#:#cloze_textgap_case_sensitive#:#Groß- und Kleinschreibung beachten +qsts#:#cloze_textgap_levenshtein_of#:#Levenshtein-Abstand von %s +qsts#:#combination_needs_more_than_one#:#Eine Kombination von Lücken muss mehr als eine Lücke enthalten. +qsts#:#confirm_delete_feedback#:#Soll diese Rückmeldung wirklich gelöscht werden? +qsts#:#confirm_delete_questions#:#Sind Sie sicher, dass Sie die folgenden Fragen entfernen wollen? +qsts#:#confirm_remove_gaps#:#Es wurden Lücken aus dem Lückentext entfernt. Sollen diese nun definitiv entfernt und alle dazugehörigen Informationen gelöscht werden. +qsts#:#cont_ed_insert_answf#:#Antwortformular einfügen +qsts#:#contained_answer_form_types#:#Enthaltene Antwortformulartypen +qsts#:#contains_answer_form_types#:#Enthält Antwortformulartypen +qsts#:#create_answer_form#:#Antwortformular erstellen +qsts#:#create_feedback#:#Rückmeldung erstellen +qsts#:#create_mode_full#:#Vollständig +qsts#:#create_mode_simple#:#Einfach +qsts#:#create_question#:#Frage erstellen +qsts#:#default_user_settings#:#Standard Einstellungen +qsts#:#default_view#:#Standard Ansicht +qsts#:#delete_combination#:#Delete Combination of Gaps +qsts#:#delete_responses#:#Antworten löschen +qsts#:#disable_marking_allowing_partial_points#:#Bewertung deaktivieren +qsts#:#disable_suggested_learning_content#:#Inhalte zur Wiederholung deaktivieren +qsts#:#disable_text_feedback#:#Rückmeldungen deaktivieren +qsts#:#edit_answer_options#:#Antwort-Optionen bearbeiten +qsts#:#edit_available_points#:#Verfügbare Punkte bearbeiten +qsts#:#edit_basic_answer_form_properties#:#Grundlegende Eigenschaften des Antwortformulars bearbeiten +qsts#:#edit_basic_question_properties#:#Grundeinstellungen der Frage bearbeiten +qsts#:#edit_feedback#:#eedback bearbeiten +qsts#:#edit_gaps#:#Lücken bearbeiten +qsts#:#edit_generic_feedback#:#Rückmeldung basierend auf den erreichten Punkten +qsts#:#edit_points#:#Punkte bearbeiten +qsts#:#edit_suggested_learning_content#:#Inhalt zur Wiederholung bearbeiten +qsts#:#enable_marking_allowing_partial_points#:#Bewertung aktivieren +qsts#:#enable_suggested_learning_content#:#Inhalte zur Wiederholung +qsts#:#enable_text_feedback#:#Rückmeldungen aktivieren +qsts#:#equal#:#Gleich %s +qsts#:#gap#:#Lücke +qsts#:#gap_combination_already_exists#:#Diese Kombination von Lücken existiert bereits. +qsts#:#gap_combinations#:#Lückentext-Kombinationen +qsts#:#gap_type#:#Art der Lücke +qsts#:#gaps#:#Lücken +qsts#:#insert_gap#:#Lücke einfügen +qsts#:#insert_legacy_texts#:#Texte aus Altdaten importieren +qsts#:#insert_legacy_texts_info#:#Es gibt Texte, die aus einer älteren Version importiert wurden. Diese Texte können nicht direkt bearbeitet werden, sie können jedoch in das Formulare importiert werden. Dabei gehen alle Formatierungen verloren. +qsts#:#legacy_text_cannot_be_edited#:#Der Text stammt aus einer überführten Frage und kann nicht geändert werden. +qsts#:#long_menu_gap#:#Longmenu +qsts#:#max_characters#:#Maximale Zeichenanzahl +qsts#:#min_auto_complete#:#Autovervollständigung +qsts#:#msg_no_questions_selected#:#Keine Fragen ausgewählt. +qsts#:#no_best_response_available#:#Keine beste Antwort definiert +qsts#:#no_gaps#:#Der eingegebene Text enthält keine Lücken. +qsts#:#no_response#:#Keine Antwort +qsts#:#no_response_given#:#Keine Antwort +qsts#:#other_response#:#Nicht die Beste Antwort +qsts#:#other_response_given#:#Dies ist nicht die bestmögliche Antwort. +qsts#:#out_of_range#:#Ausserhalb des Bereichs +qsts#:#qst_lifecycle#:#Lebenszyklus +qsts#:#qst_lifecycle_draft#:#Entwurf +qsts#:#qst_lifecycle_filter_all#:#Alle Lebenszyklen +qsts#:#qst_lifecycle_final#:#Endgültig +qsts#:#qst_lifecycle_outdated#:#Veraltet +qsts#:#qst_lifecycle_rejected#:#Abgelehnt +qsts#:#qst_lifecycle_review#:#Überarbeitung notwendig +qsts#:#qst_lifecycle_sharable#:#Verteilbar +qsts#:#qst_remarks#:#Bemerkungen +qsts#:#question_create_mode#:#Art der Fragenerstellung +qsts#:#question_text#:#Fragentext +qsts#:#questionlist#:#Fragenliste +qsts#:#questions#:#Fragen +qsts#:#range_lower_limit#:#Untere Schranke +qsts#:#range_upper_limit#:#Obere Schranke +qsts#:#reset_preview#:#Vorschau zurücksetzen +qsts#:#save_and_new#:#Speichern und weitere Frage erstellen +qsts#:#score_all#:#Alle Antworten bewerten +qsts#:#score_distinct#:#Gleiche Antworten nur einmal bewerten +qsts#:#scoring_of_identical_responses#:#Bewertung gleicher Antworten +qsts#:#select_answer_form_type#:#Art des Antwortformulars auswählen +qsts#:#select_gap#:#Auswahl-Lücke +qsts#:#select_gaps_for_combination#:#Lücken zur Kombination auswählen +qsts#:#shuffle_answers#:#Antworten mischen +qsts#:#specific_feedback#:#Rückmeldung basierend auf der gewählten Antwortoption +qsts#:#step_size#:#Präzision +qsts#:#suggested_learning_content#:#Inhalte zur Wiederholung +qsts#:#text_matching_method#:#Textvergleichsmethode +qsts#:#upload_answer_options#:#Antwort-Optionen hochladen +qsts#:#upload_answer_options_info#:#Es kann eine Text-Datei hochgeladen werden, die eine Liste von Antwort-Optionen enthält. Die Datei muss eine Liste von Antwort-Optionen enthalten, wobei jede Antwort-Option auf einer neuen Zeile stehen muss. Die Antwort-Optionen werden zur bereits bestehenden Liste hinzugefügt. +qsts#:#upper_limit_bigger_than_lower#:#Die obere Schranke muss sowohl grösser sein als die untere als auch als die Präzision. rating#:#rat_not_rated_yet#:#Noch nicht bewertet rating#:#rat_nr_ratings#:#%s Bewertungen rating#:#rat_one_rating#:#Eine Bewertung @@ -16783,7 +16863,6 @@ survey#:#questionblock#:#Fragenblock survey#:#questionblock_inserted#:#Fragenblock eingefügt survey#:#questionblocks#:#Fragenblöcke survey#:#questionblocks_inserted#:#Fragenblöcke eingefügt -survey#:#questions#:#Fragen survey#:#questions_inserted#:#Frage(n) hinzugefügt. survey#:#questions_removed#:#Fragen und/oder Fragenblock entfernt! survey#:#questiontype#:#Fragetyp @@ -17084,6 +17163,7 @@ survey#:#svy_please_select_unused_codes#:#Bitte wählen Sie zumindest einen unge survey#:#svy_print_hide_labels#:#Labels ausblenden survey#:#svy_print_show_labels#:#Labels anzeigen survey#:#svy_privacy_info#:#Persönliche Daten +survey#:#svy_questions#:#Fragen survey#:#svy_rater#:#Feedback-Geber survey#:#svy_rater_see_app_info#:#Den Feedback-Gebern werden die Namen der Feedback-Nehmern angezeigt, damit die Fragen personenbezogen beantwortet werden können. survey#:#svy_reminder_mail_template#:#Mail-Vorlage diff --git a/lang/ilias_el.lang b/lang/ilias_el.lang index 0983be59ab38..e114c3aaab9d 100644 --- a/lang/ilias_el.lang +++ b/lang/ilias_el.lang @@ -423,7 +423,6 @@ adve#:#adve_use_tiny_mce#:#Enable TinyMCE for WYSIWYG Editing###01 10 2011 new v assessment#:#activate_logging#:#Ενεργοποίηση καταγραφής διαγωνίσματος & αξιολόγησης assessment#:#activate_manual_scoring#:#Enable Manual Scoring###28 10 2024 new variable assessment#:#activate_manual_scoring_desc#:#Enables manual Scoring for all question types.###28 10 2024 new variable -assessment#:#addSuggestedSolution#:#Add suggested solution###30 04 2009 new variable assessment#:#add_answers#:#Add Antworten ###23 12 2015 new variable assessment#:#add_circle#:#Add circle area###24 07 2009 new variable assessment#:#add_gap#:#Προσθήκη κειμένου κενών @@ -448,7 +447,6 @@ assessment#:#answer_is_not_correct_but_positive#:#Παίρνετε βαθμού assessment#:#answer_is_right#:#Η λύση σας είναι σωστή assessment#:#answer_is_wrong#:#Η λύση σας είναι λάθος assessment#:#answer_of#:#Answer of###07 02 2020 new variable -assessment#:#answer_options#:#Answer Options: ###23 12 2015 new variable assessment#:#answer_question#:#Answer Question###30 08 2015 new variable assessment#:#answer_text#:#Κείμενο απάντησης assessment#:#answer_types#:#Answer Types###24 07 2009 new variable @@ -502,7 +500,6 @@ assessment#:#ass_completion_by_submission#:#Completed by Submission###12 06 2011 assessment#:#ass_completion_by_submission_info#:#If enabled, the submission of at least one file causes the completion of this question by granting the maximum score for this question. The score could be manually changed later. Switching this setting does not effect already submitted solutions.###12 06 2011 new variable assessment#:#ass_create_export_file_with_results#:#Create Test Export File (incl. Participant Results)###25 10 2016 new variable assessment#:#ass_create_export_test_archive#:#Create Test Archive File###24 10 2013 new variable -assessment#:#ass_create_question#:#Create Question###24 10 2013 new variable assessment#:#ass_imap_hint#:#Hint to be shown as Tooltip###25 10 2016 new variable assessment#:#ass_imap_map_file_not_readable#:#The uploaded image map could not be read.###25 10 2016 new variable assessment#:#ass_imap_no_map_found#:#Could not find any form in the uploaded image map.###25 10 2016 new variable @@ -572,10 +569,6 @@ assessment#:#cloze_answer_text_info#:#Spaces preceding or following the answer t assessment#:#cloze_fixed_textlength#:#Text Field Length assessment#:#cloze_fixed_textlength_description#:#Εαν εισάγετε τιμή μεγαλύτερη του 0, όλα τα κείμενα και οι αριθμητικές τιμές θα δημιουργηθούν μέ αυτόν ώς μέγιστο αριθμό επιτρεπόμενο χαρακτήρων assessment#:#cloze_gap_size_info#:#If you enter a value greater than 0, this gap text field will be created with the fixed length of this value. If you do not enter a value the gap text fiel will be created with the value of the global fixed length.###26 09 2014 new variable -assessment#:#cloze_text#:#Κείμενο με κενά -assessment#:#cloze_textgap_case_insensitive#:#Χωρίς ταίριασμα πεζών-κεφαλαίων -assessment#:#cloze_textgap_case_sensitive#:#Με ταίριασμα πεζών-κεφαλαίων -assessment#:#cloze_textgap_levenshtein_of#:#Levenshtein απόσταση του %s assessment#:#code#:#Κώδικας assessment#:#codebase#:#Κωδικοσελίδα assessment#:#concatenation#:#Συγχώνευση @@ -758,9 +751,7 @@ assessment#:#fq_formula_desc#:#You may enter predefined variables ($v1 to $vn), assessment#:#fq_no_restriction_info#:#Both decimals and fractions are accepted as input.###26 03 2014 new variable assessment#:#fq_precision_info#:#Enter the number of desired decimal places.###26 03 2014 new variable assessment#:#fq_question_desc#:#You can define variables by inserting $v1, $v2 ... $vn, results by inserting $r1, $r2 .... $rn at the desired position in the question text. Click on the button "Parse Question" to create editing forms for variables and results.###06 11 2013 new variable -assessment#:#gap#:#Κενό assessment#:#gap_combination#:#Gap Combination###26 09 2014 new variable -assessment#:#gaps#:#Gaps###29 10 2025 new variable assessment#:#glossary_term#:#Όρος γλωσσαρίου assessment#:#goto_first_question#:#Show First Question###29 10 2025 new variable assessment#:#grading_mark_msg#:#Your resulting mark is: "[mark]"###26 09 2014 new variable @@ -778,7 +769,6 @@ assessment#:#info_answer_type_change#:#The question already contains images. You assessment#:#info_text_upload#:#Choose an answer file to upload###30 08 2015 new variable assessment#:#insert_after#:#Εισαγωγή μετά assessment#:#insert_before#:#Εισαγωγή πριν -assessment#:#insert_gap#:#Insert Gap###24 10 2013 new variable assessment#:#interaction_type#:#Interaction Type###26 08 2024 new variable assessment#:#internal_links#:#Εσωτερικοί σύνδεσμοι assessment#:#intprecision#:#Divisible By###24 10 2013 new variable @@ -890,7 +880,6 @@ assessment#:#logs_wrong_test_password_provided#:#Participant entered wrong test assessment#:#longmenu#:#Longmenu###10 11 2018 new variable assessment#:#longmenu_answeroptions_differ#:#This question does not work correctly, as there are not the same amount of gaps in the text as in the correction options.###26 08 2024 new variable assessment#:#longmenu_text#:#Long Menu Text###30 08 2015 new variable -assessment#:#mainbar_button_label_questionlist#:#Questionlist###26 08 2024 new variable assessment#:#maintenance#:#Συντήρηση assessment#:#manscoring#:#Χειροκίνητη Βαθμολόγηση assessment#:#manscoring_done#:#Scored Participants###30 04 2009 new variable @@ -917,7 +906,6 @@ assessment#:#maximum_nr_of_tries_reached#:#Φθάσατε το μέγιστο α assessment#:#maximum_points#:#Μέγιστη δυνατή βαθμολογία assessment#:#maxsize#:#Maximum file upload size ###30 04 2009 new variable assessment#:#maxsize_info#:#Enter the maximum size in bytes that should be allowed for file uploads. If you leave this field empty, the maximum size of this installation will be chosen instead.###30 04 2009 new variable -assessment#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable assessment#:#min_ip_label#:#Lowest IP With Access###26 08 2024 new variable assessment#:#min_percentage_ne_0#:#Πρέπει να ορίσετε ένα ελάχιστο ποσοστό 0%! Το βαθμολογικό σχήμα δεν αποθηκεύθηκε. assessment#:#misc#:#Misc Options###24 10 2013 new variable @@ -926,7 +914,6 @@ assessment#:#mode_onebyone#:#One by One###29 10 2025 new variable assessment#:#mode_question#:#Question oriented###29 10 2025 new variable assessment#:#mode_user#:#Participant oriented###29 10 2025 new variable assessment#:#msg_circle_added#:#Circle added###24 07 2009 new variable -assessment#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable assessment#:#msg_number_of_terms_too_low#:#The number of terms must be greater or equal to the number of definitions.###12 08 2009 new variable assessment#:#msg_poly_added#:#Polygon added###24 07 2009 new variable assessment#:#msg_questions_moved#:#Question(s) moved###06 08 2009 new variable @@ -985,7 +972,6 @@ assessment#:#order#:#Order###26 08 2024 new variable assessment#:#ordering_answer_sequence_info#:#The answer sequence you define here will be taken as the correct solution sequence.###10 09 2010 new variable assessment#:#ordertext#:#Ordering Text###30 04 2009 new variable assessment#:#ordertext_info#:#Please enter the text that should be ordered horizontally. The ordering text will be separated by the whitespace signs in the text. If you need a different separation, you may use the separator %s to separate your text units.###30 04 2009 new variable -assessment#:#out_of_range#:#Out of range###27 01 2015 new variable assessment#:#output#:#Output assessment#:#output_mode#:#Κατάσταση εξόδου assessment#:#parseQuestion#:#Parse Question###24 10 2013 new variable @@ -1054,7 +1040,6 @@ assessment#:#qpl_bulk_save_add#:#Add###28 10 2024 new variable assessment#:#qpl_bulk_save_overwrite#:#Overwrite###28 10 2024 new variable assessment#:#qpl_bulkedit_success#:#Modifications saved.###28 10 2024 new variable assessment#:#qpl_cancel_skill_assigns_update#:#Cancel###30 08 2015 new variable -assessment#:#qpl_confirm_delete_questions#:#Είστε σίγουροι ότι θέλετε να διαγράψετε τις ακόλουθες ερωτήσεις; Αν διαγράψετε κλειδωμένες ερωτήσεις τα αποτελέσματα των τεστ που περιέχουν μια κλειδωμένη ερώτηση θα διαγραφούν. assessment#:#qpl_copy_insert_clipboard#:#Οι επιλεγμένες ερωτήσεις αντιγράφηκαν στο πρόχειρο assessment#:#qpl_copy_select_none#:#Παρακαλώ επιλέξτε τουλάχιστο μια ερώτηση για αντιγραφή στο πρόχειρο assessment#:#qpl_delete_rbac_error#:#Δεν έχετε δικαίωμα να διαγράψετε την ερώτηση! @@ -1107,7 +1092,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#Competence###30 08 2015 new variable assessment#:#qpl_qst_skl_usg_sklpnt_col#:#Total Sum of Competence-Points per Competence###30 08 2015 new variable assessment#:#qpl_question_is_in_use#:#Η ερώτηση που πρόκειται να επεξεργαστείτε βρίσκεται στα τεστ %s. Αν αλλάξετε την ερώτηση, δεν θ' αλλάξει μέσα στα τεστ, επειδή το σύστημα δημιουργεί αντίγραφα της ερώτησης όταν την εισάγετε σ' ένα τεστ! assessment#:#qpl_questions_deleted#:#Οι ερωτήσεις διαγράφηκαν. -assessment#:#qpl_reset_preview#:#Reset Preview###26 09 2014 new variable assessment#:#qpl_save_skill_assigns_update#:#Save Competence Assignments###30 08 2015 new variable assessment#:#qpl_settings_availability#:#Availability###26 08 2024 new variable assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#When enabled, possibly created taxonomies are shown for filtering.###24 10 2013 new variable @@ -1137,14 +1121,6 @@ assessment#:#qst_essay_chars_remaining#:#Remaining characters:###07 02 2020 new assessment#:#qst_essay_wordcounter_enabled#:#Count Words###07 02 2020 new variable assessment#:#qst_essay_wordcounter_enabled_info#:#The entered words are counted. The number of written words is shown to the participants below the text input field.###07 02 2020 new variable assessment#:#qst_essay_written_words#:#Number of entered words:###07 02 2020 new variable -assessment#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable -assessment#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable -assessment#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable -assessment#:#qst_lifecycle_final#:#Final###07 02 2020 new variable -assessment#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable -assessment#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable -assessment#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable -assessment#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable assessment#:#qst_nested_nested_answers_off#:#No indents, just order###29 07 2022 new variable assessment#:#qst_nested_nested_answers_on#:#Use indents in anwers###29 07 2022 new variable assessment#:#qst_nr_of_tries#:#Number of tries###15 05 2009 new variable @@ -1168,17 +1144,14 @@ assessment#:#question_title#:#Τίτλος ερώτησης assessment#:#question_type#:#Τύπος ερώτησης assessment#:#questionpool_not_entered#:#Παρακαλώ εισάγετε ένα όνομα για την πηγή ερωτήσεων! assessment#:#questionpool_not_selected#:#Please select a question pool!###07 02 2020 new variable -assessment#:#questions#:#Questions###26 08 2024 new variable assessment#:#questions_from#:#ερωτήσεις από assessment#:#questions_per_page_view#:#Page View###12 06 2011 new variable assessment#:#random_accept_sample#:#Αποδοχή δείγματος assessment#:#random_another_sample#:#Λήψη άλλου δείγματος assessment#:#random_selection#:#Τυχαία επιλογή assessment#:#range#:#Εύρος -assessment#:#range_lower_limit#:#Κάτω όριο assessment#:#range_max#:#Range (Maximum)###24 10 2013 new variable assessment#:#range_min#:#Range (Minimum)###24 10 2013 new variable -assessment#:#range_upper_limit#:#Άνω όριο assessment#:#rated_sign#:#Sign###24 10 2013 new variable assessment#:#rated_unit#:#Unit###24 10 2013 new variable assessment#:#rated_value#:#Value###24 10 2013 new variable @@ -1254,7 +1227,6 @@ assessment#:#search_roles#:#Αναζήτηση Ρόλων assessment#:#search_term#:#Αναζήτηση Όρου assessment#:#select_at_least_one_feedback_type_and_trigger#:#Please Select at least one type of feedback and a trigger.###26 08 2024 new variable assessment#:#select_at_least_one_lock_answer_type#:#Please select at least one type of answer lock.###29 10 2025 new variable -assessment#:#select_gap#:#Επιλογή κενού assessment#:#select_max_one_item#:#Παρακαλώ επιλέξτε μόνο ένα αντικείμενο assessment#:#select_one_user#:#Παρακαλώ επιλέξτε τουλάχιστο ένα χρήστη assessment#:#select_question#:#Select a Question###28 10 2024 new variable @@ -1281,7 +1253,6 @@ assessment#:#show_old_introduction#:#Show old introduction###26 08 2024 new vari assessment#:#show_pass_overview#:#Εμφάνιση γενικής εικόνας αποτελεσμάτων του Χρήστη assessment#:#show_results#:#Show Results###28 10 2024 new variable assessment#:#show_user_answers#:#Εμφάνιση απαντήσεων του Χρήστη -assessment#:#shuffle_answers#:#Ανακάτεμα απαντήσεων assessment#:#skip_question#:#Do not Answer and Next###08 10 2015 new variable assessment#:#solution#:#Solution###28 10 2024 new variable assessment#:#solutionText#:#Text###30 04 2009 new variable @@ -13972,6 +13943,35 @@ qpl#:#qpl_page_type_qfbg#:#General Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qfbs#:#Special Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qht#:#Hint###29 07 2022 new variable qpl#:#qpl_page_type_qpl#:#Question Page###29 07 2022 new variable +qsts#:#answer_options#:#Answer Options ###23 12 2015 new variable +qsts#:#cloze_text#:#Κείμενο με κενά +qsts#:#cloze_textgapcase_insensitive#:#Χωρίς ταίριασμα πεζών-κεφαλαίων +qsts#:#cloze_textgapcase_sensitive#:#Με ταίριασμα πεζών-κεφαλαίων +qsts#:#cloze_textgaplevenshtein_of#:#Levenshtein απόσταση του %s +qsts#:#confirm_delete_questions#:#Είστε σίγουροι ότι θέλετε να διαγράψετε τις ακόλουθες ερωτήσεις; Αν διαγράψετε κλειδωμένες ερωτήσεις τα αποτελέσματα των τεστ που περιέχουν μια κλειδωμένη ερώτηση θα διαγραφούν. +qsts#:#create_question#:#Create Question###24 10 2013 new variable +qsts#:#gap#:#Κενό +qsts#:#gaps#:#Gaps###29 10 2025 new variable +qsts#:#insert_gap#:#Insert Gap###24 10 2013 new variable +qsts#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable +qsts#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable +qsts#:#out_of_range#:#Out of range###27 01 2015 new variable +qsts#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable +qsts#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable +qsts#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable +qsts#:#qst_lifecycle_final#:#Final###07 02 2020 new variable +qsts#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable +qsts#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable +qsts#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable +qsts#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable +qsts#:#questionlist#:#Questionlist###26 08 2024 new variable +qsts#:#questions#:#Questions###26 08 2024 new variable +qsts#:#range_lower_limit#:#Κάτω όριο +qsts#:#range_upper_limit#:#Άνω όριο +qsts#:#reset_preview#:#Reset Preview###26 09 2014 new variable +qsts#:#select_gap#:#Επιλογή κενού +qsts#:#shuffle_answers#:#Ανακάτεμα απαντήσεων +qsts#:#suggested_learning_content#:#Add suggested solution###30 04 2009 new variable rating#:#rat_not_rated_yet#:#Not Rated Yet###12 06 2011 new variable rating#:#rat_nr_ratings#:#%s Ratings###12 06 2011 new variable rating#:#rat_one_rating#:#One Rating###12 06 2011 new variable @@ -16621,7 +16621,6 @@ survey#:#questionblock#:#Μπλοκ ερώτησης survey#:#questionblock_inserted#:#Question Block inserted###06 08 2009 new variable survey#:#questionblocks#:#Μπλοκ ερωτήσεων survey#:#questionblocks_inserted#:#Question Blocks inserted###06 08 2009 new variable -survey#:#questions#:#Ερωτήσεις survey#:#questions_inserted#:#Εισαχθείσες ερωτήσεις! survey#:#questions_removed#:#Οι ερωτήσεις και/η τα μπλοκ ερωτήσεων διαγράφηκαν! survey#:#questiontype#:#Τύπος ερώτησης @@ -16922,6 +16921,7 @@ survey#:#svy_please_select_unused_codes#:#Please select at least one unused code survey#:#svy_print_hide_labels#:#Hide labels###31 08 2017 new variable survey#:#svy_print_show_labels#:#Show labels###31 08 2017 new variable survey#:#svy_privacy_info#:#Privacy###29 07 2022 new variable +survey#:#svy_questions#:#Ερωτήσεις survey#:#svy_rater#:#Rater###29 07 2022 new variable survey#:#svy_rater_see_app_info#:#The names of appraisees will be presented to raters to enable them evaluating the questions.###29 07 2022 new variable survey#:#svy_reminder_mail_template#:#Mail Template###25 10 2016 new variable diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index 512dd28c41af..f683044b08a7 100644 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -422,7 +422,6 @@ adve#:#adve_use_tiny_mce#:#Enable TinyMCE Editor for WYSIWYG Editing assessment#:#activate_logging#:#Activate Test and Assessment Logging assessment#:#activate_manual_scoring#:#Enable Scoring assessment#:#activate_manual_scoring_desc#:#Enables Scoring for all question types. -assessment#:#addSuggestedSolution#:#Add Content for Recapitulation assessment#:#add_answers#:#Add answers assessment#:#add_circle#:#Add circle area assessment#:#add_gap#:#Add Gap Text @@ -447,7 +446,6 @@ assessment#:#answer_is_not_correct_but_positive#:#You've got points for your sol assessment#:#answer_is_right#:#Your solution is correct assessment#:#answer_is_wrong#:#Your solution is wrong assessment#:#answer_of#:#Answer of -assessment#:#answer_options#:#Answer Options: assessment#:#answer_question#:#Answer Question assessment#:#answer_text#:#Answer Text assessment#:#answer_types#:#Editor for Answers @@ -501,7 +499,6 @@ assessment#:#ass_completion_by_submission#:#Completed by Submission assessment#:#ass_completion_by_submission_info#:#If enabled, the submission of at least one file causes the completion of this question by granting the maximum score for this question. The score could be manually changed later. Switching this setting does not effect already submitted solutions. assessment#:#ass_create_export_file_with_results#:#incl. Participant Results assessment#:#ass_create_export_test_archive#:#as Archive File -assessment#:#ass_create_question#:#Create Question assessment#:#ass_imap_hint#:#Hint to be shown as Tooltip assessment#:#ass_imap_map_file_not_readable#:#The uploaded image map could not be read. assessment#:#ass_imap_no_map_found#:#Could not find any form in the uploaded image map. @@ -571,10 +568,6 @@ assessment#:#cloze_answer_text_info#:#Spaces preceding or following the answer t assessment#:#cloze_fixed_textlength#:#Text Field Length assessment#:#cloze_fixed_textlength_description#:#If you enter a value, all text gap fields, not providing an own maximum character limitation, as well as all numeric gap fields will be created with a fixed length of this value, so entering more than the allowed characters is not possible. Note, that for numeric gaps the decimal separator is counted as a regular character. assessment#:#cloze_gap_size_info#:#If you enter a value greater than 0, this gap text field will be created with the fixed length of this value. If you do not enter a value, the gap text field will be created with the value of the global fixed length. -assessment#:#cloze_text#:#Cloze Text -assessment#:#cloze_textgap_case_insensitive#:#Case Insensitive -assessment#:#cloze_textgap_case_sensitive#:#Case Sensitive -assessment#:#cloze_textgap_levenshtein_of#:#Levenshtein Distance of %s assessment#:#code#:#Code assessment#:#codebase#:#Codebase assessment#:#concatenation#:#Concatenation @@ -757,9 +750,7 @@ assessment#:#fq_formula_desc#:#You may enter predefined variables ($v1 to $vn), assessment#:#fq_no_restriction_info#:#Both decimals and fractions are accepted as input. assessment#:#fq_precision_info#:#Enter the number of desired decimal places. assessment#:#fq_question_desc#:#You can define variables by inserting $v1, $v2 ... $vn, results by inserting $r1, $r2 .... $rn at the desired position in the question text. Click on the button ‘Parse Question’ to create editing forms for variables and results. -assessment#:#gap#:#Gap assessment#:#gap_combination#:#Gap Combination -assessment#:#gaps#:#Gaps assessment#:#glossary_term#:#Glossary Term assessment#:#goto_first_question#:#Show First Question assessment#:#grading_mark_msg#:#Your grade is: "[mark]" @@ -777,7 +768,6 @@ assessment#:#info_answer_type_change#:#The question already contains images. You assessment#:#info_text_upload#:#Choose an answer text (UTF-8) file to upload. assessment#:#insert_after#:#Insert After assessment#:#insert_before#:#Insert Before -assessment#:#insert_gap#:#Insert Gap assessment#:#interaction_type#:#Interaction Type assessment#:#internal_links#:#Internal Links assessment#:#intprecision#:#Divisible By @@ -889,7 +879,6 @@ assessment#:#logs_wrong_test_password_provided#:#Participant entered wrong test assessment#:#longmenu#:#Longmenu assessment#:#longmenu_answeroptions_differ#:#This question does not work correctly, as there are not the same amount of gaps in the text as in the correction options. assessment#:#longmenu_text#:#Long Menu Text -assessment#:#mainbar_button_label_questionlist#:#Question List assessment#:#maintenance#:#Maintenance assessment#:#manscoring#:#Scoring assessment#:#manscoring_done#:#Scored Participants @@ -916,7 +905,6 @@ assessment#:#maximum_nr_of_tries_reached#:#You have already taken this test the assessment#:#maximum_points#:#Maximum Available Points assessment#:#maxsize#:#Maximum file upload size assessment#:#maxsize_info#:#Enter the maximum size in bytes that should be allowed for file uploads. If you leave this field empty, the maximum size of this installation will be chosen instead. -assessment#:#min_auto_complete#:#Autocomplete assessment#:#min_ip_label#:#Lowest IP With Access assessment#:#min_percentage_ne_0#:#One of your grade categories needs to start at the ‘Minimum Score Required (in %)’ level of 0% Your grading system hasn’t been saved. assessment#:#misc#:#Misc Options @@ -925,7 +913,6 @@ assessment#:#mode_onebyone#:#One by One assessment#:#mode_question#:#Question oriented assessment#:#mode_user#:#Participant oriented assessment#:#msg_circle_added#:#Circle added -assessment#:#msg_no_questions_selected#:#No questions were selected. assessment#:#msg_number_of_terms_too_low#:#The number of terms must be greater or equal to the number of definitions. assessment#:#msg_poly_added#:#Polygon added assessment#:#msg_questions_moved#:#Question(s) moved @@ -984,7 +971,6 @@ assessment#:#order#:#Order assessment#:#ordering_answer_sequence_info#:#The answer sequence you define here will be taken as the correct solution sequence. assessment#:#ordertext#:#Ordering Text assessment#:#ordertext_info#:#Please enter the text that should be ordered horizontally. The ordering text will be separated by the whitespace signs in the text. If you need a different separation, you may use the separator %s to separate your text units. -assessment#:#out_of_range#:#Out of range assessment#:#output#:#Output assessment#:#output_mode#:#Output Mode assessment#:#parseQuestion#:#Parse Question @@ -1053,7 +1039,6 @@ assessment#:#qpl_bulk_save_add#:#Add assessment#:#qpl_bulk_save_overwrite#:#Overwrite assessment#:#qpl_bulkedit_success#:#Modifications saved. assessment#:#qpl_cancel_skill_assigns_update#:#Cancel -assessment#:#qpl_confirm_delete_questions#:#Are you sure you want to remove the following questions? assessment#:#qpl_copy_insert_clipboard#:#Selected question(s) successfully copied to clipboard. assessment#:#qpl_copy_select_none#:#Please check at least one question to copy it to the clipboard assessment#:#qpl_delete_rbac_error#:#You have no rights to remove this question! @@ -1106,7 +1091,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#Competence assessment#:#qpl_qst_skl_usg_sklpnt_col#:#Total Sum of Competence-Points per Competence assessment#:#qpl_question_is_in_use#:#The question you are about to edit exists in %s test(s). If you change this question, you will NOT change the question(s) in the test(s), because the system creates a copy of a question when it is inserted in a test! assessment#:#qpl_questions_deleted#:#Question(s) removed. -assessment#:#qpl_reset_preview#:#Reset Preview assessment#:#qpl_save_skill_assigns_update#:#Save Competence Assignments assessment#:#qpl_settings_availability#:#Availability assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#Existing taxonomies in this pool are offered for question filtering. @@ -1136,14 +1120,6 @@ assessment#:#qst_essay_chars_remaining#:#Remaining characters: assessment#:#qst_essay_wordcounter_enabled#:#Count Words assessment#:#qst_essay_wordcounter_enabled_info#:#The entered words are counted. The number of written words is shown to the participants below the text input field. assessment#:#qst_essay_written_words#:#Number of entered words: -assessment#:#qst_lifecycle#:#Lifecycle -assessment#:#qst_lifecycle_draft#:#Draft -assessment#:#qst_lifecycle_filter_all#:#All Lifecycles -assessment#:#qst_lifecycle_final#:#Final -assessment#:#qst_lifecycle_outdated#:#Outdated -assessment#:#qst_lifecycle_rejected#:#Rejected -assessment#:#qst_lifecycle_review#:#To be Reviewed -assessment#:#qst_lifecycle_sharable#:#Shareable assessment#:#qst_nested_nested_answers_off#:#No indents, just order assessment#:#qst_nested_nested_answers_on#:#Use indents in anwers assessment#:#qst_nr_of_tries#:#Number of Tries @@ -1168,17 +1144,14 @@ assessment#:#question_type#:#Question Type assessment#:#questionlist_cannot_be_altered#:#The questionlist cannot be altered as the test already contains participant data sets. assessment#:#questionpool_not_entered#:#Please enter a name for a question pool! assessment#:#questionpool_not_selected#:#Please select a question pool! -assessment#:#questions#:#Questions assessment#:#questions_from#:#questions from assessment#:#questions_per_page_view#:#Page View assessment#:#random_accept_sample#:#Accept Sample assessment#:#random_another_sample#:#Get another Sample assessment#:#random_selection#:#Random Selection assessment#:#range#:#Range -assessment#:#range_lower_limit#:#Lower Bound assessment#:#range_max#:#Range (Maximum) assessment#:#range_min#:#Range (Minimum) -assessment#:#range_upper_limit#:#Upper Bound assessment#:#rated_sign#:#Sign assessment#:#rated_unit#:#Unit assessment#:#rated_value#:#Value @@ -1254,7 +1227,6 @@ assessment#:#search_roles#:#Search Roles assessment#:#search_term#:#Search Term assessment#:#select_at_least_one_feedback_type_and_trigger#:#Please Select at least one type of feedback and a trigger. assessment#:#select_at_least_one_lock_answer_type#:#Please select at least one type of answer lock. -assessment#:#select_gap#:#Select Gap assessment#:#select_max_one_item#:#Please select one item only assessment#:#select_one_user#:#Please select at least one user. assessment#:#select_question#:#Select a Question @@ -1281,7 +1253,6 @@ assessment#:#show_old_introduction#:#Show old introduction assessment#:#show_pass_overview#:#Show Marked Pass Overview assessment#:#show_results#:#Show Results assessment#:#show_user_answers#:#Show User’s Marked Answers -assessment#:#shuffle_answers#:#Shuffle Answers assessment#:#skip_question#:#Do not Answer and Next assessment#:#solution#:#Solution assessment#:#solutionText#:#Text @@ -5109,6 +5080,8 @@ common#:#obj_ps_desc#:#Configure global privacy and security settings here. common#:#obj_qpl#:#Question Pool for Tests common#:#obj_qpl_duplicate#:#Copy Question Pool for Tests common#:#obj_qpl_select#:#-- Please select one question pool for tests -- +common#:#obj_qsts#:#Questions +common#:#obj_qsts_desc#:#Global Settings For Questions common#:#obj_rcat#:#ECS Category common#:#obj_rcrs#:#ECS Course common#:#obj_recf#:#Restored Objects @@ -14103,6 +14076,113 @@ qpl#:#qpl_page_type_qfbg#:#General Feedback qpl#:#qpl_page_type_qfbs#:#Special Feedback qpl#:#qpl_page_type_qht#:#Hint qpl#:#qpl_page_type_qpl#:#Question Page +qsts#:#above_range#:#Above Range +qsts#:#add_gap_combination#:#Add Combination of Gaps +qsts#:#answer_form#:#Answer Form +qsts#:#answer_options#:#Answer Options +qsts#:#answer_options_awarding_points#:#Answer Options Awarding Points +qsts#:#answer_options_must_be_unique#:#All answer options must be unique. +qsts#:#async_view#:#Asynchronous View +qsts#:#at_least_one_gap_positiv_points#:#At least one gap needs to award points. +qsts#:#available_points#:#Available Points +qsts#:#awarded_points#:#Awarded Points +qsts#:#basic_answer_form_properties#:#Basic Answer Form Properties +qsts#:#below_range#:#Below Range +qsts#:#best_response#:#Best Response +qsts#:#best_response_given#:#You have provided the best possible response. +qsts#:#between#:#Between %s and %s +qsts#:#broken_answer_form#:#Broken Answer Form +qsts#:#byline_create_mode_full#:#The page editor is used. The question text can contain rich content and multiple answer forms. +qsts#:#byline_create_mode_simple#:#Only a simple text input is shown to enter the question text. The question text cannot be formatted and only one answer form can be created. At the end of the creation process a button is shown to directly create an additional question after saving. +qsts#:#cloze#:#Cloze +qsts#:#cloze_enable_gap_combinations#:#Lückenkombinationen aktivieren +qsts#:#cloze_text#:#Cloze Text +qsts#:#cloze_textgap_case_insensitive#:#Case Insensitive +qsts#:#cloze_textgap_case_sensitive#:#Case Sensitive +qsts#:#cloze_textgap_levenshtein_of#:#Levenshtein Distance of %s +qsts#:#combination_needs_more_than_one#:#A combination of gaps needs to contain more than one gap. +qsts#:#confirm_delete_feedback#:#Do you really want to delete this feedback. +qsts#:#confirm_delete_questions#:#Are you sure you want to remove the following questions? +qsts#:#confirm_remove_gaps#:#You have removed gaps from the cloze text. Do you really want to delete them and all associated information? +qsts#:#cont_ed_insert_answf#:#Insert Answer Form +qsts#:#contained_answer_form_types#:#Contained Answer Form Types +qsts#:#contains_answer_form_types#:#Contains Answer Form Types +qsts#:#create_answer_form#:#Create Answer Form +qsts#:#create_feedback#:#Create Feedback +qsts#:#create_mode_full#:#Full +qsts#:#create_mode_simple#:#Simple +qsts#:#create_question#:#Create Question +qsts#:#default_user_settings#:#Default Settings +qsts#:#default_view#:#Default View +qsts#:#delete_combination#:#Delete Combination of Gaps +qsts#:#delete_responses#:#Delete Responses +qsts#:#disable_marking_allowing_partial_points#:#Disable Marking +qsts#:#disable_suggested_learning_content#:#Disable Suggested Learning Content +qsts#:#disable_text_feedback#:#Disable Feedback +qsts#:#edit_answer_options#:#Edit Answer Options +qsts#:#edit_available_points#:#Edit Available Points +qsts#:#edit_basic_answer_form_properties#:#Edit Basic Answer Form Properties +qsts#:#edit_basic_question_properties#:#Edit Basic Question Properties +qsts#:#edit_feedback#:#Edit Feedback +qsts#:#edit_gaps#:#Edit Gaps +qsts#:#edit_generic_feedback#:#Feedback Based On Reached Points +qsts#:#edit_points#:#Edit Points +qsts#:#edit_suggested_learning_content#:#Edit Suggested Learning Content +qsts#:#enable_marking_allowing_partial_points#:#Enable Marking +qsts#:#enable_suggested_learning_content#:#Enable Suggested Learning Content +qsts#:#enable_text_feedback#:#Enable Feedback +qsts#:#equal#:#Equal to %s +qsts#:#gap#:#Gap +qsts#:#gap_combination_already_exists#:#This combination of gaps already exists. +qsts#:#gap_combinations#:#Combinations of Gaps +qsts#:#gap_type#:#Type of Gap +qsts#:#gaps#:#Gaps +qsts#:#insert_gap#:#Insert Gap +qsts#:#insert_legacy_texts#:#Import Legacy Texts +qsts#:#insert_legacy_texts_info#:#Some texts have been migrated from a previous version. They cannot be edited directed, but you can import them into the form. All formatting will be lost. +qsts#:#legacy_text_cannot_be_edited#:#This text was migrated from the previous question implementation and cannot be edited. +qsts#:#long_menu_gap#:#Longmenu +qsts#:#max_characters#:#Character Limit +qsts#:#min_auto_complete#:#Autocomplete +qsts#:#msg_no_questions_selected#:#No questions were selected. +qsts#:#no_best_response_available#:#The Best Response is not Defined +qsts#:#no_gaps#:#The provided text does not contain any gaps. +qsts#:#no_response#:#No Response +qsts#:#no_response_given#:#No Response Given +qsts#:#other_response#:#Not the Best Response +qsts#:#other_response_given#:#There are better responses. +qsts#:#out_of_range#:#Out of range +qsts#:#qst_lifecycle#:#Lifecycle +qsts#:#qst_lifecycle_draft#:#Draft +qsts#:#qst_lifecycle_filter_all#:#All Lifecycles +qsts#:#qst_lifecycle_final#:#Final +qsts#:#qst_lifecycle_outdated#:#Outdated +qsts#:#qst_lifecycle_rejected#:#Rejected +qsts#:#qst_lifecycle_review#:#To be Reviewed +qsts#:#qst_lifecycle_sharable#:#Shareable +qsts#:#qst_remarks#:#Remarks +qsts#:#question_create_mode#:#Question Create Mode +qsts#:#question_text#:#Question Text +qsts#:#questionlist#:#Question List +qsts#:#questions#:#Questions +qsts#:#range_lower_limit#:#Lower Bound +qsts#:#range_upper_limit#:#Upper Bound +qsts#:#reset_preview#:#Reset Preview +qsts#:#save_and_new#:#Save and New +qsts#:#score_all#:#All Responses are Scored +qsts#:#score_distinct#:#Identical Responses are Only Scored Once +qsts#:#scoring_of_identical_responses#:#Scoring of Identical Responses +qsts#:#select_answer_form_type#:#Select Type of Answer Form +qsts#:#select_gap#:#Select Gap +qsts#:#select_gaps_for_combination#:#Select Gap for Combination +qsts#:#shuffle_answers#:#Shuffle Answers +qsts#:#specific_feedback#:#Feedback Based On Answer Options +qsts#:#step_size#:#Step Size +qsts#:#suggested_learning_content#:#Suggested Content +qsts#:#text_matching_method#:#Text Matching Method +qsts#:#upload_answer_options#:#Upload Answer Options +qsts#:#upload_answer_options_info#:#You can upload a file containing a list of answer options that will be added to the list. Each answer option needs to be on a new line. +qsts#:#upper_limit_bigger_than_lower#:#The upper bound must be bigger than the lower bound and the step size. rating#:#rat_not_rated_yet#:#Not Rated Yet rating#:#rat_nr_ratings#:#%s Ratings rating#:#rat_one_rating#:#One Rating @@ -16752,7 +16832,6 @@ survey#:#questionblock#:#Question Block survey#:#questionblock_inserted#:#Question block inserted. survey#:#questionblocks#:#Question Blocks survey#:#questionblocks_inserted#:#Question blocks inserted. -survey#:#questions#:#Questions survey#:#questions_inserted#:#Question(s) added. survey#:#questions_removed#:#Question and/or question block removal successful. survey#:#questiontype#:#Question Type @@ -17035,6 +17114,7 @@ survey#:#svy_please_select_unused_codes#:#Please select at least one unused code survey#:#svy_print_hide_labels#:#Hide labels survey#:#svy_print_show_labels#:#Show labels survey#:#svy_privacy_info#:#Privacy +survey#:#svy_questions#:#Questions survey#:#svy_rater#:#Rater survey#:#svy_rater_see_app_info#:#Raters are shown the names of their appraisees so that they can meaningfully respond to the questions concerning those individuals. survey#:#svy_reminder_mail_template#:#Mail Template diff --git a/lang/ilias_es.lang b/lang/ilias_es.lang index 52c4dd49cd9a..cb63a766aa2f 100644 --- a/lang/ilias_es.lang +++ b/lang/ilias_es.lang @@ -425,7 +425,6 @@ adve#:#adve_use_tiny_mce#:#Activar el editor TinyMCE para edición WYSIWYG assessment#:#activate_logging#:#Activar el registro de pruebas y evaluaciones assessment#:#activate_manual_scoring#:#Activar puntuación manual assessment#:#activate_manual_scoring_desc#:#Habilita la puntuación manual para todos los tipos de preguntas. -assessment#:#addSuggestedSolution#:#Agregar contenido para recapitulación assessment#:#add_answers#:#Agregar respuestas assessment#:#add_circle#:#Agregar área circular assessment#:#add_gap#:#Agregar texto de hueco @@ -450,7 +449,6 @@ assessment#:#answer_is_not_correct_but_positive#:#Has obtenido puntos por tu sol assessment#:#answer_is_right#:#Tu solución es correcta assessment#:#answer_is_wrong#:#Tu solución es incorrecta assessment#:#answer_of#:#Respuesta de -assessment#:#answer_options#:#Opciones de respuesta: assessment#:#answer_question#:#Responder pregunta assessment#:#answer_text#:#Texto de la respuesta assessment#:#answer_types#:#Editor de respuestas @@ -504,7 +502,6 @@ assessment#:#ass_completion_by_submission#:#Completado por envío assessment#:#ass_completion_by_submission_info#:#Si está habilitado, la entrega de al menos un archivo provoca la finalización de esta pregunta otorgando la puntuación máxima para esta pregunta. La puntuación puede modificarse manualmente más tarde. Cambiar esta configuración no afecta a las soluciones ya enviadas. assessment#:#ass_create_export_file_with_results#:#incl. Resultados de participantes assessment#:#ass_create_export_test_archive#:#como archivo comprimido -assessment#:#ass_create_question#:#Crear pregunta assessment#:#ass_imap_hint#:#Sugerencia que se mostrará como información sobre herramientas assessment#:#ass_imap_map_file_not_readable#:#No se pudo leer el mapa de imagen subido. assessment#:#ass_imap_no_map_found#:#No se pudo encontrar ningún formulario en el mapa de imagen subido. @@ -574,10 +571,6 @@ assessment#:#cloze_answer_text_info#:#Los espacios que precedan o sigan al texto assessment#:#cloze_fixed_textlength#:#Longitud del campo de texto assessment#:#cloze_fixed_textlength_description#:#Si introduce un valor, todos los campos de hueco de texto que no proporcionen una limitación propia de caracteres, así como todos los campos de hueco numéricos, se crearán con una longitud fija de este valor, por lo que no será posible introducir más caracteres de los permitidos. Tenga en cuenta que para los huecos numéricos el separador decimal se cuenta como un carácter normal. assessment#:#cloze_gap_size_info#:#Si introduce un valor mayor que 0, este campo de texto del hueco se creará con la longitud fija de ese valor. Si no introduce un valor, el campo de texto del hueco se creará con el valor de la longitud fija global. -assessment#:#cloze_text#:#Texto Cloze -assessment#:#cloze_textgap_case_insensitive#:#Sin distinción de mayúsculas/minúsculas -assessment#:#cloze_textgap_case_sensitive#:#Con distinción de mayúsculas/minúsculas -assessment#:#cloze_textgap_levenshtein_of#:#Distancia de Levenshtein de %s assessment#:#code#:#Código assessment#:#codebase#:#Base de código assessment#:#concatenation#:#Concatenación @@ -761,9 +754,7 @@ assessment#:#fq_formula_desc#:#Puede introducir variables predefinidas ($v1 a $v assessment#:#fq_no_restriction_info#:#Se aceptan tanto decimales como fracciones. assessment#:#fq_precision_info#:#Introduzca el número de decimales deseados. assessment#:#fq_question_desc#:#Puede definir variables insertando $v1, $v2 ... $vn, resultados insertando $r1, $r2 ... $rn en la posición deseada del texto de la pregunta. Haga clic en el botón ‘Analizar pregunta’ para crear formularios de edición para variables y resultados. -assessment#:#gap#:#Hueco assessment#:#gap_combination#:#Combinación de huecos -assessment#:#gaps#:#Huecos assessment#:#glossary_term#:#Término del glosario assessment#:#goto_first_question#:#Mostrar la primera pregunta assessment#:#grading_mark_msg#:#Su calificación es: "[mark]" @@ -781,7 +772,6 @@ assessment#:#info_answer_type_change#:#La pregunta ya contiene imágenes. No pue assessment#:#info_text_upload#:#Seleccione un archivo de texto de respuesta (UTF-8) para subir. assessment#:#insert_after#:#Insertar después assessment#:#insert_before#:#Insertar antes -assessment#:#insert_gap#:#Insertar hueco assessment#:#interaction_type#:#Tipo de interacción assessment#:#internal_links#:#Enlaces internos assessment#:#intprecision#:#Divisible por @@ -893,7 +883,6 @@ assessment#:#logs_wrong_test_password_provided#:#El participante introdujo una c assessment#:#longmenu#:#Menú largo assessment#:#longmenu_answeroptions_differ#:#Esta pregunta no funciona correctamente, ya que no hay la misma cantidad de huecos en el texto que en las opciones de corrección. assessment#:#longmenu_text#:#Texto del menú largo -assessment#:#mainbar_button_label_questionlist#:#Lista de preguntas assessment#:#maintenance#:#Mantenimiento assessment#:#manscoring#:#Calificación manual assessment#:#manscoring_done#:#Participantes calificados @@ -920,7 +909,6 @@ assessment#:#maximum_nr_of_tries_reached#:#Ha alcanzado el número máximo de in assessment#:#maximum_points#:#Puntos máximos disponibles assessment#:#maxsize#:#Tamaño máximo de archivo subido assessment#:#maxsize_info#:#Introduzca el tamaño máximo en bytes que se permitirá para las cargas de archivos. Si deja este campo vacío, se elegirá en su lugar el tamaño máximo de esta instalación. -assessment#:#min_auto_complete#:#Autocompletar assessment#:#min_ip_label#:#IP más baja con acceso assessment#:#min_percentage_ne_0#:#Una de sus categorías de calificación debe comenzar en el nivel ‘Puntuación mínima requerida (en %)’ de 0%. Su sistema de calificaciones no se ha guardado. assessment#:#misc#:#Opciones varias @@ -929,7 +917,6 @@ assessment#:#mode_onebyone#:#One by One assessment#:#mode_question#:#Question oriented assessment#:#mode_user#:#Participant oriented assessment#:#msg_circle_added#:#Círculo añadido -assessment#:#msg_no_questions_selected#:#No se seleccionaron preguntas. assessment#:#msg_number_of_terms_too_low#:#El número de términos debe ser mayor o igual que el número de definiciones. assessment#:#msg_poly_added#:#Polígono añadido assessment#:#msg_questions_moved#:#Pregunta(s) movida(s) @@ -988,7 +975,6 @@ assessment#:#order#:#Orden assessment#:#ordering_answer_sequence_info#:#La secuencia de respuestas que defina aquí se considerará la secuencia de solución correcta. assessment#:#ordertext#:#Texto para ordenar assessment#:#ordertext_info#:#Por favor, introduzca el texto que debe ordenarse horizontalmente. El texto para ordenar será separado por los espacios en blanco del texto. Si necesita una separación diferente, puede usar el separador %s para separar sus unidades de texto. -assessment#:#out_of_range#:#Fuera de rango assessment#:#output#:#Salida assessment#:#output_mode#:#Modo de salida assessment#:#parseQuestion#:#Analizar pregunta @@ -1057,7 +1043,6 @@ assessment#:#qpl_bulk_save_add#:#Agregar assessment#:#qpl_bulk_save_overwrite#:#Sobrescribir assessment#:#qpl_bulkedit_success#:#Modificaciones guardadas. assessment#:#qpl_cancel_skill_assigns_update#:#Cancelar -assessment#:#qpl_confirm_delete_questions#:#¿Está seguro de que desea eliminar las siguientes preguntas? assessment#:#qpl_copy_insert_clipboard#:#La(s) pregunta(s) seleccionada(s) se han copiado al portapapeles assessment#:#qpl_copy_select_none#:#Por favor seleccione al menos una pregunta para copiarla al portapapeles assessment#:#qpl_delete_rbac_error#:#¡No tiene permisos para eliminar esta pregunta! @@ -1110,7 +1095,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#Competencia assessment#:#qpl_qst_skl_usg_sklpnt_col#:#Suma total de puntos de competencia por competencia assessment#:#qpl_question_is_in_use#:#La pregunta que está a punto de editar existe en %s prueba(s). Si cambia esta pregunta, NO cambiará la(s) pregunta(s) en la(s) prueba(s), ¡porque el sistema crea una copia de una pregunta cuando se inserta en una prueba! assessment#:#qpl_questions_deleted#:#Pregunta(s) eliminada(s). -assessment#:#qpl_reset_preview#:#Restablecer vista previa assessment#:#qpl_save_skill_assigns_update#:#Guardar asignaciones de competencias assessment#:#qpl_settings_availability#:#Disponibilidad assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#Las taxonomías existentes en este banco de preguntas se ofrecen para filtrar preguntas. @@ -1140,14 +1124,6 @@ assessment#:#qst_essay_chars_remaining#:#Caracteres restantes: assessment#:#qst_essay_wordcounter_enabled#:#Contar palabras assessment#:#qst_essay_wordcounter_enabled_info#:#Se cuentan las palabras introducidas. El número de palabras escritas se muestra a los participantes debajo del campo de entrada de texto. assessment#:#qst_essay_written_words#:#Número de palabras introducidas: -assessment#:#qst_lifecycle#:#Ciclo de vida -assessment#:#qst_lifecycle_draft#:#Borrador -assessment#:#qst_lifecycle_filter_all#:#Todos los ciclos de vida -assessment#:#qst_lifecycle_final#:#Final -assessment#:#qst_lifecycle_outdated#:#Obsoleto -assessment#:#qst_lifecycle_rejected#:#Rechazado -assessment#:#qst_lifecycle_review#:#Por revisar -assessment#:#qst_lifecycle_sharable#:#Compartible assessment#:#qst_nested_nested_answers_off#:#Sin sangrías, solo orden assessment#:#qst_nested_nested_answers_on#:#Usar sangrías en las respuestas assessment#:#qst_nr_of_tries#:#Número de intentos @@ -1172,17 +1148,14 @@ assessment#:#question_type#:#Tipo de pregunta assessment#:#questionlist_cannot_be_altered#:#La lista de preguntas no puede modificarse porque la prueba ya contiene conjuntos de datos de participantes. assessment#:#questionpool_not_entered#:#¡Por favor ingrese un nombre para el banco de preguntas! assessment#:#questionpool_not_selected#:#¡Por favor seleccione un banco de preguntas! -assessment#:#questions#:#Preguntas assessment#:#questions_from#:#preguntas de assessment#:#questions_per_page_view#:#Vista de página assessment#:#random_accept_sample#:#Aceptar muestra assessment#:#random_another_sample#:#Obtener otra muestra assessment#:#random_selection#:#Selección aleatoria assessment#:#range#:#Rango -assessment#:#range_lower_limit#:#Límite inferior assessment#:#range_max#:#Rango (máximo) assessment#:#range_min#:#Rango (mínimo) -assessment#:#range_upper_limit#:#Límite superior assessment#:#rated_sign#:#Signo assessment#:#rated_unit#:#Unidad assessment#:#rated_value#:#Valor @@ -1258,7 +1231,6 @@ assessment#:#search_roles#:#Buscar Roles assessment#:#search_term#:#Término de búsqueda assessment#:#select_at_least_one_feedback_type_and_trigger#:#Por favor, seleccione al menos un tipo de feedback y un desencadenador. assessment#:#select_at_least_one_lock_answer_type#:#Por favor, seleccione al menos un tipo de bloqueo de respuesta. -assessment#:#select_gap#:#Seleccionar hueco assessment#:#select_max_one_item#:#Por favor, seleccione solo un elemento assessment#:#select_one_user#:#Por favor, seleccione al menos un usuario. assessment#:#select_question#:#Seleccionar una pregunta @@ -1285,7 +1257,6 @@ assessment#:#show_old_introduction#:#Mostrar introducción antigua assessment#:#show_pass_overview#:#Mostrar resumen de pases marcados assessment#:#show_results#:#Mostrar resultados assessment#:#show_user_answers#:#Mostrar respuestas marcadas del usuario -assessment#:#shuffle_answers#:#Barajar respuestas assessment#:#skip_question#:#No responder y pasar a la siguiente assessment#:#solution#:#Solución assessment#:#solutionText#:#Texto @@ -14036,6 +14007,35 @@ qpl#:#qpl_page_type_qfbg#:#Feedback general qpl#:#qpl_page_type_qfbs#:#Feedback especial qpl#:#qpl_page_type_qht#:#Sugerencia qpl#:#qpl_page_type_qpl#:#Página de pregunta +qsts#:#answer_options#:#Opciones de respuesta +qsts#:#cloze_text#:#Texto Cloze +qsts#:#cloze_textgapcase_insensitive#:#Sin distinción de mayúsculas/minúsculas +qsts#:#cloze_textgapcase_sensitive#:#Con distinción de mayúsculas/minúsculas +qsts#:#cloze_textgaplevenshtein_of#:#Distancia de Levenshtein de %s +qsts#:#confirm_delete_questions#:#¿Está seguro de que desea eliminar las siguientes preguntas? +qsts#:#create_question#:#Crear pregunta +qsts#:#gap#:#Hueco +qsts#:#gaps#:#Huecos +qsts#:#insert_gap#:#Insertar hueco +qsts#:#min_auto_complete#:#Autocompletar +qsts#:#msg_no_questions_selected#:#No se seleccionaron preguntas. +qsts#:#out_of_range#:#Fuera de rango +qsts#:#qst_lifecycle#:#Ciclo de vida +qsts#:#qst_lifecycle_draft#:#Borrador +qsts#:#qst_lifecycle_filter_all#:#Todos los ciclos de vida +qsts#:#qst_lifecycle_final#:#Final +qsts#:#qst_lifecycle_outdated#:#Obsoleto +qsts#:#qst_lifecycle_rejected#:#Rechazado +qsts#:#qst_lifecycle_review#:#Por revisar +qsts#:#qst_lifecycle_sharable#:#Compartible +qsts#:#questionlist#:#Lista de preguntas +qsts#:#questions#:#Preguntas +qsts#:#range_lower_limit#:#Límite inferior +qsts#:#range_upper_limit#:#Límite superior +qsts#:#reset_preview#:#Restablecer vista previa +qsts#:#select_gap#:#Seleccionar hueco +qsts#:#shuffle_answers#:#Barajar respuestas +qsts#:#suggested_learning_content#:#Agregar contenido para recapitulación rating#:#rat_not_rated_yet#:#Aún sin calificar rating#:#rat_nr_ratings#:#%s Calificaciones rating#:#rat_one_rating#:#Una calificación @@ -16691,7 +16691,6 @@ survey#:#questionblock#:#Bloque de preguntas survey#:#questionblock_inserted#:#Bloque de preguntas insertado. survey#:#questionblocks#:#Bloques de preguntas survey#:#questionblocks_inserted#:#Bloques de preguntas insertados. -survey#:#questions#:#Preguntas survey#:#questions_inserted#:#Pregunta(s) añadida(s). survey#:#questions_removed#:#La eliminación de preguntas y/o bloques de preguntas se ha realizado con éxito. survey#:#questiontype#:#Tipo de pregunta @@ -16992,6 +16991,7 @@ survey#:#svy_please_select_unused_codes#:#Por favor, seleccione al menos un cód survey#:#svy_print_hide_labels#:#Ocultar etiquetas survey#:#svy_print_show_labels#:#Mostrar etiquetas survey#:#svy_privacy_info#:#Privacidad +survey#:#svy_questions#:#Preguntas survey#:#svy_rater#:#Evaluador survey#:#svy_rater_see_app_info#:#A los evaluadores se les muestran los nombres de sus evaluados para que puedan responder de forma significativa a las preguntas relativas a esas personas. survey#:#svy_reminder_mail_template#:#Plantilla de correo diff --git a/lang/ilias_et.lang b/lang/ilias_et.lang index c7ab92d89998..9a2ed9537ce7 100644 --- a/lang/ilias_et.lang +++ b/lang/ilias_et.lang @@ -422,7 +422,6 @@ adve#:#adve_use_tiny_mce#:#Võimalda TinyMCE WYSIWYG toimetamiseks assessment#:#activate_logging#:#aktiveeri testi ja hindamise logimine assessment#:#activate_manual_scoring#:#Enable Manual Scoring###28 10 2024 new variable assessment#:#activate_manual_scoring_desc#:#Enables manual Scoring for all question types.###28 10 2024 new variable -assessment#:#addSuggestedSolution#:#Lisa soovituslik lahendus assessment#:#add_answers#:#Add Antworten ###23 12 2015 new variable assessment#:#add_circle#:#Lisa ringiala assessment#:#add_gap#:#Lisa lünka tekst @@ -447,7 +446,6 @@ assessment#:#answer_is_not_correct_but_positive#:#Said vastuse eest punkti(d), k assessment#:#answer_is_right#:#Lahendus on õige assessment#:#answer_is_wrong#:#Lahendus on vale assessment#:#answer_of#:#Answer of###07 02 2020 new variable -assessment#:#answer_options#:#Answer Options: ###23 12 2015 new variable assessment#:#answer_question#:#Answer Question###30 08 2015 new variable assessment#:#answer_text#:#Vastustekst assessment#:#answer_types#:#Vastusetüübid @@ -501,7 +499,6 @@ assessment#:#ass_completion_by_submission#:#Tööd saadetud assessment#:#ass_completion_by_submission_info#:#Võimaldamise korral loetakse antud küsimus vastatuks vähemalt ühe faili esitamisel ja selle eest saab maksimaalse punktisumma. Punktisummat saab hiljem käsitsi muuta. Antud seade vahetamine ei muuda olemasolevaid ehk juba ära saadetud vastuseid. assessment#:#ass_create_export_file_with_results#:#Create Test Export File (incl. Participant Results)###25 10 2016 new variable assessment#:#ass_create_export_test_archive#:#Create Test Archive File -assessment#:#ass_create_question#:#Create Question assessment#:#ass_imap_hint#:#Hint to be shown as Tooltip###25 10 2016 new variable assessment#:#ass_imap_map_file_not_readable#:#The uploaded image map could not be read.###25 10 2016 new variable assessment#:#ass_imap_no_map_found#:#Could not find any form in the uploaded image map.###25 10 2016 new variable @@ -571,10 +568,6 @@ assessment#:#cloze_answer_text_info#:#Spaces preceding or following the answer t assessment#:#cloze_fixed_textlength#:#Tekstivälja pikkus assessment#:#cloze_fixed_textlength_description#:#Kui sisestad nullist suurema väärtuse, luuakse kõik teksti- ja numbriväljad selles väärtuses fikseeritud pikkuses. assessment#:#cloze_gap_size_info#:#If you enter a value greater than 0, this gap text field will be created with the fixed length of this value. If you do not enter a value the gap text fiel will be created with the value of the global fixed length. -assessment#:#cloze_text#:#Lünktekst -assessment#:#cloze_textgap_case_insensitive#:#Mittetundlik variant -assessment#:#cloze_textgap_case_sensitive#:#Tundlik variant -assessment#:#cloze_textgap_levenshtein_of#:#Levenšteini kaugus of %s assessment#:#code#:#Kood assessment#:#codebase#:#Koodibaas assessment#:#concatenation#:#Jätkamine @@ -757,9 +750,7 @@ assessment#:#fq_formula_desc#:#You may enter predefined variables ($v1 to $vn), assessment#:#fq_no_restriction_info#:#Both decimals and fractions are accepted as input. assessment#:#fq_precision_info#:#Enter the number of desired decimal places. assessment#:#fq_question_desc#:#You can define variables by inserting $v1, $v2 ... $vn, results by inserting $r1, $r2 .... $rn at the desired position in the question text. Click on the button "Parse Question" to create editing forms for variables and results. -assessment#:#gap#:#Lünk assessment#:#gap_combination#:#Gap Combination -assessment#:#gaps#:#Gaps###29 10 2025 new variable assessment#:#glossary_term#:#Sõnastiku mõiste assessment#:#goto_first_question#:#Show First Question###29 10 2025 new variable assessment#:#grading_mark_msg#:#Your resulting mark is: "[mark]" @@ -777,7 +768,6 @@ assessment#:#info_answer_type_change#:#Küsimus juba sisaldab pilte. Te ei saa m assessment#:#info_text_upload#:#Choose an answer file to upload###30 08 2015 new variable assessment#:#insert_after#:#Sisesta pärast assessment#:#insert_before#:#Sisesta enne -assessment#:#insert_gap#:#Insert Gap assessment#:#interaction_type#:#Interaction Type###26 08 2024 new variable assessment#:#internal_links#:#Sisemised lingid assessment#:#intprecision#:#Divisible By @@ -889,7 +879,6 @@ assessment#:#logs_wrong_test_password_provided#:#Participant entered wrong test assessment#:#longmenu#:#Longmenu###10 11 2018 new variable assessment#:#longmenu_answeroptions_differ#:#This question does not work correctly, as there are not the same amount of gaps in the text as in the correction options.###26 08 2024 new variable assessment#:#longmenu_text#:#Long Menu Text###30 08 2015 new variable -assessment#:#mainbar_button_label_questionlist#:#Questionlist###26 08 2024 new variable assessment#:#maintenance#:#Säilitamine assessment#:#manscoring#:#Käsitsi punktihaldus assessment#:#manscoring_done#:#Hinnatud osalejad @@ -916,7 +905,6 @@ assessment#:#maximum_nr_of_tries_reached#:#Lubatud soorituste arv on täis. Test assessment#:#maximum_points#:#Maksimaalsed võimalikud punktid assessment#:#maxsize#:#Üleslaetava faili maksimaalne suurus assessment#:#maxsize_info#:#Sisesta üleslaetavate failide lubatud maksimumsuurus baitides. Kui jätad selle välja tühjaks, valib see installatsioon lubatud maksimumsuruse ise. -assessment#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable assessment#:#min_ip_label#:#Lowest IP With Access###26 08 2024 new variable assessment#:#min_percentage_ne_0#:#Sa pead määrama miinimumtasemeks 0 protsenti! Hindeskeemi ei salvestatud. assessment#:#misc#:#Misc Options @@ -925,7 +913,6 @@ assessment#:#mode_onebyone#:#One by One###29 10 2025 new variable assessment#:#mode_question#:#Question oriented###29 10 2025 new variable assessment#:#mode_user#:#Participant oriented###29 10 2025 new variable assessment#:#msg_circle_added#:#Ringiala lisatud -assessment#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable assessment#:#msg_number_of_terms_too_low#:#Mõistete arv peab olema võrdne (või suurem) definitsioonide arvuga. assessment#:#msg_poly_added#:#Hulknurk lisatud assessment#:#msg_questions_moved#:#Küsimus(ed) on ümber tõstetud @@ -984,7 +971,6 @@ assessment#:#order#:#Order###26 08 2024 new variable assessment#:#ordering_answer_sequence_info#:#Vastuste järjestust siin arvestatakse kui õige vastuse järjestust. assessment#:#ordertext#:#Teksti järjestamine assessment#:#ordertext_info#:#Palun sisesta tekst, mis peaks olema järjestatud horisontaalselt. Järjestatav tekst eraldatakse tekstis olevate valgete tühikutega. Kui vajad muud eraldajat, võid kasutada eraldajat %s oma tekstiosade eraldamiseks. -assessment#:#out_of_range#:#Out of range assessment#:#output#:#Väljund assessment#:#output_mode#:#Väljundi olek assessment#:#parseQuestion#:#Parse Question @@ -1053,7 +1039,6 @@ assessment#:#qpl_bulk_save_add#:#Add###28 10 2024 new variable assessment#:#qpl_bulk_save_overwrite#:#Overwrite###28 10 2024 new variable assessment#:#qpl_bulkedit_success#:#Modifications saved.###28 10 2024 new variable assessment#:#qpl_cancel_skill_assigns_update#:#Cancel###30 08 2015 new variable -assessment#:#qpl_confirm_delete_questions#:#Kas oled kindel, et soovid järgmise(d) küsimuse(d) kustutada? Kui sa kustutad lukustatud küsimused, kustutatakse ka kõiki lukustatud küsimusi sisaldavad testitulemused. assessment#:#qpl_copy_insert_clipboard#:#Valitud küsimus(ed) on puhvrisse salvestatud. assessment#:#qpl_copy_select_none#:#Palun märgista puhvrisse kopeerimiseks vähemalt üks küsimus assessment#:#qpl_delete_rbac_error#:#Sul ei ole piisavalt õigusi selle küsimuse kustutamiseks! @@ -1106,7 +1091,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#Competence###30 08 2015 new variable assessment#:#qpl_qst_skl_usg_sklpnt_col#:#Total Sum of Competence-Points per Competence###30 08 2015 new variable assessment#:#qpl_question_is_in_use#:#Küsimus, mida muuta soovid, on käigus juba %s testis. Kui muudad seda küsimust, ei muuda sa seda küsimust testi(de)s, sest süsteem loob testidesse sisestamiseks küsimusest koopia! assessment#:#qpl_questions_deleted#:#Küsimus(ed) on kustutatud. -assessment#:#qpl_reset_preview#:#Reset Preview assessment#:#qpl_save_skill_assigns_update#:#Save Competence Assignments###30 08 2015 new variable assessment#:#qpl_settings_availability#:#Availability###26 08 2024 new variable assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#When enabled, possibly created taxonomies are shown for filtering. @@ -1136,14 +1120,6 @@ assessment#:#qst_essay_chars_remaining#:#Remaining characters:###07 02 2020 new assessment#:#qst_essay_wordcounter_enabled#:#Count Words###07 02 2020 new variable assessment#:#qst_essay_wordcounter_enabled_info#:#The entered words are counted. The number of written words is shown to the participants below the text input field.###07 02 2020 new variable assessment#:#qst_essay_written_words#:#Number of entered words:###07 02 2020 new variable -assessment#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable -assessment#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable -assessment#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable -assessment#:#qst_lifecycle_final#:#Final###07 02 2020 new variable -assessment#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable -assessment#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable -assessment#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable -assessment#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable assessment#:#qst_nested_nested_answers_off#:#No indents, just order###29 07 2022 new variable assessment#:#qst_nested_nested_answers_on#:#Use indents in anwers###29 07 2022 new variable assessment#:#qst_nr_of_tries#:#Katsetuste arv @@ -1167,17 +1143,14 @@ assessment#:#question_title#:#Küsimuse pealkiri assessment#:#question_type#:#Küsimuse liik assessment#:#questionpool_not_entered#:#Palun sisesta küsimustevaramu nimi! assessment#:#questionpool_not_selected#:#Please select a question pool!###07 02 2020 new variable -assessment#:#questions#:#Questions###26 08 2024 new variable assessment#:#questions_from#:#küsimust assessment#:#questions_per_page_view#:#Lehe vaade assessment#:#random_accept_sample#:#Kinnita küsimused assessment#:#random_another_sample#:#Küsi uusi küsimusi assessment#:#random_selection#:#Juhuvalik assessment#:#range#:#Vahemik -assessment#:#range_lower_limit#:#Alumine piir assessment#:#range_max#:#Range (Maximum) assessment#:#range_min#:#Range (Minimum) -assessment#:#range_upper_limit#:#Ülemine piir assessment#:#rated_sign#:#Sign assessment#:#rated_unit#:#Unit assessment#:#rated_value#:#Value @@ -1253,7 +1226,6 @@ assessment#:#search_roles#:#Otsi rolle assessment#:#search_term#:#Otsingusõna assessment#:#select_at_least_one_feedback_type_and_trigger#:#Please Select at least one type of feedback and a trigger.###26 08 2024 new variable assessment#:#select_at_least_one_lock_answer_type#:#Please select at least one type of answer lock.###29 10 2025 new variable -assessment#:#select_gap#:#Vali lünk assessment#:#select_max_one_item#:#Palun vali ainult üks element. assessment#:#select_one_user#:#Palun vali vähemalt üks kasutaja. assessment#:#select_question#:#Select a Question###28 10 2024 new variable @@ -1280,7 +1252,6 @@ assessment#:#show_old_introduction#:#Show old introduction###26 08 2024 new vari assessment#:#show_pass_overview#:#Näita märgistatud soorituse ülevaadet assessment#:#show_results#:#Show Results###28 10 2024 new variable assessment#:#show_user_answers#:#Näita kasutaja märgitud vastuseid -assessment#:#shuffle_answers#:#Sega vastuseid assessment#:#skip_question#:#Do not Answer and Next###08 10 2015 new variable assessment#:#solution#:#Solution###28 10 2024 new variable assessment#:#solutionText#:#Tekst @@ -13971,6 +13942,35 @@ qpl#:#qpl_page_type_qfbg#:#General Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qfbs#:#Special Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qht#:#Hint###29 07 2022 new variable qpl#:#qpl_page_type_qpl#:#Question Page###29 07 2022 new variable +qsts#:#answer_options#:#Answer Options ###23 12 2015 new variable +qsts#:#cloze_text#:#Lünktekst +qsts#:#cloze_textgapcase_insensitive#:#Mittetundlik variant +qsts#:#cloze_textgapcase_sensitive#:#Tundlik variant +qsts#:#cloze_textgaplevenshtein_of#:#Levenšteini kaugus of %s +qsts#:#confirm_delete_questions#:#Kas oled kindel, et soovid järgmise(d) küsimuse(d) kustutada? Kui sa kustutad lukustatud küsimused, kustutatakse ka kõiki lukustatud küsimusi sisaldavad testitulemused. +qsts#:#create_question#:#Create Question +qsts#:#gap#:#Lünk +qsts#:#gaps#:#Gaps###29 10 2025 new variable +qsts#:#insert_gap#:#Insert Gap +qsts#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable +qsts#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable +qsts#:#out_of_range#:#Out of range +qsts#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable +qsts#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable +qsts#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable +qsts#:#qst_lifecycle_final#:#Final###07 02 2020 new variable +qsts#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable +qsts#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable +qsts#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable +qsts#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable +qsts#:#questionlist#:#Questionlist###26 08 2024 new variable +qsts#:#questions#:#Questions###26 08 2024 new variable +qsts#:#range_lower_limit#:#Alumine piir +qsts#:#range_upper_limit#:#Ülemine piir +qsts#:#reset_preview#:#Reset Preview +qsts#:#select_gap#:#Vali lünk +qsts#:#shuffle_answers#:#Sega vastuseid +qsts#:#suggested_learning_content#:#Lisa soovituslik lahendus rating#:#rat_not_rated_yet#:#Pole veel hääletatud rating#:#rat_nr_ratings#:#%s Ratings rating#:#rat_one_rating#:#Üks hääletus @@ -16620,7 +16620,6 @@ survey#:#questionblock#:#Küsimusteplokk survey#:#questionblock_inserted#:#Küsimusteplokk on sisestatud survey#:#questionblocks#:#Küsimusteplokid survey#:#questionblocks_inserted#:#Küsimusteplokid on sisestatud -survey#:#questions#:#Küsimused survey#:#questions_inserted#:#Küsimus(ed) on sisestatud! survey#:#questions_removed#:#Küsimus(ed) ja/või küsimusteplok(id) on eemaldatud! survey#:#questiontype#:#Küsimuse tüüp @@ -16921,6 +16920,7 @@ survey#:#svy_please_select_unused_codes#:#Please select at least one unused code survey#:#svy_print_hide_labels#:#Hide labels###31 08 2017 new variable survey#:#svy_print_show_labels#:#Show labels###31 08 2017 new variable survey#:#svy_privacy_info#:#Privacy###29 07 2022 new variable +survey#:#svy_questions#:#Küsimused survey#:#svy_rater#:#Rater###29 07 2022 new variable survey#:#svy_rater_see_app_info#:#The names of appraisees will be presented to raters to enable them evaluating the questions.###29 07 2022 new variable survey#:#svy_reminder_mail_template#:#Mail Template###25 10 2016 new variable diff --git a/lang/ilias_fa.lang b/lang/ilias_fa.lang index 6dd4ffa6a623..fedabde89a79 100644 --- a/lang/ilias_fa.lang +++ b/lang/ilias_fa.lang @@ -422,7 +422,6 @@ adve#:#adve_use_tiny_mce#:#Enable TinyMCE for WYSIWYG Editing assessment#:#activate_logging#:#فعال کردن ثبت وقایع تست و ارزیابی assessment#:#activate_manual_scoring#:#Enable Manual Scoring###28 10 2024 new variable assessment#:#activate_manual_scoring_desc#:#Enables manual Scoring for all question types.###28 10 2024 new variable -assessment#:#addSuggestedSolution#:#اضافه کردن راه حل پیشنهادی assessment#:#add_answers#:#Add Antworten ###23 12 2015 new variable assessment#:#add_circle#:#Add circle area assessment#:#add_gap#:#Add Gap Text @@ -447,7 +446,6 @@ assessment#:#answer_is_not_correct_but_positive#:#You've got points for your sol assessment#:#answer_is_right#:#Your solution is correct assessment#:#answer_is_wrong#:#Your solution is wrong assessment#:#answer_of#:#Answer of###07 02 2020 new variable -assessment#:#answer_options#:#Answer Options: ###23 12 2015 new variable assessment#:#answer_question#:#Answer Question###30 08 2015 new variable assessment#:#answer_text#:#متن جواب assessment#:#answer_types#:#انواع جواب @@ -501,7 +499,6 @@ assessment#:#ass_completion_by_submission#:#کامل شده با ارسال assessment#:#ass_completion_by_submission_info#:#اگر فعال شود، ارسال حداقل یک فایل باعث کامل شدن سوال شده و نمره دهی هم انجام می شود. assessment#:#ass_create_export_file_with_results#:#Create Test Export File (incl. Participant Results)###25 10 2016 new variable assessment#:#ass_create_export_test_archive#:#Create Test Archive File###24 10 2013 new variable -assessment#:#ass_create_question#:#Create Question###24 10 2013 new variable assessment#:#ass_imap_hint#:#Hint to be shown as Tooltip###25 10 2016 new variable assessment#:#ass_imap_map_file_not_readable#:#The uploaded image map could not be read.###25 10 2016 new variable assessment#:#ass_imap_no_map_found#:#Could not find any form in the uploaded image map.###25 10 2016 new variable @@ -571,10 +568,6 @@ assessment#:#cloze_answer_text_info#:#Spaces preceding or following the answer t assessment#:#cloze_fixed_textlength#:#طول فیلد متن assessment#:#cloze_fixed_textlength_description#:#اگر مقداری بیشتر از 0 وارد کنید، همه فیلدهای متن با این اندازه ساخته خواهند شد. assessment#:#cloze_gap_size_info#:#If you enter a value greater than 0, this gap text field will be created with the fixed length of this value. If you do not enter a value the gap text fiel will be created with the value of the global fixed length.###26 09 2014 new variable -assessment#:#cloze_text#:#متن -assessment#:#cloze_textgap_case_insensitive#:#غیر حساس به کوچک و بزرگی حروف -assessment#:#cloze_textgap_case_sensitive#:#حساس به کوچک و بزرگی حروف -assessment#:#cloze_textgap_levenshtein_of#:#فاصله Levenshtein %s assessment#:#code#:#کد assessment#:#codebase#:#پایگاه کد assessment#:#concatenation#:#Concatenation @@ -757,9 +750,7 @@ assessment#:#fq_formula_desc#:#You may enter predefined variables ($v1 to $vn), assessment#:#fq_no_restriction_info#:#Both decimals and fractions are accepted as input.###26 03 2014 new variable assessment#:#fq_precision_info#:#Enter the number of desired decimal places.###26 03 2014 new variable assessment#:#fq_question_desc#:#You can define variables by inserting $v1, $v2 ... $vn, results by inserting $r1, $r2 .... $rn at the desired position in the question text. Click on the button "Parse Question" to create editing forms for variables and results.###06 11 2013 new variable -assessment#:#gap#:#Gap assessment#:#gap_combination#:#Gap Combination###26 09 2014 new variable -assessment#:#gaps#:#Gaps###29 10 2025 new variable assessment#:#glossary_term#:#اصطلاح لغتنامه assessment#:#goto_first_question#:#Show First Question###29 10 2025 new variable assessment#:#grading_mark_msg#:#Your resulting mark is: "[mark]"###26 09 2014 new variable @@ -777,7 +768,6 @@ assessment#:#info_answer_type_change#:#The question already contains images. You assessment#:#info_text_upload#:#Choose an answer file to upload###30 08 2015 new variable assessment#:#insert_after#:#Insert After assessment#:#insert_before#:#Insert Before -assessment#:#insert_gap#:#Insert Gap###24 10 2013 new variable assessment#:#interaction_type#:#Interaction Type###26 08 2024 new variable assessment#:#internal_links#:#Internal Links assessment#:#intprecision#:#Divisible By###24 10 2013 new variable @@ -889,7 +879,6 @@ assessment#:#logs_wrong_test_password_provided#:#Participant entered wrong test assessment#:#longmenu#:#Longmenu###10 11 2018 new variable assessment#:#longmenu_answeroptions_differ#:#This question does not work correctly, as there are not the same amount of gaps in the text as in the correction options.###26 08 2024 new variable assessment#:#longmenu_text#:#Long Menu Text###30 08 2015 new variable -assessment#:#mainbar_button_label_questionlist#:#Questionlist###26 08 2024 new variable assessment#:#maintenance#:#نگهداری assessment#:#manscoring#:#امتیازدهی دستی assessment#:#manscoring_done#:#شرکت کنندگان دارای نمره @@ -916,7 +905,6 @@ assessment#:#maximum_nr_of_tries_reached#:#You have reached the maximum number o assessment#:#maximum_points#:#حداکثر امتیازات موجود assessment#:#maxsize#:#حداکثر اندازه فایل برای آپلود assessment#:#maxsize_info#:#مقدار به بایت -assessment#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable assessment#:#min_ip_label#:#Lowest IP With Access###26 08 2024 new variable assessment#:#min_percentage_ne_0#:#You must define a minimum percentage of 0 percent! The mark schema wasn't saved. assessment#:#misc#:#Misc Options###24 10 2013 new variable @@ -925,7 +913,6 @@ assessment#:#mode_onebyone#:#One by One###29 10 2025 new variable assessment#:#mode_question#:#Question oriented###29 10 2025 new variable assessment#:#mode_user#:#Participant oriented###29 10 2025 new variable assessment#:#msg_circle_added#:#Circle added -assessment#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable assessment#:#msg_number_of_terms_too_low#:#The number of terms must be greater or equal to the number of definitions. assessment#:#msg_poly_added#:#Polygon added assessment#:#msg_questions_moved#:#Question(s) moved @@ -984,7 +971,6 @@ assessment#:#order#:#Order###26 08 2024 new variable assessment#:#ordering_answer_sequence_info#:#دنباله جوابی که اینجا مشخص می کنید به عنوان دنباله جواب در نظر گرفته خواهد شد. assessment#:#ordertext#:#متن مرتب سازی assessment#:#ordertext_info#:#لطفا متنی را که باید بصورت افقی مرتب شود را وارد کنید. متنها با فاصله از هم جدا می شوند. می توانید از %s هم برای جدا کردن واحدهای متن استفاده کنید. -assessment#:#out_of_range#:#Out of range###27 01 2015 new variable assessment#:#output#:#Output assessment#:#output_mode#:#حالت خروجی assessment#:#parseQuestion#:#Parse Question###24 10 2013 new variable @@ -1053,7 +1039,6 @@ assessment#:#qpl_bulk_save_add#:#Add###28 10 2024 new variable assessment#:#qpl_bulk_save_overwrite#:#Overwrite###28 10 2024 new variable assessment#:#qpl_bulkedit_success#:#Modifications saved.###28 10 2024 new variable assessment#:#qpl_cancel_skill_assigns_update#:#Cancel###30 08 2015 new variable -assessment#:#qpl_confirm_delete_questions#:#Are you sure you want to delete the following question(s)? assessment#:#qpl_copy_insert_clipboard#:#The selected question(s) are copied to the clipboard assessment#:#qpl_copy_select_none#:#Please check at least one question to copy it to the clipboard assessment#:#qpl_delete_rbac_error#:#You have no rights to delete this question! @@ -1106,7 +1091,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#Competence###30 08 2015 new variable assessment#:#qpl_qst_skl_usg_sklpnt_col#:#Total Sum of Competence-Points per Competence###30 08 2015 new variable assessment#:#qpl_question_is_in_use#:#The question you are about to edit exists in %s test(s). If you change this question, you will NOT change the question(s) in the test(s), because the system creates a copy of a question when it is inserted in a test! assessment#:#qpl_questions_deleted#:#Question(s) deleted. -assessment#:#qpl_reset_preview#:#Reset Preview###26 09 2014 new variable assessment#:#qpl_save_skill_assigns_update#:#Save Competence Assignments###30 08 2015 new variable assessment#:#qpl_settings_availability#:#Availability###26 08 2024 new variable assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#When enabled, possibly created taxonomies are shown for filtering.###24 10 2013 new variable @@ -1136,14 +1120,6 @@ assessment#:#qst_essay_chars_remaining#:#Remaining characters:###07 02 2020 new assessment#:#qst_essay_wordcounter_enabled#:#Count Words###07 02 2020 new variable assessment#:#qst_essay_wordcounter_enabled_info#:#The entered words are counted. The number of written words is shown to the participants below the text input field.###07 02 2020 new variable assessment#:#qst_essay_written_words#:#Number of entered words:###07 02 2020 new variable -assessment#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable -assessment#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable -assessment#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable -assessment#:#qst_lifecycle_final#:#Final###07 02 2020 new variable -assessment#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable -assessment#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable -assessment#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable -assessment#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable assessment#:#qst_nested_nested_answers_off#:#No indents, just order###29 07 2022 new variable assessment#:#qst_nested_nested_answers_on#:#Use indents in anwers###29 07 2022 new variable assessment#:#qst_nr_of_tries#:#Number of tries @@ -1167,17 +1143,14 @@ assessment#:#question_title#:#عنوان سوال assessment#:#question_type#:#نوع سوال assessment#:#questionpool_not_entered#:#Please enter a name for a question pool! assessment#:#questionpool_not_selected#:#Please select a question pool!###07 02 2020 new variable -assessment#:#questions#:#Questions###26 08 2024 new variable assessment#:#questions_from#:#questions from assessment#:#questions_per_page_view#:#نمای صفحه assessment#:#random_accept_sample#:#Accept Sample assessment#:#random_another_sample#:#Get another Sample assessment#:#random_selection#:#Random Selection assessment#:#range#:#محدوده -assessment#:#range_lower_limit#:#حد پایین assessment#:#range_max#:#Range (Maximum)###24 10 2013 new variable assessment#:#range_min#:#Range (Minimum)###24 10 2013 new variable -assessment#:#range_upper_limit#:#حد بالا assessment#:#rated_sign#:#Sign###24 10 2013 new variable assessment#:#rated_unit#:#Unit###24 10 2013 new variable assessment#:#rated_value#:#Value###24 10 2013 new variable @@ -1253,7 +1226,6 @@ assessment#:#search_roles#:#Search Roles assessment#:#search_term#:#Search Term assessment#:#select_at_least_one_feedback_type_and_trigger#:#Please Select at least one type of feedback and a trigger.###26 08 2024 new variable assessment#:#select_at_least_one_lock_answer_type#:#Please select at least one type of answer lock.###29 10 2025 new variable -assessment#:#select_gap#:#Select Gap assessment#:#select_max_one_item#:#Please select one item only assessment#:#select_one_user#:#لطفا حداقل یک کاربر انتخاب کنید. assessment#:#select_question#:#Select a Question###28 10 2024 new variable @@ -1280,7 +1252,6 @@ assessment#:#show_old_introduction#:#Show old introduction###26 08 2024 new vari assessment#:#show_pass_overview#:#نمایش مرور کلی دوره انتخاب شده assessment#:#show_results#:#Show Results###28 10 2024 new variable assessment#:#show_user_answers#:#نمایش پاسخهای کاربر انتخاب شده -assessment#:#shuffle_answers#:#قاطی کردن جوابها assessment#:#skip_question#:#Do not Answer and Next###08 10 2015 new variable assessment#:#solution#:#Solution###28 10 2024 new variable assessment#:#solutionText#:#متن @@ -13971,6 +13942,35 @@ qpl#:#qpl_page_type_qfbg#:#General Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qfbs#:#Special Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qht#:#Hint###29 07 2022 new variable qpl#:#qpl_page_type_qpl#:#Question Page###29 07 2022 new variable +qsts#:#answer_options#:#Answer Options ###23 12 2015 new variable +qsts#:#cloze_text#:#متن +qsts#:#cloze_textgapcase_insensitive#:#غیر حساس به کوچک و بزرگی حروف +qsts#:#cloze_textgapcase_sensitive#:#حساس به کوچک و بزرگی حروف +qsts#:#cloze_textgaplevenshtein_of#:#فاصله Levenshtein %s +qsts#:#confirm_delete_questions#:#Are you sure you want to delete the following question(s)? +qsts#:#create_question#:#Create Question###24 10 2013 new variable +qsts#:#gap#:#Gap +qsts#:#gaps#:#Gaps###29 10 2025 new variable +qsts#:#insert_gap#:#Insert Gap###24 10 2013 new variable +qsts#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable +qsts#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable +qsts#:#out_of_range#:#Out of range###27 01 2015 new variable +qsts#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable +qsts#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable +qsts#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable +qsts#:#qst_lifecycle_final#:#Final###07 02 2020 new variable +qsts#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable +qsts#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable +qsts#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable +qsts#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable +qsts#:#questionlist#:#Questionlist###26 08 2024 new variable +qsts#:#questions#:#Questions###26 08 2024 new variable +qsts#:#range_lower_limit#:#حد پایین +qsts#:#range_upper_limit#:#حد بالا +qsts#:#reset_preview#:#Reset Preview###26 09 2014 new variable +qsts#:#select_gap#:#Select Gap +qsts#:#shuffle_answers#:#قاطی کردن جوابها +qsts#:#suggested_learning_content#:#اضافه کردن راه حل پیشنهادی rating#:#rat_not_rated_yet#:#Not Rated Yet rating#:#rat_nr_ratings#:#%s Ratings rating#:#rat_one_rating#:#One Rating @@ -16621,7 +16621,6 @@ survey#:#questionblock#:#Question Block survey#:#questionblock_inserted#:#Question Block inserted survey#:#questionblocks#:#بلوکهای سوال survey#:#questionblocks_inserted#:#Question Blocks inserted -survey#:#questions#:#سوالها survey#:#questions_inserted#:#Question(s) inserted! survey#:#questions_removed#:#Question(s) and/or question block(s) removed! survey#:#questiontype#:#Question Type @@ -16922,6 +16921,7 @@ survey#:#svy_please_select_unused_codes#:#Please select at least one unused code survey#:#svy_print_hide_labels#:#Hide labels###31 08 2017 new variable survey#:#svy_print_show_labels#:#Show labels###31 08 2017 new variable survey#:#svy_privacy_info#:#Privacy###29 07 2022 new variable +survey#:#svy_questions#:#سوالها survey#:#svy_rater#:#Rater###29 07 2022 new variable survey#:#svy_rater_see_app_info#:#The names of appraisees will be presented to raters to enable them evaluating the questions.###29 07 2022 new variable survey#:#svy_reminder_mail_template#:#Mail Template###25 10 2016 new variable diff --git a/lang/ilias_fr.lang b/lang/ilias_fr.lang index 2ebad3a3957f..b4fe4570f655 100644 --- a/lang/ilias_fr.lang +++ b/lang/ilias_fr.lang @@ -407,7 +407,6 @@ adve#:#adve_use_tiny_mce#:#Activez TinyMCE pour une Edition WYSIWYG assessment#:#activate_logging#:#Activer le Suivi des Tests et Evaluations assessment#:#activate_manual_scoring#:#Enable Manual Scoring###28 10 2024 new variable assessment#:#activate_manual_scoring_desc#:#Enables manual Scoring for all question types.###28 10 2024 new variable -assessment#:#addSuggestedSolution#:#Ajouter solution suggérée assessment#:#add_answers#:#Add Antworten assessment#:#add_circle#:#Ajouter Zone Circulaire assessment#:#add_gap#:#Ajouter une nouvelle valeur @@ -432,7 +431,6 @@ assessment#:#answer_is_not_correct_but_positive#:#Vous obtenez des points pour v assessment#:#answer_is_right#:#Votre réponse est correcte assessment#:#answer_is_wrong#:#Votre réponse est incorrecte assessment#:#answer_of#:#Answer of -assessment#:#answer_options#:#Options de réponse: assessment#:#answer_question#:#Répondez à la question assessment#:#answer_text#:#Texte de Réponse assessment#:#answer_types#:#Types de réponse @@ -486,7 +484,6 @@ assessment#:#ass_completion_by_submission#:#Validation Automatique assessment#:#ass_completion_by_submission_info#:#Le dépôt d'un fichier permet de valider cette question en attribuant le score maximum. Le score peut toutefois être modifié ultérieurement. Modifier cette option n'affecte pas les réponses déjà fournies. assessment#:#ass_create_export_file_with_results#:#Créer un fichier d’exportation de tests (y compris les résultats des participants) assessment#:#ass_create_export_test_archive#:#Créer Archive du Test -assessment#:#ass_create_question#:#Créer Question assessment#:#ass_imap_hint#:#Indice à montrer comme info-bulle assessment#:#ass_imap_map_file_not_readable#:#L’image de carte chargée n’a pas pu être lue. assessment#:#ass_imap_no_map_found#:#N’a pas pu trouver de forme dans la carte d’image chargée. @@ -556,10 +553,6 @@ assessment#:#cloze_answer_text_info#:#Spaces preceding or following the answer t assessment#:#cloze_fixed_textlength#:#Longueur du Champ Texte assessment#:#cloze_fixed_textlength_description#:#Si une valeur supérieure à zéro est saisie , tous les champs de texte seront créés avec cette valeur comme longueur de champ. assessment#:#cloze_gap_size_info#:#Si vous entrez une valeur supérieure à 0, ce champ de texte d'écart sera créé avec la longueur fixe de cette valeur. Si vous n'entrez pas de valeur, le champ de texte d'écart sera créé avec la valeur de la longueur fixe globale. -assessment#:#cloze_text#:#Texte à trous -assessment#:#cloze_textgap_case_insensitive#:#Ne pas distinguer majuscules et minuscules -assessment#:#cloze_textgap_case_sensitive#:#Distinguer majuscules et minuscules -assessment#:#cloze_textgap_levenshtein_of#:#Distance Levenshtein de %s assessment#:#code#:#Code assessment#:#codebase#:#Codebase assessment#:#concatenation#:#Concaténation @@ -742,9 +735,7 @@ assessment#:#fq_formula_desc#:#Vous pouvez entrer des variables prédéfinies ($ assessment#:#fq_no_restriction_info#:#Les décimales et les fractions sont des entrées acceptées. assessment#:#fq_precision_info#:#Entrez le nombre de décimales souhaitées. assessment#:#fq_question_desc#:#Vous pouvez définir des variables en insérant $v1, $v2 ... $vn, des résultats en insérant $r1, $r2 .... $rn aux positions souhaitées dans le texte de la question. Cliquer sur le bouton "Traiter la Question" pour créer le formulaire d'édition des variables et réponses. -assessment#:#gap#:#Trou assessment#:#gap_combination#:#Association de Gap -assessment#:#gaps#:#Gaps###29 10 2025 new variable assessment#:#glossary_term#:#Terme de glossaire assessment#:#goto_first_question#:#Show First Question###29 10 2025 new variable assessment#:#grading_mark_msg#:#Votre résultat est: "[mark]" @@ -762,7 +753,6 @@ assessment#:#info_answer_type_change#:#La question contient déjà des images. assessment#:#info_text_upload#:#Choisissez un fichier de réponses à télécharger assessment#:#insert_after#:#Ajouter après assessment#:#insert_before#:#Ajouter avant -assessment#:#insert_gap#:#Insérer Ecart assessment#:#interaction_type#:#Interaction Type###26 08 2024 new variable assessment#:#internal_links#:#Liens internes assessment#:#intprecision#:#Divisible Par @@ -874,7 +864,6 @@ assessment#:#logs_wrong_test_password_provided#:#Le Participant a entré un mot assessment#:#longmenu#:#Longmenu assessment#:#longmenu_answeroptions_differ#:#This question does not work correctly, as there are not the same amount of gaps in the text as in the correction options. assessment#:#longmenu_text#:#Texte de menu long -assessment#:#mainbar_button_label_questionlist#:#Questionlist###31 10 2023 new variable assessment#:#maintenance#:#Maintenance assessment#:#manscoring#:#Notation Manuelle assessment#:#manscoring_done#:#Participants notés @@ -901,7 +890,6 @@ assessment#:#maximum_nr_of_tries_reached#:#Vous avez atteint le nombre de tentat assessment#:#maximum_points#:#Nombre Maximum de Points assessment#:#maxsize#:#Taille maximale du fichier à télécharger assessment#:#maxsize_info#:#Saisir la taille maximale en bytes autorisée pour le téléversement. Si cette zone de saisie reste vide, le maximum par défaut sera proposé. -assessment#:#min_auto_complete#:#Saisie automatique assessment#:#min_ip_label#:#Lowest IP With Access###26 08 2024 new variable assessment#:#min_percentage_ne_0#:#Vous devez définir un pourcentage minimal de 0 %. L'échelle d'évaluation n'a pas été enregistrée. assessment#:#misc#:#Options Mixtes @@ -910,7 +898,6 @@ assessment#:#mode_onebyone#:#One by One###29 10 2025 new variable assessment#:#mode_question#:#Question oriented###29 10 2025 new variable assessment#:#mode_user#:#Participant oriented###29 10 2025 new variable assessment#:#msg_circle_added#:#Cercle ajouté -assessment#:#msg_no_questions_selected#:#No questions were selected.###31 10 2023 new variable assessment#:#msg_number_of_terms_too_low#:#Le nombre de termes doit être supérieur ou équal au nombre de définitions. assessment#:#msg_poly_added#:#Polygone ajouté assessment#:#msg_questions_moved#:#Question(s) déplacée(s) @@ -969,7 +956,6 @@ assessment#:#order#:#Order assessment#:#ordering_answer_sequence_info#:#La séquence de réponse définie, ici, sera prise comme séquence correcte de la solution. assessment#:#ordertext#:#Texte à ordonner assessment#:#ordertext_info#:#Veuillez saisir le texte devant être ordonné horizontalement. Le texte à ordonner devra être séparé par des caractères blancs dans le texte. Vous pouvez aussi utiliser le séparateur %s pour séparer vos groupes de texte. -assessment#:#out_of_range#:#Hors de portée assessment#:#output#:#Restitution assessment#:#output_mode#:#Mode de Restitution assessment#:#parseQuestion#:#Traiter la Question @@ -1038,7 +1024,6 @@ assessment#:#qpl_bulk_save_add#:#Add###28 10 2024 new variable assessment#:#qpl_bulk_save_overwrite#:#Overwrite###28 10 2024 new variable assessment#:#qpl_bulkedit_success#:#Modifications saved.###28 10 2024 new variable assessment#:#qpl_cancel_skill_assigns_update#:#Annuler -assessment#:#qpl_confirm_delete_questions#:#Etes-vous sûr(e) de vouloir supprimer la(les) question(s) suivante(s) ? Si vous détruisez des questions verrouillées, les résultats de tous les tests contenant une question verrouillée seront détruits également. assessment#:#qpl_copy_insert_clipboard#:#La ou les questions sélectionnés sont copiées dans le presse-papier assessment#:#qpl_copy_select_none#:#Cocher au moins une question à copier dans le presse-papier assessment#:#qpl_delete_rbac_error#:#Vous n'avez pas le droit de supprimer cette question! @@ -1091,7 +1076,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#Compétence assessment#:#qpl_qst_skl_usg_sklpnt_col#:#Somme totale de points de compétence par compétence assessment#:#qpl_question_is_in_use#:#La question que vous êtes sur le point de modifier existe dans %s test(s). Si vous changez cette question, vous ne la changerez pas dans ce(s) test(s), car le système crée une copie de la question quand elle est ajoutée dans un test. assessment#:#qpl_questions_deleted#:#Question(s) détruite(s). -assessment#:#qpl_reset_preview#:#Réinitialiser l'aperàçu assessment#:#qpl_save_skill_assigns_update#:#Sauvegarder les tâches de compétence assessment#:#qpl_settings_availability#:#Availability###31 10 2023 new variable assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#Lorsque activé, les taxinomies créées sont utilisées pour le filtrage. @@ -1121,14 +1105,6 @@ assessment#:#qst_essay_chars_remaining#:#Remaining characters: assessment#:#qst_essay_wordcounter_enabled#:#Count Words assessment#:#qst_essay_wordcounter_enabled_info#:#The entered words are counted. The number of written words is shown to the participants below the text input field. assessment#:#qst_essay_written_words#:#Number of entered words: -assessment#:#qst_lifecycle#:#Lifecycle -assessment#:#qst_lifecycle_draft#:#Draft -assessment#:#qst_lifecycle_filter_all#:#All Lifecycles -assessment#:#qst_lifecycle_final#:#Final -assessment#:#qst_lifecycle_outdated#:#Outdated -assessment#:#qst_lifecycle_rejected#:#Rejected -assessment#:#qst_lifecycle_review#:#To be Reviewed -assessment#:#qst_lifecycle_sharable#:#Sharable assessment#:#qst_nested_nested_answers_off#:#No indents, just order assessment#:#qst_nested_nested_answers_on#:#Use indents in anwers assessment#:#qst_nr_of_tries#:#Nombre de tentatives @@ -1152,17 +1128,14 @@ assessment#:#question_title#:#Titre de la Question assessment#:#question_type#:#Type de Question assessment#:#questionpool_not_entered#:#Veuillez saisir un nom pour un groupe de questions. assessment#:#questionpool_not_selected#:#Please select a question pool! -assessment#:#questions#:#Questions###31 10 2023 new variable assessment#:#questions_from#:#questions de assessment#:#questions_per_page_view#:#Vue par Page assessment#:#random_accept_sample#:#Accepter échantillon assessment#:#random_another_sample#:#Obtenir un autre échantillon assessment#:#random_selection#:#Tirage Aléatoire assessment#:#range#:#Etendue -assessment#:#range_lower_limit#:#Limite Inférieure assessment#:#range_max#:#Intervalle (Maximum) assessment#:#range_min#:#Intervalle (Minimum) -assessment#:#range_upper_limit#:#Limite Supérieure assessment#:#rated_sign#:#Signe assessment#:#rated_unit#:#Unité assessment#:#rated_value#:#Valeur @@ -1238,7 +1211,6 @@ assessment#:#search_roles#:#Recherche des rôles assessment#:#search_term#:#Rechercher des termes assessment#:#select_at_least_one_feedback_type_and_trigger#:#Please Select at least one type of feedback and a trigger.###26 08 2024 new variable assessment#:#select_at_least_one_lock_answer_type#:#Please select at least one type of answer lock.###29 10 2025 new variable -assessment#:#select_gap#:#Selectionner trou assessment#:#select_max_one_item#:#Veuillez ne saisir qu'un objet assessment#:#select_one_user#:#Veuillez sélectionner au moins un utilisateur assessment#:#select_question#:#Select a Question###28 10 2024 new variable @@ -1265,7 +1237,6 @@ assessment#:#show_old_introduction#:#Afficher l'ancienne introduction assessment#:#show_pass_overview#:#Afficher les Résultats assessment#:#show_results#:#Show Results###28 10 2024 new variable assessment#:#show_user_answers#:#Afficher les Réponses de l'Utilisateur -assessment#:#shuffle_answers#:#Mélanger les Réponses assessment#:#skip_question#:#Ne pas répondre et passer assessment#:#solution#:#Solution###28 10 2024 new variable assessment#:#solutionText#:#Texte @@ -13956,6 +13927,35 @@ qpl#:#qpl_page_type_qfbg#:#General Feedback qpl#:#qpl_page_type_qfbs#:#Special Feedback qpl#:#qpl_page_type_qht#:#Hint qpl#:#qpl_page_type_qpl#:#Question Page +qsts#:#answer_options#:#Options de réponse +qsts#:#cloze_text#:#Texte à trous +qsts#:#cloze_textgapcase_insensitive#:#Ne pas distinguer majuscules et minuscules +qsts#:#cloze_textgapcase_sensitive#:#Distinguer majuscules et minuscules +qsts#:#cloze_textgaplevenshtein_of#:#Distance Levenshtein de %s +qsts#:#confirm_delete_questions#:#Etes-vous sûr(e) de vouloir supprimer la(les) question(s) suivante(s) ? Si vous détruisez des questions verrouillées, les résultats de tous les tests contenant une question verrouillée seront détruits également. +qsts#:#create_question#:#Créer Question +qsts#:#gap#:#Trou +qsts#:#gaps#:#Gaps###29 10 2025 new variable +qsts#:#insert_gap#:#Insérer Ecart +qsts#:#min_auto_complete#:#Saisie automatique +qsts#:#msg_no_questions_selected#:#No questions were selected.###31 10 2023 new variable +qsts#:#out_of_range#:#Hors de portée +qsts#:#qst_lifecycle#:#Lifecycle +qsts#:#qst_lifecycle_draft#:#Draft +qsts#:#qst_lifecycle_filter_all#:#All Lifecycles +qsts#:#qst_lifecycle_final#:#Final +qsts#:#qst_lifecycle_outdated#:#Outdated +qsts#:#qst_lifecycle_rejected#:#Rejected +qsts#:#qst_lifecycle_review#:#To be Reviewed +qsts#:#qst_lifecycle_sharable#:#Sharable +qsts#:#questionlist#:#Questionlist###31 10 2023 new variable +qsts#:#questions#:#Questions###31 10 2023 new variable +qsts#:#range_lower_limit#:#Limite Inférieure +qsts#:#range_upper_limit#:#Limite Supérieure +qsts#:#reset_preview#:#Réinitialiser l'aperàçu +qsts#:#select_gap#:#Selectionner trou +qsts#:#shuffle_answers#:#Mélanger les Réponses +qsts#:#suggested_learning_content#:#Ajouter solution suggérée rating#:#rat_not_rated_yet#:#Pas Encore Noté rating#:#rat_nr_ratings#:#%s Notations rating#:#rat_one_rating#:#Une Notation @@ -16605,7 +16605,6 @@ survey#:#questionblock#:#Bloc de Questions survey#:#questionblock_inserted#:#Bloc de question ajouté survey#:#questionblocks#:#Blocs de Questions survey#:#questionblocks_inserted#:#Bloc de questions ajouté -survey#:#questions#:#Questions survey#:#questions_inserted#:#Question(s) ajoutée(s) survey#:#questions_removed#:#Question(s) et/ou bloc(s) de questions supprimé(es) ! survey#:#questiontype#:#Type de Question @@ -16906,6 +16905,7 @@ survey#:#svy_please_select_unused_codes#:#Please select at least one unused code survey#:#svy_print_hide_labels#:#Masquer les vignettes survey#:#svy_print_show_labels#:#Montrer les vignettes survey#:#svy_privacy_info#:#Privacy###31 10 2023 new variable +survey#:#svy_questions#:#Questions survey#:#svy_rater#:#Rater###31 10 2023 new variable survey#:#svy_rater_see_app_info#:#The names of appraisees will be presented to raters to enable them evaluating the questions.###31 10 2023 new variable survey#:#svy_reminder_mail_template#:#Modèle d’e-mail diff --git a/lang/ilias_hr.lang b/lang/ilias_hr.lang index 77f56a71ffb7..29bd1184a5b7 100644 --- a/lang/ilias_hr.lang +++ b/lang/ilias_hr.lang @@ -422,7 +422,6 @@ adve#:#adve_use_tiny_mce#:#Enable TinyMCE for WYSIWYG Editing assessment#:#activate_logging#:#Aktivirajte protokoliranje testa i procjene assessment#:#activate_manual_scoring#:#Enable Manual Scoring###28 10 2024 new variable assessment#:#activate_manual_scoring_desc#:#Enables manual Scoring for all question types.###28 10 2024 new variable -assessment#:#addSuggestedSolution#:#Sadržaji za ponavljanje assessment#:#add_answers#:#Dodaj odgovore assessment#:#add_circle#:#Dodaj krug assessment#:#add_gap#:#Dodaj tekst za praznine @@ -447,7 +446,6 @@ assessment#:#answer_is_not_correct_but_positive#:#Dobili ste bodove za svoje rje assessment#:#answer_is_right#:#Vaše rješenje je ispravno. assessment#:#answer_is_wrong#:#Vaše rješenje je pogrešno. assessment#:#answer_of#:#Answer of###04 06 2021 new variable -assessment#:#answer_options#:#Opcija odgovora: assessment#:#answer_question#:#Odgovori na pitanje assessment#:#answer_text#:#Tekst odgovora assessment#:#answer_types#:#Urednik odgovora @@ -501,7 +499,6 @@ assessment#:#ass_completion_by_submission#:#Polaganje dostavom assessment#:#ass_completion_by_submission_info#:#Ako je to aktivirano, dostava datoteke s rješenjem dovodi do dodjele maksimalnog broja bodova za ovo pitanje. Procjena se u svakom trenutku može prilagoditi ručno. Promjena ove postavke nema nikakvih naknadnih učinaka na već dostavljena rješenja. assessment#:#ass_create_export_file_with_results#:#Izradi datoteku za izvoz (uklj. rezultate sudionika) assessment#:#ass_create_export_test_archive#:#Izradi datoteku arhive za test -assessment#:#ass_create_question#:#Izradi pitanje assessment#:#ass_imap_hint#:#Napomena (prikazuje kao tooltip) assessment#:#ass_imap_map_file_not_readable#:#Učitani se Imagemap ne može pročitati. assessment#:#ass_imap_no_map_found#:#U učitanom Imapemap-u nije pronađen nijedan oblik koji se podržava. @@ -571,10 +568,6 @@ assessment#:#cloze_answer_text_info#:#Spaces preceding or following the answer t assessment#:#cloze_fixed_textlength#:#Duljina tekstualnog polja assessment#:#cloze_fixed_textlength_description#:#Ako ovdje unesete vrijednost, generiraju se praznine u tekstu koje ne definiraju vlastitu vrijednost za maksimalnu duljinu kao i numeričke praznine te duljine, tako da nije moguće upisati veći broj znakova od dopuštenog. Kod numeričkih praznina također treba voditi računa da se decimalni separator također računa kao znak. assessment#:#cloze_gap_size_info#:#Ako je upisana vrijednost veća od 0, to se tekstualno polje s prazninom generira s ovdje navedenom duljinom. Ako nije upisana vrijednost, to se tekstualno polje s prazninom generira s globalno određenom duljinom polja za tekst. -assessment#:#cloze_text#:#Pitanje s prazninom u tekstu -assessment#:#cloze_textgap_case_insensitive#:#Neosjetljivo na velika i mala slova -assessment#:#cloze_textgap_case_sensitive#:#Osjetljivo na velika i mala slova -assessment#:#cloze_textgap_levenshtein_of#:#Levenshtein razmak od%s assessment#:#code#:#Kod assessment#:#codebase#:#Kodna osnova assessment#:#concatenation#:#Povezivanje @@ -757,9 +750,7 @@ assessment#:#fq_formula_desc#:#Možete unijeti unaprijed definirane varijable ($ assessment#:#fq_no_restriction_info#:#Kao unos prihvaćeni su kako decimale tako i razlomci. assessment#:#fq_precision_info#:#Unesite broj željenih decimalnih mjesta. assessment#:#fq_question_desc#:#Varijable možete definirati tako da umetnete $v1, $v2 ... $ VN, a rezultate da umetnete $r1, $r2 .... $rn na željeno mjesto u tekstu pitanja. Kliknite na uklopnu površinu „Analiziraj Pitanje” kako bi se generirali obrasci za obradu varijabli i rezultata. -assessment#:#gap#:#Praznina assessment#:#gap_combination#:#Kombinacija praznina -assessment#:#gaps#:#Gaps###29 10 2025 new variable assessment#:#glossary_term#:#Pojam iz pojmovnika assessment#:#goto_first_question#:#Show First Question###29 10 2025 new variable assessment#:#grading_mark_msg#:#Vaša ocjena je. "[ocjena]" @@ -777,7 +768,6 @@ assessment#:#info_answer_type_change#:#Pitanje već sadrži slike. Ne možete pr assessment#:#info_text_upload#:#Odaberite datoteku s tekstom odgovora (UTF-8) kako biste je učitali. assessment#:#insert_after#:#Umetni iza assessment#:#insert_before#:#Umetni ispred -assessment#:#insert_gap#:#Umetni prazninu assessment#:#interaction_type#:#Interaction Type###26 08 2024 new variable assessment#:#internal_links#:#Unutarnje poveznice assessment#:#intprecision#:#Djeljivo s @@ -889,7 +879,6 @@ assessment#:#logs_wrong_test_password_provided#:#Sudionik je unio pogrešnu lozi assessment#:#longmenu#:#Dugi izbornik assessment#:#longmenu_answeroptions_differ#:#This question does not work correctly, as there are not the same amount of gaps in the text as in the correction options.###26 08 2024 new variable assessment#:#longmenu_text#:#Tekst dugog izbornika -assessment#:#mainbar_button_label_questionlist#:#Questionlist###26 08 2024 new variable assessment#:#maintenance#:#Održavanje assessment#:#manscoring#:#Ručno bodovanje assessment#:#manscoring_done#:#Sudionici koji su već bodovani @@ -916,7 +905,6 @@ assessment#:#maximum_nr_of_tries_reached#:#Dosegli ste maksimalan broj prolaza z assessment#:#maximum_points#:#Maksimalno raspoloživi broj bodova assessment#:#maxsize#:#Maksimalna veličina datoteke za učitavanje assessment#:#maxsize_info#:#Navedite u bajtovima maksimalnu veličinu koju bi smjela imati datoteka namijenjena za učitavanje. Ako ovo polje ostavite prazno, koristit će se umjesto toga maksimalna veličina osnovnog sustava. -assessment#:#min_auto_complete#:#Automatsko dopunjavanje assessment#:#min_ip_label#:#Lowest IP With Access###26 08 2024 new variable assessment#:#min_percentage_ne_0#:#Morate odrediti minimalni postotak od 0 posto! Shema ocjena nije spremljena. assessment#:#misc#:#Razne opcije @@ -925,7 +913,6 @@ assessment#:#mode_onebyone#:#One by One###29 10 2025 new variable assessment#:#mode_question#:#Question oriented###29 10 2025 new variable assessment#:#mode_user#:#Participant oriented###29 10 2025 new variable assessment#:#msg_circle_added#:#Dodan je krug -assessment#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable assessment#:#msg_number_of_terms_too_low#:#Broj pojmova mora biti veći ili jednak broju definicija. assessment#:#msg_poly_added#:#Dodan je poligon assessment#:#msg_questions_moved#:#Pitanje(a) je(su) pomaknuto(a) @@ -984,7 +971,6 @@ assessment#:#order#:#Order###26 08 2024 new variable assessment#:#ordering_answer_sequence_info#:#Slijed odgovora koji ovdje definirate uzet će se kao ispravan redoslijed rješenja. assessment#:#ordertext#:#Tekst za raspoređivanje assessment#:#ordertext_info#:#Unesite tekst koji treba rasporediti vodoravno. Tekst koji treba rasporediti bit će odvojen razmaknicama u tekstu. Ako trebate drugačiji odvajanje, za razdvajanje tekstualnih jedinica umjesto razmaknica možete koristiti separator%s. -assessment#:#out_of_range#:#Izvan raspona assessment#:#output#:#Izdavanje assessment#:#output_mode#:#Način izdavanja assessment#:#parseQuestion#:#Analiziraj pitanje @@ -1053,7 +1039,6 @@ assessment#:#qpl_bulk_save_add#:#Add###28 10 2024 new variable assessment#:#qpl_bulk_save_overwrite#:#Overwrite###28 10 2024 new variable assessment#:#qpl_bulkedit_success#:#Modifications saved.###28 10 2024 new variable assessment#:#qpl_cancel_skill_assigns_update#:#Otkaži -assessment#:#qpl_confirm_delete_questions#:#Jeste li sigurni da želite ukloniti sljedeća pitanja? assessment#:#qpl_copy_insert_clipboard#:#Odabrano(a) pitanje(a) se kopiraju u međuspremnik assessment#:#qpl_copy_select_none#:#Molimo odaberite najmanje jedno pitanje za kopiranje u međuspremnik! assessment#:#qpl_delete_rbac_error#:#Nemate prava za uklanjanje ovog pitanja! @@ -1106,7 +1091,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#Kompetencija assessment#:#qpl_qst_skl_usg_sklpnt_col#:#Ukupan broj kompetencijskih bodova po kompetenciji assessment#:#qpl_question_is_in_use#:#Pitanje koje želite urediti postoji već u u %s testova. Ako promijenite ovo pitanje, to NEĆE imati nikakav utjecaj na pitanja koja su već sadržana u testovima, jer sustav automatski izrađuje kopiju pitanja kada se umetne u test! assessment#:#qpl_questions_deleted#:#Pitanje(a) izbrisano(a). -assessment#:#qpl_reset_preview#:#Resetiraj pregled assessment#:#qpl_save_skill_assigns_update#:#Spremi dodjelu kompetencija assessment#:#qpl_settings_availability#:#Availability###26 08 2024 new variable assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#Postojeće taksonomije u ovom bazenu mogu se koristiti za filtriranje pitanja. @@ -1136,14 +1120,6 @@ assessment#:#qst_essay_chars_remaining#:#Remaining characters:###04 06 2021 new assessment#:#qst_essay_wordcounter_enabled#:#Count Words###04 06 2021 new variable assessment#:#qst_essay_wordcounter_enabled_info#:#The entered words are counted. The number of written words is shown to the participants below the text input field.###04 06 2021 new variable assessment#:#qst_essay_written_words#:#Number of entered words:###04 06 2021 new variable -assessment#:#qst_lifecycle#:#Lifecycle###04 06 2021 new variable -assessment#:#qst_lifecycle_draft#:#Draft###04 06 2021 new variable -assessment#:#qst_lifecycle_filter_all#:#All Lifecycles###04 06 2021 new variable -assessment#:#qst_lifecycle_final#:#Final###04 06 2021 new variable -assessment#:#qst_lifecycle_outdated#:#Outdated###04 06 2021 new variable -assessment#:#qst_lifecycle_rejected#:#Rejected###04 06 2021 new variable -assessment#:#qst_lifecycle_review#:#To be Reviewed###04 06 2021 new variable -assessment#:#qst_lifecycle_sharable#:#Sharable###04 06 2021 new variable assessment#:#qst_nested_nested_answers_off#:#No indents, just order###29 07 2022 new variable assessment#:#qst_nested_nested_answers_on#:#Use indents in anwers###29 07 2022 new variable assessment#:#qst_nr_of_tries#:#Broj pokušaja @@ -1167,17 +1143,14 @@ assessment#:#question_title#:#Naziv pitanja assessment#:#question_type#:#Tip pitanja assessment#:#questionpool_not_entered#:#Unesite naziv za bazen pitanja! assessment#:#questionpool_not_selected#:#Please select a question pool!###04 06 2021 new variable -assessment#:#questions#:#Questions###26 08 2024 new variable assessment#:#questions_from#:#Pitanja iz assessment#:#questions_per_page_view#:#Prikaz stranice assessment#:#random_accept_sample#:#Prihvati uzorak assessment#:#random_another_sample#:#Uzmite drugi uzorak assessment#:#random_selection#:#Slučajni odabir assessment#:#range#:#Raspon -assessment#:#range_lower_limit#:#Donja granica assessment#:#range_max#:#Raspon (maksimalni) assessment#:#range_min#:#Raspon (minimalni) -assessment#:#range_upper_limit#:#Gornja granica assessment#:#rated_sign#:#Predznak assessment#:#rated_unit#:#Jedinica assessment#:#rated_value#:#Vrijednost @@ -1253,7 +1226,6 @@ assessment#:#search_roles#:#Pretraži po ulogama assessment#:#search_term#:#Pojam za pretraživanje assessment#:#select_at_least_one_feedback_type_and_trigger#:#Please Select at least one type of feedback and a trigger.###26 08 2024 new variable assessment#:#select_at_least_one_lock_answer_type#:#Please select at least one type of answer lock.###29 10 2025 new variable -assessment#:#select_gap#:#Odaberi prazninu assessment#:#select_max_one_item#:#Molimo odaberite samo jedan objekt assessment#:#select_one_user#:#Molimo odaberite najmanje jednog korisnika. assessment#:#select_question#:#Select a Question###28 10 2024 new variable @@ -1280,7 +1252,6 @@ assessment#:#show_old_introduction#:#Show old introduction###26 08 2024 new vari assessment#:#show_pass_overview#:#Prikaži pregled rezultata ocijenjenog prolaza testa assessment#:#show_results#:#Show Results###28 10 2024 new variable assessment#:#show_user_answers#:#Pokaži ocijenjene odgovore korisnika -assessment#:#shuffle_answers#:#Pomiješaj odgovore assessment#:#skip_question#:#Nemoj odgovoriti i dalje assessment#:#solution#:#Solution###28 10 2024 new variable assessment#:#solutionText#:#Tekst @@ -13971,6 +13942,35 @@ qpl#:#qpl_page_type_qfbg#:#General Feedback###04 06 2021 new variable qpl#:#qpl_page_type_qfbs#:#Special Feedback###04 06 2021 new variable qpl#:#qpl_page_type_qht#:#Hint###04 06 2021 new variable qpl#:#qpl_page_type_qpl#:#Question Page###04 06 2021 new variable +qsts#:#answer_options#:#Opcija odgovora +qsts#:#cloze_text#:#Pitanje s prazninom u tekstu +qsts#:#cloze_textgapcase_insensitive#:#Neosjetljivo na velika i mala slova +qsts#:#cloze_textgapcase_sensitive#:#Osjetljivo na velika i mala slova +qsts#:#cloze_textgaplevenshtein_of#:#Levenshtein razmak od%s +qsts#:#confirm_delete_questions#:#Jeste li sigurni da želite ukloniti sljedeća pitanja? +qsts#:#create_question#:#Izradi pitanje +qsts#:#gap#:#Praznina +qsts#:#gaps#:#Gaps###29 10 2025 new variable +qsts#:#insert_gap#:#Umetni prazninu +qsts#:#min_auto_complete#:#Automatsko dopunjavanje +qsts#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable +qsts#:#out_of_range#:#Izvan raspona +qsts#:#qst_lifecycle#:#Lifecycle###04 06 2021 new variable +qsts#:#qst_lifecycle_draft#:#Draft###04 06 2021 new variable +qsts#:#qst_lifecycle_filter_all#:#All Lifecycles###04 06 2021 new variable +qsts#:#qst_lifecycle_final#:#Final###04 06 2021 new variable +qsts#:#qst_lifecycle_outdated#:#Outdated###04 06 2021 new variable +qsts#:#qst_lifecycle_rejected#:#Rejected###04 06 2021 new variable +qsts#:#qst_lifecycle_review#:#To be Reviewed###04 06 2021 new variable +qsts#:#qst_lifecycle_sharable#:#Sharable###04 06 2021 new variable +qsts#:#questionlist#:#Questionlist###26 08 2024 new variable +qsts#:#questions#:#Questions###26 08 2024 new variable +qsts#:#range_lower_limit#:#Donja granica +qsts#:#range_upper_limit#:#Gornja granica +qsts#:#reset_preview#:#Resetiraj pregled +qsts#:#select_gap#:#Odaberi prazninu +qsts#:#shuffle_answers#:#Pomiješaj odgovore +qsts#:#suggested_learning_content#:#Sadržaji za ponavljanje rating#:#rat_not_rated_yet#:#Not Rated Yet rating#:#rat_nr_ratings#:#%s Ratings rating#:#rat_one_rating#:#One Rating @@ -16620,7 +16620,6 @@ survey#:#questionblock#:#Blok pitanja survey#:#questionblock_inserted#:#Blok pitanja je umetnut survey#:#questionblocks#:#Blokovi pitanja survey#:#questionblocks_inserted#:#Blokovi pitanja su umetnuti -survey#:#questions#:#Pitanja survey#:#questions_inserted#:#Pitanje(a) je(su) umetnuto(a)! survey#:#questions_removed#:#Pitanje(a) i/ili blok(ovi) pitanja je(su) uklonjeno(i)! survey#:#questiontype#:#Tip pitanja @@ -16921,6 +16920,7 @@ survey#:#svy_please_select_unused_codes#:#Please select at least one unused code survey#:#svy_print_hide_labels#:#Sakrij oznake survey#:#svy_print_show_labels#:#Pokaži oznake survey#:#svy_privacy_info#:#Privacy###29 07 2022 new variable +survey#:#svy_questions#:#Pitanja survey#:#svy_rater#:#Rater###29 07 2022 new variable survey#:#svy_rater_see_app_info#:#The names of appraisees will be presented to raters to enable them evaluating the questions.###29 07 2022 new variable survey#:#svy_reminder_mail_template#:#Predložak pošte diff --git a/lang/ilias_hu.lang b/lang/ilias_hu.lang index f1271ccc3d4a..b6c765634413 100644 --- a/lang/ilias_hu.lang +++ b/lang/ilias_hu.lang @@ -422,7 +422,6 @@ adve#:#adve_use_tiny_mce#:#TinyMCE engedélyezése WYSIWYG szerkesztéshez assessment#:#activate_logging#:#Teszt és értékelés naplózásának bekapcsolása assessment#:#activate_manual_scoring#:#Enable Manual Scoring###28 10 2024 new variable assessment#:#activate_manual_scoring_desc#:#Enables manual Scoring for all question types.###28 10 2024 new variable -assessment#:#addSuggestedSolution#:#Ismétlő összefoglaláshoz tartalom hozzáadása assessment#:#add_answers#:#Válasz hozzáadása assessment#:#add_circle#:#Kör alakú terület hozzáadása assessment#:#add_gap#:#Kiegészítendő szöveg hozzáadása @@ -447,7 +446,6 @@ assessment#:#answer_is_not_correct_but_positive#:#Szerzett pontot a megoldásár assessment#:#answer_is_right#:#A megoldása helyes assessment#:#answer_is_wrong#:#A megoldása rossz assessment#:#answer_of#:#Válasz -assessment#:#answer_options#:#Válaszlehetőségek: assessment#:#answer_question#:#Kérdés megválaszolása assessment#:#answer_text#:#Válaszszöveg assessment#:#answer_types#:#Válaszokhoz szerkesztő @@ -501,7 +499,6 @@ assessment#:#ass_completion_by_submission#:#Befejezve elküldéssel assessment#:#ass_completion_by_submission_info#:#Ha be van kapcsolva, legalább egy fájl elküldése ennek a kérdésnek a teljesítését jelenti a maximális pontszám megszerzésével. A pont a későbbiekben manuálisan módosítható. Ennek a beállításnak a bekapcsolása nincs hatással a már beküldött megoldásokra. assessment#:#ass_create_export_file_with_results#:#Export fájl létrehozása (résztvevők eredményeivel) assessment#:#ass_create_export_test_archive#:#Tesztarchívum fájl létrehozása -assessment#:#ass_create_question#:#Kérdés létrehozása assessment#:#ass_imap_hint#:#Tippek buboréksúgóként jelenjenek meg assessment#:#ass_imap_map_file_not_readable#:#A feltöltött képtérkép nem olvasható. assessment#:#ass_imap_no_map_found#:#Egy űrlap sem található a feltöltött képtérképben. @@ -571,10 +568,6 @@ assessment#:#cloze_answer_text_info#:#Spaces preceding or following the answer t assessment#:#cloze_fixed_textlength#:#Szövegmező hossza assessment#:#cloze_fixed_textlength_description#:#Ha megad egy értéket, akkor az összes szöveg- és szám mező evvel a rögzített hosszal kerül létrehozásra, így nincs lehetőség a több karakter bevitelére. Szövegmezőknek lehet saját korlátjuk a karakterszámra. Számmezők esetén a tizedesvessző is egy karakternek számít. assessment#:#cloze_gap_size_info#:#Ha 0-nál nagyobb értéked ad meg, ilyen hosszúságú lesz a szövegmező. Amennyiben nem ad meg értéket, a globális hossz lesz ez az érték. -assessment#:#cloze_text#:#Kiegészítendő szöveg -assessment#:#cloze_textgap_case_insensitive#:#Kis-/nagybetűre nem érzékeny -assessment#:#cloze_textgap_case_sensitive#:#Kis-/nagybetűre érzékeny -assessment#:#cloze_textgap_levenshtein_of#:#'%s' Levenshtein-távolsága assessment#:#code#:#Kód assessment#:#codebase#:#Kódbázis assessment#:#concatenation#:#Szavak összekapcsolása @@ -757,9 +750,7 @@ assessment#:#fq_formula_desc#:#Beírhat megadott változókat ($v1, $v2, ..., $v assessment#:#fq_no_restriction_info#:#Tizedestört és tört egyaránt megadható. assessment#:#fq_precision_info#:#Adja meg a tizedeshelyek számát. assessment#:#fq_question_desc#:#Definiálhat változókat $v1, $v2 ... $vn beírásával, végeredményeket $r1, $r2 .... $rn beírásával a kérdés szövegének tetszőleges helyén. Kattints a 'Kérdés elemzése' gombra, hogy a változók és a végeredmények szerkeszthető űrlapja megjelenjen. -assessment#:#gap#:#Kitöltendő hely assessment#:#gap_combination#:#Kitöltéskombináció -assessment#:#gaps#:#Gaps###29 10 2025 new variable assessment#:#glossary_term#:#Fogalomtár fogalma assessment#:#goto_first_question#:#Show First Question###29 10 2025 new variable assessment#:#grading_mark_msg#:#Érdemjegye: "[mark]" @@ -777,7 +768,6 @@ assessment#:#info_answer_type_change#:#A kérdés képet tartalmaz. Nem változt assessment#:#info_text_upload#:#Válasszon egy UTF-8 kódolású válaszfájlt a feltöltéshez assessment#:#insert_after#:#Beszúrás utána assessment#:#insert_before#:#Beszúrás elé -assessment#:#insert_gap#:#Kitöltendő hely beszúrása assessment#:#interaction_type#:#Interaction Type###26 08 2024 new variable assessment#:#internal_links#:#Belső linkek assessment#:#intprecision#:#Osztható a következővel @@ -889,7 +879,6 @@ assessment#:#logs_wrong_test_password_provided#:#A résztvevő rossz jelszót ad assessment#:#longmenu#:#Étlap assessment#:#longmenu_answeroptions_differ#:#This question does not work correctly, as there are not the same amount of gaps in the text as in the correction options.###26 08 2024 new variable assessment#:#longmenu_text#:#'Étlap'-szöveg -assessment#:#mainbar_button_label_questionlist#:#Questionlist###26 08 2024 new variable assessment#:#maintenance#:#Karbantartás assessment#:#manscoring#:#Manuális pontozás assessment#:#manscoring_done#:#Pontozott résztvevők @@ -916,7 +905,6 @@ assessment#:#maximum_nr_of_tries_reached#:#Elfogyott az összes próbálkozási assessment#:#maximum_points#:#Maximálisan elérhető pont assessment#:#maxsize#:#Feltölthető maximális fájlméret assessment#:#maxsize_info#:#Adja meg bájtban a feltölthető fájlméretet. Ha üresen hagyja ezt a mezőt, a rendszerben beállított maximális méret kerül használatra. -assessment#:#min_auto_complete#:#Automatikus kiegészítés assessment#:#min_ip_label#:#Lowest IP With Access###26 08 2024 new variable assessment#:#min_percentage_ne_0#:#Meg kell határoznia a 0 százalék minimális pontértékét. Az érdemjegyképzést nem mentettük. assessment#:#misc#:#Egyéb beállítások @@ -925,7 +913,6 @@ assessment#:#mode_onebyone#:#One by One###29 10 2025 new variable assessment#:#mode_question#:#Question oriented###29 10 2025 new variable assessment#:#mode_user#:#Participant oriented###29 10 2025 new variable assessment#:#msg_circle_added#:#Kör hozzáadva -assessment#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable assessment#:#msg_number_of_terms_too_low#:#A kifejezések száma nagyobb vagy egyenlő legyen, mint a definíciók száma. assessment#:#msg_poly_added#:#Sokszög hozzáadva assessment#:#msg_questions_moved#:#Kérdés(ek) áthelyezve. @@ -984,7 +971,6 @@ assessment#:#order#:#Order###26 08 2024 new variable assessment#:#ordering_answer_sequence_info#:#Az itt megadott válaszsorozat a helyes megoldások sorozataként kerül felhasználásra. assessment#:#ordertext#:#Sorrendbe rakó szöveg assessment#:#ordertext_info#:#Adja meg a vízszintesen sorba rakandó szöveget. A rendezendő szövegegységek nem látható (fehér) karakterekkel lesznek elválasztva. Ha más elválasztójelre van szüksége, használhatja a %s elválasztójelet szövegegységeinek elkülönítésére. -assessment#:#out_of_range#:#Tartományon kívül esik assessment#:#output#:#Kimenet (output) assessment#:#output_mode#:#Kimeneti mód assessment#:#parseQuestion#:#Kérdés elemzése @@ -1053,7 +1039,6 @@ assessment#:#qpl_bulk_save_add#:#Add###28 10 2024 new variable assessment#:#qpl_bulk_save_overwrite#:#Overwrite###28 10 2024 new variable assessment#:#qpl_bulkedit_success#:#Modifications saved.###28 10 2024 new variable assessment#:#qpl_cancel_skill_assigns_update#:#Mégsem -assessment#:#qpl_confirm_delete_questions#:#Biztos, hogy eltávolítja az alábbi kérdés(eke)t? assessment#:#qpl_copy_insert_clipboard#:#A kijelölt kérdés(ek)et a vágólapra helyezte. assessment#:#qpl_copy_select_none#:#Jelöljön ki legalább egy kérdést a másoláshoz! assessment#:#qpl_delete_rbac_error#:#Nincs joga a kérdés(ek) eltávolításához. @@ -1106,7 +1091,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#Kompetencia assessment#:#qpl_qst_skl_usg_sklpnt_col#:#Kompetencia-pontszámok összege kérdésenként assessment#:#qpl_question_is_in_use#:#A módosítandó kérdés %s tesztben szerepel. Ha megváltoztatja a kérdést, az a teszt(ek)ben nem fog módosulni, mert a rendszer másolatot hoz létre a kérdésekről, mielőtt beilleszti azokat a teszt(ek)be. assessment#:#qpl_questions_deleted#:#Kérdés(eke)et sikeresen eltávolított. -assessment#:#qpl_reset_preview#:#Előnézet alaphelyzetbe állítása assessment#:#qpl_save_skill_assigns_update#:#Kompetencia-összerendelése mentése assessment#:#qpl_settings_availability#:#Availability###26 08 2024 new variable assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#A már létező taxonómiák a kérdések szűrésére használhatóak ebben a gyűjteményben. @@ -1136,14 +1120,6 @@ assessment#:#qst_essay_chars_remaining#:#Fennmaradó karakterek száma: assessment#:#qst_essay_wordcounter_enabled#:#Szavak számolása assessment#:#qst_essay_wordcounter_enabled_info#:#A beírt szavak számát számoljuk és megjelenítjük a felhasználóknak a bevitelő mező alatt . assessment#:#qst_essay_written_words#:#Szavak megadott száma: -assessment#:#qst_lifecycle#:#Életciklus -assessment#:#qst_lifecycle_draft#:#Piszkozat -assessment#:#qst_lifecycle_filter_all#:#Összes életciklus -assessment#:#qst_lifecycle_final#:#Végső -assessment#:#qst_lifecycle_outdated#:#Elavult -assessment#:#qst_lifecycle_rejected#:#Elutasított -assessment#:#qst_lifecycle_review#:#Átnézendő -assessment#:#qst_lifecycle_sharable#:#Megosztható assessment#:#qst_nested_nested_answers_off#:#No indents, just order###29 07 2022 new variable assessment#:#qst_nested_nested_answers_on#:#Use indents in anwers###29 07 2022 new variable assessment#:#qst_nr_of_tries#:#Próbálkozások száma @@ -1167,17 +1143,14 @@ assessment#:#question_title#:#Kérdéscím assessment#:#question_type#:#Kérdéstípus assessment#:#questionpool_not_entered#:#Adjon nevet a kérdésgyűjteménynek. assessment#:#questionpool_not_selected#:#Kérem, válasszon ki egy kérdésgyűjteményt. -assessment#:#questions#:#Questions###26 08 2024 new variable assessment#:#questions_from#:#kérdések innen: assessment#:#questions_per_page_view#:#Lapnézet assessment#:#random_accept_sample#:#Minta elfogadása assessment#:#random_another_sample#:#Másik minta kérése assessment#:#random_selection#:#Véletlen-kiválasztás assessment#:#range#:#Terület -assessment#:#range_lower_limit#:#Alsó határ assessment#:#range_max#:#Tartomány (Maximum) assessment#:#range_min#:#Tartomány (Minimum) -assessment#:#range_upper_limit#:#Felső határ assessment#:#rated_sign#:#Jel assessment#:#rated_unit#:#Mértékegység assessment#:#rated_value#:#Érték @@ -1253,7 +1226,6 @@ assessment#:#search_roles#:#Talált szerepek assessment#:#search_term#:#Fogalom keresése assessment#:#select_at_least_one_feedback_type_and_trigger#:#Please Select at least one type of feedback and a trigger.###26 08 2024 new variable assessment#:#select_at_least_one_lock_answer_type#:#Please select at least one type of answer lock.###29 10 2025 new variable -assessment#:#select_gap#:#Szöveghely választása assessment#:#select_max_one_item#:#Csak egy elemet válasszon! assessment#:#select_one_user#:#Válasszon ki legalább egy felhasználót! assessment#:#select_question#:#Select a Question###28 10 2024 new variable @@ -1280,7 +1252,6 @@ assessment#:#show_old_introduction#:#Show old introduction###26 08 2024 new vari assessment#:#show_pass_overview#:#Figyelembe vett tesztkitöltés áttekintése assessment#:#show_results#:#Show Results###28 10 2024 new variable assessment#:#show_user_answers#:#Figyelembe vett tesztkitöltés válaszainak megjelenítése -assessment#:#shuffle_answers#:#Kevert válaszok assessment#:#skip_question#:#Válaszadás kihagyása és következő assessment#:#solution#:#Solution###28 10 2024 new variable assessment#:#solutionText#:#Szöveg @@ -13971,6 +13942,35 @@ qpl#:#qpl_page_type_qfbg#:#Általános visszajelzés qpl#:#qpl_page_type_qfbs#:#Speciális visszajelzés qpl#:#qpl_page_type_qht#:#Tipp qpl#:#qpl_page_type_qpl#:#Kérdésoldala +qsts#:#answer_options#:#Válaszlehetőségek +qsts#:#cloze_text#:#Kiegészítendő szöveg +qsts#:#cloze_textgapcase_insensitive#:#Kis-/nagybetűre nem érzékeny +qsts#:#cloze_textgapcase_sensitive#:#Kis-/nagybetűre érzékeny +qsts#:#cloze_textgaplevenshtein_of#:#'%s' Levenshtein-távolsága +qsts#:#confirm_delete_questions#:#Biztos, hogy eltávolítja az alábbi kérdés(eke)t? +qsts#:#create_question#:#Kérdés létrehozása +qsts#:#gap#:#Kitöltendő hely +qsts#:#gaps#:#Gaps###29 10 2025 new variable +qsts#:#insert_gap#:#Kitöltendő hely beszúrása +qsts#:#min_auto_complete#:#Automatikus kiegészítés +qsts#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable +qsts#:#out_of_range#:#Tartományon kívül esik +qsts#:#qst_lifecycle#:#Életciklus +qsts#:#qst_lifecycle_draft#:#Piszkozat +qsts#:#qst_lifecycle_filter_all#:#Összes életciklus +qsts#:#qst_lifecycle_final#:#Végső +qsts#:#qst_lifecycle_outdated#:#Elavult +qsts#:#qst_lifecycle_rejected#:#Elutasított +qsts#:#qst_lifecycle_review#:#Átnézendő +qsts#:#qst_lifecycle_sharable#:#Megosztható +qsts#:#questionlist#:#Questionlist###26 08 2024 new variable +qsts#:#questions#:#Questions###26 08 2024 new variable +qsts#:#range_lower_limit#:#Alsó határ +qsts#:#range_upper_limit#:#Felső határ +qsts#:#reset_preview#:#Előnézet alaphelyzetbe állítása +qsts#:#select_gap#:#Szöveghely választása +qsts#:#shuffle_answers#:#Kevert válaszok +qsts#:#suggested_learning_content#:#Ismétlő összefoglaláshoz tartalom hozzáadása rating#:#rat_not_rated_yet#:#Még nincs értékelve rating#:#rat_nr_ratings#:#%s értékelés rating#:#rat_one_rating#:#Egy értékelés @@ -16621,7 +16621,6 @@ survey#:#questionblock#:#Kérdésblokk survey#:#questionblock_inserted#:#Kérdésblokk beszúrva survey#:#questionblocks#:#Kérdésblokkok survey#:#questionblocks_inserted#:#Kérdésblokkok beszúrva -survey#:#questions#:#Kérdések survey#:#questions_inserted#:#Kérdés(ek) beszúrva. survey#:#questions_removed#:#Kérdés(ek)/kérdésblokk(ok) eltávolítva. survey#:#questiontype#:#Kérdéstípus @@ -16922,6 +16921,7 @@ survey#:#svy_please_select_unused_codes#:#Please select at least one unused code survey#:#svy_print_hide_labels#:#Címkék elrejtése survey#:#svy_print_show_labels#:#Címkék megjelenítése survey#:#svy_privacy_info#:#Privacy###29 07 2022 new variable +survey#:#svy_questions#:#Kérdések survey#:#svy_rater#:#Rater###29 07 2022 new variable survey#:#svy_rater_see_app_info#:#The names of appraisees will be presented to raters to enable them evaluating the questions.###29 07 2022 new variable survey#:#svy_reminder_mail_template#:#Levélsablon diff --git a/lang/ilias_it.lang b/lang/ilias_it.lang index ee903ef4af9d..3f5a718d345c 100644 --- a/lang/ilias_it.lang +++ b/lang/ilias_it.lang @@ -421,7 +421,6 @@ adve#:#adve_use_tiny_mce#:#Abilita TinyMCE come Editor WYSIWYG (What You See Is assessment#:#activate_logging#:#Activate Test and Assessment Logging###28 11 2025 new variable assessment#:#activate_manual_scoring#:#Enable Manual Scoring###28 11 2025 new variable assessment#:#activate_manual_scoring_desc#:#Enables manual Scoring for all question types.###28 11 2025 new variable -assessment#:#addSuggestedSolution#:#Contenuti per il ripasso assessment#:#add_answers#:#Aggiungi risposte assessment#:#add_circle#:#Aggiungi cerchio assessment#:#add_gap#:#Aggiungi lacuna di testo @@ -446,7 +445,6 @@ assessment#:#answer_is_not_correct_but_positive#:#Hai ricevuto dei punti per la assessment#:#answer_is_right#:#La soluzione è corretta. assessment#:#answer_is_wrong#:#La soluzione è sbagliata. assessment#:#answer_of#:#Risposta di -assessment#:#answer_options#:#Opzioni di risposte: assessment#:#answer_question#:#Risposta domanda assessment#:#answer_text#:#Testo della risposta assessment#:#answer_types#:#Tipi di risposta @@ -500,7 +498,6 @@ assessment#:#ass_completion_by_submission#:#Completato da presentazione assessment#:#ass_completion_by_submission_info#:#Se abilitato, la presentazione di almeno un file causa il completamento di questa domanda garantendo il punteggio massimo per questa domanda. Il punteggio può essere modificato manualmente in seguito. In cambiamento di questa impostazione non effetto sulle domande già presentate. assessment#:#ass_create_export_file_with_results#:#Crea file di esportazione dei test (inclusi i risultati dei partecipanti) assessment#:#ass_create_export_test_archive#:#Crea file archivio dei test -assessment#:#ass_create_question#:#Crea domanda assessment#:#ass_imap_hint#:#Il suggerimento deve essere mostrato come Descrizione comando assessment#:#ass_imap_map_file_not_readable#:#L’immagine caricata della mappa non può essere letta. assessment#:#ass_imap_no_map_found#:#Non è possibile trovare alcun modello nell’immagine della mappa caricata. @@ -570,10 +567,6 @@ assessment#:#cloze_answer_text_info#:#Spaces preceding or following the answer t assessment#:#cloze_fixed_textlength#:#Lunghezza del campo di testo assessment#:#cloze_fixed_textlength_description#:#Se si inserisce un valore maggiore di 0 tutte le mancanze di testo e di numero avranno quella lunghezza. assessment#:#cloze_gap_size_info#:#Se si immette un valore maggiore di 0, questo campo di testo spazio verrà creato con la lunghezza fissa di questo valore. Se non si immette un valore, il campo di testo del gap verrà creato con il valore della lunghezza fissa globale. -assessment#:#cloze_text#:#Testo corrispondente -assessment#:#cloze_textgap_case_insensitive#:#No Maiuscole/minuscole -assessment#:#cloze_textgap_case_sensitive#:#Maiuscole/minuscole -assessment#:#cloze_textgap_levenshtein_of#:#Distanza di Levenshtein del %s assessment#:#code#:#Codice assessment#:#codebase#:#Base dei codici assessment#:#concatenation#:#Concatenazione @@ -756,9 +749,7 @@ assessment#:#fq_formula_desc#:#È possibile inserire variabili predefinite (da $ assessment#:#fq_no_restriction_info#:#Entrambi i decimali e le frazioni sono accettati come input. assessment#:#fq_precision_info#:#Digita il numero delle posizioni decimali desiderate. assessment#:#fq_question_desc#:#È possibile definire le variabili inserendo $ v1, $ v2 ... $ vn, i risultati inserendo $ r1, $ r2 .... $ rn nella posizione desiderata nel testo della domanda. Fare clic sul pulsante "Analizza domanda" per creare moduli di modifica per variabili e risultati. -assessment#:#gap#:#Spazio vuoto assessment#:#gap_combination#:#Combinazione di Gap -assessment#:#gaps#:#Gaps###28 11 2025 new variable assessment#:#glossary_term#:#Voce del glossario assessment#:#goto_first_question#:#Show first question###28 11 2025 new variable assessment#:#grading_mark_msg#:#Il tuo punteggio risultante è: "[mark]" @@ -776,7 +767,6 @@ assessment#:#info_answer_type_change#:#La domanda contiene già delle immagini. assessment#:#info_text_upload#:#Scegli un file di risposte da caricare assessment#:#insert_after#:#Inserisci dopo assessment#:#insert_before#:#Inserisci prima -assessment#:#insert_gap#:#Inserisci Gap assessment#:#interaction_type#:#Interaction Type###28 11 2025 new variable assessment#:#internal_links#:#Link interni assessment#:#intprecision#:#Divisibile per @@ -888,7 +878,6 @@ assessment#:#logs_wrong_test_password_provided#:#Participant entered wrong test assessment#:#longmenu#:#Menù esteso assessment#:#longmenu_answeroptions_differ#:#This question does not work correctly, as there are not the same amount of gaps in the text as in the correction options.###28 07 2023 new variable assessment#:#longmenu_text#:#Testo menu esteso -assessment#:#mainbar_button_label_questionlist#:#Questionlist###30 04 2024 new variable assessment#:#maintenance#:#Manutenzione assessment#:#manscoring#:#Punteggio manuale assessment#:#manscoring_done#:#Partecipanti Valutati @@ -915,7 +904,6 @@ assessment#:#maximum_nr_of_tries_reached#:#Hai raggiunto il numero massimo di pa assessment#:#maximum_points#:#Punteggio massimo disponibile assessment#:#maxsize#:#massima dimensione file di upload assessment#:#maxsize_info#:#Inserire la massima dimensione in, bytes, dei files da caricare. Se il campo è lasciato in bianco, la dimensione sarà definita dal sistema. -assessment#:#min_auto_complete#:#Completamento automatico assessment#:#min_ip_label#:#Lowest IP With Access###28 11 2025 new variable assessment#:#min_percentage_ne_0#:#Devi raggiungere la percentuale minima dello 0 percento! Lo schema voti non e' stato salvato. assessment#:#misc#:#Opzioni varie @@ -924,7 +912,6 @@ assessment#:#mode_onebyone#:#One by One###28 11 2025 new variable assessment#:#mode_question#:#Question oriented###28 11 2025 new variable assessment#:#mode_user#:#Participant oriented###28 11 2025 new variable assessment#:#msg_circle_added#:#Circonferenza aggiunta -assessment#:#msg_no_questions_selected#:#No questions were selected.###30 04 2024 new variable assessment#:#msg_number_of_terms_too_low#:#Il numero dei termini deve essere maggiore o uguale al numero delle definizioni assessment#:#msg_poly_added#:#Aggiunto poligono assessment#:#msg_questions_moved#:#Domanda(e) spostata(e) @@ -983,7 +970,6 @@ assessment#:#order#:#Order###09 11 2022 new variable assessment#:#ordering_answer_sequence_info#:#La sequenza di risposte che definisci qui sara' presa come sequenza di risposte corrette. assessment#:#ordertext#:#Ordinando il testo assessment#:#ordertext_info#:#Per favore, inserisci il testo che dovrebbe essere ordinato orizzontalmente. Il testo ordinato sarà separato da spazi vuoti. Se c'e' bisogno di una differente spaziatura, si può usare il separatore %s per separare il testo.
-assessment#:#out_of_range#:#Fuori portata assessment#:#output#:#Uscita assessment#:#output_mode#:#Modalità di output assessment#:#parseQuestion#:#Analizza domanda @@ -1052,7 +1038,6 @@ assessment#:#qpl_bulk_save_add#:#Add###28 11 2025 new variable assessment#:#qpl_bulk_save_overwrite#:#Overwrite###28 11 2025 new variable assessment#:#qpl_bulkedit_success#:#Modifications saved.###28 11 2025 new variable assessment#:#qpl_cancel_skill_assigns_update#:#Annulla -assessment#:#qpl_confirm_delete_questions#:#Sei sicuro di voler eliminare le seguenti domande? Se cancelli le domande bloccate, anche i risultati dei test che le contengono verranno cancellati. assessment#:#qpl_copy_insert_clipboard#:#Le domande selezionate sono state copiate negli appunti assessment#:#qpl_copy_select_none#:#Seleziona almeno una domanda da copiare negli appunti assessment#:#qpl_delete_rbac_error#:#Non hai l'autorizzazione per eliminare questa raccolta di domande! @@ -1105,7 +1090,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#competenze assessment#:#qpl_qst_skl_usg_sklpnt_col#:#Somma totale di punti di competenze per competenze assessment#:#qpl_question_is_in_use#:#La domanda che stai per modificare è inserita in un %s dei test. Se modifichi la domanda, NON verranno modificate le domande nei test, perchè il sistema crea una copia delle domande quando queste vengono inserite in un test! assessment#:#qpl_questions_deleted#:#Domande eliminate -assessment#:#qpl_reset_preview#:#Ripristina anteprima assessment#:#qpl_save_skill_assigns_update#:#Salva assegnazioni della competenze assessment#:#qpl_settings_availability#:#Availability###30 04 2024 new variable assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#Se abilitato, le tassonomie eventualmente create vengono mostrate per il filtro. @@ -1135,14 +1119,6 @@ assessment#:#qst_essay_chars_remaining#:#Caratteri rimanenti: assessment#:#qst_essay_wordcounter_enabled#:#Conta parole assessment#:#qst_essay_wordcounter_enabled_info#:#Le parole inserite vengono contate. Il numero di parole scritte viene mostrato ai partecipanti sotto il campo di immissione del testo. assessment#:#qst_essay_written_words#:#Numero di parole inserite: -assessment#:#qst_lifecycle#:#Ciclo vitale -assessment#:#qst_lifecycle_draft#:#Bozza -assessment#:#qst_lifecycle_filter_all#:#Tutti i cicli di vita -assessment#:#qst_lifecycle_final#:#Finale -assessment#:#qst_lifecycle_outdated#:#Obsoleto -assessment#:#qst_lifecycle_rejected#:#Respinto -assessment#:#qst_lifecycle_review#:#Da rivedere -assessment#:#qst_lifecycle_sharable#:#Condivisibile assessment#:#qst_nested_nested_answers_off#:#No indents, just order###29 07 2022 new variable assessment#:#qst_nested_nested_answers_on#:#Use indents in anwers###29 07 2022 new variable assessment#:#qst_nr_of_tries#:#Numero di tentativi @@ -1166,17 +1142,14 @@ assessment#:#question_title#:#Titolo della domanda assessment#:#question_type#:#Tipo di domanda assessment#:#questionpool_not_entered#:#Per favore inserisci il nome della raccolta di domande! assessment#:#questionpool_not_selected#:#Please select a question pool! -assessment#:#questions#:#Questions###30 04 2024 new variable assessment#:#questions_from#:#domande da assessment#:#questions_per_page_view#:#Visualizza pagina assessment#:#random_accept_sample#:#Accetta esempio assessment#:#random_another_sample#:#Prendi un altro esempio assessment#:#random_selection#:#Scegli casualmente assessment#:#range#:#Intervallo -assessment#:#range_lower_limit#:#Limite inferiore assessment#:#range_max#:#Portata (Massima) assessment#:#range_min#:#Portata (Minima) -assessment#:#range_upper_limit#:#Limite superiore assessment#:#rated_sign#:#Segno assessment#:#rated_unit#:#Unità assessment#:#rated_value#:#Valore @@ -1252,7 +1225,6 @@ assessment#:#search_roles#:#Ricerca ruoli assessment#:#search_term#:#Ricerca termini assessment#:#select_at_least_one_feedback_type_and_trigger#:#Please Select at least one type of feedback and a trigger.###30 04 2024 new variable assessment#:#select_at_least_one_lock_answer_type#:#Please select at least one type of answer lock.###28 11 2025 new variable -assessment#:#select_gap#:#Seleziona spazio assessment#:#select_max_one_item#:#Per favore scegli un solo elemento assessment#:#select_one_user#:#Seleziona almeno un utente assessment#:#select_question#:#Select a Question###28 11 2025 new variable @@ -1279,7 +1251,6 @@ assessment#:#show_old_introduction#:#Show old introduction###30 04 2024 new vari assessment#:#show_pass_overview#:#Mostra il sommario dei voti assessment#:#show_results#:#Show Results###28 11 2025 new variable assessment#:#show_user_answers#:#Mostra le risposte valutate degli utenti -assessment#:#shuffle_answers#:#Mescola le risposte assessment#:#skip_question#:#Non rispondere e Avanti assessment#:#solution#:#Solution###28 11 2025 new variable assessment#:#solutionText#:#testo @@ -13977,6 +13948,35 @@ qpl#:#qpl_page_type_qfbg#:#Feedback generale qpl#:#qpl_page_type_qfbs#:#Feedback speciali qpl#:#qpl_page_type_qht#:#Suggerimento qpl#:#qpl_page_type_qpl#:#Pagina delle domande +qsts#:#answer_options#:#Opzioni di risposte +qsts#:#cloze_text#:#Testo corrispondente +qsts#:#cloze_textgapcase_insensitive#:#No Maiuscole/minuscole +qsts#:#cloze_textgapcase_sensitive#:#Maiuscole/minuscole +qsts#:#cloze_textgaplevenshtein_of#:#Distanza di Levenshtein del %s +qsts#:#confirm_delete_questions#:#Sei sicuro di voler eliminare le seguenti domande? Se cancelli le domande bloccate, anche i risultati dei test che le contengono verranno cancellati. +qsts#:#create_question#:#Crea domanda +qsts#:#gap#:#Spazio vuoto +qsts#:#gaps#:#Gaps###28 11 2025 new variable +qsts#:#insert_gap#:#Inserisci Gap +qsts#:#min_auto_complete#:#Completamento automatico +qsts#:#msg_no_questions_selected#:#No questions were selected.###30 04 2024 new variable +qsts#:#out_of_range#:#Fuori portata +qsts#:#qst_lifecycle#:#Ciclo vitale +qsts#:#qst_lifecycle_draft#:#Bozza +qsts#:#qst_lifecycle_filter_all#:#Tutti i cicli di vita +qsts#:#qst_lifecycle_final#:#Finale +qsts#:#qst_lifecycle_outdated#:#Obsoleto +qsts#:#qst_lifecycle_rejected#:#Respinto +qsts#:#qst_lifecycle_review#:#Da rivedere +qsts#:#qst_lifecycle_sharable#:#Condivisibile +qsts#:#questionlist#:#Questionlist###30 04 2024 new variable +qsts#:#questions#:#Questions###30 04 2024 new variable +qsts#:#range_lower_limit#:#Limite inferiore +qsts#:#range_upper_limit#:#Limite superiore +qsts#:#reset_preview#:#Ripristina anteprima +qsts#:#select_gap#:#Seleziona spazio +qsts#:#shuffle_answers#:#Mescola le risposte +qsts#:#suggested_learning_content#:#Contenuti per il ripasso rating#:#rat_not_rated_yet#:#Non valutato rating#:#rat_nr_ratings#:#%s Valutazioni rating#:#rat_one_rating#:#Una valutazione @@ -16627,7 +16627,6 @@ survey#:#questionblock#:#Blocco di domande survey#:#questionblock_inserted#:#Blocco di domande inserito survey#:#questionblocks#:#Blocchi di domande survey#:#questionblocks_inserted#:#Blocchi di domande inseriti -survey#:#questions#:#Domande survey#:#questions_inserted#:#Domande inserite! survey#:#questions_removed#:#Domande e/o blocco di domande cancellate! survey#:#questiontype#:#Tipo di domanda @@ -16928,6 +16927,7 @@ survey#:#svy_please_select_unused_codes#:#Please select at least one unused code survey#:#svy_print_hide_labels#:#Nascondi etichette survey#:#svy_print_show_labels#:#Mostra etichette survey#:#svy_privacy_info#:#Privacy###31 03 2023 new variable +survey#:#svy_questions#:#Domande survey#:#svy_rater#:#Rater###31 03 2023 new variable survey#:#svy_rater_see_app_info#:#The names of appraisees will be presented to raters to enable them evaluating the questions.###31 03 2023 new variable survey#:#svy_reminder_mail_template#:#Modello di mail diff --git a/lang/ilias_ja.lang b/lang/ilias_ja.lang index cac05820e23c..9bae515b8ec3 100644 --- a/lang/ilias_ja.lang +++ b/lang/ilias_ja.lang @@ -422,7 +422,6 @@ adve#:#adve_use_tiny_mce#:#WYSIWYG編集用にTinyMCEを有効にする assessment#:#activate_logging#:#テストとログをアクティブにする assessment#:#activate_manual_scoring#:#手動並び替えを有効にする assessment#:#activate_manual_scoring_desc#:#全ての問題タイプの手動並び替えを有効にする -assessment#:#addSuggestedSolution#:#要約用のコンテンツを追加 assessment#:#add_answers#:#解答を追加 assessment#:#add_circle#:#円形エリアを追加 assessment#:#add_gap#:#穴埋めテキストを追加 @@ -447,7 +446,6 @@ assessment#:#answer_is_not_correct_but_positive#:#ベストな正解ではあり assessment#:#answer_is_right#:#解答は正解 assessment#:#answer_is_wrong#:#解答は誤り assessment#:#answer_of#:#回答者 -assessment#:#answer_options#:#解答オプション: assessment#:#answer_question#:#問題回答 assessment#:#answer_text#:#答案テキスト assessment#:#answer_types#:#答案タイプ @@ -501,7 +499,6 @@ assessment#:#ass_completion_by_submission#:#提出すると完了 assessment#:#ass_completion_by_submission_info#:#有効にすると提出ファイルの一つはこの問題の最高点で承認して完了した事になります。点数は後で手動にて修正可能です。この設定の変更は既に提出済みの解答には影響しません。 assessment#:#ass_create_export_file_with_results#:#テストエクスポートファイル(受験者結果を含む)を作成 assessment#:#ass_create_export_test_archive#:#テストアーカイブファイルを作成 -assessment#:#ass_create_question#:#問題を作成 assessment#:#ass_imap_hint#:#ツールチップとしてヒントを表示 assessment#:#ass_imap_map_file_not_readable#:#アップロードされたイメージマップを読み込む事ができませんでした。 assessment#:#ass_imap_no_map_found#:#アップロードされたイメージマップ内に書式が見つかりませんでした。 @@ -571,10 +568,6 @@ assessment#:#cloze_answer_text_info#:#フォームが保存されると回答テ assessment#:#cloze_fixed_textlength#:#テキスト欄の長さ assessment#:#cloze_fixed_textlength_description#:#0以上の値を入力すると全てのテキストと数値穴埋めテキストフィールドがこの値の固定長で作成されます。 assessment#:#cloze_gap_size_info#:#0以上を入力するとこのギャップテキストフィールドはこの値の固定長で作成されます。値を入力しない場合はギャップテキストフィールドはグローバル固定長で作成されます。 -assessment#:#cloze_text#:#穴埋め問題のテキスト -assessment#:#cloze_textgap_case_insensitive#:#大文字・小文字を非区分 -assessment#:#cloze_textgap_case_sensitive#:#大文字・小文字を区分 -assessment#:#cloze_textgap_levenshtein_of#:#%sのレーベンシュタイン距離 assessment#:#code#:#コード assessment#:#codebase#:#コードベース assessment#:#concatenation#:#結び付け @@ -757,9 +750,7 @@ assessment#:#fq_formula_desc#:#事前定義変数($v1を$vnへ)を(例 $r1)の assessment#:#fq_no_restriction_info#:#10進と分母両方共に入力として承認されます。 assessment#:#fq_precision_info#:#希望する10進位置を入力してください。 assessment#:#fq_question_desc#:#問題文章中の必要な位置で$r1, $r2 .... $rnを挿入する事により$v1, $v2 ... $vnの挿入による変数を定義できます。変数と結果のフォーム編集を作成するには"問題解析"ボタンをクリックします。 -assessment#:#gap#:#穴埋め assessment#:#gap_combination#:#ギャップ連結 -assessment#:#gaps#:#Gaps assessment#:#glossary_term#:#用語集 assessment#:#goto_first_question#:#第1問を表示 assessment#:#grading_mark_msg#:#結果: "[mark]" @@ -777,7 +768,6 @@ assessment#:#info_answer_type_change#:#問題にはイメージが既に含ま assessment#:#info_text_upload#:#アップロードする回答テキスト(UTF-8)を選択 assessment#:#insert_after#:#後に挿入 assessment#:#insert_before#:#前に挿入 -assessment#:#insert_gap#:#ギャップを挿入 assessment#:#interaction_type#:#インタラクションタイプ assessment#:#internal_links#:#内部リンク assessment#:#intprecision#:#非表示者 @@ -889,7 +879,6 @@ assessment#:#logs_wrong_test_password_provided#:#受験者が間違ったテス assessment#:#longmenu#:#ロングメニュー assessment#:#longmenu_answeroptions_differ#:#訂正オプションのテキスト内ギャップ数が同じではないので問題は正しく動作しません。 assessment#:#longmenu_text#:#長いメニューテキスト -assessment#:#mainbar_button_label_questionlist#:#問題リスト assessment#:#maintenance#:#メンテナンス assessment#:#manscoring#:#手動採点 assessment#:#manscoring_done#:#採点済み受講者 @@ -916,7 +905,6 @@ assessment#:#maximum_nr_of_tries_reached#:#このテストの最大受験回数 assessment#:#maximum_points#:#最高得点 assessment#:#maxsize#:#最大アップロードファイルサイズ assessment#:#maxsize_info#:#ファイル アップロードを許可するバイト単位の最大サイズを入力します。このフィールドを空白のままにする場合は、このインストールの最大サイズが代わりに選択されます。 -assessment#:#min_auto_complete#:#オートコンプリート assessment#:#min_ip_label#:#アクセス可能な最低IP assessment#:#min_percentage_ne_0#:#最小0のパーセントを定義する必要があります! 評価尺度は保存されませんでした。 assessment#:#misc#:#その他オプション @@ -925,7 +913,6 @@ assessment#:#mode_onebyone#:#一つずつ assessment#:#mode_question#:#問題指向 assessment#:#mode_user#:#受験者志向 assessment#:#msg_circle_added#:#円の追加 -assessment#:#msg_no_questions_selected#:#問題が選択されませんでした。 assessment#:#msg_number_of_terms_too_low#:#用語数は定義数と等しいかそれ以上である必要があります。 assessment#:#msg_poly_added#:#多角形の追加 assessment#:#msg_questions_moved#:#問題の移動 @@ -984,7 +971,6 @@ assessment#:#order#:#順番 assessment#:#ordering_answer_sequence_info#:#ここで定義する解答順序は正解として使用されます。 assessment#:#ordertext#:#整列するテキスト assessment#:#ordertext_info#:#水平方向にそろえるテキストを入力してください。整列するテキストはテキストの空白記号で区切られます。別の区切りが必要な場合は、%s の区切り記号を使用してテキストの単位を区切る事ができます。 -assessment#:#out_of_range#:#範囲外 assessment#:#output#:#出力 assessment#:#output_mode#:#出力モード assessment#:#parseQuestion#:#問題解析 @@ -1053,7 +1039,6 @@ assessment#:#qpl_bulk_save_add#:#追加 assessment#:#qpl_bulk_save_overwrite#:#上書き assessment#:#qpl_bulkedit_success#:#変更を保存しました。 assessment#:#qpl_cancel_skill_assigns_update#:#キャンセル -assessment#:#qpl_confirm_delete_questions#:#本当に次の問題を削除しますか? assessment#:#qpl_copy_insert_clipboard#:#選択した問題をクリップボードへコピーします assessment#:#qpl_copy_select_none#:#クリップボードへコピーする問題を最低1個チェックしてください assessment#:#qpl_delete_rbac_error#:#この問題の削除権がありません! @@ -1106,7 +1091,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#能力 assessment#:#qpl_qst_skl_usg_sklpnt_col#:#能力当たりの能力-ポイントの総合計 assessment#:#qpl_question_is_in_use#:#テスト%s 内にある修正しようとしている問題です。 この問題を修正しても、テストへ挿入の時点でシステムが問題のコピーを作成するためテストの問題は修正されません! assessment#:#qpl_questions_deleted#:#問題は削除されました。 -assessment#:#qpl_reset_preview#:#プレビューをリセット assessment#:#qpl_save_skill_assigns_update#:#能力割り当てを保存 assessment#:#qpl_settings_availability#:#Availability(空き状況) assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#有効にすると作成可能性のある分類基準がフィルタ用として表示されます。 @@ -1136,14 +1120,6 @@ assessment#:#qst_essay_chars_remaining#:#残り文字数: assessment#:#qst_essay_wordcounter_enabled#:#カウント文字数 assessment#:#qst_essay_wordcounter_enabled_info#:#入力の文字数をカウントします。受験者は以下のテキスト入力欄へ書きこんだ文字数が表示されます。 assessment#:#qst_essay_written_words#:#入力した文字数: -assessment#:#qst_lifecycle#:#ライフサイクル -assessment#:#qst_lifecycle_draft#:#ドラフト -assessment#:#qst_lifecycle_filter_all#:#全てのライフサイクル -assessment#:#qst_lifecycle_final#:#最終 -assessment#:#qst_lifecycle_outdated#:#旧 -assessment#:#qst_lifecycle_rejected#:#拒否されました -assessment#:#qst_lifecycle_review#:#要レビュー -assessment#:#qst_lifecycle_sharable#:#共有化 assessment#:#qst_nested_nested_answers_off#:#インデントなし、順番のみ assessment#:#qst_nested_nested_answers_on#:#回答にインデントを使用 assessment#:#qst_nr_of_tries#:#実行回数 @@ -1168,17 +1144,14 @@ assessment#:#question_type#:#問題のタイプ assessment#:#questionlist_cannot_be_altered#:#テストにはすでに参加者のデータセットが含まれているため、質問リストを変更することはできません。 assessment#:#questionpool_not_entered#:#問題集の名前を入力してください! assessment#:#questionpool_not_selected#:#問題集を選択して下さい! -assessment#:#questions#:#問題 assessment#:#questions_from#:#質問者 assessment#:#questions_per_page_view#:#ページ表示 assessment#:#random_accept_sample#:#サンプルを承諾 assessment#:#random_another_sample#:#他のサンプルを入手 assessment#:#random_selection#:#ランダム選択 assessment#:#range#:#範囲 -assessment#:#range_lower_limit#:#下限 assessment#:#range_max#:#範囲(最大) assessment#:#range_min#:#範囲(最小) -assessment#:#range_upper_limit#:#上限 assessment#:#rated_sign#:#サイン assessment#:#rated_unit#:#ユニット assessment#:#rated_value#:#値 @@ -1254,7 +1227,6 @@ assessment#:#search_roles#:#役割を検索 assessment#:#search_term#:#用語を検索 assessment#:#select_at_least_one_feedback_type_and_trigger#:#ファイードバックとトリガーの一つは選択してください。 assessment#:#select_at_least_one_lock_answer_type#:#ロックされた回答の一つは選択してください。 -assessment#:#select_gap#:#穴埋めを選択 assessment#:#select_max_one_item#:#一アイテムのみ選択してください assessment#:#select_one_user#:#最低ユーザ1名を選択してください。 assessment#:#select_question#:#問題を選択 @@ -1281,7 +1253,6 @@ assessment#:#show_old_introduction#:#過去の序論を表示 assessment#:#show_pass_overview#:#マークした試験状況を表示 assessment#:#show_results#:#結果を表示 assessment#:#show_user_answers#:#マークしたユーザの解答を表示 -assessment#:#shuffle_answers#:#解答をシャッフル assessment#:#skip_question#:#回答しないで次へ assessment#:#solution#:#解答 assessment#:#solutionText#:#テキスト @@ -14049,6 +14020,35 @@ qpl#:#qpl_page_type_qfbg#:#全般フィードバック qpl#:#qpl_page_type_qfbs#:#特別フィードバック qpl#:#qpl_page_type_qht#:#ヒント qpl#:#qpl_page_type_qpl#:#質問ページ +qsts#:#answer_options#:#解答オプション +qsts#:#cloze_text#:#穴埋め問題のテキスト +qsts#:#cloze_textgapcase_insensitive#:#大文字・小文字を非区分 +qsts#:#cloze_textgapcase_sensitive#:#大文字・小文字を区分 +qsts#:#cloze_textgaplevenshtein_of#:#%sのレーベンシュタイン距離 +qsts#:#confirm_delete_questions#:#本当に次の問題を削除しますか? +qsts#:#create_question#:#問題を作成 +qsts#:#gap#:#穴埋め +qsts#:#gaps#:#Gaps +qsts#:#insert_gap#:#ギャップを挿入 +qsts#:#min_auto_complete#:#オートコンプリート +qsts#:#msg_no_questions_selected#:#問題が選択されませんでした。 +qsts#:#out_of_range#:#範囲外 +qsts#:#qst_lifecycle#:#ライフサイクル +qsts#:#qst_lifecycle_draft#:#ドラフト +qsts#:#qst_lifecycle_filter_all#:#全てのライフサイクル +qsts#:#qst_lifecycle_final#:#最終 +qsts#:#qst_lifecycle_outdated#:#旧 +qsts#:#qst_lifecycle_rejected#:#拒否されました +qsts#:#qst_lifecycle_review#:#要レビュー +qsts#:#qst_lifecycle_sharable#:#共有化 +qsts#:#questionlist#:#問題リスト +qsts#:#questions#:#問題 +qsts#:#range_lower_limit#:#下限 +qsts#:#range_upper_limit#:#上限 +qsts#:#reset_preview#:#プレビューをリセット +qsts#:#select_gap#:#穴埋めを選択 +qsts#:#shuffle_answers#:#解答をシャッフル +qsts#:#suggested_learning_content#:#要約用のコンテンツを追加 rating#:#rat_not_rated_yet#:#星評価なし rating#:#rat_nr_ratings#:#%s星評価 rating#:#rat_one_rating#:#1つ星評価 @@ -16698,7 +16698,6 @@ survey#:#questionblock#:#質問ブロック survey#:#questionblock_inserted#:#質問ブロックの挿入 survey#:#questionblocks#:#質問ブロック survey#:#questionblocks_inserted#:#質問ブロックの挿入 -survey#:#questions#:#質問 survey#:#questions_inserted#:#質問を挿入しました! survey#:#questions_removed#:#質問や質問ブロックを削除しました! survey#:#questiontype#:#質問タイプ @@ -16981,6 +16980,7 @@ survey#:#svy_please_select_unused_codes#:#未使用のコードを一つは選 survey#:#svy_print_hide_labels#:#ラベルを非表示 survey#:#svy_print_show_labels#:#ラベルを表示 survey#:#svy_privacy_info#:#プライバシー +survey#:#svy_questions#:#質問 survey#:#svy_rater#:#評価者 survey#:#svy_rater_see_app_info#:#評価対象者名は問題評価を可能にする評価者へ表示されます。 survey#:#svy_reminder_mail_template#:#メールテンプレート diff --git a/lang/ilias_ka.lang b/lang/ilias_ka.lang index c1ebbd302731..9610bb082db7 100644 --- a/lang/ilias_ka.lang +++ b/lang/ilias_ka.lang @@ -422,7 +422,6 @@ adve#:#adve_use_tiny_mce#:#Enable TinyMCE for WYSIWYG Editing assessment#:#activate_logging#:#ტესტისა და შეფასების ჩატვირთვის გააქტიურება assessment#:#activate_manual_scoring#:#Enable Manual Scoring###28 10 2024 new variable assessment#:#activate_manual_scoring_desc#:#Enables manual Scoring for all question types.###28 10 2024 new variable -assessment#:#addSuggestedSolution#:#დაამატეთ შემოთავაზებული ამოხსნა assessment#:#add_answers#:#Add Antworten ###23 12 2015 new variable assessment#:#add_circle#:#დაამატეთ წრის სივრცე assessment#:#add_gap#:#დაამტეთ ნაპრალის ტექსტი @@ -447,7 +446,6 @@ assessment#:#answer_is_not_correct_but_positive#:#თქვენ მიიღ assessment#:#answer_is_right#:#თქვენი პასუხი სწორია assessment#:#answer_is_wrong#:#თქვენი პასუხი არასწორია assessment#:#answer_of#:#Answer of###07 02 2020 new variable -assessment#:#answer_options#:#Answer Options: ###23 12 2015 new variable assessment#:#answer_question#:#Answer Question###30 08 2015 new variable assessment#:#answer_text#:#პასუხის ტექსტი assessment#:#answer_types#:#პასუხის სახეობები @@ -501,7 +499,6 @@ assessment#:#ass_completion_by_submission#:#დასრულებულია assessment#:#ass_completion_by_submission_info#:#თუ გააქტიურებულია, ერთი ფაილის ჩაბარებაც იწვევს დავალების დასრულებას. ნიშნის ხელით შეცვლა შესაძლებელია მოგვიანებით. Aმ პარამეტრების ჩართვა არ იწვევს ცვლიბებს უკვე ჩაბარებულ პასუხებში assessment#:#ass_create_export_file_with_results#:#Create Test Export File (incl. Participant Results)###25 10 2016 new variable assessment#:#ass_create_export_test_archive#:#Create Test Archive File###24 10 2013 new variable -assessment#:#ass_create_question#:#Create Question###24 10 2013 new variable assessment#:#ass_imap_hint#:#Hint to be shown as Tooltip###25 10 2016 new variable assessment#:#ass_imap_map_file_not_readable#:#The uploaded image map could not be read.###25 10 2016 new variable assessment#:#ass_imap_no_map_found#:#Could not find any form in the uploaded image map.###25 10 2016 new variable @@ -571,10 +568,6 @@ assessment#:#cloze_answer_text_info#:#Spaces preceding or following the answer t assessment#:#cloze_fixed_textlength#:#ტექსტის ველის სიგრძე assessment#:#cloze_fixed_textlength_description#:#თუ თქვენ შეიყვანთ 0-ზე მეტ ღირებულებას მთლიანი ტექსტი და რიცხვითი "ნაპრალები" ამ ღირებულებით განისაზღვრება assessment#:#cloze_gap_size_info#:#If you enter a value greater than 0, this gap text field will be created with the fixed length of this value. If you do not enter a value the gap text fiel will be created with the value of the global fixed length.###26 09 2014 new variable -assessment#:#cloze_text#:#ტექსტის დახურვა -assessment#:#cloze_textgap_case_insensitive#:#შემთხვევა აუთვისებადია -assessment#:#cloze_textgap_case_sensitive#:#შემთხვევა ათვისებულია -assessment#:#cloze_textgap_levenshtein_of#:# assessment#:#code#:#კოდი assessment#:#codebase#:#კოდების ბაზა assessment#:#concatenation#:#ურთიერთაკვშირი, დამთხვევა @@ -757,9 +750,7 @@ assessment#:#fq_formula_desc#:#You may enter predefined variables ($v1 to $vn), assessment#:#fq_no_restriction_info#:#Both decimals and fractions are accepted as input.###26 03 2014 new variable assessment#:#fq_precision_info#:#Enter the number of desired decimal places.###26 03 2014 new variable assessment#:#fq_question_desc#:#You can define variables by inserting $v1, $v2 ... $vn, results by inserting $r1, $r2 .... $rn at the desired position in the question text. Click on the button "Parse Question" to create editing forms for variables and results.###06 11 2013 new variable -assessment#:#gap#:#ნაპრალი assessment#:#gap_combination#:#Gap Combination###26 09 2014 new variable -assessment#:#gaps#:#Gaps###29 10 2025 new variable assessment#:#glossary_term#:#ლექსიკონის პერიოდი assessment#:#goto_first_question#:#Show First Question###29 10 2025 new variable assessment#:#grading_mark_msg#:#Your resulting mark is: "[mark]"###26 09 2014 new variable @@ -777,7 +768,6 @@ assessment#:#info_answer_type_change#:#კითხვა უკვე შეი assessment#:#info_text_upload#:#Choose an answer file to upload###30 08 2015 new variable assessment#:#insert_after#:#ჩავსვათ შემდეგ assessment#:#insert_before#:#წინასწარ ჩავსვათ -assessment#:#insert_gap#:#Insert Gap###24 10 2013 new variable assessment#:#interaction_type#:#Interaction Type###26 08 2024 new variable assessment#:#internal_links#:#შიდა ლინკები assessment#:#intprecision#:#Divisible By###24 10 2013 new variable @@ -889,7 +879,6 @@ assessment#:#logs_wrong_test_password_provided#:#Participant entered wrong test assessment#:#longmenu#:#Longmenu###10 11 2018 new variable assessment#:#longmenu_answeroptions_differ#:#This question does not work correctly, as there are not the same amount of gaps in the text as in the correction options.###26 08 2024 new variable assessment#:#longmenu_text#:#Long Menu Text###30 08 2015 new variable -assessment#:#mainbar_button_label_questionlist#:#Questionlist###26 08 2024 new variable assessment#:#maintenance#:#შენარჩუნება assessment#:#manscoring#:#ხელით შეფასება assessment#:#manscoring_done#:#შეფასებული მონაწილეები @@ -916,7 +905,6 @@ assessment#:#maximum_nr_of_tries_reached#:#თქვენ მიაღწეთ assessment#:#maximum_points#:#ქულების მაქსიმალური რაოდენობა assessment#:#maxsize#:#ატვირთული ფაილის მაქსიმალური ზომა assessment#:#maxsize_info#:#შეიყვანეთ მაქსიმალური ზომა ბაიტებში რის ატვირთვაც იქნება შესაძლებელი. თუ თქვენ ამ ველს ცარიელს დატოვებთ, ინსტალაციისთვის მაქსიმალური ზომა იქნება დაშვებული. -assessment#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable assessment#:#min_ip_label#:#Lowest IP With Access###26 08 2024 new variable assessment#:#min_percentage_ne_0#:#თქვენ უნდა განსაზღვროთ 0ის მინიმალური %s. Qქულის სქემა არ არის შენახული assessment#:#misc#:#Misc Options###24 10 2013 new variable @@ -925,7 +913,6 @@ assessment#:#mode_onebyone#:#One by One###29 10 2025 new variable assessment#:#mode_question#:#Question oriented###29 10 2025 new variable assessment#:#mode_user#:#Participant oriented###29 10 2025 new variable assessment#:#msg_circle_added#:#წრე დაემატა -assessment#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable assessment#:#msg_number_of_terms_too_low#:#დასახელებების რიცხვი მეტი ან ტოლი უნდა იყოს განსაზღვრებების რიცხვისა assessment#:#msg_poly_added#:#პოლიგონი დაემატა assessment#:#msg_questions_moved#:#კითხვა გადაადგილდა @@ -984,7 +971,6 @@ assessment#:#order#:#Order###26 08 2024 new variable assessment#:#ordering_answer_sequence_info#:#პასუხების თანმიმდევრობა რომელიც თქვენ აქ განსაზღვრეთ გამოიყენება როგორც სწორი პასუხების თანმიმდევრობა assessment#:#ordertext#:#თანმიმდევრობითი ტექსტი assessment#:#ordertext_info#:#გთხოვთ შეიყვანოთ ტექსტი რომელიც ჰორიზონტალურად უნდა დალაგდეს. თანმიმდევრობითი ტექსტი გამოიყოფა თეთრი თავისუფალი სივრცის ნიშნებით. თუ გსურთ სხვა სახის გამოყოფა, თქვენ შეგიძლიათ გამოიყენოთ სხვა გამომყოფი %s. -assessment#:#out_of_range#:#Out of range###27 01 2015 new variable assessment#:#output#:#შედეგი assessment#:#output_mode#:#შედეგის სახეობა assessment#:#parseQuestion#:#Parse Question###24 10 2013 new variable @@ -1053,7 +1039,6 @@ assessment#:#qpl_bulk_save_add#:#Add###28 10 2024 new variable assessment#:#qpl_bulk_save_overwrite#:#Overwrite###28 10 2024 new variable assessment#:#qpl_bulkedit_success#:#Modifications saved.###28 10 2024 new variable assessment#:#qpl_cancel_skill_assigns_update#:#Cancel###30 08 2015 new variable -assessment#:#qpl_confirm_delete_questions#:#დარწმუნებული ხართ რომ ნამდვილად გსურთ შემდეგი კითხვების წაშლა? assessment#:#qpl_copy_insert_clipboard#:#მონიშნული კითხვების ასლი გადავიდა დაფაზე assessment#:#qpl_copy_select_none#:#გთხოვთ შეამოწმოთ სულ მცირე ერთი კითხვა რომ გადაიტანოთ ის დაფაზე assessment#:#qpl_delete_rbac_error#:#თქვენ არ გაქვთ უფლება წაშალოთ ეს კითხვა! @@ -1106,7 +1091,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#Competence###30 08 2015 new variable assessment#:#qpl_qst_skl_usg_sklpnt_col#:#Total Sum of Competence-Points per Competence###30 08 2015 new variable assessment#:#qpl_question_is_in_use#:#კითხვები რომლის რედაქტირებასაც თქვენ ცდილობთ არსებობს %s ტესტში. თუ თქვენ შეცვლით ამ ტესტს, თქვენ ვეღარ შეცვლით კითხვებს ტესტებში, რადგან სისტემა ქმნის კითხვების ასლს როცა მას ტესტში სვავთ assessment#:#qpl_questions_deleted#:#კითხვები წაიშალა -assessment#:#qpl_reset_preview#:#Reset Preview###26 09 2014 new variable assessment#:#qpl_save_skill_assigns_update#:#Save Competence Assignments###30 08 2015 new variable assessment#:#qpl_settings_availability#:#Availability###26 08 2024 new variable assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#When enabled, possibly created taxonomies are shown for filtering.###24 10 2013 new variable @@ -1136,14 +1120,6 @@ assessment#:#qst_essay_chars_remaining#:#Remaining characters:###07 02 2020 new assessment#:#qst_essay_wordcounter_enabled#:#Count Words###07 02 2020 new variable assessment#:#qst_essay_wordcounter_enabled_info#:#The entered words are counted. The number of written words is shown to the participants below the text input field.###07 02 2020 new variable assessment#:#qst_essay_written_words#:#Number of entered words:###07 02 2020 new variable -assessment#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable -assessment#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable -assessment#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable -assessment#:#qst_lifecycle_final#:#Final###07 02 2020 new variable -assessment#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable -assessment#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable -assessment#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable -assessment#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable assessment#:#qst_nested_nested_answers_off#:#No indents, just order###29 07 2022 new variable assessment#:#qst_nested_nested_answers_on#:#Use indents in anwers###29 07 2022 new variable assessment#:#qst_nr_of_tries#:#მცდელობების რაოდენობა @@ -1167,17 +1143,14 @@ assessment#:#question_title#:#კითხვის დასახელებ assessment#:#question_type#:#კითხვის სახეობა assessment#:#questionpool_not_entered#:#გთხოვთ შეიყვანოთ სახელი კითხვების ნაკადისთვის assessment#:#questionpool_not_selected#:#Please select a question pool!###07 02 2020 new variable -assessment#:#questions#:#Questions###26 08 2024 new variable assessment#:#questions_from#:#კითხვების ფორმა assessment#:#questions_per_page_view#:#გვერდის ხედვა assessment#:#random_accept_sample#:#ნიმუშის მიღება assessment#:#random_another_sample#:#სხვა ნიმუშის მიღება assessment#:#random_selection#:#შემთხვევითი მონიშვნა assessment#:#range#:#რიგი -assessment#:#range_lower_limit#:#ქვედა ზღვარი assessment#:#range_max#:#Range (Maximum)###24 10 2013 new variable assessment#:#range_min#:#Range (Minimum)###24 10 2013 new variable -assessment#:#range_upper_limit#:#ზედა ზღვარი assessment#:#rated_sign#:#Sign###24 10 2013 new variable assessment#:#rated_unit#:#Unit###24 10 2013 new variable assessment#:#rated_value#:#Value###24 10 2013 new variable @@ -1253,7 +1226,6 @@ assessment#:#search_roles#:#პასუხისმგებლობეის assessment#:#search_term#:#დასახელების ძიება assessment#:#select_at_least_one_feedback_type_and_trigger#:#Please Select at least one type of feedback and a trigger.###26 08 2024 new variable assessment#:#select_at_least_one_lock_answer_type#:#Please select at least one type of answer lock.###29 10 2025 new variable -assessment#:#select_gap#:#აირჩიეთ ნაპრალი assessment#:#select_max_one_item#:#გთხოვთ აირჩიოთ მხოლოდ ერთი პუნქტი assessment#:#select_one_user#:#გთხოვთ აირჩიოთ სულ მცირე ერთი მომხმარებელი assessment#:#select_question#:#Select a Question###28 10 2024 new variable @@ -1280,7 +1252,6 @@ assessment#:#show_old_introduction#:#Show old introduction###26 08 2024 new vari assessment#:#show_pass_overview#:#აჩვენე შეფასებული ჩაბარების მიმოხილვა assessment#:#show_results#:#Show Results###28 10 2024 new variable assessment#:#show_user_answers#:#აჩვენე მომხმარებელთა შეფასებული პასუხები -assessment#:#shuffle_answers#:#პასუხების არევა assessment#:#skip_question#:#Do not Answer and Next###08 10 2015 new variable assessment#:#solution#:#Solution###28 10 2024 new variable assessment#:#solutionText#:#ტექსტი @@ -13971,6 +13942,35 @@ qpl#:#qpl_page_type_qfbg#:#General Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qfbs#:#Special Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qht#:#Hint###29 07 2022 new variable qpl#:#qpl_page_type_qpl#:#Question Page###29 07 2022 new variable +qsts#:#answer_options#:#Answer Options ###23 12 2015 new variable +qsts#:#cloze_text#:#ტექსტის დახურვა +qsts#:#cloze_textgapcase_insensitive#:#შემთხვევა აუთვისებადია +qsts#:#cloze_textgapcase_sensitive#:#შემთხვევა ათვისებულია +qsts#:#cloze_textgaplevenshtein_of#:# +qsts#:#confirm_delete_questions#:#დარწმუნებული ხართ რომ ნამდვილად გსურთ შემდეგი კითხვების წაშლა? +qsts#:#create_question#:#Create Question###24 10 2013 new variable +qsts#:#gap#:#ნაპრალი +qsts#:#gaps#:#Gaps###29 10 2025 new variable +qsts#:#insert_gap#:#Insert Gap###24 10 2013 new variable +qsts#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable +qsts#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable +qsts#:#out_of_range#:#Out of range###27 01 2015 new variable +qsts#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable +qsts#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable +qsts#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable +qsts#:#qst_lifecycle_final#:#Final###07 02 2020 new variable +qsts#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable +qsts#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable +qsts#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable +qsts#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable +qsts#:#questionlist#:#Questionlist###26 08 2024 new variable +qsts#:#questions#:#Questions###26 08 2024 new variable +qsts#:#range_lower_limit#:#ქვედა ზღვარი +qsts#:#range_upper_limit#:#ზედა ზღვარი +qsts#:#reset_preview#:#Reset Preview###26 09 2014 new variable +qsts#:#select_gap#:#აირჩიეთ ნაპრალი +qsts#:#shuffle_answers#:#პასუხების არევა +qsts#:#suggested_learning_content#:#დაამატეთ შემოთავაზებული ამოხსნა rating#:#rat_not_rated_yet#:#არ არის შეფასებული ჯერ rating#:#rat_nr_ratings#:#%s რეიტინგები rating#:#rat_one_rating#:#ერთი რეიტინგი @@ -16620,7 +16620,6 @@ survey#:#questionblock#:#კითხვის ბლოკი survey#:#questionblock_inserted#:#კითხვის ბლოკი ჩასმულია survey#:#questionblocks#:#კითხვის ბლოკები survey#:#questionblocks_inserted#:#კითხვების ბლოკები ჩასმულია -survey#:#questions#:#კითხვები survey#:#questions_inserted#:#კითხვები ჩასმულია survey#:#questions_removed#:#კითხვები და/ან კითხვების ბლოკები ამოშლილია! survey#:#questiontype#:#კითხვის სახეობა @@ -16921,6 +16920,7 @@ survey#:#svy_please_select_unused_codes#:#Please select at least one unused code survey#:#svy_print_hide_labels#:#Hide labels###31 08 2017 new variable survey#:#svy_print_show_labels#:#Show labels###31 08 2017 new variable survey#:#svy_privacy_info#:#Privacy###29 07 2022 new variable +survey#:#svy_questions#:#კითხვები survey#:#svy_rater#:#Rater###29 07 2022 new variable survey#:#svy_rater_see_app_info#:#The names of appraisees will be presented to raters to enable them evaluating the questions.###29 07 2022 new variable survey#:#svy_reminder_mail_template#:#Mail Template###25 10 2016 new variable diff --git a/lang/ilias_lt.lang b/lang/ilias_lt.lang index 23cdb510600d..babdd31c07de 100644 --- a/lang/ilias_lt.lang +++ b/lang/ilias_lt.lang @@ -422,7 +422,6 @@ adve#:#adve_use_tiny_mce#:#Enable TinyMCE for WYSIWYG Editing###01 10 2011 new v assessment#:#activate_logging#:#Aktyvuoti Testavimo ir Vertinimo registravimą assessment#:#activate_manual_scoring#:#Enable Manual Scoring###28 10 2024 new variable assessment#:#activate_manual_scoring_desc#:#Enables manual Scoring for all question types.###28 10 2024 new variable -assessment#:#addSuggestedSolution#:#Add suggested solution###24 02 2009 new variable assessment#:#add_answers#:#Add Antworten ###23 12 2015 new variable assessment#:#add_circle#:#Add circle area###24 07 2009 new variable assessment#:#add_gap#:#Pridėti Tarpo (Gap) Tekstą @@ -447,7 +446,6 @@ assessment#:#answer_is_not_correct_but_positive#:#Jūs gavote taškų už savo s assessment#:#answer_is_right#:#Jūsų sprendimas yra teisingas assessment#:#answer_is_wrong#:#Jūsų sprendimas yra klaidingas assessment#:#answer_of#:#Answer of###07 02 2020 new variable -assessment#:#answer_options#:#Answer Options: ###23 12 2015 new variable assessment#:#answer_question#:#Answer Question###30 08 2015 new variable assessment#:#answer_text#:#Atsakymo tekstas assessment#:#answer_types#:#Answer Types###24 07 2009 new variable @@ -501,7 +499,6 @@ assessment#:#ass_completion_by_submission#:#Completed by Submission###12 06 2011 assessment#:#ass_completion_by_submission_info#:#If enabled, the submission of at least one file causes the completion of this question by granting the maximum score for this question. The score could be manually changed later. Switching this setting does not effect already submitted solutions.###12 06 2011 new variable assessment#:#ass_create_export_file_with_results#:#Create Test Export File (incl. Participant Results)###25 10 2016 new variable assessment#:#ass_create_export_test_archive#:#Create Test Archive File###24 10 2013 new variable -assessment#:#ass_create_question#:#Create Question###24 10 2013 new variable assessment#:#ass_imap_hint#:#Hint to be shown as Tooltip###25 10 2016 new variable assessment#:#ass_imap_map_file_not_readable#:#The uploaded image map could not be read.###25 10 2016 new variable assessment#:#ass_imap_no_map_found#:#Could not find any form in the uploaded image map.###25 10 2016 new variable @@ -571,10 +568,6 @@ assessment#:#cloze_answer_text_info#:#Spaces preceding or following the answer t assessment#:#cloze_fixed_textlength#:#Text Field Length###02 04 2007 new variable assessment#:#cloze_fixed_textlength_description#:#If you enter a value greater than 0, all text and numeric gap text fields will be created with the fixed length of this value.###02 04 2007 new variable assessment#:#cloze_gap_size_info#:#If you enter a value greater than 0, this gap text field will be created with the fixed length of this value. If you do not enter a value the gap text fiel will be created with the value of the global fixed length.###26 09 2014 new variable -assessment#:#cloze_text#:#Sąlygos tekstas -assessment#:#cloze_textgap_case_insensitive#:#ABC/abc nesvarbu -assessment#:#cloze_textgap_case_sensitive#:#ABC/abc svarbu -assessment#:#cloze_textgap_levenshtein_of#:#Levenshtein atstumas iš %s assessment#:#code#:#Kodas assessment#:#codebase#:#Kodo bazė assessment#:#concatenation#:#Tarpusavio ryšis @@ -757,9 +750,7 @@ assessment#:#fq_formula_desc#:#You may enter predefined variables ($v1 to $vn), assessment#:#fq_no_restriction_info#:#Both decimals and fractions are accepted as input.###26 03 2014 new variable assessment#:#fq_precision_info#:#Enter the number of desired decimal places.###26 03 2014 new variable assessment#:#fq_question_desc#:#You can define variables by inserting $v1, $v2 ... $vn, results by inserting $r1, $r2 .... $rn at the desired position in the question text. Click on the button "Parse Question" to create editing forms for variables and results.###06 11 2013 new variable -assessment#:#gap#:#Tarpas (Gap) assessment#:#gap_combination#:#Gap Combination###26 09 2014 new variable -assessment#:#gaps#:#Gaps###29 10 2025 new variable assessment#:#glossary_term#:#Glosarijaus terminas assessment#:#goto_first_question#:#Show First Question###29 10 2025 new variable assessment#:#grading_mark_msg#:#Your resulting mark is: "[mark]"###26 09 2014 new variable @@ -777,7 +768,6 @@ assessment#:#info_answer_type_change#:#The question already contains images. You assessment#:#info_text_upload#:#Choose an answer file to upload###30 08 2015 new variable assessment#:#insert_after#:#Įterpti po assessment#:#insert_before#:#Įterpti prieš -assessment#:#insert_gap#:#Insert Gap###24 10 2013 new variable assessment#:#interaction_type#:#Interaction Type###26 08 2024 new variable assessment#:#internal_links#:#Vidinės Nuorodos assessment#:#intprecision#:#Divisible By###24 10 2013 new variable @@ -889,7 +879,6 @@ assessment#:#logs_wrong_test_password_provided#:#Participant entered wrong test assessment#:#longmenu#:#Longmenu###10 11 2018 new variable assessment#:#longmenu_answeroptions_differ#:#This question does not work correctly, as there are not the same amount of gaps in the text as in the correction options.###26 08 2024 new variable assessment#:#longmenu_text#:#Long Menu Text###30 08 2015 new variable -assessment#:#mainbar_button_label_questionlist#:#Questionlist###26 08 2024 new variable assessment#:#maintenance#:#Priežiūra assessment#:#manscoring#:#Individualus Vertinimas assessment#:#manscoring_done#:#Scored Participants###24 02 2009 new variable @@ -916,7 +905,6 @@ assessment#:#maximum_nr_of_tries_reached#:#Jūs pasiekėte maksimalų testo band assessment#:#maximum_points#:#Maksimalus galimas balų skaičius assessment#:#maxsize#:#Maximum file upload size ###24 02 2009 new variable assessment#:#maxsize_info#:#Enter the maximum size in bytes that should be allowed for file uploads. If you leave this field empty, the maximum size of this installation will be chosen instead.###24 02 2009 new variable -assessment#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable assessment#:#min_ip_label#:#Lowest IP With Access###26 08 2024 new variable assessment#:#min_percentage_ne_0#:#Jūs privalote nustatyti minimalų procentų skaičių lygų 0 procentų! Vertinimo sistema neišsaugota. assessment#:#misc#:#Misc Options###24 10 2013 new variable @@ -925,7 +913,6 @@ assessment#:#mode_onebyone#:#One by One###29 10 2025 new variable assessment#:#mode_question#:#Question oriented###29 10 2025 new variable assessment#:#mode_user#:#Participant oriented###29 10 2025 new variable assessment#:#msg_circle_added#:#Circle added###24 07 2009 new variable -assessment#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable assessment#:#msg_number_of_terms_too_low#:#The number of terms must be greater or equal to the number of definitions.###12 08 2009 new variable assessment#:#msg_poly_added#:#Polygon added###24 07 2009 new variable assessment#:#msg_questions_moved#:#Question(s) moved###06 08 2009 new variable @@ -984,7 +971,6 @@ assessment#:#order#:#Order###26 08 2024 new variable assessment#:#ordering_answer_sequence_info#:#The answer sequence you define here will be taken as the correct solution sequence.###10 09 2010 new variable assessment#:#ordertext#:#Ordering Text###24 02 2009 new variable assessment#:#ordertext_info#:#Please enter the text that should be ordered horizontally. The ordering text will be separated by the whitespace signs in the text. If you need a different separation, you may use the separator %s to separate your text units.###24 02 2009 new variable -assessment#:#out_of_range#:#Out of range###27 01 2015 new variable assessment#:#output#:#Išvestis assessment#:#output_mode#:#Išvesties būdas assessment#:#parseQuestion#:#Parse Question###24 10 2013 new variable @@ -1053,7 +1039,6 @@ assessment#:#qpl_bulk_save_add#:#Add###28 10 2024 new variable assessment#:#qpl_bulk_save_overwrite#:#Overwrite###28 10 2024 new variable assessment#:#qpl_bulkedit_success#:#Modifications saved.###28 10 2024 new variable assessment#:#qpl_cancel_skill_assigns_update#:#Cancel###30 08 2015 new variable -assessment#:#qpl_confirm_delete_questions#:#Ar jūs tikrai norite ištrinti šį(-iuos) klausimą(-us)? Jei ištrinsite užrakintus klausimus, visų testų, kuriuose yra užrakintas klausimas rezultatai bus ištrinti taip pat. assessment#:#qpl_copy_insert_clipboard#:#Pasirinktas(-i) klausimas(-ai) nukopijuoti į atminties krepšelį assessment#:#qpl_copy_select_none#:#Norėdami perkelti į atminties krepšelį pasirinkite bent vieną klausimą assessment#:#qpl_delete_rbac_error#:#Jūs neturite teisių ištrinti šį klausimą! @@ -1106,7 +1091,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#Competence###30 08 2015 new variable assessment#:#qpl_qst_skl_usg_sklpnt_col#:#Total Sum of Competence-Points per Competence###30 08 2015 new variable assessment#:#qpl_question_is_in_use#:#Klausimas, kurį ruošiatės redaguoti įtrauktas į %s testą(-us). Jei pakeisite šį klausimą, klausimas(-ai) teste(-uose) NEpasikeis, kadangi sistema sukuria įterpiamo į testą klausimo kopiją! assessment#:#qpl_questions_deleted#:#Klausimas(-ai) ištrintas(-i). -assessment#:#qpl_reset_preview#:#Reset Preview###26 09 2014 new variable assessment#:#qpl_save_skill_assigns_update#:#Save Competence Assignments###30 08 2015 new variable assessment#:#qpl_settings_availability#:#Availability###26 08 2024 new variable assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#When enabled, possibly created taxonomies are shown for filtering.###24 10 2013 new variable @@ -1136,14 +1120,6 @@ assessment#:#qst_essay_chars_remaining#:#Remaining characters:###07 02 2020 new assessment#:#qst_essay_wordcounter_enabled#:#Count Words###07 02 2020 new variable assessment#:#qst_essay_wordcounter_enabled_info#:#The entered words are counted. The number of written words is shown to the participants below the text input field.###07 02 2020 new variable assessment#:#qst_essay_written_words#:#Number of entered words:###07 02 2020 new variable -assessment#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable -assessment#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable -assessment#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable -assessment#:#qst_lifecycle_final#:#Final###07 02 2020 new variable -assessment#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable -assessment#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable -assessment#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable -assessment#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable assessment#:#qst_nested_nested_answers_off#:#No indents, just order###29 07 2022 new variable assessment#:#qst_nested_nested_answers_on#:#Use indents in anwers###29 07 2022 new variable assessment#:#qst_nr_of_tries#:#Number of tries###15 05 2009 new variable @@ -1167,17 +1143,14 @@ assessment#:#question_title#:#Klausimo pavadinimas assessment#:#question_type#:#Klausimo Tipas assessment#:#questionpool_not_entered#:#Prašome įrašyti apklausos pavadinimą! assessment#:#questionpool_not_selected#:#Please select a question pool!###07 02 2020 new variable -assessment#:#questions#:#Questions###26 08 2024 new variable assessment#:#questions_from#:#klausimai iš assessment#:#questions_per_page_view#:#Page View###12 06 2011 new variable assessment#:#random_accept_sample#:#Priimti pavyzdį assessment#:#random_another_sample#:#Pasirinkite kitą pavyzdį assessment#:#random_selection#:#Atsitiktinis pasirinkimas assessment#:#range#:#Ribos -assessment#:#range_lower_limit#:#Žemutinė riba assessment#:#range_max#:#Range (Maximum)###24 10 2013 new variable assessment#:#range_min#:#Range (Minimum)###24 10 2013 new variable -assessment#:#range_upper_limit#:#Viršutinė riba assessment#:#rated_sign#:#Sign###24 10 2013 new variable assessment#:#rated_unit#:#Unit###24 10 2013 new variable assessment#:#rated_value#:#Value###24 10 2013 new variable @@ -1253,7 +1226,6 @@ assessment#:#search_roles#:#Ieškoti Rolių assessment#:#search_term#:#Ieškoti Žodžio assessment#:#select_at_least_one_feedback_type_and_trigger#:#Please Select at least one type of feedback and a trigger.###26 08 2024 new variable assessment#:#select_at_least_one_lock_answer_type#:#Please select at least one type of answer lock.###29 10 2025 new variable -assessment#:#select_gap#:#Pasirinkite tarpą assessment#:#select_max_one_item#:#Pasirinkite tik vieną elementą assessment#:#select_one_user#:#Pasirinkite bent vieną vartotoją assessment#:#select_question#:#Select a Question###28 10 2024 new variable @@ -1280,7 +1252,6 @@ assessment#:#show_old_introduction#:#Show old introduction###26 08 2024 new vari assessment#:#show_pass_overview#:#Show Marked Pass Overview###31 05 2007 new variable assessment#:#show_results#:#Show Results###28 10 2024 new variable assessment#:#show_user_answers#:#Show User's Marked Anwers###31 05 2007 new variable -assessment#:#shuffle_answers#:#Išmaišyti atsakymus assessment#:#skip_question#:#Do not Answer and Next###08 10 2015 new variable assessment#:#solution#:#Solution###28 10 2024 new variable assessment#:#solutionText#:#Text###24 02 2009 new variable @@ -13971,6 +13942,35 @@ qpl#:#qpl_page_type_qfbg#:#General Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qfbs#:#Special Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qht#:#Hint###29 07 2022 new variable qpl#:#qpl_page_type_qpl#:#Question Page###29 07 2022 new variable +qsts#:#answer_options#:#Answer Options ###23 12 2015 new variable +qsts#:#cloze_text#:#Sąlygos tekstas +qsts#:#cloze_textgapcase_insensitive#:#ABC/abc nesvarbu +qsts#:#cloze_textgapcase_sensitive#:#ABC/abc svarbu +qsts#:#cloze_textgaplevenshtein_of#:#Levenshtein atstumas iš %s +qsts#:#confirm_delete_questions#:#Ar jūs tikrai norite ištrinti šį(-iuos) klausimą(-us)? Jei ištrinsite užrakintus klausimus, visų testų, kuriuose yra užrakintas klausimas rezultatai bus ištrinti taip pat. +qsts#:#create_question#:#Create Question###24 10 2013 new variable +qsts#:#gap#:#Tarpas (Gap) +qsts#:#gaps#:#Gaps###29 10 2025 new variable +qsts#:#insert_gap#:#Insert Gap###24 10 2013 new variable +qsts#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable +qsts#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable +qsts#:#out_of_range#:#Out of range###27 01 2015 new variable +qsts#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable +qsts#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable +qsts#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable +qsts#:#qst_lifecycle_final#:#Final###07 02 2020 new variable +qsts#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable +qsts#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable +qsts#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable +qsts#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable +qsts#:#questionlist#:#Questionlist###26 08 2024 new variable +qsts#:#questions#:#Questions###26 08 2024 new variable +qsts#:#range_lower_limit#:#Žemutinė riba +qsts#:#range_upper_limit#:#Viršutinė riba +qsts#:#reset_preview#:#Reset Preview###26 09 2014 new variable +qsts#:#select_gap#:#Pasirinkite tarpą +qsts#:#shuffle_answers#:#Išmaišyti atsakymus +qsts#:#suggested_learning_content#:#Add suggested solution###24 02 2009 new variable rating#:#rat_not_rated_yet#:#Not Rated Yet###12 06 2011 new variable rating#:#rat_nr_ratings#:#%s Ratings###12 06 2011 new variable rating#:#rat_one_rating#:#One Rating###12 06 2011 new variable @@ -16620,7 +16620,6 @@ survey#:#questionblock#:#Klausimo blokas survey#:#questionblock_inserted#:#Question Block inserted###06 08 2009 new variable survey#:#questionblocks#:#Klausimo blokai survey#:#questionblocks_inserted#:#Question Blocks inserted###06 08 2009 new variable -survey#:#questions#:#Klausimai survey#:#questions_inserted#:#Klausimas(-ai) įterpti! survey#:#questions_removed#:#Klausimas(-ai) ir/ar klausimo blokas(-ai) pašalinti! survey#:#questiontype#:#Klausimo tipas @@ -16921,6 +16920,7 @@ survey#:#svy_please_select_unused_codes#:#Please select at least one unused code survey#:#svy_print_hide_labels#:#Hide labels###31 08 2017 new variable survey#:#svy_print_show_labels#:#Show labels###31 08 2017 new variable survey#:#svy_privacy_info#:#Privacy###29 07 2022 new variable +survey#:#svy_questions#:#Klausimai survey#:#svy_rater#:#Rater###29 07 2022 new variable survey#:#svy_rater_see_app_info#:#The names of appraisees will be presented to raters to enable them evaluating the questions.###29 07 2022 new variable survey#:#svy_reminder_mail_template#:#Mail Template###25 10 2016 new variable diff --git a/lang/ilias_nl.lang b/lang/ilias_nl.lang index 10383e62b332..d59bfefa87f8 100644 --- a/lang/ilias_nl.lang +++ b/lang/ilias_nl.lang @@ -422,7 +422,6 @@ adve#:#adve_use_tiny_mce#:#Inschakelen TinyMCE voor WYSIWYG Editen assessment#:#activate_logging#:#Inschakelen Toets&Beoordeling logging assessment#:#activate_manual_scoring#:#Enable Manual Scoring###28 10 2024 new variable assessment#:#activate_manual_scoring_desc#:#Enables manual Scoring for all question types.###28 10 2024 new variable -assessment#:#addSuggestedSolution#:#Voeg voorgestelde oplossing toe assessment#:#add_answers#:#Add Antworten ###23 12 2015 new variable assessment#:#add_circle#:#Voeg cirkel gebied toe assessment#:#add_gap#:#Voeg spatie toe @@ -447,7 +446,6 @@ assessment#:#answer_is_not_correct_but_positive#:#Je hebt punten gekregen voor d assessment#:#answer_is_right#:#Je oplossing is goed assessment#:#answer_is_wrong#:#Je oplossing is fout assessment#:#answer_of#:#Answer of###07 02 2020 new variable -assessment#:#answer_options#:#Answer Options: ###23 12 2015 new variable assessment#:#answer_question#:#Answer Question###30 08 2015 new variable assessment#:#answer_text#:#Tekst assessment#:#answer_types#:#Antwoord type @@ -501,7 +499,6 @@ assessment#:#ass_completion_by_submission#:#Completed by Submission assessment#:#ass_completion_by_submission_info#:#If enabled, the submission of at least one file causes the completion of this question by granting the maximum score for this question. The score could be manually changed later. Switching this setting does not effect already submitted solutions. assessment#:#ass_create_export_file_with_results#:#Create Test Export File (incl. Participant Results)###25 10 2016 new variable assessment#:#ass_create_export_test_archive#:#Aanmaken van een Test Archive bestand -assessment#:#ass_create_question#:#Vraag aanmaken assessment#:#ass_imap_hint#:#Hint to be shown as Tooltip###25 10 2016 new variable assessment#:#ass_imap_map_file_not_readable#:#The uploaded image map could not be read.###25 10 2016 new variable assessment#:#ass_imap_no_map_found#:#Could not find any form in the uploaded image map.###25 10 2016 new variable @@ -571,10 +568,6 @@ assessment#:#cloze_answer_text_info#:#Spaces preceding or following the answer t assessment#:#cloze_fixed_textlength#:#Tekst veldlengte assessment#:#cloze_fixed_textlength_description#:#Als je een waarde hoger dan 0 ingeeft, dan zullen alle tekst en numerieke velden een lengte krijgen van deze waarde. assessment#:#cloze_gap_size_info#:#Als je een waarde groter dan 0 ingeeft, zal dit gap tekstveld worden aangemaakt met deze lengte. Als je geen waarde ingeeft, zal het gap tekstveld worden aangemaakt met de waarde van het globale vaste lengte. -assessment#:#cloze_text#:#Verhaspelde zin -assessment#:#cloze_textgap_case_insensitive#:#Hoofdletter ongevoelig -assessment#:#cloze_textgap_case_sensitive#:#Hoofdletter gevoelig -assessment#:#cloze_textgap_levenshtein_of#:#Levenshtein afstand van %s assessment#:#code#:#Code assessment#:#codebase#:#Codebase assessment#:#concatenation#:#Aaneenschakeling @@ -757,9 +750,7 @@ assessment#:#fq_formula_desc#:#You may enter predefined variables ($v1 to $vn), assessment#:#fq_no_restriction_info#:#Zowel decimalen als breuken worden geaccepteerd als input assessment#:#fq_precision_info#:#Gewenste aantal decimale plaatsen assessment#:#fq_question_desc#:#Je kunt variabelen definieren met $v1, $v2 ... $vn, resultaten door het gebruik van $r1, $r2 .... $rn op de gewenste positie in de vraagtekst. Klik op de knop "Ontleed vraag" om editforms voor variabelen en resultaten te openen. -assessment#:#gap#:#Gat assessment#:#gap_combination#:#Gap Combinatie -assessment#:#gaps#:#Gaps###29 10 2025 new variable assessment#:#glossary_term#:#Verklarende term assessment#:#goto_first_question#:#Show First Question###29 10 2025 new variable assessment#:#grading_mark_msg#:#Je resultaat is: "[mark]" @@ -777,7 +768,6 @@ assessment#:#info_answer_type_change#:#De vraag bevat reeds plaatjes.U kunt het assessment#:#info_text_upload#:#Choose an answer file to upload###30 08 2015 new variable assessment#:#insert_after#:#Voeg in na assessment#:#insert_before#:#Voeg in voor -assessment#:#insert_gap#:#Insert Gap assessment#:#interaction_type#:#Interaction Type###26 08 2024 new variable assessment#:#internal_links#:#Interne links assessment#:#intprecision#:#Deelbaar door @@ -889,7 +879,6 @@ assessment#:#logs_wrong_test_password_provided#:#Participant entered wrong test assessment#:#longmenu#:#Longmenu###10 11 2018 new variable assessment#:#longmenu_answeroptions_differ#:#This question does not work correctly, as there are not the same amount of gaps in the text as in the correction options.###26 08 2024 new variable assessment#:#longmenu_text#:#Long Menu Text###30 08 2015 new variable -assessment#:#mainbar_button_label_questionlist#:#Questionlist###26 08 2024 new variable assessment#:#maintenance#:#Onderhoud assessment#:#manscoring#:#Handmatige score assessment#:#manscoring_done#:#Scorende Deelnemers @@ -916,7 +905,6 @@ assessment#:#maximum_nr_of_tries_reached#:#Je hebt het maximum aantal pogingen v assessment#:#maximum_points#:#Maximum behaalbare punten assessment#:#maxsize#:#Maximum file upload size assessment#:#maxsize_info#:#Vul in de maximale hoeveelheid bytes die wordt toegestaan voor bestand-uploads. Als je dit vak blanco laat, wordt de maximale grootte van deze installatie gekozen. -assessment#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable assessment#:#min_ip_label#:#Lowest IP With Access###26 08 2024 new variable assessment#:#min_percentage_ne_0#:#Je moet een minimum percentage opgeven van ministens 0 procent! Het punten schema is niet opgeslaan assessment#:#misc#:#Misc Options @@ -925,7 +913,6 @@ assessment#:#mode_onebyone#:#One by One###29 10 2025 new variable assessment#:#mode_question#:#Question oriented###29 10 2025 new variable assessment#:#mode_user#:#Participant oriented###29 10 2025 new variable assessment#:#msg_circle_added#:#Cirkel toegevoegd -assessment#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable assessment#:#msg_number_of_terms_too_low#:#Het aantal termen moet groter of gelijk zijn aan het aantal definities. assessment#:#msg_poly_added#:#Polygon toegevoegd assessment#:#msg_questions_moved#:#Vragen verplaatst @@ -984,7 +971,6 @@ assessment#:#order#:#Order###26 08 2024 new variable assessment#:#ordering_answer_sequence_info#:#De volgorde van de antwoorden die je hebt bepaald, wordt beschouwd als de juiste volgorde. assessment#:#ordertext#:#Tekstvolgorde assessment#:#ordertext_info#:#Voer de tekst in die horizontaal komt te staan. De tekst wordt gescheiden door spaties. Als je daarvoor iets anders wenst gebruik dan %s om je tekstonderdelen te scheiden. -assessment#:#out_of_range#:#Out of range###06 02 2015 new variable assessment#:#output#:#Output assessment#:#output_mode#:#Output modus assessment#:#parseQuestion#:#Vraag ontleden @@ -1053,7 +1039,6 @@ assessment#:#qpl_bulk_save_add#:#Add###28 10 2024 new variable assessment#:#qpl_bulk_save_overwrite#:#Overwrite###28 10 2024 new variable assessment#:#qpl_bulkedit_success#:#Modifications saved.###28 10 2024 new variable assessment#:#qpl_cancel_skill_assigns_update#:#Cancel###30 08 2015 new variable -assessment#:#qpl_confirm_delete_questions#:#Weet je zeker dat je de volgende vraag(vragen) wilt verwijderen Als je een geblokkeerde vraag verwijderd, dan worden de resultaten van alle toetsen die deze geblokkeerde vraag bevatten, ook verwijderd. assessment#:#qpl_copy_insert_clipboard#:#De geselecteerde vragen zijn gekopieerd naar het klembord assessment#:#qpl_copy_select_none#:#Selecteer minstens één vraag om te kopiëren naar het klembord assessment#:#qpl_delete_rbac_error#:#Je hebt geen toegang om deze vraag te verwijderen! @@ -1106,7 +1091,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#Competence###30 08 2015 new variable assessment#:#qpl_qst_skl_usg_sklpnt_col#:#Total Sum of Competence-Points per Competence###30 08 2015 new variable assessment#:#qpl_question_is_in_use#:#De vraag, die je nu gaat wijzigen, bestaat al in %s toets(en). Als je deze vraag nu wijzigt, wijzig je niet de vraag/vragen in de toets(en), omdat het systeem een kopie van de vraag maakt als de vraag in een toets wordt opgenomen! assessment#:#qpl_questions_deleted#:#Vraag/vragen verwijderd. -assessment#:#qpl_reset_preview#:#Herstel beeld assessment#:#qpl_save_skill_assigns_update#:#Save Competence Assignments###30 08 2015 new variable assessment#:#qpl_settings_availability#:#Availability###26 08 2024 new variable assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#When enabled, possibly created taxonomies are shown for filtering. @@ -1136,14 +1120,6 @@ assessment#:#qst_essay_chars_remaining#:#Remaining characters:###07 02 2020 new assessment#:#qst_essay_wordcounter_enabled#:#Count Words###07 02 2020 new variable assessment#:#qst_essay_wordcounter_enabled_info#:#The entered words are counted. The number of written words is shown to the participants below the text input field.###07 02 2020 new variable assessment#:#qst_essay_written_words#:#Number of entered words:###07 02 2020 new variable -assessment#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable -assessment#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable -assessment#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable -assessment#:#qst_lifecycle_final#:#Final###07 02 2020 new variable -assessment#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable -assessment#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable -assessment#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable -assessment#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable assessment#:#qst_nested_nested_answers_off#:#No indents, just order###29 07 2022 new variable assessment#:#qst_nested_nested_answers_on#:#Use indents in anwers###29 07 2022 new variable assessment#:#qst_nr_of_tries#:#Aantal pogingen @@ -1167,17 +1143,14 @@ assessment#:#question_title#:#Vraag titel assessment#:#question_type#:#Vraag type assessment#:#questionpool_not_entered#:#Geef een naam voor de vragenpool! assessment#:#questionpool_not_selected#:#Please select a question pool!###07 02 2020 new variable -assessment#:#questions#:#Questions###26 08 2024 new variable assessment#:#questions_from#:#vragen van assessment#:#questions_per_page_view#:#Paginabeeld assessment#:#random_accept_sample#:#Accepteer voorbeeld assessment#:#random_another_sample#:#Kies een ander voorbeeld assessment#:#random_selection#:#Willekeurige selectie assessment#:#range#:#Limiet -assessment#:#range_lower_limit#:#Verlaag limit assessment#:#range_max#:#Bereik (Maximum) assessment#:#range_min#:#Bereik (Minimum) -assessment#:#range_upper_limit#:#Verhoog limit assessment#:#rated_sign#:#Teken assessment#:#rated_unit#:#Eenheid assessment#:#rated_value#:#Beoordeling @@ -1253,7 +1226,6 @@ assessment#:#search_roles#:#Zoek rollen assessment#:#search_term#:#Zoek begrippen assessment#:#select_at_least_one_feedback_type_and_trigger#:#Please Select at least one type of feedback and a trigger.###26 08 2024 new variable assessment#:#select_at_least_one_lock_answer_type#:#Please select at least one type of answer lock.###29 10 2025 new variable -assessment#:#select_gap#:#Selecteer gaten assessment#:#select_max_one_item#:#Selecteer minstens 1 item assessment#:#select_one_user#:#Selecteer minstens 1 gebruiker assessment#:#select_question#:#Select a Question###28 10 2024 new variable @@ -1280,7 +1252,6 @@ assessment#:#show_old_introduction#:#Show old introduction###26 08 2024 new vari assessment#:#show_pass_overview#:#Toon gemarkeerde afgelopen toetsen assessment#:#show_results#:#Show Results###28 10 2024 new variable assessment#:#show_user_answers#:#Toon gebruikers gemarkeerde antwoorden. -assessment#:#shuffle_answers#:#Antwoorden mixen assessment#:#skip_question#:#Do not Answer and Next###08 10 2015 new variable assessment#:#solution#:#Solution###28 10 2024 new variable assessment#:#solutionText#:#Tekst @@ -13971,6 +13942,35 @@ qpl#:#qpl_page_type_qfbg#:#General Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qfbs#:#Special Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qht#:#Hint###29 07 2022 new variable qpl#:#qpl_page_type_qpl#:#Question Page###29 07 2022 new variable +qsts#:#answer_options#:#Answer Options ###23 12 2015 new variable +qsts#:#cloze_text#:#Verhaspelde zin +qsts#:#cloze_textgapcase_insensitive#:#Hoofdletter ongevoelig +qsts#:#cloze_textgapcase_sensitive#:#Hoofdletter gevoelig +qsts#:#cloze_textgaplevenshtein_of#:#Levenshtein afstand van %s +qsts#:#confirm_delete_questions#:#Weet je zeker dat je de volgende vraag(vragen) wilt verwijderen Als je een geblokkeerde vraag verwijderd, dan worden de resultaten van alle toetsen die deze geblokkeerde vraag bevatten, ook verwijderd. +qsts#:#create_question#:#Vraag aanmaken +qsts#:#gap#:#Gat +qsts#:#gaps#:#Gaps###29 10 2025 new variable +qsts#:#insert_gap#:#Insert Gap +qsts#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable +qsts#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable +qsts#:#out_of_range#:#Out of range###06 02 2015 new variable +qsts#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable +qsts#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable +qsts#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable +qsts#:#qst_lifecycle_final#:#Final###07 02 2020 new variable +qsts#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable +qsts#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable +qsts#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable +qsts#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable +qsts#:#questionlist#:#Questionlist###26 08 2024 new variable +qsts#:#questions#:#Questions###26 08 2024 new variable +qsts#:#range_lower_limit#:#Verlaag limit +qsts#:#range_upper_limit#:#Verhoog limit +qsts#:#reset_preview#:#Herstel beeld +qsts#:#select_gap#:#Selecteer gaten +qsts#:#shuffle_answers#:#Antwoorden mixen +qsts#:#suggested_learning_content#:#Voeg voorgestelde oplossing toe rating#:#rat_not_rated_yet#:#Nog geen beoordelingen rating#:#rat_nr_ratings#:#%s Beoordelingen rating#:#rat_one_rating#:#Eén beoordeling @@ -16620,7 +16620,6 @@ survey#:#questionblock#:#Vragenblok survey#:#questionblock_inserted#:#Vraagblok ingevoegd survey#:#questionblocks#:#Vraagblokken survey#:#questionblocks_inserted#:#Vraakblokken ingevoegd -survey#:#questions#:#Vragen survey#:#questions_inserted#:#Vraag(vragen) ingevoegd! survey#:#questions_removed#:#Vraag(vragen) en/of vraag-blok(ken)verwijderd! survey#:#questiontype#:#Vraagtype @@ -16922,6 +16921,7 @@ survey#:#svy_please_select_unused_codes#:#Please select at least one unused code survey#:#svy_print_hide_labels#:#Hide labels###31 08 2017 new variable survey#:#svy_print_show_labels#:#Show labels###31 08 2017 new variable survey#:#svy_privacy_info#:#Privacy###29 07 2022 new variable +survey#:#svy_questions#:#Vragen survey#:#svy_rater#:#Rater###29 07 2022 new variable survey#:#svy_rater_see_app_info#:#The names of appraisees will be presented to raters to enable them evaluating the questions.###29 07 2022 new variable survey#:#svy_reminder_mail_template#:#Mail Template###25 10 2016 new variable diff --git a/lang/ilias_pl.lang b/lang/ilias_pl.lang index 31cfeefd861d..66d094c45806 100644 --- a/lang/ilias_pl.lang +++ b/lang/ilias_pl.lang @@ -422,7 +422,6 @@ adve#:#adve_use_tiny_mce#:#Aktywuj TinyMCE do edytowania WYSIWYG assessment#:#activate_logging#:#Aktywuj śledzenie testów i oceniania assessment#:#activate_manual_scoring#:#Enable Manual Scoring###28 10 2024 new variable assessment#:#activate_manual_scoring_desc#:#Enables manual Scoring for all question types.###28 10 2024 new variable -assessment#:#addSuggestedSolution#:#Pokaż sugerowane rozwiązanie assessment#:#add_answers#:#Dodaj odpowiedzi assessment#:#add_circle#:#Dodaj krąg assessment#:#add_gap#:#Dodaj tekst odstępu @@ -447,7 +446,6 @@ assessment#:#answer_is_not_correct_but_positive#:#Otrzymałeś punkty za swoją assessment#:#answer_is_right#:#Twoja odpowiedź jest prawidłowa. assessment#:#answer_is_wrong#:#Twoja odpowiedź jest błędna. assessment#:#answer_of#:#Answer of###07 02 2020 new variable -assessment#:#answer_options#:#Opcje z odpowiedziami: assessment#:#answer_question#:#Odpowiedz na pytanie assessment#:#answer_text#:#Tekst odpowiedzi assessment#:#answer_types#:#Edytor odpowiedzi @@ -501,7 +499,6 @@ assessment#:#ass_completion_by_submission#:#Zaliczenie przez oddanie assessment#:#ass_completion_by_submission_info#:#Jeśli ta opcja jest aktywna, oddanie pliku z odpowiedziami skutkuje naliczeniem maksymalnej liczny punktów dla danego pytania. Ocenę można w każdej chwili dopasować ręcznie. Zmiana tego ustawienia nie ma żadnego wpływu na już oddane rozwiązania. assessment#:#ass_create_export_file_with_results#:#Utwórz plik eksportowy (łącznie z wynikami uczestników) assessment#:#ass_create_export_test_archive#:#Utwórz plik archiwalny dla testu -assessment#:#ass_create_question#:#Utwórz pytanie assessment#:#ass_imap_hint#:#Wskazówka (wyświetlona jako porada) assessment#:#ass_imap_map_file_not_readable#:#Nie udało się wczytać wysłanej mapy bitowej. assessment#:#ass_imap_no_map_found#:#W wysłanej mapie bitowej nie udało się znaleźć obsługiwanego formatu. @@ -571,10 +568,6 @@ assessment#:#cloze_answer_text_info#:#Spaces preceding or following the answer t assessment#:#cloze_fixed_textlength#:#Długość pola tekstowego assessment#:#cloze_fixed_textlength_description#:#Jeśli wpiszesz tutaj wartość, zostaną utworzone luki w tekście, które nie mają określonej maksymalnej długości, oraz luki numeryczne o tej długości, dzięki czemu nie będzie możliwości wprowadzić większej liczby znaków. W przypadku luk numerycznych należy również pamiętać, że przy liczeniu uwzględniany jest separator dziesiętny. assessment#:#cloze_gap_size_info#:#Jeśli wpisana wartość jest większa niż 0, ta przerwa generowane jest o tutaj wpisanej długości. Jeśli nie podano żadnej wartości, ta przerwa zostanie wygenerowana o długości pola tekstowego podanej globalnie. -assessment#:#cloze_text#:#Uzupełnij tekst -assessment#:#cloze_textgap_case_insensitive#:#Nie uwzględniaj wielkości liter -assessment#:#cloze_textgap_case_sensitive#:#Uwzględniaj wielkość liter -assessment#:#cloze_textgap_levenshtein_of#:#Odległość Levenshtein %s assessment#:#code#:#Kod assessment#:#codebase#:#Baza kodu assessment#:#concatenation#:#Złączenie @@ -757,9 +750,7 @@ assessment#:#fq_formula_desc#:#Dozwolone jest użycie już zdefiniowanych zmienn assessment#:#fq_no_restriction_info#:#Dozwolone są zarówno liczby dziesiętne jak i ułamki. assessment#:#fq_precision_info#:#Wpisz tutaj wymaganą liczbę miejsc po przecinku. assessment#:#fq_question_desc#:#Zmienne definiujesz przez podanie w wybranych pozycjach w tekście $v1, $v2 ... $vn, pól z wynikami z $r1, $r2 .... $rn. Następnie kliknij na przycisk "Analizuj pytanie", aby wygenerować formularze edycji dla wszystkich zmiennych i wyników. -assessment#:#gap#:#Luka assessment#:#gap_combination#:#Kombinacji tekstu do uzupełnienia -assessment#:#gaps#:#Gaps###29 10 2025 new variable assessment#:#glossary_term#:#Hasło słownika assessment#:#goto_first_question#:#Show First Question###29 10 2025 new variable assessment#:#grading_mark_msg#:#Otrzymałeś/otrzymałaś ocenę Note "[mark]" @@ -777,7 +768,6 @@ assessment#:#info_answer_type_change#:#Pytanie zawiera już obrazy. Dlatego nie assessment#:#info_text_upload#:#Wybierz plik tekstowy (UTF-8) z odpowiedziami do załadowania. assessment#:#insert_after#:#Wstaw po assessment#:#insert_before#:#Wstaw przed -assessment#:#insert_gap#:#Wypełnij lukę assessment#:#interaction_type#:#Interaction Type###26 08 2024 new variable assessment#:#internal_links#:#Linki wewnętrzne assessment#:#intprecision#:#Dzielone przez @@ -889,7 +879,6 @@ assessment#:#logs_wrong_test_password_provided#:#Uczestnik wprowadził nieprawid assessment#:#longmenu#:#Longmenu assessment#:#longmenu_answeroptions_differ#:#This question does not work correctly, as there are not the same amount of gaps in the text as in the correction options.###26 08 2024 new variable assessment#:#longmenu_text#:#Tekst ‘Long Menu’ -assessment#:#mainbar_button_label_questionlist#:#Questionlist###26 08 2024 new variable assessment#:#maintenance#:#Konserwacja assessment#:#manscoring#:#Ręczna punktacja assessment#:#manscoring_done#:#Już ocenieni uczestnicy @@ -916,7 +905,6 @@ assessment#:#maximum_nr_of_tries_reached#:#Przekroczyłeś maksymalną ilość p assessment#:#maximum_points#:#Maksymalna liczba dostępnych punktów assessment#:#maxsize#:#Maksymalny rozmiar pliku assessment#:#maxsize_info#:#Podaj maksymalny rozmiar pliku w bytach, którego ładowany plik nie może przekroczyć. Jeśli to pole będzie puste, zastosowane zostanie ustawienie podstawowego systemu. -assessment#:#min_auto_complete#:#Automatyczne uzupełnianie assessment#:#min_ip_label#:#Lowest IP With Access###26 08 2024 new variable assessment#:#min_percentage_ne_0#:#Musisz określić minimum na 0 procent! Schemat zaznaczeń nie został zapisany. Schemat zaznaczeń nie został zapisany. assessment#:#misc#:#Różne opcje @@ -925,7 +913,6 @@ assessment#:#mode_onebyone#:#One by One###29 10 2025 new variable assessment#:#mode_question#:#Question oriented###29 10 2025 new variable assessment#:#mode_user#:#Participant oriented###29 10 2025 new variable assessment#:#msg_circle_added#:#Dodano krąg. -assessment#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable assessment#:#msg_number_of_terms_too_low#:#Liczba pojęć musi być większa lub równa liczbie definicji. assessment#:#msg_poly_added#:#Dodano wielobok. assessment#:#msg_questions_moved#:#Przesunięto pytanie(a). @@ -984,7 +971,6 @@ assessment#:#order#:#Order###26 08 2024 new variable assessment#:#ordering_answer_sequence_info#:#Ustalona tutaj kolejność odpowiedzi używana jest jako prawidłowa kolejność rozwiązań. assessment#:#ordertext#:#Tekst, który trzeba umieścić assessment#:#ordertext_info#:#Proszę podaj tekst w takiej kolejności, w jakiej ma zostać umieszczony w układzie poziomym. Poszczególne elementy składowe oddzielone są znakiem pustym. Jeśli konieczny jest inny sposób oddzielenia, należy zamiast znaku pustego użyć separatora %s. -assessment#:#out_of_range#:#Poza obszarem assessment#:#output#:#Wyjście assessment#:#output_mode#:#Tryb wyjścia assessment#:#parseQuestion#:#Analizuj pytanie @@ -1053,7 +1039,6 @@ assessment#:#qpl_bulk_save_add#:#Add###28 10 2024 new variable assessment#:#qpl_bulk_save_overwrite#:#Overwrite###28 10 2024 new variable assessment#:#qpl_bulkedit_success#:#Modifications saved.###28 10 2024 new variable assessment#:#qpl_cancel_skill_assigns_update#:#Przerwij -assessment#:#qpl_confirm_delete_questions#:#Czy jesteś pewien, że chcesz usunąć następujące pytania? Jeśli usuniesz zaznaczone pytania, to rezultaty wszystkich testów zawierających zaznaczone pytania będą również usunięte. assessment#:#qpl_copy_insert_clipboard#:#Wybrane pytania zostały skopiowane do schowka assessment#:#qpl_copy_select_none#:#Wybierz co najmniej jedno pytanie do skopiowania do schowka assessment#:#qpl_delete_rbac_error#:#Nie masz uprawnień do usunięcia tego pytania! @@ -1106,7 +1091,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#Kompetencje assessment#:#qpl_qst_skl_usg_sklpnt_col#:#Suma wszystkich punktów kompetencyjnych dla każdej kompetencji assessment#:#qpl_question_is_in_use#:#Pytanie, które modyfikujesz, jest w %s testach. Jeśli zmieniasz to pytanie, to NIE będziesz mógł zmienić pytań w testach, ponieważ system tworzy kopie pytań po wstawieniu ich do testu! assessment#:#qpl_questions_deleted#:#Usunięte pytania. -assessment#:#qpl_reset_preview#:#Przywróć podgląd assessment#:#qpl_save_skill_assigns_update#:#Zapisz przyporządkowane kompetencje assessment#:#qpl_settings_availability#:#Availability###26 08 2024 new variable assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#Istniejące klasyfikacje można użyć do filtrowania pytań. @@ -1136,14 +1120,6 @@ assessment#:#qst_essay_chars_remaining#:#Remaining characters:###07 02 2020 new assessment#:#qst_essay_wordcounter_enabled#:#Count Words###07 02 2020 new variable assessment#:#qst_essay_wordcounter_enabled_info#:#The entered words are counted. The number of written words is shown to the participants below the text input field.###07 02 2020 new variable assessment#:#qst_essay_written_words#:#Number of entered words:###07 02 2020 new variable -assessment#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable -assessment#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable -assessment#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable -assessment#:#qst_lifecycle_final#:#Final###07 02 2020 new variable -assessment#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable -assessment#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable -assessment#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable -assessment#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable assessment#:#qst_nested_nested_answers_off#:#No indents, just order###29 07 2022 new variable assessment#:#qst_nested_nested_answers_on#:#Use indents in anwers###29 07 2022 new variable assessment#:#qst_nr_of_tries#:#Ilość prób @@ -1167,17 +1143,14 @@ assessment#:#question_title#:#Tytuł pytania assessment#:#question_type#:#Typ pytania assessment#:#questionpool_not_entered#:#Podaj nazwę puli pytań! assessment#:#questionpool_not_selected#:#Please select a question pool!###07 02 2020 new variable -assessment#:#questions#:#Questions###26 08 2024 new variable assessment#:#questions_from#:#pytania z assessment#:#questions_per_page_view#:#Podgląd strony assessment#:#random_accept_sample#:#Akceptuj próbkę assessment#:#random_another_sample#:#Pobierz inną próbkę assessment#:#random_selection#:#Wybór przypadkowy assessment#:#range#:#Zakres -assessment#:#range_lower_limit#:#Limit dolny assessment#:#range_max#:#Zakres (maksimum) assessment#:#range_min#:#Zakres (minimum) -assessment#:#range_upper_limit#:#Limit górny assessment#:#rated_sign#:#Znak liczby assessment#:#rated_unit#:#Jednostka assessment#:#rated_value#:#Wartość @@ -1253,7 +1226,6 @@ assessment#:#search_roles#:#Szukaj ról assessment#:#search_term#:#Szukaj frazy assessment#:#select_at_least_one_feedback_type_and_trigger#:#Please Select at least one type of feedback and a trigger.###26 08 2024 new variable assessment#:#select_at_least_one_lock_answer_type#:#Please select at least one type of answer lock.###29 10 2025 new variable -assessment#:#select_gap#:#Wybierz odstęp assessment#:#select_max_one_item#:#Wybierz tylko jedną pozycję. assessment#:#select_one_user#:#Wybierz co najmniej jednego użytkownika. assessment#:#select_question#:#Select a Question###28 10 2024 new variable @@ -1280,7 +1252,6 @@ assessment#:#show_old_introduction#:#Show old introduction###26 08 2024 new vari assessment#:#show_pass_overview#:#Pokaż podgląd zaznaczonych wykonań assessment#:#show_results#:#Show Results###28 10 2024 new variable assessment#:#show_user_answers#:#Pokaż zaznaczone przez użytkownika odpowiedzi -assessment#:#shuffle_answers#:#Mieszaj odpowiedzi assessment#:#skip_question#:#Nie udzielaj odpowiedzi i przejdź do następnego pytania assessment#:#solution#:#Solution###28 10 2024 new variable assessment#:#solutionText#:#Tekst @@ -13971,6 +13942,35 @@ qpl#:#qpl_page_type_qfbg#:#General Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qfbs#:#Special Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qht#:#Hint###29 07 2022 new variable qpl#:#qpl_page_type_qpl#:#Question Page###29 07 2022 new variable +qsts#:#answer_options#:#Opcje z odpowiedziami +qsts#:#cloze_text#:#Uzupełnij tekst +qsts#:#cloze_textgapcase_insensitive#:#Nie uwzględniaj wielkości liter +qsts#:#cloze_textgapcase_sensitive#:#Uwzględniaj wielkość liter +qsts#:#cloze_textgaplevenshtein_of#:#Odległość Levenshtein %s +qsts#:#confirm_delete_questions#:#Czy jesteś pewien, że chcesz usunąć następujące pytania? Jeśli usuniesz zaznaczone pytania, to rezultaty wszystkich testów zawierających zaznaczone pytania będą również usunięte. +qsts#:#create_question#:#Utwórz pytanie +qsts#:#gap#:#Luka +qsts#:#gaps#:#Gaps###29 10 2025 new variable +qsts#:#insert_gap#:#Wypełnij lukę +qsts#:#min_auto_complete#:#Automatyczne uzupełnianie +qsts#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable +qsts#:#out_of_range#:#Poza obszarem +qsts#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable +qsts#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable +qsts#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable +qsts#:#qst_lifecycle_final#:#Final###07 02 2020 new variable +qsts#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable +qsts#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable +qsts#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable +qsts#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable +qsts#:#questionlist#:#Questionlist###26 08 2024 new variable +qsts#:#questions#:#Questions###26 08 2024 new variable +qsts#:#range_lower_limit#:#Limit dolny +qsts#:#range_upper_limit#:#Limit górny +qsts#:#reset_preview#:#Przywróć podgląd +qsts#:#select_gap#:#Wybierz odstęp +qsts#:#shuffle_answers#:#Mieszaj odpowiedzi +qsts#:#suggested_learning_content#:#Pokaż sugerowane rozwiązanie rating#:#rat_not_rated_yet#:#Jeszcze nie oceniono rating#:#rat_nr_ratings#:#%s oceny rating#:#rat_one_rating#:#Ocena @@ -16620,7 +16620,6 @@ survey#:#questionblock#:#Blok pytań survey#:#questionblock_inserted#:#Wstawiono blok pytań survey#:#questionblocks#:#Bloki pytań survey#:#questionblocks_inserted#:#Wstawiono bloki pytań -survey#:#questions#:#Pytania survey#:#questions_inserted#:#Pytania wstawione! survey#:#questions_removed#:#Pytania i/lub bloki pytań usunięte! survey#:#questiontype#:#Typ pytania @@ -16921,6 +16920,7 @@ survey#:#svy_please_select_unused_codes#:#Please select at least one unused code survey#:#svy_print_hide_labels#:#Ukryj etykiety survey#:#svy_print_show_labels#:#Pokaż etykiety survey#:#svy_privacy_info#:#Privacy###29 07 2022 new variable +survey#:#svy_questions#:#Pytania survey#:#svy_rater#:#Rater###29 07 2022 new variable survey#:#svy_rater_see_app_info#:#The names of appraisees will be presented to raters to enable them evaluating the questions.###29 07 2022 new variable survey#:#svy_reminder_mail_template#:#Szablon wiadomości mailowej diff --git a/lang/ilias_pt.lang b/lang/ilias_pt.lang index 275e0811abf8..4d30fa0888d9 100644 --- a/lang/ilias_pt.lang +++ b/lang/ilias_pt.lang @@ -422,7 +422,6 @@ adve#:#adve_use_tiny_mce#:#Ativar TinyMCE para a edição WYSIWYG assessment#:#activate_logging#:#Novas notificações de correio assessment#:#activate_manual_scoring#:#Enable Manual Scoring###28 10 2024 new variable assessment#:#activate_manual_scoring_desc#:#Enables manual Scoring for all question types.###28 10 2024 new variable -assessment#:#addSuggestedSolution#:#Adicionar conteúdo para recapitulação assessment#:#add_answers#:#Adicionar respostas assessment#:#add_circle#:#Adicionar área de círculo assessment#:#add_gap#:#Adicionar texto do intervalo @@ -447,7 +446,6 @@ assessment#:#answer_is_not_correct_but_positive#:#Tem pontos para a sua soluçã assessment#:#answer_is_right#:#A sua solução é correta assessment#:#answer_is_wrong#:#A sua solução está errada assessment#:#answer_of#:#Answer of###07 02 2020 new variable -assessment#:#answer_options#:#Opções de resposta: assessment#:#answer_question#:#Responder a pergunta assessment#:#answer_text#:#Texto de resposta assessment#:#answer_types#:#Editor para respostas @@ -501,7 +499,6 @@ assessment#:#ass_completion_by_submission#:#Completado por submissão assessment#:#ass_completion_by_submission_info#:#Se estiver ativado, a submissão de pelo menos um ficheiro leva à conclusão desta pergunta, concedendo a pontuação máxima para esta pergunta. A pontuação pode ser alterada mais tarde manualmente. Mudar esta definição não afeta as soluções já submetidas. assessment#:#ass_create_export_file_with_results#:#Criar ficheiro de exportação de texto (incl. resultados do participante) assessment#:#ass_create_export_test_archive#:#Criar ficheiro de arquivo de teste -assessment#:#ass_create_question#:#Criar pergunta assessment#:#ass_imap_hint#:#Sugestão para ser apresentado como dica de contexto. assessment#:#ass_imap_map_file_not_readable#:#Não foi possível ler o mapa de imagem carregada. assessment#:#ass_imap_no_map_found#:#Não foi possível encontrar qualquer forma na mapa de imagem carregada. @@ -571,10 +568,6 @@ assessment#:#cloze_answer_text_info#:#Spaces preceding or following the answer t assessment#:#cloze_fixed_textlength#:#Comprimento do campo de texto assessment#:#cloze_fixed_textlength_description#:#Se introduzir um novo valor, todos os campos de espaços do texto que não impõem uma limitação máxima própria de caracteres, assim como todos os campos de espaços numéricos, serão criados com um comprimento fixo deste valor, não sendo assim possível introduzir mais do que os caracteres permitidos. Repare que para os espaços numéricos, o separador decimal conta como um caracter regular. assessment#:#cloze_gap_size_info#:#Se introduzir um valor superior a 0, este campo de texto de espaços será criado com o comprimento fixo deste valor. Se não introduzir um valor, o campo de texto de espaços será criado com o valor do comprimento global fixo. -assessment#:#cloze_text#:#Fechar texto -assessment#:#cloze_textgap_case_insensitive#:#Caso insensível -assessment#:#cloze_textgap_case_sensitive#:#Caso sensível -assessment#:#cloze_textgap_levenshtein_of#:#Levenshtein distância de %s assessment#:#code#:#Código assessment#:#codebase#:#Base do código assessment#:#concatenation#:#Concatenação @@ -757,9 +750,7 @@ assessment#:#fq_formula_desc#:#Pode introduzir variáveis predefinidas ($v1 até assessment#:#fq_no_restriction_info#:#São aceites entradas decimais e também frações. assessment#:#fq_precision_info#:#Introduza o número de casas decimais pretendido. assessment#:#fq_question_desc#:#Pode definir variáveis ao inserir $v1, $v2 ... $vn, resultados ao inserir $r1, $r2 .... $rn na posição pretendida no texto de perguntas. Clique no botão ‘Analisar pergunta’ para criar formas de edição para variáveis e resultados. -assessment#:#gap#:#Intervalo assessment#:#gap_combination#:#Combinação de intervalo -assessment#:#gaps#:#Gaps###29 10 2025 new variable assessment#:#glossary_term#:#Termo do glossário assessment#:#goto_first_question#:#Show First Question###29 10 2025 new variable assessment#:#grading_mark_msg#:#A sua marca é: "[mark]" @@ -777,7 +768,6 @@ assessment#:#info_answer_type_change#:#A pergunta já contém imagens. Não pode assessment#:#info_text_upload#:#Escolha um ficheiro de texto de resposta (UTF-8) para carregar. assessment#:#insert_after#:#Inserir depois assessment#:#insert_before#:#Inserir antes -assessment#:#insert_gap#:#Inserir intervalo assessment#:#interaction_type#:#Interaction Type###26 08 2024 new variable assessment#:#internal_links#:#Links internos assessment#:#intprecision#:#Divisível por @@ -889,7 +879,6 @@ assessment#:#logs_wrong_test_password_provided#:#O participante introduziu uma p assessment#:#longmenu#:#Menu comprido assessment#:#longmenu_answeroptions_differ#:#This question does not work correctly, as there are not the same amount of gaps in the text as in the correction options.###26 08 2024 new variable assessment#:#longmenu_text#:#Texto de menu comprido -assessment#:#mainbar_button_label_questionlist#:#Questionlist###26 08 2024 new variable assessment#:#maintenance#:#Manutenção assessment#:#manscoring#:#Pontuação manual assessment#:#manscoring_done#:#Participantes pontuados @@ -916,7 +905,6 @@ assessment#:#maximum_nr_of_tries_reached#:#Chegou ao número máximo de tentativ assessment#:#maximum_points#:#Pontos máximos disponíveis assessment#:#maxsize#:#Tamanho máximo para carregar ficheiro: assessment#:#maxsize_info#:#Introduzir o tamanho máximo em bytes que deve ser permitido para carregar ficheiros. Se deixar este campo vazio, será em vez disso escolhido o tamanho máximo desta instalação. -assessment#:#min_auto_complete#:#Autocompletado assessment#:#min_ip_label#:#Lowest IP With Access###26 08 2024 new variable assessment#:#min_percentage_ne_0#:#Tem e definir uma percentagem mínima de 0 por cento! O esquema de anotações não foi guardado. assessment#:#misc#:#Opções combinadas @@ -925,7 +913,6 @@ assessment#:#mode_onebyone#:#One by One###29 10 2025 new variable assessment#:#mode_question#:#Question oriented###29 10 2025 new variable assessment#:#mode_user#:#Participant oriented###29 10 2025 new variable assessment#:#msg_circle_added#:#Círculo adicionado -assessment#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable assessment#:#msg_number_of_terms_too_low#:#O número de termos tem de ser superior ou igual ao número de definições. assessment#:#msg_poly_added#:#Polígono adicionado assessment#:#msg_questions_moved#:#Pergunta(s) movida(s) @@ -984,7 +971,6 @@ assessment#:#order#:#Order###26 08 2024 new variable assessment#:#ordering_answer_sequence_info#:#A sequência de respostas que define aqui será assumida como a sequência de solução correta. assessment#:#ordertext#:#Ordenação texto assessment#:#ordertext_info#:#Introduza o texto que deve ser ordenado na horizontal. O texto de ordenação será separado pelos sinais de espaço em branco no texto. Se precisar de uma separação diferente, pode usar o separador %s para separar as suas unidades de texto. -assessment#:#out_of_range#:#Fora de alcance assessment#:#output#:#Saída assessment#:#output_mode#:#Modo saída assessment#:#parseQuestion#:#Analisar pergunta @@ -1053,7 +1039,6 @@ assessment#:#qpl_bulk_save_add#:#Add###28 10 2024 new variable assessment#:#qpl_bulk_save_overwrite#:#Overwrite###28 10 2024 new variable assessment#:#qpl_bulkedit_success#:#Modifications saved.###28 10 2024 new variable assessment#:#qpl_cancel_skill_assigns_update#:#Cancelar -assessment#:#qpl_confirm_delete_questions#:#Tem a certeza que quer remover as seguintes perguntas? assessment#:#qpl_copy_insert_clipboard#:#A(s) pergunta(s) selecionada(s) são copiada(s) para a área de transferência assessment#:#qpl_copy_select_none#:#Selecione pelo menos uma pergunta para copiar para a área de transferência! assessment#:#qpl_delete_rbac_error#:#Não tem permissão para remover esta pergunta! @@ -1106,7 +1091,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#Competência assessment#:#qpl_qst_skl_usg_sklpnt_col#:#Total dos pontos de competência por competência assessment#:#qpl_question_is_in_use#:#A pergunta que está prestes a editar existe em %s teste(s). Se mudar esta pergunta, NÃO muda a(s) pergunta(s) no teste(s), porque o sistema cria uma cópia de uma pergunta quando é inserida num teste! assessment#:#qpl_questions_deleted#:#Pergunta(s) removida(s). -assessment#:#qpl_reset_preview#:#Repor pré-visualização assessment#:#qpl_save_skill_assigns_update#:#Guardar atribuições de competência assessment#:#qpl_settings_availability#:#Availability###26 08 2024 new variable assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#São propostas taxonomias existentes neste banco para filtrar as perguntas. @@ -1136,14 +1120,6 @@ assessment#:#qst_essay_chars_remaining#:#Remaining characters:###07 02 2020 new assessment#:#qst_essay_wordcounter_enabled#:#Count Words###07 02 2020 new variable assessment#:#qst_essay_wordcounter_enabled_info#:#The entered words are counted. The number of written words is shown to the participants below the text input field.###07 02 2020 new variable assessment#:#qst_essay_written_words#:#Number of entered words:###07 02 2020 new variable -assessment#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable -assessment#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable -assessment#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable -assessment#:#qst_lifecycle_final#:#Final###07 02 2020 new variable -assessment#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable -assessment#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable -assessment#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable -assessment#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable assessment#:#qst_nested_nested_answers_off#:#No indents, just order###29 07 2022 new variable assessment#:#qst_nested_nested_answers_on#:#Use indents in anwers###29 07 2022 new variable assessment#:#qst_nr_of_tries#:#Número de tentativas @@ -1167,17 +1143,14 @@ assessment#:#question_title#:#Título da pergunta assessment#:#question_type#:#Tipo de pergunta assessment#:#questionpool_not_entered#:#Introduza um nome para um banco de perguntas! assessment#:#questionpool_not_selected#:#Please select a question pool!###07 02 2020 new variable -assessment#:#questions#:#Questions###26 08 2024 new variable assessment#:#questions_from#:#perguntas de assessment#:#questions_per_page_view#:#Vista de página assessment#:#random_accept_sample#:#Aceitar amostra assessment#:#random_another_sample#:#Obter outra amostra assessment#:#random_selection#:#Seleção aleatória assessment#:#range#:#Faixa -assessment#:#range_lower_limit#:#Limite inferior assessment#:#range_max#:#Faixa (máxima) assessment#:#range_min#:#Faixa (mínima) -assessment#:#range_upper_limit#:#Limite superior assessment#:#rated_sign#:#Sinal assessment#:#rated_unit#:#Unidade assessment#:#rated_value#:#Valor @@ -1253,7 +1226,6 @@ assessment#:#search_roles#:#Pesquisar funções assessment#:#search_term#:#Pesquisar termo assessment#:#select_at_least_one_feedback_type_and_trigger#:#Please Select at least one type of feedback and a trigger.###26 08 2024 new variable assessment#:#select_at_least_one_lock_answer_type#:#Please select at least one type of answer lock.###29 10 2025 new variable -assessment#:#select_gap#:#Selecionar espaço assessment#:#select_max_one_item#:#Selecione apenas um item assessment#:#select_one_user#:#Selecione pelo menos um utilizador. assessment#:#select_question#:#Select a Question###28 10 2024 new variable @@ -1280,7 +1252,6 @@ assessment#:#show_old_introduction#:#Show old introduction###26 08 2024 new vari assessment#:#show_pass_overview#:#Mostrar vista geral de tentativas marcadas assessment#:#show_results#:#Show Results###28 10 2024 new variable assessment#:#show_user_answers#:#Mostrar respostas marcadas do utilizador -assessment#:#shuffle_answers#:#Misturar respostas assessment#:#skip_question#:#Não responder e próximo assessment#:#solution#:#Solution###28 10 2024 new variable assessment#:#solutionText#:#Texto @@ -13971,6 +13942,35 @@ qpl#:#qpl_page_type_qfbg#:#General Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qfbs#:#Special Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qht#:#Hint###29 07 2022 new variable qpl#:#qpl_page_type_qpl#:#Question Page###29 07 2022 new variable +qsts#:#answer_options#:#Opções de resposta +qsts#:#cloze_text#:#Fechar texto +qsts#:#cloze_textgapcase_insensitive#:#Caso insensível +qsts#:#cloze_textgapcase_sensitive#:#Caso sensível +qsts#:#cloze_textgaplevenshtein_of#:#Levenshtein distância de %s +qsts#:#confirm_delete_questions#:#Tem a certeza que quer remover as seguintes perguntas? +qsts#:#create_question#:#Criar pergunta +qsts#:#gap#:#Intervalo +qsts#:#gaps#:#Gaps###29 10 2025 new variable +qsts#:#insert_gap#:#Inserir intervalo +qsts#:#min_auto_complete#:#Autocompletado +qsts#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable +qsts#:#out_of_range#:#Fora de alcance +qsts#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable +qsts#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable +qsts#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable +qsts#:#qst_lifecycle_final#:#Final###07 02 2020 new variable +qsts#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable +qsts#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable +qsts#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable +qsts#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable +qsts#:#questionlist#:#Questionlist###26 08 2024 new variable +qsts#:#questions#:#Questions###26 08 2024 new variable +qsts#:#range_lower_limit#:#Limite inferior +qsts#:#range_upper_limit#:#Limite superior +qsts#:#reset_preview#:#Repor pré-visualização +qsts#:#select_gap#:#Selecionar espaço +qsts#:#shuffle_answers#:#Misturar respostas +qsts#:#suggested_learning_content#:#Adicionar conteúdo para recapitulação rating#:#rat_not_rated_yet#:#Não classificado ainda rating#:#rat_nr_ratings#:#%s Classificações rating#:#rat_one_rating#:#Uma classificação @@ -16620,7 +16620,6 @@ survey#:#questionblock#:#Bloco de perguntas survey#:#questionblock_inserted#:#Bloco de perguntas inserido survey#:#questionblocks#:#Questionblocks survey#:#questionblocks_inserted#:#Blocos de perguntas inseridos -survey#:#questions#:#Perguntas survey#:#questions_inserted#:#Pergunta(s) inserida(s)! survey#:#questions_removed#:#Pergunta(s) e/ou bloco(s) de perguntas removidos! survey#:#questiontype#:#Tipo de pergunta @@ -16921,6 +16920,7 @@ survey#:#svy_please_select_unused_codes#:#Please select at least one unused code survey#:#svy_print_hide_labels#:#Ocultar etiquetas survey#:#svy_print_show_labels#:#Mostrar etiquetas survey#:#svy_privacy_info#:#Privacy###29 07 2022 new variable +survey#:#svy_questions#:#Perguntas survey#:#svy_rater#:#Rater###29 07 2022 new variable survey#:#svy_rater_see_app_info#:#The names of appraisees will be presented to raters to enable them evaluating the questions.###29 07 2022 new variable survey#:#svy_reminder_mail_template#:#Modelo mail diff --git a/lang/ilias_ro.lang b/lang/ilias_ro.lang index 02aca9fa8d8b..5d9da532a00c 100644 --- a/lang/ilias_ro.lang +++ b/lang/ilias_ro.lang @@ -422,7 +422,6 @@ adve#:#adve_use_tiny_mce#:#Enable TinyMCE for WYSIWYG Editing###01 10 2011 new v assessment#:#activate_logging#:#Activati log-ul de Testare si Evaluare assessment#:#activate_manual_scoring#:#Enable Manual Scoring###28 10 2024 new variable assessment#:#activate_manual_scoring_desc#:#Enables manual Scoring for all question types.###28 10 2024 new variable -assessment#:#addSuggestedSolution#:#Add suggested solution###24 02 2009 new variable assessment#:#add_answers#:#Add Antworten ###23 12 2015 new variable assessment#:#add_circle#:#Add circle area###24 07 2009 new variable assessment#:#add_gap#:#Adaugati text cu spatii goale @@ -447,7 +446,6 @@ assessment#:#answer_is_not_correct_but_positive#:#You've got points for your sol assessment#:#answer_is_right#:#Solutia dumneavoastra este corecta###28 Jul 2006 content changed assessment#:#answer_is_wrong#:# Solutia dumneavoastra este gresita assessment#:#answer_of#:#Answer of###07 02 2020 new variable -assessment#:#answer_options#:#Answer Options: ###23 12 2015 new variable assessment#:#answer_question#:#Answer Question###30 08 2015 new variable assessment#:#answer_text#:#Raspuns la text assessment#:#answer_types#:#Answer Types###24 07 2009 new variable @@ -501,7 +499,6 @@ assessment#:#ass_completion_by_submission#:#Completed by Submission###12 06 2011 assessment#:#ass_completion_by_submission_info#:#If enabled, the submission of at least one file causes the completion of this question by granting the maximum score for this question. The score could be manually changed later. Switching this setting does not effect already submitted solutions.###12 06 2011 new variable assessment#:#ass_create_export_file_with_results#:#Create Test Export File (incl. Participant Results)###25 10 2016 new variable assessment#:#ass_create_export_test_archive#:#Create Test Archive File###24 10 2013 new variable -assessment#:#ass_create_question#:#Create Question###24 10 2013 new variable assessment#:#ass_imap_hint#:#Hint to be shown as Tooltip###25 10 2016 new variable assessment#:#ass_imap_map_file_not_readable#:#The uploaded image map could not be read.###25 10 2016 new variable assessment#:#ass_imap_no_map_found#:#Could not find any form in the uploaded image map.###25 10 2016 new variable @@ -571,10 +568,6 @@ assessment#:#cloze_answer_text_info#:#Spaces preceding or following the answer t assessment#:#cloze_fixed_textlength#:#Text Field Length###02 04 2007 new variable assessment#:#cloze_fixed_textlength_description#:#If you enter a value greater than 0, all text and numeric gap text fields will be created with the fixed length of this value.###02 04 2007 new variable assessment#:#cloze_gap_size_info#:#If you enter a value greater than 0, this gap text field will be created with the fixed length of this value. If you do not enter a value the gap text fiel will be created with the value of the global fixed length.###26 09 2014 new variable -assessment#:#cloze_text#:#Text cu spatii se completat -assessment#:#cloze_textgap_case_insensitive#:#Case insensitive###03 Nov 2005 new variable -assessment#:#cloze_textgap_case_sensitive#:#Case sensitive###03 Nov 2005 new variable -assessment#:#cloze_textgap_levenshtein_of#:#Levenshtein distance of %s###03 Nov 2005 new variable assessment#:#code#:#Cod assessment#:#codebase#:#Codebase###10 Jul 2006 new variable assessment#:#concatenation#:#Concatenare @@ -757,9 +750,7 @@ assessment#:#fq_formula_desc#:#You may enter predefined variables ($v1 to $vn), assessment#:#fq_no_restriction_info#:#Both decimals and fractions are accepted as input.###26 03 2014 new variable assessment#:#fq_precision_info#:#Enter the number of desired decimal places.###26 03 2014 new variable assessment#:#fq_question_desc#:#You can define variables by inserting $v1, $v2 ... $vn, results by inserting $r1, $r2 .... $rn at the desired position in the question text. Click on the button "Parse Question" to create editing forms for variables and results.###06 11 2013 new variable -assessment#:#gap#:#Spatiu assessment#:#gap_combination#:#Gap Combination###26 09 2014 new variable -assessment#:#gaps#:#Gaps###29 10 2025 new variable assessment#:#glossary_term#:#Termen de glosar assessment#:#goto_first_question#:#Show First Question###29 10 2025 new variable assessment#:#grading_mark_msg#:#Your resulting mark is: "[mark]"###26 09 2014 new variable @@ -777,7 +768,6 @@ assessment#:#info_answer_type_change#:#The question already contains images. You assessment#:#info_text_upload#:#Choose an answer file to upload###30 08 2015 new variable assessment#:#insert_after#:#Inserati dupa assessment#:#insert_before#:#Inserati inainte -assessment#:#insert_gap#:#Insert Gap###24 10 2013 new variable assessment#:#interaction_type#:#Interaction Type###26 08 2024 new variable assessment#:#internal_links#:#Legaturi Interne assessment#:#intprecision#:#Divisible By###24 10 2013 new variable @@ -889,7 +879,6 @@ assessment#:#logs_wrong_test_password_provided#:#Participant entered wrong test assessment#:#longmenu#:#Longmenu###10 11 2018 new variable assessment#:#longmenu_answeroptions_differ#:#This question does not work correctly, as there are not the same amount of gaps in the text as in the correction options.###26 08 2024 new variable assessment#:#longmenu_text#:#Long Menu Text###30 08 2015 new variable -assessment#:#mainbar_button_label_questionlist#:#Questionlist###26 08 2024 new variable assessment#:#maintenance#:#Intretinere assessment#:#manscoring#:#Manual Scoring###12 11 2006 new variable assessment#:#manscoring_done#:#Scored Participants###24 02 2009 new variable @@ -916,7 +905,6 @@ assessment#:#maximum_nr_of_tries_reached#:#Ati ajuns la numarul maxim de incerca assessment#:#maximum_points#:#Maxium available points###10 Jul 2006 new variable assessment#:#maxsize#:#Maximum file upload size ###24 02 2009 new variable assessment#:#maxsize_info#:#Enter the maximum size in bytes that should be allowed for file uploads. If you leave this field empty, the maximum size of this installation will be chosen instead.###24 02 2009 new variable -assessment#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable assessment#:#min_ip_label#:#Lowest IP With Access###26 08 2024 new variable assessment#:#min_percentage_ne_0#:#Trebuie sa definiti un procentaj minim de 0 la suta! Schema de notare nu a fost salvata.###10 Jul 2006 content changed assessment#:#misc#:#Misc Options###24 10 2013 new variable @@ -925,7 +913,6 @@ assessment#:#mode_onebyone#:#One by One###29 10 2025 new variable assessment#:#mode_question#:#Question oriented###29 10 2025 new variable assessment#:#mode_user#:#Participant oriented###29 10 2025 new variable assessment#:#msg_circle_added#:#Circle added###24 07 2009 new variable -assessment#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable assessment#:#msg_number_of_terms_too_low#:#The number of terms must be greater or equal to the number of definitions.###12 08 2009 new variable assessment#:#msg_poly_added#:#Polygon added###24 07 2009 new variable assessment#:#msg_questions_moved#:#Question(s) moved###06 08 2009 new variable @@ -984,7 +971,6 @@ assessment#:#order#:#Order###26 08 2024 new variable assessment#:#ordering_answer_sequence_info#:#The answer sequence you define here will be taken as the correct solution sequence.###10 09 2010 new variable assessment#:#ordertext#:#Ordering Text###24 02 2009 new variable assessment#:#ordertext_info#:#Please enter the text that should be ordered horizontally. The ordering text will be separated by the whitespace signs in the text. If you need a different separation, you may use the separator %s to separate your text units.###24 02 2009 new variable -assessment#:#out_of_range#:#Out of range###27 01 2015 new variable assessment#:#output#:#Output###25 10 2006 new variable assessment#:#output_mode#:#Output mode###10 Jul 2006 new variable assessment#:#parseQuestion#:#Parse Question###24 10 2013 new variable @@ -1053,7 +1039,6 @@ assessment#:#qpl_bulk_save_add#:#Add###28 10 2024 new variable assessment#:#qpl_bulk_save_overwrite#:#Overwrite###28 10 2024 new variable assessment#:#qpl_bulkedit_success#:#Modifications saved.###28 10 2024 new variable assessment#:#qpl_cancel_skill_assigns_update#:#Cancel###30 08 2015 new variable -assessment#:#qpl_confirm_delete_questions#:#Sunteti sigur/a ca doriti sa stergeti urmatoarele intrebari)? Daca stergeti intrebarile blocate rezultatele tuturor testelor continand intrebari blocate vor fi sterse de asemenea.###10 Jul 2006 content changed assessment#:#qpl_copy_insert_clipboard#:#The selected question(s) are copied to the clipboard###03 Nov 2005 new variable assessment#:#qpl_copy_select_none#:#Please check at least one question to copy it to the clipboard###03 Nov 2005 new variable assessment#:#qpl_delete_rbac_error#:#Nu aveti dreptul sa stergeti aceasta intrebare! @@ -1106,7 +1091,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#Competence###30 08 2015 new variable assessment#:#qpl_qst_skl_usg_sklpnt_col#:#Total Sum of Competence-Points per Competence###30 08 2015 new variable assessment#:#qpl_question_is_in_use#:#Intrebarea pe care doriti sa o editati axista in %s test(e). Daca schimbati aceasta intrebare, NU veti putea schimba intrebarea din alte teste pentru ca sistemul creaza o copie a intrebarii atunci cand este introdusa in test! assessment#:#qpl_questions_deleted#:#Intrebare stearsa. -assessment#:#qpl_reset_preview#:#Reset Preview###26 09 2014 new variable assessment#:#qpl_save_skill_assigns_update#:#Save Competence Assignments###30 08 2015 new variable assessment#:#qpl_settings_availability#:#Availability###26 08 2024 new variable assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#When enabled, possibly created taxonomies are shown for filtering.###24 10 2013 new variable @@ -1136,14 +1120,6 @@ assessment#:#qst_essay_chars_remaining#:#Remaining characters:###07 02 2020 new assessment#:#qst_essay_wordcounter_enabled#:#Count Words###07 02 2020 new variable assessment#:#qst_essay_wordcounter_enabled_info#:#The entered words are counted. The number of written words is shown to the participants below the text input field.###07 02 2020 new variable assessment#:#qst_essay_written_words#:#Number of entered words:###07 02 2020 new variable -assessment#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable -assessment#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable -assessment#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable -assessment#:#qst_lifecycle_final#:#Final###07 02 2020 new variable -assessment#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable -assessment#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable -assessment#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable -assessment#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable assessment#:#qst_nested_nested_answers_off#:#No indents, just order###29 07 2022 new variable assessment#:#qst_nested_nested_answers_on#:#Use indents in anwers###29 07 2022 new variable assessment#:#qst_nr_of_tries#:#Number of tries###15 05 2009 new variable @@ -1167,17 +1143,14 @@ assessment#:#question_title#:#Titlul intrebarii assessment#:#question_type#:#Tipul intrebarii assessment#:#questionpool_not_entered#:#Va rugam introduceti un nume pentru baza de intrebari! assessment#:#questionpool_not_selected#:#Please select a question pool!###07 02 2020 new variable -assessment#:#questions#:#Questions###26 08 2024 new variable assessment#:#questions_from#:#Forma intrebarii assessment#:#questions_per_page_view#:#Page View###12 06 2011 new variable assessment#:#random_accept_sample#:#Acceptati mostra assessment#:#random_another_sample#:#Alta mostra assessment#:#random_selection#:#Selectie la intamplare assessment#:#range#:#Range###22 Feb 2006 new variable -assessment#:#range_lower_limit#:#Lower limit###22 Feb 2006 new variable assessment#:#range_max#:#Range (Maximum)###24 10 2013 new variable assessment#:#range_min#:#Range (Minimum)###24 10 2013 new variable -assessment#:#range_upper_limit#:#Upper limit###22 Feb 2006 new variable assessment#:#rated_sign#:#Sign###24 10 2013 new variable assessment#:#rated_unit#:#Unit###24 10 2013 new variable assessment#:#rated_value#:#Value###24 10 2013 new variable @@ -1253,7 +1226,6 @@ assessment#:#search_roles#:#Cauta roluri assessment#:#search_term#:#Cauta termeni assessment#:#select_at_least_one_feedback_type_and_trigger#:#Please Select at least one type of feedback and a trigger.###26 08 2024 new variable assessment#:#select_at_least_one_lock_answer_type#:#Please select at least one type of answer lock.###29 10 2025 new variable -assessment#:#select_gap#:#Selectati un spatiu assessment#:#select_max_one_item#:#Selectati un singur obiect assessment#:#select_one_user#:#Please select at least one user###23 Dec 2005 new variable assessment#:#select_question#:#Select a Question###28 10 2024 new variable @@ -1280,7 +1252,6 @@ assessment#:#show_old_introduction#:#Show old introduction###26 08 2024 new vari assessment#:#show_pass_overview#:#Show Marked Pass Overview###31 05 2007 new variable assessment#:#show_results#:#Show Results###28 10 2024 new variable assessment#:#show_user_answers#:#Show User's Marked Anwers###31 05 2007 new variable -assessment#:#shuffle_answers#:#Amestecati raspunsurile assessment#:#skip_question#:#Do not Answer and Next###08 10 2015 new variable assessment#:#solution#:#Solution###28 10 2024 new variable assessment#:#solutionText#:#Text###24 02 2009 new variable @@ -13971,6 +13942,35 @@ qpl#:#qpl_page_type_qfbg#:#General Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qfbs#:#Special Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qht#:#Hint###29 07 2022 new variable qpl#:#qpl_page_type_qpl#:#Question Page###29 07 2022 new variable +qsts#:#answer_options#:#Answer Options ###23 12 2015 new variable +qsts#:#cloze_text#:#Text cu spatii se completat +qsts#:#cloze_textgapcase_insensitive#:#Case insensitive###03 Nov 2005 new variable +qsts#:#cloze_textgapcase_sensitive#:#Case sensitive###03 Nov 2005 new variable +qsts#:#cloze_textgaplevenshtein_of#:#Levenshtein distance of %s###03 Nov 2005 new variable +qsts#:#confirm_delete_questions#:#Sunteti sigur/a ca doriti sa stergeti urmatoarele intrebari)? Daca stergeti intrebarile blocate rezultatele tuturor testelor continand intrebari blocate vor fi sterse de asemenea.###10 Jul 2006 content changed +qsts#:#create_question#:#Create Question###24 10 2013 new variable +qsts#:#gap#:#Spatiu +qsts#:#gaps#:#Gaps###29 10 2025 new variable +qsts#:#insert_gap#:#Insert Gap###24 10 2013 new variable +qsts#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable +qsts#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable +qsts#:#out_of_range#:#Out of range###27 01 2015 new variable +qsts#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable +qsts#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable +qsts#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable +qsts#:#qst_lifecycle_final#:#Final###07 02 2020 new variable +qsts#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable +qsts#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable +qsts#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable +qsts#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable +qsts#:#questionlist#:#Questionlist###26 08 2024 new variable +qsts#:#questions#:#Questions###26 08 2024 new variable +qsts#:#range_lower_limit#:#Lower limit###22 Feb 2006 new variable +qsts#:#range_upper_limit#:#Upper limit###22 Feb 2006 new variable +qsts#:#reset_preview#:#Reset Preview###26 09 2014 new variable +qsts#:#select_gap#:#Selectati un spatiu +qsts#:#shuffle_answers#:#Amestecati raspunsurile +qsts#:#suggested_learning_content#:#Add suggested solution###24 02 2009 new variable rating#:#rat_not_rated_yet#:#Not Rated Yet###12 06 2011 new variable rating#:#rat_nr_ratings#:#%s Ratings###12 06 2011 new variable rating#:#rat_one_rating#:#One Rating###12 06 2011 new variable @@ -16620,7 +16620,6 @@ survey#:#questionblock#:#Baraj de intrebari survey#:#questionblock_inserted#:#Question Block inserted###06 08 2009 new variable survey#:#questionblocks#:#Baraje de intrebari survey#:#questionblocks_inserted#:#Question Blocks inserted###06 08 2009 new variable -survey#:#questions#:#Intrebari survey#:#questions_inserted#:#Intrebari inserate! survey#:#questions_removed#:#Intrebarile sau barajele de intrebari eliminate! survey#:#questiontype#:#Tipul intrebarii @@ -16921,6 +16920,7 @@ survey#:#svy_please_select_unused_codes#:#Please select at least one unused code survey#:#svy_print_hide_labels#:#Hide labels###31 08 2017 new variable survey#:#svy_print_show_labels#:#Show labels###31 08 2017 new variable survey#:#svy_privacy_info#:#Privacy###29 07 2022 new variable +survey#:#svy_questions#:#Intrebari survey#:#svy_rater#:#Rater###29 07 2022 new variable survey#:#svy_rater_see_app_info#:#The names of appraisees will be presented to raters to enable them evaluating the questions.###29 07 2022 new variable survey#:#svy_reminder_mail_template#:#Mail Template###25 10 2016 new variable diff --git a/lang/ilias_ru.lang b/lang/ilias_ru.lang index 8156d786d99e..f1b0db7970d2 100644 --- a/lang/ilias_ru.lang +++ b/lang/ilias_ru.lang @@ -422,7 +422,6 @@ adve#:#adve_use_tiny_mce#:#Enable TinyMCE for WYSIWYG Editing assessment#:#activate_logging#:#Активировать тестирование assessment#:#activate_manual_scoring#:#Enable Manual Scoring###28 10 2024 new variable assessment#:#activate_manual_scoring_desc#:#Enables manual Scoring for all question types.###28 10 2024 new variable -assessment#:#addSuggestedSolution#:#Добавить предполагаемое решение assessment#:#add_answers#:#Add Antworten ###23 12 2015 new variable assessment#:#add_circle#:#Добавить зону в виде круга assessment#:#add_gap#:#Добавить пропущенный текст @@ -447,7 +446,6 @@ assessment#:#answer_is_not_correct_but_positive#:#Вы получили балл assessment#:#answer_is_right#:#Ваше решение верно assessment#:#answer_is_wrong#:#Ваше решение ошибочно assessment#:#answer_of#:#Answer of###07 02 2020 new variable -assessment#:#answer_options#:#Answer Options: ###23 12 2015 new variable assessment#:#answer_question#:#Answer Question###30 08 2015 new variable assessment#:#answer_text#:#Текст ответа assessment#:#answer_types#:#Типы ответа @@ -501,7 +499,6 @@ assessment#:#ass_completion_by_submission#:#Completed by Submission assessment#:#ass_completion_by_submission_info#:#If enabled, the submission of at least one file causes the completion of this question by granting the maximum score for this question. The score could be manually changed later. Switching this setting does not effect already submitted solutions. assessment#:#ass_create_export_file_with_results#:#Create Test Export File (incl. Participant Results)###25 10 2016 new variable assessment#:#ass_create_export_test_archive#:#Создать файл архива теста -assessment#:#ass_create_question#:#Создать вопрос assessment#:#ass_imap_hint#:#Hint to be shown as Tooltip###25 10 2016 new variable assessment#:#ass_imap_map_file_not_readable#:#The uploaded image map could not be read.###25 10 2016 new variable assessment#:#ass_imap_no_map_found#:#Could not find any form in the uploaded image map.###25 10 2016 new variable @@ -571,10 +568,6 @@ assessment#:#cloze_answer_text_info#:#Spaces preceding or following the answer t assessment#:#cloze_fixed_textlength#:#Длинна текстового поля assessment#:#cloze_fixed_textlength_description#:#If you enter a value greater than 0, all text and numeric gap text fields will be created with the fixed length of this value. assessment#:#cloze_gap_size_info#:#If you enter a value greater than 0, this gap text field will be created with the fixed length of this value. If you do not enter a value the gap text fiel will be created with the value of the global fixed length.###26 09 2014 new variable -assessment#:#cloze_text#:#Текст с пропусками -assessment#:#cloze_textgap_case_insensitive#:#Нечувствительный к регистру -assessment#:#cloze_textgap_case_sensitive#:#Чувствительный к регистру -assessment#:#cloze_textgap_levenshtein_of#:#Расстояние Левинштейна %s assessment#:#code#:#Код assessment#:#codebase#:#Кодовая база assessment#:#concatenation#:#Конкатенация @@ -757,9 +750,7 @@ assessment#:#fq_formula_desc#:#You may enter predefined variables ($v1 to $vn), assessment#:#fq_no_restriction_info#:#Both decimals and fractions are accepted as input. assessment#:#fq_precision_info#:#Enter the number of desired decimal places. assessment#:#fq_question_desc#:#You can define variables by inserting $v1, $v2 ... $vn, results by inserting $r1, $r2 .... $rn at the desired position in the question text. Click on the button "Parse Question" to create editing forms for variables and results. -assessment#:#gap#:#Пропуск assessment#:#gap_combination#:#Gap Combination###26 09 2014 new variable -assessment#:#gaps#:#Gaps###29 10 2025 new variable assessment#:#glossary_term#:#Термин глоссария assessment#:#goto_first_question#:#Show First Question###29 10 2025 new variable assessment#:#grading_mark_msg#:#Your resulting mark is: "[mark]"###26 09 2014 new variable @@ -777,7 +768,6 @@ assessment#:#info_answer_type_change#:#The question already contains images. You assessment#:#info_text_upload#:#Choose an answer file to upload###30 08 2015 new variable assessment#:#insert_after#:#Вставить после assessment#:#insert_before#:#Вставить перед -assessment#:#insert_gap#:#Вставить пропуск assessment#:#interaction_type#:#Interaction Type###26 08 2024 new variable assessment#:#internal_links#:#Внутренние связи assessment#:#intprecision#:#Divisible By @@ -889,7 +879,6 @@ assessment#:#logs_wrong_test_password_provided#:#Participant entered wrong test assessment#:#longmenu#:#Longmenu###10 11 2018 new variable assessment#:#longmenu_answeroptions_differ#:#This question does not work correctly, as there are not the same amount of gaps in the text as in the correction options.###26 08 2024 new variable assessment#:#longmenu_text#:#Long Menu Text###30 08 2015 new variable -assessment#:#mainbar_button_label_questionlist#:#Questionlist###26 08 2024 new variable assessment#:#maintenance#:#Обслуживание assessment#:#manscoring#:#Ручная проверка assessment#:#manscoring_done#:#Оцененые участники @@ -916,7 +905,6 @@ assessment#:#maximum_nr_of_tries_reached#:#Вы достигли максима assessment#:#maximum_points#:#Максимальное число баллов assessment#:#maxsize#:#Максимальный размер загружаемого файла assessment#:#maxsize_info#:#Enter the maximum size in bytes that should be allowed for file uploads. If you leave this field empty, the maximum size of this installation will be chosen instead. -assessment#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable assessment#:#min_ip_label#:#Lowest IP With Access###26 08 2024 new variable assessment#:#min_percentage_ne_0#:#Вы должны определить минимальное значение как 0 процентов. Схема оценки не сохранена. assessment#:#misc#:#Прочие опции @@ -925,7 +913,6 @@ assessment#:#mode_onebyone#:#One by One###29 10 2025 new variable assessment#:#mode_question#:#Question oriented###29 10 2025 new variable assessment#:#mode_user#:#Participant oriented###29 10 2025 new variable assessment#:#msg_circle_added#:#Круг добавлен -assessment#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable assessment#:#msg_number_of_terms_too_low#:#Число терминов должно быть больше или равно числу определений. assessment#:#msg_poly_added#:#Многогранник добавлен assessment#:#msg_questions_moved#:#Вопрос(ы) перемещены @@ -984,7 +971,6 @@ assessment#:#order#:#Order###26 08 2024 new variable assessment#:#ordering_answer_sequence_info#:#The answer sequence you define here will be taken as the correct solution sequence. assessment#:#ordertext#:#Упорядоченный текст assessment#:#ordertext_info#:#Пожалуйста введите текст который был бы упорядочен горизонтально. Упорядоченный текст должен быть разделен пробелами. Если вам нужен другой разделитель, вы можете использовать %s для отделение ваших блоков текста. -assessment#:#out_of_range#:#Out of range###27 01 2015 new variable assessment#:#output#:#Вывод assessment#:#output_mode#:#Режим вывода assessment#:#parseQuestion#:#Разобрать вопрос @@ -1053,7 +1039,6 @@ assessment#:#qpl_bulk_save_add#:#Add###28 10 2024 new variable assessment#:#qpl_bulk_save_overwrite#:#Overwrite###28 10 2024 new variable assessment#:#qpl_bulkedit_success#:#Modifications saved.###28 10 2024 new variable assessment#:#qpl_cancel_skill_assigns_update#:#Cancel###30 08 2015 new variable -assessment#:#qpl_confirm_delete_questions#:#Вы действительно желаете удалить следующие вопрос(ы)? assessment#:#qpl_copy_insert_clipboard#:#Выбранные вопросы скопированы в буфер обмена assessment#:#qpl_copy_select_none#:#Please check at least one question to copy it to the clipboard assessment#:#qpl_delete_rbac_error#:#У вас нет прав удаления этого вопроса! @@ -1106,7 +1091,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#Competence###30 08 2015 new variable assessment#:#qpl_qst_skl_usg_sklpnt_col#:#Total Sum of Competence-Points per Competence###30 08 2015 new variable assessment#:#qpl_question_is_in_use#:#Вопрос, который вы выбрали на редактирование существует в %s тесте(ах). Если вы измените этот вопрос, то вы не сможете изменить вопрос(ы) в тесте(ах), потому, что система создает копию этого вопроса, когда добавляет его в тест! assessment#:#qpl_questions_deleted#:#Вопрос(ы) удален(ы). -assessment#:#qpl_reset_preview#:#Reset Preview###26 09 2014 new variable assessment#:#qpl_save_skill_assigns_update#:#Save Competence Assignments###30 08 2015 new variable assessment#:#qpl_settings_availability#:#Availability###26 08 2024 new variable assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#When enabled, possibly created taxonomies are shown for filtering. @@ -1136,14 +1120,6 @@ assessment#:#qst_essay_chars_remaining#:#Remaining characters:###07 02 2020 new assessment#:#qst_essay_wordcounter_enabled#:#Count Words###07 02 2020 new variable assessment#:#qst_essay_wordcounter_enabled_info#:#The entered words are counted. The number of written words is shown to the participants below the text input field.###07 02 2020 new variable assessment#:#qst_essay_written_words#:#Number of entered words:###07 02 2020 new variable -assessment#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable -assessment#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable -assessment#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable -assessment#:#qst_lifecycle_final#:#Final###07 02 2020 new variable -assessment#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable -assessment#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable -assessment#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable -assessment#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable assessment#:#qst_nested_nested_answers_off#:#No indents, just order###29 07 2022 new variable assessment#:#qst_nested_nested_answers_on#:#Use indents in anwers###29 07 2022 new variable assessment#:#qst_nr_of_tries#:#Число попыток @@ -1167,17 +1143,14 @@ assessment#:#question_title#:#Название вопроса assessment#:#question_type#:#Тип вопроса assessment#:#questionpool_not_entered#:#Пожалуйста, задайте имя для набора вопросов! assessment#:#questionpool_not_selected#:#Please select a question pool!###07 02 2020 new variable -assessment#:#questions#:#Questions###26 08 2024 new variable assessment#:#questions_from#:#форма вопросов assessment#:#questions_per_page_view#:#Page View assessment#:#random_accept_sample#:#Взять пример assessment#:#random_another_sample#:#Взять другой пример assessment#:#random_selection#:#Случайный выбор assessment#:#range#:#Диапазон -assessment#:#range_lower_limit#:#Нижняя граница assessment#:#range_max#:#Range (Maximum) assessment#:#range_min#:#Range (Minimum) -assessment#:#range_upper_limit#:#Верхняя граница assessment#:#rated_sign#:#Sign assessment#:#rated_unit#:#Unit assessment#:#rated_value#:#Value @@ -1253,7 +1226,6 @@ assessment#:#search_roles#:#Поиск ролей assessment#:#search_term#:#Поиск терминов assessment#:#select_at_least_one_feedback_type_and_trigger#:#Please Select at least one type of feedback and a trigger.###26 08 2024 new variable assessment#:#select_at_least_one_lock_answer_type#:#Please select at least one type of answer lock.###29 10 2025 new variable -assessment#:#select_gap#:#Выберите пропуск assessment#:#select_max_one_item#:#Пожалуйста, выберите только один пункт assessment#:#select_one_user#:#Please select at least one user. assessment#:#select_question#:#Select a Question###28 10 2024 new variable @@ -1280,7 +1252,6 @@ assessment#:#show_old_introduction#:#Show old introduction###26 08 2024 new vari assessment#:#show_pass_overview#:#Показать обзор отмеченных верных ответов assessment#:#show_results#:#Show Results###28 10 2024 new variable assessment#:#show_user_answers#:#Показать отмеченные пользователем ответы -assessment#:#shuffle_answers#:#Перемешать ответы assessment#:#skip_question#:#Do not Answer and Next###08 10 2015 new variable assessment#:#solution#:#Solution###28 10 2024 new variable assessment#:#solutionText#:#Текст @@ -13971,6 +13942,35 @@ qpl#:#qpl_page_type_qfbg#:#General Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qfbs#:#Special Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qht#:#Hint###29 07 2022 new variable qpl#:#qpl_page_type_qpl#:#Question Page###29 07 2022 new variable +qsts#:#answer_options#:#Answer Options ###23 12 2015 new variable +qsts#:#cloze_text#:#Текст с пропусками +qsts#:#cloze_textgapcase_insensitive#:#Нечувствительный к регистру +qsts#:#cloze_textgapcase_sensitive#:#Чувствительный к регистру +qsts#:#cloze_textgaplevenshtein_of#:#Расстояние Левинштейна %s +qsts#:#confirm_delete_questions#:#Вы действительно желаете удалить следующие вопрос(ы)? +qsts#:#create_question#:#Создать вопрос +qsts#:#gap#:#Пропуск +qsts#:#gaps#:#Gaps###29 10 2025 new variable +qsts#:#insert_gap#:#Вставить пропуск +qsts#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable +qsts#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable +qsts#:#out_of_range#:#Out of range###27 01 2015 new variable +qsts#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable +qsts#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable +qsts#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable +qsts#:#qst_lifecycle_final#:#Final###07 02 2020 new variable +qsts#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable +qsts#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable +qsts#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable +qsts#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable +qsts#:#questionlist#:#Questionlist###26 08 2024 new variable +qsts#:#questions#:#Questions###26 08 2024 new variable +qsts#:#range_lower_limit#:#Нижняя граница +qsts#:#range_upper_limit#:#Верхняя граница +qsts#:#reset_preview#:#Reset Preview###26 09 2014 new variable +qsts#:#select_gap#:#Выберите пропуск +qsts#:#shuffle_answers#:#Перемешать ответы +qsts#:#suggested_learning_content#:#Добавить предполагаемое решение rating#:#rat_not_rated_yet#:#Not Rated Yet rating#:#rat_nr_ratings#:#%s Ratings rating#:#rat_one_rating#:#One Rating @@ -16620,7 +16620,6 @@ survey#:#questionblock#:#Блок вопроса survey#:#questionblock_inserted#:#Question Block inserted survey#:#questionblocks#:#Блоки вопроса survey#:#questionblocks_inserted#:#Question Blocks inserted -survey#:#questions#:#Вопросы survey#:#questions_inserted#:#Вопрос(ы) вставлен(ы)! survey#:#questions_removed#:#Вопрос(ы) и/или удаленный блок(и) вопроса! survey#:#questiontype#:#Тип вопроса @@ -16921,6 +16920,7 @@ survey#:#svy_please_select_unused_codes#:#Please select at least one unused code survey#:#svy_print_hide_labels#:#Hide labels###31 08 2017 new variable survey#:#svy_print_show_labels#:#Show labels###31 08 2017 new variable survey#:#svy_privacy_info#:#Privacy###29 07 2022 new variable +survey#:#svy_questions#:#Вопросы survey#:#svy_rater#:#Rater###29 07 2022 new variable survey#:#svy_rater_see_app_info#:#The names of appraisees will be presented to raters to enable them evaluating the questions.###29 07 2022 new variable survey#:#svy_reminder_mail_template#:#Mail Template###25 10 2016 new variable diff --git a/lang/ilias_sk.lang b/lang/ilias_sk.lang index 675ccc20e0fe..396d593f4dbb 100644 --- a/lang/ilias_sk.lang +++ b/lang/ilias_sk.lang @@ -422,7 +422,6 @@ adve#:#adve_use_tiny_mce#:#Enable TinyMCE for WYSIWYG Editing###01 10 2011 new v assessment#:#activate_logging#:#Aktivovat záznam Test a Hodnocení assessment#:#activate_manual_scoring#:#Enable Manual Scoring###28 10 2024 new variable assessment#:#activate_manual_scoring_desc#:#Enables manual Scoring for all question types.###28 10 2024 new variable -assessment#:#addSuggestedSolution#:#Přidat doporučené řešení assessment#:#add_answers#:#Add Antworten ###23 12 2015 new variable assessment#:#add_circle#:#Přidat kruhovou plochu assessment#:#add_gap#:#Přidat textovou mezeru @@ -447,7 +446,6 @@ assessment#:#answer_is_not_correct_but_positive#:#Obdržel(a) jste body za Vaše assessment#:#answer_is_right#:#Vaše řešení je správné assessment#:#answer_is_wrong#:#Vaše řešení je chybné assessment#:#answer_of#:#Answer of###07 02 2020 new variable -assessment#:#answer_options#:#Answer Options: ###23 12 2015 new variable assessment#:#answer_question#:#Answer Question###30 08 2015 new variable assessment#:#answer_text#:#Text odpovědi assessment#:#answer_types#:#Typy odpovědi @@ -501,7 +499,6 @@ assessment#:#ass_completion_by_submission#:#Completed by Submission###12 06 2011 assessment#:#ass_completion_by_submission_info#:#If enabled, the submission of at least one file causes the completion of this question by granting the maximum score for this question. The score could be manually changed later. Switching this setting does not effect already submitted solutions.###12 06 2011 new variable assessment#:#ass_create_export_file_with_results#:#Create Test Export File (incl. Participant Results)###25 10 2016 new variable assessment#:#ass_create_export_test_archive#:#Create Test Archive File###24 10 2013 new variable -assessment#:#ass_create_question#:#Create Question###24 10 2013 new variable assessment#:#ass_imap_hint#:#Hint to be shown as Tooltip###25 10 2016 new variable assessment#:#ass_imap_map_file_not_readable#:#The uploaded image map could not be read.###25 10 2016 new variable assessment#:#ass_imap_no_map_found#:#Could not find any form in the uploaded image map.###25 10 2016 new variable @@ -571,10 +568,6 @@ assessment#:#cloze_answer_text_info#:#Spaces preceding or following the answer t assessment#:#cloze_fixed_textlength#:#Délka textového pole assessment#:#cloze_fixed_textlength_description#:#Pokud vložíte hodnotu větší než 0, všechna textová pole textových i numerických mezer budou vytvářeny s pevnou délkou rovnou této hodnotě. assessment#:#cloze_gap_size_info#:#If you enter a value greater than 0, this gap text field will be created with the fixed length of this value. If you do not enter a value the gap text fiel will be created with the value of the global fixed length.###26 09 2014 new variable -assessment#:#cloze_text#:#Doplňovaný text -assessment#:#cloze_textgap_case_insensitive#:#Nerozlišuje velká/malá písmena -assessment#:#cloze_textgap_case_sensitive#:#Rozlišuje velká/malá písmena -assessment#:#cloze_textgap_levenshtein_of#:#Vzdálenost Levenshtein %s assessment#:#code#:#Kód assessment#:#codebase#:#Databáze kódu assessment#:#concatenation#:#Sřetězení @@ -757,9 +750,7 @@ assessment#:#fq_formula_desc#:#You may enter predefined variables ($v1 to $vn), assessment#:#fq_no_restriction_info#:#Both decimals and fractions are accepted as input.###26 03 2014 new variable assessment#:#fq_precision_info#:#Enter the number of desired decimal places.###26 03 2014 new variable assessment#:#fq_question_desc#:#You can define variables by inserting $v1, $v2 ... $vn, results by inserting $r1, $r2 .... $rn at the desired position in the question text. Click on the button "Parse Question" to create editing forms for variables and results.###06 11 2013 new variable -assessment#:#gap#:#Mezera assessment#:#gap_combination#:#Gap Combination###26 09 2014 new variable -assessment#:#gaps#:#Gaps###29 10 2025 new variable assessment#:#glossary_term#:#Pojem glosáře assessment#:#goto_first_question#:#Show First Question###29 10 2025 new variable assessment#:#grading_mark_msg#:#Your resulting mark is: "[mark]"###26 09 2014 new variable @@ -777,7 +768,6 @@ assessment#:#info_answer_type_change#:#Tato otázka již obsahuje obrázky. Nem assessment#:#info_text_upload#:#Choose an answer file to upload###30 08 2015 new variable assessment#:#insert_after#:#Vložit za assessment#:#insert_before#:#Vložit před -assessment#:#insert_gap#:#Insert Gap###24 10 2013 new variable assessment#:#interaction_type#:#Interaction Type###26 08 2024 new variable assessment#:#internal_links#:#Interní odkazy assessment#:#intprecision#:#Divisible By###24 10 2013 new variable @@ -889,7 +879,6 @@ assessment#:#logs_wrong_test_password_provided#:#Participant entered wrong test assessment#:#longmenu#:#Longmenu###10 11 2018 new variable assessment#:#longmenu_answeroptions_differ#:#This question does not work correctly, as there are not the same amount of gaps in the text as in the correction options.###26 08 2024 new variable assessment#:#longmenu_text#:#Long Menu Text###30 08 2015 new variable -assessment#:#mainbar_button_label_questionlist#:#Questionlist###26 08 2024 new variable assessment#:#maintenance#:#Údržba assessment#:#manscoring#:#Manuální hodnocení assessment#:#manscoring_done#:#Vyhodnocení účastníci @@ -916,7 +905,6 @@ assessment#:#maximum_nr_of_tries_reached#:#Dosáhl(a) jste maximální počet po assessment#:#maximum_points#:#Maxium dostupných bodů assessment#:#maxsize#:#Maximální velkost ukládaného souboru assessment#:#maxsize_info#:#Vložit maximální velikost v bytech, která je povolena pro ukládané soubory. Pokud necháte toto pole prázdné, bude namísto něj nastavena maximální velikost této instalace. -assessment#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable assessment#:#min_ip_label#:#Lowest IP With Access###26 08 2024 new variable assessment#:#min_percentage_ne_0#:#Musíte definovat minimální procento od specifikace 0 procent! Schéma známkování nebylo uloženo. assessment#:#misc#:#Misc Options###24 10 2013 new variable @@ -925,7 +913,6 @@ assessment#:#mode_onebyone#:#One by One###29 10 2025 new variable assessment#:#mode_question#:#Question oriented###29 10 2025 new variable assessment#:#mode_user#:#Participant oriented###29 10 2025 new variable assessment#:#msg_circle_added#:#Kruh přidán -assessment#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable assessment#:#msg_number_of_terms_too_low#:#Počet výrazů musí být větší, nebo roven počtu definicí. assessment#:#msg_poly_added#:#Polygon přidán assessment#:#msg_questions_moved#:#Otázky přesunuty @@ -984,7 +971,6 @@ assessment#:#order#:#Order###26 08 2024 new variable assessment#:#ordering_answer_sequence_info#:#The answer sequence you define here will be taken as the correct solution sequence.###28 10 2010 new variable assessment#:#ordertext#:#Řazení textu assessment#:#ordertext_info#:#Vložte prosím text, který má být řazen horizontálně. Řazený text bude oddělen pomocí značek mezer v textu. Pokud potřebujete jiné oddělení, můžete použít separátor %s k oddělení Vašich textových jednotek. -assessment#:#out_of_range#:#Out of range###27 01 2015 new variable assessment#:#output#:#Výstup assessment#:#output_mode#:#Výstupní mód assessment#:#parseQuestion#:#Parse Question###24 10 2013 new variable @@ -1053,7 +1039,6 @@ assessment#:#qpl_bulk_save_add#:#Add###28 10 2024 new variable assessment#:#qpl_bulk_save_overwrite#:#Overwrite###28 10 2024 new variable assessment#:#qpl_bulkedit_success#:#Modifications saved.###28 10 2024 new variable assessment#:#qpl_cancel_skill_assigns_update#:#Cancel###30 08 2015 new variable -assessment#:#qpl_confirm_delete_questions#:#Jste si jist(a), že chcete smazat následující otázky? assessment#:#qpl_copy_insert_clipboard#:#Vybrané otázky jsou zkopírovány do schránky assessment#:#qpl_copy_select_none#:#Označte alespoň jednu otázku ke zkopírování do schránky assessment#:#qpl_delete_rbac_error#:#Nemáte oprávnění smazat tuto otázku! @@ -1106,7 +1091,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#Competence###30 08 2015 new variable assessment#:#qpl_qst_skl_usg_sklpnt_col#:#Total Sum of Competence-Points per Competence###30 08 2015 new variable assessment#:#qpl_question_is_in_use#:#Otázka, kterou upravujete, existuje v %s testech. Pokud tuto otázku změníte, NEZMĚNÍTE otázku(y) v testu(ech), protože systém vytváří kopii otázky, která je vložena do testu! assessment#:#qpl_questions_deleted#:#Otázky smazány. -assessment#:#qpl_reset_preview#:#Reset Preview###26 09 2014 new variable assessment#:#qpl_save_skill_assigns_update#:#Save Competence Assignments###30 08 2015 new variable assessment#:#qpl_settings_availability#:#Availability###26 08 2024 new variable assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#When enabled, possibly created taxonomies are shown for filtering.###24 10 2013 new variable @@ -1136,14 +1120,6 @@ assessment#:#qst_essay_chars_remaining#:#Remaining characters:###07 02 2020 new assessment#:#qst_essay_wordcounter_enabled#:#Count Words###07 02 2020 new variable assessment#:#qst_essay_wordcounter_enabled_info#:#The entered words are counted. The number of written words is shown to the participants below the text input field.###07 02 2020 new variable assessment#:#qst_essay_written_words#:#Number of entered words:###07 02 2020 new variable -assessment#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable -assessment#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable -assessment#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable -assessment#:#qst_lifecycle_final#:#Final###07 02 2020 new variable -assessment#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable -assessment#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable -assessment#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable -assessment#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable assessment#:#qst_nested_nested_answers_off#:#No indents, just order###29 07 2022 new variable assessment#:#qst_nested_nested_answers_on#:#Use indents in anwers###29 07 2022 new variable assessment#:#qst_nr_of_tries#:#Počet pokusů @@ -1167,17 +1143,14 @@ assessment#:#question_title#:#Název otázky assessment#:#question_type#:#Typ otázky assessment#:#questionpool_not_entered#:#Vložte prosím název zásobníku otázek! assessment#:#questionpool_not_selected#:#Please select a question pool!###07 02 2020 new variable -assessment#:#questions#:#Questions###26 08 2024 new variable assessment#:#questions_from#:#otázky z assessment#:#questions_per_page_view#:#Page View###12 06 2011 new variable assessment#:#random_accept_sample#:#Akceptovat příklad assessment#:#random_another_sample#:#Vybrat jiný příklad assessment#:#random_selection#:#Náhodný výběr assessment#:#range#:#Rozmezí -assessment#:#range_lower_limit#:#Spodní hranice assessment#:#range_max#:#Range (Maximum)###24 10 2013 new variable assessment#:#range_min#:#Range (Minimum)###24 10 2013 new variable -assessment#:#range_upper_limit#:#Horní hranice assessment#:#rated_sign#:#Sign###24 10 2013 new variable assessment#:#rated_unit#:#Unit###24 10 2013 new variable assessment#:#rated_value#:#Value###24 10 2013 new variable @@ -1253,7 +1226,6 @@ assessment#:#search_roles#:#Vyhledat role assessment#:#search_term#:#Vyhledat termín assessment#:#select_at_least_one_feedback_type_and_trigger#:#Please Select at least one type of feedback and a trigger.###26 08 2024 new variable assessment#:#select_at_least_one_lock_answer_type#:#Please select at least one type of answer lock.###29 10 2025 new variable -assessment#:#select_gap#:#Vybírací mezera assessment#:#select_max_one_item#:#Vyberte prosím pouze jednu položku assessment#:#select_one_user#:#Vyberte prosím alespoň jednoho uživatele assessment#:#select_question#:#Select a Question###28 10 2024 new variable @@ -1280,7 +1252,6 @@ assessment#:#show_old_introduction#:#Show old introduction###26 08 2024 new vari assessment#:#show_pass_overview#:#Zobrazit přehled označených průchodů assessment#:#show_results#:#Show Results###28 10 2024 new variable assessment#:#show_user_answers#:#Zobrazit označené odpovědi uživatelů -assessment#:#shuffle_answers#:#Zamíchat odpovědi assessment#:#skip_question#:#Do not Answer and Next###08 10 2015 new variable assessment#:#solution#:#Solution###28 10 2024 new variable assessment#:#solutionText#:#Text @@ -13971,6 +13942,35 @@ qpl#:#qpl_page_type_qfbg#:#General Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qfbs#:#Special Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qht#:#Hint###29 07 2022 new variable qpl#:#qpl_page_type_qpl#:#Question Page###29 07 2022 new variable +qsts#:#answer_options#:#Answer Options ###23 12 2015 new variable +qsts#:#cloze_text#:#Doplňovaný text +qsts#:#cloze_textgapcase_insensitive#:#Nerozlišuje velká/malá písmena +qsts#:#cloze_textgapcase_sensitive#:#Rozlišuje velká/malá písmena +qsts#:#cloze_textgaplevenshtein_of#:#Vzdálenost Levenshtein %s +qsts#:#confirm_delete_questions#:#Jste si jist(a), že chcete smazat následující otázky? +qsts#:#create_question#:#Create Question###24 10 2013 new variable +qsts#:#gap#:#Mezera +qsts#:#gaps#:#Gaps###29 10 2025 new variable +qsts#:#insert_gap#:#Insert Gap###24 10 2013 new variable +qsts#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable +qsts#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable +qsts#:#out_of_range#:#Out of range###27 01 2015 new variable +qsts#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable +qsts#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable +qsts#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable +qsts#:#qst_lifecycle_final#:#Final###07 02 2020 new variable +qsts#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable +qsts#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable +qsts#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable +qsts#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable +qsts#:#questionlist#:#Questionlist###26 08 2024 new variable +qsts#:#questions#:#Questions###26 08 2024 new variable +qsts#:#range_lower_limit#:#Spodní hranice +qsts#:#range_upper_limit#:#Horní hranice +qsts#:#reset_preview#:#Reset Preview###26 09 2014 new variable +qsts#:#select_gap#:#Vybírací mezera +qsts#:#shuffle_answers#:#Zamíchat odpovědi +qsts#:#suggested_learning_content#:#Přidat doporučené řešení rating#:#rat_not_rated_yet#:#Not Rated Yet###12 06 2011 new variable rating#:#rat_nr_ratings#:#%s Ratings###12 06 2011 new variable rating#:#rat_one_rating#:#One Rating###12 06 2011 new variable @@ -16620,7 +16620,6 @@ survey#:#questionblock#:#Blok otázek survey#:#questionblock_inserted#:#Blok otázek vložen survey#:#questionblocks#:#Bloky otázek survey#:#questionblocks_inserted#:#Bloky otázek vloženy -survey#:#questions#:#Otázky survey#:#questions_inserted#:#Otázky vloženy survey#:#questions_removed#:#Otázky a/nebo bloky otázek odstraněny! survey#:#questiontype#:#Typ otázky @@ -16921,6 +16920,7 @@ survey#:#svy_please_select_unused_codes#:#Please select at least one unused code survey#:#svy_print_hide_labels#:#Hide labels###31 08 2017 new variable survey#:#svy_print_show_labels#:#Show labels###31 08 2017 new variable survey#:#svy_privacy_info#:#Privacy###29 07 2022 new variable +survey#:#svy_questions#:#Otázky survey#:#svy_rater#:#Rater###29 07 2022 new variable survey#:#svy_rater_see_app_info#:#The names of appraisees will be presented to raters to enable them evaluating the questions.###29 07 2022 new variable survey#:#svy_reminder_mail_template#:#Mail Template###25 10 2016 new variable diff --git a/lang/ilias_sl.lang b/lang/ilias_sl.lang index f28ff5c9c8e3..4031825ec3e8 100644 --- a/lang/ilias_sl.lang +++ b/lang/ilias_sl.lang @@ -422,7 +422,6 @@ adve#:#adve_use_tiny_mce#:#Enable TinyMCE for WYSIWYG Editing assessment#:#activate_logging#:#Aktiviraj protokol testov in ocen assessment#:#activate_manual_scoring#:#Enable Manual Scoring###28 10 2024 new variable assessment#:#activate_manual_scoring_desc#:#Enables manual Scoring for all question types.###28 10 2024 new variable -assessment#:#addSuggestedSolution#:#Vsebine za ponavljanje assessment#:#add_answers#:#Dodaj odgovore assessment#:#add_circle#:#Dodaj krog assessment#:#add_gap#:#Dodaj besedilo s prazninami @@ -447,7 +446,6 @@ assessment#:#answer_is_not_correct_but_positive#:#Dobili ste točke za svojo re assessment#:#answer_is_right#:#Vaša rešitev je pravilna. assessment#:#answer_is_wrong#:#Vaša rešitev je napačna. assessment#:#answer_of#:#Answer of -assessment#:#answer_options#:#Možnosti odgovora: assessment#:#answer_question#:#Odgovori na vprašanje assessment#:#answer_text#:#Besedilo odgovora assessment#:#answer_types#:#Urednik odgovorov @@ -501,7 +499,6 @@ assessment#:#ass_completion_by_submission#:#Obstoj z oddajo assessment#:#ass_completion_by_submission_info#:#Če je to aktivirano, oddaja datoteke z rešitvijo povzroči največjo oceno za to vprašanje. Oceno lahko kadar koli ročno prilagodite. Sprememba te nastavitve naknadno ne vpliva na že oddane rešitve. assessment#:#ass_create_export_file_with_results#:#Ustvari datoteko za izvoz (vključno z rezultati udeležencev) assessment#:#ass_create_export_test_archive#:#Ustvari arhivsko datoteko za test -assessment#:#ass_create_question#:#Ustvari vprašanje assessment#:#ass_imap_hint#:#Opomba (prikaže se kot opis orodja) assessment#:#ass_imap_map_file_not_readable#:#Naloženega slikovnega zemljevida ni mogoče prebrati. assessment#:#ass_imap_no_map_found#:#V naloženem Imapemap ni bila najdena nobena podprta oblika. @@ -571,10 +568,6 @@ assessment#:#cloze_answer_text_info#:#Spaces preceding or following the answer t assessment#:#cloze_fixed_textlength#:#Dolžina besedilnega polja assessment#:#cloze_fixed_textlength_description#:#Če tukaj vnesete vrednost, se v besedilu ustvarijo praznine, ki ne določajo lastne vrednosti za maksimalno dolžino, kot tudi praznine za številčni odgovor s to dolžino, zato ni mogoče vnesti več znakov, kot je dovoljeno. Pri prazninah za številčni odgovor je poleg tega treba upoštevati, da se pri štetju upoštevajo tudi decimalna ločila. assessment#:#cloze_gap_size_info#:#Če je vnesena vrednost večja od 0, se ta praznina ustvari z dolžino, ki je tukaj navedena. Če ni vnesena nobena vrednost, se ta praznina ustvari z globalno določeno dolžino besedilnega polja. -assessment#:#cloze_text#:#Vprašanje s praznino v besedilu -assessment#:#cloze_textgap_case_insensitive#:#Ni razlikovanja med velikimi in malimi črkami -assessment#:#cloze_textgap_case_sensitive#:#Upoštevaj razlikovanje velikih in malih črk -assessment#:#cloze_textgap_levenshtein_of#:#Levenshteinov razmik v %s assessment#:#code#:#Koda assessment#:#codebase#:#Osnova kode assessment#:#concatenation#:#Povezava @@ -757,9 +750,7 @@ assessment#:#fq_formula_desc#:#Dovoljena je uporaba že definiranih spremenljivk assessment#:#fq_no_restriction_info#:#Kot vnos se sprejmejo tako decimalne številke kot ulomki. assessment#:#fq_precision_info#:#Tukaj vnesite želeno število decimalnih mest. assessment#:#fq_question_desc#:#Spremenljivke lahko definirate tako, da vstavite $ v1, $ v2 ... $vn, rezultate pa tako, da vstavite $ r1, $ r2 .... $ rn na želeno mesto v besedilu. Nato kliknite na gumb "Analiziraj vprašanje", da ustvarite obrazce za obdelavo spremenljivk in rezultatov. -assessment#:#gap#:#Praznina assessment#:#gap_combination#:#Kombinacija besedila s prazninami -assessment#:#gaps#:#Gaps###29 10 2025 new variable assessment#:#glossary_term#:#Pojem iz glosarja assessment#:#goto_first_question#:#Show First Question###29 10 2025 new variable assessment#:#grading_mark_msg#:#Vaša ocena je "[mark]""; @@ -777,7 +768,6 @@ assessment#:#info_answer_type_change#:#Vprašanje že vsebuje slike. Tipa odgovo assessment#:#info_text_upload#:#Izberite besedilno datoteko (UTF-8) z odgovori, ki jih želite naložiti. assessment#:#insert_after#:#Vstavi za assessment#:#insert_before#:#Vstavi pred -assessment#:#insert_gap#:#Vstavi praznino assessment#:#interaction_type#:#Interaction Type###26 08 2024 new variable assessment#:#internal_links#:#Notranje opombe assessment#:#intprecision#:#Deljivo z @@ -889,7 +879,6 @@ assessment#:#logs_wrong_test_password_provided#:#Udeleženec je vnesel napačno assessment#:#longmenu#:#Longmenu assessment#:#longmenu_answeroptions_differ#:#This question does not work correctly, as there are not the same amount of gaps in the text as in the correction options.###26 08 2024 new variable assessment#:#longmenu_text#:#Besedilo ‘Long Menu’ -assessment#:#mainbar_button_label_questionlist#:#Questionlist###26 08 2024 new variable assessment#:#maintenance#:#Vzdrževanje assessment#:#manscoring#:#Ročno ocenjevanje assessment#:#manscoring_done#:#Udeleženci, ki so že bili ocenjeni @@ -916,7 +905,6 @@ assessment#:#maximum_nr_of_tries_reached#:#Dosegli ste maksimalno dovoljeno šte assessment#:#maximum_points#:#Maksimalno možno število točk assessment#:#maxsize#:#Maksimalna velikost datoteke assessment#:#maxsize_info#:#Navedite maksimalno velikost v bitih, ki jo lahko ima datoteka, ki se nalaga. Če pustite to polje prazno, bo uporabljena nastavitev osnovnega sistema. -assessment#:#min_auto_complete#:#Samodejno dopolnjevanje assessment#:#min_ip_label#:#Lowest IP With Access###26 08 2024 new variable assessment#:#min_percentage_ne_0#:#Določiti morate minimalni odstotek v vrednosti 0, da se lahko upoštevajo vse dosežene točke. Shema ocenjevanja ni shranjena! assessment#:#misc#:#Različne možnosti @@ -925,7 +913,6 @@ assessment#:#mode_onebyone#:#One by One###29 10 2025 new variable assessment#:#mode_question#:#Question oriented###29 10 2025 new variable assessment#:#mode_user#:#Participant oriented###29 10 2025 new variable assessment#:#msg_circle_added#:#Dodan je krog -assessment#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable assessment#:#msg_number_of_terms_too_low#:#Število izrazov mora biti večje ali enako številu definicij. assessment#:#msg_poly_added#:#Dodan je poligon assessment#:#msg_questions_moved#:#Vprašanje/-a je/so premaknjeno/-a. @@ -984,7 +971,6 @@ assessment#:#order#:#Order###26 08 2024 new variable assessment#:#ordering_answer_sequence_info#:#Na tem mestu definirano zaporedje odgovorov se uporabi kot pravilen vrstni red rešitev. assessment#:#ordertext#:#Besedilo za razporeditev assessment#:#ordertext_info#:#Vnesite besedilo v takšnem zaporedju, kot naj bo razvrščeno horizontalno. Posamezne sestavine so ločene s presledki. Če potrebujete drugačno ločitev, namesto presledkov uporabite ločilo %s. -assessment#:#out_of_range#:#Izven področja assessment#:#output#:#Izdaja assessment#:#output_mode#:#Način izdaje assessment#:#parseQuestion#:#Analiziraj vprašanje @@ -1053,7 +1039,6 @@ assessment#:#qpl_bulk_save_add#:#Add###28 10 2024 new variable assessment#:#qpl_bulk_save_overwrite#:#Overwrite###28 10 2024 new variable assessment#:#qpl_bulkedit_success#:#Modifications saved.###28 10 2024 new variable assessment#:#qpl_cancel_skill_assigns_update#:#Prekini -assessment#:#qpl_confirm_delete_questions#:#Ali ste prepričani, da želite odstraniti naslednja vprašanja? assessment#:#qpl_copy_insert_clipboard#:#Izbrano/-a vprašanje/-a se kopira/-jo v odložišče assessment#:#qpl_copy_select_none#:#Izberite vsaj eno vprašanje za kopiranje v odložišče assessment#:#qpl_delete_rbac_error#:#Nimate pravice za odstranitev tega vprašanja! @@ -1106,7 +1091,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#Kompetenca assessment#:#qpl_qst_skl_usg_sklpnt_col#:#Skupno število kompetenčnih točk na kompetenco assessment#:#qpl_question_is_in_use#:#Vprašanje, ki ga zdaj želite obdelati, že obstaja v %st testu/-ih. Če zdaj spremenite to vprašanje, to NE bo vplivalo na vprašanja, ki so že vključena v testih, ker sistem samodejno naredi kopijo vprašanja, ko je vključeno v test! assessment#:#qpl_questions_deleted#:#Vprašanje/-a izbrisano/-a -assessment#:#qpl_reset_preview#:#Ponastavite predogled assessment#:#qpl_save_skill_assigns_update#:#Shranite dodelitev kompetenc assessment#:#qpl_settings_availability#:#Availability###26 08 2024 new variable assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#Obstoječe taksonomije se lahko uporabijo za filtriranje vprašanj. @@ -1136,14 +1120,6 @@ assessment#:#qst_essay_chars_remaining#:#Remaining characters assessment#:#qst_essay_wordcounter_enabled#:#Count Words assessment#:#qst_essay_wordcounter_enabled_info#:#The entered words are counted. The number of written words is shown to the participants below the text input field. assessment#:#qst_essay_written_words#:#Number of entered words -assessment#:#qst_lifecycle#:#Lifecycle -assessment#:#qst_lifecycle_draft#:#Draft -assessment#:#qst_lifecycle_filter_all#:#All Lifecycles -assessment#:#qst_lifecycle_final#:#Final -assessment#:#qst_lifecycle_outdated#:#Outdated -assessment#:#qst_lifecycle_rejected#:#Rejected -assessment#:#qst_lifecycle_review#:#To be Reviewed -assessment#:#qst_lifecycle_sharable#:#Sharable assessment#:#qst_nested_nested_answers_off#:#No indents, just order###19 08 2022 new variable assessment#:#qst_nested_nested_answers_on#:#Use indents in anwers###19 08 2022 new variable assessment#:#qst_nr_of_tries#:#Število poskusov @@ -1167,17 +1143,14 @@ assessment#:#question_title#:#Naslov vprašanja assessment#:#question_type#:#Tip vprašanja assessment#:#questionpool_not_entered#:#Vnesite ime za skupino vprašanj! assessment#:#questionpool_not_selected#:#Please select a question pool! -assessment#:#questions#:#Questions###26 08 2024 new variable assessment#:#questions_from#:#Vprašanja iz assessment#:#questions_per_page_view#:#Prikaz strani assessment#:#random_accept_sample#:#Sprejmite seznam assessment#:#random_another_sample#:#Nov seznam assessment#:#random_selection#:#Naključna izbira assessment#:#range#:#Področje -assessment#:#range_lower_limit#:#Spodnja meja assessment#:#range_max#:#Področje (maksimalno) assessment#:#range_min#:#Področje (minimalno) -assessment#:#range_upper_limit#:#Zgornja meja assessment#:#rated_sign#:#Predznak assessment#:#rated_unit#:#Enota assessment#:#rated_value#:#Vrednost @@ -1253,7 +1226,6 @@ assessment#:#search_roles#:#po vlogah assessment#:#search_term#:#Iskani pojem assessment#:#select_at_least_one_feedback_type_and_trigger#:#Please Select at least one type of feedback and a trigger.###26 08 2024 new variable assessment#:#select_at_least_one_lock_answer_type#:#Please select at least one type of answer lock.###29 10 2025 new variable -assessment#:#select_gap#:#Izbrana praznina assessment#:#select_max_one_item#:#Izberite samo en objekt assessment#:#select_one_user#:#Izberite vsaj enega uporabnika. assessment#:#select_question#:#Select a Question###28 10 2024 new variable @@ -1280,7 +1252,6 @@ assessment#:#show_old_introduction#:#Show old introduction###26 08 2024 new vari assessment#:#show_pass_overview#:#Pregled rezultatov (ocenjeno opravljanje testa) assessment#:#show_results#:#Show Results###28 10 2024 new variable assessment#:#show_user_answers#:#Odgovori (ocenjeno opravljanje testa) -assessment#:#shuffle_answers#:#Pomešaj odgovore assessment#:#skip_question#:#Ne odgovarjaj in naprej assessment#:#solution#:#Solution###28 10 2024 new variable assessment#:#solutionText#:#Besedilo @@ -13971,6 +13942,35 @@ qpl#:#qpl_page_type_qfbg#:#General Feedback qpl#:#qpl_page_type_qfbs#:#Special Feedback qpl#:#qpl_page_type_qht#:#Hint qpl#:#qpl_page_type_qpl#:#Question Page +qsts#:#answer_options#:#Možnosti odgovora +qsts#:#cloze_text#:#Vprašanje s praznino v besedilu +qsts#:#cloze_textgapcase_insensitive#:#Ni razlikovanja med velikimi in malimi črkami +qsts#:#cloze_textgapcase_sensitive#:#Upoštevaj razlikovanje velikih in malih črk +qsts#:#cloze_textgaplevenshtein_of#:#Levenshteinov razmik v %s +qsts#:#confirm_delete_questions#:#Ali ste prepričani, da želite odstraniti naslednja vprašanja? +qsts#:#create_question#:#Ustvari vprašanje +qsts#:#gap#:#Praznina +qsts#:#gaps#:#Gaps###29 10 2025 new variable +qsts#:#insert_gap#:#Vstavi praznino +qsts#:#min_auto_complete#:#Samodejno dopolnjevanje +qsts#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable +qsts#:#out_of_range#:#Izven področja +qsts#:#qst_lifecycle#:#Lifecycle +qsts#:#qst_lifecycle_draft#:#Draft +qsts#:#qst_lifecycle_filter_all#:#All Lifecycles +qsts#:#qst_lifecycle_final#:#Final +qsts#:#qst_lifecycle_outdated#:#Outdated +qsts#:#qst_lifecycle_rejected#:#Rejected +qsts#:#qst_lifecycle_review#:#To be Reviewed +qsts#:#qst_lifecycle_sharable#:#Sharable +qsts#:#questionlist#:#Questionlist###26 08 2024 new variable +qsts#:#questions#:#Questions###26 08 2024 new variable +qsts#:#range_lower_limit#:#Spodnja meja +qsts#:#range_upper_limit#:#Zgornja meja +qsts#:#reset_preview#:#Ponastavite predogled +qsts#:#select_gap#:#Izbrana praznina +qsts#:#shuffle_answers#:#Pomešaj odgovore +qsts#:#suggested_learning_content#:#Vsebine za ponavljanje rating#:#rat_not_rated_yet#:#Not Rated Yet rating#:#rat_nr_ratings#:#%s Ratings rating#:#rat_one_rating#:#One Rating @@ -16620,7 +16620,6 @@ survey#:#questionblock#:#Blok vprašanj survey#:#questionblock_inserted#:#Blok vprašanj je vstavljen survey#:#questionblocks#:#Bloki vprašanj survey#:#questionblocks_inserted#:#Bloki vprašanj so vstavljeni -survey#:#questions#:#Vprašanja survey#:#questions_inserted#:#Vstavljeno/-a vprašanje/-a! survey#:#questions_removed#:#Vprašanje/-a in/ali bloki vprašanj je bilo/so bili odstranjeno/-i! survey#:#questiontype#:#Tip vprašanja @@ -16921,6 +16920,7 @@ survey#:#svy_please_select_unused_codes#:#Please select at least one unused code survey#:#svy_print_hide_labels#:#Skrij oznake survey#:#svy_print_show_labels#:#Prikaži oznake survey#:#svy_privacy_info#:#Privacy###19 08 2022 new variable +survey#:#svy_questions#:#Vprašanja survey#:#svy_rater#:#Rater###19 08 2022 new variable survey#:#svy_rater_see_app_info#:#The names of appraisees will be presented to raters to enable them evaluating the questions.###19 08 2022 new variable survey#:#svy_reminder_mail_template#:#Predloga za e-pošto diff --git a/lang/ilias_sq.lang b/lang/ilias_sq.lang index 4ac3b6b07264..4227dbadd080 100644 --- a/lang/ilias_sq.lang +++ b/lang/ilias_sq.lang @@ -422,7 +422,6 @@ adve#:#adve_use_tiny_mce#:#Enable TinyMCE for WYSIWYG Editing###01 10 2011 new v assessment#:#activate_logging#:#Aktivizo testin&Ruaj aktivitetin e vlerësimit assessment#:#activate_manual_scoring#:#Enable Manual Scoring###28 10 2024 new variable assessment#:#activate_manual_scoring_desc#:#Enables manual Scoring for all question types.###28 10 2024 new variable -assessment#:#addSuggestedSolution#:#Add suggested solution###24 02 2009 new variable assessment#:#add_answers#:#Add Antworten ###23 12 2015 new variable assessment#:#add_circle#:#Add circle area###24 07 2009 new variable assessment#:#add_gap#:#Shto zbrazëtirë për tekst @@ -447,7 +446,6 @@ assessment#:#answer_is_not_correct_but_positive#:#You've got points for your sol assessment#:#answer_is_right#:#Zgjidhja juaj është e drejtë###28 Jul 2006 content changed assessment#:#answer_is_wrong#:#Zgjidhja juaj është e padrejtë assessment#:#answer_of#:#Answer of###07 02 2020 new variable -assessment#:#answer_options#:#Answer Options: ###23 12 2015 new variable assessment#:#answer_question#:#Answer Question###30 08 2015 new variable assessment#:#answer_text#:#Përgjigju tekstit assessment#:#answer_types#:#Answer Types###24 07 2009 new variable @@ -501,7 +499,6 @@ assessment#:#ass_completion_by_submission#:#Completed by Submission###12 06 2011 assessment#:#ass_completion_by_submission_info#:#If enabled, the submission of at least one file causes the completion of this question by granting the maximum score for this question. The score could be manually changed later. Switching this setting does not effect already submitted solutions.###12 06 2011 new variable assessment#:#ass_create_export_file_with_results#:#Create Test Export File (incl. Participant Results)###25 10 2016 new variable assessment#:#ass_create_export_test_archive#:#Create Test Archive File###24 10 2013 new variable -assessment#:#ass_create_question#:#Create Question###24 10 2013 new variable assessment#:#ass_imap_hint#:#Hint to be shown as Tooltip###25 10 2016 new variable assessment#:#ass_imap_map_file_not_readable#:#The uploaded image map could not be read.###25 10 2016 new variable assessment#:#ass_imap_no_map_found#:#Could not find any form in the uploaded image map.###25 10 2016 new variable @@ -571,10 +568,6 @@ assessment#:#cloze_answer_text_info#:#Spaces preceding or following the answer t assessment#:#cloze_fixed_textlength#:#Text Field Length###02 04 2007 new variable assessment#:#cloze_fixed_textlength_description#:#If you enter a value greater than 0, all text and numeric gap text fields will be created with the fixed length of this value.###02 04 2007 new variable assessment#:#cloze_gap_size_info#:#If you enter a value greater than 0, this gap text field will be created with the fixed length of this value. If you do not enter a value the gap text fiel will be created with the value of the global fixed length.###26 09 2014 new variable -assessment#:#cloze_text#:#Mbyll tekstin###20 Jun 2005 content changed -assessment#:#cloze_textgap_case_insensitive#:#Case insensitive###03 Nov 2005 new variable -assessment#:#cloze_textgap_case_sensitive#:#Case sensitive###03 Nov 2005 new variable -assessment#:#cloze_textgap_levenshtein_of#:#Levenshtein distance of %s###03 Nov 2005 new variable assessment#:#code#:#Kod assessment#:#codebase#:#Codebase###10 Jul 2006 new variable assessment#:#concatenation#:#Concatenation ### 2006-8-11 --- Add new translation here. @@ -757,9 +750,7 @@ assessment#:#fq_formula_desc#:#You may enter predefined variables ($v1 to $vn), assessment#:#fq_no_restriction_info#:#Both decimals and fractions are accepted as input.###26 03 2014 new variable assessment#:#fq_precision_info#:#Enter the number of desired decimal places.###26 03 2014 new variable assessment#:#fq_question_desc#:#You can define variables by inserting $v1, $v2 ... $vn, results by inserting $r1, $r2 .... $rn at the desired position in the question text. Click on the button "Parse Question" to create editing forms for variables and results.###06 11 2013 new variable -assessment#:#gap#:#Zbrazëtirë assessment#:#gap_combination#:#Gap Combination###26 09 2014 new variable -assessment#:#gaps#:#Gaps###29 10 2025 new variable assessment#:#glossary_term#:#Term nga fjalorthi assessment#:#goto_first_question#:#Show First Question###29 10 2025 new variable assessment#:#grading_mark_msg#:#Your resulting mark is: "[mark]"###26 09 2014 new variable @@ -777,7 +768,6 @@ assessment#:#info_answer_type_change#:#The question already contains images. You assessment#:#info_text_upload#:#Choose an answer file to upload###30 08 2015 new variable assessment#:#insert_after#:#Vendos pas assessment#:#insert_before#:#Vendos para -assessment#:#insert_gap#:#Insert Gap###24 10 2013 new variable assessment#:#interaction_type#:#Interaction Type###26 08 2024 new variable assessment#:#internal_links#:#Vendos linqe assessment#:#intprecision#:#Divisible By###24 10 2013 new variable @@ -889,7 +879,6 @@ assessment#:#logs_wrong_test_password_provided#:#Participant entered wrong test assessment#:#longmenu#:#Longmenu###10 11 2018 new variable assessment#:#longmenu_answeroptions_differ#:#This question does not work correctly, as there are not the same amount of gaps in the text as in the correction options.###26 08 2024 new variable assessment#:#longmenu_text#:#Long Menu Text###30 08 2015 new variable -assessment#:#mainbar_button_label_questionlist#:#Questionlist###26 08 2024 new variable assessment#:#maintenance#:#Mirëmbajtje assessment#:#manscoring#:#Manual Scoring###12 11 2006 new variable assessment#:#manscoring_done#:#Scored Participants###24 02 2009 new variable @@ -916,7 +905,6 @@ assessment#:#maximum_nr_of_tries_reached#:#Keni arritur numrin maksimal të tent assessment#:#maximum_points#:#Maxium available points###10 Jul 2006 new variable assessment#:#maxsize#:#Maximum file upload size ###24 02 2009 new variable assessment#:#maxsize_info#:#Enter the maximum size in bytes that should be allowed for file uploads. If you leave this field empty, the maximum size of this installation will be chosen instead.###24 02 2009 new variable -assessment#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable assessment#:#min_ip_label#:#Lowest IP With Access###26 08 2024 new variable assessment#:#min_percentage_ne_0#:#Duhet të definoni minimumin e përqindjes të 0 përqind! Skema e markuar nuk është ruajtur.###10 Jul 2006 content changed assessment#:#misc#:#Misc Options###24 10 2013 new variable @@ -925,7 +913,6 @@ assessment#:#mode_onebyone#:#One by One###29 10 2025 new variable assessment#:#mode_question#:#Question oriented###29 10 2025 new variable assessment#:#mode_user#:#Participant oriented###29 10 2025 new variable assessment#:#msg_circle_added#:#Circle added###24 07 2009 new variable -assessment#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable assessment#:#msg_number_of_terms_too_low#:#The number of terms must be greater or equal to the number of definitions.###12 08 2009 new variable assessment#:#msg_poly_added#:#Polygon added###24 07 2009 new variable assessment#:#msg_questions_moved#:#Question(s) moved###06 08 2009 new variable @@ -984,7 +971,6 @@ assessment#:#order#:#Order###26 08 2024 new variable assessment#:#ordering_answer_sequence_info#:#The answer sequence you define here will be taken as the correct solution sequence.###10 09 2010 new variable assessment#:#ordertext#:#Ordering Text###24 02 2009 new variable assessment#:#ordertext_info#:#Please enter the text that should be ordered horizontally. The ordering text will be separated by the whitespace signs in the text. If you need a different separation, you may use the separator %s to separate your text units.###24 02 2009 new variable -assessment#:#out_of_range#:#Out of range###27 01 2015 new variable assessment#:#output#:#Output###25 10 2006 new variable assessment#:#output_mode#:#Output mode###10 Jul 2006 new variable assessment#:#parseQuestion#:#Parse Question###24 10 2013 new variable @@ -1053,7 +1039,6 @@ assessment#:#qpl_bulk_save_add#:#Add###28 10 2024 new variable assessment#:#qpl_bulk_save_overwrite#:#Overwrite###28 10 2024 new variable assessment#:#qpl_bulkedit_success#:#Modifications saved.###28 10 2024 new variable assessment#:#qpl_cancel_skill_assigns_update#:#Cancel###30 08 2015 new variable -assessment#:#qpl_confirm_delete_questions#:#A jeni të sigurt për të fshirë pyetjen/pyetjet ? Nëse i fshini pyetjet e mbyllura po ashtu do të fshihen edhe rezultatet e testeve që i përmbajn ato pyetje.###10 Jul 2006 content changed assessment#:#qpl_copy_insert_clipboard#:#The selected question(s) are copied to the clipboard###03 Nov 2005 new variable assessment#:#qpl_copy_select_none#:#Please check at least one question to copy it to the clipboard###03 Nov 2005 new variable assessment#:#qpl_delete_rbac_error#:#Nuk keni të drejta për të fshirë këtë pyetje! @@ -1106,7 +1091,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#Competence###30 08 2015 new variable assessment#:#qpl_qst_skl_usg_sklpnt_col#:#Total Sum of Competence-Points per Competence###30 08 2015 new variable assessment#:#qpl_question_is_in_use#:#Pyetjen që jeni duke e edituar ekziston në %s test(e). Nëse e ndërroni këtë pyetje, nuk do të ndërroni pyetjen/pyetjet në testin/testet, për shkak se sistemi krijon një kopje të pyetjes kur ajo futet në test! assessment#:#qpl_questions_deleted#:#Pyetja/pyetjet janë fshirë -assessment#:#qpl_reset_preview#:#Reset Preview###26 09 2014 new variable assessment#:#qpl_save_skill_assigns_update#:#Save Competence Assignments###30 08 2015 new variable assessment#:#qpl_settings_availability#:#Availability###26 08 2024 new variable assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#When enabled, possibly created taxonomies are shown for filtering.###24 10 2013 new variable @@ -1136,14 +1120,6 @@ assessment#:#qst_essay_chars_remaining#:#Remaining characters:###07 02 2020 new assessment#:#qst_essay_wordcounter_enabled#:#Count Words###07 02 2020 new variable assessment#:#qst_essay_wordcounter_enabled_info#:#The entered words are counted. The number of written words is shown to the participants below the text input field.###07 02 2020 new variable assessment#:#qst_essay_written_words#:#Number of entered words:###07 02 2020 new variable -assessment#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable -assessment#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable -assessment#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable -assessment#:#qst_lifecycle_final#:#Final###07 02 2020 new variable -assessment#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable -assessment#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable -assessment#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable -assessment#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable assessment#:#qst_nested_nested_answers_off#:#No indents, just order###29 07 2022 new variable assessment#:#qst_nested_nested_answers_on#:#Use indents in anwers###29 07 2022 new variable assessment#:#qst_nr_of_tries#:#Number of tries###15 05 2009 new variable @@ -1167,17 +1143,14 @@ assessment#:#question_title#:#Titulli i pyetjes assessment#:#question_type#:#Lloji i pyetjes assessment#:#questionpool_not_entered#:#Ju lutem të emërtoni fondin e pyetjeve! assessment#:#questionpool_not_selected#:#Please select a question pool!###07 02 2020 new variable -assessment#:#questions#:#Questions###26 08 2024 new variable assessment#:#questions_from#:#formë e pyetjeve assessment#:#questions_per_page_view#:#Page View###12 06 2011 new variable assessment#:#random_accept_sample#:#Prano shembullin assessment#:#random_another_sample#:#Merr një shembull tjetër assessment#:#random_selection#:#Përzgjedhje sipas rastit assessment#:#range#:#Range###22 Feb 2006 new variable -assessment#:#range_lower_limit#:#Lower limit###22 Feb 2006 new variable assessment#:#range_max#:#Range (Maximum)###24 10 2013 new variable assessment#:#range_min#:#Range (Minimum)###24 10 2013 new variable -assessment#:#range_upper_limit#:#Upper limit###22 Feb 2006 new variable assessment#:#rated_sign#:#Sign###24 10 2013 new variable assessment#:#rated_unit#:#Unit###24 10 2013 new variable assessment#:#rated_value#:#Value###24 10 2013 new variable @@ -1253,7 +1226,6 @@ assessment#:#search_roles#:#Search Roles ### 2006-8-11 --- Add new translation h assessment#:#search_term#:#Search Term ### 2006-8-11 --- Add new translation here. assessment#:#select_at_least_one_feedback_type_and_trigger#:#Please Select at least one type of feedback and a trigger.###26 08 2024 new variable assessment#:#select_at_least_one_lock_answer_type#:#Please select at least one type of answer lock.###29 10 2025 new variable -assessment#:#select_gap#:#Zgjedh zbrazëtirë assessment#:#select_max_one_item#:#Ju lutem të zgjedhni vetëm një artikull assessment#:#select_one_user#:#Please select at least one user###23 Dec 2005 new variable assessment#:#select_question#:#Select a Question###28 10 2024 new variable @@ -1280,7 +1252,6 @@ assessment#:#show_old_introduction#:#Show old introduction###26 08 2024 new vari assessment#:#show_pass_overview#:#Show Marked Pass Overview###31 05 2007 new variable assessment#:#show_results#:#Show Results###28 10 2024 new variable assessment#:#show_user_answers#:#Show User's Marked Anwers###31 05 2007 new variable -assessment#:#shuffle_answers#:#Përziej përgjigjet assessment#:#skip_question#:#Do not Answer and Next###08 10 2015 new variable assessment#:#solution#:#Solution###28 10 2024 new variable assessment#:#solutionText#:#Text###24 02 2009 new variable @@ -13971,6 +13942,35 @@ qpl#:#qpl_page_type_qfbg#:#General Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qfbs#:#Special Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qht#:#Hint###29 07 2022 new variable qpl#:#qpl_page_type_qpl#:#Question Page###29 07 2022 new variable +qsts#:#answer_options#:#Answer Options ###23 12 2015 new variable +qsts#:#cloze_text#:#Mbyll tekstin###20 Jun 2005 content changed +qsts#:#cloze_textgapcase_insensitive#:#Case insensitive###03 Nov 2005 new variable +qsts#:#cloze_textgapcase_sensitive#:#Case sensitive###03 Nov 2005 new variable +qsts#:#cloze_textgaplevenshtein_of#:#Levenshtein distance of %s###03 Nov 2005 new variable +qsts#:#confirm_delete_questions#:#A jeni të sigurt për të fshirë pyetjen/pyetjet ? Nëse i fshini pyetjet e mbyllura po ashtu do të fshihen edhe rezultatet e testeve që i përmbajn ato pyetje.###10 Jul 2006 content changed +qsts#:#create_question#:#Create Question###24 10 2013 new variable +qsts#:#gap#:#Zbrazëtirë +qsts#:#gaps#:#Gaps###29 10 2025 new variable +qsts#:#insert_gap#:#Insert Gap###24 10 2013 new variable +qsts#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable +qsts#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable +qsts#:#out_of_range#:#Out of range###27 01 2015 new variable +qsts#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable +qsts#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable +qsts#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable +qsts#:#qst_lifecycle_final#:#Final###07 02 2020 new variable +qsts#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable +qsts#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable +qsts#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable +qsts#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable +qsts#:#questionlist#:#Questionlist###26 08 2024 new variable +qsts#:#questions#:#Questions###26 08 2024 new variable +qsts#:#range_lower_limit#:#Lower limit###22 Feb 2006 new variable +qsts#:#range_upper_limit#:#Upper limit###22 Feb 2006 new variable +qsts#:#reset_preview#:#Reset Preview###26 09 2014 new variable +qsts#:#select_gap#:#Zgjedh zbrazëtirë +qsts#:#shuffle_answers#:#Përziej përgjigjet +qsts#:#suggested_learning_content#:#Add suggested solution###24 02 2009 new variable rating#:#rat_not_rated_yet#:#Not Rated Yet###12 06 2011 new variable rating#:#rat_nr_ratings#:#%s Ratings###12 06 2011 new variable rating#:#rat_one_rating#:#One Rating###12 06 2011 new variable @@ -16620,7 +16620,6 @@ survey#:#questionblock#:#Grupi i pyetjeve survey#:#questionblock_inserted#:#Question Block inserted###06 08 2009 new variable survey#:#questionblocks#:#Grupet e pyetjeve survey#:#questionblocks_inserted#:#Question Blocks inserted###06 08 2009 new variable -survey#:#questions#:#Pyetjet survey#:#questions_inserted#:#Pyetjet janë vendosur! survey#:#questions_removed#:#Pyetjet dhe/ose grupet e pyetjeve janë fshirë! survey#:#questiontype#:#Tipi i pyetjes @@ -16921,6 +16920,7 @@ survey#:#svy_please_select_unused_codes#:#Please select at least one unused code survey#:#svy_print_hide_labels#:#Hide labels###31 08 2017 new variable survey#:#svy_print_show_labels#:#Show labels###31 08 2017 new variable survey#:#svy_privacy_info#:#Privacy###29 07 2022 new variable +survey#:#svy_questions#:#Pyetjet survey#:#svy_rater#:#Rater###29 07 2022 new variable survey#:#svy_rater_see_app_info#:#The names of appraisees will be presented to raters to enable them evaluating the questions.###29 07 2022 new variable survey#:#svy_reminder_mail_template#:#Mail Template###25 10 2016 new variable diff --git a/lang/ilias_sr.lang b/lang/ilias_sr.lang index 07aba9eaa1af..90c1ffe1245d 100644 --- a/lang/ilias_sr.lang +++ b/lang/ilias_sr.lang @@ -422,7 +422,6 @@ adve#:#adve_use_tiny_mce#:#Enable TinyMCE for WYSIWYG Editing###01 10 2011 new v assessment#:#activate_logging#:#Aktiviraj test i proveru prijave assessment#:#activate_manual_scoring#:#Enable Manual Scoring###28 10 2024 new variable assessment#:#activate_manual_scoring_desc#:#Enables manual Scoring for all question types.###28 10 2024 new variable -assessment#:#addSuggestedSolution#:#Add suggested solution###24 02 2009 new variable assessment#:#add_answers#:#Add Antworten ###23 12 2015 new variable assessment#:#add_circle#:#Add circle area###24 07 2009 new variable assessment#:#add_gap#:#dodajte razmak u tekstu @@ -447,7 +446,6 @@ assessment#:#answer_is_not_correct_but_positive#:#You've got points for your sol assessment#:#answer_is_right#:#Vaše rešenje je ispravno###28 Jul 2006 content changed assessment#:#answer_is_wrong#:#Vaše rešenje je pogrešno assessment#:#answer_of#:#Answer of###07 02 2020 new variable -assessment#:#answer_options#:#Answer Options: ###23 12 2015 new variable assessment#:#answer_question#:#Answer Question###30 08 2015 new variable assessment#:#answer_text#:#Tekst odgovora assessment#:#answer_types#:#Answer Types###24 07 2009 new variable @@ -501,7 +499,6 @@ assessment#:#ass_completion_by_submission#:#Completed by Submission###12 06 2011 assessment#:#ass_completion_by_submission_info#:#If enabled, the submission of at least one file causes the completion of this question by granting the maximum score for this question. The score could be manually changed later. Switching this setting does not effect already submitted solutions.###12 06 2011 new variable assessment#:#ass_create_export_file_with_results#:#Create Test Export File (incl. Participant Results)###25 10 2016 new variable assessment#:#ass_create_export_test_archive#:#Create Test Archive File###24 10 2013 new variable -assessment#:#ass_create_question#:#Create Question###24 10 2013 new variable assessment#:#ass_imap_hint#:#Hint to be shown as Tooltip###25 10 2016 new variable assessment#:#ass_imap_map_file_not_readable#:#The uploaded image map could not be read.###25 10 2016 new variable assessment#:#ass_imap_no_map_found#:#Could not find any form in the uploaded image map.###25 10 2016 new variable @@ -571,10 +568,6 @@ assessment#:#cloze_answer_text_info#:#Spaces preceding or following the answer t assessment#:#cloze_fixed_textlength#:#Text Field Length###02 04 2007 new variable assessment#:#cloze_fixed_textlength_description#:#If you enter a value greater than 0, all text and numeric gap text fields will be created with the fixed length of this value.###02 04 2007 new variable assessment#:#cloze_gap_size_info#:#If you enter a value greater than 0, this gap text field will be created with the fixed length of this value. If you do not enter a value the gap text fiel will be created with the value of the global fixed length.###26 09 2014 new variable -assessment#:#cloze_text#:#Zatvori tekst###20 Jun 2005 content changed -assessment#:#cloze_textgap_case_insensitive#:#Case insensitive###03 Nov 2005 new variable -assessment#:#cloze_textgap_case_sensitive#:#Case sensitive###03 Nov 2005 new variable -assessment#:#cloze_textgap_levenshtein_of#:#Levenshtein distance of %s###03 Nov 2005 new variable assessment#:#code#:#Kod assessment#:#codebase#:#Codebase###10 Jul 2006 new variable assessment#:#concatenation#:#Concatenation ### 2006-8-11 --- Add new translation here. @@ -757,9 +750,7 @@ assessment#:#fq_formula_desc#:#You may enter predefined variables ($v1 to $vn), assessment#:#fq_no_restriction_info#:#Both decimals and fractions are accepted as input.###26 03 2014 new variable assessment#:#fq_precision_info#:#Enter the number of desired decimal places.###26 03 2014 new variable assessment#:#fq_question_desc#:#You can define variables by inserting $v1, $v2 ... $vn, results by inserting $r1, $r2 .... $rn at the desired position in the question text. Click on the button "Parse Question" to create editing forms for variables and results.###06 11 2013 new variable -assessment#:#gap#:#Razmak assessment#:#gap_combination#:#Gap Combination###26 09 2014 new variable -assessment#:#gaps#:#Gaps###29 10 2025 new variable assessment#:#glossary_term#:#Recnik termina assessment#:#goto_first_question#:#Show First Question###29 10 2025 new variable assessment#:#grading_mark_msg#:#Your resulting mark is: "[mark]"###26 09 2014 new variable @@ -777,7 +768,6 @@ assessment#:#info_answer_type_change#:#The question already contains images. You assessment#:#info_text_upload#:#Choose an answer file to upload###30 08 2015 new variable assessment#:#insert_after#:#Ubacite posle assessment#:#insert_before#:#Ubacite pre -assessment#:#insert_gap#:#Insert Gap###24 10 2013 new variable assessment#:#interaction_type#:#Interaction Type###26 08 2024 new variable assessment#:#internal_links#:#Unutrašnje veze assessment#:#intprecision#:#Divisible By###24 10 2013 new variable @@ -889,7 +879,6 @@ assessment#:#logs_wrong_test_password_provided#:#Participant entered wrong test assessment#:#longmenu#:#Longmenu###10 11 2018 new variable assessment#:#longmenu_answeroptions_differ#:#This question does not work correctly, as there are not the same amount of gaps in the text as in the correction options.###26 08 2024 new variable assessment#:#longmenu_text#:#Long Menu Text###30 08 2015 new variable -assessment#:#mainbar_button_label_questionlist#:#Questionlist###26 08 2024 new variable assessment#:#maintenance#:#Održavanje assessment#:#manscoring#:#Manual Scoring###12 11 2006 new variable assessment#:#manscoring_done#:#Scored Participants###24 02 2009 new variable @@ -916,7 +905,6 @@ assessment#:#maximum_nr_of_tries_reached#:#Dostigli ste maksimalan broj pokušaj assessment#:#maximum_points#:#Maxium available points###10 Jul 2006 new variable assessment#:#maxsize#:#Maximum file upload size ###24 02 2009 new variable assessment#:#maxsize_info#:#Enter the maximum size in bytes that should be allowed for file uploads. If you leave this field empty, the maximum size of this installation will be chosen instead.###24 02 2009 new variable -assessment#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable assessment#:#min_ip_label#:#Lowest IP With Access###26 08 2024 new variable assessment#:#min_percentage_ne_0#:#Morate definisati minimalan procenat od 0 procenata! Šema ocena nije sacuvana.###10 Jul 2006 content changed assessment#:#misc#:#Misc Options###24 10 2013 new variable @@ -925,7 +913,6 @@ assessment#:#mode_onebyone#:#One by One###29 10 2025 new variable assessment#:#mode_question#:#Question oriented###29 10 2025 new variable assessment#:#mode_user#:#Participant oriented###29 10 2025 new variable assessment#:#msg_circle_added#:#Circle added###24 07 2009 new variable -assessment#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable assessment#:#msg_number_of_terms_too_low#:#The number of terms must be greater or equal to the number of definitions.###12 08 2009 new variable assessment#:#msg_poly_added#:#Polygon added###24 07 2009 new variable assessment#:#msg_questions_moved#:#Question(s) moved###06 08 2009 new variable @@ -984,7 +971,6 @@ assessment#:#order#:#Order###26 08 2024 new variable assessment#:#ordering_answer_sequence_info#:#The answer sequence you define here will be taken as the correct solution sequence.###10 09 2010 new variable assessment#:#ordertext#:#Ordering Text###24 02 2009 new variable assessment#:#ordertext_info#:#Please enter the text that should be ordered horizontally. The ordering text will be separated by the whitespace signs in the text. If you need a different separation, you may use the separator %s to separate your text units.###24 02 2009 new variable -assessment#:#out_of_range#:#Out of range###27 01 2015 new variable assessment#:#output#:#Output###25 10 2006 new variable assessment#:#output_mode#:#Output mode###10 Jul 2006 new variable assessment#:#parseQuestion#:#Parse Question###24 10 2013 new variable @@ -1053,7 +1039,6 @@ assessment#:#qpl_bulk_save_add#:#Add###28 10 2024 new variable assessment#:#qpl_bulk_save_overwrite#:#Overwrite###28 10 2024 new variable assessment#:#qpl_bulkedit_success#:#Modifications saved.###28 10 2024 new variable assessment#:#qpl_cancel_skill_assigns_update#:#Cancel###30 08 2015 new variable -assessment#:#qpl_confirm_delete_questions#:#Da li ste sigurni da želite da obrišete sledece(a) pitanje(a)? Ukoliko obrišete zakljucana pitanja, rezultati svih testova koji sadrže zakljucana pitanja bice takode obrisani.###10 Jul 2006 content changed assessment#:#qpl_copy_insert_clipboard#:#The selected question(s) are copied to the clipboard###03 Nov 2005 new variable assessment#:#qpl_copy_select_none#:#Please check at least one question to copy it to the clipboard###03 Nov 2005 new variable assessment#:#qpl_delete_rbac_error#:#Nemate prava da obrišete ovo pitanje! @@ -1106,7 +1091,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#Competence###30 08 2015 new variable assessment#:#qpl_qst_skl_usg_sklpnt_col#:#Total Sum of Competence-Points per Competence###30 08 2015 new variable assessment#:#qpl_question_is_in_use#:#Pitanje koje nameravate da izmenite nalazi se u %s testova. Ukoliko promenite ovo pitanje, NECETE promeniti pitanje(a) u testu/testovima, zato što sistem pravi kopije pitanja kada su una ubacena u test! assessment#:#qpl_questions_deleted#:#Pitanje(a) obrisano(a). -assessment#:#qpl_reset_preview#:#Reset Preview###26 09 2014 new variable assessment#:#qpl_save_skill_assigns_update#:#Save Competence Assignments###30 08 2015 new variable assessment#:#qpl_settings_availability#:#Availability###26 08 2024 new variable assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#When enabled, possibly created taxonomies are shown for filtering.###24 10 2013 new variable @@ -1136,14 +1120,6 @@ assessment#:#qst_essay_chars_remaining#:#Remaining characters:###07 02 2020 new assessment#:#qst_essay_wordcounter_enabled#:#Count Words###07 02 2020 new variable assessment#:#qst_essay_wordcounter_enabled_info#:#The entered words are counted. The number of written words is shown to the participants below the text input field.###07 02 2020 new variable assessment#:#qst_essay_written_words#:#Number of entered words:###07 02 2020 new variable -assessment#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable -assessment#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable -assessment#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable -assessment#:#qst_lifecycle_final#:#Final###07 02 2020 new variable -assessment#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable -assessment#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable -assessment#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable -assessment#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable assessment#:#qst_nested_nested_answers_off#:#No indents, just order###29 07 2022 new variable assessment#:#qst_nested_nested_answers_on#:#Use indents in anwers###29 07 2022 new variable assessment#:#qst_nr_of_tries#:#Number of tries###15 05 2009 new variable @@ -1167,17 +1143,14 @@ assessment#:#question_title#:#Naziv pitanja assessment#:#question_type#:#Vrsta pitanja assessment#:#questionpool_not_entered#:#Molimo unesite naziv bazu pitanja! assessment#:#questionpool_not_selected#:#Please select a question pool!###07 02 2020 new variable -assessment#:#questions#:#Questions###26 08 2024 new variable assessment#:#questions_from#:#pitanje iz assessment#:#questions_per_page_view#:#Page View###12 06 2011 new variable assessment#:#random_accept_sample#:#Prihvati primer assessment#:#random_another_sample#:#Uzmi drugi primer assessment#:#random_selection#:#Slucajni izbor assessment#:#range#:#Range###22 Feb 2006 new variable -assessment#:#range_lower_limit#:#Lower limit###22 Feb 2006 new variable assessment#:#range_max#:#Range (Maximum)###24 10 2013 new variable assessment#:#range_min#:#Range (Minimum)###24 10 2013 new variable -assessment#:#range_upper_limit#:#Upper limit###22 Feb 2006 new variable assessment#:#rated_sign#:#Sign###24 10 2013 new variable assessment#:#rated_unit#:#Unit###24 10 2013 new variable assessment#:#rated_value#:#Value###24 10 2013 new variable @@ -1253,7 +1226,6 @@ assessment#:#search_roles#:#Search Roles ### 2006-8-11 --- Add new translation h assessment#:#search_term#:#Search Term ### 2006-8-11 --- Add new translation here. assessment#:#select_at_least_one_feedback_type_and_trigger#:#Please Select at least one type of feedback and a trigger.###26 08 2024 new variable assessment#:#select_at_least_one_lock_answer_type#:#Please select at least one type of answer lock.###29 10 2025 new variable -assessment#:#select_gap#:#Odaberite razmak assessment#:#select_max_one_item#:#Molimo odaberite samo jedno polje assessment#:#select_one_user#:#Please select at least one user###23 Dec 2005 new variable assessment#:#select_question#:#Select a Question###28 10 2024 new variable @@ -1280,7 +1252,6 @@ assessment#:#show_old_introduction#:#Show old introduction###26 08 2024 new vari assessment#:#show_pass_overview#:#Show Marked Pass Overview###31 05 2007 new variable assessment#:#show_results#:#Show Results###28 10 2024 new variable assessment#:#show_user_answers#:#Show User's Marked Anwers###31 05 2007 new variable -assessment#:#shuffle_answers#:#Izbegni odgovore assessment#:#skip_question#:#Do not Answer and Next###08 10 2015 new variable assessment#:#solution#:#Solution###28 10 2024 new variable assessment#:#solutionText#:#Text###24 02 2009 new variable @@ -13971,6 +13942,35 @@ qpl#:#qpl_page_type_qfbg#:#General Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qfbs#:#Special Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qht#:#Hint###29 07 2022 new variable qpl#:#qpl_page_type_qpl#:#Question Page###29 07 2022 new variable +qsts#:#answer_options#:#Answer Options ###23 12 2015 new variable +qsts#:#cloze_text#:#Zatvori tekst###20 Jun 2005 content changed +qsts#:#cloze_textgapcase_insensitive#:#Case insensitive###03 Nov 2005 new variable +qsts#:#cloze_textgapcase_sensitive#:#Case sensitive###03 Nov 2005 new variable +qsts#:#cloze_textgaplevenshtein_of#:#Levenshtein distance of %s###03 Nov 2005 new variable +qsts#:#confirm_delete_questions#:#Da li ste sigurni da želite da obrišete sledece(a) pitanje(a)? Ukoliko obrišete zakljucana pitanja, rezultati svih testova koji sadrže zakljucana pitanja bice takode obrisani.###10 Jul 2006 content changed +qsts#:#create_question#:#Create Question###24 10 2013 new variable +qsts#:#gap#:#Razmak +qsts#:#gaps#:#Gaps###29 10 2025 new variable +qsts#:#insert_gap#:#Insert Gap###24 10 2013 new variable +qsts#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable +qsts#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable +qsts#:#out_of_range#:#Out of range###27 01 2015 new variable +qsts#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable +qsts#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable +qsts#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable +qsts#:#qst_lifecycle_final#:#Final###07 02 2020 new variable +qsts#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable +qsts#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable +qsts#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable +qsts#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable +qsts#:#questionlist#:#Questionlist###26 08 2024 new variable +qsts#:#questions#:#Questions###26 08 2024 new variable +qsts#:#range_lower_limit#:#Lower limit###22 Feb 2006 new variable +qsts#:#range_upper_limit#:#Upper limit###22 Feb 2006 new variable +qsts#:#reset_preview#:#Reset Preview###26 09 2014 new variable +qsts#:#select_gap#:#Odaberite razmak +qsts#:#shuffle_answers#:#Izbegni odgovore +qsts#:#suggested_learning_content#:#Add suggested solution###24 02 2009 new variable rating#:#rat_not_rated_yet#:#Not Rated Yet###12 06 2011 new variable rating#:#rat_nr_ratings#:#%s Ratings###12 06 2011 new variable rating#:#rat_one_rating#:#One Rating###12 06 2011 new variable @@ -16620,7 +16620,6 @@ survey#:#questionblock#:#Blok pitanja survey#:#questionblock_inserted#:#Question Block inserted###06 08 2009 new variable survey#:#questionblocks#:#Blokovi pitanja survey#:#questionblocks_inserted#:#Question Blocks inserted###06 08 2009 new variable -survey#:#questions#:#Pitanja survey#:#questions_inserted#:#Pitanja ubacena! survey#:#questions_removed#:#Pitanja i/ili blok pitanja je uklonjen! survey#:#questiontype#:#Vrsta pitanja @@ -16921,6 +16920,7 @@ survey#:#svy_please_select_unused_codes#:#Please select at least one unused code survey#:#svy_print_hide_labels#:#Hide labels###31 08 2017 new variable survey#:#svy_print_show_labels#:#Show labels###31 08 2017 new variable survey#:#svy_privacy_info#:#Privacy###29 07 2022 new variable +survey#:#svy_questions#:#Pitanja survey#:#svy_rater#:#Rater###29 07 2022 new variable survey#:#svy_rater_see_app_info#:#The names of appraisees will be presented to raters to enable them evaluating the questions.###29 07 2022 new variable survey#:#svy_reminder_mail_template#:#Mail Template###25 10 2016 new variable diff --git a/lang/ilias_sv.lang b/lang/ilias_sv.lang index 552360765072..173e0180d12f 100644 --- a/lang/ilias_sv.lang +++ b/lang/ilias_sv.lang @@ -422,7 +422,6 @@ adve#:#adve_use_tiny_mce#:#Aktivera TinyMCE för WYSIWYG-redigering assessment#:#activate_logging#:#Aktivera loggning av test och bedömning assessment#:#activate_manual_scoring#:#Enable Manual Scoring###28 10 2024 new variable assessment#:#activate_manual_scoring_desc#:#Enables manual Scoring for all question types.###28 10 2024 new variable -assessment#:#addSuggestedSolution#:#Innehåll för repetition assessment#:#add_answers#:#Lägg till svar assessment#:#add_circle#:#Lägg till cirkel assessment#:#add_gap#:#Lägg till cloze @@ -501,7 +500,6 @@ assessment#:#ass_completion_by_submission#:#Passera genom att lämna in assessment#:#ass_completion_by_submission_info#:#Om den är aktiverad leder inlämnandet av en lösningsfil till att maximalt antal poäng tilldelas för denna fråga. Poängen kan justeras manuellt när som helst. Ändring av denna inställning har ingen efterföljande effekt på redan inlämnade lösningar. assessment#:#ass_create_export_file_with_results#:#Skapa exportfil (inkl. deltagarresultat) assessment#:#ass_create_export_test_archive#:#Skapa arkivfil för test -assessment#:#ass_create_question#:#Skapa fråga assessment#:#ass_imap_hint#:#Note (visas som verktygstips) assessment#:#ass_imap_map_file_not_readable#:#Den uppladdade bildkartan kan inte läsas. assessment#:#ass_imap_no_map_found#:#Ingen stödd form kunde hittas i den uppladdade bildkartan. @@ -571,10 +569,6 @@ assessment#:#cloze_answer_text_info#:#Spaces preceding or following the answer t assessment#:#cloze_fixed_textlength#:#Längd på textfältet assessment#:#cloze_fixed_textlength_description#:#Om du anger ett värde här skapas textgap som inte definierar ett eget värde för en maximal längd, samt numeriska gap med denna längd, så att det inte är möjligt att ange ett större antal tecken. För numeriska luckor, observera att decimalavgränsaren ocks assessment#:#cloze_gap_size_info#:#Om ett värde större än 0 anges skapas detta mellanrum med den längd som anges här. Om inget värde anges skapas detta mellanrum med den globalt angivna textfältslängden. -assessment#:#cloze_text#:#Cloze fråga -assessment#:#cloze_textgap_case_insensitive#:#Ingen skillnad görs mellan stora och små bokstäver -assessment#:#cloze_textgap_case_sensitive#:#Känsliga gemener -assessment#:#cloze_textgap_levenshtein_of#:#Levenshtein avstånd från %s assessment#:#code#:#Kod assessment#:#codebase#:#Codebase assessment#:#concatenation#:#Länk @@ -757,9 +751,7 @@ assessment#:#fq_formula_desc#:#Tillåtet är användning av redan definierade va assessment#:#fq_no_restriction_info#:#Både decimaltal och bråktal accepteras som indata. assessment#:#fq_precision_info#:#Ange det antal decimaler du vill ha här. assessment#:#fq_question_desc#:#Du definierar variabler genom att ange $v1, $v2 ... $vn, resultatfält med $r1, $r2 .... $rn på önskade positioner i texten. Klicka sedan på knappen "Analysera fråga" för att generera redigeringsformulär för alla variabler och resultat. -assessment#:#gap#:#Gap assessment#:#gap_combination#:#Cloze kombination -assessment#:#gaps#:#Gaps###29 10 2025 new variable assessment#:#glossary_term#:#Glossarisk term assessment#:#goto_first_question#:#Show First Question###29 10 2025 new variable assessment#:#grading_mark_msg#:#Du har uppnått märket "[märke]". @@ -777,7 +769,6 @@ assessment#:#info_answer_type_change#:#Frågan innehåller redan bilder. Fråget assessment#:#info_text_upload#:#Välj en textfil (UTF-8) med svar som ska laddas upp. assessment#:#insert_after#:#Insätt bakom assessment#:#insert_before#:#Infoga före -assessment#:#insert_gap#:#Insert gap assessment#:#interaction_type#:#Interaction Type###26 08 2024 new variable assessment#:#internal_links#:#Interna referenser assessment#:#intprecision#:#Kan delas av @@ -889,7 +880,6 @@ assessment#:#logs_wrong_test_password_provided#:#Deltagaren har angett fel testl assessment#:#longmenu#:#Lång meny assessment#:#longmenu_answeroptions_differ#:#Den här frågan fungerar inte korrekt eftersom den inte har samma antal luckor i texten som i svarsalternativen. assessment#:#longmenu_text#:#"Lång meny" text -assessment#:#mainbar_button_label_questionlist#:#Questionlist###21 11 2023 new variable assessment#:#maintenance#:#Underhåll assessment#:#manscoring#:#Manuell bedömning assessment#:#manscoring_done#:#Redan betygsatta deltagare @@ -916,7 +906,6 @@ assessment#:#maximum_nr_of_tries_reached#:#Du har förbrukat det maximala antale assessment#:#maximum_points#:#Maximalt uppnåelig poäng assessment#:#maxsize#:#Maximal filstorlek assessment#:#maxsize_info#:#Anger den maximala storleken i bytes som en uppladdad fil får ha. Om du lämnar fältet tomt används inställningen för det underliggande systemet. -assessment#:#min_auto_complete#:#Autokomplett assessment#:#min_ip_label#:#Lowest IP With Access###26 08 2024 new variable assessment#:#min_percentage_ne_0#:#Du måste ange en lägsta procentsats på 0 procent. Betygsschemat har inte sparats. assessment#:#misc#:#Olika alternativ @@ -925,7 +914,6 @@ assessment#:#mode_onebyone#:#One by One###29 10 2025 new variable assessment#:#mode_question#:#Question oriented###29 10 2025 new variable assessment#:#mode_user#:#Participant oriented###29 10 2025 new variable assessment#:#msg_circle_added#:#Circle tillagd -assessment#:#msg_no_questions_selected#:#No questions were selected.###21 11 2023 new variable assessment#:#msg_number_of_terms_too_low#:#Antalet termer måste vara större än eller lika med antalet definitioner. assessment#:#msg_poly_added#:#Polygon tillagd assessment#:#msg_questions_moved#:#Frågeställningar uppskjutna @@ -984,7 +972,6 @@ assessment#:#order#:#Sortering assessment#:#ordering_answer_sequence_info#:#Den svarsordning som anges här används som korrekt lösningsordning. assessment#:#ordertext#:#Texten ska ordnas assessment#:#ordertext_info#:#Var vänlig ange texten i den ordning som den ska placeras horisontellt. De enskilda komponenterna separeras med mellanslag. Om du vill ha en annan avgränsning kan du använda avgränsaren %s istället för mellanslag. -assessment#:#out_of_range#:#Utanför intervall assessment#:#output#:#Utmatning assessment#:#output_mode#:#Utmatningsläge assessment#:#parseQuestion#:#Analysera frågan @@ -1053,7 +1040,6 @@ assessment#:#qpl_bulk_save_add#:#Add###28 10 2024 new variable assessment#:#qpl_bulk_save_overwrite#:#Overwrite###28 10 2024 new variable assessment#:#qpl_bulkedit_success#:#Modifications saved.###28 10 2024 new variable assessment#:#qpl_cancel_skill_assigns_update#:#Avbryt -assessment#:#qpl_confirm_delete_questions#:#Är du säker på att du vill ta bort följande frågor? assessment#:#qpl_copy_insert_clipboard#:#De valda frågorna har kopierats till Urklipp assessment#:#qpl_copy_select_none#:#Välj minst en fråga för att kopiera den till urklipp! assessment#:#qpl_delete_rbac_error#:#Du har inte tillstånd att ta bort denna fråga! @@ -1106,7 +1092,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#Kompetens assessment#:#qpl_qst_skl_usg_sklpnt_col#:#Summa av alla kompetenspoäng per kompetens assessment#:#qpl_question_is_in_use#:#Frågan du vill redigera nu finns redan i %s test(s). Om du ändrar den här frågan nu kommer det INTE att påverka frågor som redan ingår i test, eftersom systemet automatiskt skapar en kopia av frågan när den ingår i ett test! assessment#:#qpl_questions_deleted#:#Frågor borttagna -assessment#:#qpl_reset_preview#:#Återställ förhandsgranskning assessment#:#qpl_save_skill_assigns_update#:#Spara kompetensuppdrag assessment#:#qpl_settings_availability#:#Availability###21 11 2023 new variable assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#Existerande taxonomier kan användas för att filtrera frågorna. @@ -1136,14 +1121,6 @@ assessment#:#qst_essay_chars_remaining#:#Andra tecken assessment#:#qst_essay_wordcounter_enabled#:#Räkna ord assessment#:#qst_essay_wordcounter_enabled_info#:#De inmatade orden räknas. Antalet ord visas för deltagarna under textinmatningsfältet. assessment#:#qst_essay_written_words#:#Antal inmatade ord -assessment#:#qst_lifecycle#:#Livscykel -assessment#:#qst_lifecycle_draft#:#Draft -assessment#:#qst_lifecycle_filter_all#:#Alla livscykler -assessment#:#qst_lifecycle_final#:#Final -assessment#:#qst_lifecycle_outdated#:#Veraltet -assessment#:#qst_lifecycle_rejected#:#Avvisad -assessment#:#qst_lifecycle_review#:#Revision nödvändig -assessment#:#qst_lifecycle_sharable#:#Distribuerbar assessment#:#qst_nested_nested_answers_off#:# Utan indragning assessment#:#qst_nested_nested_answers_on#:#Med indrag assessment#:#qst_nr_of_tries#:#Antal försök @@ -1167,17 +1144,14 @@ assessment#:#question_title#:#Frågans titel assessment#:#question_type#:#Typ av fråga assessment#:#questionpool_not_entered#:#Var vänlig ange ett namn för frågepoolen! assessment#:#questionpool_not_selected#:#Välkomna att välja en frågepool! -assessment#:#questions#:#Questions###21 11 2023 new variable assessment#:#questions_from#:#Frågor från assessment#:#questions_per_page_view#:#Sidvisning assessment#:#random_accept_sample#:#Acceptera sammanställning assessment#:#random_another_sample#:#Ny sammanställning assessment#:#random_selection#:#Slumpmässigt urval assessment#:#range#:#Område -assessment#:#range_lower_limit#:#Lägre barriär assessment#:#range_max#:#Range (maximalt) assessment#:#range_min#:#Range (Minimum) -assessment#:#range_upper_limit#:#Övre barriär assessment#:#rated_sign#:#Omens assessment#:#rated_unit#:#Enhet assessment#:#rated_value#:#Värde @@ -1253,7 +1227,6 @@ assessment#:#search_roles#:#Efter rullar assessment#:#search_term#:#Sökord assessment#:#select_at_least_one_feedback_type_and_trigger#:#Please Select at least one type of feedback and a trigger.###26 08 2024 new variable assessment#:#select_at_least_one_lock_answer_type#:#Please select at least one type of answer lock.###29 10 2025 new variable -assessment#:#select_gap#:#Selektionsgap assessment#:#select_max_one_item#:#Välj endast ett objekt! assessment#:#select_one_user#:#Välj minst en användare! assessment#:#select_question#:#Select a Question###28 10 2024 new variable @@ -1280,7 +1253,6 @@ assessment#:#show_old_introduction#:#Show old introduction###21 11 2023 new vari assessment#:#show_pass_overview#:#Resultatöversikt (poängsatt provkörning) assessment#:#show_results#:#Show Results###28 10 2024 new variable assessment#:#show_user_answers#:#Answers (poängsatt testkörning) -assessment#:#shuffle_answers#:#Svar från Shuffle assessment#:#skip_question#:#Svara inte och fortsätt assessment#:#solution#:#Solution###28 10 2024 new variable assessment#:#solutionText#:#Text @@ -13971,6 +13943,34 @@ qpl#:#qpl_page_type_qfbg#:#Allmän feedback qpl#:#qpl_page_type_qfbs#:#Särskild feedback qpl#:#qpl_page_type_qht#:#Note qpl#:#qpl_page_type_qpl#:#Frågesida +qsts#:#cloze_text#:#Cloze fråga +qsts#:#cloze_textgapcase_insensitive#:#Ingen skillnad görs mellan stora och små bokstäver +qsts#:#cloze_textgapcase_sensitive#:#Känsliga gemener +qsts#:#cloze_textgaplevenshtein_of#:#Levenshtein avstånd från %s +qsts#:#confirm_delete_questions#:#Är du säker på att du vill ta bort följande frågor? +qsts#:#create_question#:#Skapa fråga +qsts#:#gap#:#Gap +qsts#:#gaps#:#Gaps###29 10 2025 new variable +qsts#:#insert_gap#:#Insert gap +qsts#:#min_auto_complete#:#Autokomplett +qsts#:#msg_no_questions_selected#:#No questions were selected.###21 11 2023 new variable +qsts#:#out_of_range#:#Utanför intervall +qsts#:#qst_lifecycle#:#Livscykel +qsts#:#qst_lifecycle_draft#:#Draft +qsts#:#qst_lifecycle_filter_all#:#Alla livscykler +qsts#:#qst_lifecycle_final#:#Final +qsts#:#qst_lifecycle_outdated#:#Veraltet +qsts#:#qst_lifecycle_rejected#:#Avvisad +qsts#:#qst_lifecycle_review#:#Revision nödvändig +qsts#:#qst_lifecycle_sharable#:#Distribuerbar +qsts#:#questionlist#:#Questionlist###21 11 2023 new variable +qsts#:#questions#:#Questions###21 11 2023 new variable +qsts#:#range_lower_limit#:#Lägre barriär +qsts#:#range_upper_limit#:#Övre barriär +qsts#:#reset_preview#:#Återställ förhandsgranskning +qsts#:#select_gap#:#Selektionsgap +qsts#:#shuffle_answers#:#Svar från Shuffle +qsts#:#suggested_learning_content#:#Innehåll för repetition rating#:#rat_not_rated_yet#:#Not yet rated rating#:#rat_nr_ratings#:#%s betyg rating#:#rat_one_rating#:#En bedömning @@ -16620,7 +16620,6 @@ survey#:#questionblock#:#Frågeställning block survey#:#questionblock_inserted#:#Frågeblock infört survey#:#questionblocks#:#Frågeblock survey#:#questionblocks_inserted#:#Frågeblock infogade -survey#:#questions#:#Frågor och svar survey#:#questions_inserted#:#Frågor tillagda survey#:#questions_removed#:#Frågor och/eller frågeblock borttagna! survey#:#questiontype#:#Typ av fråga @@ -16921,6 +16920,7 @@ survey#:#svy_please_select_unused_codes#:#Välj minst en oanvänd åtkomstnyckel survey#:#svy_print_hide_labels#:#Dölj Etiketter survey#:#svy_print_show_labels#:#Visa Märkningar survey#:#svy_privacy_info#:#Personuppgifter +survey#:#svy_questions#:#Frågor och svar survey#:#svy_rater#:#Feedback givare survey#:#svy_rater_see_app_info#:#feedbackgivare, namnen på de som tar emot feedback visas så att de kan svara på frågorna i förhållande till den som tar emot feedback. survey#:#svy_reminder_mail_template#:#Mall för e-post diff --git a/lang/ilias_tr.lang b/lang/ilias_tr.lang index c630f5961f1a..6419a607890c 100644 --- a/lang/ilias_tr.lang +++ b/lang/ilias_tr.lang @@ -422,7 +422,6 @@ adve#:#adve_use_tiny_mce#:#WYSIWYG düzenleme için TinyMCE kullan assessment#:#activate_logging#:#Test ve Değerlendirme Günlüğünü Aktif Yap assessment#:#activate_manual_scoring#:#Enable Manual Scoring###28 10 2024 new variable assessment#:#activate_manual_scoring_desc#:#Enables manual Scoring for all question types.###28 10 2024 new variable -assessment#:#addSuggestedSolution#:#Önerilen çözüm ekle assessment#:#add_answers#:#Add Antworten ###23 12 2015 new variable assessment#:#add_circle#:#Daire alanı ekle assessment#:#add_gap#:#Boşluk Metni Ekle @@ -447,7 +446,6 @@ assessment#:#answer_is_not_correct_but_positive#:#Çözümünüz için puan ald assessment#:#answer_is_right#:#Çözümünüz doğru assessment#:#answer_is_wrong#:#Çözümünüz yanlış assessment#:#answer_of#:#Answer of###07 02 2020 new variable -assessment#:#answer_options#:#Answer Options: ###23 12 2015 new variable assessment#:#answer_question#:#Answer Question###30 08 2015 new variable assessment#:#answer_text#:#Yanıt Metin assessment#:#answer_types#:#Yanıt Türleri @@ -501,7 +499,6 @@ assessment#:#ass_completion_by_submission#:#Başvuru ile Tamamlandı assessment#:#ass_completion_by_submission_info#:#Etkinse, bu soru için en az bir dosya gönderme maksimum puan alma için yeterlidir. Puan daha sonra elle değiştirilebilir. Bu ayarı değiştirme önceden gönderilen sorulara etki etmez assessment#:#ass_create_export_file_with_results#:#Create Test Export File (incl. Participant Results)###25 10 2016 new variable assessment#:#ass_create_export_test_archive#:#Create Test Archive File###24 10 2013 new variable -assessment#:#ass_create_question#:#Create Question###24 10 2013 new variable assessment#:#ass_imap_hint#:#Hint to be shown as Tooltip###25 10 2016 new variable assessment#:#ass_imap_map_file_not_readable#:#The uploaded image map could not be read.###25 10 2016 new variable assessment#:#ass_imap_no_map_found#:#Could not find any form in the uploaded image map.###25 10 2016 new variable @@ -571,10 +568,6 @@ assessment#:#cloze_answer_text_info#:#Spaces preceding or following the answer t assessment#:#cloze_fixed_textlength#:#Metin Alanı Uzunluğu assessment#:#cloze_fixed_textlength_description#:#Eğer 0'dan büyük bir değer girerseniz , tüm metin ve sayısal alan boşluklarının karakter uzunluğu bu değer olacaktır. assessment#:#cloze_gap_size_info#:#If you enter a value greater than 0, this gap text field will be created with the fixed length of this value. If you do not enter a value the gap text fiel will be created with the value of the global fixed length.###26 09 2014 new variable -assessment#:#cloze_text#:#Boşluklu Metin -assessment#:#cloze_textgap_case_insensitive#:#Büyük/Küçük Harf Duyarsız -assessment#:#cloze_textgap_case_sensitive#:#Büyük/Küçük Harf Duyarlı -assessment#:#cloze_textgap_levenshtein_of#:#%S in Levenshtein Uzaklığı assessment#:#code#:#Kod assessment#:#codebase#:#Codebase assessment#:#concatenation#:#Birleştirme @@ -757,9 +750,7 @@ assessment#:#fq_formula_desc#:#You may enter predefined variables ($v1 to $vn), assessment#:#fq_no_restriction_info#:#Both decimals and fractions are accepted as input.###26 03 2014 new variable assessment#:#fq_precision_info#:#Enter the number of desired decimal places.###26 03 2014 new variable assessment#:#fq_question_desc#:#You can define variables by inserting $v1, $v2 ... $vn, results by inserting $r1, $r2 .... $rn at the desired position in the question text. Click on the button "Parse Question" to create editing forms for variables and results.###06 11 2013 new variable -assessment#:#gap#:#Boşluk assessment#:#gap_combination#:#Gap Combination###26 09 2014 new variable -assessment#:#gaps#:#Gaps###29 10 2025 new variable assessment#:#glossary_term#:#Sözlük Terim assessment#:#goto_first_question#:#Show First Question###29 10 2025 new variable assessment#:#grading_mark_msg#:#Your resulting mark is: "[mark]"###26 09 2014 new variable @@ -777,7 +768,6 @@ assessment#:#info_answer_type_change#:#Soru zaten resimler içeriyor. Cevap tür assessment#:#info_text_upload#:#Choose an answer file to upload###30 08 2015 new variable assessment#:#insert_after#:#Sonrasına Ekle assessment#:#insert_before#:#Öncesine Ekle -assessment#:#insert_gap#:#Insert Gap###24 10 2013 new variable assessment#:#interaction_type#:#Interaction Type###26 08 2024 new variable assessment#:#internal_links#:#Dahili Linkler assessment#:#intprecision#:#Divisible By###24 10 2013 new variable @@ -889,7 +879,6 @@ assessment#:#logs_wrong_test_password_provided#:#Participant entered wrong test assessment#:#longmenu#:#Longmenu###10 11 2018 new variable assessment#:#longmenu_answeroptions_differ#:#This question does not work correctly, as there are not the same amount of gaps in the text as in the correction options.###26 08 2024 new variable assessment#:#longmenu_text#:#Long Menu Text###30 08 2015 new variable -assessment#:#mainbar_button_label_questionlist#:#Questionlist###26 08 2024 new variable assessment#:#maintenance#:#Bakım assessment#:#manscoring#:#Manuel Puanlama assessment#:#manscoring_done#:#Puanlanan Katılımcılar @@ -916,7 +905,6 @@ assessment#:#maximum_nr_of_tries_reached#:#Bu testi tamamladınız. assessment#:#maximum_points#:#Maksimum Kullanılabilir Puanlar assessment#:#maxsize#:#Maksimum dosya yükleme boyutu assessment#:#maxsize_info#:#Dosya yüklemeleri için izin verilen en büyük boyutu bayt cinsinden girin. Bu alanı boş bırakırsanız, bu yükleme maksimum boyut yerine geçicek. -assessment#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable assessment#:#min_ip_label#:#Lowest IP With Access###26 08 2024 new variable assessment#:#min_percentage_ne_0#:#%0 yerine minimum yüzde tanımlamalısınız. Not şeması kayıt edilmedi. assessment#:#misc#:#Misc Options###24 10 2013 new variable @@ -925,7 +913,6 @@ assessment#:#mode_onebyone#:#One by One###29 10 2025 new variable assessment#:#mode_question#:#Question oriented###29 10 2025 new variable assessment#:#mode_user#:#Participant oriented###29 10 2025 new variable assessment#:#msg_circle_added#:#Daire eklendi -assessment#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable assessment#:#msg_number_of_terms_too_low#:#Terimler sayısı tanımlar sayısından büyük veya eşit olmalı assessment#:#msg_poly_added#:#Poligon eklendi assessment#:#msg_questions_moved#:#Soru(lar) taşındı @@ -984,7 +971,6 @@ assessment#:#order#:#Order###26 08 2024 new variable assessment#:#ordering_answer_sequence_info#:#Burada tanımladığınız cevap sırası doğru çözüm sırası olarak alınacak assessment#:#ordertext#:#Metin Sıralama assessment#:#ordertext_info#:#Yatay sıralanabilen metni giriniz. Sıralama metni, metin içinde boşluk(whitespace) ile ayrılacaktır.. Eğer farklı bir ayraç kullanmak isterseniz, text birimlerini ayırmak için %s ayracını kullanabilirsiniz. -assessment#:#out_of_range#:#Out of range###27 01 2015 new variable assessment#:#output#:#Çıktı assessment#:#output_mode#:#Çıkış Modu assessment#:#parseQuestion#:#Parse Question###24 10 2013 new variable @@ -1053,7 +1039,6 @@ assessment#:#qpl_bulk_save_add#:#Add###28 10 2024 new variable assessment#:#qpl_bulk_save_overwrite#:#Overwrite###28 10 2024 new variable assessment#:#qpl_bulkedit_success#:#Modifications saved.###28 10 2024 new variable assessment#:#qpl_cancel_skill_assigns_update#:#Cancel###30 08 2015 new variable -assessment#:#qpl_confirm_delete_questions#:#Aşağıdaki soruları silmek istediğinizden emin misiniz? assessment#:#qpl_copy_insert_clipboard#:#Seçilen soru(lar) panoya kopyalandı assessment#:#qpl_copy_select_none#:#Panoya kopyalamak için en az bir soruyu seçiniz assessment#:#qpl_delete_rbac_error#:#Bu soruyu silmek için yetkiniz yok ! @@ -1106,7 +1091,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#Competence###30 08 2015 new variable assessment#:#qpl_qst_skl_usg_sklpnt_col#:#Total Sum of Competence-Points per Competence###30 08 2015 new variable assessment#:#qpl_question_is_in_use#:#Düzenlemek üzere olduğunuz soru test %s içinde var Eğer bu soruyu değiştirirseniz, test(ler) içindeki soruyu değiştiremezsiniz, çünkü sistem soru teste eklendiğinde bir kopyasını alır. assessment#:#qpl_questions_deleted#:#Soru(lar) silindi. -assessment#:#qpl_reset_preview#:#Reset Preview###26 09 2014 new variable assessment#:#qpl_save_skill_assigns_update#:#Save Competence Assignments###30 08 2015 new variable assessment#:#qpl_settings_availability#:#Availability###26 08 2024 new variable assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#When enabled, possibly created taxonomies are shown for filtering.###24 10 2013 new variable @@ -1136,14 +1120,6 @@ assessment#:#qst_essay_chars_remaining#:#Remaining characters:###07 02 2020 new assessment#:#qst_essay_wordcounter_enabled#:#Count Words###07 02 2020 new variable assessment#:#qst_essay_wordcounter_enabled_info#:#The entered words are counted. The number of written words is shown to the participants below the text input field.###07 02 2020 new variable assessment#:#qst_essay_written_words#:#Number of entered words:###07 02 2020 new variable -assessment#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable -assessment#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable -assessment#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable -assessment#:#qst_lifecycle_final#:#Final###07 02 2020 new variable -assessment#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable -assessment#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable -assessment#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable -assessment#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable assessment#:#qst_nested_nested_answers_off#:#No indents, just order###29 07 2022 new variable assessment#:#qst_nested_nested_answers_on#:#Use indents in anwers###29 07 2022 new variable assessment#:#qst_nr_of_tries#:#Deneme Sayısı @@ -1167,17 +1143,14 @@ assessment#:#question_title#:#Soru Başlığı assessment#:#question_type#:#Soru Türü assessment#:#questionpool_not_entered#:#Soru havuzu için bir ad girin! assessment#:#questionpool_not_selected#:#Please select a question pool!###07 02 2020 new variable -assessment#:#questions#:#Questions###26 08 2024 new variable assessment#:#questions_from#:#sorular assessment#:#questions_per_page_view#:#Sayfa Görünümü assessment#:#random_accept_sample#:#Örnek Kabul assessment#:#random_another_sample#:#Başka bir örnek alın assessment#:#random_selection#:#Rasgele Seçim assessment#:#range#:#Aralık -assessment#:#range_lower_limit#:#Alt Sınır assessment#:#range_max#:#Range (Maximum)###24 10 2013 new variable assessment#:#range_min#:#Range (Minimum)###24 10 2013 new variable -assessment#:#range_upper_limit#:#Üst Sınır assessment#:#rated_sign#:#Sign###24 10 2013 new variable assessment#:#rated_unit#:#Unit###24 10 2013 new variable assessment#:#rated_value#:#Value###24 10 2013 new variable @@ -1253,7 +1226,6 @@ assessment#:#search_roles#:#Rolleri Ara assessment#:#search_term#:#Terim Ara assessment#:#select_at_least_one_feedback_type_and_trigger#:#Please Select at least one type of feedback and a trigger.###26 08 2024 new variable assessment#:#select_at_least_one_lock_answer_type#:#Please select at least one type of answer lock.###29 10 2025 new variable -assessment#:#select_gap#:#Boşluk Seç assessment#:#select_max_one_item#:#Yalnızca bir öğe seçiniz assessment#:#select_one_user#:#En az bir kullanıcı seçiniz. assessment#:#select_question#:#Select a Question###28 10 2024 new variable @@ -1280,7 +1252,6 @@ assessment#:#show_old_introduction#:#Show old introduction###26 08 2024 new vari assessment#:#show_pass_overview#:#Geçti İşaretlilere Genel Bakışı Göster assessment#:#show_results#:#Show Results###28 10 2024 new variable assessment#:#show_user_answers#:#Kullanıcının işaretli cevaplarını göster -assessment#:#shuffle_answers#:#Cevapları Karıştır assessment#:#skip_question#:#Do not Answer and Next###08 10 2015 new variable assessment#:#solution#:#Solution###28 10 2024 new variable assessment#:#solutionText#:#Metin @@ -13971,6 +13942,35 @@ qpl#:#qpl_page_type_qfbg#:#General Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qfbs#:#Special Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qht#:#Hint###29 07 2022 new variable qpl#:#qpl_page_type_qpl#:#Question Page###29 07 2022 new variable +qsts#:#answer_options#:#Answer Options ###23 12 2015 new variable +qsts#:#cloze_text#:#Boşluklu Metin +qsts#:#cloze_textgapcase_insensitive#:#Büyük/Küçük Harf Duyarsız +qsts#:#cloze_textgapcase_sensitive#:#Büyük/Küçük Harf Duyarlı +qsts#:#cloze_textgaplevenshtein_of#:#%S in Levenshtein Uzaklığı +qsts#:#confirm_delete_questions#:#Aşağıdaki soruları silmek istediğinizden emin misiniz? +qsts#:#create_question#:#Create Question###24 10 2013 new variable +qsts#:#gap#:#Boşluk +qsts#:#gaps#:#Gaps###29 10 2025 new variable +qsts#:#insert_gap#:#Insert Gap###24 10 2013 new variable +qsts#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable +qsts#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable +qsts#:#out_of_range#:#Out of range###27 01 2015 new variable +qsts#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable +qsts#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable +qsts#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable +qsts#:#qst_lifecycle_final#:#Final###07 02 2020 new variable +qsts#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable +qsts#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable +qsts#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable +qsts#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable +qsts#:#questionlist#:#Questionlist###26 08 2024 new variable +qsts#:#questions#:#Questions###26 08 2024 new variable +qsts#:#range_lower_limit#:#Alt Sınır +qsts#:#range_upper_limit#:#Üst Sınır +qsts#:#reset_preview#:#Reset Preview###26 09 2014 new variable +qsts#:#select_gap#:#Boşluk Seç +qsts#:#shuffle_answers#:#Cevapları Karıştır +qsts#:#suggested_learning_content#:#Önerilen çözüm ekle rating#:#rat_not_rated_yet#:#Henüz Oylanmadı rating#:#rat_nr_ratings#:#%s Puan rating#:#rat_one_rating#:#Bir Değerlendirme @@ -16620,7 +16620,6 @@ survey#:#questionblock#:#Soru Blok survey#:#questionblock_inserted#:#Soru Blok takılı survey#:#questionblocks#:#Soru Blokları survey#:#questionblocks_inserted#:#Takılı Soru Blokları -survey#:#questions#:#Sorular survey#:#questions_inserted#:#Soru (ler) eklenir! survey#:#questions_removed#:#Soru (ler) ve / veya söz konusu bloğun (ler) kaldırıldı! survey#:#questiontype#:#Soru Türü @@ -16921,6 +16920,7 @@ survey#:#svy_please_select_unused_codes#:#Please select at least one unused code survey#:#svy_print_hide_labels#:#Hide labels###31 08 2017 new variable survey#:#svy_print_show_labels#:#Show labels###31 08 2017 new variable survey#:#svy_privacy_info#:#Privacy###29 07 2022 new variable +survey#:#svy_questions#:#Sorular survey#:#svy_rater#:#Rater###29 07 2022 new variable survey#:#svy_rater_see_app_info#:#The names of appraisees will be presented to raters to enable them evaluating the questions.###29 07 2022 new variable survey#:#svy_reminder_mail_template#:#Mail Template###25 10 2016 new variable diff --git a/lang/ilias_uk.lang b/lang/ilias_uk.lang index ed5334ea9912..09f1856bd91e 100644 --- a/lang/ilias_uk.lang +++ b/lang/ilias_uk.lang @@ -422,7 +422,6 @@ adve#:#adve_use_tiny_mce#:#Enable TinyMCE for WYSIWYG Editing###01 10 2011 new v assessment#:#activate_logging#:#Активувати логування Тестів & Оцінок assessment#:#activate_manual_scoring#:#Enable Manual Scoring###28 10 2024 new variable assessment#:#activate_manual_scoring_desc#:#Enables manual Scoring for all question types.###28 10 2024 new variable -assessment#:#addSuggestedSolution#:#Add suggested solution###24 02 2009 new variable assessment#:#add_answers#:#Add Antworten ###23 12 2015 new variable assessment#:#add_circle#:#Add circle area###24 07 2009 new variable assessment#:#add_gap#:#Додати текст з проміжком @@ -447,7 +446,6 @@ assessment#:#answer_is_not_correct_but_positive#:#You've got points for your sol assessment#:#answer_is_right#:#Ваша відповідь вірна assessment#:#answer_is_wrong#:#Вага відповідь невірна assessment#:#answer_of#:#Answer of###07 02 2020 new variable -assessment#:#answer_options#:#Answer Options: ###23 12 2015 new variable assessment#:#answer_question#:#Answer Question###30 08 2015 new variable assessment#:#answer_text#:#Текст з відповідю assessment#:#answer_types#:#Answer Types###24 07 2009 new variable @@ -501,7 +499,6 @@ assessment#:#ass_completion_by_submission#:#Completed by Submission###12 06 2011 assessment#:#ass_completion_by_submission_info#:#If enabled, the submission of at least one file causes the completion of this question by granting the maximum score for this question. The score could be manually changed later. Switching this setting does not effect already submitted solutions.###12 06 2011 new variable assessment#:#ass_create_export_file_with_results#:#Create Test Export File (incl. Participant Results)###25 10 2016 new variable assessment#:#ass_create_export_test_archive#:#Create Test Archive File###24 10 2013 new variable -assessment#:#ass_create_question#:#Create Question###24 10 2013 new variable assessment#:#ass_imap_hint#:#Hint to be shown as Tooltip###25 10 2016 new variable assessment#:#ass_imap_map_file_not_readable#:#The uploaded image map could not be read.###25 10 2016 new variable assessment#:#ass_imap_no_map_found#:#Could not find any form in the uploaded image map.###25 10 2016 new variable @@ -571,10 +568,6 @@ assessment#:#cloze_answer_text_info#:#Spaces preceding or following the answer t assessment#:#cloze_fixed_textlength#:#Text Field Length###02 04 2007 new variable assessment#:#cloze_fixed_textlength_description#:#If you enter a value greater than 0, all text and numeric gap text fields will be created with the fixed length of this value.###02 04 2007 new variable assessment#:#cloze_gap_size_info#:#If you enter a value greater than 0, this gap text field will be created with the fixed length of this value. If you do not enter a value the gap text fiel will be created with the value of the global fixed length.###26 09 2014 new variable -assessment#:#cloze_text#:#Закрити текст###20 Jun 2005 content changed -assessment#:#cloze_textgap_case_insensitive#:#Case insensitive###03 Nov 2005 new variable -assessment#:#cloze_textgap_case_sensitive#:#Case sensitive###03 Nov 2005 new variable -assessment#:#cloze_textgap_levenshtein_of#:#Levenshtein distance of %s###03 Nov 2005 new variable assessment#:#code#:#Код assessment#:#codebase#:#Codebase###25 02 2007 new variable assessment#:#concatenation#:#Об'єднання @@ -757,9 +750,7 @@ assessment#:#fq_formula_desc#:#You may enter predefined variables ($v1 to $vn), assessment#:#fq_no_restriction_info#:#Both decimals and fractions are accepted as input.###26 03 2014 new variable assessment#:#fq_precision_info#:#Enter the number of desired decimal places.###26 03 2014 new variable assessment#:#fq_question_desc#:#You can define variables by inserting $v1, $v2 ... $vn, results by inserting $r1, $r2 .... $rn at the desired position in the question text. Click on the button "Parse Question" to create editing forms for variables and results.###06 11 2013 new variable -assessment#:#gap#:#Проміжок assessment#:#gap_combination#:#Gap Combination###26 09 2014 new variable -assessment#:#gaps#:#Gaps###29 10 2025 new variable assessment#:#glossary_term#:#Термін глосарію assessment#:#goto_first_question#:#Show First Question###29 10 2025 new variable assessment#:#grading_mark_msg#:#Your resulting mark is: "[mark]"###26 09 2014 new variable @@ -777,7 +768,6 @@ assessment#:#info_answer_type_change#:#The question already contains images. You assessment#:#info_text_upload#:#Choose an answer file to upload###30 08 2015 new variable assessment#:#insert_after#:#Вставити після assessment#:#insert_before#:#Вставити перед -assessment#:#insert_gap#:#Insert Gap###24 10 2013 new variable assessment#:#interaction_type#:#Interaction Type###26 08 2024 new variable assessment#:#internal_links#:#Внутрішні Посилання assessment#:#intprecision#:#Divisible By###24 10 2013 new variable @@ -889,7 +879,6 @@ assessment#:#logs_wrong_test_password_provided#:#Participant entered wrong test assessment#:#longmenu#:#Longmenu###10 11 2018 new variable assessment#:#longmenu_answeroptions_differ#:#This question does not work correctly, as there are not the same amount of gaps in the text as in the correction options.###26 08 2024 new variable assessment#:#longmenu_text#:#Long Menu Text###30 08 2015 new variable -assessment#:#mainbar_button_label_questionlist#:#Questionlist###26 08 2024 new variable assessment#:#maintenance#:#Підтримка assessment#:#manscoring#:#Manual Scoring###25 02 2007 new variable assessment#:#manscoring_done#:#Scored Participants###24 02 2009 new variable @@ -916,7 +905,6 @@ assessment#:#maximum_nr_of_tries_reached#:#Ви досялги максимал assessment#:#maximum_points#:#Maxium Available Points###25 02 2007 new variable assessment#:#maxsize#:#Maximum file upload size ###24 02 2009 new variable assessment#:#maxsize_info#:#Enter the maximum size in bytes that should be allowed for file uploads. If you leave this field empty, the maximum size of this installation will be chosen instead.###24 02 2009 new variable -assessment#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable assessment#:#min_ip_label#:#Lowest IP With Access###26 08 2024 new variable assessment#:#min_percentage_ne_0#:#Ви маєете вказати мінімальне процентне співвідношення до нуля процентів! Схема оцінок не збережена. assessment#:#misc#:#Misc Options###24 10 2013 new variable @@ -925,7 +913,6 @@ assessment#:#mode_onebyone#:#One by One###29 10 2025 new variable assessment#:#mode_question#:#Question oriented###29 10 2025 new variable assessment#:#mode_user#:#Participant oriented###29 10 2025 new variable assessment#:#msg_circle_added#:#Circle added###24 07 2009 new variable -assessment#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable assessment#:#msg_number_of_terms_too_low#:#The number of terms must be greater or equal to the number of definitions.###12 08 2009 new variable assessment#:#msg_poly_added#:#Polygon added###24 07 2009 new variable assessment#:#msg_questions_moved#:#Question(s) moved###06 08 2009 new variable @@ -984,7 +971,6 @@ assessment#:#order#:#Order###26 08 2024 new variable assessment#:#ordering_answer_sequence_info#:#The answer sequence you define here will be taken as the correct solution sequence.###10 09 2010 new variable assessment#:#ordertext#:#Ordering Text###24 02 2009 new variable assessment#:#ordertext_info#:#Please enter the text that should be ordered horizontally. The ordering text will be separated by the whitespace signs in the text. If you need a different separation, you may use the separator %s to separate your text units.###24 02 2009 new variable -assessment#:#out_of_range#:#Out of range###27 01 2015 new variable assessment#:#output#:#Output###25 02 2007 new variable assessment#:#output_mode#:#Output Mode###25 02 2007 new variable assessment#:#parseQuestion#:#Parse Question###24 10 2013 new variable @@ -1053,7 +1039,6 @@ assessment#:#qpl_bulk_save_add#:#Add###28 10 2024 new variable assessment#:#qpl_bulk_save_overwrite#:#Overwrite###28 10 2024 new variable assessment#:#qpl_bulkedit_success#:#Modifications saved.###28 10 2024 new variable assessment#:#qpl_cancel_skill_assigns_update#:#Cancel###30 08 2015 new variable -assessment#:#qpl_confirm_delete_questions#:#Ви впевнені що бажаєте видалити наступні питання ? Якщо ви видалите заблоковані питання то результати усіх тестів що містять залоковані питання буде також видалено. assessment#:#qpl_copy_insert_clipboard#:#The selected question(s) are copied to the clipboard###03 Nov 2005 new variable assessment#:#qpl_copy_select_none#:#Please check at least one question to copy it to the clipboard###03 Nov 2005 new variable assessment#:#qpl_delete_rbac_error#:#У вас немає прав видалити це питання! @@ -1106,7 +1091,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#Competence###30 08 2015 new variable assessment#:#qpl_qst_skl_usg_sklpnt_col#:#Total Sum of Competence-Points per Competence###30 08 2015 new variable assessment#:#qpl_question_is_in_use#:#Питання що ви збираєтесь редагувати існує в %s тесті(ах). Якщо ви зміните це питання, ви НЕ зміните питання в тесті(ах), тому що система створює копію питання коли воно вставляється в тест! assessment#:#qpl_questions_deleted#:#Питання виделено(і). -assessment#:#qpl_reset_preview#:#Reset Preview###26 09 2014 new variable assessment#:#qpl_save_skill_assigns_update#:#Save Competence Assignments###30 08 2015 new variable assessment#:#qpl_settings_availability#:#Availability###26 08 2024 new variable assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#When enabled, possibly created taxonomies are shown for filtering.###24 10 2013 new variable @@ -1136,14 +1120,6 @@ assessment#:#qst_essay_chars_remaining#:#Remaining characters:###07 02 2020 new assessment#:#qst_essay_wordcounter_enabled#:#Count Words###07 02 2020 new variable assessment#:#qst_essay_wordcounter_enabled_info#:#The entered words are counted. The number of written words is shown to the participants below the text input field.###07 02 2020 new variable assessment#:#qst_essay_written_words#:#Number of entered words:###07 02 2020 new variable -assessment#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable -assessment#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable -assessment#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable -assessment#:#qst_lifecycle_final#:#Final###07 02 2020 new variable -assessment#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable -assessment#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable -assessment#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable -assessment#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable assessment#:#qst_nested_nested_answers_off#:#No indents, just order###29 07 2022 new variable assessment#:#qst_nested_nested_answers_on#:#Use indents in anwers###29 07 2022 new variable assessment#:#qst_nr_of_tries#:#Number of tries###15 05 2009 new variable @@ -1167,17 +1143,14 @@ assessment#:#question_title#:#Назва питання assessment#:#question_type#:#Тип Питання assessment#:#questionpool_not_entered#:#Будб ласка введіть ім'я для пула з питаннями! assessment#:#questionpool_not_selected#:#Please select a question pool!###07 02 2020 new variable -assessment#:#questions#:#Questions###26 08 2024 new variable assessment#:#questions_from#:#питання з assessment#:#questions_per_page_view#:#Page View###12 06 2011 new variable assessment#:#random_accept_sample#:#Прийняти приклад assessment#:#random_another_sample#:#Взяти інший приклад assessment#:#random_selection#:#Випадковий вибір assessment#:#range#:#Range###25 02 2007 new variable -assessment#:#range_lower_limit#:#Lower Bound###25 02 2007 new variable assessment#:#range_max#:#Range (Maximum)###24 10 2013 new variable assessment#:#range_min#:#Range (Minimum)###24 10 2013 new variable -assessment#:#range_upper_limit#:#Upper Bound###25 02 2007 new variable assessment#:#rated_sign#:#Sign###24 10 2013 new variable assessment#:#rated_unit#:#Unit###24 10 2013 new variable assessment#:#rated_value#:#Value###24 10 2013 new variable @@ -1253,7 +1226,6 @@ assessment#:#search_roles#:#Пошук Ролей assessment#:#search_term#:#Пошук Термінів assessment#:#select_at_least_one_feedback_type_and_trigger#:#Please Select at least one type of feedback and a trigger.###26 08 2024 new variable assessment#:#select_at_least_one_lock_answer_type#:#Please select at least one type of answer lock.###29 10 2025 new variable -assessment#:#select_gap#:#Вибрати проміжок assessment#:#select_max_one_item#:#Будь ласка виберіть тільки один пункт assessment#:#select_one_user#:#Please select at least one user###23 Dec 2005 new variable assessment#:#select_question#:#Select a Question###28 10 2024 new variable @@ -1280,7 +1252,6 @@ assessment#:#show_old_introduction#:#Show old introduction###26 08 2024 new vari assessment#:#show_pass_overview#:#Show Marked Pass Overview###31 05 2007 new variable assessment#:#show_results#:#Show Results###28 10 2024 new variable assessment#:#show_user_answers#:#Show User's Marked Anwers###31 05 2007 new variable -assessment#:#shuffle_answers#:#Перерозташувати відповіді assessment#:#skip_question#:#Do not Answer and Next###08 10 2015 new variable assessment#:#solution#:#Solution###28 10 2024 new variable assessment#:#solutionText#:#Text###24 02 2009 new variable @@ -13971,6 +13942,35 @@ qpl#:#qpl_page_type_qfbg#:#General Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qfbs#:#Special Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qht#:#Hint###29 07 2022 new variable qpl#:#qpl_page_type_qpl#:#Question Page###29 07 2022 new variable +qsts#:#answer_options#:#Answer Options ###23 12 2015 new variable +qsts#:#cloze_text#:#Закрити текст###20 Jun 2005 content changed +qsts#:#cloze_textgapcase_insensitive#:#Case insensitive###03 Nov 2005 new variable +qsts#:#cloze_textgapcase_sensitive#:#Case sensitive###03 Nov 2005 new variable +qsts#:#cloze_textgaplevenshtein_of#:#Levenshtein distance of %s###03 Nov 2005 new variable +qsts#:#confirm_delete_questions#:#Ви впевнені що бажаєте видалити наступні питання ? Якщо ви видалите заблоковані питання то результати усіх тестів що містять залоковані питання буде також видалено. +qsts#:#create_question#:#Create Question###24 10 2013 new variable +qsts#:#gap#:#Проміжок +qsts#:#gaps#:#Gaps###29 10 2025 new variable +qsts#:#insert_gap#:#Insert Gap###24 10 2013 new variable +qsts#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable +qsts#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable +qsts#:#out_of_range#:#Out of range###27 01 2015 new variable +qsts#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable +qsts#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable +qsts#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable +qsts#:#qst_lifecycle_final#:#Final###07 02 2020 new variable +qsts#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable +qsts#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable +qsts#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable +qsts#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable +qsts#:#questionlist#:#Questionlist###26 08 2024 new variable +qsts#:#questions#:#Questions###26 08 2024 new variable +qsts#:#range_lower_limit#:#Lower Bound###25 02 2007 new variable +qsts#:#range_upper_limit#:#Upper Bound###25 02 2007 new variable +qsts#:#reset_preview#:#Reset Preview###26 09 2014 new variable +qsts#:#select_gap#:#Вибрати проміжок +qsts#:#shuffle_answers#:#Перерозташувати відповіді +qsts#:#suggested_learning_content#:#Add suggested solution###24 02 2009 new variable rating#:#rat_not_rated_yet#:#Not Rated Yet###12 06 2011 new variable rating#:#rat_nr_ratings#:#%s Ratings###12 06 2011 new variable rating#:#rat_one_rating#:#One Rating###12 06 2011 new variable @@ -16620,7 +16620,6 @@ survey#:#questionblock#:#Блок питань survey#:#questionblock_inserted#:#Question Block inserted###06 08 2009 new variable survey#:#questionblocks#:#Блоки питань survey#:#questionblocks_inserted#:#Question Blocks inserted###06 08 2009 new variable -survey#:#questions#:#Питання survey#:#questions_inserted#:#Питання вставлені! survey#:#questions_removed#:#Питання та/або блоки питань видалені! survey#:#questiontype#:#Тип питання @@ -16921,6 +16920,7 @@ survey#:#svy_please_select_unused_codes#:#Please select at least one unused code survey#:#svy_print_hide_labels#:#Hide labels###31 08 2017 new variable survey#:#svy_print_show_labels#:#Show labels###31 08 2017 new variable survey#:#svy_privacy_info#:#Privacy###29 07 2022 new variable +survey#:#svy_questions#:#Питання survey#:#svy_rater#:#Rater###29 07 2022 new variable survey#:#svy_rater_see_app_info#:#The names of appraisees will be presented to raters to enable them evaluating the questions.###29 07 2022 new variable survey#:#svy_reminder_mail_template#:#Mail Template###25 10 2016 new variable diff --git a/lang/ilias_vi.lang b/lang/ilias_vi.lang index 333e15b4fa3c..60c7391a064d 100644 --- a/lang/ilias_vi.lang +++ b/lang/ilias_vi.lang @@ -424,7 +424,6 @@ adve#:#adve_use_tiny_mce#:#Enable TinyMCE for WYSIWYG Editing###01 10 2011 new v assessment#:#activate_logging#:#Kích hoạt ghi nhật ký Kiểm tra và Đánh giá assessment#:#activate_manual_scoring#:#Enable Manual Scoring###28 10 2024 new variable assessment#:#activate_manual_scoring_desc#:#Enables manual Scoring for all question types.###28 10 2024 new variable -assessment#:#addSuggestedSolution#:#Add suggested solution###24 02 2009 new variable assessment#:#add_answers#:#Add Antworten ###23 12 2015 new variable assessment#:#add_circle#:#Add circle area###24 07 2009 new variable assessment#:#add_gap#:#Thêm từ @@ -449,7 +448,6 @@ assessment#:#answer_is_not_correct_but_positive#:#You've got points for your sol assessment#:#answer_is_right#:#Giải pháp của bạn đúng assessment#:#answer_is_wrong#:#Giải pháp của bạn sai assessment#:#answer_of#:#Answer of###07 02 2020 new variable -assessment#:#answer_options#:#Answer Options: ###23 12 2015 new variable assessment#:#answer_question#:#Answer Question###30 08 2015 new variable assessment#:#answer_text#:#Phương án trả lời assessment#:#answer_types#:#Answer Types###24 07 2009 new variable @@ -503,7 +501,6 @@ assessment#:#ass_completion_by_submission#:#Completed by Submission###12 06 2011 assessment#:#ass_completion_by_submission_info#:#If enabled, the submission of at least one file causes the completion of this question by granting the maximum score for this question. The score could be manually changed later. Switching this setting does not effect already submitted solutions.###12 06 2011 new variable assessment#:#ass_create_export_file_with_results#:#Create Test Export File (incl. Participant Results)###25 10 2016 new variable assessment#:#ass_create_export_test_archive#:#Create Test Archive File###24 10 2013 new variable -assessment#:#ass_create_question#:#Create Question###24 10 2013 new variable assessment#:#ass_imap_hint#:#Hint to be shown as Tooltip###25 10 2016 new variable assessment#:#ass_imap_map_file_not_readable#:#The uploaded image map could not be read.###25 10 2016 new variable assessment#:#ass_imap_no_map_found#:#Could not find any form in the uploaded image map.###25 10 2016 new variable @@ -573,10 +570,6 @@ assessment#:#cloze_answer_text_info#:#Spaces preceding or following the answer t assessment#:#cloze_fixed_textlength#:#Text Field Length###02 04 2007 new variable assessment#:#cloze_fixed_textlength_description#:#If you enter a value greater than 0, all text and numeric gap text fields will be created with the fixed length of this value.###02 04 2007 new variable assessment#:#cloze_gap_size_info#:#If you enter a value greater than 0, this gap text field will be created with the fixed length of this value. If you do not enter a value the gap text fiel will be created with the value of the global fixed length.###26 09 2014 new variable -assessment#:#cloze_text#:#Đoạn văn điền chỗ trống -assessment#:#cloze_textgap_case_insensitive#:#Không phân biệt chữ hoa chữ thường -assessment#:#cloze_textgap_case_sensitive#:#Phân biệt chữ hoa chữ thường -assessment#:#cloze_textgap_levenshtein_of#:#Levenshtein distance of %s/// assessment#:#code#:#Mã assessment#:#codebase#:#Codebase###25 02 2007 new variable assessment#:#concatenation#:#Kết hợp @@ -759,9 +752,7 @@ assessment#:#fq_formula_desc#:#You may enter predefined variables ($v1 to $vn), assessment#:#fq_no_restriction_info#:#Both decimals and fractions are accepted as input.###26 03 2014 new variable assessment#:#fq_precision_info#:#Enter the number of desired decimal places.###26 03 2014 new variable assessment#:#fq_question_desc#:#You can define variables by inserting $v1, $v2 ... $vn, results by inserting $r1, $r2 .... $rn at the desired position in the question text. Click on the button "Parse Question" to create editing forms for variables and results.###06 11 2013 new variable -assessment#:#gap#:#Chỗ trống assessment#:#gap_combination#:#Gap Combination###26 09 2014 new variable -assessment#:#gaps#:#Gaps###29 10 2025 new variable assessment#:#glossary_term#:#Bảng chú giải thuật ngữ assessment#:#goto_first_question#:#Show First Question###29 10 2025 new variable assessment#:#grading_mark_msg#:#Your resulting mark is: "[mark]"###26 09 2014 new variable @@ -779,7 +770,6 @@ assessment#:#info_answer_type_change#:#The question already contains images. You assessment#:#info_text_upload#:#Choose an answer file to upload###30 08 2015 new variable assessment#:#insert_after#:#Chèn vào sau assessment#:#insert_before#:#Chèn vào trước -assessment#:#insert_gap#:#Insert Gap###24 10 2013 new variable assessment#:#interaction_type#:#Interaction Type###26 08 2024 new variable assessment#:#internal_links#:#Liên kết nội bộ assessment#:#intprecision#:#Divisible By###24 10 2013 new variable @@ -891,7 +881,6 @@ assessment#:#logs_wrong_test_password_provided#:#Participant entered wrong test assessment#:#longmenu#:#Longmenu###10 11 2018 new variable assessment#:#longmenu_answeroptions_differ#:#This question does not work correctly, as there are not the same amount of gaps in the text as in the correction options.###26 08 2024 new variable assessment#:#longmenu_text#:#Long Menu Text###30 08 2015 new variable -assessment#:#mainbar_button_label_questionlist#:#Questionlist###26 08 2024 new variable assessment#:#maintenance#:#Bảo quản - bảo trì assessment#:#manscoring#:#Manual Scoring###25 02 2007 new variable assessment#:#manscoring_done#:#Scored Participants###24 02 2009 new variable @@ -918,7 +907,6 @@ assessment#:#maximum_nr_of_tries_reached#:#Bạn đã hết số lần có thể assessment#:#maximum_points#:#Maxium Available Points###25 02 2007 new variable assessment#:#maxsize#:#Maximum file upload size ###24 02 2009 new variable assessment#:#maxsize_info#:#Enter the maximum size in bytes that should be allowed for file uploads. If you leave this field empty, the maximum size of this installation will be chosen instead.###24 02 2009 new variable -assessment#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable assessment#:#min_ip_label#:#Lowest IP With Access###26 08 2024 new variable assessment#:#min_percentage_ne_0#:#Bạn phải định nghĩa phần trăm tối thiếu là 0 phần trăm! Giản đồ điểm không được ghi. assessment#:#misc#:#Misc Options###24 10 2013 new variable @@ -927,7 +915,6 @@ assessment#:#mode_onebyone#:#One by One###29 10 2025 new variable assessment#:#mode_question#:#Question oriented###29 10 2025 new variable assessment#:#mode_user#:#Participant oriented###29 10 2025 new variable assessment#:#msg_circle_added#:#Circle added###24 07 2009 new variable -assessment#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable assessment#:#msg_number_of_terms_too_low#:#The number of terms must be greater or equal to the number of definitions.###12 08 2009 new variable assessment#:#msg_poly_added#:#Polygon added###24 07 2009 new variable assessment#:#msg_questions_moved#:#Question(s) moved###06 08 2009 new variable @@ -986,7 +973,6 @@ assessment#:#order#:#Order###26 08 2024 new variable assessment#:#ordering_answer_sequence_info#:#The answer sequence you define here will be taken as the correct solution sequence.###10 09 2010 new variable assessment#:#ordertext#:#Ordering Text###24 02 2009 new variable assessment#:#ordertext_info#:#Please enter the text that should be ordered horizontally. The ordering text will be separated by the whitespace signs in the text. If you need a different separation, you may use the separator %s to separate your text units.###24 02 2009 new variable -assessment#:#out_of_range#:#Out of range###27 01 2015 new variable assessment#:#output#:#Output###25 02 2007 new variable assessment#:#output_mode#:#Output Mode###25 02 2007 new variable assessment#:#parseQuestion#:#Parse Question###24 10 2013 new variable @@ -1055,7 +1041,6 @@ assessment#:#qpl_bulk_save_add#:#Add###28 10 2024 new variable assessment#:#qpl_bulk_save_overwrite#:#Overwrite###28 10 2024 new variable assessment#:#qpl_bulkedit_success#:#Modifications saved.###28 10 2024 new variable assessment#:#qpl_cancel_skill_assigns_update#:#Cancel###30 08 2015 new variable -assessment#:#qpl_confirm_delete_questions#:#Bạn có chắc chắn muốn xóa (các) câu hỏi sau không? Nếu bạn xóa các câu hỏi đã khóa kết quả của tất cả bài kiểm tra chứa câu hỏi đã khóa sẽ bị xóa theo. assessment#:#qpl_copy_insert_clipboard#:#(Những) câu hỏi bạn chọn đã được sao chép vào bộ nhớ đệm assessment#:#qpl_copy_select_none#:#Hãy chọn ít nhất một câu hỏi để sao chép vào bộ nhớ đệm assessment#:#qpl_delete_rbac_error#:#Bạn không có quyền xóa câu hỏi này! @@ -1108,7 +1093,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#Competence###30 08 2015 new variable assessment#:#qpl_qst_skl_usg_sklpnt_col#:#Total Sum of Competence-Points per Competence###30 08 2015 new variable assessment#:#qpl_question_is_in_use#:#Câu hỏi bạn muốn sửa đang tồn tại trong %s bài kiểm tra. Nếu bạn thay đổi câu hỏi này, bạn sẽ không thay đổi những câu hỏi trong các bài kiểm tra, vì hệ thống tạo ra bản sao của câu hỏi khi chèn câu hỏi vào bài kiểm tra! assessment#:#qpl_questions_deleted#:#Đã xóa (các) câu hỏi. -assessment#:#qpl_reset_preview#:#Reset Preview###26 09 2014 new variable assessment#:#qpl_save_skill_assigns_update#:#Save Competence Assignments###30 08 2015 new variable assessment#:#qpl_settings_availability#:#Availability###26 08 2024 new variable assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#When enabled, possibly created taxonomies are shown for filtering.###24 10 2013 new variable @@ -1138,14 +1122,6 @@ assessment#:#qst_essay_chars_remaining#:#Remaining characters:###07 02 2020 new assessment#:#qst_essay_wordcounter_enabled#:#Count Words###07 02 2020 new variable assessment#:#qst_essay_wordcounter_enabled_info#:#The entered words are counted. The number of written words is shown to the participants below the text input field.###07 02 2020 new variable assessment#:#qst_essay_written_words#:#Number of entered words:###07 02 2020 new variable -assessment#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable -assessment#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable -assessment#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable -assessment#:#qst_lifecycle_final#:#Final###07 02 2020 new variable -assessment#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable -assessment#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable -assessment#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable -assessment#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable assessment#:#qst_nested_nested_answers_off#:#No indents, just order###29 07 2022 new variable assessment#:#qst_nested_nested_answers_on#:#Use indents in anwers###29 07 2022 new variable assessment#:#qst_nr_of_tries#:#Number of tries###15 05 2009 new variable @@ -1169,17 +1145,14 @@ assessment#:#question_title#:#Tiêu đề câu hỏi assessment#:#question_type#:#Kiểu câu hỏi assessment#:#questionpool_not_entered#:#Hãy nhập vào tên thư viện câu hỏi! assessment#:#questionpool_not_selected#:#Please select a question pool!###07 02 2020 new variable -assessment#:#questions#:#Questions###26 08 2024 new variable assessment#:#questions_from#:#câu hỏi từ assessment#:#questions_per_page_view#:#Page View###12 06 2011 new variable assessment#:#random_accept_sample#:#Chấp nhận mẫu assessment#:#random_another_sample#:#Lấy mẫu khác assessment#:#random_selection#:#Lựa chọn ngẫu nhiên assessment#:#range#:#Range###25 02 2007 new variable -assessment#:#range_lower_limit#:#Lower Bound###25 02 2007 new variable assessment#:#range_max#:#Range (Maximum)###24 10 2013 new variable assessment#:#range_min#:#Range (Minimum)###24 10 2013 new variable -assessment#:#range_upper_limit#:#Upper Bound###25 02 2007 new variable assessment#:#rated_sign#:#Sign###24 10 2013 new variable assessment#:#rated_unit#:#Unit###24 10 2013 new variable assessment#:#rated_value#:#Value###24 10 2013 new variable @@ -1255,7 +1228,6 @@ assessment#:#search_roles#:#Tìm kiếm vai trò assessment#:#search_term#:#Tìm kiếm thuật ngữ assessment#:#select_at_least_one_feedback_type_and_trigger#:#Please Select at least one type of feedback and a trigger.###26 08 2024 new variable assessment#:#select_at_least_one_lock_answer_type#:#Please select at least one type of answer lock.###29 10 2025 new variable -assessment#:#select_gap#:#Lựa chọn từ điền assessment#:#select_max_one_item#:#Hãy chọn chỉ một mục assessment#:#select_one_user#:#Hãy chọn ít nhất một người sử dụng assessment#:#select_question#:#Select a Question###28 10 2024 new variable @@ -1282,7 +1254,6 @@ assessment#:#show_old_introduction#:#Show old introduction###26 08 2024 new vari assessment#:#show_pass_overview#:#Show Marked Pass Overview###31 05 2007 new variable assessment#:#show_results#:#Show Results###28 10 2024 new variable assessment#:#show_user_answers#:#Show User's Marked Anwers###31 05 2007 new variable -assessment#:#shuffle_answers#:#Xáo trộn các lựa chọn assessment#:#skip_question#:#Do not Answer and Next###08 10 2015 new variable assessment#:#solution#:#Solution###28 10 2024 new variable assessment#:#solutionText#:#Text###24 02 2009 new variable @@ -13973,6 +13944,35 @@ qpl#:#qpl_page_type_qfbg#:#General Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qfbs#:#Special Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qht#:#Hint###29 07 2022 new variable qpl#:#qpl_page_type_qpl#:#Question Page###29 07 2022 new variable +qsts#:#answer_options#:#Answer Options ###23 12 2015 new variable +qsts#:#cloze_text#:#Đoạn văn điền chỗ trống +qsts#:#cloze_textgapcase_insensitive#:#Không phân biệt chữ hoa chữ thường +qsts#:#cloze_textgapcase_sensitive#:#Phân biệt chữ hoa chữ thường +qsts#:#cloze_textgaplevenshtein_of#:#Levenshtein distance of %s/// +qsts#:#confirm_delete_questions#:#Bạn có chắc chắn muốn xóa (các) câu hỏi sau không? Nếu bạn xóa các câu hỏi đã khóa kết quả của tất cả bài kiểm tra chứa câu hỏi đã khóa sẽ bị xóa theo. +qsts#:#create_question#:#Create Question###24 10 2013 new variable +qsts#:#gap#:#Chỗ trống +qsts#:#gaps#:#Gaps###29 10 2025 new variable +qsts#:#insert_gap#:#Insert Gap###24 10 2013 new variable +qsts#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable +qsts#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable +qsts#:#out_of_range#:#Out of range###27 01 2015 new variable +qsts#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable +qsts#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable +qsts#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable +qsts#:#qst_lifecycle_final#:#Final###07 02 2020 new variable +qsts#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable +qsts#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable +qsts#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable +qsts#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable +qsts#:#questionlist#:#Questionlist###26 08 2024 new variable +qsts#:#questions#:#Questions###26 08 2024 new variable +qsts#:#range_lower_limit#:#Lower Bound###25 02 2007 new variable +qsts#:#range_upper_limit#:#Upper Bound###25 02 2007 new variable +qsts#:#reset_preview#:#Reset Preview###26 09 2014 new variable +qsts#:#select_gap#:#Lựa chọn từ điền +qsts#:#shuffle_answers#:#Xáo trộn các lựa chọn +qsts#:#suggested_learning_content#:#Add suggested solution###24 02 2009 new variable rating#:#rat_not_rated_yet#:#Not Rated Yet###12 06 2011 new variable rating#:#rat_nr_ratings#:#%s Ratings###12 06 2011 new variable rating#:#rat_one_rating#:#One Rating###12 06 2011 new variable @@ -16622,7 +16622,6 @@ survey#:#questionblock#:#Khối câu hỏi survey#:#questionblock_inserted#:#Question Block inserted###06 08 2009 new variable survey#:#questionblocks#:#Khối câu hỏi survey#:#questionblocks_inserted#:#Question Blocks inserted###06 08 2009 new variable -survey#:#questions#:#Câu hỏi survey#:#questions_inserted#:#Đã thêm (các) câu hỏi! survey#:#questions_removed#:#Câu hỏi và/hoặc khối câu hỏi đã xóa! survey#:#questiontype#:#Kiểu câu hỏi @@ -16923,6 +16922,7 @@ survey#:#svy_please_select_unused_codes#:#Please select at least one unused code survey#:#svy_print_hide_labels#:#Hide labels###31 08 2017 new variable survey#:#svy_print_show_labels#:#Show labels###31 08 2017 new variable survey#:#svy_privacy_info#:#Privacy###29 07 2022 new variable +survey#:#svy_questions#:#Câu hỏi survey#:#svy_rater#:#Rater###29 07 2022 new variable survey#:#svy_rater_see_app_info#:#The names of appraisees will be presented to raters to enable them evaluating the questions.###29 07 2022 new variable survey#:#svy_reminder_mail_template#:#Mail Template###25 10 2016 new variable diff --git a/lang/ilias_zh.lang b/lang/ilias_zh.lang index dbdeaeaf4282..f906601c469c 100644 --- a/lang/ilias_zh.lang +++ b/lang/ilias_zh.lang @@ -421,7 +421,6 @@ adve#:#adve_use_tiny_mce#:#为所见即所得编辑启用TinyMCE。 assessment#:#activate_logging#:#激测试 & 评估日志 assessment#:#activate_manual_scoring#:#Enable Manual Scoring###28 10 2024 new variable assessment#:#activate_manual_scoring_desc#:#Enables manual Scoring for all question types.###28 10 2024 new variable -assessment#:#addSuggestedSolution#:#添加建议解决方案 assessment#:#add_answers#:#Add Antworten ###23 12 2015 new variable assessment#:#add_circle#:#添加圆区域 assessment#:#add_gap#:#添加填空文字 @@ -446,7 +445,6 @@ assessment#:#answer_is_not_correct_but_positive#:#您已经获取了解决方案 assessment#:#answer_is_right#:#回答正确 assessment#:#answer_is_wrong#:#回答错误 assessment#:#answer_of#:#Answer of###07 02 2020 new variable -assessment#:#answer_options#:#Answer Options: ###23 12 2015 new variable assessment#:#answer_question#:#Answer Question###30 08 2015 new variable assessment#:#answer_text#:#答案文本 assessment#:#answer_types#:#答案类型 @@ -500,7 +498,6 @@ assessment#:#ass_completion_by_submission#:#提交已完成 assessment#:#ass_completion_by_submission_info#:#如果启用,至少一个文件因为通过为这个题目授予最高分完成这个题目而提交。这个分数以后可以手工修改。切换这个设置不影响已提交的方案。 assessment#:#ass_create_export_file_with_results#:#Create Test Export File (incl. Participant Results)###25 10 2016 new variable assessment#:#ass_create_export_test_archive#:#Create Test Archive File###24 10 2013 new variable -assessment#:#ass_create_question#:#Create Question###24 10 2013 new variable assessment#:#ass_imap_hint#:#Hint to be shown as Tooltip###25 10 2016 new variable assessment#:#ass_imap_map_file_not_readable#:#The uploaded image map could not be read.###25 10 2016 new variable assessment#:#ass_imap_no_map_found#:#Could not find any form in the uploaded image map.###25 10 2016 new variable @@ -570,10 +567,6 @@ assessment#:#cloze_answer_text_info#:#Spaces preceding or following the answer t assessment#:#cloze_fixed_textlength#:#文本域长度 assessment#:#cloze_fixed_textlength_description#:#如果输入一个大于0的值,所有文本和数字的文本域将以这个值的固定长度被创建。 assessment#:#cloze_gap_size_info#:#If you enter a value greater than 0, this gap text field will be created with the fixed length of this value. If you do not enter a value the gap text fiel will be created with the value of the global fixed length.###26 09 2014 new variable -assessment#:#cloze_text#:#填充测验文本 -assessment#:#cloze_textgap_case_insensitive#:#不区分大小写 -assessment#:#cloze_textgap_case_sensitive#:#区分大小定 -assessment#:#cloze_textgap_levenshtein_of#:#Levenshtein distance of %s assessment#:#code#:#代码 assessment#:#codebase#:#代码库 assessment#:#concatenation#:#级联 @@ -756,9 +749,7 @@ assessment#:#fq_formula_desc#:#You may enter predefined variables ($v1 to $vn), assessment#:#fq_no_restriction_info#:#Both decimals and fractions are accepted as input.###26 03 2014 new variable assessment#:#fq_precision_info#:#Enter the number of desired decimal places.###26 03 2014 new variable assessment#:#fq_question_desc#:#You can define variables by inserting $v1, $v2 ... $vn, results by inserting $r1, $r2 .... $rn at the desired position in the question text. Click on the button "Parse Question" to create editing forms for variables and results.###06 11 2013 new variable -assessment#:#gap#:#间隔 assessment#:#gap_combination#:#Gap Combination###26 09 2014 new variable -assessment#:#gaps#:#Gaps###29 10 2025 new variable assessment#:#glossary_term#:#术语表 assessment#:#goto_first_question#:#Show First Question###29 10 2025 new variable assessment#:#grading_mark_msg#:#Your resulting mark is: "[mark]"###26 09 2014 new variable @@ -776,7 +767,6 @@ assessment#:#info_answer_type_change#:#这道题已经包含图片。您不能 assessment#:#info_text_upload#:#Choose an answer file to upload###30 08 2015 new variable assessment#:#insert_after#:#后插 assessment#:#insert_before#:#前插 -assessment#:#insert_gap#:#Insert Gap###24 10 2013 new variable assessment#:#interaction_type#:#Interaction Type###26 08 2024 new variable assessment#:#internal_links#:#内部链接 assessment#:#intprecision#:#Divisible By###24 10 2013 new variable @@ -888,7 +878,6 @@ assessment#:#logs_wrong_test_password_provided#:#Participant entered wrong test assessment#:#longmenu#:#Longmenu###10 11 2018 new variable assessment#:#longmenu_answeroptions_differ#:#This question does not work correctly, as there are not the same amount of gaps in the text as in the correction options.###26 08 2024 new variable assessment#:#longmenu_text#:#Long Menu Text###30 08 2015 new variable -assessment#:#mainbar_button_label_questionlist#:#Questionlist###26 08 2024 new variable assessment#:#maintenance#:#Maintenance维护 assessment#:#manscoring#:#Manual Scoring手动评分 assessment#:#manscoring_done#:#Scored Participants评分参加者 @@ -915,7 +904,6 @@ assessment#:#maximum_nr_of_tries_reached#:#用户已经达到了每日所限制 assessment#:#maximum_points#:#最大有效分值 assessment#:#maxsize#:#文件上传的最大尺寸 assessment#:#maxsize_info#:#输入将被允许上传的文件的最大值(单位字节)。如果这个域留空,这次安装的最大尺寸将被选择替代。 -assessment#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable assessment#:#min_ip_label#:#Lowest IP With Access###26 08 2024 new variable assessment#:#min_percentage_ne_0#:#您必须定义一个最低百分比0%!这个标记模式没被保存。 assessment#:#misc#:#Misc Options###24 10 2013 new variable @@ -924,7 +912,6 @@ assessment#:#mode_onebyone#:#One by One###29 10 2025 new variable assessment#:#mode_question#:#Question oriented###29 10 2025 new variable assessment#:#mode_user#:#Participant oriented###29 10 2025 new variable assessment#:#msg_circle_added#:#加入圈子 -assessment#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable assessment#:#msg_number_of_terms_too_low#:#项目的数量必需大于或等于定义的数量。 assessment#:#msg_poly_added#:#添加多边形 assessment#:#msg_questions_moved#:#题目被移除 @@ -983,7 +970,6 @@ assessment#:#order#:#Order###26 08 2024 new variable assessment#:#ordering_answer_sequence_info#:#您定义在这里的答案序列将被视为正确的方案序列。 assessment#:#ordertext#:#文本排序 assessment#:#ordertext_info#:#请输入水平排序的文本。这个排序文本将被空白标志分隔开。如果您需要一个不同的分隔方式,您可以使用分隔器 %s 去分隔您的文本单元。 -assessment#:#out_of_range#:#Out of range###27 01 2015 new variable assessment#:#output#:#输出 assessment#:#output_mode#:#输出模式 assessment#:#parseQuestion#:#Parse Question###24 10 2013 new variable @@ -1052,7 +1038,6 @@ assessment#:#qpl_bulk_save_add#:#Add###28 10 2024 new variable assessment#:#qpl_bulk_save_overwrite#:#Overwrite###28 10 2024 new variable assessment#:#qpl_bulkedit_success#:#Modifications saved.###28 10 2024 new variable assessment#:#qpl_cancel_skill_assigns_update#:#Cancel###30 08 2015 new variable -assessment#:#qpl_confirm_delete_questions#:#您确认删除下面的题目?如果您删除已经锁定的题目,所有包含被锁定题目的测试结果也将被删除。 assessment#:#qpl_copy_insert_clipboard#:#被选择的题目已经复制到了剪贴板上 assessment#:#qpl_copy_select_none#:#请至少勾选一个题目,然后复制到剪贴板上 assessment#:#qpl_delete_rbac_error#:#您无权删除此题目! @@ -1105,7 +1090,6 @@ assessment#:#qpl_qst_skl_usg_skill_col#:#Competence###30 08 2015 new variable assessment#:#qpl_qst_skl_usg_sklpnt_col#:#Total Sum of Competence-Points per Competence###30 08 2015 new variable assessment#:#qpl_question_is_in_use#:#您即将编辑的题目已存在于 %s 测试中。如果您改变这个题目,您将不能改变这个测试中的题目,因为当一个题目插入到一个测试中时,系统会创建一个它的复本。 assessment#:#qpl_questions_deleted#:#题目已删除 -assessment#:#qpl_reset_preview#:#Reset Preview###26 09 2014 new variable assessment#:#qpl_save_skill_assigns_update#:#Save Competence Assignments###30 08 2015 new variable assessment#:#qpl_settings_availability#:#Availability###26 08 2024 new variable assessment#:#qpl_settings_general_form_prop_show_tax_desc#:#When enabled, possibly created taxonomies are shown for filtering.###24 10 2013 new variable @@ -1135,14 +1119,6 @@ assessment#:#qst_essay_chars_remaining#:#Remaining characters:###07 02 2020 new assessment#:#qst_essay_wordcounter_enabled#:#Count Words###07 02 2020 new variable assessment#:#qst_essay_wordcounter_enabled_info#:#The entered words are counted. The number of written words is shown to the participants below the text input field.###07 02 2020 new variable assessment#:#qst_essay_written_words#:#Number of entered words:###07 02 2020 new variable -assessment#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable -assessment#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable -assessment#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable -assessment#:#qst_lifecycle_final#:#Final###07 02 2020 new variable -assessment#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable -assessment#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable -assessment#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable -assessment#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable assessment#:#qst_nested_nested_answers_off#:#No indents, just order###29 07 2022 new variable assessment#:#qst_nested_nested_answers_on#:#Use indents in anwers###29 07 2022 new variable assessment#:#qst_nr_of_tries#:#尝试次数。 @@ -1166,17 +1142,14 @@ assessment#:#question_title#:#题目标题 assessment#:#question_type#:#题目类型 assessment#:#questionpool_not_entered#:#请为题目池输入一个名字! assessment#:#questionpool_not_selected#:#Please select a question pool!###07 02 2020 new variable -assessment#:#questions#:#Questions###26 08 2024 new variable assessment#:#questions_from#:#题目表单 assessment#:#questions_per_page_view#:#页面视图 assessment#:#random_accept_sample#:#Accept sample assessment#:#random_another_sample#:#获取其它示例 assessment#:#random_selection#:#随机选择 assessment#:#range#:#范围 -assessment#:#range_lower_limit#:#下限 assessment#:#range_max#:#Range (Maximum)###24 10 2013 new variable assessment#:#range_min#:#Range (Minimum)###24 10 2013 new variable -assessment#:#range_upper_limit#:#上限 assessment#:#rated_sign#:#Sign###24 10 2013 new variable assessment#:#rated_unit#:#Unit###24 10 2013 new variable assessment#:#rated_value#:#Value###24 10 2013 new variable @@ -1252,7 +1225,6 @@ assessment#:#search_roles#:#搜索角色 assessment#:#search_term#:#搜索条目 assessment#:#select_at_least_one_feedback_type_and_trigger#:#Please Select at least one type of feedback and a trigger.###26 08 2024 new variable assessment#:#select_at_least_one_lock_answer_type#:#Please select at least one type of answer lock.###29 10 2025 new variable -assessment#:#select_gap#:#选择间隔 assessment#:#select_max_one_item#:#请仅选择一个条目 assessment#:#select_one_user#:#请至少选择一个用户 assessment#:#select_question#:#Select a Question###28 10 2024 new variable @@ -1279,7 +1251,6 @@ assessment#:#show_old_introduction#:#Show old introduction###26 08 2024 new vari assessment#:#show_pass_overview#:#展示已标注通过的概况 assessment#:#show_results#:#Show Results###28 10 2024 new variable assessment#:#show_user_answers#:#展示用户的已标注答案 -assessment#:#shuffle_answers#:#简答题 assessment#:#skip_question#:#Do not Answer and Next###08 10 2015 new variable assessment#:#solution#:#Solution###28 10 2024 new variable assessment#:#solutionText#:#Text文档 @@ -13970,6 +13941,35 @@ qpl#:#qpl_page_type_qfbg#:#General Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qfbs#:#Special Feedback###29 07 2022 new variable qpl#:#qpl_page_type_qht#:#Hint###29 07 2022 new variable qpl#:#qpl_page_type_qpl#:#Question Page###29 07 2022 new variable +qsts#:#answer_options#:#Answer Options ###23 12 2015 new variable +qsts#:#cloze_text#:#填充测验文本 +qsts#:#cloze_textgapcase_insensitive#:#不区分大小写 +qsts#:#cloze_textgapcase_sensitive#:#区分大小定 +qsts#:#cloze_textgaplevenshtein_of#:#Levenshtein distance of %s +qsts#:#confirm_delete_questions#:#您确认删除下面的题目?如果您删除已经锁定的题目,所有包含被锁定题目的测试结果也将被删除。 +qsts#:#create_question#:#Create Question###24 10 2013 new variable +qsts#:#gap#:#间隔 +qsts#:#gaps#:#Gaps###29 10 2025 new variable +qsts#:#insert_gap#:#Insert Gap###24 10 2013 new variable +qsts#:#min_auto_complete#:#Autocomplete###25 10 2016 new variable +qsts#:#msg_no_questions_selected#:#No questions were selected.###26 08 2024 new variable +qsts#:#out_of_range#:#Out of range###27 01 2015 new variable +qsts#:#qst_lifecycle#:#Lifecycle###07 02 2020 new variable +qsts#:#qst_lifecycle_draft#:#Draft###07 02 2020 new variable +qsts#:#qst_lifecycle_filter_all#:#All Lifecycles###07 02 2020 new variable +qsts#:#qst_lifecycle_final#:#Final###07 02 2020 new variable +qsts#:#qst_lifecycle_outdated#:#Outdated###07 02 2020 new variable +qsts#:#qst_lifecycle_rejected#:#Rejected###07 02 2020 new variable +qsts#:#qst_lifecycle_review#:#To be Reviewed###07 02 2020 new variable +qsts#:#qst_lifecycle_sharable#:#Sharable###07 02 2020 new variable +qsts#:#questionlist#:#Questionlist###26 08 2024 new variable +qsts#:#questions#:#Questions###26 08 2024 new variable +qsts#:#range_lower_limit#:#下限 +qsts#:#range_upper_limit#:#上限 +qsts#:#reset_preview#:#Reset Preview###26 09 2014 new variable +qsts#:#select_gap#:#选择间隔 +qsts#:#shuffle_answers#:#简答题 +qsts#:#suggested_learning_content#:#添加建议解决方案 rating#:#rat_not_rated_yet#:#还没有评级 rating#:#rat_nr_ratings#:#%s 评级 rating#:#rat_one_rating#:#One 评级 @@ -16619,7 +16619,6 @@ survey#:#questionblock#:#题目块 survey#:#questionblock_inserted#:#问题块已插入 survey#:#questionblocks#:#题目块 survey#:#questionblocks_inserted#:#题目块已插入 -survey#:#questions#:#题目。 survey#:#questions_inserted#:#题目已插入! survey#:#questions_removed#:#题目和/或题目块已删除! survey#:#questiontype#:#题目类型 @@ -16920,6 +16919,7 @@ survey#:#svy_please_select_unused_codes#:#Please select at least one unused code survey#:#svy_print_hide_labels#:#Hide labels###31 08 2017 new variable survey#:#svy_print_show_labels#:#Show labels###31 08 2017 new variable survey#:#svy_privacy_info#:#Privacy###29 07 2022 new variable +survey#:#svy_questions#:#题目。 survey#:#svy_rater#:#Rater###29 07 2022 new variable survey#:#svy_rater_see_app_info#:#The names of appraisees will be presented to raters to enable them evaluating the questions.###29 07 2022 new variable survey#:#svy_reminder_mail_template#:#Mail Template###25 10 2016 new variable diff --git a/templates/default/070-components/_index.scss b/templates/default/070-components/_index.scss index fa625b1ab3aa..7cc0aefd786c 100755 --- a/templates/default/070-components/_index.scss +++ b/templates/default/070-components/_index.scss @@ -88,6 +88,7 @@ @use "./legacy/Modules/_component_orgunit.scss"; @use "./legacy/Modules/_component_poll.scss"; @use "./legacy/Modules/_component_portfolio.scss"; +@use "./legacy/Modules/_component_questions.scss"; @use "./legacy/Modules/_component_scormaicc.scss"; @use "./legacy/Modules/_component_survey.scss"; @use "./legacy/Modules/_component_test.scss"; diff --git a/templates/default/070-components/legacy/Modules/_component_questions.scss b/templates/default/070-components/legacy/Modules/_component_questions.scss new file mode 100755 index 000000000000..06791bee6405 --- /dev/null +++ b/templates/default/070-components/legacy/Modules/_component_questions.scss @@ -0,0 +1,19 @@ +@use "../../../010-settings/" as *; +@use "../../../050-layout/basics" as *; + +$il-questions-solution-value-background: $il-highlight-bg; +$il-questions-solution-value-padding: $il-padding-base-vertical; +$il-questions-async-answerform-padding: $il-padding-large-vertical; + +.c-questions__solution-value { + background: $il-questions-solution-value-background; + padding: $il-questions-solution-value-padding; +} + +.c-questions__async-answerform { + padding-bottom: $il-questions-async-answerform-padding; +} + +.c-questions_async-answerform-feedback { + padding-top: $il-questions-async-answerform-padding; +} diff --git a/templates/default/070-components/legacy/Modules/_component_test.scss b/templates/default/070-components/legacy/Modules/_component_test.scss index ddb96549b42e..8ca27135fdda 100755 --- a/templates/default/070-components/legacy/Modules/_component_test.scss +++ b/templates/default/070-components/legacy/Modules/_component_test.scss @@ -16,6 +16,8 @@ $il-test-margin-large-horizontal: $il-padding-large-horizontal; $il-test-working-time-font-size: $il-font-size-large; $il-test-working-time-font-weight: $il-font-weight-bold; +$il-test-solution-value-background: $il-highlight-bg; + // general layout #tst_output { display: flex; @@ -172,9 +174,15 @@ $cons-scoring-bottom-fade-height: $il-padding-xxxlarge-vertical * 2; display: none; } - // fix for ordering question - .ilc_qanswer_Answer.solutionbox { - width: auto; + .ilc_qanswer_Answer { + // fix for ordering question + .solutionbox { + width: auto; + } + + .ilc_answers.answers.ilAssClozeTest div input[type=text] { + width: 100%; + } } } @@ -390,4 +398,4 @@ $cons-scoring-bottom-fade-height: $il-padding-xxxlarge-vertical * 2; } } } -} +} \ No newline at end of file diff --git a/templates/default/070-components/legacy/Modules/_component_test_legacy.scss b/templates/default/070-components/legacy/Modules/_component_test_legacy.scss index fc89928f1838..5d0f70e7bd21 100755 --- a/templates/default/070-components/legacy/Modules/_component_test_legacy.scss +++ b/templates/default/070-components/legacy/Modules/_component_test_legacy.scss @@ -538,8 +538,8 @@ td.ilc_Page { } .ilTestMarkQuestionIcon { - width: 12px; - height: 12px; + width: 28px; + height: 28px; } .ilTestAnswerStatusIcon { diff --git a/templates/default/delos.css b/templates/default/delos.css index 0b838ddd9024..dc942606aae3 100644 --- a/templates/default/delos.css +++ b/templates/default/delos.css @@ -15942,6 +15942,19 @@ body.ilPrtfPdfBody .ilPCMyCoursesToggle img { visibility: hidden; } +.c-questions__solution-value { + background: rgb(226.2857142857, 231.6428571429, 238.7142857143); + padding: 3px; +} + +.c-questions__async-answerform { + padding-bottom: 6px; +} + +.c-questions_async-answerform-feedback { + padding-top: 6px; +} + /* Modules/ScormAicc */ table.il_ScormTable { color: #161616; @@ -16623,8 +16636,8 @@ td.ilc_Page { } .ilTestMarkQuestionIcon { - width: 12px; - height: 12px; + width: 28px; + height: 28px; } .ilTestAnswerStatusIcon { @@ -16905,6 +16918,9 @@ div.ilc_Page.readonly textarea[disabled] { .c-consecutive-scoring__answer__body .ilc_qanswer_Answer.solutionbox { width: auto; } +.c-consecutive-scoring__answer__body .ilc_qanswer_Answer.ilc_answers.answers.ilAssClozeTest div input[type=text] { + width: 100%; +} .c-consecutive-scoring__answer__grade-card { width: 40%; border-left: 1px solid #dddddd; diff --git a/templates/default/delos.scss.map b/templates/default/delos.scss.map new file mode 100644 index 000000000000..11f17f06e2dc --- /dev/null +++ b/templates/default/delos.scss.map @@ -0,0 +1 @@ +{"version":3,"sourceRoot":"","sources":["020-dependencies/_index.scss","020-dependencies/modifications/datetimepicker/bootstrap-datetimepicker.scss","030-tools/_tool_browser-prefixes.scss","010-settings/_settings_typography.scss","030-tools/_tool_screen-reader-only.scss","010-settings/_settings_borders.scss","010-settings/_settings_color-palette.scss","010-settings/_settings_button.scss","010-settings/legacy-settings/_legacy-settings_menu.scss","070-components/UI-framework/Dropdown/_ui-component_dropdown.scss","030-tools/legacy-bootstrap-mixins/_nav-divider.scss","050-layout/basics/_layout_spacing-variables.scss","010-settings/legacy-settings/_legacy-settings_form.scss","020-dependencies/modifications/_jquery-autocomplete.scss","020-dependencies/modifications/_additions_tinymce.scss","020-dependencies/modifications/_additions_yui2.scss","040-normalize/_index.scss","040-normalize/_normalize_print.scss","040-normalize/_normalize_typography.scss","040-normalize/_normalize_input.scss","040-normalize/_normalize_structure.scss","040-normalize/_normalize_table.scss","050-layout/_layout_grid.scss","050-layout/_layout_container.scss","050-layout/_layout_element-bar.scss","050-layout/_layout_visibility-utilities.scss","060-elements/_index.scss","060-elements/_elements_dialog.scss","060-elements/_elements_html-body.scss","060-elements/_elements_input.scss","030-tools/_tool_focus-outline.scss","060-elements/_elements_lists.scss","060-elements/_elements_media.scss","060-elements/_elements_objects.scss","060-elements/_elements_tables.scss","060-elements/_elements_typography.scss","060-elements/_elements_details-summary.scss","070-components/_index.scss","070-components/UI-framework/_ui-component_tooltip.scss","030-tools/_tool_typography-mixins.scss","070-components/UI-framework/Breadcrumbs/_ui-component_breadcrumbs.scss","050-layout/standardpage/_layout_standardpage.scss","070-components/UI-framework/Button/_ui-component_button.scss","030-tools/_tool_buttons.scss","070-components/UI-framework/Button/_ui-component_tag.scss","070-components/UI-framework/Button/_ui-component_toggle.scss","070-components/UI-framework/Card/_ui-component_card.scss","010-settings/legacy-settings/_legacy-settings_panel.scss","070-components/UI-framework/Chart/_ui-component_chart.scss","010-settings/legacy-settings/_legacy-settings_chart.scss","070-components/UI-framework/Counter/_ui-component_counter.scss","070-components/UI-framework/Deck/_ui-component_deck.scss","070-components/UI-framework/Divider/_ui-component_divider.scss","070-components/UI-framework/Dropzone/_ui-component_dropzone.scss","010-settings/legacy-settings/_legacy-settings_dropzone.scss","070-components/UI-framework/Entity/_ui-component_entity.scss","070-components/UI-framework/Item/_ui-component_item.scss","070-components/UI-framework/Launcher/_ui-component_launcher.scss","010-settings/legacy-settings/_legacy-settings_symbol.scss","070-components/UI-framework/MainControls/_ui-component_metabar.scss","050-layout/standardpage/_layout_standardpage-mobile.scss","070-components/UI-framework/Layout/_ui-component_standardpage.scss","050-layout/basics/_layout_z-index.scss","010-settings/_settings_header.scss","050-layout/_layout_container-query.scss","070-components/UI-framework/Layout/_ui-component_alignment.scss","070-components/UI-framework/Link/_ui-component_link.scss","070-components/UI-framework/Listing/_ui-component_properties.scss","030-tools/_tool_clearfix.scss","070-components/UI-framework/Listing/_ui-component_characteristic_value.scss","050-layout/basics/_layout_positioning.scss","070-components/UI-framework/Listing/_ui-component_workflow.scss","070-components/UI-framework/Listing/_ui-component_entitylisting.scss","070-components/UI-framework/MainControls/Slate/_ui-component_slate.scss","030-tools/_tool_multi-line-cap.scss","070-components/legacy/_component_screen-reader-only.scss","070-components/UI-framework/MainControls/_ui-component_mainbar.scss","010-settings/_settings_mainbar.scss","070-components/UI-framework/MainControls/_ui-component_footer.scss","010-settings/_settings_footer.scss","050-layout/_layout_breakpoints.scss","070-components/UI-framework/MainControls/_ui-component_mode_info.scss","010-settings/_settings_shadows.scss","070-components/UI-framework/MainControls/_ui-component_system_info.scss","070-components/UI-framework/Menu/_ui-component_drilldown.scss","070-components/UI-framework/Input/_ui-component_tag.scss","070-components/UI-framework/Input/_ui-component_password.scss","070-components/UI-framework/Input/_ui-component_radio.scss","070-components/UI-framework/Input/_ui-component_multiselect.scss","070-components/UI-framework/Input/_ui-component_filter.scss","070-components/UI-framework/Input/_ui-component_file.scss","010-settings/legacy-settings/_legacy-settings_ui-input-file.scss","070-components/UI-framework/Input/_ui-component_markdown.scss","050-layout/_layout_form.scss","070-components/UI-framework/Input/_ui-component_option-filter.scss","070-components/UI-framework/Input/_ui-component_rating.scss","070-components/UI-framework/Input/_ui-component_section.scss","070-components/UI-framework/Input/_ui-component_numeric.scss","070-components/UI-framework/Input/_ui-component_optionalgroups.scss","070-components/UI-framework/Input/_ui-component_tree_select.scss","070-components/UI-framework/Input/_ui-component_input.scss","070-components/UI-framework/MessageBox/_ui-component_messagebox.scss","070-components/UI-framework/Modal/_ui-component_modal.scss","030-tools/_tool_dialog-patterns.scss","070-components/UI-framework/Navigation/_ui-component_sequence.scss","070-components/UI-framework/Panel/_ui-component_panel.scss","030-tools/_tool_border-radius.scss","070-components/UI-framework/Player/_ui-component_player.scss","020-dependencies/modifications/webui-popover/jquery.webui-popover.scss","070-components/UI-framework/Popover/_ui-component_popover.scss","070-components/UI-framework/Progress/_ui-component_progress_bar.scss","070-components/UI-framework/Symbol/_ui-component_icon.scss","070-components/UI-framework/Symbol/_ui-component_glyph.scss","070-components/UI-framework/Symbol/_ui-component_avatar.scss","070-components/UI-framework/Table/_ui-component_table.scss","030-tools/_tool_highlighted-box.scss","070-components/UI-framework/Toast/_ui-component_toast.scss","070-components/UI-framework/Tree/_ui-component_tree.scss","010-settings/legacy-settings/_legacy-settings_tree.scss","070-components/UI-framework/ViewControl/_ui-component_viewcontrol.scss","070-components/legacy/_component_agreement.scss","070-components/legacy/_component_alert.scss","070-components/legacy/_component_bottom-center-area.scss","070-components/legacy/_component_headline.scss","070-components/legacy/_component_helpsidebar.scss","070-components/legacy/_component_icon.scss","070-components/legacy/_component_LeftNavSpace.scss","070-components/legacy/_component_link.scss","070-components/legacy/_component_map.scss","070-components/legacy/_component_media-object.scss","070-components/legacy/_component_rightPanel.scss","070-components/legacy/_component_delostable.scss","070-components/legacy/_component_well.scss","070-components/legacy/_component_php.scss","070-components/legacy/_component_animated-collapse-fade.scss","070-components/legacy/_component_btn-group.scss","050-layout/_layout_responsive-img.scss","070-components/legacy/_component_carousel.scss","070-components/legacy/_component_input-group.scss","070-components/legacy/Modules/_component_bibliographic.scss","070-components/legacy/Modules/_component_blog.scss","070-components/legacy/Modules/_component_bookingmanager.scss","070-components/legacy/Modules/_component_chatroom.scss","070-components/legacy/Modules/_component_course.scss","070-components/legacy/Modules/_component_datacollection.scss","070-components/legacy/Modules/_component_excercise.scss","070-components/legacy/Modules/_component_forum.scss","070-components/legacy/Modules/_component_learningmodule.scss","070-components/legacy/Modules/_component_learningsequence.scss","070-components/legacy/Modules/_component_lticonsumer.scss","070-components/legacy/Modules/_component_mediacast.scss","070-components/legacy/Modules/_component_mediapool.scss","070-components/legacy/Modules/_component_orgunit.scss","070-components/legacy/Modules/_component_poll.scss","070-components/legacy/Modules/_component_portfolio.scss","070-components/legacy/Modules/_component_questions.scss","070-components/legacy/Modules/_component_scormaicc.scss","070-components/legacy/Modules/_component_survey.scss","070-components/legacy/Modules/_component_test_legacy.scss","070-components/legacy/Modules/_component_test.scss","030-tools/_tool_fade-edge.scss","070-components/legacy/Modules/_component_wiki.scss","070-components/legacy/Modules/_component_workspacefolder.scss","070-components/legacy/Modules/_component_studyprogramme.scss","070-components/legacy/Services/_component_accesscontrol.scss","070-components/legacy/Services/_component_accordion.scss","070-components/legacy/Services/_component_awareness.scss","070-components/legacy/Services/_component_cron.scss","070-components/legacy/Services/_component_badge.scss","070-components/legacy/Services/_component_block.scss","070-components/legacy/Services/_component_bookmarks.scss","070-components/legacy/Services/_component_calendar.scss","070-components/legacy/Services/_component_chart.scss","070-components/legacy/Services/_component_container.scss","070-components/legacy/Services/_component_copage.scss","070-components/legacy/Services/_component_fileupload.scss","070-components/legacy/Services/_component_form.scss","070-components/legacy/Services/_component_help.scss","070-components/legacy/Services/_component_infoscreen.scss","070-components/legacy/Services/_component_init.scss","070-components/legacy/Services/_component_learninghistory.scss","070-components/legacy/Services/_component_like.scss","070-components/legacy/Services/_component_mail.scss","070-components/legacy/Services/_component_openlayers.scss","070-components/legacy/Services/_component_mediaobjects.scss","070-components/legacy/Services/_component_membership.scss","070-components/legacy/Services/_component_navigation.scss","070-components/legacy/Services/_component_news.scss","070-components/legacy/Services/_component_notes.scss","070-components/legacy/Services/_component_object.scss","070-components/legacy/Services/_component_onscreenchat.scss","070-components/legacy/Services/_component_rating.scss","070-components/legacy/Services/_component_search.scss","070-components/legacy/Services/_component_skill.scss","070-components/legacy/Services/_component_style.scss","070-components/legacy/Services/_component_table.scss","070-components/legacy/Services/_component_tags.scss","070-components/legacy/Services/_component_termsofservice.scss","070-components/legacy/Services/UIComponent/_component_checklist.scss","070-components/legacy/Services/UIComponent/_component_explorer2.scss","070-components/legacy/Services/UIComponent/_component_lightbox.scss","070-components/legacy/Services/UIComponent/_component_modal.scss","070-components/legacy/Services/UIComponent/_component_progressbar.scss","070-components/legacy/Services/UIComponent/_component_tabs.scss","070-components/legacy/Services/UIComponent/_component_toolbar.scss","070-components/legacy/Services/_component_user.scss","070-components/legacy/Services/_component_webdav.scss","080-hacks/_index.scss","050-layout/_layout_pull-float.scss"],"names":[],"mappings":"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A;;AAGJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EACI,aAfa;;ADzEzB;AAAA;EC4FQ;EACA;EACA,QA3BW;EA4BX;EACA;EAGA,axChIoB;EwCiIpB;EACA,aD3HQ;EC4HR;EACA,axCrGiB;EwCsGjB,iBDlIY;;AZ+BnB;AAAA;EACC,SAHwB;EAIxB;;AYPF;AAAA;EC+GQ,Y/B1Gc;E+B8GV,W/B9GU;E+BgHd,WxC3Ic;EwC6Id;EACA,KhCtIsB;;A+BgB5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAEE;;;AAKF;AAAA;AAAA;AAAA;EAEE;;;AAKJ;AAAA;EACE,W9BjB0B;;A8BmB1B;EAHF;AAAA;IAII;;;;AAIJ;ECuFQ,Y/B1Gc;E+B8GV,W/B9GU;E+BgHd,WxC3Ic;EwC6Id;EACA,KhCtIsB;EgC2ItB,kBrC3JQ;EqC4JR,OpCtJgB;EoCuJhB,cDvJS;ECwJT;EACA,crC/JQ;EqCiKJ,etC/JY;;AsCmKhB;EACI,iBDlKQ;ECoKR,kBAxGS;EAyGT,OpCnKY;EoCoKZ,cDpKK;ECqKL;EACA,cA5GS;;AAgHb;EACI,WDjKgB;ECmKhB,kBAvGU;EAwGV,OpC9KY;EoC+KZ,cD/KK;ECgLL;EACA,cA3GU;;AA+Gd;EACI,OpCtLY;EoCuLZ,iBDzLQ;;AC6LZ;AAAA;AAAA;EAEI,kBpChKS;EoCiKT,cD9LK;EC+LL;EACA,cpClKa;EoCmKb,OpCrKY;EoCsKZ,QAnGU;EAoGV;;AAqBJ;EACI,kBpC1MQ;EoC2MR,cD3Je;EC4Jf;EACA,crClOI;EqCmOJ,OrCjMI;;;AoC2ChB;ECgEQ,Y/B1Gc;E+B8GV,W/B9GU;E+BgHd,WxC3Ic;EwC6Id;EACA,KhCtIsB;EgC2ItB,kBpC5IY;EoC6IZ,OpC/Ie;EoCgJf,cDvJS;ECwJT;EACA,cpC9IgB;EoCgJZ,etC/JY;;AsCmKhB;EACI,iBDlKQ;ECoKR,kBAxGS;EAyGT,OpC5JW;EoC6JX,cDpKK;ECqKL;EACA,cA5GS;;AAgHb;EACI,WDjKgB;ECmKhB,kBAvGU;EAwGV,OpCvKW;EoCwKX,cD/KK;ECgLL;EACA,cA3GU;;AA+Gd;EACI,OpC/KW;EoCgLX,iBDzLQ;;AC6LZ;AAAA;EAEI,kBpChKS;EoCiKT,cD9LK;EC+LL;EACA,cpClKa;EoCmKb,OpCrKY;EoCsKZ,QAnGU;EAoGV;;AAqBJ;EACI,kBpC1MQ;EoC2MR,cDpIe;ECqIf;EACA,cpCjNY;EoCkNZ,OrCjMI;;;AoC6DhB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ECkBY;EACA;;AAGJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EACI,aAfa;;ADRzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EC2BQ;EACA;EACA,QA3BW;EA4BX;EACA;EAGA,axChIoB;EwCiIpB;EACA,aD3HQ;E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CA;IACA;;EAEA;IACC;;EAED;IACC;IACA;IACA;IACA;IACA,YpBrLmB;;EoBuLpB;IACC;IACA;IACA;IACA;;EAIH;IACC;IACA;;EACA;IACC;IACA;IACA;IACA;IACA;;EACA;IACC;IACG;IACH;IACA;IACA;IACA;;EACA;IACC,YpBhNkB;;EoBmNnB;IACC;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA,SC5QmC;;ED6QnC;IACC;;EAIF;IAEC;;EAQL;IACC;IACA;IACA;IACA;;EAID;IACC;IACG,QD3SkC;IC4SlC;IACH,YpBxPsC;;EoB4PvC;IACC,ODhTkC;;ECoTnC;IACC;;EAID;IACC;;EAGD;IACC;;EAEA;IACC;IACA;IACA;IACA;;EAED;IACC;IACA;;EAGD;IACC;IACA;IACA;IACA;IACA,OvD/TgB;IuDgUhB,W1DjUmB;I0DkUnB;IACA;IACA;IACA;IACA;;EACA;IACI;IACA;IACA,W1D1Ue;I0D2Uf,clDzVW;;EkD6VhB;IACC;;EAED;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;IAQC,YvDjVU;IuDkVV,cvDlVU;IuDmVV,OvD3VgB;IuD4VhB;;EAGD;IACC;;EAKF;IACC;IACA;;EAID;IACC;IACA;IACA;IACA,YDjYsC;ICkYtC,YA5XyD;IA6XzD;IACA,SCjYgC;;EDqYjC;IACC;IACG;IACH;IACG;;EAGJ;IACC,QD9Y4B;IC+Y5B,OD9Y2B;;ECkZ3B;IACC,QDpZ2B;ICqZ3B,ODpZ0B;;ECsZ3B;IACC;IACA;IACA;IACA;;EAGD;IACI,QD/ZwB;;EC0a7B;IACC;IACA;IACA;IACA;;EAQD;IACC;;EACA;IACC;;EAKF;IACC;;EAGD;IACC;IACA;IACA;IACA,gBDxcsC;;ECyctC;IACC;;EAIF;IACC,epBrcwB;;;AoByc1B;AAAA;AAAA;AAAA;AAAA;AAMA;EACC;IACC;IACA;;EAEA;IACC;IACA;IACA;;EAEA;AAAA;AAAA;AAAA;AAAA;IAKC;;EAGF;IACC;;;AI3eH;EACC;EACA;EACA;EACA,KtDI6B;;AsDD5B;EACC;;AACA;EAF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ACI,kBpC1MQ;EoC2MR,cD3Je;EC4Jf;EACA,crClOI;EqCmOJ,OrCjMI;;;AoC2ChB;ECgEQ,Y/B1Gc;E+B8GV,W/B9GU;E+BgHd,WxC3Ic;EwC6Id;EACA,KhCtIsB;EgC2ItB,kBpC5IY;EoC6IZ,OpC/Ie;EoCgJf,cDvJS;ECwJT;EACA,cpC9IgB;EoCgJZ,etC/JY;;;AsCmKhB;EACI,iBDlKQ;ECoKR,kBAxGS;EAyGT,OpC5JW;EoC6JX,cDpKK;ECqKL;EACA,cA5GS;;;AAgHb;EACI,WDjKgB;ECmKhB,kBAvGU;EAwGV,OpCvKW;EoCwKX,cD/KK;ECgLL;EACA,cA3GU;;;AA+Gd;EACI,OpC/KW;EoCgLX,iBDzLQ;;;AC6LZ;AAAA;EAEI,kBpChKS;EoCiKT,cD9LK;EC+LL;EACA,cpClKa;EoCmKb,OpCrKY;EoCsKZ,QAnGU;EAoGV;;;AAqBJ;EACI,kBpC1MQ;EoC2MR,cDpIe;ECqIf;EACA,cpCjNY;EoCkNZ,OrCjMI;;;AoC6DhB;AAAA;AAAA;AAAA;AAAA;ECkBY;EACA;;;AAGJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EACI,aAfa;;;ADRzB;AAAA;AAAA;AAAA;AAAA;EC2BQ;EACA;EACA,QA3BW;EA4BX;EACA;EAGA,axChIoB;EwCiIpB;EACA,aD3HQ;EC4HR;EACA,axCrGiB;EwCsGjB,iBDlIY;;;AZ+BnB;AAAA;AAAA;AAAA;AAAA;EACC,SAHwB;EAIxB;;;AY0DF;AAAA;AAAA;AAAA;AAAA;EC8CQ,YpC/GmB;EoCmHf,WpCnHe;EoCqHnB,WxC3Ic;EwC6Id;EACA,KhCtIsB;EgC2ItB,kBrC5HU;EqC6HV,OrC5JQ;EqC6JR,cDvJS;ECwJT;EACA,crChIU;EqCkIN,etC1JuB;;;AsC8J3B;AAAA;AAAA;AAAA;AAAA;EACI,iBDlKQ;ECoKR,kBAxGS;EAyGT,OrCzKI;EqC0KJ,cDpKK;ECqKL;EACA,crC5KI;;;AqCgLR;AAAA;AAAA;AAAA;AAAA;EACI,WDjKgB;ECmKhB,kBAvGU;EAwGV,OrCpLI;EqCqLJ,cD/KK;ECgLL;EACA,cA3GU;;;AA+Gd;AAAA;AAAA;AAAA;AAAA;EACI,OrC5LI;EqC6LJ,iBDzLQ;;;AC6LZ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAEI,kBpChKS;EoCiKT,cD9LK;EC+LL;EACA,cpClKa;EoCmKb,OpCrKY;EoCsKZ,QAnGU;EAoGV;;;AAqBJ;AAAA;AAAA;AAAA;AAAA;EACI,kBA/IW;EAgJX,cAvIe;EAwIf;EACA,crCnMM;EqCoMN,OAlJa;;;ADyBvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAEE;EACA,kBpCxFS;;;AoC0FX;AAAA;AAAA;AAAA;AAAA;EACE;;;AAKF;AAAA;AAAA;EAGE,a/BrGuB;;;A+BoH3B;EACE;EACD;EACA;EACC;EACA;EACA,avC7GuB;EuC8GvB,OpC9Ic;;;AoCgJd;EAKE;ExCpFF,oBwCqFE;ExCpFM,YwCoFN;;;AAEF;EAGE;;;AAEF;EACE,OpC9GkB;EoC+GlB,iBvCtHuB;EuCuHvB;;;AAEF;EAEE,kBnCjIiB;EmCkIjB,OnCnIoB;EmCoIpB;EACA,Q9BvIc;;;A8BwId;EACE;;;AAGJ;EACE,OpC3IY;EoC4IZ,kBpC/Ic;;;AoCyJlB;AAAA;EC3CQ,YDtHiB;ECwHb;EAIJ,WxCzIe;EwC2If;EACA,KhCnJoB;EgCwJpB,kBrC/HY;EqCgIZ,OrC1HQ;EqC2HR,cDvJS;ECwJT;EACA,crCnIY;EqCqIR,etC/JY;;;AsCmKhB;AAAA;EACI,iBDlKQ;ECoKR,kBrCzIM;EqC0IN,OrCvII;EqCwIJ,cDpKK;ECqKL;EACA,crC7IM;;;AqCiJV;AAAA;EAGI,kBAvGU;EAwGV,OrClJI;EqCmJJ,cD/KK;ECgLL;EACA,cA3GU;;;AA+Gd;AAAA;EACI,OrC1JI;EqC2JJ,iBDzLQ;;;AC6LZ;AAAA;AAAA;AAAA;AAAA;AAAA;EAEI,kBpChKS;EoCiKT,cD9LK;EC+LL;EACA,cpClKa;EoCmKb,OpCrKY;EoCsKZ,QAnGU;;;AAyHd;AAAA;EACI,kBrChMM;EqCiMN,cDhBe;ECiBf;EACA,crCtMQ;EqCuMR,ODrBa;;;ACqCb;AAAA;EACI;EACA;EACA,KhCnPY;EgCoPZ;;;AAGJ;AAAA;EACI;;;AbrLf;AAAA;EACC;EACA,QAJwB;EAKxB;;;AYoJA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAEE,eD/LuB;;;ACgMvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EACE;EACA;;;AAQN;EC7FQ,YDtHiB;EC0Hb,WD1Ha;EC4HjB,WxCvIc;EwCyId;EACA,KhCtIsB;;;A+BkO9B;ECrGQ,YDyGe;ECrGX,WDqGW;ECnGf,WxC7Ie;EwC+If;EACA,KhCtIsB;;;A+B4O9B;EACE;;;AAGF;EACE;;;AAOF;EACC;EACC,kBnCvOmB;EmCwOnB,cnCvOuB;;;AmCwOvB;EACE,kBnC1OiB;EmC2OjB,cnC1OqB;;;AmCoOzB;EAQC;EACA;EACA;;;AAGD;EACE;EACA,avCrPuB;EuCsPvB;EACA,OpCvQuB;EoCwQvB;EACA;;;AAEA;EAEE,OpClRe;EoCmRf;;;AAGF;EACE;EACA;EACA;EACA;;;AAIJ;EACE;;;A0CtRF;EACI;EACA;EACA;;AAGA;EACI;EACA;EACA;;AAGJ;EACI,YA1BiB;EA2BjB;;AAGJ;EACI;EAEA;EACA;EACA,Y3C9BgB;E2CgChB,e3CdmB;E2CenB;EACA;EACA;;AAEA;EACI,WjFtCa;EiFuCb;EACA;;AAEJ;EACI;EACA;EACA;;AAGJ;EACI;;AAEJ;EACI;EACA;EAEA;EACA;EACA;EACA;;AACA;EACI;EACA;EACA;EACA;;AtDPf;EACC;EACA,QAJwB;EAKxB;;AsDQM;EACI,cAhEc;;AAiEd;EACI;;AAEJ;EACI;;AASR;EACI;;AAMJ;AAAA;AAAA;AAAA;AAAA;EAKI;;AAGJ;EACI;;AAKJ;AAAA;AAAA;AAAA;EAGI;;AAEJ;EACI;;AAIR;EACI;;AAIA;EACI;;AAKI;AAAA;AAAA;AAAA;AAAA;AAAA;EAGI;;AAIZ;EACI,Y9EnHM;;A8EoHN;EACI;;AAKZ;EAEQ;IACI;;EAEJ;IACI;;EAGA;IACI;;EAEJ;IACI;;EAGR;IACI,cAxJU;;EAyJV;IACI;;EAEJ;IACI;;EAMR;IACI;;EAGI;IACI,kBA7KC;;EA+KL;IACI;;EAGA;AAAA;AAAA;IAGI;IACA,kB9ErKV;I8EsKU;IACA;;EACA;AAAA;AAAA;IACI,kBA3LP;IA4LO,cA5LP;;EAiMD;IACI,kBA3LO;IA4LP,cA5LO;;EA6LP;IACI,kBArMP;IAsMO,cAtMP;;EA0MG;AAAA;AAAA;IAGI;;EAOhB;AAAA;AAAA;AAAA;IAGI;;EAEJ;IACI;;EAGR;IACI;IACA;IACA,Y1CtNS;I0CuNT;IACA;;EAEA;IACI;IACA;;EAIJ;AAAA;IAEI;;EAGR;IACI,YAhPS;;EAkPb;IACI;;EAEA;IACI,Y9EpOF;;E8EuOE;AAAA;AAAA;AAAA;IAGI,kB9E1ON;I8E2OM;IACA;;EACA;AAAA;AAAA;AAAA;IACI,kBAhQH;IAiQG,cAjQH;;;AA0QrB;EACI;;AAEJ;AAAA;AAAA;EAGI,kBAzQ2B;EA0Q3B,cA1Q2B;;AA2Q3B;AAAA;AAAA;EACI,kBAnRa;EAoRb,cApRa;EAsRb;;AAGR;AAAA;EAEI;EACA;EACA,cAxRkB;EA0RlB,e3C5QmB;;A2C+QvB;AAAA;EAEI;EACA;EACA,cAjSkB;EAmSlB,e3CrRmB;;A2CwRvB;EACI,azEtToB;EyEuTpB,gBzEvToB;;;A0EF5B;EACC;EACA,kB/EkBY;E+EhBZ;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,alFMsB;;;AmF3BrB;EACE;EACA;EACA;;AACA;EACE;;AAEF;EACE;;AAKF;EACE;;AAEF;EACE;;;ACfL;EACC;EACA;EAEA;EACA,Y5EE0B;;A4ED1B;EACC;;AAED;EACC;EACA,Y5EPwB;E4EQxB,c5EVe;E4EWf;EACA;EACA;EACA;;AAED;EACC;EACA;;AAED;EACC;EACA;;;ACzBH;EACC;EACA;EACA;;AACA;EACC;;AACA;EACC;;AAED;EACC;;AACA;EACC,c7ETc;E6EUd;;AAED;EACC;;;ACsBH;EACC;;AAED;EACC,a9ElC0B;E8EmC1B,gB9EnC0B;E8EoC1B;;AAID;EACC;EACA;;AAGD;EACC;EACA;EACA;;AAGD;EACC,OA3CqC;EA4CrC,QA3CsC;EA4CtC;;AAEA;EACC,OAhDoC;EAiDpC,QAhDqC;EAiDrC,QAhDsC;EAiDtC,kBnF1CU;;AmF6CX;EACS;EACR,QAvDqC;;AA0DtC;EACC,QA3DqC;EA4DrC,SArD0C;;AAwD3C;EACC,kBnFjDiB;;AmFqDnB;EACC;;AAGD;EACC;;AAGD;EACC;EACA;EACA;EACA,OA9E+B;;AAiFhC;EACC;EACA;EACA;EACA,OApFsC;;AAsFtC;EACC;;AAIF;EACC,c9E9GgB;;A8EiHjB;AAAA;EAEC;EACA,QAtFiD;EAuFjD,SAtFkD;;AA2FjD;EACC;;AAIF;EACC;EACA;EACA,OAlGsD;;AAsGxD;EACC;;AAED;EACC;;AAED;EACC;;AAED;EACC;;AAED;EACC;;AAED;EACC;;AAED;EACC;;AAED;EACC;;;AAIF;EACC;EACA;EACA,kBnFxIiB;;AmF0IjB;EACC;;AAEA;EACC,kBnF3IiB;;AmF8IlB;EACC;EACA,OAhKoC;EAiKpC;EACA;EACA;EACA,WtFtKoB;EsFuKpB;;AAEA;EACC,eAhLyC;;AAqL5C;EACC,cArL2C;;;AAyL7C;EACC,QA7KoC;EA8KpC,c9ElMiB;E8EmMjB,gBA7K4C;;AA+K5C;EACC;EACA;EACA;EACA,WtF7LqB;;AsF+LrB;EACC;EACA,kBnF/KiB;;;AmFqLnB;EACC,WtFxMqB;;;AsF6MvB;EACC,kBnF/LiB;EmFgMjB;;AAEA;EACC;EACA;EACA;EACA;EACA,kBnF3MW;EmF4MX,WtFzNoB;EsF0NpB;EACA;EACA;EACA;;;AAIF;EACC,YAlO6C;EAmO7C;EACA,e9E9OiB;E8E+OjB,QAnOyC;EAoOzC,YnFrNiB;;AmFuNjB;EACC,WtFzOoB;EsF0OpB;;AAGD;EACC,QA3OoD;;AA8OrD;EACC,Q9EjP4B;;;A8EqP9B;EACC;EACA;EACA;EACA;;;AAIA;EACC,OnF9NoB;;;AmFkOtB;EACE;EACA,WtFnQqB;EsFoQrB,atF9OuB;EsF+OvB;EACA,OnF9Oc;EmF+Od;EACA,kBnFzPgB;EmF0PhB;EACA;;;AClRF;EACC;EACA;;;AAGD;EACC,WvFEsB;EuFDtB,a/EXiB;;;A+EclB;EACC,WvFHsB;EuFItB,OpFJiB;;;AoFOlB;AAAA;EAEC,OnFgBoB;EmFfpB;EACA;;;AAGD;EACC;EACA,e/EvB2B;E+EwB3B,S/E7BiB;;;A+EgClB;EACC,WvFrBsB;;AuFsBtB;EACC;;AAED;EACC,a/EtCgB;E+EuChB;;AAED;EACC,c/E1CgB;;A+E4CjB;EACC,c/E7CgB;E+E8ChB;;;AAIF;EACC,QClD+B;EDmD/B,kBpFtBmB;EoFuBnB,Y/ErDiB;E+EsDjB;EACA;EACA;;AAEA;EACC,kBrF1D0B;EqF2D1B;EACA;EACA;EACA;EACA;EACA;;AACA;EACC,kBpF7DgB;;AoF+DjB;EACC,kBpF5DgB;;;AsFfnB;AAAA;AAAA;AASA;EACE;EACA;EACA;EACA,KjFNyB;EiFOzB,ejFPyB;;;AiFa3B;AAAA;EAEE,OtFMW;;;AsFHb;EACE,YCpBmB;EDqBnB,QvFpBe;EuFqBf;EACA;EACA,cjFnB4B;EiFoB5B,ejFpB4B;EiFqB5B;;;AAIF;EACE;;;AAGF;EACE;;AAEA;EACE;;;AErCF;AAAA;EACE;EACA;EACA;EACA;EACA;;AAEA;AAAA;EACE;;AAEF;AAAA;EACE;;AAGF;AAAA;EACE,YnFZsB;EmFatB;;AAGF;AAAA;EACE;EACA;EACA;EACA;EACA;;AACA;AAAA;EACE;EACA;EACA;EACA,enF1BoB;EmF2BpB;;AACA;AAAA;EACE,cnF5BoB;;AmF8BtB;AAAA;EACE;;AAGJ;AAAA;EACE,anFnCsB;EmFoCtB;;AAIJ;AAAA;EACE,YnF1CsB;EmF2CtB;;AAIF;AAAA;EACE;EACA;;AAEA;AAAA;EACE;;AAEA;AAAA;EACE;EACA;;AAKN;AAAA;EACE;EACA;;AAEA;AAAA;EACE;;AAEA;AAAA;EACE;EACA;EACA,enFvEkB;;AmFgFxB;AAAA;EACE;;AAIA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAIE;;AAIF;AAAA;AAAA;AAAA;EAEE;;AAIF;AAAA;EACE;;AAMN;AAAA;EACE;EACA,QzF/Ga;;AyFiHb;AAAA;EACE;;AAGF;AAAA;EACE;;AAGF;AAAA;EACE,YnFvHsB;EmFwHtB;EACA,QzF5HW;EyF6HX;EACA,YAlIY;EAmIZ;;;AAMN;EACE;IACE;IACA;IACA;;EAEF;IACE;IACA;IACA;;EAEF;IACE;IACA;IACA;;EAEF;IACE;IACA;IACA;;;AAKJ;EACE;IACE;IACA;IACA;;EAEF;IACE;IACA;IACA;;EAEF;IACE;IACA;IACA;;EAEF;IACE;IACA;IACA;;;AAKJ;EACE;IACE;;EAEF;IACE,YA9Lc;;;ACChB;EACE;;AAEF;EACE;;AAEF;EACE;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA,OzFEc;;AyFAhB;EACE;EACA,OzFCY;EyFAZ;;AACA;EACE;;AAIF;EACE,OzFOgB;;AyFJlB;EACE;;AAIJ;AAAA;EAEE,OzFHkB;;AyFKpB;EACI;;AAIF;AAAA;AAAA;EAGE,QnF7BY;EmF8BZ,OzF5CmB;;AyF+CrB;EACE;;AAIJ;EACE;EACA;EACA,kBzF1Cc;;AyF4ChB;EACE;EACA,kBzF3DqB;EyF4DrB;;;AClFJ;EACE;EACA;EACA;;AAEA;EACE;EACA;;AACA;EACE;;AAEF;EACE;EACA,W7FKmB;E6FJnB,a7FwBmB;;A6FpBvB;EACE;EACA;;AAGF;EAEE;;AAGF;EACE,YrFrB0B;EqFsB1B;EACA;;;AClCJ;EACC;;;ACkBD;AAAA;EAEC;EACA;;AAEA;AAAA;EACC;EACA;EACA;;AACA;AAAA;EACC;;AACA;AAAA;EACC,YvFrBwB;;AuFkB1B;AAAA;EAKC;EACA,kB5FFe;;A4F1BjB;AAAA;AAAA;EAEE,YA2BiC;EA1BjC,kB5F0BiB;;A4FxBlB;AAAA;AAAA;AAAA;EAEC;EACA;;AAyBF;AAAA;EACC;;AAGD;AAAA;EACC;;AAzBF;AAAA;EA4BC;EACA;EACA;EACA;;AACA;AAAA;EACC;EACA;EACA;;;AAMD;EACC;;AAIA;EACC;;AA5DF;EAEE,YA4DgC;EA3DhC,kBA2DsC;;AAzDvC;AAAA;EAEC;EACA;;;AA0DH;EACC;;AAKA;EACC;EACA;EACA;EACA;EACA;;AAEA;EACC;EACA;;AAGD;EACC;EACA;;;AlBtCC;EkB4CH;AAAA;IAEC;IACA;;EAIA;AAAA;IACC;IACA;;;AAOH;AAAA;EAEC;;AACA;AAAA;EACC;;;AdzFF;EACI;EACA;EACA;;AAGA;EACI;EACA;EACA;;AAGJ;EACI,YA1BiB;EA2BjB;;AAGJ;EACI;EAEA;EACA;EACA,Y3C9BgB;E2CgChB,e3CdmB;E2CenB;EACA;EACA;;AAEA;EACI,WjFtCa;EiFuCb;EACA;;AAEJ;EACI;EACA;EACA;;AAGJ;EACI;;AAEJ;EACI;EACA;EAEA;EACA;EACA;EACA;;AACA;EACI;EACA;EACA;EACA;;AtDPf;EACC;EACA,QAJwB;EAKxB;;AsDQM;EACI,cAhEc;;AAiEd;EACI;;AAEJ;EACI;;AASR;EACI;;AAMJ;AAAA;AAAA;AAAA;AAAA;EAKI;;AAGJ;EACI;;AAKJ;AAAA;AAAA;AAAA;EAGI;;AAEJ;EACI;;AAIR;EACI;;AAIA;EACI;;AAKI;AAAA;AAAA;AAAA;AAAA;AAAA;EAGI;;AAIZ;EACI,Y9EnHM;;A8EoHN;EACI;;AAKZ;EAEQ;IACI;;EAEJ;IACI;;EAGA;IACI;;EAEJ;IACI;;EAGR;IACI,cAxJU;;EAyJV;IACI;;EAEJ;IACI;;EAMR;IACI;;EAGI;IACI,kBA7KC;;EA+KL;IACI;;EAGA;AAAA;AAAA;IAGI;IACA,kB9ErKV;I8EsKU;IACA;;EACA;AAAA;AAAA;IACI,kBA3LP;IA4LO,cA5LP;;EAiMD;IACI,kBA3LO;IA4LP,cA5LO;;EA6LP;IACI,kBArMP;IAsMO,cAtMP;;EA0MG;AAAA;AAAA;IAGI;;EAOhB;AAAA;AAAA;AAAA;IAGI;;EAEJ;IACI;;EAGR;IACI;IACA;IACA,Y1CtNS;I0CuNT;IACA;;EAEA;IACI;IACA;;EAIJ;AAAA;IAEI;;EAGR;IACI,YAhPS;;EAkPb;IACI;;EAEA;IACI,Y9EpOF;;E8EuOE;AAAA;AAAA;AAAA;IAGI,kB9E1ON;I8E2OM;IACA;;EACA;AAAA;AAAA;AAAA;IACI,kBAhQH;IAiQG,cAjQH;;;AA0QrB;EACI;;AAEJ;AAAA;AAAA;EAGI,kBAzQ2B;EA0Q3B,cA1Q2B;;AA2Q3B;AAAA;AAAA;EACI,kBAnRa;EAoRb,cApRa;EAsRb;;AAGR;AAAA;EAEI;EACA;EACA,cAxRkB;EA0RlB,e3C5QmB;;A2C+QvB;AAAA;EAEI;EACA;EACA,cAjSkB;EAmSlB,e3CrRmB;;A2CwRvB;EACI,azEtToB;EyEuTpB,gBzEvToB;;;AwFWtB;AAAA;EAEE;;AAON;EAME;EACA;EACA;EACA;EACA;EACA,KxF/BwB;EwFgCxB;EACA;EACA;EACA;;AAKA;EAIE;;AAEA;EACE,cfrCiB;;AeyCrB;EAEE;EACA;;ArEaL;EACC;EACA,QAJwB;EAKxB;;AqEbE;ExDiFI,YDtHiB;EC0Hb,WD1Ha;EC4HjB,WxCzIe;EwC2If;EACA,KhCnJoB;EgCwJpB,kByC/Ie;EzCgJf,OrC1HQ;EqC2HR,cDvJS;ECwJT;EACA,cyCnJe;EzCqJX,etC/JY;;AsCmKhB;EACI,iBDlKQ;ECoKR,kByC3Ja;EzC4Jb,OrCvII;EqCwIJ,cDpKK;ECqKL;EACA,cyC/Ja;;AzCmKjB;EAGI,kBAvGU;EAwGV,OrClJI;EqCmJJ,cD/KK;ECgLL;EACA,cA3GU;;AA+Gd;EACI,OrC1JI;EqC2JJ,iBDzLQ;;AC6LZ;AAAA;EAEI,kBA/HY;EAgIZ,cD9LK;EC+LL;EACA,cAhIgB;EAiIhB,OAlIc;EAmId,QAnGU;;AAyHd;EACI,kBA/IW;EAgJX,cAvIe;EAwIf;EACA,cyCtNW;EzCuNX,OAlJa;;AwDrBrB;EAmBE,e1D/CqB;E0DgDrB;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;;AAGF;EACE;;AAGF;EAEE;IACE,YfvFiB;;Ee0FnB;IACE;;EAIA;IACE,YfhGe;;EemGf;IACE,kB7FlFM;I6FmFN;IACA;;EACA;IACE,kBfxGW;IeyGX,cfzGW;;EeiHf;IACE,kBflHa;;EeqHb;IACE;IACA,kB7FrGI;I6FsGJ;IACA;;EACA;IACE,kBf3HS;Ie4HT,cf5HS;;EeiIb;IACE,kBf3HqB;Ie4HrB,cf5HqB;;Ee6HrB;IACE,kBfrIS;IesIT,cftIS;;Ee8IjB;IACE;;EAMI;IACE;;EAKA;IACE;;;;ACnIlB;EACE,ezF9B4B;;AyFgC5B;AAAA;EAEE;EACA;EACA;;AAEA;AAAA;EACE;;AAGF;AAAA;EACE;;AAGJ;EACE,YzF/C0B;;;AyFoD9B;EACE;EACA;EACA;EAIA,YzF3D4B;;AyF4D5B;EACE;;AAGF;EACE;EACA;EACA,ezFxE0B;;AyF0E1B;EACE,czF/EqB;;AyFmFzB;EACE;;AAGF;EACE;;AAGF;EACE;;ApB7CA;EoBcJ;IAmCI;IACA;;EAIA;IACE,YzFtGqB;;;;AyF+GzB;EACE;EACA,kB9FxFc;;A8F0FhB;EACE;EACA,kB9FtFc;;;A8F2FhB;EACE,YzFxHwB;EyFyHxB;;AAEF;EACE,kB9F5DiB;E8F6DjB;;AAEF;EACE,YzFpHwB;EyFqHxB,WjG5HkB;;AiG8HpB;EACE,YzFxHwB;;AyFyHxB;EACE;;AAHJ;EAKE;;AAEF;EACE;EACA,czFjJc;;AyFmJhB;AAAA;EAEA,QPpJmB;EOqJnB;;AAEA;AAAA;AAAA;AAAA;AAAA;EAKE;EACA;EACA,WjGrJkB;;AiGuJpB;AAAA;EAEE;EACA,WjG1JkB;;AiG4JpB;EACE;;;AChKH;EACC,YANyB;;AAQ1B;EACC,YARmC;EASnC,kB/FegB;E+FdhB;EACA,WlGJoB;EkGKpB,O/F4BoB;;;AgGPtB;AAAA;EAEC;EACA;EpG4BC,oBoG3BD;EpG4BS,YoG5BT;;AAGC;AAAA;EACC,kBhG7BgB;EgG8BhB,OhGtBU;;AgGuBV;AAAA;EACC,qBhGxBS;;AgG0BV;AAAA;EACC;;AAED;AAAA;EACC;;AAED;AAAA;EACC,OhGjCS;EgGkCT;;AAGF;AAAA;EACC,OhGtCU;EgGuCV,kBhG/CgB;EgGgDhB;;;AAWH;EACC;;;AAIC;EACD;EACA;EACA;EACA;EACA;EACA,SA/E0B;EAgF1B;EACA;EACA;EAKA;;AAGA;EACC;EACA,qBAvE0B;EAwE1B;EACA;;AAED;EpGkCC;EACI;EACC;EACG;EAkER;EACG;EACE;EACG;;AoGtGT;EpG8BC;EACI;EACC;EACG;;AoG7BR;EACC,kBhGhGgB;EgGiGhB,OhGzFU;;AgG0FV;EACC,qBhG3FS;;AgG6FV;EACC;;AAED;EACC;;AAED;EACC,OhGpGS;EgGqGT;;AAGF;EACC,OhGzGU;EgG0GV,kBhGlHgB;EgGmHhB;;;AAID;EACD;EACA;;;AAIC;EACD;EACA;EACA;;;AAIC;EACD;EACA,kBA5I8C;EA6I9C;EACA;EACA;EACA,ejGpJwB;EiGuJxB;;;AAGC;EACC;;;AAID;EACD;EACA;EACA;EACA;EACA;EACA,SArK0B;EAsK1B,kBC/K6B;;ADiL7B;EpGqDC;EACA,SoGtDyB;;AAC1B;EpGoDC;EACA,SqGnO4B;;;ADiL5B;AAAA;AAAA;AAAA;AAAA;AAAA;EAMC,SAjKmB;;AlC1BpB;AAAA;AAAA;AAAA;AAAA;AAAA;EACE;EACA;EACA;;;AkC8LF;EACD;;;AAGC;EACD;;;AAIC;EACD,WnGzLqB;EmG0LrB;EACA,anGlLsB;EmGmLtB;;;AAKC;EACD;;AAGG;EACC;;;AAMH;EACD;EACA;;AlChOC;EACE;EACA;EACA;;AkCiOH;EACE;EACA;;AAGF;EACE;;AAGF;EACE;;;AAKD;EACD;EACA;EACA;EACA;EACA;;;AAOA;EAEC;IACC,OA3OkC;IA4OlC;;EAED;IpG9LA,oBoG+LC;IpG9LO,YoG8LP;;EAID;IAAmB,OAlPgB;;;AAqPpC;EACC;IAAmB,OAvPgB;;;AtB8BjC;EsB+NJ;IAEE;IACA;IACA;IACA;;;AAED;AAAA;AAAA;EAEC;EACA;;AACA;AAAA;AAAA;EACC;EACA;;AAED;AAAA;AAAA;EACC;EACA;;AtBhPC;EsBkPF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;IAEE;IACA;IACA;IACA;;;;AAUA;EACC;;AAED;EACC;;;AAOH;EACC,e3FhT4B;;A2FkT3B;EACC;EACA;EACA;;AAED;EACC;EACA;EACA;EACA;EACA,OhG/RiB;EgGgSjB,c3FzTyB;;;A6FhB9B;EACE;EACA,QnGFe;EmGGf,enGIiC;EmGHjC,kBlGiBW;EkGhBX;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,K7FL4B;E6FM5B,a7FVyB;E6FWzB,kBlGIS;EkGHT,YApBuB;EAqBvB;;AAEA;EACE;EACA;EACA;EACA,K7FrBwB;E6FsBxB;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAEA;EACE;;AAIJ;EACE;;AxBIJ;EwB1BA;IA0BI;;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AACA;EACE;;AAGF;EACE;;AAIN;EACE;;AxBpBA;EwBsBF;IAEI;;;;AAMJ;EACE,K/D3CkC;;;AgE5BtC;EACC,etGcyB;EsGbzB,Q1DTiB;E0DUjB,epGFkC;EoGGlC,oB1DZiB;E0DajB,Y1DbiB;;A0DejB;EACC,S1DlByB;E0DmBzB;EACA;ECrBA,wBDsB2B;ECrB3B,yBDqB2B;;AAE3B;EACC,OlGfqB;;AkGmBvB;AAAA;EAEC,kBnGFgB;EmGGhB;EACA;;AAEA;AAAA;AAAA;EACC;EACA,OnGCa;EmGAb,WtGpBsB;EsGqBtB,atGXqB;EsGYrB,atGFsB;;AsGKvB;AAAA;AAAA;EACC;EACA,OnGPa;EmGQb,WtGhCoB;;A6EqCnB;EyBDH;IAEE;IACA;IACA;;;AzBDC;EyBHH;IAOE;IACA;IACA;;;AzBHC;EyBNH;IAYE;IACA;IACA;IACA;;;AAIF;EACC;EACA,kBnG9CW;;A8D3BX;EACE;EACA;EACA;;;AqC2EJ;EACC,S1DvEyB;E0DwEzB,kBnGjDiB;EmGkDjB;ECvEC,4BDwE6B;ECvE7B,2BDuE6B;;;AAK9B;EACC;EACA;EACA;EACA,K9FvFgB;;A8FyFjB;EACC;;AAED;EACC;EACA,K9F9FgB;E8F+FhB;EACA;;AzB/CE;EyB2CH;IAME;;;AzB/CC;EyByCH;IASE;;;AzB/CC;EyBsCH;IAYE;;;AAGF;EACC;EACA;EACA;EACA;;AAED;EACC;EACA;EACA;;;AAMD;EACC,atGvFuB;;;AsG4FzB;EACC,oB1DhIiB;E0DiIjB,Y1DjIiB;;A0DmIhB;EACC,WtGvHmB;;AsG0HpB;EACC,WtG3HmB;;AsGgIpB;EACC;;AACA;EACC;;AAED;EACC;;AAKA;EACC;EACA;;AAIF;EACC;;AAGD;EACC;;AAGD;EACC,c9F/J2B;;A8FiK3B;EACC;;AAMJ;EACC;EACA;EACA,WtGxKqB;EsGyKrB,OnGjJc;EmGkJd;;AAGD;EACC,epGxLe;;;AoG6LjB;EACC;EACA;EACA;EAEA;;AAEA;EACC;;AAIA;EACC,S1D7MwB;;;A0DoN1B;EACC;;AAED;EACC,e9FlN0B;;A8FoN3B;EACC;;;AAMF;EACC,kBnGtMiB;EmGuMjB,ehEzNkC;;AgE2NlC;EACC,OnGjMc;EmGkMd,WtGtNuB;EsGuNvB,atGnMuB;EsGoMvB;EACA,S1D1OyB;E0D2OzB;EACA,atGjNsB;;AsGoNvB;EACC;;AAED;EACC,S1DnPyB;;A0DqPzB;EACC,gB9FpOyB;;;AgGd1B;EACE,OANoB;;;AASxB;EACE;EACA,QAVuB;EAWvB,OAZsB;;;AAetB;EACE;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;;;AC4DJ;EACE;;;AAGF;EACG;EACA;;;AAGH;AACA;EACE;EACA;EACA;EACA,SArDgB;EAsDhB;EACA,WA7FoC;EA8FpC,YA5FoC;EA6FpC;EACA;EACA;EACA,kBAxGkC;EAyGlC;EACA;EACA;EACA,eA3EkC;EAelC,oBA6DoB;EA5DZ,YA4DY;;AAEpB;EAAmC;;AACnC;EAAuC,aA3FH;;AA4FpC;EAAwC,YA5FJ;;AA6FpC;EAAqC;;AAGrC;EAnDE,mBAoDmB;EAnDd,cAmDc;EAlDX,WAkDW;EA9CpB,oBA+CqB;EA9Cf,eA8Ce;EA7CZ,YA6CY;EAlEtB,SAmEmB;EAhEnB;;AAkEA;EA5CE,6BA6C6B;EA5CxB,wBA4CwB;EA3CrB,qBA2CqB;EAnD9B,oBAoDqB;EAnDf,eAmDe;EAlDZ,YAkDY;EAvEtB,SAwEmB;EArEnB;;AAwEA;EAxDC,oBAyDqB;EAxDf,eAwDe;EAvDZ,YAuDY;EA5EtB,SA6EmB;EA1EnB;;AA4EA;EA/EA,SAgFmB;EA7EnB;;AAgFA;EAtEE,mBAsEuB;EArElB,cAqEkB;EApEf,WAoEe;EAnFzB,SAmFiD;EAhFjD;;AAkFA;EACE;EACA;EACA;;AACA;EACE;;;AAON;EACE;EACA;EACA;EACA,WAjHkC;EAkHlC;EACA,aAnHkC;EAoHlC,OAnHkC;EAoHlC;EAzGA,SA0GiB;EAvGjB;EAwGA;;AACA;EA5GA,SA6GkB;EA1GlB;;AA4GA;EACE;EACA;EACA;EACA;EAEA;;;AAIJ;EACE;EACA;EACA,WA7IgC;EA8IhC;EACA;EACA,kBApLkC;EAqLlC;EACA;;;AAGF;EACE;EACA;EACA;;;AAIF;EACE,kBA/LsB;EAgMtB,OA9LyB;;AAgMzB;EACC,YAnMqB;EAoMrB;EACA,OAnMwB;;;AAwM1B;EACC;;AAED;EACC;EACE;;AACA;EACD;;AAEC;EACD;;;AAOD;EACA;EACA;EACA;EACA;EACA;EACA;;;AAIF;EACE,cA9MoC;;;AAgNtC;EACE,cAtNoC;EAuNpC;;;AAIA;EAIE;EACD;EACA;EACA,kBAzNmC;EA0NnC,kBA5NmC;EA6NnC;;AACA;EACG;EACA;EACF;EACA,kBAvOkC;EAwOlC;;AAGF;EAGE;EACA;EACA;EACA;EACA,oBA3OkC;EA4OlC,oBA9OkC;;AA+OlC;EACE;EACA;EACA;EACA;EACA,oBAzPgC;;AA4PpC;EAIE;EACD;EACA;EACA,qBA5PmC;EA6PnC,qBA/PmC;EAgQnC;;AACA;EACG;EACA;EACF;EACA,qBA1QkC;EA2QlC;;AAGF;EAGE;EACA;EACA;EACA;EACA,mBA9QkC;EA+QlC,mBAjRkC;;AAkRlC;EACE;EACA;EACA;EACA,mBA3RgC;EA4RhC;;;AASJ;EACC,kBA3TqB;;AAiUtB;EACC,oBAlUqB;;AAwUtB;EACC,qBAzUqB;;AA+UtB;EACC,mBAhVqB;;;AAqVxB;EACE;;;AAGF;EACG;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAIH;EACE;IAAM;;;AAGR;EACE;IAAM;;;AAGR;EACE;EACA;EACA;EACA;EACA;EACA;EACA,SA3UyB;;;AAgVvB;EACE;EACA;EACA;EACA;EACA;EACA;;;AC5VN;EACE,WA9BqB;;AA+BrB;EACD;;AAEC;EACD,YArBuB;EAsBvB;;AACA;EACE;;AACA;EACD;EACA;EACA;EACA;;AAIA;EACD;EACA;;AAGC;EACD,kBvGhCmB;;AuGiCnB;EACE,W1GlDmB;E0GmDnB,a1G/BsB;E0GgCtB;EACA;EACA;EACA,OvGjCa;;AuGqCd;EACD;;;AC1ED;EACE;EACA;;AAEA;EACE,W3GSkB;E2GRlB;;AAEA;ExCRA;;AwCaF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGA;EACE,YxGKc;EwGJd,ezGrBmB;;AyGuBrB;EACE,YxG3BU;;AwG6BZ;EACE;EACA,YxGbmB;;AwGiBrB;EACE,YxGpCU;EwGqCV,ezGlCmB;;AyGoCrB;EACE;EACA,YxGvBmB;;AwG4BrB;EACE,OxG3Ca;;AwG4Cb;EACE,OxG7CW;;AwGkDf;EACE,YxGnDa;;AwGuDf;EACE,YxGxDa;;AwG6Df;EACE,OxGxDY;;AwGyDZ;EACE,OxG1DU;;AwG+Dd;EACE,YxGhEY;;AwGoEd;EACE,YxGrEY;;AwGyEhB;EACE,W3G5EkB;E2G6ElB;EACA;;AAEA;EACE;EACA;;;AClFN;EACC;EACA;;AAEA;EACC,QrDfmB;EqDgBnB,arDhBmB;;AqDkBpB;EACC,QrDjBoB;EqDkBpB,arDlBoB;;AqDoBrB;EACC,QrDnBmB;EqDoBnB,arDpBmB;;AqDsBpB;EACC;EACA,arD1BoB;;AqD6BrB;EACC,gBA1BiC,iBA0BkB;EACnD,QA3BiC;;AA8BlC;EACC;IACC;;;;ACvBF;EACE;EACA;EACA;;AASH;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAIqC;EAAW;;;AACX;EAAW;;;AAEX;AAAA;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AASX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AACX;EAAW;;;AAMjD;EACC,O1G3Te;;A0G6Tf;EACC,OA9T2B;;AA+T3B;EACC,O1GxTgB;;A0G4TlB;EACC,O1GnTuB;E0GoTvB;;AAGD;EACC,O1G1RoB;E0G2RpB;;AAGE;EACE;;;AAKL;EACC;;;AAIF;AAAA;EAEC;;;AAGD;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAID;EACC;EACA;;;AAED;EACC;EACA;;;AAED;EACC;EACA;;;AAED;EACC;EACA;;;AAED;EACC;EACA;;;AAED;EACC;EACA;;;AAED;EACC;EACA;;;AAED;EACC;EACA;;;AAED;EACC;EACA;;;AAED;EACC;EACA;;;AAED;EACC;EACA;;;AAED;EACC;EACA;;;AAED;EACC;EACA;;;AAED;EACC;EACA;;;AAED;EACC;EACA;;;AAED;EACC;EACA;;;AAED;EACC;EACA;;;AAED;EACC;EACA;;;AAED;EACC;EACA;;;AAED;EACC;EACA;;;AAED;EACC;EACA;;;AAED;EACC;EACA;;;AAED;EACC;EACA;;;AAED;EACC;EACA;;;AAED;EACC;EACA;;;AAED;EACC;EACA;;;AAED;EACE;EACA;;;AAEF;EACE;EACA;;;AAEF;EACE;EACA;;;AAGF;EACC;EACA;;;AAED;EACE;EACA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A;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EACC,YhHhB6B;EgHiB7B,WhHjB6B;EgHkB7B,elH1CiC;;AkH6ClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EACC;EACA;EACA;EACA,kBjHzBgB;;;AiHkCjB;AAAA;AAAA;EAEC,elH5DiC;;AkH8DlC;AAAA;AAAA;EACC;;AAGD;AAAA;AAAA;EACC;EACA,M5GvE4B;E4GwE5B;EACA,OjH7Ec;EiH8Ed;;;AAIF;EACC;;;AAID;AAAA;AAAA;EAGC;EACA;EACA;;;AAKA;EACC;;;AAID;EACC;EACA;EACA;;;AAID;EACC;EACA;EACA;;;AAMD;EACC;EACA;;;AAKF;EACC,elHvHkC;;AkHwHlC;EACC;;AAED;EACC;;AAGD;EACC;;;AAKF;EACC,SzD5IoB;;;AzBuCrB;AmF/CA;EACC;EACA;EACA;EACA;;;ACWD;EACC,SAbe;EAcf,etHYyB;EsHXzB;EACA,epHTwB;;AoHYxB;EACE;EACA;;AAIF;EACE,atHUsB;;AsHNxB;AAAA;EAEE;;AAGF;EACE;;;AASD;AAAA;EAED;;AAGA;AAAA;EACE;EACA;EACA;EACA;;;AAsBD;EAbD,OnHzBe;EmH0Bf,kBnHLqB;EmHMrB,cnHNqB;;AmHQrB;EACE;;AAGF;EACE;;;AAQD;EAjBD,OnHzBe;EmH0Bf,kBnHDkB;EmHElB,cnHFkB;;AmHIlB;EACE;;AAGF;EACE;;;AAYD;EArBD,OnHzBe;EmH0Bf,kBnHGqB;EmHFrB,cnHEqB;;AmHArB;EACE;;AAGF;EACE;;;AAgBD;EAzBD,OnHzBe;EmH0Bf,kBnHOoB;EmHNpB,cnHMoB;;AmHJpB;EACE;;AAGF;EACE;;;AAwBH;EACC,OnHrFkB;;;AmHwFnB;EACC,OnHzFkB;;;AmH4FnB;EACC,WtH7FqB;EsH8FrB;EACA,OnH/FkB;;;AmHmGlB;EACC;;;AAID;EACC;;;ACtHF;AACA;EACC;EACA;EACA;EACA;EACA,kBpHoBY;EoHnBZ;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC;;;ACrCD;AACA;EACC,OrHekB;EqHdlB;EACA,WxHkBoB;EwHjBpB,axHiCwB;;AwHhCxB;EALD;IAMQ,WxHakB;;;;AwHT1B;EACC;;AACA;EAFD;IAGQ;;;;AAIR;EACI;EACA;;;AAGJ;EACC,axHcwB;EwHbxB;EACA;EACA;EACA,WxHVwB;EwHWxB,OrHZkB;;;AqHenB;EACC;EACA;EACA,YhH5B2B;EgH6B3B,chHhB6B;EgHiB7B,ehH9B2B;EgH+B3B;;AACA;EAPD;IAQQ;IACN;IACA;IACA;;;;AAIF;EACC;EACA;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;AAAA;EAEC;;;AAGD;EACC,WxHpDsB;EwHqDtB;EACA,OrH9Be;;;AqHiChB;EACC;EACA;EACA;;AACA;EAJD;IAKE,OrHtCc;;;AqHwCf;EACC;;;AAIF;EACC;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;;;AAGD;AACA;AACC;EACA;;;AAGD;EACC;;;AAGD;EACC;EACA;;;AAGD;EACC;EACA;;AACA;EAHD;IAIE;IACA;IACA;;;;AAIF;EACC;;;AAGD;EACC;;;AChID;EACC;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC,WzHGqB;EyHFrB,OtHwBe;EsHvBf;EACA;;;AAGD;EACC;;;AAED;AACA;EACC,WzHZqB;EyHarB,azHWwB;;;A0HvCzB;AACA;EACC;EACA;;;AAGD;AACA;EACC;EACA;;;ACPD;AACC;EACA;;AACA;EAHD;IAIE;;;;ACNF;AACA;EACC;EACA;;;AAGD;EACC;;;ACJA;EADD;IAKE;;;;AAKD;EADD;IAEQ;;;;ACRR;EAEI,YtHGwB;;AsHDxB;EACI;;;AAIR;AA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Be;EyIrBf;EACA;EACA;;;AAED;EACC;EACA,OzIee;EyIdf;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;;;AAID;EACC;;;AAID;EACC,W5IxCqB;;;A4I2CtB;EACC,W5IxCqB;E4IyCrB;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;;;AAID;AAEA;EACC;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;E7IfE,oB6IgBD;E7IfS,Y6IeT;;;AAGD;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC,kBzI3EwB;;;AyI8EzB;EACC;;;AAGD;EACC;;;AAGD;EACC;;;AC7GD;AAEA;EACC;;;AAGD;EACC;EACA,W7IIqB;;;A6IDtB;EACC;EACA,W7ICsB;;;A6IEvB;EACE;;;AAGF;EACC,a7IewB;E6IdxB;EACA,W7IXqB;E6IYlB;EACA;;;AAGJ;EACE;;;AAGF;EACC;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;;;AChDD;AAEA;EACC,ctIU+B;EsIT/B,etIS+B;;;AsINhC;EACC;EACA;EACA;;;AAGD;EACC;EACA;;;AAGD;EACC;;;AAGD;EACC;EACA,W9IbqB;;;A8IgBtB;EACC;EACA;EACA;;;AAED;EACC,kB3ILiB;E2IMjB;;;AAGD;EACC;;AACA;EACC;EACA;;AAED;EACC;;AAED;EACC;EACA;;;AAKF;EACC;;;AAGD;EACC;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;EACA,O3I7DwB;E2I8DxB;EACA;EACA;EACA;;;ACtFD;AAQA;EACC,a/I0BwB;;;A+IvBzB;EACC,a/IwBwB;;;A+IrBzB;EACC;EACA,a/ImBwB;;;A+IhBzB;EACC;EACA;EACA;EACA,W/IdqB;;;A+IiBtB;EACC,a/IQwB;E+IPxB,W/InBqB;E+IoBrB;;;AAGD;AACA;EACC;EACA;EACA;EACA,kB5IfY;;;A4IkBb;EACC;EACA;EACA;;AACA;EACC;EACA;EACA;EACA;;AACA;EALD;IAME;IACA;;;;AAKH;EACC;EACA;EACA;EACA;;;AAGD;EACC,YvInD8B;EuIoD9B,W/IpDqB;;A+IqDrB;EAHD;IAIE,YvI/D0B;IuIgE1B,W/IzDqB;;;;A+I6DvB;EACC,O5I/BqB;;;A4IkCtB;EACC;EACA;EACA;;AACA;EAJD;IAKE,cvIhE4B;IuIiE5B;;;AAGA;EADD;IAEE;IACA;;;;AAKH;EACC;EACA;;AAEA;EACC;;AAGD;EACC;EACA;EACA;EACA;EACA;;AAGD;EACC;;;AAIF;EACC,YvIrG8B;;;AuIwG/B;EACC;EACA,evI1G8B;EuI2G9B;;AAEA;EACC,kB5IrGW;E4IsGX;;AAGD;EACC;EACA;;AACA;EACC;;AAED;EACC;;AAGA;EADD;IAEE;;;AAKF;EADD;IAEE;IACA;;;;AAKH;EAEE;IACC;;EAED;IACC;;EAED;IACC;;EAED;IACC;;EAED;IACC;;EAED;IACC;;EAED;IACC;;EAED;IACC;;EAED;IACC;;;AAKH;EACC;EACA;EACA,a/ItJwB;E+IuJxB,O5IjLkB;;;A4IoLnB;EACC;EACA,a/I5JwB;;A+I6JxB;EACC,a/IhKuB;;A+IkKxB;EACC;;;AAIF;EACC;EACA;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC,YvIzM8B;;;AuI4M/B;EACC;;;AAGD;EACC,O5IrNkB;;;A4IwNnB;EACC,a/I/LwB;;;A+IkMzB;EACC;EACA;EACA;EACA;EACA;EACA;EACA,O5IzMe;E4I0Mf;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;;;ACrQD;AAEA;EACC;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;AACA;EACC;EACA;EACA;EACA;EACA;AACA;EACA,kB7IJiB;E6IKjB;AACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;;;AAID;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;;;AC7GD;EACI,kB9IqBS;E8IpBT,oBrGFc;EqGGd,YrGHc;;AqGIjB;AAAA;EAEC;EACA;EACA;;;AAKD;EACC;;;AAGF;EACC;EACA,kB9IOiB;;A8INjB;EACC;;AAED;EACC;;;AAMA;EACC;;;AAKH;EACC,kB9IXiB;;A8IajB;EACC;;;AAGF;AAEA;EACC;;;AClDD;AAUA;EACE;EACA;;;AAEF;EACE;EACA,SARmB;EASnB,OAfiB;EAgBjB,QAfkB;;;AAiBpB;ACpBA;EACE;;AACA;EACE;;;AAKF;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIA;EACE;EACA;EACA;;AAGF;EACE;;AACA;EACE;;AAHJ;EAKE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAIA;EACE;;AAIJ;EACE;;;ACjEJ;AAEA;EACC;EACA;EACA;EACA;;;AAGD;EACC,kBjJoBiB;EiJnBjB;;;AAGD;EACC;;;AChBC;EACD;EACA;EACA;;AAGC;EACD;;AAGC;EACD;EACA;;AAGC;EACD;EACA;;AAGC;EACD;EACA;EACA;;;AAID;EACE;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;;;ACtDF;AAEA;EACC;EACA,WtJSqB;EsJRrB,OnJQkB;;;AmJLnB;EACC;EACA,WtJGqB;EsJFrB,OnJmCqB;;;AmJhCtB;EACC;EACA;EACA;EACA,WtJLqB;EsJMrB;;;AAGD;EACC;EACA;EACA;EACA,WtJbqB;EsJcrB,OnJmBqB;;;AmJhBtB;EACC;EACA;;;AAGD;EACC;EACA,WtJxBqB;;;AsJ2BtB;EACC;;;AAGD;EACC;;;AC7CD;AAEA;EACC;;AACA;EACC;EACA;;;AAIF;EACC;EACA;;;AAGD;EACC;;;AAGD;EACC;EACA,WvJRqB;AuJSrB;;;AAGD;EAEC;EACA;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;EACA;;;AAGD;EACC;;;AAIA;EACC;EACA;;AAHF;EAKC;;;AAGD;EACC;;;AAGD;EACC;;;ACvDD;EACG,YrJ6Be;EqJ5Bf;;;AAGH;EACI,gBhJJwB;;;AiJR5B;AAEA;EACC,OtJoCe;EsJnCf,kBtJ0BiB;EsJzBjB;EACA;;;AAGD;EACC,kBtJoBiB;EsJnBjB,OtJ4Be;EsJ3Bf;EACA;EACA;;;AAGD;EACC;EACA,OtJoBe;EsJnBf;EACA;EACA;;;ACpBD;AAEA;EAEC;IACC;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAGD;IACC;IACA;IACA;IACA;;EAGD;IACC;IACA;IACA;IACA;;EAGD;IACC;IACA;;EAGD;IAEC;IACA;AACA;AAAA;AAAA;;EAKD;IAEC;IACA;IACA;;EAGD;IAEC;IACA,elJ9C4B;;EkJiD7B;IACC;;EAGD;IACC;IACA;;EAGD;IACC;IACA;IACA;IACA;IACA;;EAGD;IACC;IACA,a1JtCuB;I0JuCvB;IACA;IACA;;EAGD;IACC;IACA;;EAGD;IACC;IACA;IACA;IACA;IACA;IACA;IACA;;EAGD;IACC;;EAGD;IACC;;EAGD;IACC;;EAGD;IACC;IACA,a1JzEuB;I0J0EvB;;EAGD;IACC;;EAGD;IACC;;EAGD;IACC;;EAGD;IACC;;EAGD;IACC;IACA;IACA,a1JhGuB;I0JiGvB;IACA;;;AAIF;AAEA;EACC;EACA;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;EACA,kBvJxIiB;EuJyIjB;;;AAGD;EACC;EACA;;;AAGD;EACC;;AACA;EACC;;AAGD;EACC;EACA;EACA;EACA;EACA;;;AAMA;EACC;;;AC/LH;AAcA;EACC;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;;;AAIA;EACC;EACA,W3JtBqB;E2JuBrB,a3JDuB;;A2JGxB;EACC,W3JtBuB;E2JuBvB,a3JHuB;;;A2JOzB;EACC;EACA;;;AAGD;EACC;EACA;;;AAGD;EACC;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EAEI;EACH;;;AAGD;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;;AAEA;EAHD;IAIE;;;;AAIF;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EAEI;EACA;EACA;;;AAGJ;EACC;EACA;EACA;EAEA,SnJtH+B;;AmJuH/B;EACC,cnJxH8B;EmJyH9B,enJzH8B;;;AmJ6HhC;EACI,W3J9HmB;E2J+HnB,Y3J/HmB;;;A2JkIvB;EACC;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;;;AAGD;AAEA;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;EACA;;;AAGD;EACC;;;AAGD;EAEC;EACA;EACA;;;AAGD;EAEC;EACA;EACA;;;AAGD;EAEC;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;;;AAID;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAOC;;;AAGD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EASI;;;AAEJ;AAAA;EAGI;;;AAEJ;EAEI;EACA;;;AAEJ;EAEI;EACA;EACA;;;AAKJ;EAEI;;;AAEJ;EAEI;;;AAEJ;AAAA;EAGI;;;AAGJ;EAEC;;;AAED;EAEC;;;AAED;EAEC;;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ACC;EACA;EACA;;;AAWD;EACI;;;ACzDJ;AAEA;EACC;EACA,OhKiCe;EgKhCf;;;AAGD;EACC;;AACA;EACC,kBhKiBgB;EgKhBhB;;;AAIF;EACC;;;AAGD;EACC;EACA;;;AAED;EACC;;;AAED;EACC;;;AAIA;EACC;;AAED;EACC,kBhKFgB;;AgKIjB;EACC;;;AAKF;EACC;EACA;EpKsBC,oBoKrBD;EpKsBS,YoKtBT;;;AAGD;EACC;;;AAGD;EACC;EACA;;;AAGD;EACC;EACA,kBhKrCY;;AwBjBZ;EACC;EACG;;AAEJ;EACC;EAEA,SARwB;EASxB;;AAEA;EACC;EACA;EACA;EACA;EACA;EACA;EAEA,QAnBuB;EAqBvB,SAtBuB;;AwI8DzB;EACC;EACA;;AAEA;EACC;EACA;;;AAKH;EACC;EACA;;;AAGD;EACC;;;AAGD;EACC,OhK5CqB;EgK6CrB,WnK9EqB;EmK+ErB;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC,kBhK5EiB;EgK6EjB;EACA,WnK/FqB;;;AmKkGtB;EACC,OhKzEe;;;AgK4EhB;EACC,OhKxEqB;;;AgK2EtB;EACC;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;EACA;;;AAGD;EACC;EACA;;;AAGD;EACC;;;AAGD;AACC;EACA;EACA;EACA;AACA;AACA;AACA;EACA;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;EACA;;;AAIA;EACC;EACA;EACA;;;AAIF;EACC;EACA;;;AAGD;EACC,kBhK3JiB;EgK4JjB,OhKlJqB;;;AgKqJtB;EACC;;;AAGD;AACA;EACC;EACA;EACA;;;AAGD;EACC;;;ACjND;AAIA;EACC;;;ACLD;AAEA;EACC;EACA;;;AAKG;EACE;EACA;EACA;;;AAKN;EACC;;;AAGD;EACC;;;AAGD;EACC;EACA;;AAEA;EACC;EACA;EACA;;AAEA;EACC;EACA;EACA;;AAIF;EACC;EACA;EACA;;;AAKD;EACC;EACA;EACA;;;ACjDF;AAEA;EACC;EACA;E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wKiVtB;EACA;;;AAGD;EACC;EACA;;;AAGD;EACC;EACA;EACA,axKvUwB;;;AwK0UzB;EACC;EACA;;;AAID;EAEC;;;AAID;EACC,OrKrVe;EqKsVf,kBrK/ViB;;;AqKkWlB;EACC;EACA,WxKrXqB;EwKsXrB,axK9VwB;EwK+VxB,kBrK1WY;;;AqKgXb;EACC,axKlWyB;;;AwKqW1B;EACC;EACA;;;AAGD;EACC;EACA;EACA,kBrK5XY;EqK6XZ;EACA;EACA;EACA,ctKxZsB;EsKyZtB,WxK9YqB;EwK+YrB;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA,ctKnasB;EsKoatB,WxKzZqB;EwK0ZrB;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA,ctK9asB;EsK+atB,WxKpaqB;EwKqarB;EACA;;;AAGD;EACC;EACA,kBrK1ZiB;EqK2ZjB;EACA;EACA,ctKzbsB;EsK0btB,WxK/aqB;EwKgbrB;EACA;;;AAGD;EACC;EACA,kBrKraiB;EqKsajB;EACA;EACA,ctKpcsB;EsKqctB,WxK1bqB;EwK2brB;EACA;;;AAGD;EACC;EACA,kBrKpbY;EqKqbZ,WxKpcsB;EwKqctB;EACA;;;AAGD;EACC,OrK9bwB;;;AqKgczB;EACC;EACA;;;AAGD;EACC;EACA,YhKtd2B;;;AgKyd5B;EACC;;AACA;EACC;EACA;;;AAMF;EACC;EACA;;;AAGD;EACC;;;ACnfD;AAEA;EACC;;;AAGD;EACC;;;ACDD;AACA;EACC;;;AAGD;EACC;EACA;EACA,W1KCqB;;;A0KEtB;EACC;;;AAGD;AACA;EACC;EACA;;AAEA;EACC;;;AAIF;EACC;EACA;EACA;;;AAIA;EACC,W1KtBqB;E0KuBrB;EACA;EACA;;AACA;EACC;EACA;EACA;;AAED;EACC;EACA;EACA;EACA;;;AAKH;EACC;;;AAGD;EACC;EACG;;;AAGJ;EACC,kBvK7BiB;EuK8BjB;;;AAID;EACC;;;AAGD;AACC;AAAA;EAEA;EACA;EACA,elKnE8B;;AkKqE9B;EACC;;;AAIF;AACC;AAAA;AAAA;EAGA;EACA;;;AAED;EACC;EACA;;;AAGD;EACC,alKvF8B;;;AkK0F/B;AAAA;EAEC,OnHnGqB;EmHoGrB,QnHpGqB;EmHqGrB;;;AAGD;AACC;EACA;EACA;EACA;;;AAGD;AACA;AACC;;;AAGD;EACC;EACA,clKxHiB;EkKyHjB;;;AAGD;EACC,kBvKlGiB;EuKmGjB,S9H/H0B;E8HgI1B,a1K7FwB;E0K8FxB;EACA;;AACA;EACC;EACA,OvKhGc;;AuKmGf;EACC;EACA;;;AAIF;EACC;EACA;EACA;EACA;AACA;EACA,kBvKxHiB;EuKyHjB;;;AAID;EACC;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;EACA,Q9HlKiB;E8HmKjB,exK3JkC;EwK4JlC,oB9HrKiB;E8HsKd,Y9HtKc;;;A8HyKlB;EACC;;;AAGD;EACC;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC,kBvKnKY;;;AuKsKb;EACC,kBvKnKiB;EuKoKjB;;;AAID;EACC;EACA,kBvK9KY;;;AuKiLb;EACC;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;EACA,kBvK3LiB;EuK4LjB;EACA;;;AAGD;EACO;;;AAGP;EACO;;;AAGP;EACC;;;AAGD;EACC;;;AAED;EACC,W1K7NqB;;;A2KdtB;AAEA;EACC;EACA;EACA;EACA;EACA,qBxKawB;EwKZxB,oBxKYwB;EwKXxB;EACA,W3KFsB;E2KGtB;EACA;;;AAGD;EACC;EACA;EACA;EACA,W3KTqB;E2KUrB;EACA;EACA;EACA;EACA,qBxKJwB;EwKKxB,oBxKLwB;;AwKMxB;EACC,OxKcoB;;;AwKVtB;EACC;EACA;EACA,W3K1BsB;E2K2BtB,kBxKZY;EwKaZ;;;AAGD;EACC;EACA;EACA;EACA;EACA,W3KlCqB;;;A2KqCtB;EACC;;;AAGD;EACC;;;AAID;AAAA;AAAA;EAGC;EACA;EACA,W3KnDqB;E2KoDrB;EACA;EACA;EACA;EACA;;;AAQA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EACC,OxKzEc;EwK0Ed,W3K9DoB;;;A2KkEtB;EACC;EACA,OxKvCqB;;;AwK0CtB;EACC;;;AAED;EACC;EACA;;;AAGD;EACC;EACA;EACA,OxKrDqB;EwKsDrB;EACA,W3KxFqB;E2KyFrB;EACA;EACA;;;AAID;EACC;EACA;EACA;;;AAGD;EACC;EACA,OxKtEqB;;;AwK0EtB;EACC;;;AAGD;EACC;EACA;EACA;;;AAIA;EACC;;AAED;EACC;;;AAIF;EACC;EACA,kBxKxIe;EwKyIf;EACA,W3KlIqB;E2KmIrB;EACA;EACA;EACA;EACA,Q3KzHyB;E2K0HzB;EACA;;;AAIA;AAAA;AAAA;AAAA;AAAA;EAGC;;AAED;EACC;;;AAIF;EACC,kBxK9JoB;;;AwKkKrB;EACC;;;AAGD;EACC;EACA;;;AAGD;EACC,OxKtKkB;EwKuKlB;EACA,W3KxKqB;E2KyKrB;EACA;EACA,kBxK1JiB;;;AwK8JlB;EACC;EACA;EACA;;;AAGD;EACC,cxK5LoB;;;AwK+LrB;EAEC;EACA;;;AAGD;EACC;EACA;;;AAGD;EACC,kBxKxLY;EwKyLZ;EACA;EACA;EACA;EACA;EACA,W3KzMsB;E2K0MtB;;;AAGD;EACC;;;AAMD;EACC;EACA;;;AAGD;EACC,kBxK/MY;EwKgNZ;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA,kBxK9NY;;;AwKiOb;EACC;;;AAED;EACC;;;AAGD;EACC;EACA;;;AAGD;EACC;;;AAGD;EACC;AACA;AACA;EACA;EACA;;AAEA;EACC;EACA;EACA;;;AAIF;EACC;EACA,YxKjQY;EwKkQZ;EACA;;;AAGD;EACC;;;AAGD;EACC;EACA;;;AAGD;EACC;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC;;AAGE;EACC;EACA;EACA;EACA;EACA;;AAED;EACC,OxKpRkB;EwKqRlB;;AAKF;EACC;;AAIF;EACC;;;AAOF;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;;;AAGD;AACA;EACC;;;AAGD;EACC;EACA;;;AAGD;AACA;EACC,W3KzWqB;E2K0WrB;EACA,kBxK1ViB;EwK2VjB;EACA;;;AAGD;EACC;EACA,W3KhXsB;E2KiXtB;EACA;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC,OxK7Ye;EwK8Yf;EACA;;;AAGD;EACC,OxKnWqB;;;AwKsWtB;EACC,OxKjUqB;;;AwKoUtB;AACA;EACC;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;;;AAGD;EACC,kBxKrZY;EwKsZZ;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;;AACA;EACC;EACA;;;AAIF;EACC;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC,kBxKzbY;EwK0bZ;EACA;EACA;EACA;;;AAGD;EACC,kBxKjcY;;;AwKocb;EACC;EACA,W3K/cqB;E2KgdrB;;;AAGD;EACC;EACA,kBxK5cY;EwK6cZ;EACA;;;AAGD;AAEA;EACC;EACA;;;AAGD;EACC,cxK9ee;;;AwKifhB;EACC,cxK1ekB;;;AwK6enB;EACC,cxKpfoB;;;AwKufrB;EACC;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;EACA,OxKleqB;;;AwKqetB;EACC,OxKteqB;;;AwKyetB;EACC;;;AAGD;EACC;;;AAGD;EACC,cnKlhB8B;EmKmhB9B,enKnhB8B;EmKohB9B;;AACA;EACC;;AAGA;EACC;EACA;EACA;EACA;EACA,kBxK5gBe;EwK6gBf;EACA;;AAIF;EACC;;AAGD;EACC;EACA;;AAGD;EACC;EACA;;;AAIF;EACC;EACA;EACA;EACA;EACA,W3KvjBqB;E2KwjBrB;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA,kBxKxjBiB;EwKyjBjB;;;AAGD;EACC,kBxKvjBiB;;;AwK0jBlB;EACC;EACA;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;AAAA;EAEC;;;AAID;EACC;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;AACA;EACC;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;AAAA;AAAA;EAGC;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EACC,anLkCwB;;;AmL/BzB;EACC,anLgCwB;;;AmL7BzB;EACC;EACA;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;;;AAIA;EACC;;AAED;EACC;EACA;;;AAOC;EACC;EACA;EACA;EACA;;;ACvDJ;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;AAAA;EAEE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAIA;EACE;EACA;;;AC/KJ;AAEA;EACC;;;AAGD;EACC;EACA;EACA;EACA;;;AAGD;EACC;EACA,WrLHqB;EqLIrB;EACA;EACA;AACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;;;A1J/BA;AAAA;EACC;EACG;;AAEJ;AAAA;EACC;EAEA,SARwB;EASxB;;AAEA;AAAA;EACC;EACA;EACA;EACA;EACA;EACA;EAEA,QAnBuB;EAqBvB,SAtBuB;;;AAEzB;EACC;EACG;;AAEJ;EACC;EAEA,SARwB;EASxB;;AAEA;EACC;EACA;EACA;EACA;EACA;EACA;EAEA,QAnBuB;EAqBvB,SAtBuB;;;AAoEzB;EACC;EACA,QAJwB;EAKxB;;;AAUD;AAAA;AAAA;EACC;EACA;EACA;EACG;;AAOJ;AAAA;AAAA;EACC;EACA;EACA;EACG;;AA5BJ;AAAA;AAAA;EACC;EACA,QAJwB;EAKxB;;;A0JfF;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;;;AC3ED;AAEA;EACC;EACA;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;EACA;;;ACnBD;AAEA;EACC;EACA;EACA;EACA;EACA;EACA,WvLGsB;;;AuLAvB;EACC;EACA;EACA;EACA;EACA;EACA,kBpLgBmB;;;AoLbpB;EACC;EACA;EACA;EACA;;;ACtBD;AAEA;EACC;;AACA;EACC;;AAED;EACC;;AAED;EACC;;AAED;EACC;EACA;;AAED;EACC;;;AAIF;EACC;IACC;;;AAIF;EACC,kBrLlBkB;EqLmBlB;EACA;EACA,WxLvBsB;EwLwBtB;EACA;EACA;;AACA;EACC;EACA;;;AAIF;AAEA;EACC;EACA;EACA;;AACA;EACC;EACA;EACA;EACA;EACA;EACA,kBtLpD0B;EsLqD1B;EACA;;AACA;EATD;IAUE;;;AAGF;EACC;EACA;EACA;;AACA;EACC;EACA;;AACA;EAHD;IAIE;;;AAED;EACC;EACA;EACA;EACA;;AACA;EALD;IAME;IACA;;;AAIH;EArBD;IAsBE;;;AAED;EACC;EACA;;AAED;EACC;;AAED;EACC;EACA;EACA;EACA;EACA,YrLrEe;EqLsEf,Q5I/Fe;;A4IgGf;EAPD;IAQE;;;AAED;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAED;EACC;;AAGF;EACC;EACA;EACA;EACA;EACA;EACA,WxL9GoB;EwL+GpB;EACA;EACA;EACA,kBrL1GsB;EqL2GtB;EACA;EACA;EACA;;AACA;EAfD;IAgBE;IACA;IACA;;;AAED;EACC,WxL7HmB;;AwL+HpB;EACC,WxLpImB;;AwLsIpB;EACC;EACA;;AAGF;EACC;;AACA;EAFD;IAGE;;;AAED;EACC;EACA;EACA;EACA;;AACA;EALD;IAME;IACA;IACA;IACA;;;AAGF;EACC;EACA;EACA;EACA;;AACA;EALD;IAME;IACA;IACA;IACA;;;AAKJ;EACC;EACA;;AAED;EACC,kBrL5JgB;;;AqLgKlB;AACC;AAAA;AAAA;;;AAKD;EACC;EACA;;;AAGD;EACC;EACA;;;AAGD;EACC;EACA;EACA;;AAEA;EALD;IAME;;;;AAIF;EACC;EACA;;AAEA;EAJD;IAKE;IACA;;;;AAIF;EACC;;AACA;EAFD;IAGE;;;;AAIF;EACC;;;AAGD;EACC;;;AAGD;EACC;EACA;;;AAGD;AACA;EACC;EACA;EzL9DC,oByL+DD;EzL9DM,eyL8DN;EzL7DS,YyL6DT;EACA;;;AAGD;AACA;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;EACA;;;AClRD;AAEA;AACI;;;AAEJ;EACI;;;AAEJ;EACC,kBtLeY;EsLdZ;;;AAGD;EACC,kBtLUY;;;AsLPb;EACC;EACA;;;AAGD;EACC;;;AAGD;EACC;EACA;;AACA;EACC;EACA;;;AAIF;EACC,WzLzBqB;EyL0BrB;;;AAGD;EACC,kBtLbiB;EsLcjB;EACA;;AAEC;EACC;EACA;;;AAMF;EACC;;;AAIF;EACC;;;AAGD;EACC,WzLhDqB;EyLiDrB,OtL3Be;EsL4Bf;;;AAGD;EACC,WzLxDsB;EyLyDtB;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;;;AAIA;EACC;;AAED;EACC;;;AAIF;EACC,ejLzF4B;;;AiL6F5B;EACC;EACA;;;AAIF;EACC,Y7I1GiB;;A6I2GjB;EACC;;;AAIF;EACC;;;ACtHD;AAcA;EACC;EACA;;;AAID;EACC,W1LVsB;E0LWtB;EACA;EACA;EACA;;;AAGD;EACC;EACA;;;AAGD;EACC,exL7BgB;EwL8BhB,kBvLTY;;AuLUT;EACI;;;AAKR;EACI;;AAEI;EACI,kBvLpBC;;AuLuBD;EACI;;AAGA;EACI;;;ACtDpB;EACC;;;AAIA;EACC;EACA;EACA;EACA;;;AAIF;EACC;EACA;EACA;EACA;EACA;AACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;EACA;EACA;;AAGD;EACC;EACA;EACA;;AACA;EACC;EACA;;AAEA;EACC;;AAGD;EACC;EACA;;AAKH;EACC;EACA;EACA;EACA;EACA;EACA,kBxLjCW;;AwLmCX;EACC;;AAGD;EACC;EACA;;AAGD;EACC;;AAGD;EACC;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;;AAGD;EACC;EACA;;AAGD;EACC;EACA;EACA;EACA;EACA;;AAGD;EACC;EACA;EACA;EACA;EACA;;AAED;EACC;EACA;EACA;;AAED;EACC;EACA;EACA;;AAED;EACC;EACA;EACA;EACA;EACA;EACA;EACA;;AAGD;EACC;EACA;EACA;EACA;EACA;;AAGD;EACC;EACA;EACA;EACA;;AAEA;EACC;;AAGD;EACC;;AAGD;EACC;;AAEA;EACC;EACA;;AAGD;EACC;EACA;EACA;;AAGD;EACC;EACA,kBxLxIa;;AwL0Ib;EACC,W3L5JgB;E2L6JhB;EACA;;AAIF;EACC;EACA;;AAIA;EACC;EACA;EACA;;AAKD;EACC;EACA;;AAMA;EACC,W3L3LgB;;A2L+LlB;EACC;EACA,OxL9JgB;EwL+JhB,W3LhMgB;;A2LsMpB;EACC;EACA;EACA;;AAEA;EACC,W3L9MmB;E2L+MnB;EACA;EACA;EACA;EACA;;AAEA;EACC;;AAGD;EACC;EACA;EACA;;AAGD;EACC;;AAGD;EACC;EACA;;AAIF;EACC;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;EACA;EACA;EACA;EACA;EACA;;AAKH;EACC;EACA;EACA;EACA;;;AAOH;EACC;;AAED;EACC;;;AAIF;EACC;EACA;EACA;EACA;EACA;;;AChTD;AAEA;EACC;EACA;;;AAGD;EACC;EACA;;;AAGD;EACC;;AAEA;EACC;;;AAIF;EACG;EACA;EA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;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AqIsPF;ArInPC;;AACA;EACC;IACC;IACA;IACA;IACA,oBqI8O+B;IrI7O/B;;EAGD;IACC;;;AqIwOH;ArIpOC;;AACA;EACC;IACC;IACA;IACA;IACA,iBqI+N+B;IrI9N/B;;EAGD;IACC;;;;AqIgOH;ExKpQC;;;AwKwQD;ExKhRC;;;AwKoRD;ExKhRC;;;AwKoRD;EACC;;;AAGD;EACC;;;AAGD;AACA;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;AACA;EACC;EACA,W5M3SsB;;;A4M8SvB;EACC;EACA,W5MlTqB;;;A4MqTtB;EACC;EACA,W5MzTsB;;;A4M4TvB;EACC;EACA,W5M5TqB;E4M6TrB;;;AAGD;EACC;EACA,W5MlUqB;E4MmUrB,OzMjUiB;;;AyMoUlB;EACC,a5M/SwB;E4MgTxB;EACA;;;AAGD;EACC;EACA,a5MpTwB;E4MqTxB,OzM7UiB;;;AyMgVlB;EACC;EACA,a5M1TwB;E4M2TxB;EACA,OzMpViB;;;AyMuVlB;EACC,OzMxViB;EyMyVjB,W5M3VqB;;;A4M8VtB;EACC;EACA,a5MxUwB;;;A4M4UzB;EACC;EACA,a5M9UwB;;;A4MiVzB;EACC;EACA,a5MnVwB;E4MoVxB,W5M5WqB;;;A4M+WtB;EACC,a5MtVwB;;;A4MyVzB;EACC,a5M1VwB;;;A4M6VzB;EACC,OzMvVqB","file":"delos.scss"} \ No newline at end of file