diff --git a/.github/workflows/legacy-ui.yml b/.github/workflows/legacy-ui.yml index 23391ebafe16..8ae579f43c92 100644 --- a/.github/workflows/legacy-ui.yml +++ b/.github/workflows/legacy-ui.yml @@ -12,7 +12,6 @@ on: type: choice options: - trunk - - release_8 permissions: contents: read pull-requests: read @@ -37,7 +36,7 @@ jobs: args: --no-interaction --no-progress --ignore-platform-reqs --no-scripts - name: 'PHStan Custom Rules' - run: CI/PHPStan/run_legacy_ui_report.sh + run: scripts/PHPStan/run_legacy_ui_report.sh - name: 'Store Report' uses: actions/upload-artifact@v3 diff --git a/CI/PHPStan/constants.php b/CI/PHPStan/constants.php index 0f74a99b1dc7..fd237ae5ce98 100644 --- a/CI/PHPStan/constants.php +++ b/CI/PHPStan/constants.php @@ -1,4 +1,4 @@ -blpg > 0) diff --git a/Modules/Blog/classes/class.ilObjBlog.php b/Modules/Blog/classes/class.ilObjBlog.php index 8a18ba28466f..074c890b5a15 100644 --- a/Modules/Blog/classes/class.ilObjBlog.php +++ b/Modules/Blog/classes/class.ilObjBlog.php @@ -642,7 +642,7 @@ public static function deliverRSS(string $a_wsp_id): void $feed = new ilFeedWriter(); - $url = ilLink::_getStaticLink($a_wsp_id, "blog", true, $is_wsp); + $url = ilLink::_getStaticLink($a_wsp_id, "blog", true, (string) $is_wsp); $url = str_replace("&", "&", $url); // #11870 diff --git a/Modules/Blog/classes/class.ilObjBlogGUI.php b/Modules/Blog/classes/class.ilObjBlogGUI.php index 879ab7bcc93f..5c298e3ace3d 100644 --- a/Modules/Blog/classes/class.ilObjBlogGUI.php +++ b/Modules/Blog/classes/class.ilObjBlogGUI.php @@ -22,13 +22,14 @@ /** * Class ilObjBlogGUI * @author Jörg Lützenkirchen - * @ilCtrl_Calls ilObjBlogGUI: ilBlogPostingGUI, ilWorkspaceAccessGUI, ilPortfolioPageGUI + * @ilCtrl_Calls ilObjBlogGUI: ilBlogPostingGUI, ilWorkspaceAccessGUI * @ilCtrl_Calls ilObjBlogGUI: ilInfoScreenGUI, ilNoteGUI, ilCommonActionDispatcherGUI * @ilCtrl_Calls ilObjBlogGUI: ilPermissionGUI, ilObjectCopyGUI, ilRepositorySearchGUI * @ilCtrl_Calls ilObjBlogGUI: ilExportGUI, ilObjectContentStyleSettingsGUI, ilBlogExerciseGUI, ilObjNotificationSettingsGUI */ class ilObjBlogGUI extends ilObject2GUI implements ilDesktopItemHandling { + protected string $rendered_content = ""; protected \ILIAS\Notes\Service $notes; protected \ILIAS\Blog\ReadingTime\BlogSettingsGUI $reading_time_gui; protected \ILIAS\Blog\ReadingTime\ReadingTimeManager $reading_time_manager; @@ -489,6 +490,7 @@ protected function setTabs(): void $ilHelp->setScreenIdComponent("blog"); if ($this->checkPermissionBool("read")) { + $this->ctrl->setParameterByClass(self::class, "bmn", null); $this->tabs_gui->addTab( "content", $lng->txt("content"), @@ -583,9 +585,9 @@ public function executeCommand(): void $link = $ilCtrl->getLinkTargetByClass(["ilrepositorygui", "ilObjBlogGUI"], "preview"); $ilNavigationHistory->addItem($this->node_id, $link, "blog"); } - switch ($next_class) { case 'ilblogpostinggui': + $this->ctrl->saveParameter($this, "user_page"); if (!$this->prtf_embed) { $tpl->loadStandardTemplate(); } @@ -691,7 +693,8 @@ public function executeCommand(): void $this->addHeaderActionForCommand($cmd); $this->filterInactivePostings(); $nav = $this->renderNavigation("gethtml", $cmd); - $this->buildEmbedded($ret, $nav); + // this is important for embedded blog pages! + $this->rendered_content = $this->buildEmbedded($ret, $nav); return; // ilias/editor @@ -718,7 +721,9 @@ public function executeCommand(): void if ($public_action) { $this->tpl->setOnScreenMessage('success', implode("
", $info)); } else { - $this->tpl->setOnScreenMessage('info', implode("
", $info)); + if (count($info) > 0) { + $this->tpl->setOnScreenMessage('info', implode("
", $info)); + } } // revert to edit cmd to avoid confusion @@ -805,7 +810,7 @@ public function executeCommand(): void } else { $settings_gui = $this->content_style_gui ->objectSettingsGUIForObjId( - 0, + null, $this->object->getId() ); } @@ -851,10 +856,15 @@ public function executeCommand(): void if (!$cmd) { $cmd = "render"; } - $this->$cmd(); + $this->rendered_content = (string) $this->$cmd(); } } + public function getRenderedContent(): string + { + return $this->rendered_content; + } + protected function triggerAssignmentTool(): void { $be = new ilBlogExercise($this->node_id); @@ -1024,7 +1034,7 @@ public function render(): void $list = $nav = ""; if ($list_items) { $list = $this->renderList($list_items, "preview", "", $is_owner); - $nav = $this->renderNavigation("render", "preview", "", $is_owner); + $nav = $this->renderNavigation("render", "edit", "", $is_owner); } $this->setContentStyleSheet(); @@ -1793,6 +1803,22 @@ protected function renderNavigationByDate( } $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 { @@ -2057,9 +2083,9 @@ public function renderToolbarNavigation( $ctrl->setParameterByClass("ilblogpostinggui", "blpg", $this->blpg); - if ($this->prtf_embed) { + /*if ($this->prtf_embed) { $this->ctrl->setParameterByClass("ilobjportfoliogui", "ppage", $this->user_page); - } + }*/ $link = $ctrl->getLinkTargetByClass("ilblogpostinggui", "edit"); $toolbar->addComponent($f->button()->standard($lng->txt("blog_edit_posting"), $link)); } @@ -2986,4 +3012,9 @@ public function printPostings(): void $print_view = $this->getPrintView(); $print_view->sendPrintView(); } + + protected function forwardExport(): void + { + $this->ctrl->redirectByClass(ilExportGUI::class); + } } diff --git a/Modules/Blog/classes/class.ilObjBlogListGUI.php b/Modules/Blog/classes/class.ilObjBlogListGUI.php index 62ec6c5f00dc..c13c92811a0a 100644 --- a/Modules/Blog/classes/class.ilObjBlogListGUI.php +++ b/Modules/Blog/classes/class.ilObjBlogListGUI.php @@ -69,6 +69,13 @@ public function insertCommand( ): void { $ctrl = $this->ctrl; + if ($cmd === "export" + && ilObjBlogAccess::isCommentsExportPossible($this->obj_id) + && (bool) $this->settings->get('item_cmd_asynch')) { + $href = $this->getCommandLink("forwardExport"); + $cmd = "forwardExport"; + $onclick = ""; + } if ($cmd !== "export" || !ilObjBlogAccess::isCommentsExportPossible($this->obj_id)) { parent::insertCommand($href, $text, $frame, $img, $cmd, $onclick); return; diff --git a/Modules/Blog/templates/default/tpl.blog_list_navigation_by_date.html b/Modules/Blog/templates/default/tpl.blog_list_navigation_by_date.html index 5a4f0ea5f405..b8d76d375b4c 100644 --- a/Modules/Blog/templates/default/tpl.blog_list_navigation_by_date.html +++ b/Modules/Blog/templates/default/tpl.blog_list_navigation_by_date.html @@ -1,3 +1,4 @@ +

{STARTING_PAGE}

diff --git a/Modules/BookingManager/Objects/class.ilBookingObject.php b/Modules/BookingManager/Objects/class.ilBookingObject.php index 3ed70e7f9704..b9b577ce6990 100644 --- a/Modules/BookingManager/Objects/class.ilBookingObject.php +++ b/Modules/BookingManager/Objects/class.ilBookingObject.php @@ -410,6 +410,20 @@ public function delete(): int return 0; } + public function deleteReservationsAndCalEntries(int $object_id): void + { + $repo_fac = new ilBookingReservationDBRepositoryFactory(); + $reservation_db = $repo_fac->getRepo(); + $reservation_ids = $reservation_db->getReservationIdsByBookingObjectId($object_id); + + foreach ($reservation_ids as $reservation_id) { + $reservation = new ilBookingReservation($reservation_id); + $entry = new ilCalendarEntry($reservation->getCalendarEntry()); + $reservation_db->delete($reservation_id); + $entry->delete(); + } + } + /** * Get nr of available items for a set of object ids * @param int[] $a_obj_ids diff --git a/Modules/BookingManager/Objects/class.ilBookingObjectGUI.php b/Modules/BookingManager/Objects/class.ilBookingObjectGUI.php index 5ce1fc26fff6..90da3b3f4b58 100644 --- a/Modules/BookingManager/Objects/class.ilBookingObjectGUI.php +++ b/Modules/BookingManager/Objects/class.ilBookingObjectGUI.php @@ -536,6 +536,7 @@ public function delete(): void $lng = $this->lng; $obj = new ilBookingObject($this->object_id); + $obj->deleteReservationsAndCalEntries($this->object_id); $obj->delete(); $this->tpl->setOnScreenMessage('success', $lng->txt('book_object_deleted'), true); diff --git a/Modules/BookingManager/Reservations/class.ilBookingReservation.php b/Modules/BookingManager/Reservations/class.ilBookingReservation.php index ac3d138bc653..d2f0b06e72aa 100644 --- a/Modules/BookingManager/Reservations/class.ilBookingReservation.php +++ b/Modules/BookingManager/Reservations/class.ilBookingReservation.php @@ -172,13 +172,13 @@ protected function read(): void if ($this->id) { $row = $this->repo->getForId($this->id); $this->setUserId($row['user_id']); - $this->setAssignerId($row['assigner_id']); + $this->setAssignerId((int) $row['assigner_id']); $this->setObjectId($row['object_id']); $this->setFrom($row['date_from']); $this->setTo($row['date_to']); $this->setStatus($row['status']); - $this->setGroupId($row['group_id']); - $this->setContextObjId($row['context_obj_id']); + $this->setGroupId((int) $row['group_id']); + $this->setContextObjId((int) $row['context_obj_id']); } } diff --git a/Modules/BookingManager/Reservations/class.ilBookingReservationDBRepository.php b/Modules/BookingManager/Reservations/class.ilBookingReservationDBRepository.php index 6c0c69fab296..5dce56bef44a 100644 --- a/Modules/BookingManager/Reservations/class.ilBookingReservationDBRepository.php +++ b/Modules/BookingManager/Reservations/class.ilBookingReservationDBRepository.php @@ -328,4 +328,20 @@ public function getCachedContextObjBookingInfo( return ($row["context_obj_id"] == $context_obj_id); }); } + + public function getReservationIdsByBookingObjectId(int $booking_object_id): array + { + $set = $this->db->queryF( + "SELECT booking_reservation_id FROM booking_reservation " . + " WHERE object_id = %s ", + ["integer"], + [$booking_object_id] + ); + $ret = []; + while ($row = $this->db->fetchAssoc($set)) { + $ret[] = (int) $row['booking_reservation_id']; + } + + return $ret; + } } diff --git a/Modules/Category/classes/class.ilObjCategoryGUI.php b/Modules/Category/classes/class.ilObjCategoryGUI.php index 76b44f88c7c7..529205357e4c 100755 --- a/Modules/Category/classes/class.ilObjCategoryGUI.php +++ b/Modules/Category/classes/class.ilObjCategoryGUI.php @@ -199,7 +199,7 @@ public function executeCommand(): void case "ilobjectcontentstylesettingsgui": $this->checkPermission("write"); $this->setTitleAndDescription(); - //$this->showContainerPageTabs(); + $this->showContainerPageTabs(); $settings_gui = $this->content_style_gui ->objectSettingsGUIForRefId( null, @@ -1069,7 +1069,7 @@ public function assignRolesObject(): void ); $f_result[$counter]['title'] = $role_obj->getTitle() ?: ""; $f_result[$counter]['desc'] = $role_obj->getDescription() ?: ""; - $f_result[$counter]['type'] = $role['role_type'] === 'global' ? + $f_result[$counter]['type'] = ($role['role_type'] ?? '') === 'global' ? $this->lng->txt('global') : $this->lng->txt('local'); diff --git a/Modules/Chatroom/classes/class.ilChatroomTabGUIFactory.php b/Modules/Chatroom/classes/class.ilChatroomTabGUIFactory.php index 1376d347bd69..f1221a995105 100644 --- a/Modules/Chatroom/classes/class.ilChatroomTabGUIFactory.php +++ b/Modules/Chatroom/classes/class.ilChatroomTabGUIFactory.php @@ -35,6 +35,8 @@ class ilChatroomTabGUIFactory private ilRbacSystem $rbacSystem; private GlobalHttpState $http; private Refinery $refinery; + private ?string $activated_tab = null; + private ?string $activated_sub_tab = null; public function __construct(ilObjectGUI $gui) { @@ -132,7 +134,10 @@ public function getAdminTabsForCommand(string $command): void ]; $DIC->ctrl()->clearParametersByClass(ilPermissionGUI::class); - $is_in_permission_gui = strtolower($DIC->ctrl()->getCmdClass()) === strtolower(ilPermissionGUI::class); + $is_in_permission_gui = ( + strtolower($DIC->ctrl()->getCmdClass()) === strtolower(ilPermissionGUI::class) || + strtolower($DIC->ctrl()->getCmdClass()) === strtolower(ilObjectPermissionStatusGUI::class) + ); $commandParts = explode('_', $command, 2); if ($command === 'ban_show') { @@ -264,16 +269,29 @@ private function activateTab(array $commandParts, array $config): void if (count($commandParts) > 1) { if (isset($config[$commandParts[0]])) { $DIC->tabs()->activateTab($commandParts[0]); + $this->activated_tab = $commandParts[0]; if (isset($config[$commandParts[0]]['subtabs'][$commandParts[1]])) { $DIC->tabs()->activateSubTab($commandParts[1]); + $this->activated_sub_tab = $commandParts[1]; } } } elseif (count($commandParts) === 1) { $DIC->tabs()->activateTab($commandParts[0]); + $this->activated_tab = $commandParts[0]; } } + public function getActivatedTab(): ?string + { + return $this->activated_tab; + } + + public function getActivatedSubTab(): ?string + { + return $this->activated_sub_tab; + } + /** * Builds $config and $commandparts arrays to assign them as parameters * when calling $this->buildTabs and $this->activateTab. diff --git a/Modules/Chatroom/classes/class.ilObjChatroom.php b/Modules/Chatroom/classes/class.ilObjChatroom.php index 3b8133b0d599..d1405120538e 100644 --- a/Modules/Chatroom/classes/class.ilObjChatroom.php +++ b/Modules/Chatroom/classes/class.ilObjChatroom.php @@ -90,12 +90,6 @@ public function update(): bool $activation->setTimingStart($this->getAccessBegin()); $activation->setTimingEnd($this->getAccessEnd()); $activation->toggleVisible((bool) $this->getAccessVisibility()); - $activation->setSuggestionStart(0); - $activation->setSuggestionStartRelative(0); - $activation->setSuggestionEnd(0); - $activation->setSuggestionEndRelative(0); - $activation->setEarliestStart(0); - $activation->setEarliestStartRelative(0); $activation->toggleChangeable(true); $activation->update($this->ref_id); } diff --git a/Modules/Chatroom/classes/class.ilObjChatroomAdminGUI.php b/Modules/Chatroom/classes/class.ilObjChatroomAdminGUI.php index ff12afe1a689..3f34239667f3 100644 --- a/Modules/Chatroom/classes/class.ilObjChatroomAdminGUI.php +++ b/Modules/Chatroom/classes/class.ilObjChatroomAdminGUI.php @@ -87,6 +87,11 @@ public function executeCommand(): void $this->dispatchCall($res[0], $res[1]); } + + if ($tabFactory->getActivatedTab() !== null && + $this->tabs_gui->getActiveTab() !== $tabFactory->getActivatedTab()) { + $this->tabs_gui->activateTab($tabFactory->getActivatedTab()); + } } public function getConnector(): ilChatroomServerConnector diff --git a/Modules/Chatroom/classes/class.ilObjChatroomGUI.php b/Modules/Chatroom/classes/class.ilObjChatroomGUI.php index bafe2e1e7ccf..1ee49caee50d 100644 --- a/Modules/Chatroom/classes/class.ilObjChatroomGUI.php +++ b/Modules/Chatroom/classes/class.ilObjChatroomGUI.php @@ -163,6 +163,7 @@ public function executeCommand(): void $next_class = $this->ctrl->getNextClass(); + $tabFactory = null; if (!$this->getCreationMode()) { $tabFactory = new ilChatroomTabGUIFactory($this); @@ -255,6 +256,12 @@ public function executeCommand(): void } break; } + + if ($tabFactory !== null && + $tabFactory->getActivatedTab() !== null && + $this->tabs_gui->getActiveTab() !== $tabFactory->getActivatedTab()) { + $this->tabs_gui->activateTab($tabFactory->getActivatedTab()); + } } public function getConnector(): ilChatroomServerConnector diff --git a/Modules/CmiXapi/PRIVACY.md b/Modules/CmiXapi/PRIVACY.md new file mode 100755 index 000000000000..9013a92c23ba --- /dev/null +++ b/Modules/CmiXapi/PRIVACY.md @@ -0,0 +1,75 @@ +# Module cmix Privacy + +Disclaimer: This documentation does not warrant completeness or correctness. Please report any missing or wrong information using the [ILIAS issue tracker](https://mantis.ilias.de) or contribute a fix via [Pull Request](docs/development/contributing.md#pull-request-to-the-repositories). + +## General Information + +This module cmix allows uploading an object or using an external object. An object is a Learning Record Provider (LRP), usually content or instructional activities. Personal data flow from an LRP to a Learning Record Store (LRS). +LRS and LRP are to store and manage states and statements on learning activities or learning progress of a person. +ILIAS only holds the identification of a person to connect Learning Progress or statements on learning activities to an ILIAS account. + +* The LRP communicates directly with the LRS: ILIAS queries Learning Progress or statements of learning activities from the LRS. To connect this to an account, ILIAS must identify a record with a user-ID. + * If the LRP is launched by ILIAS, ILIAS provides a User-ID. + * If the LRP is launched without ILIAS, a person provides their e-mail address used for the LRS. +* The LRP communicates directly with ILIAS: ILIAS behaves as an LRS for the LRP. ILIAS functions as a proxy between LRP and LRS. Thus ILIAS can filter statements of learning activities being sent to the LRS. + + +## Data being stored + +1. LRP / content generates and sends states like page last visited, number of page views, results of questions, number of times a question was viewed or answered, number of times a page was viewed, language or audio preference. ILIAS does not process this data. +2. LRP / content generates and sends statements on learning activities to the LRS. These statements follow a grammar subject-verb-object may comprise context data. However, the data is generated in the LRP / content and sent to the LRS. Statements are triggered by events and document what happened in a granular fashion i.e.: + * User x viewed the page z on date DD.MM.YYYY HH:MM:SS for duration d. + * User x wrote the comment r on that page z on date DD.MM.YYYY HH:MM:SS. + * User a launched the test q on date DD.MM.YYYY HH:MM:SS. + * User a answered the question l selected answer option u, gained 4 of 7 points on date DD.MM.YYYY HH:MM:SS. + * User b experienced PDF File p on date DD.MM.YYYY HH:MM:SS. + * User b completed test q with score t on date DD.MM.YYYY HH:MM:SS. +3. If cmi5 is used, then ILIAS generates and sends only the following fixed set of statements on learning activities to the LRS: + * "user x launched the activity y", + * "user q abandoned the activity p" and + * "user w satisfied the activity z". + +After a statement was sent, changing ILIAS settings i.e. learning progress, cannot change the statement. + +Typically enourmous amounts of behavioral data, often times personalized behavioral data, is generated and sent around. +In all these cases ILIAS stores some kind of user identification, i.e. User-ID, E-Mail address or hash value. +This "unregulated" scenario is not advisable to run. + +The unimpeded communication between LRP / content and LRS should be curbed by using configuration: +At Administration > Extending ILIAS > xAPI/cmi5 a person with "Edit Settings" permission can add an additional LRS-Type. +* Activating the "CronJob necessary for Learning Progress" prevents curbing the amount of data sent. +* In the option "User Identification" one can select which piece of information is transmitted to the flow of data to LRP and LRS. Selecting "Hash combined with a unique ILIAS platform id formatted as an E-Mail address" is advised. One and the same LRS can be shared between learning platforms. It MUST be ensured that User Identification data is hashed at least. +* In the option "User Name" one can select which piece of information is transmitted to the flow of data to LRP and LRS. Selecting "No one" is advised. +* The settings offer an option to reduce the amount of statements being send from the LRP / content to LRS, i.e. +* Activate "Save learning success data only" to retain only statements comprising specific and selected verbs. +* Activate "Blacken Data" to replace actual calendar dates or durations by fixed values. +* Activate "Do not store substatements" to discard subordinate statements. +* To force privacy settings on repository objects: "Configurations Options: Settings are not changeable for Objects". + +In the repository an object type "xAPI/cmi5" can be added, the LRS-Type MUST be selected in the ILIAS creation dialogue. +* If in Administration > Extending ILIAS > xAPI/cmi5 > Add LRS-Type > Configuration Options is set to "Default Settings, changeable for Objects", then a person can overrule the above mentioned options and create their own privacy policy. This option makes it impossible to specify a document for consent, since at any given time the privacy regime could be changed by a person with "Edit Settings" permission for an xAPI/cmi5-object. +* If in Administration > Extending ILIAS > xAPI/cmi5 > Add LRS-Type > Configuration Options is set to "Settings are not changeable for Objects", then the globally set privacy options cannot be changed in an xAPI/cmi5-object. To specify a document for consent, several LRS-Types should be set up to serve different educational practices i.e. exams require more personal data while some content can be quite restrictve privacy-wise. +* Creating an "xAPI/cmi5" object in the repositry allows selecting an LRS-Type. In Administration > Extending ILIAS > xAPI/cmi5 > Add LRS-Type the setting "Availability" determines if an LRS-Type is available in the creation dialogue. LRS-Types set to "Not available" do not receive new data. This prepares an orderly deletion of LRS data sets. + +The Module cmix employs the following services, please consult the respective privacy.mds: Metadata, AccessControl, Learning Progress. + + +## Data being presented + +* No xAPI related data is presented in the global administration. +* A person with "Edit Settings" permission for an xAPI/cmi5 object in the repository can activate statement viewer and ranking viewer in the tab Settings. + * If activated, the person with "Read" permission is presented with new tabs "Learning Experiences" and "Ranking". + * A table in "Learning Experiences" presents data sets comprising statements on the person him- or herself: On Date, User, Verb, Object. A modal dialogue can be opened to present the full statement which may comprise a lot more data like duration, selected answer option, success, context and so forth. The amount of data tracked depends on the LRP. + * In tab "Ranking", tables are presented according to options selected in the Settings-tab: Own rank of person, top ranking of other persons, both. Date, Percentage and duration might be presented. + * A Person with "View learning experiences of other users" is always is presented with tabs "Learning Experiences" and "Ranking". + * A table in "Learning Experiences" presents all data set on all the persons that have interacted with object: On Date, User, Verb, Object. This person can also view all full statements on all users. + * In "Ranking", tables are presented according to options selected in the Settings-tab: Own rank of person, top ranking of other persons, both. Date, Percentage and duration might be presented. +* The data stored in the LRS are always displayed only in relation to the LRP. + + +## Data being deleted + +The xAPI specification does not envisage any kind of deletion of data in the LRS at all. +When an object "xAPI/cmi5" in the repository is deleted, the personal data of the statements persists in the LRS. Once personal data is communicated to the LRS, ILIAS has no control over the deletion this personal data. +There is no way to delete data in the LRS from ILIAS. +Thus you MUST ensure that the data produced is properly pseudonymized as laid out in the section on storage. \ No newline at end of file diff --git a/Modules/CmiXapi/classes/XapiReport/class.ilCmiXapiStatementsReport.php b/Modules/CmiXapi/classes/XapiReport/class.ilCmiXapiStatementsReport.php index d2e1f5792b10..f28aba222bb8 100644 --- a/Modules/CmiXapi/classes/XapiReport/class.ilCmiXapiStatementsReport.php +++ b/Modules/CmiXapi/classes/XapiReport/class.ilCmiXapiStatementsReport.php @@ -148,9 +148,9 @@ protected function fetchVerbId(array $statement): string protected function fetchVerbDisplay(array $statement): string { - try { + if (isset($statement['verb']['display']['en-US'])) { return $statement['verb']['display']['en-US']; - } catch (Exception $e) { + } else { return $statement['verb']['id']; } } diff --git a/Modules/CmiXapi/classes/XapiResults/class.ilXapiStatementEvaluation.php b/Modules/CmiXapi/classes/XapiResults/class.ilXapiStatementEvaluation.php index 3f51cbbe8587..7b68a57dee42 100755 --- a/Modules/CmiXapi/classes/XapiResults/class.ilXapiStatementEvaluation.php +++ b/Modules/CmiXapi/classes/XapiResults/class.ilXapiStatementEvaluation.php @@ -135,9 +135,12 @@ public function evaluateStatement(object $xapiStatement, int $usrId): void if (($xapiVerb == ilCmiXapiVerbList::COMPLETED || $xapiVerb == ilCmiXapiVerbList::PASSED) && $this->isLpModeInterestedInResultStatus($newResultStatus, false)) { // it is possible to check against authToken usrId! $cmixUser = $this->getCmixUser($xapiStatement); - $cmixUser->setSatisfied(true); - $cmixUser->save(); - $this->sendSatisfiedStatement($cmixUser); + // avoid multiple satisfied + if (!$cmixUser->getSatisfied()) { + $cmixUser->setSatisfied(true); + $cmixUser->save(); + $this->sendSatisfiedStatement($cmixUser); + } } } } diff --git a/Modules/CmiXapi/classes/class.ilObjCmiXapiGUI.php b/Modules/CmiXapi/classes/class.ilObjCmiXapiGUI.php index 13dc8caa85e1..3691dd5a51a7 100755 --- a/Modules/CmiXapi/classes/class.ilObjCmiXapiGUI.php +++ b/Modules/CmiXapi/classes/class.ilObjCmiXapiGUI.php @@ -109,7 +109,7 @@ protected function initCreateForm(string $a_new_type): \ilPropertyFormGUI $item->setRequired(true); $types = ilCmiXapiLrsTypeList::getTypesData(false, ilCmiXapiLrsType::AVAILABILITY_CREATE); foreach ($types as $type) { - $option = new ilRadioOption($type['title'], $type['type_id'], $type['description']); + $option = new ilRadioOption((string) $type['title'], (string) $type['type_id'], (string) $type['description']); $item->addOption($option); } #$item->setValue($this->object->typedef->getTypeId()); diff --git a/Modules/ContentPage/classes/PageMetrics/PageMetricsService.php b/Modules/ContentPage/classes/PageMetrics/PageMetricsService.php index d501cacc2df4..a714cb190c79 100644 --- a/Modules/ContentPage/classes/PageMetrics/PageMetricsService.php +++ b/Modules/ContentPage/classes/PageMetrics/PageMetricsService.php @@ -47,7 +47,7 @@ public function __construct(PageMetricsRepository $pageMetricsRepository, Factor protected function doesPageExistsForLanguage(int $contentPageId, string $language): bool { - return ilContentPagePage::_exists(self::OBJ_TYPE, $contentPageId, $language); + return ilContentPagePage::_exists(self::OBJ_TYPE, $contentPageId, $language, true); } protected function ensurePageObjectExists(int $contentPageId, string $language): void diff --git a/Modules/ContentPage/classes/class.ilObjContentPage.php b/Modules/ContentPage/classes/class.ilObjContentPage.php index c6e0bf9f1db5..b41f09fabb70 100644 --- a/Modules/ContentPage/classes/class.ilObjContentPage.php +++ b/Modules/ContentPage/classes/class.ilObjContentPage.php @@ -74,7 +74,7 @@ protected function doCloneObject(ilObject2 $new_obj, int $a_target_id, ?int $a_c $ot = ilObjectTranslation::getInstance($this->getId()); $ot->copy($new_obj->getId()); - if (ilContentPagePage::_exists($this->getType(), $this->getId())) { + if (ilContentPagePage::_exists($this->getType(), $this->getId(), '', true)) { $translations = ilContentPagePage::lookupTranslations($this->getType(), $this->getId()); foreach ($translations as $language) { $originalPageObject = new ilContentPagePage($this->getId(), 0, $language); @@ -161,7 +161,7 @@ protected function doDelete(): void { parent::doDelete(); - if (ilContentPagePage::_exists($this->getType(), $this->getId())) { + if (ilContentPagePage::_exists($this->getType(), $this->getId(), '', true)) { $originalPageObject = new ilContentPagePage($this->getId()); $originalPageObject->delete(); } diff --git a/Modules/ContentPage/classes/class.ilObjContentPageGUI.php b/Modules/ContentPage/classes/class.ilObjContentPageGUI.php index 15a448abcd4d..e9f2dc1a2c9d 100644 --- a/Modules/ContentPage/classes/class.ilObjContentPageGUI.php +++ b/Modules/ContentPage/classes/class.ilObjContentPageGUI.php @@ -560,6 +560,20 @@ protected function initEditCustomForm(ilPropertyFormGUI $a_form): void ilObjectServiceSettingsGUI::INFO_TAB_VISIBILITY ] ); + + if ($this->getCreationMode() !== true && count($this->object->getObjectTranslation()->getLanguages()) > 1) { + $languages = ilMDLanguageItem::_getLanguages(); + $a_form->getItemByPostVar('title') + ->setInfo( + implode( + ': ', + [ + $this->lng->txt('language'), + $languages[$this->object->getObjectTranslation()->getDefaultLanguage()] + ] + ) + ); + } } private function addAvailabilitySection(ilPropertyFormGUI $form): void @@ -577,6 +591,11 @@ protected function getEditFormCustomValues(array &$a_values): void { $a_values['activation_online'] = $this->object->getOfflineStatus() === false; $a_values[ilObjectServiceSettingsGUI::INFO_TAB_VISIBILITY] = $this->infoScreenEnabled; + + if (count($this->object->getObjectTranslation()->getLanguages()) > 1) { + $a_values['title'] = $this->object->getObjectTranslation()->getDefaultTitle(); + $a_values['desc'] = $this->object->getObjectTranslation()->getDefaultDescription(); + } } protected function updateCustom(ilPropertyFormGUI $form): void diff --git a/Modules/Course/classes/Badges/class.ilCourseLPBadge.php b/Modules/Course/classes/Badges/class.ilCourseLPBadge.php index 8805e0eb0234..cb0dcf45b392 100644 --- a/Modules/Course/classes/Badges/class.ilCourseLPBadge.php +++ b/Modules/Course/classes/Badges/class.ilCourseLPBadge.php @@ -76,7 +76,7 @@ public function evaluate(int $a_user_id, array $a_params, ?array $a_config): boo // check if all subitems are completed now if ($a_config !== null && isset($a_config['subitems'])) { foreach ($a_config['subitems'] as $subitem_id) { - $subitem_obj_id = $subitem_obj_ids[$subitem_id]; + $subitem_obj_id = (int) $subitem_obj_ids[$subitem_id]; if (ilLPStatus::_lookupStatus($subitem_obj_id, $a_user_id) !== ilLPStatus::LP_STATUS_COMPLETED_NUM) { $completed = false; break; diff --git a/Modules/Course/classes/Badges/class.ilCourseLPBadgeGUI.php b/Modules/Course/classes/Badges/class.ilCourseLPBadgeGUI.php index e080fa866edf..44ca14589b7b 100644 --- a/Modules/Course/classes/Badges/class.ilCourseLPBadgeGUI.php +++ b/Modules/Course/classes/Badges/class.ilCourseLPBadgeGUI.php @@ -55,6 +55,8 @@ public function initConfigForm(ilPropertyFormGUI $a_form, int $a_parent_ref_id): } $exp->setTypeWhiteList($white); $subitems->setTitleModifier(function ($a_id): string { + $a_id = (int) $a_id; + $obj_id = ilObject::_lookupObjId($a_id); $olp = ilObjectLP::getInstance($obj_id); $invalid_modes = ilCourseLPBadgeGUI::getInvalidLPModes(); @@ -147,7 +149,7 @@ public function validateForm(ilPropertyFormGUI $a_form): bool $invalid = array(); $invalid_modes = self::getInvalidLPModes(); foreach ($a_form->getInput("subitems") as $ref_id) { - $obj_id = ilObject::_lookupObjId($ref_id); + $obj_id = ilObject::_lookupObjId((int) $ref_id); $olp = ilObjectLP::getInstance($obj_id); if (in_array($olp->getCurrentMode(), $invalid_modes)) { $invalid[] = ilObject::_lookupTitle($obj_id); diff --git a/Modules/Course/classes/Objectives/Setup/ilCourseObjectiveDBUpdateSteps.php b/Modules/Course/classes/Objectives/Setup/ilCourseObjectiveDBUpdateSteps.php new file mode 100644 index 000000000000..184082538d4c --- /dev/null +++ b/Modules/Course/classes/Objectives/Setup/ilCourseObjectiveDBUpdateSteps.php @@ -0,0 +1,43 @@ +db = $db; + } + + public function step_1(): void + { + if (!$this->db->indexExistsByFields('loc_settings', ['itest'])) { + $this->db->addIndex('loc_settings', ['itest'], 'i1'); + } + } + + public function step_2(): void + { + if (!$this->db->indexExistsByFields('loc_settings', ['qtest'])) { + $this->db->addIndex('loc_settings', ['qtest'], 'i2'); + } + } +} diff --git a/Modules/Course/classes/Objectives/Setup/ilCourseObjectiveSetupAgent.php b/Modules/Course/classes/Objectives/Setup/ilCourseObjectiveSetupAgent.php new file mode 100644 index 000000000000..037bce54366a --- /dev/null +++ b/Modules/Course/classes/Objectives/Setup/ilCourseObjectiveSetupAgent.php @@ -0,0 +1,56 @@ +tree = $DIC->repositoryTree(); $this->db = $DIC->database(); $this->objective_id = $a_objective_id; + $this->logger = $DIC->logger()->crs(); $this->__read(); } diff --git a/Modules/Course/classes/Objectives/class.ilCourseObjectiveQuestion.php b/Modules/Course/classes/Objectives/class.ilCourseObjectiveQuestion.php index d51729992970..43abc8323772 100644 --- a/Modules/Course/classes/Objectives/class.ilCourseObjectiveQuestion.php +++ b/Modules/Course/classes/Objectives/class.ilCourseObjectiveQuestion.php @@ -1,6 +1,5 @@ @@ -127,6 +128,9 @@ public function cloneDependencies(int $a_new_objective, int $a_copy_id): void // Copy tests foreach ($this->getTests() as $test) { + if (!isset($mappings["$test[ref_id]"])) { + continue; + } $new_test_id = $mappings["$test[ref_id]"]; $query = "UPDATE crs_objective_tst " . diff --git a/Modules/Course/classes/Objectives/class.ilCourseObjectivesTableGUI.php b/Modules/Course/classes/Objectives/class.ilCourseObjectivesTableGUI.php index bb8ba8975f83..bf8ad115df76 100644 --- a/Modules/Course/classes/Objectives/class.ilCourseObjectivesTableGUI.php +++ b/Modules/Course/classes/Objectives/class.ilCourseObjectivesTableGUI.php @@ -120,6 +120,16 @@ protected function fillRow(array $a_set): void $this->tpl->setVariable('LM_IMG', ilObject::_getIcon($data['obj_id'], "tiny", $data['type'])); $this->tpl->setVariable('LM_ALT', $this->lng->txt('obj_' . $data['type'])); + if ($data['online']) { + $this->tpl->setCurrentBlock('mat_online'); + $this->tpl->setVariable('MAT_VAL_ONOFFLINE', $this->lng->txt('online')); + } else { + $this->tpl->setCurrentBlock('mat_offline'); + $this->tpl->setVariable('MAT_VAL_ONOFFLINE', $this->lng->txt('offline')); + } + $this->tpl->parseCurrentBlock(); + $this->tpl->setCurrentBlock('mat_row'); + if ($data['type'] == 'catr' || $data['type'] == 'crsr' || $data['type'] == 'grpr') { $this->tpl->setVariable( 'LM_TITLE', @@ -128,6 +138,7 @@ protected function fillRow(array $a_set): void } else { $this->tpl->setVariable('LM_TITLE', ilObject::_lookupTitle($data['obj_id'])); } + $this->tpl->parseCurrentBlock(); } @@ -292,6 +303,7 @@ public function parse(array $a_objective_ids): void default: } + $materials[$material['ref_id']]['online'] = !ilObject::lookupOfflineStatus($obj_id); } $objective_data['materials'] = $materials; $question_obj = new ilCourseObjectiveQuestion($objective_id); diff --git a/Modules/Course/classes/class.ilCourseParticipantsGroupsGUI.php b/Modules/Course/classes/class.ilCourseParticipantsGroupsGUI.php index 6ef4eada4a1f..010afd9f438e 100644 --- a/Modules/Course/classes/class.ilCourseParticipantsGroupsGUI.php +++ b/Modules/Course/classes/class.ilCourseParticipantsGroupsGUI.php @@ -157,15 +157,33 @@ protected function remove(): void $this->ctrl->redirect($this, "show"); } - protected function add(): void + protected function getMultiCommandGroupID(): int { $grp_id = 0; - if ($this->http->wrapper()->post()->has('grp_id')) { + $table_command = ''; + if ( + $this->http->wrapper()->post()->has('cmd') && + $this->http->wrapper()->post()->has('grp_id') + ) { $grp_id = $this->http->wrapper()->post()->retrieve( 'grp_id', $this->refinery->kindlyTo()->int() ); + } elseif ( + $this->http->wrapper()->post()->has('table_top_cmd') && + $this->http->wrapper()->post()->has('grp_id_2') + ) { + $grp_id = $this->http->wrapper()->post()->retrieve( + 'grp_id_2', + $this->refinery->kindlyTo()->int() + ); } + return $grp_id; + } + + protected function add(): void + { + $grp_id = $this->getMultiCommandGroupID(); $usr_ids = []; if ($this->http->wrapper()->post()->has('usrs')) { $usr_ids = $this->http->wrapper()->post()->retrieve( @@ -182,9 +200,11 @@ protected function add(): void } $members_obj = ilGroupParticipants::_getInstanceByObjId($this->objectDataCache->lookupObjId($grp_id)); + $rejected_count = 0; foreach ($usr_ids as $new_member) { if (!$members_obj->add($new_member, ilParticipants::IL_GRP_MEMBER)) { - $this->error->raiseError("An Error occured while assigning user to group !", $this->error->MESSAGE); + $rejected_count++; + continue; } $members_obj->sendNotification( @@ -192,7 +212,18 @@ protected function add(): void $new_member ); } - $this->tpl->setOnScreenMessage('success', $this->lng->txt("grp_msg_member_assigned")); + + if ($rejected_count === 0) { + $message = $this->lng->txt('grp_msg_member_assigned'); + } else { + $accepted_count = count($usr_ids) - $rejected_count; + $message = sprintf( + $this->lng->txt('grp_not_all_users_assigned_msg'), + $accepted_count, + $rejected_count + ); + } + $this->tpl->setOnScreenMessage('success', $message); } $this->show(); } diff --git a/Modules/Course/classes/class.ilCourseParticipantsTableGUI.php b/Modules/Course/classes/class.ilCourseParticipantsTableGUI.php index 809dbd0717be..adf928e074c2 100644 --- a/Modules/Course/classes/class.ilCourseParticipantsTableGUI.php +++ b/Modules/Course/classes/class.ilCourseParticipantsTableGUI.php @@ -161,7 +161,7 @@ protected function fillRow(array $a_set): void foreach ($this->getSelectedColumns() as $field) { switch ($field) { case 'gender': - $a_set['gender'] = $a_set['gender'] ? $this->lng->txt('gender_' . $a_set['gender']) : ''; + $a_set['gender'] = ($a_set['gender'] ?? '') ? $this->lng->txt('gender_' . $a_set['gender']) : ''; $this->tpl->setCurrentBlock('custom_fields'); $this->tpl->setVariable('VAL_CUST', $a_set[$field]); $this->tpl->parseCurrentBlock(); diff --git a/Modules/Course/classes/class.ilObjCourseGUI.php b/Modules/Course/classes/class.ilObjCourseGUI.php index 9f95436f604c..b2dbe98ac4a7 100755 --- a/Modules/Course/classes/class.ilObjCourseGUI.php +++ b/Modules/Course/classes/class.ilObjCourseGUI.php @@ -1,7 +1,5 @@ tabs_gui->activateTab('view_content'); + parent::deleteObject($error); + } + public function renderContainer(): void { parent::renderObject(); @@ -828,6 +833,7 @@ public function updateObject(): void $this->object->setWaitingListAutoFill(false); break; } + $this->object->handleAutoFill(); $obj_service->commonSettings()->legacyForm($form, $this->object)->saveTitleIconVisibility(); $obj_service->commonSettings()->legacyForm($form, $this->object)->saveTopActionsVisibility(); @@ -908,6 +914,26 @@ public function updateObject(): void $this->editObject($form); return; } + + // 29589 + if ( + $sub_type === ilCourseConstants::IL_CRS_SUBSCRIPTION_DEACTIVATED && + ( + !is_null($sub_period->getStart()) || + !is_null($sub_period->getEnd()) + ) + ) { + $this->tpl->setOnScreenMessage( + 'failure', + $this->lng->txt('crs_msg_no_self_registration_period_if_self_enrolment_disabled'), + true + ); + $form->setValuesByPost(); + $this->tpl->setOnScreenMessage('failure', $GLOBALS['DIC']->language()->txt('err_check_input')); + $this->editObject($form); + return; + } + $this->afterUpdate(); } @@ -2179,6 +2205,7 @@ public function executeCommand(): void $this->checkPermission("write"); $this->setTitleAndDescription(); + $this->showContainerPageTabs(); $settings_gui = $DIC->contentStyle()->gui() ->objectSettingsGUIForRefId( null, @@ -2383,6 +2410,7 @@ public function executeCommand(): void && $cmd != 'deliverCertificate' && $cmd != 'performUnsubscribe' && $cmd != 'removeFromDesk' + && $cmd !== 'leave' && !$this->access->checkAccess("read", '', $this->object->getRefId()) || $cmd == 'join' || $cmd == 'subscribe') { diff --git a/Modules/Course/templates/default/tpl.crs_objectives_table_row.html b/Modules/Course/templates/default/tpl.crs_objectives_table_row.html index 4f1a230c0575..325cad63fbf6 100644 --- a/Modules/Course/templates/default/tpl.crs_objectives_table_row.html +++ b/Modules/Course/templates/default/tpl.crs_objectives_table_row.html @@ -28,12 +28,31 @@ -
+ +
+ + +
+ {MAT_VAL_ONOFFLINE} +
+ + +
+ {MAT_VAL_ONOFFLINE} +
+ + +
+ @@ -60,4 +79,4 @@ {VAL_ACTIONS}
- \ No newline at end of file + diff --git a/Modules/DataCollection/classes/Content/class.ilDclRecordEditGUI.php b/Modules/DataCollection/classes/Content/class.ilDclRecordEditGUI.php index 7176ade55c2d..0a53d6288b93 100644 --- a/Modules/DataCollection/classes/Content/class.ilDclRecordEditGUI.php +++ b/Modules/DataCollection/classes/Content/class.ilDclRecordEditGUI.php @@ -1,4 +1,5 @@ table_id, $record_obj->getId()); + $ref_id = $this->http->wrapper()->query()->retrieve('ref_id', $this->refinery->kindlyTo()->int()); + $objDataCollection = new ilObjDataCollection($ref_id); + $objDataCollection->sendNotification("new_record", $this->table_id, $record_obj->getId()); } else { $dispatchEventData['prev_record'] = $unchanged_obj; } diff --git a/Modules/DataCollection/classes/Fields/Base/class.ilDclBaseFieldModel.php b/Modules/DataCollection/classes/Fields/Base/class.ilDclBaseFieldModel.php index 2f8b3510178c..2f8890fe54d8 100644 --- a/Modules/DataCollection/classes/Fields/Base/class.ilDclBaseFieldModel.php +++ b/Modules/DataCollection/classes/Fields/Base/class.ilDclBaseFieldModel.php @@ -669,7 +669,7 @@ public function getRecordQuerySortObject( $sql_obj->setSelectStatement($select_str); $sql_obj->setJoinStatement($join_str); - $sql_obj->setOrderStatement("field_{$this->getId()} {$direction}"); + $sql_obj->setOrderStatement("field_{$this->getId()} {$direction}, ID ASC"); return $sql_obj; } diff --git a/Modules/DataCollection/classes/Fields/Base/class.ilDclBaseRecordModel.php b/Modules/DataCollection/classes/Fields/Base/class.ilDclBaseRecordModel.php index 130f69fa3d56..713db8029f00 100644 --- a/Modules/DataCollection/classes/Fields/Base/class.ilDclBaseRecordModel.php +++ b/Modules/DataCollection/classes/Fields/Base/class.ilDclBaseRecordModel.php @@ -1,4 +1,5 @@ getTableId(), $this->id); + $ref_id = $this->http->wrapper()->query()->retrieve('ref_id', $this->refinery->kindlyTo()->int()); + $objDataCollection = new ilObjDataCollection($ref_id); + $objDataCollection->sendNotification("update_record", $this->getTableId(), $this->id); } } @@ -658,7 +660,9 @@ public function doDelete(bool $omit_notification = false): void $this->table->loadRecords(); if (!$omit_notification) { - ilObjDataCollection::sendNotification("delete_record", $this->getTableId(), $this->getId()); + $ref_id = $this->http->wrapper()->query()->retrieve('ref_id', $this->refinery->kindlyTo()->int()); + $objDataCollection = new ilObjDataCollection($ref_id); + $objDataCollection->sendNotification("delete_record", $this->getTableId(), $this->getId()); $this->event->raise( 'Modules/DataCollection', diff --git a/Modules/DataCollection/classes/Fields/IliasReference/class.ilDclIliasReferenceFieldModel.php b/Modules/DataCollection/classes/Fields/IliasReference/class.ilDclIliasReferenceFieldModel.php index 8cc3103fff6b..616f5827223b 100644 --- a/Modules/DataCollection/classes/Fields/IliasReference/class.ilDclIliasReferenceFieldModel.php +++ b/Modules/DataCollection/classes/Fields/IliasReference/class.ilDclIliasReferenceFieldModel.php @@ -52,7 +52,7 @@ public function getRecordQuerySortObject( $sql_obj = new ilDclRecordQueryObject(); $sql_obj->setSelectStatement($select_str); $sql_obj->setJoinStatement($join_str); - $sql_obj->setOrderStatement("field_{$this->getId()} " . $direction); + $sql_obj->setOrderStatement("field_{$this->getId()} " . $direction. ", ID ASC"); return $sql_obj; } diff --git a/Modules/DataCollection/classes/Fields/Rating/class.ilDclRatingFieldModel.php b/Modules/DataCollection/classes/Fields/Rating/class.ilDclRatingFieldModel.php index bc36eb0ea58f..9f56bb466484 100644 --- a/Modules/DataCollection/classes/Fields/Rating/class.ilDclRatingFieldModel.php +++ b/Modules/DataCollection/classes/Fields/Rating/class.ilDclRatingFieldModel.php @@ -34,7 +34,7 @@ public function getRecordQuerySortObject( $sql_obj = new ilDclRecordQueryObject(); $sql_obj->setSelectStatement($select_str); $sql_obj->setJoinStatement($join_str); - $sql_obj->setOrderStatement("field_{$this->getId()} " . $direction); + $sql_obj->setOrderStatement("field_{$this->getId()} " . $direction. ", ID ASC"); return $sql_obj; } diff --git a/Modules/DataCollection/classes/Fields/Reference/class.ilDclReferenceFieldModel.php b/Modules/DataCollection/classes/Fields/Reference/class.ilDclReferenceFieldModel.php index 5049ca15b25c..fd7571e9bf91 100644 --- a/Modules/DataCollection/classes/Fields/Reference/class.ilDclReferenceFieldModel.php +++ b/Modules/DataCollection/classes/Fields/Reference/class.ilDclReferenceFieldModel.php @@ -56,7 +56,7 @@ public function getRecordQuerySortObject( $sql_obj = new ilDclRecordQueryObject(); $sql_obj->setSelectStatement($select_str); $sql_obj->setJoinStatement($join_str); - $sql_obj->setOrderStatement("field_{$this->getId()} " . $direction); + $sql_obj->setOrderStatement("field_{$this->getId()} " . $direction . ", ID ASC"); return $sql_obj; } diff --git a/Modules/DataCollection/classes/Fields/Selection/class.ilDclSelectionFieldModel.php b/Modules/DataCollection/classes/Fields/Selection/class.ilDclSelectionFieldModel.php index 132075347fb3..996d45c8a564 100644 --- a/Modules/DataCollection/classes/Fields/Selection/class.ilDclSelectionFieldModel.php +++ b/Modules/DataCollection/classes/Fields/Selection/class.ilDclSelectionFieldModel.php @@ -284,7 +284,7 @@ public function getRecordQuerySortObject( $sql_obj->setSelectStatement($select_str); $sql_obj->setJoinStatement($join_str); - $sql_obj->setOrderStatement("field_{$this->getId()} {$direction}"); + $sql_obj->setOrderStatement("field_{$this->getId()} {$direction} , ID ASC"); return $sql_obj; } diff --git a/Modules/DataCollection/classes/Fields/class.ilDclFieldFactory.php b/Modules/DataCollection/classes/Fields/class.ilDclFieldFactory.php index 0ee41c671f42..dedba0c1593b 100644 --- a/Modules/DataCollection/classes/Fields/class.ilDclFieldFactory.php +++ b/Modules/DataCollection/classes/Fields/class.ilDclFieldFactory.php @@ -1,4 +1,5 @@ getId() == ilDclDatatype::INPUTFORMAT_PLUGIN) { if ($field->hasProperty(ilDclBaseFieldModel::PROP_PLUGIN_HOOK_NAME)) { - if ($component_repository->getPluginSlotById(ilDclFieldTypePlugin::SLOT_ID)->hasPluginName($field->getProperty(ilDclBaseFieldModel::PROP_PLUGIN_HOOK_NAME))) { + if (!$component_repository->getPluginSlotById(ilDclFieldTypePlugin::SLOT_ID)->hasPluginName($field->getProperty(ilDclBaseFieldModel::PROP_PLUGIN_HOOK_NAME))) { throw new ilDclException( "Something went wrong by initializing the FieldHook-Plugin '" . $field->getProperty(ilDclBaseFieldModel::PROP_PLUGIN_HOOK_NAME) . "' on Component '" diff --git a/Modules/DataCollection/classes/class.ilObjDataCollection.php b/Modules/DataCollection/classes/class.ilObjDataCollection.php index 58573ecb21e8..c53cd58a612f 100644 --- a/Modules/DataCollection/classes/class.ilObjDataCollection.php +++ b/Modules/DataCollection/classes/class.ilObjDataCollection.php @@ -14,8 +14,7 @@ * https://www.ilias.de * https://github.com/ILIAS-eLearning * - ******************************************************************** - */ + *********************************************************************/ /** * Class ilObjDataCollection @@ -129,46 +128,34 @@ protected function doUpdate(): void * @param $a_table_id * @param null $a_record_id */ - public static function sendNotification($a_action, $a_table_id, $a_record_id = null) + public function sendNotification($a_action, $a_table_id, $a_record_id = null) { global $DIC; - $ilUser = $DIC['ilUser']; - $ilAccess = $DIC['ilAccess']; - $http = $DIC->http(); - $refinery = $DIC->refinery(); - - $ref_id = $http->wrapper()->query()->retrieve('ref_id', $refinery->kindlyTo()->int()); + $ilUser = $DIC->user(); // If coming from trash, never send notifications and don't load dcl Object - if ($ref_id === SYSTEM_FOLDER_ID) { + if ($this->getRefId() === SYSTEM_FOLDER_ID) { return; } - $dclObj = new ilObjDataCollection($ref_id); - - if ($dclObj->getNotification() != 1) { + if ($this->getNotification() != 1) { return; } $obj_table = ilDclCache::getTableCache($a_table_id); - $obj_dcl = $obj_table->getCollectionObject(); // recipients $users = ilNotification::getNotificationsForObject( ilNotification::TYPE_DATA_COLLECTION, - $obj_dcl->getId(), + $this->getId(), true ); if (!count($users)) { return; } - ilNotification::updateNotificationTime(ilNotification::TYPE_DATA_COLLECTION, $obj_dcl->getId(), $users); + ilNotification::updateNotificationTime(ilNotification::TYPE_DATA_COLLECTION, $this->getId(), $users); - $http = $DIC->http(); - $refinery = $DIC->refinery(); - $ref_id = $http->wrapper()->query()->retrieve('ref_id', $refinery->kindlyTo()->int()); - - $link = ilLink::_getLink($ref_id); + $link = ilLink::_getLink($this->getRefId()); // prepare mail content // use language of recipient to compose message @@ -186,11 +173,11 @@ public static function sendNotification($a_action, $a_table_id, $a_record_id = n $ulng = ilLanguageFactory::_getLanguageOfUser($user_id); $ulng->loadLanguageModule('dcl'); - $subject = sprintf($ulng->txt('dcl_change_notification_subject'), $obj_dcl->getTitle()); + $subject = sprintf($ulng->txt('dcl_change_notification_subject'), $this->getTitle()); // update/delete $message = $ulng->txt("dcl_hello") . " " . ilObjUser::_lookupFullname($user_id) . ",\n\n"; $message .= $ulng->txt('dcl_change_notification_dcl_' . $a_action) . ":\n\n"; - $message .= $ulng->txt('obj_dcl') . ": " . $obj_dcl->getTitle() . "\n\n"; + $message .= $ulng->txt('obj_dcl') . ": " . $this->getTitle() . "\n\n"; $message .= $ulng->txt('dcl_table') . ": " . $obj_table->getTitle() . "\n\n"; $message .= $ulng->txt('dcl_record') . ":\n"; $message .= "------------------------------------\n"; @@ -201,9 +188,7 @@ public static function sendNotification($a_action, $a_table_id, $a_record_id = n // $message .= $ulng->txt('dcl_record_id').": ".$a_record_id.":\n"; $t = ""; - $ref_id = $http->wrapper()->query()->retrieve('ref_id', $refinery->kindlyTo()->int()); - - if ($tableview_id = $record->getTable()->getFirstTableViewId($ref_id, $user_id)) { + if ($tableview_id = $record->getTable()->getFirstTableViewId($this->getRefId(), $user_id)) { $visible_fields = ilDclTableView::find($tableview_id)->getVisibleFields(); if (empty($visible_fields)) { continue; @@ -221,7 +206,7 @@ public static function sendNotification($a_action, $a_table_id, $a_record_id = n } } } - $message .= $t; + $message .= $this->prepareMessageText($t); } $message .= "------------------------------------\n"; $message .= $ulng->txt('dcl_changed_by') . ": " . $ilUser->getFullname() . " " . ilUserUtil::getNamePresentation($ilUser->getId()) @@ -297,7 +282,7 @@ protected function doCloneObject(ilObject2 $new_obj, int $a_target_id, ?int $a_c $cp_options = ilCopyWizardOptions::_getInstance($a_copy_id); if (!$cp_options->isRootNode($this->getRefId())) { - $new_obj->setOnline($this->getOnline()); + $new_obj->setOnline(true); } $new_obj->cloneStructure($this->getRefId()); @@ -471,4 +456,24 @@ public function getStyleSheetId(): int { return 0; } + + public function prepareMessageText(string $body): string + { + if (preg_match_all('/<.*?br.*?>/', $body, $matches)) { + $matches = array_unique($matches[0]); + $brNewLineMatches = array_map(static function($match): string { + return $match . "\n"; + }, $matches); + + //Remove carriage return to guarantee all new line can be properly found + $body = str_replace("\r", '', $body); + //Replace occurrence of
+ \n with a single \n + $body = str_replace($brNewLineMatches, "\n", $body); + //Replace additional
with a \” + $body = str_replace($matches, "\n", $body); + //Revert removal of carriage return + return str_replace("\n", "\r\n", $body); + } + return $body; + } } diff --git a/Modules/EmployeeTalk/classes/Service/VEvent.php b/Modules/EmployeeTalk/classes/Service/VEvent.php index 3e1d373a815f..ff6c62fb5d08 100644 --- a/Modules/EmployeeTalk/classes/Service/VEvent.php +++ b/Modules/EmployeeTalk/classes/Service/VEvent.php @@ -101,13 +101,19 @@ public function __construct( private function getStartAndEnd(): string { + // creating DateTimes from Unix timestamps automatically sets the initial timezone to UTC + $start = new \DateTimeImmutable('@' . $this->startTime); + $start = $start->setTimezone(new \DateTimeZone('Europe/Paris')); + $end = new \DateTimeImmutable('@' . $this->endTime); + $end = $end->setTimezone(new \DateTimeZone('Europe/Paris')); + if ($this->allDay) { - return 'DTSTART;TZID=Europe/Paris;VALUE=DATE:' . date("Ymd", $this->startTime) . "\r\n" . - 'DTEND;TZID=Europe/Paris;VALUE=DATE:' . date("Ymd", $this->endTime) . "\r\n" . + return 'DTSTART;TZID=Europe/Paris;VALUE=DATE:' . $start->format('Ymd') . "\r\n" . + 'DTEND;TZID=Europe/Paris;VALUE=DATE:' . $end->format('Ymd') . "\r\n" . "X-MICROSOFT-CDO-ALLDAYEVENT: TRUE\r\n"; } else { - return 'DTSTART;TZID=Europe/Paris:' . date("Ymd\THis", $this->startTime) . "\r\n" . - 'DTEND;TZID=Europe/Paris:' . date("Ymd\THis", $this->endTime) . "\r\n"; + return 'DTSTART;TZID=Europe/Paris:' . $start->format('Ymd\THis') . "\r\n" . + 'DTEND;TZID=Europe/Paris:' . $end->format('Ymd\THis') . "\r\n"; } } diff --git a/Modules/EmployeeTalk/test/ilModulesEmployeeTalkVEventTest.php b/Modules/EmployeeTalk/test/ilModulesEmployeeTalkVEventTest.php index eef299c1311d..63c3dd809e9d 100644 --- a/Modules/EmployeeTalk/test/ilModulesEmployeeTalkVEventTest.php +++ b/Modules/EmployeeTalk/test/ilModulesEmployeeTalkVEventTest.php @@ -1,7 +1,5 @@ getIndividualDeadlines(); - foreach ($idl as $user_id => $v) { - if (!isset($mem[$user_id])) { - if (ilObjUser::_exists($user_id)) { - $name = ilObjUser::_lookupName($user_id); - $mem[$user_id] = - array( - "name" => $name["lastname"] . ", " . $name["firstname"], - "login" => $name["login"], - "usr_id" => $user_id, - "lastname" => $name["lastname"], - "firstname" => $name["firstname"] - ); + if (!$this->ass_type->usesTeams()) { + foreach ($idl as $user_id => $v) { + if (!isset($mem[$user_id])) { + if (ilObjUser::_exists((int) $user_id)) { + $name = ilObjUser::_lookupName($user_id); + $mem[$user_id] = + array( + "name" => $name["lastname"] . ", " . $name["firstname"], + "login" => $name["login"], + "usr_id" => $user_id, + "lastname" => $name["lastname"], + "firstname" => $name["firstname"] + ); + } } } } diff --git a/Modules/Exercise/Assignment/class.ilExAssignmentMemberStatus.php b/Modules/Exercise/Assignment/class.ilExAssignmentMemberStatus.php index b07ac2a993a0..1fd02f44e846 100644 --- a/Modules/Exercise/Assignment/class.ilExAssignmentMemberStatus.php +++ b/Modules/Exercise/Assignment/class.ilExAssignmentMemberStatus.php @@ -220,7 +220,7 @@ protected function getFields(): array { return array( "notice" => array("text", $this->getNotice()) - ,"returned" => array("integer", $this->getReturned()) + ,"returned" => array("integer", (int) $this->getReturned()) ,"solved" => array("integer", $this->getSolved()) ,"status_time" => array("timestamp", $this->getStatusTime()) ,"sent" => array("integer", $this->getSent()) diff --git a/Modules/Exercise/Assignment/class.ilExAssignmentPeerReviewTableGUI.php b/Modules/Exercise/Assignment/class.ilExAssignmentPeerReviewTableGUI.php index 2017b580d65f..3839e27e4e77 100644 --- a/Modules/Exercise/Assignment/class.ilExAssignmentPeerReviewTableGUI.php +++ b/Modules/Exercise/Assignment/class.ilExAssignmentPeerReviewTableGUI.php @@ -43,7 +43,7 @@ public function __construct( $this->ass = $a_ass; $this->user_id = $a_user_id; $this->peer_data = $a_peer_data; - + $this->setId("exc_peer_rv_fb"); parent::__construct($a_parent_obj, $a_parent_cmd); $this->setLimit(9999); diff --git a/Modules/Exercise/PeerReview/Criteria/class.ilExcCriteriaGUI.php b/Modules/Exercise/PeerReview/Criteria/class.ilExcCriteriaGUI.php index 9c3a8b3d3b61..62532eed74a7 100644 --- a/Modules/Exercise/PeerReview/Criteria/class.ilExcCriteriaGUI.php +++ b/Modules/Exercise/PeerReview/Criteria/class.ilExcCriteriaGUI.php @@ -238,7 +238,7 @@ protected function importForm( $lng = $this->lng; $is_edit = (bool) $a_crit_obj->getId(); - + $ilCtrl->setParameter($this, "type", $this->request->getCriteriaType()); $form = $this->initForm($a_crit_obj); if ($form->checkInput()) { $a_crit_obj->setTitle($form->getInput("title")); diff --git a/Modules/Exercise/Submission/class.ilExSubmission.php b/Modules/Exercise/Submission/class.ilExSubmission.php index 425b355f6650..4269460e2da9 100644 --- a/Modules/Exercise/Submission/class.ilExSubmission.php +++ b/Modules/Exercise/Submission/class.ilExSubmission.php @@ -1022,6 +1022,7 @@ public static function downloadAllAssignmentFiles( global $DIC; $lng = $DIC->language(); + $log = ilLoggerFactory::getLogger("exc"); $storage = new ilFSStorageExercise($a_ass->getExerciseId(), $a_ass->getId()); $storage->create(); @@ -1101,8 +1102,11 @@ public static function downloadAllAssignmentFiles( $targetdir = $team_dir . $targetdir; } } + + $log->debug("Creation target directory: " . $targetdir); ilFileUtils::makeDir($targetdir); + $log->debug("Scanning source directory: " . $sourcedir); $sourcefiles = scandir($sourcedir); $duplicates = array(); foreach ($sourcefiles as $sourcefile) { @@ -1150,6 +1154,8 @@ public static function downloadAllAssignmentFiles( $targetfile = $targetdir . DIRECTORY_SEPARATOR . $targetfile; $sourcefile = $sourcedir . DIRECTORY_SEPARATOR . $sourcefile; + $log->debug("Copying: " . $sourcefile . " -> " . $targetfile); + if (!copy($sourcefile, $targetfile)) { throw new ilExerciseException("Could not copy " . basename($sourcefile) . " to '" . $targetfile . "'."); } else { @@ -1159,6 +1165,9 @@ public static function downloadAllAssignmentFiles( // blogs and portfolios are stored as zip and have to be unzipped if ($ass_type == ilExAssignment::TYPE_PORTFOLIO || $ass_type == ilExAssignment::TYPE_BLOG) { + $log->debug("Unzipping: " . $targetfile); + $log->debug("Current directory is: " . getcwd()); + ilFileUtils::unzip($targetfile); unlink($targetfile); } diff --git a/Modules/Exercise/Submission/class.ilExerciseSubmissionTableGUI.php b/Modules/Exercise/Submission/class.ilExerciseSubmissionTableGUI.php index c1bf963ee37d..45271ca7a76b 100644 --- a/Modules/Exercise/Submission/class.ilExerciseSubmissionTableGUI.php +++ b/Modules/Exercise/Submission/class.ilExerciseSubmissionTableGUI.php @@ -305,7 +305,7 @@ protected function parseRow( // do not grade or mark if no team yet if (!$has_no_team_yet) { // status - $this->tpl->setVariable("SEL_" . strtoupper($a_row["status"]), ' selected="selected" '); + $this->tpl->setVariable("SEL_" . strtoupper($a_row["status"] ?? ""), ' selected="selected" '); $this->tpl->setVariable("TXT_NOTGRADED", $this->lng->txt("exc_notgraded")); $this->tpl->setVariable("TXT_PASSED", $this->lng->txt("exc_passed")); $this->tpl->setVariable("TXT_FAILED", $this->lng->txt("exc_failed")); diff --git a/Modules/Exercise/classes/BackgroundTasks/class.ilExDownloadSubmissionsZipInteraction.php b/Modules/Exercise/classes/BackgroundTasks/class.ilExDownloadSubmissionsZipInteraction.php index 6a481c9fa25c..d77079d25238 100644 --- a/Modules/Exercise/classes/BackgroundTasks/class.ilExDownloadSubmissionsZipInteraction.php +++ b/Modules/Exercise/classes/BackgroundTasks/class.ilExDownloadSubmissionsZipInteraction.php @@ -99,9 +99,8 @@ public function interaction( $this->logger->debug("Delete dir: " . dirname($path)); $filesystem->deleteDir(dirname($path)); } - $out = new StringValue(); - $out->setValue($input); - return $out; + + return $download_name; } $this->logger->info("Delivering File."); @@ -121,8 +120,6 @@ public function interaction( $this->logger->debug("As: " . $zip_name); ilFileDelivery::deliverFileAttached($download_name->getValue(), $zip_name); - $out = new StringValue(); - $out->setValue($input); - return $out; + return $download_name; } } diff --git a/Modules/Exercise/classes/class.ilExerciseManagementGUI.php b/Modules/Exercise/classes/class.ilExerciseManagementGUI.php index ef15d6ca49c7..7937184e72ef 100644 --- a/Modules/Exercise/classes/class.ilExerciseManagementGUI.php +++ b/Modules/Exercise/classes/class.ilExerciseManagementGUI.php @@ -143,6 +143,7 @@ public function __construct(InternalService $service, ilExAssignment $a_ass = nu $this->ctrl->saveParameter($this, array("vw", "member_id")); $this->http = $DIC->http(); + $this->ctrl->saveParameter($this, array("part_id")); } /** @@ -2146,9 +2147,11 @@ public function openSubmissionViewObject(bool $print_version = false): void // e.g. ///ilExercise/3/exc_367/subm_1//20210628175716_368 $zip_original_full_path = $this->getSubmissionZipFilePath($submission, $print_version); + $this->log->debug("zip original full path: " . $zip_original_full_path); // e.g. ilExercise/3/exc_367/subm_1//20210628175716_368 $zip_internal_path = $this->getWebFilePathFromExternalFilePath($zip_original_full_path); + $this->log->debug("zip internal path: " . $zip_internal_path); $arr = explode("_", basename($zip_original_full_path)); $obj_date = $arr[0]; @@ -2174,6 +2177,7 @@ public function openSubmissionViewObject(bool $print_version = false): void $obj_dir . DIRECTORY_SEPARATOR . "index.html"; + $this->log->debug("index html file: " . $index_html_file); ilWACSignedPath::signFolderOfStartFile($index_html_file); @@ -2184,10 +2188,12 @@ public function openSubmissionViewObject(bool $print_version = false): void $error_msg = ""; if ($zip_original_full_path) { $file_copied = $this->copyFileToWebDir($zip_internal_path); + $this->log->debug("file copied: " . $file_copied); if ($file_copied) { ilFileUtils::unzip($file_copied, true); $web_filesystem->delete($zip_internal_path); + $this->log->debug("deleting: " . $zip_internal_path); $submission_repository = $this->service->repo()->submission(); $submission_repository->updateWebDirAccessTime($this->assignment->getId(), $member_id); @@ -2245,11 +2251,13 @@ protected function copyFileToWebDir( $zip_file = basename($internal_file_path); if ($data_filesystem->has($internal_file_path)) { + $this->log->debug("internal file path: " . $internal_file_path); if (!$web_filesystem->hasDir($internal_dirs)) { $web_filesystem->createDir($internal_dirs); } if (!$web_filesystem->has($internal_file_path)) { + $this->log->debug("writing: " . $internal_file_path); $stream = $data_filesystem->readStream($internal_file_path); $web_filesystem->writeStream($internal_file_path, $stream); diff --git a/Modules/File/classes/Setup/class.ilFileObjectToStorageMigration.php b/Modules/File/classes/Setup/class.ilFileObjectToStorageMigration.php index da6de0ee9ee8..4a23d187d1ad 100644 --- a/Modules/File/classes/Setup/class.ilFileObjectToStorageMigration.php +++ b/Modules/File/classes/Setup/class.ilFileObjectToStorageMigration.php @@ -71,6 +71,9 @@ public function prepare(Environment $environment): void $irss_helper ); + global $DIC; + $DIC['ilDB'] = $irss_helper->getDatabase(); // needed inside some method calls to ILIAS + $storage_configuration = new LocalConfig($irss_helper->getClientDataDir()); $f = new FlySystemFilesystemFactory(); diff --git a/Modules/File/classes/Setup/class.ilFileObjectToStorageMigrationRunner.php b/Modules/File/classes/Setup/class.ilFileObjectToStorageMigrationRunner.php index a5d3a954520a..c54d38f5faf3 100644 --- a/Modules/File/classes/Setup/class.ilFileObjectToStorageMigrationRunner.php +++ b/Modules/File/classes/Setup/class.ilFileObjectToStorageMigrationRunner.php @@ -37,7 +37,7 @@ class ilFileObjectToStorageMigrationRunner { - protected string $movement_implementation; + protected string $movement_implementation = ''; protected ConsumerFactory $consumer_factory; protected Manager $storage_manager; diff --git a/Modules/Folder/classes/class.ilObjFolderGUI.php b/Modules/Folder/classes/class.ilObjFolderGUI.php index de2534a6e83b..6adbee8f143b 100755 --- a/Modules/Folder/classes/class.ilObjFolderGUI.php +++ b/Modules/Folder/classes/class.ilObjFolderGUI.php @@ -161,7 +161,7 @@ public function executeCommand(): void case "ilobjectcontentstylesettingsgui": $this->checkPermission("write"); $this->setTitleAndDescription(); - //$this->showContainerPageTabs(); + $this->showContainerPageTabs(); $settings_gui = $this->content_style_gui ->objectSettingsGUIForRefId( null, @@ -600,4 +600,10 @@ public function setSubTabs(string $a_tab): void $ilTabs->activateSubTab($a_tab); $ilTabs->activateTab("settings"); } + + public function deleteObject(bool $error = false): void + { + $this->tabs_gui->activateTab('view_content'); + parent::deleteObject($error); + } } diff --git a/Modules/Forum/classes/CoPage/class.ilForumPageCommandForwarder.php b/Modules/Forum/classes/CoPage/class.ilForumPageCommandForwarder.php index 3ad5764ce92d..a7bcce4ac3c4 100644 --- a/Modules/Forum/classes/CoPage/class.ilForumPageCommandForwarder.php +++ b/Modules/Forum/classes/CoPage/class.ilForumPageCommandForwarder.php @@ -37,6 +37,7 @@ class ilForumPageCommandForwarder implements ilForumObjectConstants * presentation mode for embedded presentation, e.g. in a kiosk mode */ public const PRESENTATION_MODE_EMBEDDED_PRESENTATION = 'PRESENTATION_MODE_EMBEDDED_PRESENTATION'; + public const DEFAULT_LANGUAGE = '-'; protected string $presentationMode = self::PRESENTATION_MODE_EDITING; protected ilCtrlInterface $ctrl; @@ -195,14 +196,11 @@ public function forward(string $ctrlLink = ''): string switch ($this->presentationMode) { case self::PRESENTATION_MODE_EDITING: - $pageObjectGui = $this->buildEditingPageObjectGUI(''); + $pageObjectGui = $this->buildEditingPageObjectGUI(self::DEFAULT_LANGUAGE); return (string) $this->ctrl->forwardCommand($pageObjectGui); case self::PRESENTATION_MODE_PRESENTATION: - $ot = ilObjectTranslation::getInstance($this->parentObject->getId()); - $language = $ot->getEffectiveContentLang($this->actor->getCurrentLanguage(), $this->parentObject->getType()); - - $pageObjectGUI = $this->buildPresentationPageObjectGUI($language); + $pageObjectGUI = $this->buildPresentationPageObjectGUI(self::DEFAULT_LANGUAGE); if (is_string($ctrlLink) && $ctrlLink !== '') { $pageObjectGUI->setFileDownloadLink($ctrlLink . '&cmd=' . self::UI_CMD_COPAGE_DOWNLOAD_FILE); @@ -213,10 +211,7 @@ public function forward(string $ctrlLink = ''): string return $this->ctrl->getHTML($pageObjectGUI); case self::PRESENTATION_MODE_EMBEDDED_PRESENTATION: - $ot = ilObjectTranslation::getInstance($this->parentObject->getId()); - $language = $ot->getEffectiveContentLang($this->actor->getCurrentLanguage(), $this->parentObject->getType()); - - $pageObjectGUI = $this->buildEmbeddedPresentationPageObjectGUI($language); + $pageObjectGUI = $this->buildEmbeddedPresentationPageObjectGUI(self::DEFAULT_LANGUAGE); if (is_string($ctrlLink) && $ctrlLink !== '') { $pageObjectGUI->setFileDownloadLink($ctrlLink . '&cmd=' . self::UI_CMD_COPAGE_DOWNLOAD_FILE); diff --git a/Modules/Forum/classes/Notification/class.ilForumNotification.php b/Modules/Forum/classes/Notification/class.ilForumNotification.php index c5f0f356ecae..6cee36c1790e 100644 --- a/Modules/Forum/classes/Notification/class.ilForumNotification.php +++ b/Modules/Forum/classes/Notification/class.ilForumNotification.php @@ -1,7 +1,5 @@ - * @version $Id:$ - * @ingroup ModulesForum - */ +declare(strict_types=1); + class ilForumNotification { /** @var array[]> */ @@ -408,6 +402,7 @@ public function read(): array $result[(int) $row['user_id']]['admin_force_noti'] = (int) $row['admin_force_noti']; $result[(int) $row['user_id']]['user_toggle_noti'] = (int) $row['user_toggle_noti']; $result[(int) $row['user_id']]['interested_events'] = (int) $row['interested_events']; + $result[(int) $row['user_id']]['user_id_noti'] = (int) $row['user_id_noti']; } return $result; diff --git a/Modules/Forum/classes/class.ilForum.php b/Modules/Forum/classes/class.ilForum.php index 71031a19c318..674803d64fde 100755 --- a/Modules/Forum/classes/class.ilForum.php +++ b/Modules/Forum/classes/class.ilForum.php @@ -411,8 +411,11 @@ public function moveThreads(array $thread_ids, ilObjForum $src_forum, int $targe [$oldFrmData->getTopPk()] ); + $last_post_src = ''; $row = $this->db->fetchObject($res); - $last_post_src = $oldFrmData->getTopPk() . '#' . $row->pos_thr_fk . '#' . $row->pos_pk; + if ($row !== null) { + $last_post_src = $oldFrmData->getTopPk() . '#' . $row->pos_thr_fk . '#' . $row->pos_pk; + } $this->db->manipulateF( 'UPDATE frm_data ' . @@ -435,8 +438,11 @@ public function moveThreads(array $thread_ids, ilObjForum $src_forum, int $targe [$newFrmData->getTopPk()] ); + $last_post_dest = ''; $row = $this->db->fetchObject($res); - $last_post_dest = $newFrmData->getTopPk() . '#' . $row->pos_thr_fk . '#' . $row->pos_pk; + if ($row !== null) { + $last_post_dest = $newFrmData->getTopPk() . '#' . $row->pos_thr_fk . '#' . $row->pos_pk; + } $this->db->manipulateF( 'UPDATE frm_data SET top_num_posts = top_num_posts + %s, top_num_threads = top_num_threads + %s, ' . diff --git a/Modules/Forum/classes/class.ilObjForumGUI.php b/Modules/Forum/classes/class.ilObjForumGUI.php index 9f517c6e195f..27ac8de66b62 100755 --- a/Modules/Forum/classes/class.ilObjForumGUI.php +++ b/Modules/Forum/classes/class.ilObjForumGUI.php @@ -1724,10 +1724,12 @@ public function prepareThreadScreen(ilObjForum $a_forum_obj): void $this->tpl->setTitleIcon(ilObject::_getIcon(0, "big", "frm")); $ref_id = $this->retrieveRefId(); - $this->tabs_gui->setBackTarget( $this->lng->txt('frm_all_threads'), - 'ilias.php?baseClass=ilRepositoryGUI&ref_id=' . $ref_id + $this->ctrl->getLinkTarget( + $this, + 'showThreads' + ) ); /** @var ilForum $frm */ @@ -3139,12 +3141,14 @@ public function viewThreadObject(): void } // no posts - if ($firstNodeInThread->getId() === 0 && !$numberOfPostings = count($subtree_nodes)) { + $numberOfPostings = count($subtree_nodes); + if ($numberOfPostings === 0 && $firstNodeInThread->getId() === 0) { $this->tpl->setOnScreenMessage('info', $this->lng->txt('forums_no_posts_available')); } $pageSize = $frm->getPageHits(); $postIndex = 0; + if ($numberOfPostings > $pageSize) { $this->ctrl->setParameter($this, 'ref_id', $this->object->getRefId()); $this->ctrl->setParameter($this, 'thr_pk', $this->objCurrentTopic->getId()); diff --git a/Modules/Forum/test/ilForumNotificationTest.php b/Modules/Forum/test/ilForumNotificationTest.php index 0bc6a07448f4..812d574373ad 100644 --- a/Modules/Forum/test/ilForumNotificationTest.php +++ b/Modules/Forum/test/ilForumNotificationTest.php @@ -1,7 +1,5 @@ 20, 'user_toggle_noti' => 90, 'interested_events' => 8, + 'user_id_noti' => 6, ]; $mockStatement = $this->getMockBuilder(ilDBStatement::class)->disableOriginalConstructor()->getMock(); $this->database->expects(self::exactly(2))->method('fetchAssoc')->willReturn( diff --git a/Modules/Glossary/LuceneObjectDefinition.xml b/Modules/Glossary/LuceneObjectDefinition.xml index f4ef27f31032..6226817fea9e 100644 --- a/Modules/Glossary/LuceneObjectDefinition.xml +++ b/Modules/Glossary/LuceneObjectDefinition.xml @@ -17,7 +17,7 @@ - SELECT 'glo' type,term,glo_id,id,'term' metaSubType,id metaSubId + SELECT 'glo' objType,term,glo_id,id,'term' metaType,id metaObjId,glo_id metaRbacId FROM glossary_term gt WHERE glo_id IN (?) @@ -25,7 +25,7 @@ - + diff --git a/Modules/Glossary/Presentation/class.ilGlossaryPresentationGUI.php b/Modules/Glossary/Presentation/class.ilGlossaryPresentationGUI.php index 30bfdf982a39..919635d116bd 100755 --- a/Modules/Glossary/Presentation/class.ilGlossaryPresentationGUI.php +++ b/Modules/Glossary/Presentation/class.ilGlossaryPresentationGUI.php @@ -604,11 +604,17 @@ public function showDefinitionTabs(string $a_act): void $ilCtrl->setParameterByClass("ilglossarytermgui", "term_id", $this->term_id); if (ilGlossaryTerm::_lookGlossaryID($this->term_id) == $this->glossary->getId()) { - $ilTabs->addNonTabbedLink( - "editing_view", - $lng->txt("glo_editing_view"), - $ilCtrl->getLinkTargetByClass(array("ilglossaryeditorgui", "ilobjglossarygui", "ilglossarytermgui"), "listDefinitions") - ); + if ($this->access->checkAccess("write", "", (int) $this->requested_ref_id) || + $this->access->checkAccess("edit_content", "", (int) $this->requested_ref_id)) { + $ilTabs->addNonTabbedLink( + "editing_view", + $lng->txt("glo_editing_view"), + $ilCtrl->getLinkTargetByClass(array("ilglossaryeditorgui", + "ilobjglossarygui", + "ilglossarytermgui" + ), "listDefinitions") + ); + } //"ilias.php?baseClass=ilGlossaryEditorGUI&ref_id=".$this->requested_ref_id."&edit_term=".$this->term_id); } $ilTabs->activateTab($a_act); diff --git a/Modules/Group/UserActions/classes/class.ilGroupAddToGroupActionGUI.php b/Modules/Group/UserActions/classes/class.ilGroupAddToGroupActionGUI.php index f1fb21e809c6..c8ea198e0ac6 100644 --- a/Modules/Group/UserActions/classes/class.ilGroupAddToGroupActionGUI.php +++ b/Modules/Group/UserActions/classes/class.ilGroupAddToGroupActionGUI.php @@ -48,6 +48,18 @@ public function __construct() } protected function initGroupRefIdFromQuery(): int + { + $ref_id = 0; + if ($this->http->wrapper()->query()->has('grp_act_ref_id')) { + $ref_id = $this->http->wrapper()->query()->retrieve( + 'grp_act_ref_id', + $this->refinery->kindlyTo()->int() + ); + } + return $ref_id; + } + + protected function initGroupParentRefIdFromQuery(): int { $ref_id = 0; if ($this->http->wrapper()->query()->has('grp_act_par_ref_id')) { @@ -190,7 +202,6 @@ public function confirmAddUser(): void $ref_id = $this->initGroupRefIdFromQuery(); $user_id = $this->initUserIdFromQuery(); - $participants = ilParticipants::getInstanceByObjId(ilObject::_lookupObjId($ref_id)); if ($participants->isMember($user_id)) { $url = $ctrl->getLinkTarget($this, "selectGroup", "", true); @@ -270,7 +281,7 @@ public function createGroup($form = null): void { $lng = $this->lng; - $ref_id = $this->initGroupRefIdFromQuery(); + $ref_id = $this->initGroupParentRefIdFromQuery(); if ($form == null) { $form = $this->getGroupCreationForm(); @@ -344,7 +355,7 @@ public function createGroupAndAddUser(): void $lng = $this->lng; $user_id = $this->initUserIdFromQuery(); - $ref_id = $this->initGroupRefIdFromQuery(); + $ref_id = $this->initGroupParentRefIdFromQuery(); $form = $this->getGroupCreationForm(); $form->checkInput(); diff --git a/Modules/Group/classes/class.ilGroupXMLParser.php b/Modules/Group/classes/class.ilGroupXMLParser.php index 977eb76012a5..2be96302aa83 100644 --- a/Modules/Group/classes/class.ilGroupXMLParser.php +++ b/Modules/Group/classes/class.ilGroupXMLParser.php @@ -441,7 +441,7 @@ public function save(): bool $owner = ilUtil::__extractId($owner, (int) IL_INST_ID); } if (is_numeric($owner) && $owner > 0) { - $this->group_obj->setOwner($owner); + $this->group_obj->setOwner((int) $owner); $ownerChanged = true; } } diff --git a/Modules/Group/classes/class.ilObjGroupGUI.php b/Modules/Group/classes/class.ilObjGroupGUI.php index 607418af7ddf..51a5a9f08016 100755 --- a/Modules/Group/classes/class.ilObjGroupGUI.php +++ b/Modules/Group/classes/class.ilObjGroupGUI.php @@ -248,6 +248,7 @@ public function executeCommand(): void $this->checkPermission("write"); $this->setTitleAndDescription(); + $this->showContainerPageTabs(); $settings_gui = $DIC->contentStyle()->gui() ->objectSettingsGUIForRefId( null, diff --git a/Modules/HTMLLearningModule/classes/class.ilObjFileBasedLMGUI.php b/Modules/HTMLLearningModule/classes/class.ilObjFileBasedLMGUI.php index de1958c50283..c1479e1f5d35 100755 --- a/Modules/HTMLLearningModule/classes/class.ilObjFileBasedLMGUI.php +++ b/Modules/HTMLLearningModule/classes/class.ilObjFileBasedLMGUI.php @@ -518,9 +518,8 @@ public function showInfoScreen(): void protected function setTabs(): void { - $this->tpl->setTitleIcon(ilUtil::getImagePath("icon_lm.svg")); $this->getTabs(); - $this->tpl->setTitle($this->object->getTitle()); + $this->setTitleAndDescription(); } protected function getTabs(): void diff --git a/Modules/ItemGroup/classes/class.ilItemGroupItems.php b/Modules/ItemGroup/classes/class.ilItemGroupItems.php index 9f6b4706bf2f..7a671d048671 100644 --- a/Modules/ItemGroup/classes/class.ilItemGroupItems.php +++ b/Modules/ItemGroup/classes/class.ilItemGroupItems.php @@ -155,11 +155,11 @@ public function getAssignableItems(): array // filter hidden files // see http://www.ilias.de/mantis/view.php?id=10269 if ($node['type'] == "file" && - ilObjFileAccess::_isFileHidden($node['title'])) { + ilObjFileAccess::_isFileHidden((string) $node['title'])) { continue; } - if ($objDefinition->isInactivePlugin($node['type'])) { + if ($objDefinition->isInactivePlugin((string) $node['type'])) { continue; } diff --git a/Modules/LTIConsumer/LuceneObjectDefinition.xml b/Modules/LTIConsumer/LuceneObjectDefinition.xml index c8a1350f3e10..4ad494282d0a 100644 --- a/Modules/LTIConsumer/LuceneObjectDefinition.xml +++ b/Modules/LTIConsumer/LuceneObjectDefinition.xml @@ -1,24 +1,7 @@ - - - - SELECT title FROM object_data - - - - - - - SELECT description FROM object_data - - - - diff --git a/Modules/LearningModule/Presentation/class.ilLMNavigationRendererGUI.php b/Modules/LearningModule/Presentation/class.ilLMNavigationRendererGUI.php index 653ac6fc843a..b1af44f4f176 100644 --- a/Modules/LearningModule/Presentation/class.ilLMNavigationRendererGUI.php +++ b/Modules/LearningModule/Presentation/class.ilLMNavigationRendererGUI.php @@ -121,7 +121,7 @@ protected function render(bool $top = true): string $pre_id, $this->lm->getPageHeader(), $this->lm->isActiveNumbering(), - $this->lm_set->get("time_scheduled_page_activation"), + (bool) $this->lm_set->get("time_scheduled_page_activation"), false, 0, $this->lang, @@ -169,7 +169,7 @@ protected function render(bool $top = true): string $succ_id, $this->lm->getPageHeader(), $this->lm->isActiveNumbering(), - $this->lm_set->get("time_scheduled_page_activation"), + (bool) $this->lm_set->get("time_scheduled_page_activation"), false, 0, $this->lang, diff --git a/Modules/LearningModule/Presentation/class.ilLMNavigationStatus.php b/Modules/LearningModule/Presentation/class.ilLMNavigationStatus.php index e8f41f3356ef..a46819d9f0e3 100644 --- a/Modules/LearningModule/Presentation/class.ilLMNavigationStatus.php +++ b/Modules/LearningModule/Presentation/class.ilLMNavigationStatus.php @@ -279,7 +279,7 @@ public function getPredecessorPageId(): int $active = ilLMPage::_lookupActive( $c_id, $this->lm->getType(), - $this->lm_set->get("time_scheduled_page_activation") + (bool) $this->lm_set->get("time_scheduled_page_activation") ); } if (is_array($pre_node) && $pre_node["obj_id"] > 0 && diff --git a/Modules/LearningModule/Presentation/class.ilLMPresentationLinker.php b/Modules/LearningModule/Presentation/class.ilLMPresentationLinker.php index 13fbf20a3bdc..ccbbb9653fe3 100644 --- a/Modules/LearningModule/Presentation/class.ilLMPresentationLinker.php +++ b/Modules/LearningModule/Presentation/class.ilLMPresentationLinker.php @@ -201,6 +201,9 @@ public function getLink( if ($a_type != "") { $this->ctrl->setParameterByClass(self::TARGET_GUI, "obj_type", $a_type); } + if ($a_anchor !== "") { + $a_anchor = "copganc_" . $a_anchor; + } $link = $this->ctrl->getLinkTargetByClass( self::TARGET_GUI, $a_cmd, @@ -409,8 +412,11 @@ public function getLinkXML( $nframe = ($ltarget == "") ? $this->frame : $ltarget; - $href = - $this->getLink($a_cmd = "glossary", (int) $target_id, $nframe, $type); + $href = ""; + if (ilGlossaryTerm::_exists((int) $target_id)) { + $href = + $this->getLink($a_cmd = "glossary", (int) $target_id, $nframe, $type); + } break; case "MediaObject": @@ -455,7 +461,7 @@ public function getLinkXML( case "WikiPage": $wiki_anc = ""; if ($int_link["Anchor"] != "") { - $wiki_anc = "#".rawurlencode($int_link["Anchor"]); + $wiki_anc = "#" . rawurlencode($int_link["Anchor"]); } $href = ilWikiPage::getGotoForWikiPageTarget($target_id) . $wiki_anc; if ($this->embed_mode) { diff --git a/Modules/LearningModule/classes/class.ilChapterHierarchyFormGUI.php b/Modules/LearningModule/classes/class.ilChapterHierarchyFormGUI.php index cdb6808f909c..bb334564def9 100644 --- a/Modules/LearningModule/classes/class.ilChapterHierarchyFormGUI.php +++ b/Modules/LearningModule/classes/class.ilChapterHierarchyFormGUI.php @@ -309,8 +309,9 @@ public function getChildIconAlt(array $a_item): string return $lng->txt("cont_page_deactivated_elements"); } } + return $lng->txt("pg"); } - return ilUtil::getImagePath("icon_" . $a_item["type"] . ".svg"); + return $lng->txt("st");; } /** diff --git a/Modules/LearningModule/classes/class.ilLMObject.php b/Modules/LearningModule/classes/class.ilLMObject.php index 25839a2b48eb..7e4cf53b24a9 100755 --- a/Modules/LearningModule/classes/class.ilLMObject.php +++ b/Modules/LearningModule/classes/class.ilLMObject.php @@ -702,7 +702,7 @@ public static function _lookupContObjID(int $a_id): int $obj_set = $ilDB->query($query); $obj_rec = $ilDB->fetchAssoc($obj_set); - return (int) $obj_rec["lm_id"]; + return (int) ($obj_rec["lm_id"] ?? 0); } /** diff --git a/Modules/LearningModule/classes/class.ilLearningModuleNotification.php b/Modules/LearningModule/classes/class.ilLearningModuleNotification.php index 0d1e8a9f43fb..ee135bcbeebd 100644 --- a/Modules/LearningModule/classes/class.ilLearningModuleNotification.php +++ b/Modules/LearningModule/classes/class.ilLearningModuleNotification.php @@ -132,7 +132,7 @@ protected function getPageTitle(): string $this->page_id, $this->learning_module->getPageHeader(), $this->learning_module->isActiveNumbering(), - $this->lm_set->get("time_scheduled_page_activation"), + (bool) $this->lm_set->get("time_scheduled_page_activation"), false, 0, $this->lng->getLangKey() diff --git a/Modules/LearningModule/classes/class.ilObjContentObject.php b/Modules/LearningModule/classes/class.ilObjContentObject.php index e83eb992d5fe..5f8d86f70735 100755 --- a/Modules/LearningModule/classes/class.ilObjContentObject.php +++ b/Modules/LearningModule/classes/class.ilObjContentObject.php @@ -972,8 +972,10 @@ public function createProperties(): void { $ilDB = $this->db; - $q = "INSERT INTO content_object (id) VALUES (" . $ilDB->quote($this->getId(), "integer") . ")"; - $ilDB->manipulate($q); + $this->db->insert("content_object", [ + "id" => ["integer", $this->getId()], + "page_header" => ["text", ilLMObject::PAGE_TITLE] + ]); // #14661 $this->notes->domain()->activateComments($this->getId()); diff --git a/Modules/LearningModule/classes/class.ilObjContentObjectGUI.php b/Modules/LearningModule/classes/class.ilObjContentObjectGUI.php index f65f68797da9..24f81b65197d 100755 --- a/Modules/LearningModule/classes/class.ilObjContentObjectGUI.php +++ b/Modules/LearningModule/classes/class.ilObjContentObjectGUI.php @@ -2918,6 +2918,7 @@ public function showLMGlossarySelector(): void $exp = new ilSearchRootSelector($ilCtrl->getLinkTarget($this, 'showLMGlossarySelector')); $exp->setExpand($this->requested_search_root_expand ?: $tree->readRootId()); + $exp->setPathOpen($this->object->getRefId()); $exp->setExpandTarget($ilCtrl->getLinkTarget($this, 'showLMGlossarySelector')); $exp->setTargetClass(get_class($this)); $exp->setCmd('confirmGlossarySelection'); diff --git a/Modules/LearningModule/classes/class.ilStructureObjectGUI.php b/Modules/LearningModule/classes/class.ilStructureObjectGUI.php index 55b61d308cba..0fb4ab8f823a 100755 --- a/Modules/LearningModule/classes/class.ilStructureObjectGUI.php +++ b/Modules/LearningModule/classes/class.ilStructureObjectGUI.php @@ -44,7 +44,7 @@ public function __construct( $this->lng = $DIC->language(); $this->tabs = $DIC->tabs(); $this->log = $DIC["ilLog"]; - $this->tpl = $DIC["tpl"]; + $this->tpl = $DIC->ui()->mainTemplate(); parent::__construct($a_content_obj); $this->tree = $a_tree; } @@ -750,6 +750,12 @@ public function insertPage(): void ilLMObject::putInTree($page, $parent_id, $target); } + if ($num == 1) { + $this->tpl->setOnScreenMessage("success", $this->lng->txt("lm_page_added"), true); + } else { + $this->tpl->setOnScreenMessage("success", $this->lng->txt("lm_pages_added"), true); + } + $ilCtrl->redirect($this, "showHierarchy"); } @@ -966,6 +972,12 @@ public function insertPageFromTemplate(): void $data["sec"]["title"] ); + if ($this->request->getMulti() <= 1) { + $this->tpl->setOnScreenMessage("success", $this->lng->txt("lm_page_added"), true); + } else { + $this->tpl->setOnScreenMessage("success", $this->lng->txt("lm_pages_added"), true); + } + //$ilCtrl->setParameter($this, "highlight", $page_ids); $ilCtrl->redirect($this, "showHierarchy", "node_" . $node_id); } diff --git a/Modules/LearningSequence/classes/LearnerProgress/class.ilLSLP.php b/Modules/LearningSequence/classes/LearnerProgress/class.ilLSLP.php index 0e99b8c9547b..4cfb9ade49fc 100644 --- a/Modules/LearningSequence/classes/LearnerProgress/class.ilLSLP.php +++ b/Modules/LearningSequence/classes/LearnerProgress/class.ilLSLP.php @@ -1,7 +1,5 @@ getExtroImage(); $settings = $settings->withExtroImage(); } - if (!empty($delete)) { + if ($delete !== '' && str_starts_with($delete, $this->getStoragePath())) { $this->deleteFile($delete); } return $settings; diff --git a/Modules/LearningSequence/classes/class.ilObjLearningSequenceGUI.php b/Modules/LearningSequence/classes/class.ilObjLearningSequenceGUI.php index 7e4c7874dd6a..739553734c9a 100644 --- a/Modules/LearningSequence/classes/class.ilObjLearningSequenceGUI.php +++ b/Modules/LearningSequence/classes/class.ilObjLearningSequenceGUI.php @@ -106,6 +106,7 @@ class ilObjLearningSequenceGUI extends ilContainerGUI implements ilCtrlBaseClass public const ACCESS_READ = 'read'; public const ACCESS_VISIBLE = 'visible'; + protected \ILIAS\Style\Content\Service $content_style; protected string $obj_type; protected ilNavigationHistory $navigation_history; @@ -222,6 +223,7 @@ public function __construct() $this->request_wrapper = $DIC->http()->wrapper()->query(); $this->post_wrapper = $DIC->http()->wrapper()->post(); $this->refinery = $DIC->refinery(); + $this->content_style = $DIC->contentStyle(); $this->help->setScreenIdComponent($this->obj_type); $this->lng->loadLanguageModule($this->obj_type); @@ -327,6 +329,7 @@ public function executeCommand(): void $gui_class = 'ilObjLearningSequenceEditExtroGUI'; } + $this->addContentStyleCss(); $this->addSubTabsForContent($which_tab); $page_id = $this->object->getContentPageId($which_page); @@ -453,6 +456,14 @@ public function executeCommand(): void } } + public function addContentStyleCss(): void + { + $this->content_style->gui()->addCss( + $this->tpl, + $this->object->getRefId() + ); + } + public function addToNavigationHistory(): void { if ( @@ -537,6 +548,7 @@ protected function manageContent(string $cmd = self::CMD_CONTENT): void protected function learnerView(string $cmd = self::CMD_LEARNER_VIEW): void { + $this->addContentStyleCss(); $this->tabs->activateTab(self::TAB_CONTENT_MAIN); $this->addSubTabsForContent(self::TAB_VIEW_CONTENT); diff --git a/Modules/LearningSequence/test/LearnerProgress/ilLSLPTest.php b/Modules/LearningSequence/test/LearnerProgress/ilLSLPTest.php index c23a218ce2e7..b132c1957abb 100644 --- a/Modules/LearningSequence/test/LearnerProgress/ilLSLPTest.php +++ b/Modules/LearningSequence/test/LearnerProgress/ilLSLPTest.php @@ -1,7 +1,5 @@ assertIsArray($result); $this->assertNotEmpty($result); - $this->assertEquals(ilLPObjSettings::LP_MODE_DEACTIVATED, array_pop($result)); + $this->assertEquals( + [ilLPObjSettings::LP_MODE_DEACTIVATED, ilLPObjSettings::LP_MODE_COLLECTION], + $result + ); + } + + public function testGetDefaultModesLPDeactive(): void + { + $obj = new ilLSLPStub(); + $result = $obj->getDefaultModes(false); + + $this->assertIsArray($result); + $this->assertNotEmpty($result); + $this->assertEquals( + [ilLPObjSettings::LP_MODE_DEACTIVATED], + $result + ); } public function testGetDefaultMode(): void diff --git a/Modules/MediaCast/LearningProgress/LearningProgressManager.php b/Modules/MediaCast/LearningProgress/LearningProgressManager.php new file mode 100644 index 000000000000..18dcdf53d7fb --- /dev/null +++ b/Modules/MediaCast/LearningProgress/LearningProgressManager.php @@ -0,0 +1,54 @@ +media_cast = $media_cast; + } + + public function addItemToLP(int $mob_id): void + { + $lp = \ilObjectLP::getInstance($this->media_cast->getId()); + + // see ilLPListOfSettingsGUI assign + $collection = $lp->getCollectionInstance(); + if ( + $collection && + $collection->hasSelectableItems() && + $this->media_cast->getNewItemsInLearningProgress() + ) { + $collection->activateEntries([$mob_id]); + $lp->resetCaches(); + \ilLPStatusWrapper::_refreshStatus($this->media_cast->getId()); + } + } + + public function isCollectionMode(): bool + { + $lp = \ilObjectLP::getInstance($this->media_cast->getId()); + return $lp->getCurrentMode() === \ilLPObjSettings::LP_MODE_COLLECTION_MOBS; + } +} diff --git a/Modules/MediaCast/Presentation/class.McstImageGalleryGUI.php b/Modules/MediaCast/Presentation/class.McstImageGalleryGUI.php index 5f9b1a5dfe8c..ed91cb3fefe2 100644 --- a/Modules/MediaCast/Presentation/class.McstImageGalleryGUI.php +++ b/Modules/MediaCast/Presentation/class.McstImageGalleryGUI.php @@ -23,6 +23,9 @@ */ class McstImageGalleryGUI { + protected \ILIAS\MediaCast\InternalDomainService $domain; + protected string $completed_callback; + protected string $rss_link; protected \ilObjMediaCast $media_cast; protected ilGlobalTemplateInterface $tpl; protected \ILIAS\DI\UIServices $ui; @@ -31,17 +34,22 @@ class McstImageGalleryGUI protected \ilCtrl $ctrl; protected \ilToolbarGUI $toolbar; - public function __construct(\ilObjMediaCast $obj, $tpl = null) - { + public function __construct( + \ilObjMediaCast $obj, + $tpl = null, + string $rss_link = "" + ) { global $DIC; $this->ui = $DIC->ui(); + $this->rss_link = $rss_link; $this->lng = $DIC->language(); $this->media_cast = $obj; $this->tpl = $tpl; $this->user = $DIC->user(); $this->ctrl = $DIC->ctrl(); $this->toolbar = $DIC->toolbar(); + $this->domain = $DIC->mediaCast()->internal()->domain(); } public function executeCommand(): void @@ -73,11 +81,23 @@ public function getHTML(): string $toolbar->addFormButton($lng->txt("mcst_download_all"), "downloadAll"); } + if ($this->rss_link !== "") { + $b = $f->link()->standard( + $lng->txt("mcst_webfeed"), + $this->rss_link + )->withOpenInNewViewport(true); + $toolbar->addComponent($b); + } + // cards and modals $cards = []; $modals = []; $pages = []; + + $lp_collection_mode = $this->domain->learningProgress($this->media_cast)->isCollectionMode(); + + $mob_modals = []; foreach ($this->media_cast->getSortedItemsArray() as $item) { $mob = new \ilObjMediaObject($item["mob_id"]); $med = $mob->getMediaItem("Standard"); @@ -99,8 +119,15 @@ public function getHTML(): string ); $pages[] = $f->modal()->lightboxImagePage($image, $mob->getTitle()); + if ($lp_collection_mode) { + $mob_modals[$mob->getId()] = $f->modal()->lightbox($pages); + $pages = []; + } + } + $main_modal = null; + if (!$lp_collection_mode) { + $main_modal = $f->modal()->lightbox($pages); } - $main_modal = $f->modal()->lightbox($pages); $cnt = 0; foreach ($this->media_cast->getSortedItemsArray() as $item) { @@ -133,13 +160,28 @@ public function getHTML(): string $mob->getTitle() ); - $modal = $main_modal; + if (!$lp_collection_mode) { + $modal = $main_modal; + } else { + $modal = $mob_modals[$mob->getId()]; + } $card_image = $preview_image->withAction($modal->getShowSignal()); - $card_image = $card_image->withAdditionalOnLoadCode(function ($id) use ($cnt) { - return "$('#$id').click(function(e) { document.querySelector('.modal-body .carousel [data-slide-to=\"" . $cnt . "\"]').click(); });"; + $slide_to = ""; + $completed_cb = ""; + if (!$lp_collection_mode) { + $slide_to = "document.querySelector('.modal-body .carousel [data-slide-to=\"" . $cnt . "\"]').click();"; + } else { + $completed_cb = $this->completed_callback . '&mob_id=' . $mob->getId(); + $completed_cb = "$.ajax({type:'GET', url: '$completed_cb'});"; + } + + $card_image = $card_image->withAdditionalOnLoadCode(function ($id) use ($slide_to, $completed_cb) { + return "$('#$id').click(function(e) { $slide_to $completed_cb });"; }); - $cnt++; + if (!$lp_collection_mode) { + $cnt++; + } $sections = ($mob->getDescription()) ? [$f->legacy($mob->getDescription())] @@ -168,10 +210,15 @@ public function getHTML(): string $deck = $f->deck($cards); - if (count($pages) == 0) { + if (count($pages) === 0 && count($mob_modals) === 0) { return ""; } - return ""; + if (!$lp_collection_mode) { + $modals = [$main_modal]; + } else { + $modals = $mob_modals; + } + return ""; } protected function downloadAll(): void @@ -193,4 +240,9 @@ protected function downloadAll(): void $this->ctrl->redirectByClass("ilobjmediacastgui", "showContent"); } + + public function setCompletedCallback(string $completed_callback): void + { + $this->completed_callback = $completed_callback; + } } diff --git a/Modules/MediaCast/Presentation/class.McstPodcastGUI.php b/Modules/MediaCast/Presentation/class.McstPodcastGUI.php index b65bdf97ae96..f1e358d5a749 100644 --- a/Modules/MediaCast/Presentation/class.McstPodcastGUI.php +++ b/Modules/MediaCast/Presentation/class.McstPodcastGUI.php @@ -24,6 +24,7 @@ */ class McstPodcastGUI { + protected string $rss_link = ""; protected ilMediaObjectsPlayerWrapperGUI $player_wrapper; protected ilCtrl $ctrl; protected \ilObjMediaCast $media_cast; @@ -34,7 +35,8 @@ class McstPodcastGUI public function __construct( \ilObjMediaCast $obj, - ilGlobalTemplateInterface $tpl = null + ilGlobalTemplateInterface $tpl = null, + string $rss_link = "" ) { global $DIC; @@ -44,6 +46,7 @@ public function __construct( $this->tpl = $tpl; $this->user = $DIC->user(); $this->ctrl = $DIC->ctrl(); + $this->rss_link = $rss_link; $this->player_wrapper = $DIC->mediaObjects() ->internal() ->gui() @@ -126,6 +129,16 @@ public function getHTML(): string ] ); + if ($this->rss_link !== "") { + $actions = [ + $f->link()->standard( + $lng->txt("mcst_webfeed"), + $this->rss_link + )->withOpenInNewViewport(true) + ]; + $list = $list->withActions($f->dropdown()->standard($actions)); + } + return $renderer->render($list); } } diff --git a/Modules/MediaCast/Presentation/class.VideoViewGUI.php b/Modules/MediaCast/Presentation/class.VideoViewGUI.php index fc63b6c7b413..781c0aaf652b 100644 --- a/Modules/MediaCast/Presentation/class.VideoViewGUI.php +++ b/Modules/MediaCast/Presentation/class.VideoViewGUI.php @@ -28,6 +28,7 @@ */ class VideoViewGUI { + protected string $rss_link; protected \ilToolbarGUI $toolbar; protected \ilGlobalTemplateInterface $main_tpl; protected \ilObjMediaCast $media_cast; @@ -40,11 +41,16 @@ class VideoViewGUI protected VideoSequence $video_sequence; protected string $video_wrapper_id = "mcst_video"; - public function __construct(\ilObjMediaCast $obj, \ilGlobalTemplateInterface $tpl = null) + public function __construct( + \ilObjMediaCast $obj, + \ilGlobalTemplateInterface $tpl = null, + string $rss_link = "" + ) { global $DIC; $this->ui = $DIC->ui(); + $this->rss_link = $rss_link; $this->lng = $DIC->language(); $this->media_cast = $obj; $this->tpl = $tpl; @@ -120,6 +126,17 @@ public function renderToolbar(): void il.VideoPlaylist.autoplay('mcst_playlist', false); });"); } + + if ($this->rss_link !== "") { + $f = $this->ui->factory(); + $actions = [ + $f->link()->standard( + $lng->txt("mcst_webfeed"), + $this->rss_link + )->withOpenInNewViewport(true) + ]; + $toolbar->addComponent($f->dropdown()->standard($actions)); + } } protected function getAutoplay(): bool diff --git a/Modules/MediaCast/Service/class.InternalDomainService.php b/Modules/MediaCast/Service/class.InternalDomainService.php index 053937c43f3f..d4dc72239968 100644 --- a/Modules/MediaCast/Service/class.InternalDomainService.php +++ b/Modules/MediaCast/Service/class.InternalDomainService.php @@ -22,6 +22,7 @@ use ILIAS\DI\Container; use ILIAS\Repository\GlobalDICDomainServices; +use ILIAS\MediaCast\LearningProgress\LearningProgressManager; /** * @author Alexander Killing @@ -54,8 +55,13 @@ public function access(int $ref_id, int $user_id) : Access\AccessManager ); }*/ - public function mediaCast() : MediaCastManager + public function mediaCast(): MediaCastManager { return new MediaCastManager(); } + + public function learningProgress(\ilObjMediaCast $cast): LearningProgressManager + { + return new LearningProgressManager($cast); + } } diff --git a/Modules/MediaCast/classes/class.ilObjMediaCast.php b/Modules/MediaCast/classes/class.ilObjMediaCast.php index 0d6e85a34357..30e259c14bd8 100755 --- a/Modules/MediaCast/classes/class.ilObjMediaCast.php +++ b/Modules/MediaCast/classes/class.ilObjMediaCast.php @@ -34,6 +34,7 @@ class ilObjMediaCast extends ilObject public const AUTOPLAY_NO = 0; public const AUTOPLAY_ACT = 1; public const AUTOPLAY_INACT = 2; + protected \ILIAS\MediaCast\InternalDomainService $domain; protected \ILIAS\MediaObjects\Tracking\TrackingManager $mob_tracking; protected array $itemsarray; @@ -65,6 +66,7 @@ public function __construct( $this->mob_tracking = $DIC->mediaObjects()->internal() ->domain() ->tracking(); + $this->domain = $DIC->mediaCast()->internal()->domain(); parent::__construct($a_id, $a_call_by_reference); } @@ -389,6 +391,9 @@ protected function copyOrder( ): void { $items = []; foreach ($this->readOrder() as $i) { + if (!array_key_exists($i, $mapping)) { + continue; + } $items[] = $mapping[$i]; } $newObj->saveOrder($items); @@ -517,22 +522,12 @@ public function addMobToCast( $mc_item->setContent($long_desc); } $mc_item->setLimitation(false); - // @todo handle visibility + $mc_item->setVisibility($this->getDefaultAccess() == 0 ? "users" : "public"); $mc_item->create(); - $lp = ilObjectLP::getInstance($this->getId()); - - // see ilLPListOfSettingsGUI assign - $collection = $lp->getCollectionInstance(); - if ( - $collection && - $collection->hasSelectableItems() && - $this->getNewItemsInLearningProgress() - ) { - $collection->activateEntries([$mob_id]); - $lp->resetCaches(); - ilLPStatusWrapper::_refreshStatus($this->getId()); - } + $lp = $this->domain->learningProgress($this); + $lp->addItemToLP($mob_id); + return $mc_item->getId(); } diff --git a/Modules/MediaCast/classes/class.ilObjMediaCastGUI.php b/Modules/MediaCast/classes/class.ilObjMediaCastGUI.php index b5b173a0be60..e836163d932d 100755 --- a/Modules/MediaCast/classes/class.ilObjMediaCastGUI.php +++ b/Modules/MediaCast/classes/class.ilObjMediaCastGUI.php @@ -186,12 +186,12 @@ public function executeCommand(): void break; case "mcstimagegallerygui": - $view = new \McstImageGalleryGUI($this->object, $this->tpl); + $view = new \McstImageGalleryGUI($this->object, $this->tpl, $this->getFeedLink()); $this->ctrl->forwardCommand($view); break; case "mcstpodcastgui": - $view = new \McstPodcastGUI($this->object, $this->tpl); + $view = new \McstPodcastGUI($this->object, $this->tpl, $this->getFeedLink()); $this->ctrl->forwardCommand($view); break; @@ -272,85 +272,27 @@ public function listItemsObject(bool $a_presentation_mode = false): void $table_gui->addMultiCommand("confirmDeletionItems", $lng->txt("delete")); $table_gui->setSelectAllCheckbox("item_id"); } - - $feed_icon_html = $this->getFeedIconsHTML(); - if ($feed_icon_html !== "") { - $table_gui->setHeaderHTML($feed_icon_html); - } - $tpl->setContent($table_gui->getHTML()); } - public function getFeedIconsHTML(): string + public function getFeedLink(): string { - $lng = $this->lng; - $row1 = ""; - $row2 = ""; - - $html = ""; - $public_feed = ilBlockSetting::_lookup( "news", "public_feed", 0, $this->object->getId() ); - + $url = ""; // rss icon/link if ($public_feed) { $news_set = new ilSetting("news"); $enable_internal_rss = $news_set->get("enable_rss_for_internal"); - if ($enable_internal_rss) { - // create dummy object in db (we need an id) - $items = $this->object->getItemsArray(); - foreach (ilObjMediaCast::$purposes as $purpose) { - foreach ($items as $id => $item) { - $mob = new ilObjMediaObject($item["mob_id"]); - $mob->read(); - if ($mob->hasPurposeItem($purpose)) { - if ($html == "") { - $html = " "; - } - $url = ILIAS_HTTP_PATH . "/feed.php?client_id=" . rawurlencode(CLIENT_ID) . "&" . "ref_id=" . $this->requested_ref_id . "&purpose=$purpose"; - $title = $lng->txt("news_feed_url"); - - switch (strtolower($purpose)) { - case "audioportable": - $type1 = ilRSSButtonGUI::ICON_RSS_AUDIO; - $type2 = ilRSSButtonGUI::ICON_ITUNES_AUDIO; - break; - - case "videoportable": - $type1 = ilRSSButtonGUI::ICON_RSS_VIDEO; - $type2 = ilRSSButtonGUI::ICON_ITUNES_VIDEO; - break; - - default: - $type1 = ilRSSButtonGUI::ICON_RSS; - $type2 = ilRSSButtonGUI::ICON_ITUNES; - break; - } - $row1 .= " " . ilRSSButtonGUI::get($type1, $url); - if ($this->object->getPublicFiles()) { - $url = preg_replace("/https?/i", "itpc", $url); - $title = $lng->txt("news_feed_url"); - - $row2 .= " " . ilRSSButtonGUI::get($type2, $url); - } - break; - } - } - } - if ($html != "") { - $html .= $row1; - if ($row2 != "") { - $html .= $row2; - } - } + $url = ILIAS_HTTP_PATH . "/feed.php?client_id=" . rawurlencode(CLIENT_ID) . "&" . "ref_id=" . $this->requested_ref_id; } } - return $html; + return $url; } /** @@ -943,7 +885,7 @@ public function deleteItemsObject(): void $mc_item = new ilNewsItem($item_id); $mc_item->delete(); } - + $this->object->saveOrder($this->object->readItems()); $ilCtrl->redirect($this, "listItems"); } @@ -1546,19 +1488,26 @@ public function showContentObject(): void if ($this->object->getViewMode() == ilObjMediaCast::VIEW_GALLERY) { $this->showGallery(); } elseif ($this->object->getViewMode() == ilObjMediaCast::VIEW_IMG_GALLERY) { - $view = new \McstImageGalleryGUI($this->object, $this->tpl); + $view = new \McstImageGalleryGUI($this->object, $this->tpl, $this->getFeedLink()); + $view->setCompletedCallback($this->ctrl->getLinkTarget( + $this, + "handlePlayerCompletedEvent", + "", + true, + false + )); $this->tabs->activateTab("content"); $this->addContentSubTabs("content"); $tpl->setContent($this->ctrl->getHTML($view)); } elseif ($this->object->getViewMode() == ilObjMediaCast::VIEW_PODCAST) { - $view = new \McstPodcastGUI($this->object, $this->tpl); + $view = new \McstPodcastGUI($this->object, $this->tpl, $this->getFeedLink()); $this->tabs->activateTab("content"); $this->addContentSubTabs("content"); $tpl->setContent($this->ctrl->getHTML($view)); } elseif ($this->object->getViewMode() == ilObjMediaCast::VIEW_VCAST) { $ilTabs->activateTab("content"); $this->addContentSubTabs("content"); - $view = new \ILIAS\MediaCast\Presentation\VideoViewGUI($this->object, $tpl); + $view = new \ILIAS\MediaCast\Presentation\VideoViewGUI($this->object, $tpl, $this->getFeedLink()); $view->setCompletedCallback($this->ctrl->getLinkTarget( $this, "handlePlayerCompletedEvent", diff --git a/Modules/MediaPool/LuceneObjectDefinition.xml b/Modules/MediaPool/LuceneObjectDefinition.xml index e9ae8ddb0c69..8677139efe93 100644 --- a/Modules/MediaPool/LuceneObjectDefinition.xml +++ b/Modules/MediaPool/LuceneObjectDefinition.xml @@ -20,7 +20,7 @@ - SELECT title, obj_id, foreign_id AS metaObjId, mep_id, type AS metaType,0 AS metaRbacId, child FROM mep_tree + SELECT title, obj_id, foreign_id AS metaObjId, mep_id, type AS metaType,0 AS metaRbacId, 'mep' as objType, child FROM mep_tree JOIN mep_item ON child = obj_id WHERE type = 'mob' AND mep_id IN (?) @@ -31,6 +31,7 @@ + SELECT caption, location, text_representation FROM media_item @@ -46,7 +47,7 @@ - SELECT title,content, obj_id AS metaObjId, mep_id, type AS metaType,0 AS metaRbacId, child, parent_type FROM mep_tree + SELECT title,content, obj_id AS metaObjId, mep_id, 'mpg' AS metaType, mep_id AS metaRbacId, child, parent_type FROM mep_tree JOIN mep_item ON child = obj_id JOIN page_object po ON obj_id = po.page_id WHERE type = 'pg' AND parent_type = 'mep' @@ -61,6 +62,7 @@ + \ No newline at end of file diff --git a/Modules/OrgUnit/classes/Positions/UserAssignment/class.ilOrgUnitRecursiveUserAssignmentTableGUI.php b/Modules/OrgUnit/classes/Positions/UserAssignment/class.ilOrgUnitRecursiveUserAssignmentTableGUI.php index c1fb31068dae..9907b708fb92 100644 --- a/Modules/OrgUnit/classes/Positions/UserAssignment/class.ilOrgUnitRecursiveUserAssignmentTableGUI.php +++ b/Modules/OrgUnit/classes/Positions/UserAssignment/class.ilOrgUnitRecursiveUserAssignmentTableGUI.php @@ -106,13 +106,18 @@ public function loadData(): array $usr_id = (int) $usr_id; if (!array_key_exists($usr_id, $data)) { $user = new ilObjUser($usr_id); - $set["login"] = $user->getLogin(); - $set["first_name"] = $user->getFirstname(); - $set["last_name"] = $user->getLastname(); - $set["user_id"] = $usr_id; - $set["orgu_assignments"] = []; - $set['view_lp'] = false; + $set = [ + 'login' => $user->getLogin(), + 'first_name' => $user->getFirstname(), + 'last_name' => $user->getLastname(), + 'user_id' => $usr_id, + 'active' => $user->getActive(), + 'orgu_assignments' => [], + 'view_lp' => false + ]; $data[$usr_id] = $set; + } else { + $data[$usr_id]["active"] = \ilObjUser::_lookupActive($usr_id); } $data[$usr_id]['orgu_assignments'][] = ilObject::_lookupTitle(ilObject::_lookupObjId($ref_id)); $data[$usr_id]['view_lp'] = $permission_view_lp || $data[$usr_id]['view_lp']; @@ -159,6 +164,10 @@ public function fillRow(array $a_set): void $this->tpl->setVariable("LOGIN", $a_set["login"]); $this->tpl->setVariable("FIRST_NAME", $a_set["first_name"]); $this->tpl->setVariable("LAST_NAME", $a_set["last_name"]); + if($a_set["active"] === false) { + $this->tpl->setVariable("INACTIVE", $this->lng->txt('usr_account_inactive')); + } + $orgus = $a_set['orgu_assignments']; sort($orgus); $this->tpl->setVariable("ORG_UNITS", implode(',', $orgus)); diff --git a/Modules/OrgUnit/classes/Positions/UserAssignment/class.ilOrgUnitUserAssignmentTableGUI.php b/Modules/OrgUnit/classes/Positions/UserAssignment/class.ilOrgUnitUserAssignmentTableGUI.php index 6a6e8c2a4883..9236b3375fe9 100644 --- a/Modules/OrgUnit/classes/Positions/UserAssignment/class.ilOrgUnitUserAssignmentTableGUI.php +++ b/Modules/OrgUnit/classes/Positions/UserAssignment/class.ilOrgUnitUserAssignmentTableGUI.php @@ -70,7 +70,7 @@ public function parseData(): void */ private function parseRows(array $user_ids): array { - $data = array(); + $data = []; foreach ($user_ids as $user_id) { $data[] = $this->getRowForUser($user_id); } @@ -80,13 +80,14 @@ private function parseRows(array $user_ids): array private function getRowForUser(int $user_id): array { $user = new ilObjUser($user_id); - $set = []; - $set["login"] = $user->getLogin(); - $set["first_name"] = $user->getFirstname(); - $set["last_name"] = $user->getLastname(); - $set["user_object"] = $user; - $set["user_id"] = $user_id; - return $set; + return [ + 'login' => $user->getLogin(), + 'first_name' => $user->getFirstname(), + 'last_name' => $user->getLastname(), + 'user_object' => $user, + 'user_id' => $user_id, + 'active' => $user->getActive() + ]; } public function fillRow(array $a_set): void @@ -98,8 +99,11 @@ public function fillRow(array $a_set): void $this->tpl->setVariable("LOGIN", $a_set["login"]); $this->tpl->setVariable("FIRST_NAME", $a_set["first_name"]); $this->tpl->setVariable("LAST_NAME", $a_set["last_name"]); - // $this->ctrl->setParameterByClass(ilLearningProgressGUI::class, "obj_id", $set["user_id"]); - // $this->ctrl->setParameterByClass(ilObjOrgUnitGUI::class, "obj_id", $set["user_id"]); + + if($a_set["active"] === false) { + $this->tpl->setVariable("INACTIVE", $this->lng->txt('usr_account_inactive')); + } + $this->ctrl->setParameterByClass(ilOrgUnitUserAssignmentGUI::class, 'usr_id', $a_set["user_id"]); $this->ctrl->setParameterByClass( ilOrgUnitUserAssignmentGUI::class, diff --git a/Modules/OrgUnit/classes/Setup/class.ilOrgUnitOperationContextRegisteredObjective.php b/Modules/OrgUnit/classes/Setup/class.ilOrgUnitOperationContextRegisteredObjective.php index b5bdd40fcbfe..52cef8caa244 100644 --- a/Modules/OrgUnit/classes/Setup/class.ilOrgUnitOperationContextRegisteredObjective.php +++ b/Modules/OrgUnit/classes/Setup/class.ilOrgUnitOperationContextRegisteredObjective.php @@ -1,7 +1,5 @@ getResource(Environment::RESOURCE_DATABASE); + $query = 'DELETE FROM il_orgu_ua' . PHP_EOL + . 'WHERE user_id NOT IN (' . PHP_EOL + . 'SELECT usr_id FROM usr_data' . PHP_EOL + . ')'; + $db->manipulate($query); + return $environment; + } + + public function isApplicable(Environment $environment): bool + { + return true; + } +} diff --git a/Modules/OrgUnit/classes/Setup/class.ilOrgUnitSetupAgent.php b/Modules/OrgUnit/classes/Setup/class.ilOrgUnitSetupAgent.php new file mode 100644 index 000000000000..28b67359563e --- /dev/null +++ b/Modules/OrgUnit/classes/Setup/class.ilOrgUnitSetupAgent.php @@ -0,0 +1,78 @@ +refinery = $refinery; + } + + public function hasConfig(): bool + { + return false; + } + + public function getArrayToConfigTransformation(): Refinery\Transformation + { + throw new LogicException("Agent has no config"); + } + + public function getInstallObjective(Setup\Config $config = null): Setup\Objective + { + return new Setup\Objective\NullObjective(); + } + + public function getUpdateObjective(Setup\Config $config = null): Setup\Objective + { + return new Setup\Objective\NullObjective(); + } + + public function getBuildArtifactObjective(): Setup\Objective + { + return new Setup\Objective\NullObjective(); + } + + public function getStatusObjective(Setup\Metrics\Storage $storage): Setup\Objective + { + return new Setup\Objective\NullObjective(); + } + + public function getMigrations(): array + { + return []; + } + + public function getNamedObjectives(?Setup\Config $config = null): array + { + return [ + 'removeDeletedUsersFromOrgUnits' => new Setup\ObjectiveConstructor( + 'clean assignments of deleted users', + static fn(): Setup\Objective => new ilOrgUnitRemoveDeletedUsersObjective() + ) + ]; + } +} diff --git a/Modules/OrgUnit/classes/class.ilObjOrgUnit.php b/Modules/OrgUnit/classes/class.ilObjOrgUnit.php index d735afb15402..cefb84d4ab13 100644 --- a/Modules/OrgUnit/classes/class.ilObjOrgUnit.php +++ b/Modules/OrgUnit/classes/class.ilObjOrgUnit.php @@ -1,4 +1,5 @@ getRefId()) { ilOrgUnitPathStorage::writePathByRefId($this->getRefId()); + } else { + throw new \LogicException('No ref_id on OrgU with id ' . $this->getId(), 1); } } } diff --git a/Modules/OrgUnit/classes/class.ilObjOrgUnitGUI.php b/Modules/OrgUnit/classes/class.ilObjOrgUnitGUI.php index dbb48780ccc8..60538b91104d 100644 --- a/Modules/OrgUnit/classes/class.ilObjOrgUnitGUI.php +++ b/Modules/OrgUnit/classes/class.ilObjOrgUnitGUI.php @@ -268,6 +268,18 @@ public function executeCommand(): void $ilOrgUnitUserAssignmentGUI = new ilOrgUnitUserAssignmentGUI(); $this->ctrl->forwardCommand($ilOrgUnitUserAssignmentGUI); break; + case strtolower(ilPropertyFormGUI::class): + /* + * Only used for async loading of the repository tree in custom md + * internal links (see #24875). This is necessary since OrgUnits don't + * use ilObjectMetaDataGUI. + */ + $form = $this->initAdvancedSettingsForm(); + $gui = new ilAdvancedMDRecordGUI(ilAdvancedMDRecordGUI::MODE_EDITOR, 'orgu', $this->object->getId(), 'orgu_type', $this->object->getOrgUnitTypeId()); + $gui->setPropertyForm($form); + $gui->parse(); + $this->ctrl->forwardCommand($form); + break; default: $this->tabs_gui->activateTab(self::TAB_VIEW_CONTENT); switch ($cmd) { @@ -352,8 +364,10 @@ public function executeCommand(): void protected function afterSave(ilObject $new_object): void { + $new_object->writePath(); $this->tpl->setOnScreenMessage('success', $this->lng->txt("object_added"), true); $this->ctrl->setParameter($this, "ref_id", $new_object->getRefId()); + ilUtil::redirect($this->getReturnLocation( "save", $this->ctrl->getLinkTarget($this, self::CMD_EDIT_SETTINGS, "") diff --git a/Modules/OrgUnit/templates/default/tpl.staff_row.html b/Modules/OrgUnit/templates/default/tpl.staff_row.html index 90aa2f426b37..e678699fb92e 100644 --- a/Modules/OrgUnit/templates/default/tpl.staff_row.html +++ b/Modules/OrgUnit/templates/default/tpl.staff_row.html @@ -2,6 +2,12 @@ {LOGIN} + +
+ + {INACTIVE} + + diff --git a/Modules/Poll/classes/class.ilObjPollGUI.php b/Modules/Poll/classes/class.ilObjPollGUI.php index acfb14531d9b..8f6b23f192af 100644 --- a/Modules/Poll/classes/class.ilObjPollGUI.php +++ b/Modules/Poll/classes/class.ilObjPollGUI.php @@ -524,7 +524,7 @@ public function vote(): void } $session_last_poll_vote = ilSession::get('last_poll_vote'); - if ($valid) { + if ($valid && $this->user->getId() != ANONYMOUS_USER_ID) { unset($session_last_poll_vote[$this->object->getId()]); ilSession::set('last_poll_vote', $session_last_poll_vote); $this->object->saveVote($this->user->getId(), $aw); diff --git a/Modules/Poll/classes/class.ilPollBlock.php b/Modules/Poll/classes/class.ilPollBlock.php index bcaf30f111c2..b1aa629cfcd1 100644 --- a/Modules/Poll/classes/class.ilPollBlock.php +++ b/Modules/Poll/classes/class.ilPollBlock.php @@ -68,7 +68,7 @@ public function hasAnyContent(int $a_user_id, int $a_ref_id): bool return false; } - if (!$this->mayVote($a_user_id) && + if (!$this->maySeeQuestion($a_user_id) && !$this->maySeeResults($a_user_id)) { return false; } @@ -76,16 +76,12 @@ public function hasAnyContent(int $a_user_id, int $a_ref_id): bool return true; } - public function mayVote(int $a_user_id): bool + public function maySeeQuestion($a_user_id): bool { if (!$this->active) { return false; } - if ($a_user_id === ANONYMOUS_USER_ID) { - return false; - } - if ($this->poll->hasUserVoted($a_user_id)) { return false; } @@ -99,6 +95,11 @@ public function mayVote(int $a_user_id): bool return true; } + public function mayVote($a_user_id): bool + { + return $this->maySeeQuestion($a_user_id) && $a_user_id != ANONYMOUS_USER_ID; + } + public function mayNotResultsYet(): bool { if ($this->poll->getViewResults() === ilObjPoll::VIEW_RESULTS_AFTER_PERIOD && @@ -122,7 +123,7 @@ public function maySeeResults(int $a_user_id): bool case ilObjPoll::VIEW_RESULTS_ALWAYS: // fallthrough - // #12023 - see mayNotResultsYet() + // #12023 - see mayNotResultsYet() case ilObjPoll::VIEW_RESULTS_AFTER_PERIOD: return true; diff --git a/Modules/Poll/classes/class.ilPollBlockGUI.php b/Modules/Poll/classes/class.ilPollBlockGUI.php index 0de9096d92cf..58fe991d7c46 100644 --- a/Modules/Poll/classes/class.ilPollBlockGUI.php +++ b/Modules/Poll/classes/class.ilPollBlockGUI.php @@ -123,7 +123,7 @@ public function fillRow(array $a_set): void if (!$this->container_view_manager->isAdminView()) { // vote - if ($this->poll_block->mayVote($this->user->getId())) { + if ($this->poll_block->maySeeQuestion($this->user->getId())) { $this->tpl->setCurrentBlock("mode_info_bl"); if ($this->poll_block->getPoll()->getNonAnonymous()) { $mode_info = $this->lng->txt("poll_non_anonymous_warning"); @@ -157,6 +157,7 @@ public function fillRow(array $a_set): void $this->tpl->setCurrentBlock("answer"); foreach ($a_set->getAnswers() as $item) { + $status = []; $id = (int) ($item['id'] ?? 0); $answer = (string) ($item['answer'] ?? 0); if (!$is_multi_answer) { @@ -167,30 +168,41 @@ public function fillRow(array $a_set): void $this->tpl->setVariable("ANSWER_NAME", "aw[]"); if (!empty($last_vote) && is_array($last_vote) && in_array($id, $last_vote)) { - $this->tpl->setVariable("ANSWER_STATUS", 'checked="checked"'); + $status[] = 'checked="checked"'; } } + + if (!$this->poll_block->mayVote($this->user->getId())) { + $status[] = 'disabled'; + } + + if (!empty($status)) { + $this->tpl->setVariable("ANSWER_STATUS", implode(' ', $status)); + } + $this->tpl->setVariable("VALUE_ANSWER", $id); $this->tpl->setVariable("TXT_ANSWER_VOTE", nl2br($answer)); $this->tpl->parseCurrentBlock(); } - $this->ctrl->setParameterByClass( - $this->getRepositoryObjectGUIName(), - "ref_id", - $this->getRefId() - ); - $url = $this->ctrl->getLinkTargetByClass( - array("ilrepositorygui", $this->getRepositoryObjectGUIName()), - "vote" - ); - $this->ctrl->clearParametersByClass($this->getRepositoryObjectGUIName()); + if ($this->poll_block->mayVote($this->user->getId())) { + $this->ctrl->setParameterByClass( + $this->getRepositoryObjectGUIName(), + "ref_id", + $this->getRefId() + ); + $url = $this->ctrl->getLinkTargetByClass( + array("ilrepositorygui", $this->getRepositoryObjectGUIName()), + "vote" + ); + $this->ctrl->clearParametersByClass($this->getRepositoryObjectGUIName()); - $url .= "#poll" . $a_set->getID(); + $url .= "#poll" . $a_set->getID(); - $this->tpl->setVariable("URL_FORM", $url); - $this->tpl->setVariable("CMD_FORM", "vote"); - $this->tpl->setVariable("TXT_SUBMIT", $this->lng->txt("poll_vote")); + $this->tpl->setVariable("URL_FORM", $url); + $this->tpl->setVariable("CMD_FORM", "vote"); + $this->tpl->setVariable("TXT_SUBMIT", $this->lng->txt("poll_vote")); + } if ($this->poll_block->getPoll()->getVotingPeriod()) { $this->tpl->setVariable( @@ -293,7 +305,7 @@ public function fillRow(array $a_set): void } } - if (!$this->poll_block->mayVote($this->user->getId()) && !$this->poll_block->getPoll()->hasUserVoted($this->user->getId())) { + if (!$this->poll_block->maySeeQuestion($this->user->getId()) && !$this->poll_block->getPoll()->hasUserVoted($this->user->getId())) { if ($this->poll_block->getPoll()->getVotingPeriod()) { $this->tpl->setVariable( "TXT_VOTING_PERIOD", @@ -323,6 +335,13 @@ public function fillRow(array $a_set): void } + if ($this->user->getId() == ANONYMOUS_USER_ID) { + $this->tpl->setCurrentBlock("anon_warning"); + $this->tpl->setVariable("ANON_WARNING", $this->lng->txt('no_access_item_public')); + $this->tpl->parseCurrentBlock(); + } + + if ($this->poll_block->showComments()) { $this->tpl->setCurrentBlock("comment_link"); $this->tpl->setVariable("LANG_COMMENTS", $this->lng->txt('poll_comments')); diff --git a/Modules/Poll/classes/class.ilPollUserTableGUI.php b/Modules/Poll/classes/class.ilPollUserTableGUI.php index ee68ae7c3328..01439b743d75 100644 --- a/Modules/Poll/classes/class.ilPollUserTableGUI.php +++ b/Modules/Poll/classes/class.ilPollUserTableGUI.php @@ -19,6 +19,8 @@ ******************************************************************** */ +use ILIAS\UI\Component\Symbol\Icon\Icon; + /** * TableGUI class for poll users * @@ -27,6 +29,7 @@ class ilPollUserTableGUI extends ilTable2GUI { protected array $answer_ids = []; + protected string $rendered_checked_icon; public function __construct(object $a_parent_obj, string $a_parent_cmd) { @@ -36,6 +39,18 @@ public function __construct(object $a_parent_obj, string $a_parent_cmd) $this->lng = $DIC->language(); $ilCtrl = $DIC->ctrl(); $lng = $DIC->language(); + $ui_factory = $DIC->ui()->factory(); + $ui_renderer = $DIC->ui()->renderer(); + + $lng->loadLanguageModule('poll'); + + $this->rendered_checked_icon = $ui_renderer->render( + $ui_factory->symbol()->icon()->custom( + ilUtil::getImagePath('icon_ok.svg'), + $lng->txt('poll_answer_selected_alt_text'), + Icon::MEDIUM + ) + ); $this->setId("ilobjpollusr"); @@ -88,7 +103,7 @@ protected function fillRow(array $a_set): void $this->tpl->setCurrentBlock("answer_bl"); foreach ($this->answer_ids as $answer_id) { if ($a_set["answer" . $answer_id]) { - $this->tpl->setVariable("ANSWER", ''); + $this->tpl->setVariable("ANSWER", $this->rendered_checked_icon); } else { $this->tpl->setVariable("ANSWER", " "); } diff --git a/Modules/Poll/templates/default/tpl.block.html b/Modules/Poll/templates/default/tpl.block.html index decf778429cb..11f29853e997 100644 --- a/Modules/Poll/templates/default/tpl.block.html +++ b/Modules/Poll/templates/default/tpl.block.html @@ -31,8 +31,10 @@ + - + +
{TXT_VOTING_END_PERIOD}
@@ -40,7 +42,7 @@ - + @@ -65,6 +67,10 @@ + + +
{ANON_WARNING}
+
diff --git a/Modules/Portfolio/Page/class.ilPortfolioPage.php b/Modules/Portfolio/Page/class.ilPortfolioPage.php index aa2b70159eaa..79aaed00f44b 100644 --- a/Modules/Portfolio/Page/class.ilPortfolioPage.php +++ b/Modules/Portfolio/Page/class.ilPortfolioPage.php @@ -344,7 +344,7 @@ public static function updateInternalLinks( // outgoing links to be fixed if (count($fix) > 0) { $t = ilObject::_lookupType($pid); - if (is_array($all_fixes[$t . ":" . $copied_id])) { + if (is_array($all_fixes[$t . ":" . $copied_id] ?? false)) { $all_fixes[$t . ":" . $copied_id] += $fix; } else { $all_fixes[$t . ":" . $copied_id] = $fix; diff --git a/Modules/Portfolio/Page/class.ilPortfolioPageGUI.php b/Modules/Portfolio/Page/class.ilPortfolioPageGUI.php index 5993ad411350..26f6aef5d17d 100644 --- a/Modules/Portfolio/Page/class.ilPortfolioPageGUI.php +++ b/Modules/Portfolio/Page/class.ilPortfolioPageGUI.php @@ -125,7 +125,8 @@ public function executeCommand(): string $blog_gui = new ilObjBlogGUI($blog_node_id, ilObject2GUI::WORKSPACE_NODE_ID); $blog_gui->disableNotes(!$this->enable_comments); $blog_gui->prtf_embed = true; // disables prepareOutput()/getStandardTemplate() in blog - return (string) $ilCtrl->forwardCommand($blog_gui); + $ilCtrl->forwardCommand($blog_gui); + return $blog_gui->getRenderedContent(); case "ilcalendarmonthgui": $this->ctrl->saveParameter($this, "chuid"); @@ -1179,7 +1180,11 @@ private function createPersistentCertificateUrl( ilUserCertificateRepository $userCertificateRepository, string $url ): string { - $presentation = $userCertificateRepository->fetchActiveCertificateForPresentation($this->user->getId(), $a_id); + try { + $presentation = $userCertificateRepository->fetchActiveCertificateForPresentation($this->user->getId(), $a_id); + } catch (Exception $e) { + return ""; + } $caption = $this->lng->txt('certificate') . ': '; $caption .= $this->lng->txt($presentation->getUserCertificate()->getObjType()) . ' '; $caption .= '"' . $presentation->getObjectTitle() . '"'; diff --git a/Modules/Portfolio/Template/class.ilObjPortfolioTemplateGUI.php b/Modules/Portfolio/Template/class.ilObjPortfolioTemplateGUI.php index 84c3a807cb09..d4b30eb69ffd 100644 --- a/Modules/Portfolio/Template/class.ilObjPortfolioTemplateGUI.php +++ b/Modules/Portfolio/Template/class.ilObjPortfolioTemplateGUI.php @@ -510,7 +510,7 @@ protected function saveBlog(): void public function preview( bool $a_return = false, - bool $a_content = false, + $a_content = false, bool $a_show_notes = true ): string { if (!$this->checkPermissionBool("write") && diff --git a/Modules/Portfolio/classes/class.ilObjPortfolioBaseGUI.php b/Modules/Portfolio/classes/class.ilObjPortfolioBaseGUI.php index 3c8628d03403..5ed5b4039dc7 100644 --- a/Modules/Portfolio/classes/class.ilObjPortfolioBaseGUI.php +++ b/Modules/Portfolio/classes/class.ilObjPortfolioBaseGUI.php @@ -157,7 +157,6 @@ protected function handlePageCall(string $a_cmd): void $page_gui->setStyleId($this->content_style_domain->getEffectiveStyleId()); $ret = $this->ctrl->forwardCommand($page_gui); - if ($ret != "" && $ret !== true) { // preview (fullscreen) if ($this->page_mode === "preview") { @@ -487,7 +486,7 @@ public function savePage(): void $layout_id = $form->getInput("tmpl"); if ($layout_id) { $layout_obj = new ilPageLayout($layout_id); - $page->setXMLContent($layout_obj->getXMLContent()); + $page->setXMLContent($layout_obj->copyXmlContent(false)); } $page->create(); @@ -613,9 +612,12 @@ public function deletePortfolioPages(): void $this->ctrl->redirect($this, "view"); } + /** + * @param string|bool $a_content (may be content from embedded blog) + */ public function preview( bool $a_return = false, - bool $a_content = false, + $a_content = false, bool $a_show_notes = true ): string { $ilSetting = $this->settings; diff --git a/Modules/Portfolio/classes/class.ilObjPortfolioGUI.php b/Modules/Portfolio/classes/class.ilObjPortfolioGUI.php index b3ad2965f7db..c81c9068ad56 100644 --- a/Modules/Portfolio/classes/class.ilObjPortfolioGUI.php +++ b/Modules/Portfolio/classes/class.ilObjPortfolioGUI.php @@ -76,7 +76,6 @@ protected function checkPermissionBool( public function executeCommand(): void { $this->checkPermission("read"); - $this->setTitleAndDescription(); $next_class = $this->ctrl->getNextClass($this); @@ -96,7 +95,6 @@ public function executeCommand(): void // trigger assignment tool $this->triggerAssignmentTool(); - switch ($next_class) { case "ilworkspaceaccessgui": if ($this->checkPermissionBool("write")) { @@ -608,6 +606,7 @@ protected function getPageGUIInstance( 0, $this->object->hasPublicComments() ); + $page_gui->setStyleId($this->content_style_domain->getEffectiveStyleId()); $page_gui->setAdditional($this->getAdditional()); return $page_gui; } diff --git a/Modules/RootFolder/classes/class.ilObjRootFolderGUI.php b/Modules/RootFolder/classes/class.ilObjRootFolderGUI.php index 49059c880f76..140230486ce9 100755 --- a/Modules/RootFolder/classes/class.ilObjRootFolderGUI.php +++ b/Modules/RootFolder/classes/class.ilObjRootFolderGUI.php @@ -139,7 +139,7 @@ public function executeCommand(): void case "ilobjectcontentstylesettingsgui": $this->checkPermission("write"); $this->setTitleAndDescription(); - //$this->showContainerPageTabs(); + $this->showContainerPageTabs(); $settings_gui = $this->content_style_gui ->objectSettingsGUIForRefId( null, diff --git a/Modules/Scorm2004/classes/class.ilObjSCORM2004LearningModule.php b/Modules/Scorm2004/classes/class.ilObjSCORM2004LearningModule.php index 39f39fceb380..3c513f8e2e55 100755 --- a/Modules/Scorm2004/classes/class.ilObjSCORM2004LearningModule.php +++ b/Modules/Scorm2004/classes/class.ilObjSCORM2004LearningModule.php @@ -193,16 +193,7 @@ public function readObject(): string //check for SCORM 1.2 $this->convert_1_2_to_2004($manifest_file); - // start SCORM 2004 package parser/importer - // if ($this->getEditable()) { - // return $newPack->il_importLM( - // $this, - // $this->getDataDirectory(), - // $this->getImportSequencing() - // ); - // } else { return (new ilSCORM13Package())->il_import($this->getDataDirectory(), $this->getId()); - // } } @@ -218,14 +209,13 @@ public function fixReload(): void public function convert_1_2_to_2004(string $manifest): void { - $ilDB = $this->db; $ilLog = $this->log; ##check manifest-file for version. Check for schemaversion as this is a required element for SCORM 2004 ##accept 2004 3rd Edition an CAM 1.3 as valid schemas //set variables - $this->packageFolder = $this->getDataDirectory(); + $packageFolder = $this->getDataDirectory(); $this->imsmanifestFile = $manifest; $doc = new DomDocument(); @@ -233,14 +223,15 @@ public function convert_1_2_to_2004(string $manifest): void $this->fixReload(); $doc->load($this->imsmanifestFile); $elements = $doc->getElementsByTagName("schemaversion"); - $schema = $elements->item(0)->nodeValue; + $schema = ""; + if (isset($elements->item(0)->nodeValue)) { + $schema = $elements->item(0)->nodeValue; + } if (strtolower(trim($schema)) === "cam 1.3" || strtolower(trim($schema)) === "2004 3rd edition" || strtolower(trim($schema)) === "2004 4th edition") { //no conversion - $this->converted = false; return; } - $this->converted = true; //convert to SCORM 2004 //check for broken SCORM 1.2 manifest file (missing organization default-common error in a lot of manifest files) @@ -264,7 +255,7 @@ public function convert_1_2_to_2004(string $manifest): void //first copy wrappers - $wrapperdir = $this->packageFolder . "/GenericRunTimeWrapper1.0_aadlc"; + $wrapperdir = $packageFolder . "/GenericRunTimeWrapper1.0_aadlc"; if (!mkdir($wrapperdir) && !is_dir($wrapperdir)) { throw new \RuntimeException(sprintf('Directory "%s" was not created', $wrapperdir)); } @@ -272,12 +263,12 @@ public function convert_1_2_to_2004(string $manifest): void copy(self::WRAPPER_JS, $wrapperdir . "/SCOPlayerWrapper.js"); //backup manifestfile - $this->backupManifest = $this->packageFolder . "/imsmanifest.xml.back"; - $ret = copy($this->imsmanifestFile, $this->backupManifest); + $backupManifest = $packageFolder . "/imsmanifest.xml.back"; + $ret = copy($this->imsmanifestFile, $backupManifest); //transform manifest file - $this->totransform = $doc; - $ilLog->write("SCORM: about to transform to SCORM 2004"); + $totransform = $doc; + $ilLog->debug("SCORM: about to transform to SCORM 2004"); $xsl = new DOMDocument(); $xsl->async = false; @@ -285,9 +276,9 @@ public function convert_1_2_to_2004(string $manifest): void $prc = new XSLTProcessor(); $r = @$prc->importStyleSheet($xsl); - file_put_contents($this->imsmanifestFile, $prc->transformToXML($this->totransform)); + file_put_contents($this->imsmanifestFile, $prc->transformToXML($totransform)); - $ilLog->write("SCORM: Transformation completed"); + $ilLog->debug("SCORM: Transformation completed"); } /** diff --git a/Modules/Scorm2004/classes/class.ilSCORM13MDImporter.php b/Modules/Scorm2004/classes/class.ilSCORM13MDImporter.php index ef5446fb956b..ad0d724f5261 100644 --- a/Modules/Scorm2004/classes/class.ilSCORM13MDImporter.php +++ b/Modules/Scorm2004/classes/class.ilSCORM13MDImporter.php @@ -48,22 +48,22 @@ public function __construct(\DOMDocument $a_manifest_dom, int $a_obj_id) public function setTitle(string $a_val): void { - $this->title = $a_val; + $this->title = ilUtil::stripSlashes($a_val); } public function getTitle(): string { - return $this->title; + return ilUtil::stripSlashes($this->title); } public function setDescription(string $a_val): void { - $this->description = $a_val; + $this->description = ilUtil::stripSlashes($a_val); } public function getDescription(): string { - return $this->description; + return ilUtil::stripSlashes($this->description); } public function import(): void @@ -130,7 +130,7 @@ public function handlerBeginTag($a_xml_parser, string $a_name, array $a_attribs) strtolower(get_class($par)) === 'ilmdannotation' or strtolower(get_class($par)) === 'ilmdclassification') { // todo -// $par->setDescriptionLanguage(new ilMDLanguageItem($a_attribs['Language'])); + // $par->setDescriptionLanguage(new ilMDLanguageItem($a_attribs['Language'])); } elseif ($this->in("general")) { $this->md_des = $par->addDescription(); $this->md_des->save(); @@ -147,11 +147,11 @@ public function handlerBeginTag($a_xml_parser, string $a_name, array $a_attribs) } break; - // todo - /*case 'Coverage': - $par =& $this->__getParent(); - $par->setCoverageLanguage(new ilMDLanguageItem($a_attribs['Language'])); - break;*/ + // todo + /*case 'Coverage': + $par =& $this->__getParent(); + $par->setCoverageLanguage(new ilMDLanguageItem($a_attribs['Language'])); + break;*/ case 'lifeCycle': $par = $this->__getParent(); @@ -165,201 +165,201 @@ public function handlerBeginTag($a_xml_parser, string $a_name, array $a_attribs) // nothing to do here break; - /*case 'metaMetadata': - $par = $this->__getParent(); - $this->md_met = $par->addMetaMetadata(); - $this->md_met->setMetaDataScheme($a_attribs['MetadataScheme']); - $this->md_met->setLanguage(new ilMDLanguageItem($a_attribs['Language'])); - $this->md_met->save(); - $this->__pushParent($this->md_met); - break;*/ - - // todo - /*case 'Contribute': - $par =& $this->__getParent(); - $this->md_con =& $par->addContribute(); - $this->md_con->setRole($a_attribs['Role']); - $this->md_con->save(); - $this->__pushParent($this->md_con); - break; - - case 'Entity': - $par =& $this->__getParent(); + /*case 'metaMetadata': + $par = $this->__getParent(); + $this->md_met = $par->addMetaMetadata(); + $this->md_met->setMetaDataScheme($a_attribs['MetadataScheme']); + $this->md_met->setLanguage(new ilMDLanguageItem($a_attribs['Language'])); + $this->md_met->save(); + $this->__pushParent($this->md_met); + break;*/ - if(strtolower(get_class($par)) == 'ilmdcontribute') - { - $this->md_ent =& $par->addEntity(); - $this->md_ent->save(); - $this->__pushParent($this->md_ent); + // todo + /*case 'Contribute': + $par =& $this->__getParent(); + $this->md_con =& $par->addContribute(); + $this->md_con->setRole($a_attribs['Role']); + $this->md_con->save(); + $this->__pushParent($this->md_con); break; - } - else - { - // single element in 'Annotation' + + case 'Entity': + $par =& $this->__getParent(); + + if(strtolower(get_class($par)) == 'ilmdcontribute') + { + $this->md_ent =& $par->addEntity(); + $this->md_ent->save(); + $this->__pushParent($this->md_ent); + break; + } + else + { + // single element in 'Annotation' + break; + } + case 'Date': break; - } - case 'Date': - break; - case 'Technical': - $par =& $this->__getParent(); - $this->md_tec =& $par->addTechnical(); - $this->md_tec->save(); - $this->__pushParent($this->md_tec); - break; + case 'Technical': + $par =& $this->__getParent(); + $this->md_tec =& $par->addTechnical(); + $this->md_tec->save(); + $this->__pushParent($this->md_tec); + break; - case 'Format': - $par =& $this->__getParent(); - $this->md_for =& $par->addFormat(); - $this->md_for->save(); - $this->__pushParent($this->md_for); - break; + case 'Format': + $par =& $this->__getParent(); + $this->md_for =& $par->addFormat(); + $this->md_for->save(); + $this->__pushParent($this->md_for); + break; - case 'Size': - break; + case 'Size': + break; - case 'Location': - $par =& $this->__getParent(); - $this->md_loc =& $par->addLocation(); - $this->md_loc->setLocationType($a_attribs['Type']); - $this->md_loc->save(); - $this->__pushParent($this->md_loc); - break; + case 'Location': + $par =& $this->__getParent(); + $this->md_loc =& $par->addLocation(); + $this->md_loc->setLocationType($a_attribs['Type']); + $this->md_loc->save(); + $this->__pushParent($this->md_loc); + break; - case 'Requirement': - $par =& $this->__getParent(); - $this->md_req =& $par->addRequirement(); - $this->md_req->save(); - $this->__pushParent($this->md_req); - break; + case 'Requirement': + $par =& $this->__getParent(); + $this->md_req =& $par->addRequirement(); + $this->md_req->save(); + $this->__pushParent($this->md_req); + break; - case 'OrComposite': - $par =& $this->__getParent(); - $this->md_orc =& $par->addOrComposite(); - $this->__pushParent($this->md_orc); - break; + case 'OrComposite': + $par =& $this->__getParent(); + $this->md_orc =& $par->addOrComposite(); + $this->__pushParent($this->md_orc); + break; - case 'Type': - break; + case 'Type': + break; - case 'OperatingSystem': - $par =& $this->__getParent(); - $par->setOperatingSystemName($a_attribs['Name']); - $par->setOperatingSystemMinimumVersion($a_attribs['MinimumVersion']); - $par->setOperatingSystemMaximumVersion($a_attribs['MaximumVersion']); - break; + case 'OperatingSystem': + $par =& $this->__getParent(); + $par->setOperatingSystemName($a_attribs['Name']); + $par->setOperatingSystemMinimumVersion($a_attribs['MinimumVersion']); + $par->setOperatingSystemMaximumVersion($a_attribs['MaximumVersion']); + break; - case 'Browser': - $par =& $this->__getParent(); - $par->setBrowserName($a_attribs['Name']); - $par->setBrowserMinimumVersion($a_attribs['MinimumVersion']); - $par->setBrowserMaximumVersion($a_attribs['MaximumVersion']); - break; + case 'Browser': + $par =& $this->__getParent(); + $par->setBrowserName($a_attribs['Name']); + $par->setBrowserMinimumVersion($a_attribs['MinimumVersion']); + $par->setBrowserMaximumVersion($a_attribs['MaximumVersion']); + break; - case 'InstallationRemarks': - $par =& $this->__getParent(); - $par->setInstallationRemarksLanguage(new ilMDLanguageItem($a_attribs['Language'])); - break; + case 'InstallationRemarks': + $par =& $this->__getParent(); + $par->setInstallationRemarksLanguage(new ilMDLanguageItem($a_attribs['Language'])); + break; - case 'OtherPlatformRequirements': - $par =& $this->__getParent(); - $par->setOtherPlatformRequirementsLanguage(new ilMDLanguageItem($a_attribs['Language'])); - break; + case 'OtherPlatformRequirements': + $par =& $this->__getParent(); + $par->setOtherPlatformRequirementsLanguage(new ilMDLanguageItem($a_attribs['Language'])); + break; - case 'Duration': - break; + case 'Duration': + break; - case 'Educational': - $par =& $this->__getParent(); - $this->md_edu =& $par->addEducational(); - $this->md_edu->setInteractivityType($a_attribs['InteractivityType']); - $this->md_edu->setLearningResourceType($a_attribs['LearningResourceType']); - $this->md_edu->setInteractivityLevel($a_attribs['InteractivityLevel']); - $this->md_edu->setSemanticDensity($a_attribs['SemanticDensity']); - $this->md_edu->setIntendedEndUserRole($a_attribs['IntendedEndUserRole']); - $this->md_edu->setContext($a_attribs['Context']); - $this->md_edu->setDifficulty($a_attribs['Difficulty']); - $this->md_edu->save(); - $this->__pushParent($this->md_edu); - break; + case 'Educational': + $par =& $this->__getParent(); + $this->md_edu =& $par->addEducational(); + $this->md_edu->setInteractivityType($a_attribs['InteractivityType']); + $this->md_edu->setLearningResourceType($a_attribs['LearningResourceType']); + $this->md_edu->setInteractivityLevel($a_attribs['InteractivityLevel']); + $this->md_edu->setSemanticDensity($a_attribs['SemanticDensity']); + $this->md_edu->setIntendedEndUserRole($a_attribs['IntendedEndUserRole']); + $this->md_edu->setContext($a_attribs['Context']); + $this->md_edu->setDifficulty($a_attribs['Difficulty']); + $this->md_edu->save(); + $this->__pushParent($this->md_edu); + break; - case 'TypicalAgeRange': - $par =& $this->__getParent(); - $this->md_typ =& $par->addTypicalAgeRange(); - $this->md_typ->setTypicalAgeRangeLanguage(new ilMDLanguageItem($a_attribs['Language'])); - $this->md_typ->save(); - $this->__pushParent($this->md_typ); - break; + case 'TypicalAgeRange': + $par =& $this->__getParent(); + $this->md_typ =& $par->addTypicalAgeRange(); + $this->md_typ->setTypicalAgeRangeLanguage(new ilMDLanguageItem($a_attribs['Language'])); + $this->md_typ->save(); + $this->__pushParent($this->md_typ); + break; - case 'TypicalLearningTime': - break; + case 'TypicalLearningTime': + break; - case 'Rights': - $par =& $this->__getParent(); - $this->md_rig =& $par->addRights(); - $this->md_rig->setCosts($a_attribs['Cost']); - $this->md_rig->setCopyrightAndOtherRestrictions($a_attribs['CopyrightAndOtherRestrictions']); - $this->md_rig->save(); - $this->__pushParent($this->md_rig); - break; + case 'Rights': + $par =& $this->__getParent(); + $this->md_rig =& $par->addRights(); + $this->md_rig->setCosts($a_attribs['Cost']); + $this->md_rig->setCopyrightAndOtherRestrictions($a_attribs['CopyrightAndOtherRestrictions']); + $this->md_rig->save(); + $this->__pushParent($this->md_rig); + break; - case 'Relation': - $par =& $this->__getParent(); - $this->md_rel =& $par->addRelation(); - $this->md_rel->setKind($a_attribs['Kind']); - $this->md_rel->save(); - $this->__pushParent($this->md_rel); - break; + case 'Relation': + $par =& $this->__getParent(); + $this->md_rel =& $par->addRelation(); + $this->md_rel->setKind($a_attribs['Kind']); + $this->md_rel->save(); + $this->__pushParent($this->md_rel); + break; - case 'Resource': - break; + case 'Resource': + break; - case 'Identifier_': - $par =& $this->__getParent(); - $this->md_ide_ =& $par->addIdentifier_(); - $this->md_ide_->setCatalog($a_attribs['Catalog']); - $this->md_ide_->setEntry($a_attribs['Entry']); - $this->md_ide_->save(); - $this->__pushParent($this->md_ide_); - break; + case 'Identifier_': + $par =& $this->__getParent(); + $this->md_ide_ =& $par->addIdentifier_(); + $this->md_ide_->setCatalog($a_attribs['Catalog']); + $this->md_ide_->setEntry($a_attribs['Entry']); + $this->md_ide_->save(); + $this->__pushParent($this->md_ide_); + break; - case 'Annotation': - $par =& $this->__getParent(); - $this->md_ann =& $par->addAnnotation(); - $this->md_ann->save(); - $this->__pushParent($this->md_ann); - break; + case 'Annotation': + $par =& $this->__getParent(); + $this->md_ann =& $par->addAnnotation(); + $this->md_ann->save(); + $this->__pushParent($this->md_ann); + break; - case 'Classification': - $par =& $this->__getParent(); - $this->md_cla =& $par->addClassification(); - $this->md_cla->setPurpose($a_attribs['Purpose']); - $this->md_cla->save(); - $this->__pushParent($this->md_cla); - break; + case 'Classification': + $par =& $this->__getParent(); + $this->md_cla =& $par->addClassification(); + $this->md_cla->setPurpose($a_attribs['Purpose']); + $this->md_cla->save(); + $this->__pushParent($this->md_cla); + break; - case 'TaxonPath': - $par =& $this->__getParent(); - $this->md_taxp =& $par->addTaxonPath(); - $this->md_taxp->save(); - $this->__pushParent($this->md_taxp); - break; + case 'TaxonPath': + $par =& $this->__getParent(); + $this->md_taxp =& $par->addTaxonPath(); + $this->md_taxp->save(); + $this->__pushParent($this->md_taxp); + break; - case 'Source': - $par =& $this->__getParent(); - $par->setSourceLanguage(new ilMDLanguageItem($a_attribs['Language'])); - break; + case 'Source': + $par =& $this->__getParent(); + $par->setSourceLanguage(new ilMDLanguageItem($a_attribs['Language'])); + break; - case 'Taxon': - $par =& $this->__getParent(); - $this->md_tax =& $par->addTaxon(); - $this->md_tax->setTaxonLanguage(new ilMDLanguageItem($a_attribs['Language'])); - $this->md_tax->setTaxonId($a_attribs['Id']); - $this->md_tax->save(); - $this->__pushParent($this->md_tax); - break; - */ + case 'Taxon': + $par =& $this->__getParent(); + $this->md_tax =& $par->addTaxon(); + $this->md_tax->setTaxonLanguage(new ilMDLanguageItem($a_attribs['Language'])); + $this->md_tax->setTaxonId($a_attribs['Id']); + $this->md_tax->save(); + $this->__pushParent($this->md_tax); + break; + */ case 'string': $par = $this->__getParent(); @@ -449,11 +449,11 @@ public function handlerEndTag($a_xml_parser, string $a_name): void } break; - // todo - /*case 'Coverage': - $par =& $this->__getParent(); - $par->setCoverage($this->__getCharacterData()); - break;*/ + // todo + /*case 'Coverage': + $par =& $this->__getParent(); + $par->setCoverage($this->__getCharacterData()); + break;*/ case 'lifeCycle': $par = $this->__getParent(); @@ -466,168 +466,168 @@ public function handlerEndTag($a_xml_parser, string $a_name): void break; - // todo - /*case 'Contribute': - $par =& $this->__getParent(); - $par->update(); - $this->__popParent(); - break; - - case 'Entity': - $par =& $this->__getParent(); - - if(strtolower(get_class($par)) == 'ilmdentity') - { - $par->setEntity($this->__getCharacterData()); + // todo + /*case 'Contribute': + $par =& $this->__getParent(); $par->update(); $this->__popParent(); - } - else - { - // Single element in 'Annotation' - $par->setEntity($this->__getCharacterData()); - } - break; + break; - case 'Date': - $par =& $this->__getParent(); - $par->setDate($this->__getCharacterData()); - break; + case 'Entity': + $par =& $this->__getParent(); - case 'Meta-Metadata': - $par =& $this->__getParent(); - $par->update(); - $this->__popParent(); - break; + if(strtolower(get_class($par)) == 'ilmdentity') + { + $par->setEntity($this->__getCharacterData()); + $par->update(); + $this->__popParent(); + } + else + { + // Single element in 'Annotation' + $par->setEntity($this->__getCharacterData()); + } + break; - case 'Technical': - $par =& $this->__getParent(); - $par->update(); - $this->__popParent(); - break; + case 'Date': + $par =& $this->__getParent(); + $par->setDate($this->__getCharacterData()); + break; - case 'Format': - $par =& $this->__getParent(); - $par->setFormat($this->__getCharacterData()); - $par->update(); - $this->__popParent(); - break; + case 'Meta-Metadata': + $par =& $this->__getParent(); + $par->update(); + $this->__popParent(); + break; - case 'Size': - $par =& $this->__getParent(); - $par->setSize($this->__getCharacterData()); - break; + case 'Technical': + $par =& $this->__getParent(); + $par->update(); + $this->__popParent(); + break; - case 'Location': - $par =& $this->__getParent(); - $par->setLocation($this->__getCharacterData()); - $par->update(); - $this->__popParent(); - break; + case 'Format': + $par =& $this->__getParent(); + $par->setFormat($this->__getCharacterData()); + $par->update(); + $this->__popParent(); + break; - case 'Requirement': - $par =& $this->__getParent(); - $par->update(); - $this->__popParent(); - break; + case 'Size': + $par =& $this->__getParent(); + $par->setSize($this->__getCharacterData()); + break; - case 'OrComposite': - $this->__popParent(); - break; + case 'Location': + $par =& $this->__getParent(); + $par->setLocation($this->__getCharacterData()); + $par->update(); + $this->__popParent(); + break; - case 'Type': - break; + case 'Requirement': + $par =& $this->__getParent(); + $par->update(); + $this->__popParent(); + break; - case 'OperatingSystem': - break; + case 'OrComposite': + $this->__popParent(); + break; - case 'Browser': - break; + case 'Type': + break; - case 'InstallationRemarks': - $par =& $this->__getParent(); - $par->setInstallationRemarks($this->__getCharacterData()); - break; + case 'OperatingSystem': + break; - case 'OtherPlatformRequirements': - $par =& $this->__getParent(); - $par->setOtherPlatformRequirements($this->__getCharacterData()); - break; + case 'Browser': + break; - case 'Duration': - $par =& $this->__getParent(); - $par->setDuration($this->__getCharacterData()); - break; + case 'InstallationRemarks': + $par =& $this->__getParent(); + $par->setInstallationRemarks($this->__getCharacterData()); + break; - case 'Educational': - $par =& $this->__getParent(); - $par->update(); - $this->__popParent(); - break; + case 'OtherPlatformRequirements': + $par =& $this->__getParent(); + $par->setOtherPlatformRequirements($this->__getCharacterData()); + break; - case 'TypicalAgeRange': - $par =& $this->__getParent(); - $par->setTypicalAgeRange($this->__getCharacterData()); - $par->update(); - $this->__popParent(); - break; + case 'Duration': + $par =& $this->__getParent(); + $par->setDuration($this->__getCharacterData()); + break; - case 'TypicalLearningTime': - $par =& $this->__getParent(); - $par->setTypicalLearningTime($this->__getCharacterData()); - break; + case 'Educational': + $par =& $this->__getParent(); + $par->update(); + $this->__popParent(); + break; - case 'Rights': - $par =& $this->__getParent(); - $par->update(); - $this->__popParent(); - break; + case 'TypicalAgeRange': + $par =& $this->__getParent(); + $par->setTypicalAgeRange($this->__getCharacterData()); + $par->update(); + $this->__popParent(); + break; - case 'Relation': - $par =& $this->__getParent(); - $par->update(); - $this->__popParent(); - break; + case 'TypicalLearningTime': + $par =& $this->__getParent(); + $par->setTypicalLearningTime($this->__getCharacterData()); + break; - case 'Resource': - break; + case 'Rights': + $par =& $this->__getParent(); + $par->update(); + $this->__popParent(); + break; - case 'Identifier_': - $par =& $this->__getParent(); - $par->update(); - $this->__popParent(); - break; + case 'Relation': + $par =& $this->__getParent(); + $par->update(); + $this->__popParent(); + break; - case 'Annotation': - $par =& $this->__getParent(); - $par->update(); - $this->__popParent(); - break; + case 'Resource': + break; - case 'Classification': - $par =& $this->__getParent(); - $par->update(); - $this->__popParent(); - break; + case 'Identifier_': + $par =& $this->__getParent(); + $par->update(); + $this->__popParent(); + break; - case 'TaxonPath': - $par =& $this->__getParent(); - $par->update(); - $this->__popParent(); - break; + case 'Annotation': + $par =& $this->__getParent(); + $par->update(); + $this->__popParent(); + break; - case 'Taxon': - $par =& $this->__getParent(); - $par->setTaxon($this->__getCharacterData()); - $par->update(); - $this->__popParent(); - break; + case 'Classification': + $par =& $this->__getParent(); + $par->update(); + $this->__popParent(); + break; - case 'Source': - $par =& $this->__getParent(); - $par->setSource($this->__getCharacterData()); - break; - */ + case 'TaxonPath': + $par =& $this->__getParent(); + $par->update(); + $this->__popParent(); + break; + + case 'Taxon': + $par =& $this->__getParent(); + $par->setTaxon($this->__getCharacterData()); + $par->update(); + $this->__popParent(); + break; + + case 'Source': + $par =& $this->__getParent(); + $par->setSource($this->__getCharacterData()); + break; + */ case 'string': $par = $this->__getParent(); diff --git a/Modules/Scorm2004/classes/class.ilSCORM13PlayerGUI.php b/Modules/Scorm2004/classes/class.ilSCORM13PlayerGUI.php index 3e4fd94be592..d181b25ee1a3 100755 --- a/Modules/Scorm2004/classes/class.ilSCORM13PlayerGUI.php +++ b/Modules/Scorm2004/classes/class.ilSCORM13PlayerGUI.php @@ -1,7 +1,5 @@ slm->getSequencing() == true) { $initSuspendData = json_decode($this->getSuspendDataInit()); $initAdlactData = json_decode($this->getADLActDataInit()); @@ -1581,6 +1582,7 @@ public function openLog(): void { global $DIC; $filename = ilUtil::stripSlashes($DIC->http()->wrapper()->query()->retrieve('logFile', $DIC->refinery()->kindlyTo()->string())); + $filename = str_replace('/', '', $filename); //Header header('Content-Type: text/html; charset=UTF-8'); echo file_get_contents($this->logDirectory() . "/" . $filename); @@ -1842,6 +1844,7 @@ public function postLogEntry(): void //delete files if ($logdata->action === "DELETE") { $filename = $logdata->value; + $filename = str_replace('/', '', $filename); $path = $this->logDirectory() . "/" . $filename; unlink($path); return; diff --git a/Modules/Scorm2004/classes/ilSCORM13Package.php b/Modules/Scorm2004/classes/ilSCORM13Package.php index 509b77afe92f..7b62a666a575 100755 --- a/Modules/Scorm2004/classes/ilSCORM13Package.php +++ b/Modules/Scorm2004/classes/ilSCORM13Package.php @@ -34,12 +34,12 @@ class ilSCORM13Package public const WRAPPER_JS = './Modules/Scorm2004/scripts/converter/GenericRunTimeWrapper1.0_aadlc/SCOPlayerWrapper.js'; -// private $packageFile; + // private $packageFile; private string $packageFolder; private string $packagesFolder; private array $packageData = []; -// private $slm; -// private $slm_tree; + // private $slm; + // private $slm_tree; public \DOMDocument $imsmanifest; /** @@ -47,13 +47,13 @@ class ilSCORM13Package */ public $manifest; public array $diagnostic; -// public $status; + // public $status; public int $packageId; public string $packageName = ""; public string $packageHash = ""; public int $userId; -// private $idmap = array(); + // private $idmap = array(); private float $progress = 0.0; /** @@ -226,7 +226,10 @@ public function il_import(string $packageFolder, int $packageId, bool $reimport $j['base'] = $packageFolder . '/'; $j['foreignId'] = floatval($x['foreignId']); // manifest cp_node_id for associating global (package wide) objectives $j['id'] = strval($x['id']); // manifest id for associating global (package wide) objectives - + $j['item']['title'] = ilUtil::stripSlashes($j['item']['title']); + for($i = 0; $i < count($j['item']['item']); $i++) { + $j['item']['item'][$i]['title'] = ilUtil::stripSlashes($j['item']['item'][$i]['title']); + } //last step - build ADL Activity tree $act = new SeqTreeBuilder(); @@ -352,35 +355,62 @@ public function dbImport(object $node, ?int &$lft = 1, ?int $depth = 1, ?int $pa foreach ($node->attributes as $attr) { switch (strtolower($attr->name)) { - case 'completionsetbycontent': $names[] = 'completionbycontent';break; - case 'objectivesetbycontent': $names[] = 'objectivebycontent';break; - case 'type': $names[] = 'c_type';break; - case 'mode': $names[] = 'c_mode';break; - case 'language': $names[] = 'c_language';break; - case 'condition': $names[] = 'c_condition';break; - case 'operator': $names[] = 'c_operator';break; -// case 'condition': $names[] = 'c_condition';break; - case 'readnormalizedmeasure': $names[] = 'readnormalmeasure';break; - case 'writenormalizedmeasure': $names[] = 'writenormalmeasure';break; - case 'minnormalizedmeasure': $names[] = 'minnormalmeasure';break; - case 'primary': $names[] = 'c_primary';break; -// case 'minnormalizedmeasure': $names[] = 'minnormalmeasure';break; - case 'persistpreviousattempts': $names[] = 'persistprevattempts';break; - case 'identifier': $names[] = 'c_identifier';break; - case 'settings': $names[] = 'c_settings';break; - case 'activityabsolutedurationlimit': $names[] = 'activityabsdurlimit';break; - case 'activityexperienceddurationlimit': $names[] = 'activityexpdurlimit';break; - case 'attemptabsolutedurationlimit': $names[] = 'attemptabsdurlimit';break; - case 'measuresatisfactionifactive': $names[] = 'measuresatisfactive';break; - case 'objectivemeasureweight': $names[] = 'objectivemeasweight';break; - case 'requiredforcompleted': $names[] = 'requiredcompleted';break; - case 'requiredforincomplete': $names[] = 'requiredincomplete';break; - case 'requiredfornotsatisfied': $names[] = 'requirednotsatisfied';break; - case 'rollupobjectivesatisfied': $names[] = 'rollupobjectivesatis';break; - case 'rollupprogresscompletion': $names[] = 'rollupprogcompletion';break; - case 'usecurrentattemptobjectiveinfo': $names[] = 'usecurattemptobjinfo';break; - case 'usecurrentattemptprogressinfo': $names[] = 'usecurattemptproginfo';break; - default: $names[] = strtolower($attr->name);break; + case 'completionsetbycontent': $names[] = 'completionbycontent'; + break; + case 'objectivesetbycontent': $names[] = 'objectivebycontent'; + break; + case 'type': $names[] = 'c_type'; + break; + case 'mode': $names[] = 'c_mode'; + break; + case 'language': $names[] = 'c_language'; + break; + case 'condition': $names[] = 'c_condition'; + break; + case 'operator': $names[] = 'c_operator'; + break; + // case 'condition': $names[] = 'c_condition';break; + case 'readnormalizedmeasure': $names[] = 'readnormalmeasure'; + break; + case 'writenormalizedmeasure': $names[] = 'writenormalmeasure'; + break; + case 'minnormalizedmeasure': $names[] = 'minnormalmeasure'; + break; + case 'primary': $names[] = 'c_primary'; + break; + // case 'minnormalizedmeasure': $names[] = 'minnormalmeasure';break; + case 'persistpreviousattempts': $names[] = 'persistprevattempts'; + break; + case 'identifier': $names[] = 'c_identifier'; + break; + case 'settings': $names[] = 'c_settings'; + break; + case 'activityabsolutedurationlimit': $names[] = 'activityabsdurlimit'; + break; + case 'activityexperienceddurationlimit': $names[] = 'activityexpdurlimit'; + break; + case 'attemptabsolutedurationlimit': $names[] = 'attemptabsdurlimit'; + break; + case 'measuresatisfactionifactive': $names[] = 'measuresatisfactive'; + break; + case 'objectivemeasureweight': $names[] = 'objectivemeasweight'; + break; + case 'requiredforcompleted': $names[] = 'requiredcompleted'; + break; + case 'requiredforincomplete': $names[] = 'requiredincomplete'; + break; + case 'requiredfornotsatisfied': $names[] = 'requirednotsatisfied'; + break; + case 'rollupobjectivesatisfied': $names[] = 'rollupobjectivesatis'; + break; + case 'rollupprogresscompletion': $names[] = 'rollupprogcompletion'; + break; + case 'usecurrentattemptobjectiveinfo': $names[] = 'usecurattemptobjinfo'; + break; + case 'usecurrentattemptprogressinfo': $names[] = 'usecurattemptproginfo'; + break; + default: $names[] = strtolower($attr->name); + break; } if (in_array( diff --git a/Modules/ScormAicc/classes/SCORM/class.ilSCORMPackageParser.php b/Modules/ScormAicc/classes/SCORM/class.ilSCORMPackageParser.php index ee7a89c08977..fc63608cf6e8 100755 --- a/Modules/ScormAicc/classes/SCORM/class.ilSCORMPackageParser.php +++ b/Modules/ScormAicc/classes/SCORM/class.ilSCORMPackageParser.php @@ -79,7 +79,7 @@ public function startParsing(): void public function getPackageTitle(): string { - return $this->package_title; + return ilUtil::stripSlashes($this->package_title); } /** @@ -194,7 +194,11 @@ public function handlerBeginTag($a_xml_parser, string $a_name, array $a_attribs) case "organizations": $organizations = new ilSCORMOrganizations(); $organizations->setSLMId($this->slm_object->getId()); - $organizations->setDefaultOrganization($a_attribs["default"]); + if (isset($a_attribs["default"])) { + $organizations->setDefaultOrganization($a_attribs["default"]); + } else { + $organizations->setDefaultOrganization(""); + } $organizations->create(); $this->sc_tree->insertNode($organizations->getId(), $this->getCurrentParent()); $this->parent_stack[] = $organizations->getId(); @@ -338,14 +342,14 @@ public function handlerCharacterData($a_xml_parser, ?string $a_data): void switch ($this->getAncestorElement(1)) { case "organization": $this->current_organization->setTitle( - $this->current_organization->getTitle() . $a_data + ilUtil::stripSlashes($this->current_organization->getTitle() . $a_data) ); - $this->package_title = $this->current_organization->getTitle(); + $this->package_title = ilUtil::stripSlashes($this->current_organization->getTitle()); break; case "item": $this->item_stack[count($this->item_stack) - 1]->setTitle( - $this->item_stack[count($this->item_stack) - 1]->getTitle() . $a_data + ilUtil::stripSlashes($this->item_stack[count($this->item_stack) - 1]->getTitle() . $a_data) ); break; } diff --git a/Modules/ScormAicc/classes/Setup/class.ilScormAiccDatabaseUpdateSteps.php b/Modules/ScormAicc/classes/Setup/class.ilScormAiccDatabaseUpdateSteps.php new file mode 100755 index 000000000000..182db7ada78f --- /dev/null +++ b/Modules/ScormAicc/classes/Setup/class.ilScormAiccDatabaseUpdateSteps.php @@ -0,0 +1,35 @@ +db = $db; + } + + public function step_1(): void + { + $this->db->modifyTableColumn("cp_manifest", "defaultorganization", array("type" => "text", "length" => 255, "notnull" => false, 'default' => null)); + } + +} diff --git a/Modules/ScormAicc/classes/Setup/class.ilScormAiccSetupAgent.php b/Modules/ScormAicc/classes/Setup/class.ilScormAiccSetupAgent.php new file mode 100755 index 000000000000..2bea4c96a4eb --- /dev/null +++ b/Modules/ScormAicc/classes/Setup/class.ilScormAiccSetupAgent.php @@ -0,0 +1,68 @@ +access(); $ilErr = $DIC['ilErr']; + $ilLog = ilLoggerFactory::getLogger('sahs'); $refId = $DIC->http()->wrapper()->query()->retrieve('ref_id', $DIC->refinery()->kindlyTo()->int()); $importFromXml = false; @@ -384,6 +385,8 @@ public function uploadObject(): void $name = $this->lng->txt("no_title"); } + $description = ""; + $subType = "scorm2004"; if ($DIC->http()->wrapper()->post()->has('sub_type')) { $subType = $DIC->http()->wrapper()->post()->retrieve('sub_type', $DIC->refinery()->kindlyTo()->string()); @@ -398,9 +401,6 @@ public function uploadObject(): void switch ($subType) { case "scorm2004": $newObj = new ilObjSCORM2004LearningModule(); - // $newObj->setEditable(false);//$_POST["editable"] == 'y'); - // $newObj->setImportSequencing($_POST["import_sequencing"]); - // $newObj->setSequencingExpertMode($_POST["import_sequencing"]); break; case "scorm": @@ -412,35 +412,35 @@ public function uploadObject(): void $fType = $sFile["type"]; $cFileTypes = ["application/zip", "application/x-compressed","application/x-zip-compressed"]; if (in_array($fType, $cFileTypes)) { - $timeStamp = time(); $tempFile = $sFile["tmp_name"]; - $lmDir = ilFileUtils::getWebspaceDir("filesystem") . "/lm_data/"; - $lmTempDir = $lmDir . $timeStamp; - if (!file_exists($lmTempDir)) { - if (!mkdir($lmTempDir, 0755, true) && !is_dir($lmTempDir)) { - throw new \RuntimeException(sprintf('Directory "%s" was not created', $lmTempDir)); - } - } + $lmTempDir = ilFileUtils::ilTempnam(); + ilFileUtils::makeDir($lmTempDir); $zar = new ZipArchive(); $zar->open($tempFile); $zar->extractTo($lmTempDir); $zar->close(); + ilFileUtils::renameExecutables($lmTempDir); $importer = new ilScormAiccImporter(); $import_dirname = $lmTempDir . '/' . substr($_FILES["scormfile"]["name"], 0, -4); $importer->importXmlRepresentation("sahs", "", $import_dirname, null); + $import_result = $importer->getResult(); + $importFromXml = true; - // if ($importer->importXmlRepresentation("sahs", "", $import_dirname, null) == true) { - // $importFromXml = true; - // } - $mprops = $importer->moduleProperties; - $subType = (string) $mprops["SubType"]; - if ($subType === "scorm") { - $newObj = new ilObjSCORMLearningModule(); + if ($import_result->isOK()) { + $properties = $import_result->value(); + if (($subType = $properties['SubType']) === 'scorm') { + $newObj = new ilObjSCORMLearningModule(); + } else { + $newObj = new ilObjSCORM2004LearningModule(); + } + $name = $properties['Title']; + $description = $properties['Description']; } else { - $newObj = new ilObjSCORM2004LearningModule(); - // $newObj->setEditable($_POST["editable"]=='y'); - // $newObj->setImportSequencing($_POST["import_sequencing"]); - // $newObj->setSequencingExpertMode($_POST["import_sequencing"]); + ilFileUtils::delDir($lmTempDir, false); + $ilLog->error('SCORM import of ILIAS exportfile not possible because parsing error'); + $ilLog->error($import_result->error()); + $this->tpl->setOnScreenMessage('failure', $this->lng->txt("import_file_not_valid"), true); + return; } } break; @@ -448,7 +448,7 @@ public function uploadObject(): void $newObj->setTitle($name); $newObj->setSubType($subType); - $newObj->setDescription(""); + $newObj->setDescription($description); $newObj->setOfflineStatus(false); $newObj->create(true); $newObj->createReference(); diff --git a/Modules/ScormAicc/classes/class.ilObjSCORMLearningModuleGUI.php b/Modules/ScormAicc/classes/class.ilObjSCORMLearningModuleGUI.php index 5f05bbc296bc..cfbcee94aa76 100755 --- a/Modules/ScormAicc/classes/class.ilObjSCORMLearningModuleGUI.php +++ b/Modules/ScormAicc/classes/class.ilObjSCORMLearningModuleGUI.php @@ -1,7 +1,5 @@ + */ + private array $module_properties = []; public function __construct() { $this->dataset = new ilScormAiccDataSet(); - //todo: at the moment restricted to one module in xml file, extend? - $this->moduleProperties = []; - //$this->manifest = []; + $this->df = new DataTypeFactory(); + $this->initResult(); + + parent::__construct(); + } + + private function initResult(): void + { + $this->publishResult($this->df->error('No XML parsed, yet')); + $this->module_properties = []; + } + + private function publishResult(\ILIAS\Data\Result $result): \ILIAS\Data\Result + { + $this->result = $result; + return $this->result; + } + + /** + * @return \ILIAS\Data\Result|\ILIAS\Data\Result\Ok> + */ + public function getResult(): \ILIAS\Data\Result + { + return $this->result; } public function init(): void @@ -35,182 +64,168 @@ public function init(): void } /** - * Import XML * @throws ilDatabaseException * @throws ilFileUtilsException * @throws ilObjectNotFoundException */ - public function importXmlRepresentation(string $a_entity, string $a_id, string $a_import_dirname, ?ilImportMapping $a_mapping): void - { + public function importXmlRepresentation( + string $a_entity, + string $a_id, + string $a_xml, + ?ilImportMapping $a_mapping + ): void { global $DIC; - $ilLog = ilLoggerFactory::getLogger('sahs'); - - // if ($this->handleEditableLmXml($a_entity, $a_id, $a_xml, $a_mapping)) { - // return; - // } - // case i container - if ($a_id !== "" && $a_mapping !== null && $new_id = $a_mapping->getMapping('Services/Container', 'objs', $a_id)) { - $newObj = ilObjectFactory::getInstanceByObjId((int) $new_id, false); - // $exportDir = ilExport::_getExportDirectory((int) $a_id); - // $tempFile = dirname($exportDir) . '/export/' . basename($this->getImportDirectory()) . '.zip'; - // $timeStamp = time(); - // $lmDir = ilFileUtils::getWebspaceDir("filesystem") . "/lm_data/"; - // $lmTempDir = $lmDir . $timeStamp; - // if (!file_exists($lmTempDir)) { - // if (!mkdir($lmTempDir, 0755, true) && !is_dir($lmTempDir)) { - // throw new \RuntimeException(sprintf('Directory "%s" was not created', $lmTempDir)); - // } - // } - // $zar = new ZipArchive(); - // $zar->open($tempFile); - // $zar->extractTo($lmTempDir); - // $zar->close(); - // $a_xml = $lmTempDir . '/' . basename($this->getImportDirectory()); - $a_import_dirname = $this->getImportDirectory(); - } - - - - $result = false; - if (file_exists($a_import_dirname)) { - $manifestFile = $a_import_dirname . "/manifest.xml"; - if (file_exists($manifestFile)) { - $manifest = file_get_contents($manifestFile); - if (isset($manifest)) { - $propertiesFile = $a_import_dirname . "/properties.xml"; - $xml = file_get_contents($propertiesFile); - - if (isset($xml)) { - $xmlRoot = simplexml_load_string($xml); - - foreach ($this->dataset->properties as $key => $value) { - $this->moduleProperties[$key] = $xmlRoot->$key; - } - $this->moduleProperties["Title"] = $xmlRoot->Title; - $this->moduleProperties["Description"] = $xmlRoot->Description; - foreach ($this->moduleProperties as $key => $xmlRoot) { - $xmlRootValue = $xmlRoot->__toString(); - $filteredValue = preg_replace('%\s%', '', $xmlRootValue); - $this->moduleProperties[$key] = $filteredValue; + $this->initResult(); + + $xml_directory = $a_xml; + $new_object = null; + + $this->publishResult( + $this->df + ->ok('Parsing started') + ->then( + function (string $message) use ( + &$new_object, + &$xml_directory, + $a_id, + $a_mapping + ): ?\ILIAS\Data\Result { + if ($a_id !== '' && + $a_mapping !== null && + ($new_id = $a_mapping->getMapping('Services/Container', 'objs', $a_id))) { + $new_object = ilObjectFactory::getInstanceByObjId((int) $new_id, false); + $xml_directory = $this->getImportDirectory(); } - if ($a_id != null && $new_id = $a_mapping->getMapping('Services/Container', 'objs', $a_id)) { - $this->dataset->writeData("sahs", "5.1.0", $newObj->getId(), $this->moduleProperties); - - $newObj->createReference(); - - $scormFile = "content.zip"; - $scormFilePath = $a_import_dirname . "/" . $scormFile; - $targetPath = $newObj->getDataDirectory() . "/" . $scormFile; - $file_path = $targetPath; - - ilFileUtils::rename($scormFilePath, $targetPath); - ilFileUtils::unzip($file_path); - unlink($file_path); - // ilUtil::delDir($lmTempDir, false); - // $ilLog->write($scormFilePath.'----'. $targetPath); - ilFileUtils::renameExecutables($newObj->getDataDirectory()); - - $newId = $newObj->getRefId(); - // $newObj->putInTree($newId); - // $newObj->setPermissions($newId); - $subType = $this->moduleProperties["SubType"]; - if ($subType == "scorm") { - $newObj = new ilObjSCORMLearningModule($newId); - } else { - $newObj = new ilObjSCORM2004LearningModule($newId); + return $this->df->ok($xml_directory); + } + ) + ->then(function (string $xml_directory): ?\ILIAS\Data\Result { + if (!is_dir($xml_directory)) { + return $this->df->error( + sprintf('Directory lost while importing: %s', $xml_directory) + ); + } + + return null; + }) + ->then(function (string $xml_directory): ?\ILIAS\Data\Result { + $manifest_file = $xml_directory . '/manifest.xml'; + if (!file_exists($manifest_file)) { + return $this->df->error( + sprintf( + 'No manifest file found in import directory "%s": %s', + $xml_directory, + $manifest_file + ) + ); + } + + return $this->df->ok($manifest_file); + }) + ->then(function (string $manifest_file): ?\ILIAS\Data\Result { + $manifest_file_content = file_get_contents($manifest_file); + if (!is_string($manifest_file_content) || $manifest_file_content === '') { + return $this->df->error( + sprintf( + 'Could not read content from manifest file: %s', + $manifest_file + ) + ); + } + + return $this->df->ok($manifest_file_content); + }) + ->then(function (string $manifest_file_content) use ($xml_directory): ?\ILIAS\Data\Result { + $properties_file = $xml_directory . '/properties.xml'; + $properties_file_content = file_get_contents($properties_file); + if (!is_string($properties_file_content) || $properties_file_content === '') { + return $this->df->error( + sprintf( + 'Could not read file: %s', + $properties_file + ) + ); + } + + return $this->df->ok($properties_file_content); + }) + ->then(function (string $properties_file_content): ?\ILIAS\Data\Result { + return (new ilScormImportParser($this->df))->parse($properties_file_content); + }) + ->then( + function (SimpleXMLElement $properties_xml_doc): ?\ILIAS\Data\Result { + try { + foreach ($this->dataset->properties as $key => $value) { + $this->module_properties[$key] = $properties_xml_doc->{$key}; } - $title = $newObj->readObject(); - //auto set learning progress settings - $newObj->setLearningProgressSettingsAtUpload(); - } + $this->module_properties['Title'] = $properties_xml_doc->Title; + $this->module_properties['Description'] = $properties_xml_doc->Description; + + foreach ($this->module_properties as $key => $property_node) { + $property_value = $property_node->__toString(); + $filteredValue = preg_replace('%\s%', '', $property_value); + $this->module_properties[$key] = ilUtil::stripSlashes($filteredValue); + } - $result = true; - } else { - $ilLog->write("error parsing xml file for scorm import"); - //error xml parsing + return $this->df->ok($this->module_properties); + } catch (Exception $exception) { + return $this->df->error($exception); + } } - } else { - $ilLog->write("error reading manifest file"); - } - } else { - $ilLog->write("error no manifest file found"); - } - } else { - $ilLog->write("error file lost while importing"); - //error non existing file - } - // - // if (file_exists($a_xml)) { - // $manifestFile = $a_xml . "/manifest.xml"; - // if (file_exists($manifestFile)) { - // $manifest = file_get_contents($manifestFile); - // if (isset($manifest)) { - // $propertiesFile = $a_xml . "/properties.xml"; - // $xml = file_get_contents($propertiesFile); - // if (isset($xml)) { - // $xmlRoot = simplexml_load_string($xml); - // foreach ($this->dataset->properties as $key => $value) { - // $this->moduleProperties[$key] = $xmlRoot->$key; - // } - // $this->moduleProperties["Title"] = $xmlRoot->Title; - // $this->moduleProperties["Description"] = $xmlRoot->Description; - // - // if ($a_id !== "" && $a_mapping !== null && $new_id = $a_mapping->getMapping('Services/Container', 'objs', $a_id)) { - // if ($newObj !== null) { - // $this->dataset->writeData("sahs", "5.1.0", $newObj->getId(), $this->moduleProperties); - // - // $newObj->createReference(); - // - // $scormFile = "content.zip"; - // $scormFilePath = $a_xml . "/" . $scormFile; - // $targetPath = $newObj->getDataDirectory() . "/" . $scormFile; - // $file_path = $targetPath; - // - // ilFileUtils::rename($scormFilePath, $targetPath); - // ilFileUtils::unzip($file_path); - // unlink($file_path); - // ilFileUtils::delDir($lmTempDir, false); - // ilFileUtils::renameExecutables($newObj->getDataDirectory()); - // - // $newId = $newObj->getRefId(); - // // $newObj->putInTree($newId); - // // $newObj->setPermissions($newId); - // $subType = $this->moduleProperties["SubType"][0]; - // if ($subType === "scorm") { - // $newObj = new ilObjSCORMLearningModule($newId); - // } else { - // $newObj = new ilObjSCORM2004LearningModule($newId); - // } - // $title = $newObj->readObject(); - // //auto set learning progress settings - // $newObj->setLearningProgressSettingsAtUpload(); - // } - // } - // - // - // $result = true; - // } else { - // $ilLog->write("error parsing xml file for scorm import"); - // //error xml parsing - // } - // } else { - // $ilLog->write("error reading manifest file"); - // } - // } else { - // $ilLog->write("error no manifest file found"); - // } - // } else { - // $ilLog->write("error file lost while importing"); - // //error non existing file - // } + )->then(function (array $module_properties) use ( + $xml_directory, + $a_id, + $a_mapping, + $new_object + ): ?\ILIAS\Data\Result { + if ($a_id !== '' && + $a_mapping !== null && + ($new_id = $a_mapping->getMapping( + 'Services/Container', + 'objs', + $a_id + ))) { + $this->dataset->writeData( + 'sahs', + '5.1.0', + $new_object->getId(), + $this->module_properties + ); + + $new_object->createReference(); + + $scormFile = 'content.zip'; + $scormFilePath = $xml_directory . '/' . $scormFile; + $targetPath = $new_object->getDataDirectory() . '/' . $scormFile; + $file_path = $targetPath; + + ilFileUtils::rename($scormFilePath, $targetPath); + ilFileUtils::unzip($file_path); + unlink($file_path); + ilFileUtils::renameExecutables($new_object->getDataDirectory()); + + $new_ref_id = $new_object->getRefId(); + $subType = $module_properties['SubType']; + if ($subType === 'scorm') { + $new_object = new ilObjSCORMLearningModule($new_ref_id); + } else { + $new_object = new ilObjSCORM2004LearningModule($new_ref_id); + } + + $title = $new_object->readObject(); + $new_object->setLearningProgressSettingsAtUpload(); + } + + return null; + }) + ); } public function writeData(string $a_entity, string $a_version, int $a_id): void { - $this->dataset->writeData($a_entity, $a_version, $a_id, $this->moduleProperties); + $this->dataset->writeData($a_entity, $a_version, $a_id, $this->module_properties); } - } diff --git a/Modules/ScormAicc/classes/class.ilScormImportParser.php b/Modules/ScormAicc/classes/class.ilScormImportParser.php new file mode 100644 index 000000000000..0743c5fea874 --- /dev/null +++ b/Modules/ScormAicc/classes/class.ilScormImportParser.php @@ -0,0 +1,120 @@ + */ + private array $error_stack = []; + private DataTypeFactory $df; + + public function __construct(DataTypeFactory $data_factory) + { + $this->df = $data_factory; + } + + private function formatError(LibXMLError $error): string + { + return implode(',', [ + 'level=' . $error->level, + 'code=' . $error->code, + 'line=' . $error->line, + 'col=' . $error->column, + 'msg=' . trim($error->message) + ]); + } + + private function formatErrors(LibXMLError ...$errors): string + { + $text = ''; + foreach ($errors as $error) { + $text .= $this->formatError($error) . "\n"; + } + + return $text; + } + + private function beginLogging(): void + { + if ([] === $this->error_stack) { + $this->xml_error_state = libxml_use_internal_errors(true); + libxml_clear_errors(); + } else { + $this->addErrors(); + } + + $this->error_stack[] = []; + } + + private function addErrors(): void + { + $currentErrors = libxml_get_errors(); + libxml_clear_errors(); + + $level = count($this->error_stack) - 1; + $this->error_stack[$level] = array_merge($this->error_stack[$level], $currentErrors); + } + + /** + * @return LibXMLError[] An array with the LibXMLErrors which has occurred since beginLogging() was called. + */ + private function endLogging(): array + { + $this->addErrors(); + + $errors = array_pop($this->error_stack); + + if ([] === $this->error_stack) { + libxml_use_internal_errors($this->xml_error_state); + } + + return $errors; + } + + public function parse(string $xmlString): \ILIAS\Data\Result + { + try { + $this->beginLogging(); + + $xml = new SimpleXMLElement($xmlString); + + $errors = $this->endLogging(); + + if ($xml->xpath('//SubType')) { + return $this->df->ok($xml); + } + + $error = new LibXMLError(); + $error->level = LIBXML_ERR_FATAL; + $error->code = 0; + $error->message = 'No "SubType" element found'; + $error->line = 1; + $error->column = 0; + + $errors[] = $error; + + return $this->df->error($this->formatErrors(...$errors)); + } catch (Throwable $e) { + return $this->df->error($this->formatErrors(...$this->endLogging())); + } + } +} diff --git a/Modules/Session/classes/class.ilSessionOverviewTableGUI.php b/Modules/Session/classes/class.ilSessionOverviewTableGUI.php index 43ab86f896ef..043b66a4e2c0 100644 --- a/Modules/Session/classes/class.ilSessionOverviewTableGUI.php +++ b/Modules/Session/classes/class.ilSessionOverviewTableGUI.php @@ -1,7 +1,5 @@ setFormAction($this->ctrl->getFormAction($a_parent_obj, $a_parent_cmd)); + $this->setTitle($this->lng->txt('event_overview')); $this->addColumn($this->lng->txt('name'), 'name'); diff --git a/Modules/StudyProgramme/classes/class.ilObjStudyProgramme.php b/Modules/StudyProgramme/classes/class.ilObjStudyProgramme.php index e82c46882949..77d2b5207f75 100644 --- a/Modules/StudyProgramme/classes/class.ilObjStudyProgramme.php +++ b/Modules/StudyProgramme/classes/class.ilObjStudyProgramme.php @@ -1,7 +1,5 @@ getAssignments()) > 0; + $filter = new ilPRGAssignmentFilter($this->lng); + $count = $this->assignment_repository->countAllForNodeIsContained( + $this->getId(), + null, + $filter + ); + return $count > 0; + } /** @@ -1146,12 +1153,16 @@ public function addMissingProgresses(): void */ public function hasRelevantProgresses(): bool { - $assignments = $this->getAssignments(); - $relevant = array_filter( - $assignments, - fn ($ass) => $ass->getProgressForNode($this->getId())->isRelevant() + $filter = new ilPRGAssignmentFilter($this->lng); + $filter = $filter->withValues([ + 'prg_status_hide_irrelevant'=> true + ]); + $count = $this->assignment_repository->countAllForNodeIsContained( + $this->getId(), + null, + $filter ); - return count($relevant) > 0; + return $count > 0; } public function getIdsOfUsersWithRelevantProgress(): array @@ -1487,14 +1498,6 @@ public static function setProgressesCompletedFor(int $obj_id, int $user_id): voi // We only use courses via crs_refs $type = ilObject::_lookupType($obj_id); if ($type === "crsr") { - require_once("Services/ContainerReference/classes/class.ilContainerReference.php"); - $crs_reference_obj_ids = ilContainerReference::_lookupSourceIds($obj_id); - foreach ($crs_reference_obj_ids as $crs_reference_obj_id) { - foreach (ilObject::_getAllReferences($crs_reference_obj_id) as $ref_id) { - self::setProgressesCompletedIfParentIsProgrammeInLPCompletedMode($ref_id, $crs_reference_obj_id, $user_id); - } - } - } else { foreach (ilObject::_getAllReferences($obj_id) as $ref_id) { self::setProgressesCompletedIfParentIsProgrammeInLPCompletedMode($ref_id, $obj_id, $user_id); } diff --git a/Modules/StudyProgramme/classes/class.ilObjStudyProgrammeGUI.php b/Modules/StudyProgramme/classes/class.ilObjStudyProgrammeGUI.php index cc8de0366556..01136599bf1c 100644 --- a/Modules/StudyProgramme/classes/class.ilObjStudyProgrammeGUI.php +++ b/Modules/StudyProgramme/classes/class.ilObjStudyProgrammeGUI.php @@ -36,6 +36,7 @@ * @ilCtrl_Calls ilObjStudyProgrammeGUI: ilObjectTranslationGUI * @ilCtrl_Calls ilObjStudyProgrammeGUI: ilCertificateGUI * @ilCtrl_Calls ilObjStudyProgrammeGUI: ilObjStudyProgrammeAutoCategoriesGUI + * @ilCtrl_Calls ilObjStudyProgrammeGUI: ilPropertyFormGUI */ class ilObjStudyProgrammeGUI extends ilContainerGUI { @@ -229,7 +230,24 @@ public function executeCommand(): void $output_gui = $guiFactory->create($this->object); $this->ctrl->forwardCommand($output_gui); break; - + case strtolower(ilPropertyFormGUI::class): + /* + * Only used for async loading of the repository tree in custom md + * internal links (see #28060, #37974). This is necessary since StudyProgrammes don't + * use ilObjectMetaDataGUI. + */ + $form = $this->initAdvancedSettingsForm(); + $gui = new ilAdvancedMDRecordGUI( + ilAdvancedMDRecordGUI::MODE_EDITOR, + 'prg', + $this->object->getId(), + 'prg_type', + $this->object->getSettings()->getTypeSettings()->getTypeId() + ); + $gui->setPropertyForm($form); + $gui->parse(); + $this->ctrl->forwardCommand($form); + break; case false: $this->getSubTabs($cmd); switch ($cmd) { diff --git a/Modules/StudyProgramme/classes/class.ilObjStudyProgrammeListGUI.php b/Modules/StudyProgramme/classes/class.ilObjStudyProgrammeListGUI.php index 5280cdf5de75..bbf00787dbca 100644 --- a/Modules/StudyProgramme/classes/class.ilObjStudyProgrammeListGUI.php +++ b/Modules/StudyProgramme/classes/class.ilObjStudyProgrammeListGUI.php @@ -1,7 +1,5 @@ getAssignments(); - if ($this->getCheckboxStatus() && count($assignments) > 0) { + if ($this->getCheckboxStatus() && $prg->hasAssignments()) { $this->setAdditionalInformation($this->lng->txt("prg_can_not_manage_in_repo")); $this->enableCheckbox(false); } else { diff --git a/Modules/StudyProgramme/classes/model/Assignments/PRGAssignmentRepository.php b/Modules/StudyProgramme/classes/model/Assignments/PRGAssignmentRepository.php index 62969c7d7d40..1ab49ac567bf 100644 --- a/Modules/StudyProgramme/classes/model/Assignments/PRGAssignmentRepository.php +++ b/Modules/StudyProgramme/classes/model/Assignments/PRGAssignmentRepository.php @@ -1,7 +1,5 @@ internal() ->gui() ->print(); + $this->sequence_manager = $DIC->survey()->internal()->domain()->sequence( + $this->object->getSurveyId(), + $this->object + ); } public function setRequestedPgov(string $pgov): void diff --git a/Modules/Survey/Editing/class.ilSurveyQuestionTableGUI.php b/Modules/Survey/Editing/class.ilSurveyQuestionTableGUI.php index 327f38f74cf3..a79e6971f5a3 100644 --- a/Modules/Survey/Editing/class.ilSurveyQuestionTableGUI.php +++ b/Modules/Survey/Editing/class.ilSurveyQuestionTableGUI.php @@ -163,7 +163,7 @@ protected function importData(): void $table_data[$id]["pool"] = $questionpools[$original_fi]; } else { // #11186 - $table_data[$id]["pool"] = $this->lng->txt("status_no_permission"); + $table_data[$id]["pool"] = "-"; } } diff --git a/Modules/Survey/Evaluation/class.ilSurveyEvaluationGUI.php b/Modules/Survey/Evaluation/class.ilSurveyEvaluationGUI.php index 082cad922567..630572bc0f35 100644 --- a/Modules/Survey/Evaluation/class.ilSurveyEvaluationGUI.php +++ b/Modules/Survey/Evaluation/class.ilSurveyEvaluationGUI.php @@ -494,7 +494,7 @@ protected function exportResultsDetailsExcel( $row_results[1], $excel_row, $a_eval->getExportGrid($row_results[1]), - is_array($texts[$row_title]) + is_array($texts[$row_title] ?? false) ? array("" => $texts[$row_title]) : null ); @@ -1213,17 +1213,13 @@ public function competenceEval(): void $sskill = new ilSurveySkill($survey); $self_levels = array(); foreach ($sskill->determineSkillLevelsForAppraisee($appr_id, true) as $sl) { - $self_levels[$sl["base_skill_id"]][$sl["tref_id"]] = $sl["new_level_id"]; + $self_levels[$sl["base_skill_id"]][$sl["tref_id"]] = $sl["new_level_id"] ?? 0; } $pskills_gui->setGapAnalysisSelfEvalLevels($self_levels); } $html = $pskills_gui->getGapAnalysisHTML($appr_id); } else { // must be all survey competences - #23743 - if ($survey->getMode() !== ilObjSurvey::MODE_SELF_EVAL && - $survey->getMode() !== ilObjSurvey::MODE_IND_FEEDB) { - $pskills_gui->setGapAnalysisActualStatusModePerObject($survey->getId(), $lng->txt("skmg_eval_type_1")); - } + $pskills_gui->setGapAnalysisActualStatusModePerObject($survey->getId(), $lng->txt("skmg_eval_type_1")); if ($survey->getFinishedIdForAppraiseeIdAndRaterId($appr_id, $appr_id) > 0) { $sskill = new ilSurveySkill($survey); $self_levels = array(); diff --git a/Modules/Survey/Export/class.ilSurveyExporter.php b/Modules/Survey/Export/class.ilSurveyExporter.php index 7003b925e5e7..a15b63f31c8c 100644 --- a/Modules/Survey/Export/class.ilSurveyExporter.php +++ b/Modules/Survey/Export/class.ilSurveyExporter.php @@ -47,6 +47,15 @@ public function getXmlRepresentation( // Unzip, since survey deletes this dir ilFileUtils::unzip($zip); + // unzip does not extract the included directory + // Modules/Survey/set_1 anymore (since 7/2023) + $missing = $svy_exp->export_dir . "/" . $svy_exp->subdir . + "/Modules/Survey/set_1"; + ilFileUtils::makeDirParents($missing); + + // here: svy_data/svy_301/export/1698817474__0__svy_301 + // svy_301/export/1698817474__0__svy_301/Modules/Survey/set_1 + // svy_data/svy_301/export/1698817474__0__svy_301.zip $GLOBALS['ilLog']->write(__METHOD__ . ': Created zip file ' . $zip); return ""; } else { diff --git a/Modules/Survey/Participants/class.InvitationsDBRepository.php b/Modules/Survey/Participants/class.InvitationsDBRepository.php index a87f8aa9487f..e459819561aa 100644 --- a/Modules/Survey/Participants/class.InvitationsDBRepository.php +++ b/Modules/Survey/Participants/class.InvitationsDBRepository.php @@ -59,6 +59,18 @@ public function remove(int $survey_id, int $user_id): void ); } + public function removeAll(int $survey_id): void + { + $db = $this->db; + + $db->manipulateF( + "DELETE FROM svy_invitation WHERE " . + " survey_id = %s", + ["integer"], + [$survey_id] + ); + } + /** * Add invitation diff --git a/Modules/Survey/Participants/class.InvitationsManager.php b/Modules/Survey/Participants/class.InvitationsManager.php index f4c405f22fea..48d0a019722c 100644 --- a/Modules/Survey/Participants/class.InvitationsManager.php +++ b/Modules/Survey/Participants/class.InvitationsManager.php @@ -55,6 +55,12 @@ public function remove( $this->repo->remove($survey_id, $user_id); } + public function removeAll( + int $survey_id + ): void { + $this->repo->removeAll($survey_id); + } + /** * Add invitation diff --git a/Modules/Survey/Sequence/SequenceDBRepository.php b/Modules/Survey/Sequence/SequenceDBRepository.php new file mode 100644 index 000000000000..15a92ee8bf1c --- /dev/null +++ b/Modules/Survey/Sequence/SequenceDBRepository.php @@ -0,0 +1,64 @@ +db = $db; + $this->data = $data; + } + + protected function count(int $survey_id): int + { + $set = $this->db->queryF( + "SELECT count(*) cnt FROM svy_svy_qst " . + " WHERE survey_fi = %s ", + ["integer"], + [$survey_id] + ); + if ($rec = $this->db->fetchAssoc($set)) { + return (int) $rec["cnt"]; + } + return 0; + } + + public function insert(int $survey_id, int $svy_question_id): int + { + $order_nr = $this->count($survey_id); + $next_id = $this->db->nextId('svy_svy_qst'); + $this->db->manipulateF( + "INSERT INTO svy_svy_qst (survey_question_id, survey_fi," . + "question_fi, sequence, tstamp) VALUES (%s, %s, %s, %s, %s)", + array('integer', 'integer', 'integer', 'integer', 'integer'), + array($next_id, $survey_id, $svy_question_id, $order_nr, time()) + ); + return $next_id; + } +} diff --git a/Modules/Survey/Sequence/SequenceManager.php b/Modules/Survey/Sequence/SequenceManager.php new file mode 100644 index 000000000000..d85180c8b1ef --- /dev/null +++ b/Modules/Survey/Sequence/SequenceManager.php @@ -0,0 +1,73 @@ +question_repo = $repo->sequence(); + $this->domain = $domain; + $this->survey_id = $survey_id; // not object id + $this->log = $domain->log(); + $this->survey = $survey; + } + + public function appendQuestion( + int $survey_question_id, + bool $duplicate = true, + bool $force_duplicate = false + ): int { + $this->log->debug("append question, id: " . $survey_question_id . ", duplicate: " . $duplicate . ", force: " . $force_duplicate); + + // create duplicate if pool question (or forced for question blocks copy) + if ($duplicate) { + // this does nothing if this is not a pool question and $a_force_duplicate is false + $survey_question_id = $this->survey->duplicateQuestionForSurvey($survey_question_id, $force_duplicate); + } + + // check if question is not already in the survey, see #22018 + if ($this->survey->isQuestionInSurvey($survey_question_id)) { + return $survey_question_id; + } + + // append to survey + $next_id = $this->question_repo->insert($this->survey_id, $survey_question_id); + + $this->log->debug("insert svy_svy_qst, id: " . $next_id . ", qfi: " . $survey_question_id); + + return $survey_question_id; + } + +} diff --git a/Modules/Survey/Service/class.InternalDomainService.php b/Modules/Survey/Service/class.InternalDomainService.php index 19b5fd40174b..58bbc871d377 100644 --- a/Modules/Survey/Service/class.InternalDomainService.php +++ b/Modules/Survey/Service/class.InternalDomainService.php @@ -25,6 +25,7 @@ use ILIAS\Survey\Code\CodeManager; use ILIAS\Repository\GlobalDICDomainServices; use ILIAS\Survey\Editing\EditManager; +use ILIAS\Survey\Sequence\SequenceManager; /** * Survey internal domain service @@ -56,6 +57,11 @@ public function __construct( $this->mode_factory = $mode_factory; } + public function log(): \ilLogger + { + return $this->logger()->svy(); + } + public function modeFeatureConfig(int $mode): FeatureConfig { $mode_provider = $this->mode_factory->getModeById($mode); @@ -122,4 +128,15 @@ public function edit(): EditManager $this ); } + + public function sequence(int $survey_id, \ilObjSurvey $survey): SequenceManager + { + return new SequenceManager( + $this->repo_service, + $this, + $survey_id, + $survey + ); + } + } diff --git a/Modules/Survey/Service/class.InternalRepoService.php b/Modules/Survey/Service/class.InternalRepoService.php index 13c88d414ca3..709c648b4058 100644 --- a/Modules/Survey/Service/class.InternalRepoService.php +++ b/Modules/Survey/Service/class.InternalRepoService.php @@ -80,4 +80,12 @@ public function evaluation(): Evaluation\EvaluationSessionRepo { return new Evaluation\EvaluationSessionRepo(); } + + public function sequence(): Sequence\SequenceDBRepository + { + return new Sequence\SequenceDBRepository( + $this->data, + $this->db + ); + } } diff --git a/Modules/Survey/Settings/class.SettingsFormGUI.php b/Modules/Survey/Settings/class.SettingsFormGUI.php index 7fdb99135cbb..4639f29ec056 100644 --- a/Modules/Survey/Settings/class.SettingsFormGUI.php +++ b/Modules/Survey/Settings/class.SettingsFormGUI.php @@ -495,8 +495,8 @@ public function withAfterEnd( $txt[] = "[" . strtoupper($placeholder) . "]: " . $lng->txt($caption); } $txt = implode("
", $txt); - $participantdatainfo = new \ilNonEditableValueGUI($lng->txt("mailparticipantdata_placeholder"), "", true); - $participantdatainfo->setValue($txt); + $participantdatainfo = new \ilNonEditableValueGUI($lng->txt("svy_placeholders_label"), "", true); + $participantdatainfo->setValue($lng->txt("mailparticipantdata_placeholder") . "
" . $txt); $mailnotification->addSubItem($mailaddresses); $mailnotification->addSubItem($participantdata); diff --git a/Modules/Survey/Skills/class.ilSurveySkillThresholds.php b/Modules/Survey/Skills/class.ilSurveySkillThresholds.php index a24d66934847..5623ae899ede 100644 --- a/Modules/Survey/Skills/class.ilSurveySkillThresholds.php +++ b/Modules/Survey/Skills/class.ilSurveySkillThresholds.php @@ -75,4 +75,22 @@ public function writeThreshold( array("threshold" => array("integer", $a_threshold)) ); } + + public function cloneTo(ilObjSurvey $target_survey, array $mapping) : void + { + $target_thresholds = new self($target_survey); + $set = $this->db->queryF("SELECT * FROM svy_skill_threshold " . + " WHERE survey_id = %s ", + ["integer"], + [$this->survey->getId()] + ); + while ($rec = $this->db->fetchAssoc($set)) { + $target_thresholds->writeThreshold( + (int) $rec["base_skill_id"], + (int) $rec["tref_id"], + (int) $rec["level_id"], + (int) $rec["threshold"] + ); + } + } } diff --git a/Modules/Survey/classes/class.ilObjSurvey.php b/Modules/Survey/classes/class.ilObjSurvey.php index aee8f6dc26bf..e78b548b105a 100755 --- a/Modules/Survey/classes/class.ilObjSurvey.php +++ b/Modules/Survey/classes/class.ilObjSurvey.php @@ -383,6 +383,8 @@ public function deleteAllUserData( $lp_obj = ilObjectLP::getInstance($this->getId()); $lp_obj->resetLPDataForCompleteObject(); } + + $this->invitation_manager->removeAll($this->getSurveyId()); } /** @@ -596,6 +598,13 @@ public function isQuestionInSurvey( public function insertQuestionblock( int $questionblock_id ): void { + + $sequence_manager = $this->survey_service->domain()->sequence( + $this->getSurveyId(), + $this + ); + + $ilDB = $this->db; $result = $ilDB->queryF( "SELECT svy_qblk.title, svy_qblk.show_questiontext, svy_qblk.show_blocktitle," . @@ -608,15 +617,20 @@ public function insertQuestionblock( array($questionblock_id) ); $questions = array(); - $show_questiontext = 0; - $show_blocktitle = 0; + $show_questiontext = false; + $show_blocktitle = false; $title = ""; + $this->svy_log->debug("insert block, original id: " . $questionblock_id); while ($row = $ilDB->fetchAssoc($result)) { - $duplicate_id = $this->duplicateQuestionForSurvey($row["question_fi"]); + $this->svy_log->debug("question: " . $row["question_fi"]); + $duplicate_id = $sequence_manager->appendQuestion($row["question_fi"], true); + //$duplicate_id = $this->duplicateQuestionForSurvey($row["question_fi"]); + $this->svy_log->debug("question copy: " . $duplicate_id); $questions[] = $duplicate_id; - $title = $row["title"]; - $show_questiontext = $row["show_questiontext"]; - $show_blocktitle = $row["show_blocktitle"]; + $title = (string) $row["title"]; + $this->svy_log->debug("title: " . $title); + $show_questiontext = (bool) $row["show_questiontext"]; + $show_blocktitle = (bool) $row["show_blocktitle"]; } $this->createQuestionblock($title, $show_questiontext, $show_blocktitle, $questions); } @@ -2457,6 +2471,11 @@ public function sendNotificationMail( $user_id = (int) ilObjUser::_lookupId($recipient); if ($user_id > 0) { $ntf->sendMailAndReturnRecipients([$user_id]); + } else { + $user_ids = ilObjUser::getUserIdsByEmail($recipient); + if (count($user_ids) > 0) { + $ntf->sendMailAndReturnRecipients([current($user_ids)]); + } } /* note: this block is replace by the single line above since the UI asks for account names and the "e-mail" fallback leads @@ -2905,7 +2924,7 @@ public function getQuestionblocksTable( $questions_array[$key] = "$counter. $value"; $counter++; } - if (strlen($surveytitles[$row["obj_fi"]])) { // only questionpools which are not in trash + if (strlen($surveytitles[$row["obj_fi"]] ?? "")) { // only questionpools which are not in trash $rows[$row["questionblock_id"]] = array( "questionblock_id" => $row["questionblock_id"], "title" => $row["title"], @@ -3346,6 +3365,9 @@ public function cloneObject(int $target_id, int $copy_id = 0, bool $omit_tree = $tgt_skills->addQuestionSkillAssignment($tgt_qst_id, $qst_skill["base_skill_id"], $qst_skill["tref_id"]); } } + + $thresholds = new ilSurveySkillThresholds($this); + $thresholds->cloneTo($newObj, $mapping); } // clone the questionblocks diff --git a/Modules/Survey/classes/class.ilObjSurveyAccess.php b/Modules/Survey/classes/class.ilObjSurveyAccess.php index fd66b39b1e6f..cec6df8a82a7 100644 --- a/Modules/Survey/classes/class.ilObjSurveyAccess.php +++ b/Modules/Survey/classes/class.ilObjSurveyAccess.php @@ -420,14 +420,23 @@ public static function _checkGoto(string $target): bool $ilAccess = $DIC->access(); $t_arr = explode("_", $target); - if ($t_arr[0] !== "svy" || ((int) $t_arr[1]) <= 0) { return false; } // 360° external raters - if ($request->getAccessCode()) { - if (ilObjSurvey::validateExternalRaterCode($t_arr[1], $request->getAccessCode())) { + $access_code = ($request->getAccessCode() !== "") + ? $request->getAccessCode() + : ($t_arr[2] ?? ""); + if ($access_code !== "") { + $survey = new ilObjSurvey((int) $t_arr[1]); + $run_manager = $DIC->survey()->internal()->domain()->execution()->run($survey, $DIC->user()->getId()); + try { + $run_manager->initSession($access_code); + } catch (Exception $e) { + return false; + } + if (ilObjSurvey::validateExternalRaterCode((int) $t_arr[1], $access_code)) { return true; } } diff --git a/Modules/Survey/classes/class.ilObjSurveyGUI.php b/Modules/Survey/classes/class.ilObjSurveyGUI.php index 2eca67f97c90..4ea4baeb65ed 100755 --- a/Modules/Survey/classes/class.ilObjSurveyGUI.php +++ b/Modules/Survey/classes/class.ilObjSurveyGUI.php @@ -813,28 +813,42 @@ public static function _goto( $lng = $DIC->language(); $ctrl = $DIC->ctrl(); + $t_arr = explode("_", $a_target); + $ref_id = (int) $t_arr[0]; + if ($a_access_code === "" && isset($t_arr[1])) { + $a_access_code = $t_arr[1]; + } + // see ilObjSurveyAccess::_checkGoto() if ($a_access_code !== '') { $sess = $DIC->survey()->internal()->repo() ->execution()->runSession(); - $sess->setCode(ilObject::_lookupObjId($a_target), $a_access_code); - $ctrl->setParameterByClass("ilObjSurveyGUI", "ref_id", $a_target); + $sess->setCode(ilObject::_lookupObjId($ref_id), $a_access_code); + $ctrl->setParameterByClass("ilObjSurveyGUI", "ref_id", $ref_id); + $ctrl->redirectByClass("ilObjSurveyGUI", "infoScreen"); + } + + // write permission -> info screen + if ($ilAccess->checkAccess("write", "", $ref_id)) { + $ctrl->setParameterByClass("ilObjSurveyGUI", "ref_id", $ref_id); $ctrl->redirectByClass("ilObjSurveyGUI", "infoScreen"); } - if ($ilAccess->checkAccess("visible", "", $a_target) || - $ilAccess->checkAccess("read", "", $a_target)) { - $am = $DIC->survey()->internal()->domain()->access($a_target, $DIC->user()->getId()); + + // read permission and evaluation access -> evaluation + if ($ilAccess->checkAccess("visible", "", $ref_id) || + $ilAccess->checkAccess("read", "", $ref_id)) { + $am = $DIC->survey()->internal()->domain()->access($ref_id, $DIC->user()->getId()); if (/*!$am->canStartSurvey() &&*/ $am->canAccessEvaluation()) { - $ctrl->setParameterByClass("ilObjSurveyGUI", "ref_id", $a_target); + $ctrl->setParameterByClass("ilObjSurveyGUI", "ref_id", $ref_id); $ctrl->redirectByClass(["ilObjSurveyGUI", "ilSurveyEvaluationGUI"], "openEvaluation"); } - $ctrl->setParameterByClass("ilObjSurveyGUI", "ref_id", $a_target); + $ctrl->setParameterByClass("ilObjSurveyGUI", "ref_id", $ref_id); $ctrl->redirectByClass("ilObjSurveyGUI", "infoScreen"); } elseif ($ilAccess->checkAccess("read", "", ROOT_FOLDER_ID)) { $main_tpl->setOnScreenMessage('failure', sprintf( $lng->txt("msg_no_perm_read_item"), - ilObject::_lookupTitle(ilObject::_lookupObjId($a_target)) + ilObject::_lookupTitle(ilObject::_lookupObjId($ref_id)) ), true); ilObjectGUI::_gotoRepositoryRoot(); } diff --git a/Modules/SurveyQuestionPool/Questions/class.SurveyMatrixQuestionGUI.php b/Modules/SurveyQuestionPool/Questions/class.SurveyMatrixQuestionGUI.php index 54056ea21689..b8c0b5e89332 100755 --- a/Modules/SurveyQuestionPool/Questions/class.SurveyMatrixQuestionGUI.php +++ b/Modules/SurveyQuestionPool/Questions/class.SurveyMatrixQuestionGUI.php @@ -175,9 +175,9 @@ protected function importEditFormValues(ilPropertyFormGUI $a_form): void $this->object->getColumns()->addCategory($value, $columns['other'][$key] ?? 0, 0, null, $columns['scale'][$key]); } } - if (isset($columns["neutral"]) && is_string($columns["neutral"])) { + if (isset($columns["neutral"][0]) && is_string($columns["neutral"][0])) { $this->object->getColumns()->addCategory( - $columns['neutral'], + $columns['neutral'][0], 0, 1, null, diff --git a/Modules/SystemFolder/classes/class.ilAccessibilitySupportContactsGUI.php b/Modules/SystemFolder/classes/class.ilAccessibilitySupportContactsGUI.php index 9c1531c83508..332fbc67eb34 100644 --- a/Modules/SystemFolder/classes/class.ilAccessibilitySupportContactsGUI.php +++ b/Modules/SystemFolder/classes/class.ilAccessibilitySupportContactsGUI.php @@ -1,6 +1,20 @@ ctrl(); - $tpl = $DIC["tpl"]; $lng = $DIC->language(); $http = $DIC->http(); $this->ctrl = $ctrl; - $this->tpl = $tpl; $this->lng = $lng; $this->http = $http; } - - /** - * Execute command - */ - public function executeCommand() + public function executeCommand(): void { $cmd = $this->ctrl->getCmd("sendIssueMail"); if (in_array($cmd, array("sendIssueMail"))) { @@ -59,31 +48,27 @@ public function executeCommand() } } - public function sendIssueMail(): void { - $back_url = $this->http->request()->getServerParams()['HTTP_REFERER']; + $back_url = $this->http->request()->getServerParams()["HTTP_REFERER"]; $this->ctrl->redirectToURL( ilMailFormCall::getRedirectTarget( $back_url, - '', + "", [], [ - 'type' => 'new', - 'rcp_to' => $this->getContactLogins(), - 'sig' => $this->getAccessibilityIssueMailMessage($back_url) + "type" => "new", + "rcp_to" => $this->getContactLogins(), + "sig" => $this->getAccessibilityIssueMailMessage($back_url) ] ) ); } - /** - * @return string - */ private function getAccessibilityIssueMailMessage(string $back_url): string { $sig = chr(13) . chr(10) . chr(13) . chr(10) . chr(13) . chr(10); - $sig .= $this->lng->txt('report_accessibility_link'); + $sig .= $this->lng->txt("report_accessibility_link"); $sig .= chr(13) . chr(10); $sig .= $back_url; $sig = rawurlencode(base64_encode($sig)); @@ -93,8 +78,6 @@ private function getAccessibilityIssueMailMessage(string $back_url): string /** * Get accessibility support contacts as comma separated string - * - * @return string */ private function getContactLogins(): string { @@ -104,15 +87,10 @@ private function getContactLogins(): string $logins[] = ilObjUser::_lookupLogin($contact_id); } - return implode(',', $logins); + return implode(",", $logins); } - /** - * Get footer link - * - * @return string footer link - */ - public static function getFooterLink() + public static function getFooterLink(): string { global $DIC; @@ -120,35 +98,30 @@ public static function getFooterLink() $user = $DIC->user(); $http = $DIC->http(); $lng = $DIC->language(); + $rbac_system = $DIC->rbac()->system(); - - $users = ilAccessibilitySupportContacts::getValidSupportContactIds(); - if (count($users) > 0) { - if (!$user->getId() || $user->getId() == ANONYMOUS_USER_ID) { + $contacts = ilAccessibilitySupportContacts::getValidSupportContactIds(); + if (count($contacts) > 0) { + if ($rbac_system->checkAccess("internal_mail", ilMailGlobalServices::getMailObjectRefId())) { + return $ctrl->getLinkTargetByClass("ilaccessibilitysupportcontactsgui", ""); + } else { $mails = ilLegacyFormElementsUtil::prepareFormOutput( ilAccessibilitySupportContacts::getMailsToAddress() ); $request_scheme = - isset($http->request()->getServerParams()['HTTPS']) - && $http->request()->getServerParams()['HTTPS'] !== 'off' - ? 'https' : 'http'; - $url = $request_scheme . '://' - . $http->request()->getServerParams()['HTTP_HOST'] - . $http->request()->getServerParams()['REQUEST_URI']; + isset($http->request()->getServerParams()["HTTPS"]) + && $http->request()->getServerParams()["HTTPS"] !== "off" + ? "https" : "http"; + $url = $request_scheme . "://" + . $http->request()->getServerParams()["HTTP_HOST"] + . $http->request()->getServerParams()["REQUEST_URI"]; return "mailto:" . $mails . "?body=%0D%0A%0D%0A" . $lng->txt("report_accessibility_link_mailto") . "%0A" . rawurlencode($url); - } else { - return $ctrl->getLinkTargetByClass("ilaccessibilitysupportcontactsgui", ""); } } return ""; } - /** - * Get footer text - * - * @return string footer text - */ - public static function getFooterText() + public static function getFooterText(): string { global $DIC; diff --git a/Modules/SystemFolder/classes/class.ilBenchmarkTableGUI.php b/Modules/SystemFolder/classes/class.ilBenchmarkTableGUI.php index 82121e382531..8efddc6f0880 100644 --- a/Modules/SystemFolder/classes/class.ilBenchmarkTableGUI.php +++ b/Modules/SystemFolder/classes/class.ilBenchmarkTableGUI.php @@ -23,11 +23,7 @@ */ class ilBenchmarkTableGUI extends ilTable2GUI { - /** - * @var ilAccessHandler - */ - protected $access; - + protected ilAccessHandler $access; /** * Constructor @@ -85,24 +81,18 @@ public function __construct($a_parent_obj, $a_parent_cmd, $a_records, $a_mode = $this->setRowTemplate("tpl.db_bench.html", "Modules/SystemFolder"); $this->disable("footer"); $this->setEnableTitle(true); - - // $this->addMultiCommand("", $lng->txt("")); -// $this->addCommandButton("", $lng->txt("")); } /** * Get first occurence of string - * - * @param - * @return */ - public function getFirst($a_str, $a_needles) + public function getFirst(string $a_str, array $a_needles): int { $pos = 0; foreach ($a_needles as $needle) { - $pos2 = strpos($a_str, $needle); + $pos2 = strpos($a_str, (string) $needle); - if ($pos2 > 0 && ($pos2 < $pos || $pos == 0)) { + if ($pos2 > 0 && ($pos2 < $pos || $pos === 0)) { $pos = $pos2; } } @@ -112,49 +102,49 @@ public function getFirst($a_str, $a_needles) /** * Extract first table from sql - * - * @param - * @return */ - public function extractFirstTableFromSQL($a_sql) + public function extractFirstTableFromSQL(string $a_sql): string { - $pos1 = $this->getFirst(strtolower($a_sql), array("from ", "from\n", "from\t", "from\r")); + $pos1 = $this->getFirst(strtolower($a_sql), ["from ", "from\n", "from\t", "from\r"]); $table = ""; if ($pos1 > 0) { $tablef = substr(strtolower($a_sql), $pos1 + 5); - $pos2 = $this->getFirst($tablef, array(" ", "\n", "\t", "\r")); - if ($pos2 > 0) { - $table = substr($tablef, 0, $pos2); - } else { - $table = $tablef; - } + $pos2 = $this->getFirst($tablef, [" ", "\n", "\t", "\r"]); + $table = $pos2 > 0 ? substr($tablef, 0, $pos2) : $tablef; } - if (trim($table) != "") { + if (trim($table) !== "") { return $table; } return ""; } - /** * Get data by first table - * - * @param - * @return */ - public function getDataByFirstTable($a_records) + public function getDataByFirstTable(array $a_records): array { - $data = array(); + $data = []; foreach ($a_records as $r) { $table = $this->extractFirstTableFromSQL($r["sql"]); + if (trim($table) === '') { + continue; + } $data[$table]["table"] = $table; - $data[$table]["cnt"]++; - $data[$table]["time"] += $r["time"]; + if (!isset($data[$table]["cnt"])) { + $data[$table]["cnt"] = 1; + } else { + $data[$table]["cnt"]++; + } + if (!isset($data[$table]["time"])) { + $data[$table]["time"] = $r["time"]; + } else { + $data[$table]["time"] += $r["time"]; + } } - if (count($data) > 0) { - $data = ilArrayUtil::sortArray($data, "time", "desc", true); + if ($data !== []) { + return ilArrayUtil::sortArray($data, "time", "desc", true); } return $data; diff --git a/Modules/Test/classes/AccessFileUploadPreview.php b/Modules/Test/classes/AccessFileUploadPreview.php new file mode 100644 index 000000000000..49c09b5efaa4 --- /dev/null +++ b/Modules/Test/classes/AccessFileUploadPreview.php @@ -0,0 +1,115 @@ + */ + private Closure $references_of; + /** @var Closure(int, bool): string */ + private Closure $type_of; + + /** + * @param ilDBInterface $database + * @param ilAccess $access + * @param Incident $incident + * @param Closure(int): list $references_of + * @param Closure(int, bool): string $type_of + */ + public function __construct( + ilDBInterface $database, + ilAccess $access, + ?Incident $incident = null, + $references_of = [ilObject::class, '_getAllReferences'], + $type_of = [ilObject::class, '_lookupType'] + ) { + $this->database = $database; + $this->access = $access; + $this->incident = $incident ?? new Incident(); + $this->references_of = Closure::fromCallable($references_of); + $this->type_of = Closure::fromCallable($type_of); + } + + public function isPermitted(string $path): Result + { + $question_id = $this->questionId($path); + if (!$question_id) { + return new Error('Not a question image path of test questions.'); + } + + $object_id = $this->objectId($question_id); + if (!$object_id) { + return new Ok(false); + } + + $permitted = $this->incident->any([$this, 'refIdPermitted'], ($this->references_of)($object_id)); + + return new Ok($permitted); + } + + /** + * @param int $ref_id + */ + public function refIdPermitted(int $ref_id): bool + { + $ref_id = $ref_id; + $type = ($this->type_of)($ref_id, true); + + switch ($type) { + case 'qpl': return $this->access->checkAccess('read', '', $ref_id); + case 'tst': return $this->access->checkAccess('write', '', $ref_id); + default: return false; + } + } + + private function questionId(string $path): ?int + { + $results = []; + if (!preg_match(':/assessment/qst_preview/\d+/(\d+)/fileuploads/([^/]+)$:', $path, $results)) { + return null; + } + + return (int) $results[1]; + } + + private function objectId(int $question_id): ?int + { + $object_id = $this->database->fetchAssoc($this->database->queryF( + 'SELECT obj_fi FROM qpl_questions WHERE question_id = %s', + [ilDBConstants::T_INTEGER], + [$question_id] + ))['obj_fi'] ?? null; + + return $object_id ? (int) $object_id : null; + } +} diff --git a/Modules/Test/classes/AccessQuestionImage.php b/Modules/Test/classes/AccessQuestionImage.php index d719472c5df1..39858a367b66 100644 --- a/Modules/Test/classes/AccessQuestionImage.php +++ b/Modules/Test/classes/AccessQuestionImage.php @@ -23,8 +23,6 @@ use ILIAS\Data\Result; use ILIAS\Data\Result\Ok; use ILIAS\Data\Result\Error; -use ILIAS\DI\Container; -use Closure; class AccessQuestionImage implements SimpleAccess { diff --git a/Modules/Test/classes/ScoreReporting/ilObjTestSettingsResultSummary.php b/Modules/Test/classes/ScoreReporting/ilObjTestSettingsResultSummary.php index 1e6a1adce077..9a4fbefdf1fa 100644 --- a/Modules/Test/classes/ScoreReporting/ilObjTestSettingsResultSummary.php +++ b/Modules/Test/classes/ScoreReporting/ilObjTestSettingsResultSummary.php @@ -66,7 +66,7 @@ function ($v) { if ($reporting_date !== null) { $reporting_date = $reporting_date->setTimezone( new DateTimeZone($environment['user_time_zone']) - )->format($environment['user_date_format']); + )->format($environment['user_date_format']->toString() . ' H:m'); } $results_time_group = $f->switchableGroup( @@ -79,6 +79,7 @@ function ($v) { $f->dateTime($lng->txt('tst_reporting_date'), "") ->withTimezone($environment['user_time_zone']) ->withUseTime(true) + ->withFormat($environment['user_date_format']) ->withValue( $reporting_date ) @@ -98,7 +99,6 @@ function ($v) { $results_time_group = $results_time_group->withValue($this->getScoreReporting()); } - $optional_group = $f->optionalGroup( [ 'score_reporting_mode' => $results_time_group, diff --git a/Modules/Test/classes/class.assMark.php b/Modules/Test/classes/class.assMark.php index e4618345ffa4..6f0870591a70 100755 --- a/Modules/Test/classes/class.assMark.php +++ b/Modules/Test/classes/class.assMark.php @@ -60,6 +60,18 @@ public function __construct( $this->setPassed($passed); } + /** + * Stephan Kergomard, 2023-11-08: We need an explicit __unserialize function + * here because of changes to the corresponding classes with ILIAS 8. + */ + public function __unserialize(array $data): void + { + $this->short_name = $data['short_name']; + $this->official_name = $data['short_name']; + $this->minimum_level = (float) $data['minimum_level']; + $this->passed = (int) $data['passed']; + } + public function getShortName(): string { return $this->short_name; diff --git a/Modules/Test/classes/class.ilMarkSchemaGUI.php b/Modules/Test/classes/class.ilMarkSchemaGUI.php index 8a4b11009e98..815d52a132d2 100644 --- a/Modules/Test/classes/class.ilMarkSchemaGUI.php +++ b/Modules/Test/classes/class.ilMarkSchemaGUI.php @@ -19,6 +19,10 @@ use ILIAS\HTTP\Wrapper\RequestWrapper; use GuzzleHttp\Psr7\Request; use ILIAS\Refinery\Factory as Refinery; +use ILIAS\UI\Factory as UIFactory; +use ILIAS\UI\Renderer as UIRenderer; +use ILIAS\UI\Component\Button\Standard as StandardButton; +use ILIAS\UI\Component\Modal\Interruptive as InterruptiveModal; /** * Class ilMarkSchemaGUI @@ -27,6 +31,7 @@ */ class ilMarkSchemaGUI { + private const RESET_MARK_BUTTON_LABEL = 'tst_mark_reset_to_simple_mark_schema'; private RequestWrapper $post_wrapper; private Request $request; private Refinery $refinery; @@ -39,6 +44,9 @@ class ilMarkSchemaGUI protected ilCtrl $ctrl; protected ilGlobalPageTemplate $tpl; protected ilToolbarGUI $toolbar; + protected ilTabsGUI $tabs; + protected UIFactory $ui_factory; + protected UIRenderer $ui_renderer; /** * @param ilMarkSchemaAware|ilEctsGradesEnabled $object @@ -56,6 +64,8 @@ public function __construct($object) $this->post_wrapper = $DIC->http()->wrapper()->post(); $this->request = $DIC->http()->request(); $this->refinery = $DIC->refinery(); + $this->ui_factory = $DIC['ui.factory']; + $this->ui_renderer = $DIC['ui.renderer']; } public function executeCommand(): void @@ -64,6 +74,9 @@ public function executeCommand(): void $DIC->tabs()->activateTab(ilTestTabsManager::TAB_ID_SETTINGS); $cmd = $this->ctrl->getCmd('showMarkSchema'); + if ($cmd === self::RESET_MARK_BUTTON_LABEL) { + $cmd = 'resetToSimpleMarkSchema'; + } $this->$cmd(); } @@ -127,7 +140,7 @@ protected function saveMarkSchemaFormData(): bool return $no_save_error; } - protected function addSimpleMarkSchema(): void + protected function resetToSimpleMarkSchema(): void { $this->ensureMarkSchemaCanBeEdited(); @@ -231,34 +244,53 @@ protected function showMarkSchema(?ilPropertyFormGUI $ects_form = null): void $mark_schema_table = new ilMarkSchemaTableGUI($this, 'showMarkSchema', '', $this->object); $mark_schema_table->setShowRowsSelector(false); + $rendered_modal = ''; if ($this->object->canEditMarks()) { - require_once 'Services/UIComponent/Button/classes/class.ilSubmitButton.php'; - $create_simple_mark_schema_button = ilSubmitButton::getInstance(); - $create_simple_mark_schema_button->setCaption($this->lng->txt('tst_mark_create_simple_mark_schema'), false); - $create_simple_mark_schema_button->setCommand('addSimpleMarkSchema'); - $this->toolbar->addButtonInstance($create_simple_mark_schema_button); - - require_once 'Services/UIComponent/Button/classes/class.ilButton.php'; - $create_new_mark_step_button = ilButton::getInstance(); - $create_new_mark_step_button->setCaption($this->lng->txt('tst_mark_create_new_mark_step'), false); - $create_new_mark_step_button->setButtonType(ilButton::BUTTON_TYPE_SUBMIT); - $create_new_mark_step_button->setForm('form_' . $mark_schema_table->getId()); - $create_new_mark_step_button->setName('addMarkStep'); - $this->toolbar->addButtonInstance($create_new_mark_step_button); + $confirmation_modal = $this->ui_factory->modal()->interruptive( + $this->lng->txt(self::RESET_MARK_BUTTON_LABEL), + $this->lng->txt('tst_mark_reset_to_simple_mark_schema_confirmation'), + $this->ctrl->getFormAction($this, 'resetToSimpleMarkSchema') + )->withActionButtonLabel(self::RESET_MARK_BUTTON_LABEL); + $this->populateToolbar($confirmation_modal, $mark_schema_table->getId()); + $rendered_modal = $this->ui_renderer->render($confirmation_modal); } + $this->tpl->setContent( + $mark_schema_table->getHTML() . $rendered_modal + ); + } - $content_parts = array($mark_schema_table->getHTML()); + private function populateToolbar(InterruptiveModal $confirmation_modal, string $mark_schema_id): void + { + $create_simple_schema_button = $this->ui_factory->button()->standard( + $this->lng->txt(self::RESET_MARK_BUTTON_LABEL), + $confirmation_modal->getShowSignal() + ); + $this->toolbar->addComponent($create_simple_schema_button); - if ($this->objectSupportsEctsGrades() && $this->object->canShowEctsGrades()) { - if (!($ects_form instanceof ilPropertyFormGUI)) { - $ects_form = $this->getEctsForm(); - $this->populateEctsForm($ects_form); - } - $content_parts[] = $ects_form->getHTML(); - } + $create_step_button = $this->buildCreateStepButton($mark_schema_id); + $this->toolbar->addComponent($create_step_button); + } - $this->tpl->setContent(implode('
', $content_parts)); + private function buildCreateStepButton(string $mark_schema_id): StandardButton + { + return $this->ui_factory->button()->standard( + $this->lng->txt('tst_mark_create_new_mark_step'), + '' + )->withAdditionalOnLoadCode( + fn (string $id): string => + "{$id}.addEventListener('click', " + . ' (e) => {' + . ' e.preventDefault();' + . ' e.target.name = "cmd[addMarkStep]";' + . " let form = document.getElementById('form_{$mark_schema_id}');" + . ' let submitter = e.target.cloneNode();' + . ' submitter.style.visibility = "hidden";' + . ' form.appendChild(submitter);' + . ' form.requestSubmit(submitter);' + . ' }' + . ');' + ); } protected function populateEctsForm(ilPropertyFormGUI $form): void diff --git a/Modules/Test/classes/class.ilObjTest.php b/Modules/Test/classes/class.ilObjTest.php index e98f83e66ee1..e21ef59643c8 100755 --- a/Modules/Test/classes/class.ilObjTest.php +++ b/Modules/Test/classes/class.ilObjTest.php @@ -7296,14 +7296,14 @@ public static function _getBestPass($active_id): ?int } $bestrow = null; - $bestfactor = 0; + $bestfactor = 0.0; while ($row = $ilDB->fetchAssoc($result)) { - if ($row["maxpoints"] > 0) { - $factor = $row["points"] / $row["maxpoints"]; + if ($row["maxpoints"] > 0.0) { + $factor = (float) ($row["points"] / $row["maxpoints"]); } else { - $factor = 0; + $factor = 0.0; } - if ($factor === 0 && $bestfactor === 0 + if ($factor === 0.0 && $bestfactor === 0.0 || $factor > $bestfactor) { $bestrow = $row; $bestfactor = $factor; @@ -10556,6 +10556,7 @@ public function recalculateScores($preserve_manscoring = false) $scoring = new ilTestScoring($this); $scoring->setPreserveManualScores($preserve_manscoring); $scoring->recalculateSolutions(); + ilLPStatusWrapper::_updateStatus($this->getId(), $this->user->getId()); } public static function getTestObjIdsWithActiveForUserId($userId): array diff --git a/Modules/Test/classes/class.ilObjTestAccess.php b/Modules/Test/classes/class.ilObjTestAccess.php index 9416774fb743..26e6f08dc409 100644 --- a/Modules/Test/classes/class.ilObjTestAccess.php +++ b/Modules/Test/classes/class.ilObjTestAccess.php @@ -18,6 +18,7 @@ use ILIAS\Modules\Test\AccessFileUploadAnswer; use ILIAS\Modules\Test\AccessQuestionImage; +use ILIAS\Modules\Test\AccessFileUploadPreview; use ILIAS\Modules\Test\SimpleAccess; use ILIAS\Modules\Test\Readable; use ILIAS\Data\Result; @@ -45,6 +46,7 @@ public function canBeDelivered(ilWACPath $ilWACPath): bool $can_it = $this->findMatch($ilWACPath->getPath(), [ new AccessFileUploadAnswer($DIC, $readable), new AccessQuestionImage($readable), + new AccessFileUploadPreview($DIC->database(), $DIC->access()), ]); @@ -220,7 +222,7 @@ public static function isFailed($user_id, $a_obj_id): bool if (!$result->numRows()) { $result = $ilDB->queryF( - "SELECT tst_pass_result.*, tst_tests.pass_scoring, tst_tests.random_test, tst_tests.test_id FROM tst_pass_result, tst_active, tst_tests WHERE tst_active.test_fi = tst_tests.test_id AND tst_active.user_fi = %s AND tst_tests.obj_fi = %s AND tst_pass_result.active_fi = tst_active.active_id ORDER BY tst_pass_result.pass", + "SELECT tst_pass_result.*, tst_tests.pass_scoring FROM tst_pass_result, tst_active, tst_tests WHERE tst_active.test_fi = tst_tests.test_id AND tst_active.user_fi = %s AND tst_tests.obj_fi = %s AND tst_pass_result.active_fi = tst_active.active_id ORDER BY tst_pass_result.pass", array('integer','integer'), array($user_id, $a_obj_id) ); diff --git a/Modules/Test/classes/class.ilObjTestGUI.php b/Modules/Test/classes/class.ilObjTestGUI.php index 33973d363225..49921db989bd 100755 --- a/Modules/Test/classes/class.ilObjTestGUI.php +++ b/Modules/Test/classes/class.ilObjTestGUI.php @@ -524,7 +524,9 @@ public function executeCommand(): void $this->getTestObject(), $ilAccess, $DIC->http(), - $DIC->refinery() + $DIC->refinery(), + $DIC['ui.factory'], + $DIC['ui.renderer'] ); $gui->setWriteAccess($ilAccess->checkAccess("write", "", $this->ref_id)); $gui->init(); @@ -957,32 +959,17 @@ private function userResultsGatewayObject() $this->forwardToEvaluationGUI(); } - private function testResultsGatewayObject() + private function testResultsGatewayObject(): void { - global $DIC; - $this->tabs_gui->clearTargets(); - - $this->prepareOutput(); - $this->addHeaderAction(); - - $this->ctrl->setCmdClass('ilParticipantsTestResultsGUI'); - $this->ctrl->setCmd('showParticipants'); - - - $gui = new ilParticipantsTestResultsGUI(); - $gui->setTestObj($this->object); - - $factory = new ilTestQuestionSetConfigFactory( - $this->tree, - $DIC->database(), - $DIC['component.repository'], - $this->object + $this->ctrl->redirectByClass( + [ + ilRepositoryGUI::class, + __CLASS__, + ilTestResultsGUI::class, + ilParticipantsTestResultsGUI::class + ], + 'showParticipants' ); - $gui->setQuestionSetConfig($factory->getQuestionSetConfig()); - $gui->setObjectiveParent(new ilTestObjectiveOrientedContainer()); - $gui->setTestAccess($this->getTestAccess()); - $this->tabs_gui->activateTab('results'); - $this->ctrl->forwardCommand($gui); } /** @@ -3446,7 +3433,7 @@ public function applyTemplate($templateData, ilObjTest $object) $score_settings = $object->getScoreSettings(); foreach ($simpleSetters as $field => $setter) { - if (! array_key_exists($field, $templateData)) { + if (!array_key_exists($field, $templateData)) { continue; } diff --git a/Modules/Test/classes/class.ilObjTestSettingsGeneralGUI.php b/Modules/Test/classes/class.ilObjTestSettingsGeneralGUI.php index e53248d90e6c..3df88f1f85f6 100644 --- a/Modules/Test/classes/class.ilObjTestSettingsGeneralGUI.php +++ b/Modules/Test/classes/class.ilObjTestSettingsGeneralGUI.php @@ -1,4 +1,5 @@ testOBJ->setShowFinalStatement((bool) $form->getInput('showfinalstatement')); $this->testOBJ->setFinalStatement($form->getInput('finalstatement') ?? ''); - if ($form->getItemByPostVar('redirection_enabled')->getChecked()) { - $this->testOBJ->setRedirectionMode((bool) $form->getInput('redirection_mode')); + if ($this->formPropertyExists($form,'redirection_enabled')) { + if (empty($form->getInput('redirection_enabled'))){ + $this->testOBJ->setRedirectionMode(REDIRECT_NONE); + } else { + $this->testOBJ->setRedirectionMode(($form->getInput('redirection_mode'))); + } } else { $this->testOBJ->setRedirectionMode(REDIRECT_NONE); } - if (strlen($form->getItemByPostVar('redirection_url')->getValue())) { + if ($this->formPropertyExists($form,'redirection_url')) { $this->testOBJ->setRedirectionUrl($form->getInput('redirection_url')); } else { $this->testOBJ->setRedirectionUrl(null); diff --git a/Modules/Test/classes/class.ilObjTestSettingsScoringResultsGUI.php b/Modules/Test/classes/class.ilObjTestSettingsScoringResultsGUI.php index c0804df46c15..fb44499da0bd 100644 --- a/Modules/Test/classes/class.ilObjTestSettingsScoringResultsGUI.php +++ b/Modules/Test/classes/class.ilObjTestSettingsScoringResultsGUI.php @@ -159,7 +159,7 @@ public function executeCommand() ->getData(); $this->storeScoreSettings($settings); $this->testOBJ->recalculateScores(true); - $this->tpl->setOnScreenMessage('success', $this->lng->txt("msg_obj_modified_and_recalc"), true); + $this->tpl->setOnScreenMessage('success', $this->lng->txt("msg_score_settings_modified_and_recalc"), true); $this->ctrl->redirect($this, self::CMD_SHOW_FORM); break; case self::CMD_CANCEL_RECALC: @@ -239,7 +239,7 @@ private function buildForm(): Form default: $date_format = $df->standard(); } - $environment['user_date_format'] = $date_format->toString() . 'H:i'; + $environment['user_date_format'] = $date_format; $environment['user_time_zone'] = $this->active_user->getTimeZone(); $disabled_flag = ($this->areScoringSettingsWritable() === false); diff --git a/Modules/Test/classes/class.ilTestLP.php b/Modules/Test/classes/class.ilTestLP.php index 84ca3d4c5a58..cfd1fe444eca 100644 --- a/Modules/Test/classes/class.ilTestLP.php +++ b/Modules/Test/classes/class.ilTestLP.php @@ -76,7 +76,7 @@ public function setTestObject(\ilObjTest $test) $this->testObj = $test; } - protected function resetCustomLPDataForUserIds(array $a_user_ids, bool $a_recursive = true): void + protected function resetCustomLPDataForUserIds(array $user_ids, bool $recursive = true): void { /* @var ilObjTest $testOBJ */ if ($this->testObj) { @@ -85,32 +85,36 @@ protected function resetCustomLPDataForUserIds(array $a_user_ids, bool $a_recurs } else { $testOBJ = ilObjectFactory::getInstanceByObjId($this->obj_id); } - $testOBJ->removeTestResultsByUserIds($a_user_ids); + $testOBJ->removeTestResultsByUserIds($user_ids); // :TODO: there has to be a better way - $test_ref_id = (int) $this->request->raw("ref_id"); + $test_ref_id = $this->request->int('ref_id'); if ($this->testObj && $this->testObj->getRefId()) { $test_ref_id = $this->testObj->getRefId(); } - if ($test_ref_id) { - $course_obj_id = ilLOTestAssignments::lookupContainerForTest($test_ref_id); - if ($course_obj_id) { - // remove objective results data - $lo_assignments = ilLOTestAssignments::getInstance($course_obj_id); - ilLOUserResults::deleteResultsFromLP( - $course_obj_id, - $a_user_ids, - $lo_assignments->getTypeByTest($test_ref_id) === ilLOSettings::TYPE_TEST_INITIAL, - $lo_assignments->getTypeByTest($test_ref_id) === ilLOSettings::TYPE_TEST_QUALIFIED, - ilLOTestAssignments::lookupObjectivesForTest($test_ref_id) - ); - $lp_status = ilLPStatusFactory::_getInstance($course_obj_id); - if (strtolower(get_class($lp_status)) != "illpstatus") { - foreach ($a_user_ids as $user_id) { - $lp_status->_updateStatus($course_obj_id, $user_id); - } - } + if ($test_ref_id === 0) { + return; + } + + $course_obj_id = ilLOTestAssignments::lookupContainerForTest($test_ref_id); + if ($course_obj_id === 0) { + return; + } + + // remove objective results data + $lo_assignments = ilLOTestAssignments::getInstance($course_obj_id); + ilLOUserResults::deleteResultsFromLP( + $course_obj_id, + $user_ids, + $lo_assignments->getTypeByTest($test_ref_id) === ilLOSettings::TYPE_TEST_INITIAL, + $lo_assignments->getTypeByTest($test_ref_id) === ilLOSettings::TYPE_TEST_QUALIFIED, + ilLOTestAssignments::lookupObjectivesForTest($test_ref_id) + ); + $lp_status = ilLPStatusFactory::_getInstance($course_obj_id); + if (strtolower(get_class($lp_status)) !== 'illpstatus') { + foreach ($user_ids as $user_id) { + $lp_status->_updateStatus($course_obj_id, $user_id); } } } diff --git a/Modules/Test/classes/class.ilTestPassesSelector.php b/Modules/Test/classes/class.ilTestPassesSelector.php index 24bb74cb4beb..0709ceb473bc 100644 --- a/Modules/Test/classes/class.ilTestPassesSelector.php +++ b/Modules/Test/classes/class.ilTestPassesSelector.php @@ -294,12 +294,16 @@ private function isProcessingTimeReached($pass): bool */ public function getLastFinishedPassTimestamp(): ?int { - if ($this->getLastFinishedPass() === null || $this->getLastFinishedPass() === -1) { + $last_finished_pass = $this->getLastFinishedPass(); + if ($last_finished_pass === null || $last_finished_pass === -1) { return null; } $passes = $this->getLazyLoadedPasses(); - return $passes[$this->getLastFinishedPass()]['tstamp']; + if(!isset($passes[$last_finished_pass])) { + return null; + } + return $passes[$last_finished_pass]['tstamp']; } public function hasTestPassedOnce($activeId): bool diff --git a/Modules/Test/classes/class.ilTestPlayerAbstractGUI.php b/Modules/Test/classes/class.ilTestPlayerAbstractGUI.php index cad2ab3aad9d..f6d86430902a 100755 --- a/Modules/Test/classes/class.ilTestPlayerAbstractGUI.php +++ b/Modules/Test/classes/class.ilTestPlayerAbstractGUI.php @@ -544,7 +544,7 @@ public function handleUserSettings() global $DIC; $ilUser = $DIC['ilUser']; $post_array = $_POST; - if (! is_array($post_array)) { + if (!is_array($post_array)) { $request = $DIC->http()->request(); $post_array = $request->getParsedBody(); } @@ -611,17 +611,9 @@ public function autosaveCmd() if (!$this->canSaveResult() || $this->isParticipantsAnswerFixed($this->getCurrentQuestionId())) { $result = '-IGNORE-'; } else { - // answer is changed from authorized solution, so save the change as intermediate solution - if ($this->getAnswerChangedParameter()) { - $res = $this->saveQuestionSolution(false, true); - } - // answer is not changed from authorized solution, so delete an intermediate solution - else { - // @PHP8-CR: This looks like (yet) another issue in the dreaded autosaving. - // Any advice how to deal with it? - $db_res = $this->removeIntermediateSolution(); - $res = is_int($db_res); - } + $authorize = !$this->getAnswerChangedParameter(); + $res = $this->saveQuestionSolution($authorize, true); + if ($res) { $result = $this->lng->txt("autosave_success"); } else { @@ -640,8 +632,7 @@ public function autosaveCmd() public function autosaveOnTimeLimitCmd() { if (!$this->isParticipantsAnswerFixed($this->getCurrentQuestionId())) { - // time limit saves the user solution as authorized - $this->saveQuestionSolution(true, true); + $this->saveQuestionSolution(false, true); } $this->ctrl->redirect($this, ilTestPlayerCommands::REDIRECT_ON_TIME_LIMIT); } @@ -1238,7 +1229,7 @@ protected function showQuestionEditable(assQuestionGUI $questionGui, $formAction $questionNavigationGUI->setDiscardSolutionButtonEnabled(true); // fau: testNav - set answere status in question header $questionGui->getQuestionHeaderBlockBuilder()->setQuestionAnswered(true); - // fau. + // fau. } elseif ($this->object->isPostponingEnabled()) { $questionNavigationGUI->setSkipQuestionLinkTarget( $this->ctrl->getLinkTarget($this, ilTestPlayerCommands::SKIP_QUESTION) @@ -1879,22 +1870,22 @@ protected function prepareSummaryPage() protected function initTestPageTemplate() { $onload_js = << { - if( event.key === 13 && event.target.tagName.toLowerCase() === "a" ) { - return; - } - if (event.key === 13 && - event.target.tagName.toLowerCase() !== "textarea" && - (event.target.tagName.toLowerCase() !== "input" || event.target.type.toLowerCase() !== "submit")) { - event.preventDefault(); - } - }; + let key_event = (event) => { + if( event.key === 13 && event.target.tagName.toLowerCase() === "a" ) { + return; + } + if (event.key === 13 && + event.target.tagName.toLowerCase() !== "textarea" && + (event.target.tagName.toLowerCase() !== "input" || event.target.type.toLowerCase() !== "submit")) { + event.preventDefault(); + } + }; - let form = document.getElementById('taForm'); - form.onkeyup = key_event; - form.onkeydown = key_event; - form.onkeypress = key_event; - JS; + let form = document.getElementById('taForm'); + form.onkeyup = key_event; + form.onkeydown = key_event; + form.onkeypress = key_event; +JS; $this->tpl->addOnLoadCode($onload_js); $this->tpl->addBlockFile( $this->getContentBlockName(), diff --git a/Modules/Test/classes/class.ilTestRandomQuestionsQuantitiesDistribution.php b/Modules/Test/classes/class.ilTestRandomQuestionsQuantitiesDistribution.php index 65bc65e64938..9b9e02ec9816 100644 --- a/Modules/Test/classes/class.ilTestRandomQuestionsQuantitiesDistribution.php +++ b/Modules/Test/classes/class.ilTestRandomQuestionsQuantitiesDistribution.php @@ -140,14 +140,9 @@ protected function resetQuestRelatedSrcPoolDefRegister() $this->questRelatedSrcPoolDefRegister = array(); } - /** - * @param integer $questionId - * @param ilTestRandomQuestionSetSourcePoolDefinition $definition - */ - protected function registerQuestRelatedSrcPoolDef($questionId, ilTestRandomQuestionSetSourcePoolDefinition $definition) + protected function registerQuestRelatedSrcPoolDef(int $questionId, ilTestRandomQuestionSetSourcePoolDefinition $definition): void { - if (!array_key_exists($questionId, $this->questRelatedSrcPoolDefRegister) || - !is_numeric($this->questRelatedSrcPoolDefRegister[$questionId])) { + if (!array_key_exists($questionId, $this->questRelatedSrcPoolDefRegister)) { $this->questRelatedSrcPoolDefRegister[$questionId] = $this->buildSourcePoolDefinitionListInstance(); } @@ -218,10 +213,13 @@ protected function initialiseRegisters() $randomQuestion ); - $this->registerQuestRelatedSrcPoolDef( - $randomQuestion->getQuestionId(), - $sourcePoolDefinition - ); + if ($sourcePoolDefinition && $randomQuestion->getQuestionId()) { + $this->registerQuestRelatedSrcPoolDef( + $randomQuestion->getQuestionId(), + $sourcePoolDefinition + ); + } + } } diff --git a/Modules/Test/classes/class.ilTestScoring.php b/Modules/Test/classes/class.ilTestScoring.php index 682c45fd46a9..2f89e5036e54 100644 --- a/Modules/Test/classes/class.ilTestScoring.php +++ b/Modules/Test/classes/class.ilTestScoring.php @@ -199,6 +199,7 @@ public function calculateBestSolutionForTest(): string foreach ($this->test->getAllQuestions() as $question) { /** @var AssQuestionGUI $question_gui */ $question_gui = $this->test->createQuestionGUI("", $question['question_id']); + $solution .= '

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

'; $solution .= $question_gui->getSolutionOutput(0, null, true, true, false, false, true, false); } @@ -217,7 +218,7 @@ public function getRecalculatedPassesByActives(): array public function addRecalculatedPassByActive($activeId, $pass) { - if (! array_key_exists($activeId, $this->recalculatedPasses) + if (!array_key_exists($activeId, $this->recalculatedPasses) || !is_array($this->recalculatedPasses[$activeId]) ) { $this->recalculatedPasses[$activeId] = array(); diff --git a/Modules/Test/classes/class.ilTestScoringByQuestionsGUI.php b/Modules/Test/classes/class.ilTestScoringByQuestionsGUI.php index 9a6d6fcd2593..23a322ea52c7 100644 --- a/Modules/Test/classes/class.ilTestScoringByQuestionsGUI.php +++ b/Modules/Test/classes/class.ilTestScoringByQuestionsGUI.php @@ -396,7 +396,8 @@ protected function getAnswerDetail() false, $this->object->getShowSolutionFeedback(), false, - true + true, + false ); $tmp_tpl->setVariable('TEXT_ASOLUTION_OUTPUT', $this->lng->txt('autosavecontent')); $tmp_tpl->setVariable('ASOLUTION_OUTPUT', $aresult_output); diff --git a/Modules/Test/classes/class.ilTestScoringGUI.php b/Modules/Test/classes/class.ilTestScoringGUI.php index 83f72828a029..0748633ebd5f 100755 --- a/Modules/Test/classes/class.ilTestScoringGUI.php +++ b/Modules/Test/classes/class.ilTestScoringGUI.php @@ -459,7 +459,24 @@ private function buildManScoringParticipantForm($questionGuiList, $activeId, $pa $cust->setHtml($questionSolution); $form->addItem($cust); - $text = new ilTextInputGUI($lng->txt('tst_change_points_for_question'), "question__{$questionId}__points"); + if ($questionGUI instanceof assTextQuestionGUI && $this->object->getAutosave()) { + $aresult_output = $questionGUI->getAutoSavedSolutionOutput( + $activeId, + $pass, + false, + false, + false, + false, + false, + false, + false + ); + $cust = new ilCustomInputGUI($this->lng->txt('autosavecontent')); + $cust->setHtml($aresult_output); + $form->addItem($cust); + } + + $text = new ilTextInputGUI($this->lng->txt('tst_change_points_for_question'), "question__{$questionId}__points"); if ($initValues) { $text->setValue((string) assQuestion::_getReachedPoints($activeId, $questionId, $pass)); } diff --git a/Modules/Test/classes/class.ilTestServiceGUI.php b/Modules/Test/classes/class.ilTestServiceGUI.php index 0b153bb484d1..3bc88a1568e8 100755 --- a/Modules/Test/classes/class.ilTestServiceGUI.php +++ b/Modules/Test/classes/class.ilTestServiceGUI.php @@ -439,16 +439,52 @@ public function getPassListOfAnswers( if ($show_best_solution) { $compare_template = new ilTemplate('tpl.il_as_tst_answers_compare.html', true, true, 'Modules/Test'); - $compare_template->setVariable("HEADER_PARTICIPANT", $this->lng->txt('tst_header_participant')); + $test_session = $this->testSessionFactory->getSession($active_id); + if ($pass <= $test_session->getLastFinishedPass()) { + $compare_template->setVariable("HEADER_PARTICIPANT", $this->lng->txt('tst_header_participant')); + } else { + $compare_template->setVariable("HEADER_PARTICIPANT", $this->lng->txt('tst_header_participant_no_answer')); + } + $compare_template->setVariable("HEADER_SOLUTION", $this->lng->txt('tst_header_solution')); $result_output = $question_gui->getSolutionOutput($active_id, $pass, $show_graphical_output, false, $show_question_only, $show_feedback); $best_output = $question_gui->getSolutionOutput($active_id, $pass, false, false, $show_question_only, false, true); $compare_template->setVariable('PARTICIPANT', $result_output); $compare_template->setVariable('SOLUTION', $best_output); + if ($question_gui instanceof assTextQuestionGUI && $this->object->getAutosave()) { + $intermediate_output = $question_gui->getAutoSavedSolutionOutput( + $active_id, + $pass, + false, + false, + false, + false, + false, + false, + false + ); + $compare_template->setVariable('TXT_INTERMEDIATE', $this->lng->txt('autosavecontent')); + $compare_template->setVariable('INTERMEDIATE', $intermediate_output); + } $template->setVariable('SOLUTION_OUTPUT', $compare_template->get()); } else { $result_output = $question_gui->getSolutionOutput($active_id, $pass, $show_graphical_output, false, $show_question_only, $show_feedback); + if ($question_gui instanceof assTextQuestionGUI && $this->object->getAutosave()) { + $intermediate_output = $question_gui->getAutoSavedSolutionOutput( + $active_id, + $pass, + false, + false, + false, + false, + false, + false, + false + ); + $template->setVariable('TXT_INTERMEDIATE', $this->lng->txt('autosavecontent')); + $template->setVariable('INTERMEDIATE', $intermediate_output); + } $template->setVariable('SOLUTION_OUTPUT', $result_output); } @@ -751,10 +787,6 @@ public function getAdditionalUsrDataHtmlAndPopulateWindowTitle($testSession, $ac */ public function getCorrectSolutionOutput($question_id, $active_id, $pass, ilTestQuestionRelatedObjectivesList $objectivesList = null): string { - global $DIC; - $ilUser = $DIC['ilUser']; - - $test_id = $this->object->getTestId(); $question_gui = $this->object->createQuestionGUI("", $question_id); if ($this->isPdfDeliveryRequest()) { @@ -764,6 +796,20 @@ public function getCorrectSolutionOutput($question_id, $active_id, $pass, ilTest $template = new ilTemplate("tpl.il_as_tst_correct_solution_output.html", true, true, "Modules/Test"); $show_question_only = ($this->object->getShowSolutionAnswersOnly()) ? true : false; $result_output = $question_gui->getSolutionOutput($active_id, $pass, true, false, $show_question_only, $this->object->getShowSolutionFeedback(), false, false, true); + if ($question_gui instanceof assTextQuestionGUI && $this->object->getAutosave()) { + $result_output .= $question_gui->getAutoSavedSolutionOutput( + $active_id, + $pass, + false, + false, + false, + false, + false, + false, + false, + true + ); + } $best_output = $question_gui->getSolutionOutput($active_id, $pass, false, false, $show_question_only, false, true, false, false); if ($this->object->getShowSolutionFeedback() && $this->testrequest->raw('cmd') != 'outCorrectSolution') { $specificAnswerFeedback = $question_gui->getSpecificFeedbackOutput( diff --git a/Modules/Test/classes/class.ilTestTabsManager.php b/Modules/Test/classes/class.ilTestTabsManager.php index 8e09236e7d97..0cf7596354c5 100644 --- a/Modules/Test/classes/class.ilTestTabsManager.php +++ b/Modules/Test/classes/class.ilTestTabsManager.php @@ -569,7 +569,7 @@ protected function setupTabsGuiConfig() if ($this->isWriteAccessGranted()) { if (!$this->isHiddenTab('settings')) { $settingsCommands = array( - "marks", "showMarkSchema","addMarkStep", "deleteMarkSteps", "addSimpleMarkSchema", "saveMarks", + "marks", "showMarkSchema","addMarkStep", "deleteMarkSteps", "resetToSimpleMarkSchema", "saveMarks", "certificate", "certificateEditor", "certificateRemoveBackground", "certificateSave", "certificatePreview", "certificateDelete", "certificateUpload", "certificateImport", "scoring", "defaults", "addDefaults", "deleteDefaults", "applyDefaults", diff --git a/Modules/Test/classes/tables/class.ilEvaluationAllTableGUI.php b/Modules/Test/classes/tables/class.ilEvaluationAllTableGUI.php index eeb49c7b9729..6ea23d6b76fd 100644 --- a/Modules/Test/classes/tables/class.ilEvaluationAllTableGUI.php +++ b/Modules/Test/classes/tables/class.ilEvaluationAllTableGUI.php @@ -63,7 +63,7 @@ public function __construct($a_parent_obj, $a_parent_cmd, $anonymity = false, $o if (strcmp($c, 'email') == 0) { $this->addColumn($this->lng->txt("email"), 'email', ''); } - if (strcmp($c, 'exam_id') == 0 && $this->parent_obj->getTestObject()->isShowExamIdInTestResultsEnabled()) { + if (strcmp($c, 'exam_id') == 0 && $this->parent_obj->getObject()->isShowExamIdInTestResultsEnabled()) { $this->addColumn($this->lng->txt("exam_id_label"), 'exam_id', ''); } if (strcmp($c, 'institution') == 0) { diff --git a/Modules/Test/classes/tables/class.ilParticipantsTestResultsTableGUI.php b/Modules/Test/classes/tables/class.ilParticipantsTestResultsTableGUI.php index 4c0d3a03ac08..b901bbd53aa3 100644 --- a/Modules/Test/classes/tables/class.ilParticipantsTestResultsTableGUI.php +++ b/Modules/Test/classes/tables/class.ilParticipantsTestResultsTableGUI.php @@ -117,7 +117,7 @@ public function initColumns(): void $this->addColumn($this->lng->txt("login"), 'login'); $this->addColumn($this->lng->txt("tst_tbl_col_scored_pass"), 'scored_pass'); - $this->addColumn($this->lng->txt("tst_tbl_col_pass_finished"), 'pass_finished'); + $this->addColumn($this->lng->txt("tst_tbl_col_pass_finished"), 'scored_pass_finished_timestamp'); $this->addColumn($this->lng->txt("tst_tbl_col_answered_questions"), 'answered_questions'); $this->addColumn($this->lng->txt("tst_tbl_col_reached_points"), 'reached_points'); diff --git a/Modules/Test/classes/tables/class.ilTestQuestionBrowserTableGUI.php b/Modules/Test/classes/tables/class.ilTestQuestionBrowserTableGUI.php index 8372b6dd5747..5ef13665bd91 100644 --- a/Modules/Test/classes/tables/class.ilTestQuestionBrowserTableGUI.php +++ b/Modules/Test/classes/tables/class.ilTestQuestionBrowserTableGUI.php @@ -18,12 +18,17 @@ * *********************************************************************/ +use ILIAS\UI\Factory as UIFactory; +use ILIAS\UI\Renderer as UIRenderer; +use ILIAS\Modules\Test\QuestionPoolLinkedTitleBuilder; + /** * @author Helmut Schottmüller * @ilCtrl_Calls ilTestQuestionBrowserTableGUI: ilFormPropertyDispatchGUI */ class ilTestQuestionBrowserTableGUI extends ilTable2GUI { + use QuestionPoolLinkedTitleBuilder; private const REPOSITORY_ROOT_NODE_ID = 1; public const CONTEXT_PARAMETER = 'question_browse_context'; @@ -51,6 +56,9 @@ class ilTestQuestionBrowserTableGUI extends ilTable2GUI private ilObjTest $testOBJ; private ilAccessHandler $access; + private UIFactory $ui_factory; + private UIRenderer $ui_renderer; + /** @var array */ private array $filter = []; @@ -65,7 +73,9 @@ public function __construct( ilObjTest $testOBJ, ilAccessHandler $access, ILIAS\HTTP\GlobalHttpState $httpState, - ILIAS\Refinery\Factory $refinery + ILIAS\Refinery\Factory $refinery, + UIFactory $ui_factory, + UIRenderer $ui_renderer ) { $this->ctrl = $ctrl; $this->mainTpl = $mainTpl; @@ -78,6 +88,8 @@ public function __construct( $this->access = $access; $this->httpState = $httpState; $this->refinery = $refinery; + $this->ui_factory = $ui_factory; + $this->ui_renderer = $ui_renderer; $this->setId('qpl_brows_tabl_' . $this->testOBJ->getId()); global $DIC; @@ -440,7 +452,42 @@ public function fillRow(array $a_set): void "QUESTION_UPDATED", ilDatePresentation::formatDate(new ilDate($a_set["tstamp"], IL_CAL_UNIX)) ); - $this->tpl->setVariable("QUESTION_POOL", $a_set['parent_title']); + $this->tpl->setVariable( + "QUESTION_POOL_OR_TEST_TITLE", + $this->buildPossiblyLinkedQuestonPoolOrTestTitle( + (int) $a_set["obj_fi"], + $a_set["parent_title"] + ) + ); + } + + private function buildPossiblyLinkedQuestonPoolOrTestTitle(int $obj_id, string $parent_title): string + { + switch ($this->fetchModeParameter()) { + case self::MODE_BROWSE_POOLS: + return $this->buildPossiblyLinkedQuestonPoolTitle( + $this->ctrl, + $this->access, + $this->lng, + $this->ui_factory, + $this->ui_renderer, + $obj_id, + $parent_title + ); + + case self::MODE_BROWSE_TESTS: + return $this->buildPossiblyLinkedTestTitle( + $this->ctrl, + $this->access, + $this->lng, + $this->ui_factory, + $this->ui_renderer, + $obj_id, + $parent_title + ); + } + + return ''; } private function buildTestQuestionSetConfig(): ilTestQuestionSetConfig diff --git a/Modules/Test/classes/tables/class.ilTestQuestionsTableGUI.php b/Modules/Test/classes/tables/class.ilTestQuestionsTableGUI.php index 22a600978ccc..13ff5e4ffecb 100644 --- a/Modules/Test/classes/tables/class.ilTestQuestionsTableGUI.php +++ b/Modules/Test/classes/tables/class.ilTestQuestionsTableGUI.php @@ -16,6 +16,8 @@ * *********************************************************************/ +use ILIAS\Modules\Test\QuestionPoolLinkedTitleBuilder; + /** * * @author Helmut Schottmüller @@ -26,6 +28,7 @@ */ class ilTestQuestionsTableGUI extends ilTable2GUI { + use QuestionPoolLinkedTitleBuilder; private const CLASS_PATH_FOR_QUESTION_EDIT_LINKS = [ilRepositoryGUI::class, ilObjQuestionPoolGUI::class]; /** @@ -60,6 +63,7 @@ class ilTestQuestionsTableGUI extends ilTable2GUI protected \ILIAS\UI\Renderer $renderer; protected \ILIAS\UI\Factory $factory; + private ilAccess $access; public function __construct($a_parent_obj, $a_parent_cmd, $parentRefId) { @@ -67,6 +71,7 @@ public function __construct($a_parent_obj, $a_parent_cmd, $parentRefId) $this->renderer = $DIC->ui()->renderer(); $this->factory = $DIC->ui()->factory(); + $this->access = $DIC['ilAccess']; $this->setId('tst_qst_lst_' . $parentRefId); @@ -220,11 +225,25 @@ public function fillRow(array $a_set): void } } - if (ilObject::_lookupType((int) $a_set["orig_obj_fi"]) == 'qpl') { - $this->tpl->setVariable("QUESTION_POOL", ilObject::_lookupTitle($a_set["orig_obj_fi"])); - } else { - $this->tpl->setVariable("QUESTION_POOL", $this->lng->txt('tst_question_not_from_pool_info')); + $question_pool_title = $this->lng->txt('tst_question_not_from_pool_info'); + + if (isset($a_set['orig_obj_fi']) && ilObject::_lookupTitle($a_set['orig_obj_fi']) !== null) { + $question_pool_title = $this->buildPossiblyLinkedQuestonPoolTitle( + $this->ctrl, + $this->access, + $this->lng, + $this->factory, + $this->renderer, + $a_set["orig_obj_fi"], + ilObject::_lookupTitle($a_set["orig_obj_fi"]) + ); } + + $this->tpl->setVariable( + "QUESTION_POOL", + $question_pool_title + ); + $actions = new ilAdvancedSelectionListGUI(); $actions->setId('qst' . $a_set["question_id"]); $actions->setListTitle($this->lng->txt('actions')); @@ -378,7 +397,6 @@ protected function buildObligatoryColumnContent(array $rowData): string $icon = $this->factory->symbol()->icon()->custom( ilUtil::getImagePath("icon_checked.svg"), $this->lng->txt('question_obligatory') - ); return $this->renderer->render($icon); } diff --git a/Modules/Test/classes/tables/class.ilTestRandomQuestionSetSourcePoolDefinitionListTableGUI.php b/Modules/Test/classes/tables/class.ilTestRandomQuestionSetSourcePoolDefinitionListTableGUI.php index ee31178ccdc2..5abad6a61464 100644 --- a/Modules/Test/classes/tables/class.ilTestRandomQuestionSetSourcePoolDefinitionListTableGUI.php +++ b/Modules/Test/classes/tables/class.ilTestRandomQuestionSetSourcePoolDefinitionListTableGUI.php @@ -16,6 +16,10 @@ * *********************************************************************/ +use ILIAS\Modules\Test\QuestionPoolLinkedTitleBuilder; +use ILIAS\UI\Factory as UIFactory; +use ILIAS\UI\Renderer as UIRenderer; + /** * * @author Björn Heyser @@ -25,11 +29,17 @@ */ class ilTestRandomQuestionSetSourcePoolDefinitionListTableGUI extends ilTable2GUI { + use QuestionPoolLinkedTitleBuilder; public const IDENTIFIER = 'tstRndPools'; private bool $definitionEditModeEnabled; private bool $questionAmountColumnEnabled; private bool $showMappedTaxonomyFilter = false; private ?ilTestTaxonomyFilterLabelTranslater $taxonomyLabelTranslater = null; + + private ilAccess $access; + private UIFactory $ui_factory; + private UIRenderer $ui_renderer; + private \ILIAS\Test\InternalRequestService $testrequest; public function __construct(ilCtrl $ctrl, ilLanguage $lng, $parentGUI, $parentCMD) @@ -40,6 +50,9 @@ public function __construct(ilCtrl $ctrl, ilLanguage $lng, $parentGUI, $parentCM $this->lng = $lng; global $DIC; $this->testrequest = $DIC->test()->internal()->request(); + $this->access = $DIC['ilAccess']; + $this->ui_factory = $DIC['ui.factory']; + $this->ui_renderer = $DIC['ui.renderer']; $this->definitionEditModeEnabled = false; $this->questionAmountColumnEnabled = false; } @@ -115,11 +128,22 @@ public function fillRow(array $a_set): void $this->tpl->parseCurrentBlock(); } - $this->tpl->setVariable('SOURCE_POOL_LABEL', $a_set['source_pool_label']); + $this->tpl->setVariable( + 'SOURCE_POOL_LABEL', + $this->buildPossiblyLinkedQuestonPoolTitle( + $this->ctrl, + $this->access, + $this->lng, + $this->ui_factory, + $this->ui_renderer, + $a_set['ref_id'], + $a_set['source_pool_label'], + true + ) + ); // fau: taxFilter/typeFilter - set taxonomy/type filter label in a single coulumn each + $this->tpl->setVariable('TAXONOMY_FILTER', $this->taxonomyLabelTranslater->getTaxonomyFilterLabel($a_set['taxonomy_filter'], '
')); - #$this->tpl->setVariable('FILTER_TAXONOMY', $this->getTaxonomyTreeLabel($set['filter_taxonomy'])); - #$this->tpl->setVariable('FILTER_TAX_NODE', $this->getTaxonomyNodeLabel($set['filter_tax_node'])); $this->tpl->setVariable('LIFECYCLE_FILTER', $this->taxonomyLabelTranslater->getLifecycleFilterLabel($a_set['lifecycle_filter'])); $this->tpl->setVariable('TYPE_FILTER', $this->taxonomyLabelTranslater->getTypeFilterLabel($a_set['type_filter'])); // fau. @@ -291,7 +315,7 @@ public function init(ilTestRandomQuestionSetSourcePoolDefinitionList $sourcePool $set['type_filter'] = $sourcePoolDefinition->getTypeFilter(); // fau. $set['question_amount'] = $sourcePoolDefinition->getQuestionAmount(); - + $set['ref_id'] = $sourcePoolDefinition->getPoolRefId(); $rows[] = $set; } diff --git a/Modules/Test/templates/default/tpl.il_as_tst_answers_compare.html b/Modules/Test/templates/default/tpl.il_as_tst_answers_compare.html index a3f9b046b1ff..bd398d4888a0 100644 --- a/Modules/Test/templates/default/tpl.il_as_tst_answers_compare.html +++ b/Modules/Test/templates/default/tpl.il_as_tst_answers_compare.html @@ -2,6 +2,10 @@
{HEADER_PARTICIPANT}
{PARTICIPANT}
+ +
{TXT_INTERMEDIATE}
+
{INTERMEDIATE}
+
{HEADER_SOLUTION}
diff --git a/Modules/Test/templates/default/tpl.il_as_tst_question_browser_row.html b/Modules/Test/templates/default/tpl.il_as_tst_question_browser_row.html index 026d82ea6b65..b69e5f416fed 100644 --- a/Modules/Test/templates/default/tpl.il_as_tst_question_browser_row.html +++ b/Modules/Test/templates/default/tpl.il_as_tst_question_browser_row.html @@ -9,5 +9,5 @@ {QUESTION_LIFECYCLE} {QUESTION_CREATED} {QUESTION_UPDATED} - {QUESTION_POOL} + {QUESTION_POOL_OR_TEST_TITLE} \ No newline at end of file diff --git a/Modules/Test/test/AccessFileUploadPreviewTest.php b/Modules/Test/test/AccessFileUploadPreviewTest.php new file mode 100644 index 000000000000..1e2de67719cc --- /dev/null +++ b/Modules/Test/test/AccessFileUploadPreviewTest.php @@ -0,0 +1,125 @@ +getMockBuilder(ilDBInterface::class)->disableOriginalConstructor()->getMock(); + $access = $this->getMockBuilder(ilAccess::class)->disableOriginalConstructor()->getMock(); + $this->assertInstanceOf(AccessFileUploadPreview::class, new AccessFileUploadPreview($database, $access)); + } + + public function testNoUploadPath() : void + { + $database = $this->getMockBuilder(ilDBInterface::class)->disableOriginalConstructor()->getMock(); + $access = $this->getMockBuilder(ilAccess::class)->disableOriginalConstructor()->getMock(); + + $instance = new AccessFileUploadPreview($database, $access); + $result = $instance->isPermitted('/data/some/path/file.pdf'); + $this->assertFalse($result->isOk()); + } + + public function testFalseWithInvalidId() : void + { + $database = $this->getMockBuilder(ilDBInterface::class)->disableOriginalConstructor()->getMock(); + $access = $this->getMockBuilder(ilAccess::class)->disableOriginalConstructor()->getMock(); + $statement = $this->getMockBuilder(ilDBStatement::class)->disableOriginalConstructor()->getMock(); + + $database->expects(self::once())->method('queryF')->with('SELECT obj_fi FROM qpl_questions WHERE question_id = %s', [ilDBConstants::T_INTEGER], [383])->willReturn($statement); + $database->expects(self::once())->method('fetchAssoc')->with($statement)->willReturn(null); + + $instance = new AccessFileUploadPreview($database, $access); + $result = $instance->isPermitted('http://my-ilias/assessment/qst_preview/123/383/fileuploads/my-file.pdf'); + $this->assertTrue($result->isOk()); + $this->assertFalse($result->value()); + } + + /** + * @dataProvider types + */ + public function testWithTypes(?string $type, bool $permitted, ?string $requires_permission) : void + { + $database = $this->getMockBuilder(ilDBInterface::class)->disableOriginalConstructor()->getMock(); + $access = $this->getMockBuilder(ilAccess::class)->disableOriginalConstructor()->getMock(); + $statement = $this->getMockBuilder(ilDBStatement::class)->disableOriginalConstructor()->getMock(); + $incident = $this->getMockBuilder(Incident::class)->disableOriginalConstructor()->getMock(); + + $ref_called = 0; + $type_called = 0; + $references_of = $this->expectCall(383, [987], $ref_called); + $type_of = $this->expectCall(987, $type, $type_called); + + $database->expects(self::once())->method('queryF')->with('SELECT obj_fi FROM qpl_questions WHERE question_id = %s', [ilDBConstants::T_INTEGER], [383])->willReturn($statement); + $database->expects(self::once())->method('fetchAssoc')->with($statement)->willReturn(['obj_fi' => '383']); + + $incident->expects(self::once())->method('any')->willReturnCallback(function (callable $call_me, array $ref_ids) : bool { + $this->assertEquals([987], $ref_ids); + return $call_me(987); + }); + + if (null === $requires_permission) { + $access->expects(self::never())->method('checkAccess'); + } else { + $access->expects(self::once())->method('checkAccess')->with($requires_permission, '', 987)->willReturn($permitted); + } + + + $instance = new AccessFileUploadPreview($database, $access, $incident, $references_of, $type_of); + $result = $instance->isPermitted('http://my-ilias/assessment/qst_preview/123/383/fileuploads/my-file.pdf'); + $this->assertTrue($result->isOk()); + $this->assertSame($permitted, $result->value()); + + $this->assertSame(1, $ref_called); + $this->assertSame(1, $type_called); + } + + public function types() : array + { + return [ + 'Type qpl with access rights.' => ['qpl', false, 'read'], + 'Type qpl without access rights.' => ['qpl', true, 'read'], + 'Type tst with access rights.' => ['tst', false, 'write'], + 'Type tst without access rights.' => ['tst', true, 'write'], + 'Type crs will never has access rights.' => ['crs', false, null], + 'Unknown types will never have access rights.' => [null, false, null], + ]; + } + + private function expectCall($expected, $return, &$called): Closure + { + return function ($value) use ($expected, $return, &$called) { + $this->assertSame($expected, $value); + $called++; + return $return; + }; + } +} diff --git a/Modules/Test/test/ScoreSettingsTest.php b/Modules/Test/test/ScoreSettingsTest.php index e32db4997cb0..5ad3f0ea5d72 100644 --- a/Modules/Test/test/ScoreSettingsTest.php +++ b/Modules/Test/test/ScoreSettingsTest.php @@ -261,9 +261,7 @@ public function testScoreSettingsSectionSummary(): void $actual = $this->getDefaultRenderer()->render( $s->toForm(...array_merge($ui, [[ 'user_time_zone' => 'Europe/Berlin', - 'user_date_format' => $data_factory->dateFormat()->withTime24( - $data_factory->dateFormat()->standard() - ) + 'user_date_format' => $data_factory->dateFormat()->standard() ]])) ); diff --git a/Modules/Test/test/ilMarkSchemaGUITest.php b/Modules/Test/test/ilMarkSchemaGUITest.php index 3fb5d0ac14b9..b727383a6498 100644 --- a/Modules/Test/test/ilMarkSchemaGUITest.php +++ b/Modules/Test/test/ilMarkSchemaGUITest.php @@ -34,6 +34,8 @@ protected function setUp(): void $this->addGlobal_lng(); $this->addGlobal_tpl(); $this->addGlobal_ilToolbar(); + $this->addGlobal_uiFactory(); + $this->addGlobal_uiRenderer(); $this->testObj = new ilMarkSchemaGUI( $this->createMock(ilMarkSchemaAware::class) diff --git a/Modules/Test/test/tables/ilTestQuestionBrowserTableGUITest.php b/Modules/Test/test/tables/ilTestQuestionBrowserTableGUITest.php index 828f9d691b9f..ecf749716d65 100644 --- a/Modules/Test/test/tables/ilTestQuestionBrowserTableGUITest.php +++ b/Modules/Test/test/tables/ilTestQuestionBrowserTableGUITest.php @@ -79,6 +79,8 @@ protected function setUp(): void new \ILIAS\Data\Factory(), $this->getMockBuilder(ilLanguage::class)->disableOriginalConstructor()->getMock() ), + $this->createMock(ILIAS\UI\Factory::class), + $this->createMock(ILIAS\UI\Renderer::class), ); } diff --git a/Modules/Test/test/tables/ilTestQuestionsTableGUITest.php b/Modules/Test/test/tables/ilTestQuestionsTableGUITest.php index 4b99bb733cf5..0d0467a25856 100644 --- a/Modules/Test/test/tables/ilTestQuestionsTableGUITest.php +++ b/Modules/Test/test/tables/ilTestQuestionsTableGUITest.php @@ -31,6 +31,8 @@ protected function setUp(): void { parent::setUp(); + $this->addGlobal_ilAccess(); + $lng_mock = $this->createMock(ilLanguage::class); $ctrl_mock = $this->createMock(ilCtrl::class); $ctrl_mock->expects($this->any()) diff --git a/Modules/Test/test/tables/ilTestRandomQuestionSetSourcePoolDefinitionListTableGUITest.php b/Modules/Test/test/tables/ilTestRandomQuestionSetSourcePoolDefinitionListTableGUITest.php index 52da6e082abc..07f36963c3b0 100644 --- a/Modules/Test/test/tables/ilTestRandomQuestionSetSourcePoolDefinitionListTableGUITest.php +++ b/Modules/Test/test/tables/ilTestRandomQuestionSetSourcePoolDefinitionListTableGUITest.php @@ -31,6 +31,10 @@ protected function setUp(): void { parent::setUp(); + $this->addGlobal_uiFactory(); + $this->addGlobal_uiRenderer(); + $this->addGlobal_ilAccess(); + $lng_mock = $this->createMock(ilLanguage::class); $ctrl_mock = $this->createMock(ilCtrl::class); $ctrl_mock->expects($this->any()) @@ -54,7 +58,12 @@ protected function setUp(): void $ctrl_mock, $lng_mock, $this->parentObj_mock, - "" + "", + $this->createMock(ilAccess::class), + $this->createMock(ILIAS\UI\Factory::class), + $this->createMock(ILIAS\UI\Renderer::class), + [], + [] ); } diff --git a/Modules/Test/traits/QuestionPoolLinkedTitleBuilder.php b/Modules/Test/traits/QuestionPoolLinkedTitleBuilder.php new file mode 100644 index 000000000000..284f8545dfb4 --- /dev/null +++ b/Modules/Test/traits/QuestionPoolLinkedTitleBuilder.php @@ -0,0 +1,154 @@ +buildPossiblyLinkedTitle( + $ctrl, + $access, + $lng, + $ui_factory, + $ui_renderer, + $test_id, + $title, + \ilObjTestGUI::class + ); + } + + public function buildPossiblyLinkedQuestonPoolTitle( + \ilCtrl $ctrl, + \ilAccessHandler $access, + \ilLanguage $lng, + UIFactory $ui_factory, + UIRenderer $ui_renderer, + ?int $qpl_id, + string $title, + bool $reference = false + ): string { + if ($qpl_id === null) { + return $title; + } + + if (ilObject::_lookupType($qpl_id, $reference) !== 'qpl') { + return $lng->txt('tst_question_not_from_pool_info'); + } + + $qpl_obj_id = $qpl_id; + if ($reference) { + $qpl_obj_id = ilObject::_lookupObjId($qpl_id); + } + + return $this->buildPossiblyLinkedTitle( + $ctrl, + $access, + $lng, + $ui_factory, + $ui_renderer, + $qpl_obj_id, + $title, + \ilObjQuestionPoolGUI::class, + $reference + ); + } + + private function buildPossiblyLinkedTitle( + \ilCtrl $ctrl, + \ilAccessHandler $access, + \ilLanguage $lng, + UIFactory $ui_factory, + UIRenderer $ui_renderer, + int $obj_id, + string $title, + string $target_class_type, + bool $reference = false + ): string { + $ref_id = $this->getFirstReferenceWithCurrentUserAccess( + $access, + $reference, + $obj_id, + ilObject::_getAllReferences($obj_id) + ); + + if ($ref_id === null) { + return $title . ' (' . $lng->txt('status_no_permission') . ')'; + } + + return $ui_renderer->render( + $this->getLinkedTitle($ctrl, $ui_factory, $ref_id, $title, $target_class_type) + ); + } + + private function getLinkedTitle( + \ilCtrl $ctrl, + UIFactory $ui_factory, + int $ref_id, + string $title, + string $target_class_type + ): Link { + $ctrl->setParameterByClass($target_class_type, 'ref_id', $ref_id); + $linked_title = $ui_factory->link()->standard( + $title, + $ctrl->getLinkTargetByClass( + [$target_class_type] + ) + ); + $ctrl->clearParametersByClass($target_class_type); + return $linked_title; + } + + private function getFirstReferenceWithCurrentUserAccess( + \ilAccessHandler $access, + bool $reference, + int $obj_id, + array $all_ref_ids + ): ?int { + if ($reference && $access->checkAccess('read', '', $obj_id)) { + return $obj_id; + } + + $references_with_access = array_filter( + array_values($all_ref_ids), + function ($ref_id) use ($access) { + return $access->checkAccess('read', '', $ref_id); + } + ); + if ($references_with_access !== []) { + return array_shift($references_with_access); + } + return null; + } +} diff --git a/Modules/TestQuestionPool/Setup/class.ilFixMissingQuestionDuplicationMigration.php b/Modules/TestQuestionPool/Setup/class.ilFixMissingQuestionDuplicationMigration.php new file mode 100644 index 000000000000..0545ac0832b5 --- /dev/null +++ b/Modules/TestQuestionPool/Setup/class.ilFixMissingQuestionDuplicationMigration.php @@ -0,0 +1,110 @@ +db = $environment->getResource(Environment::RESOURCE_DATABASE); + } + + public function step(Environment $environment): void + { + $query_main = 'SELECT qst.question_id FROM tst_tests AS tst' + . ' INNER JOIN tst_test_question AS tst_qst' + . ' ON tst.test_id = tst_qst.test_fi' + . ' INNER JOIN qpl_questions AS qst' + . ' ON tst_qst.question_fi = qst.question_id' + . ' WHERE tst.question_set_type = "FIXED_QUEST_SET"' + . ' AND qst.obj_fi != tst.obj_fi' + . ' AND ISNULL(qst.original_id)'; + $result_main = $this->db->query($query_main); + while ($question = $this->db->fetchAssoc($result_main)) { + $question_obj = assQuestion::_instanciateQuestion((int) $question['question_id']); + $clone_id = $question_obj->duplicate(false); + + $this->db->update( + 'qpl_questions', + ['original_id' => ['integer', $clone_id]], + ['original_id' => ['integer', $question['question_id']]] + ); + + $test_query = 'SELECT obj_fi FROM tst_test_question INNER JOIN tst_tests ON tst_test_question.test_fi = tst_tests.test_id WHERE question_fi = %s'; + $test_result = $this->db->queryF($test_query, ['integer'], [$question['question_id']]); + + $this->db->update( + 'qpl_questions', + [ + 'original_id' => ['integer', $clone_id], + 'obj_fi' => ['integer', ($this->db->fetchObject($test_result))->obj_fi] + ], + ['question_id' => ['integer', $question['question_id']]] + ); + } + } + + public function getRemainingAmountOfSteps(): int + { + $query = 'SELECT COUNT(qst.question_id) AS cnt FROM tst_tests AS tst' + . ' INNER JOIN tst_test_question AS tst_qst' + . ' ON tst.test_id = tst_qst.test_fi' + . ' INNER JOIN qpl_questions AS qst' + . ' ON tst_qst.question_fi = qst.question_id' + . ' WHERE tst.question_set_type = "FIXED_QUEST_SET"' + . ' AND qst.obj_fi != tst.obj_fi' + . ' AND ISNULL(qst.original_id)'; + $result = $this->db->query($query); + $row = $this->db->fetchAssoc($result); + + return (int) ($row['cnt'] ?? 0); + } + +} diff --git a/Modules/TestQuestionPool/Setup/class.ilTestQuestionPoolSetupAgent.php b/Modules/TestQuestionPool/Setup/class.ilTestQuestionPoolSetupAgent.php index 1623732bdcbb..fed93034d7fa 100644 --- a/Modules/TestQuestionPool/Setup/class.ilTestQuestionPoolSetupAgent.php +++ b/Modules/TestQuestionPool/Setup/class.ilTestQuestionPoolSetupAgent.php @@ -16,19 +16,52 @@ * *********************************************************************/ -use ILIAS\Setup\Agent\NullAgent; +use ILIAS\Setup\Agent; +use ILIAS\Setup\Agent\HasNoNamedObjective; +use ILIAS\Setup\Config; use ILIAS\Setup\Objective; -use ILIAS\Setup\Metrics; +use ILIAS\Setup\Objective\NullObjective; +use ILIAS\Setup\Metrics\Storage; +use ILIAS\Refinery\Transformation; -class ilTestQuestionPoolSetupAgent extends NullAgent +class ilTestQuestionPoolSetupAgent implements Agent { + use HasNoNamedObjective; + public function getUpdateObjective(ILIAS\Setup\Config $config = null): ILIAS\Setup\Objective { return new ilDatabaseUpdateStepsExecutedObjective(new ilTestQuestionPool80DBUpdateSteps()); } - public function getStatusObjective(Metrics\Storage $storage): Objective + public function hasConfig(): bool + { + return false; + } + + public function getArrayToConfigTransformation(): Transformation + { + throw new \LogicException('Agent has no config.'); + } + + public function getInstallObjective(Config $config = null): Objective + { + return new NullObjective(); + } + + public function getBuildArtifactObjective(): Objective + { + return new NullObjective(); + } + + public function getStatusObjective(Storage $storage): Objective { return new \ilDatabaseUpdateStepsMetricsCollectedObjective($storage, new ilTestQuestionPool80DBUpdateSteps()); } + + public function getMigrations(): array + { + return [ + new ilFixMissingQuestionDuplicationMigration() + ]; + } } diff --git a/Modules/TestQuestionPool/classes/Form/class.ilClozeGapInputBuilderGUI.php b/Modules/TestQuestionPool/classes/Form/class.ilClozeGapInputBuilderGUI.php index a4d16621fb75..5d275b86a4ea 100644 --- a/Modules/TestQuestionPool/classes/Form/class.ilClozeGapInputBuilderGUI.php +++ b/Modules/TestQuestionPool/classes/Form/class.ilClozeGapInputBuilderGUI.php @@ -294,6 +294,7 @@ public function insert(ilTemplate $template): void $custom_template->setVariable('GAP_SIZE', $lng->txt('cloze_fixed_textlength')); $custom_template->setVariable('GAP_SIZE_INFO', $lng->txt('cloze_gap_size_info')); $custom_template->setVariable('ANSWER_TEXT', $lng->txt('answer_text')); + $custom_template->setVariable('ANSWER_BYLINE', $lng->txt('cloze_answer_text_info')); $custom_template->setVariable('POINTS', $lng->txt('points')); $custom_template->setVariable('VALUE', $lng->txt('value')); $custom_template->setVariable('UPPER_BOUND', $lng->txt('range_upper_limit')); @@ -315,9 +316,6 @@ public function insert(ilTemplate $template): void $custom_template->setVariable('VALUES', $lng->txt('values')); $custom_template->setVariable('GAP_COMBINATION', $lng->txt('gap_combination')); $custom_template->setVariable('COPY', $lng->txt('copy_of')); - $custom_template->setVariable('WHITESPACE_FRONT', $lng->txt('cloze_textgap_whitespace_before')); - $custom_template->setVariable('WHITESPACE_BACK', $lng->txt('cloze_textgap_whitespace_after')); - $custom_template->setVariable('WHITESPACE_MULTIPLE', $lng->txt('cloze_textgap_multiple_whitespace')); $template->setCurrentBlock('prop_generic'); $template->setVariable('PROP_GENERIC', $custom_template->get()); $template->parseCurrentBlock(); diff --git a/Modules/TestQuestionPool/classes/class.assAnswerMultipleResponse.php b/Modules/TestQuestionPool/classes/class.assAnswerMultipleResponse.php index 69ef53d9215a..c6c5ff9d9c39 100755 --- a/Modules/TestQuestionPool/classes/class.assAnswerMultipleResponse.php +++ b/Modules/TestQuestionPool/classes/class.assAnswerMultipleResponse.php @@ -32,10 +32,6 @@ class ASS_AnswerMultipleResponse extends ASS_AnswerSimple { /** * The points given to the answer when the answer is not checked - * - * The points given to the answer when the answer is not checked - * - * @var float|int|string|null */ public $points_unchecked; diff --git a/Modules/TestQuestionPool/classes/class.assClozeGap.php b/Modules/TestQuestionPool/classes/class.assClozeGap.php index 2009b75eecd9..fe226c75144d 100755 --- a/Modules/TestQuestionPool/classes/class.assClozeGap.php +++ b/Modules/TestQuestionPool/classes/class.assClozeGap.php @@ -385,11 +385,12 @@ public function getBestSolutionOutput(Transformation $shuffler, $combinations = array_push($best_solutions[$combinations['points']], $combinations['answer']); } else { foreach ($this->getItems($shuffler) as $answer) { - if (isset($best_solutions[$answer->getPoints()]) && is_array($best_solutions[$answer->getPoints()])) { - array_push($best_solutions[$answer->getPoints()], $answer->getAnswertext()); + $points_string_for_key = (string) $answer->getPoints(); + if (isset($best_solutions[$points_string_for_key]) && is_array($best_solutions[$points_string_for_key])) { + array_push($best_solutions[$points_string_for_key], $answer->getAnswertext()); } else { - $best_solutions[$answer->getPoints()] = []; - array_push($best_solutions[$answer->getPoints()], $answer->getAnswertext()); + $best_solutions[$points_string_for_key] = []; + array_push($best_solutions[$points_string_for_key], $answer->getAnswertext()); } } } diff --git a/Modules/TestQuestionPool/classes/class.assClozeTest.php b/Modules/TestQuestionPool/classes/class.assClozeTest.php index 866fe03a86cd..ed2de5d45ed9 100755 --- a/Modules/TestQuestionPool/classes/class.assClozeTest.php +++ b/Modules/TestQuestionPool/classes/class.assClozeTest.php @@ -593,7 +593,7 @@ public function getClozeText(): string public function getClozeTextForHTMLOutput(): string { $gaps = []; - preg_match_all('/\[gap\].*\[\/gap\]/', $this->getClozeText(), $gaps); + preg_match_all('/\[gap\].*?\[\/gap\]/', $this->getClozeText(), $gaps); $string_with_replaced_gaps = str_replace($gaps[0], '######GAP######', $this->getClozeText()); $cleaned_text = $this->getHtmlQuestionContentPurifier()->purify( $string_with_replaced_gaps @@ -775,7 +775,7 @@ public function addGapAnswer($gap_index, $order, $answer): void // only allow notation with "." for real numbers $answer = str_replace(",", ".", $answer); } - $this->gaps[$gap_index]->addItem(new assAnswerCloze($answer, 0, $order)); + $this->gaps[$gap_index]->addItem(new assAnswerCloze(trim($answer), 0, $order)); } } @@ -1211,26 +1211,19 @@ public function getTextgapPoints($a_original, $a_entered, $max_points): float * * @param string $a_original The original (correct) text * @param string $a_entered The text entered by the user - * @param integer $max_points The maximum number of points for the solution + * @param float $max_points The maximum number of points for the solution * @access public */ - public function getNumericgapPoints($a_original, $a_entered, $max_points, $lowerBound, $upperBound): int + public function getNumericgapPoints($a_original, $a_entered, $max_points, $lowerBound, $upperBound): float { - // fau: fixGapFormula - check entered value by evalMath - // if( ! $this->checkForValidFormula($a_entered) ) - // { - // return 0; - // } - include_once "./Services/Math/classes/class.EvalMath.php"; $eval = new EvalMath(); $eval->suppress_errors = true; - $result = 0; + $result = 0.0; if ($eval->e($a_entered) === false) { - return 0; + return 0.0; } elseif (($eval->e($lowerBound) !== false) && ($eval->e($upperBound) !== false)) { - // fau. if (($eval->e($a_entered) >= $eval->e($lowerBound)) && ($eval->e($a_entered) <= $eval->e($upperBound))) { $result = $max_points; } @@ -1242,10 +1235,8 @@ public function getNumericgapPoints($a_original, $a_entered, $max_points, $lower if (($eval->e($a_entered) >= $eval->e($a_original)) && ($eval->e($a_entered) <= $eval->e($upperBound))) { $result = $max_points; } - } else { - if ($eval->e($a_entered) == $eval->e($a_original)) { - $result = $max_points; - } + } elseif ($eval->e($a_entered) == $eval->e($a_original)) { + $result = $max_points; } return $result; } @@ -1336,11 +1327,11 @@ public function fetchSolutionSubmit($submit): array if (!$post_wrapper->has("gap_$index")) { continue; } - $value = $post_wrapper->retrieve( + $value = trim($post_wrapper->retrieve( "gap_$index", $this->dic->refinery()->kindlyTo()->string() - ); - if ($value === "") { + )); + if ($value === '') { continue; } @@ -1409,13 +1400,12 @@ public function saveWorkingData($active_id, $pass = null, $authorized = true): b $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(function () use (&$entered_values, $active_id, $pass, $authorized) { $this->removeCurrentSolution($active_id, $pass, $authorized); - foreach ($this->getSolutionSubmit() as $val1 => $val2) { - $value = trim($val2); + foreach ($this->getSolutionSubmit() as $key => $value) { if ($value !== null && $value !== '') { - $gap = $this->getGap(trim(ilUtil::stripSlashes($val1))); + $gap = $this->getGap(trim(ilUtil::stripSlashes($key))); if (is_object($gap)) { if (!(($gap->getType() == CLOZE_SELECT) && ($value == -1))) { - $this->saveCurrentSolution($active_id, $pass, $val1, $value, $authorized); + $this->saveCurrentSolution($active_id, $pass, $key, $value, $authorized); $entered_values++; } } diff --git a/Modules/TestQuestionPool/classes/class.assClozeTestGUI.php b/Modules/TestQuestionPool/classes/class.assClozeTestGUI.php index 0aa32c413d3d..21f4ab5b2154 100755 --- a/Modules/TestQuestionPool/classes/class.assClozeTestGUI.php +++ b/Modules/TestQuestionPool/classes/class.assClozeTestGUI.php @@ -1285,7 +1285,8 @@ public function getSpecificFeedbackOutput(array $userSolution): string foreach ($this->object->gaps as $gapIndex => $gap) { $answerValue = $this->object->fetchAnswerValueForGap($userSolution, $gapIndex); - if ($answerValue === '') { + if ($gap->getType() !== assClozeGap::TYPE_TEXT + && $answerValue === '') { continue; } $answerIndex = $this->object->feedbackOBJ->determineAnswerIndexForAnswerValue($gap, $answerValue); diff --git a/Modules/TestQuestionPool/classes/class.assErrorTextGUI.php b/Modules/TestQuestionPool/classes/class.assErrorTextGUI.php index 8bf515dd61f6..4af063698a3e 100644 --- a/Modules/TestQuestionPool/classes/class.assErrorTextGUI.php +++ b/Modules/TestQuestionPool/classes/class.assErrorTextGUI.php @@ -509,7 +509,7 @@ public function getAnswersFrequency($relevant_answers, $question_index): array $answers = []; foreach ($answers_by_active_and_pass as $answer) { - $error_text = $this->object->assembleErrorTextOutput($answer); + $error_text = '
' . $this->object->assembleErrorTextOutput($answer) . '
'; $error_text_hashed = md5($error_text); if (!isset($answers[$error_text_hashed])) { diff --git a/Modules/TestQuestionPool/classes/class.assFileUpload.php b/Modules/TestQuestionPool/classes/class.assFileUpload.php index e0c1815d601f..ce16a02c4a49 100644 --- a/Modules/TestQuestionPool/classes/class.assFileUpload.php +++ b/Modules/TestQuestionPool/classes/class.assFileUpload.php @@ -295,7 +295,7 @@ public function getMaximumPoints(): float * @param boolean $returndetails (deprecated !!) * @return integer/array $points/$details (array $details is deprecated !!) */ - public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution = true, $returndetails = false): int + public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution = true, $returndetails = false) { if ($returndetails) { throw new ilTestException('return details not implemented for ' . __METHOD__); diff --git a/Modules/TestQuestionPool/classes/class.assFlashQuestion.php b/Modules/TestQuestionPool/classes/class.assFlashQuestion.php index 5cdcd62a0bd6..78274d2a9e43 100644 --- a/Modules/TestQuestionPool/classes/class.assFlashQuestion.php +++ b/Modules/TestQuestionPool/classes/class.assFlashQuestion.php @@ -394,7 +394,7 @@ public function getMaximumPoints(): float * @param boolean $returndetails (deprecated !!) * @return integer/array $points/$details (array $details is deprecated !!) */ - public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution = true, $returndetails = false): int + public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution = true, $returndetails = false) { if ($returndetails) { throw new ilTestException('return details not implemented for ' . __METHOD__); diff --git a/Modules/TestQuestionPool/classes/class.assFormulaQuestion.php b/Modules/TestQuestionPool/classes/class.assFormulaQuestion.php index b60642197602..e53249b0399f 100755 --- a/Modules/TestQuestionPool/classes/class.assFormulaQuestion.php +++ b/Modules/TestQuestionPool/classes/class.assFormulaQuestion.php @@ -264,7 +264,7 @@ public function substituteVariables(array $userdata, bool $graphicalOutput = fal return false; } - $text = $this->getQuestionForHTMLOutput(); + $text = $this->getQuestion(); foreach ($this->fetchAllVariables($this->getQuestion()) as $varObj) { if (isset($userdata[$varObj->getVariable()]) && strlen($userdata[$varObj->getVariable()])) { @@ -277,6 +277,8 @@ public function substituteVariables(array $userdata, bool $graphicalOutput = fal $text = preg_replace("/\\$" . substr($varObj->getVariable(), 1) . "(?![0-9]+)/", $val . " " . $unit . "\\1", $text); } + $text = $this->purifyAndPrepareTextAreaOutput($text); + if (preg_match_all("/(\\\$r\\d+)/ims", $this->getQuestion(), $rmatches)) { foreach ($rmatches[1] as $result) { $resObj = $this->getResult($result); @@ -293,10 +295,14 @@ public function substituteVariables(array $userdata, bool $graphicalOutput = fal if (is_array($userdata) && isset($userdata[$result]) && isset($userdata[$result]["value"])) { - $input = $this->generateResultInputHtml($result, $userdata[$result]["value"]); + + $input = $this->generateResultInputHTML($result, $userdata[$result]["value"], $forsolution); } elseif ($forsolution) { - $value = $resObj->calculateFormula($this->getVariables(), $this->getResults(), parent::getId()); - $value = sprintf("%." . $resObj->getPrecision() . "f", $value); + $value = ''; + if (!is_array($userdata)) { + $value = $resObj->calculateFormula($this->getVariables(), $this->getResults(), parent::getId()); + $value = sprintf("%." . $resObj->getPrecision() . "f", $value); + } if ($is_frac) { $value = assFormulaQuestionResult::convertDecimalToCoprimeFraction($value); @@ -304,14 +310,11 @@ public function substituteVariables(array $userdata, bool $graphicalOutput = fal $frac_helper = $value[1]; $value = $value[0]; } - $value = ' value="' . $value . '"'; } - $input = '' . ilLegacyFormElementsUtil::prepareFormOutput( - $value - ) . ''; + $input = $this->generateResultInputHTML($result, $value, true); } else { - $input = $this->generateResultInputHTML($result, ''); + $input = $this->generateResultInputHTML($result, '', false); } $units = ""; @@ -352,7 +355,7 @@ public function substituteVariables(array $userdata, bool $graphicalOutput = fal $units .= ' ' . $this->lng->txt('expected_result_type') . ': ' . $this->lng->txt('result_dec'); break; case assFormulaQuestionResult::RESULT_FRAC: - if (strlen($frac_helper)) { + if ($frac_helper !== '') { $units .= ' ≈ ' . $frac_helper . ', '; } elseif (is_array($userdata) && array_key_exists($result, $userdata) && @@ -365,7 +368,7 @@ public function substituteVariables(array $userdata, bool $graphicalOutput = fal $units .= ' ' . $this->lng->txt('expected_result_type') . ': ' . $this->lng->txt('result_frac'); break; case assFormulaQuestionResult::RESULT_CO_FRAC: - if (strlen($frac_helper)) { + if ($frac_helper !== '') { $units .= ' ≈ ' . $frac_helper . ', '; } elseif (is_array($userdata) && isset($userdata[$result]) && isset($userdata[$result]["frac_helper"]) && $userdata[$result]["frac_helper"] !== '') { if (!preg_match('-/-', $value)) { @@ -452,8 +455,13 @@ public function substituteVariables(array $userdata, bool $graphicalOutput = fal return $text; } - protected function generateResultInputHTML(string $result_key, string $result_value): string + protected function generateResultInputHTML(string $result_key, string $result_value, bool $forsolution): string { + if ($forsolution) { + return '' + . ilLegacyFormElementsUtil::prepareFormOutput($result_value) + . ''; + } $input = 'getSolutionMaxPass($active_id); diff --git a/Modules/TestQuestionPool/classes/class.assImagemapQuestion.php b/Modules/TestQuestionPool/classes/class.assImagemapQuestion.php index 4a6eca19f303..765b7bbf2554 100755 --- a/Modules/TestQuestionPool/classes/class.assImagemapQuestion.php +++ b/Modules/TestQuestionPool/classes/class.assImagemapQuestion.php @@ -682,7 +682,7 @@ public function getMaximumPoints(): float * @param boolean $returndetails (deprecated !!) * @return integer/array $points/$details (array $details is deprecated !!) */ - public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution = true, $returndetails = false): int + public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution = true, $returndetails = false) { if ($returndetails) { throw new ilTestException('return details not implemented for ' . __METHOD__); diff --git a/Modules/TestQuestionPool/classes/class.assLongMenu.php b/Modules/TestQuestionPool/classes/class.assLongMenu.php index 3d70f5bc0f0f..85fe35ea3178 100644 --- a/Modules/TestQuestionPool/classes/class.assLongMenu.php +++ b/Modules/TestQuestionPool/classes/class.assLongMenu.php @@ -178,9 +178,9 @@ public function isComplete(): bool return false; } - public function saveToDb(): void + public function saveToDb(int $original_id = -1): void { - $this->saveQuestionDataToDb(-1); + $this->saveQuestionDataToDb($original_id); $this->saveAdditionalQuestionDataToDb(); $this->saveAnswerSpecificDataToDb(); parent::saveToDb(); diff --git a/Modules/TestQuestionPool/classes/class.assLongMenuGUI.php b/Modules/TestQuestionPool/classes/class.assLongMenuGUI.php index 1c0581a14537..43e6e1d42750 100644 --- a/Modules/TestQuestionPool/classes/class.assLongMenuGUI.php +++ b/Modules/TestQuestionPool/classes/class.assLongMenuGUI.php @@ -552,6 +552,9 @@ private function getTextGapTemplate($key, $value, $solution, $ok = false, $graph $tpl->setVariable("ICON_OK", $correctness_icon); } } + if ($solution) { + $tpl->setVariable('SIZE', 'size="' . mb_strlen($value) . '"'); + } $tpl->setVariable('VALUE', $value); $tpl->setVariable('KEY', $key); diff --git a/Modules/TestQuestionPool/classes/class.assOrderingHorizontal.php b/Modules/TestQuestionPool/classes/class.assOrderingHorizontal.php index 6706d2eb765d..28ded903ab10 100644 --- a/Modules/TestQuestionPool/classes/class.assOrderingHorizontal.php +++ b/Modules/TestQuestionPool/classes/class.assOrderingHorizontal.php @@ -277,7 +277,7 @@ public function getMaximumPoints(): float * @param boolean $returndetails (deprecated !!) * @return integer/array $points/$details (array $details is deprecated !!) */ - public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution = true, $returndetails = false): int + public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution = true, $returndetails = false) { if ($returndetails) { throw new ilTestException('return details not implemented for ' . __METHOD__); @@ -343,6 +343,11 @@ public function getSolutionSubmit() */ public function saveWorkingData($active_id, $pass = null, $authorized = true): bool { + global $DIC; + if($DIC->testQuestionPool()->internal()->request()->raw('test_answer_changed') === null) { + return true; + } + global $DIC; $ilDB = $DIC['ilDB']; $ilUser = $DIC['ilUser']; diff --git a/Modules/TestQuestionPool/classes/class.assOrderingQuestion.php b/Modules/TestQuestionPool/classes/class.assOrderingQuestion.php index f44cb174d57f..ff45a0550b3f 100755 --- a/Modules/TestQuestionPool/classes/class.assOrderingQuestion.php +++ b/Modules/TestQuestionPool/classes/class.assOrderingQuestion.php @@ -674,7 +674,7 @@ public function getAnswerCount(): int * @param boolean $returndetails (deprecated !!) * @return integer/array $points/$details (array $details is deprecated !!) */ - public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution = true, $returndetails = false): int + public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution = true, $returndetails = false) { if ($returndetails) { throw new ilTestException('return details not implemented for ' . __METHOD__); @@ -870,6 +870,11 @@ public function validateSolutionSubmit(): bool */ public function saveWorkingData($active_id, $pass = null, $authorized = true): bool { + global $DIC; + if($DIC->testQuestionPool()->internal()->request()->raw('test_answer_changed') === null) { + return true; + } + $entered_values = 0; if (is_null($pass)) { @@ -1263,12 +1268,11 @@ public function getSolutionListFromPostSubmit(): ilAssOrderingElementList { if ($this->postSolutionOrderingElementList === null) { $post_array = $_POST; - if (! is_array($post_array)) { + if (!is_array($post_array)) { global $DIC; $request = $DIC->http()->request(); $post_array = $request->getParsedBody(); } - $list = $this->fetchSolutionListFromFormSubmissionData($post_array); $this->postSolutionOrderingElementList = $list; } diff --git a/Modules/TestQuestionPool/classes/class.assOrderingQuestionGUI.php b/Modules/TestQuestionPool/classes/class.assOrderingQuestionGUI.php index bfd3d80ff0d8..da571dacb385 100755 --- a/Modules/TestQuestionPool/classes/class.assOrderingQuestionGUI.php +++ b/Modules/TestQuestionPool/classes/class.assOrderingQuestionGUI.php @@ -190,9 +190,11 @@ public function writeQuestionSpecificPostData(ilPropertyFormGUI $form): void $this->updateImageFiles(); } - $this->object->setPoints((int)$this->request->raw("points")); + $points = (float) str_replace(',', '.', $this->request->raw('points')); - $use_nested = $this->request->raw(self::F_USE_NESTED) === "1"; + $this->object->setPoints($points); + + $use_nested = $this->request->raw(self::F_USE_NESTED) === '1'; $this->object->setNestingType($use_nested); } @@ -434,7 +436,7 @@ public function supportsIntermediateSolutionOutput() * @param boolean $result_output Show the reached points for parts of the question * @param boolean $show_question_only Show the question without the ILIAS content around * @param boolean $show_feedback Show the question feedback - * @param boolean $show_correct_solution Show the correct solution instead of the user solution + * @param boolean $show_correct_solution Show the correct solution instead of the user solution * @param boolean $show_manual_scoring Show specific information for the manual scoring output * @param bool $show_question_text * @return string The solution output of the question as HTML code @@ -472,7 +474,9 @@ public function getSolutionOutput( $answers_gui->setCorrectnessTrueElementList( $solutionOrderingList->getParityTrueElementList($this->object->getOrderingElementList()) ); - + if (!$show_correct_solution) { + $answers_gui->setShowCorrectnessIconsEnabled(true); + } $solution_html = $answers_gui->getHTML(); $template = new ilTemplate("tpl.il_as_qpl_nested_ordering_output_solution.html", true, true, "Modules/TestQuestionPool"); diff --git a/Modules/TestQuestionPool/classes/class.assQuestion.php b/Modules/TestQuestionPool/classes/class.assQuestion.php index 561215a1e24c..3168411a21c3 100755 --- a/Modules/TestQuestionPool/classes/class.assQuestion.php +++ b/Modules/TestQuestionPool/classes/class.assQuestion.php @@ -61,6 +61,8 @@ abstract class assQuestion protected const DEFAULT_THUMB_SIZE = 150; protected const MINIMUM_THUMB_SIZE = 20; + public const TRIM_PATTERN = '/^[\p{C}\p{Z}]+|[\p{C}\p{Z}]+$/u'; + protected ILIAS\HTTP\Services $http; protected ILIAS\Refinery\Factory $refinery; @@ -125,7 +127,7 @@ abstract class assQuestion */ protected array $suggested_solutions; - protected ?int $original_id; + protected ?int $original_id = null; /** * Page object @@ -2608,7 +2610,7 @@ public function setPoints(float $points): void $this->points = $points; } - public function getSolutionMaxPass(int $active_id): int + public function getSolutionMaxPass(int $active_id) { return self::_getSolutionMaxPass($this->getId(), $active_id); } @@ -2616,7 +2618,7 @@ public function getSolutionMaxPass(int $active_id): int /** * Returns the maximum pass a users question solution */ - public static function _getSolutionMaxPass(int $question_id, int $active_id): int + public static function _getSolutionMaxPass(int $question_id, int $active_id) { /* include_once "./Modules/Test/classes/class.ilObjTest.php"; $pass = ilObjTest::_getPass($active_id); @@ -2635,7 +2637,7 @@ public static function _getSolutionMaxPass(int $question_id, int $active_id): in ); if ($result->numRows() == 1) { $row = $ilDB->fetchAssoc($result); - return (int) $row["maxpass"]; + return $row["maxpass"]; } return 0; @@ -2966,13 +2968,18 @@ public function getQuestion(): string public function getQuestionForHTMLOutput(): string { - $question_text = $this->getHtmlQuestionContentPurifier()->purify($this->question); + return $this->purifyAndPrepareTextAreaOutput($this->question); + } + + protected function purifyAndPrepareTextAreaOutput(string $content) : string + { + $purified_content = $this->getHtmlQuestionContentPurifier()->purify($content); if ($this->isAdditionalContentEditingModePageObject() || !(new ilSetting('advanced_editing'))->get('advanced_editing_javascript_editor') === 'tinymce') { - $question_text = nl2br($question_text); + $purified_content = nl2br($purified_content); } return $this->prepareTextareaOutput( - $question_text, + $purified_content, true, true ); @@ -4194,4 +4201,24 @@ public function isInActiveTest(): bool $res = $this->db->query($query); return $res->numRows() > 0; } + + /** + * Trim non-printable characters from the beginning and end of a string. + * + * Note: The PHP trim() function is not fully Unicode-compatible and may not handle + * non-printable characters effectively. As a result, it may not trim certain Unicode + * characters, such as control characters, zero width characters or ideographic space as expected. + * + * This method provides a workaround for trimming non-printable characters until PHP 8.4, + * where the mb_trim() function is introduced. Users are encouraged to migrate to mb_trim() + * for proper Unicode and non-printable character handling. + * + * @param string $value The string to trim. + * @return string The trimmed string. + */ + public static function extendedTrim(string $value): string + { + return preg_replace(self::TRIM_PATTERN, '', $value); + } + } diff --git a/Modules/TestQuestionPool/classes/class.assQuestionGUI.php b/Modules/TestQuestionPool/classes/class.assQuestionGUI.php index 9441389548e3..05eb57811c27 100755 --- a/Modules/TestQuestionPool/classes/class.assQuestionGUI.php +++ b/Modules/TestQuestionPool/classes/class.assQuestionGUI.php @@ -748,16 +748,23 @@ public function save(): void $test = new ilObjTest($this->request->raw("calling_test"), true); $testQuestionSetConfigFactory = new ilTestQuestionSetConfigFactory($tree, $ilDB, $component_repository, $test); + $new_q_id = $this->object->getId(); + if ($test->getRefId() !== $this->request->int('ref_id')) { + $new_q_id = $this->object->duplicate(true, $this->object->getTitle(), $this->object->getAuthor(), $this->object->getOwner(), $test->getId()); + } + $test->insertQuestion( $testQuestionSetConfigFactory->getQuestionSetConfig(), - $this->object->getId(), + $new_q_id, true ); if ($this->request->isset('prev_qid')) { - $test->moveQuestionAfter($this->object->getId(), $this->request->raw('prev_qid')); + $test->moveQuestionAfter($new_q_id, $this->request->raw('prev_qid')); } + $this->ctrl->setParameter($this, 'q_id', $new_q_id); + $this->ctrl->setParameter($this, 'ref_id', $this->request->raw('calling_test')); $this->ctrl->setParameter($this, 'calling_test', $this->request->raw("calling_test")); } $this->tpl->setOnScreenMessage('success', $this->lng->txt("msg_obj_modified"), true); @@ -836,16 +843,23 @@ public function saveReturn(): void $testQuestionSetConfigFactory = new ilTestQuestionSetConfigFactory($tree, $ilDB, $component_repository, $test); + $new_q_id = $this->object->getId(); + if ($test->getRefId() !== $this->request->int('ref_id')) { + $new_q_id = $this->object->duplicate(true, $this->object->getTitle(), $this->object->getAuthor(), $this->object->getOwner(), $test->getId()); + } + $test->insertQuestion( $testQuestionSetConfigFactory->getQuestionSetConfig(), - $this->object->getId(), + $new_q_id, true ); if ($this->request->isset('prev_qid')) { - $test->moveQuestionAfter($this->object->getId(), $this->request->raw('prev_qid')); + $test->moveQuestionAfter($new_q_id, $this->request->raw('prev_qid')); } + $this->ctrl->setParameter($this, 'q_id', $new_q_id); + $this->ctrl->setParameter($this, 'ref_id', $this->request->raw('calling_test')); $this->ctrl->setParameter($this, 'calling_test', $this->request->raw("calling_test")); } $this->tpl->setOnScreenMessage('success', $this->lng->txt("msg_obj_modified"), true); diff --git a/Modules/TestQuestionPool/classes/class.assSingleChoice.php b/Modules/TestQuestionPool/classes/class.assSingleChoice.php index f4f9738a605f..be3ecd32039b 100755 --- a/Modules/TestQuestionPool/classes/class.assSingleChoice.php +++ b/Modules/TestQuestionPool/classes/class.assSingleChoice.php @@ -565,7 +565,7 @@ public function getMaximumPoints(): float * @param boolean $returndetails (deprecated !!) * @return integer/array $points/$details (array $details is deprecated !!) */ - public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution = true, $returndetails = false): int + public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution = true, $returndetails = false) { if ($returndetails) { throw new ilTestException('return details not implemented for ' . __METHOD__); diff --git a/Modules/TestQuestionPool/classes/class.assTextQuestion.php b/Modules/TestQuestionPool/classes/class.assTextQuestion.php index 8b4bbb1bcac3..48cbb467c8c8 100755 --- a/Modules/TestQuestionPool/classes/class.assTextQuestion.php +++ b/Modules/TestQuestionPool/classes/class.assTextQuestion.php @@ -1092,6 +1092,9 @@ public function countLetters($text): int public function countWords($text): int { + if($text === '') { + return 0; + } $text = str_replace(' ', ' ', $text); $text = preg_replace('/[.,:;!?\-_#\'"+*\\/=()&%§$]/m', '', $text); @@ -1103,10 +1106,8 @@ public function countWords($text): int return count(explode(' ', $text)); } - public function getLatestAutosaveContent($active_id) + public function getLatestAutosaveContent(int $active_id, int $pass): ?string { - $question_fi = $this->getId(); - // Do we have an unauthorized result? $cntresult = $this->db->query( ' @@ -1115,6 +1116,7 @@ public function getLatestAutosaveContent($active_id) WHERE active_fi = ' . $this->db->quote($active_id, 'int') . ' AND question_fi = ' . $this->db->quote($this->getId(), 'int') . ' AND authorized = ' . $this->db->quote(0, 'int') + . ' AND pass = ' . $this->db->quote($pass, 'int') ); $row = $this->db->fetchAssoc($cntresult); if ($row['cnt'] > 0) { @@ -1125,10 +1127,11 @@ public function getLatestAutosaveContent($active_id) WHERE active_fi = ' . $this->db->quote($active_id, 'int') . ' AND question_fi = ' . $this->db->quote($this->getId(), 'int') . ' AND authorized = ' . $this->db->quote(0, 'int') + . ' AND pass = ' . $this->db->quote($pass, 'int') ); $trow = $this->db->fetchAssoc($tresult); return $trow['value1']; } - return ''; + return null; } } diff --git a/Modules/TestQuestionPool/classes/class.assTextQuestionGUI.php b/Modules/TestQuestionPool/classes/class.assTextQuestionGUI.php index 81d34bff40ab..b8ffd53625f6 100755 --- a/Modules/TestQuestionPool/classes/class.assTextQuestionGUI.php +++ b/Modules/TestQuestionPool/classes/class.assTextQuestionGUI.php @@ -277,7 +277,8 @@ public function getAutoSavedSolutionOutput( $show_feedback = false, $show_correct_solution = false, $show_manual_scoring = false, - $show_question_text = true + $show_question_text = true, + $show_autosave_title = false ): string { // get the solution of the user for the active pass or from the last pass if allowed @@ -294,15 +295,24 @@ public function getAutoSavedSolutionOutput( $template = new ilTemplate("tpl.il_as_qpl_text_question_output_solution.html", true, true, "Modules/TestQuestionPool"); $solutiontemplate = new ilTemplate("tpl.il_as_tst_solution_output.html", true, true, "Modules/TestQuestionPool"); - $solution = $this->object->getHtmlUserSolutionPurifier()->purify($this->object->getLatestAutosaveContent($active_id)); - if ($this->renderPurposeSupportsFormHtml()) { - $template->setCurrentBlock('essay_div'); - $template->setVariable("DIV_ESSAY", $this->object->prepareTextareaOutput($solution, true)); - } else { - $template->setCurrentBlock('essay_textarea'); - $template->setVariable("TA_ESSAY", $this->object->prepareTextareaOutput($solution, true, true)); + $solution = ''; + $autosaved_solution = $this->object->getLatestAutosaveContent($active_id, $pass); + if(!is_null($autosaved_solution)) { + if ($show_autosave_title) { + $template->setCurrentBlock('autosave_title'); + $template->setVariable('AUTOSAVE_TITLE', $this->lng->txt('autosavecontent')); + $template->parseCurrentBlock(); + } + $solution = $this->object->getHtmlUserSolutionPurifier()->purify($autosaved_solution); + if ($this->renderPurposeSupportsFormHtml()) { + $template->setCurrentBlock('essay_div'); + $template->setVariable("DIV_ESSAY", ilLegacyFormElementsUtil::prepareTextareaOutput($solution, true)); + } else { + $template->setCurrentBlock('essay_textarea'); + $template->setVariable("TA_ESSAY", ilLegacyFormElementsUtil::prepareTextareaOutput($solution, true, true)); + } + $template->parseCurrentBlock(); } - $template->parseCurrentBlock(); if (!$show_correct_solution) { $max_no_of_chars = $this->object->getMaxNumOfChars(); diff --git a/Modules/TestQuestionPool/classes/class.assTextSubset.php b/Modules/TestQuestionPool/classes/class.assTextSubset.php index 8d045cf05be0..d83f57a32cbe 100755 --- a/Modules/TestQuestionPool/classes/class.assTextSubset.php +++ b/Modules/TestQuestionPool/classes/class.assTextSubset.php @@ -541,7 +541,7 @@ public function setTextRating($a_text_rating): void * @param boolean $returndetails (deprecated !!) * @return integer/array $points/$details (array $details is deprecated !!) */ - public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution = true, $returndetails = false): int + public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution = true, $returndetails = false) { if ($returndetails) { throw new ilTestException('return details not implemented for ' . __METHOD__); @@ -853,7 +853,7 @@ protected function getSolutionSubmit(): array $this->dic->refinery()->kindlyTo()->string() ); if ($value) { - $value = trim($value); + $value = $this->extendedTrim($value); $value = $purifier->purify($value); $solutionSubmit[] = $value; } @@ -865,7 +865,6 @@ protected function getSolutionSubmit(): array /** * @param $enteredTexts - * @return int */ protected function calculateReachedPointsForSolution($enteredTexts): float { diff --git a/Modules/TestQuestionPool/classes/class.assTextSubsetGUI.php b/Modules/TestQuestionPool/classes/class.assTextSubsetGUI.php index c2b9e3024eb7..bb2d1aa905be 100755 --- a/Modules/TestQuestionPool/classes/class.assTextSubsetGUI.php +++ b/Modules/TestQuestionPool/classes/class.assTextSubsetGUI.php @@ -167,11 +167,12 @@ public function getSolutionOutput( } else { $rank = array(); foreach ($this->object->answers as $answer) { + $points_string_for_key = (string) $answer->getPoints(); if ($answer->getPoints() > 0) { - if (!array_key_exists($answer->getPoints(), $rank)) { - $rank[$answer->getPoints()] = array(); + if (!array_key_exists($points_string_for_key, $rank)) { + $rank[$points_string_for_key] = array(); } - array_push($rank[$answer->getPoints()], $answer->getAnswertext()); + array_push($rank[$points_string_for_key], $answer->getAnswertext()); } } krsort($rank, SORT_NUMERIC); @@ -255,7 +256,7 @@ public function getPreview($show_question_only = false, $showInlineFeedback = fa } } $template->setVariable("COUNTER", $i + 1); - $template->setVariable("TEXTFIELD_ID", $i + 1); + $template->setVariable("TEXTFIELD_ID", $i); $template->setVariable("TEXTFIELD_SIZE", $width); $template->parseCurrentBlock(); } @@ -288,7 +289,7 @@ public function getTestOutput($active_id, $pass = null, $is_postponed = false, $ } } $template->setVariable("COUNTER", $i + 1); - $template->setVariable("TEXTFIELD_ID", $i + 1); + $template->setVariable("TEXTFIELD_ID", $i); $template->setVariable("TEXTFIELD_SIZE", $width); $template->parseCurrentBlock(); } @@ -315,7 +316,8 @@ public function writeAnswerSpecificPostData(ilPropertyFormGUI $form): void // Delete all existing answers and create new answers from the form data $this->object->flushAnswers(); foreach ($this->answers_from_post as $index => $answertext) { - $this->object->addAnswer(htmlentities(trim($answertext)), $_POST['answers']['points'][$index], $index); + $answertext = assQuestion::extendedTrim($answertext); + $this->object->addAnswer(htmlentities($answertext), $_POST['answers']['points'][$index], $index); } } diff --git a/Modules/TestQuestionPool/classes/class.ilAssQuestionPreviewGUI.php b/Modules/TestQuestionPool/classes/class.ilAssQuestionPreviewGUI.php index 332ab888623f..3aa8c5e6b1a5 100644 --- a/Modules/TestQuestionPool/classes/class.ilAssQuestionPreviewGUI.php +++ b/Modules/TestQuestionPool/classes/class.ilAssQuestionPreviewGUI.php @@ -151,7 +151,9 @@ public function initQuestion($questionId, $parentObjId): void $this->tabs->setBackTarget($this->lng->txt("qpl"), ilLink::_getLink($ref_id)); } } else { - $this->tabs->setBackTarget($this->lng->txt("backtocallingpool"), $this->ctrl->getLinkTargetByClass("ilobjquestionpoolgui", "questions")); + $this->ctrl->clearParameterByClass(ilObjQuestionPoolGUI::class, 'q_id'); + $this->tabs->setBackTarget($this->lng->txt("backtocallingpool"), $this->ctrl->getLinkTargetByClass(ilObjQuestionPoolGUI::class, "questions")); + $this->ctrl->setParameterByClass(ilObjQuestionPoolGUI::class, 'q_id', $questionId); } } } diff --git a/Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php b/Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php index 33c33c76707c..620ebb5f1c51 100755 --- a/Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php +++ b/Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php @@ -1329,8 +1329,11 @@ public function cloneObject(int $a_target_id, int $a_copy_id = 0, bool $a_omit_t { $newObj = parent::cloneObject($a_target_id, $a_copy_id, $a_omit_tree); - $newObj->setOnline(false); - $newObj->update(); + $cp_options = ilCopyWizardOptions::_getInstance($a_copy_id); + $newObj->setOnline($this->getOnline()); + if ($cp_options->isRootNode($this->getRefId())) { + $newObj->setOnline(0); + } $newObj->setSkillServiceEnabled($this->isSkillServiceEnabled()); $newObj->setShowTaxonomies($this->getShowTaxonomies()); diff --git a/Modules/TestQuestionPool/classes/class.ilObjQuestionPoolGUI.php b/Modules/TestQuestionPool/classes/class.ilObjQuestionPoolGUI.php index bf71d1c6e342..3b3ee909b2c3 100755 --- a/Modules/TestQuestionPool/classes/class.ilObjQuestionPoolGUI.php +++ b/Modules/TestQuestionPool/classes/class.ilObjQuestionPoolGUI.php @@ -556,7 +556,7 @@ public function download_paragraphObject(): void public function uploadQplObject($questions_only = false) { $this->ctrl->setParameter($this, 'new_type', $this->qplrequest->raw('new_type')); - if ($_FILES["xmldoc"]["error"] > UPLOAD_ERR_OK) { + if (!isset($_FILES['xmldoc']) || !isset($_FILES['xmldoc']['error']) || $_FILES['xmldoc']['error'] > UPLOAD_ERR_OK) { $this->tpl->setOnScreenMessage('failure', $this->lng->txt("error_upload"), true); if (!$questions_only) { $this->ctrl->redirect($this, 'create'); @@ -655,7 +655,7 @@ public function uploadQplObject($questions_only = false) ilSession::set("qpl_import_subdir", $subdir); $this->tpl->addBlockFile("ADM_CONTENT", "adm_content", "tpl.qpl_import_verification.html", "Modules/TestQuestionPool"); - $table = new ilQuestionPoolImportVerificationTableGUI($this, 'uploadQplObject'); + $table = new ilQuestionPoolImportVerificationTableGUI($this, 'uploadQpl'); $rows = array(); foreach ($founditems as $item) { diff --git a/Modules/TestQuestionPool/classes/feedback/class.ilAssClozeTestFeedback.php b/Modules/TestQuestionPool/classes/feedback/class.ilAssClozeTestFeedback.php index a62bd6f3e575..2a4d7594a2ca 100644 --- a/Modules/TestQuestionPool/classes/feedback/class.ilAssClozeTestFeedback.php +++ b/Modules/TestQuestionPool/classes/feedback/class.ilAssClozeTestFeedback.php @@ -770,6 +770,28 @@ protected function fetchFeedbackIdsForGapAnswersMode(): array return $feedbackIds; } + public function isSpecificAnswerFeedbackAvailable(int $question_id): bool + { + if ($this->questionOBJ->getFeedbackMode() === self::FB_MODE_GAP_QUESTION) { + $feedback_ids = $this->fetchFeedbackIdsForGapQuestionMode(); + } else { + $feedback_ids = $this->fetchFeedbackIdsForGapAnswersMode(); + } + + if ($this->questionOBJ->isAdditionalContentEditingModePageObject()) { + $all_feedback_content = ''; + foreach ($feedback_ids as $feedback_id) { + $all_feedback_content .= $this->getPageObjectXML( + $this->getSpecificAnswerFeedbackPageObjectType(), + $feedback_id + ); + } + return trim(strip_tags($all_feedback_content)) !== ''; + } + + return implode('', $this->getSpecificFeedbackContentForFeedbackIds($feedback_ids)) !== ''; + } + /** * @param int[] $feedbackIds */ diff --git a/Modules/TestQuestionPool/classes/feedback/class.ilAssMultiOptionQuestionFeedback.php b/Modules/TestQuestionPool/classes/feedback/class.ilAssMultiOptionQuestionFeedback.php index 23ae5954bd80..e04b014e5842 100644 --- a/Modules/TestQuestionPool/classes/feedback/class.ilAssMultiOptionQuestionFeedback.php +++ b/Modules/TestQuestionPool/classes/feedback/class.ilAssMultiOptionQuestionFeedback.php @@ -153,7 +153,7 @@ public function getAllSpecificAnswerFeedbackContents(int $questionId): string require_once 'Services/RTE/classes/class.ilRTE.php'; $res = $this->db->queryF( - "SELECT * FROM {$this->getSpecificFeedbackTableName()} WHERE question_fi = %s", + "SELECT feedback FROM {$this->getSpecificFeedbackTableName()} WHERE question_fi = %s", ['integer'], [$questionId] ); @@ -298,6 +298,25 @@ final protected function getSpecificAnswerFeedbackId(int $questionId, int $quest return $row['feedback_id'] ?? -1; } + /** + * + * @param array $feedback_ids + * @return array + */ + protected function getSpecificFeedbackContentForFeedbackIds(array $feedback_ids): array + { + $res = $this->db->query( + "SELECT feedback_id, feedback FROM {$this->getSpecificFeedbackTableName()} WHERE " + . $this->db->in('feedback_id', $feedback_ids, false, ilDBConstants::T_INTEGER) + ); + + $content = []; + while($row = $this->db->fetchAssoc($res)) { + $content[$row['feedback_id']] = $row['feedback']; + } + return $content; + } + protected function isSpecificAnswerFeedbackId(int $feedbackId): bool { $row = $this->db->fetchAssoc($this->db->queryF( diff --git a/Modules/TestQuestionPool/classes/forms/class.ilAssNestedOrderingElementsInputGUI.php b/Modules/TestQuestionPool/classes/forms/class.ilAssNestedOrderingElementsInputGUI.php index 471890ab0fd1..184ece8320f5 100644 --- a/Modules/TestQuestionPool/classes/forms/class.ilAssNestedOrderingElementsInputGUI.php +++ b/Modules/TestQuestionPool/classes/forms/class.ilAssNestedOrderingElementsInputGUI.php @@ -34,6 +34,8 @@ class ilAssNestedOrderingElementsInputGUI extends ilMultipleNestedOrderingElemen public const ILC_CSS_CLASS_LIST = 'ilc_qordul_OrderList'; public const ILC_CSS_CLASS_ITEM = 'ilc_qordli_OrderListItem'; + public const DEFAULT_THUMBNAIL_PREFIX = 'thumb.'; + /** * @var string */ @@ -49,8 +51,6 @@ class ilAssNestedOrderingElementsInputGUI extends ilMultipleNestedOrderingElemen */ protected $orderingType = null; - public const DEFAULT_THUMBNAIL_PREFIX = 'thumb.'; - /** * @var string */ @@ -335,6 +335,7 @@ protected function getItemHtml($element, $identifier, $position, $itemSubFieldPo $correctness = 'correct'; } $tpl->setCurrentBlock('correctness_icon'); + $tpl->setVariable("ICON_OK", $this->getCorrectnessIcon($correctness)); $tpl->parseCurrentBlock(); } diff --git a/Modules/TestQuestionPool/classes/forms/class.ilIdentifiedMultiValuesInputGUI.php b/Modules/TestQuestionPool/classes/forms/class.ilIdentifiedMultiValuesInputGUI.php index 9b569430d7b1..eea91e38938b 100644 --- a/Modules/TestQuestionPool/classes/forms/class.ilIdentifiedMultiValuesInputGUI.php +++ b/Modules/TestQuestionPool/classes/forms/class.ilIdentifiedMultiValuesInputGUI.php @@ -213,7 +213,7 @@ protected function buildMultiValueSubmitVar($identifier, $positionIndex, $submit final public function setValueByArray(array $a_values): void { - if (!is_array($a_values[$this->getPostVar()])) { + if (!isset($a_values[$this->getPostVar()]) || !is_array($a_values[$this->getPostVar()])) { $a_values[$this->getPostVar()] = []; } diff --git a/Modules/TestQuestionPool/classes/forms/class.ilMultipleImagesInputGUI.php b/Modules/TestQuestionPool/classes/forms/class.ilMultipleImagesInputGUI.php index 16d2f52d985b..9ccaa46c6d0b 100644 --- a/Modules/TestQuestionPool/classes/forms/class.ilMultipleImagesInputGUI.php +++ b/Modules/TestQuestionPool/classes/forms/class.ilMultipleImagesInputGUI.php @@ -175,40 +175,40 @@ public function onCheckInput(): bool switch ($error) { case UPLOAD_ERR_FORM_SIZE: case UPLOAD_ERR_INI_SIZE: - $this->setAlert($this->lng->txt("form_msg_file_size_exceeds")); - return false; - break; + $this->setAlert($this->lng->txt("form_msg_file_size_exceeds")); + return false; + break; case UPLOAD_ERR_PARTIAL: - $this->setAlert($this->lng->txt("form_msg_file_partially_uploaded")); - return false; - break; - - case UPLOAD_ERR_NO_FILE: - if (!$this->getRequired()) { + $this->setAlert($this->lng->txt("form_msg_file_partially_uploaded")); + return false; break; - } elseif (isset($F[self::FILE_DATA_INDEX_DODGING_FILE][$index]) && $F[self::FILE_DATA_INDEX_DODGING_FILE][$index] !== '') { + + case UPLOAD_ERR_NO_FILE: + if (!$this->getRequired()) { + break; + } elseif (isset($F[self::FILE_DATA_INDEX_DODGING_FILE][$index]) && $F[self::FILE_DATA_INDEX_DODGING_FILE][$index] !== '') { + break; + } + $this->setAlert($this->lng->txt("form_msg_file_no_upload")); + return false; break; - } - $this->setAlert($this->lng->txt("form_msg_file_no_upload")); - return false; - break; - case UPLOAD_ERR_NO_TMP_DIR: - $this->setAlert($this->lng->txt("form_msg_file_missing_tmp_dir")); - return false; - break; + case UPLOAD_ERR_NO_TMP_DIR: + $this->setAlert($this->lng->txt("form_msg_file_missing_tmp_dir")); + return false; + break; - case UPLOAD_ERR_CANT_WRITE: - $this->setAlert($this->lng->txt("form_msg_file_cannot_write_to_disk")); - return false; - break; + case UPLOAD_ERR_CANT_WRITE: + $this->setAlert($this->lng->txt("form_msg_file_cannot_write_to_disk")); + return false; + break; - case UPLOAD_ERR_EXTENSION: - $this->setAlert($this->lng->txt("form_msg_file_upload_stopped_ext")); - return false; - break; - } + case UPLOAD_ERR_EXTENSION: + $this->setAlert($this->lng->txt("form_msg_file_upload_stopped_ext")); + return false; + break; + } } } } @@ -338,27 +338,31 @@ public function render(string $a_mode = ""): string $tpl->setVariable("COMMANDS_TEXT", $lng->txt('actions')); if (!$this->getDisabled()) { - $config = [ - 'fieldContainerSelector' => '.ilWzdContainerImage', - 'reindexingRequiredElementsSelectors' => [ - 'input:hidden[name*="[' . self::ITERATOR_SUBFIELD_NAME . ']"]', - 'input:file[id*="__' . self::IMAGE_UPLOAD_SUBFIELD_NAME . '__"]', - 'input:submit[name*="[' . $this->getImageUploadCommand() . ']"]', - 'input:submit[name*="[' . $this->getImageRemovalCommand() . ']"]', - 'button' - ], - 'handleRowCleanUpCallback' => 'function(rowElem) - { - $(rowElem).find("div.imagepresentation").remove(); - $(rowElem).find("input[type=text]").val(""); - }' - ]; + $iterator_subfield_name = self::ITERATOR_SUBFIELD_NAME; + $image_upload_subfield_name = self::IMAGE_UPLOAD_SUBFIELD_NAME; + + $init_code = <<tpl->addJavascript("./Services/Form/js/ServiceFormWizardInput.js"); $this->tpl->addJavascript("./Services/Form/js/ServiceFormIdentifiedWizardInputExtend.js"); - $this->tpl->addOnLoadCode("$.extend({}, ilWizardInput, ilIdentifiedWizardInputExtend).init(" - . json_encode($config) - . ");"); + $this->tpl->addOnLoadCode($init_code); } return $tpl->get(); diff --git a/Modules/TestQuestionPool/classes/questions/class.ilAssOrderingElementList.php b/Modules/TestQuestionPool/classes/questions/class.ilAssOrderingElementList.php index baab4d297111..59ce55b8fdb1 100644 --- a/Modules/TestQuestionPool/classes/questions/class.ilAssOrderingElementList.php +++ b/Modules/TestQuestionPool/classes/questions/class.ilAssOrderingElementList.php @@ -295,11 +295,9 @@ public function elementExistByPosition($position): bool public function getElementByRandomIdentifier($randomIdentifier): ?ilAssOrderingElement { foreach ($this as $element) { - if ($element->getRandomIdentifier() != $randomIdentifier) { - continue; + if ($element->getRandomIdentifier() === intval($randomIdentifier)) { + return $element; } - - return $element; } return null; @@ -486,8 +484,10 @@ protected function fetchIdentifier(ilAssOrderingElement $element, string $identi protected function populateIdentifier(ilAssOrderingElement $element, $identifierType, $identifier): void { switch ($identifierType) { - case self::IDENTIFIER_TYPE_SOLUTION: $element->setSolutionIdentifier($identifier); break; - case self::IDENTIFIER_TYPE_RANDOM: $element->setRandomIdentifier($identifier); break; + case self::IDENTIFIER_TYPE_SOLUTION: $element->setSolutionIdentifier($identifier); + break; + case self::IDENTIFIER_TYPE_RANDOM: $element->setRandomIdentifier($identifier); + break; default: $this->throwUnknownIdentifierTypeException($identifierType); } } diff --git a/Modules/TestQuestionPool/classes/tables/class.ilAnswerFrequencyStatisticTableGUI.php b/Modules/TestQuestionPool/classes/tables/class.ilAnswerFrequencyStatisticTableGUI.php index 014efdacaf50..ba487b58f6e8 100644 --- a/Modules/TestQuestionPool/classes/tables/class.ilAnswerFrequencyStatisticTableGUI.php +++ b/Modules/TestQuestionPool/classes/tables/class.ilAnswerFrequencyStatisticTableGUI.php @@ -141,7 +141,7 @@ public function initColumns(): void public function fillRow(array $a_set): void { $this->tpl->setCurrentBlock('answer'); - $this->tpl->setVariable('ANSWER', ilLegacyFormElementsUtil::prepareFormOutput($a_set['answer'])); + $this->tpl->setVariable('ANSWER', ilHtmlPurifierFactory::getInstanceByType('qpl_usersolution')->purify($a_set['answer'])); $this->tpl->parseCurrentBlock(); $this->tpl->setCurrentBlock('frequency'); @@ -183,4 +183,18 @@ protected function buildAddAnswerAction($data): string return $ui_renderer->render($show_modal_button); } + + protected function purifyAndPrepareTextAreaOutput(string $content): string + { + $purified_content = $this->getHtmlQuestionContentPurifier()->purify($content); + if ($this->isAdditionalContentEditingModePageObject() + || !(new ilSetting('advanced_editing'))->get('advanced_editing_javascript_editor') === 'tinymce') { + $purified_content = nl2br($purified_content); + } + return ilLegacyFormElementsUtil::prepareTextareaOutput( + $purified_content, + true, + true + ); + } } diff --git a/Modules/TestQuestionPool/classes/tables/class.ilQuestionPoolImportVerificationTableGUI.php b/Modules/TestQuestionPool/classes/tables/class.ilQuestionPoolImportVerificationTableGUI.php index e548f13f2cf3..e588e830ad0c 100644 --- a/Modules/TestQuestionPool/classes/tables/class.ilQuestionPoolImportVerificationTableGUI.php +++ b/Modules/TestQuestionPool/classes/tables/class.ilQuestionPoolImportVerificationTableGUI.php @@ -38,7 +38,7 @@ public function __construct($a_parent_obj, $a_parent_cmd = "", $a_template_conte $this->setRowTemplate('tpl.qpl_import_verification_row.html', 'Modules/TestQuestionPool'); $this->addMultiCommand('importVerifiedFile', $this->lng->txt("import")); $this->addCommandButton('cancelImport', $this->lng->txt("cancel")); - + $this->setShowRowsSelector(false); // Removed due to #38976 - losing upload file on roundtrip $this->initColumns(); } diff --git a/Modules/TestQuestionPool/templates/default/clozeQuestionGapBuilder.js b/Modules/TestQuestionPool/templates/default/clozeQuestionGapBuilder.js index 60d9e213f078..bff50c245dd9 100644 --- a/Modules/TestQuestionPool/templates/default/clozeQuestionGapBuilder.js +++ b/Modules/TestQuestionPool/templates/default/clozeQuestionGapBuilder.js @@ -19,7 +19,6 @@ var ClozeGlobals = { form_error: 'form_error', form_warning: 'form_warning', best_combination: '', - whitespace_cleaner: false, best_possible_solution_error: false, debug: false, jour_fixe_incompatible: false, @@ -61,9 +60,12 @@ var ClozeQuestionGapBuilder = (function () { if (gap.type === 'text' || gap.type === 'select') { gap.values.forEach( (value) => { - value.answer = value.answer.replace('{','{'); - value.answer = value.answer.replace('}','}'); - } + if (value.answer === undefined) { + value.answer = ''; + } + value.answer = value.answer.replace('{', '{'); + value.answer = value.answer.replace('}', '}'); + }, ); } } @@ -863,8 +865,6 @@ var ClozeQuestionGapBuilder = (function () { $('#gap_error_' + gap_id).find('.value.form_error').addClass('prototype'); } } - pro.checkInputTextForWhitespaces(gap_id, selector, selector.val()); - ClozeGlobals.whitespace_cleaner = false; }; pro.checkForm = function () { @@ -935,9 +935,6 @@ var ClozeQuestionGapBuilder = (function () { } var failed = pro.checkInputElementNotEmpty($('#gap_' + row + '\\[answer\\]\\[' + counter + '\\]'), values.answer); input_failed += failed; - if (entry.type == 'text' && failed === 0) { - pro.checkInputTextForWhitespaces(row, $('#gap_' + row + '\\[answer\\]\\[' + counter + '\\]'), values.answer); - } counter++; }); if (input_failed > 0) { @@ -974,7 +971,6 @@ var ClozeQuestionGapBuilder = (function () { } } row++; - ClozeGlobals.whitespace_cleaner = false; }); $('#gap_json_post').val(JSON.stringify(ClozeSettings.gaps_php)); $('#gap_json_combination_post').val(JSON.stringify(ClozeSettings.gaps_combination)); @@ -1040,48 +1036,6 @@ var ClozeQuestionGapBuilder = (function () { } }; - pro.checkInputTextForWhitespaces = function (id, selector, value) { - var error = false; - if (/^\s/.test(value)) { - pro.showHidePrototypes(id, 'wsB', true); - error = true; - ClozeGlobals.whitespace_cleaner = true; - } - else if (!error && !ClozeGlobals.whitespace_cleaner) { - pro.showHidePrototypes(id, 'wsB', false); - } - if (/\s$/.test(value)) { - pro.showHidePrototypes(id, 'wsA', true); - error = true; - ClozeGlobals.whitespace_cleaner = true; - } - else if (!error && !ClozeGlobals.whitespace_cleaner) { - pro.showHidePrototypes(id, 'wsA', false); - } - if (/\s{2,}/.test(value)) { - pro.showHidePrototypes(id, 'wsM', true); - error = true; - ClozeGlobals.whitespace_cleaner = true; - } - else if (!error && !ClozeGlobals.whitespace_cleaner) { - pro.showHidePrototypes(id, 'wsM', false); - } - if (error === true) { - pro.highlightYellow(selector); - } - else if (!error && !ClozeGlobals.whitespace_cleaner) { - pro.removeHighlightYellow(selector); - } - - }; - - pro.clearInputTextWithWhitespaces = function (value) { - value = value.replace(/\s{2,}/g, ''); - value = value.replace(/^\s/, ''); - value = value.replace(/\s$/, ''); - return value; - }; - pro.focusOnFormular = function (pos) { pro.cloneFormPart(pos[0]); //ToDo: fix fokus diff --git a/Modules/TestQuestionPool/templates/default/tpl.il_as_cloze_gap_builder.html b/Modules/TestQuestionPool/templates/default/tpl.il_as_cloze_gap_builder.html index 52fdac4398bf..9427c3314d09 100644 --- a/Modules/TestQuestionPool/templates/default/tpl.il_as_cloze_gap_builder.html +++ b/Modules/TestQuestionPool/templates/default/tpl.il_as_cloze_gap_builder.html @@ -121,6 +121,7 @@

+
{ANSWER_BYLINE}
@@ -207,9 +208,6 @@

{MISSING_VALUE}
{NOT_A_FORMULA}
{NOT_A_NUMBER}
- {WHITESPACE_FRONT}
- {WHITESPACE_BACK}
- {WHITESPACE_MULTIPLE}
@@ -248,7 +246,7 @@

GAP_COMBINATION

{POINTS}
- +
@@ -274,4 +272,4 @@

{BEST_POSSIBLE_SOLUTION_HEADER}

- + diff --git a/Modules/TestQuestionPool/templates/default/tpl.il_as_qpl_longmenu_question_text_gap.html b/Modules/TestQuestionPool/templates/default/tpl.il_as_qpl_longmenu_question_text_gap.html index 28035decd4e7..7d08da3c1b39 100644 --- a/Modules/TestQuestionPool/templates/default/tpl.il_as_qpl_longmenu_question_text_gap.html +++ b/Modules/TestQuestionPool/templates/default/tpl.il_as_qpl_longmenu_question_text_gap.html @@ -1 +1 @@ -{ICON_OK} \ No newline at end of file +{ICON_OK} \ No newline at end of file diff --git a/Modules/TestQuestionPool/templates/default/tpl.il_as_qpl_question_printview.html b/Modules/TestQuestionPool/templates/default/tpl.il_as_qpl_question_printview.html index 0c21244477a2..3a8506299b28 100644 --- a/Modules/TestQuestionPool/templates/default/tpl.il_as_qpl_question_printview.html +++ b/Modules/TestQuestionPool/templates/default/tpl.il_as_qpl_question_printview.html @@ -10,7 +10,11 @@
{OBJECTIVES}

{SOLUTION_OUTPUT}

- + +

{TXT_INTERMEDIATE}

+

{INTERMEDIATE}

+ +

{RESULT_POINTS}

diff --git a/Modules/TestQuestionPool/templates/default/tpl.il_as_qpl_text_question_output_solution.html b/Modules/TestQuestionPool/templates/default/tpl.il_as_qpl_text_question_output_solution.html index 99a99b7f88bc..56dfd8176783 100755 --- a/Modules/TestQuestionPool/templates/default/tpl.il_as_qpl_text_question_output_solution.html +++ b/Modules/TestQuestionPool/templates/default/tpl.il_as_qpl_text_question_output_solution.html @@ -1,3 +1,6 @@ + +{AUTOSAVE_TITLE}: +
{QUESTIONTEXT}
diff --git a/Modules/WebResource/classes/Setup/class.ilWebResourceDBUpdateSteps.php b/Modules/WebResource/classes/Setup/class.ilWebResourceDBUpdateSteps.php index 99c9a87eb558..4ed50bf1e74c 100644 --- a/Modules/WebResource/classes/Setup/class.ilWebResourceDBUpdateSteps.php +++ b/Modules/WebResource/classes/Setup/class.ilWebResourceDBUpdateSteps.php @@ -1,7 +1,5 @@ @@ -38,4 +38,16 @@ public function step_1(): void $this->db->addIndex('webr_items', ['webr_id'], 'i3'); } } + + public function step_2(): void + { + // Add combined index + // 32201 + if ( + $this->db->tableExists('webr_items') && + !$this->db->indexExistsByFields('webr_items', ['webr_id', 'active']) + ) { + $this->db->addIndex('webr_items', ['webr_id', 'active'], 'i4'); + } + } } diff --git a/Modules/WebResource/classes/class.ilObjLinkResource.php b/Modules/WebResource/classes/class.ilObjLinkResource.php index 26e36719fda0..345f21c19e2b 100755 --- a/Modules/WebResource/classes/class.ilObjLinkResource.php +++ b/Modules/WebResource/classes/class.ilObjLinkResource.php @@ -1,7 +1,5 @@ */ class ilObjLinkResource extends ilObject { + protected ilWebLinkDatabaseRepository $repo; + public function __construct(int $a_id = 0, bool $a_call_by_reference = true) { $this->type = "webr"; @@ -32,7 +34,10 @@ public function __construct(int $a_id = 0, bool $a_call_by_reference = true) protected function getWebLinkRepo(): ilWebLinkRepository { - return new ilWebLinkDatabaseRepository($this->getId()); + if (isset($this->repo)) { + return $this->repo; + } + return $this->repo = new ilWebLinkDatabaseRepository($this->getId()); } /** @@ -66,7 +71,11 @@ protected function doMDUpdateListener(string $a_element): void $description = $md_des->getDescription(); break; } - if ($a_element === 'General' && $this->getWebLinkRepo()->doesOnlyOneItemExist(true)) { + if ( + $a_element === 'General' && + !$this->getWebLinkRepo()->doesListExist() && + $this->getWebLinkRepo()->doesOnlyOneItemExist() + ) { $item = ilObjLinkResourceAccess::_getFirstLink($this->getId()); $draft = new ilWebLinkDraftItem( $item->isInternal(), @@ -78,6 +87,17 @@ protected function doMDUpdateListener(string $a_element): void ); $this->getWebLinkRepo()->updateItem($item, $draft); } + if ( + $a_element === 'General' && + $this->getWebLinkRepo()->doesListExist() + ) { + $list = $this->getWebLinkRepo()->getList(); + $draft = new ilWebLinkDraftList( + $title, + $description + ); + $this->getWebLinkRepo()->updateList($list, $draft); + } } public function delete(): bool @@ -146,7 +166,7 @@ public function cloneObject( public function toXML(ilXmlWriter $writer): void { $attribs = array("obj_id" => "il_" . IL_INST_ID . "_webr_" . $this->getId( - ) + ) ); $writer->xmlStartTag('WebLinks', $attribs); @@ -174,6 +194,13 @@ public function toXML(ilXmlWriter $writer): void break; } + if ($this->getWebLinkRepo()->doesListExist()) { + $writer->xmlStartTag('ListSettings'); + $writer->xmlElement('ListTitle', [], $this->getTitle()); + $writer->xmlElement('ListDescription', [], $this->getDescription()); + $writer->xmlEndTag('ListSettings'); + } + // All items $items = $this->getWebLinkRepo()->getAllItemsAsContainer() ->sort() diff --git a/Modules/WebResource/classes/class.ilWebLinkXmlParser.php b/Modules/WebResource/classes/class.ilWebLinkXmlParser.php index df8f2d25f4b2..7952793804c6 100644 --- a/Modules/WebResource/classes/class.ilWebLinkXmlParser.php +++ b/Modules/WebResource/classes/class.ilWebLinkXmlParser.php @@ -1,7 +1,5 @@ @@ -53,6 +53,10 @@ class ilWebLinkXmlParser extends ilMDSaxParser private ?string $current_description; private ?bool $current_internal; + private bool $is_list = false; + private string $list_title; + private string $list_description; + public function __construct(ilObjLinkResource $webr, string $xml) { parent::__construct(); @@ -212,10 +216,12 @@ public function handlerBeginTag( case 'WebLinks': $this->sorting_positions = array(); - // no break + // no break case 'Title': case 'Description': case 'Target': + case 'ListTitle': + case 'ListDescription': // Nothing to do break; @@ -258,6 +264,10 @@ public function handlerBeginTag( $this->current_parameters[] = $param; break; + + case 'ListSettings': + $this->is_list = true; + break; } } @@ -274,6 +284,22 @@ public function handlerEndTag($a_xml_parser, string $a_name): void break; case 'WebLinks': + if ($this->is_list || !$this->web_link_repo->doesOnlyOneItemExist()) { + $list_draft = new ilWebLinkDraftList( + $this->list_title ?? $this->getWebLink()->getTitle(), + $this->list_description ?? $this->getWebLink()->getDescription() + ); + + if (!$this->web_link_repo->doesListExist()) { + $this->web_link_repo->createList($list_draft); + } else { + $this->web_link_repo->updateList( + $this->web_link_repo->getList(), + $list_draft + ); + } + } + $this->getWebLink()->MDUpdateListener('General'); $this->getWebLink()->update(); @@ -345,6 +371,14 @@ public function handlerEndTag($a_xml_parser, string $a_name): void case 'Target': $this->current_target = trim($this->cdata); break; + + case 'ListTitle': + $this->list_title = trim($this->cdata); + break; + + case 'ListDescription': + $this->list_description = trim($this->cdata); + break; } // Reset cdata diff --git a/Modules/Wiki/LuceneObjectDefinition.xml b/Modules/Wiki/LuceneObjectDefinition.xml index e17b5ee8c2de..7d1a9ee27498 100644 --- a/Modules/Wiki/LuceneObjectDefinition.xml +++ b/Modules/Wiki/LuceneObjectDefinition.xml @@ -48,7 +48,7 @@ - SELECT 'wiki' type,id,title,wiki_id,create_user,content,'wpg' metaSubType,id metaSubId + SELECT 'wiki' objType,id,title,wiki_id,wiki_id metaRbacId,create_user,content,'wpg' metaType,id metaObjId FROM il_wiki_page wp JOIN page_object po ON wp.id = po.page_id WHERE wiki_id IN(?) @@ -62,7 +62,7 @@ - + diff --git a/Modules/Wiki/classes/class.ilObjWiki.php b/Modules/Wiki/classes/class.ilObjWiki.php index dd643362a8b9..32a8a5731f89 100755 --- a/Modules/Wiki/classes/class.ilObjWiki.php +++ b/Modules/Wiki/classes/class.ilObjWiki.php @@ -701,6 +701,7 @@ public function cloneObject(int $target_id, int $copy_id = 0, bool $omit_tree = foreach (self::_lookupImportantPagesList($this->getId()) as $ip) { $new_obj->addImportantPage($map[$ip["page_id"]], $ip["ord"], $ip["indent"]); } + $this->updateInternalLinksOnCopy($map); // copy rating categories foreach (ilRatingCategory::getAllForObject($this->getId()) as $rc) { @@ -714,6 +715,26 @@ public function cloneObject(int $target_id, int $copy_id = 0, bool $omit_tree = return $new_obj; } + protected function updateInternalLinksOnCopy(array $map) : void + { + foreach ($map as $old_page_id => $new_page_id) { + // get links with targets inside the wiki + $targets = ilInternalLink::_getTargetsOfSource( + "wpg:pg", + $old_page_id, + "-" + ); + foreach ($targets as $t) { + if ((int) $t["inst"] === 0 && in_array($t["type"], ["wpag", "wpage"]) && isset($map[(int) $t["id"]])) { + $new_page = new ilWikiPage($new_page_id); + if ($new_page->moveIntLinks([$t["id"] => $map[(int) $t["id"]]])) { + $new_page->update(true, true); + } + } + } + } + } + /** * Get template selection on creation? If more than one template (including empty page template) * is activated -> return true diff --git a/Modules/Wiki/classes/class.ilPCAMDPageList.php b/Modules/Wiki/classes/class.ilPCAMDPageList.php index 55384e99e107..9f350a5d7dcf 100644 --- a/Modules/Wiki/classes/class.ilPCAMDPageList.php +++ b/Modules/Wiki/classes/class.ilPCAMDPageList.php @@ -268,14 +268,14 @@ public function modifyPageContentPostXsl( } /** - * Migrate search/filter values on advmd change + * Migrate search/filter values on advmd change. In the mapping, keys are + * indices of old options, and values indices of associated new options. + * @param int $a_field_id + * @param string[] $mapping */ public static function migrateField( - int $a_obj_id, int $a_field_id, - string $old_option, - string $new_option, - bool $a_is_multi = false + array $mapping ): void { global $DIC; @@ -287,24 +287,29 @@ public static function migrateField( " WHERE field_id = " . $ilDB->quote($a_field_id, "integer")); while ($row = $ilDB->fetchAssoc($set)) { $data = unserialize(unserialize($row["sdata"], ["allowed_classes" => false]), ["allowed_classes" => false]); - if (is_array($data) && - in_array($old_option, $data)) { + if (!is_array($data)) { + continue; + } + $updated_data = $data; + foreach ($mapping as $old_option => $new_option) { + if (!in_array($old_option, $data)) { + continue; + } $idx = array_search($old_option, $data); - if ($new_option) { - $data[$idx] = $new_option; + if ($new_option !== '') { + $updated_data[$idx] = $new_option; } else { - unset($data[$idx]); + unset($updated_data[$idx]); } - - $fields = array( - "sdata" => array("text", serialize(serialize($data))) - ); - $primary = array( - "id" => array("integer", $row["id"]), - "field_id" => array("integer", $row["field_id"]) - ); - $ilDB->update("pg_amd_page_list", $fields, $primary); } + $fields = array( + "sdata" => array("text", serialize(serialize($updated_data))) + ); + $primary = array( + "id" => array("integer", $row["id"]), + "field_id" => array("integer", $row["field_id"]) + ); + $ilDB->update("pg_amd_page_list", $fields, $primary); } } } diff --git a/Modules/Wiki/classes/class.ilWikiPage.php b/Modules/Wiki/classes/class.ilWikiPage.php index 755cfc90c029..3b5afeef7597 100755 --- a/Modules/Wiki/classes/class.ilWikiPage.php +++ b/Modules/Wiki/classes/class.ilWikiPage.php @@ -236,10 +236,10 @@ public function read( $rec = $ilDB->fetchAssoc($set); $this->setTitle($rec["title"]); - $this->setWikiId($rec["wiki_id"]); - $this->setBlocked($rec["blocked"]); - $this->setRating($rec["rating"]); - $this->hideAdvancedMetadata($rec["hide_adv_md"]); + $this->setWikiId((int) $rec["wiki_id"]); + $this->setBlocked((bool) $rec["blocked"]); + $this->setRating((bool) $rec["rating"]); + $this->hideAdvancedMetadata((bool) $rec["hide_adv_md"]); // get co page if (!$a_omit_page_read) { @@ -539,6 +539,7 @@ public function saveInternalLinks( // *** STEP 2: Other Pages -> This Page *** + $this->log->debug("this page <- other pages."); // Check, whether ANOTHER page links to this page as a "missing" page // (this is the case, when this page is created newly) @@ -549,6 +550,8 @@ public function saveInternalLinks( array($this->getWikiId(), ilWikiUtil::makeDbTitle($this->getTitle())) ); while ($anmiss = $ilDB->fetchAssoc($set)) { // insert internal links instead + $this->log->debug("link found, source: " . $anmiss["source_id"] . ", target" . + $this->getId()); //echo "adding link"; ilInternalLink::_saveLink( "wpg:pg", @@ -569,6 +572,7 @@ public function saveInternalLinks( // *** STEP 3: This Page -> Other Pages *** + $this->log->debug("this page -> other pages."); // remove the exising "missing page" links for THIS page (they will be re-inserted below) $ilDB->manipulateF( @@ -582,9 +586,11 @@ public function saveInternalLinks( $xml = $a_domdoc->saveXML(); $int_wiki_links = ilWikiUtil::collectInternalLinks($xml, $this->getWikiId(), true); foreach ($int_wiki_links as $wlink) { + $this->log->debug("found link : " . $wlink); $page_id = self::_getPageIdForWikiTitle($this->getWikiId(), $wlink); - if ($page_id > 0) { // save internal link for existing page + $this->log->debug("save link " . + $this->getId() . ", target " . $page_id); ilInternalLink::_saveLink( "wpg:pg", $this->getId(), @@ -599,6 +605,8 @@ public function saveInternalLinks( array("integer", "integer", "text"), array($this->getWikiId(), $this->getId(), $wlink) ); + $this->log->debug("target does not exist, save missing page source" . + $this->getId() . ", target " . $wlink); $ilDB->manipulateF( "INSERT INTO il_wiki_missing_page (wiki_id, source_id, target_name)" . " VALUES (%s,%s,%s)", @@ -738,17 +746,24 @@ public function rename( if ($pg_id == 0 || $pg_id == $this->getId()) { $sources = ilInternalLink::_getSourcesOfTarget("wpg", $this->getId(), 0); + $this->log->debug("nr of pages linking to renamed page: " . count($sources)); foreach ($sources as $s) { if ($s["type"] === "wpg:pg" && ilPageObject::_exists("wpg", $s["id"])) { $wpage = new ilWikiPage($s["id"]); - + $wiki_id = ilWikiPage::lookupWikiId($s["id"]); + $this->log->debug("Getting links of page " . $s["id"]); $col = ilWikiUtil::collectInternalLinks( $wpage->getXMLContent(), - 0 + $wiki_id, + false, + IL_WIKI_MODE_EXT_COLLECT ); + $this->log->debug("nr internal links: " . count($col)); $new_content = $wpage->getXMLContent(); foreach ($col as $c) { + $this->log->debug("found link " . print_r($c, true)); + // this complicated procedure is needed due to the fact // that depending on the collation e = é is true // in the (mysql) database diff --git a/Modules/Wiki/classes/class.ilWikiUtil.php b/Modules/Wiki/classes/class.ilWikiUtil.php index dffd66b29a13..bc52e0324b4f 100755 --- a/Modules/Wiki/classes/class.ilWikiUtil.php +++ b/Modules/Wiki/classes/class.ilWikiUtil.php @@ -69,14 +69,22 @@ public static function replaceInternalLinks( public static function collectInternalLinks( string $s, int $a_wiki_id, - bool $a_collect_non_ex = false + bool $a_collect_non_ex = false, + string $mode = IL_WIKI_MODE_COLLECT ): array { - return self::processInternalLinks( + $log = ilLoggerFactory::getLogger("wiki"); + + $log->debug("collect interna links wiki id: " . $a_wiki_id . ", collect nonex: " . $a_collect_non_ex); + + $result = self::processInternalLinks( $s, $a_wiki_id, - IL_WIKI_MODE_COLLECT, + $mode, $a_collect_non_ex ); + $log->debug("content: " . $s); + $log->debug("found: " . print_r($result, true)); + return $result; } /** @@ -91,6 +99,7 @@ public static function processInternalLinks( bool $a_collect_non_ex = false, bool $a_offline = false ) { + include_once("./Modules/Wiki/libs/Sanitizer.php"); $collect = array(); // both from mediawiki DefaulSettings.php @@ -258,8 +267,7 @@ public function lc($a_key): bool } else { $db_title = self::makeDbTitle($nt->mTextform); - if ((ilWikiPage::_wikiPageExists($a_wiki_id, $db_title) || - $a_collect_non_ex) + if (($a_collect_non_ex || ilWikiPage::_wikiPageExists($a_wiki_id, $db_title)) && !in_array($db_title, $collect)) { $collect[] = $db_title; @@ -468,6 +476,10 @@ public static function sendNotification( $ilObjDataCache = $DIC["ilObjDataCache"]; $ilAccess = $DIC->access(); + if ($a_wiki_ref_id === 0) { + return; + } + $wiki_id = $ilObjDataCache->lookupObjId($a_wiki_ref_id); $wiki = new ilObjWiki($a_wiki_ref_id, true); $page = new ilWikiPage($a_page_id); diff --git a/Modules/Wiki/test/WikiUtilTest.php b/Modules/Wiki/test/WikiUtilTest.php index 46a42625084c..430a39ef5b38 100644 --- a/Modules/Wiki/test/WikiUtilTest.php +++ b/Modules/Wiki/test/WikiUtilTest.php @@ -1,6 +1,25 @@ createMock(ilDBInterface::class); + $this->setGlobalVariable( + "ilDB", + $db_mock + ); + } + protected function tearDown(): void { } - /** - * Test make URL title - */ - public function testRefId(): void + public function testMakeUrlTitle(): void { $input_expected = [ ["a", "a"] @@ -36,6 +81,7 @@ public function testRefId(): void ,[":", "%3A"] ,["-", "-"] ,["#", "%23"] + ,["?", "%3F"] ,["\x00", ""] ,["\n", ""] ,["\r", ""] @@ -49,4 +95,222 @@ public function testRefId(): void ); } } + + public function testMakeDbTitle(): void + { + $input_expected = [ + ["a", "a"] + ,["z", "z"] + ,["0", "0"] + ,[" ", " "] + ,["_", " "] + ,["!", "!"] + ,["§", "§"] + ,["$", "$"] + ,["%", "%"] + ,["&", "&"] + ,["/", "/"] + ,["(", "("] + ,["+", "+"] + ,[";", ";"] + ,[":", ":"] + ,["-", "-"] + ,["#", "#"] + ,["?", "?"] + ,["\x00", ""] + ,["\n", ""] + ,["\r", ""] + ]; + foreach ($input_expected as $ie) { + $result = ilWikiUtil::makeDbTitle($ie[0]); + + $this->assertEquals( + $ie[1], + $result + ); + } + } + + protected function processInternalLinksExtCollect(string $xml):array + { + return ilWikiUtil::collectInternalLinks( + $xml, + 0, + true, + IL_WIKI_MODE_EXT_COLLECT + ); + } + + public function testProcessInternalLinksExtCollect(): void + { + $input_expected = [ + ["", []] + ,["", []] + ]; + foreach ($input_expected as $ie) { + $result = $this->processInternalLinksExtCollect($ie[0]); + + $this->assertEquals( + $ie[1], + $result + ); + } + } + + public function testProcessInternalLinksExtCollectOneSimple(): void + { + $xml = "[[bar]]"; + $r = $this->processInternalLinksExtCollect($xml); + $this->assertEquals( + "bar", + $r[0]["nt"]->mTextform + ); + $this->assertEquals( + "bar", + $r[0]["text"] + ); + } + + public function testProcessInternalLinksExtCollectMultipleSimple(): void + { + $xml = "[[bar]][[bar1]] some text [[bar2]]"; + $r = $this->processInternalLinksExtCollect($xml); + $this->assertEquals( + "bar", + $r[0]["nt"]->mTextform + ); + $this->assertEquals( + "bar1", + $r[1]["nt"]->mTextform + ); + $this->assertEquals( + "bar2", + $r[2]["nt"]->mTextform + ); + } + + public function testProcessInternalLinksExtCollectMultipleSame(): void + { + $xml = "[[bar]][[bar1]] some text [[bar]]"; + $r = $this->processInternalLinksExtCollect($xml); + $this->assertEquals( + "bar", + $r[0]["nt"]->mTextform + ); + $this->assertEquals( + "bar1", + $r[1]["nt"]->mTextform + ); + $this->assertEquals( + "bar", + $r[2]["nt"]->mTextform + ); + } + + public function testProcessInternalLinksExtCollectOneText(): void + { + $xml = "[[bar|some text]]"; + $r = $this->processInternalLinksExtCollect($xml); + $this->assertEquals( + "bar", + $r[0]["nt"]->mTextform + ); + $this->assertEquals( + "some text", + $r[0]["text"] + ); + } + + public function testProcessInternalLinksExtCollectMultiText(): void + { + $xml = "lore [[bar|some text]] ipsumMore [[second link|some text for second]]"; + $r = $this->processInternalLinksExtCollect($xml); + $this->assertEquals( + "bar", + $r[0]["nt"]->mTextform + ); + $this->assertEquals( + "some text", + $r[0]["text"] + ); + $this->assertEquals( + "second link", + $r[1]["nt"]->mTextform + ); + $this->assertEquals( + "some text for second", + $r[1]["text"] + ); + } + + protected function processInternalLinksCollect(string $xml):array + { + return ilWikiUtil::collectInternalLinks( + $xml, + 0, + true, + IL_WIKI_MODE_COLLECT + ); + } + + public function testProcessInternalLinksCollectOneSimple(): void + { + $xml = "[[bar]]"; + $r = $this->processInternalLinksCollect($xml); + $this->assertEquals( + ["bar"], + $r + ); + } + + public function testProcessInternalLinksCollectMultipleSame(): void + { + $xml = "[[bar]][[bar1]] some text [[bar]]"; + $r = $this->processInternalLinksCollect($xml); + $this->assertEquals( + ["bar", "bar1"], + $r + ); + } + + public function testProcessInternalLinksCollectMultiText(): void + { + $xml = "lore [[bar|some text]] ipsumMore [[second link|some text for second]]"; + $r = $this->processInternalLinksCollect($xml); + $this->assertEquals( + ["bar", "second link"], + $r + ); + } + + protected function processInternalLinksReplace(string $xml):string + { + return ilWikiUtil::replaceInternalLinks( + $xml, + 0, + false + ); + } + + public function testProcessInternalLinksReplaceWithoutLink(): void + { + $xml = "Some text without a link"; + $r = $this->processInternalLinksReplace($xml); + $this->assertEquals( + $xml, + $r + ); + } + + /* + public function testProcessInternalLinksReplaceSimple(): void + { + $xml = "Some text with [[simple]] a link"; + $r = $this->processInternalLinksReplace($xml); + $this->assertEquals( + "todo", + $r + ); + }*/ + } diff --git a/Services/ADT/classes/Types/DateTime/class.ilADTDateTimeSearchBridgeRange.php b/Services/ADT/classes/Types/DateTime/class.ilADTDateTimeSearchBridgeRange.php index 4927047de147..8309af7cf210 100644 --- a/Services/ADT/classes/Types/DateTime/class.ilADTDateTimeSearchBridgeRange.php +++ b/Services/ADT/classes/Types/DateTime/class.ilADTDateTimeSearchBridgeRange.php @@ -171,11 +171,11 @@ public function isInCondition(ilADT $a_adt): bool assert($a_adt instanceof ilADTDateTime); if (!$this->getLowerADT()->isNull() && !$this->getUpperADT()->isNull()) { - return $a_adt->isInbetweenOrEqual($this->getLowerADT(), $this->getUpperADT()); + return (bool) $a_adt->isInbetweenOrEqual($this->getLowerADT(), $this->getUpperADT()); } elseif (!$this->getLowerADT()->isNull()) { - return $a_adt->isLargerOrEqual($this->getLowerADT()); + return (bool) $a_adt->isLargerOrEqual($this->getLowerADT()); } else { - return $a_adt->isSmallerOrEqual($this->getUpperADT()); + return (bool) $a_adt->isSmallerOrEqual($this->getUpperADT()); } } diff --git a/Services/ADT/classes/Types/Integer/class.ilADTIntegerDefinition.php b/Services/ADT/classes/Types/Integer/class.ilADTIntegerDefinition.php index c7c70c4afb54..49fa1b2d161d 100644 --- a/Services/ADT/classes/Types/Integer/class.ilADTIntegerDefinition.php +++ b/Services/ADT/classes/Types/Integer/class.ilADTIntegerDefinition.php @@ -1,7 +1,5 @@ min_value; } - public function setMin(int $a_value): void + public function setMin(?int $a_value): void { $this->min_value = $this->handleNumber($a_value); } @@ -54,7 +53,7 @@ public function getMax(): ?int return $this->max_value; } - public function setMax(int $a_value): void + public function setMax(?int $a_value): void { $this->max_value = $this->handleNumber($a_value); } diff --git a/Services/ADT/classes/Types/LocalizedText/class.ilADTLocalizedTextDefinition.php b/Services/ADT/classes/Types/LocalizedText/class.ilADTLocalizedTextDefinition.php index 8afe5de4322f..5c2942d7616d 100644 --- a/Services/ADT/classes/Types/LocalizedText/class.ilADTLocalizedTextDefinition.php +++ b/Services/ADT/classes/Types/LocalizedText/class.ilADTLocalizedTextDefinition.php @@ -14,14 +14,14 @@ class ilADTLocalizedTextDefinition extends ilADTDefinition */ private array $active_languages = []; private string $default_language = ''; - private int $max_length; + private ?int $max_length; public function getMaxLength(): ?int { return $this->max_length; } - public function setMaxLength(int $max_length): void + public function setMaxLength(?int $max_length): void { $this->max_length = $max_length; } diff --git a/Services/AccessControl/classes/class.ilAccess.php b/Services/AccessControl/classes/class.ilAccess.php index 91c7f55f83a3..312082e022ea 100644 --- a/Services/AccessControl/classes/class.ilAccess.php +++ b/Services/AccessControl/classes/class.ilAccess.php @@ -55,22 +55,20 @@ class ilAccess implements ilAccessHandler protected ilLogger $ac_logger; protected ilDBInterface $db; protected ilTree $repositoryTree; - protected ilLanguage $language; protected ilObjectDefinition $objDefinition; + protected ?ilLanguage $language = null; + public function __construct() { global $DIC; - $rbacsystem = $DIC->rbac()->system(); - $this->user = $DIC->user(); $this->db = $DIC->database(); - $this->rbacsystem = $rbacsystem; + $this->rbacsystem = $DIC['rbacsystem']; $this->results = array(); $this->current_info = new ilAccessInfo(); $this->repositoryTree = $DIC->repositoryTree(); - $this->language = $DIC->language(); $this->objDefinition = $DIC['objDefinition']; // use function enable to switch on/off tests (only cache is used so far) @@ -90,6 +88,16 @@ public function __construct() $this->ac_logger = ilLoggerFactory::getLogger('ac'); } + private function getLanguage(): ilLanguage + { + if ($this->language === null) { + global $DIC; + $this->language = $DIC['lng']; + } + + return $this->language; + } + /** * @inheritdoc */ @@ -249,7 +257,6 @@ public function checkAccessOfUser( global $DIC; $ilBench = $DIC['ilBench']; - $lng = $DIC['lng']; $this->setPreventCachingLastResult(false); // for external db based caches @@ -262,7 +269,7 @@ public function checkAccessOfUser( if ($cached["hit"]) { // Store access result if (!$cached["granted"]) { - $this->current_info->addInfoItem(ilAccessInfo::IL_NO_PERMISSION, $lng->txt("status_no_permission")); + $this->current_info->addInfoItem(ilAccessInfo::IL_NO_PERMISSION, $this->getLanguage()->txt("status_no_permission")); } if ($cached["prevent_db_cache"]) { $this->setPreventCachingLastResult(true); // should have been saved in previous call already @@ -295,14 +302,14 @@ public function checkAccessOfUser( // check if object is in tree and not deleted if ($a_tree_id != 1 && !$this->doTreeCheck($a_permission, $a_cmd, $a_ref_id, $a_user_id)) { - $this->current_info->addInfoItem(ilAccessInfo::IL_NO_PERMISSION, $lng->txt("status_no_permission")); + $this->current_info->addInfoItem(ilAccessInfo::IL_NO_PERMISSION, $this->getLanguage()->txt("status_no_permission")); $this->storeAccessResult($a_permission, $a_cmd, $a_ref_id, false, $a_user_id); return false; } // rbac check for current object if (!$this->doRBACCheck($a_permission, $a_cmd, $a_ref_id, $a_user_id, $a_type)) { - $this->current_info->addInfoItem(ilAccessInfo::IL_NO_PERMISSION, $lng->txt("status_no_permission")); + $this->current_info->addInfoItem(ilAccessInfo::IL_NO_PERMISSION, $this->getLanguage()->txt("status_no_permission")); $this->storeAccessResult($a_permission, $a_cmd, $a_ref_id, false, $a_user_id); return false; } @@ -318,7 +325,7 @@ public function checkAccessOfUser( ); if (!$act_check) { - $this->current_info->addInfoItem(ilAccessInfo::IL_NO_PERMISSION, $lng->txt('status_no_permission')); + $this->current_info->addInfoItem(ilAccessInfo::IL_NO_PERMISSION, $this->getLanguage()->txt('status_no_permission')); $this->storeAccessResult($a_permission, $a_cmd, $a_ref_id, false, $a_user_id); return false; } @@ -326,14 +333,14 @@ public function checkAccessOfUser( // check read permission for all parents $par_check = $this->doPathCheck($a_permission, $a_cmd, $a_ref_id, $a_user_id); if (!$par_check) { - $this->current_info->addInfoItem(ilAccessInfo::IL_NO_PERMISSION, $lng->txt("status_no_permission")); + $this->current_info->addInfoItem(ilAccessInfo::IL_NO_PERMISSION, $this->getLanguage()->txt("status_no_permission")); $this->storeAccessResult($a_permission, $a_cmd, $a_ref_id, false, $a_user_id); return false; } // condition check (currently only implemented for read permission) if (!$this->doConditionCheck($a_permission, $a_cmd, $a_ref_id, $a_user_id, $a_obj_id, $a_type)) { - $this->current_info->addInfoItem(ilAccessInfo::IL_NO_PERMISSION, $lng->txt("status_no_permission")); + $this->current_info->addInfoItem(ilAccessInfo::IL_NO_PERMISSION, $this->getLanguage()->txt("status_no_permission")); $this->storeAccessResult($a_permission, $a_cmd, $a_ref_id, false, $a_user_id); $this->setPreventCachingLastResult(true); // do not store this in db, since condition updates are not monitored return false; @@ -341,7 +348,7 @@ public function checkAccessOfUser( // object type specific check if (!$this->doStatusCheck($a_permission, $a_cmd, $a_ref_id, $a_user_id, $a_obj_id, $a_type)) { - $this->current_info->addInfoItem(ilAccessInfo::IL_NO_PERMISSION, $lng->txt("status_no_permission")); + $this->current_info->addInfoItem(ilAccessInfo::IL_NO_PERMISSION, $this->getLanguage()->txt("status_no_permission")); $this->storeAccessResult($a_permission, $a_cmd, $a_ref_id, false, $a_user_id); $this->setPreventCachingLastResult(true); // do not store this in db, since status updates are not monitored return false; @@ -420,7 +427,7 @@ public function doTreeCheck(string $a_permission, string $a_cmd, int $a_ref_id, if (!$this->obj_tree_cache[$tree_cache_key]) { $this->current_info->addInfoItem( ilAccessInfo::IL_NO_PERMISSION, - $this->language->txt("status_no_permission") + $this->getLanguage()->txt("status_no_permission") ); } $this->storeAccessResult( @@ -443,7 +450,7 @@ public function doTreeCheck(string $a_permission, string $a_cmd, int $a_ref_id, } // Store in result cache - $this->current_info->addInfoItem(ilAccessInfo::IL_DELETED, $this->language->txt("object_deleted")); + $this->current_info->addInfoItem(ilAccessInfo::IL_DELETED, $this->getLanguage()->txt("object_deleted")); $this->storeAccessResult($a_permission, $a_cmd, $a_ref_id, false, $a_user_id); return false; } @@ -493,7 +500,7 @@ public function doRBACCheck( if (!$access) { $this->current_info->addInfoItem( ilAccessInfo::IL_NO_PERMISSION, - $this->language->txt("status_no_permission") + $this->getLanguage()->txt("status_no_permission") ); } if ($a_permission != "create") { @@ -521,7 +528,7 @@ public function doPathCheck( if ($access == false) { $this->current_info->addInfoItem( ilAccessInfo::IL_NO_PARENT_ACCESS, - $this->language->txt("no_parent_access"), + $this->getLanguage()->txt("no_parent_access"), (string) $id ); if ($a_all == false) { @@ -543,7 +550,7 @@ public function doActivationCheck( int $a_obj_id, string $a_type ): bool { - $cache_perm = ($a_permission == "visible") + $cache_perm = ($a_permission === "visible" || $a_permission === 'leave') ? "visible" : "other"; @@ -552,7 +559,7 @@ public function doActivationCheck( } // nothings needs to be done if current permission is write permission - if ($a_permission == 'write') { + if ($a_permission === 'write') { return true; } @@ -600,8 +607,9 @@ public function doActivationCheck( return true; } - // if current permission is visible and visible is set in activation - if ($a_permission == 'visible' && $item_data['visible']) { + // if current permission is visible or leave and visible is set in activation + if (($a_permission === 'visible' || $a_permission === 'leave') + && $item_data['visible']) { $this->ac_cache[$cache_perm][$a_ref_id][$a_user_id] = true; return true; } @@ -637,9 +645,9 @@ public function doConditionCheck( foreach ($conditions as $condition) { $this->current_info->addInfoItem( ilAccessInfo::IL_MISSING_PRECONDITION, - $this->language->txt("missing_precondition") . ": " . + $this->getLanguage()->txt("missing_precondition") . ": " . ilObject::_lookupTitle($condition["trigger_obj_id"]) . " " . - $this->language->txt("condition_" . $condition["operator"]) . " " . + $this->getLanguage()->txt("condition_" . $condition["operator"]) . " " . $condition["value"], serialize($condition) ); @@ -656,9 +664,9 @@ public function doConditionCheck( foreach ($conditions as $condition) { $this->current_info->addInfoItem( ilAccessInfo::IL_MISSING_PRECONDITION, - $this->language->txt("missing_precondition") . ": " . + $this->getLanguage()->txt("missing_precondition") . ": " . ilObject::_lookupTitle($condition["trigger_obj_id"]) . " " . - $this->language->txt("condition_" . $condition["operator"]) . " " . + $this->getLanguage()->txt("condition_" . $condition["operator"]) . " " . $condition["value"], serialize($condition) ); diff --git a/Services/AccessControl/classes/class.ilObjRole.php b/Services/AccessControl/classes/class.ilObjRole.php index f0a6c5b2b346..8541780271de 100755 --- a/Services/AccessControl/classes/class.ilObjRole.php +++ b/Services/AccessControl/classes/class.ilObjRole.php @@ -1,7 +1,5 @@ @@ -162,7 +162,7 @@ public function read(): void if ($res->numRows() > 0) { $row = $this->db->fetchAssoc($res); $this->setAllowRegister((bool) $row['allow_register']); - $this->toggleAssignUsersStatus((bool) ($row['assign_user'] ?? false)); + $this->toggleAssignUsersStatus((bool) ($row['assign_users'] ?? false)); } else { $this->logger->logStack(ilLogLevel::ERROR); throw new ilObjectException('There is no dataset with id: ' . $this->id); diff --git a/Services/AccessControl/classes/class.ilObjRoleGUI.php b/Services/AccessControl/classes/class.ilObjRoleGUI.php index 3c06c1e031bc..1e1edfdb862b 100755 --- a/Services/AccessControl/classes/class.ilObjRoleGUI.php +++ b/Services/AccessControl/classes/class.ilObjRoleGUI.php @@ -433,7 +433,7 @@ protected function permObject(bool $a_show_admin_permissions = false): void } if (!$this->checkAccess('write', 'edit_permission')) { - $this->tpl->setOnScreenMessage('msg_no_perm_write', $this->lng->txt('permission_denied'), true); + $this->tpl->setOnScreenMessage('failure', $this->lng->txt('msg_no_perm_write'), true); $this->ctrl->redirectByClass(ilRepositoryGUI::class); } @@ -531,6 +531,9 @@ protected function adoptPermObject(): void { $output = []; $parent_role_ids = $this->rbac_review->getParentRoleIds($this->obj_ref_id, true); + + $this->tabs_gui->clearTargets(); + $ids = []; foreach (array_keys($parent_role_ids) as $id) { $ids[] = $id; @@ -561,6 +564,8 @@ protected function adoptPermObject(): void */ protected function confirmDeleteRoleObject(): void { + $this->tabs_gui->clearTargets(); + if (!$this->checkAccess('visible,write', 'edit_permission')) { $this->tpl->setOnScreenMessage('msg_no_perm_perm', $this->lng->txt('permission_denied'), true); $this->ctrl->redirectByClass(ilRepositoryGUI::class); @@ -872,7 +877,7 @@ public function deassignUserObject(): void $this->object->getId(), $assigned_global_roles )) { - $userObj = $this->ilias->obj_factory->getInstanceByObjId($user); + $userObj = new ilObjUser($user); $last_role[$user] = $userObj->getFullName(); unset($userObj); } @@ -1011,7 +1016,6 @@ protected function addAdminLocatorItems(bool $do_not_add_object = false): void protected function getTabs(): void { $base_role_container = $this->rbac_review->getFoldersAssignedToRole($this->object->getId(), true); - $activate_role_edit = false; // todo: activate the following (allow editing of local roles in diff --git a/Services/AccessControl/classes/class.ilRoleXmlImporter.php b/Services/AccessControl/classes/class.ilRoleXmlImporter.php index e2ac9c6c35aa..696a7a2308bb 100644 --- a/Services/AccessControl/classes/class.ilRoleXmlImporter.php +++ b/Services/AccessControl/classes/class.ilRoleXmlImporter.php @@ -150,8 +150,12 @@ public function importSimpleXml(SimpleXMLElement $role): int foreach ($role->operations as $sxml_operations) { foreach ($sxml_operations as $sxml_op) { + $operation = trim((string) $sxml_op); + if (!array_key_exists($operation, $operations)) { + continue; + } $ops_group = (string) $sxml_op['group']; - $ops_id = (int) $operations[trim((string) $sxml_op)]; + $ops_id = (int) $operations[$operation]; $ops = trim((string) $sxml_op); if ($ops_group && $ops_id) { diff --git a/Services/AccessControl/classes/class.ilStartingPoint.php b/Services/AccessControl/classes/class.ilStartingPoint.php index 34ef691128c2..7c5ee8a7e871 100644 --- a/Services/AccessControl/classes/class.ilStartingPoint.php +++ b/Services/AccessControl/classes/class.ilStartingPoint.php @@ -240,6 +240,9 @@ public static function getGlobalRolesWithoutStartingPoint(): array $roles = array(); foreach ($ids_roles_without_sp as $roleid) { + if ($roleid === ANONYMOUS_ROLE_ID) { + continue; + } $role_obj = new ilObjRole($roleid); $roles[] = array( "id" => $role_obj->getId(), diff --git a/Services/ActiveRecord/Cache/class.arObjectCache.php b/Services/ActiveRecord/Cache/class.arObjectCache.php index 400d837569aa..a747d19e650f 100644 --- a/Services/ActiveRecord/Cache/class.arObjectCache.php +++ b/Services/ActiveRecord/Cache/class.arObjectCache.php @@ -1,18 +1,21 @@ getCacheIdentifier() !== '') { - if ($instance->getCache()->exists($instance->getCacheIdentifier())) { - return true; - } - } - if (!isset(self::$cache[$class])) { return false; } @@ -47,11 +43,6 @@ public static function isCached($class, $id): bool public static function store(ActiveRecord $object): void { - if ($object instanceof CachedActiveRecord && $object->getCacheIdentifier() !== '') { - if ($object->getCache()->set($object->getCacheIdentifier(), $object, $object->getTTL())) { - return; - } - } if (!isset($object->is_new)) { self::$cache[get_class($object)][$object->getPrimaryFieldValue()] = $object; } @@ -75,11 +66,6 @@ public static function printStats(): void public static function get($class, $id): \ActiveRecord { $instance = new $class(); - if ($instance instanceof CachedActiveRecord && $instance->getCacheIdentifier() !== '') { - if ($instance->getCache()->exists($instance->getCacheIdentifier())) { - return $instance->getCache()->get($instance->getCacheIdentifier()); - } - } if (!self::isCached($class, $id)) { throw new arException(arException::GET_UNCACHED_OBJECT, $class . ': ' . $id); } @@ -89,9 +75,6 @@ public static function get($class, $id): \ActiveRecord public static function purge(ActiveRecord $object): void { - if ($object instanceof CachedActiveRecord && $object->getCacheIdentifier() !== '') { - $object->getCache()->delete($object->getCacheIdentifier()); - } unset(self::$cache[get_class($object)][$object->getPrimaryFieldValue()]); } @@ -100,11 +83,6 @@ public static function purge(ActiveRecord $object): void */ public static function flush($class_name): void { - $instance = new $class_name(); - if ($instance instanceof CachedActiveRecord && $instance->getCacheIdentifier() !== '') { - $instance->getCache()->flush(); - } - if ($class_name instanceof ActiveRecord) { $class_name = get_class($class_name); } diff --git a/Services/AdvancedMetaData/LuceneDataSource.xml b/Services/AdvancedMetaData/LuceneDataSource.xml index c36c1ea97f49..83ba53ea9951 100644 --- a/Services/AdvancedMetaData/LuceneDataSource.xml +++ b/Services/AdvancedMetaData/LuceneDataSource.xml @@ -9,7 +9,7 @@ - SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, vd.value + SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, vd.value, vd.idx FROM adv_md_values_enum val JOIN adv_mdf_enum vd ON (val.field_id = vd.field_id AND value_index = idx) JOIN adv_mdf_definition def ON (def.field_id = val.field_id) @@ -19,19 +19,19 @@ AND robj.sub_type = '-' AND rec.active = 1 AND def.searchable = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?); - + - SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, vd.value + SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, vd.value, vd.idx FROM adv_md_values_enum val JOIN adv_mdf_enum vd ON (val.field_id = vd.field_id AND value_index = idx) JOIN object_data obj ON (obj.obj_id = val.obj_id) @@ -45,19 +45,19 @@ AND def.searchable = 1 AND cont.keyword = 'cont_custom_md' AND cont.value = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?); - + - SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, vd.value + SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, vd.value, vd.idx FROM adv_md_values_enum val JOIN adv_mdf_enum vd ON (val.field_id = vd.field_id AND value_index = idx) JOIN object_data obj ON (obj.obj_id = val.obj_id) @@ -77,7 +77,7 @@ - + @@ -99,7 +99,7 @@ AND robj.sub_type = '-' AND rec.active = 1 AND def.searchable = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?); @@ -124,7 +124,7 @@ AND def.searchable = 1 AND cont.keyword = 'cont_custom_md' AND cont.value = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?); @@ -170,7 +170,7 @@ AND robj.sub_type = '-' AND rec.active = 1 AND def.searchable = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?); @@ -195,7 +195,7 @@ AND def.searchable = 1 AND cont.keyword = 'cont_custom_md' AND cont.value = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?); @@ -241,7 +241,7 @@ AND robj.sub_type = '-' AND rec.active = 1 AND def.searchable = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?); @@ -266,7 +266,7 @@ AND def.searchable = 1 AND cont.keyword = 'cont_custom_md' AND cont.value = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?); @@ -312,7 +312,7 @@ AND robj.sub_type = '-' AND rec.active = 1 AND def.searchable = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?); @@ -337,7 +337,7 @@ AND def.searchable = 1 AND cont.keyword = 'cont_custom_md' AND cont.value = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?); @@ -383,7 +383,7 @@ AND robj.sub_type = '-' AND rec.active = 1 AND def.searchable = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?); @@ -408,7 +408,7 @@ AND def.searchable = 1 AND cont.keyword = 'cont_custom_md' AND cont.value = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?); @@ -454,7 +454,7 @@ AND robj.sub_type = '-' AND rec.active = 1 AND def.searchable = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?); @@ -481,7 +481,7 @@ AND def.searchable = 1 AND cont.keyword = 'cont_custom_md' AND cont.value = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?); @@ -531,7 +531,7 @@ AND robj.sub_type = '-' AND rec.active = 1 AND def.searchable = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?); @@ -556,7 +556,7 @@ AND def.searchable = 1 AND cont.keyword = 'cont_custom_md' AND cont.value = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?); diff --git a/Services/AdvancedMetaData/LuceneSubItemDataSource.xml b/Services/AdvancedMetaData/LuceneSubItemDataSource.xml index ac43be6631d9..149cd730240f 100644 --- a/Services/AdvancedMetaData/LuceneSubItemDataSource.xml +++ b/Services/AdvancedMetaData/LuceneSubItemDataSource.xml @@ -9,36 +9,34 @@ - SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, vd.value + SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, vd.value, vd.idx FROM adv_md_values_enum val JOIN adv_mdf_enum vd ON (val.field_id = vd.field_id AND value_index = idx) - JOIN object_data obj ON (obj.obj_id = val.obj_id) JOIN adv_mdf_definition def ON (def.field_id = val.field_id) JOIN adv_md_record rec ON (rec.record_id = def.record_id) JOIN adv_md_record_objs robj ON (robj.record_id = rec.record_id AND robj.sub_type = val.sub_type) WHERE robj.optional = 0 AND rec.active = 1 AND def.searchable = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?) AND val.sub_type IN (?) AND val.sub_id IN (?); - - - - + + + + - SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, vd.value + SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, vd.value, vd.idx FROM adv_md_values_enum val JOIN adv_mdf_enum vd ON (val.field_id = vd.field_id AND value_index = idx) - JOIN object_data obj ON (obj.obj_id = val.obj_id) JOIN adv_mdf_definition def ON (def.field_id = val.field_id) JOIN adv_md_record rec ON (rec.record_id = def.record_id) JOIN adv_md_record_objs robj ON (robj.record_id = rec.record_id AND robj.sub_type = val.sub_type) @@ -48,26 +46,25 @@ AND def.searchable = 1 AND cont.keyword = 'cont_custom_md' AND cont.value = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?) AND val.sub_type IN (?) AND val.sub_id IN (?); - - - - + + + + - SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, vd.value + SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, vd.value, vd.idx FROM adv_md_values_enum val JOIN adv_mdf_enum vd ON (val.field_id = vd.field_id AND value_index = idx) - JOIN object_data obj ON (obj.obj_id = val.obj_id) JOIN adv_mdf_definition def ON (def.field_id = val.field_id) JOIN adv_md_record rec ON (rec.record_id = def.record_id) JOIN adv_md_record_objs robj ON (robj.record_id = rec.record_id AND robj.sub_type = val.sub_type) @@ -84,16 +81,16 @@ AND val.sub_id IN (?); - - - - + + + + @@ -101,23 +98,22 @@ SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, val.value FROM adv_md_values_ltext val - JOIN object_data obj ON (obj.obj_id = val.obj_id) JOIN adv_mdf_definition def ON (def.field_id = val.field_id) JOIN adv_md_record rec ON (rec.record_id = def.record_id) JOIN adv_md_record_objs robj ON (robj.record_id = rec.record_id AND robj.sub_type = val.sub_type) WHERE robj.optional = 0 AND rec.active = 1 AND def.searchable = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?) AND val.sub_type IN (?) AND val.sub_id IN (?); - - - + + + @@ -126,7 +122,6 @@ SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, val.value FROM adv_md_values_ltext val - JOIN object_data obj ON (obj.obj_id = val.obj_id) JOIN adv_mdf_definition def ON (def.field_id = val.field_id) JOIN adv_md_record rec ON (rec.record_id = def.record_id) JOIN adv_md_record_objs robj ON (robj.record_id = rec.record_id AND robj.sub_type = val.sub_type) @@ -136,16 +131,16 @@ AND def.searchable = 1 AND cont.keyword = 'cont_custom_md' AND cont.value = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?) AND val.sub_type IN (?) AND val.sub_id IN (?); - - - + + + @@ -154,7 +149,6 @@ SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, val.value FROM adv_md_values_ltext val - JOIN object_data obj ON (obj.obj_id = val.obj_id) JOIN adv_mdf_definition def ON (def.field_id = val.field_id) JOIN adv_md_record rec ON (rec.record_id = def.record_id) JOIN adv_md_record_objs robj ON (robj.record_id = rec.record_id AND robj.sub_type = val.sub_type) @@ -171,9 +165,9 @@ AND val.sub_id IN (?); - - - + + + @@ -182,23 +176,22 @@ SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, val.value FROM adv_md_values_date val - JOIN object_data obj ON (obj.obj_id = val.obj_id) JOIN adv_mdf_definition def ON (def.field_id = val.field_id) JOIN adv_md_record rec ON (rec.record_id = def.record_id) JOIN adv_md_record_objs robj ON (robj.record_id = rec.record_id AND robj.sub_type = val.sub_type) WHERE robj.optional = 0 AND rec.active = 1 AND def.searchable = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?) AND val.sub_type IN (?) AND val.sub_id IN (?); - - - + + + @@ -207,7 +200,6 @@ SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, val.value FROM adv_md_values_date val - JOIN object_data obj ON (obj.obj_id = val.obj_id) JOIN adv_mdf_definition def ON (def.field_id = val.field_id) JOIN adv_md_record rec ON (rec.record_id = def.record_id) JOIN adv_md_record_objs robj ON (robj.record_id = rec.record_id AND robj.sub_type = val.sub_type) @@ -217,16 +209,16 @@ AND def.searchable = 1 AND cont.keyword = 'cont_custom_md' AND cont.value = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?) AND val.sub_type IN (?) AND val.sub_id IN (?); - - - + + + @@ -235,7 +227,6 @@ SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, val.value FROM adv_md_values_date val - JOIN object_data obj ON (obj.obj_id = val.obj_id) JOIN adv_mdf_definition def ON (def.field_id = val.field_id) JOIN adv_md_record rec ON (rec.record_id = def.record_id) JOIN adv_md_record_objs robj ON (robj.record_id = rec.record_id AND robj.sub_type = val.sub_type) @@ -252,9 +243,9 @@ AND val.sub_id IN (?); - - - + + + @@ -263,23 +254,22 @@ SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, val.value FROM adv_md_values_datetime val - JOIN object_data obj ON (obj.obj_id = val.obj_id) JOIN adv_mdf_definition def ON (def.field_id = val.field_id) JOIN adv_md_record rec ON (rec.record_id = def.record_id) JOIN adv_md_record_objs robj ON (robj.record_id = rec.record_id AND robj.sub_type = val.sub_type) WHERE robj.optional = 0 AND rec.active = 1 AND def.searchable = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?) AND val.sub_type IN (?) AND val.sub_id IN (?); - - - + + + @@ -288,7 +278,6 @@ SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, val.value FROM adv_md_values_datetime val - JOIN object_data obj ON (obj.obj_id = val.obj_id) JOIN adv_mdf_definition def ON (def.field_id = val.field_id) JOIN adv_md_record rec ON (rec.record_id = def.record_id) JOIN adv_md_record_objs robj ON (robj.record_id = rec.record_id AND robj.sub_type = val.sub_type) @@ -298,16 +287,16 @@ AND def.searchable = 1 AND cont.keyword = 'cont_custom_md' AND cont.value = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?) AND val.sub_type IN (?) AND val.sub_id IN (?); - - - + + + @@ -316,7 +305,6 @@ SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, val.value FROM adv_md_values_datetime val - JOIN object_data obj ON (obj.obj_id = val.obj_id) JOIN adv_mdf_definition def ON (def.field_id = val.field_id) JOIN adv_md_record rec ON (rec.record_id = def.record_id) JOIN adv_md_record_objs robj ON (robj.record_id = rec.record_id AND robj.sub_type = val.sub_type) @@ -333,9 +321,9 @@ AND val.sub_id IN (?); - - - + + + @@ -344,23 +332,22 @@ SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, val.value FROM adv_md_values_float val - JOIN object_data obj ON (obj.obj_id = val.obj_id) JOIN adv_mdf_definition def ON (def.field_id = val.field_id) JOIN adv_md_record rec ON (rec.record_id = def.record_id) JOIN adv_md_record_objs robj ON (robj.record_id = rec.record_id AND robj.sub_type = val.sub_type) WHERE robj.optional = 0 AND rec.active = 1 AND def.searchable = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?) AND val.sub_type IN (?) AND val.sub_id IN (?); - - - + + + @@ -369,7 +356,6 @@ SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, val.value FROM adv_md_values_float val - JOIN object_data obj ON (obj.obj_id = val.obj_id) JOIN adv_mdf_definition def ON (def.field_id = val.field_id) JOIN adv_md_record rec ON (rec.record_id = def.record_id) JOIN adv_md_record_objs robj ON (robj.record_id = rec.record_id AND robj.sub_type = val.sub_type) @@ -379,16 +365,16 @@ AND def.searchable = 1 AND cont.keyword = 'cont_custom_md' AND cont.value = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?) AND val.sub_type IN (?) AND val.sub_id IN (?); - - - + + + @@ -397,7 +383,6 @@ SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, val.value FROM adv_md_values_float val - JOIN object_data obj ON (obj.obj_id = val.obj_id) JOIN adv_mdf_definition def ON (def.field_id = val.field_id) JOIN adv_md_record rec ON (rec.record_id = def.record_id) JOIN adv_md_record_objs robj ON (robj.record_id = rec.record_id AND robj.sub_type = val.sub_type) @@ -414,9 +399,9 @@ AND val.sub_id IN (?); - - - + + + @@ -425,23 +410,22 @@ SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, val.value FROM adv_md_values_int val - JOIN object_data obj ON (obj.obj_id = val.obj_id) JOIN adv_mdf_definition def ON (def.field_id = val.field_id) JOIN adv_md_record rec ON (rec.record_id = def.record_id) JOIN adv_md_record_objs robj ON (robj.record_id = rec.record_id AND robj.sub_type = val.sub_type) WHERE robj.optional = 0 AND rec.active = 1 AND def.searchable = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?) AND val.sub_type IN (?) AND val.sub_id IN (?); - - - + + + @@ -450,7 +434,6 @@ SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, val.value FROM adv_md_values_int val - JOIN object_data obj ON (obj.obj_id = val.obj_id) JOIN adv_mdf_definition def ON (def.field_id = val.field_id) JOIN adv_md_record rec ON (rec.record_id = def.record_id) JOIN adv_md_record_objs robj ON (robj.record_id = rec.record_id AND robj.sub_type = val.sub_type) @@ -460,16 +443,16 @@ AND def.searchable = 1 AND cont.keyword = 'cont_custom_md' AND cont.value = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?) AND val.sub_type IN (?) AND val.sub_id IN (?); - - - + + + @@ -478,7 +461,6 @@ SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, val.value FROM adv_md_values_int val - JOIN object_data obj ON (obj.obj_id = val.obj_id) JOIN adv_mdf_definition def ON (def.field_id = val.field_id) JOIN adv_md_record rec ON (rec.record_id = def.record_id) JOIN adv_md_record_objs robj ON (robj.record_id = rec.record_id AND robj.sub_type = val.sub_type) @@ -495,9 +477,9 @@ AND val.sub_id IN (?); - - - + + + @@ -506,23 +488,22 @@ SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, val.value, val.title FROM adv_md_values_extlink val - JOIN object_data obj ON (obj.obj_id = val.obj_id) JOIN adv_mdf_definition def ON (def.field_id = val.field_id) JOIN adv_md_record rec ON (rec.record_id = def.record_id) JOIN adv_md_record_objs robj ON (robj.record_id = rec.record_id AND robj.sub_type = val.sub_type) WHERE robj.optional = 0 AND rec.active = 1 AND def.searchable = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?) AND val.sub_type IN (?) AND val.sub_id IN (?); - - - + + + @@ -533,7 +514,6 @@ SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, val.value, val.title FROM adv_md_values_extlink val - JOIN object_data obj ON (obj.obj_id = val.obj_id) JOIN adv_mdf_definition def ON (def.field_id = val.field_id) JOIN adv_md_record rec ON (rec.record_id = def.record_id) JOIN adv_md_record_objs robj ON (robj.record_id = rec.record_id AND robj.sub_type = val.sub_type) @@ -543,16 +523,16 @@ AND def.searchable = 1 AND cont.keyword = 'cont_custom_md' AND cont.value = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?) AND val.sub_type IN (?) AND val.sub_id IN (?); - - - + + + @@ -563,7 +543,6 @@ SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, val.value, val.title FROM adv_md_values_extlink val - JOIN object_data obj ON (obj.obj_id = val.obj_id) JOIN adv_mdf_definition def ON (def.field_id = val.field_id) JOIN adv_md_record rec ON (rec.record_id = def.record_id) JOIN adv_md_record_objs robj ON (robj.record_id = rec.record_id AND robj.sub_type = val.sub_type) @@ -580,9 +559,9 @@ AND val.sub_id IN (?); - - - + + + @@ -593,23 +572,22 @@ SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, val.value FROM adv_md_values_intlink val - JOIN object_data obj ON (obj.obj_id = val.obj_id) JOIN adv_mdf_definition def ON (def.field_id = val.field_id) JOIN adv_md_record rec ON (rec.record_id = def.record_id) JOIN adv_md_record_objs robj ON (robj.record_id = rec.record_id AND robj.sub_type = val.sub_type) WHERE robj.optional = 0 AND rec.active = 1 AND def.searchable = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?) AND val.sub_type IN (?) AND val.sub_id IN (?); - - - + + + @@ -618,7 +596,6 @@ SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, val.value FROM adv_md_values_intlink val - JOIN object_data obj ON (obj.obj_id = val.obj_id) JOIN adv_mdf_definition def ON (def.field_id = val.field_id) JOIN adv_md_record rec ON (rec.record_id = def.record_id) JOIN adv_md_record_objs robj ON (robj.record_id = rec.record_id AND robj.sub_type = val.sub_type) @@ -628,16 +605,16 @@ AND def.searchable = 1 AND cont.keyword = 'cont_custom_md' AND cont.value = 1 - AND rec.parent_obj IS NULL + AND IFNULL(rec.parent_obj, 0) = 0 AND robj.obj_type IN (?) AND val.obj_id IN (?) AND val.sub_type IN (?) AND val.sub_id IN (?); - - - + + + @@ -646,7 +623,6 @@ SELECT concat('advancedMetaData_', val.field_id) field_name, val.field_id, val.value FROM adv_md_values_intlink val - JOIN object_data obj ON (obj.obj_id = val.obj_id) JOIN adv_mdf_definition def ON (def.field_id = val.field_id) JOIN adv_md_record rec ON (rec.record_id = def.record_id) JOIN adv_md_record_objs robj ON (robj.record_id = rec.record_id AND robj.sub_type = val.sub_type) @@ -663,9 +639,9 @@ AND val.sub_id IN (?); - - - + + + diff --git a/Services/AdvancedMetaData/ROADMAP.md b/Services/AdvancedMetaData/ROADMAP.md new file mode 100644 index 000000000000..994ad9dfb8e0 --- /dev/null +++ b/Services/AdvancedMetaData/ROADMAP.md @@ -0,0 +1,11 @@ +# Roadmap + +## Short Term + +## Mid Term + +Enum entries (for select and multiselect fields) should get an actual ID. +The current ID acts more like a ordering parameter, which makes mapping entries +during editing of the fields unnecessarily tedious and error prone. + +## Long Term diff --git a/Services/AdvancedMetaData/classes/Record/class.ilAdvancedMDRecordTableGUI.php b/Services/AdvancedMetaData/classes/Record/class.ilAdvancedMDRecordTableGUI.php index 87cd85bc8151..54178c54b0d5 100644 --- a/Services/AdvancedMetaData/classes/Record/class.ilAdvancedMDRecordTableGUI.php +++ b/Services/AdvancedMetaData/classes/Record/class.ilAdvancedMDRecordTableGUI.php @@ -44,7 +44,7 @@ public function __construct( $this->addColumn($this->lng->txt('md_adv_col_presentation_ordering'), 'position'); $this->addColumn($this->lng->txt('title'), 'title'); $this->addColumn($this->lng->txt('md_fields'), 'fields'); - $this->addColumn($this->lng->txt('md_adv_scope'), 'scope'); + $this->addColumn($this->lng->txt('md_adv_scope'), 'first_scope'); $this->addColumn($this->lng->txt('md_obj_types'), 'obj_types'); $this->addColumn($this->lng->txt('md_adv_active'), 'active'); diff --git a/Services/AdvancedMetaData/classes/Translation/class.ilAdvancedMDRecordTranslationGUI.php b/Services/AdvancedMetaData/classes/Translation/class.ilAdvancedMDRecordTranslationGUI.php index 8964f91dbd12..e3f7cc445dec 100644 --- a/Services/AdvancedMetaData/classes/Translation/class.ilAdvancedMDRecordTranslationGUI.php +++ b/Services/AdvancedMetaData/classes/Translation/class.ilAdvancedMDRecordTranslationGUI.php @@ -29,7 +29,7 @@ protected function translations(): void */ protected function saveTranslations(): void { - $languages = (array) $this->request->getParsedBody()['active_languages']; + $languages = (array) ($this->request->getParsedBody()['active_languages'] ?? []); $default = (string) $this->request->getParsedBody()['default']; if (!in_array($default, $languages)) { diff --git a/Services/AdvancedMetaData/classes/Types/class.ilAdvancedMDFieldDefinitionInteger.php b/Services/AdvancedMetaData/classes/Types/class.ilAdvancedMDFieldDefinitionInteger.php index b8ca54c7b78c..416b5ab971ca 100644 --- a/Services/AdvancedMetaData/classes/Types/class.ilAdvancedMDFieldDefinitionInteger.php +++ b/Services/AdvancedMetaData/classes/Types/class.ilAdvancedMDFieldDefinitionInteger.php @@ -1,7 +1,5 @@ @@ -66,8 +66,8 @@ public function setSuffixTranslations(array $suffix_translations): void protected function initADTDefinition(): ilADTDefinition { $def = ilADTFactory::getInstance()->getDefinitionInstanceByType('Integer'); - $def->setMin((int) $this->getMin()); - $def->setMax((int) $this->getMax()); + $def->setMin($this->getMin()); + $def->setMax($this->getMax()); $def->setSuffix((string) ($this->getSuffixTranslations()[$this->language] ?? $this->getSuffix())); return $def; } @@ -113,8 +113,8 @@ public function getSuffix(): ?string protected function importFieldDefinition(array $a_def): void { - $this->setMin($a_def["min"]); - $this->setMax($a_def["max"]); + $this->setMin(isset($a_def["min"]) ? (int) $a_def["min"] : null); + $this->setMax(isset($a_def["max"]) ? (int) $a_def["max"] : null); $this->setSuffix($a_def["suffix"]); $this->setSuffixTranslations($a_def['suffix_translations'] ?? []); } @@ -188,10 +188,10 @@ protected function addCustomFieldToDefinitionForm( public function importCustomDefinitionFormPostValues(ilPropertyFormGUI $a_form, string $language = ''): void { $min = $a_form->getInput("min"); - $this->setMin(($min !== "") ? (int) $min : null); + $this->setMin(($min !== "" && !is_null($min)) ? (int) $min : null); $max = $a_form->getInput("max"); - $this->setMax(($max !== "") ? (int) $max : null); + $this->setMax(($max !== "" && !is_null($max)) ? (int) $max : null); if ($this->useDefaultLanguageMode($language)) { $suffix = $a_form->getInput("suffix"); diff --git a/Services/AdvancedMetaData/classes/Types/class.ilAdvancedMDFieldDefinitionSelect.php b/Services/AdvancedMetaData/classes/Types/class.ilAdvancedMDFieldDefinitionSelect.php index 9cbedba3fa98..7ed17cf03221 100644 --- a/Services/AdvancedMetaData/classes/Types/class.ilAdvancedMDFieldDefinitionSelect.php +++ b/Services/AdvancedMetaData/classes/Types/class.ilAdvancedMDFieldDefinitionSelect.php @@ -34,12 +34,9 @@ class ilAdvancedMDFieldDefinitionSelect extends ilAdvancedMDFieldDefinition protected ?array $old_options = null; protected array $option_translations = []; - private \ilGlobalTemplateInterface $main_tpl; public function __construct(?int $a_field_id = null, string $language = '') { parent::__construct($a_field_id, $language); - global $DIC; - $this->main_tpl = $DIC->ui()->mainTemplate(); } public function getType(): int @@ -284,23 +281,24 @@ protected function importNewSelectOptions( if (sizeof($missing)) { $this->confirmed_objects = $this->buildConfirmedObjects($a_form); + $already_confirmed = is_array($this->confirmed_objects); - if (!is_array($this->confirmed_objects)) { - $search = ilADTFactory::getInstance()->getSearchBridgeForDefinitionInstance( - $this->getADTDefinition(), - false, - $multi - ); - foreach ($missing as $missing_idx => $missing_value) { - $in_use = $this->findBySingleValue($search, $missing_idx); - if (is_array($in_use)) { - foreach ($in_use as $item) { - if (array_key_exists($missing_idx, $index_map)) { - $complete_id = $item[0] . "_" . $item[1] . "_" . $item[2]; - $new_index = $index_map[$missing_idx]; - $this->confirmed_objects[$missing_idx][$complete_id] = $new_index; - continue; - } + $search = ilADTFactory::getInstance()->getSearchBridgeForDefinitionInstance( + $this->getADTDefinition(), + false, + $multi + ); + foreach ($missing as $missing_idx => $missing_value) { + $in_use = $this->findBySingleValue($search, $missing_idx); + if (is_array($in_use)) { + foreach ($in_use as $item) { + if (array_key_exists($missing_idx, $index_map)) { + $complete_id = $item[0] . "_" . $item[1] . "_" . $item[2]; + $new_index = $index_map[$missing_idx]; + $this->confirmed_objects[$missing_idx][$complete_id] = $new_index; + continue; + } + if (!$already_confirmed) { $this->confirm_objects[$missing_idx][] = $item; $this->confirm_objects_values[$missing_idx] = $old[$missing_idx]; } @@ -428,7 +426,7 @@ public function prepareCustomDefinitionFormConfirmation(ilPropertyFormGUI $a_for $sel->setValue($post_conf_det[$this->getFieldId()][$old_option]); } elseif ($post_conf_det[$this->getFieldId()][$old_option] == "sum") { $sel->setAlert($lng->txt("msg_input_is_required")); - $this->main_tpl->setOnScreenMessage('failure', $lng->txt("form_input_not_valid")); + $DIC->ui()->mainTemplate()->setOnScreenMessage('failure', $lng->txt("form_input_not_valid")); } } $single = new ilRadioOption($lng->txt("md_adv_confirm_definition_select_option_single"), "sgl"); @@ -486,7 +484,7 @@ public function prepareCustomDefinitionFormConfirmation(ilPropertyFormGUI $a_for $sel->setValue($post_conf[$this->getFieldId()][$old_option][$item_id]); } elseif ($post_conf_det[$this->getFieldId()][$old_option] == "sgl") { $sel->setAlert($lng->txt("msg_input_is_required")); - $this->main_tpl->setOnScreenMessage('failure', $lng->txt("form_input_not_valid")); + $DIC->ui()->mainTemplate()->setOnScreenMessage('failure', $lng->txt("form_input_not_valid")); } } @@ -547,6 +545,8 @@ public function update(): void $search = ilADTFactory::getInstance()->getSearchBridgeForDefinitionInstance($def, false, true); ilADTFactory::initActiveRecordByType(); + $page_list_mappings = []; + foreach ($this->confirmed_objects as $old_option => $item_ids) { // get complete old values $old_values = array(); @@ -603,17 +603,18 @@ public function update(): void if ($sub_type == "wpg") { // #15763 - adapt advmd page lists - ilPCAMDPageList::migrateField( - (int) $obj_id, - $this->getFieldId(), - (string) $old_option, - (string) $new_option, - true - ); + $page_list_mappings[(string) $old_option] = (string) $new_option; } } } + if (!empty($page_list_mappings)) { + ilPCAMDPageList::migrateField( + $this->getFieldId(), + $page_list_mappings + ); + } + $this->confirmed_objects = array(); } diff --git a/Services/AdvancedMetaData/classes/Types/class.ilAdvancedMDFieldDefinitionText.php b/Services/AdvancedMetaData/classes/Types/class.ilAdvancedMDFieldDefinitionText.php index b98922f49b00..414d779a0232 100644 --- a/Services/AdvancedMetaData/classes/Types/class.ilAdvancedMDFieldDefinitionText.php +++ b/Services/AdvancedMetaData/classes/Types/class.ilAdvancedMDFieldDefinitionText.php @@ -28,8 +28,8 @@ class ilAdvancedMDFieldDefinitionText extends ilAdvancedMDFieldDefinitionGroupBa public const XML_SEPARATOR_TRANSLATIONS = "~|~"; public const XML_SEPARATOR_TRANSLATION = '~+~'; - protected int $max_length = 0; - protected $multi = false; + protected ?int $max_length = null; + protected bool $multi = false; // // generic types @@ -64,7 +64,7 @@ protected function initADTDefinition(): ilADTDefinition $field_translations = ilAdvancedMDFieldTranslations::getInstanceByRecordId($this->getRecordId()); $definition = ilADTFactory::getInstance()->getDefinitionInstanceByType(ilADTFactory::TYPE_LOCALIZED_TEXT); - $definition->setMaxLength($this->getMaxLength() ?? 0); + $definition->setMaxLength($this->getMaxLength()); $definition->setActiveLanguages($field_translations->getActivatedLanguages($this->getFieldId(), true)); $definition->setDefaultLanguage($field_translations->getDefaultLanguage()); return $definition; @@ -75,23 +75,12 @@ protected function initADTDefinition(): ilADTDefinition // properties // - /** - * Set max length - * @param int $a_value - */ - public function setMaxLength($a_value) + public function setMaxLength(?int $max_length) { - if ($a_value !== null) { - $a_value = (int) $a_value; - } - $this->max_length = (int) $a_value; + $this->max_length = $max_length; } - /** - * Get max length - * @return int - */ - public function getMaxLength() + public function getMaxLength(): ?int { return $this->max_length; } @@ -121,7 +110,7 @@ public function isMulti() protected function importFieldDefinition(array $a_def): void { - $this->setMaxLength($a_def["max"] ?? null); + $this->setMaxLength(isset($a_def["max"]) ? (int) $a_def["max"] : null); $this->setMulti($a_def["multi"]); } @@ -193,8 +182,7 @@ protected function addCustomFieldToDefinitionForm( public function importCustomDefinitionFormPostValues(ilPropertyFormGUI $a_form, string $language = ''): void { $max = $a_form->getInput("max"); - $this->setMaxLength(($max !== "") ? $max : null); - + $this->setMaxLength(($max !== "" && $max !== null) ? (int) $max : null); $this->setMulti($a_form->getInput("multi")); } @@ -211,7 +199,7 @@ protected function addPropertiesToXML(ilXmlWriter $a_writer): void public function importXMLProperty(string $a_key, string $a_value): void { if ($a_key == "max") { - $this->setMaxLength($a_value != "" ? $a_value : null); + $this->setMaxLength($a_value != "" ? (int) $a_value : null); } if ($a_key == "multi") { $this->setMulti($a_value != "" ? $a_value : null); diff --git a/Services/AdvancedMetaData/classes/class.ilAdvancedMDClaimingPlugin.php b/Services/AdvancedMetaData/classes/class.ilAdvancedMDClaimingPlugin.php index 0a2eb102bca3..1d3daa35f193 100644 --- a/Services/AdvancedMetaData/classes/class.ilAdvancedMDClaimingPlugin.php +++ b/Services/AdvancedMetaData/classes/class.ilAdvancedMDClaimingPlugin.php @@ -213,8 +213,7 @@ public static function createDBField( $field_id = $ilDB->nextId("adv_mdf_definition"); // validating type - $a_type = $a_type; - if ($a_type < 1 || $a_type > 8) { + if (!ilAdvancedMDFieldDefinition::isValidType($a_type)) { return null; } diff --git a/Services/AdvancedMetaData/classes/class.ilAdvancedMDFieldTableGUI.php b/Services/AdvancedMetaData/classes/class.ilAdvancedMDFieldTableGUI.php index 2d2f88017256..ab55a78ded90 100644 --- a/Services/AdvancedMetaData/classes/class.ilAdvancedMDFieldTableGUI.php +++ b/Services/AdvancedMetaData/classes/class.ilAdvancedMDFieldTableGUI.php @@ -55,7 +55,7 @@ public function __construct( $this->addColumn($this->lng->txt('position'), 'position', "5%"); $this->addColumn($this->lng->txt('title'), 'title', "30%"); $this->addColumn($this->lng->txt('md_adv_field_fields'), 'type', "35%"); - $this->addColumn($this->lng->txt('options'), 'obj_types', "30%"); + $this->addColumn($this->lng->txt('options'), 'searchable', "30%"); $this->setFormAction($this->ctrl->getFormAction($a_parent_obj)); $this->setRowTemplate("tpl.edit_fields_row.html", "Services/AdvancedMetaData"); diff --git a/Services/AdvancedMetaData/classes/class.ilAdvancedMDSettingsGUI.php b/Services/AdvancedMetaData/classes/class.ilAdvancedMDSettingsGUI.php index 354516641214..8881b00e23db 100644 --- a/Services/AdvancedMetaData/classes/class.ilAdvancedMDSettingsGUI.php +++ b/Services/AdvancedMetaData/classes/class.ilAdvancedMDSettingsGUI.php @@ -2148,6 +2148,12 @@ protected function getParsedRecordObjects(): array $tmp_arr['title'] = $record->getTitle(); $tmp_arr['description'] = $record->getDescription(); $tmp_arr['fields'] = []; + /* + * This is a workaround to fix sorting by scope, see #21963 + */ + $tmp_arr['first_scope'] = ilObject::_lookupTitle( + ilObject::_lookupObjId($record->getScopeRefIds()[0] ?? 0) + ); $tmp_arr['obj_types'] = $record->getAssignedObjectTypes(); foreach ($record->getAssignedObjectTypes() as $idx => $item) { $tmp_arr['obj_types'][$idx]['context'] = null; diff --git a/Services/AdvancedMetaData/classes/class.ilAdvancedMDValues.php b/Services/AdvancedMetaData/classes/class.ilAdvancedMDValues.php index 5b8991517133..18e90297ab0b 100644 --- a/Services/AdvancedMetaData/classes/class.ilAdvancedMDValues.php +++ b/Services/AdvancedMetaData/classes/class.ilAdvancedMDValues.php @@ -261,7 +261,12 @@ public static function preloadedRead(string $a_type, int $a_obj_id): array // record is optional, check activation for object if ($item[1]) { $found = false; - foreach (ilAdvancedMDRecord::_getSelectedRecordsByObject($a_type, $a_obj_id) as $record) { + foreach (ilAdvancedMDRecord::_getSelectedRecordsByObject( + $a_type, + $a_obj_id, + '', + false + ) as $record) { if ($record->getRecordId() == $item[0]) { $found = true; } @@ -357,7 +362,7 @@ public static function _cloneValues( } $target_sel[] = $record_id; } - ilAdvancedMDRecord::saveObjRecSelection($a_target_id, $a_sub_type, $target_sel); + ilAdvancedMDRecord::saveObjRecSelection($a_target_id, (string) $a_sub_type, $target_sel); } // clone values diff --git a/Services/Authentication/classes/class.ilSession.php b/Services/Authentication/classes/class.ilSession.php index 969d658af767..69eb46c19dce 100755 --- a/Services/Authentication/classes/class.ilSession.php +++ b/Services/Authentication/classes/class.ilSession.php @@ -1,7 +1,5 @@ * @@ -226,9 +226,9 @@ public static function _exists(string $a_session_id): bool /** * Destroy session * - * @param string|array session id|s - * @param int closing context - * @param int|bool expired at timestamp + * @param string|array $a_session_id session id|s + * @param int|null $a_closing_context closing context + * @param int|bool $a_expired_at expired at timestamp */ public static function _destroy($a_session_id, ?int $a_closing_context = null, $a_expired_at = null): bool { @@ -258,6 +258,18 @@ public static function _destroy($a_session_id, ?int $a_closing_context = null, $ $ilDB->manipulate($q); + try { + // only delete session cookie if it is set in the current request + if ($DIC->http()->wrapper()->cookie()->has(session_name()) && + $DIC->http()->wrapper()->cookie()->retrieve(session_name(), $DIC->refinery()->kindlyTo()->string()) === $a_session_id) { + $cookieJar = $DIC->http()->cookieJar()->without(session_name()); + $cookieJar->renderIntoResponseHeader($DIC->http()->response()); + } + } catch (\Throwable $e) { + // ignore + // this is needed for "header already" sent errors when the random cleanup of expired sessions is triggered + } + return true; } diff --git a/Services/Awareness/GlobalScreen/classes/class.ilAwarenessMetaBarProvider.php b/Services/Awareness/GlobalScreen/classes/class.ilAwarenessMetaBarProvider.php index f376a4468b28..5339c57fc4a2 100644 --- a/Services/Awareness/GlobalScreen/classes/class.ilAwarenessMetaBarProvider.php +++ b/Services/Awareness/GlobalScreen/classes/class.ilAwarenessMetaBarProvider.php @@ -67,10 +67,13 @@ public function getMetaBarItems(): array $counter = $manager->processMetaBar(); - $content = function () use ($gui) { - $result = $gui->getAwarenessList(true); - return $this->dic->ui()->factory()->legacy($result["html"]); - }; + $result = $gui->getAwarenessList(true); + $online = explode(":", $result["cnt"]); + $online = (int) $online[0]; + $content = $this->dic->ui()->factory()->legacy($result["html"]); + if ($online === 0) { + $is_widget_visible = false; + } $mb = $this->globalScreen()->metaBar(); @@ -88,7 +91,7 @@ public function getMetaBarItems(): array } return $c; }) - ->withLegacyContent($content()) + ->withLegacyContent($content) ->withSymbol( $this->dic->ui()->factory() ->symbol() diff --git a/Services/Awareness/classes/Provider/AwarenessToastProvider.php b/Services/Awareness/classes/Provider/AwarenessToastProvider.php index 08b6b88f7d42..7c6c91c4485c 100644 --- a/Services/Awareness/classes/Provider/AwarenessToastProvider.php +++ b/Services/Awareness/classes/Provider/AwarenessToastProvider.php @@ -47,6 +47,7 @@ public function getToasts(): array $toasts = []; if ( + $settings->get('awrn_enabled', '0') !== '1' || $settings->get('use_osd', '0') !== '1' || 0 === $this->dic->user()->getId() || $this->dic->user()->isAnonymous() diff --git a/Services/Badge/classes/class.ilBadgeHandler.php b/Services/Badge/classes/class.ilBadgeHandler.php index 2cacdee505d3..25b78ee3a67c 100644 --- a/Services/Badge/classes/class.ilBadgeHandler.php +++ b/Services/Badge/classes/class.ilBadgeHandler.php @@ -208,7 +208,7 @@ public function setInactiveTypes(array $a_types = null): void * Get badges types * @return array */ - public function getAvailableTypes(): array + public function getAvailableTypes(bool $exclude_inactive = true): array { $res = []; @@ -218,7 +218,7 @@ public function getAvailableTypes(): array if ($provider) { foreach ($provider->getBadgeTypes() as $type) { $id = $this->getUniqueTypeId($component_id, $type); - if (!in_array($id, $inactive, true)) { + if (!$exclude_inactive || !in_array($id, $inactive, true)) { $res[$id] = $type; } } diff --git a/Services/Badge/classes/class.ilBadgeWAC.php b/Services/Badge/classes/class.ilBadgeWAC.php index 6d8d03269976..4ca2dc7851aa 100644 --- a/Services/Badge/classes/class.ilBadgeWAC.php +++ b/Services/Badge/classes/class.ilBadgeWAC.php @@ -23,6 +23,118 @@ class ilBadgeWAC implements ilWACCheckingClass { public function canBeDelivered(ilWACPath $ilWACPath): bool { - return true; + global $DIC; + + if (strpos($ilWACPath->getPath(), '..') !== false) { + return false; + } + + if (!preg_match('@ilBadge\/(\d+\/)*?badge(tmpl)?_(\d+)\/@ui', $ilWACPath->getPath())) { + return false; + } + + $obj_id = array_keys(ilObject::_getObjectsByType('bdga'))[0] ?? null; + $admin_ref_id = null; + if ($obj_id > 0) { + $admin_ref_id = array_values(ilObject::_getAllReferences($obj_id))[0] ?? null; + } + + $has_global_badge_administration_access = ( + $admin_ref_id > 0 && + $DIC->rbac()->system()->checkAccessOfUser($DIC->user()->getId(), 'read', $admin_ref_id) + ); + + if (preg_match('@\/badgetmpl_(\d+)\/@ui', $ilWACPath->getPath())) { + // Badge template images must only be accessible for accounts with `read` permission on the badge administration node + return $has_global_badge_administration_access; + } + + if (preg_match('@\/badge_(\d+)\/@ui', $ilWACPath->getPath(), $matches)) { + if ($has_global_badge_administration_access) { + return true; + } + + $badge_id = (int) $matches[1]; + + return ( + $this->isAssignedBadge($DIC, $badge_id) || + $this->isAssignedBadgeOfPublishedUserProfile($DIC, $badge_id) || + $this->hasAccessToBadgeParentIdNode($DIC, $badge_id, $has_global_badge_administration_access) + ); + } + + return false; + } + + private function hasAccessToBadgeParentIdNode( + \ILIAS\DI\Container $DIC, + int $badge_id, + bool $has_global_badge_administration_access + ) : bool { + // If the acting user still does not have access, check if the image is used in an object badge type + $badge = new ilBadge($badge_id); + if ($badge->getParentId() > 0) { + return false; + } + + $badge_handler = ilBadgeHandler::getInstance(); + if (!$badge_handler->isObjectActive((int) $badge->getParentId())) { + return false; + } + + $context_ref_id = array_values(ilObject::_getAllReferences((int) $badge->getParentId()))[0] ?? null; + if (!($context_ref_id > 0)) { + return false; + } + + $context_ref_id = (int) $context_ref_id; + if ($DIC->repositoryTree()->isGrandChild((int) SYSTEM_FOLDER_ID, $context_ref_id)) { + $has_access = $has_global_badge_administration_access; + } else { + $has_access = $DIC->access()->checkAccessOfUser( + $DIC->user()->getId(), + 'write', + '', + $context_ref_id + ); + } + + return $has_access; + } + + private function isAssignedBadge(\ILIAS\DI\Container $DIC, int $badge_id) : bool + { + // First, check all badge assignments of the current user for a match + $badges_of_user = ilBadgeAssignment::getInstancesByUserId($DIC->user()->getId()); + foreach ($badges_of_user as $user_badge) { + if ((int) $user_badge->getBadgeId() === $badge_id) { + return true; + } + } + + return false; + } + + private function isAssignedBadgeOfPublishedUserProfile(\ILIAS\DI\Container $DIC, int $badge_id) : bool + { + // It seems the badge is not assigned to the curent user, so check if the profile of the badge user is made visible + $assignments = ilBadgeAssignment::getInstancesByBadgeId($badge_id); + foreach ($assignments as $assignment) { + if (!$assignment->getPosition()) { + continue; + } + + $user = ilObjectFactory::getInstanceByObjId((int) $assignment->getUserId(), false); + if (!$user instanceof ilObjUser) { + continue; + } + + $profile_visibility = $user->getPref('public_profile'); + if ($profile_visibility === 'g' || ($profile_visibility === 'y' && !$DIC->user()->isAnonymous())) { + return true; + } + } + + return false; } } diff --git a/Services/Badge/classes/class.ilObjBadgeAdministrationGUI.php b/Services/Badge/classes/class.ilObjBadgeAdministrationGUI.php index 6183305b8289..a779ccbeab23 100644 --- a/Services/Badge/classes/class.ilObjBadgeAdministrationGUI.php +++ b/Services/Badge/classes/class.ilObjBadgeAdministrationGUI.php @@ -343,7 +343,7 @@ protected function initImageTemplateForm( $types->setRequired(true); $type_spec->addSubItem($types); - foreach (ilBadgeHandler::getInstance()->getAvailableTypes() as $id => $type) { + foreach (ilBadgeHandler::getInstance()->getAvailableTypes(false) as $id => $type) { $types->addOption(new ilCheckboxOption($type->getCaption(), $id)); } diff --git a/Services/Badge/classes/class.ilObjectBadgeTableGUI.php b/Services/Badge/classes/class.ilObjectBadgeTableGUI.php index 15c817f91b96..d83f008e3f64 100644 --- a/Services/Badge/classes/class.ilObjectBadgeTableGUI.php +++ b/Services/Badge/classes/class.ilObjectBadgeTableGUI.php @@ -113,7 +113,7 @@ public function getItems(): void $data = []; - $types = ilBadgeHandler::getInstance()->getAvailableTypes(); + $types = ilBadgeHandler::getInstance()->getAvailableTypes(false); foreach (ilBadge::getObjectInstances($this->filter) as $badge_item) { // :TODO: container presentation diff --git a/Services/Block/classes/class.ilBlockGUI.php b/Services/Block/classes/class.ilBlockGUI.php index 643dad65a7ee..fc344a581b6e 100644 --- a/Services/Block/classes/class.ilBlockGUI.php +++ b/Services/Block/classes/class.ilBlockGUI.php @@ -821,6 +821,12 @@ protected function addRepoCommands(): void $lng->txt("delete") ); + $this->addBlockCommand( + "ilias.php?baseClass=ilRepositoryGUI&ref_id=" . $this->requested_ref_id . "&cmd=link" . + "&item_ref_id=" . $this->getRefId(), + $lng->txt("link") + ); + // see ilObjectListGUI::insertCutCommand(); $this->addBlockCommand( "ilias.php?baseClass=ilRepositoryGUI&ref_id=" . $this->requested_ref_id . "&cmd=cut" . diff --git a/Services/Block/classes/class.ilColumnGUI.php b/Services/Block/classes/class.ilColumnGUI.php index b374cbeddfab..cfcd33eb31df 100644 --- a/Services/Block/classes/class.ilColumnGUI.php +++ b/Services/Block/classes/class.ilColumnGUI.php @@ -1,7 +1,5 @@ "Services/News/", "ilCalendarBlockGUI" => "Services/Calendar/", "ilPDCalendarBlockGUI" => "Services/Calendar/", + "ilConsultationHoursCalendarBlockGUI" => "Services/Calendar/", "ilPDTasksBlockGUI" => "Services/Tasks/", "ilPDMailBlockGUI" => "Services/Mail/", "ilPDSelectedItemsBlockGUI" => "Services/Dashboard/ItemsBlock/", @@ -91,6 +92,7 @@ class ilColumnGUI "ilNewsForContextBlockGUI" => "news", "ilCalendarBlockGUI" => "cal", "ilPDCalendarBlockGUI" => "pdcal", + "ilConsultationHoursCalendarBlockGUI" => "chcal", "ilPDSelectedItemsBlockGUI" => "pditems", 'ilPollBlockGUI' => 'poll', 'ilClassificationBlockGUI' => 'clsfct', @@ -107,11 +109,13 @@ class ilColumnGUI "crs" => array( "ilNewsForContextBlockGUI" => IL_COL_RIGHT, "ilCalendarBlockGUI" => IL_COL_RIGHT, + "ilConsultationHoursCalendarBlockGUI" => IL_COL_RIGHT, "ilClassificationBlockGUI" => IL_COL_RIGHT ), "grp" => array( "ilNewsForContextBlockGUI" => IL_COL_RIGHT, "ilCalendarBlockGUI" => IL_COL_RIGHT, + "ilConsultationHoursCalendarBlockGUI" => IL_COL_RIGHT, "ilClassificationBlockGUI" => IL_COL_RIGHT ), "frm" => array("ilNewsForContextBlockGUI" => IL_COL_RIGHT), @@ -148,6 +152,7 @@ class ilColumnGUI array("news" => true, "cal" => true, "pdcal" => true, + "chcal" => true, "pdnews" => true, "pdtag" => true, "pdmail" => true, @@ -564,6 +569,9 @@ public function determineBlocks(): void if ($type == "cal") { $nr = -8; } + if ($type == "chcal") { // consultation hours always directly below calendar + $nr = -7; + } if ($type == "pdfeedb") { // always show feedback request second $nr = -10; } @@ -714,7 +722,7 @@ protected function isGloballyActivated( ); } elseif ($ilSetting->get("block_activated_" . $a_type)) { return true; - } elseif ($a_type == 'cal') { + } elseif ($a_type == 'cal' || $a_type == 'chcal') { return ilCalendarSettings::lookupCalendarContentPresentationEnabled($ilCtrl->getContextObjId()); } elseif ($a_type == 'pdcal') { if (!$this->dash_side_panel_settings->isEnabled($this->dash_side_panel_settings::CALENDAR)) { diff --git a/Services/COPage/Editor/Components/MediaObject/class.ilPCMediaObjectEditorGUI.php b/Services/COPage/Editor/Components/MediaObject/class.ilPCMediaObjectEditorGUI.php index 4f831c539675..0c171ec22398 100644 --- a/Services/COPage/Editor/Components/MediaObject/class.ilPCMediaObjectEditorGUI.php +++ b/Services/COPage/Editor/Components/MediaObject/class.ilPCMediaObjectEditorGUI.php @@ -81,6 +81,10 @@ public function getEditComponentForm( $pc_media_gui->setStyleId($style_id); $pc_media_gui->getCharacteristicsOfCurrentStyle(["media_cont"]); + if (is_null($pc_media->getMediaObject())) { + return "
" . $ui_wrapper->getRenderedInfoBox($lng->txt("copg_pc_mob_does_not_exist")) . + $ui_wrapper->getRenderedButton($lng->txt("cancel"), "form-button", "component.cancel", null, "Page") . "
"; + } $media = $pc_media->getMediaObject()->getMediaItem("Standard"); // title diff --git a/Services/COPage/Editor/Components/Page/class.PageQueryActionHandler.php b/Services/COPage/Editor/Components/Page/class.PageQueryActionHandler.php index 51ab1c2d2f0e..4a85ad97bc4b 100644 --- a/Services/COPage/Editor/Components/Page/class.PageQueryActionHandler.php +++ b/Services/COPage/Editor/Components/Page/class.PageQueryActionHandler.php @@ -144,8 +144,10 @@ protected function getAddCommands(): array // plugins foreach ($this->component_factory->getActivePluginsInSlot("pgcp") as $plugin) { - $commands["plug_" . $plugin->getPluginName()] = - $plugin->txt(\ilPageComponentPlugin::TXT_CMD_INSERT); + if ($plugin->isValidParentType($this->page_gui->getPageObject()->getParentType())) { + $commands["plug_" . $plugin->getPluginName()] = + $plugin->txt(\ilPageComponentPlugin::TXT_CMD_INSERT); + } } return $commands; } diff --git a/Services/COPage/Editor/Components/Section/class.SectionCommandActionHandler.php b/Services/COPage/Editor/Components/Section/class.SectionCommandActionHandler.php index 6e36ee708563..2f080586e714 100644 --- a/Services/COPage/Editor/Components/Section/class.SectionCommandActionHandler.php +++ b/Services/COPage/Editor/Components/Section/class.SectionCommandActionHandler.php @@ -26,6 +26,7 @@ */ class SectionCommandActionHandler implements Server\CommandActionHandler { + protected \ilCtrlInterface $ctrl; protected \ILIAS\DI\UIServices $ui; protected \ilLanguage $lng; protected \ilPageObjectGUI $page_gui; @@ -38,6 +39,7 @@ public function __construct(\ilPageObjectGUI $page_gui) $this->ui = $DIC->ui(); $this->lng = $DIC->language(); + $this->ctrl = $DIC->ctrl(); $this->page_gui = $page_gui; $this->user = $DIC->user(); @@ -80,9 +82,22 @@ protected function insertCommand(array $body): Server\Response // note: we have everyting in _POST here, form works the usual way $updated = true; - if ($form->checkInput()) { + if ($sec_gui->checkInput($form)) { $sec_gui->setValuesFromForm($form); $updated = $page->update(); + } else { + $html = $this->ctrl->getHTML( + $sec_gui, + [ + "form" => true, + "ui_wrapper" => $this->ui_wrapper, + "update_fail" => true, + "insert" => true, + "buttons" => [["Page", "component.save", $this->lng->txt("insert")], + ["Page", "component.cancel", $this->lng->txt("cancel")]] + ] + ); + return $this->ui_wrapper->sendFormError($html); } return $this->ui_wrapper->sendPage($this->page_gui, $updated); @@ -95,15 +110,28 @@ protected function updateCommand(array $body): Server\Response $hier_id = $page->getHierIdForPcId($body["pcid"]); $sec = $page->getContentObjectForPcId($body["pcid"]); $sec_gui = new \ilPCSectionGUI($page, $sec, $hier_id, $body["pcid"]); + $sec_gui->setStyleId($this->page_gui->getStyleId()); $sec_gui->setPageConfig($page->getPageConfig()); $form = $sec_gui->initForm(false); // note: we have everyting in _POST here, form works the usual way $updated = true; - if ($form->checkInput()) { + if ($sec_gui->checkInput($form)) { $sec_gui->setValuesFromForm($form); $updated = $page->update(); + } else { + $html = $this->ctrl->getHTML( + $sec_gui, + [ + "form" => true, + "ui_wrapper" => $this->ui_wrapper, + "update_fail" => true, + "buttons" => [["Page", "component.update", $this->lng->txt("save")], + ["Page", "component.cancel", $this->lng->txt("cancel")]] + ] + ); + return $this->ui_wrapper->sendFormError($html); } return $this->ui_wrapper->sendPage($this->page_gui, $updated); diff --git a/Services/COPage/Editor/Components/Section/class.ilPCSectionEditorGUI.php b/Services/COPage/Editor/Components/Section/class.ilPCSectionEditorGUI.php index 35ba043d544c..f44a8dc6d7ae 100644 --- a/Services/COPage/Editor/Components/Section/class.ilPCSectionEditorGUI.php +++ b/Services/COPage/Editor/Components/Section/class.ilPCSectionEditorGUI.php @@ -64,6 +64,7 @@ protected function getCreationForm( [ "form" => true, "ui_wrapper" => $ui_wrapper, + "insert" => true, "buttons" => [["Page", "component.save", $lng->txt("insert")], ["Page", "component.cancel", $lng->txt("cancel")]] ] diff --git a/Services/COPage/Editor/Server/class.UIWrapper.php b/Services/COPage/Editor/Server/class.UIWrapper.php index cbded5884e0f..677858a4f83b 100644 --- a/Services/COPage/Editor/Server/class.UIWrapper.php +++ b/Services/COPage/Editor/Server/class.UIWrapper.php @@ -41,7 +41,8 @@ public function getButton( string $type, string $action, array $data = null, - string $component = "" + string $component = "", + string $aria_label = "" ): \ILIAS\UI\Component\Button\Standard { $ui = $this->ui; $f = $ui->factory(); @@ -50,10 +51,13 @@ public function getButton( $data = []; } $b = $b->withOnLoadCode( - function ($id) use ($type, $data, $action, $component) { + function ($id) use ($type, $data, $action, $component, $aria_label) { $code = "document.querySelector('#$id').setAttribute('data-copg-ed-type', '$type'); document.querySelector('#$id').setAttribute('data-copg-ed-component', '$component'); - document.querySelector('#$id').setAttribute('data-copg-ed-action', '$action')"; + document.querySelector('#$id').setAttribute('data-copg-ed-action', '$action'); "; + if ($aria_label !== "") { + $code.= "document.querySelector('#$id').setAttribute('aria-label', '$aria_label'); "; + } foreach ($data as $key => $val) { $code .= "\n document.querySelector('#$id').setAttribute('data-copg-ed-par-$key', '$val');"; } @@ -86,10 +90,11 @@ public function getRenderedButton( string $type, string $action, array $data = null, - string $component = "" + string $component = "", + string $aria_label = "" ): string { $ui = $this->ui; - $b = $this->getButton($content, $type, $action, $data, $component); + $b = $this->getButton($content, $type, $action, $data, $component, $aria_label); return $ui->renderer()->renderAsync($b); } @@ -198,7 +203,7 @@ public function sendPage( } $data = new \stdClass(); - $data->renderedContent = $page_data; + $data->renderedContent = $page_data . $this->getOnloadCode($page_gui); $data->pcModel = $pc_model; $data->error = $error; if ($last_change) { @@ -209,6 +214,29 @@ public function sendPage( return new Response($data); } + protected function getOnloadCode(\ilPageObjectGUI $page_gui) : string + { + $page = $page_gui->getPageObject(); + $defs = \ilCOPagePCDef::getPCDefinitions(); + $all_onload_code = []; + foreach ($defs as $def) { + $pc_class = $def["pc_class"]; + /** @var \ilPageContent $pc_obj */ + $pc_obj = new $pc_class($page); + + // onload code + $onload_code = $pc_obj->getOnloadCode("edit"); + foreach ($onload_code as $code) { + $all_onload_code[] = $code; + } + } + $code_str = ""; + if (count($all_onload_code) > 0) { + $code_str = ""; + } + return $code_str; + } + public function sendFormError( string $form ): Response { diff --git a/Services/COPage/Editor/js/src/components/page/ui/page-ui-action-handler.js b/Services/COPage/Editor/js/src/components/page/ui/page-ui-action-handler.js index 3dc122bf72df..34408f19c789 100644 --- a/Services/COPage/Editor/js/src/components/page/ui/page-ui-action-handler.js +++ b/Services/COPage/Editor/js/src/components/page/ui/page-ui-action-handler.js @@ -428,8 +428,14 @@ export default class PageUIActionHandler { ); this.client.sendCommand(update_action).then(result => { - this.ui.handlePageReloadResponse(result); - dispatch.dispatch(af.page().editor().enablePageEditing()); + const p = result.payload; + if (p.formError) { + //document.querySelector(".copg-new-content-placeholder img").outerHTML = this.ui.uiModel.components[model.getCurrentPCName()].icon; + this.ui.showFormAfterError(p.form); + } else { + this.ui.handlePageReloadResponse(result); + dispatch.dispatch(af.page().editor().enablePageEditing()); + } }); } diff --git a/Services/COPage/Editor/js/src/components/paragraph/ui/paragraph-ui.js b/Services/COPage/Editor/js/src/components/paragraph/ui/paragraph-ui.js index b811fba9767e..62a55cd58c25 100644 --- a/Services/COPage/Editor/js/src/components/paragraph/ui/paragraph-ui.js +++ b/Services/COPage/Editor/js/src/components/paragraph/ui/paragraph-ui.js @@ -439,6 +439,7 @@ export default class ParagraphUI { if (fc) { this.log("SETTin DROP DOWN BUTTON: " + i) fc.firstChild.textContent = ddbtn.textContent + " "; + fc.ariaLabel = il.Language.txt('copg_par_format_selection') + ": " + ddbtn.textContent; } this.tinyWrapper.setParagraphClass(i); } @@ -714,6 +715,11 @@ export default class ParagraphUI { } } + escape() { + const b = document.querySelector("[data-copg-ed-action='save.return']"); + b.focus(); + } + initWrapperCallbacks() { const wrapper = this.tinyWrapper; const parUI = this; @@ -734,6 +740,11 @@ export default class ParagraphUI { parUI.switchToNext(); } }); + wrapper.addCallback(TINY_CB.ESCAPE, () => { + if (pageModel.getCurrentPCName() === "Paragraph") { + parUI.escape(); + } + }); wrapper.addCallback(TINY_CB.SWITCH_DOWN, () => { if (pageModel.getCurrentPCName() === "Paragraph") { parUI.switchToNext(); @@ -856,6 +867,10 @@ export default class ParagraphUI { const action = this.actionFactory; const ef = action.paragraph().editor(); const tblact = action.table().editor(); + const ifrm = document.getElementById('tinytarget_ifr'); + if (ifrm) { + ifrm.title = il.Language.txt("copg_edit_iframe_title"); + } //#0017152 $('#tinytarget_ifr').contents().find("html").attr('lang', $('html').attr('lang')); diff --git a/Services/COPage/Editor/js/src/components/paragraph/ui/tiny-wrapper-cb-types.js b/Services/COPage/Editor/js/src/components/paragraph/ui/tiny-wrapper-cb-types.js index 8b5bdf1b0d87..3301a5ba7501 100644 --- a/Services/COPage/Editor/js/src/components/paragraph/ui/tiny-wrapper-cb-types.js +++ b/Services/COPage/Editor/js/src/components/paragraph/ui/tiny-wrapper-cb-types.js @@ -26,7 +26,8 @@ const CBTYPES = { KEY_UP: 7, AFTER_INIT: 8, TAB: 9, - SHIFT_TAB: 10 + SHIFT_TAB: 10, + ESCAPE: 11 }; export default CBTYPES; \ No newline at end of file diff --git a/Services/COPage/Editor/js/src/components/paragraph/ui/tiny-wrapper.js b/Services/COPage/Editor/js/src/components/paragraph/ui/tiny-wrapper.js index d2cad404b5bd..64c1f528519f 100644 --- a/Services/COPage/Editor/js/src/components/paragraph/ui/tiny-wrapper.js +++ b/Services/COPage/Editor/js/src/components/paragraph/ui/tiny-wrapper.js @@ -337,8 +337,13 @@ export default class TinyWrapper { cb(); }); - const currentRng = tiny.selection.getRng(); + if (ev.key === "Escape") { + wrapper.getCallbacks(CB.ESCAPE).forEach((cb) => { + cb(); + }); + } + const currentRng = tiny.selection.getRng(); // down, right if ([39,40].includes(ev.keyCode)) { if ( diff --git a/Services/COPage/GlobalScreen/classes/class.ilCOPageEditGSToolProvider.php b/Services/COPage/GlobalScreen/classes/class.ilCOPageEditGSToolProvider.php index b890afc7c30d..03a021e1dad6 100644 --- a/Services/COPage/GlobalScreen/classes/class.ilCOPageEditGSToolProvider.php +++ b/Services/COPage/GlobalScreen/classes/class.ilCOPageEditGSToolProvider.php @@ -60,7 +60,9 @@ public function getToolsForContextStack(CalledContexts $called_contexts): array return $c->withAdditionalOnLoadCode(static function ($id) use ($hashed) { return " $('body').on('il-copg-editor-slate', function(){ - il.UI.maincontrols.mainbar.engageTool('$hashed'); + if (!il.UI.page.isSmallScreen()) { + il.UI.maincontrols.mainbar.engageTool('$hashed'); + } });"; }); } diff --git a/Services/COPage/Layout/classes/class.ilPageLayout.php b/Services/COPage/Layout/classes/class.ilPageLayout.php index f5b084ef20dd..e6f8ace86a68 100644 --- a/Services/COPage/Layout/classes/class.ilPageLayout.php +++ b/Services/COPage/Layout/classes/class.ilPageLayout.php @@ -207,6 +207,12 @@ public function getXMLContent(): string return $layout_page->getXMLContent(); } + public function copyXmlContent(bool $self_ass = true): string + { + $layout_page = new ilPageLayoutPage($this->layout_id); + return $layout_page->copyXmlContent(true, 0, 0, $self_ass); + } + public function getPreview(): string { return $this->generatePreview(); diff --git a/Services/COPage/classes/class.ilCOPageImporter.php b/Services/COPage/classes/class.ilCOPageImporter.php index ba63b11afc79..43a52d215399 100644 --- a/Services/COPage/classes/class.ilCOPageImporter.php +++ b/Services/COPage/classes/class.ilCOPageImporter.php @@ -102,6 +102,9 @@ public function importXmlRepresentation( $page->updateFromXML(); $this->extractPluginProperties($page); } else { + if (ilPageObject::_exists($id[0], (int) $id[1], "-", true)) { + return; + } $new_page = ilPageObjectFactory::getInstance($id[0]); $new_page->setImportMode(true); $new_page->setId($id[1]); diff --git a/Services/COPage/classes/class.ilPCImageMapEditorGUI.php b/Services/COPage/classes/class.ilPCImageMapEditorGUI.php index 66df4308668d..d781bff1b75f 100755 --- a/Services/COPage/classes/class.ilPCImageMapEditorGUI.php +++ b/Services/COPage/classes/class.ilPCImageMapEditorGUI.php @@ -90,7 +90,7 @@ public function saveArea(): string $lng = $this->lng; $ilCtrl = $this->ctrl; - switch ($this->map_repo->getMode()) { + switch ($this->map->getMode()) { // save edited link case "edit_link": // $std_alias_item = new ilMediaAliasItem($this->content_obj->dom, @@ -99,19 +99,19 @@ public function saveArea(): string $area_link_type = $this->edit_request->getString("area_link_type"); if ($area_link_type == IL_INT_LINK) { $this->std_alias_item->setAreaIntLink( - $this->map_repo->getAreaNr(), - $this->map_repo->getLinkType(), - $this->map_repo->getLinkTarget(), - $this->map_repo->getLinkFrame() + $this->map->getAreaNr(), + $this->map->getLinkType(), + $this->map->getLinkTarget(), + $this->map->getLinkFrame() ); } elseif ($area_link_type == IL_NO_LINK) { $this->std_alias_item->setAreaExtLink( - $this->map_repo->getAreaNr(), + $this->map->getAreaNr(), "" ); } else { $this->std_alias_item->setAreaExtLink( - $this->map_repo->getAreaNr(), + $this->map->getAreaNr(), $this->edit_request->getString("area_link_ext") ); } @@ -121,18 +121,18 @@ public function saveArea(): string // save edited shape case "edit_shape": $this->std_alias_item->setShape( - $this->map_repo->getAreaNr(), - $this->map_repo->getAreaType(), - $this->map_repo->getCoords() + $this->map->getAreaNr(), + $this->map->getAreaType(), + $this->map->getCoords() ); $this->page->update(); break; // save new area default: - $area_type = $this->map_repo->getAreaType(); - $coords = $this->map_repo->getCoords(); - + $area_type = $this->map->getAreaType(); + $coords = $this->map->getCoords(); + $link = []; $area_link_type = $this->edit_request->getString("area_link_type"); switch ($area_link_type) { case IL_EXT_LINK: @@ -148,11 +148,12 @@ public function saveArea(): string break; case IL_INT_LINK: + $int_link = $this->map->getInternalLink(); $link = array( "LinkType" => IL_INT_LINK, - "Type" => $this->map_repo->getLinkType(), - "Target" => $this->map_repo->getLinkTarget(), - "TargetFrame" => $this->map_repo->getLinkFrame()); + "Type" => $int_link["type"], + "Target" => $int_link["target"], + "TargetFrame" => $int_link["target_frame"]); break; } @@ -162,7 +163,7 @@ public function saveArea(): string $area_type, $coords, $this->edit_request->getString("area_name"), - [] + $link ); $this->page->update(); break; diff --git a/Services/COPage/classes/class.ilPCMediaObject.php b/Services/COPage/classes/class.ilPCMediaObject.php index dffb212abaeb..88da6bc0a771 100755 --- a/Services/COPage/classes/class.ilPCMediaObject.php +++ b/Services/COPage/classes/class.ilPCMediaObject.php @@ -328,7 +328,7 @@ public static function beforePageDelete( ilObjMediaObject::_deleteAllUsages( $a_page->getParentType() . ":pg", $a_page->getId(), - false, + null, $a_page->getLanguage() ); @@ -364,8 +364,9 @@ public static function saveMobUsage( DOMDocument $a_domdoc, int $a_old_nr = 0 ): array { - $usages = array(); + $log = ilLoggerFactory::getLogger("copg"); + $usages = array(); // media aliases $xpath = new DOMXPath($a_domdoc); $nodes = $xpath->query('//MediaAlias'); @@ -410,9 +411,11 @@ public static function saveMobUsage( $a_old_nr, $a_page->getLanguage() ); + $log->debug("Deleted all mob usages page id: " . $a_page->getId() . ", lang" . $a_page->getLanguage() . ", old nr: " . $a_old_nr); foreach ($usages as $mob_id => $val) { // save usage, if object exists... if (ilObject::_lookupType($mob_id) == "mob") { + $log->debug("Save usage mob id: " . $mob_id . ", old nr: " . $a_old_nr); ilObjMediaObject::_saveUsage( $mob_id, $a_page->getParentType() . ":pg", @@ -458,6 +461,24 @@ public function modifyPageContentPostXsl( return $a_output; } + if ($a_mode === "edit") { + $a_output = str_replace( + "{{{{{Unsupported Media Type}}}}}", + $this->ui->renderer()->render( + $this->ui->factory()->messageBox()->info( + $this->lng->txt("copg_unsupported_media_type") + ) + ), + $a_output + ); + } else { + $a_output = str_replace( + "{{{{{Unsupported Media Type}}}}}", + "", + $a_output + ); + } + // add fullscreen modals $page = $this->getPage(); $suffix = "-" . $page->getParentType() . "-" . $page->getId(); @@ -566,7 +587,9 @@ public function checkInstanceEditing(): bool return true; } } - return false; + // see https://mantis.ilias.de/view.php?id=38582 + // we allow instance editing regardless of number of usages + return true; } public static function deleteHistoryLowerEqualThan( diff --git a/Services/COPage/classes/class.ilPCMediaObjectGUI.php b/Services/COPage/classes/class.ilPCMediaObjectGUI.php index f633a81bc671..ec5fa8d04343 100755 --- a/Services/COPage/classes/class.ilPCMediaObjectGUI.php +++ b/Services/COPage/classes/class.ilPCMediaObjectGUI.php @@ -643,7 +643,11 @@ public function initAliasForm(): void // width height $width_height = new ilWidthHeightInputGUI($lng->txt("cont_width") . " / " . $lng->txt("cont_height"), "st_width_height"); - $width_height->setConstrainProportions(true); + if (is_int(strpos($std_item->getFormat(), "image")) + && $std_item->getLocationType() === "LocalFile") { + $width_height->setSupportConstraintsProps(true); + $width_height->setConstrainProportions(true); + } $op2->addSubItem($width_height); $radio_size->addOption($op2); @@ -762,7 +766,11 @@ public function initAliasForm(): void // width height $width_height = new ilWidthHeightInputGUI($lng->txt("cont_width") . " / " . $lng->txt("cont_height"), "full_width_height"); - $width_height->setConstrainProportions(true); + if (is_int(strpos($full_item->getFormat(), "image")) + && $full_item->getLocationType() === "LocalFile") { + $width_height->setSupportConstraintsProps(true); + $width_height->setConstrainProportions(true); + } $op2->addSubItem($width_height); $radio_size->addOption($op2); @@ -870,9 +878,24 @@ public function getAliasValues(): void $values["st_format"] = $std_item->getFormat(); // size - $values["st_width_height"]["width"] = $std_alias_item->getWidth(); - $values["st_width_height"]["height"] = $std_alias_item->getHeight(); - $values["st_width_height"]["constr_prop"] = true; + if ($std_alias_item->definesSize()) { + $values["st_width_height"]["width"] = $std_alias_item->getWidth(); + $values["st_width_height"]["height"] = $std_alias_item->getHeight(); + $values["st_width_height"]["constr_prop"] = true; + } else { + if ($std_item->getWidth() !== "" || $std_item->getHeight() !== "") { + $values["st_width_height"]["width"] = $std_item->getWidth(); + $values["st_width_height"]["height"] = $std_item->getHeight(); + $values["st_width_height"]["constr_prop"] = true; + } else { + $orig_size = $std_item->getOriginalSize(); + if (!is_null($orig_size)) { + $values["st_width_height"]["width"] = $orig_size["width"]; + $values["st_width_height"]["height"] = $orig_size["height"]; + $values["st_width_height"]["constr_prop"] = true; + } + } + } // caption $values["st_caption"] = $std_alias_item->getCaption(); @@ -908,10 +931,6 @@ public function getAliasValues(): void $values["st_derive_size"] = $std_alias_item->definesSize() ? "n" : "y"; - if ($values["st_derive_size"] == "y") { - $values["st_width_height"]["width"] = $std_item->getWidth(); - $values["st_width_height"]["height"] = $std_item->getHeight(); - } $values["st_derive_caption"] = $std_alias_item->definesCaption() ? "n" : "y"; @@ -939,9 +958,25 @@ public function getAliasValues(): void $values["full_location"] = $full_item->getLocation(); $values["full_format"] = $full_item->getFormat(); - $values["full_width_height"]["width"] = $full_alias_item->getWidth(); - $values["full_width_height"]["height"] = $full_alias_item->getHeight(); - $values["full_width_height"]["constr_prop"] = true; + + if ($full_alias_item->definesSize()) { + $values["full_width_height"]["width"] = $full_alias_item->getWidth(); + $values["full_width_height"]["height"] = $full_alias_item->getHeight(); + $values["full_width_height"]["constr_prop"] = true; + } else { + if ($full_item->getWidth() !== "" || $full_item->getHeight() !== "") { + $values["full_width_height"]["width"] = $full_item->getWidth(); + $values["full_width_height"]["height"] = $full_item->getHeight(); + $values["full_width_height"]["constr_prop"] = true; + } else { + $orig_full_size = $full_item->getOriginalSize(); + if (!is_null($orig_full_size)) { + $values["full_width_height"]["width"] = $orig_full_size["width"]; + $values["full_width_height"]["height"] = $orig_full_size["height"]; + $values["full_width_height"]["constr_prop"] = true; + } + } + } $values["full_caption"] = $full_alias_item->getCaption(); if (trim($full_item->getCaption()) == "") { $values["full_def_caption"] = $lng->txt("cont_no_caption"); @@ -958,10 +993,6 @@ public function getAliasValues(): void $values["full_derive_size"] = $full_alias_item->definesSize() ? "n" : "y"; - if ($values["full_derive_size"] == "y") { - $values["full_width_height"]["width"] = $full_item->getWidth(); - $values["full_width_height"]["height"] = $full_item->getHeight(); - } $values["full_derive_caption"] = $full_alias_item->definesCaption() ? "n" : "y"; @@ -992,8 +1023,7 @@ public function getAliasValues(): void $values["full_def_parameters"] = $full_item->getParameterString(); } } - - $this->form_gui->setValuesByArray($values); + $this->form_gui->setValuesByArray($values, true); } /** diff --git a/Services/COPage/classes/class.ilPCMediaObjectQuickEdit.php b/Services/COPage/classes/class.ilPCMediaObjectQuickEdit.php index be5f0a958b0b..dbe2fcffc1fa 100644 --- a/Services/COPage/classes/class.ilPCMediaObjectQuickEdit.php +++ b/Services/COPage/classes/class.ilPCMediaObjectQuickEdit.php @@ -24,7 +24,7 @@ class ilPCMediaObjectQuickEdit { protected ilPCMediaObject $pcmedia; - protected ilObjMediaObject $mob; + protected ?ilObjMediaObject $mob; protected int $usage_cnt; public function __construct( @@ -32,7 +32,9 @@ public function __construct( ) { $this->pcmedia = $pcmedia; $this->mob = $pcmedia->getMediaObject(); - $this->usage_cnt = count($this->mob->getUsages()); + if (!is_null($this->mob)) { + $this->usage_cnt = count($this->mob->getUsages()); + } } // TITLE diff --git a/Services/COPage/classes/class.ilPCParagraph.php b/Services/COPage/classes/class.ilPCParagraph.php index 28cc8ca0e5b8..857b1344d5d5 100755 --- a/Services/COPage/classes/class.ilPCParagraph.php +++ b/Services/COPage/classes/class.ilPCParagraph.php @@ -1229,7 +1229,7 @@ public static function xml2output( $a_text = preg_replace('~~i', "[iln " . $inst_str . "media=\"" . $target_id . "\"/]", $a_text); } else { $a_text = preg_replace('~~i', "[iln media=\"" . $target_id . "\"" . - " target=\"" . $attribs["TargetFrame"] . "\"]", $a_text); + " target=\"" . ($attribs["TargetFrame"] ?? "") . "\"]", $a_text); } break; @@ -1267,27 +1267,27 @@ public static function xml2output( //$found[1] = str_replace("?", "\?", $found[1]); $tstr = ""; if (in_array(($attribs["TargetFrame"] ?? ""), array("FAQ", "Glossary", "Media"))) { - $tstr = ' target="' . $attribs["TargetFrame"] . '"'; + $tstr = ' target="' . ($attribs["TargetFrame"] ?? "") . '"'; } - $a_text = str_replace("", "[xln url=\"" . $attribs["Href"] . "\"$tstr]", $a_text); + $a_text = str_replace("", "[xln url=\"" . ($attribs["Href"] ?? "") . "\"$tstr]", $a_text); } $a_text = str_replace("", "[/xln]", $a_text); // anchor while (preg_match('~~i', $a_text, $found)) { $attribs = self::attribsToArray($found[1]); - $a_text = str_replace("", "[anc name=\"" . $attribs["Name"] . "\"][/anc]", $a_text); + $a_text = str_replace("", "[anc name=\"" . ($attribs["Name"] ?? "") . "\"][/anc]", $a_text); } while (preg_match('~~i', $a_text, $found)) { $attribs = self::attribsToArray($found[1]); - $a_text = str_replace("", "[anc name=\"" . $attribs["Name"] . "\"]", $a_text); + $a_text = str_replace("", "[anc name=\"" . ($attribs["Name"] ?? "") . "\"]", $a_text); } $a_text = str_replace("", "[/anc]", $a_text); // marked text while (preg_match('~~i', $a_text, $found)) { $attribs = self::attribsToArray($found[1]); - $a_text = str_replace("", "[marked class=\"" . $attribs["Class"] . "\"]", $a_text); + $a_text = str_replace("", "[marked class=\"" . ($attribs["Class"] ?? "") . "\"]", $a_text); } $a_text = str_replace("", "[/marked]", $a_text); diff --git a/Services/COPage/classes/class.ilPCPlaceHolder.php b/Services/COPage/classes/class.ilPCPlaceHolder.php index e7d5d36f3dd3..b2ff26823e85 100644 --- a/Services/COPage/classes/class.ilPCPlaceHolder.php +++ b/Services/COPage/classes/class.ilPCPlaceHolder.php @@ -166,4 +166,25 @@ public function getCssFiles(string $a_mode): array { return [ilObjStyleSheet::getPlaceHolderStylePath()]; } + + public static function handleCopiedContent( + DOMDocument $a_domdoc, + bool $a_self_ass = true, + bool $a_clone_mobs = false, + int $new_parent_id = 0, + int $obj_copy_id = 0 + ): void { + // remove question placholders + if (!$a_self_ass) { + // Get question IDs + $path = "//PlaceHolder[@ContentClass = 'Question']"; + $xpath = new DOMXPath($a_domdoc); + $nodes = $xpath->query($path); + + foreach ($nodes as $node) { + $parent = $node->parentNode; + $parent->parentNode->removeChild($parent); + } + } + } } diff --git a/Services/COPage/classes/class.ilPCPlugged.php b/Services/COPage/classes/class.ilPCPlugged.php index d37d1a2ea909..021037fd9bd5 100755 --- a/Services/COPage/classes/class.ilPCPlugged.php +++ b/Services/COPage/classes/class.ilPCPlugged.php @@ -167,32 +167,36 @@ public static function handleCopiedPluggedContent( $plugin_name = $node->getAttribute('PluginName'); $plugin_version = $node->getAttribute('PluginVersion'); - $plugin_info = $component_repository->getPluginByName($plugin_name); - if ($plugin_info->isActive()) { - /** @var ilPageComponentPlugin $plugin_obj */ - $plugin_obj = $component_factory->getPlugin($plugin_info->getId()); - $plugin_obj->setPageObj($a_page); - - $properties = array(); - /** @var DOMElement $child */ - foreach ($node->childNodes as $child) { - $properties[$child->getAttribute('Name')] = $child->nodeValue; - } - - // let the plugin copy additional content - // and allow it to modify the saved parameters - $plugin_obj->onClone($properties, $plugin_version); - - foreach ($node->childNodes as $child) { - $node->removeChild($child); - } - foreach ($properties as $name => $value) { - $child = new DOMElement('PluggedProperty', - str_replace("&", "&", $value) - ); - $node->appendChild($child); - $child->setAttribute('Name', $name); + try { + $plugin_info = $component_repository->getPluginByName($plugin_name); + if ($plugin_info->isActive()) { + /** @var ilPageComponentPlugin $plugin_obj */ + $plugin_obj = $component_factory->getPlugin($plugin_info->getId()); + $plugin_obj->setPageObj($a_page); + + $properties = array(); + /** @var DOMElement $child */ + foreach ($node->childNodes as $child) { + $properties[$child->getAttribute('Name')] = $child->nodeValue; + } + + // let the plugin copy additional content + // and allow it to modify the saved parameters + $plugin_obj->onClone($properties, $plugin_version); + + foreach ($node->childNodes as $child) { + $node->removeChild($child); + } + foreach ($properties as $name => $value) { + $child = new DOMElement( + 'PluggedProperty', + str_replace("&", "&", $value) + ); + $node->appendChild($child); + $child->setAttribute('Name', $name); + } } + } catch (Exception $e) { } } } diff --git a/Services/COPage/classes/class.ilPCQuestion.php b/Services/COPage/classes/class.ilPCQuestion.php index f49ab3930266..ec59d2a1fec1 100755 --- a/Services/COPage/classes/class.ilPCQuestion.php +++ b/Services/COPage/classes/class.ilPCQuestion.php @@ -314,7 +314,7 @@ public function getOnloadCode(string $a_mode): array if ($this->getPage()->getPageConfig()->getEnableSelfAssessment()) { if (!$this->getPage()->getPageConfig()->getEnableSelfAssessmentScorm() && $a_mode != ilPageObjectGUI::PREVIEW - && $a_mode != "offline") { + && $a_mode != "offline" && $a_mode !== "edit") { $ilCtrl->setParameterByClass(strtolower(get_class($this->getPage())) . "gui", "page_id", $this->getPage()->getId()); $url = $ilCtrl->getLinkTargetByClass(strtolower(get_class($this->getPage())) . "gui", "processAnswer", "", true, false); $code[] = "ilCOPageQuestionHandler.initCallback('" . $url . "');"; diff --git a/Services/COPage/classes/class.ilPCQuestionGUI.php b/Services/COPage/classes/class.ilPCQuestionGUI.php index ee703d29b9e9..a479d5525085 100755 --- a/Services/COPage/classes/class.ilPCQuestionGUI.php +++ b/Services/COPage/classes/class.ilPCQuestionGUI.php @@ -91,7 +91,7 @@ public function getSelfAssessmentMode(): bool return $this->selfassessmentmode; } - public function setInsertTabs(bool $a_active): void + public function setInsertTabs(string $a_active): void { $ilTabs = $this->tabs; $ilCtrl = $this->ctrl; diff --git a/Services/COPage/classes/class.ilPCResourcesGUI.php b/Services/COPage/classes/class.ilPCResourcesGUI.php index f3cdb3919b3b..229c6c55f40f 100755 --- a/Services/COPage/classes/class.ilPCResourcesGUI.php +++ b/Services/COPage/classes/class.ilPCResourcesGUI.php @@ -268,7 +268,17 @@ public static function insertResourcesIntoPageContent( if (isset($childs_by_type[$type]) && count($childs_by_type[$type]) > 0) { foreach ($childs_by_type[$type] as $child) { $tpl->setCurrentBlock("row"); - $tpl->setVariable("IMG", ilUtil::img(ilObject::_getIcon((int) $child["obj_id"], "small"))); + $tpl->setVariable("IMG", + ilUtil::img( + ilObject::_getIcon((int) $child["obj_id"], "small"), + null, + "", + "", + 0, + "", + "ilListItemIcon" + ) + ); $tpl->setVariable("TITLE", $child["title"]); $tpl->parseCurrentBlock(); $cnt++; diff --git a/Services/COPage/classes/class.ilPCSectionGUI.php b/Services/COPage/classes/class.ilPCSectionGUI.php index 2f7e9dcfc4e7..da295eeffdf2 100755 --- a/Services/COPage/classes/class.ilPCSectionGUI.php +++ b/Services/COPage/classes/class.ilPCSectionGUI.php @@ -44,7 +44,7 @@ public function getHTML(array $params): string $this->getCharacteristicsOfCurrentStyle(["section"]); if ($params["form"] == true) { - $insert = !($this->content_obj); + $insert = $params["insert"] ?? false; $form = $this->initForm($insert); $form->setShowTopButtons(false); @@ -75,6 +75,11 @@ public function getHTML(array $params): string $onload_code = array_merge($onload_code, $rep_sel->getOnloadCode()); } + if (($params["update_fail"] ?? false) === true) { + $this->checkInput($form); + $form->setValuesByPost(); + } + $html = $params["ui_wrapper"]->getRenderedForm( $form, $params["buttons"] @@ -89,6 +94,22 @@ public function getHTML(array $params): string return ""; } + public function checkInput(ilPropertyFormGUI $form) : bool + { + $ret = $form->checkInput(); + if ($ret) { + $from = $form->getItemByPostVar("active_from")->getDate(); + $to = $form->getItemByPostVar("active_to")->getDate(); + if ($from && $to && $from->get(IL_CAL_UNIX) > $to->get(IL_CAL_UNIX)) { + $form->getItemByPostVar("active_to")->setAlert( + $this->lng->txt("copg_active_to_small") + ); + $ret = false; + } + } + return $ret; + } + public static function _getStandardCharacteristics(): array { global $DIC; diff --git a/Services/COPage/classes/class.ilPCTab.php b/Services/COPage/classes/class.ilPCTab.php index 4468a30f2296..22f915e4a35e 100755 --- a/Services/COPage/classes/class.ilPCTab.php +++ b/Services/COPage/classes/class.ilPCTab.php @@ -88,20 +88,22 @@ public function modifyPageContentPostXsl( $storage = new ilAccordionPropertiesStorageGUI(); $opened = $storage->getPropertyForIdStartsWith("ilc_accordion_" . $this->getPage()->getId() . "_", $this->user_id, "opened"); - - $script = ""; } - $script.= ""; diff --git a/Services/COPage/classes/class.ilPageComponentPlugin.php b/Services/COPage/classes/class.ilPageComponentPlugin.php index 67a203db808b..5a73f1330813 100644 --- a/Services/COPage/classes/class.ilPageComponentPlugin.php +++ b/Services/COPage/classes/class.ilPageComponentPlugin.php @@ -31,9 +31,9 @@ abstract class ilPageComponentPlugin extends ilPlugin protected string $mode; /** - * Determines the resources that allow to include the - * new content component. - * @param string $a_type Parent type (e.g. "cat", "lm", "glo", "wiki", ...) + * Determines the parent types that allow to include the new content component. + * See https://docu.ilias.de/goto_docu_pg_56942_42.html + * @param string $a_type * @return bool true/false if the resource type allows */ abstract public function isValidParentType(string $a_type): bool; diff --git a/Services/COPage/classes/class.ilPageLinker.php b/Services/COPage/classes/class.ilPageLinker.php index 5c45dbd556f5..bc5084efdce7 100644 --- a/Services/COPage/classes/class.ilPageLinker.php +++ b/Services/COPage/classes/class.ilPageLinker.php @@ -121,8 +121,8 @@ public function getLinkXML(array $int_links): string break; case "GlossaryItem": - if ($targetframe == "None") { - $targetframe = "Glossary"; + if ($targetframe == "Glossary") { + $ltarget = ""; } $href = "./goto.php?target=git_" . $target_id; break; @@ -146,7 +146,7 @@ public function getLinkXML(array $int_links): string case "WikiPage": $wiki_anc = ""; if (($int_link["Anchor"] ?? "") != "") { - $wiki_anc = "#" . rawurlencode($int_link["Anchor"]); + $wiki_anc = "#" . rawurlencode("copganc_" . $int_link["Anchor"]); } $href = ilWikiPage::getGotoForWikiPageTarget($target_id) . $wiki_anc; break; diff --git a/Services/COPage/classes/class.ilPageObject.php b/Services/COPage/classes/class.ilPageObject.php index 2a8d23e84312..ecd926af4e5e 100755 --- a/Services/COPage/classes/class.ilPageObject.php +++ b/Services/COPage/classes/class.ilPageObject.php @@ -903,7 +903,8 @@ public function getXMLContent(bool $a_incl_head = false): string public function copyXmlContent( bool $a_clone_mobs = false, int $a_new_parent_id = 0, - int $obj_copy_id = 0 + int $obj_copy_id = 0, + bool $self_ass = true ): string { $xml = $this->getXMLContent(); $temp_dom = domxml_open_mem( @@ -912,7 +913,7 @@ public function copyXmlContent( $error ); if (empty($error)) { - $this->handleCopiedContent($temp_dom, true, $a_clone_mobs, $a_new_parent_id, $obj_copy_id); + $this->handleCopiedContent($temp_dom, $self_ass, $a_clone_mobs, $a_new_parent_id, $obj_copy_id); } $xml = $temp_dom->dump_mem(0, $this->encoding); $xml = preg_replace('/<\?xml[^>]*>/i', "", $xml); @@ -2027,7 +2028,6 @@ public function moveIntLinks(array $a_from_to): bool $this->buildDom(); $changed = false; - // resolve normal internal links $xpc = xpath_new_context($this->dom); $path = "//IntLink"; @@ -2049,6 +2049,10 @@ public function moveIntLinks(array $a_from_to): bool $res->nodeset[$i]->set_attribute("Target", "il__ppage_" . $a_from_to[$obj_id]); $changed = true; } + if ($type == "WikiPage") { + $res->nodeset[$i]->set_attribute("Target", "il__wpage_" . $a_from_to[$obj_id]); + $changed = true; + } } } unset($xpc); @@ -2602,7 +2606,6 @@ public function update(bool $a_validate = true, bool $a_no_history = false) $old_rec["lang"] ) ); - // the following lines are a workaround for // bug 6741 $last_c = $old_rec["last_change"]; @@ -2621,14 +2624,13 @@ public function update(bool $a_validate = true, bool $a_no_history = false) "ilias_version" => array("text", ILIAS_VERSION_NUMERIC), "nr" => array("integer", (int) $last_nr["mnr"] + 1) )); - $old_content = $old_rec["content"]; $old_domdoc = new DOMDocument(); $old_nr = $last_nr["mnr"] + 1; $old_domdoc->loadXML('' . $old_content); // after history entry creation event - $this->log->debug("calling __afterHistoryEntry"); + $this->log->debug("calling __afterHistoryEntry $old_nr"); $this->__afterHistoryEntry($old_domdoc, $old_content, $old_nr); // only save one time @@ -4273,7 +4275,7 @@ public function compareVersion( ); } - protected function preparePageForCompare(ilPageObject $page) : void + protected function preparePageForCompare(ilPageObject $page): void { } diff --git a/Services/COPage/classes/class.ilPageObjectGUI.php b/Services/COPage/classes/class.ilPageObjectGUI.php index 7f8b6d2b5e98..5bacdd6865d1 100755 --- a/Services/COPage/classes/class.ilPageObjectGUI.php +++ b/Services/COPage/classes/class.ilPageObjectGUI.php @@ -1778,18 +1778,37 @@ public static function getTinyMenu( $ctrl = $DIC->ctrl(); $ui = $DIC->ui(); + $style_service = $DIC->contentStyle()->internal(); + $style_access_manager = $style_service->domain()->access( + 0, + $DIC->user()->getId() + ); + $char_manager = $style_service->domain()->characteristic( + $a_style_id, + $style_access_manager + ); + $aset = new ilSetting("adve"); + $f = static function (string $type, string $code) use ($char_manager, $lng): string { + $title = $char_manager->getPresentationTitle("text_inline", $type); + if ($title === $type) { + $title = $lng->txt("cont_char_style_" . $code); + } + return $title; + }; + // character styles $chars = array( - "Comment" => array("code" => "com", "txt" => $lng->txt("cont_char_style_com")), - "Quotation" => array("code" => "quot", "txt" => $lng->txt("cont_char_style_quot")), - "Accent" => array("code" => "acc", "txt" => $lng->txt("cont_char_style_acc")), - "Code" => array("code" => "code", "txt" => $lng->txt("cont_char_style_code")) + "Comment" => array("code" => "com", "txt" => $f("Comment", "com")), + "Quotation" => array("code" => "quot", "txt" => $f("Quotation", "quot")), + "Accent" => array("code" => "acc", "txt" => $f("Accent", "acc")), + "Code" => array("code" => "code", "txt" => $f("Code", "code")) ); foreach (ilPCParagraphGUI::_getTextCharacteristics($a_style_id) as $c) { if (!isset($chars[$c])) { - $chars[$c] = array("code" => "", "txt" => $c); + $title = $char_manager->getPresentationTitle("text_inline", $c); + $chars[$c] = array("code" => "", "txt" => $title); } } $char_formats = []; @@ -1838,50 +1857,77 @@ public static function getTinyMenu( case "str": $c_formats[] = ["text" => '' . $str . '', "action" => "selection.format", - "data" => ["format" => "Strong"] + "data" => ["format" => "Strong"], + "aria-label" => $lng->txt("cont_text_str") ]; break; case "emp": $c_formats[] = ["text" => '' . $emp . '', "action" => "selection.format", - "data" => ["format" => "Emph"] + "data" => ["format" => "Emph"], + "aria-label" => $lng->txt("cont_text_emp") ]; break; case "imp": $c_formats[] = ["text" => '' . $imp . '', "action" => "selection.format", - "data" => ["format" => "Important"] + "data" => ["format" => "Important"], + "aria-label" => $lng->txt("cont_text_imp") ]; break; case "sup": $c_formats[] = ["text" => 'x2', "action" => "selection.format", - "data" => ["format" => "Sup"] + "data" => ["format" => "Sup"], + "aria-label" => $lng->txt("cont_text_sup") ]; break; case "sub": $c_formats[] = ["text" => 'x2', "action" => "selection.format", - "data" => ["format" => "Sub"] + "data" => ["format" => "Sub"], + "aria-label" => $lng->txt("cont_text_sub") ]; break; } } } $c_formats[] = ["text" => "A", - "action" => $char_formats + "action" => $char_formats, + "aria-label" => $lng->txt("copg_more_character_formats") ]; $c_formats[] = ["text" => 'Tx', "action" => "selection.removeFormat", - "data" => [] + "data" => [], + "aria-label" => $lng->txt("copg_remove_formats") ]; $menu = [ "cont_char_format" => $c_formats, "cont_lists" => [ - ["text" => $bullet_list, "action" => "list.bullet", "data" => []], - ["text" => $numbered_list, "action" => "list.number", "data" => []], - ["text" => $outdent, "action" => "list.outdent", "data" => []], - ["text" => $indent, "action" => "list.indent", "data" => []] + [ + "text" => $bullet_list, + "action" => "list.bullet", + "data" => [], + "aria-label" => $lng->txt("cont_bullet_list") + ], + [ + "text" => $numbered_list, + "action" => "list.number", + "data" => [], + "aria-label" => $lng->txt("cont_numbered_list") + ], + [ + "text" => $outdent, + "action" => "list.outdent", + "data" => [], + "aria-label" => $lng->txt("cont_list_outdent") + ], + [ + "text" => $indent, + "action" => "list.indent", + "data" => [], + "aria-label" => $lng->txt("cont_list_indent") + ] ] ]; @@ -1944,13 +1990,28 @@ public static function getTinyMenu( if (is_array($item["action"])) { $buttons = []; foreach ($item["action"] as $i) { - $buttons[] = $ui_wrapper->getButton($i["text"], "par-action", $i["action"], $i["data"]); + $buttons[] = $ui_wrapper->getButton( + $i["text"], + "par-action", + $i["action"], + $i["data"], + "", + $i["aria-label"] ?? "" + ); } - $dd = $ui->factory()->dropdown()->standard($buttons)->withLabel($item["text"]); + $dd = $ui->factory()->dropdown()->standard($buttons)->withLabel($item["text"]) + ->withAriaLabel($item["aria-label"] ?? ""); $btpl->setCurrentBlock("button"); $btpl->setVariable("BUTTON", $ui->renderer()->renderAsync($dd)); } else { - $b = $ui_wrapper->getRenderedButton($item["text"], "par-action", $item["action"], $item["data"]); + $b = $ui_wrapper->getRenderedButton( + $item["text"], + "par-action", + $item["action"], + $item["data"], + "", + $item["aria-label"] ?? "" + ); $btpl->setCurrentBlock("button"); $btpl->setVariable("BUTTON", $b); } @@ -2388,6 +2449,8 @@ protected function initEditing(): void $this->lng->toJS("cont_ed_item_up"); $this->lng->toJS("cont_ed_item_down"); $this->lng->toJS("cont_ed_delete_item"); + $this->lng->toJS("copg_edit_iframe_title"); + $this->lng->toJS("copg_par_format_selection"); // workaroun: we need this js for the new editor version, e.g. for new section form to work // @todo: solve this in a smarter way $this->tpl->addJavaScript("./Services/UIComponent/AdvancedSelectionList/js/AdvancedSelectionList.js"); diff --git a/Services/COPage/templates/default/tpl.tiny_menu.html b/Services/COPage/templates/default/tpl.tiny_menu.html index b05da6dfc2d0..f0288db01454 100644 --- a/Services/COPage/templates/default/tpl.tiny_menu.html +++ b/Services/COPage/templates/default/tpl.tiny_menu.html @@ -16,20 +16,20 @@

{TINY_HEADER}

 

-

{TXT_PAR_FORMAT}

+

{TXT_PAR_FORMAT}

{STYLE_SELECTOR}
-

{TXT_SECTION}

+

{TXT_SECTION}

{BUTTON}
-

{TXT_BLOCK}

+

{TXT_BLOCK}

{BLOCK_STYLE_SELECTOR}
diff --git a/Services/COPage/xsl/page.xsl b/Services/COPage/xsl/page.xsl index 5f140e727eb0..504f380df951 100755 --- a/Services/COPage/xsl/page.xsl +++ b/Services/COPage/xsl/page.xsl @@ -130,7 +130,7 @@
Break
- +
@@ -280,10 +280,10 @@ - - + +copganc_ - + @@ -1174,8 +1174,7 @@ -#fn[] - + #fn[][fn][/fn] @@ -1909,9 +1908,9 @@
- + - + @@ -2942,7 +2941,7 @@ - Unsupported Media Type + {{{{{Unsupported Media Type}}}}} diff --git a/Services/Calendar/classes/ConsultationHours/class.ilConsultationHourUtils.php b/Services/Calendar/classes/ConsultationHours/class.ilConsultationHourUtils.php index 70677117a985..624fc5e40f72 100644 --- a/Services/Calendar/classes/ConsultationHours/class.ilConsultationHourUtils.php +++ b/Services/Calendar/classes/ConsultationHours/class.ilConsultationHourUtils.php @@ -1,8 +1,22 @@ @@ -17,7 +31,6 @@ public static function getConsultationHourLinksForRepositoryObject( global $DIC; $ctrl = $DIC->ctrl(); - $lng = $DIC->language(); $logger = $DIC->logger()->cal(); $ctrl->setParameterByClass(end($ctrl_class_structure), 'seed', ''); $ctrl->setParameterByClass(end($ctrl_class_structure), 'category_id', ''); @@ -63,11 +76,7 @@ public static function getConsultationHourLinksForRepositoryObject( } $current_link = [ 'link' => $ctrl->getLinkTargetByClass($ctrl_class_structure, 'selectCHCalendarOfUser'), - 'txt' => str_replace( - "%1", - ilObjUser::_lookupFullname($user_id), - $lng->txt("cal_consultation_hours_for_user") - ) + 'txt' => ilObjUser::_lookupFullname($user_id) ]; $links[] = $current_link; } diff --git a/Services/Calendar/classes/Export/class.ilCalendarExport.php b/Services/Calendar/classes/Export/class.ilCalendarExport.php index ebbd00717a31..3b7fb69c94ce 100644 --- a/Services/Calendar/classes/Export/class.ilCalendarExport.php +++ b/Services/Calendar/classes/Export/class.ilCalendarExport.php @@ -67,6 +67,9 @@ public function getUserSettings(): ilCalendarUserSettings return $this->user_settings; } + /** + * @param int[] $a_apps + */ public function setAppointments(array $a_apps): void { $this->appointments = $a_apps; @@ -183,6 +186,9 @@ protected function addCategories(int $remaining_bytes): ilICalWriter $time_now = new ilDateTime(time(), IL_CAL_UNIX); $str_time_now = $time_now->get(IL_CAL_FKT_DATE, 'Ymd', ilTimeZone::UTC); $str_time_start = $a->getStart()->get(IL_CAL_FKT_DATE, 'Ymd', $this->il_user->getTimeZone()); + if ($str_time_start === null) { + return false; + } $start = new DateTimeImmutable($str_time_start); $now = new DateTimeImmutable($str_time_now); $lower_bound = $now->sub(new DateInterval('P30D')); diff --git a/Services/Calendar/classes/class.ilCalendarAppointmentGUI.php b/Services/Calendar/classes/class.ilCalendarAppointmentGUI.php index 9fcec2cdb2fc..39d16a4f91dc 100644 --- a/Services/Calendar/classes/class.ilCalendarAppointmentGUI.php +++ b/Services/Calendar/classes/class.ilCalendarAppointmentGUI.php @@ -519,7 +519,11 @@ protected function save(bool $a_as_milestone = false): void } } else { $this->form->setValuesByPost(); - $this->tpl->setOnScreenMessage('failure', $this->error->getMessage()); + if ($this->error->getMessage() !== '') { + $this->tpl->setOnScreenMessage('failure', $this->error->getMessage()); + } else { + $this->tpl->setOnScreenMessage('failure', $this->lng->txt('err_check_input')); + } $this->add($this->form); return; } @@ -650,7 +654,7 @@ protected function askEdit(): void $this->ctrl->saveParameter($this, array('seed', 'app_id', 'dt', 'idate')); $confirm = new ilConfirmationGUI(); - $confirm->setHeaderText($this->lng->txt('cal_delete_cal')); + $confirm->setHeaderText($this->lng->txt('cal_edit_single_or_all_info')); $confirm->setFormAction($this->ctrl->getFormAction($this)); $confirm->setCancel($this->lng->txt('cancel'), 'cancel'); $confirm->addItem('appointments[]', (string) $this->app->getEntryId(), $this->app->getTitle()); diff --git a/Services/Calendar/classes/class.ilCalendarBlockGUI.php b/Services/Calendar/classes/class.ilCalendarBlockGUI.php index 9ad581ffb48f..45779ec3409d 100644 --- a/Services/Calendar/classes/class.ilCalendarBlockGUI.php +++ b/Services/Calendar/classes/class.ilCalendarBlockGUI.php @@ -1,7 +1,5 @@ addConsultationHourButtons($panel_tpl); $this->addSubscriptionButton($panel_tpl); return $tpl->get() . $panel_tpl->get(); @@ -908,43 +907,6 @@ public function getNoItemFoundContent(): string return $this->lng->txt("cal_no_events_block"); } - /** - * Add consultation hour buttons - */ - protected function addConsultationHourButtons(ilTemplate $panel_template): void - { - global $DIC; - - $user = $DIC->user(); - - if (!$this->getRepositoryMode()) { - return; - } - - $links = \ilConsultationHourUtils::getConsultationHourLinksForRepositoryObject( - (int) $this->requested_ref_id, - $user->getId(), - $this->getTargetGUIClassPath() - ); - $counter = 0; - foreach ($links as $link) { - $ui_factory = $DIC->ui()->factory(); - $ui_renderer = $DIC->ui()->renderer(); - - $link_button = $ui_factory->button()->shy( - $link['txt'], - $link['link'] - ); - if ($counter) { - $panel_template->touchBlock('consultation_hour_buttons_multi'); - } - $panel_template->setCurrentBlock('consultation_hour_buttons'); - $panel_template->setVariable('SHY_BUTTON', $ui_renderer->render([$link_button])); - $panel_template->parseCurrentBlock(); - $counter++; - } - } - /** * Add subscription button */ diff --git a/Services/Calendar/classes/class.ilCalendarCategoryGUI.php b/Services/Calendar/classes/class.ilCalendarCategoryGUI.php index 2d8acd649a9c..d0a887a0bb84 100644 --- a/Services/Calendar/classes/class.ilCalendarCategoryGUI.php +++ b/Services/Calendar/classes/class.ilCalendarCategoryGUI.php @@ -498,7 +498,6 @@ public function sharePerformSearch(): void return; } - $this->getSearchToolbar(); $res_sum = new ilSearchResult(); $query_parser = new ilQueryParser(ilUtil::stripSlashes($query)); @@ -553,6 +552,8 @@ public function sharePerformSearch(): void $this->showRoleList($res_sum->getResultIds()); break; } + + $this->getSearchToolbar(); } /** diff --git a/Services/Calendar/classes/class.ilCalendarEntry.php b/Services/Calendar/classes/class.ilCalendarEntry.php index c99a37f01b59..e104b0c63fcc 100644 --- a/Services/Calendar/classes/class.ilCalendarEntry.php +++ b/Services/Calendar/classes/class.ilCalendarEntry.php @@ -118,7 +118,7 @@ public function getStart(): ?ilDateTime return $this->start; } - public function setStart(ilDateTime $a_start): void + public function setStart(?ilDateTime $a_start): void { $this->start = $a_start; } @@ -128,7 +128,7 @@ public function getEnd(): ?ilDateTime return $this->end; } - public function setEnd(ilDateTime $a_end): void + public function setEnd(?ilDateTime $a_end): void { $this->end = $a_end; } diff --git a/Services/Calendar/classes/class.ilCalendarMailNotification.php b/Services/Calendar/classes/class.ilCalendarMailNotification.php index 583e9ec45cf7..add740271f6f 100644 --- a/Services/Calendar/classes/class.ilCalendarMailNotification.php +++ b/Services/Calendar/classes/class.ilCalendarMailNotification.php @@ -1,27 +1,22 @@ setExportType(ilCalendarExport::EXPORT_APPOINTMENTS); - $export->setAppointments(array(new ilCalendarEntry($this->getAppointmentId()))); + $export->setAppointments([(int) $this->getAppointmentId()]); $export->export(); $attachment = new ilFileDataMail($this->getSender()); diff --git a/Services/Calendar/classes/class.ilCalendarRemoteAccessHandler.php b/Services/Calendar/classes/class.ilCalendarRemoteAccessHandler.php index df33f44a9b16..2b913705e645 100644 --- a/Services/Calendar/classes/class.ilCalendarRemoteAccessHandler.php +++ b/Services/Calendar/classes/class.ilCalendarRemoteAccessHandler.php @@ -122,12 +122,12 @@ protected function initTokenHandler(): void ); } - protected function initLimitEnabled() + protected function initLimitEnabled(): void { - $this->limit_enabled = (bool) $_GET[self::LIMITED_QUERY_PARAM]; + $this->limit_enabled = (bool) ($_GET[self::LIMITED_QUERY_PARAM] ?? false); } - protected function initIlias() + protected function initIlias(): void { include_once './Services/Context/classes/class.ilContext.php'; ilContext::init(ilContext::CONTEXT_ICAL); diff --git a/Services/Calendar/classes/class.ilCalendarSchedule.php b/Services/Calendar/classes/class.ilCalendarSchedule.php index 7346d8bf7a7c..930c578e8ae0 100644 --- a/Services/Calendar/classes/class.ilCalendarSchedule.php +++ b/Services/Calendar/classes/class.ilCalendarSchedule.php @@ -1,27 +1,22 @@ addFilter(new ilCalendarScheduleFilterBookingPool($this->user->getId())); } + + if (ilCalendarCategories::_getInstance()->getMode() === ilCalendarCategories::MODE_REPOSITORY) { + $this->addFilter(new ilCalendarScheduleFilterConsultationHourInRepository()); + } + $this->addFilter(new ilCalendarScheduleFilterExercise($this->user->getId())); $this->addFilter(new ilCalendarScheduleFilterTimings($this->user->getId())); } diff --git a/Services/Calendar/classes/class.ilCalendarScheduleFilterConsultationHourInRepository.php b/Services/Calendar/classes/class.ilCalendarScheduleFilterConsultationHourInRepository.php new file mode 100644 index 000000000000..83776165c42e --- /dev/null +++ b/Services/Calendar/classes/class.ilCalendarScheduleFilterConsultationHourInRepository.php @@ -0,0 +1,69 @@ +cats = ilCalendarCategories::_getInstance(); + } + + public function filterCategories(array $a_cats): array + { + return $a_cats; + } + + public function modifyEvent(ilCalendarEntry $a_event): ?ilCalendarEntry + { + /* + * Do not filter if not in repository object context, or if + * the entry is not from a consultation hour category. + */ + if ( + $this->cats->getMode() !== ilCalendarCategories::MODE_REPOSITORY || + !$this->cats->getSourceRefId() + ) { + return $a_event; + } + foreach (ilCalendarCategoryAssignments::_lookupCategories($a_event->getEntryId()) as $category_id) { + if (((int) $this->cats->getCategoryInfo($category_id)['type']) !== ilCalendarCategory::TYPE_CH) { + return $a_event; + } + } + + $booking = new ilBookingEntry($a_event->getContextId()); + + /* + * Only show consultation hour entries assigned to the current object, + * or those without assignment. + */ + if ($booking->isTargetObjectVisible($this->cats->getSourceRefId())) { + return $a_event; + } + return null; + } + + public function addCustomEvents(ilDate $start, ilDate $end, array $a_categories): array + { + return []; + } +} diff --git a/Services/Calendar/classes/class.ilCalendarUtil.php b/Services/Calendar/classes/class.ilCalendarUtil.php index 0cfae18aa18d..09ea561e638f 100755 --- a/Services/Calendar/classes/class.ilCalendarUtil.php +++ b/Services/Calendar/classes/class.ilCalendarUtil.php @@ -97,7 +97,7 @@ public static function _buildWeekDayList(ilDate $a_day, int $a_weekstart): ilDat $start = clone $a_day; $start_info = $start->get(IL_CAL_FKT_GETDATE, '', 'UTC'); $day_diff = $a_weekstart - $start_info['isoday']; - if ($day_diff == 7) { + if (abs($day_diff) === 7) { $day_diff = 0; } $start->increment(IL_CAL_DAY, $day_diff); diff --git a/Services/Calendar/classes/class.ilConsultationHoursCalendarBlockGUI.php b/Services/Calendar/classes/class.ilConsultationHoursCalendarBlockGUI.php new file mode 100644 index 000000000000..24dd90b68ba6 --- /dev/null +++ b/Services/Calendar/classes/class.ilConsultationHoursCalendarBlockGUI.php @@ -0,0 +1,104 @@ +lng->loadLanguageModule("dateplaner"); + + $this->setBlockId('ch_' . $this->ctrl->getContextObjId()); + $this->setLimit(5); + $this->setEnableNumInfo(false); + $this->setTitle($this->lng->txt('consultation_hours_block_title')); + $this->setPresentation(self::PRES_SEC_LIST); + } + + public function getBlockType(): string + { + return 'chcal'; + } + + protected function isRepositoryObject(): bool + { + return false; + } + + /** + * Get target gui class path (for presenting the calendar) + */ + public function getTargetGUIClassPath(): array + { + $target_class = []; + if (!$this->getRepositoryMode()) { + $target_class = ["ildashboardgui", "ilcalendarpresentationgui"]; + } else { + switch (ilObject::_lookupType((int) $this->requested_ref_id, true)) { + case "crs": + $target_class = ["ilobjcoursegui", "ilcalendarpresentationgui"]; + break; + + case "grp": + $target_class = ["ilobjgroupgui", "ilcalendarpresentationgui"]; + break; + } + } + return $target_class; + } + + public function getData(): array + { + if (isset($this->consultation_hour_links)) { + return $this->consultation_hour_links; + } + return $this->consultation_hour_links = \ilConsultationHourUtils::getConsultationHourLinksForRepositoryObject( + (int) $this->requested_ref_id, + $this->user->getId(), + $this->getTargetGUIClassPath() + ); + } + + protected function getListItemForData(array $data): Item + { + $button = $this->ui->factory()->button()->shy( + $data['txt'] ?? '', + $data['link'] ?? '' + ); + return $this->ui->factory()->item()->standard($button); + } + + public function getHTMLNew(): string + { + if (empty($this->getData())) { + return ''; + } + return parent::getHTMLNew(); + } +} diff --git a/Services/Calendar/classes/class.ilDatePresentation.php b/Services/Calendar/classes/class.ilDatePresentation.php index 6b2689871c6d..5a596fa32619 100644 --- a/Services/Calendar/classes/class.ilDatePresentation.php +++ b/Services/Calendar/classes/class.ilDatePresentation.php @@ -90,6 +90,7 @@ public static function formatDate(ilDateTime $date, bool $a_skip_day = false, bo global $DIC; $lng = $DIC['lng']; + $lng->loadLanguageModule('dateplaner'); $ilUser = $DIC['ilUser']; if ($date->isNull()) { @@ -99,13 +100,14 @@ public static function formatDate(ilDateTime $date, bool $a_skip_day = false, bo $has_time = !is_a($date, 'ilDate'); // Converting pure dates to user timezone might return wrong dates + $date_info = []; if ($has_time) { $date_info = $date->get(IL_CAL_FKT_GETDATE, '', $ilUser->getTimeZone()); } else { $date_info = $date->get(IL_CAL_FKT_GETDATE, '', 'UTC'); } - $date_str = null; + $date_str = ''; if (!$a_skip_day) { $sep = ", "; if (self::isToday($date) and self::useRelativeDates()) { @@ -117,9 +119,9 @@ public static function formatDate(ilDateTime $date, bool $a_skip_day = false, bo } else { $date_str = ""; if ($a_include_wd) { - $date_str = $lng->txt(self::$weekdays[$date->get(IL_CAL_FKT_DATE, 'w')]) . ", "; + $date_str = $lng->txt(self::$weekdays[$date_info['wday']]) . ", "; } - $date_str .= $date->get(IL_CAL_FKT_DATE, 'd') . '. ' . + $date_str .= $date_info['mday'] . '. ' . ilCalendarUtil::_numericMonthToString($date_info['mon'], false) . ' ' . $date_info['year']; } diff --git a/Services/Certificate/classes/File/Certificate/class.ilPdfGenerator.php b/Services/Certificate/classes/File/Certificate/class.ilPdfGenerator.php index 7c79d89640ab..14c72e2a6ace 100644 --- a/Services/Certificate/classes/File/Certificate/class.ilPdfGenerator.php +++ b/Services/Certificate/classes/File/Certificate/class.ilPdfGenerator.php @@ -106,7 +106,7 @@ private function createPDFScalar(ilUserCertificate $certificate): string $certificateContent = str_replace( ['[BACKGROUND_IMAGE]', '[CLIENT_WEB_DIR]'], - ['[CLIENT_WEB_DIR]' . $certificate->getBackgroundImagePath(), CLIENT_WEB_DIR], + ['[CLIENT_WEB_DIR]' . $certificate->getBackgroundImagePath(), 'file://' . CLIENT_WEB_DIR], $certificateContent ); diff --git a/Services/Certificate/classes/Form/Repository/class.ilCertificateSettingsFormRepository.php b/Services/Certificate/classes/Form/Repository/class.ilCertificateSettingsFormRepository.php index 996300f212bb..67b25309a4b0 100644 --- a/Services/Certificate/classes/Form/Repository/class.ilCertificateSettingsFormRepository.php +++ b/Services/Certificate/classes/Form/Repository/class.ilCertificateSettingsFormRepository.php @@ -1,7 +1,5 @@ toolbar = $toolbar; $this->placeholderDescriptionObject = $placeholderDescriptionObject; $this->hasAdditionalElements = $hasAdditionalElements; + $this->global_certificate_settings = new ilObjCertificateSettings(); $database = $DIC->database(); @@ -206,22 +208,31 @@ public function createForm(ilCertificateGUI $certificateGUI): ilPropertyFormGUI $bgimage->setAllowDeletion(true); if (!$this->backGroundImageFileService->hasBackgroundImage($certificateTemplate)) { - if (ilObjCertificateSettingsAccess::hasBackgroundImage()) { + if ($this->global_certificate_settings->hasBackgroundImage()) { ilWACSignedPath::setTokenMaxLifetimeInSeconds(15); - $imagePath = ilWACSignedPath::signFile(ilObjCertificateSettingsAccess::getBackgroundImageThumbPathWeb()); + $imagePath = ilWACSignedPath::signFile($this->global_certificate_settings->getBackgroundImageThumbPathWeb()); $bgimage->setImage($imagePath); $bgimage->setAllowDeletion(false); } } else { ilWACSignedPath::setTokenMaxLifetimeInSeconds(15); - $thumbnailPath = $this->backGroundImageFileService->getBackgroundImageThumbPath(); - - if (!is_file($thumbnailPath)) { - $thumbnailPath = ilObjCertificateSettingsAccess::getBackgroundImageThumbPath(); - $bgimage->setAllowDeletion(false); + $thumbnail_path = $this->backGroundImageFileService->getBackgroundImageThumbPath(); + + if (!is_file($thumbnail_path)) { + //Trying if it uses default image path + $thumbnail_path = CLIENT_WEB_DIR . $certificateTemplate->getBackgroundImagePath() . '.thumb.jpg'; + if (!is_file($thumbnail_path)) { + //Trying to use global default image + $thumbnail_path = $this->global_certificate_settings->getDefaultBackgroundImageThumbPath(); + if (!is_file($thumbnail_path)) { + //No image global default configured + $thumbnail_path = ''; + } + } + $bgimage->setALlowDeletion(false); } - $imagePath = ilWACSignedPath::signFile($thumbnailPath); + $imagePath = ilWACSignedPath::signFile($thumbnail_path); $bgimage->setImage($imagePath); } diff --git a/Services/Certificate/classes/Placeholder/Values/class.ilScormPlaceholderValues.php b/Services/Certificate/classes/Placeholder/Values/class.ilScormPlaceholderValues.php index 994a4d414cd1..03233e468291 100644 --- a/Services/Certificate/classes/Placeholder/Values/class.ilScormPlaceholderValues.php +++ b/Services/Certificate/classes/Placeholder/Values/class.ilScormPlaceholderValues.php @@ -98,14 +98,15 @@ public function getPlaceholderValues(int $userId, int $objId): array $object = $this->objectHelper->getInstanceByObjId($objId); $points = $object->getPointsInPercent(); - $txtPoints = number_format( - $points, - 1, - $this->language->txt('lang_sep_decimal'), - $this->language->txt('lang_sep_thousand') - ) . ' %'; if (is_null($points)) { $txtPoints = $this->language->txt('certificate_points_notavailable'); + } else { + $txtPoints = number_format( + $points, + 1, + $this->language->txt('lang_sep_decimal'), + $this->language->txt('lang_sep_thousand') + ) . ' %'; } $max_points = $object->getMaxPoints(); diff --git a/Services/Certificate/classes/Placeholder/Values/class.ilStudyProgrammePlaceholderValues.php b/Services/Certificate/classes/Placeholder/Values/class.ilStudyProgrammePlaceholderValues.php index a6587312e5dd..810919046351 100644 --- a/Services/Certificate/classes/Placeholder/Values/class.ilStudyProgrammePlaceholderValues.php +++ b/Services/Certificate/classes/Placeholder/Values/class.ilStudyProgrammePlaceholderValues.php @@ -154,8 +154,8 @@ protected function getLatestSuccessfulAssignment(array $assignments): ?ilPRGAssi if (count($unlimited) > 0) { $successful = $unlimited; usort($successful, static function (ilPRGAssignment $a, ilPRGAssignment $b): int { - $a_dat =$a->getProgressTree()->getCompletionDate(); - $b_dat =$b->getProgressTree()->getCompletionDate(); + $a_dat = $a->getProgressTree()->getCompletionDate(); + $b_dat = $b->getProgressTree()->getCompletionDate(); if ($a_dat > $b_dat) { return -1; } elseif ($a_dat < $b_dat) { @@ -172,8 +172,8 @@ protected function getLatestSuccessfulAssignment(array $assignments): ?ilPRGAssi ); $successful = $limited; usort($successful, static function (ilPRGAssignment $a, ilPRGAssignment $b): int { - $a_dat =$a->getProgressTree()->getValidityOfQualification(); - $b_dat =$b->getProgressTree()->getValidityOfQualification(); + $a_dat = $a->getProgressTree()->getValidityOfQualification(); + $b_dat = $b->getProgressTree()->getValidityOfQualification(); if ($a_dat > $b_dat) { return -1; } elseif ($a_dat < $b_dat) { diff --git a/Services/Certificate/classes/Setup/class.ilCertificateDatabaseUpdateSteps.php b/Services/Certificate/classes/Setup/class.ilCertificateDatabaseUpdateSteps.php index b79b86d6cb6c..4aad39124252 100644 --- a/Services/Certificate/classes/Setup/class.ilCertificateDatabaseUpdateSteps.php +++ b/Services/Certificate/classes/Setup/class.ilCertificateDatabaseUpdateSteps.php @@ -1,7 +1,5 @@ db->renameTableColumn('il_cert_user_cert', 'user_id', 'usr_id'); } } + + public function step_5(): void + { + if ( + $this->db->tableExists('il_cert_template') + && !$this->db->indexExistsByFields('il_cert_template', ['background_image_path', 'currently_active']) + ) { + $this->db->addIndex('il_cert_template', ['background_image_path', 'currently_active'], 'i5'); + } + + if ( + $this->db->tableExists('il_cert_user_cert') + && !$this->db->indexExistsByFields('il_cert_user_cert', ['background_image_path', 'currently_active']) + ) { + $this->db->addIndex('il_cert_user_cert', ['background_image_path', 'currently_active'], 'i7'); + } + } } diff --git a/Services/Certificate/classes/Template/Action/Clone/class.ilCertificateCloneAction.php b/Services/Certificate/classes/Template/Action/Clone/class.ilCertificateCloneAction.php index 332ef09e556e..e7f5728d5208 100644 --- a/Services/Certificate/classes/Template/Action/Clone/class.ilCertificateCloneAction.php +++ b/Services/Certificate/classes/Template/Action/Clone/class.ilCertificateCloneAction.php @@ -1,7 +1,5 @@ objectHelper = $objectHelper; + if (!$global_certificate_settings) { + $global_certificate_settings = new ilObjCertificateSettings(); + } + $this->global_certificate_settings = $global_certificate_settings; + if (null === $global_certificate_path) { - $global_certificate_path = str_replace( - '[CLIENT_WEB_DIR]', - '', - ilObjCertificateSettingsAccess::getBackgroundImagePath(true) - ); + $global_certificate_path = $this->global_certificate_settings->getDefaultBackgroundImagePath(true); } $this->global_certificate_path = $global_certificate_path; diff --git a/Services/Certificate/classes/Template/Action/Delete/class.ilCertificateTemplateDeleteAction.php b/Services/Certificate/classes/Template/Action/Delete/class.ilCertificateTemplateDeleteAction.php index 2afeed16938d..39711559e194 100644 --- a/Services/Certificate/classes/Template/Action/Delete/class.ilCertificateTemplateDeleteAction.php +++ b/Services/Certificate/classes/Template/Action/Delete/class.ilCertificateTemplateDeleteAction.php @@ -1,7 +1,5 @@ */ @@ -75,27 +75,5 @@ public function delete(int $templateId, int $objectId): void ); $this->templateRepository->save($certificateTemplate); - - $this->overwriteBackgroundImageThumbnail($certificateTemplate); - } - - private function overwriteBackgroundImageThumbnail(ilCertificateTemplate $previousTemplate): void - { - $relativePath = $previousTemplate->getBackgroundImagePath(); - - if ('' === $relativePath) { - $relativePath = '/certificates/default/background.jpg'; - } - - $pathInfo = pathinfo($relativePath); - - $newFilePath = $pathInfo['dirname'] . '/background.jpg.thumb.jpg'; - - $this->utilHelper->convertImage( - $this->rootDirectory . $relativePath, - $this->rootDirectory . $newFilePath, - 'JPEG', - "100" - ); } } diff --git a/Services/Certificate/classes/Template/Action/Preview/ilCertificateTemplatePreviewAction.php b/Services/Certificate/classes/Template/Action/Preview/ilCertificateTemplatePreviewAction.php index 9435bec5bc67..9a991e2345a7 100644 --- a/Services/Certificate/classes/Template/Action/Preview/ilCertificateTemplatePreviewAction.php +++ b/Services/Certificate/classes/Template/Action/Preview/ilCertificateTemplatePreviewAction.php @@ -146,7 +146,7 @@ private function exchangeCertificateVariables( $certificate_text = str_replace( '[CLIENT_WEB_DIR]', - $this->rootDirectory, + 'file://' . $this->rootDirectory, $certificate_text ); @@ -154,7 +154,7 @@ private function exchangeCertificateVariables( return str_replace( '[BACKGROUND_IMAGE]', - $this->rootDirectory . $backgroundImagePath, + 'file://' . $this->rootDirectory . $backgroundImagePath, $certificate_text ); } diff --git a/Services/Certificate/classes/Template/class.ilCertificateTemplateDatabaseRepository.php b/Services/Certificate/classes/Template/class.ilCertificateTemplateDatabaseRepository.php index da6dd94ce666..fe6e89f2813f 100644 --- a/Services/Certificate/classes/Template/class.ilCertificateTemplateDatabaseRepository.php +++ b/Services/Certificate/classes/Template/class.ilCertificateTemplateDatabaseRepository.php @@ -1,7 +1,5 @@ * Repository that allows interaction with the database @@ -392,6 +392,56 @@ private function deactivatePreviousTemplates(int $objId): void $this->logger->debug(sprintf('END - Certificate template deactivated for object: "%s"', $objId)); } + public function updateDefaultBackgroundImagePaths(string $old_relative_path, string $new_relative_path): void + { + $this->logger->debug(sprintf( + 'START - Update all default background image paths from "%s" to "%s"', + $old_relative_path, + $new_relative_path + )); + + $affected_rows = $this->database->manipulateF( + 'UPDATE il_cert_template SET background_image_path = %s WHERE currently_active = 1 AND (background_image_path = %s OR background_image_path = %s )', + [ + 'text', + 'text', + 'text' + ], + [ + $new_relative_path, + $old_relative_path, + '/certificates/default/background.jpg'] + ); + + $this->logger->debug(sprintf( + 'END - Updated %s certificate templates using old path', + $affected_rows + )); + } + + public function isBackgroundImageUsed(string $relative_image_path): bool + { + $this->logger->debug(sprintf( + 'START - Checking if any certificate template uses background image path "%s"', + $relative_image_path + )); + + $result = $this->database->queryF( + 'SELECT EXISTS(SELECT 1 FROM il_cert_template WHERE background_image_path = %s AND currently_active = 1) AS does_exist', + ['text'], + [$relative_image_path] + ); + + $exists = (bool) ($this->database->fetchAssoc($result)['does_exist'] ?? false); + + $this->logger->debug(sprintf( + 'END - Image path "%s" is ' . $exists ? "in use" : "unused", + $relative_image_path + )); + + return $exists; + } + /** * @param array $row * @return ilCertificateTemplate diff --git a/Services/Certificate/classes/User/class.ilUserCertificateRepository.php b/Services/Certificate/classes/User/class.ilUserCertificateRepository.php index c1f060205453..e712e2f8ee44 100644 --- a/Services/Certificate/classes/User/class.ilUserCertificateRepository.php +++ b/Services/Certificate/classes/User/class.ilUserCertificateRepository.php @@ -1,7 +1,5 @@ */ @@ -583,6 +583,29 @@ private function deactivatePreviousCertificates(int $objId, int $userId): void )); } + public function isBackgroundImageUsed(string $relative_image_path): bool + { + $this->logger->debug(sprintf( + 'START - Checking if any certificate template uses background image path "%s"', + $relative_image_path + )); + + $result = $this->database->queryF( + 'SELECT EXISTS(SELECT 1 FROM il_cert_user_cert WHERE background_image_path = %s AND currently_active = 1) AS does_exist', + ['text'], + [$relative_image_path] + ); + + $exists = (bool) ($this->database->fetchAssoc($result)['does_exist'] ?? false); + + $this->logger->debug(sprintf( + 'END - Image path "%s" is ' . $exists ? "in use" : "unused", + $relative_image_path + )); + + return $exists; + } + /** * @param array $row * @return ilUserCertificate diff --git a/Services/Certificate/classes/class.ilCertificateGUI.php b/Services/Certificate/classes/class.ilCertificateGUI.php index c1cf51bf7ecd..9851acf75387 100644 --- a/Services/Certificate/classes/class.ilCertificateGUI.php +++ b/Services/Certificate/classes/class.ilCertificateGUI.php @@ -1,7 +1,5 @@ access = $DIC['ilAccess']; $this->toolbar = $DIC['ilToolbar']; + $this->global_certificate_settings = new ilObjCertificateSettings(); + $this->lng->loadLanguageModule('certificate'); $this->lng->loadLanguageModule('cert'); $this->lng->loadLanguageModule("trac"); @@ -462,8 +465,8 @@ private function saveCertificate(ilPropertyFormGUI $form, array $form_fields, $o } if ($backgroundImagePath === '') { if ($backgroundDelete || $previousCertificateTemplate->getBackgroundImagePath() === '') { - $globalBackgroundImagePath = ilObjCertificateSettingsAccess::getBackgroundImagePath(true); - $backgroundImagePath = str_replace('[CLIENT_WEB_DIR]', '', $globalBackgroundImagePath); + $backgroundImagePath = $this->global_certificate_settings->getDefaultBackgroundImagePath(true); + } else { $backgroundImagePath = $previousCertificateTemplate->getBackgroundImagePath(); } diff --git a/Services/Certificate/classes/class.ilObjCertificateSettings.php b/Services/Certificate/classes/class.ilObjCertificateSettings.php index a27f6d87afa7..c632963cb136 100644 --- a/Services/Certificate/classes/class.ilObjCertificateSettings.php +++ b/Services/Certificate/classes/class.ilObjCertificateSettings.php @@ -18,6 +18,9 @@ declare(strict_types=1); +use ILIAS\Data\UUID\Factory; +use ILIAS\FileUpload\DTO\UploadResult; + /** * Class ilObjCertificateSettings * @author Helmut Schottmüller @@ -27,14 +30,22 @@ class ilObjCertificateSettings extends ilObject { private ilLogger $cert_logger; + private Factory $uuid_factory; + private ilSetting $certificate_settings; + private ilCertificateTemplateDatabaseRepository $certificate_repo; + private ilUserCertificateRepository $user_certificate_repo; public function __construct(int $a_id = 0, bool $a_reference = true) { global $DIC; parent::__construct($a_id, $a_reference); - $this->type = "cert"; + $this->type = 'cert'; $this->cert_logger = $DIC->logger()->cert(); + $this->uuid_factory = new Factory(); + $this->certificate_settings = new ilSetting('certificate'); + $this->certificate_repo = new ilCertificateTemplateDatabaseRepository($DIC->database()); + $this->user_certificate_repo = new ilUserCertificateRepository($DIC->database()); } /** @@ -43,83 +54,110 @@ public function __construct(int $a_id = 0, bool $a_reference = true) * @return bool True on success, otherwise false * @throws ilException */ - public function uploadBackgroundImage(\ILIAS\FileUpload\DTO\UploadResult $upload_result): bool + public function uploadBackgroundImage(UploadResult $upload_result): bool { - $image_tempfilename = $upload_result->getPath(); - if ($image_tempfilename !== '') { + $image_temp_file_name = $upload_result->getPath(); + if ($image_temp_file_name !== '') { $extension = pathinfo($upload_result->getName(), PATHINFO_EXTENSION); + $image_path = $this->getBackgroundImageDefaultFolder(); + $new_image_file_name = "background_{$this->uuid_factory->uuid4AsString()}.jpg"; + $new_image_path = $image_path . $new_image_file_name; - $convert_filename = ilCertificateBackgroundImageFileService::BACKGROUND_IMAGE_NAME; - $imagepath = $this->getBackgroundImageDefaultFolder(); - if (!is_dir($imagepath)) { - ilFileUtils::makeDirParents($imagepath); + if (!is_dir($image_path)) { + ilFileUtils::makeDirParents($image_path); } // upload the file if (!ilFileUtils::moveUploadedFile( - $image_tempfilename, - basename($this->getDefaultBackgroundImageTempfilePath($extension)), - $this->getDefaultBackgroundImageTempfilePath($extension) + $image_temp_file_name, + basename($this->getDefaultBackgroundImageTempFilePath($extension)), + $this->getDefaultBackgroundImageTempFilePath($extension) )) { $this->cert_logger->error(sprintf( "Could not upload certificate background image from '%s' to temporary file '%s' (name: '%s')", - $image_tempfilename, - $this->getDefaultBackgroundImageTempfilePath($extension), - basename($this->getDefaultBackgroundImageTempfilePath($extension)) + $image_temp_file_name, + $this->getDefaultBackgroundImageTempFilePath($extension), + basename($this->getDefaultBackgroundImageTempFilePath($extension)) )); return false; } - if (!is_file($this->getDefaultBackgroundImageTempfilePath($extension))) { + if (!is_file($this->getDefaultBackgroundImageTempFilePath($extension))) { $this->cert_logger->error(sprintf( "Uploaded certificate background image could not be moved to temporary file '%s'", - $this->getDefaultBackgroundImageTempfilePath($extension) + $this->getDefaultBackgroundImageTempFilePath($extension) )); return false; } // convert the uploaded file to JPEG ilShellUtil::convertImage( - $this->getDefaultBackgroundImageTempfilePath($extension), - $this->getDefaultBackgroundImagePath(), + $this->getDefaultBackgroundImageTempFilePath($extension), + $new_image_path, 'JPEG' ); ilShellUtil::convertImage( - $this->getDefaultBackgroundImageTempfilePath($extension), - $this->getDefaultBackgroundImageThumbPath(), + $this->getDefaultBackgroundImageTempFilePath($extension), + $new_image_path . ilCertificateBackgroundImageFileService::BACKGROUND_THUMBNAIL_FILE_ENDING, 'JPEG', '100' ); - if (!is_file($this->getDefaultBackgroundImagePath())) { + if (!is_file($new_image_path) || !file_exists($new_image_path)) { // Something went wrong converting the file. Use the original file and hope, that PDF can work with it. $this->cert_logger->error(sprintf( - "Could not convert certificate background image from '%s' as JPEG to '%s', trying fallback ...", - $this->getDefaultBackgroundImageTempfilePath($extension), - $this->getDefaultBackgroundImagePath() + "Could not convert certificate background image from '%s' as JPEG to '%s', trying fallbacj ...", + $this->getDefaultBackgroundImageTempFilePath($extension), + $new_image_path )); if (!ilFileUtils::moveUploadedFile( - $this->getDefaultBackgroundImageTempfilePath($extension), - $convert_filename, - $this->getDefaultBackgroundImagePath() + $this->getDefaultBackgroundImageTempFilePath($extension), + $new_image_file_name, + $new_image_path )) { $this->cert_logger->error(sprintf( "Could not upload certificate background image from '%s' to final file '%s' (name: '%s')", - $this->getDefaultBackgroundImageTempfilePath($extension), - $this->getDefaultBackgroundImagePath(), - $convert_filename + $this->getDefaultBackgroundImageTempFilePath($extension), + $new_image_path, + $new_image_file_name )); return false; } } - unlink($this->getDefaultBackgroundImageTempfilePath($extension)); - if (is_file($this->getDefaultBackgroundImagePath()) && filesize($this->getDefaultBackgroundImagePath()) > 0) { + if ( + is_file($this->getDefaultBackgroundImageTempFilePath($extension)) + && file_exists($this->getDefaultBackgroundImageTempFilePath($extension)) + ) { + unlink($this->getDefaultBackgroundImageTempFilePath($extension)); + } + + if (file_exists($new_image_path) && (filesize($new_image_path) > 0)) { + $old_path = $this->getDefaultBackgroundImagePath(); + $old_path_thumb = $this->getDefaultBackgroundImageThumbPath(); + $old_relative_path = $this->getDefaultBackgroundImagePath(true); + $this->certificate_settings->set('defaultImageFileName', $new_image_file_name); + $new_relative_path = $this->getDefaultBackgroundImagePath(true); + + $this->certificate_repo->updateDefaultBackgroundImagePaths($old_relative_path, $new_relative_path); + + if ( + !$this->certificate_repo->isBackgroundImageUsed($old_relative_path) + && !$this->user_certificate_repo->isBackgroundImageUsed($old_relative_path) + ) { + if (is_file($old_path) && file_exists($old_path)) { + unlink($old_path); + } + + if (is_file($old_path_thumb) && file_exists($old_path_thumb)) { + unlink($old_path_thumb); + } + } return true; } $this->cert_logger->error(sprintf( "Final background image '%s' does not exist or is empty", - $this->getDefaultBackgroundImagePath() + $new_image_path )); } @@ -129,39 +167,53 @@ public function uploadBackgroundImage(\ILIAS\FileUpload\DTO\UploadResult $upload public function deleteBackgroundImage(): bool { $result = true; - if (is_file($this->getDefaultBackgroundImageThumbPath())) { - $result &= unlink($this->getDefaultBackgroundImageThumbPath()); - } - if (is_file($this->getDefaultBackgroundImagePath())) { - $result &= unlink($this->getDefaultBackgroundImagePath()); - } - foreach (ilCertificateBackgroundImageFileService::VALID_BACKGROUND_IMAGE_EXTENSIONS as $extension) { - if (file_exists($this->getDefaultBackgroundImageTempfilePath($extension))) { - $result &= unlink($this->getDefaultBackgroundImageTempfilePath($extension)); + + + if ( + $this->certificate_settings->get('defaultImageFileName', '') + && !$this->certificate_repo->isBackgroundImageUsed($this->getDefaultBackgroundImagePath(true)) + && !$this->user_certificate_repo->isBackgroundImageUsed($this->getDefaultBackgroundImagePath(true)) + ) { + //No certificates exist using the currently configured file, deleting file possible. + + if (is_file($this->getDefaultBackgroundImageThumbPath())) { + $result &= unlink($this->getDefaultBackgroundImageThumbPath()); + } + if (is_file($this->getDefaultBackgroundImagePath())) { + $result &= unlink($this->getDefaultBackgroundImagePath()); + } + + foreach (ilCertificateBackgroundImageFileService::VALID_BACKGROUND_IMAGE_EXTENSIONS as $extension) { + if (is_file($this->getDefaultBackgroundImageTempFilePath($extension))) { + $result &= unlink($this->getDefaultBackgroundImageTempFilePath($extension)); + } } } + $this->certificate_settings->set('defaultImageFileName', ''); + /** @noinspection PhpCastIsUnnecessaryInspection */ /** @noinspection UnnecessaryCastingInspection */ return (bool) $result; // Don't remove the cast, otherwise $result will be 1 or 0 } - private function getBackgroundImageDefaultFolder(): string + public function getBackgroundImageDefaultFolder(bool $relativePath = false): string { - return CLIENT_WEB_DIR . "/certificates/default/"; + return ($relativePath ? '' : CLIENT_WEB_DIR) . '/certificates/default/'; } - private function getDefaultBackgroundImagePath(): string + public function getDefaultBackgroundImagePath(bool $relativePath = false): string { - return $this->getBackgroundImageDefaultFolder() . ilCertificateBackgroundImageFileService::BACKGROUND_IMAGE_NAME; + return $this->getBackgroundImageDefaultFolder($relativePath) + . $this->certificate_settings->get('defaultImageFileName', ''); } - private function getDefaultBackgroundImageThumbPath(): string + public function getDefaultBackgroundImageThumbPath(bool $relativePath = false): string { - return $this->getBackgroundImageDefaultFolder() . ilCertificateBackgroundImageFileService::BACKGROUND_IMAGE_NAME . ilCertificateBackgroundImageFileService::BACKGROUND_THUMBNAIL_FILE_ENDING; + return $this->getDefaultBackgroundImagePath($relativePath) . ilCertificateBackgroundImageFileService::BACKGROUND_THUMBNAIL_FILE_ENDING; } - private function getDefaultBackgroundImageTempfilePath(string $extension): string + private function getDefaultBackgroundImageTempFilePath(string $extension): string { return implode('', [ $this->getBackgroundImageDefaultFolder(), diff --git a/Services/Certificate/classes/class.ilObjCertificateSettingsAccess.php b/Services/Certificate/classes/class.ilObjCertificateSettingsAccess.php index 03839456a7a9..8ae5562da7a4 100644 --- a/Services/Certificate/classes/class.ilObjCertificateSettingsAccess.php +++ b/Services/Certificate/classes/class.ilObjCertificateSettingsAccess.php @@ -1,7 +1,5 @@ @@ -25,49 +25,4 @@ */ class ilObjCertificateSettingsAccess extends ilObjectAccess { - public static function hasBackgroundImage(): bool - { - return is_file(self::getBackgroundImagePath()) && filesize(self::getBackgroundImagePath()) > 0; - } - - public static function getBackgroundImageDefaultFolder(): string - { - return CLIENT_WEB_DIR . "/certificates/default/"; - } - - public static function getBackgroundImagePath(bool $asRelative = false): string - { - $imagePath = self::getBackgroundImageDefaultFolder() . self::getBackgroundImageName(); - - if ($asRelative) { - return str_replace( - [CLIENT_WEB_DIR, '//'], - ['[CLIENT_WEB_DIR]', '/'], - $imagePath - ); - } - - return $imagePath; - } - - public static function getBackgroundImageName(): string - { - return "background.jpg"; - } - - public static function getBackgroundImageThumbPath(): string - { - return self::getBackgroundImageDefaultFolder() . self::getBackgroundImageName() . ".thumb.jpg"; - } - - public static function getBackgroundImageThumbPathWeb(): string - { - return str_replace( - ilFileUtils::removeTrailingPathSeparators( - ILIAS_ABSOLUTE_PATH - ), - ilFileUtils::removeTrailingPathSeparators(ILIAS_HTTP_PATH), - self::getBackgroundImageThumbPath() - ); - } } diff --git a/Services/Certificate/classes/class.ilObjCertificateSettingsGUI.php b/Services/Certificate/classes/class.ilObjCertificateSettingsGUI.php index 3a64e543b0f0..eddd7c16551d 100644 --- a/Services/Certificate/classes/class.ilObjCertificateSettingsGUI.php +++ b/Services/Certificate/classes/class.ilObjCertificateSettingsGUI.php @@ -1,7 +1,5 @@ @@ -112,6 +112,16 @@ public function settings(): void $bgimage = new ilImageFileInputGUI($this->lng->txt('certificate_background_image'), 'background'); $bgimage->setRequired(false); + if (strcmp($this->ctrl->getCmd(), 'save') === 0) { + $backgroundDelete = $this->httpState->wrapper()->post()->has('background_delete') && $this->httpState->wrapper()->post()->retrieve( + 'background_delete', + $this->refinery->kindlyTo()->bool() + ); + if ($backgroundDelete) { + $this->object->deleteBackgroundImage(); + } + } + if ( $this->upload->hasUploads() && $this->httpState->request()->getMethod() === 'POST' && @@ -194,16 +204,6 @@ public function settings(): void $form->addItem($persistentCertificateMode); $this->tpl->setContent($form->getHTML()); - - if (strcmp($this->ctrl->getCmd(), 'save') === 0) { - $backgroundDelete = $this->httpState->wrapper()->post()->has('background_delete') && $this->httpState->wrapper()->post()->retrieve( - 'background_delete', - $this->refinery->kindlyTo()->bool() - ); - if ($backgroundDelete) { - $this->object->deleteBackgroundImage(); - } - } } public function save(): void diff --git a/Services/Certificate/test/ilCertificateCloneActionTest.php b/Services/Certificate/test/ilCertificateCloneActionTest.php index f6a0e209a378..86ab41df7ff3 100644 --- a/Services/Certificate/test/ilCertificateCloneActionTest.php +++ b/Services/Certificate/test/ilCertificateCloneActionTest.php @@ -1,7 +1,5 @@ */ @@ -116,6 +116,10 @@ public function testCloneCertificate(): void $objectHelper->method('lookupObjId') ->willReturn(1000); + $global_certificate_settings = $this->getMockBuilder(ilObjCertificateSettings::class) + ->disableOriginalConstructor() + ->getMock(); + $cloneAction = new ilCertificateCloneAction( $database, new ilCertificatePathFactory(), @@ -123,6 +127,7 @@ public function testCloneCertificate(): void $fileSystem, $logger, $objectHelper, + $global_certificate_settings, 'some/web/directory', '/certificates/default/background.jpg' ); diff --git a/Services/Certificate/test/ilCertificateTemplateDeleteActionTest.php b/Services/Certificate/test/ilCertificateTemplateDeleteActionTest.php index 5bbead9eca55..2bd0608a438b 100644 --- a/Services/Certificate/test/ilCertificateTemplateDeleteActionTest.php +++ b/Services/Certificate/test/ilCertificateTemplateDeleteActionTest.php @@ -1,7 +1,5 @@ */ @@ -46,59 +46,12 @@ public function testDeleteTemplateAndUseOldThumbnail(): void 'samples/background.jpg' )); - $utilHelper = $this->getMockBuilder(ilCertificateUtilHelper::class) - ->getMock(); - - $utilHelper - ->expects($this->once()) - ->method('convertImage'); - - $objectHelper = $this->getMockBuilder(ilCertificateObjectHelper::class) - ->getMock(); - - $objectHelper->method('lookUpType') - ->willReturn('crs'); - - $action = new ilCertificateTemplateDeleteAction( - $templateRepositoryMock, - __DIR__, - $utilHelper, - $objectHelper, - 'v5.4.0' - ); - - $action->delete(100, 2000); - } - - public function testDeleteTemplateButNoThumbnailWillBeCopiedFromOldCertificate(): void - { - $templateRepositoryMock = $this->getMockBuilder(ilCertificateTemplateRepository::class)->getMock(); - - $templateRepositoryMock - ->method('deleteTemplate') - ->with(100, 2000); - - $templateRepositoryMock->method('activatePreviousCertificate') - ->with(2000) - ->willReturn(new ilCertificateTemplate( - 2000, - 'crs', - 'something', - md5('something'), - '[]', - 1, - 'v5.4.0', - 1234567890, - true - )); + $templateRepositoryMock->expects($this->once())->method("deleteTemplate"); + $templateRepositoryMock->expects($this->once())->method("save"); $utilHelper = $this->getMockBuilder(ilCertificateUtilHelper::class) ->getMock(); - $utilHelper - ->expects($this->once()) - ->method('convertImage'); - $objectHelper = $this->getMockBuilder(ilCertificateObjectHelper::class) ->getMock(); diff --git a/Services/Component/classes/class.ilArtifactComponentRepository.php b/Services/Component/classes/class.ilArtifactComponentRepository.php index 64825a1dc9b9..a06ad8552ac5 100644 --- a/Services/Component/classes/class.ilArtifactComponentRepository.php +++ b/Services/Component/classes/class.ilArtifactComponentRepository.php @@ -1,4 +1,5 @@ plugin_state_db->remove($plugin_id); $this->buildDatabase(); } + + public function hasActivatedPlugin(string $id): bool + { + return ($this->hasPluginId($id) && $this->getPluginById($id)->isActivated()); + } } diff --git a/Services/Component/classes/class.ilComponentRepository.php b/Services/Component/classes/class.ilComponentRepository.php index edda64714eed..d1c8b0568e24 100644 --- a/Services/Component/classes/class.ilComponentRepository.php +++ b/Services/Component/classes/class.ilComponentRepository.php @@ -1,4 +1,5 @@ [ + "Services", + "Service2", + "Slot4", + "Plugin3", + "2.9.2", + "8.1", + "8.999", + "Richard Klees", + "richard.klees@concepts-and-training.de", + null, + true, + false ] ]; @@ -74,6 +89,10 @@ protected function setUp(): void $this->plugin_state_db = new class () implements ilPluginStateDB { public function isPluginActivated(string $id): bool { + if ($id == 'plg3') { + return true; + } + return false; } public function setActivation(string $id, bool $activated): void @@ -191,6 +210,7 @@ public function _buildDatabase(): void $plugins4 ); $slots4 = ["slt4" => $this->slt4]; + $this->plg2 = new ilPluginInfo( $this->ilias_version, $this->slt4, @@ -209,6 +229,25 @@ public function _buildDatabase(): void false ); $plugins4["plg2"] = $this->plg2; + + $this->plg3 = new ilPluginInfo( + $this->ilias_version, + $this->slt4, + "plg3", + "Plugin3", + true, + $this->data_factory->version("0.9.1"), + 13, + $this->data_factory->version("2.9.2"), + $this->data_factory->version("8.1"), + $this->data_factory->version("8.999"), + "Richard Klees", + "richard.klees@concepts-and-training.de", + false, + true, + false + ); + $plugins4["plg3"] = $this->plg3; } public function testHasComponent(): void @@ -339,7 +378,7 @@ public function testGetPlugins(): void $plugins = iterator_to_array($this->db->getPlugins()); $ids = array_keys($plugins); - $expected_ids = ["plg1", "plg2"]; + $expected_ids = ["plg1", "plg2", "plg3"]; sort($ids); sort($expected_ids); @@ -692,4 +731,17 @@ protected function readPluginData(): array $this->assertEquals(2, $db->build_called); } + + public function testHasPluginId(): void + { + $this->assertTrue($this->db->hasPluginId("plg1")); + $this->assertFalse($this->db->hasPluginId("plg666")); + } + + public function testHasActivatedPlugin(): void + { + $this->assertFalse($this->db->hasActivatedPlugin("plg1")); // exists, but is not activated + $this->assertFalse($this->db->hasActivatedPlugin("plg666")); // does not exist + $this->assertTrue($this->db->hasActivatedPlugin("plg3")); // exists and is activated + } } diff --git a/Services/Conditions/classes/class.ilConditionHandler.php b/Services/Conditions/classes/class.ilConditionHandler.php index 0107a7877df9..e925b7a33073 100755 --- a/Services/Conditions/classes/class.ilConditionHandler.php +++ b/Services/Conditions/classes/class.ilConditionHandler.php @@ -1114,7 +1114,7 @@ public static function cloneDependencies(int $a_src_ref_id, int $a_target_ref_id ilObject::_lookupObjId($a_src_ref_id) ); foreach ($conditions as $con) { - if ($mappings[$con['trigger_ref_id']]) { + if ($mappings[$con['trigger_ref_id']] ?? false) { $newCondition = new ilConditionHandler(); $target_obj = ilObject::_lookupObjId($a_target_ref_id); diff --git a/Services/Contact/BuddySystem/classes/class.ilBuddySystemNotification.php b/Services/Contact/BuddySystem/classes/class.ilBuddySystemNotification.php index 668f91c16b26..89899230182a 100644 --- a/Services/Contact/BuddySystem/classes/class.ilBuddySystemNotification.php +++ b/Services/Contact/BuddySystem/classes/class.ilBuddySystemNotification.php @@ -1,7 +1,5 @@ txt('buddy_noti_cr_profile_not_published')), - '#' - ); } $links[] = new ilNotificationLink( new ilNotificationParameter('buddy_notification_contact_request_link_osd', [], 'buddysystem'), diff --git a/Services/Contact/classes/class.ilContactGUI.php b/Services/Contact/classes/class.ilContactGUI.php index f8dcbe3806a5..0e0f6e2ded71 100644 --- a/Services/Contact/classes/class.ilContactGUI.php +++ b/Services/Contact/classes/class.ilContactGUI.php @@ -79,7 +79,7 @@ public function executeCommand(): bool $forward_class = $this->ctrl->getNextClass($this); - $this->umail->savePostData($this->user->getId(), [], '', '', '', '', '', false); + $this->umail->persistToStage($this->user->getId(), [], '', '', '', '', '', false); switch (strtolower($forward_class)) { case strtolower(ilMailSearchCoursesGUI::class): @@ -408,7 +408,7 @@ protected function mailToUsers(): void } $logins = []; - $mail_data = $this->umail->getSavedData(); + $mail_data = $this->umail->retrieveFromStage(); foreach ($usr_ids as $usr_id) { $login = ilObjUser::_lookupLogin($usr_id); if (!$this->umail->existsRecipient($login, (string) $mail_data['rcp_to'])) { @@ -419,7 +419,7 @@ protected function mailToUsers(): void if ($logins !== []) { $mail_data = $this->umail->appendSearchResult($logins, 'to'); - $this->umail->savePostData( + $this->umail->persistToStage( (int) $mail_data['user_id'], $mail_data['attachments'], $mail_data['rcp_to'], diff --git a/Services/Contact/classes/class.ilMailSearchGUI.php b/Services/Contact/classes/class.ilMailSearchGUI.php index 095058978282..4e7105b08474 100644 --- a/Services/Contact/classes/class.ilMailSearchGUI.php +++ b/Services/Contact/classes/class.ilMailSearchGUI.php @@ -130,8 +130,8 @@ public function adopt(): void private function saveMailData(): void { - $mail_data = $this->umail->getSavedData(); - $this->umail->savePostData( + $mail_data = $this->umail->retrieveFromStage(); + $this->umail->persistToStage( (int) $mail_data['user_id'], $mail_data['attachments'], $mail_data['rcp_to'], diff --git a/Services/Contact/classes/class.ilMailSearchObjectGUI.php b/Services/Contact/classes/class.ilMailSearchObjectGUI.php index eef78369edfe..276df86d7785 100644 --- a/Services/Contact/classes/class.ilMailSearchObjectGUI.php +++ b/Services/Contact/classes/class.ilMailSearchObjectGUI.php @@ -260,7 +260,7 @@ protected function mail(): void protected function mailObjects(): void { $members = []; - $mail_data = $this->umail->getSavedData(); + $mail_data = $this->umail->retrieveFromStage(); $obj_ids = []; if ($this->http->wrapper()->query()->has('search_' . $this->getObjectType())) { @@ -301,9 +301,9 @@ protected function mailObjects(): void } } - $mail_data = $members !== [] ? $this->umail->appendSearchResult(array_unique($members), 'to') : $this->umail->getSavedData(); + $mail_data = $members !== [] ? $this->umail->appendSearchResult(array_unique($members), 'to') : $this->umail->retrieveFromStage(); - $this->umail->savePostData( + $this->umail->persistToStage( (int) $mail_data['user_id'], $mail_data['attachments'], $mail_data['rcp_to'], @@ -337,7 +337,7 @@ public function mailMembers(): void ); } - $mail_data = $this->umail->getSavedData(); + $mail_data = $this->umail->retrieveFromStage(); foreach ($usr_ids as $usr_id) { $login = ilObjUser::_lookupLogin($usr_id); if (!$this->umail->existsRecipient($login, (string) $mail_data['rcp_to'])) { @@ -346,7 +346,7 @@ public function mailMembers(): void } $mail_data = $this->umail->appendSearchResult(array_unique($members), 'to'); - $this->umail->savePostData( + $this->umail->persistToStage( (int) $mail_data['user_id'], $mail_data['attachments'], $mail_data['rcp_to'], diff --git a/Services/Contact/classes/class.ilMailingList.php b/Services/Contact/classes/class.ilMailingList.php index 3749fcc52be6..48b1655791b5 100644 --- a/Services/Contact/classes/class.ilMailingList.php +++ b/Services/Contact/classes/class.ilMailingList.php @@ -27,7 +27,7 @@ class ilMailingList private int $mail_id; private int $user_id; private string $title = ''; - private string $description = ''; + private ?string $description = ''; private string $createdate; private ?string $changedate; @@ -259,12 +259,12 @@ public function getTitle(): string return $this->title; } - public function setDescription(string $a_description = ''): void + public function setDescription(?string $a_description = ''): void { $this->description = $a_description; } - public function getDescription(): string + public function getDescription(): ?string { return $this->description; } diff --git a/Services/Contact/classes/class.ilMailingListsGUI.php b/Services/Contact/classes/class.ilMailingListsGUI.php index 6883aa81b39e..d2f7c79abb07 100644 --- a/Services/Contact/classes/class.ilMailingListsGUI.php +++ b/Services/Contact/classes/class.ilMailingListsGUI.php @@ -195,7 +195,7 @@ public function mailToList(): bool return true; } - $mail_data = $this->umail->getSavedData(); + $mail_data = $this->umail->retrieveFromStage(); $lists = []; foreach ($ml_ids as $id) { if ($this->mlists->isOwner($id, $this->user->getId()) && @@ -206,7 +206,7 @@ public function mailToList(): bool if (count($lists)) { $mail_data = $this->umail->appendSearchResult(array_values($lists), 'to'); - $this->umail->savePostData( + $this->umail->persistToStage( (int) $mail_data['user_id'], $mail_data['attachments'], $mail_data['rcp_to'], @@ -254,7 +254,7 @@ public function showMailingLists(): bool $result[$counter]['check'] = ilLegacyFormElementsUtil::formCheckbox(false, 'ml_id[]', (string) $entry->getId()); $result[$counter]['title'] = $entry->getTitle() . " [#il_ml_" . $entry->getId() . "]"; - $result[$counter]['description'] = $entry->getDescription(); + $result[$counter]['description'] = $entry->getDescription() ?? ''; $result[$counter]['members'] = count($entry->getAssignedEntries()); $this->ctrl->setParameter($this, 'ml_id', $entry->getId()); @@ -388,7 +388,7 @@ private function setValuesByObject(): void { $this->form_gui->setValuesByArray([ 'title' => $this->mlists->getCurrentMailingList()->getTitle(), - 'description' => $this->mlists->getCurrentMailingList()->getDescription() + 'description' => $this->mlists->getCurrentMailingList()->getDescription() ?? '' ]); } diff --git a/Services/Container/Content/class.ilContainerContentGUI.php b/Services/Container/Content/class.ilContainerContentGUI.php index e2e37fed01ad..238b100c186b 100644 --- a/Services/Container/Content/class.ilContainerContentGUI.php +++ b/Services/Container/Content/class.ilContainerContentGUI.php @@ -276,7 +276,8 @@ protected function initRenderer(): void , $sorting->getBlockPositions(), $this->container_gui, - $this->getViewMode() + $this->getViewMode(), + $this->getContainerGUI()->isActiveAdministrationPanel() ); // all event items are included per session rendering @@ -902,7 +903,14 @@ public function renderItemGroup(array $a_itgr): void $position = 1; foreach ($items as $item) { // we are NOT using hasItem() here, because item might be in multiple item groups - $html2 = $this->renderItem($item, $position++, false, "[itgr][" . $a_itgr['obj_id'] . "]", $item_group->getListPresentation()); + + $it_pres = $item_group->getListPresentation(); + if ($this->getContainerGUI()->isActiveOrdering() || + $this->getContainerGUI()->isActiveAdministrationPanel()) { + $it_pres = "list"; + } + + $html2 = $this->renderItem($item, $position++, false, "[itgr][" . $a_itgr['obj_id'] . "]", $it_pres); if ($html2 != "") { // :TODO: show it multiple times? $this->renderer->addItemToBlock($a_itgr["ref_id"], $item["type"], $item["child"], $html2, true); diff --git a/Services/Container/Content/class.ilContainerRenderer.php b/Services/Container/Content/class.ilContainerRenderer.php index d20694c13f31..0e9d24db1851 100644 --- a/Services/Container/Content/class.ilContainerRenderer.php +++ b/Services/Container/Content/class.ilContainerRenderer.php @@ -24,6 +24,7 @@ class ilContainerRenderer { protected const UNIQUE_SEPARATOR = "-"; + protected bool $admin_panel; protected ilLanguage $lng; protected ilSetting $settings; @@ -64,10 +65,12 @@ public function __construct( bool $a_active_block_ordering = false, array $a_block_custom_positions = [], ?ilContainerGUI $container_gui_obj = null, - int $a_view_mode = ilContainerContentGUI::VIEW_MODE_LIST + int $a_view_mode = ilContainerContentGUI::VIEW_MODE_LIST, + bool $admin_panel = false ) { global $DIC; + $this->admin_panel = $admin_panel; $this->lng = $DIC->language(); $this->settings = $DIC->settings(); $this->ui = $DIC->ui(); @@ -472,7 +475,7 @@ protected function renderHelperGeneric( if (is_numeric($a_block_id)) { $item_group = new ilObjItemGroup($a_block_id); if ($item_group->getListPresentation() !== "") { - $view_mode = ($item_group->getListPresentation() === "tile") + $view_mode = ($item_group->getListPresentation() === "tile" && !$this->active_block_ordering && !$this->admin_panel) ? ilContainerContentGUI::VIEW_MODE_TILE : ilContainerContentGUI::VIEW_MODE_LIST; $tile_size = $item_group->getTileSize(); diff --git a/Services/Container/Content/class.ilContainerSessionsContentGUI.php b/Services/Container/Content/class.ilContainerSessionsContentGUI.php index a549dfc2e439..6378bd84d5f1 100644 --- a/Services/Container/Content/class.ilContainerSessionsContentGUI.php +++ b/Services/Container/Content/class.ilContainerSessionsContentGUI.php @@ -174,10 +174,14 @@ protected function renderSessionLimitLink( if ($prefp) { $tpl->setVariable('TXT_TITLE_LINKED', $lng->txt('crs_link_hide_prev_sessions')); + $ilCtrl->setParameterByClass(get_class($this->getContainerGUI()), 'crs_prev_sess', (int) !$prefp); + $tpl->setVariable('HREF_TITLE_LINKED', $ilCtrl->getLinkTargetByClass(get_class($this->getContainerGUI()), 'view')); } else { $tpl->setVariable('TXT_TITLE_LINKED', $lng->txt('crs_link_show_all_prev_sessions')); + $ilCtrl->setParameterByClass(get_class($this->getContainerGUI()), 'crs_prev_sess', (int) !$prefp); + $tpl->setVariable('HREF_TITLE_LINKED', $ilCtrl->getLinkTargetByClass(get_class($this->getContainerGUI()), 'view')); } - $ilCtrl->setParameterByClass(get_class($this->getContainerGUI()), 'crs_prev_sess', (int) !$prefp); + $ilCtrl->clearParametersByClass(get_class($this->getContainerGUI())); } else { $prefn = $ilUser->getPref('crs_sess_show_next_' . $this->getContainerObject()->getId()); @@ -187,9 +191,9 @@ protected function renderSessionLimitLink( $tpl->setVariable('TXT_TITLE_LINKED', $lng->txt('crs_link_show_all_next_sessions')); } $ilCtrl->setParameterByClass(get_class($this->getContainerGUI()), 'crs_next_sess', (int) !$prefn); + $tpl->setVariable('HREF_TITLE_LINKED', $ilCtrl->getLinkTargetByClass(get_class($this->getContainerGUI()), 'view')); + $ilCtrl->clearParametersByClass(get_class($this->getContainerGUI())); } - $tpl->setVariable('HREF_TITLE_LINKED', $ilCtrl->getLinkTargetByClass(get_class($this->getContainerGUI()))); - $ilCtrl->clearParametersByClass(get_class($this->getContainerGUI())); $tpl->parseCurrentBlock(); return $tpl->get(); diff --git a/Services/Container/Export/class.ilContainerXmlParser.php b/Services/Container/Export/class.ilContainerXmlParser.php index 95bb0222fec8..e3fda2f5ce7a 100644 --- a/Services/Container/Export/class.ilContainerXmlParser.php +++ b/Services/Container/Export/class.ilContainerXmlParser.php @@ -160,11 +160,6 @@ protected function parseTiming( $crs_item->setSuggestionEnd($dt->get(IL_CAL_UNIX)); break; - case 'EarliestStart': - $dt = new ilDateTime((string) $sub, IL_CAL_DATETIME, ilTimeZone::UTC); - $crs_item->setEarliestStart($dt->get(IL_CAL_UNIX)); - break; - case 'LatestEnd': break; } diff --git a/Services/Container/Skills/classes/class.ilContSkillAdminGUI.php b/Services/Container/Skills/classes/class.ilContSkillAdminGUI.php index 228757e3b0d3..20603743ab4e 100644 --- a/Services/Container/Skills/classes/class.ilContSkillAdminGUI.php +++ b/Services/Container/Skills/classes/class.ilContSkillAdminGUI.php @@ -304,7 +304,7 @@ public function deassignCompetencesConfirm(): void foreach ($user_ids as $i) { $name = ilUserUtil::getNamePresentation($i, false, false, "", true); - $cgui->addItem("usr_ids[]", $i, $name); + $cgui->addItem("usr_ids[]", (string) $i, $name); } $tpl->setContent($cgui->getHTML()); @@ -405,7 +405,7 @@ public function confirmRemoveSelectedSkill(): void foreach ($this->requested_combined_skill_ids as $i) { $s = explode(":", $i); - $cgui->addItem("id[]", $i, ilBasicSkill::_lookupTitle((int) $s[0], (int) $s[1])); + $cgui->addItem("id[]", (string) $i, ilBasicSkill::_lookupTitle((int) $s[0], (int) $s[1])); } $tpl->setContent($cgui->getHTML()); @@ -541,7 +541,7 @@ public function confirmRemoveSelectedGlobalProfiles(): void $this->tpl->setOnScreenMessage('info', $lng->txt("cont_skill_removal_not_possible"), true); $ctrl->redirect($this, "listProfiles"); } - $cgui->addItem("id[]", $i, $this->profile_service->lookupTitle($i)); + $cgui->addItem("id[]", (string) $i, $this->profile_service->lookupTitle($i)); } $tpl->setContent($cgui->getHTML()); @@ -630,7 +630,7 @@ public function confirmDeleteSelectedLocalProfiles(): void $this->tpl->setOnScreenMessage('info', $lng->txt("cont_skill_deletion_not_possible"), true); $ctrl->redirect($this, "listProfiles"); } - $cgui->addItem("id[]", $i, $this->profile_service->lookupTitle($i)); + $cgui->addItem("id[]", (string) $i, $this->profile_service->lookupTitle($i)); } $tpl->setContent($cgui->getHTML()); diff --git a/Services/Container/Sorting/class.ilContainerSorting.php b/Services/Container/Sorting/class.ilContainerSorting.php index 41bde6f18e97..04b6715c4921 100644 --- a/Services/Container/Sorting/class.ilContainerSorting.php +++ b/Services/Container/Sorting/class.ilContainerSorting.php @@ -101,12 +101,15 @@ public function cloneSorting( ); if (($rec = $ilDB->fetchAssoc($set)) && $rec["block_ids"] != "") { $ilLog->debug("Got block sorting for obj_id = " . $this->obj_id . ": " . $rec["block_ids"]); - $new_ids = implode(";", array_map(static function ($block_id) use ($mappings) { - if (is_numeric($block_id)) { - $block_id = $mappings[$block_id]; + $new_block_ids = []; + foreach (explode(";", $rec["block_ids"]) as $block_id) { + if (is_numeric($block_id) && isset($mappings[$block_id])) { + $new_block_ids[] = $mappings[$block_id]; + } else { + $new_block_ids[] = $block_id; } - return $block_id; - }, explode(";", $rec["block_ids"]))); + } + $new_ids = implode(";", $new_block_ids); $ilDB->replace( "container_sorting_bl", diff --git a/Services/Container/classes/class.ilContainer.php b/Services/Container/classes/class.ilContainer.php index c09877ec8af6..2e956404595c 100755 --- a/Services/Container/classes/class.ilContainer.php +++ b/Services/Container/classes/class.ilContainer.php @@ -693,7 +693,7 @@ public function getSubItems( $object['type'], ['file', 'fold', 'cat'], true - ) && ilObjFileAccess::_isFileHidden($object['title'])) { + ) && ilObjFileAccess::_isFileHidden((string) $object['title'])) { $this->setHiddenFilesFound(true); if (!$a_admin_panel_enabled) { continue; @@ -703,7 +703,7 @@ public function getSubItems( // including event items! if (!self::$data_preloaded) { - $preloader->addItem($object["obj_id"], $object["type"], $object["child"]); + $preloader->addItem((int) $object["obj_id"], $object["type"], $object["child"]); } // filter side block items @@ -711,7 +711,7 @@ public function getSubItems( continue; } - $all_ref_ids[] = $object["child"]; + $all_ref_ids[] = (int) $object["child"]; } // data preloader @@ -1105,19 +1105,7 @@ protected function applyContainerUserFilter( } $obj_ids = array_intersect($obj_ids, $result_obj_ids); } elseif ((int) $field_id === ilContainerFilterField::STD_FIELD_COPYRIGHT) { - $result = null; - $set = $db->queryF( - "SELECT DISTINCT(rbac_id) FROM il_meta_rights " . - " WHERE " . $db->in("rbac_id", $obj_ids, false, "integer") . - " AND description = %s ", - ["text"], - ['il_copyright_entry__' . IL_INST_ID . '__' . $val] - ); - $result_obj_ids = []; - while ($rec = $db->fetchAssoc($set)) { - $result_obj_ids[] = $rec["rbac_id"]; - } - $obj_ids = array_intersect($obj_ids, $result_obj_ids); + $obj_ids = $this->filterObjIdsByCopyright($obj_ids, $val); } else { #$query_parser->setCombination($this->options['title_ao']); $query_parser->setCombination(ilQueryParser::QP_COMBINATION_OR); @@ -1127,9 +1115,13 @@ protected function applyContainerUserFilter( //$meta_search->setFilter($this->filter); // object types ['lm', ...] switch ($field_id) { case ilContainerFilterField::STD_FIELD_TITLE_DESCRIPTION: + $meta_search->setMode('title_description'); + break; case ilContainerFilterField::STD_FIELD_DESCRIPTION: + $meta_search->setMode('description'); + break; case ilContainerFilterField::STD_FIELD_TITLE: - $meta_search->setMode('title_description'); + $meta_search->setMode('title'); break; case ilContainerFilterField::STD_FIELD_KEYWORD: $meta_search->setMode('keyword_all'); @@ -1202,6 +1194,55 @@ static function (array $i): int { return $objects; } + protected function filterObjIdsByCopyright(array $obj_ids, string $copyright_id): array + { + $identifier = \ilMDCopyrightSelectionEntry::createIdentifier($copyright_id); + $default_identifier = \ilMDCopyrightSelectionEntry::createIdentifier( + \ilMDCopyrightSelectionEntry::getDefault() + ); + + if ($identifier === $default_identifier) { + return $this->filterObjIdsByDefaultCopyright($obj_ids, $default_identifier); + } + + $db = $this->db; + $set = $db->queryF( + "SELECT DISTINCT(rbac_id) FROM il_meta_rights " . + " WHERE " . $db->in("rbac_id", $obj_ids, false, "integer") . + " AND description = %s ", + array("text"), + array($identifier) + ); + $result_obj_ids = []; + while ($rec = $db->fetchAssoc($set)) { + $result_obj_ids[] = $rec["rbac_id"]; + } + return array_intersect($obj_ids, $result_obj_ids); + } + + protected function filterObjIdsByDefaultCopyright( + array $obj_ids, + string $default_identifier + ): array { + /* + * Objects with no entry in il_meta_rights need to be treated like they + * have the default copyright. + */ + $db = $this->db; + $set = $db->queryF( + "SELECT DISTINCT(rbac_id) FROM il_meta_rights " . + " WHERE " . $db->in("rbac_id", $obj_ids, false, "integer") . + " AND NOT description = %s ", + array("text"), + array($default_identifier) + ); + $filtered_out_obj_ids = []; + while ($rec = $db->fetchAssoc($set)) { + $filtered_out_obj_ids[] = $rec["rbac_id"]; + } + return array_diff($obj_ids, $filtered_out_obj_ids); + } + /** * Legacy online filter * diff --git a/Services/Container/classes/class.ilContainerGUI.php b/Services/Container/classes/class.ilContainerGUI.php index c0d2b7d7ded7..47e07048d1d3 100644 --- a/Services/Container/classes/class.ilContainerGUI.php +++ b/Services/Container/classes/class.ilContainerGUI.php @@ -351,13 +351,13 @@ public function getContentGUI(): ilContainerContentGUI $container_view = new ilContainerObjectiveGUI($this); break; - // all items in one block + // all items in one block case ilContainer::VIEW_SESSIONS: case ilCourseConstants::IL_CRS_VIEW_TIMING: // not nice this workaround $container_view = new ilContainerSessionsContentGUI($this); break; - // all items in one block + // all items in one block case ilContainer::VIEW_BY_TYPE: default: $container_view = new ilContainerByTypeContentGUI($this, $this->container_user_filter); @@ -463,6 +463,7 @@ public function showAdministrationPanel(): void $main_tpl->setPageFormAction($this->ctrl->getFormAction($this)); $toolbar = new ilToolbarGUI(); + $toolbar->setId("admclip"); $this->ctrl->setParameter($this, "type", ""); $this->ctrl->setParameter($this, "item_ref_id", ""); @@ -482,6 +483,7 @@ public function showAdministrationPanel(): void $main_tpl->setPageFormAction($this->ctrl->getFormAction($this)); $toolbar = new ilToolbarGUI(); + $toolbar->setId("adm"); $this->ctrl->setParameter($this, "type", ""); $this->ctrl->setParameter($this, "item_ref_id", ""); @@ -521,7 +523,7 @@ public function showAdministrationPanel(): void $toolbar->addSeparator(); } - $toolbar->addButton( + $this->toolbar->addButton( $this->lng->txt('cntr_adopt_content'), $this->ctrl->getLinkTargetByClass( 'ilObjectCopyGUI', @@ -918,7 +920,6 @@ public function disableAdministrationPanelObject(): void public function editOrderObject(): void { $ilTabs = $this->tabs; - $this->edit_order = true; $this->view_manager->setContentView(); $this->renderObject(); @@ -2059,19 +2060,9 @@ protected function showContainerPageTabs(): void { $ctrl = $this->ctrl; $tabs = $this->tabs; + $tabs->clearTargets(); $page_gui = new ilContainerPageGUI($this->object->getId()); - $style_id = $this->content_style_domain - ->styleForRefId($this->object->getRefId()) - ->getEffectiveStyleId(); - if (ilObject::_lookupType($style_id) === "sty") { - $page_gui->setStyleId($style_id); - } else { - $style_id = 0; - } - $page_gui->setTabHook($this, "addPageTabs"); - $ctrl->getHTML($page_gui); - $tabs->setTabActive("obj_sty"); - $tabs->setBackTarget($this->lng->txt('back'), ilLink::_getLink($this->ref_id)); + $tabs->setBackTarget($this->lng->txt('back'), $this->ctrl->getLinkTarget($page_gui, "edit")); } public function getAsynchItemListObject(): void @@ -2674,6 +2665,11 @@ protected function initFilter(): void if (!$this->object || !ilContainer::_lookupContainerSetting($this->object->getId(), "filter", '0')) { return; } + + if ($this->isActiveOrdering() || $this->ctrl->getCmd() === "editOrder") { + return; + } + $filter_service = $this->container_filter_service; $request = $DIC->http()->request(); diff --git a/Services/ContainerReference/classes/class.ilContainerReferenceGUI.php b/Services/ContainerReference/classes/class.ilContainerReferenceGUI.php index 89dbaf40747e..0b17986fd5c1 100644 --- a/Services/ContainerReference/classes/class.ilContainerReferenceGUI.php +++ b/Services/ContainerReference/classes/class.ilContainerReferenceGUI.php @@ -129,6 +129,8 @@ public function createObject(): void )) { $ilErr->raiseError($this->lng->txt("permission_denied"), $ilErr->MESSAGE); } + $this->ctrl->saveParameter($this, "crtptrefid"); + $this->ctrl->saveParameter($this, "crtcb"); $form = $this->initForm(self::MODE_CREATE); $this->tpl->setContent($form->getHTML()); } diff --git a/Services/Context/classes/class.ilContextScorm.php b/Services/Context/classes/class.ilContextScorm.php index fc7998b83ab7..6c50c7b9c7a9 100644 --- a/Services/Context/classes/class.ilContextScorm.php +++ b/Services/Context/classes/class.ilContextScorm.php @@ -40,12 +40,12 @@ public static function usesHTTP(): bool public static function hasHTML(): bool { - return false; + return true; } public static function usesTemplate(): bool { - return false; + return true; } public static function initClient(): bool diff --git a/Services/Cron/classes/class.ilCronJob.php b/Services/Cron/classes/class.ilCronJob.php index 2d8d0b300c93..4df26b61f23b 100644 --- a/Services/Cron/classes/class.ilCronJob.php +++ b/Services/Cron/classes/class.ilCronJob.php @@ -1,7 +1,5 @@ date_time_provider !== null) { + if ($this->date_time_provider === null) { + $now = new DateTimeImmutable('@' . time(), new DateTimeZone(date_default_timezone_get())); + } else { $now = ($this->date_time_provider)(); } @@ -135,7 +136,7 @@ private function checkSchedule(?DateTimeImmutable $last_run, ?int $schedule_type } /** - * @param Closure|null $date_time_provider + * @param Closure():DateTimeInterface|null $date_time_provider */ public function setDateTimeProvider(?Closure $date_time_provider): void { diff --git a/Services/Cron/classes/class.ilCronJobRepositoryImpl.php b/Services/Cron/classes/class.ilCronJobRepositoryImpl.php index 10ea52a89bbf..770006633cc7 100644 --- a/Services/Cron/classes/class.ilCronJobRepositoryImpl.php +++ b/Services/Cron/classes/class.ilCronJobRepositoryImpl.php @@ -1,7 +1,5 @@ hasAutoActivation()) { - $this->activateJob($job); + $this->activateJob($job, new DateTimeImmutable('@' . time())); $job->activationWasToggled($this->db, $this->setting, true); } else { // to overwrite dependent settings @@ -314,6 +314,7 @@ public function resetJob(ilCronJob $job): void public function updateJobResult( ilCronJob $job, + DateTimeImmutable $when, ilObjUser $actor, ilCronJobResult $result, bool $wasManualExecution = false @@ -326,7 +327,7 @@ public function updateJobResult( ' , job_result_code = ' . $this->db->quote($result->getCode(), 'text') . ' , job_result_message = ' . $this->db->quote($result->getMessage(), 'text') . ' , job_result_type = ' . $this->db->quote((int) $wasManualExecution, 'integer') . - ' , job_result_ts = ' . $this->db->quote(time(), 'integer') . + ' , job_result_ts = ' . $this->db->quote($when->getTimestamp(), 'integer') . ' , job_result_dur = ' . $this->db->quote($result->getDuration() * 1000, 'integer') . ' WHERE job_id = ' . $this->db->quote($job->getId(), 'text'); $this->db->manipulate($query); @@ -354,8 +355,12 @@ public function updateJobSchedule(ilCronJob $job, ?int $scheduleType, ?int $sche } } - public function activateJob(ilCronJob $job, ?ilObjUser $actor = null, bool $wasManuallyExecuted = false): void - { + public function activateJob( + ilCronJob $job, + DateTimeImmutable $when, + ?ilObjUser $actor = null, + bool $wasManuallyExecuted = false + ): void { $usrId = 0; if ($wasManuallyExecuted && $actor instanceof ilObjUser) { $usrId = $actor->getId(); @@ -365,20 +370,28 @@ public function activateJob(ilCronJob $job, ?ilObjUser $actor = null, bool $wasM ' job_status = ' . $this->db->quote(1, 'integer') . ' , job_status_user_id = ' . $this->db->quote($usrId, 'integer') . ' , job_status_type = ' . $this->db->quote($wasManuallyExecuted, 'integer') . - ' , job_status_ts = ' . $this->db->quote(time(), 'integer') . + ' , job_status_ts = ' . $this->db->quote($when->getTimestamp(), 'integer') . ' WHERE job_id = ' . $this->db->quote($job->getId(), 'text'); $this->db->manipulate($query); } - public function deactivateJob(ilCronJob $job, ilObjUser $actor, bool $wasManuallyExecuted = false): void - { + public function deactivateJob( + ilCronJob $job, + DateTimeImmutable $when, + ilObjUser $actor, + bool $wasManuallyExecuted = false + ): void { $usrId = $wasManuallyExecuted ? $actor->getId() : 0; $query = 'UPDATE cron_job SET ' . ' job_status = ' . $this->db->quote(0, 'integer') . + ' , job_result_status = ' . $this->db->quote(null, 'text') . + ' , job_result_message = ' . $this->db->quote(null, 'text') . + ' , job_result_type = ' . $this->db->quote(null, 'text') . + ' , job_result_code = ' . $this->db->quote(null, 'text') . ' , job_status_user_id = ' . $this->db->quote($usrId, 'integer') . ' , job_status_type = ' . $this->db->quote($wasManuallyExecuted, 'integer') . - ' , job_status_ts = ' . $this->db->quote(time(), 'integer') . + ' , job_status_ts = ' . $this->db->quote($when->getTimestamp(), 'integer') . ' WHERE job_id = ' . $this->db->quote($job->getId(), 'text'); $this->db->manipulate($query); } diff --git a/Services/Cron/classes/class.ilCronManagerGUI.php b/Services/Cron/classes/class.ilCronManagerGUI.php index ca792b09c04f..300c251ecee2 100644 --- a/Services/Cron/classes/class.ilCronManagerGUI.php +++ b/Services/Cron/classes/class.ilCronManagerGUI.php @@ -403,6 +403,8 @@ public function confirmedActivate(): void } $this->tpl->setOnScreenMessage('success', $this->lng->txt('cron_action_activate_success'), true); + } else { + $this->tpl->setOnScreenMessage('info', $this->lng->txt('no_checkbox'), true); } $this->ctrl->redirect($this, 'render'); @@ -428,6 +430,8 @@ public function confirmedDeactivate(): void } $this->tpl->setOnScreenMessage('success', $this->lng->txt('cron_action_deactivate_success'), true); + } else { + $this->tpl->setOnScreenMessage('info', $this->lng->txt('no_checkbox'), true); } $this->ctrl->redirect($this, 'render'); @@ -450,6 +454,8 @@ public function confirmedReset(): void $this->cronManager->resetJob($job, $this->actor); } $this->tpl->setOnScreenMessage('success', $this->lng->txt('cron_action_reset_success'), true); + } else { + $this->tpl->setOnScreenMessage('info', $this->lng->txt('no_checkbox'), true); } $this->ctrl->redirect($this, 'render'); @@ -469,7 +475,7 @@ protected function getMultiActionData(): array } catch (\ILIAS\Refinery\ConstraintViolationException | OutOfBoundsException $e) { $job_ids = $this->getRequestValue('mjid', $this->refinery->kindlyTo()->listOf( $this->refinery->kindlyTo()->string() - ), false); + ), false, []); } } catch (\ILIAS\Refinery\ConstraintViolationException | OutOfBoundsException $e) { } @@ -492,6 +498,7 @@ protected function confirm(string $a_action): void $jobs = $this->getMultiActionData(); if ($jobs === []) { + $this->tpl->setOnScreenMessage('info', $this->lng->txt('no_checkbox'), true); $this->ctrl->redirect($this, 'render'); } diff --git a/Services/Cron/classes/class.ilCronManagerImpl.php b/Services/Cron/classes/class.ilCronManagerImpl.php index b89c57fe3caf..9809aeaab6dc 100644 --- a/Services/Cron/classes/class.ilCronManagerImpl.php +++ b/Services/Cron/classes/class.ilCronManagerImpl.php @@ -1,7 +1,5 @@ - * @ingroup ServicesCron - */ +declare(strict_types=1); + +use ILIAS\Data\Clock\ClockFactory; + class ilCronManagerImpl implements ilCronManager { private ilCronJobRepository $cronRepository; private ilDBInterface $db; private ilSetting $settings; private ilLogger $logger; + private ClockFactory $clock_factory; public function __construct( ilCronJobRepository $cronRepository, ilDBInterface $db, ilSetting $settings, - ilLogger $logger + ilLogger $logger, + ClockFactory $clock_factory ) { $this->cronRepository = $cronRepository; $this->db = $db; $this->settings = $settings; $this->logger = $logger; + $this->clock_factory = $clock_factory; } private function getMicrotime(): float { - [$usec, $sec] = explode(' ', microtime()); - return ((float) $usec + (float) $sec); + return ((int) $this->clock_factory->system()->now()->format('Uu')) / 1000000; } public function runActiveJobs(ilObjUser $actor): void { $this->logger->info('CRON - batch start'); - $ts = time(); + $ts = $this->clock_factory->system()->now()->getTimestamp(); $this->settings->set('last_cronjob_start_ts', (string) $ts); $useRelativeDates = ilDatePresentation::useRelativeDates(); @@ -133,6 +132,10 @@ private function runJob(ilCronJob $job, ilObjUser $actor, ?array $jobData = null $jobData = array_pop($jobsData); } + $job->setDateTimeProvider(function (): DateTimeImmutable { + return $this->clock_factory->system()->now(); + }); + // already running? if ($jobData['alive_ts']) { $this->logger->info('CRON - job ' . $jobData['job_id'] . ' still running'); @@ -140,7 +143,7 @@ private function runJob(ilCronJob $job, ilObjUser $actor, ?array $jobData = null $cut = 60 * 60 * 3; // is running (and has not pinged) for 3 hours straight, we assume it crashed - if (time() - ((int) $jobData['alive_ts']) > $cut) { + if ($this->clock_factory->system()->now()->getTimestamp() - ((int) $jobData['alive_ts']) > $cut) { $this->cronRepository->updateRunInformation($jobData['job_id'], 0, 0); $this->deactivateJob($job, $actor); // #13082 @@ -149,20 +152,32 @@ private function runJob(ilCronJob $job, ilObjUser $actor, ?array $jobData = null $result->setCode(ilCronJobResult::CODE_SUPPOSED_CRASH); $result->setMessage('Cron job deactivated because it has been inactive for 3 hours'); - $this->cronRepository->updateJobResult($job, $actor, $result, $isManualExecution); + $this->cronRepository->updateJobResult( + $job, + $this->clock_factory->system()->now(), + $actor, + $result, + $isManualExecution + ); $this->logger->info('CRON - job ' . $jobData['job_id'] . ' deactivated (assumed crash)'); } } // initiate run? elseif ($job->isDue( - $jobData['job_result_ts'] ? new DateTimeImmutable('@' . $jobData['job_result_ts']) : null, + $jobData['job_result_ts'] ? (new DateTimeImmutable( + '@' . $jobData['job_result_ts'] + ))->setTimezone($this->clock_factory->system()->now()->getTimezone()) : null, $jobData['schedule_type'] ? (int) $jobData['schedule_type'] : null, $jobData['schedule_value'] ? (int) $jobData['schedule_value'] : null, $isManualExecution )) { $this->logger->info('CRON - job ' . $jobData['job_id'] . ' started'); - $this->cronRepository->updateRunInformation($jobData['job_id'], time(), time()); + $this->cronRepository->updateRunInformation( + $jobData['job_id'], + $this->clock_factory->system()->now()->getTimestamp(), + $this->clock_factory->system()->now()->getTimestamp() + ); $ts_in = $this->getMicrotime(); try { @@ -190,7 +205,13 @@ private function runJob(ilCronJob $job, ilObjUser $actor, ?array $jobData = null $result->setDuration($ts_dur); - $this->cronRepository->updateJobResult($job, $actor, $result, $isManualExecution); + $this->cronRepository->updateJobResult( + $job, + $this->clock_factory->system()->now(), + $actor, + $result, + $isManualExecution + ); $this->cronRepository->updateRunInformation($jobData['job_id'], 0, 0); $this->logger->info('CRON - job ' . $jobData['job_id'] . ' finished'); @@ -208,7 +229,13 @@ public function resetJob(ilCronJob $job, ilObjUser $actor): void $result->setCode(ilCronJobResult::CODE_MANUAL_RESET); $result->setMessage('Cron job re-activated by admin'); - $this->cronRepository->updateJobResult($job, $actor, $result, true); + $this->cronRepository->updateJobResult( + $job, + $this->clock_factory->system()->now(), + $actor, + $result, + true + ); $this->cronRepository->resetJob($job); $this->activateJob($job, $actor, true); @@ -216,13 +243,13 @@ public function resetJob(ilCronJob $job, ilObjUser $actor): void public function activateJob(ilCronJob $job, ilObjUser $actor, bool $wasManuallyExecuted = false): void { - $this->cronRepository->activateJob($job, $actor, $wasManuallyExecuted); + $this->cronRepository->activateJob($job, $this->clock_factory->system()->now(), $actor, $wasManuallyExecuted); $job->activationWasToggled($this->db, $this->settings, true); } public function deactivateJob(ilCronJob $job, ilObjUser $actor, bool $wasManuallyExecuted = false): void { - $this->cronRepository->deactivateJob($job, $actor, $wasManuallyExecuted); + $this->cronRepository->deactivateJob($job, $this->clock_factory->system()->now(), $actor, $wasManuallyExecuted); $job->activationWasToggled($this->db, $this->settings, false); } @@ -245,7 +272,7 @@ public function ping(string $jobId): void $this->db->manipulateF( 'UPDATE cron_job SET alive_ts = %s WHERE job_id = %s', ['integer', 'text'], - [time(), $jobId] + [$this->clock_factory->system()->now()->getTimestamp(), $jobId] ); } } diff --git a/Services/Cron/interfaces/interface.ilCronJobRepository.php b/Services/Cron/interfaces/interface.ilCronJobRepository.php index 8ebe4d487b40..be6a2b8f990b 100644 --- a/Services/Cron/interfaces/interface.ilCronJobRepository.php +++ b/Services/Cron/interfaces/interface.ilCronJobRepository.php @@ -1,7 +1,5 @@ createMock(ILIAS\Data\Clock\ClockFactory::class); + $now = new DateTimeImmutable('@' . time()); + $clock_factory->method('system')->willReturn( + new class ($now) implements \ILIAS\Data\Clock\ClockInterface { + private DateTimeImmutable $now; + + public function __construct(DateTimeImmutable $now) + { + $this->now = $now; + } + + public function now(): DateTimeImmutable + { + return $this->now; + } + } + ); + + return $clock_factory; + } + public function testCronManagerActivatesJobWhenJobWasReset(): void { $db = $this->createMock(ilDBInterface::class); @@ -34,15 +57,19 @@ public function testCronManagerActivatesJobWhenJobWasReset(): void ->onlyMethods(['getId']) ->getMock(); + $clock_factory = $this->createClockFactoryMock(); + $cronManager = new ilCronManagerImpl( $repository, $db, $setting, - $logger + $logger, + $clock_factory ); $repository->expects($this->once())->method('updateJobResult')->with( $job, + $clock_factory->system()->now(), $user, $this->isInstanceOf(ilCronJobResult::class), true @@ -54,6 +81,7 @@ public function testCronManagerActivatesJobWhenJobWasReset(): void $repository->expects($this->once())->method('activateJob')->with( $job, + $clock_factory->system()->now(), $user, true ); @@ -79,16 +107,19 @@ public function testCronManagerNotifiesJobWhenJobGetsActivated(): void ->onlyMethods(['getId']) ->getMock(); + $clock_factory = $this->createClockFactoryMock(); + $cronManager = new ilCronManagerImpl( $repository, $db, $setting, - $logger + $logger, + $clock_factory ); $repository->expects($this->exactly(2))->method('activateJob')->withConsecutive( - [$job, $user, true], - [$job, $user, false] + [$job, $clock_factory->system()->now(), $user, true], + [$job, $clock_factory->system()->now(), $user, false] ); $job->expects($this->exactly(2))->method('activationWasToggled')->with( @@ -113,16 +144,19 @@ public function testCronManagerNotifiesJobWhenJobGetsDeactivated(): void ->onlyMethods(['getId']) ->getMock(); + $clock_factory = $this->createClockFactoryMock(); + $cronManager = new ilCronManagerImpl( $repository, $db, $setting, - $logger + $logger, + $clock_factory ); $repository->expects($this->exactly(2))->method('deactivateJob')->withConsecutive( - [$job, $user, true], - [$job, $user, false] + [$job, $clock_factory->system()->now(), $user, true], + [$job, $clock_factory->system()->now(), $user, false] ); $job->expects($this->exactly(2))->method('activationWasToggled')->with( diff --git a/Services/DidacticTemplate/classes/Setting/class.ilDidacticTemplateSettingsGUI.php b/Services/DidacticTemplate/classes/Setting/class.ilDidacticTemplateSettingsGUI.php index c7aef6c39640..4b8a038c2925 100644 --- a/Services/DidacticTemplate/classes/Setting/class.ilDidacticTemplateSettingsGUI.php +++ b/Services/DidacticTemplate/classes/Setting/class.ilDidacticTemplateSettingsGUI.php @@ -288,6 +288,14 @@ protected function importTemplate(): void } catch (ilDidacticTemplateImportException $e) { $this->logger->error('Import failed with message: ' . $e->getMessage()); $this->tpl->setOnScreenMessage('failure', $this->lng->txt('didactic_import_failed') . ': ' . $e->getMessage()); + $form->setValuesByPost(); + + if ($setting instanceof ilDidacticTemplateSetting) { + $this->showEditImportForm($form); + } else { + $this->showImportForm($form); + } + return; } $this->tpl->setOnScreenMessage('success', $this->lng->txt('didactic_import_success'), true); diff --git a/Services/DidacticTemplate/classes/Setting/class.ilDidacticTemplateSettingsTableGUI.php b/Services/DidacticTemplate/classes/Setting/class.ilDidacticTemplateSettingsTableGUI.php index a80f84649f90..991aeb58f2ca 100644 --- a/Services/DidacticTemplate/classes/Setting/class.ilDidacticTemplateSettingsTableGUI.php +++ b/Services/DidacticTemplate/classes/Setting/class.ilDidacticTemplateSettingsTableGUI.php @@ -182,13 +182,15 @@ protected function fillRow(array $a_set): void ); // Export - $actions->addItem( - $this->lng->txt('didactic_do_export'), - '', - $this->ctrl->getLinkTargetByClass(get_class($this->getParentObject()), 'exportTemplate') - ); + if (!$a_set['automatic_generated']) { + $actions->addItem( + $this->lng->txt('didactic_do_export'), + '', + $this->ctrl->getLinkTargetByClass(get_class($this->getParentObject()), 'exportTemplate') + ); + } $this->tpl->setVariable('ACTION_DROPDOWN', $actions->getHTML()); - } else { + } elseif (!$a_set['automatic_generated']) { //don't use dropdown if just one item is given ... // Export $this->tpl->setCurrentBlock('action_link'); @@ -198,6 +200,8 @@ protected function fillRow(array $a_set): void ); $this->tpl->setVariable('A_TEXT', $this->lng->txt('didactic_do_export')); $this->tpl->parseCurrentBlock(); + } else { + $this->tpl->touchBlock('action_link'); } } } diff --git a/Services/DidacticTemplate/classes/class.ilDidacticTemplateImport.php b/Services/DidacticTemplate/classes/class.ilDidacticTemplateImport.php index c55255925ccf..651f755dbd42 100644 --- a/Services/DidacticTemplate/classes/class.ilDidacticTemplateImport.php +++ b/Services/DidacticTemplate/classes/class.ilDidacticTemplateImport.php @@ -273,9 +273,15 @@ protected function parseActions(ilDidacticTemplateSetting $set, SimpleXMLElement // extract role foreach ($lpo->role as $roleDef) { - $rimporter = new ilRoleXmlImporter(ROLE_FOLDER_ID); - $role_id = $rimporter->importSimpleXml($roleDef); - $act->setRoleTemplateId($role_id); + try { + $rimporter = new ilRoleXmlImporter(ROLE_FOLDER_ID); + $role_id = $rimporter->importSimpleXml($roleDef); + $act->setRoleTemplateId($role_id); + } catch (ilRoleImporterException $e) { + // delete half-imported template + $set->delete(); + throw new ilDidacticTemplateImportException($e->getMessage()); + } } } diff --git a/Services/Exceptions/classes/class.ilDelegatingHandler.php b/Services/Exceptions/classes/class.ilDelegatingHandler.php index abae3e1f81c0..2ef32e84c398 100644 --- a/Services/Exceptions/classes/class.ilDelegatingHandler.php +++ b/Services/Exceptions/classes/class.ilDelegatingHandler.php @@ -1,7 +1,5 @@ hideSensitiveData($_SERVER); - $handler = $this->error_handling->getHandler(); - $handler->setRun($this->getRun()); - $handler->setException($this->getException()); - $handler->setInspector($this->getInspector()); - $handler->handle(); - return null; + $this->current_handler = $this->error_handling->getHandler(); + $this->current_handler->setRun($this->getRun()); + $this->current_handler->setException($this->getException()); + $this->current_handler->setInspector($this->getInspector()); + return $this->current_handler->handle(); + } + + /** + * This is an implicit interface method of the Whoops handlers + * @see: \Whoops\Run::handleException + */ + public function contentType(): ?string + { + if ($this->current_handler === null || + !method_exists($this->current_handler, 'contentType')) { + return null; + } + + return $this->current_handler->contentType(); } } diff --git a/Services/Exceptions/classes/class.ilPlainTextHandler.php b/Services/Exceptions/classes/class.ilPlainTextHandler.php index 2aa37fb34650..1018bc8cec3a 100644 --- a/Services/Exceptions/classes/class.ilPlainTextHandler.php +++ b/Services/Exceptions/classes/class.ilPlainTextHandler.php @@ -1,7 +1,5 @@ */ -class ilPlainTextHandler extends Handler +class ilPlainTextHandler extends \Whoops\Handler\PlainTextHandler { protected const KEY_SPACE = 25; + /** @var list */ + private array $exclusion_list = []; + /** - * Last missing method from HandlerInterface. + * @param list $exclusion_list */ - public function handle(): ?int + public function withExclusionList(array $exclusion_list): self { - header("Content-Type: text/plain"); - echo "
\n";
-        echo $this->content();
-        echo "
\n"; - return null; + $clone = clone $this; + $clone->exclusion_list = $exclusion_list; + return $clone; } - /** - * Assemble the output for this handler. - */ - protected function content(): string + private function stripNullBytes(string $ret): string { - return $this->pageHeader() - . $this->exceptionContent() - . $this->tablesContent(); + return str_replace("\0", '', $ret); } - /** - * Get the header for the page. - */ - protected function pageHeader(): string + public function generateResponse(): string { - return ""; + return $this->getExceptionOutput() . $this->tablesContent() . "\n"; } /** * Get a short info about the exception. */ - protected function exceptionContent(): string + protected function getExceptionOutput(): string { return Formatter::formatExceptionPlain($this->getInspector()); } @@ -73,7 +65,7 @@ protected function exceptionContent(): string */ protected function tablesContent(): string { - $ret = ""; + $ret = ''; foreach ($this->tables() as $title => $content) { $ret .= "\n\n-- $title --\n\n"; if (count($content) > 0) { @@ -83,7 +75,7 @@ protected function tablesContent(): string // indent multiline values, first print_r, split in lines, // indent all but first line, then implode again. $first = true; - $indentation = str_pad("", self::KEY_SPACE); + $indentation = str_pad('', self::KEY_SPACE); $value = implode( "\n", array_map( @@ -104,7 +96,8 @@ static function ($line) use (&$first, $indentation): string { $ret .= "empty\n"; } } - return $ret; + + return $this->stripNullBytes($ret); } /** @@ -112,14 +105,62 @@ static function ($line) use (&$first, $indentation): string { */ protected function tables(): array { + $post = $_POST; + $server = $_SERVER; + + $post = $this->hideSensitiveData($post); + $server = $this->hideSensitiveData($server); + $server = $this->shortenPHPSessionId($server); + return [ - "GET Data" => $_GET, - "POST Data" => $_POST, - "Files" => $_FILES, - "Cookies" => $_COOKIE, - "Session" => $_SESSION ?? [], - "Server/Request Data" => $_SERVER, - "Environment Variables" => $_ENV, + 'GET Data' => $_GET, + 'POST Data' => $post, + 'Files' => $_FILES, + 'Cookies' => $_COOKIE, + 'Session' => $_SESSION ?? [], + 'Server/Request Data' => $server, + 'Environment Variables' => $_ENV, ]; } + + /** + * @param array $super_global + * @return array + */ + private function hideSensitiveData(array $super_global): array + { + foreach ($this->exclusion_list as $parameter) { + if (isset($super_global[$parameter])) { + $super_global[$parameter] = 'REMOVED FOR SECURITY'; + } + + if (isset($super_global['post_vars'][$parameter])) { + $super_global['post_vars'][$parameter] = 'REMOVED FOR SECURITY'; + } + } + + return $super_global; + } + + /** + * @param array $server + * @return array + */ + private function shortenPHPSessionId(array $server): array + { + $cookie_content = $server['HTTP_COOKIE']; + $cookie_content = explode(';', $cookie_content); + + foreach ($cookie_content as $key => $content) { + $content_array = explode('=', $content); + if (trim($content_array[0]) === session_name()) { + $content_array[1] = substr($content_array[1], 0, 5) . ' (SHORTENED FOR SECURITY)'; + $cookie_content[$key] = implode('=', $content_array); + } + } + + $server['HTTP_COOKIE'] = implode(';', $cookie_content); + + return $server; + } } diff --git a/Services/Exceptions/classes/class.ilTestingHandler.php b/Services/Exceptions/classes/class.ilTestingHandler.php index d427edf33888..bd5b969c52ea 100644 --- a/Services/Exceptions/classes/class.ilTestingHandler.php +++ b/Services/Exceptions/classes/class.ilTestingHandler.php @@ -1,7 +1,5 @@ pageHeader() - . $this->exceptionContent(); + return "DEAR TESTER! AN ERROR OCCURRED... PLEASE INCLUDE THE FOLLOWING OUTPUT AS ADDITIONAL INFORMATION IN YOUR BUG REPORT.\n\n" + . $this->getExceptionOutput(); } } diff --git a/Services/Export/classes/class.ilExport.php b/Services/Export/classes/class.ilExport.php index 72b4f86484b0..c165d8cae69f 100644 --- a/Services/Export/classes/class.ilExport.php +++ b/Services/Export/classes/class.ilExport.php @@ -378,6 +378,7 @@ public function exportObject( // zip the file $this->log->debug("zip: " . $export_dir . "/" . $new_file); + $this->log->debug("run dir: " . $this->export_run_dir); ilFileUtils::zip($this->export_run_dir, $export_dir . "/" . $new_file); ilFileUtils::delDir($this->export_run_dir); diff --git a/Services/Feeds/classes/class.ilUserFeedWriter.php b/Services/Feeds/classes/class.ilUserFeedWriter.php index 73ed3fa0b26b..cba638cf5660 100644 --- a/Services/Feeds/classes/class.ilUserFeedWriter.php +++ b/Services/Feeds/classes/class.ilUserFeedWriter.php @@ -84,9 +84,9 @@ public function __construct( $title = ilNewsItem::determineNewsTitle( $item["context_obj_type"], $item["title"], - $item["content_is_lang_var"], - $item["agg_ref_id"], - $item["aggregation"] + (bool) $item["content_is_lang_var"], + (int) ($item["agg_ref_id"] ?? 0), + $item["aggregation"] ?? [] ); // path diff --git a/Services/FileServices/classes/class.ilFileUtils.php b/Services/FileServices/classes/class.ilFileUtils.php index 38ba0cb4127f..b38fd102e10d 100644 --- a/Services/FileServices/classes/class.ilFileUtils.php +++ b/Services/FileServices/classes/class.ilFileUtils.php @@ -899,7 +899,12 @@ public static function unzip( // rename executables self::renameExecutables($unzippable_zip_directory); - // now we have to move the files to the original directory. if $a_flat is true, we move the files only without directories, otherwise we move the whole directory + // now we have to move the files to the original directory. + // if $a_flat is true, we move the files only without directories, otherwise we move the whole directory. + // since some provide a realtive path here, we have to get the absolute path first + $target_dir_name = $original_zip_path_info["dirname"]; + $target_dir_name = realpath($target_dir_name); + if ($unpack_flat) { $file_array = []; self::recursive_dirscan($temporary_unzip_directory, $file_array); @@ -911,13 +916,17 @@ public static function unzip( ) { copy( $file_array["path"][$k] . $f, - $original_zip_path_info["dirname"] . DIRECTORY_SEPARATOR . $f + $target_dir_name . DIRECTORY_SEPARATOR . $f ); } } } } else { - self::rCopy($temporary_unzip_directory, $original_zip_path_info["dirname"]); + $target_directory = $target_dir_name; + self::rCopy( + $temporary_unzip_directory, + $target_directory + ); } self::delDir($temporary_unzip_directory); diff --git a/Services/Form/classes/class.ilCheckboxInputGUI.php b/Services/Form/classes/class.ilCheckboxInputGUI.php index 09f0f8939841..bbd8a2180fa8 100755 --- a/Services/Form/classes/class.ilCheckboxInputGUI.php +++ b/Services/Form/classes/class.ilCheckboxInputGUI.php @@ -1,7 +1,5 @@ setVariable("ARIA_LABEL", ilLegacyFormElementsUtil::prepareFormOutput($this->getTitle())); + if ($this->getInfo() !== '') { + $tpl->setVariable('DESCRIBED_BY_FIELD_ID', $this->getFieldId()); + } return $tpl->get(); } diff --git a/Services/Form/classes/class.ilColorPickerInputGUI.php b/Services/Form/classes/class.ilColorPickerInputGUI.php index 9e9e80bc865d..1faa1aaff577 100644 --- a/Services/Form/classes/class.ilColorPickerInputGUI.php +++ b/Services/Form/classes/class.ilColorPickerInputGUI.php @@ -180,7 +180,7 @@ public function insert(ilTemplate $a_tpl): void $tpl->setVariable("POST_VAR", $this->getPostVar()); $tpl->setVariable("PROP_COLOR_ID", $this->getFieldId()); - if (substr(trim($this->getValue()), 0, 1) == "!" && $this->getAcceptNamedColors()) { + if (substr(trim($this->getValue() ?? ""), 0, 1) == "!" && $this->getAcceptNamedColors()) { $tpl->setVariable( "PROPERTY_VALUE_COLOR", ilLegacyFormElementsUtil::prepareFormOutput(trim($this->getValue())) diff --git a/Services/Form/classes/class.ilLocationInputGUI.php b/Services/Form/classes/class.ilLocationInputGUI.php index df9e239d6d19..d1d0210296eb 100755 --- a/Services/Form/classes/class.ilLocationInputGUI.php +++ b/Services/Form/classes/class.ilLocationInputGUI.php @@ -1,7 +1,5 @@ lng; $val = $this->strArray($this->getPostVar()); - if ($this->getRequired() && - (trim($val["latitude"]) == "" || trim($val["longitude"]) == "")) { + if ($this->getRequired() && ( + !isset($val["latitude"]) || trim($val["latitude"]) == "" || + !isset($val["longitude"]) || trim($val["longitude"]) == "" + )) { $this->setAlert($lng->txt("msg_input_is_required")); return false; } @@ -113,8 +115,8 @@ public function getInput(): array { $val = $this->strArray($this->getPostVar()); return [ - "latitude" => (float) $val["latitude"], - "longitude" => (float) $val["longitude"], + "latitude" => (float) ($val["latitude"] ?? 0), + "longitude" => (float) ($val["longitude"] ?? 0), "zoom" => (int) ($val["zoom"] ?? 0), "address" => ($val["address"] ?? "") ]; diff --git a/Services/Form/classes/class.ilNumberInputGUI.php b/Services/Form/classes/class.ilNumberInputGUI.php index 3010abceccb1..d46447b76a78 100755 --- a/Services/Form/classes/class.ilNumberInputGUI.php +++ b/Services/Form/classes/class.ilNumberInputGUI.php @@ -1,7 +1,5 @@ setVariable("PROPERTY_VALUE", ilLegacyFormElementsUtil::prepareFormOutput((string) $this->getValue())); $tpl->parseCurrentBlock(); } + + if ($this->getInfo() !== '') { + $tpl->setCurrentBlock('described_by_description'); + $tpl->setVariable('DESCRIBED_BY_DESCRIPTION_FIELD_ID', $this->getFieldId()); + $tpl->parseCurrentBlock(); + } + + // constraints + $constraints = ""; + $delim = ""; + if ($this->areDecimalsAllowed() && $this->getDecimals() > 0) { + $constraints = $lng->txt("form_format") . ": ###." . str_repeat("#", $this->getDecimals()); + $delim = ", "; + } + if ($this->getMinValue() !== null && $this->minvalue_visible) { + $constraints .= $delim . $lng->txt("form_min_value") . ": " . (($this->minvalueShouldBeGreater()) ? "> " : "") . $this->getMinValue(); + $delim = ", "; + } + if ($this->getMaxValue() !== null && $this->maxvalue_visible) { + $constraints .= $delim . $lng->txt("form_max_value") . ": " . (($this->maxvalueShouldBeLess()) ? "< " : "") . $this->getMaxValue(); + $delim = ", "; + } + + if ($constraints !== "") { + $tpl->setCurrentBlock('described_by_constraint'); + $tpl->setVariable('DESCRIBED_BY_CONSTRAINT_FIELD_ID', $this->getFieldId()); + $tpl->parseCurrentBlock(); + } + $tpl->setCurrentBlock("prop_number"); $tpl->setVariable("POST_VAR", $this->getPostVar()); @@ -300,24 +329,12 @@ public function render(): string $tpl->setVariable("JS_ID", $this->getFieldId()); } - - // constraints - $constraints = ""; - $delim = ""; - if ($this->areDecimalsAllowed() && $this->getDecimals() > 0) { - $constraints = $lng->txt("form_format") . ": ###." . str_repeat("#", $this->getDecimals()); - $delim = ", "; - } - if ($this->getMinValue() !== null && $this->minvalue_visible) { - $constraints .= $delim . $lng->txt("form_min_value") . ": " . (($this->minvalueShouldBeGreater()) ? "> " : "") . $this->getMinValue(); - $delim = ", "; - } - if ($this->getMaxValue() !== null && $this->maxvalue_visible) { - $constraints .= $delim . $lng->txt("form_max_value") . ": " . (($this->maxvalueShouldBeLess()) ? "< " : "") . $this->getMaxValue(); - $delim = ", "; - } - if ($constraints != "") { + if ($constraints !== '') { $tpl->setVariable("TXT_NUMBER_CONSTRAINTS", $constraints); + $tpl->setVariable( + "CONSTRAINT_FOR_ID", + $this->getFieldId() + ); } if ($this->getRequired()) { diff --git a/Services/Form/classes/class.ilPropertyFormGUI.php b/Services/Form/classes/class.ilPropertyFormGUI.php index 8ff381db489f..b15890882df1 100644 --- a/Services/Form/classes/class.ilPropertyFormGUI.php +++ b/Services/Form/classes/class.ilPropertyFormGUI.php @@ -680,6 +680,10 @@ public function insertItem( "PROPERTY_DESCRIPTION", $item->getInfo() ); + $this->tpl->setVariable( + "DESCRIPTION_FOR_ID", + $item->getFieldId() + ); $this->tpl->parseCurrentBlock(); } diff --git a/Services/Form/classes/class.ilRadioGroupInputGUI.php b/Services/Form/classes/class.ilRadioGroupInputGUI.php index a4414e21205b..6d8c5921aa69 100755 --- a/Services/Form/classes/class.ilRadioGroupInputGUI.php +++ b/Services/Form/classes/class.ilRadioGroupInputGUI.php @@ -1,7 +1,5 @@ getInfo() != "") { $tpl->setCurrentBlock("radio_option_desc"); $tpl->setVariable("RADIO_OPTION_DESC", $option->getInfo()); + if ($option->getInfo() !== '') { + $tpl->setVariable('DESCRIPTION_FOR_ID', $this->getFieldId() . "_" . $option->getValue()); + } $tpl->parseCurrentBlock(); } @@ -154,6 +157,9 @@ public function render(): string $tpl->setVariable("POST_VAR", $this->getPostVar()); $tpl->setVariable("VAL_RADIO_OPTION", $option->getValue()); $tpl->setVariable("OP_ID", $this->getFieldId() . "_" . $option->getValue()); + if ($option->getInfo() !== '') { + $tpl->setVariable('DESCRIBED_BY_FIELD_ID', $this->getFieldId() . "_" . $option->getValue()); + } $tpl->setVariable("FID", $this->getFieldId()); if ($this->getDisabled() or $option->getDisabled()) { $tpl->setVariable('DISABLED', 'disabled="disabled" '); diff --git a/Services/Form/classes/class.ilTextAreaInputGUI.php b/Services/Form/classes/class.ilTextAreaInputGUI.php index e3135cd3c2a5..f8069bae4504 100755 --- a/Services/Form/classes/class.ilTextAreaInputGUI.php +++ b/Services/Form/classes/class.ilTextAreaInputGUI.php @@ -1,7 +1,5 @@ getInfo() !== '') { + $ttpl->setCurrentBlock('described_by_description'); + $ttpl->setVariable('DESCRIBED_BY_DESCRIPTION_FIELD_ID', $this->getFieldId()); + $ttpl->parseCurrentBlock(); + } + // disabled rte if ($this->getUseRte() && $this->getDisabled()) { $ttpl->setCurrentBlock("disabled_rte"); diff --git a/Services/Form/classes/class.ilTextInputGUI.php b/Services/Form/classes/class.ilTextInputGUI.php index 44ec39ba59b6..c3332727dcef 100755 --- a/Services/Form/classes/class.ilTextInputGUI.php +++ b/Services/Form/classes/class.ilTextInputGUI.php @@ -414,6 +414,9 @@ public function render(string $a_mode = ""): string } $tpl->setVariable("ARIA_LABEL", ilLegacyFormElementsUtil::prepareFormOutput($this->getTitle())); + if ($this->getInfo() !== '') { + $tpl->setVariable('DESCRIBED_BY_FIELD_ID', $this->getFieldId()); + } return $tpl->get(); } diff --git a/Services/Form/js/ServiceFormMulti.js b/Services/Form/js/ServiceFormMulti.js index 6b6c49e0db72..a2c83f40879b 100644 --- a/Services/Form/js/ServiceFormMulti.js +++ b/Services/Form/js/ServiceFormMulti.js @@ -261,7 +261,7 @@ var ilMultiFormValues = { // try to set value if(preset != '') { - $(element).find('select[id*="' + group_id + '"] option[value="' + preset + '"]').attr('selected', true); + $(element).find('select[id*="' + group_id + '"] option[value="' + il.Form.escapeSelector(preset) + '"]').attr('selected', true); $(element).find('input:text[id*="' + group_id + '"]').attr('value', preset); } else { diff --git a/Services/Form/templates/default/tpl.prop_checkbox.html b/Services/Form/templates/default/tpl.prop_checkbox.html index edfd28b1b74d..d6b1bd74ef9e 100644 --- a/Services/Form/templates/default/tpl.prop_checkbox.html +++ b/Services/Form/templates/default/tpl.prop_checkbox.html @@ -1,5 +1,5 @@
- +aria-describedby="desc_{DESCRIBED_BY_FIELD_ID}" onclick="il.Form.showSubForm('subform_{ID}','il_prop_cont_{ID}', this);" type="checkbox" id="{ID}" name="{POST_VAR}" value="{PROPERTY_VALUE}" {PROPERTY_CHECKED} {DISABLED} {PROP_CHECK_ATTRS} /> {OPTION_TITLE} 
diff --git a/Services/Form/templates/default/tpl.prop_number.html b/Services/Form/templates/default/tpl.prop_number.html index feea24a6ac67..652b129ee095 100644 --- a/Services/Form/templates/default/tpl.prop_number.html +++ b/Services/Form/templates/default/tpl.prop_number.html @@ -1,7 +1,10 @@
- value="{PROPERTY_VALUE}" {DISABLED} {REQUIRED} /> {INPUT_SUFFIX} -
{TXT_NUMBER_CONSTRAINTS}
+ aria-describedby="desc_{DESCRIBED_BY_DESCRIPTION_FIELD_ID} con_{DESCRIBED_BY_CONSTRAINT_FIELD_ID}" + value="{PROPERTY_VALUE}" {DISABLED} {REQUIRED} /> {INPUT_SUFFIX} +
{TXT_NUMBER_CONSTRAINTS}
diff --git a/Services/Form/templates/default/tpl.prop_radio.html b/Services/Form/templates/default/tpl.prop_radio.html index 439cadd74583..99d0acbb94b1 100644 --- a/Services/Form/templates/default/tpl.prop_radio.html +++ b/Services/Form/templates/default/tpl.prop_radio.html @@ -1,10 +1,10 @@
-
diff --git a/Services/Form/templates/default/tpl.prop_textarea.html b/Services/Form/templates/default/tpl.prop_textarea.html index 6c060f478bbd..c98b93f165fb 100755 --- a/Services/Form/templates/default/tpl.prop_textarea.html +++ b/Services/Form/templates/default/tpl.prop_textarea.html @@ -1,5 +1,8 @@ - +
diff --git a/Services/Form/templates/default/tpl.prop_textinput.html b/Services/Form/templates/default/tpl.prop_textinput.html index 2a1deeb69cc6..50a9476d79d7 100644 --- a/Services/Form/templates/default/tpl.prop_textinput.html +++ b/Services/Form/templates/default/tpl.prop_textinput.html @@ -1,7 +1,7 @@
- onkeypress="return il.Util.submitOnEnter(event, this.form);" type="{PROP_INPUT_TYPE}" style="{CSS_STYLE}" id="{ID}" maxlength="{MAXLENGTH}" name="{POST_VAR}" value="{PROPERTY_VALUE}" {DISABLED} {AUTOCOMPLETE} {REQUIRED}/> {INPUT_SUFFIX} + aria-describedby="desc_{DESCRIBED_BY_FIELD_ID}" class="form-control" onkeypress="return il.Util.submitOnEnter(event, this.form);" type="{PROP_INPUT_TYPE}" style="{CSS_STYLE}" id="{ID}" maxlength="{MAXLENGTH}" name="{POST_VAR}" value="{PROPERTY_VALUE}" {DISABLED} {AUTOCOMPLETE} {REQUIRED}/> {INPUT_SUFFIX} {HIDDEN_INPUT} {MULTI_ICONS} diff --git a/Services/Form/templates/default/tpl.property_form.html b/Services/Form/templates/default/tpl.property_form.html index c97fa6e523e1..034cfc475778 100644 --- a/Services/Form/templates/default/tpl.property_form.html +++ b/Services/Form/templates/default/tpl.property_form.html @@ -82,7 +82,7 @@
-
{PROPERTY_DESCRIPTION}
+
{PROPERTY_DESCRIPTION}
{PROP_SUB_FORM}
diff --git a/Services/Imprint/classes/class.ilImprintGUI.php b/Services/Imprint/classes/class.ilImprintGUI.php index a5b96ae4a001..d6926d9ea3b9 100644 --- a/Services/Imprint/classes/class.ilImprintGUI.php +++ b/Services/Imprint/classes/class.ilImprintGUI.php @@ -86,7 +86,9 @@ public function executeCommand(): string $title, $this->ctrl->getLinkTarget($this, "preview") ); - return parent::executeCommand(); + $ret = parent::executeCommand(); + $this->tabs_gui->activateTab("pg"); + return $ret; } } diff --git a/Services/InfoScreen/classes/class.ilInfoScreenGUI.php b/Services/InfoScreen/classes/class.ilInfoScreenGUI.php index 514356f49e7a..2a004ea45d25 100644 --- a/Services/InfoScreen/classes/class.ilInfoScreenGUI.php +++ b/Services/InfoScreen/classes/class.ilInfoScreenGUI.php @@ -993,7 +993,7 @@ public function showNotesSection(): string // global switch if ($ilSetting->get("disable_comments")) { - $notes_gui->enablePublicNotes(false); + return ""; } else { $ref_id = $this->gui_object->getObject()->getRefId(); $has_write = $ilAccess->checkAccess("write", "", $ref_id); diff --git a/Services/Init/classes/class.ilErrorHandling.php b/Services/Init/classes/class.ilErrorHandling.php index 070e6e760e70..9abdce2b706a 100755 --- a/Services/Init/classes/class.ilErrorHandling.php +++ b/Services/Init/classes/class.ilErrorHandling.php @@ -37,6 +37,17 @@ */ class ilErrorHandling extends PEAR { + private const SENSTIVE_PARAMETER_NAMES = [ + 'password', + 'passwd', + 'passwd_retype', + 'current_password', + 'usr_password', + 'usr_password_retype', + 'new_password', + 'new_password_retype', + ]; + protected ?RunInterface $whoops; protected string $message; @@ -287,6 +298,7 @@ protected function defaultHandler(): HandlerInterface $logger = ilLoggingErrorSettings::getInstance(); if (!empty($logger->folder())) { $lwriter = new ilLoggingErrorFileStorage($inspector, $logger->folder(), $file_name); + $lwriter = $lwriter->withExclusionList(self::SENSTIVE_PARAMETER_NAMES); $lwriter->write(); } @@ -330,10 +342,10 @@ protected function devmodeHandler(): HandlerInterface switch (ERROR_HANDLER) { case "TESTING": - return new ilTestingHandler(); + return (new ilTestingHandler())->withExclusionList(self::SENSTIVE_PARAMETER_NAMES); case "PLAIN_TEXT": - return new ilPlainTextHandler(); + return (new ilPlainTextHandler())->withExclusionList(self::SENSTIVE_PARAMETER_NAMES); case "PRETTY_PAGE": // fallthrough @@ -349,6 +361,10 @@ protected function devmodeHandler(): HandlerInterface $this->addEditorSupport($prettyPageHandler); + foreach (self::SENSTIVE_PARAMETER_NAMES as $param) { + $prettyPageHandler->blacklist('_POST', $param); + } + return $prettyPageHandler; } } diff --git a/Services/Init/classes/class.ilInitialisation.php b/Services/Init/classes/class.ilInitialisation.php index f48fdb0d5c17..b519e3c0896e 100644 --- a/Services/Init/classes/class.ilInitialisation.php +++ b/Services/Init/classes/class.ilInitialisation.php @@ -1,4 +1,5 @@ database(), $c->settings(), - $c->logger()->cron() + $c->logger()->cron(), + (new \ILIAS\Data\Factory())->clock() ); }; } @@ -970,11 +972,12 @@ protected static function goToLogin(): void { global $DIC; - $a_auth_stat = ""; + $session_expired = false; ilLoggerFactory::getLogger('init')->debug('Redirecting to login page.'); if ($DIC['ilAuthSession']->isExpired()) { ilSession::setClosingContext(ilSession::SESSION_CLOSE_EXPIRE); + $session_expired = true; } if (!$DIC['ilAuthSession']->isAuthenticated()) { ilSession::setClosingContext(ilSession::SESSION_CLOSE_LOGIN); @@ -999,8 +1002,8 @@ protected static function goToLogin(): void ]) ); - $script = "login.php?" . $target . "client_id=" . $client_id . - "&auth_stat=" . $a_auth_stat; + $script = "login.php?" . $target . "client_id=" . $client_id; + $script .= $session_expired ? "&session_expired=1" : ""; self::redirect( $script, @@ -1373,6 +1376,10 @@ public static function resumeUserSession(): void !$DIC['ilAuthSession']->isAuthenticated() or $DIC['ilAuthSession']->isExpired() ) { + if ($GLOBALS['DIC']['ilAuthSession']->isExpired()) { + ilSession::_destroy($_COOKIE[session_name()], ilSession::SESSION_CLOSE_EXPIRE); + } + ilLoggerFactory::getLogger('init')->debug('Current session is invalid: ' . $GLOBALS['DIC']['ilAuthSession']->getId()); $current_script = substr(strrchr($_SERVER["PHP_SELF"], "/"), 1); if (self::blockedAuthentication($current_script)) { @@ -1468,7 +1475,6 @@ private static function initGlobalScreen(\ILIAS\DI\Container $c): void }; $c->globalScreen()->tool()->context()->stack()->clear(); $c->globalScreen()->tool()->context()->claim()->main(); -// $c->globalScreen()->tool()->context()->current()->addAdditionalData('DEVMODE', (bool) DEVMODE); } /** @@ -1729,7 +1735,7 @@ protected static function translateMessage(string $a_message_id, array $a_messag $lang = "en"; if ($ilUser) { $lang = $ilUser->getLanguage(); - } elseif ($_REQUEST["lang"]) { + } elseif (isset($_REQUEST["lang"])) { $lang = (string) $_REQUEST["lang"]; } elseif ($ilSetting) { $lang = $ilSetting->get("language", ''); diff --git a/Services/Init/classes/class.ilStartUpGUI.php b/Services/Init/classes/class.ilStartUpGUI.php index b762260792c4..e93042b2eb38 100755 --- a/Services/Init/classes/class.ilStartUpGUI.php +++ b/Services/Init/classes/class.ilStartUpGUI.php @@ -308,7 +308,7 @@ protected function showLoginPage(ilPropertyFormGUI $form = null): void $page_editor_html = $this->purgePlaceholders($page_editor_html); // check expired session and send message - if ($this->authSession->isExpired()) { + if ($this->authSession->isExpired() || $this->http->wrapper()->query()->has('session_expired')) { $this->mainTemplate->setOnScreenMessage('failure', $this->lng->txt('auth_err_expired')); } elseif ($this->http->wrapper()->query()->has('reg_confirmation_msg')) { $this->lng->loadLanguageModule('registration'); diff --git a/Services/JavaScript/js/Basic.js b/Services/JavaScript/js/Basic.js index c4691e35e3b8..7639bde31f0a 100644 --- a/Services/JavaScript/js/Basic.js +++ b/Services/JavaScript/js/Basic.js @@ -224,13 +224,12 @@ il.Util = { fixPosition: function (el) { var r = il.Util.getRegion(el), vp = il.Util.getViewportRegion(); - // we only fix absolute positioned items if ($(el).css("position") != "absolute") { return; } - if (vp.right - 20 < r.right) { + if (vp.right - 15 < r.right) { il.Util.setX(el, r.x - (r.right - vp.right + 20)); } @@ -570,18 +569,22 @@ il.UICore = { var tabs = $('#ilTab.ilCollapsable'), tabsHeight, count, children, collapsed; if (tabs) { tabsHeight = tabs.innerHeight(); - if (tabsHeight >= 50) { - if (tabsHeight > 50) { - $('#ilLastTab a').removeClass("ilNoDisplay"); - } + let more_than_two_lines; + more_than_two_lines = tabsHeight >= 50; + if (more_than_two_lines) { + $('#ilLastTab a').removeClass('ilNoDisplay'); // as long as we have two lines... - while (tabsHeight > 50) { + while (more_than_two_lines) { children = tabs.children('li:not(:last-child)'); count = children.length; // ...put last child into collapsed drop down $(children[count-1]).prependTo('#ilTabDropDown'); - tabsHeight = tabs.innerHeight(); + if(count == 0) { + more_than_two_lines = false; + } else { + more_than_two_lines = tabs.innerHeight() >= 50; + } } } else { // as long as we have one line... diff --git a/Services/LDAP/classes/class.ilAuthProviderLDAP.php b/Services/LDAP/classes/class.ilAuthProviderLDAP.php index b7d137570257..7c0cd799579e 100644 --- a/Services/LDAP/classes/class.ilAuthProviderLDAP.php +++ b/Services/LDAP/classes/class.ilAuthProviderLDAP.php @@ -1,7 +1,5 @@ diff --git a/Services/LDAP/classes/class.ilLDAPAttributeMapping.php b/Services/LDAP/classes/class.ilLDAPAttributeMapping.php index 309e22332665..72422975d5cc 100644 --- a/Services/LDAP/classes/class.ilLDAPAttributeMapping.php +++ b/Services/LDAP/classes/class.ilLDAPAttributeMapping.php @@ -1,7 +1,5 @@ diff --git a/Services/LDAP/classes/class.ilLDAPPlugin.php b/Services/LDAP/classes/class.ilLDAPPlugin.php index 3cabfea7f29a..d3046cb6d198 100644 --- a/Services/LDAP/classes/class.ilLDAPPlugin.php +++ b/Services/LDAP/classes/class.ilLDAPPlugin.php @@ -1,7 +1,5 @@ @@ -593,8 +593,8 @@ public function bind(int $a_binding_type = ilLDAPQuery::LDAP_BIND_DEFAULT, strin /** @noinspection PhpMissingBreakStatementInspection */ case self::LDAP_BIND_TEST: ldap_set_option($this->lh, LDAP_OPT_NETWORK_TIMEOUT, ilLDAPServer::DEFAULT_NETWORK_TIMEOUT); - // fall through - // no break + // fall through + // no break case self::LDAP_BIND_DEFAULT: // Now bind anonymously or as user if ( diff --git a/Services/LDAP/classes/class.ilLDAPQueryException.php b/Services/LDAP/classes/class.ilLDAPQueryException.php index b8a4888cad30..d6d0d9e0e49a 100644 --- a/Services/LDAP/classes/class.ilLDAPQueryException.php +++ b/Services/LDAP/classes/class.ilLDAPQueryException.php @@ -1,7 +1,5 @@ diff --git a/Services/LDAP/classes/class.ilLDAPResult.php b/Services/LDAP/classes/class.ilLDAPResult.php index c335bcf6f9cb..8fff630098c1 100644 --- a/Services/LDAP/classes/class.ilLDAPResult.php +++ b/Services/LDAP/classes/class.ilLDAPResult.php @@ -1,7 +1,5 @@ diff --git a/Services/LDAP/classes/class.ilLDAPRoleAssignmentRules.php b/Services/LDAP/classes/class.ilLDAPRoleAssignmentRules.php index 293de0c63265..b45e6b978dd8 100644 --- a/Services/LDAP/classes/class.ilLDAPRoleAssignmentRules.php +++ b/Services/LDAP/classes/class.ilLDAPRoleAssignmentRules.php @@ -1,7 +1,5 @@ diff --git a/Services/LDAP/classes/class.ilLDAPRoleGroupMapping.php b/Services/LDAP/classes/class.ilLDAPRoleGroupMapping.php index dd1a2dbf6151..cccc9c4d8980 100644 --- a/Services/LDAP/classes/class.ilLDAPRoleGroupMapping.php +++ b/Services/LDAP/classes/class.ilLDAPRoleGroupMapping.php @@ -1,7 +1,5 @@ */ diff --git a/Services/LDAP/classes/class.ilLDAPRoleGroupMappingSetting.php b/Services/LDAP/classes/class.ilLDAPRoleGroupMappingSetting.php index 1a87955d7a5a..6cbb6c0a097a 100644 --- a/Services/LDAP/classes/class.ilLDAPRoleGroupMappingSetting.php +++ b/Services/LDAP/classes/class.ilLDAPRoleGroupMappingSetting.php @@ -1,7 +1,5 @@ */ diff --git a/Services/LDAP/classes/class.ilLDAPRoleGroupMappingSettings.php b/Services/LDAP/classes/class.ilLDAPRoleGroupMappingSettings.php index 378b4b5f0a9c..562455932abc 100644 --- a/Services/LDAP/classes/class.ilLDAPRoleGroupMappingSettings.php +++ b/Services/LDAP/classes/class.ilLDAPRoleGroupMappingSettings.php @@ -1,7 +1,5 @@ */ diff --git a/Services/LDAP/classes/class.ilLDAPRoleMappingTableGUI.php b/Services/LDAP/classes/class.ilLDAPRoleMappingTableGUI.php index fc827a691cf2..789608dc336b 100644 --- a/Services/LDAP/classes/class.ilLDAPRoleMappingTableGUI.php +++ b/Services/LDAP/classes/class.ilLDAPRoleMappingTableGUI.php @@ -1,7 +1,5 @@ */ diff --git a/Services/LDAP/classes/class.ilLDAPServer.php b/Services/LDAP/classes/class.ilLDAPServer.php index 970e48be50d6..d570012dd60a 100644 --- a/Services/LDAP/classes/class.ilLDAPServer.php +++ b/Services/LDAP/classes/class.ilLDAPServer.php @@ -1,7 +1,5 @@ */ diff --git a/Services/LDAP/classes/class.ilLDAPServerTableGUI.php b/Services/LDAP/classes/class.ilLDAPServerTableGUI.php index 0dc56341cdde..60e663ab759e 100644 --- a/Services/LDAP/classes/class.ilLDAPServerTableGUI.php +++ b/Services/LDAP/classes/class.ilLDAPServerTableGUI.php @@ -1,7 +1,5 @@ */ diff --git a/Services/LDAP/classes/class.ilLDAPSettingsGUI.php b/Services/LDAP/classes/class.ilLDAPSettingsGUI.php index dbaa1110bc1a..c8c8a5cfcb79 100644 --- a/Services/LDAP/classes/class.ilLDAPSettingsGUI.php +++ b/Services/LDAP/classes/class.ilLDAPSettingsGUI.php @@ -1,7 +1,5 @@ */ @@ -192,7 +192,7 @@ public function executeCommand(): bool if (!$cmd) { $cmd = "serverList"; } - $this->$cmd(); + $this->$cmd(); break; } return true; diff --git a/Services/LDAP/classes/class.ilLDAPUserSynchronisation.php b/Services/LDAP/classes/class.ilLDAPUserSynchronisation.php index 4ad9a7203f12..1613dcc68069 100644 --- a/Services/LDAP/classes/class.ilLDAPUserSynchronisation.php +++ b/Services/LDAP/classes/class.ilLDAPUserSynchronisation.php @@ -1,7 +1,5 @@ il_setup_language->getInstalledLanguages() ?: ['en']; + return $this->il_setup_language->getInstalledLanguages() ?? ['en']; } /** @@ -77,7 +76,9 @@ public function isNotable(): bool */ public function getPreconditions(Setup\Environment $environment): array { - return []; + return [ + new ilDatabaseInitializedObjective() + ]; } /** diff --git a/Services/Language/classes/class.ilLanguage.php b/Services/Language/classes/class.ilLanguage.php index 91eb3699cdcc..4196b52f9df1 100755 --- a/Services/Language/classes/class.ilLanguage.php +++ b/Services/Language/classes/class.ilLanguage.php @@ -85,7 +85,8 @@ public function __construct(string $a_lang_key) $this->lang_path = ILIAS_ABSOLUTE_PATH . "/lang"; $this->cust_lang_path = ILIAS_ABSOLUTE_PATH . "/Customizing/global/lang"; - $this->lang_default = $client_ini->readVariable("language", "default") ?: 'en'; + $this->lang_default = $client_ini->readVariable("language", "default") ?? 'en'; + $this->lang_user = $this->lang_default; if ($DIC->offsetExists("ilSetting")) { $ilSetting = $DIC->settings(); @@ -125,7 +126,7 @@ public function getLangKey(): string */ public function getDefaultLanguage(): string { - return $this->lang_default ?: "en"; + return $this->lang_default ?? "en"; } /** diff --git a/Services/Language/classes/class.ilObjLanguage.php b/Services/Language/classes/class.ilObjLanguage.php index d0b9e70a6992..515fcd76b18c 100755 --- a/Services/Language/classes/class.ilObjLanguage.php +++ b/Services/Language/classes/class.ilObjLanguage.php @@ -1,7 +1,5 @@ key] ?? false) { global $DIC; - /** @var ilErrorHandling $ilErr */ - $ilErr = $DIC["ilErr"]; - $ilErr->raiseError( + $DIC->ui()->mainTemplate()->setOnScreenMessage( + 'failure', "Duplicate Language Entry in $lang_file:\n$val", - $ilErr->MESSAGE - ); + true); + $DIC->ctrl()->redirectByClass(ilobjlanguagefoldergui::class, 'view'); } $double_checker[$separated[0]][$separated[1]][$this->key] = true; @@ -167,13 +166,12 @@ protected function checkModules(): void $unserialied = unserialize($module["lang_array"], ["allowed_classes" => false]); if (!is_array($unserialied)) { global $DIC; - /** @var ilErrorHandling $ilErr */ - $ilErr = $DIC["ilErr"]; - $ilErr->raiseError( + $DIC->ui()->mainTemplate()->setOnScreenMessage( + 'failure', "Data for module '" . $module["module"] . "' of language '" . $this->key . "' is not correctly saved. " . "Please check the collation of your database tables lng_data and lng_modules. It must be utf8_unicode_ci.", - $ilErr->MESSAGE - ); + true); + $DIC->ctrl()->redirectByClass(ilobjlanguagefoldergui::class, 'view'); } } } diff --git a/Services/Language/classes/class.ilObjLanguageExt.php b/Services/Language/classes/class.ilObjLanguageExt.php index e4837dd8e1da..3cf191a4a5a4 100644 --- a/Services/Language/classes/class.ilObjLanguageExt.php +++ b/Services/Language/classes/class.ilObjLanguageExt.php @@ -18,8 +18,6 @@ declare(strict_types=1); -require_once "./Services/Language/classes/class.ilObjLanguage.php"; - /** * Class ilObjLanguageExt * @@ -411,11 +409,10 @@ public static function _saveValues(string $a_lang_key, array $a_values = array() if (!is_array($a_values)) { return; } - $save_array = array(); - $save_date = date("Y-m-d H:i:s", time()); + $save_array = []; + $save_date = (new DateTime())->format("Y-m-d H:i:s"); // read and get the global values - require_once "./Services/Language/classes/class.ilLanguageFile.php"; $global_file_obj = ilLanguageFile::_getGlobalLanguageFile($a_lang_key); $file_values = $global_file_obj->getAllValues(); $file_comments = $global_file_obj->getAllComments(); @@ -427,25 +424,26 @@ public static function _saveValues(string $a_lang_key, array $a_values = array() // save the single translations in lng_data foreach ($a_values as $key => $value) { $keys = explode($lng->separator, $key); - if (count($keys) === 2) { - $module = $keys[0]; - $topic = $keys[1]; - $save_array[$module][$topic] = $value; - - if (!isset($global_values[$key])) continue; - $are_comments_set = isset($global_comments[$key]) && isset($a_remarks[$key]); - $are_changes_made = $global_values[$key] != $value || $db_values[$key] != $value; - if ($are_changes_made || ($are_comments_set ? $global_comments[$key] != $a_remarks[$key] : $are_comments_set)) { - $local_change = $db_values[$key] == $value || $global_values[$key] != $value ? $save_date : null; - ilObjLanguage::replaceLangEntry( - $module, - $topic, - $a_lang_key, - $value, - $local_change, - $a_remarks[$key] ?? null - ); - } + + if (count($keys) !== 2) { + continue; + } + + list($module, $topic) = $keys; + $save_array[$module][$topic] = $value; + + $are_comments_set = array_key_exists($key, $global_comments) && array_key_exists($key, $a_remarks); + $are_changes_made = (isset($global_values[$key]) ? $global_values[$key] != $value : true) || (isset($db_values[$key]) ? $db_values[$key] != $value : true); + if ($are_changes_made || ($are_comments_set ? $global_comments[$key] != $a_remarks[$key] : $are_comments_set)) { + $local_change = (isset($db_values[$key]) ? $db_values[$key] == $value : true) || (isset($global_values[$key]) ? $global_values[$key] != $value : true) ? $save_date : null; + ilObjLanguage::replaceLangEntry( + $module, + $topic, + $a_lang_key, + $value, + $local_change, + $a_remarks[$key] ?? null + ); } } diff --git a/Services/Language/classes/class.ilObjLanguageFolder.php b/Services/Language/classes/class.ilObjLanguageFolder.php index 9f9d12a28537..7529541418bb 100755 --- a/Services/Language/classes/class.ilObjLanguageFolder.php +++ b/Services/Language/classes/class.ilObjLanguageFolder.php @@ -1,7 +1,5 @@ language(); - $this->type = "lngf"; parent::__construct($a_id, $a_call_by_reference); - $this->lang_path = $lng->lang_path; - $this->lang_default = $lng->lang_default; - $this->lang_user = $lng->lang_user; - $this->separator = $lng->separator; + $this->lang_path = $this->lng->lang_path; + $this->lang_default = $this->lng->lang_default; + $this->lang_user = $this->lng->lang_user; + $this->separator = $this->lng->separator; } /** @@ -99,10 +92,7 @@ public function __construct(int $a_id, bool $a_call_by_reference = true) */ public function getLanguages(): array { - global $DIC; - $lng = $DIC->language(); - - $lng->loadLanguageModule("meta"); + $this->lng->loadLanguageModule("meta"); // set path to directory where lang-files reside $d = dir($this->lang_path); @@ -169,7 +159,7 @@ public function getLanguages(): array // setting language's full names foreach ($languages as $lang_key => $lang_data) { - $languages[$lang_key]["name"] = $lng->txt("meta_l_" . $lang_key); + $languages[$lang_key]["name"] = $this->lng->txt("meta_l_" . $lang_key); } $this->languages = $languages; @@ -223,9 +213,6 @@ public function addNewLanguages(array $a_languages): array */ public function removeLanguages(array $a_languages): array { - global $DIC; - $ilDB = $DIC->database(); - foreach ($a_languages as $lang_key => $lang_data) { if ($lang_data["desc"] === "not_installed" && $lang_data["info"] === "file_not_found") { // update languages array @@ -233,9 +220,9 @@ public function removeLanguages(array $a_languages): array // update object_data table $query = "DELETE FROM object_data " . - "WHERE type = " . $ilDB->quote("lng", "text") . " " . - "AND title = " . $ilDB->quote($lang_key, "text"); - $ilDB->manipulate($query); + "WHERE type = " . $this->db->quote("lng", "text") . " " . + "AND title = " . $this->db->quote($lang_key, "text"); + $this->db->manipulate($query); } } @@ -252,10 +239,6 @@ public function removeLanguages(array $a_languages): array */ public function checkAllLanguages(): string { - global $DIC; - // TODO: lng object should not be used in this class - $lng = $DIC->language(); - // set path to directory where lang-files reside $d = dir($this->lang_path); $tmpPath = getcwd(); @@ -269,53 +252,76 @@ public function checkAllLanguages(): string while ($entry = $d->read()) { if (is_file($entry) && (preg_match("~(^ilias_.{2}\.lang$)~", $entry))) { // textmeldung, wenn langfile gefunden wurde - $output .= "

" . $lng->txt("langfile_found") . ": " . $entry; + $output .= "

" . $this->lng->txt("langfile_found") . ": " . $entry; $content = file($entry); - + $lines_full = count($content); $found = true; - $error = false; + $error_param = false; + $error_double = false; + $double_checker = []; if ($content = ilObjLanguage::cut_header($content)) { + $lines_cut = count($content); foreach ($content as $key => $val) { $separated = explode($this->separator, trim($val)); $num = count($separated); + $line = $key + $lines_full - $lines_cut + 1; if ($num !== 3) { - $error = true; - $line = $key + 37; + $error_param = true; - $output .= "
" . $lng->txt("err_in_line") . " " . $line . " !  "; - $output .= $lng->txt("module") . ": " . $separated[0]; - $output .= ", " . $lng->txt("identifier") . ": " . $separated[1]; - $output .= ", " . $lng->txt("value") . ": " . $separated[2]; + $output .= "
" . $this->lng->txt("err_in_line") . " " . $line . " !  "; switch ($num) { case 1: if (empty($separated[0])) { - $output .= "
" . $lng->txt("err_no_param") . " " . $lng->txt("check_langfile"); + $output .= "
" . $this->lng->txt("err_no_param") . " " . $this->lng->txt("check_langfile"); } else { - $output .= "
" . $lng->txt("err_1_param") . " " . $lng->txt("check_langfile"); + $output .= $this->lng->txt("module") . ": " . $separated[0]; + $output .= "
" . $this->lng->txt("err_1_param") . " " . $this->lng->txt("check_langfile"); } break; case 2: - $output .= "
" . $lng->txt("err_2_param") . " " . $lng->txt("check_langfile"); + $output .= $this->lng->txt("module") . ": " . $separated[0]; + $output .= ", " . $this->lng->txt("identifier") . ": " . $separated[1]; + $output .= "
" . $this->lng->txt("err_2_param") . " " . $this->lng->txt("check_langfile"); break; default: - $output .= "
" . $lng->txt("err_over_3_param") . " " . $lng->txt("check_langfile"); + $output .= $this->lng->txt("module") . ": " . $separated[0]; + $output .= ", " . $this->lng->txt("identifier") . ": " . $separated[1]; + $output .= ", " . $this->lng->txt("value") . ": " . $separated[2]; + $output .= "
" . $this->lng->txt("err_over_3_param") . " " . $this->lng->txt("check_langfile"); break; } + continue; } + if ($double_checker[strtolower($separated[0])][strtolower($separated[1])] ?? false) { + $error_double = true; + + $output .= "
" . $this->lng->txt("err_in_line") . " " . $double_checker[strtolower($separated[0])][strtolower($separated[1])] . " " . $this->lng->txt("and") . " " . $line . " !  "; + $output .= $this->lng->txt("module") . ": " . $separated[0]; + $output .= ", " . $this->lng->txt("identifier") . ": " . $separated[1]; + $output .= ", " . $this->lng->txt("value") . ": " . $separated[2]; + } + $double_checker[strtolower($separated[0])][strtolower($separated[1])] = $line; } - if ($error) { - $output .= "
" . $lng->txt("file_not_valid") . " " . $lng->txt("err_count_param"); + if ($error_param || $error_double) { + $reason = ""; + if ($error_param) { + $reason .= " " . $this->lng->txt("err_count_param"); + } + if ($error_double) { + $reason .= " " . $this->lng->txt("err_double_entries"); + } + $output .= "
" . $this->lng->txt("file_not_valid") . $reason; } else { - $output .= "
" . $lng->txt("file_valid"); + $output .= "
" . $this->lng->txt("file_valid"); } } else { - $output .= "
" . $lng->txt("file_not_valid") . " " . $lng->txt("err_wrong_header"); + $output .= "
" . $this->lng->txt("file_not_valid") . " " . $this->lng->txt("err_wrong_header"); } } } @@ -323,7 +329,7 @@ public function checkAllLanguages(): string $d->close(); if (!$found) { - $output .= "
" . $lng->txt("err_no_langfile_found"); + $output .= "
" . $this->lng->txt("err_no_langfile_found"); } chdir($tmpPath); diff --git a/Services/Language/classes/class.ilObjLanguageFolderGUI.php b/Services/Language/classes/class.ilObjLanguageFolderGUI.php index 25c20650ce14..851e872be1d9 100755 --- a/Services/Language/classes/class.ilObjLanguageFolderGUI.php +++ b/Services/Language/classes/class.ilObjLanguageFolderGUI.php @@ -1,7 +1,5 @@ data = $this->lng->txt("selected_languages_updated"); $this->lng->loadLanguageModule("meta"); + $refreshed = []; foreach ($this->getPostId() as $id) { $langObj = new ilObjLanguage((int) $id, false); @@ -263,13 +259,14 @@ public function uninstallChangesObject(): void $langObj->setTitle($langObj->getKey()); $langObj->setDescription("installed"); $langObj->update(); + $refreshed[] = $langObj->getKey(); } $this->data .= "
" . $this->lng->txt("meta_l_" . $langObj->getKey()); } unset($langObj); } - + ilObjLanguage::refreshPlugins($refreshed); $this->out(); } diff --git a/Services/LearningHistory/README.md b/Services/LearningHistory/README.md index fff3a57653a5..65043ec3ba5c 100644 --- a/Services/LearningHistory/README.md +++ b/Services/LearningHistory/README.md @@ -1,4 +1,4 @@ -#Learning History Service +# Learning History Service Welcome to the history of learning. @@ -26,9 +26,10 @@ trac#:#trac_lhist_obj_completed_in#:#$1$ was completed in $2$. If you additionally want to emphasize certain words (mostly titles) in your text, please use the method `$this->getEmphasizedTitle($string)`. -# JF Decisions +## Deletion Behaviour -8 Oct 2018 +The service will keep entries even if the referenced objects ($ref_id, $obj_id) are deleted in the meantime. However the consuming components must take care of their own deletion processes and historise data that is necessary to provide older entries for the learning history. [^2] -- General introduction of the service -- https://github.com/ILIAS-eLearning/ILIAS/pull/1210 \ No newline at end of file +[^1] 8 Oct 2018, General introduction of the service: https://github.com/ILIAS-eLearning/ILIAS/pull/1210 + +[^2] 18 Sep 2023, Clarification Deletion Behaviour, 4.5: https://docu.ilias.de/goto_docu_wiki_wpage_8022_1357.html \ No newline at end of file diff --git a/Services/Locator/classes/class.ilLocatorGUI.php b/Services/Locator/classes/class.ilLocatorGUI.php index 5af73af5380d..7fc0bbf0ed1d 100755 --- a/Services/Locator/classes/class.ilLocatorGUI.php +++ b/Services/Locator/classes/class.ilLocatorGUI.php @@ -144,14 +144,12 @@ public function addRepositoryItems(int $a_ref_id = 0): void $row["title"] = $this->lng->txt("repository"); } - $ilCtrl->setParameterByClass("ilrepositorygui", "ref_id", $row["child"]); $this->addItem( $row["title"], - $ilCtrl->getLinkTargetByClass("ilrepositorygui", ""), + ilLink::_getLink((int) $row["child"]), ilFrameTargetInfo::_getFrame("MainContent"), $row["child"] ); - $ilCtrl->setParameterByClass("ilrepositorygui", "ref_id", $this->ref_id); } } } diff --git a/Services/Logging/classes/error/class.ilLoggingErrorFileStorage.php b/Services/Logging/classes/error/class.ilLoggingErrorFileStorage.php index 3f0294b0b496..7effaa9447c3 100644 --- a/Services/Logging/classes/error/class.ilLoggingErrorFileStorage.php +++ b/Services/Logging/classes/error/class.ilLoggingErrorFileStorage.php @@ -32,7 +32,8 @@ class ilLoggingErrorFileStorage protected Inspector $inspector; protected string $file_path; protected string $file_name; - + /** @var list */ + private array $exclusion_list = []; public function __construct(Inspector $inspector, string $file_path, string $file_name) { @@ -41,6 +42,16 @@ public function __construct(Inspector $inspector, string $file_path, string $fil $this->file_name = $file_name; } + /** + * @param list $exclusion_list + */ + public function withExclusionList(array $exclusion_list): self + { + $clone = clone $this; + $clone->exclusion_list = $exclusion_list; + return $clone; + } + protected function createDir(string $path): void { if (!is_dir($this->file_path)) { @@ -121,7 +132,8 @@ protected function tables(): array $post = $_POST; $server = $_SERVER; - $post = $this->hidePassword($post); + $post = $this->hideSensitiveData($post); + $server = $this->hideSensitiveData($server); $server = $this->shortenPHPSessionId($server); return array( "GET Data" => $_GET @@ -135,16 +147,22 @@ protected function tables(): array } /** - * Replace passwort from post array with security message + * @param array $super_global + * @return array */ - private function hidePassword(array $post): array + private function hideSensitiveData(array $super_global): array { - ilSystemStyleLessVariable::class; - if (isset($post["password"])) { - $post["password"] = "REMOVED FOR SECURITY"; + foreach ($this->exclusion_list as $parameter) { + if (isset($super_global[$parameter])) { + $super_global[$parameter] = 'REMOVED FOR SECURITY'; + } + + if (isset($super_global['post_vars'][$parameter])) { + $super_global['post_vars'][$parameter] = 'REMOVED FOR SECURITY'; + } } - return $post; + return $super_global; } /** diff --git a/Services/Mail/classes/Address/Type/class.ilMailAddressTypeHelper.php b/Services/Mail/classes/Address/Type/class.ilMailAddressTypeHelper.php index 70b1dab27d36..98d989692aa6 100644 --- a/Services/Mail/classes/Address/Type/class.ilMailAddressTypeHelper.php +++ b/Services/Mail/classes/Address/Type/class.ilMailAddressTypeHelper.php @@ -38,7 +38,7 @@ public function doesGroupNameExists(string $name): bool public function getGroupObjIdByTitle(string $title): int { - return ilObjGroup::_lookupIdByTitle($title); + return (int) ilObjGroup::_lookupIdByTitle($title); } public function getInstanceByRefId(int $refId): ilObject diff --git a/Services/Mail/classes/class.ilMail.php b/Services/Mail/classes/class.ilMail.php index 10a3c36e5288..389e355ed5c4 100755 --- a/Services/Mail/classes/class.ilMail.php +++ b/Services/Mail/classes/class.ilMail.php @@ -384,7 +384,8 @@ protected function fetchMailData(?array $row): ?array } if (isset($row['attachments'])) { - $row['attachments'] = unserialize(stripslashes($row['attachments']), ['allowed_classes' => false]); + $unserialized = unserialize(stripslashes($row['attachments']), ['allowed_classes' => false]); + $row['attachments'] = is_array($unserialized) ? $unserialized : []; } else { $row['attachments'] = []; } @@ -416,6 +417,13 @@ protected function fetchMailData(?array $row): ?array $row['use_placeholders'] = (bool) $row['use_placeholders']; } + $null_to_string_properties = ['m_subject', 'm_message', 'rcp_to', 'rcp_cc', 'rcp_bcc']; + foreach ($null_to_string_properties as $null_to_string_property) { + if (!isset($row[$null_to_string_property])) { + $row[$null_to_string_property] = ''; + } + } + return $row; } @@ -961,7 +969,7 @@ protected function checkRecipients(string $recipients): array * @param array|null $a_tpl_ctx_params * @return bool */ - public function savePostData( + public function persistToStage( int $a_user_id, array $a_attachments, string $a_rcp_to, @@ -991,12 +999,12 @@ public function savePostData( ] ); - $this->getSavedData(); + $this->retrieveFromStage(); return true; } - public function getSavedData(): array + public function retrieveFromStage(): array { $res = $this->db->queryF( "SELECT * FROM $this->table_mail_saved WHERE user_id = %s", @@ -1006,7 +1014,7 @@ public function getSavedData(): array $this->mail_data = $this->fetchMailData($this->db->fetchAssoc($res)); if (!is_array($this->mail_data)) { - $this->savePostData($this->user_id, [], '', '', '', '', '', false); + $this->persistToStage($this->user_id, [], '', '', '', '', '', false); } return $this->mail_data; diff --git a/Services/Mail/classes/class.ilMailAttachmentGUI.php b/Services/Mail/classes/class.ilMailAttachmentGUI.php index e64b2ace0243..f943140ad66e 100644 --- a/Services/Mail/classes/class.ilMailAttachmentGUI.php +++ b/Services/Mail/classes/class.ilMailAttachmentGUI.php @@ -180,7 +180,7 @@ public function confirmDeleteAttachments(): void if ($error !== '') { $this->tpl->setOnScreenMessage('failure', $this->lng->txt('mail_error_delete_file') . ' ' . $error); } else { - $mail_data = $this->umail->getSavedData(); + $mail_data = $this->umail->retrieveFromStage(); if (is_array($mail_data['attachments'])) { $tmp = []; foreach ($mail_data['attachments'] as $attachment) { @@ -244,7 +244,7 @@ public function showAttachments(): void $table = new ilMailAttachmentTableGUI($this, 'showAttachments'); - $mail_data = $this->umail->getSavedData(); + $mail_data = $this->umail->retrieveFromStage(); $files = $this->mfile->getUserFilesData(); $data = []; $counter = 0; diff --git a/Services/Mail/classes/class.ilMailAttachmentTableGUI.php b/Services/Mail/classes/class.ilMailAttachmentTableGUI.php index f42233ad95ff..aee7911753e3 100644 --- a/Services/Mail/classes/class.ilMailAttachmentTableGUI.php +++ b/Services/Mail/classes/class.ilMailAttachmentTableGUI.php @@ -1,7 +1,5 @@ * @ingroup ServicesMail @@ -90,7 +90,7 @@ protected function formatValue(string $column, string $value): ?string return ilDatePresentation::formatDate(new ilDateTime($value, IL_CAL_UNIX)); case 'filesize': - return ilUtil::formatSize((int) $value); + return ilUtil::formatSize((int) $value, 'long'); default: return $value; diff --git a/Services/Mail/classes/class.ilMailAutoCompleteSentMailsRecipientsProvider.php b/Services/Mail/classes/class.ilMailAutoCompleteSentMailsRecipientsProvider.php index 63b74de4a4be..74bfe927a2af 100644 --- a/Services/Mail/classes/class.ilMailAutoCompleteSentMailsRecipientsProvider.php +++ b/Services/Mail/classes/class.ilMailAutoCompleteSentMailsRecipientsProvider.php @@ -23,15 +23,16 @@ */ class ilMailAutoCompleteSentMailsRecipientsProvider extends ilMailAutoCompleteRecipientProvider { - /** @var string[] */ - protected array $users_stack = []; + /** @var list */ + private array $users_stack = []; /** - * @return array{login?: string, firstname?: string, lastname?: string} + * @return array{login: string, firstname: string, lastname: string} + * @throws OutOfBoundsException */ public function current(): array { - if (is_array($this->data)) { + if (is_array($this->data) && !empty($this->data)) { return [ 'login' => $this->data['login'], 'firstname' => '', @@ -39,7 +40,7 @@ public function current(): array ]; } - if (count($this->users_stack) > 0) { + if ($this->users_stack !== []) { return [ 'login' => array_shift($this->users_stack), 'firstname' => '', @@ -47,11 +48,7 @@ public function current(): array ]; } - return [ - 'login' => '', - 'firstname' => '', - 'lastname' => '', - ]; + throw new OutOfBoundsException('No more users available'); } public function key(): string @@ -60,8 +57,8 @@ public function key(): string return $this->data['login']; } - if (count($this->users_stack) > 0) { - return $this->users_stack[0]; + if ($this->users_stack !== []) { + return $this->users_stack[0]['login']; } return ''; @@ -70,17 +67,18 @@ public function key(): string public function valid(): bool { $this->data = $this->db->fetchAssoc($this->res); - if ( - is_array($this->data) && + if (is_array($this->data) && + !empty($this->data) && ( strpos($this->data['login'], ',') || strpos($this->data['login'], ';') - ) - ) { - $parts = array_filter(array_map( - 'trim', - preg_split("/[ ]*[;,][ ]*/", trim($this->data['login'])) - )); + )) { + $parts = array_filter( + array_map( + 'trim', + preg_split("/[ ]*[;,][ ]*/", trim($this->data['login'])) + ) + ); foreach ($parts as $part) { if (ilStr::strPos(ilStr::strToLower($part), ilStr::strToLower($this->term)) !== false) { @@ -89,11 +87,11 @@ public function valid(): bool } if ($this->users_stack) { - $this->data = []; + $this->data = null; } } - return is_array($this->data) || count($this->users_stack) > 0; + return (is_array($this->data) && !empty($this->data)) || $this->users_stack !== []; } public function rewind(): void @@ -108,6 +106,7 @@ public function rewind(): void mail.rcp_to login FROM mail WHERE " . $this->db->like('mail.rcp_to', 'text', $this->quoted_term) . " + AND mail.rcp_to IS NOT NULL AND mail.rcp_to != '' AND sender_id = " . $this->db->quote($this->user_id, 'integer') . " AND mail.sender_id = mail.user_id"; diff --git a/Services/Mail/classes/class.ilMailExplorer.php b/Services/Mail/classes/class.ilMailExplorer.php index 68ad6586f8b5..202b0d4b64ae 100755 --- a/Services/Mail/classes/class.ilMailExplorer.php +++ b/Services/Mail/classes/class.ilMailExplorer.php @@ -102,7 +102,7 @@ protected function getNodeStateToggleCmdClasses($record): array public function getNodeContent($a_node): string { - $content = $a_node['title']; + $content = ilLegacyFormElementsUtil::prepareFormOutput($a_node['title']); if ((int) $a_node['child'] === (int) $this->getNodeId($this->getRootNode())) { $content = $this->lng->txt('mail_folders'); diff --git a/Services/Mail/classes/class.ilMailFolderTableGUI.php b/Services/Mail/classes/class.ilMailFolderTableGUI.php index e1b642e02ec4..8fabbf920d62 100644 --- a/Services/Mail/classes/class.ilMailFolderTableGUI.php +++ b/Services/Mail/classes/class.ilMailFolderTableGUI.php @@ -479,7 +479,7 @@ protected function fetchTableData(): self ); } else { $mail['img_sender'] = ''; - $mail['from'] = $mail['mail_login'] = trim(($mail['import_name'] ?? ''). ' (' + $mail['from'] = $mail['mail_login'] = trim(($mail['import_name'] ?? '') . ' (' . $this->lng->txt('user_deleted') . ')'); } } diff --git a/Services/Mail/classes/class.ilMailFormGUI.php b/Services/Mail/classes/class.ilMailFormGUI.php index 599e56b3a811..79dbcbf4dfd3 100644 --- a/Services/Mail/classes/class.ilMailFormGUI.php +++ b/Services/Mail/classes/class.ilMailFormGUI.php @@ -205,7 +205,7 @@ public function sendMessage(): void $this->requestAttachments = $files; $this->showSubmissionErrors($errors); } else { - $mailer->savePostData( + $mailer->persistToStage( $this->user->getId(), [], '', @@ -307,7 +307,7 @@ public function searchUsers(bool $save = true): void ); // Note: For security reasons, ILIAS only allows Plain text strings in E-Mails. - $this->umail->savePostData( + $this->umail->persistToStage( $this->user->getId(), $files, ilUtil::securePlainString($this->getBodyParam('rcp_to', $this->refinery->kindlyTo()->string(), '')), @@ -409,7 +409,7 @@ public function editAttachments(): void ); // Note: For security reasons, ILIAS only allows Plain text strings in E-Mails. - $this->umail->savePostData( + $this->umail->persistToStage( $this->user->getId(), $files, ilUtil::securePlainString($this->getBodyParam('rcp_to', $this->refinery->kindlyTo()->string(), '')), @@ -533,7 +533,7 @@ public function showForm(): void break; case self::MAIL_FORM_TYPE_SEARCH_RESULT: - $mailData = $this->umail->getSavedData(); + $mailData = $this->umail->retrieveFromStage(); if (ilSession::get('mail_search_results_to')) { $mailData = $this->umail->appendSearchResult( @@ -566,7 +566,7 @@ public function showForm(): void break; case self::MAIL_FORM_TYPE_ATTACH: - $mailData = $this->umail->getSavedData(); + $mailData = $this->umail->retrieveFromStage(); break; case self::MAIL_FORM_TYPE_DRAFT: @@ -932,7 +932,7 @@ protected function saveMailBeforeSearch(): void [] ); - $this->umail->savePostData( + $this->umail->persistToStage( $this->user->getId(), $files, ilUtil::securePlainString($this->getBodyParam('rcp_to', $this->refinery->kindlyTo()->string(), '')), diff --git a/Services/Mail/test/ilMailTest.php b/Services/Mail/test/ilMailTest.php index 99765cea1b71..b8b5ca325456 100644 --- a/Services/Mail/test/ilMailTest.php +++ b/Services/Mail/test/ilMailTest.php @@ -262,7 +262,15 @@ public function testGetMailsOfFolder(): void { $filter = ['status' => 'yes']; $rowData = ['mail_id' => 8908]; - $one = $rowData + ['attachments' => [], 'tpl_ctx_params' => []]; + $one = $rowData + [ + 'attachments' => [], + 'tpl_ctx_params' => [], + 'm_subject' => '', + 'm_message' => '', + 'rcp_to' => '', + 'rcp_cc' => '', + 'rcp_bcc' => '', + ]; $expected = [$one, $one]; $folderId = 89; $userId = 901; @@ -395,7 +403,7 @@ public function testUpdateDraft(): void $this->assertSame($draftId, $instance->updateDraft($folderId, [], $to, $cc, $bcc, $subject, $message, $draftId, $usePlaceholders, $contextId, $params)); } - public function testSavePostData(): void + public function testPersistingToStage(): void { $userId = 897; $attachments = []; @@ -430,7 +438,7 @@ public function testSavePostData(): void 'rcp_to' => 'phpunit' ]); - $instance->savePostData( + $instance->persistToStage( 78979078, $attachments, $rcpTo, @@ -444,7 +452,7 @@ public function testSavePostData(): void ); } - public function testGetSavedData(): void + public function testRetrievalFromStage(): void { $userId = 789; $instance = $this->create(67, $userId); @@ -454,7 +462,7 @@ public function testGetSavedData(): void 'rcp_to' => 'phpunit' ]); - $mail_data = $instance->getSavedData(); + $mail_data = $instance->retrieveFromStage(); $this->assertIsArray($mail_data); $this->assertEquals('phpunit', $mail_data['rcp_to']); diff --git a/Services/MainMenu/classes/Items/class.ilMMCustomItemStorage.php b/Services/MainMenu/classes/Items/class.ilMMCustomItemStorage.php index 3078a7eee688..891fe21f9231 100644 --- a/Services/MainMenu/classes/Items/class.ilMMCustomItemStorage.php +++ b/Services/MainMenu/classes/Items/class.ilMMCustomItemStorage.php @@ -1,5 +1,21 @@ global_role_ids); + return array_map("intval", explode(",", $this->global_role_ids)); } diff --git a/Services/MainMenu/classes/Items/class.ilMMItemInformation.php b/Services/MainMenu/classes/Items/class.ilMMItemInformation.php index e75c72f7f4ed..ab8d63dda72d 100644 --- a/Services/MainMenu/classes/Items/class.ilMMItemInformation.php +++ b/Services/MainMenu/classes/Items/class.ilMMItemInformation.php @@ -66,7 +66,7 @@ public function customTranslationForUser(hasTitle $item): hasTitle static $default_language; // see https://mantis.ilias.de/view.php?id=32276 - if (!isset($usr_language_key) && $DIC->user()->getId() === 0 || $DIC->user()->isAnonymous()) { + if (!isset($usr_language_key) && ($DIC->user()->getId() === 0 || $DIC->user()->isAnonymous())) { $usr_language_key = $DIC->http()->wrapper()->query()->has('lang') ? $DIC->http()->wrapper()->query()->retrieve('lang', $DIC->refinery()->to()->string()) : null; diff --git a/Services/MainMenu/classes/Provider/CustomMainBarProvider.php b/Services/MainMenu/classes/Provider/CustomMainBarProvider.php index abbe021b10d5..babe6213068e 100644 --- a/Services/MainMenu/classes/Provider/CustomMainBarProvider.php +++ b/Services/MainMenu/classes/Provider/CustomMainBarProvider.php @@ -1,6 +1,22 @@ globalScreen()->identification()->core($this)->identifier($storage->getIdentifier()); - $item = $this->globalScreen()->mainBar()->custom($storage->getType(), $identification)->withVisibilityCallable( - $this->mm_access->isCurrentUserAllowedToSeeCustomItem($storage) + $item = $this->globalScreen()->mainBar()->custom($storage->getType(), $identification); + + $item = $item->withVisibilityCallable( + $this->mm_access->isCurrentUserAllowedToSeeCustomItem( + $storage, + function () use ($item) { + return $item->isVisible(); + } + ) ); if ($item instanceof hasTitle && !empty($storage->getDefaultTitle())) { diff --git a/Services/MainMenu/classes/TypeHandler/class.ilMMTypeHandlerRepositoryLink.php b/Services/MainMenu/classes/TypeHandler/class.ilMMTypeHandlerRepositoryLink.php index 7cfb83c61b37..5a36065147b7 100644 --- a/Services/MainMenu/classes/TypeHandler/class.ilMMTypeHandlerRepositoryLink.php +++ b/Services/MainMenu/classes/TypeHandler/class.ilMMTypeHandlerRepositoryLink.php @@ -49,9 +49,13 @@ public function enrichItem(isItem $item): isItem $ref_id = (int) $this->links[$item->getProviderIdentification()->serialize()][self::F_ACTION]; $item = $item->withRefId($ref_id) ->withVisibilityCallable( - function () use ($DIC, $ref_id) { - return $DIC->access()->checkAccess('join', '', $ref_id) + function () use ($DIC, $ref_id, $item) { + $is_visible_parent = $item->isVisible(); + $has_access = $DIC->access()->checkAccess('join', '', $ref_id) || $DIC->access()->checkAccess('read', '', $ref_id); + + return $is_visible_parent + && $has_access; } ); } diff --git a/Services/MainMenu/classes/class.ilObjMainMenuAccess.php b/Services/MainMenu/classes/class.ilObjMainMenuAccess.php index dc26ded47d61..6355c1f2529a 100644 --- a/Services/MainMenu/classes/class.ilObjMainMenuAccess.php +++ b/Services/MainMenu/classes/class.ilObjMainMenuAccess.php @@ -1,5 +1,21 @@ rbacreview->assignedGlobalRoles($this->user->getId()); if (!$item->hasRoleBasedVisibility()) { - return true; + return $current(); } if (!empty($item->getGlobalRoleIDs())) { foreach ($roles_of_current_user as $role_of_current_user) { - if (in_array($role_of_current_user, $item->getGlobalRoleIDs())) { - return true; + if (in_array((int) $role_of_current_user, $item->getGlobalRoleIDs(), true)) { + return $current(); } } } diff --git a/Services/MainMenu/interfaces/interface.ilMainMenuAccess.php b/Services/MainMenu/interfaces/interface.ilMainMenuAccess.php index da1686beb291..a534963cd16b 100644 --- a/Services/MainMenu/interfaces/interface.ilMainMenuAccess.php +++ b/Services/MainMenu/interfaces/interface.ilMainMenuAccess.php @@ -1,5 +1,21 @@ b ? 1 : a < b ? -1 : 0; -} -/** - * @param {Array} arr Array. - * @param {number} target Target. - * @param {number} direction 0 means return the nearest, > 0 - * means return the largest nearest, < 0 means return the - * smallest nearest. - * @return {number} Index. - */ -function linearFindNearest(arr, target, direction) { - var n = arr.length; - if (arr[0] <= target) { - return 0; - } - else if (target <= arr[n - 1]) { - return n - 1; - } - else { - var i = void 0; - if (direction > 0) { - for (i = 1; i < n; ++i) { - if (arr[i] < target) { - return i - 1; - } - } - } - else if (direction < 0) { - for (i = 1; i < n; ++i) { - if (arr[i] <= target) { - return i; - } - } - } - else { - for (i = 1; i < n; ++i) { - if (arr[i] == target) { - return i; - } - else if (arr[i] < target) { - if (arr[i - 1] - target < target - arr[i]) { - return i - 1; - } - else { - return i; - } - } - } - } - return n - 1; - } -} -/** - * @param {Array} arr The array to modify. - * @param {!Array|VALUE} data The elements or arrays of elements to add to arr. - * @template VALUE - */ -function extend(arr, data) { - var extension = Array.isArray(data) ? data : [data]; - var length = extension.length; - for (var i = 0; i < length; i++) { - arr[arr.length] = extension[i]; - } -} -/** - * @param {Array|Uint8ClampedArray} arr1 The first array to compare. - * @param {Array|Uint8ClampedArray} arr2 The second array to compare. - * @return {boolean} Whether the two arrays are equal. - */ -function equals(arr1, arr2) { - var len1 = arr1.length; - if (len1 !== arr2.length) { - return false; - } - for (var i = 0; i < len1; i++) { - if (arr1[i] !== arr2[i]) { - return false; - } - } - return true; -} -/** - * @param {Array<*>} arr The array to test. - * @param {Function=} opt_func Comparison function. - * @param {boolean=} opt_strict Strictly sorted (default false). - * @return {boolean} Return index. - */ -function isSorted(arr, opt_func, opt_strict) { - var compare = opt_func || numberSafeCompareFunction; - return arr.every(function (currentVal, index) { - if (index === 0) { - return true; - } - var res = compare(arr[index - 1], currentVal); - return !(res > 0 || (opt_strict && res === 0)); - }); -} - -/** - * @module ol/functions - */ -/** - * Always returns true. - * @returns {boolean} true. - */ -function TRUE() { - return true; -} -/** - * Always returns false. - * @returns {boolean} false. - */ -function FALSE() { - return false; -} -/** - * A reusable function, used e.g. as a default for callbacks. - * - * @return {void} Nothing. - */ -function VOID() { } -/** - * Wrap a function in another function that remembers the last return. If the - * returned function is called twice in a row with the same arguments and the same - * this object, it will return the value from the first call in the second call. - * - * @param {function(...any): ReturnType} fn The function to memoize. - * @return {function(...any): ReturnType} The memoized function. - * @template ReturnType - */ -function memoizeOne(fn) { - var called = false; - /** @type {ReturnType} */ - var lastResult; - /** @type {Array} */ - var lastArgs; - var lastThis; - return function () { - var nextArgs = Array.prototype.slice.call(arguments); - if (!called || this !== lastThis || !equals(nextArgs, lastArgs)) { - called = true; - lastThis = this; - lastArgs = nextArgs; - lastResult = fn.apply(this, arguments); - } - return lastResult; - }; -} - -/** - * @module ol/obj - */ -/** - * Polyfill for Object.assign(). Assigns enumerable and own properties from - * one or more source objects to a target object. - * See https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign. - * - * @param {!Object} target The target object. - * @param {...Object} var_sources The source object(s). - * @return {!Object} The modified target object. - */ -var assign = typeof Object.assign === 'function' - ? Object.assign - : function (target, var_sources) { - if (target === undefined || target === null) { - throw new TypeError('Cannot convert undefined or null to object'); - } - var output = Object(target); - for (var i = 1, ii = arguments.length; i < ii; ++i) { - var source = arguments[i]; - if (source !== undefined && source !== null) { - for (var key in source) { - if (source.hasOwnProperty(key)) { - output[key] = source[key]; - } - } - } - } - return output; - }; -/** - * Removes all properties from an object. - * @param {Object} object The object to clear. - */ -function clear(object) { - for (var property in object) { - delete object[property]; - } -} -/** - * Polyfill for Object.values(). Get an array of property values from an object. - * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values - * - * @param {!Object} object The object from which to get the values. - * @return {!Array} The property values. - * @template K,V - */ -var getValues = typeof Object.values === 'function' - ? Object.values - : function (object) { - var values = []; - for (var property in object) { - values.push(object[property]); - } - return values; - }; -/** - * Determine if an object has any properties. - * @param {Object} object The object to check. - * @return {boolean} The object is empty. - */ -function isEmpty(object) { - var property; - for (property in object) { - return false; - } - return !property; -} - -var __extends = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -/** - * @typedef {EventTarget|Target} EventTargetLike - */ -/** - * @classdesc - * A simplified implementation of the W3C DOM Level 2 EventTarget interface. - * See https://www.w3.org/TR/2000/REC-DOM-Level-2-Events-20001113/events.html#Events-EventTarget. - * - * There are two important simplifications compared to the specification: - * - * 1. The handling of `useCapture` in `addEventListener` and - * `removeEventListener`. There is no real capture model. - * 2. The handling of `stopPropagation` and `preventDefault` on `dispatchEvent`. - * There is no event target hierarchy. When a listener calls - * `stopPropagation` or `preventDefault` on an event object, it means that no - * more listeners after this one will be called. Same as when the listener - * returns false. - */ -var Target = /** @class */ (function (_super) { - __extends(Target, _super); - /** - * @param {*=} opt_target Default event target for dispatched events. - */ - function Target(opt_target) { - var _this = _super.call(this) || this; - /** - * @private - * @type {*} - */ - _this.eventTarget_ = opt_target; - /** - * @private - * @type {Object} - */ - _this.pendingRemovals_ = null; - /** - * @private - * @type {Object} - */ - _this.dispatching_ = null; - /** - * @private - * @type {Object>} - */ - _this.listeners_ = null; - return _this; - } - /** - * @param {string} type Type. - * @param {import("../events.js").Listener} listener Listener. - */ - Target.prototype.addEventListener = function (type, listener) { - if (!type || !listener) { - return; - } - var listeners = this.listeners_ || (this.listeners_ = {}); - var listenersForType = listeners[type] || (listeners[type] = []); - if (listenersForType.indexOf(listener) === -1) { - listenersForType.push(listener); - } - }; - /** - * Dispatches an event and calls all listeners listening for events - * of this type. The event parameter can either be a string or an - * Object with a `type` property. - * - * @param {import("./Event.js").default|string} event Event object. - * @return {boolean|undefined} `false` if anyone called preventDefault on the - * event object or if any of the listeners returned false. - * @api - */ - Target.prototype.dispatchEvent = function (event) { - /** @type {import("./Event.js").default|Event} */ - var evt = typeof event === 'string' ? new BaseEvent(event) : event; - var type = evt.type; - if (!evt.target) { - evt.target = this.eventTarget_ || this; - } - var listeners = this.listeners_ && this.listeners_[type]; - var propagate; - if (listeners) { - var dispatching = this.dispatching_ || (this.dispatching_ = {}); - var pendingRemovals = this.pendingRemovals_ || (this.pendingRemovals_ = {}); - if (!(type in dispatching)) { - dispatching[type] = 0; - pendingRemovals[type] = 0; - } - ++dispatching[type]; - for (var i = 0, ii = listeners.length; i < ii; ++i) { - if ('handleEvent' in listeners[i]) { - propagate = /** @type {import("../events.js").ListenerObject} */ (listeners[i]).handleEvent(evt); - } - else { - propagate = /** @type {import("../events.js").ListenerFunction} */ (listeners[i]).call(this, evt); - } - if (propagate === false || evt.propagationStopped) { - propagate = false; - break; - } - } - --dispatching[type]; - if (dispatching[type] === 0) { - var pr = pendingRemovals[type]; - delete pendingRemovals[type]; - while (pr--) { - this.removeEventListener(type, VOID); - } - delete dispatching[type]; - } - return propagate; - } - }; - /** - * Clean up. - */ - Target.prototype.disposeInternal = function () { - this.listeners_ && clear(this.listeners_); - }; - /** - * Get the listeners for a specified event type. Listeners are returned in the - * order that they will be called in. - * - * @param {string} type Type. - * @return {Array|undefined} Listeners. - */ - Target.prototype.getListeners = function (type) { - return (this.listeners_ && this.listeners_[type]) || undefined; - }; - /** - * @param {string=} opt_type Type. If not provided, - * `true` will be returned if this event target has any listeners. - * @return {boolean} Has listeners. - */ - Target.prototype.hasListener = function (opt_type) { - if (!this.listeners_) { - return false; - } - return opt_type - ? opt_type in this.listeners_ - : Object.keys(this.listeners_).length > 0; - }; - /** - * @param {string} type Type. - * @param {import("../events.js").Listener} listener Listener. - */ - Target.prototype.removeEventListener = function (type, listener) { - var listeners = this.listeners_ && this.listeners_[type]; - if (listeners) { - var index = listeners.indexOf(listener); - if (index !== -1) { - if (this.pendingRemovals_ && type in this.pendingRemovals_) { - // make listener a no-op, and remove later in #dispatchEvent() - listeners[index] = VOID; - ++this.pendingRemovals_[type]; - } - else { - listeners.splice(index, 1); - if (listeners.length === 0) { - delete this.listeners_[type]; - } - } - } - } - }; - return Target; -}(Disposable)); - -/** - * @module ol/events/EventType - */ -/** - * @enum {string} - * @const - */ -var EventType = { - /** - * Generic change event. Triggered when the revision counter is increased. - * @event module:ol/events/Event~BaseEvent#change - * @api - */ - CHANGE: 'change', - /** - * Generic error event. Triggered when an error occurs. - * @event module:ol/events/Event~BaseEvent#error - * @api - */ - ERROR: 'error', - BLUR: 'blur', - CLEAR: 'clear', - CONTEXTMENU: 'contextmenu', - CLICK: 'click', - DBLCLICK: 'dblclick', - DRAGENTER: 'dragenter', - DRAGOVER: 'dragover', - DROP: 'drop', - FOCUS: 'focus', - KEYDOWN: 'keydown', - KEYPRESS: 'keypress', - LOAD: 'load', - RESIZE: 'resize', - TOUCHMOVE: 'touchmove', - WHEEL: 'wheel', -}; - -/** - * @module ol/events - */ -/** - * Key to use with {@link module:ol/Observable~Observable#unByKey}. - * @typedef {Object} EventsKey - * @property {ListenerFunction} listener - * @property {import("./events/Target.js").EventTargetLike} target - * @property {string} type - * @api - */ -/** - * Listener function. This function is called with an event object as argument. - * When the function returns `false`, event propagation will stop. - * - * @typedef {function((Event|import("./events/Event.js").default)): (void|boolean)} ListenerFunction - * @api - */ -/** - * @typedef {Object} ListenerObject - * @property {ListenerFunction} handleEvent - */ -/** - * @typedef {ListenerFunction|ListenerObject} Listener - */ -/** - * Registers an event listener on an event target. Inspired by - * https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html - * - * This function efficiently binds a `listener` to a `this` object, and returns - * a key for use with {@link module:ol/events~unlistenByKey}. - * - * @param {import("./events/Target.js").EventTargetLike} target Event target. - * @param {string} type Event type. - * @param {ListenerFunction} listener Listener. - * @param {Object=} opt_this Object referenced by the `this` keyword in the - * listener. Default is the `target`. - * @param {boolean=} opt_once If true, add the listener as one-off listener. - * @return {EventsKey} Unique key for the listener. - */ -function listen(target, type, listener, opt_this, opt_once) { - if (opt_this && opt_this !== target) { - listener = listener.bind(opt_this); - } - if (opt_once) { - var originalListener_1 = listener; - listener = function () { - target.removeEventListener(type, listener); - originalListener_1.apply(this, arguments); - }; - } - var eventsKey = { - target: target, - type: type, - listener: listener, - }; - target.addEventListener(type, listener); - return eventsKey; -} -/** - * Registers a one-off event listener on an event target. Inspired by - * https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html - * - * This function efficiently binds a `listener` as self-unregistering listener - * to a `this` object, and returns a key for use with - * {@link module:ol/events~unlistenByKey} in case the listener needs to be - * unregistered before it is called. - * - * When {@link module:ol/events~listen} is called with the same arguments after this - * function, the self-unregistering listener will be turned into a permanent - * listener. - * - * @param {import("./events/Target.js").EventTargetLike} target Event target. - * @param {string} type Event type. - * @param {ListenerFunction} listener Listener. - * @param {Object=} opt_this Object referenced by the `this` keyword in the - * listener. Default is the `target`. - * @return {EventsKey} Key for unlistenByKey. - */ -function listenOnce(target, type, listener, opt_this) { - return listen(target, type, listener, opt_this, true); -} -/** - * Unregisters event listeners on an event target. Inspired by - * https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html - * - * The argument passed to this function is the key returned from - * {@link module:ol/events~listen} or {@link module:ol/events~listenOnce}. - * - * @param {EventsKey} key The key. - */ -function unlistenByKey(key) { - if (key && key.target) { - key.target.removeEventListener(key.type, key.listener); - clear(key); - } -} - -var __extends$1 = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -/** - * @classdesc - * Abstract base class; normally only used for creating subclasses and not - * instantiated in apps. - * An event target providing convenient methods for listener registration - * and unregistration. A generic `change` event is always available through - * {@link module:ol/Observable~Observable#changed}. - * - * @fires import("./events/Event.js").default - * @api - */ -var Observable = /** @class */ (function (_super) { - __extends$1(Observable, _super); - function Observable() { - var _this = _super.call(this) || this; - /** - * @private - * @type {number} - */ - _this.revision_ = 0; - return _this; - } - /** - * Increases the revision counter and dispatches a 'change' event. - * @api - */ - Observable.prototype.changed = function () { - ++this.revision_; - this.dispatchEvent(EventType.CHANGE); - }; - /** - * Get the version number for this object. Each time the object is modified, - * its version number will be incremented. - * @return {number} Revision. - * @api - */ - Observable.prototype.getRevision = function () { - return this.revision_; - }; - /** - * Listen for a certain type of event. - * @param {string|Array} type The event type or array of event types. - * @param {function(?): ?} listener The listener function. - * @return {import("./events.js").EventsKey|Array} Unique key for the listener. If - * called with an array of event types as the first argument, the return - * will be an array of keys. - * @api - */ - Observable.prototype.on = function (type, listener) { - if (Array.isArray(type)) { - var len = type.length; - var keys = new Array(len); - for (var i = 0; i < len; ++i) { - keys[i] = listen(this, type[i], listener); - } - return keys; - } - else { - return listen(this, /** @type {string} */ (type), listener); - } - }; - /** - * Listen once for a certain type of event. - * @param {string|Array} type The event type or array of event types. - * @param {function(?): ?} listener The listener function. - * @return {import("./events.js").EventsKey|Array} Unique key for the listener. If - * called with an array of event types as the first argument, the return - * will be an array of keys. - * @api - */ - Observable.prototype.once = function (type, listener) { - var key; - if (Array.isArray(type)) { - var len = type.length; - key = new Array(len); - for (var i = 0; i < len; ++i) { - key[i] = listenOnce(this, type[i], listener); - } - } - else { - key = listenOnce(this, /** @type {string} */ (type), listener); - } - /** @type {Object} */ (listener).ol_key = key; - return key; - }; - /** - * Unlisten for a certain type of event. - * @param {string|Array} type The event type or array of event types. - * @param {function(?): ?} listener The listener function. - * @api - */ - Observable.prototype.un = function (type, listener) { - var key = /** @type {Object} */ (listener).ol_key; - if (key) { - unByKey(key); - } - else if (Array.isArray(type)) { - for (var i = 0, ii = type.length; i < ii; ++i) { - this.removeEventListener(type[i], listener); - } - } - else { - this.removeEventListener(type, listener); - } - }; - return Observable; -}(Target)); -/** - * Removes an event listener using the key returned by `on()` or `once()`. - * @param {import("./events.js").EventsKey|Array} key The key returned by `on()` - * or `once()` (or an array of keys). - * @api - */ -function unByKey(key) { - if (Array.isArray(key)) { - for (var i = 0, ii = key.length; i < ii; ++i) { - unlistenByKey(key[i]); - } - } - else { - unlistenByKey(/** @type {import("./events.js").EventsKey} */ (key)); - } -} - -/** - * @module ol/util - */ -/** - * @return {?} Any return. - */ -function abstract() { - return /** @type {?} */ ((function () { - throw new Error('Unimplemented abstract method.'); - })()); -} -/** - * Counter for getUid. - * @type {number} - * @private - */ -var uidCounter_ = 0; -/** - * Gets a unique ID for an object. This mutates the object so that further calls - * with the same object as a parameter returns the same value. Unique IDs are generated - * as a strictly increasing sequence. Adapted from goog.getUid. - * - * @param {Object} obj The object to get the unique ID for. - * @return {string} The unique ID for the object. - * @api - */ -function getUid(obj) { - return obj.ol_uid || (obj.ol_uid = String(++uidCounter_)); -} -/** - * OpenLayers version. - * @type {string} - */ -var VERSION = '6.5.0'; - -var __extends$2 = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -/** - * @classdesc - * Events emitted by {@link module:ol/Object~BaseObject} instances are instances of this type. - */ -var ObjectEvent = /** @class */ (function (_super) { - __extends$2(ObjectEvent, _super); - /** - * @param {string} type The event type. - * @param {string} key The property name. - * @param {*} oldValue The old value for `key`. - */ - function ObjectEvent(type, key, oldValue) { - var _this = _super.call(this, type) || this; - /** - * The name of the property whose value is changing. - * @type {string} - * @api - */ - _this.key = key; - /** - * The old value. To get the new value use `e.target.get(e.key)` where - * `e` is the event object. - * @type {*} - * @api - */ - _this.oldValue = oldValue; - return _this; - } - return ObjectEvent; -}(BaseEvent)); -/** - * @classdesc - * Abstract base class; normally only used for creating subclasses and not - * instantiated in apps. - * Most non-trivial classes inherit from this. - * - * This extends {@link module:ol/Observable} with observable - * properties, where each property is observable as well as the object as a - * whole. - * - * Classes that inherit from this have pre-defined properties, to which you can - * add your owns. The pre-defined properties are listed in this documentation as - * 'Observable Properties', and have their own accessors; for example, - * {@link module:ol/Map~Map} has a `target` property, accessed with - * `getTarget()` and changed with `setTarget()`. Not all properties are however - * settable. There are also general-purpose accessors `get()` and `set()`. For - * example, `get('target')` is equivalent to `getTarget()`. - * - * The `set` accessors trigger a change event, and you can monitor this by - * registering a listener. For example, {@link module:ol/View~View} has a - * `center` property, so `view.on('change:center', function(evt) {...});` would - * call the function whenever the value of the center property changes. Within - * the function, `evt.target` would be the view, so `evt.target.getCenter()` - * would return the new center. - * - * You can add your own observable properties with - * `object.set('prop', 'value')`, and retrieve that with `object.get('prop')`. - * You can listen for changes on that property value with - * `object.on('change:prop', listener)`. You can get a list of all - * properties with {@link module:ol/Object~BaseObject#getProperties}. - * - * Note that the observable properties are separate from standard JS properties. - * You can, for example, give your map object a title with - * `map.title='New title'` and with `map.set('title', 'Another title')`. The - * first will be a `hasOwnProperty`; the second will appear in - * `getProperties()`. Only the second is observable. - * - * Properties can be deleted by using the unset method. E.g. - * object.unset('foo'). - * - * @fires ObjectEvent - * @api - */ -var BaseObject = /** @class */ (function (_super) { - __extends$2(BaseObject, _super); - /** - * @param {Object=} opt_values An object with key-value pairs. - */ - function BaseObject(opt_values) { - var _this = _super.call(this) || this; - // Call {@link module:ol/util~getUid} to ensure that the order of objects' ids is - // the same as the order in which they were created. This also helps to - // ensure that object properties are always added in the same order, which - // helps many JavaScript engines generate faster code. - getUid(_this); - /** - * @private - * @type {Object} - */ - _this.values_ = null; - if (opt_values !== undefined) { - _this.setProperties(opt_values); - } - return _this; - } - /** - * Gets a value. - * @param {string} key Key name. - * @return {*} Value. - * @api - */ - BaseObject.prototype.get = function (key) { - var value; - if (this.values_ && this.values_.hasOwnProperty(key)) { - value = this.values_[key]; - } - return value; - }; - /** - * Get a list of object property names. - * @return {Array} List of property names. - * @api - */ - BaseObject.prototype.getKeys = function () { - return (this.values_ && Object.keys(this.values_)) || []; - }; - /** - * Get an object of all property names and values. - * @return {Object} Object. - * @api - */ - BaseObject.prototype.getProperties = function () { - return (this.values_ && assign({}, this.values_)) || {}; - }; - /** - * @return {boolean} The object has properties. - */ - BaseObject.prototype.hasProperties = function () { - return !!this.values_; - }; - /** - * @param {string} key Key name. - * @param {*} oldValue Old value. - */ - BaseObject.prototype.notify = function (key, oldValue) { - var eventType; - eventType = getChangeEventType(key); - this.dispatchEvent(new ObjectEvent(eventType, key, oldValue)); - eventType = ObjectEventType.PROPERTYCHANGE; - this.dispatchEvent(new ObjectEvent(eventType, key, oldValue)); - }; - /** - * Sets a value. - * @param {string} key Key name. - * @param {*} value Value. - * @param {boolean=} opt_silent Update without triggering an event. - * @api - */ - BaseObject.prototype.set = function (key, value, opt_silent) { - var values = this.values_ || (this.values_ = {}); - if (opt_silent) { - values[key] = value; - } - else { - var oldValue = values[key]; - values[key] = value; - if (oldValue !== value) { - this.notify(key, oldValue); - } - } - }; - /** - * Sets a collection of key-value pairs. Note that this changes any existing - * properties and adds new ones (it does not remove any existing properties). - * @param {Object} values Values. - * @param {boolean=} opt_silent Update without triggering an event. - * @api - */ - BaseObject.prototype.setProperties = function (values, opt_silent) { - for (var key in values) { - this.set(key, values[key], opt_silent); - } - }; - /** - * Apply any properties from another object without triggering events. - * @param {BaseObject} source The source object. - * @protected - */ - BaseObject.prototype.applyProperties = function (source) { - if (!source.values_) { - return; - } - assign(this.values_ || (this.values_ = {}), source.values_); - }; - /** - * Unsets a property. - * @param {string} key Key name. - * @param {boolean=} opt_silent Unset without triggering an event. - * @api - */ - BaseObject.prototype.unset = function (key, opt_silent) { - if (this.values_ && key in this.values_) { - var oldValue = this.values_[key]; - delete this.values_[key]; - if (isEmpty(this.values_)) { - this.values_ = null; - } - if (!opt_silent) { - this.notify(key, oldValue); - } - } - }; - return BaseObject; -}(Observable)); -/** - * @type {Object} - */ -var changeEventTypeCache = {}; -/** - * @param {string} key Key name. - * @return {string} Change name. - */ -function getChangeEventType(key) { - return changeEventTypeCache.hasOwnProperty(key) - ? changeEventTypeCache[key] - : (changeEventTypeCache[key] = 'change:' + key); -} - -/** - * @module ol/geom/GeometryType - */ -/** - * The geometry type. One of `'Point'`, `'LineString'`, `'LinearRing'`, - * `'Polygon'`, `'MultiPoint'`, `'MultiLineString'`, `'MultiPolygon'`, - * `'GeometryCollection'`, `'Circle'`. - * @enum {string} - */ -var GeometryType = { - POINT: 'Point', - LINE_STRING: 'LineString', - LINEAR_RING: 'LinearRing', - POLYGON: 'Polygon', - MULTI_POINT: 'MultiPoint', - MULTI_LINE_STRING: 'MultiLineString', - MULTI_POLYGON: 'MultiPolygon', - GEOMETRY_COLLECTION: 'GeometryCollection', - CIRCLE: 'Circle', -}; - -/** - * @module ol/proj/Units - */ -/** - * Projection units: `'degrees'`, `'ft'`, `'m'`, `'pixels'`, `'tile-pixels'` or - * `'us-ft'`. - * @enum {string} - */ -var Units = { - /** - * Degrees - * @api - */ - DEGREES: 'degrees', - /** - * Feet - * @api - */ - FEET: 'ft', - /** - * Meters - * @api - */ - METERS: 'm', - /** - * Pixels - * @api - */ - PIXELS: 'pixels', - /** - * Tile Pixels - * @api - */ - TILE_PIXELS: 'tile-pixels', - /** - * US Feet - * @api - */ - USFEET: 'us-ft', -}; -/** - * Meters per unit lookup table. - * @const - * @type {Object} - * @api - */ -var METERS_PER_UNIT = {}; -// use the radius of the Normal sphere -METERS_PER_UNIT[Units.DEGREES] = (2 * Math.PI * 6370997) / 360; -METERS_PER_UNIT[Units.FEET] = 0.3048; -METERS_PER_UNIT[Units.METERS] = 1; -METERS_PER_UNIT[Units.USFEET] = 1200 / 3937; - -/** - * @module ol/ViewHint - */ -/** - * @enum {number} - */ -var ViewHint = { - ANIMATING: 0, - INTERACTING: 1, -}; - -/** - * @module ol/ViewProperty - */ -/** - * @enum {string} - */ -var ViewProperty = { - CENTER: 'center', - RESOLUTION: 'resolution', - ROTATION: 'rotation', -}; - -/** - * @module ol/tilegrid/common - */ -/** - * Default maximum zoom for default tile grids. - * @type {number} - */ -var DEFAULT_MAX_ZOOM = 42; -/** - * Default tile size. - * @type {number} - */ -var DEFAULT_TILE_SIZE = 256; - -/** - * @module ol/proj/Projection - */ -/** - * @typedef {Object} Options - * @property {string} code The SRS identifier code, e.g. `EPSG:4326`. - * @property {import("./Units.js").default|string} [units] Units. Required unless a - * proj4 projection is defined for `code`. - * @property {import("../extent.js").Extent} [extent] The validity extent for the SRS. - * @property {string} [axisOrientation='enu'] The axis orientation as specified in Proj4. - * @property {boolean} [global=false] Whether the projection is valid for the whole globe. - * @property {number} [metersPerUnit] The meters per unit for the SRS. - * If not provided, the `units` are used to get the meters per unit from the {@link module:ol/proj/Units~METERS_PER_UNIT} - * lookup table. - * @property {import("../extent.js").Extent} [worldExtent] The world extent for the SRS. - * @property {function(number, import("../coordinate.js").Coordinate):number} [getPointResolution] - * Function to determine resolution at a point. The function is called with a - * `{number}` view resolution and an `{import("../coordinate.js").Coordinate}` as arguments, and returns - * the `{number}` resolution in projection units at the passed coordinate. If this is `undefined`, - * the default {@link module:ol/proj#getPointResolution} function will be used. - */ -/** - * @classdesc - * Projection definition class. One of these is created for each projection - * supported in the application and stored in the {@link module:ol/proj} namespace. - * You can use these in applications, but this is not required, as API params - * and options use {@link module:ol/proj~ProjectionLike} which means the simple string - * code will suffice. - * - * You can use {@link module:ol/proj~get} to retrieve the object for a particular - * projection. - * - * The library includes definitions for `EPSG:4326` and `EPSG:3857`, together - * with the following aliases: - * * `EPSG:4326`: CRS:84, urn:ogc:def:crs:EPSG:6.6:4326, - * urn:ogc:def:crs:OGC:1.3:CRS84, urn:ogc:def:crs:OGC:2:84, - * http://www.opengis.net/gml/srs/epsg.xml#4326, - * urn:x-ogc:def:crs:EPSG:4326 - * * `EPSG:3857`: EPSG:102100, EPSG:102113, EPSG:900913, - * urn:ogc:def:crs:EPSG:6.18:3:3857, - * http://www.opengis.net/gml/srs/epsg.xml#3857 - * - * If you use [proj4js](https://github.com/proj4js/proj4js), aliases can - * be added using `proj4.defs()`. After all required projection definitions are - * added, call the {@link module:ol/proj/proj4~register} function. - * - * @api - */ -var Projection = /** @class */ (function () { - /** - * @param {Options} options Projection options. - */ - function Projection(options) { - /** - * @private - * @type {string} - */ - this.code_ = options.code; - /** - * Units of projected coordinates. When set to `TILE_PIXELS`, a - * `this.extent_` and `this.worldExtent_` must be configured properly for each - * tile. - * @private - * @type {import("./Units.js").default} - */ - this.units_ = /** @type {import("./Units.js").default} */ (options.units); - /** - * Validity extent of the projection in projected coordinates. For projections - * with `TILE_PIXELS` units, this is the extent of the tile in - * tile pixel space. - * @private - * @type {import("../extent.js").Extent} - */ - this.extent_ = options.extent !== undefined ? options.extent : null; - /** - * Extent of the world in EPSG:4326. For projections with - * `TILE_PIXELS` units, this is the extent of the tile in - * projected coordinate space. - * @private - * @type {import("../extent.js").Extent} - */ - this.worldExtent_ = - options.worldExtent !== undefined ? options.worldExtent : null; - /** - * @private - * @type {string} - */ - this.axisOrientation_ = - options.axisOrientation !== undefined ? options.axisOrientation : 'enu'; - /** - * @private - * @type {boolean} - */ - this.global_ = options.global !== undefined ? options.global : false; - /** - * @private - * @type {boolean} - */ - this.canWrapX_ = !!(this.global_ && this.extent_); - /** - * @private - * @type {function(number, import("../coordinate.js").Coordinate):number|undefined} - */ - this.getPointResolutionFunc_ = options.getPointResolution; - /** - * @private - * @type {import("../tilegrid/TileGrid.js").default} - */ - this.defaultTileGrid_ = null; - /** - * @private - * @type {number|undefined} - */ - this.metersPerUnit_ = options.metersPerUnit; - } - /** - * @return {boolean} The projection is suitable for wrapping the x-axis - */ - Projection.prototype.canWrapX = function () { - return this.canWrapX_; - }; - /** - * Get the code for this projection, e.g. 'EPSG:4326'. - * @return {string} Code. - * @api - */ - Projection.prototype.getCode = function () { - return this.code_; - }; - /** - * Get the validity extent for this projection. - * @return {import("../extent.js").Extent} Extent. - * @api - */ - Projection.prototype.getExtent = function () { - return this.extent_; - }; - /** - * Get the units of this projection. - * @return {import("./Units.js").default} Units. - * @api - */ - Projection.prototype.getUnits = function () { - return this.units_; - }; - /** - * Get the amount of meters per unit of this projection. If the projection is - * not configured with `metersPerUnit` or a units identifier, the return is - * `undefined`. - * @return {number|undefined} Meters. - * @api - */ - Projection.prototype.getMetersPerUnit = function () { - return this.metersPerUnit_ || METERS_PER_UNIT[this.units_]; - }; - /** - * Get the world extent for this projection. - * @return {import("../extent.js").Extent} Extent. - * @api - */ - Projection.prototype.getWorldExtent = function () { - return this.worldExtent_; - }; - /** - * Get the axis orientation of this projection. - * Example values are: - * enu - the default easting, northing, elevation. - * neu - northing, easting, up - useful for "lat/long" geographic coordinates, - * or south orientated transverse mercator. - * wnu - westing, northing, up - some planetary coordinate systems have - * "west positive" coordinate systems - * @return {string} Axis orientation. - * @api - */ - Projection.prototype.getAxisOrientation = function () { - return this.axisOrientation_; - }; - /** - * Is this projection a global projection which spans the whole world? - * @return {boolean} Whether the projection is global. - * @api - */ - Projection.prototype.isGlobal = function () { - return this.global_; - }; - /** - * Set if the projection is a global projection which spans the whole world - * @param {boolean} global Whether the projection is global. - * @api - */ - Projection.prototype.setGlobal = function (global) { - this.global_ = global; - this.canWrapX_ = !!(global && this.extent_); - }; - /** - * @return {import("../tilegrid/TileGrid.js").default} The default tile grid. - */ - Projection.prototype.getDefaultTileGrid = function () { - return this.defaultTileGrid_; - }; - /** - * @param {import("../tilegrid/TileGrid.js").default} tileGrid The default tile grid. - */ - Projection.prototype.setDefaultTileGrid = function (tileGrid) { - this.defaultTileGrid_ = tileGrid; - }; - /** - * Set the validity extent for this projection. - * @param {import("../extent.js").Extent} extent Extent. - * @api - */ - Projection.prototype.setExtent = function (extent) { - this.extent_ = extent; - this.canWrapX_ = !!(this.global_ && extent); - }; - /** - * Set the world extent for this projection. - * @param {import("../extent.js").Extent} worldExtent World extent - * [minlon, minlat, maxlon, maxlat]. - * @api - */ - Projection.prototype.setWorldExtent = function (worldExtent) { - this.worldExtent_ = worldExtent; - }; - /** - * Set the getPointResolution function (see {@link module:ol/proj~getPointResolution} - * for this projection. - * @param {function(number, import("../coordinate.js").Coordinate):number} func Function - * @api - */ - Projection.prototype.setGetPointResolution = function (func) { - this.getPointResolutionFunc_ = func; - }; - /** - * Get the custom point resolution function for this projection (if set). - * @return {function(number, import("../coordinate.js").Coordinate):number|undefined} The custom point - * resolution function (if set). - */ - Projection.prototype.getPointResolutionFunc = function () { - return this.getPointResolutionFunc_; - }; - return Projection; -}()); - -/** - * @module ol/math - */ -/** - * Takes a number and clamps it to within the provided bounds. - * @param {number} value The input number. - * @param {number} min The minimum value to return. - * @param {number} max The maximum value to return. - * @return {number} The input number if it is within bounds, or the nearest - * number within the bounds. - */ -function clamp(value, min, max) { - return Math.min(Math.max(value, min), max); -} -/** - * Return the hyperbolic cosine of a given number. The method will use the - * native `Math.cosh` function if it is available, otherwise the hyperbolic - * cosine will be calculated via the reference implementation of the Mozilla - * developer network. - * - * @param {number} x X. - * @return {number} Hyperbolic cosine of x. - */ -var cosh = (function () { - // Wrapped in a iife, to save the overhead of checking for the native - // implementation on every invocation. - var cosh; - if ('cosh' in Math) { - // The environment supports the native Math.cosh function, use it… - cosh = Math.cosh; - } - else { - // … else, use the reference implementation of MDN: - cosh = function (x) { - var y = /** @type {Math} */ (Math).exp(x); - return (y + 1 / y) / 2; - }; - } - return cosh; -})(); -/** - * Return the base 2 logarithm of a given number. The method will use the - * native `Math.log2` function if it is available, otherwise the base 2 - * logarithm will be calculated via the reference implementation of the - * Mozilla developer network. - * - * @param {number} x X. - * @return {number} Base 2 logarithm of x. - */ -var log2 = (function () { - // Wrapped in a iife, to save the overhead of checking for the native - // implementation on every invocation. - var log2; - if ('log2' in Math) { - // The environment supports the native Math.log2 function, use it… - log2 = Math.log2; - } - else { - // … else, use the reference implementation of MDN: - log2 = function (x) { - return Math.log(x) * Math.LOG2E; - }; - } - return log2; -})(); -/** - * Returns the square of the closest distance between the point (x, y) and the - * line segment (x1, y1) to (x2, y2). - * @param {number} x X. - * @param {number} y Y. - * @param {number} x1 X1. - * @param {number} y1 Y1. - * @param {number} x2 X2. - * @param {number} y2 Y2. - * @return {number} Squared distance. - */ -function squaredSegmentDistance(x, y, x1, y1, x2, y2) { - var dx = x2 - x1; - var dy = y2 - y1; - if (dx !== 0 || dy !== 0) { - var t = ((x - x1) * dx + (y - y1) * dy) / (dx * dx + dy * dy); - if (t > 1) { - x1 = x2; - y1 = y2; - } - else if (t > 0) { - x1 += dx * t; - y1 += dy * t; - } - } - return squaredDistance(x, y, x1, y1); -} -/** - * Returns the square of the distance between the points (x1, y1) and (x2, y2). - * @param {number} x1 X1. - * @param {number} y1 Y1. - * @param {number} x2 X2. - * @param {number} y2 Y2. - * @return {number} Squared distance. - */ -function squaredDistance(x1, y1, x2, y2) { - var dx = x2 - x1; - var dy = y2 - y1; - return dx * dx + dy * dy; -} -/** - * Solves system of linear equations using Gaussian elimination method. - * - * @param {Array>} mat Augmented matrix (n x n + 1 column) - * in row-major order. - * @return {Array} The resulting vector. - */ -function solveLinearSystem(mat) { - var n = mat.length; - for (var i = 0; i < n; i++) { - // Find max in the i-th column (ignoring i - 1 first rows) - var maxRow = i; - var maxEl = Math.abs(mat[i][i]); - for (var r = i + 1; r < n; r++) { - var absValue = Math.abs(mat[r][i]); - if (absValue > maxEl) { - maxEl = absValue; - maxRow = r; - } - } - if (maxEl === 0) { - return null; // matrix is singular - } - // Swap max row with i-th (current) row - var tmp = mat[maxRow]; - mat[maxRow] = mat[i]; - mat[i] = tmp; - // Subtract the i-th row to make all the remaining rows 0 in the i-th column - for (var j = i + 1; j < n; j++) { - var coef = -mat[j][i] / mat[i][i]; - for (var k = i; k < n + 1; k++) { - if (i == k) { - mat[j][k] = 0; - } - else { - mat[j][k] += coef * mat[i][k]; - } - } - } - } - // Solve Ax=b for upper triangular matrix A (mat) - var x = new Array(n); - for (var l = n - 1; l >= 0; l--) { - x[l] = mat[l][n] / mat[l][l]; - for (var m = l - 1; m >= 0; m--) { - mat[m][n] -= mat[m][l] * x[l]; - } - } - return x; -} -/** - * Converts degrees to radians. - * - * @param {number} angleInDegrees Angle in degrees. - * @return {number} Angle in radians. - */ -function toRadians(angleInDegrees) { - return (angleInDegrees * Math.PI) / 180; -} -/** - * Returns the modulo of a / b, depending on the sign of b. - * - * @param {number} a Dividend. - * @param {number} b Divisor. - * @return {number} Modulo. - */ -function modulo(a, b) { - var r = a % b; - return r * b < 0 ? r + b : r; -} -/** - * Calculates the linearly interpolated value of x between a and b. - * - * @param {number} a Number - * @param {number} b Number - * @param {number} x Value to be interpolated. - * @return {number} Interpolated value. - */ -function lerp(a, b, x) { - return a + x * (b - a); -} - -var __extends$3 = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -/** - * Radius of WGS84 sphere - * - * @const - * @type {number} - */ -var RADIUS = 6378137; -/** - * @const - * @type {number} - */ -var HALF_SIZE = Math.PI * RADIUS; -/** - * @const - * @type {import("../extent.js").Extent} - */ -var EXTENT = [-HALF_SIZE, -HALF_SIZE, HALF_SIZE, HALF_SIZE]; -/** - * @const - * @type {import("../extent.js").Extent} - */ -var WORLD_EXTENT = [-180, -85, 180, 85]; -/** - * Maximum safe value in y direction - * @const - * @type {number} - */ -var MAX_SAFE_Y = RADIUS * Math.log(Math.tan(Math.PI / 2)); -/** - * @classdesc - * Projection object for web/spherical Mercator (EPSG:3857). - */ -var EPSG3857Projection = /** @class */ (function (_super) { - __extends$3(EPSG3857Projection, _super); - /** - * @param {string} code Code. - */ - function EPSG3857Projection(code) { - return _super.call(this, { - code: code, - units: Units.METERS, - extent: EXTENT, - global: true, - worldExtent: WORLD_EXTENT, - getPointResolution: function (resolution, point) { - return resolution / cosh(point[1] / RADIUS); - }, - }) || this; - } - return EPSG3857Projection; -}(Projection)); -/** - * Projections equal to EPSG:3857. - * - * @const - * @type {Array} - */ -var PROJECTIONS = [ - new EPSG3857Projection('EPSG:3857'), - new EPSG3857Projection('EPSG:102100'), - new EPSG3857Projection('EPSG:102113'), - new EPSG3857Projection('EPSG:900913'), - new EPSG3857Projection('http://www.opengis.net/gml/srs/epsg.xml#3857'), -]; -/** - * Transformation from EPSG:4326 to EPSG:3857. - * - * @param {Array} input Input array of coordinate values. - * @param {Array=} opt_output Output array of coordinate values. - * @param {number=} opt_dimension Dimension (default is `2`). - * @return {Array} Output array of coordinate values. - */ -function fromEPSG4326(input, opt_output, opt_dimension) { - var length = input.length; - var dimension = opt_dimension > 1 ? opt_dimension : 2; - var output = opt_output; - if (output === undefined) { - if (dimension > 2) { - // preserve values beyond second dimension - output = input.slice(); - } - else { - output = new Array(length); - } - } - for (var i = 0; i < length; i += dimension) { - output[i] = (HALF_SIZE * input[i]) / 180; - var y = RADIUS * Math.log(Math.tan((Math.PI * (+input[i + 1] + 90)) / 360)); - if (y > MAX_SAFE_Y) { - y = MAX_SAFE_Y; - } - else if (y < -MAX_SAFE_Y) { - y = -MAX_SAFE_Y; - } - output[i + 1] = y; - } - return output; -} -/** - * Transformation from EPSG:3857 to EPSG:4326. - * - * @param {Array} input Input array of coordinate values. - * @param {Array=} opt_output Output array of coordinate values. - * @param {number=} opt_dimension Dimension (default is `2`). - * @return {Array} Output array of coordinate values. - */ -function toEPSG4326(input, opt_output, opt_dimension) { - var length = input.length; - var dimension = opt_dimension > 1 ? opt_dimension : 2; - var output = opt_output; - if (output === undefined) { - if (dimension > 2) { - // preserve values beyond second dimension - output = input.slice(); - } - else { - output = new Array(length); - } - } - for (var i = 0; i < length; i += dimension) { - output[i] = (180 * input[i]) / HALF_SIZE; - output[i + 1] = - (360 * Math.atan(Math.exp(input[i + 1] / RADIUS))) / Math.PI - 90; - } - return output; -} - -var __extends$4 = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -/** - * Semi-major radius of the WGS84 ellipsoid. - * - * @const - * @type {number} - */ -var RADIUS$1 = 6378137; -/** - * Extent of the EPSG:4326 projection which is the whole world. - * - * @const - * @type {import("../extent.js").Extent} - */ -var EXTENT$1 = [-180, -90, 180, 90]; -/** - * @const - * @type {number} - */ -var METERS_PER_UNIT$1 = (Math.PI * RADIUS$1) / 180; -/** - * @classdesc - * Projection object for WGS84 geographic coordinates (EPSG:4326). - * - * Note that OpenLayers does not strictly comply with the EPSG definition. - * The EPSG registry defines 4326 as a CRS for Latitude,Longitude (y,x). - * OpenLayers treats EPSG:4326 as a pseudo-projection, with x,y coordinates. - */ -var EPSG4326Projection = /** @class */ (function (_super) { - __extends$4(EPSG4326Projection, _super); - /** - * @param {string} code Code. - * @param {string=} opt_axisOrientation Axis orientation. - */ - function EPSG4326Projection(code, opt_axisOrientation) { - return _super.call(this, { - code: code, - units: Units.DEGREES, - extent: EXTENT$1, - axisOrientation: opt_axisOrientation, - global: true, - metersPerUnit: METERS_PER_UNIT$1, - worldExtent: EXTENT$1, - }) || this; - } - return EPSG4326Projection; -}(Projection)); -/** - * Projections equal to EPSG:4326. - * - * @const - * @type {Array} - */ -var PROJECTIONS$1 = [ - new EPSG4326Projection('CRS:84'), - new EPSG4326Projection('EPSG:4326', 'neu'), - new EPSG4326Projection('urn:ogc:def:crs:OGC:1.3:CRS84'), - new EPSG4326Projection('urn:ogc:def:crs:OGC:2:84'), - new EPSG4326Projection('http://www.opengis.net/gml/srs/epsg.xml#4326', 'neu'), -]; - -/** - * @module ol/proj/projections - */ -/** - * @type {Object} - */ -var cache = {}; -/** - * Get a cached projection by code. - * @param {string} code The code for the projection. - * @return {import("./Projection.js").default} The projection (if cached). - */ -function get(code) { - return (cache[code] || - cache[code.replace(/urn:(x-)?ogc:def:crs:EPSG:(.*:)?(\w+)$/, 'EPSG:$3')] || - null); -} -/** - * Add a projection to the cache. - * @param {string} code The projection code. - * @param {import("./Projection.js").default} projection The projection to cache. - */ -function add(code, projection) { - cache[code] = projection; -} - -/** - * @module ol/proj/transforms - */ -/** - * @private - * @type {!Object>} - */ -var transforms = {}; -/** - * Registers a conversion function to convert coordinates from the source - * projection to the destination projection. - * - * @param {import("./Projection.js").default} source Source. - * @param {import("./Projection.js").default} destination Destination. - * @param {import("../proj.js").TransformFunction} transformFn Transform. - */ -function add$1(source, destination, transformFn) { - var sourceCode = source.getCode(); - var destinationCode = destination.getCode(); - if (!(sourceCode in transforms)) { - transforms[sourceCode] = {}; - } - transforms[sourceCode][destinationCode] = transformFn; -} -/** - * Get a transform given a source code and a destination code. - * @param {string} sourceCode The code for the source projection. - * @param {string} destinationCode The code for the destination projection. - * @return {import("../proj.js").TransformFunction|undefined} The transform function (if found). - */ -function get$1(sourceCode, destinationCode) { - var transform; - if (sourceCode in transforms && destinationCode in transforms[sourceCode]) { - transform = transforms[sourceCode][destinationCode]; - } - return transform; -} - -/** - * @module ol/extent/Corner - */ -/** - * Extent corner. - * @enum {string} - */ -var Corner = { - BOTTOM_LEFT: 'bottom-left', - BOTTOM_RIGHT: 'bottom-right', - TOP_LEFT: 'top-left', - TOP_RIGHT: 'top-right', -}; - -/** - * @module ol/extent/Relationship - */ -/** - * Relationship to an extent. - * @enum {number} - */ -var Relationship = { - UNKNOWN: 0, - INTERSECTING: 1, - ABOVE: 2, - RIGHT: 4, - BELOW: 8, - LEFT: 16, -}; - -var __extends$5 = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -/** - * Error object thrown when an assertion failed. This is an ECMA-262 Error, - * extended with a `code` property. - * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error. - */ -var AssertionError = /** @class */ (function (_super) { - __extends$5(AssertionError, _super); - /** - * @param {number} code Error code. - */ - function AssertionError(code) { - var _this = this; - var path = 'v' + VERSION.split('-')[0]; - var message = 'Assertion failed. See https://openlayers.org/en/' + - path + - '/doc/errors/#' + - code + - ' for details.'; - _this = _super.call(this, message) || this; - /** - * Error code. The meaning of the code can be found on - * https://openlayers.org/en/latest/doc/errors/ (replace `latest` with - * the version found in the OpenLayers script's header comment if a version - * other than the latest is used). - * @type {number} - * @api - */ - _this.code = code; - /** - * @type {string} - */ - _this.name = 'AssertionError'; - // Re-assign message, see https://github.com/Rich-Harris/buble/issues/40 - _this.message = message; - return _this; - } - return AssertionError; -}(Error)); - -/** - * @module ol/asserts - */ -/** - * @param {*} assertion Assertion we expected to be truthy. - * @param {number} errorCode Error code. - */ -function assert(assertion, errorCode) { - if (!assertion) { - throw new AssertionError(errorCode); - } -} - -/** - * @module ol/extent - */ -/** - * An array of numbers representing an extent: `[minx, miny, maxx, maxy]`. - * @typedef {Array} Extent - * @api - */ -/** - * Build an extent that includes all given coordinates. - * - * @param {Array} coordinates Coordinates. - * @return {Extent} Bounding extent. - * @api - */ -function boundingExtent(coordinates) { - var extent = createEmpty(); - for (var i = 0, ii = coordinates.length; i < ii; ++i) { - extendCoordinate(extent, coordinates[i]); - } - return extent; -} -/** - * Creates a clone of an extent. - * - * @param {Extent} extent Extent to clone. - * @param {Extent=} opt_extent Extent. - * @return {Extent} The clone. - */ -function clone(extent, opt_extent) { - if (opt_extent) { - opt_extent[0] = extent[0]; - opt_extent[1] = extent[1]; - opt_extent[2] = extent[2]; - opt_extent[3] = extent[3]; - return opt_extent; - } - else { - return extent.slice(); - } -} -/** - * @param {Extent} extent Extent. - * @param {number} x X. - * @param {number} y Y. - * @return {number} Closest squared distance. - */ -function closestSquaredDistanceXY(extent, x, y) { - var dx, dy; - if (x < extent[0]) { - dx = extent[0] - x; - } - else if (extent[2] < x) { - dx = x - extent[2]; - } - else { - dx = 0; - } - if (y < extent[1]) { - dy = extent[1] - y; - } - else if (extent[3] < y) { - dy = y - extent[3]; - } - else { - dy = 0; - } - return dx * dx + dy * dy; -} -/** - * Check if the passed coordinate is contained or on the edge of the extent. - * - * @param {Extent} extent Extent. - * @param {import("./coordinate.js").Coordinate} coordinate Coordinate. - * @return {boolean} The coordinate is contained in the extent. - * @api - */ -function containsCoordinate(extent, coordinate) { - return containsXY(extent, coordinate[0], coordinate[1]); -} -/** - * Check if one extent contains another. - * - * An extent is deemed contained if it lies completely within the other extent, - * including if they share one or more edges. - * - * @param {Extent} extent1 Extent 1. - * @param {Extent} extent2 Extent 2. - * @return {boolean} The second extent is contained by or on the edge of the - * first. - * @api - */ -function containsExtent(extent1, extent2) { - return (extent1[0] <= extent2[0] && - extent2[2] <= extent1[2] && - extent1[1] <= extent2[1] && - extent2[3] <= extent1[3]); -} -/** - * Check if the passed coordinate is contained or on the edge of the extent. - * - * @param {Extent} extent Extent. - * @param {number} x X coordinate. - * @param {number} y Y coordinate. - * @return {boolean} The x, y values are contained in the extent. - * @api - */ -function containsXY(extent, x, y) { - return extent[0] <= x && x <= extent[2] && extent[1] <= y && y <= extent[3]; -} -/** - * Get the relationship between a coordinate and extent. - * @param {Extent} extent The extent. - * @param {import("./coordinate.js").Coordinate} coordinate The coordinate. - * @return {import("./extent/Relationship.js").default} The relationship (bitwise compare with - * import("./extent/Relationship.js").Relationship). - */ -function coordinateRelationship(extent, coordinate) { - var minX = extent[0]; - var minY = extent[1]; - var maxX = extent[2]; - var maxY = extent[3]; - var x = coordinate[0]; - var y = coordinate[1]; - var relationship = Relationship.UNKNOWN; - if (x < minX) { - relationship = relationship | Relationship.LEFT; - } - else if (x > maxX) { - relationship = relationship | Relationship.RIGHT; - } - if (y < minY) { - relationship = relationship | Relationship.BELOW; - } - else if (y > maxY) { - relationship = relationship | Relationship.ABOVE; - } - if (relationship === Relationship.UNKNOWN) { - relationship = Relationship.INTERSECTING; - } - return relationship; -} -/** - * Create an empty extent. - * @return {Extent} Empty extent. - * @api - */ -function createEmpty() { - return [Infinity, Infinity, -Infinity, -Infinity]; -} -/** - * Create a new extent or update the provided extent. - * @param {number} minX Minimum X. - * @param {number} minY Minimum Y. - * @param {number} maxX Maximum X. - * @param {number} maxY Maximum Y. - * @param {Extent=} opt_extent Destination extent. - * @return {Extent} Extent. - */ -function createOrUpdate(minX, minY, maxX, maxY, opt_extent) { - if (opt_extent) { - opt_extent[0] = minX; - opt_extent[1] = minY; - opt_extent[2] = maxX; - opt_extent[3] = maxY; - return opt_extent; - } - else { - return [minX, minY, maxX, maxY]; - } -} -/** - * Create a new empty extent or make the provided one empty. - * @param {Extent=} opt_extent Extent. - * @return {Extent} Extent. - */ -function createOrUpdateEmpty(opt_extent) { - return createOrUpdate(Infinity, Infinity, -Infinity, -Infinity, opt_extent); -} -/** - * @param {import("./coordinate.js").Coordinate} coordinate Coordinate. - * @param {Extent=} opt_extent Extent. - * @return {Extent} Extent. - */ -function createOrUpdateFromCoordinate(coordinate, opt_extent) { - var x = coordinate[0]; - var y = coordinate[1]; - return createOrUpdate(x, y, x, y, opt_extent); -} -/** - * @param {Array} coordinates Coordinates. - * @param {Extent=} opt_extent Extent. - * @return {Extent} Extent. - */ -function createOrUpdateFromCoordinates(coordinates, opt_extent) { - var extent = createOrUpdateEmpty(opt_extent); - return extendCoordinates(extent, coordinates); -} -/** - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {Extent=} opt_extent Extent. - * @return {Extent} Extent. - */ -function createOrUpdateFromFlatCoordinates(flatCoordinates, offset, end, stride, opt_extent) { - var extent = createOrUpdateEmpty(opt_extent); - return extendFlatCoordinates(extent, flatCoordinates, offset, end, stride); -} -/** - * Determine if two extents are equivalent. - * @param {Extent} extent1 Extent 1. - * @param {Extent} extent2 Extent 2. - * @return {boolean} The two extents are equivalent. - * @api - */ -function equals$1(extent1, extent2) { - return (extent1[0] == extent2[0] && - extent1[2] == extent2[2] && - extent1[1] == extent2[1] && - extent1[3] == extent2[3]); -} -/** - * Modify an extent to include another extent. - * @param {Extent} extent1 The extent to be modified. - * @param {Extent} extent2 The extent that will be included in the first. - * @return {Extent} A reference to the first (extended) extent. - * @api - */ -function extend$1(extent1, extent2) { - if (extent2[0] < extent1[0]) { - extent1[0] = extent2[0]; - } - if (extent2[2] > extent1[2]) { - extent1[2] = extent2[2]; - } - if (extent2[1] < extent1[1]) { - extent1[1] = extent2[1]; - } - if (extent2[3] > extent1[3]) { - extent1[3] = extent2[3]; - } - return extent1; -} -/** - * @param {Extent} extent Extent. - * @param {import("./coordinate.js").Coordinate} coordinate Coordinate. - */ -function extendCoordinate(extent, coordinate) { - if (coordinate[0] < extent[0]) { - extent[0] = coordinate[0]; - } - if (coordinate[0] > extent[2]) { - extent[2] = coordinate[0]; - } - if (coordinate[1] < extent[1]) { - extent[1] = coordinate[1]; - } - if (coordinate[1] > extent[3]) { - extent[3] = coordinate[1]; - } -} -/** - * @param {Extent} extent Extent. - * @param {Array} coordinates Coordinates. - * @return {Extent} Extent. - */ -function extendCoordinates(extent, coordinates) { - for (var i = 0, ii = coordinates.length; i < ii; ++i) { - extendCoordinate(extent, coordinates[i]); - } - return extent; -} -/** - * @param {Extent} extent Extent. - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @return {Extent} Extent. - */ -function extendFlatCoordinates(extent, flatCoordinates, offset, end, stride) { - for (; offset < end; offset += stride) { - extendXY(extent, flatCoordinates[offset], flatCoordinates[offset + 1]); - } - return extent; -} -/** - * @param {Extent} extent Extent. - * @param {number} x X. - * @param {number} y Y. - */ -function extendXY(extent, x, y) { - extent[0] = Math.min(extent[0], x); - extent[1] = Math.min(extent[1], y); - extent[2] = Math.max(extent[2], x); - extent[3] = Math.max(extent[3], y); -} -/** - * This function calls `callback` for each corner of the extent. If the - * callback returns a truthy value the function returns that value - * immediately. Otherwise the function returns `false`. - * @param {Extent} extent Extent. - * @param {function(import("./coordinate.js").Coordinate): S} callback Callback. - * @return {S|boolean} Value. - * @template S - */ -function forEachCorner(extent, callback) { - var val; - val = callback(getBottomLeft(extent)); - if (val) { - return val; - } - val = callback(getBottomRight(extent)); - if (val) { - return val; - } - val = callback(getTopRight(extent)); - if (val) { - return val; - } - val = callback(getTopLeft(extent)); - if (val) { - return val; - } - return false; -} -/** - * Get the size of an extent. - * @param {Extent} extent Extent. - * @return {number} Area. - * @api - */ -function getArea(extent) { - var area = 0; - if (!isEmpty$1(extent)) { - area = getWidth(extent) * getHeight(extent); - } - return area; -} -/** - * Get the bottom left coordinate of an extent. - * @param {Extent} extent Extent. - * @return {import("./coordinate.js").Coordinate} Bottom left coordinate. - * @api - */ -function getBottomLeft(extent) { - return [extent[0], extent[1]]; -} -/** - * Get the bottom right coordinate of an extent. - * @param {Extent} extent Extent. - * @return {import("./coordinate.js").Coordinate} Bottom right coordinate. - * @api - */ -function getBottomRight(extent) { - return [extent[2], extent[1]]; -} -/** - * Get the center coordinate of an extent. - * @param {Extent} extent Extent. - * @return {import("./coordinate.js").Coordinate} Center. - * @api - */ -function getCenter(extent) { - return [(extent[0] + extent[2]) / 2, (extent[1] + extent[3]) / 2]; -} -/** - * Get a corner coordinate of an extent. - * @param {Extent} extent Extent. - * @param {import("./extent/Corner.js").default} corner Corner. - * @return {import("./coordinate.js").Coordinate} Corner coordinate. - */ -function getCorner(extent, corner) { - var coordinate; - if (corner === Corner.BOTTOM_LEFT) { - coordinate = getBottomLeft(extent); - } - else if (corner === Corner.BOTTOM_RIGHT) { - coordinate = getBottomRight(extent); - } - else if (corner === Corner.TOP_LEFT) { - coordinate = getTopLeft(extent); - } - else if (corner === Corner.TOP_RIGHT) { - coordinate = getTopRight(extent); - } - else { - assert(false, 13); // Invalid corner - } - return coordinate; -} -/** - * @param {import("./coordinate.js").Coordinate} center Center. - * @param {number} resolution Resolution. - * @param {number} rotation Rotation. - * @param {import("./size.js").Size} size Size. - * @param {Extent=} opt_extent Destination extent. - * @return {Extent} Extent. - */ -function getForViewAndSize(center, resolution, rotation, size, opt_extent) { - var dx = (resolution * size[0]) / 2; - var dy = (resolution * size[1]) / 2; - var cosRotation = Math.cos(rotation); - var sinRotation = Math.sin(rotation); - var xCos = dx * cosRotation; - var xSin = dx * sinRotation; - var yCos = dy * cosRotation; - var ySin = dy * sinRotation; - var x = center[0]; - var y = center[1]; - var x0 = x - xCos + ySin; - var x1 = x - xCos - ySin; - var x2 = x + xCos - ySin; - var x3 = x + xCos + ySin; - var y0 = y - xSin - yCos; - var y1 = y - xSin + yCos; - var y2 = y + xSin + yCos; - var y3 = y + xSin - yCos; - return createOrUpdate(Math.min(x0, x1, x2, x3), Math.min(y0, y1, y2, y3), Math.max(x0, x1, x2, x3), Math.max(y0, y1, y2, y3), opt_extent); -} -/** - * Get the height of an extent. - * @param {Extent} extent Extent. - * @return {number} Height. - * @api - */ -function getHeight(extent) { - return extent[3] - extent[1]; -} -/** - * Get the intersection of two extents. - * @param {Extent} extent1 Extent 1. - * @param {Extent} extent2 Extent 2. - * @param {Extent=} opt_extent Optional extent to populate with intersection. - * @return {Extent} Intersecting extent. - * @api - */ -function getIntersection(extent1, extent2, opt_extent) { - var intersection = opt_extent ? opt_extent : createEmpty(); - if (intersects(extent1, extent2)) { - if (extent1[0] > extent2[0]) { - intersection[0] = extent1[0]; - } - else { - intersection[0] = extent2[0]; - } - if (extent1[1] > extent2[1]) { - intersection[1] = extent1[1]; - } - else { - intersection[1] = extent2[1]; - } - if (extent1[2] < extent2[2]) { - intersection[2] = extent1[2]; - } - else { - intersection[2] = extent2[2]; - } - if (extent1[3] < extent2[3]) { - intersection[3] = extent1[3]; - } - else { - intersection[3] = extent2[3]; - } - } - else { - createOrUpdateEmpty(intersection); - } - return intersection; -} -/** - * Get the top left coordinate of an extent. - * @param {Extent} extent Extent. - * @return {import("./coordinate.js").Coordinate} Top left coordinate. - * @api - */ -function getTopLeft(extent) { - return [extent[0], extent[3]]; -} -/** - * Get the top right coordinate of an extent. - * @param {Extent} extent Extent. - * @return {import("./coordinate.js").Coordinate} Top right coordinate. - * @api - */ -function getTopRight(extent) { - return [extent[2], extent[3]]; -} -/** - * Get the width of an extent. - * @param {Extent} extent Extent. - * @return {number} Width. - * @api - */ -function getWidth(extent) { - return extent[2] - extent[0]; -} -/** - * Determine if one extent intersects another. - * @param {Extent} extent1 Extent 1. - * @param {Extent} extent2 Extent. - * @return {boolean} The two extents intersect. - * @api - */ -function intersects(extent1, extent2) { - return (extent1[0] <= extent2[2] && - extent1[2] >= extent2[0] && - extent1[1] <= extent2[3] && - extent1[3] >= extent2[1]); -} -/** - * Determine if an extent is empty. - * @param {Extent} extent Extent. - * @return {boolean} Is empty. - * @api - */ -function isEmpty$1(extent) { - return extent[2] < extent[0] || extent[3] < extent[1]; -} -/** - * @param {Extent} extent Extent. - * @param {Extent=} opt_extent Extent. - * @return {Extent} Extent. - */ -function returnOrUpdate(extent, opt_extent) { - if (opt_extent) { - opt_extent[0] = extent[0]; - opt_extent[1] = extent[1]; - opt_extent[2] = extent[2]; - opt_extent[3] = extent[3]; - return opt_extent; - } - else { - return extent; - } -} -/** - * @param {Extent} extent Extent. - * @param {number} value Value. - */ -function scaleFromCenter(extent, value) { - var deltaX = ((extent[2] - extent[0]) / 2) * (value - 1); - var deltaY = ((extent[3] - extent[1]) / 2) * (value - 1); - extent[0] -= deltaX; - extent[2] += deltaX; - extent[1] -= deltaY; - extent[3] += deltaY; -} -/** - * Determine if the segment between two coordinates intersects (crosses, - * touches, or is contained by) the provided extent. - * @param {Extent} extent The extent. - * @param {import("./coordinate.js").Coordinate} start Segment start coordinate. - * @param {import("./coordinate.js").Coordinate} end Segment end coordinate. - * @return {boolean} The segment intersects the extent. - */ -function intersectsSegment(extent, start, end) { - var intersects = false; - var startRel = coordinateRelationship(extent, start); - var endRel = coordinateRelationship(extent, end); - if (startRel === Relationship.INTERSECTING || - endRel === Relationship.INTERSECTING) { - intersects = true; - } - else { - var minX = extent[0]; - var minY = extent[1]; - var maxX = extent[2]; - var maxY = extent[3]; - var startX = start[0]; - var startY = start[1]; - var endX = end[0]; - var endY = end[1]; - var slope = (endY - startY) / (endX - startX); - var x = void 0, y = void 0; - if (!!(endRel & Relationship.ABOVE) && !(startRel & Relationship.ABOVE)) { - // potentially intersects top - x = endX - (endY - maxY) / slope; - intersects = x >= minX && x <= maxX; - } - if (!intersects && - !!(endRel & Relationship.RIGHT) && - !(startRel & Relationship.RIGHT)) { - // potentially intersects right - y = endY - (endX - maxX) * slope; - intersects = y >= minY && y <= maxY; - } - if (!intersects && - !!(endRel & Relationship.BELOW) && - !(startRel & Relationship.BELOW)) { - // potentially intersects bottom - x = endX - (endY - minY) / slope; - intersects = x >= minX && x <= maxX; - } - if (!intersects && - !!(endRel & Relationship.LEFT) && - !(startRel & Relationship.LEFT)) { - // potentially intersects left - y = endY - (endX - minX) * slope; - intersects = y >= minY && y <= maxY; - } - } - return intersects; -} - -/** - * @module ol/sphere - */ -/** - * Object literal with options for the {@link getLength} or {@link getArea} - * functions. - * @typedef {Object} SphereMetricOptions - * @property {import("./proj.js").ProjectionLike} [projection='EPSG:3857'] - * Projection of the geometry. By default, the geometry is assumed to be in - * Web Mercator. - * @property {number} [radius=6371008.8] Sphere radius. By default, the - * [mean Earth radius](https://en.wikipedia.org/wiki/Earth_radius#Mean_radius) - * for the WGS84 ellipsoid is used. - */ -/** - * The mean Earth radius (1/3 * (2a + b)) for the WGS84 ellipsoid. - * https://en.wikipedia.org/wiki/Earth_radius#Mean_radius - * @type {number} - */ -var DEFAULT_RADIUS = 6371008.8; -/** - * Get the great circle distance (in meters) between two geographic coordinates. - * @param {Array} c1 Starting coordinate. - * @param {Array} c2 Ending coordinate. - * @param {number=} opt_radius The sphere radius to use. Defaults to the Earth's - * mean radius using the WGS84 ellipsoid. - * @return {number} The great circle distance between the points (in meters). - * @api - */ -function getDistance(c1, c2, opt_radius) { - var radius = opt_radius || DEFAULT_RADIUS; - var lat1 = toRadians(c1[1]); - var lat2 = toRadians(c2[1]); - var deltaLatBy2 = (lat2 - lat1) / 2; - var deltaLonBy2 = toRadians(c2[0] - c1[0]) / 2; - var a = Math.sin(deltaLatBy2) * Math.sin(deltaLatBy2) + - Math.sin(deltaLonBy2) * - Math.sin(deltaLonBy2) * - Math.cos(lat1) * - Math.cos(lat2); - return 2 * radius * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); -} - -/** - * @module ol/coordinate - */ -/** - * An array of numbers representing an xy coordinate. Example: `[16, 48]`. - * @typedef {Array} Coordinate - * @api - */ -/** - * A function that takes a {@link module:ol/coordinate~Coordinate} and - * transforms it into a `{string}`. - * - * @typedef {function((Coordinate|undefined)): string} CoordinateFormat - * @api - */ -/** - * Add `delta` to `coordinate`. `coordinate` is modified in place and returned - * by the function. - * - * Example: - * - * import {add} from 'ol/coordinate'; - * - * var coord = [7.85, 47.983333]; - * add(coord, [-2, 4]); - * // coord is now [5.85, 51.983333] - * - * @param {Coordinate} coordinate Coordinate. - * @param {Coordinate} delta Delta. - * @return {Coordinate} The input coordinate adjusted by - * the given delta. - * @api - */ -function add$2(coordinate, delta) { - coordinate[0] += +delta[0]; - coordinate[1] += +delta[1]; - return coordinate; -} -/** - * @param {Coordinate} coordinate1 First coordinate. - * @param {Coordinate} coordinate2 Second coordinate. - * @return {boolean} The two coordinates are equal. - */ -function equals$2(coordinate1, coordinate2) { - var equals = true; - for (var i = coordinate1.length - 1; i >= 0; --i) { - if (coordinate1[i] != coordinate2[i]) { - equals = false; - break; - } - } - return equals; -} -/** - * Rotate `coordinate` by `angle`. `coordinate` is modified in place and - * returned by the function. - * - * Example: - * - * import {rotate} from 'ol/coordinate'; - * - * var coord = [7.85, 47.983333]; - * var rotateRadians = Math.PI / 2; // 90 degrees - * rotate(coord, rotateRadians); - * // coord is now [-47.983333, 7.85] - * - * @param {Coordinate} coordinate Coordinate. - * @param {number} angle Angle in radian. - * @return {Coordinate} Coordinate. - * @api - */ -function rotate(coordinate, angle) { - var cosAngle = Math.cos(angle); - var sinAngle = Math.sin(angle); - var x = coordinate[0] * cosAngle - coordinate[1] * sinAngle; - var y = coordinate[1] * cosAngle + coordinate[0] * sinAngle; - coordinate[0] = x; - coordinate[1] = y; - return coordinate; -} -/** - * Scale `coordinate` by `scale`. `coordinate` is modified in place and returned - * by the function. - * - * Example: - * - * import {scale as scaleCoordinate} from 'ol/coordinate'; - * - * var coord = [7.85, 47.983333]; - * var scale = 1.2; - * scaleCoordinate(coord, scale); - * // coord is now [9.42, 57.5799996] - * - * @param {Coordinate} coordinate Coordinate. - * @param {number} scale Scale factor. - * @return {Coordinate} Coordinate. - */ -function scale(coordinate, scale) { - coordinate[0] *= scale; - coordinate[1] *= scale; - return coordinate; -} -/** - * Modifies the provided coordinate in-place to be within the real world - * extent. The lower projection extent boundary is inclusive, the upper one - * exclusive. - * - * @param {Coordinate} coordinate Coordinate. - * @param {import("./proj/Projection.js").default} projection Projection. - * @return {Coordinate} The coordinate within the real world extent. - */ -function wrapX(coordinate, projection) { - if (projection.canWrapX()) { - var worldWidth = getWidth(projection.getExtent()); - var worldsAway = getWorldsAway(coordinate, projection, worldWidth); - if (worldsAway) { - coordinate[0] -= worldsAway * worldWidth; - } - } - return coordinate; -} -/** - * @param {Coordinate} coordinate Coordinate. - * @param {import("./proj/Projection.js").default} projection Projection. - * @param {number=} opt_sourceExtentWidth Width of the source extent. - * @return {number} Offset in world widths. - */ -function getWorldsAway(coordinate, projection, opt_sourceExtentWidth) { - var projectionExtent = projection.getExtent(); - var worldsAway = 0; - if (projection.canWrapX() && - (coordinate[0] < projectionExtent[0] || coordinate[0] > projectionExtent[2])) { - var sourceExtentWidth = opt_sourceExtentWidth || getWidth(projectionExtent); - worldsAway = Math.floor((coordinate[0] - projectionExtent[0]) / sourceExtentWidth); - } - return worldsAway; -} - -/** - * @module ol/proj - */ -/** - * @param {Array} input Input coordinate array. - * @param {Array=} opt_output Output array of coordinate values. - * @param {number=} opt_dimension Dimension. - * @return {Array} Output coordinate array (new array, same coordinate - * values). - */ -function cloneTransform(input, opt_output, opt_dimension) { - var output; - if (opt_output !== undefined) { - for (var i = 0, ii = input.length; i < ii; ++i) { - opt_output[i] = input[i]; - } - output = opt_output; - } - else { - output = input.slice(); - } - return output; -} -/** - * @param {Array} input Input coordinate array. - * @param {Array=} opt_output Output array of coordinate values. - * @param {number=} opt_dimension Dimension. - * @return {Array} Input coordinate array (same array as input). - */ -function identityTransform(input, opt_output, opt_dimension) { - if (opt_output !== undefined && input !== opt_output) { - for (var i = 0, ii = input.length; i < ii; ++i) { - opt_output[i] = input[i]; - } - input = opt_output; - } - return input; -} -/** - * Add a Projection object to the list of supported projections that can be - * looked up by their code. - * - * @param {Projection} projection Projection instance. - * @api - */ -function addProjection(projection) { - add(projection.getCode(), projection); - add$1(projection, projection, cloneTransform); -} -/** - * @param {Array} projections Projections. - */ -function addProjections(projections) { - projections.forEach(addProjection); -} -/** - * Fetches a Projection object for the code specified. - * - * @param {ProjectionLike} projectionLike Either a code string which is - * a combination of authority and identifier such as "EPSG:4326", or an - * existing projection object, or undefined. - * @return {Projection} Projection object, or null if not in list. - * @api - */ -function get$2(projectionLike) { - return typeof projectionLike === 'string' - ? get(/** @type {string} */ (projectionLike)) - : /** @type {Projection} */ (projectionLike) || null; -} -/** - * Get the resolution of the point in degrees or distance units. - * For projections with degrees as the unit this will simply return the - * provided resolution. For other projections the point resolution is - * by default estimated by transforming the 'point' pixel to EPSG:4326, - * measuring its width and height on the normal sphere, - * and taking the average of the width and height. - * A custom function can be provided for a specific projection, either - * by setting the `getPointResolution` option in the - * {@link module:ol/proj/Projection~Projection} constructor or by using - * {@link module:ol/proj/Projection~Projection#setGetPointResolution} to change an existing - * projection object. - * @param {ProjectionLike} projection The projection. - * @param {number} resolution Nominal resolution in projection units. - * @param {import("./coordinate.js").Coordinate} point Point to find adjusted resolution at. - * @param {import("./proj/Units.js").default=} opt_units Units to get the point resolution in. - * Default is the projection's units. - * @return {number} Point resolution. - * @api - */ -function getPointResolution(projection, resolution, point, opt_units) { - projection = get$2(projection); - var pointResolution; - var getter = projection.getPointResolutionFunc(); - if (getter) { - pointResolution = getter(resolution, point); - if (opt_units && opt_units !== projection.getUnits()) { - var metersPerUnit = projection.getMetersPerUnit(); - if (metersPerUnit) { - pointResolution = - (pointResolution * metersPerUnit) / METERS_PER_UNIT[opt_units]; - } - } - } - else { - var units = projection.getUnits(); - if ((units == Units.DEGREES && !opt_units) || opt_units == Units.DEGREES) { - pointResolution = resolution; - } - else { - // Estimate point resolution by transforming the center pixel to EPSG:4326, - // measuring its width and height on the normal sphere, and taking the - // average of the width and height. - var toEPSG4326_1 = getTransformFromProjections(projection, get$2('EPSG:4326')); - if (toEPSG4326_1 === identityTransform && units !== Units.DEGREES) { - // no transform is available - pointResolution = resolution * projection.getMetersPerUnit(); - } - else { - var vertices = [ - point[0] - resolution / 2, - point[1], - point[0] + resolution / 2, - point[1], - point[0], - point[1] - resolution / 2, - point[0], - point[1] + resolution / 2, - ]; - vertices = toEPSG4326_1(vertices, vertices, 2); - var width = getDistance(vertices.slice(0, 2), vertices.slice(2, 4)); - var height = getDistance(vertices.slice(4, 6), vertices.slice(6, 8)); - pointResolution = (width + height) / 2; - } - var metersPerUnit = opt_units - ? METERS_PER_UNIT[opt_units] - : projection.getMetersPerUnit(); - if (metersPerUnit !== undefined) { - pointResolution /= metersPerUnit; - } - } - } - return pointResolution; -} -/** - * Registers transformation functions that don't alter coordinates. Those allow - * to transform between projections with equal meaning. - * - * @param {Array} projections Projections. - * @api - */ -function addEquivalentProjections(projections) { - addProjections(projections); - projections.forEach(function (source) { - projections.forEach(function (destination) { - if (source !== destination) { - add$1(source, destination, cloneTransform); - } - }); - }); -} -/** - * Registers transformation functions to convert coordinates in any projection - * in projection1 to any projection in projection2. - * - * @param {Array} projections1 Projections with equal - * meaning. - * @param {Array} projections2 Projections with equal - * meaning. - * @param {TransformFunction} forwardTransform Transformation from any - * projection in projection1 to any projection in projection2. - * @param {TransformFunction} inverseTransform Transform from any projection - * in projection2 to any projection in projection1.. - */ -function addEquivalentTransforms(projections1, projections2, forwardTransform, inverseTransform) { - projections1.forEach(function (projection1) { - projections2.forEach(function (projection2) { - add$1(projection1, projection2, forwardTransform); - add$1(projection2, projection1, inverseTransform); - }); - }); -} -/** - * @param {Projection|string|undefined} projection Projection. - * @param {string} defaultCode Default code. - * @return {Projection} Projection. - */ -function createProjection(projection, defaultCode) { - if (!projection) { - return get$2(defaultCode); - } - else if (typeof projection === 'string') { - return get$2(projection); - } - else { - return /** @type {Projection} */ (projection); - } -} -/** - * Checks if two projections are the same, that is every coordinate in one - * projection does represent the same geographic point as the same coordinate in - * the other projection. - * - * @param {Projection} projection1 Projection 1. - * @param {Projection} projection2 Projection 2. - * @return {boolean} Equivalent. - * @api - */ -function equivalent(projection1, projection2) { - if (projection1 === projection2) { - return true; - } - var equalUnits = projection1.getUnits() === projection2.getUnits(); - if (projection1.getCode() === projection2.getCode()) { - return equalUnits; - } - else { - var transformFunc = getTransformFromProjections(projection1, projection2); - return transformFunc === cloneTransform && equalUnits; - } -} -/** - * Searches in the list of transform functions for the function for converting - * coordinates from the source projection to the destination projection. - * - * @param {Projection} sourceProjection Source Projection object. - * @param {Projection} destinationProjection Destination Projection - * object. - * @return {TransformFunction} Transform function. - */ -function getTransformFromProjections(sourceProjection, destinationProjection) { - var sourceCode = sourceProjection.getCode(); - var destinationCode = destinationProjection.getCode(); - var transformFunc = get$1(sourceCode, destinationCode); - if (!transformFunc) { - transformFunc = identityTransform; - } - return transformFunc; -} -/** - * Given the projection-like objects, searches for a transformation - * function to convert a coordinates array from the source projection to the - * destination projection. - * - * @param {ProjectionLike} source Source. - * @param {ProjectionLike} destination Destination. - * @return {TransformFunction} Transform function. - * @api - */ -function getTransform(source, destination) { - var sourceProjection = get$2(source); - var destinationProjection = get$2(destination); - return getTransformFromProjections(sourceProjection, destinationProjection); -} -/** - * Transforms a coordinate from source projection to destination projection. - * This returns a new coordinate (and does not modify the original). - * - * See {@link module:ol/proj~transformExtent} for extent transformation. - * See the transform method of {@link module:ol/geom/Geometry~Geometry} and its - * subclasses for geometry transforms. - * - * @param {import("./coordinate.js").Coordinate} coordinate Coordinate. - * @param {ProjectionLike} source Source projection-like. - * @param {ProjectionLike} destination Destination projection-like. - * @return {import("./coordinate.js").Coordinate} Coordinate. - * @api - */ -function transform(coordinate, source, destination) { - var transformFunc = getTransform(source, destination); - return transformFunc(coordinate, undefined, coordinate.length); -} -/** - * @type {?Projection} - */ -var userProjection = null; -/** - * Get the projection for coordinates supplied from and returned by API methods. - * Note that this method is not yet a part of the stable API. Support for user - * projections is not yet complete and should be considered experimental. - * @returns {?Projection} The user projection (or null if not set). - */ -function getUserProjection() { - return userProjection; -} -/** - * Return a coordinate transformed into the user projection. If no user projection - * is set, the original coordinate is returned. - * @param {Array} coordinate Input coordinate. - * @param {ProjectionLike} sourceProjection The input coordinate projection. - * @returns {Array} The input coordinate in the user projection. - */ -function toUserCoordinate(coordinate, sourceProjection) { - { - return coordinate; - } -} -/** - * Return a coordinate transformed from the user projection. If no user projection - * is set, the original coordinate is returned. - * @param {Array} coordinate Input coordinate. - * @param {ProjectionLike} destProjection The destination projection. - * @returns {Array} The input coordinate transformed. - */ -function fromUserCoordinate(coordinate, destProjection) { - { - return coordinate; - } -} -/** - * Return an extent transformed into the user projection. If no user projection - * is set, the original extent is returned. - * @param {import("./extent.js").Extent} extent Input extent. - * @param {ProjectionLike} sourceProjection The input extent projection. - * @returns {import("./extent.js").Extent} The input extent in the user projection. - */ -function toUserExtent(extent, sourceProjection) { - { - return extent; - } -} -/** - * Return an extent transformed from the user projection. If no user projection - * is set, the original extent is returned. - * @param {import("./extent.js").Extent} extent Input extent. - * @param {ProjectionLike} destProjection The destination projection. - * @returns {import("./extent.js").Extent} The input extent transformed. - */ -function fromUserExtent(extent, destProjection) { - { - return extent; - } -} -/** - * Add transforms to and from EPSG:4326 and EPSG:3857. This function is called - * by when this module is executed and should only need to be called again after - * `clearAllProjections()` is called (e.g. in tests). - */ -function addCommon() { - // Add transformations that don't alter coordinates to convert within set of - // projections with equal meaning. - addEquivalentProjections(PROJECTIONS); - addEquivalentProjections(PROJECTIONS$1); - // Add transformations to convert EPSG:4326 like coordinates to EPSG:3857 like - // coordinates and back. - addEquivalentTransforms(PROJECTIONS$1, PROJECTIONS, fromEPSG4326, toEPSG4326); -} -addCommon(); - -/** - * @module ol/centerconstraint - */ -/** - * @typedef {function((import("./coordinate.js").Coordinate|undefined), number, import("./size.js").Size, boolean=, Array=): (import("./coordinate.js").Coordinate|undefined)} Type - */ -/** - * @param {import("./extent.js").Extent} extent Extent. - * @param {boolean} onlyCenter If true, the constraint will only apply to the view center. - * @param {boolean} smooth If true, the view will be able to go slightly out of the given extent - * (only during interaction and animation). - * @return {Type} The constraint. - */ -function createExtent(extent, onlyCenter, smooth) { - return ( - /** - * @param {import("./coordinate.js").Coordinate|undefined} center Center. - * @param {number} resolution Resolution. - * @param {import("./size.js").Size} size Viewport size; unused if `onlyCenter` was specified. - * @param {boolean=} opt_isMoving True if an interaction or animation is in progress. - * @param {Array=} opt_centerShift Shift between map center and viewport center. - * @return {import("./coordinate.js").Coordinate|undefined} Center. - */ - function (center, resolution, size, opt_isMoving, opt_centerShift) { - if (center) { - var viewWidth = onlyCenter ? 0 : size[0] * resolution; - var viewHeight = onlyCenter ? 0 : size[1] * resolution; - var shiftX = opt_centerShift ? opt_centerShift[0] : 0; - var shiftY = opt_centerShift ? opt_centerShift[1] : 0; - var minX = extent[0] + viewWidth / 2 + shiftX; - var maxX = extent[2] - viewWidth / 2 + shiftX; - var minY = extent[1] + viewHeight / 2 + shiftY; - var maxY = extent[3] - viewHeight / 2 + shiftY; - // note: when zooming out of bounds, min and max values for x and y may - // end up inverted (min > max); this has to be accounted for - if (minX > maxX) { - minX = (maxX + minX) / 2; - maxX = minX; - } - if (minY > maxY) { - minY = (maxY + minY) / 2; - maxY = minY; - } - var x = clamp(center[0], minX, maxX); - var y = clamp(center[1], minY, maxY); - var ratio = 30 * resolution; - // during an interaction, allow some overscroll - if (opt_isMoving && smooth) { - x += - -ratio * Math.log(1 + Math.max(0, minX - center[0]) / ratio) + - ratio * Math.log(1 + Math.max(0, center[0] - maxX) / ratio); - y += - -ratio * Math.log(1 + Math.max(0, minY - center[1]) / ratio) + - ratio * Math.log(1 + Math.max(0, center[1] - maxY) / ratio); - } - return [x, y]; - } - else { - return undefined; - } - }); -} -/** - * @param {import("./coordinate.js").Coordinate=} center Center. - * @return {import("./coordinate.js").Coordinate|undefined} Center. - */ -function none(center) { - return center; -} - -/** - * @module ol/resolutionconstraint - */ -/** - * @typedef {function((number|undefined), number, import("./size.js").Size, boolean=): (number|undefined)} Type - */ -/** - * Returns a modified resolution taking into account the viewport size and maximum - * allowed extent. - * @param {number} resolution Resolution - * @param {import("./extent.js").Extent} maxExtent Maximum allowed extent. - * @param {import("./size.js").Size} viewportSize Viewport size. - * @param {boolean} showFullExtent Whether to show the full extent. - * @return {number} Capped resolution. - */ -function getViewportClampedResolution(resolution, maxExtent, viewportSize, showFullExtent) { - var xResolution = getWidth(maxExtent) / viewportSize[0]; - var yResolution = getHeight(maxExtent) / viewportSize[1]; - if (showFullExtent) { - return Math.min(resolution, Math.max(xResolution, yResolution)); - } - return Math.min(resolution, Math.min(xResolution, yResolution)); -} -/** - * Returns a modified resolution to be between maxResolution and minResolution while - * still allowing the value to be slightly out of bounds. - * Note: the computation is based on the logarithm function (ln): - * - at 1, ln(x) is 0 - * - above 1, ln(x) keeps increasing but at a much slower pace than x - * The final result is clamped to prevent getting too far away from bounds. - * @param {number} resolution Resolution. - * @param {number} maxResolution Max resolution. - * @param {number} minResolution Min resolution. - * @return {number} Smoothed resolution. - */ -function getSmoothClampedResolution(resolution, maxResolution, minResolution) { - var result = Math.min(resolution, maxResolution); - var ratio = 50; - result *= - Math.log(1 + ratio * Math.max(0, resolution / maxResolution - 1)) / ratio + - 1; - if (minResolution) { - result = Math.max(result, minResolution); - result /= - Math.log(1 + ratio * Math.max(0, minResolution / resolution - 1)) / - ratio + - 1; - } - return clamp(result, minResolution / 2, maxResolution * 2); -} -/** - * @param {Array} resolutions Resolutions. - * @param {boolean=} opt_smooth If true, the view will be able to slightly exceed resolution limits. Default: true. - * @param {import("./extent.js").Extent=} opt_maxExtent Maximum allowed extent. - * @param {boolean=} opt_showFullExtent If true, allows us to show the full extent. Default: false. - * @return {Type} Zoom function. - */ -function createSnapToResolutions(resolutions, opt_smooth, opt_maxExtent, opt_showFullExtent) { - return ( - /** - * @param {number|undefined} resolution Resolution. - * @param {number} direction Direction. - * @param {import("./size.js").Size} size Viewport size. - * @param {boolean=} opt_isMoving True if an interaction or animation is in progress. - * @return {number|undefined} Resolution. - */ - function (resolution, direction, size, opt_isMoving) { - if (resolution !== undefined) { - var maxResolution = resolutions[0]; - var minResolution = resolutions[resolutions.length - 1]; - var cappedMaxRes = opt_maxExtent - ? getViewportClampedResolution(maxResolution, opt_maxExtent, size, opt_showFullExtent) - : maxResolution; - // during interacting or animating, allow intermediary values - if (opt_isMoving) { - var smooth = opt_smooth !== undefined ? opt_smooth : true; - if (!smooth) { - return clamp(resolution, minResolution, cappedMaxRes); - } - return getSmoothClampedResolution(resolution, cappedMaxRes, minResolution); - } - var capped = Math.min(cappedMaxRes, resolution); - var z = Math.floor(linearFindNearest(resolutions, capped, direction)); - if (resolutions[z] > cappedMaxRes && z < resolutions.length - 1) { - return resolutions[z + 1]; - } - return resolutions[z]; - } - else { - return undefined; - } - }); -} -/** - * @param {number} power Power. - * @param {number} maxResolution Maximum resolution. - * @param {number=} opt_minResolution Minimum resolution. - * @param {boolean=} opt_smooth If true, the view will be able to slightly exceed resolution limits. Default: true. - * @param {import("./extent.js").Extent=} opt_maxExtent Maximum allowed extent. - * @param {boolean=} opt_showFullExtent If true, allows us to show the full extent. Default: false. - * @return {Type} Zoom function. - */ -function createSnapToPower(power, maxResolution, opt_minResolution, opt_smooth, opt_maxExtent, opt_showFullExtent) { - return ( - /** - * @param {number|undefined} resolution Resolution. - * @param {number} direction Direction. - * @param {import("./size.js").Size} size Viewport size. - * @param {boolean=} opt_isMoving True if an interaction or animation is in progress. - * @return {number|undefined} Resolution. - */ - function (resolution, direction, size, opt_isMoving) { - if (resolution !== undefined) { - var cappedMaxRes = opt_maxExtent - ? getViewportClampedResolution(maxResolution, opt_maxExtent, size, opt_showFullExtent) - : maxResolution; - var minResolution = opt_minResolution !== undefined ? opt_minResolution : 0; - // during interacting or animating, allow intermediary values - if (opt_isMoving) { - var smooth = opt_smooth !== undefined ? opt_smooth : true; - if (!smooth) { - return clamp(resolution, minResolution, cappedMaxRes); - } - return getSmoothClampedResolution(resolution, cappedMaxRes, minResolution); - } - var tolerance = 1e-9; - var minZoomLevel = Math.ceil(Math.log(maxResolution / cappedMaxRes) / Math.log(power) - tolerance); - var offset = -direction * (0.5 - tolerance) + 0.5; - var capped = Math.min(cappedMaxRes, resolution); - var cappedZoomLevel = Math.floor(Math.log(maxResolution / capped) / Math.log(power) + offset); - var zoomLevel = Math.max(minZoomLevel, cappedZoomLevel); - var newResolution = maxResolution / Math.pow(power, zoomLevel); - return clamp(newResolution, minResolution, cappedMaxRes); - } - else { - return undefined; - } - }); -} -/** - * @param {number} maxResolution Max resolution. - * @param {number} minResolution Min resolution. - * @param {boolean=} opt_smooth If true, the view will be able to slightly exceed resolution limits. Default: true. - * @param {import("./extent.js").Extent=} opt_maxExtent Maximum allowed extent. - * @param {boolean=} opt_showFullExtent If true, allows us to show the full extent. Default: false. - * @return {Type} Zoom function. - */ -function createMinMaxResolution(maxResolution, minResolution, opt_smooth, opt_maxExtent, opt_showFullExtent) { - return ( - /** - * @param {number|undefined} resolution Resolution. - * @param {number} direction Direction. - * @param {import("./size.js").Size} size Viewport size. - * @param {boolean=} opt_isMoving True if an interaction or animation is in progress. - * @return {number|undefined} Resolution. - */ - function (resolution, direction, size, opt_isMoving) { - if (resolution !== undefined) { - var cappedMaxRes = opt_maxExtent - ? getViewportClampedResolution(maxResolution, opt_maxExtent, size, opt_showFullExtent) - : maxResolution; - var smooth = opt_smooth !== undefined ? opt_smooth : true; - if (!smooth || !opt_isMoving) { - return clamp(resolution, minResolution, cappedMaxRes); - } - return getSmoothClampedResolution(resolution, cappedMaxRes, minResolution); - } - else { - return undefined; - } - }); -} - -/** - * @module ol/rotationconstraint - */ -/** - * @typedef {function((number|undefined), boolean=): (number|undefined)} Type - */ -/** - * @param {number|undefined} rotation Rotation. - * @return {number|undefined} Rotation. - */ -function disable(rotation) { - if (rotation !== undefined) { - return 0; - } - else { - return undefined; - } -} -/** - * @param {number|undefined} rotation Rotation. - * @return {number|undefined} Rotation. - */ -function none$1(rotation) { - if (rotation !== undefined) { - return rotation; - } - else { - return undefined; - } -} -/** - * @param {number} n N. - * @return {Type} Rotation constraint. - */ -function createSnapToN(n) { - var theta = (2 * Math.PI) / n; - return ( - /** - * @param {number|undefined} rotation Rotation. - * @param {boolean=} opt_isMoving True if an interaction or animation is in progress. - * @return {number|undefined} Rotation. - */ - function (rotation, opt_isMoving) { - if (opt_isMoving) { - return rotation; - } - if (rotation !== undefined) { - rotation = Math.floor(rotation / theta + 0.5) * theta; - return rotation; - } - else { - return undefined; - } - }); -} -/** - * @param {number=} opt_tolerance Tolerance. - * @return {Type} Rotation constraint. - */ -function createSnapToZero(opt_tolerance) { - var tolerance = opt_tolerance || toRadians(5); - return ( - /** - * @param {number|undefined} rotation Rotation. - * @param {boolean=} opt_isMoving True if an interaction or animation is in progress. - * @return {number|undefined} Rotation. - */ - function (rotation, opt_isMoving) { - if (opt_isMoving) { - return rotation; - } - if (rotation !== undefined) { - if (Math.abs(rotation) <= tolerance) { - return 0; - } - else { - return rotation; - } - } - else { - return undefined; - } - }); -} - -/** - * @module ol/easing - */ -/** - * Start slow and speed up. - * @param {number} t Input between 0 and 1. - * @return {number} Output between 0 and 1. - * @api - */ -function easeIn(t) { - return Math.pow(t, 3); -} -/** - * Start fast and slow down. - * @param {number} t Input between 0 and 1. - * @return {number} Output between 0 and 1. - * @api - */ -function easeOut(t) { - return 1 - easeIn(1 - t); -} -/** - * Start slow, speed up, and then slow down again. - * @param {number} t Input between 0 and 1. - * @return {number} Output between 0 and 1. - * @api - */ -function inAndOut(t) { - return 3 * t * t - 2 * t * t * t; -} -/** - * Maintain a constant speed over time. - * @param {number} t Input between 0 and 1. - * @return {number} Output between 0 and 1. - * @api - */ -function linear(t) { - return t; -} - -/** - * @module ol/geom/GeometryLayout - */ -/** - * The coordinate layout for geometries, indicating whether a 3rd or 4th z ('Z') - * or measure ('M') coordinate is available. Supported values are `'XY'`, - * `'XYZ'`, `'XYM'`, `'XYZM'`. - * @enum {string} - */ -var GeometryLayout = { - XY: 'XY', - XYZ: 'XYZ', - XYM: 'XYM', - XYZM: 'XYZM', -}; - -/** - * @module ol/transform - */ -/** - * An array representing an affine 2d transformation for use with - * {@link module:ol/transform} functions. The array has 6 elements. - * @typedef {!Array} Transform - * @api - */ -/** - * Collection of affine 2d transformation functions. The functions work on an - * array of 6 elements. The element order is compatible with the [SVGMatrix - * interface](https://developer.mozilla.org/en-US/docs/Web/API/SVGMatrix) and is - * a subset (elements a to f) of a 3×3 matrix: - * ``` - * [ a c e ] - * [ b d f ] - * [ 0 0 1 ] - * ``` - */ -/** - * @private - * @type {Transform} - */ -var tmp_ = new Array(6); -/** - * Create an identity transform. - * @return {!Transform} Identity transform. - */ -function create() { - return [1, 0, 0, 1, 0, 0]; -} -/** - * Transforms the given coordinate with the given transform returning the - * resulting, transformed coordinate. The coordinate will be modified in-place. - * - * @param {Transform} transform The transformation. - * @param {import("./coordinate.js").Coordinate|import("./pixel.js").Pixel} coordinate The coordinate to transform. - * @return {import("./coordinate.js").Coordinate|import("./pixel.js").Pixel} return coordinate so that operations can be - * chained together. - */ -function apply(transform, coordinate) { - var x = coordinate[0]; - var y = coordinate[1]; - coordinate[0] = transform[0] * x + transform[2] * y + transform[4]; - coordinate[1] = transform[1] * x + transform[3] * y + transform[5]; - return coordinate; -} -/** - * Creates a composite transform given an initial translation, scale, rotation, and - * final translation (in that order only, not commutative). - * @param {!Transform} transform The transform (will be modified in place). - * @param {number} dx1 Initial translation x. - * @param {number} dy1 Initial translation y. - * @param {number} sx Scale factor x. - * @param {number} sy Scale factor y. - * @param {number} angle Rotation (in counter-clockwise radians). - * @param {number} dx2 Final translation x. - * @param {number} dy2 Final translation y. - * @return {!Transform} The composite transform. - */ -function compose(transform, dx1, dy1, sx, sy, angle, dx2, dy2) { - var sin = Math.sin(angle); - var cos = Math.cos(angle); - transform[0] = sx * cos; - transform[1] = sy * sin; - transform[2] = -sx * sin; - transform[3] = sy * cos; - transform[4] = dx2 * sx * cos - dy2 * sx * sin + dx1; - transform[5] = dx2 * sy * sin + dy2 * sy * cos + dy1; - return transform; -} -/** - * Invert the given transform. - * @param {!Transform} target Transform to be set as the inverse of - * the source transform. - * @param {!Transform} source The source transform to invert. - * @return {!Transform} The inverted (target) transform. - */ -function makeInverse(target, source) { - var det = determinant(source); - assert(det !== 0, 32); // Transformation matrix cannot be inverted - var a = source[0]; - var b = source[1]; - var c = source[2]; - var d = source[3]; - var e = source[4]; - var f = source[5]; - target[0] = d / det; - target[1] = -b / det; - target[2] = -c / det; - target[3] = a / det; - target[4] = (c * f - d * e) / det; - target[5] = -(a * f - b * e) / det; - return target; -} -/** - * Returns the determinant of the given matrix. - * @param {!Transform} mat Matrix. - * @return {number} Determinant. - */ -function determinant(mat) { - return mat[0] * mat[3] - mat[1] * mat[2]; -} -/** - * A string version of the transform. This can be used - * for CSS transforms. - * @param {!Transform} mat Matrix. - * @return {string} The transform as a string. - */ -function toString(mat) { - return 'matrix(' + mat.join(', ') + ')'; -} - -/** - * @module ol/geom/flat/transform - */ -/** - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {import("../../transform.js").Transform} transform Transform. - * @param {Array=} opt_dest Destination. - * @return {Array} Transformed coordinates. - */ -function transform2D(flatCoordinates, offset, end, stride, transform, opt_dest) { - var dest = opt_dest ? opt_dest : []; - var i = 0; - for (var j = offset; j < end; j += stride) { - var x = flatCoordinates[j]; - var y = flatCoordinates[j + 1]; - dest[i++] = transform[0] * x + transform[2] * y + transform[4]; - dest[i++] = transform[1] * x + transform[3] * y + transform[5]; - } - if (opt_dest && dest.length != i) { - dest.length = i; - } - return dest; -} -/** - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {number} angle Angle. - * @param {Array} anchor Rotation anchor point. - * @param {Array=} opt_dest Destination. - * @return {Array} Transformed coordinates. - */ -function rotate$1(flatCoordinates, offset, end, stride, angle, anchor, opt_dest) { - var dest = opt_dest ? opt_dest : []; - var cos = Math.cos(angle); - var sin = Math.sin(angle); - var anchorX = anchor[0]; - var anchorY = anchor[1]; - var i = 0; - for (var j = offset; j < end; j += stride) { - var deltaX = flatCoordinates[j] - anchorX; - var deltaY = flatCoordinates[j + 1] - anchorY; - dest[i++] = anchorX + deltaX * cos - deltaY * sin; - dest[i++] = anchorY + deltaX * sin + deltaY * cos; - for (var k = j + 2; k < j + stride; ++k) { - dest[i++] = flatCoordinates[k]; - } - } - if (opt_dest && dest.length != i) { - dest.length = i; - } - return dest; -} -/** - * Scale the coordinates. - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {number} sx Scale factor in the x-direction. - * @param {number} sy Scale factor in the y-direction. - * @param {Array} anchor Scale anchor point. - * @param {Array=} opt_dest Destination. - * @return {Array} Transformed coordinates. - */ -function scale$1(flatCoordinates, offset, end, stride, sx, sy, anchor, opt_dest) { - var dest = opt_dest ? opt_dest : []; - var anchorX = anchor[0]; - var anchorY = anchor[1]; - var i = 0; - for (var j = offset; j < end; j += stride) { - var deltaX = flatCoordinates[j] - anchorX; - var deltaY = flatCoordinates[j + 1] - anchorY; - dest[i++] = anchorX + sx * deltaX; - dest[i++] = anchorY + sy * deltaY; - for (var k = j + 2; k < j + stride; ++k) { - dest[i++] = flatCoordinates[k]; - } - } - if (opt_dest && dest.length != i) { - dest.length = i; - } - return dest; -} -/** - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {number} deltaX Delta X. - * @param {number} deltaY Delta Y. - * @param {Array=} opt_dest Destination. - * @return {Array} Transformed coordinates. - */ -function translate(flatCoordinates, offset, end, stride, deltaX, deltaY, opt_dest) { - var dest = opt_dest ? opt_dest : []; - var i = 0; - for (var j = offset; j < end; j += stride) { - dest[i++] = flatCoordinates[j] + deltaX; - dest[i++] = flatCoordinates[j + 1] + deltaY; - for (var k = j + 2; k < j + stride; ++k) { - dest[i++] = flatCoordinates[k]; - } - } - if (opt_dest && dest.length != i) { - dest.length = i; - } - return dest; -} - -var __extends$6 = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -/** - * @type {import("../transform.js").Transform} - */ -var tmpTransform = create(); -/** - * @classdesc - * Abstract base class; normally only used for creating subclasses and not - * instantiated in apps. - * Base class for vector geometries. - * - * To get notified of changes to the geometry, register a listener for the - * generic `change` event on your geometry instance. - * - * @abstract - * @api - */ -var Geometry = /** @class */ (function (_super) { - __extends$6(Geometry, _super); - function Geometry() { - var _this = _super.call(this) || this; - /** - * @private - * @type {import("../extent.js").Extent} - */ - _this.extent_ = createEmpty(); - /** - * @private - * @type {number} - */ - _this.extentRevision_ = -1; - /** - * @protected - * @type {number} - */ - _this.simplifiedGeometryMaxMinSquaredTolerance = 0; - /** - * @protected - * @type {number} - */ - _this.simplifiedGeometryRevision = 0; - /** - * Get a transformed and simplified version of the geometry. - * @abstract - * @param {number} revision The geometry revision. - * @param {number} squaredTolerance Squared tolerance. - * @param {import("../proj.js").TransformFunction} [opt_transform] Optional transform function. - * @return {Geometry} Simplified geometry. - */ - _this.simplifyTransformedInternal = memoizeOne(function (revision, squaredTolerance, opt_transform) { - if (!opt_transform) { - return this.getSimplifiedGeometry(squaredTolerance); - } - var clone = this.clone(); - clone.applyTransform(opt_transform); - return clone.getSimplifiedGeometry(squaredTolerance); - }); - return _this; - } - /** - * Get a transformed and simplified version of the geometry. - * @abstract - * @param {number} squaredTolerance Squared tolerance. - * @param {import("../proj.js").TransformFunction} [opt_transform] Optional transform function. - * @return {Geometry} Simplified geometry. - */ - Geometry.prototype.simplifyTransformed = function (squaredTolerance, opt_transform) { - return this.simplifyTransformedInternal(this.getRevision(), squaredTolerance, opt_transform); - }; - /** - * Make a complete copy of the geometry. - * @abstract - * @return {!Geometry} Clone. - */ - Geometry.prototype.clone = function () { - return abstract(); - }; - /** - * @abstract - * @param {number} x X. - * @param {number} y Y. - * @param {import("../coordinate.js").Coordinate} closestPoint Closest point. - * @param {number} minSquaredDistance Minimum squared distance. - * @return {number} Minimum squared distance. - */ - Geometry.prototype.closestPointXY = function (x, y, closestPoint, minSquaredDistance) { - return abstract(); - }; - /** - * @param {number} x X. - * @param {number} y Y. - * @return {boolean} Contains (x, y). - */ - Geometry.prototype.containsXY = function (x, y) { - var coord = this.getClosestPoint([x, y]); - return coord[0] === x && coord[1] === y; - }; - /** - * Return the closest point of the geometry to the passed point as - * {@link module:ol/coordinate~Coordinate coordinate}. - * @param {import("../coordinate.js").Coordinate} point Point. - * @param {import("../coordinate.js").Coordinate=} opt_closestPoint Closest point. - * @return {import("../coordinate.js").Coordinate} Closest point. - * @api - */ - Geometry.prototype.getClosestPoint = function (point, opt_closestPoint) { - var closestPoint = opt_closestPoint ? opt_closestPoint : [NaN, NaN]; - this.closestPointXY(point[0], point[1], closestPoint, Infinity); - return closestPoint; - }; - /** - * Returns true if this geometry includes the specified coordinate. If the - * coordinate is on the boundary of the geometry, returns false. - * @param {import("../coordinate.js").Coordinate} coordinate Coordinate. - * @return {boolean} Contains coordinate. - * @api - */ - Geometry.prototype.intersectsCoordinate = function (coordinate) { - return this.containsXY(coordinate[0], coordinate[1]); - }; - /** - * @abstract - * @param {import("../extent.js").Extent} extent Extent. - * @protected - * @return {import("../extent.js").Extent} extent Extent. - */ - Geometry.prototype.computeExtent = function (extent) { - return abstract(); - }; - /** - * Get the extent of the geometry. - * @param {import("../extent.js").Extent=} opt_extent Extent. - * @return {import("../extent.js").Extent} extent Extent. - * @api - */ - Geometry.prototype.getExtent = function (opt_extent) { - if (this.extentRevision_ != this.getRevision()) { - var extent = this.computeExtent(this.extent_); - if (isNaN(extent[0]) || isNaN(extent[1])) { - createOrUpdateEmpty(extent); - } - this.extentRevision_ = this.getRevision(); - } - return returnOrUpdate(this.extent_, opt_extent); - }; - /** - * Rotate the geometry around a given coordinate. This modifies the geometry - * coordinates in place. - * @abstract - * @param {number} angle Rotation angle in radians. - * @param {import("../coordinate.js").Coordinate} anchor The rotation center. - * @api - */ - Geometry.prototype.rotate = function (angle, anchor) { - abstract(); - }; - /** - * Scale the geometry (with an optional origin). This modifies the geometry - * coordinates in place. - * @abstract - * @param {number} sx The scaling factor in the x-direction. - * @param {number=} opt_sy The scaling factor in the y-direction (defaults to sx). - * @param {import("../coordinate.js").Coordinate=} opt_anchor The scale origin (defaults to the center - * of the geometry extent). - * @api - */ - Geometry.prototype.scale = function (sx, opt_sy, opt_anchor) { - abstract(); - }; - /** - * Create a simplified version of this geometry. For linestrings, this uses - * the [Douglas Peucker](https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm) - * algorithm. For polygons, a quantization-based - * simplification is used to preserve topology. - * @param {number} tolerance The tolerance distance for simplification. - * @return {Geometry} A new, simplified version of the original geometry. - * @api - */ - Geometry.prototype.simplify = function (tolerance) { - return this.getSimplifiedGeometry(tolerance * tolerance); - }; - /** - * Create a simplified version of this geometry using the Douglas Peucker - * algorithm. - * See https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm. - * @abstract - * @param {number} squaredTolerance Squared tolerance. - * @return {Geometry} Simplified geometry. - */ - Geometry.prototype.getSimplifiedGeometry = function (squaredTolerance) { - return abstract(); - }; - /** - * Get the type of this geometry. - * @abstract - * @return {import("./GeometryType.js").default} Geometry type. - */ - Geometry.prototype.getType = function () { - return abstract(); - }; - /** - * Apply a transform function to the coordinates of the geometry. - * The geometry is modified in place. - * If you do not want the geometry modified in place, first `clone()` it and - * then use this function on the clone. - * @abstract - * @param {import("../proj.js").TransformFunction} transformFn Transform function. - * Called with a flat array of geometry coordinates. - */ - Geometry.prototype.applyTransform = function (transformFn) { - abstract(); - }; - /** - * Test if the geometry and the passed extent intersect. - * @abstract - * @param {import("../extent.js").Extent} extent Extent. - * @return {boolean} `true` if the geometry and the extent intersect. - */ - Geometry.prototype.intersectsExtent = function (extent) { - return abstract(); - }; - /** - * Translate the geometry. This modifies the geometry coordinates in place. If - * instead you want a new geometry, first `clone()` this geometry. - * @abstract - * @param {number} deltaX Delta X. - * @param {number} deltaY Delta Y. - * @api - */ - Geometry.prototype.translate = function (deltaX, deltaY) { - abstract(); - }; - /** - * Transform each coordinate of the geometry from one coordinate reference - * system to another. The geometry is modified in place. - * For example, a line will be transformed to a line and a circle to a circle. - * If you do not want the geometry modified in place, first `clone()` it and - * then use this function on the clone. - * - * @param {import("../proj.js").ProjectionLike} source The current projection. Can be a - * string identifier or a {@link module:ol/proj/Projection~Projection} object. - * @param {import("../proj.js").ProjectionLike} destination The desired projection. Can be a - * string identifier or a {@link module:ol/proj/Projection~Projection} object. - * @return {Geometry} This geometry. Note that original geometry is - * modified in place. - * @api - */ - Geometry.prototype.transform = function (source, destination) { - /** @type {import("../proj/Projection.js").default} */ - var sourceProj = get$2(source); - var transformFn = sourceProj.getUnits() == Units.TILE_PIXELS - ? function (inCoordinates, outCoordinates, stride) { - var pixelExtent = sourceProj.getExtent(); - var projectedExtent = sourceProj.getWorldExtent(); - var scale = getHeight(projectedExtent) / getHeight(pixelExtent); - compose(tmpTransform, projectedExtent[0], projectedExtent[3], scale, -scale, 0, 0, 0); - transform2D(inCoordinates, 0, inCoordinates.length, stride, tmpTransform, outCoordinates); - return getTransform(sourceProj, destination)(inCoordinates, outCoordinates, stride); - } - : getTransform(sourceProj, destination); - this.applyTransform(transformFn); - return this; - }; - return Geometry; -}(BaseObject)); - -var __extends$7 = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -/** - * @classdesc - * Abstract base class; only used for creating subclasses; do not instantiate - * in apps, as cannot be rendered. - * - * @abstract - * @api - */ -var SimpleGeometry = /** @class */ (function (_super) { - __extends$7(SimpleGeometry, _super); - function SimpleGeometry() { - var _this = _super.call(this) || this; - /** - * @protected - * @type {import("./GeometryLayout.js").default} - */ - _this.layout = GeometryLayout.XY; - /** - * @protected - * @type {number} - */ - _this.stride = 2; - /** - * @protected - * @type {Array} - */ - _this.flatCoordinates = null; - return _this; - } - /** - * @param {import("../extent.js").Extent} extent Extent. - * @protected - * @return {import("../extent.js").Extent} extent Extent. - */ - SimpleGeometry.prototype.computeExtent = function (extent) { - return createOrUpdateFromFlatCoordinates(this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, extent); - }; - /** - * @abstract - * @return {Array<*>} Coordinates. - */ - SimpleGeometry.prototype.getCoordinates = function () { - return abstract(); - }; - /** - * Return the first coordinate of the geometry. - * @return {import("../coordinate.js").Coordinate} First coordinate. - * @api - */ - SimpleGeometry.prototype.getFirstCoordinate = function () { - return this.flatCoordinates.slice(0, this.stride); - }; - /** - * @return {Array} Flat coordinates. - */ - SimpleGeometry.prototype.getFlatCoordinates = function () { - return this.flatCoordinates; - }; - /** - * Return the last coordinate of the geometry. - * @return {import("../coordinate.js").Coordinate} Last point. - * @api - */ - SimpleGeometry.prototype.getLastCoordinate = function () { - return this.flatCoordinates.slice(this.flatCoordinates.length - this.stride); - }; - /** - * Return the {@link module:ol/geom/GeometryLayout layout} of the geometry. - * @return {import("./GeometryLayout.js").default} Layout. - * @api - */ - SimpleGeometry.prototype.getLayout = function () { - return this.layout; - }; - /** - * Create a simplified version of this geometry using the Douglas Peucker algorithm. - * @param {number} squaredTolerance Squared tolerance. - * @return {SimpleGeometry} Simplified geometry. - */ - SimpleGeometry.prototype.getSimplifiedGeometry = function (squaredTolerance) { - if (this.simplifiedGeometryRevision !== this.getRevision()) { - this.simplifiedGeometryMaxMinSquaredTolerance = 0; - this.simplifiedGeometryRevision = this.getRevision(); - } - // If squaredTolerance is negative or if we know that simplification will not - // have any effect then just return this. - if (squaredTolerance < 0 || - (this.simplifiedGeometryMaxMinSquaredTolerance !== 0 && - squaredTolerance <= this.simplifiedGeometryMaxMinSquaredTolerance)) { - return this; - } - var simplifiedGeometry = this.getSimplifiedGeometryInternal(squaredTolerance); - var simplifiedFlatCoordinates = simplifiedGeometry.getFlatCoordinates(); - if (simplifiedFlatCoordinates.length < this.flatCoordinates.length) { - return simplifiedGeometry; - } - else { - // Simplification did not actually remove any coordinates. We now know - // that any calls to getSimplifiedGeometry with a squaredTolerance less - // than or equal to the current squaredTolerance will also not have any - // effect. This allows us to short circuit simplification (saving CPU - // cycles) and prevents the cache of simplified geometries from filling - // up with useless identical copies of this geometry (saving memory). - this.simplifiedGeometryMaxMinSquaredTolerance = squaredTolerance; - return this; - } - }; - /** - * @param {number} squaredTolerance Squared tolerance. - * @return {SimpleGeometry} Simplified geometry. - * @protected - */ - SimpleGeometry.prototype.getSimplifiedGeometryInternal = function (squaredTolerance) { - return this; - }; - /** - * @return {number} Stride. - */ - SimpleGeometry.prototype.getStride = function () { - return this.stride; - }; - /** - * @param {import("./GeometryLayout.js").default} layout Layout. - * @param {Array} flatCoordinates Flat coordinates. - */ - SimpleGeometry.prototype.setFlatCoordinates = function (layout, flatCoordinates) { - this.stride = getStrideForLayout(layout); - this.layout = layout; - this.flatCoordinates = flatCoordinates; - }; - /** - * @abstract - * @param {!Array<*>} coordinates Coordinates. - * @param {import("./GeometryLayout.js").default=} opt_layout Layout. - */ - SimpleGeometry.prototype.setCoordinates = function (coordinates, opt_layout) { - abstract(); - }; - /** - * @param {import("./GeometryLayout.js").default|undefined} layout Layout. - * @param {Array<*>} coordinates Coordinates. - * @param {number} nesting Nesting. - * @protected - */ - SimpleGeometry.prototype.setLayout = function (layout, coordinates, nesting) { - /** @type {number} */ - var stride; - if (layout) { - stride = getStrideForLayout(layout); - } - else { - for (var i = 0; i < nesting; ++i) { - if (coordinates.length === 0) { - this.layout = GeometryLayout.XY; - this.stride = 2; - return; - } - else { - coordinates = /** @type {Array} */ (coordinates[0]); - } - } - stride = coordinates.length; - layout = getLayoutForStride(stride); - } - this.layout = layout; - this.stride = stride; - }; - /** - * Apply a transform function to the coordinates of the geometry. - * The geometry is modified in place. - * If you do not want the geometry modified in place, first `clone()` it and - * then use this function on the clone. - * @param {import("../proj.js").TransformFunction} transformFn Transform function. - * Called with a flat array of geometry coordinates. - * @api - */ - SimpleGeometry.prototype.applyTransform = function (transformFn) { - if (this.flatCoordinates) { - transformFn(this.flatCoordinates, this.flatCoordinates, this.stride); - this.changed(); - } - }; - /** - * Rotate the geometry around a given coordinate. This modifies the geometry - * coordinates in place. - * @param {number} angle Rotation angle in counter-clockwise radians. - * @param {import("../coordinate.js").Coordinate} anchor The rotation center. - * @api - */ - SimpleGeometry.prototype.rotate = function (angle, anchor) { - var flatCoordinates = this.getFlatCoordinates(); - if (flatCoordinates) { - var stride = this.getStride(); - rotate$1(flatCoordinates, 0, flatCoordinates.length, stride, angle, anchor, flatCoordinates); - this.changed(); - } - }; - /** - * Scale the geometry (with an optional origin). This modifies the geometry - * coordinates in place. - * @param {number} sx The scaling factor in the x-direction. - * @param {number=} opt_sy The scaling factor in the y-direction (defaults to sx). - * @param {import("../coordinate.js").Coordinate=} opt_anchor The scale origin (defaults to the center - * of the geometry extent). - * @api - */ - SimpleGeometry.prototype.scale = function (sx, opt_sy, opt_anchor) { - var sy = opt_sy; - if (sy === undefined) { - sy = sx; - } - var anchor = opt_anchor; - if (!anchor) { - anchor = getCenter(this.getExtent()); - } - var flatCoordinates = this.getFlatCoordinates(); - if (flatCoordinates) { - var stride = this.getStride(); - scale$1(flatCoordinates, 0, flatCoordinates.length, stride, sx, sy, anchor, flatCoordinates); - this.changed(); - } - }; - /** - * Translate the geometry. This modifies the geometry coordinates in place. If - * instead you want a new geometry, first `clone()` this geometry. - * @param {number} deltaX Delta X. - * @param {number} deltaY Delta Y. - * @api - */ - SimpleGeometry.prototype.translate = function (deltaX, deltaY) { - var flatCoordinates = this.getFlatCoordinates(); - if (flatCoordinates) { - var stride = this.getStride(); - translate(flatCoordinates, 0, flatCoordinates.length, stride, deltaX, deltaY, flatCoordinates); - this.changed(); - } - }; - return SimpleGeometry; -}(Geometry)); -/** - * @param {number} stride Stride. - * @return {import("./GeometryLayout.js").default} layout Layout. - */ -function getLayoutForStride(stride) { - var layout; - if (stride == 2) { - layout = GeometryLayout.XY; - } - else if (stride == 3) { - layout = GeometryLayout.XYZ; - } - else if (stride == 4) { - layout = GeometryLayout.XYZM; - } - return /** @type {import("./GeometryLayout.js").default} */ (layout); -} -/** - * @param {import("./GeometryLayout.js").default} layout Layout. - * @return {number} Stride. - */ -function getStrideForLayout(layout) { - var stride; - if (layout == GeometryLayout.XY) { - stride = 2; - } - else if (layout == GeometryLayout.XYZ || layout == GeometryLayout.XYM) { - stride = 3; - } - else if (layout == GeometryLayout.XYZM) { - stride = 4; - } - return /** @type {number} */ (stride); -} - -/** - * @module ol/geom/flat/closest - */ -/** - * Returns the point on the 2D line segment flatCoordinates[offset1] to - * flatCoordinates[offset2] that is closest to the point (x, y). Extra - * dimensions are linearly interpolated. - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset1 Offset 1. - * @param {number} offset2 Offset 2. - * @param {number} stride Stride. - * @param {number} x X. - * @param {number} y Y. - * @param {Array} closestPoint Closest point. - */ -function assignClosest(flatCoordinates, offset1, offset2, stride, x, y, closestPoint) { - var x1 = flatCoordinates[offset1]; - var y1 = flatCoordinates[offset1 + 1]; - var dx = flatCoordinates[offset2] - x1; - var dy = flatCoordinates[offset2 + 1] - y1; - var offset; - if (dx === 0 && dy === 0) { - offset = offset1; - } - else { - var t = ((x - x1) * dx + (y - y1) * dy) / (dx * dx + dy * dy); - if (t > 1) { - offset = offset2; - } - else if (t > 0) { - for (var i = 0; i < stride; ++i) { - closestPoint[i] = lerp(flatCoordinates[offset1 + i], flatCoordinates[offset2 + i], t); - } - closestPoint.length = stride; - return; - } - else { - offset = offset1; - } - } - for (var i = 0; i < stride; ++i) { - closestPoint[i] = flatCoordinates[offset + i]; - } - closestPoint.length = stride; -} -/** - * Return the squared of the largest distance between any pair of consecutive - * coordinates. - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {number} max Max squared delta. - * @return {number} Max squared delta. - */ -function maxSquaredDelta(flatCoordinates, offset, end, stride, max) { - var x1 = flatCoordinates[offset]; - var y1 = flatCoordinates[offset + 1]; - for (offset += stride; offset < end; offset += stride) { - var x2 = flatCoordinates[offset]; - var y2 = flatCoordinates[offset + 1]; - var squaredDelta = squaredDistance(x1, y1, x2, y2); - if (squaredDelta > max) { - max = squaredDelta; - } - x1 = x2; - y1 = y2; - } - return max; -} -/** - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array} ends Ends. - * @param {number} stride Stride. - * @param {number} max Max squared delta. - * @return {number} Max squared delta. - */ -function arrayMaxSquaredDelta(flatCoordinates, offset, ends, stride, max) { - for (var i = 0, ii = ends.length; i < ii; ++i) { - var end = ends[i]; - max = maxSquaredDelta(flatCoordinates, offset, end, stride, max); - offset = end; - } - return max; -} -/** - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {number} maxDelta Max delta. - * @param {boolean} isRing Is ring. - * @param {number} x X. - * @param {number} y Y. - * @param {Array} closestPoint Closest point. - * @param {number} minSquaredDistance Minimum squared distance. - * @param {Array=} opt_tmpPoint Temporary point object. - * @return {number} Minimum squared distance. - */ -function assignClosestPoint(flatCoordinates, offset, end, stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance, opt_tmpPoint) { - if (offset == end) { - return minSquaredDistance; - } - var i, squaredDistance$1; - if (maxDelta === 0) { - // All points are identical, so just test the first point. - squaredDistance$1 = squaredDistance(x, y, flatCoordinates[offset], flatCoordinates[offset + 1]); - if (squaredDistance$1 < minSquaredDistance) { - for (i = 0; i < stride; ++i) { - closestPoint[i] = flatCoordinates[offset + i]; - } - closestPoint.length = stride; - return squaredDistance$1; - } - else { - return minSquaredDistance; - } - } - var tmpPoint = opt_tmpPoint ? opt_tmpPoint : [NaN, NaN]; - var index = offset + stride; - while (index < end) { - assignClosest(flatCoordinates, index - stride, index, stride, x, y, tmpPoint); - squaredDistance$1 = squaredDistance(x, y, tmpPoint[0], tmpPoint[1]); - if (squaredDistance$1 < minSquaredDistance) { - minSquaredDistance = squaredDistance$1; - for (i = 0; i < stride; ++i) { - closestPoint[i] = tmpPoint[i]; - } - closestPoint.length = stride; - index += stride; - } - else { - // Skip ahead multiple points, because we know that all the skipped - // points cannot be any closer than the closest point we have found so - // far. We know this because we know how close the current point is, how - // close the closest point we have found so far is, and the maximum - // distance between consecutive points. For example, if we're currently - // at distance 10, the best we've found so far is 3, and that the maximum - // distance between consecutive points is 2, then we'll need to skip at - // least (10 - 3) / 2 == 3 (rounded down) points to have any chance of - // finding a closer point. We use Math.max(..., 1) to ensure that we - // always advance at least one point, to avoid an infinite loop. - index += - stride * - Math.max(((Math.sqrt(squaredDistance$1) - Math.sqrt(minSquaredDistance)) / - maxDelta) | - 0, 1); - } - } - if (isRing) { - // Check the closing segment. - assignClosest(flatCoordinates, end - stride, offset, stride, x, y, tmpPoint); - squaredDistance$1 = squaredDistance(x, y, tmpPoint[0], tmpPoint[1]); - if (squaredDistance$1 < minSquaredDistance) { - minSquaredDistance = squaredDistance$1; - for (i = 0; i < stride; ++i) { - closestPoint[i] = tmpPoint[i]; - } - closestPoint.length = stride; - } - } - return minSquaredDistance; -} -/** - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array} ends Ends. - * @param {number} stride Stride. - * @param {number} maxDelta Max delta. - * @param {boolean} isRing Is ring. - * @param {number} x X. - * @param {number} y Y. - * @param {Array} closestPoint Closest point. - * @param {number} minSquaredDistance Minimum squared distance. - * @param {Array=} opt_tmpPoint Temporary point object. - * @return {number} Minimum squared distance. - */ -function assignClosestArrayPoint(flatCoordinates, offset, ends, stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance, opt_tmpPoint) { - var tmpPoint = opt_tmpPoint ? opt_tmpPoint : [NaN, NaN]; - for (var i = 0, ii = ends.length; i < ii; ++i) { - var end = ends[i]; - minSquaredDistance = assignClosestPoint(flatCoordinates, offset, end, stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance, tmpPoint); - offset = end; - } - return minSquaredDistance; -} - -/** - * @module ol/geom/flat/deflate - */ -/** - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {import("../../coordinate.js").Coordinate} coordinate Coordinate. - * @param {number} stride Stride. - * @return {number} offset Offset. - */ -function deflateCoordinate(flatCoordinates, offset, coordinate, stride) { - for (var i = 0, ii = coordinate.length; i < ii; ++i) { - flatCoordinates[offset++] = coordinate[i]; - } - return offset; -} -/** - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array} coordinates Coordinates. - * @param {number} stride Stride. - * @return {number} offset Offset. - */ -function deflateCoordinates(flatCoordinates, offset, coordinates, stride) { - for (var i = 0, ii = coordinates.length; i < ii; ++i) { - var coordinate = coordinates[i]; - for (var j = 0; j < stride; ++j) { - flatCoordinates[offset++] = coordinate[j]; - } - } - return offset; -} -/** - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array>} coordinatess Coordinatess. - * @param {number} stride Stride. - * @param {Array=} opt_ends Ends. - * @return {Array} Ends. - */ -function deflateCoordinatesArray(flatCoordinates, offset, coordinatess, stride, opt_ends) { - var ends = opt_ends ? opt_ends : []; - var i = 0; - for (var j = 0, jj = coordinatess.length; j < jj; ++j) { - var end = deflateCoordinates(flatCoordinates, offset, coordinatess[j], stride); - ends[i++] = end; - offset = end; - } - ends.length = i; - return ends; -} - -/** - * @module ol/geom/flat/simplify - */ -/** - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {number} squaredTolerance Squared tolerance. - * @param {Array} simplifiedFlatCoordinates Simplified flat - * coordinates. - * @param {number} simplifiedOffset Simplified offset. - * @return {number} Simplified offset. - */ -function douglasPeucker(flatCoordinates, offset, end, stride, squaredTolerance, simplifiedFlatCoordinates, simplifiedOffset) { - var n = (end - offset) / stride; - if (n < 3) { - for (; offset < end; offset += stride) { - simplifiedFlatCoordinates[simplifiedOffset++] = flatCoordinates[offset]; - simplifiedFlatCoordinates[simplifiedOffset++] = - flatCoordinates[offset + 1]; - } - return simplifiedOffset; - } - /** @type {Array} */ - var markers = new Array(n); - markers[0] = 1; - markers[n - 1] = 1; - /** @type {Array} */ - var stack = [offset, end - stride]; - var index = 0; - while (stack.length > 0) { - var last = stack.pop(); - var first = stack.pop(); - var maxSquaredDistance = 0; - var x1 = flatCoordinates[first]; - var y1 = flatCoordinates[first + 1]; - var x2 = flatCoordinates[last]; - var y2 = flatCoordinates[last + 1]; - for (var i = first + stride; i < last; i += stride) { - var x = flatCoordinates[i]; - var y = flatCoordinates[i + 1]; - var squaredDistance_1 = squaredSegmentDistance(x, y, x1, y1, x2, y2); - if (squaredDistance_1 > maxSquaredDistance) { - index = i; - maxSquaredDistance = squaredDistance_1; - } - } - if (maxSquaredDistance > squaredTolerance) { - markers[(index - offset) / stride] = 1; - if (first + stride < index) { - stack.push(first, index); - } - if (index + stride < last) { - stack.push(index, last); - } - } - } - for (var i = 0; i < n; ++i) { - if (markers[i]) { - simplifiedFlatCoordinates[simplifiedOffset++] = - flatCoordinates[offset + i * stride]; - simplifiedFlatCoordinates[simplifiedOffset++] = - flatCoordinates[offset + i * stride + 1]; - } - } - return simplifiedOffset; -} -/** - * @param {number} value Value. - * @param {number} tolerance Tolerance. - * @return {number} Rounded value. - */ -function snap(value, tolerance) { - return tolerance * Math.round(value / tolerance); -} -/** - * Simplifies a line string using an algorithm designed by Tim Schaub. - * Coordinates are snapped to the nearest value in a virtual grid and - * consecutive duplicate coordinates are discarded. This effectively preserves - * topology as the simplification of any subsection of a line string is - * independent of the rest of the line string. This means that, for examples, - * the common edge between two polygons will be simplified to the same line - * string independently in both polygons. This implementation uses a single - * pass over the coordinates and eliminates intermediate collinear points. - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {number} tolerance Tolerance. - * @param {Array} simplifiedFlatCoordinates Simplified flat - * coordinates. - * @param {number} simplifiedOffset Simplified offset. - * @return {number} Simplified offset. - */ -function quantize(flatCoordinates, offset, end, stride, tolerance, simplifiedFlatCoordinates, simplifiedOffset) { - // do nothing if the line is empty - if (offset == end) { - return simplifiedOffset; - } - // snap the first coordinate (P1) - var x1 = snap(flatCoordinates[offset], tolerance); - var y1 = snap(flatCoordinates[offset + 1], tolerance); - offset += stride; - // add the first coordinate to the output - simplifiedFlatCoordinates[simplifiedOffset++] = x1; - simplifiedFlatCoordinates[simplifiedOffset++] = y1; - // find the next coordinate that does not snap to the same value as the first - // coordinate (P2) - var x2, y2; - do { - x2 = snap(flatCoordinates[offset], tolerance); - y2 = snap(flatCoordinates[offset + 1], tolerance); - offset += stride; - if (offset == end) { - // all coordinates snap to the same value, the line collapses to a point - // push the last snapped value anyway to ensure that the output contains - // at least two points - // FIXME should we really return at least two points anyway? - simplifiedFlatCoordinates[simplifiedOffset++] = x2; - simplifiedFlatCoordinates[simplifiedOffset++] = y2; - return simplifiedOffset; - } - } while (x2 == x1 && y2 == y1); - while (offset < end) { - // snap the next coordinate (P3) - var x3 = snap(flatCoordinates[offset], tolerance); - var y3 = snap(flatCoordinates[offset + 1], tolerance); - offset += stride; - // skip P3 if it is equal to P2 - if (x3 == x2 && y3 == y2) { - continue; - } - // calculate the delta between P1 and P2 - var dx1 = x2 - x1; - var dy1 = y2 - y1; - // calculate the delta between P3 and P1 - var dx2 = x3 - x1; - var dy2 = y3 - y1; - // if P1, P2, and P3 are colinear and P3 is further from P1 than P2 is from - // P1 in the same direction then P2 is on the straight line between P1 and - // P3 - if (dx1 * dy2 == dy1 * dx2 && - ((dx1 < 0 && dx2 < dx1) || dx1 == dx2 || (dx1 > 0 && dx2 > dx1)) && - ((dy1 < 0 && dy2 < dy1) || dy1 == dy2 || (dy1 > 0 && dy2 > dy1))) { - // discard P2 and set P2 = P3 - x2 = x3; - y2 = y3; - continue; - } - // either P1, P2, and P3 are not colinear, or they are colinear but P3 is - // between P3 and P1 or on the opposite half of the line to P2. add P2, - // and continue with P1 = P2 and P2 = P3 - simplifiedFlatCoordinates[simplifiedOffset++] = x2; - simplifiedFlatCoordinates[simplifiedOffset++] = y2; - x1 = x2; - y1 = y2; - x2 = x3; - y2 = y3; - } - // add the last point (P2) - simplifiedFlatCoordinates[simplifiedOffset++] = x2; - simplifiedFlatCoordinates[simplifiedOffset++] = y2; - return simplifiedOffset; -} -/** - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array} ends Ends. - * @param {number} stride Stride. - * @param {number} tolerance Tolerance. - * @param {Array} simplifiedFlatCoordinates Simplified flat - * coordinates. - * @param {number} simplifiedOffset Simplified offset. - * @param {Array} simplifiedEnds Simplified ends. - * @return {number} Simplified offset. - */ -function quantizeArray(flatCoordinates, offset, ends, stride, tolerance, simplifiedFlatCoordinates, simplifiedOffset, simplifiedEnds) { - for (var i = 0, ii = ends.length; i < ii; ++i) { - var end = ends[i]; - simplifiedOffset = quantize(flatCoordinates, offset, end, stride, tolerance, simplifiedFlatCoordinates, simplifiedOffset); - simplifiedEnds.push(simplifiedOffset); - offset = end; - } - return simplifiedOffset; -} - -/** - * @module ol/geom/flat/inflate - */ -/** - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {Array=} opt_coordinates Coordinates. - * @return {Array} Coordinates. - */ -function inflateCoordinates(flatCoordinates, offset, end, stride, opt_coordinates) { - var coordinates = opt_coordinates !== undefined ? opt_coordinates : []; - var i = 0; - for (var j = offset; j < end; j += stride) { - coordinates[i++] = flatCoordinates.slice(j, j + stride); - } - coordinates.length = i; - return coordinates; -} -/** - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array} ends Ends. - * @param {number} stride Stride. - * @param {Array>=} opt_coordinatess Coordinatess. - * @return {Array>} Coordinatess. - */ -function inflateCoordinatesArray(flatCoordinates, offset, ends, stride, opt_coordinatess) { - var coordinatess = opt_coordinatess !== undefined ? opt_coordinatess : []; - var i = 0; - for (var j = 0, jj = ends.length; j < jj; ++j) { - var end = ends[j]; - coordinatess[i++] = inflateCoordinates(flatCoordinates, offset, end, stride, coordinatess[i]); - offset = end; - } - coordinatess.length = i; - return coordinatess; -} - -/** - * @module ol/geom/flat/area - */ -/** - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @return {number} Area. - */ -function linearRing(flatCoordinates, offset, end, stride) { - var twiceArea = 0; - var x1 = flatCoordinates[end - stride]; - var y1 = flatCoordinates[end - stride + 1]; - for (; offset < end; offset += stride) { - var x2 = flatCoordinates[offset]; - var y2 = flatCoordinates[offset + 1]; - twiceArea += y1 * x2 - x1 * y2; - x1 = x2; - y1 = y2; - } - return twiceArea / 2; -} -/** - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array} ends Ends. - * @param {number} stride Stride. - * @return {number} Area. - */ -function linearRings(flatCoordinates, offset, ends, stride) { - var area = 0; - for (var i = 0, ii = ends.length; i < ii; ++i) { - var end = ends[i]; - area += linearRing(flatCoordinates, offset, end, stride); - offset = end; - } - return area; -} - -var __extends$8 = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -/** - * @classdesc - * Linear ring geometry. Only used as part of polygon; cannot be rendered - * on its own. - * - * @api - */ -var LinearRing = /** @class */ (function (_super) { - __extends$8(LinearRing, _super); - /** - * @param {Array|Array} coordinates Coordinates. - * For internal use, flat coordinates in combination with `opt_layout` are also accepted. - * @param {import("./GeometryLayout.js").default=} opt_layout Layout. - */ - function LinearRing(coordinates, opt_layout) { - var _this = _super.call(this) || this; - /** - * @private - * @type {number} - */ - _this.maxDelta_ = -1; - /** - * @private - * @type {number} - */ - _this.maxDeltaRevision_ = -1; - if (opt_layout !== undefined && !Array.isArray(coordinates[0])) { - _this.setFlatCoordinates(opt_layout, - /** @type {Array} */ (coordinates)); - } - else { - _this.setCoordinates( - /** @type {Array} */ (coordinates), opt_layout); - } - return _this; - } - /** - * Make a complete copy of the geometry. - * @return {!LinearRing} Clone. - * @api - */ - LinearRing.prototype.clone = function () { - return new LinearRing(this.flatCoordinates.slice(), this.layout); - }; - /** - * @param {number} x X. - * @param {number} y Y. - * @param {import("../coordinate.js").Coordinate} closestPoint Closest point. - * @param {number} minSquaredDistance Minimum squared distance. - * @return {number} Minimum squared distance. - */ - LinearRing.prototype.closestPointXY = function (x, y, closestPoint, minSquaredDistance) { - if (minSquaredDistance < closestSquaredDistanceXY(this.getExtent(), x, y)) { - return minSquaredDistance; - } - if (this.maxDeltaRevision_ != this.getRevision()) { - this.maxDelta_ = Math.sqrt(maxSquaredDelta(this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, 0)); - this.maxDeltaRevision_ = this.getRevision(); - } - return assignClosestPoint(this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, this.maxDelta_, true, x, y, closestPoint, minSquaredDistance); - }; - /** - * Return the area of the linear ring on projected plane. - * @return {number} Area (on projected plane). - * @api - */ - LinearRing.prototype.getArea = function () { - return linearRing(this.flatCoordinates, 0, this.flatCoordinates.length, this.stride); - }; - /** - * Return the coordinates of the linear ring. - * @return {Array} Coordinates. - * @api - */ - LinearRing.prototype.getCoordinates = function () { - return inflateCoordinates(this.flatCoordinates, 0, this.flatCoordinates.length, this.stride); - }; - /** - * @param {number} squaredTolerance Squared tolerance. - * @return {LinearRing} Simplified LinearRing. - * @protected - */ - LinearRing.prototype.getSimplifiedGeometryInternal = function (squaredTolerance) { - var simplifiedFlatCoordinates = []; - simplifiedFlatCoordinates.length = douglasPeucker(this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, squaredTolerance, simplifiedFlatCoordinates, 0); - return new LinearRing(simplifiedFlatCoordinates, GeometryLayout.XY); - }; - /** - * Get the type of this geometry. - * @return {import("./GeometryType.js").default} Geometry type. - * @api - */ - LinearRing.prototype.getType = function () { - return GeometryType.LINEAR_RING; - }; - /** - * Test if the geometry and the passed extent intersect. - * @param {import("../extent.js").Extent} extent Extent. - * @return {boolean} `true` if the geometry and the extent intersect. - * @api - */ - LinearRing.prototype.intersectsExtent = function (extent) { - return false; - }; - /** - * Set the coordinates of the linear ring. - * @param {!Array} coordinates Coordinates. - * @param {import("./GeometryLayout.js").default=} opt_layout Layout. - * @api - */ - LinearRing.prototype.setCoordinates = function (coordinates, opt_layout) { - this.setLayout(opt_layout, coordinates, 1); - if (!this.flatCoordinates) { - this.flatCoordinates = []; - } - this.flatCoordinates.length = deflateCoordinates(this.flatCoordinates, 0, coordinates, this.stride); - this.changed(); - }; - return LinearRing; -}(SimpleGeometry)); - -var __extends$9 = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -/** - * @classdesc - * Point geometry. - * - * @api - */ -var Point = /** @class */ (function (_super) { - __extends$9(Point, _super); - /** - * @param {import("../coordinate.js").Coordinate} coordinates Coordinates. - * @param {import("./GeometryLayout.js").default=} opt_layout Layout. - */ - function Point(coordinates, opt_layout) { - var _this = _super.call(this) || this; - _this.setCoordinates(coordinates, opt_layout); - return _this; - } - /** - * Make a complete copy of the geometry. - * @return {!Point} Clone. - * @api - */ - Point.prototype.clone = function () { - var point = new Point(this.flatCoordinates.slice(), this.layout); - point.applyProperties(this); - return point; - }; - /** - * @param {number} x X. - * @param {number} y Y. - * @param {import("../coordinate.js").Coordinate} closestPoint Closest point. - * @param {number} minSquaredDistance Minimum squared distance. - * @return {number} Minimum squared distance. - */ - Point.prototype.closestPointXY = function (x, y, closestPoint, minSquaredDistance) { - var flatCoordinates = this.flatCoordinates; - var squaredDistance$1 = squaredDistance(x, y, flatCoordinates[0], flatCoordinates[1]); - if (squaredDistance$1 < minSquaredDistance) { - var stride = this.stride; - for (var i = 0; i < stride; ++i) { - closestPoint[i] = flatCoordinates[i]; - } - closestPoint.length = stride; - return squaredDistance$1; - } - else { - return minSquaredDistance; - } - }; - /** - * Return the coordinate of the point. - * @return {import("../coordinate.js").Coordinate} Coordinates. - * @api - */ - Point.prototype.getCoordinates = function () { - return !this.flatCoordinates ? [] : this.flatCoordinates.slice(); - }; - /** - * @param {import("../extent.js").Extent} extent Extent. - * @protected - * @return {import("../extent.js").Extent} extent Extent. - */ - Point.prototype.computeExtent = function (extent) { - return createOrUpdateFromCoordinate(this.flatCoordinates, extent); - }; - /** - * Get the type of this geometry. - * @return {import("./GeometryType.js").default} Geometry type. - * @api - */ - Point.prototype.getType = function () { - return GeometryType.POINT; - }; - /** - * Test if the geometry and the passed extent intersect. - * @param {import("../extent.js").Extent} extent Extent. - * @return {boolean} `true` if the geometry and the extent intersect. - * @api - */ - Point.prototype.intersectsExtent = function (extent) { - return containsXY(extent, this.flatCoordinates[0], this.flatCoordinates[1]); - }; - /** - * @param {!Array<*>} coordinates Coordinates. - * @param {import("./GeometryLayout.js").default=} opt_layout Layout. - * @api - */ - Point.prototype.setCoordinates = function (coordinates, opt_layout) { - this.setLayout(opt_layout, coordinates, 0); - if (!this.flatCoordinates) { - this.flatCoordinates = []; - } - this.flatCoordinates.length = deflateCoordinate(this.flatCoordinates, 0, coordinates, this.stride); - this.changed(); - }; - return Point; -}(SimpleGeometry)); - -/** - * @module ol/geom/flat/contains - */ -/** - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {import("../../extent.js").Extent} extent Extent. - * @return {boolean} Contains extent. - */ -function linearRingContainsExtent(flatCoordinates, offset, end, stride, extent) { - var outside = forEachCorner(extent, - /** - * @param {import("../../coordinate.js").Coordinate} coordinate Coordinate. - * @return {boolean} Contains (x, y). - */ - function (coordinate) { - return !linearRingContainsXY(flatCoordinates, offset, end, stride, coordinate[0], coordinate[1]); - }); - return !outside; -} -/** - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {number} x X. - * @param {number} y Y. - * @return {boolean} Contains (x, y). - */ -function linearRingContainsXY(flatCoordinates, offset, end, stride, x, y) { - // http://geomalgorithms.com/a03-_inclusion.html - // Copyright 2000 softSurfer, 2012 Dan Sunday - // This code may be freely used and modified for any purpose - // providing that this copyright notice is included with it. - // SoftSurfer makes no warranty for this code, and cannot be held - // liable for any real or imagined damage resulting from its use. - // Users of this code must verify correctness for their application. - var wn = 0; - var x1 = flatCoordinates[end - stride]; - var y1 = flatCoordinates[end - stride + 1]; - for (; offset < end; offset += stride) { - var x2 = flatCoordinates[offset]; - var y2 = flatCoordinates[offset + 1]; - if (y1 <= y) { - if (y2 > y && (x2 - x1) * (y - y1) - (x - x1) * (y2 - y1) > 0) { - wn++; - } - } - else if (y2 <= y && (x2 - x1) * (y - y1) - (x - x1) * (y2 - y1) < 0) { - wn--; - } - x1 = x2; - y1 = y2; - } - return wn !== 0; -} -/** - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array} ends Ends. - * @param {number} stride Stride. - * @param {number} x X. - * @param {number} y Y. - * @return {boolean} Contains (x, y). - */ -function linearRingsContainsXY(flatCoordinates, offset, ends, stride, x, y) { - if (ends.length === 0) { - return false; - } - if (!linearRingContainsXY(flatCoordinates, offset, ends[0], stride, x, y)) { - return false; - } - for (var i = 1, ii = ends.length; i < ii; ++i) { - if (linearRingContainsXY(flatCoordinates, ends[i - 1], ends[i], stride, x, y)) { - return false; - } - } - return true; -} - -/** - * @module ol/geom/flat/interiorpoint - */ -/** - * Calculates a point that is likely to lie in the interior of the linear rings. - * Inspired by JTS's com.vividsolutions.jts.geom.Geometry#getInteriorPoint. - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array} ends Ends. - * @param {number} stride Stride. - * @param {Array} flatCenters Flat centers. - * @param {number} flatCentersOffset Flat center offset. - * @param {Array=} opt_dest Destination. - * @return {Array} Destination point as XYM coordinate, where M is the - * length of the horizontal intersection that the point belongs to. - */ -function getInteriorPointOfArray(flatCoordinates, offset, ends, stride, flatCenters, flatCentersOffset, opt_dest) { - var i, ii, x, x1, x2, y1, y2; - var y = flatCenters[flatCentersOffset + 1]; - /** @type {Array} */ - var intersections = []; - // Calculate intersections with the horizontal line - for (var r = 0, rr = ends.length; r < rr; ++r) { - var end = ends[r]; - x1 = flatCoordinates[end - stride]; - y1 = flatCoordinates[end - stride + 1]; - for (i = offset; i < end; i += stride) { - x2 = flatCoordinates[i]; - y2 = flatCoordinates[i + 1]; - if ((y <= y1 && y2 <= y) || (y1 <= y && y <= y2)) { - x = ((y - y1) / (y2 - y1)) * (x2 - x1) + x1; - intersections.push(x); - } - x1 = x2; - y1 = y2; - } - } - // Find the longest segment of the horizontal line that has its center point - // inside the linear ring. - var pointX = NaN; - var maxSegmentLength = -Infinity; - intersections.sort(numberSafeCompareFunction); - x1 = intersections[0]; - for (i = 1, ii = intersections.length; i < ii; ++i) { - x2 = intersections[i]; - var segmentLength = Math.abs(x2 - x1); - if (segmentLength > maxSegmentLength) { - x = (x1 + x2) / 2; - if (linearRingsContainsXY(flatCoordinates, offset, ends, stride, x, y)) { - pointX = x; - maxSegmentLength = segmentLength; - } - } - x1 = x2; - } - if (isNaN(pointX)) { - // There is no horizontal line that has its center point inside the linear - // ring. Use the center of the the linear ring's extent. - pointX = flatCenters[flatCentersOffset]; - } - if (opt_dest) { - opt_dest.push(pointX, y, maxSegmentLength); - return opt_dest; - } - else { - return [pointX, y, maxSegmentLength]; - } -} - -/** - * @module ol/geom/flat/segments - */ -/** - * This function calls `callback` for each segment of the flat coordinates - * array. If the callback returns a truthy value the function returns that - * value immediately. Otherwise the function returns `false`. - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {function(import("../../coordinate.js").Coordinate, import("../../coordinate.js").Coordinate): T} callback Function - * called for each segment. - * @return {T|boolean} Value. - * @template T - */ -function forEach(flatCoordinates, offset, end, stride, callback) { - var point1 = [flatCoordinates[offset], flatCoordinates[offset + 1]]; - var point2 = []; - var ret; - for (; offset + stride < end; offset += stride) { - point2[0] = flatCoordinates[offset + stride]; - point2[1] = flatCoordinates[offset + stride + 1]; - ret = callback(point1, point2); - if (ret) { - return ret; - } - point1[0] = point2[0]; - point1[1] = point2[1]; - } - return false; -} - -/** - * @module ol/geom/flat/intersectsextent - */ -/** - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {import("../../extent.js").Extent} extent Extent. - * @return {boolean} True if the geometry and the extent intersect. - */ -function intersectsLineString(flatCoordinates, offset, end, stride, extent) { - var coordinatesExtent = extendFlatCoordinates(createEmpty(), flatCoordinates, offset, end, stride); - if (!intersects(extent, coordinatesExtent)) { - return false; - } - if (containsExtent(extent, coordinatesExtent)) { - return true; - } - if (coordinatesExtent[0] >= extent[0] && coordinatesExtent[2] <= extent[2]) { - return true; - } - if (coordinatesExtent[1] >= extent[1] && coordinatesExtent[3] <= extent[3]) { - return true; - } - return forEach(flatCoordinates, offset, end, stride, - /** - * @param {import("../../coordinate.js").Coordinate} point1 Start point. - * @param {import("../../coordinate.js").Coordinate} point2 End point. - * @return {boolean} `true` if the segment and the extent intersect, - * `false` otherwise. - */ - function (point1, point2) { - return intersectsSegment(extent, point1, point2); - }); -} -/** - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {import("../../extent.js").Extent} extent Extent. - * @return {boolean} True if the geometry and the extent intersect. - */ -function intersectsLinearRing(flatCoordinates, offset, end, stride, extent) { - if (intersectsLineString(flatCoordinates, offset, end, stride, extent)) { - return true; - } - if (linearRingContainsXY(flatCoordinates, offset, end, stride, extent[0], extent[1])) { - return true; - } - if (linearRingContainsXY(flatCoordinates, offset, end, stride, extent[0], extent[3])) { - return true; - } - if (linearRingContainsXY(flatCoordinates, offset, end, stride, extent[2], extent[1])) { - return true; - } - if (linearRingContainsXY(flatCoordinates, offset, end, stride, extent[2], extent[3])) { - return true; - } - return false; -} -/** - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array} ends Ends. - * @param {number} stride Stride. - * @param {import("../../extent.js").Extent} extent Extent. - * @return {boolean} True if the geometry and the extent intersect. - */ -function intersectsLinearRingArray(flatCoordinates, offset, ends, stride, extent) { - if (!intersectsLinearRing(flatCoordinates, offset, ends[0], stride, extent)) { - return false; - } - if (ends.length === 1) { - return true; - } - for (var i = 1, ii = ends.length; i < ii; ++i) { - if (linearRingContainsExtent(flatCoordinates, ends[i - 1], ends[i], stride, extent)) { - if (!intersectsLineString(flatCoordinates, ends[i - 1], ends[i], stride, extent)) { - return false; - } - } - } - return true; -} - -/** - * @module ol/geom/flat/reverse - */ -/** - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - */ -function coordinates(flatCoordinates, offset, end, stride) { - while (offset < end - stride) { - for (var i = 0; i < stride; ++i) { - var tmp = flatCoordinates[offset + i]; - flatCoordinates[offset + i] = flatCoordinates[end - stride + i]; - flatCoordinates[end - stride + i] = tmp; - } - offset += stride; - end -= stride; - } -} - -/** - * @module ol/geom/flat/orient - */ -/** - * Is the linear ring oriented clockwise in a coordinate system with a bottom-left - * coordinate origin? For a coordinate system with a top-left coordinate origin, - * the ring's orientation is clockwise when this function returns false. - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @return {boolean} Is clockwise. - */ -function linearRingIsClockwise(flatCoordinates, offset, end, stride) { - // http://tinyurl.com/clockwise-method - // https://github.com/OSGeo/gdal/blob/trunk/gdal/ogr/ogrlinearring.cpp - var edge = 0; - var x1 = flatCoordinates[end - stride]; - var y1 = flatCoordinates[end - stride + 1]; - for (; offset < end; offset += stride) { - var x2 = flatCoordinates[offset]; - var y2 = flatCoordinates[offset + 1]; - edge += (x2 - x1) * (y2 + y1); - x1 = x2; - y1 = y2; - } - return edge === 0 ? undefined : edge > 0; -} -/** - * Determines if linear rings are oriented. By default, left-hand orientation - * is tested (first ring must be clockwise, remaining rings counter-clockwise). - * To test for right-hand orientation, use the `opt_right` argument. - * - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array} ends Array of end indexes. - * @param {number} stride Stride. - * @param {boolean=} opt_right Test for right-hand orientation - * (counter-clockwise exterior ring and clockwise interior rings). - * @return {boolean} Rings are correctly oriented. - */ -function linearRingsAreOriented(flatCoordinates, offset, ends, stride, opt_right) { - var right = opt_right !== undefined ? opt_right : false; - for (var i = 0, ii = ends.length; i < ii; ++i) { - var end = ends[i]; - var isClockwise = linearRingIsClockwise(flatCoordinates, offset, end, stride); - if (i === 0) { - if ((right && isClockwise) || (!right && !isClockwise)) { - return false; - } - } - else { - if ((right && !isClockwise) || (!right && isClockwise)) { - return false; - } - } - offset = end; - } - return true; -} -/** - * Orient coordinates in a flat array of linear rings. By default, rings - * are oriented following the left-hand rule (clockwise for exterior and - * counter-clockwise for interior rings). To orient according to the - * right-hand rule, use the `opt_right` argument. - * - * @param {Array} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array} ends Ends. - * @param {number} stride Stride. - * @param {boolean=} opt_right Follow the right-hand rule for orientation. - * @return {number} End. - */ -function orientLinearRings(flatCoordinates, offset, ends, stride, opt_right) { - var right = opt_right !== undefined ? opt_right : false; - for (var i = 0, ii = ends.length; i < ii; ++i) { - var end = ends[i]; - var isClockwise = linearRingIsClockwise(flatCoordinates, offset, end, stride); - var reverse = i === 0 - ? (right && isClockwise) || (!right && !isClockwise) - : (right && !isClockwise) || (!right && isClockwise); - if (reverse) { - coordinates(flatCoordinates, offset, end, stride); - } - offset = end; - } - return offset; -} - -var __extends$a = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -/** - * @classdesc - * Polygon geometry. - * - * @api - */ -var Polygon = /** @class */ (function (_super) { - __extends$a(Polygon, _super); - /** - * @param {!Array>|!Array} coordinates - * Array of linear rings that define the polygon. The first linear ring of the - * array defines the outer-boundary or surface of the polygon. Each subsequent - * linear ring defines a hole in the surface of the polygon. A linear ring is - * an array of vertices' coordinates where the first coordinate and the last are - * equivalent. (For internal use, flat coordinates in combination with - * `opt_layout` and `opt_ends` are also accepted.) - * @param {import("./GeometryLayout.js").default=} opt_layout Layout. - * @param {Array=} opt_ends Ends (for internal use with flat coordinates). - */ - function Polygon(coordinates, opt_layout, opt_ends) { - var _this = _super.call(this) || this; - /** - * @type {Array} - * @private - */ - _this.ends_ = []; - /** - * @private - * @type {number} - */ - _this.flatInteriorPointRevision_ = -1; - /** - * @private - * @type {import("../coordinate.js").Coordinate} - */ - _this.flatInteriorPoint_ = null; - /** - * @private - * @type {number} - */ - _this.maxDelta_ = -1; - /** - * @private - * @type {number} - */ - _this.maxDeltaRevision_ = -1; - /** - * @private - * @type {number} - */ - _this.orientedRevision_ = -1; - /** - * @private - * @type {Array} - */ - _this.orientedFlatCoordinates_ = null; - if (opt_layout !== undefined && opt_ends) { - _this.setFlatCoordinates(opt_layout, - /** @type {Array} */ (coordinates)); - _this.ends_ = opt_ends; - } - else { - _this.setCoordinates( - /** @type {Array>} */ (coordinates), opt_layout); - } - return _this; - } - /** - * Append the passed linear ring to this polygon. - * @param {LinearRing} linearRing Linear ring. - * @api - */ - Polygon.prototype.appendLinearRing = function (linearRing) { - if (!this.flatCoordinates) { - this.flatCoordinates = linearRing.getFlatCoordinates().slice(); - } - else { - extend(this.flatCoordinates, linearRing.getFlatCoordinates()); - } - this.ends_.push(this.flatCoordinates.length); - this.changed(); - }; - /** - * Make a complete copy of the geometry. - * @return {!Polygon} Clone. - * @api - */ - Polygon.prototype.clone = function () { - var polygon = new Polygon(this.flatCoordinates.slice(), this.layout, this.ends_.slice()); - polygon.applyProperties(this); - return polygon; - }; - /** - * @param {number} x X. - * @param {number} y Y. - * @param {import("../coordinate.js").Coordinate} closestPoint Closest point. - * @param {number} minSquaredDistance Minimum squared distance. - * @return {number} Minimum squared distance. - */ - Polygon.prototype.closestPointXY = function (x, y, closestPoint, minSquaredDistance) { - if (minSquaredDistance < closestSquaredDistanceXY(this.getExtent(), x, y)) { - return minSquaredDistance; - } - if (this.maxDeltaRevision_ != this.getRevision()) { - this.maxDelta_ = Math.sqrt(arrayMaxSquaredDelta(this.flatCoordinates, 0, this.ends_, this.stride, 0)); - this.maxDeltaRevision_ = this.getRevision(); - } - return assignClosestArrayPoint(this.flatCoordinates, 0, this.ends_, this.stride, this.maxDelta_, true, x, y, closestPoint, minSquaredDistance); - }; - /** - * @param {number} x X. - * @param {number} y Y. - * @return {boolean} Contains (x, y). - */ - Polygon.prototype.containsXY = function (x, y) { - return linearRingsContainsXY(this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, x, y); - }; - /** - * Return the area of the polygon on projected plane. - * @return {number} Area (on projected plane). - * @api - */ - Polygon.prototype.getArea = function () { - return linearRings(this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride); - }; - /** - * Get the coordinate array for this geometry. This array has the structure - * of a GeoJSON coordinate array for polygons. - * - * @param {boolean=} opt_right Orient coordinates according to the right-hand - * rule (counter-clockwise for exterior and clockwise for interior rings). - * If `false`, coordinates will be oriented according to the left-hand rule - * (clockwise for exterior and counter-clockwise for interior rings). - * By default, coordinate orientation will depend on how the geometry was - * constructed. - * @return {Array>} Coordinates. - * @api - */ - Polygon.prototype.getCoordinates = function (opt_right) { - var flatCoordinates; - if (opt_right !== undefined) { - flatCoordinates = this.getOrientedFlatCoordinates().slice(); - orientLinearRings(flatCoordinates, 0, this.ends_, this.stride, opt_right); - } - else { - flatCoordinates = this.flatCoordinates; - } - return inflateCoordinatesArray(flatCoordinates, 0, this.ends_, this.stride); - }; - /** - * @return {Array} Ends. - */ - Polygon.prototype.getEnds = function () { - return this.ends_; - }; - /** - * @return {Array} Interior point. - */ - Polygon.prototype.getFlatInteriorPoint = function () { - if (this.flatInteriorPointRevision_ != this.getRevision()) { - var flatCenter = getCenter(this.getExtent()); - this.flatInteriorPoint_ = getInteriorPointOfArray(this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, flatCenter, 0); - this.flatInteriorPointRevision_ = this.getRevision(); - } - return this.flatInteriorPoint_; - }; - /** - * Return an interior point of the polygon. - * @return {Point} Interior point as XYM coordinate, where M is the - * length of the horizontal intersection that the point belongs to. - * @api - */ - Polygon.prototype.getInteriorPoint = function () { - return new Point(this.getFlatInteriorPoint(), GeometryLayout.XYM); - }; - /** - * Return the number of rings of the polygon, this includes the exterior - * ring and any interior rings. - * - * @return {number} Number of rings. - * @api - */ - Polygon.prototype.getLinearRingCount = function () { - return this.ends_.length; - }; - /** - * Return the Nth linear ring of the polygon geometry. Return `null` if the - * given index is out of range. - * The exterior linear ring is available at index `0` and the interior rings - * at index `1` and beyond. - * - * @param {number} index Index. - * @return {LinearRing} Linear ring. - * @api - */ - Polygon.prototype.getLinearRing = function (index) { - if (index < 0 || this.ends_.length <= index) { - return null; - } - return new LinearRing(this.flatCoordinates.slice(index === 0 ? 0 : this.ends_[index - 1], this.ends_[index]), this.layout); - }; - /** - * Return the linear rings of the polygon. - * @return {Array} Linear rings. - * @api - */ - Polygon.prototype.getLinearRings = function () { - var layout = this.layout; - var flatCoordinates = this.flatCoordinates; - var ends = this.ends_; - var linearRings = []; - var offset = 0; - for (var i = 0, ii = ends.length; i < ii; ++i) { - var end = ends[i]; - var linearRing = new LinearRing(flatCoordinates.slice(offset, end), layout); - linearRings.push(linearRing); - offset = end; - } - return linearRings; - }; - /** - * @return {Array} Oriented flat coordinates. - */ - Polygon.prototype.getOrientedFlatCoordinates = function () { - if (this.orientedRevision_ != this.getRevision()) { - var flatCoordinates = this.flatCoordinates; - if (linearRingsAreOriented(flatCoordinates, 0, this.ends_, this.stride)) { - this.orientedFlatCoordinates_ = flatCoordinates; - } - else { - this.orientedFlatCoordinates_ = flatCoordinates.slice(); - this.orientedFlatCoordinates_.length = orientLinearRings(this.orientedFlatCoordinates_, 0, this.ends_, this.stride); - } - this.orientedRevision_ = this.getRevision(); - } - return this.orientedFlatCoordinates_; - }; - /** - * @param {number} squaredTolerance Squared tolerance. - * @return {Polygon} Simplified Polygon. - * @protected - */ - Polygon.prototype.getSimplifiedGeometryInternal = function (squaredTolerance) { - var simplifiedFlatCoordinates = []; - var simplifiedEnds = []; - simplifiedFlatCoordinates.length = quantizeArray(this.flatCoordinates, 0, this.ends_, this.stride, Math.sqrt(squaredTolerance), simplifiedFlatCoordinates, 0, simplifiedEnds); - return new Polygon(simplifiedFlatCoordinates, GeometryLayout.XY, simplifiedEnds); - }; - /** - * Get the type of this geometry. - * @return {import("./GeometryType.js").default} Geometry type. - * @api - */ - Polygon.prototype.getType = function () { - return GeometryType.POLYGON; - }; - /** - * Test if the geometry and the passed extent intersect. - * @param {import("../extent.js").Extent} extent Extent. - * @return {boolean} `true` if the geometry and the extent intersect. - * @api - */ - Polygon.prototype.intersectsExtent = function (extent) { - return intersectsLinearRingArray(this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, extent); - }; - /** - * Set the coordinates of the polygon. - * @param {!Array>} coordinates Coordinates. - * @param {import("./GeometryLayout.js").default=} opt_layout Layout. - * @api - */ - Polygon.prototype.setCoordinates = function (coordinates, opt_layout) { - this.setLayout(opt_layout, coordinates, 2); - if (!this.flatCoordinates) { - this.flatCoordinates = []; - } - var ends = deflateCoordinatesArray(this.flatCoordinates, 0, coordinates, this.stride, this.ends_); - this.flatCoordinates.length = ends.length === 0 ? 0 : ends[ends.length - 1]; - this.changed(); - }; - return Polygon; -}(SimpleGeometry)); -/** - * Create a polygon from an extent. The layout used is `XY`. - * @param {import("../extent.js").Extent} extent The extent. - * @return {Polygon} The polygon. - * @api - */ -function fromExtent(extent) { - var minX = extent[0]; - var minY = extent[1]; - var maxX = extent[2]; - var maxY = extent[3]; - var flatCoordinates = [ - minX, - minY, - minX, - maxY, - maxX, - maxY, - maxX, - minY, - minX, - minY, - ]; - return new Polygon(flatCoordinates, GeometryLayout.XY, [ - flatCoordinates.length, - ]); -} - -var __extends$b = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -/** - * An animation configuration - * - * @typedef {Object} Animation - * @property {import("./coordinate.js").Coordinate} [sourceCenter] - * @property {import("./coordinate.js").Coordinate} [targetCenter] - * @property {number} [sourceResolution] - * @property {number} [targetResolution] - * @property {number} [sourceRotation] - * @property {number} [targetRotation] - * @property {import("./coordinate.js").Coordinate} [anchor] - * @property {number} start - * @property {number} duration - * @property {boolean} complete - * @property {function(number):number} easing - * @property {function(boolean):void} callback - */ -/** - * @typedef {Object} Constraints - * @property {import("./centerconstraint.js").Type} center - * @property {import("./resolutionconstraint.js").Type} resolution - * @property {import("./rotationconstraint.js").Type} rotation - */ -/** - * @typedef {Object} FitOptions - * @property {import("./size.js").Size} [size] The size in pixels of the box to fit - * the extent into. Default is the current size of the first map in the DOM that - * uses this view, or `[100, 100]` if no such map is found. - * @property {!Array} [padding=[0, 0, 0, 0]] Padding (in pixels) to be - * cleared inside the view. Values in the array are top, right, bottom and left - * padding. - * @property {boolean} [nearest=false] If the view `constrainResolution` option is `true`, - * get the nearest extent instead of the closest that actually fits the view. - * @property {number} [minResolution=0] Minimum resolution that we zoom to. - * @property {number} [maxZoom] Maximum zoom level that we zoom to. If - * `minResolution` is given, this property is ignored. - * @property {number} [duration] The duration of the animation in milliseconds. - * By default, there is no animation to the target extent. - * @property {function(number):number} [easing] The easing function used during - * the animation (defaults to {@link module:ol/easing~inAndOut}). - * The function will be called for each frame with a number representing a - * fraction of the animation's duration. The function should return a number - * between 0 and 1 representing the progress toward the destination state. - * @property {function(boolean):void} [callback] Function called when the view is in - * its final position. The callback will be called with `true` if the animation - * series completed on its own or `false` if it was cancelled. - */ -/** - * @typedef {Object} ViewOptions - * @property {import("./coordinate.js").Coordinate} [center] The initial center for - * the view. If a user projection is not set, the coordinate system for the center is - * specified with the `projection` option. Layer sources will not be fetched if this - * is not set, but the center can be set later with {@link #setCenter}. - * @property {boolean|number} [constrainRotation=true] Rotation constraint. - * `false` means no constraint. `true` means no constraint, but snap to zero - * near zero. A number constrains the rotation to that number of values. For - * example, `4` will constrain the rotation to 0, 90, 180, and 270 degrees. - * @property {boolean} [enableRotation=true] Enable rotation. - * If `false`, a rotation constraint that always sets the rotation to zero is - * used. The `constrainRotation` option has no effect if `enableRotation` is - * `false`. - * @property {import("./extent.js").Extent} [extent] The extent that constrains the - * view, in other words, nothing outside of this extent can be visible on the map. - * @property {boolean} [constrainOnlyCenter=false] If true, the extent - * constraint will only apply to the view center and not the whole extent. - * @property {boolean} [smoothExtentConstraint=true] If true, the extent - * constraint will be applied smoothly, i.e. allow the view to go slightly outside - * of the given `extent`. - * @property {number} [maxResolution] The maximum resolution used to determine - * the resolution constraint. It is used together with `minResolution` (or - * `maxZoom`) and `zoomFactor`. If unspecified it is calculated in such a way - * that the projection's validity extent fits in a 256x256 px tile. If the - * projection is Spherical Mercator (the default) then `maxResolution` defaults - * to `40075016.68557849 / 256 = 156543.03392804097`. - * @property {number} [minResolution] The minimum resolution used to determine - * the resolution constraint. It is used together with `maxResolution` (or - * `minZoom`) and `zoomFactor`. If unspecified it is calculated assuming 29 - * zoom levels (with a factor of 2). If the projection is Spherical Mercator - * (the default) then `minResolution` defaults to - * `40075016.68557849 / 256 / Math.pow(2, 28) = 0.0005831682455839253`. - * @property {number} [maxZoom=28] The maximum zoom level used to determine the - * resolution constraint. It is used together with `minZoom` (or - * `maxResolution`) and `zoomFactor`. Note that if `minResolution` is also - * provided, it is given precedence over `maxZoom`. - * @property {number} [minZoom=0] The minimum zoom level used to determine the - * resolution constraint. It is used together with `maxZoom` (or - * `minResolution`) and `zoomFactor`. Note that if `maxResolution` is also - * provided, it is given precedence over `minZoom`. - * @property {boolean} [multiWorld=false] If `false` the view is constrained so - * only one world is visible, and you cannot pan off the edge. If `true` the map - * may show multiple worlds at low zoom levels. Only used if the `projection` is - * global. Note that if `extent` is also provided it is given precedence. - * @property {boolean} [constrainResolution=false] If true, the view will always - * animate to the closest zoom level after an interaction; false means - * intermediary zoom levels are allowed. - * @property {boolean} [smoothResolutionConstraint=true] If true, the resolution - * min/max values will be applied smoothly, i. e. allow the view to exceed slightly - * the given resolution or zoom bounds. - * @property {boolean} [showFullExtent=false] Allow the view to be zoomed out to - * show the full configured extent. By default, when a view is configured with an - * extent, users will not be able to zoom out so the viewport exceeds the extent in - * either dimension. This means the full extent may not be visible if the viewport - * is taller or wider than the aspect ratio of the configured extent. If - * showFullExtent is true, the user will be able to zoom out so that the viewport - * exceeds the height or width of the configured extent, but not both, allowing the - * full extent to be shown. - * @property {import("./proj.js").ProjectionLike} [projection='EPSG:3857'] The - * projection. The default is Spherical Mercator. - * @property {number} [resolution] The initial resolution for the view. The - * units are `projection` units per pixel (e.g. meters per pixel). An - * alternative to setting this is to set `zoom`. Layer sources will not be - * fetched if neither this nor `zoom` are defined, but they can be set later - * with {@link #setZoom} or {@link #setResolution}. - * @property {Array} [resolutions] Resolutions to determine the - * resolution constraint. If set the `maxResolution`, `minResolution`, - * `minZoom`, `maxZoom`, and `zoomFactor` options are ignored. - * @property {number} [rotation=0] The initial rotation for the view in radians - * (positive rotation clockwise, 0 means North). - * @property {number} [zoom] Only used if `resolution` is not defined. Zoom - * level used to calculate the initial resolution for the view. - * @property {number} [zoomFactor=2] The zoom factor used to compute the - * corresponding resolution. - * @property {!Array} [padding=[0, 0, 0, 0]] Padding (in css pixels). - * If the map viewport is partially covered with other content (overlays) along - * its edges, this setting allows to shift the center of the viewport away from - * that content. The order of the values is top, right, bottom, left. - */ -/** - * @typedef {Object} AnimationOptions - * @property {import("./coordinate.js").Coordinate} [center] The center of the view at the end of - * the animation. - * @property {number} [zoom] The zoom level of the view at the end of the - * animation. This takes precedence over `resolution`. - * @property {number} [resolution] The resolution of the view at the end - * of the animation. If `zoom` is also provided, this option will be ignored. - * @property {number} [rotation] The rotation of the view at the end of - * the animation. - * @property {import("./coordinate.js").Coordinate} [anchor] Optional anchor to remain fixed - * during a rotation or resolution animation. - * @property {number} [duration=1000] The duration of the animation in milliseconds. - * @property {function(number):number} [easing] The easing function used - * during the animation (defaults to {@link module:ol/easing~inAndOut}). - * The function will be called for each frame with a number representing a - * fraction of the animation's duration. The function should return a number - * between 0 and 1 representing the progress toward the destination state. - */ -/** - * @typedef {Object} State - * @property {import("./coordinate.js").Coordinate} center - * @property {import("./proj/Projection.js").default} projection - * @property {number} resolution - * @property {number} rotation - * @property {number} zoom - */ -/** - * Default min zoom level for the map view. - * @type {number} - */ -var DEFAULT_MIN_ZOOM = 0; -/** - * @classdesc - * A View object represents a simple 2D view of the map. - * - * This is the object to act upon to change the center, resolution, - * and rotation of the map. - * - * A View has a `projection`. The projection determines the - * coordinate system of the center, and its units determine the units of the - * resolution (projection units per pixel). The default projection is - * Spherical Mercator (EPSG:3857). - * - * ### The view states - * - * A View is determined by three states: `center`, `resolution`, - * and `rotation`. Each state has a corresponding getter and setter, e.g. - * `getCenter` and `setCenter` for the `center` state. - * - * The `zoom` state is actually not saved on the view: all computations - * internally use the `resolution` state. Still, the `setZoom` and `getZoom` - * methods are available, as well as `getResolutionForZoom` and - * `getZoomForResolution` to switch from one system to the other. - * - * ### The constraints - * - * `setCenter`, `setResolution` and `setRotation` can be used to change the - * states of the view, but any constraint defined in the constructor will - * be applied along the way. - * - * A View object can have a *resolution constraint*, a *rotation constraint* - * and a *center constraint*. - * - * The *resolution constraint* typically restricts min/max values and - * snaps to specific resolutions. It is determined by the following - * options: `resolutions`, `maxResolution`, `maxZoom` and `zoomFactor`. - * If `resolutions` is set, the other three options are ignored. See - * documentation for each option for more information. By default, the view - * only has a min/max restriction and allow intermediary zoom levels when - * pinch-zooming for example. - * - * The *rotation constraint* snaps to specific angles. It is determined - * by the following options: `enableRotation` and `constrainRotation`. - * By default rotation is allowed and its value is snapped to zero when approaching the - * horizontal. - * - * The *center constraint* is determined by the `extent` option. By - * default the view center is not constrained at all. - * - * ### Changing the view state - * - * It is important to note that `setZoom`, `setResolution`, `setCenter` and - * `setRotation` are subject to the above mentioned constraints. As such, it - * may sometimes not be possible to know in advance the resulting state of the - * View. For example, calling `setResolution(10)` does not guarantee that - * `getResolution()` will return `10`. - * - * A consequence of this is that, when applying a delta on the view state, one - * should use `adjustCenter`, `adjustRotation`, `adjustZoom` and `adjustResolution` - * rather than the corresponding setters. This will let view do its internal - * computations. Besides, the `adjust*` methods also take an `opt_anchor` - * argument which allows specifying an origin for the transformation. - * - * ### Interacting with the view - * - * View constraints are usually only applied when the view is *at rest*, meaning that - * no interaction or animation is ongoing. As such, if the user puts the view in a - * state that is not equivalent to a constrained one (e.g. rotating the view when - * the snap angle is 0), an animation will be triggered at the interaction end to - * put back the view to a stable state; - * - * @api - */ -var View = /** @class */ (function (_super) { - __extends$b(View, _super); - /** - * @param {ViewOptions=} opt_options View options. - */ - function View(opt_options) { - var _this = _super.call(this) || this; - var options = assign({}, opt_options); - /** - * @private - * @type {Array} - */ - _this.hints_ = [0, 0]; - /** - * @private - * @type {Array>} - */ - _this.animations_ = []; - /** - * @private - * @type {number|undefined} - */ - _this.updateAnimationKey_; - /** - * @private - * @const - * @type {import("./proj/Projection.js").default} - */ - _this.projection_ = createProjection(options.projection, 'EPSG:3857'); - /** - * @private - * @type {import("./size.js").Size} - */ - _this.viewportSize_ = [100, 100]; - /** - * @private - * @type {import("./coordinate.js").Coordinate|undefined} - */ - _this.targetCenter_ = null; - /** - * @private - * @type {number|undefined} - */ - _this.targetResolution_; - /** - * @private - * @type {number|undefined} - */ - _this.targetRotation_; - /** - * @private - * @type {import("./coordinate.js").Coordinate|undefined} - */ - _this.cancelAnchor_ = undefined; - if (options.center) { - options.center = fromUserCoordinate(options.center, _this.projection_); - } - if (options.extent) { - options.extent = fromUserExtent(options.extent, _this.projection_); - } - _this.applyOptions_(options); - return _this; - } - /** - * Set up the view with the given options. - * @param {ViewOptions} options View options. - */ - View.prototype.applyOptions_ = function (options) { - /** - * @type {Object} - */ - var properties = {}; - var resolutionConstraintInfo = createResolutionConstraint(options); - /** - * @private - * @type {number} - */ - this.maxResolution_ = resolutionConstraintInfo.maxResolution; - /** - * @private - * @type {number} - */ - this.minResolution_ = resolutionConstraintInfo.minResolution; - /** - * @private - * @type {number} - */ - this.zoomFactor_ = resolutionConstraintInfo.zoomFactor; - /** - * @private - * @type {Array|undefined} - */ - this.resolutions_ = options.resolutions; - /** - * Padding (in css pixels). - * If the map viewport is partially covered with other content (overlays) along - * its edges, this setting allows to shift the center of the viewport away from that - * content. The order of the values in the array is top, right, bottom, left. - * The default is no padding, which is equivalent to `[0, 0, 0, 0]`. - * @type {Array|undefined} - * @api - */ - this.padding = options.padding; - /** - * @private - * @type {number} - */ - this.minZoom_ = resolutionConstraintInfo.minZoom; - var centerConstraint = createCenterConstraint(options); - var resolutionConstraint = resolutionConstraintInfo.constraint; - var rotationConstraint = createRotationConstraint(options); - /** - * @private - * @type {Constraints} - */ - this.constraints_ = { - center: centerConstraint, - resolution: resolutionConstraint, - rotation: rotationConstraint, - }; - this.setRotation(options.rotation !== undefined ? options.rotation : 0); - this.setCenterInternal(options.center !== undefined ? options.center : null); - if (options.resolution !== undefined) { - this.setResolution(options.resolution); - } - else if (options.zoom !== undefined) { - this.setZoom(options.zoom); - } - this.setProperties(properties); - /** - * @private - * @type {ViewOptions} - */ - this.options_ = options; - }; - /** - * Get an updated version of the view options used to construct the view. The - * current resolution (or zoom), center, and rotation are applied to any stored - * options. The provided options can be used to apply new min/max zoom or - * resolution limits. - * @param {ViewOptions} newOptions New options to be applied. - * @return {ViewOptions} New options updated with the current view state. - */ - View.prototype.getUpdatedOptions_ = function (newOptions) { - var options = assign({}, this.options_); - // preserve resolution (or zoom) - if (options.resolution !== undefined) { - options.resolution = this.getResolution(); - } - else { - options.zoom = this.getZoom(); - } - // preserve center - options.center = this.getCenterInternal(); - // preserve rotation - options.rotation = this.getRotation(); - return assign({}, options, newOptions); - }; - /** - * Animate the view. The view's center, zoom (or resolution), and rotation - * can be animated for smooth transitions between view states. For example, - * to animate the view to a new zoom level: - * - * view.animate({zoom: view.getZoom() + 1}); - * - * By default, the animation lasts one second and uses in-and-out easing. You - * can customize this behavior by including `duration` (in milliseconds) and - * `easing` options (see {@link module:ol/easing}). - * - * To chain together multiple animations, call the method with multiple - * animation objects. For example, to first zoom and then pan: - * - * view.animate({zoom: 10}, {center: [0, 0]}); - * - * If you provide a function as the last argument to the animate method, it - * will get called at the end of an animation series. The callback will be - * called with `true` if the animation series completed on its own or `false` - * if it was cancelled. - * - * Animations are cancelled by user interactions (e.g. dragging the map) or by - * calling `view.setCenter()`, `view.setResolution()`, or `view.setRotation()` - * (or another method that calls one of these). - * - * @param {...(AnimationOptions|function(boolean): void)} var_args Animation - * options. Multiple animations can be run in series by passing multiple - * options objects. To run multiple animations in parallel, call the method - * multiple times. An optional callback can be provided as a final - * argument. The callback will be called with a boolean indicating whether - * the animation completed without being cancelled. - * @api - */ - View.prototype.animate = function (var_args) { - if (this.isDef() && !this.getAnimating()) { - this.resolveConstraints(0); - } - var args = new Array(arguments.length); - for (var i = 0; i < args.length; ++i) { - var options = arguments[i]; - if (options.center) { - options = assign({}, options); - options.center = fromUserCoordinate(options.center, this.getProjection()); - } - if (options.anchor) { - options = assign({}, options); - options.anchor = fromUserCoordinate(options.anchor, this.getProjection()); - } - args[i] = options; - } - this.animateInternal.apply(this, args); - }; - /** - * @param {...(AnimationOptions|function(boolean): void)} var_args Animation options. - */ - View.prototype.animateInternal = function (var_args) { - var animationCount = arguments.length; - var callback; - if (animationCount > 1 && - typeof arguments[animationCount - 1] === 'function') { - callback = arguments[animationCount - 1]; - --animationCount; - } - if (!this.isDef()) { - // if view properties are not yet set, shortcut to the final state - var state = arguments[animationCount - 1]; - if (state.center) { - this.setCenterInternal(state.center); - } - if (state.zoom !== undefined) { - this.setZoom(state.zoom); - } - if (state.rotation !== undefined) { - this.setRotation(state.rotation); - } - if (callback) { - animationCallback(callback, true); - } - return; - } - var start = Date.now(); - var center = this.targetCenter_.slice(); - var resolution = this.targetResolution_; - var rotation = this.targetRotation_; - var series = []; - for (var i = 0; i < animationCount; ++i) { - var options = /** @type {AnimationOptions} */ (arguments[i]); - var animation = { - start: start, - complete: false, - anchor: options.anchor, - duration: options.duration !== undefined ? options.duration : 1000, - easing: options.easing || inAndOut, - callback: callback, - }; - if (options.center) { - animation.sourceCenter = center; - animation.targetCenter = options.center.slice(); - center = animation.targetCenter; - } - if (options.zoom !== undefined) { - animation.sourceResolution = resolution; - animation.targetResolution = this.getResolutionForZoom(options.zoom); - resolution = animation.targetResolution; - } - else if (options.resolution) { - animation.sourceResolution = resolution; - animation.targetResolution = options.resolution; - resolution = animation.targetResolution; - } - if (options.rotation !== undefined) { - animation.sourceRotation = rotation; - var delta = modulo(options.rotation - rotation + Math.PI, 2 * Math.PI) - Math.PI; - animation.targetRotation = rotation + delta; - rotation = animation.targetRotation; - } - // check if animation is a no-op - if (isNoopAnimation(animation)) { - animation.complete = true; - // we still push it onto the series for callback handling - } - else { - start += animation.duration; - } - series.push(animation); - } - this.animations_.push(series); - this.setHint(ViewHint.ANIMATING, 1); - this.updateAnimations_(); - }; - /** - * Determine if the view is being animated. - * @return {boolean} The view is being animated. - * @api - */ - View.prototype.getAnimating = function () { - return this.hints_[ViewHint.ANIMATING] > 0; - }; - /** - * Determine if the user is interacting with the view, such as panning or zooming. - * @return {boolean} The view is being interacted with. - * @api - */ - View.prototype.getInteracting = function () { - return this.hints_[ViewHint.INTERACTING] > 0; - }; - /** - * Cancel any ongoing animations. - * @api - */ - View.prototype.cancelAnimations = function () { - this.setHint(ViewHint.ANIMATING, -this.hints_[ViewHint.ANIMATING]); - var anchor; - for (var i = 0, ii = this.animations_.length; i < ii; ++i) { - var series = this.animations_[i]; - if (series[0].callback) { - animationCallback(series[0].callback, false); - } - if (!anchor) { - for (var j = 0, jj = series.length; j < jj; ++j) { - var animation = series[j]; - if (!animation.complete) { - anchor = animation.anchor; - break; - } - } - } - } - this.animations_.length = 0; - this.cancelAnchor_ = anchor; - }; - /** - * Update all animations. - */ - View.prototype.updateAnimations_ = function () { - if (this.updateAnimationKey_ !== undefined) { - cancelAnimationFrame(this.updateAnimationKey_); - this.updateAnimationKey_ = undefined; - } - if (!this.getAnimating()) { - return; - } - var now = Date.now(); - var more = false; - for (var i = this.animations_.length - 1; i >= 0; --i) { - var series = this.animations_[i]; - var seriesComplete = true; - for (var j = 0, jj = series.length; j < jj; ++j) { - var animation = series[j]; - if (animation.complete) { - continue; - } - var elapsed = now - animation.start; - var fraction = animation.duration > 0 ? elapsed / animation.duration : 1; - if (fraction >= 1) { - animation.complete = true; - fraction = 1; - } - else { - seriesComplete = false; - } - var progress = animation.easing(fraction); - if (animation.sourceCenter) { - var x0 = animation.sourceCenter[0]; - var y0 = animation.sourceCenter[1]; - var x1 = animation.targetCenter[0]; - var y1 = animation.targetCenter[1]; - var x = x0 + progress * (x1 - x0); - var y = y0 + progress * (y1 - y0); - this.targetCenter_ = [x, y]; - } - if (animation.sourceResolution && animation.targetResolution) { - var resolution = progress === 1 - ? animation.targetResolution - : animation.sourceResolution + - progress * - (animation.targetResolution - animation.sourceResolution); - if (animation.anchor) { - var size = this.getViewportSize_(this.getRotation()); - var constrainedResolution = this.constraints_.resolution(resolution, 0, size, true); - this.targetCenter_ = this.calculateCenterZoom(constrainedResolution, animation.anchor); - } - this.targetResolution_ = resolution; - this.applyTargetState_(true); - } - if (animation.sourceRotation !== undefined && - animation.targetRotation !== undefined) { - var rotation = progress === 1 - ? modulo(animation.targetRotation + Math.PI, 2 * Math.PI) - - Math.PI - : animation.sourceRotation + - progress * - (animation.targetRotation - animation.sourceRotation); - if (animation.anchor) { - var constrainedRotation = this.constraints_.rotation(rotation, true); - this.targetCenter_ = this.calculateCenterRotate(constrainedRotation, animation.anchor); - } - this.targetRotation_ = rotation; - } - this.applyTargetState_(true); - more = true; - if (!animation.complete) { - break; - } - } - if (seriesComplete) { - this.animations_[i] = null; - this.setHint(ViewHint.ANIMATING, -1); - var callback = series[0].callback; - if (callback) { - animationCallback(callback, true); - } - } - } - // prune completed series - this.animations_ = this.animations_.filter(Boolean); - if (more && this.updateAnimationKey_ === undefined) { - this.updateAnimationKey_ = requestAnimationFrame(this.updateAnimations_.bind(this)); - } - }; - /** - * @param {number} rotation Target rotation. - * @param {import("./coordinate.js").Coordinate} anchor Rotation anchor. - * @return {import("./coordinate.js").Coordinate|undefined} Center for rotation and anchor. - */ - View.prototype.calculateCenterRotate = function (rotation, anchor) { - var center; - var currentCenter = this.getCenterInternal(); - if (currentCenter !== undefined) { - center = [currentCenter[0] - anchor[0], currentCenter[1] - anchor[1]]; - rotate(center, rotation - this.getRotation()); - add$2(center, anchor); - } - return center; - }; - /** - * @param {number} resolution Target resolution. - * @param {import("./coordinate.js").Coordinate} anchor Zoom anchor. - * @return {import("./coordinate.js").Coordinate|undefined} Center for resolution and anchor. - */ - View.prototype.calculateCenterZoom = function (resolution, anchor) { - var center; - var currentCenter = this.getCenterInternal(); - var currentResolution = this.getResolution(); - if (currentCenter !== undefined && currentResolution !== undefined) { - var x = anchor[0] - - (resolution * (anchor[0] - currentCenter[0])) / currentResolution; - var y = anchor[1] - - (resolution * (anchor[1] - currentCenter[1])) / currentResolution; - center = [x, y]; - } - return center; - }; - /** - * Returns the current viewport size. - * @private - * @param {number=} opt_rotation Take into account the rotation of the viewport when giving the size - * @return {import("./size.js").Size} Viewport size or `[100, 100]` when no viewport is found. - */ - View.prototype.getViewportSize_ = function (opt_rotation) { - var size = this.viewportSize_; - if (opt_rotation) { - var w = size[0]; - var h = size[1]; - return [ - Math.abs(w * Math.cos(opt_rotation)) + - Math.abs(h * Math.sin(opt_rotation)), - Math.abs(w * Math.sin(opt_rotation)) + - Math.abs(h * Math.cos(opt_rotation)), - ]; - } - else { - return size; - } - }; - /** - * Stores the viewport size on the view. The viewport size is not read every time from the DOM - * to avoid performance hit and layout reflow. - * This should be done on map size change. - * Note: the constraints are not resolved during an animation to avoid stopping it - * @param {import("./size.js").Size=} opt_size Viewport size; if undefined, [100, 100] is assumed - */ - View.prototype.setViewportSize = function (opt_size) { - this.viewportSize_ = Array.isArray(opt_size) - ? opt_size.slice() - : [100, 100]; - if (!this.getAnimating()) { - this.resolveConstraints(0); - } - }; - /** - * Get the view center. - * @return {import("./coordinate.js").Coordinate|undefined} The center of the view. - * @observable - * @api - */ - View.prototype.getCenter = function () { - var center = this.getCenterInternal(); - if (!center) { - return center; - } - return toUserCoordinate(center, this.getProjection()); - }; - /** - * Get the view center without transforming to user projection. - * @return {import("./coordinate.js").Coordinate|undefined} The center of the view. - */ - View.prototype.getCenterInternal = function () { - return /** @type {import("./coordinate.js").Coordinate|undefined} */ (this.get(ViewProperty.CENTER)); - }; - /** - * @return {Constraints} Constraints. - */ - View.prototype.getConstraints = function () { - return this.constraints_; - }; - /** - * @return {boolean} Resolution constraint is set - */ - View.prototype.getConstrainResolution = function () { - return this.options_.constrainResolution; - }; - /** - * @param {Array=} opt_hints Destination array. - * @return {Array} Hint. - */ - View.prototype.getHints = function (opt_hints) { - if (opt_hints !== undefined) { - opt_hints[0] = this.hints_[0]; - opt_hints[1] = this.hints_[1]; - return opt_hints; - } - else { - return this.hints_.slice(); - } - }; - /** - * Calculate the extent for the current view state and the passed size. - * The size is the pixel dimensions of the box into which the calculated extent - * should fit. In most cases you want to get the extent of the entire map, - * that is `map.getSize()`. - * @param {import("./size.js").Size=} opt_size Box pixel size. If not provided, the size - * of the map that uses this view will be used. - * @return {import("./extent.js").Extent} Extent. - * @api - */ - View.prototype.calculateExtent = function (opt_size) { - var extent = this.calculateExtentInternal(opt_size); - return toUserExtent(extent, this.getProjection()); - }; - /** - * @param {import("./size.js").Size=} opt_size Box pixel size. If not provided, - * the map's last known viewport size will be used. - * @return {import("./extent.js").Extent} Extent. - */ - View.prototype.calculateExtentInternal = function (opt_size) { - var size = opt_size || this.getViewportSize_(); - var center = /** @type {!import("./coordinate.js").Coordinate} */ (this.getCenterInternal()); - assert(center, 1); // The view center is not defined - var resolution = /** @type {!number} */ (this.getResolution()); - assert(resolution !== undefined, 2); // The view resolution is not defined - var rotation = /** @type {!number} */ (this.getRotation()); - assert(rotation !== undefined, 3); // The view rotation is not defined - return getForViewAndSize(center, resolution, rotation, size); - }; - /** - * Get the maximum resolution of the view. - * @return {number} The maximum resolution of the view. - * @api - */ - View.prototype.getMaxResolution = function () { - return this.maxResolution_; - }; - /** - * Get the minimum resolution of the view. - * @return {number} The minimum resolution of the view. - * @api - */ - View.prototype.getMinResolution = function () { - return this.minResolution_; - }; - /** - * Get the maximum zoom level for the view. - * @return {number} The maximum zoom level. - * @api - */ - View.prototype.getMaxZoom = function () { - return /** @type {number} */ (this.getZoomForResolution(this.minResolution_)); - }; - /** - * Set a new maximum zoom level for the view. - * @param {number} zoom The maximum zoom level. - * @api - */ - View.prototype.setMaxZoom = function (zoom) { - this.applyOptions_(this.getUpdatedOptions_({ maxZoom: zoom })); - }; - /** - * Get the minimum zoom level for the view. - * @return {number} The minimum zoom level. - * @api - */ - View.prototype.getMinZoom = function () { - return /** @type {number} */ (this.getZoomForResolution(this.maxResolution_)); - }; - /** - * Set a new minimum zoom level for the view. - * @param {number} zoom The minimum zoom level. - * @api - */ - View.prototype.setMinZoom = function (zoom) { - this.applyOptions_(this.getUpdatedOptions_({ minZoom: zoom })); - }; - /** - * Set whether the view shoud allow intermediary zoom levels. - * @param {boolean} enabled Whether the resolution is constrained. - * @api - */ - View.prototype.setConstrainResolution = function (enabled) { - this.applyOptions_(this.getUpdatedOptions_({ constrainResolution: enabled })); - }; - /** - * Get the view projection. - * @return {import("./proj/Projection.js").default} The projection of the view. - * @api - */ - View.prototype.getProjection = function () { - return this.projection_; - }; - /** - * Get the view resolution. - * @return {number|undefined} The resolution of the view. - * @observable - * @api - */ - View.prototype.getResolution = function () { - return /** @type {number|undefined} */ (this.get(ViewProperty.RESOLUTION)); - }; - /** - * Get the resolutions for the view. This returns the array of resolutions - * passed to the constructor of the View, or undefined if none were given. - * @return {Array|undefined} The resolutions of the view. - * @api - */ - View.prototype.getResolutions = function () { - return this.resolutions_; - }; - /** - * Get the resolution for a provided extent (in map units) and size (in pixels). - * @param {import("./extent.js").Extent} extent Extent. - * @param {import("./size.js").Size=} opt_size Box pixel size. - * @return {number} The resolution at which the provided extent will render at - * the given size. - * @api - */ - View.prototype.getResolutionForExtent = function (extent, opt_size) { - return this.getResolutionForExtentInternal(fromUserExtent(extent, this.getProjection()), opt_size); - }; - /** - * Get the resolution for a provided extent (in map units) and size (in pixels). - * @param {import("./extent.js").Extent} extent Extent. - * @param {import("./size.js").Size=} opt_size Box pixel size. - * @return {number} The resolution at which the provided extent will render at - * the given size. - */ - View.prototype.getResolutionForExtentInternal = function (extent, opt_size) { - var size = opt_size || this.getViewportSize_(); - var xResolution = getWidth(extent) / size[0]; - var yResolution = getHeight(extent) / size[1]; - return Math.max(xResolution, yResolution); - }; - /** - * Return a function that returns a value between 0 and 1 for a - * resolution. Exponential scaling is assumed. - * @param {number=} opt_power Power. - * @return {function(number): number} Resolution for value function. - */ - View.prototype.getResolutionForValueFunction = function (opt_power) { - var power = opt_power || 2; - var maxResolution = this.getConstrainedResolution(this.maxResolution_); - var minResolution = this.minResolution_; - var max = Math.log(maxResolution / minResolution) / Math.log(power); - return ( - /** - * @param {number} value Value. - * @return {number} Resolution. - */ - function (value) { - var resolution = maxResolution / Math.pow(power, value * max); - return resolution; - }); - }; - /** - * Get the view rotation. - * @return {number} The rotation of the view in radians. - * @observable - * @api - */ - View.prototype.getRotation = function () { - return /** @type {number} */ (this.get(ViewProperty.ROTATION)); - }; - /** - * Return a function that returns a resolution for a value between - * 0 and 1. Exponential scaling is assumed. - * @param {number=} opt_power Power. - * @return {function(number): number} Value for resolution function. - */ - View.prototype.getValueForResolutionFunction = function (opt_power) { - var logPower = Math.log(opt_power || 2); - var maxResolution = this.getConstrainedResolution(this.maxResolution_); - var minResolution = this.minResolution_; - var max = Math.log(maxResolution / minResolution) / logPower; - return ( - /** - * @param {number} resolution Resolution. - * @return {number} Value. - */ - function (resolution) { - var value = Math.log(maxResolution / resolution) / logPower / max; - return value; - }); - }; - /** - * Returns the size of the viewport minus padding. - * @private - * @param {number=} opt_rotation Take into account the rotation of the viewport when giving the size - * @return {import("./size.js").Size} Viewport size reduced by the padding. - */ - View.prototype.getViewportSizeMinusPadding_ = function (opt_rotation) { - var size = this.getViewportSize_(opt_rotation); - var padding = this.padding; - if (padding) { - size = [ - size[0] - padding[1] - padding[3], - size[1] - padding[0] - padding[2], - ]; - } - return size; - }; - /** - * @return {State} View state. - */ - View.prototype.getState = function () { - var projection = this.getProjection(); - var resolution = /** @type {number} */ (this.getResolution()); - var rotation = this.getRotation(); - var center = /** @type {import("./coordinate.js").Coordinate} */ (this.getCenterInternal()); - var padding = this.padding; - if (padding) { - var reducedSize = this.getViewportSizeMinusPadding_(); - center = calculateCenterOn(center, this.getViewportSize_(), [reducedSize[0] / 2 + padding[3], reducedSize[1] / 2 + padding[0]], resolution, rotation); - } - return { - center: center.slice(0), - projection: projection !== undefined ? projection : null, - resolution: resolution, - rotation: rotation, - zoom: this.getZoom(), - }; - }; - /** - * Get the current zoom level. This method may return non-integer zoom levels - * if the view does not constrain the resolution, or if an interaction or - * animation is underway. - * @return {number|undefined} Zoom. - * @api - */ - View.prototype.getZoom = function () { - var zoom; - var resolution = this.getResolution(); - if (resolution !== undefined) { - zoom = this.getZoomForResolution(resolution); - } - return zoom; - }; - /** - * Get the zoom level for a resolution. - * @param {number} resolution The resolution. - * @return {number|undefined} The zoom level for the provided resolution. - * @api - */ - View.prototype.getZoomForResolution = function (resolution) { - var offset = this.minZoom_ || 0; - var max, zoomFactor; - if (this.resolutions_) { - var nearest = linearFindNearest(this.resolutions_, resolution, 1); - offset = nearest; - max = this.resolutions_[nearest]; - if (nearest == this.resolutions_.length - 1) { - zoomFactor = 2; - } - else { - zoomFactor = max / this.resolutions_[nearest + 1]; - } - } - else { - max = this.maxResolution_; - zoomFactor = this.zoomFactor_; - } - return offset + Math.log(max / resolution) / Math.log(zoomFactor); - }; - /** - * Get the resolution for a zoom level. - * @param {number} zoom Zoom level. - * @return {number} The view resolution for the provided zoom level. - * @api - */ - View.prototype.getResolutionForZoom = function (zoom) { - if (this.resolutions_) { - if (this.resolutions_.length <= 1) { - return 0; - } - var baseLevel = clamp(Math.floor(zoom), 0, this.resolutions_.length - 2); - var zoomFactor = this.resolutions_[baseLevel] / this.resolutions_[baseLevel + 1]; - return (this.resolutions_[baseLevel] / - Math.pow(zoomFactor, clamp(zoom - baseLevel, 0, 1))); - } - else { - return (this.maxResolution_ / Math.pow(this.zoomFactor_, zoom - this.minZoom_)); - } - }; - /** - * Fit the given geometry or extent based on the given map size and border. - * The size is pixel dimensions of the box to fit the extent into. - * In most cases you will want to use the map size, that is `map.getSize()`. - * Takes care of the map angle. - * @param {import("./geom/SimpleGeometry.js").default|import("./extent.js").Extent} geometryOrExtent The geometry or - * extent to fit the view to. - * @param {FitOptions=} opt_options Options. - * @api - */ - View.prototype.fit = function (geometryOrExtent, opt_options) { - /** @type {import("./geom/SimpleGeometry.js").default} */ - var geometry; - assert(Array.isArray(geometryOrExtent) || - typeof ( /** @type {?} */(geometryOrExtent).getSimplifiedGeometry) === - 'function', 24); // Invalid extent or geometry provided as `geometry` - if (Array.isArray(geometryOrExtent)) { - assert(!isEmpty$1(geometryOrExtent), 25); // Cannot fit empty extent provided as `geometry` - var extent = fromUserExtent(geometryOrExtent, this.getProjection()); - geometry = fromExtent(extent); - } - else if (geometryOrExtent.getType() === GeometryType.CIRCLE) { - var extent = fromUserExtent(geometryOrExtent.getExtent(), this.getProjection()); - geometry = fromExtent(extent); - geometry.rotate(this.getRotation(), getCenter(extent)); - } - else { - var userProjection = getUserProjection(); - if (userProjection) { - geometry = /** @type {import("./geom/SimpleGeometry.js").default} */ (geometryOrExtent - .clone() - .transform(userProjection, this.getProjection())); - } - else { - geometry = geometryOrExtent; - } - } - this.fitInternal(geometry, opt_options); - }; - /** - * @param {import("./geom/SimpleGeometry.js").default} geometry The geometry. - * @param {FitOptions=} opt_options Options. - */ - View.prototype.fitInternal = function (geometry, opt_options) { - var options = opt_options || {}; - var size = options.size; - if (!size) { - size = this.getViewportSizeMinusPadding_(); - } - var padding = options.padding !== undefined ? options.padding : [0, 0, 0, 0]; - var nearest = options.nearest !== undefined ? options.nearest : false; - var minResolution; - if (options.minResolution !== undefined) { - minResolution = options.minResolution; - } - else if (options.maxZoom !== undefined) { - minResolution = this.getResolutionForZoom(options.maxZoom); - } - else { - minResolution = 0; - } - var coords = geometry.getFlatCoordinates(); - // calculate rotated extent - var rotation = this.getRotation(); - var cosAngle = Math.cos(-rotation); - var sinAngle = Math.sin(-rotation); - var minRotX = +Infinity; - var minRotY = +Infinity; - var maxRotX = -Infinity; - var maxRotY = -Infinity; - var stride = geometry.getStride(); - for (var i = 0, ii = coords.length; i < ii; i += stride) { - var rotX = coords[i] * cosAngle - coords[i + 1] * sinAngle; - var rotY = coords[i] * sinAngle + coords[i + 1] * cosAngle; - minRotX = Math.min(minRotX, rotX); - minRotY = Math.min(minRotY, rotY); - maxRotX = Math.max(maxRotX, rotX); - maxRotY = Math.max(maxRotY, rotY); - } - // calculate resolution - var resolution = this.getResolutionForExtentInternal([minRotX, minRotY, maxRotX, maxRotY], [size[0] - padding[1] - padding[3], size[1] - padding[0] - padding[2]]); - resolution = isNaN(resolution) - ? minResolution - : Math.max(resolution, minResolution); - resolution = this.getConstrainedResolution(resolution, nearest ? 0 : 1); - // calculate center - sinAngle = -sinAngle; // go back to original rotation - var centerRotX = (minRotX + maxRotX) / 2; - var centerRotY = (minRotY + maxRotY) / 2; - centerRotX += ((padding[1] - padding[3]) / 2) * resolution; - centerRotY += ((padding[0] - padding[2]) / 2) * resolution; - var centerX = centerRotX * cosAngle - centerRotY * sinAngle; - var centerY = centerRotY * cosAngle + centerRotX * sinAngle; - var center = this.getConstrainedCenter([centerX, centerY], resolution); - var callback = options.callback ? options.callback : VOID; - if (options.duration !== undefined) { - this.animateInternal({ - resolution: resolution, - center: center, - duration: options.duration, - easing: options.easing, - }, callback); - } - else { - this.targetResolution_ = resolution; - this.targetCenter_ = center; - this.applyTargetState_(false, true); - animationCallback(callback, true); - } - }; - /** - * Center on coordinate and view position. - * @param {import("./coordinate.js").Coordinate} coordinate Coordinate. - * @param {import("./size.js").Size} size Box pixel size. - * @param {import("./pixel.js").Pixel} position Position on the view to center on. - * @api - */ - View.prototype.centerOn = function (coordinate, size, position) { - this.centerOnInternal(fromUserCoordinate(coordinate, this.getProjection()), size, position); - }; - /** - * @param {import("./coordinate.js").Coordinate} coordinate Coordinate. - * @param {import("./size.js").Size} size Box pixel size. - * @param {import("./pixel.js").Pixel} position Position on the view to center on. - */ - View.prototype.centerOnInternal = function (coordinate, size, position) { - this.setCenterInternal(calculateCenterOn(coordinate, size, position, this.getResolution(), this.getRotation())); - }; - /** - * Calculates the shift between map and viewport center. - * @param {import("./coordinate.js").Coordinate} center Center. - * @param {number} resolution Resolution. - * @param {number} rotation Rotation. - * @param {import("./size.js").Size} size Size. - * @return {Array|undefined} Center shift. - */ - View.prototype.calculateCenterShift = function (center, resolution, rotation, size) { - var centerShift; - var padding = this.padding; - if (padding && center) { - var reducedSize = this.getViewportSizeMinusPadding_(-rotation); - var shiftedCenter = calculateCenterOn(center, size, [reducedSize[0] / 2 + padding[3], reducedSize[1] / 2 + padding[0]], resolution, rotation); - centerShift = [ - center[0] - shiftedCenter[0], - center[1] - shiftedCenter[1], - ]; - } - return centerShift; - }; - /** - * @return {boolean} Is defined. - */ - View.prototype.isDef = function () { - return !!this.getCenterInternal() && this.getResolution() !== undefined; - }; - /** - * Adds relative coordinates to the center of the view. Any extent constraint will apply. - * @param {import("./coordinate.js").Coordinate} deltaCoordinates Relative value to add. - * @api - */ - View.prototype.adjustCenter = function (deltaCoordinates) { - var center = toUserCoordinate(this.targetCenter_, this.getProjection()); - this.setCenter([ - center[0] + deltaCoordinates[0], - center[1] + deltaCoordinates[1], - ]); - }; - /** - * Adds relative coordinates to the center of the view. Any extent constraint will apply. - * @param {import("./coordinate.js").Coordinate} deltaCoordinates Relative value to add. - */ - View.prototype.adjustCenterInternal = function (deltaCoordinates) { - var center = this.targetCenter_; - this.setCenterInternal([ - center[0] + deltaCoordinates[0], - center[1] + deltaCoordinates[1], - ]); - }; - /** - * Multiply the view resolution by a ratio, optionally using an anchor. Any resolution - * constraint will apply. - * @param {number} ratio The ratio to apply on the view resolution. - * @param {import("./coordinate.js").Coordinate=} opt_anchor The origin of the transformation. - * @api - */ - View.prototype.adjustResolution = function (ratio, opt_anchor) { - var anchor = opt_anchor && fromUserCoordinate(opt_anchor, this.getProjection()); - this.adjustResolutionInternal(ratio, anchor); - }; - /** - * Multiply the view resolution by a ratio, optionally using an anchor. Any resolution - * constraint will apply. - * @param {number} ratio The ratio to apply on the view resolution. - * @param {import("./coordinate.js").Coordinate=} opt_anchor The origin of the transformation. - */ - View.prototype.adjustResolutionInternal = function (ratio, opt_anchor) { - var isMoving = this.getAnimating() || this.getInteracting(); - var size = this.getViewportSize_(this.getRotation()); - var newResolution = this.constraints_.resolution(this.targetResolution_ * ratio, 0, size, isMoving); - if (opt_anchor) { - this.targetCenter_ = this.calculateCenterZoom(newResolution, opt_anchor); - } - this.targetResolution_ *= ratio; - this.applyTargetState_(); - }; - /** - * Adds a value to the view zoom level, optionally using an anchor. Any resolution - * constraint will apply. - * @param {number} delta Relative value to add to the zoom level. - * @param {import("./coordinate.js").Coordinate=} opt_anchor The origin of the transformation. - * @api - */ - View.prototype.adjustZoom = function (delta, opt_anchor) { - this.adjustResolution(Math.pow(this.zoomFactor_, -delta), opt_anchor); - }; - /** - * Adds a value to the view rotation, optionally using an anchor. Any rotation - * constraint will apply. - * @param {number} delta Relative value to add to the zoom rotation, in radians. - * @param {import("./coordinate.js").Coordinate=} opt_anchor The rotation center. - * @api - */ - View.prototype.adjustRotation = function (delta, opt_anchor) { - if (opt_anchor) { - opt_anchor = fromUserCoordinate(opt_anchor, this.getProjection()); - } - this.adjustRotationInternal(delta, opt_anchor); - }; - /** - * @param {number} delta Relative value to add to the zoom rotation, in radians. - * @param {import("./coordinate.js").Coordinate=} opt_anchor The rotation center. - */ - View.prototype.adjustRotationInternal = function (delta, opt_anchor) { - var isMoving = this.getAnimating() || this.getInteracting(); - var newRotation = this.constraints_.rotation(this.targetRotation_ + delta, isMoving); - if (opt_anchor) { - this.targetCenter_ = this.calculateCenterRotate(newRotation, opt_anchor); - } - this.targetRotation_ += delta; - this.applyTargetState_(); - }; - /** - * Set the center of the current view. Any extent constraint will apply. - * @param {import("./coordinate.js").Coordinate|undefined} center The center of the view. - * @observable - * @api - */ - View.prototype.setCenter = function (center) { - this.setCenterInternal(fromUserCoordinate(center, this.getProjection())); - }; - /** - * Set the center using the view projection (not the user projection). - * @param {import("./coordinate.js").Coordinate|undefined} center The center of the view. - */ - View.prototype.setCenterInternal = function (center) { - this.targetCenter_ = center; - this.applyTargetState_(); - }; - /** - * @param {import("./ViewHint.js").default} hint Hint. - * @param {number} delta Delta. - * @return {number} New value. - */ - View.prototype.setHint = function (hint, delta) { - this.hints_[hint] += delta; - this.changed(); - return this.hints_[hint]; - }; - /** - * Set the resolution for this view. Any resolution constraint will apply. - * @param {number|undefined} resolution The resolution of the view. - * @observable - * @api - */ - View.prototype.setResolution = function (resolution) { - this.targetResolution_ = resolution; - this.applyTargetState_(); - }; - /** - * Set the rotation for this view. Any rotation constraint will apply. - * @param {number} rotation The rotation of the view in radians. - * @observable - * @api - */ - View.prototype.setRotation = function (rotation) { - this.targetRotation_ = rotation; - this.applyTargetState_(); - }; - /** - * Zoom to a specific zoom level. Any resolution constrain will apply. - * @param {number} zoom Zoom level. - * @api - */ - View.prototype.setZoom = function (zoom) { - this.setResolution(this.getResolutionForZoom(zoom)); - }; - /** - * Recompute rotation/resolution/center based on target values. - * Note: we have to compute rotation first, then resolution and center considering that - * parameters can influence one another in case a view extent constraint is present. - * @param {boolean=} opt_doNotCancelAnims Do not cancel animations. - * @param {boolean=} opt_forceMoving Apply constraints as if the view is moving. - * @private - */ - View.prototype.applyTargetState_ = function (opt_doNotCancelAnims, opt_forceMoving) { - var isMoving = this.getAnimating() || this.getInteracting() || opt_forceMoving; - // compute rotation - var newRotation = this.constraints_.rotation(this.targetRotation_, isMoving); - var size = this.getViewportSize_(newRotation); - var newResolution = this.constraints_.resolution(this.targetResolution_, 0, size, isMoving); - var newCenter = this.constraints_.center(this.targetCenter_, newResolution, size, isMoving, this.calculateCenterShift(this.targetCenter_, newResolution, newRotation, size)); - if (this.get(ViewProperty.ROTATION) !== newRotation) { - this.set(ViewProperty.ROTATION, newRotation); - } - if (this.get(ViewProperty.RESOLUTION) !== newResolution) { - this.set(ViewProperty.RESOLUTION, newResolution); - } - if (!this.get(ViewProperty.CENTER) || - !equals$2(this.get(ViewProperty.CENTER), newCenter)) { - this.set(ViewProperty.CENTER, newCenter); - } - if (this.getAnimating() && !opt_doNotCancelAnims) { - this.cancelAnimations(); - } - this.cancelAnchor_ = undefined; - }; - /** - * If any constraints need to be applied, an animation will be triggered. - * This is typically done on interaction end. - * Note: calling this with a duration of 0 will apply the constrained values straight away, - * without animation. - * @param {number=} opt_duration The animation duration in ms. - * @param {number=} opt_resolutionDirection Which direction to zoom. - * @param {import("./coordinate.js").Coordinate=} opt_anchor The origin of the transformation. - */ - View.prototype.resolveConstraints = function (opt_duration, opt_resolutionDirection, opt_anchor) { - var duration = opt_duration !== undefined ? opt_duration : 200; - var direction = opt_resolutionDirection || 0; - var newRotation = this.constraints_.rotation(this.targetRotation_); - var size = this.getViewportSize_(newRotation); - var newResolution = this.constraints_.resolution(this.targetResolution_, direction, size); - var newCenter = this.constraints_.center(this.targetCenter_, newResolution, size, false, this.calculateCenterShift(this.targetCenter_, newResolution, newRotation, size)); - if (duration === 0 && !this.cancelAnchor_) { - this.targetResolution_ = newResolution; - this.targetRotation_ = newRotation; - this.targetCenter_ = newCenter; - this.applyTargetState_(); - return; - } - var anchor = opt_anchor || (duration === 0 ? this.cancelAnchor_ : undefined); - this.cancelAnchor_ = undefined; - if (this.getResolution() !== newResolution || - this.getRotation() !== newRotation || - !this.getCenterInternal() || - !equals$2(this.getCenterInternal(), newCenter)) { - if (this.getAnimating()) { - this.cancelAnimations(); - } - this.animateInternal({ - rotation: newRotation, - center: newCenter, - resolution: newResolution, - duration: duration, - easing: easeOut, - anchor: anchor, - }); - } - }; - /** - * Notify the View that an interaction has started. - * The view state will be resolved to a stable one if needed - * (depending on its constraints). - * @api - */ - View.prototype.beginInteraction = function () { - this.resolveConstraints(0); - this.setHint(ViewHint.INTERACTING, 1); - }; - /** - * Notify the View that an interaction has ended. The view state will be resolved - * to a stable one if needed (depending on its constraints). - * @param {number=} opt_duration Animation duration in ms. - * @param {number=} opt_resolutionDirection Which direction to zoom. - * @param {import("./coordinate.js").Coordinate=} opt_anchor The origin of the transformation. - * @api - */ - View.prototype.endInteraction = function (opt_duration, opt_resolutionDirection, opt_anchor) { - var anchor = opt_anchor && fromUserCoordinate(opt_anchor, this.getProjection()); - this.endInteractionInternal(opt_duration, opt_resolutionDirection, anchor); - }; - /** - * Notify the View that an interaction has ended. The view state will be resolved - * to a stable one if needed (depending on its constraints). - * @param {number=} opt_duration Animation duration in ms. - * @param {number=} opt_resolutionDirection Which direction to zoom. - * @param {import("./coordinate.js").Coordinate=} opt_anchor The origin of the transformation. - */ - View.prototype.endInteractionInternal = function (opt_duration, opt_resolutionDirection, opt_anchor) { - this.setHint(ViewHint.INTERACTING, -1); - this.resolveConstraints(opt_duration, opt_resolutionDirection, opt_anchor); - }; - /** - * Get a valid position for the view center according to the current constraints. - * @param {import("./coordinate.js").Coordinate|undefined} targetCenter Target center position. - * @param {number=} opt_targetResolution Target resolution. If not supplied, the current one will be used. - * This is useful to guess a valid center position at a different zoom level. - * @return {import("./coordinate.js").Coordinate|undefined} Valid center position. - */ - View.prototype.getConstrainedCenter = function (targetCenter, opt_targetResolution) { - var size = this.getViewportSize_(this.getRotation()); - return this.constraints_.center(targetCenter, opt_targetResolution || this.getResolution(), size); - }; - /** - * Get a valid zoom level according to the current view constraints. - * @param {number|undefined} targetZoom Target zoom. - * @param {number=} [opt_direction=0] Indicate which resolution should be used - * by a renderer if the view resolution does not match any resolution of the tile source. - * If 0, the nearest resolution will be used. If 1, the nearest lower resolution - * will be used. If -1, the nearest higher resolution will be used. - * @return {number|undefined} Valid zoom level. - */ - View.prototype.getConstrainedZoom = function (targetZoom, opt_direction) { - var targetRes = this.getResolutionForZoom(targetZoom); - return this.getZoomForResolution(this.getConstrainedResolution(targetRes, opt_direction)); - }; - /** - * Get a valid resolution according to the current view constraints. - * @param {number|undefined} targetResolution Target resolution. - * @param {number=} [opt_direction=0] Indicate which resolution should be used - * by a renderer if the view resolution does not match any resolution of the tile source. - * If 0, the nearest resolution will be used. If 1, the nearest lower resolution - * will be used. If -1, the nearest higher resolution will be used. - * @return {number|undefined} Valid resolution. - */ - View.prototype.getConstrainedResolution = function (targetResolution, opt_direction) { - var direction = opt_direction || 0; - var size = this.getViewportSize_(this.getRotation()); - return this.constraints_.resolution(targetResolution, direction, size); - }; - return View; -}(BaseObject)); -/** - * @param {Function} callback Callback. - * @param {*} returnValue Return value. - */ -function animationCallback(callback, returnValue) { - setTimeout(function () { - callback(returnValue); - }, 0); -} -/** - * @param {ViewOptions} options View options. - * @return {import("./centerconstraint.js").Type} The constraint. - */ -function createCenterConstraint(options) { - if (options.extent !== undefined) { - var smooth = options.smoothExtentConstraint !== undefined - ? options.smoothExtentConstraint - : true; - return createExtent(options.extent, options.constrainOnlyCenter, smooth); - } - var projection = createProjection(options.projection, 'EPSG:3857'); - if (options.multiWorld !== true && projection.isGlobal()) { - var extent = projection.getExtent().slice(); - extent[0] = -Infinity; - extent[2] = Infinity; - return createExtent(extent, false, false); - } - return none; -} -/** - * @param {ViewOptions} options View options. - * @return {{constraint: import("./resolutionconstraint.js").Type, maxResolution: number, - * minResolution: number, minZoom: number, zoomFactor: number}} The constraint. - */ -function createResolutionConstraint(options) { - var resolutionConstraint; - var maxResolution; - var minResolution; - // TODO: move these to be ol constants - // see https://github.com/openlayers/openlayers/issues/2076 - var defaultMaxZoom = 28; - var defaultZoomFactor = 2; - var minZoom = options.minZoom !== undefined ? options.minZoom : DEFAULT_MIN_ZOOM; - var maxZoom = options.maxZoom !== undefined ? options.maxZoom : defaultMaxZoom; - var zoomFactor = options.zoomFactor !== undefined ? options.zoomFactor : defaultZoomFactor; - var multiWorld = options.multiWorld !== undefined ? options.multiWorld : false; - var smooth = options.smoothResolutionConstraint !== undefined - ? options.smoothResolutionConstraint - : true; - var showFullExtent = options.showFullExtent !== undefined ? options.showFullExtent : false; - var projection = createProjection(options.projection, 'EPSG:3857'); - var projExtent = projection.getExtent(); - var constrainOnlyCenter = options.constrainOnlyCenter; - var extent = options.extent; - if (!multiWorld && !extent && projection.isGlobal()) { - constrainOnlyCenter = false; - extent = projExtent; - } - if (options.resolutions !== undefined) { - var resolutions = options.resolutions; - maxResolution = resolutions[minZoom]; - minResolution = - resolutions[maxZoom] !== undefined - ? resolutions[maxZoom] - : resolutions[resolutions.length - 1]; - if (options.constrainResolution) { - resolutionConstraint = createSnapToResolutions(resolutions, smooth, !constrainOnlyCenter && extent, showFullExtent); - } - else { - resolutionConstraint = createMinMaxResolution(maxResolution, minResolution, smooth, !constrainOnlyCenter && extent, showFullExtent); - } - } - else { - // calculate the default min and max resolution - var size = !projExtent - ? // use an extent that can fit the whole world if need be - (360 * METERS_PER_UNIT[Units.DEGREES]) / projection.getMetersPerUnit() - : Math.max(getWidth(projExtent), getHeight(projExtent)); - var defaultMaxResolution = size / DEFAULT_TILE_SIZE / Math.pow(defaultZoomFactor, DEFAULT_MIN_ZOOM); - var defaultMinResolution = defaultMaxResolution / - Math.pow(defaultZoomFactor, defaultMaxZoom - DEFAULT_MIN_ZOOM); - // user provided maxResolution takes precedence - maxResolution = options.maxResolution; - if (maxResolution !== undefined) { - minZoom = 0; - } - else { - maxResolution = defaultMaxResolution / Math.pow(zoomFactor, minZoom); - } - // user provided minResolution takes precedence - minResolution = options.minResolution; - if (minResolution === undefined) { - if (options.maxZoom !== undefined) { - if (options.maxResolution !== undefined) { - minResolution = maxResolution / Math.pow(zoomFactor, maxZoom); - } - else { - minResolution = defaultMaxResolution / Math.pow(zoomFactor, maxZoom); - } - } - else { - minResolution = defaultMinResolution; - } - } - // given discrete zoom levels, minResolution may be different than provided - maxZoom = - minZoom + - Math.floor(Math.log(maxResolution / minResolution) / Math.log(zoomFactor)); - minResolution = maxResolution / Math.pow(zoomFactor, maxZoom - minZoom); - if (options.constrainResolution) { - resolutionConstraint = createSnapToPower(zoomFactor, maxResolution, minResolution, smooth, !constrainOnlyCenter && extent, showFullExtent); - } - else { - resolutionConstraint = createMinMaxResolution(maxResolution, minResolution, smooth, !constrainOnlyCenter && extent, showFullExtent); - } - } - return { - constraint: resolutionConstraint, - maxResolution: maxResolution, - minResolution: minResolution, - minZoom: minZoom, - zoomFactor: zoomFactor, - }; -} -/** - * @param {ViewOptions} options View options. - * @return {import("./rotationconstraint.js").Type} Rotation constraint. - */ -function createRotationConstraint(options) { - var enableRotation = options.enableRotation !== undefined ? options.enableRotation : true; - if (enableRotation) { - var constrainRotation = options.constrainRotation; - if (constrainRotation === undefined || constrainRotation === true) { - return createSnapToZero(); - } - else if (constrainRotation === false) { - return none$1; - } - else if (typeof constrainRotation === 'number') { - return createSnapToN(constrainRotation); - } - else { - return none$1; - } - } - else { - return disable; - } -} -/** - * Determine if an animation involves no view change. - * @param {Animation} animation The animation. - * @return {boolean} The animation involves no view change. - */ -function isNoopAnimation(animation) { - if (animation.sourceCenter && animation.targetCenter) { - if (!equals$2(animation.sourceCenter, animation.targetCenter)) { - return false; - } - } - if (animation.sourceResolution !== animation.targetResolution) { - return false; - } - if (animation.sourceRotation !== animation.targetRotation) { - return false; - } - return true; -} -/** - * @param {import("./coordinate.js").Coordinate} coordinate Coordinate. - * @param {import("./size.js").Size} size Box pixel size. - * @param {import("./pixel.js").Pixel} position Position on the view to center on. - * @param {number} resolution Resolution. - * @param {number} rotation Rotation. - * @return {import("./coordinate.js").Coordinate} Shifted center. - */ -function calculateCenterOn(coordinate, size, position, resolution, rotation) { - // calculate rotated position - var cosAngle = Math.cos(-rotation); - var sinAngle = Math.sin(-rotation); - var rotX = coordinate[0] * cosAngle - coordinate[1] * sinAngle; - var rotY = coordinate[1] * cosAngle + coordinate[0] * sinAngle; - rotX += (size[0] / 2 - position[0]) * resolution; - rotY += (position[1] - size[1] / 2) * resolution; - // go back to original angle - sinAngle = -sinAngle; // go back to original rotation - var centerX = rotX * cosAngle - rotY * sinAngle; - var centerY = rotY * cosAngle + rotX * sinAngle; - return [centerX, centerY]; -} - -/** - * @module ol/color - */ -/** - * Return the color as an rgba string. - * @param {Color|string} color Color. - * @return {string} Rgba string. - * @api - */ -function asString(color) { - if (typeof color === 'string') { - return color; - } - else { - return toString$1(color); - } -} -/** - * @param {Color} color Color. - * @return {string} String. - */ -function toString$1(color) { - var r = color[0]; - if (r != (r | 0)) { - r = (r + 0.5) | 0; - } - var g = color[1]; - if (g != (g | 0)) { - g = (g + 0.5) | 0; - } - var b = color[2]; - if (b != (b | 0)) { - b = (b + 0.5) | 0; - } - var a = color[3] === undefined ? 1 : color[3]; - return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; -} - -/** - * @module ol/style/IconImageCache - */ -/** - * @classdesc - * Singleton class. Available through {@link module:ol/style/IconImageCache~shared}. - */ -var IconImageCache = /** @class */ (function () { - function IconImageCache() { - /** - * @type {!Object} - * @private - */ - this.cache_ = {}; - /** - * @type {number} - * @private - */ - this.cacheSize_ = 0; - /** - * @type {number} - * @private - */ - this.maxCacheSize_ = 32; - } - /** - * FIXME empty description for jsdoc - */ - IconImageCache.prototype.clear = function () { - this.cache_ = {}; - this.cacheSize_ = 0; - }; - /** - * @return {boolean} Can expire cache. - */ - IconImageCache.prototype.canExpireCache = function () { - return this.cacheSize_ > this.maxCacheSize_; - }; - /** - * FIXME empty description for jsdoc - */ - IconImageCache.prototype.expire = function () { - if (this.canExpireCache()) { - var i = 0; - for (var key in this.cache_) { - var iconImage = this.cache_[key]; - if ((i++ & 3) === 0 && !iconImage.hasListener()) { - delete this.cache_[key]; - --this.cacheSize_; - } - } - } - }; - /** - * @param {string} src Src. - * @param {?string} crossOrigin Cross origin. - * @param {import("../color.js").Color} color Color. - * @return {import("./IconImage.js").default} Icon image. - */ - IconImageCache.prototype.get = function (src, crossOrigin, color) { - var key = getKey(src, crossOrigin, color); - return key in this.cache_ ? this.cache_[key] : null; - }; - /** - * @param {string} src Src. - * @param {?string} crossOrigin Cross origin. - * @param {import("../color.js").Color} color Color. - * @param {import("./IconImage.js").default} iconImage Icon image. - */ - IconImageCache.prototype.set = function (src, crossOrigin, color, iconImage) { - var key = getKey(src, crossOrigin, color); - this.cache_[key] = iconImage; - ++this.cacheSize_; - }; - /** - * Set the cache size of the icon cache. Default is `32`. Change this value when - * your map uses more than 32 different icon images and you are not caching icon - * styles on the application level. - * @param {number} maxCacheSize Cache max size. - * @api - */ - IconImageCache.prototype.setSize = function (maxCacheSize) { - this.maxCacheSize_ = maxCacheSize; - this.expire(); - }; - return IconImageCache; -}()); -/** - * @param {string} src Src. - * @param {?string} crossOrigin Cross origin. - * @param {import("../color.js").Color} color Color. - * @return {string} Cache key. - */ -function getKey(src, crossOrigin, color) { - var colorString = color ? asString(color) : 'null'; - return crossOrigin + ':' + src + ':' + colorString; -} -/** - * The {@link module:ol/style/IconImageCache~IconImageCache} for - * {@link module:ol/style/Icon~Icon} images. - * @api - */ -var shared = new IconImageCache(); - -/** - * @module ol/layer/Property - */ -/** - * @enum {string} - */ -var LayerProperty = { - OPACITY: 'opacity', - VISIBLE: 'visible', - EXTENT: 'extent', - Z_INDEX: 'zIndex', - MAX_RESOLUTION: 'maxResolution', - MIN_RESOLUTION: 'minResolution', - MAX_ZOOM: 'maxZoom', - MIN_ZOOM: 'minZoom', - SOURCE: 'source', -}; - -var __extends$c = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -/** - * @typedef {Object} Options - * @property {string} [className='ol-layer'] A CSS class name to set to the layer element. - * @property {number} [opacity=1] Opacity (0, 1). - * @property {boolean} [visible=true] Visibility. - * @property {import("../extent.js").Extent} [extent] The bounding extent for layer rendering. The layer will not be - * rendered outside of this extent. - * @property {number} [zIndex] The z-index for layer rendering. At rendering time, the layers - * will be ordered, first by Z-index and then by position. When `undefined`, a `zIndex` of 0 is assumed - * for layers that are added to the map's `layers` collection, or `Infinity` when the layer's `setMap()` - * method was used. - * @property {number} [minResolution] The minimum resolution (inclusive) at which this layer will be - * visible. - * @property {number} [maxResolution] The maximum resolution (exclusive) below which this layer will - * be visible. - * @property {number} [minZoom] The minimum view zoom level (exclusive) above which this layer will be - * visible. - * @property {number} [maxZoom] The maximum view zoom level (inclusive) at which this layer will - * be visible. - */ -/** - * @classdesc - * Abstract base class; normally only used for creating subclasses and not - * instantiated in apps. - * Note that with {@link module:ol/layer/Base} and all its subclasses, any property set in - * the options is set as a {@link module:ol/Object} property on the layer object, so - * is observable, and has get/set accessors. - * - * @api - */ -var BaseLayer = /** @class */ (function (_super) { - __extends$c(BaseLayer, _super); - /** - * @param {Options} options Layer options. - */ - function BaseLayer(options) { - var _this = _super.call(this) || this; - /** - * @type {Object} - */ - var properties = assign({}, options); - properties[LayerProperty.OPACITY] = - options.opacity !== undefined ? options.opacity : 1; - assert(typeof properties[LayerProperty.OPACITY] === 'number', 64); // Layer opacity must be a number - properties[LayerProperty.VISIBLE] = - options.visible !== undefined ? options.visible : true; - properties[LayerProperty.Z_INDEX] = options.zIndex; - properties[LayerProperty.MAX_RESOLUTION] = - options.maxResolution !== undefined ? options.maxResolution : Infinity; - properties[LayerProperty.MIN_RESOLUTION] = - options.minResolution !== undefined ? options.minResolution : 0; - properties[LayerProperty.MIN_ZOOM] = - options.minZoom !== undefined ? options.minZoom : -Infinity; - properties[LayerProperty.MAX_ZOOM] = - options.maxZoom !== undefined ? options.maxZoom : Infinity; - /** - * @type {string} - * @private - */ - _this.className_ = - properties.className !== undefined ? options.className : 'ol-layer'; - delete properties.className; - _this.setProperties(properties); - /** - * @type {import("./Layer.js").State} - * @private - */ - _this.state_ = null; - return _this; - } - /** - * @return {string} CSS class name. - */ - BaseLayer.prototype.getClassName = function () { - return this.className_; - }; - /** - * This method is not meant to be called by layers or layer renderers because the state - * is incorrect if the layer is included in a layer group. - * - * @param {boolean=} opt_managed Layer is managed. - * @return {import("./Layer.js").State} Layer state. - */ - BaseLayer.prototype.getLayerState = function (opt_managed) { - /** @type {import("./Layer.js").State} */ - var state = this.state_ || - /** @type {?} */ ({ - layer: this, - managed: opt_managed === undefined ? true : opt_managed, - }); - var zIndex = this.getZIndex(); - state.opacity = clamp(Math.round(this.getOpacity() * 100) / 100, 0, 1); - state.sourceState = this.getSourceState(); - state.visible = this.getVisible(); - state.extent = this.getExtent(); - state.zIndex = - zIndex !== undefined ? zIndex : state.managed === false ? Infinity : 0; - state.maxResolution = this.getMaxResolution(); - state.minResolution = Math.max(this.getMinResolution(), 0); - state.minZoom = this.getMinZoom(); - state.maxZoom = this.getMaxZoom(); - this.state_ = state; - return state; - }; - /** - * @abstract - * @param {Array=} opt_array Array of layers (to be - * modified in place). - * @return {Array} Array of layers. - */ - BaseLayer.prototype.getLayersArray = function (opt_array) { - return abstract(); - }; - /** - * @abstract - * @param {Array=} opt_states Optional list of layer - * states (to be modified in place). - * @return {Array} List of layer states. - */ - BaseLayer.prototype.getLayerStatesArray = function (opt_states) { - return abstract(); - }; - /** - * Return the {@link module:ol/extent~Extent extent} of the layer or `undefined` if it - * will be visible regardless of extent. - * @return {import("../extent.js").Extent|undefined} The layer extent. - * @observable - * @api - */ - BaseLayer.prototype.getExtent = function () { - return /** @type {import("../extent.js").Extent|undefined} */ (this.get(LayerProperty.EXTENT)); - }; - /** - * Return the maximum resolution of the layer. - * @return {number} The maximum resolution of the layer. - * @observable - * @api - */ - BaseLayer.prototype.getMaxResolution = function () { - return /** @type {number} */ (this.get(LayerProperty.MAX_RESOLUTION)); - }; - /** - * Return the minimum resolution of the layer. - * @return {number} The minimum resolution of the layer. - * @observable - * @api - */ - BaseLayer.prototype.getMinResolution = function () { - return /** @type {number} */ (this.get(LayerProperty.MIN_RESOLUTION)); - }; - /** - * Return the minimum zoom level of the layer. - * @return {number} The minimum zoom level of the layer. - * @observable - * @api - */ - BaseLayer.prototype.getMinZoom = function () { - return /** @type {number} */ (this.get(LayerProperty.MIN_ZOOM)); - }; - /** - * Return the maximum zoom level of the layer. - * @return {number} The maximum zoom level of the layer. - * @observable - * @api - */ - BaseLayer.prototype.getMaxZoom = function () { - return /** @type {number} */ (this.get(LayerProperty.MAX_ZOOM)); - }; - /** - * Return the opacity of the layer (between 0 and 1). - * @return {number} The opacity of the layer. - * @observable - * @api - */ - BaseLayer.prototype.getOpacity = function () { - return /** @type {number} */ (this.get(LayerProperty.OPACITY)); - }; - /** - * @abstract - * @return {import("../source/State.js").default} Source state. - */ - BaseLayer.prototype.getSourceState = function () { - return abstract(); - }; - /** - * Return the visibility of the layer (`true` or `false`). - * @return {boolean} The visibility of the layer. - * @observable - * @api - */ - BaseLayer.prototype.getVisible = function () { - return /** @type {boolean} */ (this.get(LayerProperty.VISIBLE)); - }; - /** - * Return the Z-index of the layer, which is used to order layers before - * rendering. The default Z-index is 0. - * @return {number} The Z-index of the layer. - * @observable - * @api - */ - BaseLayer.prototype.getZIndex = function () { - return /** @type {number} */ (this.get(LayerProperty.Z_INDEX)); - }; - /** - * Set the extent at which the layer is visible. If `undefined`, the layer - * will be visible at all extents. - * @param {import("../extent.js").Extent|undefined} extent The extent of the layer. - * @observable - * @api - */ - BaseLayer.prototype.setExtent = function (extent) { - this.set(LayerProperty.EXTENT, extent); - }; - /** - * Set the maximum resolution at which the layer is visible. - * @param {number} maxResolution The maximum resolution of the layer. - * @observable - * @api - */ - BaseLayer.prototype.setMaxResolution = function (maxResolution) { - this.set(LayerProperty.MAX_RESOLUTION, maxResolution); - }; - /** - * Set the minimum resolution at which the layer is visible. - * @param {number} minResolution The minimum resolution of the layer. - * @observable - * @api - */ - BaseLayer.prototype.setMinResolution = function (minResolution) { - this.set(LayerProperty.MIN_RESOLUTION, minResolution); - }; - /** - * Set the maximum zoom (exclusive) at which the layer is visible. - * Note that the zoom levels for layer visibility are based on the - * view zoom level, which may be different from a tile source zoom level. - * @param {number} maxZoom The maximum zoom of the layer. - * @observable - * @api - */ - BaseLayer.prototype.setMaxZoom = function (maxZoom) { - this.set(LayerProperty.MAX_ZOOM, maxZoom); - }; - /** - * Set the minimum zoom (inclusive) at which the layer is visible. - * Note that the zoom levels for layer visibility are based on the - * view zoom level, which may be different from a tile source zoom level. - * @param {number} minZoom The minimum zoom of the layer. - * @observable - * @api - */ - BaseLayer.prototype.setMinZoom = function (minZoom) { - this.set(LayerProperty.MIN_ZOOM, minZoom); - }; - /** - * Set the opacity of the layer, allowed values range from 0 to 1. - * @param {number} opacity The opacity of the layer. - * @observable - * @api - */ - BaseLayer.prototype.setOpacity = function (opacity) { - assert(typeof opacity === 'number', 64); // Layer opacity must be a number - this.set(LayerProperty.OPACITY, opacity); - }; - /** - * Set the visibility of the layer (`true` or `false`). - * @param {boolean} visible The visibility of the layer. - * @observable - * @api - */ - BaseLayer.prototype.setVisible = function (visible) { - this.set(LayerProperty.VISIBLE, visible); - }; - /** - * Set Z-index of the layer, which is used to order layers before rendering. - * The default Z-index is 0. - * @param {number} zindex The z-index of the layer. - * @observable - * @api - */ - BaseLayer.prototype.setZIndex = function (zindex) { - this.set(LayerProperty.Z_INDEX, zindex); - }; - /** - * Clean up. - */ - BaseLayer.prototype.disposeInternal = function () { - if (this.state_) { - this.state_.layer = null; - this.state_ = null; - } - _super.prototype.disposeInternal.call(this); - }; - return BaseLayer; -}(BaseObject)); - -/** - * @module ol/render/EventType - */ -/** - * @enum {string} - */ -var RenderEventType = { - /** - * Triggered before a layer is rendered. - * @event module:ol/render/Event~RenderEvent#prerender - * @api - */ - PRERENDER: 'prerender', - /** - * Triggered after a layer is rendered. - * @event module:ol/render/Event~RenderEvent#postrender - * @api - */ - POSTRENDER: 'postrender', - /** - * Triggered before layers are rendered. - * The event object will not have a `context` set. - * @event module:ol/render/Event~RenderEvent#precompose - * @api - */ - PRECOMPOSE: 'precompose', - /** - * Triggered after all layers are rendered. - * The event object will not have a `context` set. - * @event module:ol/render/Event~RenderEvent#postcompose - * @api - */ - POSTCOMPOSE: 'postcompose', - /** - * Triggered when rendering is complete, i.e. all sources and tiles have - * finished loading for the current viewport, and all tiles are faded in. - * The event object will not have a `context` set. - * @event module:ol/render/Event~RenderEvent#rendercomplete - * @api - */ - RENDERCOMPLETE: 'rendercomplete', -}; - -/** - * @module ol/source/State - */ -/** - * @enum {string} - * State of the source, one of 'undefined', 'loading', 'ready' or 'error'. - */ -var SourceState = { - UNDEFINED: 'undefined', - LOADING: 'loading', - READY: 'ready', - ERROR: 'error', -}; - -var __extends$d = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -/** - * @typedef {function(import("../PluggableMap.js").FrameState):HTMLElement} RenderFunction - */ -/** - * @typedef {Object} Options - * @property {string} [className='ol-layer'] A CSS class name to set to the layer element. - * @property {number} [opacity=1] Opacity (0, 1). - * @property {boolean} [visible=true] Visibility. - * @property {import("../extent.js").Extent} [extent] The bounding extent for layer rendering. The layer will not be - * rendered outside of this extent. - * @property {number} [zIndex] The z-index for layer rendering. At rendering time, the layers - * will be ordered, first by Z-index and then by position. When `undefined`, a `zIndex` of 0 is assumed - * for layers that are added to the map's `layers` collection, or `Infinity` when the layer's `setMap()` - * method was used. - * @property {number} [minResolution] The minimum resolution (inclusive) at which this layer will be - * visible. - * @property {number} [maxResolution] The maximum resolution (exclusive) below which this layer will - * be visible. - * @property {number} [minZoom] The minimum view zoom level (exclusive) above which this layer will be - * visible. - * @property {number} [maxZoom] The maximum view zoom level (inclusive) at which this layer will - * be visible. - * @property {import("../source/Source.js").default} [source] Source for this layer. If not provided to the constructor, - * the source can be set by calling {@link module:ol/layer/Layer#setSource layer.setSource(source)} after - * construction. - * @property {import("../PluggableMap.js").default} [map] Map. - * @property {RenderFunction} [render] Render function. Takes the frame state as input and is expected to return an - * HTML element. Will overwrite the default rendering for the layer. - */ -/** - * @typedef {Object} State - * @property {import("./Layer.js").default} layer - * @property {number} opacity Opacity, the value is rounded to two digits to appear after the decimal point. - * @property {import("../source/State.js").default} sourceState - * @property {boolean} visible - * @property {boolean} managed - * @property {import("../extent.js").Extent} [extent] - * @property {number} zIndex - * @property {number} maxResolution - * @property {number} minResolution - * @property {number} minZoom - * @property {number} maxZoom - */ -/** - * @classdesc - * Base class from which all layer types are derived. This should only be instantiated - * in the case where a custom layer is be added to the map with a custom `render` function. - * Such a function can be specified in the `options` object, and is expected to return an HTML element. - * - * A visual representation of raster or vector map data. - * Layers group together those properties that pertain to how the data is to be - * displayed, irrespective of the source of that data. - * - * Layers are usually added to a map with {@link module:ol/Map#addLayer}. Components - * like {@link module:ol/interaction/Select~Select} use unmanaged layers - * internally. These unmanaged layers are associated with the map using - * {@link module:ol/layer/Layer~Layer#setMap} instead. - * - * A generic `change` event is fired when the state of the source changes. - * - * Please note that for performance reasons several layers might get rendered to - * the same HTML element, which will cause {@link module:ol/Map~Map#forEachLayerAtPixel} to - * give false positives. To avoid this, apply different `className` properties to the - * layers at creation time. - * - * @fires import("../render/Event.js").RenderEvent#prerender - * @fires import("../render/Event.js").RenderEvent#postrender - * - * @template {import("../source/Source.js").default} SourceType - * @api - */ -var Layer = /** @class */ (function (_super) { - __extends$d(Layer, _super); - /** - * @param {Options} options Layer options. - */ - function Layer(options) { - var _this = this; - var baseOptions = assign({}, options); - delete baseOptions.source; - _this = _super.call(this, baseOptions) || this; - /** - * @private - * @type {?import("../events.js").EventsKey} - */ - _this.mapPrecomposeKey_ = null; - /** - * @private - * @type {?import("../events.js").EventsKey} - */ - _this.mapRenderKey_ = null; - /** - * @private - * @type {?import("../events.js").EventsKey} - */ - _this.sourceChangeKey_ = null; - /** - * @private - * @type {import("../renderer/Layer.js").default} - */ - _this.renderer_ = null; - // Overwrite default render method with a custom one - if (options.render) { - _this.render = options.render; - } - if (options.map) { - _this.setMap(options.map); - } - _this.addEventListener(getChangeEventType(LayerProperty.SOURCE), _this.handleSourcePropertyChange_); - var source = options.source - ? /** @type {SourceType} */ (options.source) - : null; - _this.setSource(source); - return _this; - } - /** - * @param {Array=} opt_array Array of layers (to be modified in place). - * @return {Array} Array of layers. - */ - Layer.prototype.getLayersArray = function (opt_array) { - var array = opt_array ? opt_array : []; - array.push(this); - return array; - }; - /** - * @param {Array=} opt_states Optional list of layer states (to be modified in place). - * @return {Array} List of layer states. - */ - Layer.prototype.getLayerStatesArray = function (opt_states) { - var states = opt_states ? opt_states : []; - states.push(this.getLayerState()); - return states; - }; - /** - * Get the layer source. - * @return {SourceType} The layer source (or `null` if not yet set). - * @observable - * @api - */ - Layer.prototype.getSource = function () { - return /** @type {SourceType} */ (this.get(LayerProperty.SOURCE)) || null; - }; - /** - * @return {import("../source/State.js").default} Source state. - */ - Layer.prototype.getSourceState = function () { - var source = this.getSource(); - return !source ? SourceState.UNDEFINED : source.getState(); - }; - /** - * @private - */ - Layer.prototype.handleSourceChange_ = function () { - this.changed(); - }; - /** - * @private - */ - Layer.prototype.handleSourcePropertyChange_ = function () { - if (this.sourceChangeKey_) { - unlistenByKey(this.sourceChangeKey_); - this.sourceChangeKey_ = null; - } - var source = this.getSource(); - if (source) { - this.sourceChangeKey_ = listen(source, EventType.CHANGE, this.handleSourceChange_, this); - } - this.changed(); - }; - /** - * @param {import("../pixel").Pixel} pixel Pixel. - * @return {Promise>} Promise that resolves with - * an array of features. - */ - Layer.prototype.getFeatures = function (pixel) { - return this.renderer_.getFeatures(pixel); - }; - /** - * In charge to manage the rendering of the layer. One layer type is - * bounded with one layer renderer. - * @param {?import("../PluggableMap.js").FrameState} frameState Frame state. - * @param {HTMLElement} target Target which the renderer may (but need not) use - * for rendering its content. - * @return {HTMLElement} The rendered element. - */ - Layer.prototype.render = function (frameState, target) { - var layerRenderer = this.getRenderer(); - if (layerRenderer.prepareFrame(frameState)) { - return layerRenderer.renderFrame(frameState, target); - } - }; - /** - * Sets the layer to be rendered on top of other layers on a map. The map will - * not manage this layer in its layers collection, and the callback in - * {@link module:ol/Map#forEachLayerAtPixel} will receive `null` as layer. This - * is useful for temporary layers. To remove an unmanaged layer from the map, - * use `#setMap(null)`. - * - * To add the layer to a map and have it managed by the map, use - * {@link module:ol/Map#addLayer} instead. - * @param {import("../PluggableMap.js").default} map Map. - * @api - */ - Layer.prototype.setMap = function (map) { - if (this.mapPrecomposeKey_) { - unlistenByKey(this.mapPrecomposeKey_); - this.mapPrecomposeKey_ = null; - } - if (!map) { - this.changed(); - } - if (this.mapRenderKey_) { - unlistenByKey(this.mapRenderKey_); - this.mapRenderKey_ = null; - } - if (map) { - this.mapPrecomposeKey_ = listen(map, RenderEventType.PRECOMPOSE, function (evt) { - var renderEvent = /** @type {import("../render/Event.js").default} */ (evt); - var layerStatesArray = renderEvent.frameState.layerStatesArray; - var layerState = this.getLayerState(false); - // A layer can only be added to the map once. Use either `layer.setMap()` or `map.addLayer()`, not both. - assert(!layerStatesArray.some(function (arrayLayerState) { - return arrayLayerState.layer === layerState.layer; - }), 67); - layerStatesArray.push(layerState); - }, this); - this.mapRenderKey_ = listen(this, EventType.CHANGE, map.render, map); - this.changed(); - } - }; - /** - * Set the layer source. - * @param {SourceType} source The layer source. - * @observable - * @api - */ - Layer.prototype.setSource = function (source) { - this.set(LayerProperty.SOURCE, source); - }; - /** - * Get the renderer for this layer. - * @return {import("../renderer/Layer.js").default} The layer renderer. - */ - Layer.prototype.getRenderer = function () { - if (!this.renderer_) { - this.renderer_ = this.createRenderer(); - } - return this.renderer_; - }; - /** - * @return {boolean} The layer has a renderer. - */ - Layer.prototype.hasRenderer = function () { - return !!this.renderer_; - }; - /** - * Create a renderer for this layer. - * @return {import("../renderer/Layer.js").default} A layer renderer. - * @protected - */ - Layer.prototype.createRenderer = function () { - return null; - }; - /** - * Clean up. - */ - Layer.prototype.disposeInternal = function () { - this.setSource(null); - _super.prototype.disposeInternal.call(this); - }; - return Layer; -}(BaseLayer)); -/** - * Return `true` if the layer is visible and if the provided view state - * has resolution and zoom levels that are in range of the layer's min/max. - * @param {State} layerState Layer state. - * @param {import("../View.js").State} viewState View state. - * @return {boolean} The layer is visible at the given view state. - */ -function inView(layerState, viewState) { - if (!layerState.visible) { - return false; - } - var resolution = viewState.resolution; - if (resolution < layerState.minResolution || - resolution >= layerState.maxResolution) { - return false; - } - var zoom = viewState.zoom; - return zoom > layerState.minZoom && zoom <= layerState.maxZoom; -} - -var __extends$e = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -/** - * @typedef HitMatch - * @property {import("../Feature.js").FeatureLike} feature - * @property {import("../layer/Layer.js").default} layer - * @property {import("../geom/SimpleGeometry.js").default} geometry - * @property {number} distanceSq - * @property {import("./vector.js").FeatureCallback} callback - * @template T - */ -/** - * @abstract - */ -var MapRenderer = /** @class */ (function (_super) { - __extends$e(MapRenderer, _super); - /** - * @param {import("../PluggableMap.js").default} map Map. - */ - function MapRenderer(map) { - var _this = _super.call(this) || this; - /** - * @private - * @type {import("../PluggableMap.js").default} - */ - _this.map_ = map; - return _this; - } - /** - * @abstract - * @param {import("../render/EventType.js").default} type Event type. - * @param {import("../PluggableMap.js").FrameState} frameState Frame state. - */ - MapRenderer.prototype.dispatchRenderEvent = function (type, frameState) { - abstract(); - }; - /** - * @param {import("../PluggableMap.js").FrameState} frameState FrameState. - * @protected - */ - MapRenderer.prototype.calculateMatrices2D = function (frameState) { - var viewState = frameState.viewState; - var coordinateToPixelTransform = frameState.coordinateToPixelTransform; - var pixelToCoordinateTransform = frameState.pixelToCoordinateTransform; - compose(coordinateToPixelTransform, frameState.size[0] / 2, frameState.size[1] / 2, 1 / viewState.resolution, -1 / viewState.resolution, -viewState.rotation, -viewState.center[0], -viewState.center[1]); - makeInverse(pixelToCoordinateTransform, coordinateToPixelTransform); - }; - /** - * @param {import("../coordinate.js").Coordinate} coordinate Coordinate. - * @param {import("../PluggableMap.js").FrameState} frameState FrameState. - * @param {number} hitTolerance Hit tolerance in pixels. - * @param {boolean} checkWrapped Check for wrapped geometries. - * @param {import("./vector.js").FeatureCallback} callback Feature callback. - * @param {S} thisArg Value to use as `this` when executing `callback`. - * @param {function(this: U, import("../layer/Layer.js").default): boolean} layerFilter Layer filter - * function, only layers which are visible and for which this function - * returns `true` will be tested for features. By default, all visible - * layers will be tested. - * @param {U} thisArg2 Value to use as `this` when executing `layerFilter`. - * @return {T|undefined} Callback result. - * @template S,T,U - */ - MapRenderer.prototype.forEachFeatureAtCoordinate = function (coordinate, frameState, hitTolerance, checkWrapped, callback, thisArg, layerFilter, thisArg2) { - var result; - var viewState = frameState.viewState; - /** - * @param {boolean} managed Managed layer. - * @param {import("../Feature.js").FeatureLike} feature Feature. - * @param {import("../layer/Layer.js").default} layer Layer. - * @param {import("../geom/Geometry.js").default} geometry Geometry. - * @return {T|undefined} Callback result. - */ - function forEachFeatureAtCoordinate(managed, feature, layer, geometry) { - return callback.call(thisArg, feature, managed ? layer : null, geometry); - } - var projection = viewState.projection; - var translatedCoordinate = wrapX(coordinate.slice(), projection); - var offsets = [[0, 0]]; - if (projection.canWrapX() && checkWrapped) { - var projectionExtent = projection.getExtent(); - var worldWidth = getWidth(projectionExtent); - offsets.push([-worldWidth, 0], [worldWidth, 0]); - } - var layerStates = frameState.layerStatesArray; - var numLayers = layerStates.length; - var matches = /** @type {Array>} */ ([]); - var tmpCoord = []; - for (var i = 0; i < offsets.length; i++) { - for (var j = numLayers - 1; j >= 0; --j) { - var layerState = layerStates[j]; - var layer = layerState.layer; - if (layer.hasRenderer() && - inView(layerState, viewState) && - layerFilter.call(thisArg2, layer)) { - var layerRenderer = layer.getRenderer(); - var source = layer.getSource(); - if (layerRenderer && source) { - var coordinates = source.getWrapX() - ? translatedCoordinate - : coordinate; - var callback_1 = forEachFeatureAtCoordinate.bind(null, layerState.managed); - tmpCoord[0] = coordinates[0] + offsets[i][0]; - tmpCoord[1] = coordinates[1] + offsets[i][1]; - result = layerRenderer.forEachFeatureAtCoordinate(tmpCoord, frameState, hitTolerance, callback_1, matches); - } - if (result) { - return result; - } - } - } - } - if (matches.length === 0) { - return undefined; - } - var order = 1 / matches.length; - matches.forEach(function (m, i) { return (m.distanceSq += i * order); }); - matches.sort(function (a, b) { return a.distanceSq - b.distanceSq; }); - matches.some(function (m) { - return (result = m.callback(m.feature, m.layer, m.geometry)); - }); - return result; - }; - /** - * @abstract - * @param {import("../pixel.js").Pixel} pixel Pixel. - * @param {import("../PluggableMap.js").FrameState} frameState FrameState. - * @param {number} hitTolerance Hit tolerance in pixels. - * @param {function(import("../layer/Layer.js").default, (Uint8ClampedArray|Uint8Array)): T} callback Layer - * callback. - * @param {function(import("../layer/Layer.js").default): boolean} layerFilter Layer filter - * function, only layers which are visible and for which this function - * returns `true` will be tested for features. By default, all visible - * layers will be tested. - * @return {T|undefined} Callback result. - * @template T - */ - MapRenderer.prototype.forEachLayerAtPixel = function (pixel, frameState, hitTolerance, callback, layerFilter) { - return abstract(); - }; - /** - * @param {import("../coordinate.js").Coordinate} coordinate Coordinate. - * @param {import("../PluggableMap.js").FrameState} frameState FrameState. - * @param {number} hitTolerance Hit tolerance in pixels. - * @param {boolean} checkWrapped Check for wrapped geometries. - * @param {function(this: U, import("../layer/Layer.js").default): boolean} layerFilter Layer filter - * function, only layers which are visible and for which this function - * returns `true` will be tested for features. By default, all visible - * layers will be tested. - * @param {U} thisArg Value to use as `this` when executing `layerFilter`. - * @return {boolean} Is there a feature at the given coordinate? - * @template U - */ - MapRenderer.prototype.hasFeatureAtCoordinate = function (coordinate, frameState, hitTolerance, checkWrapped, layerFilter, thisArg) { - var hasFeature = this.forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, checkWrapped, TRUE, this, layerFilter, thisArg); - return hasFeature !== undefined; - }; - /** - * @return {import("../PluggableMap.js").default} Map. - */ - MapRenderer.prototype.getMap = function () { - return this.map_; - }; - /** - * Render. - * @abstract - * @param {?import("../PluggableMap.js").FrameState} frameState Frame state. - */ - MapRenderer.prototype.renderFrame = function (frameState) { - abstract(); - }; - /** - * @param {import("../PluggableMap.js").FrameState} frameState Frame state. - * @protected - */ - MapRenderer.prototype.scheduleExpireIconCache = function (frameState) { - if (shared.canExpireCache()) { - frameState.postRenderFunctions.push(expireIconCache); - } - }; - return MapRenderer; -}(Disposable)); -/** - * @param {import("../PluggableMap.js").default} map Map. - * @param {import("../PluggableMap.js").FrameState} frameState Frame state. - */ -function expireIconCache(map, frameState) { - shared.expire(); -} - -/** - * @module ol/render/Event - */ -var __extends$f = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -var RenderEvent = /** @class */ (function (_super) { - __extends$f(RenderEvent, _super); - /** - * @param {import("./EventType.js").default} type Type. - * @param {import("../transform.js").Transform=} opt_inversePixelTransform Transform for - * CSS pixels to rendered pixels. - * @param {import("../PluggableMap.js").FrameState=} opt_frameState Frame state. - * @param {?CanvasRenderingContext2D=} opt_context Context. - */ - function RenderEvent(type, opt_inversePixelTransform, opt_frameState, opt_context) { - var _this = _super.call(this, type) || this; - /** - * Transform from CSS pixels (relative to the top-left corner of the map viewport) - * to rendered pixels on this event's `context`. Only available when a Canvas renderer is used, null otherwise. - * @type {import("../transform.js").Transform|undefined} - * @api - */ - _this.inversePixelTransform = opt_inversePixelTransform; - /** - * An object representing the current render frame state. - * @type {import("../PluggableMap.js").FrameState|undefined} - * @api - */ - _this.frameState = opt_frameState; - /** - * Canvas context. Not available when the event is dispatched by the map. Only available - * when a Canvas renderer is used, null otherwise. - * @type {CanvasRenderingContext2D|null|undefined} - * @api - */ - _this.context = opt_context; - return _this; - } - return RenderEvent; -}(BaseEvent)); - -/** - * @module ol/css - */ -/** - * @typedef {Object} FontParameters - * @property {string} style - * @property {string} variant - * @property {string} weight - * @property {string} size - * @property {string} lineHeight - * @property {string} family - * @property {Array} families - */ -/** - * The CSS class for hidden feature. - * - * @const - * @type {string} - */ -var CLASS_HIDDEN = 'ol-hidden'; -/** - * The CSS class that we'll give the DOM elements to have them selectable. - * - * @const - * @type {string} - */ -var CLASS_SELECTABLE = 'ol-selectable'; -/** - * The CSS class that we'll give the DOM elements to have them unselectable. - * - * @const - * @type {string} - */ -var CLASS_UNSELECTABLE = 'ol-unselectable'; -/** - * The CSS class for unsupported feature. - * - * @const - * @type {string} - */ -var CLASS_UNSUPPORTED = 'ol-unsupported'; -/** - * The CSS class for controls. - * - * @const - * @type {string} - */ -var CLASS_CONTROL = 'ol-control'; -/** - * The CSS class that we'll give the DOM elements that are collapsed, i.e. - * to those elements which usually can be expanded. - * - * @const - * @type {string} - */ -var CLASS_COLLAPSED = 'ol-collapsed'; - -/** - * @module ol/has - */ -var ua = typeof navigator !== 'undefined' && typeof navigator.userAgent !== 'undefined' - ? navigator.userAgent.toLowerCase() - : ''; -/** - * User agent string says we are dealing with Firefox as browser. - * @type {boolean} - */ -var FIREFOX = ua.indexOf('firefox') !== -1; -/** - * User agent string says we are dealing with Safari as browser. - * @type {boolean} - */ -var SAFARI = ua.indexOf('safari') !== -1 && ua.indexOf('chrom') == -1; -/** - * User agent string says we are dealing with a WebKit engine. - * @type {boolean} - */ -var WEBKIT = ua.indexOf('webkit') !== -1 && ua.indexOf('edge') == -1; -/** - * User agent string says we are dealing with a Mac as platform. - * @type {boolean} - */ -var MAC = ua.indexOf('macintosh') !== -1; -/** - * The ratio between physical pixels and device-independent pixels - * (dips) on the device (`window.devicePixelRatio`). - * @const - * @type {number} - * @api - */ -var DEVICE_PIXEL_RATIO = typeof devicePixelRatio !== 'undefined' ? devicePixelRatio : 1; -/** - * The execution context is a worker with OffscreenCanvas available. - * @const - * @type {boolean} - */ -var WORKER_OFFSCREEN_CANVAS = typeof WorkerGlobalScope !== 'undefined' && - typeof OffscreenCanvas !== 'undefined' && - self instanceof WorkerGlobalScope; //eslint-disable-line -/** - * Image.prototype.decode() is supported. - * @type {boolean} - */ -var IMAGE_DECODE = typeof Image !== 'undefined' && Image.prototype.decode; -/** - * @type {boolean} - */ -var PASSIVE_EVENT_LISTENERS = (function () { - var passive = false; - try { - var options = Object.defineProperty({}, 'passive', { - get: function () { - passive = true; - }, - }); - window.addEventListener('_', null, options); - window.removeEventListener('_', null, options); - } - catch (error) { - // passive not supported - } - return passive; -})(); - -/** - * @module ol/dom - */ -//FIXME Move this function to the canvas module -/** - * Create an html canvas element and returns its 2d context. - * @param {number=} opt_width Canvas width. - * @param {number=} opt_height Canvas height. - * @param {Array=} opt_canvasPool Canvas pool to take existing canvas from. - * @return {CanvasRenderingContext2D} The context. - */ -function createCanvasContext2D(opt_width, opt_height, opt_canvasPool) { - var canvas = opt_canvasPool && opt_canvasPool.length - ? opt_canvasPool.shift() - : WORKER_OFFSCREEN_CANVAS - ? new OffscreenCanvas(opt_width || 300, opt_height || 300) - : document.createElement('canvas'); - if (opt_width) { - canvas.width = opt_width; - } - if (opt_height) { - canvas.height = opt_height; - } - //FIXME Allow OffscreenCanvasRenderingContext2D as return type - return /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d')); -} -/** - * Get the current computed width for the given element including margin, - * padding and border. - * Equivalent to jQuery's `$(el).outerWidth(true)`. - * @param {!HTMLElement} element Element. - * @return {number} The width. - */ -function outerWidth(element) { - var width = element.offsetWidth; - var style = getComputedStyle(element); - width += parseInt(style.marginLeft, 10) + parseInt(style.marginRight, 10); - return width; -} -/** - * Get the current computed height for the given element including margin, - * padding and border. - * Equivalent to jQuery's `$(el).outerHeight(true)`. - * @param {!HTMLElement} element Element. - * @return {number} The height. - */ -function outerHeight(element) { - var height = element.offsetHeight; - var style = getComputedStyle(element); - height += parseInt(style.marginTop, 10) + parseInt(style.marginBottom, 10); - return height; -} -/** - * @param {Node} newNode Node to replace old node - * @param {Node} oldNode The node to be replaced - */ -function replaceNode(newNode, oldNode) { - var parent = oldNode.parentNode; - if (parent) { - parent.replaceChild(newNode, oldNode); - } -} -/** - * @param {Node} node The node to remove. - * @returns {Node} The node that was removed or null. - */ -function removeNode(node) { - return node && node.parentNode ? node.parentNode.removeChild(node) : null; -} -/** - * @param {Node} node The node to remove the children from. - */ -function removeChildren(node) { - while (node.lastChild) { - node.removeChild(node.lastChild); - } -} -/** - * Transform the children of a parent node so they match the - * provided list of children. This function aims to efficiently - * remove, add, and reorder child nodes while maintaining a simple - * implementation (it is not guaranteed to minimize DOM operations). - * @param {Node} node The parent node whose children need reworking. - * @param {Array} children The desired children. - */ -function replaceChildren(node, children) { - var oldChildren = node.childNodes; - for (var i = 0; true; ++i) { - var oldChild = oldChildren[i]; - var newChild = children[i]; - // check if our work is done - if (!oldChild && !newChild) { - break; - } - // check if children match - if (oldChild === newChild) { - continue; - } - // check if a new child needs to be added - if (!oldChild) { - node.appendChild(newChild); - continue; - } - // check if an old child needs to be removed - if (!newChild) { - node.removeChild(oldChild); - --i; - continue; - } - // reorder - node.insertBefore(newChild, oldChild); - } -} - -/** - * @module ol/render/canvas - */ -/** - * @type {BaseObject} - */ -var checkedFonts = new BaseObject(); -/** - * The label cache for text rendering. To change the default cache size of 2048 - * entries, use {@link module:ol/structs/LRUCache#setSize}. - * Deprecated - there is no label cache any more. - * @type {?} - * @api - * @deprecated - */ -var labelCache = new Target(); -labelCache.setSize = function () { - console.warn('labelCache is deprecated.'); //eslint-disable-line -}; -/** - * @param {CanvasRenderingContext2D} context Context. - * @param {number} rotation Rotation. - * @param {number} offsetX X offset. - * @param {number} offsetY Y offset. - */ -function rotateAtOffset(context, rotation, offsetX, offsetY) { - if (rotation !== 0) { - context.translate(offsetX, offsetY); - context.rotate(rotation); - context.translate(-offsetX, -offsetY); - } -} -/** - * @type {HTMLCanvasElement} - * @private - */ -var createTransformStringCanvas = null; -/** - * @param {import("../transform.js").Transform} transform Transform. - * @return {string} CSS transform. - */ -function createTransformString(transform) { - if (WORKER_OFFSCREEN_CANVAS) { - return toString(transform); - } - else { - if (!createTransformStringCanvas) { - createTransformStringCanvas = createCanvasContext2D(1, 1).canvas; - } - createTransformStringCanvas.style.transform = toString(transform); - return createTransformStringCanvas.style.transform; - } -} - -var __extends$g = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -/** - * @classdesc - * Canvas map renderer. - * @api - */ -var CompositeMapRenderer = /** @class */ (function (_super) { - __extends$g(CompositeMapRenderer, _super); - /** - * @param {import("../PluggableMap.js").default} map Map. - */ - function CompositeMapRenderer(map) { - var _this = _super.call(this, map) || this; - /** - * @type {import("../events.js").EventsKey} - */ - _this.fontChangeListenerKey_ = listen(checkedFonts, ObjectEventType.PROPERTYCHANGE, map.redrawText.bind(map)); - /** - * @private - * @type {HTMLDivElement} - */ - _this.element_ = document.createElement('div'); - var style = _this.element_.style; - style.position = 'absolute'; - style.width = '100%'; - style.height = '100%'; - style.zIndex = '0'; - _this.element_.className = CLASS_UNSELECTABLE + ' ol-layers'; - var container = map.getViewport(); - container.insertBefore(_this.element_, container.firstChild || null); - /** - * @private - * @type {Array} - */ - _this.children_ = []; - /** - * @private - * @type {boolean} - */ - _this.renderedVisible_ = true; - return _this; - } - /** - * @param {import("../render/EventType.js").default} type Event type. - * @param {import("../PluggableMap.js").FrameState} frameState Frame state. - */ - CompositeMapRenderer.prototype.dispatchRenderEvent = function (type, frameState) { - var map = this.getMap(); - if (map.hasListener(type)) { - var event_1 = new RenderEvent(type, undefined, frameState); - map.dispatchEvent(event_1); - } - }; - CompositeMapRenderer.prototype.disposeInternal = function () { - unlistenByKey(this.fontChangeListenerKey_); - this.element_.parentNode.removeChild(this.element_); - _super.prototype.disposeInternal.call(this); - }; - /** - * Render. - * @param {?import("../PluggableMap.js").FrameState} frameState Frame state. - */ - CompositeMapRenderer.prototype.renderFrame = function (frameState) { - if (!frameState) { - if (this.renderedVisible_) { - this.element_.style.display = 'none'; - this.renderedVisible_ = false; - } - return; - } - this.calculateMatrices2D(frameState); - this.dispatchRenderEvent(RenderEventType.PRECOMPOSE, frameState); - var layerStatesArray = frameState.layerStatesArray.sort(function (a, b) { - return a.zIndex - b.zIndex; - }); - var viewState = frameState.viewState; - this.children_.length = 0; - /** - * @type {Array} - */ - var declutterLayers = []; - var previousElement = null; - for (var i = 0, ii = layerStatesArray.length; i < ii; ++i) { - var layerState = layerStatesArray[i]; - frameState.layerIndex = i; - if (!inView(layerState, viewState) || - (layerState.sourceState != SourceState.READY && - layerState.sourceState != SourceState.UNDEFINED)) { - continue; - } - var layer = layerState.layer; - var element = layer.render(frameState, previousElement); - if (!element) { - continue; - } - if (element !== previousElement) { - this.children_.push(element); - previousElement = element; - } - if ('getDeclutter' in layer) { - declutterLayers.push(layer); - } - } - for (var i = declutterLayers.length - 1; i >= 0; --i) { - declutterLayers[i].renderDeclutter(frameState); - } - replaceChildren(this.element_, this.children_); - this.dispatchRenderEvent(RenderEventType.POSTCOMPOSE, frameState); - if (!this.renderedVisible_) { - this.element_.style.display = ''; - this.renderedVisible_ = true; - } - this.scheduleExpireIconCache(frameState); - }; - /** - * @param {import("../pixel.js").Pixel} pixel Pixel. - * @param {import("../PluggableMap.js").FrameState} frameState FrameState. - * @param {number} hitTolerance Hit tolerance in pixels. - * @param {function(import("../layer/Layer.js").default, (Uint8ClampedArray|Uint8Array)): T} callback Layer - * callback. - * @param {function(import("../layer/Layer.js").default): boolean} layerFilter Layer filter - * function, only layers which are visible and for which this function - * returns `true` will be tested for features. By default, all visible - * layers will be tested. - * @return {T|undefined} Callback result. - * @template T - */ - CompositeMapRenderer.prototype.forEachLayerAtPixel = function (pixel, frameState, hitTolerance, callback, layerFilter) { - var viewState = frameState.viewState; - var layerStates = frameState.layerStatesArray; - var numLayers = layerStates.length; - for (var i = numLayers - 1; i >= 0; --i) { - var layerState = layerStates[i]; - var layer = layerState.layer; - if (layer.hasRenderer() && - inView(layerState, viewState) && - layerFilter(layer)) { - var layerRenderer = layer.getRenderer(); - var data = layerRenderer.getDataAtPixel(pixel, frameState, hitTolerance); - if (data) { - var result = callback(layer, data); - if (result) { - return result; - } - } - } - } - return undefined; - }; - return CompositeMapRenderer; -}(MapRenderer)); - -/** - * @module ol/CollectionEventType - */ -/** - * @enum {string} - */ -var CollectionEventType = { - /** - * Triggered when an item is added to the collection. - * @event module:ol/Collection.CollectionEvent#add - * @api - */ - ADD: 'add', - /** - * Triggered when an item is removed from the collection. - * @event module:ol/Collection.CollectionEvent#remove - * @api - */ - REMOVE: 'remove', -}; - -var __extends$h = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -/** - * @enum {string} - * @private - */ -var Property = { - LENGTH: 'length', -}; -/** - * @classdesc - * Events emitted by {@link module:ol/Collection~Collection} instances are instances of this - * type. - */ -var CollectionEvent = /** @class */ (function (_super) { - __extends$h(CollectionEvent, _super); - /** - * @param {import("./CollectionEventType.js").default} type Type. - * @param {*=} opt_element Element. - * @param {number=} opt_index The index of the added or removed element. - */ - function CollectionEvent(type, opt_element, opt_index) { - var _this = _super.call(this, type) || this; - /** - * The element that is added to or removed from the collection. - * @type {*} - * @api - */ - _this.element = opt_element; - /** - * The index of the added or removed element. - * @type {number} - * @api - */ - _this.index = opt_index; - return _this; - } - return CollectionEvent; -}(BaseEvent)); -/** - * @typedef {Object} Options - * @property {boolean} [unique=false] Disallow the same item from being added to - * the collection twice. - */ -/** - * @classdesc - * An expanded version of standard JS Array, adding convenience methods for - * manipulation. Add and remove changes to the Collection trigger a Collection - * event. Note that this does not cover changes to the objects _within_ the - * Collection; they trigger events on the appropriate object, not on the - * Collection as a whole. - * - * @fires CollectionEvent - * - * @template T - * @api - */ -var Collection = /** @class */ (function (_super) { - __extends$h(Collection, _super); - /** - * @param {Array=} opt_array Array. - * @param {Options=} opt_options Collection options. - */ - function Collection(opt_array, opt_options) { - var _this = _super.call(this) || this; - var options = opt_options || {}; - /** - * @private - * @type {boolean} - */ - _this.unique_ = !!options.unique; - /** - * @private - * @type {!Array} - */ - _this.array_ = opt_array ? opt_array : []; - if (_this.unique_) { - for (var i = 0, ii = _this.array_.length; i < ii; ++i) { - _this.assertUnique_(_this.array_[i], i); - } - } - _this.updateLength_(); - return _this; - } - /** - * Remove all elements from the collection. - * @api - */ - Collection.prototype.clear = function () { - while (this.getLength() > 0) { - this.pop(); - } - }; - /** - * Add elements to the collection. This pushes each item in the provided array - * to the end of the collection. - * @param {!Array} arr Array. - * @return {Collection} This collection. - * @api - */ - Collection.prototype.extend = function (arr) { - for (var i = 0, ii = arr.length; i < ii; ++i) { - this.push(arr[i]); - } - return this; - }; - /** - * Iterate over each element, calling the provided callback. - * @param {function(T, number, Array): *} f The function to call - * for every element. This function takes 3 arguments (the element, the - * index and the array). The return value is ignored. - * @api - */ - Collection.prototype.forEach = function (f) { - var array = this.array_; - for (var i = 0, ii = array.length; i < ii; ++i) { - f(array[i], i, array); - } - }; - /** - * Get a reference to the underlying Array object. Warning: if the array - * is mutated, no events will be dispatched by the collection, and the - * collection's "length" property won't be in sync with the actual length - * of the array. - * @return {!Array} Array. - * @api - */ - Collection.prototype.getArray = function () { - return this.array_; - }; - /** - * Get the element at the provided index. - * @param {number} index Index. - * @return {T} Element. - * @api - */ - Collection.prototype.item = function (index) { - return this.array_[index]; - }; - /** - * Get the length of this collection. - * @return {number} The length of the array. - * @observable - * @api - */ - Collection.prototype.getLength = function () { - return this.get(Property.LENGTH); - }; - /** - * Insert an element at the provided index. - * @param {number} index Index. - * @param {T} elem Element. - * @api - */ - Collection.prototype.insertAt = function (index, elem) { - if (this.unique_) { - this.assertUnique_(elem); - } - this.array_.splice(index, 0, elem); - this.updateLength_(); - this.dispatchEvent(new CollectionEvent(CollectionEventType.ADD, elem, index)); - }; - /** - * Remove the last element of the collection and return it. - * Return `undefined` if the collection is empty. - * @return {T|undefined} Element. - * @api - */ - Collection.prototype.pop = function () { - return this.removeAt(this.getLength() - 1); - }; - /** - * Insert the provided element at the end of the collection. - * @param {T} elem Element. - * @return {number} New length of the collection. - * @api - */ - Collection.prototype.push = function (elem) { - if (this.unique_) { - this.assertUnique_(elem); - } - var n = this.getLength(); - this.insertAt(n, elem); - return this.getLength(); - }; - /** - * Remove the first occurrence of an element from the collection. - * @param {T} elem Element. - * @return {T|undefined} The removed element or undefined if none found. - * @api - */ - Collection.prototype.remove = function (elem) { - var arr = this.array_; - for (var i = 0, ii = arr.length; i < ii; ++i) { - if (arr[i] === elem) { - return this.removeAt(i); - } - } - return undefined; - }; - /** - * Remove the element at the provided index and return it. - * Return `undefined` if the collection does not contain this index. - * @param {number} index Index. - * @return {T|undefined} Value. - * @api - */ - Collection.prototype.removeAt = function (index) { - var prev = this.array_[index]; - this.array_.splice(index, 1); - this.updateLength_(); - this.dispatchEvent(new CollectionEvent(CollectionEventType.REMOVE, prev, index)); - return prev; - }; - /** - * Set the element at the provided index. - * @param {number} index Index. - * @param {T} elem Element. - * @api - */ - Collection.prototype.setAt = function (index, elem) { - var n = this.getLength(); - if (index < n) { - if (this.unique_) { - this.assertUnique_(elem, index); - } - var prev = this.array_[index]; - this.array_[index] = elem; - this.dispatchEvent(new CollectionEvent(CollectionEventType.REMOVE, prev, index)); - this.dispatchEvent(new CollectionEvent(CollectionEventType.ADD, elem, index)); - } - else { - for (var j = n; j < index; ++j) { - this.insertAt(j, undefined); - } - this.insertAt(index, elem); - } - }; - /** - * @private - */ - Collection.prototype.updateLength_ = function () { - this.set(Property.LENGTH, this.array_.length); - }; - /** - * @private - * @param {T} elem Element. - * @param {number=} opt_except Optional index to ignore. - */ - Collection.prototype.assertUnique_ = function (elem, opt_except) { - for (var i = 0, ii = this.array_.length; i < ii; ++i) { - if (this.array_[i] === elem && i !== opt_except) { - throw new AssertionError(58); - } - } - }; - return Collection; -}(BaseObject)); - -var __extends$i = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -/** - * @typedef {Object} Options - * @property {number} [opacity=1] Opacity (0, 1). - * @property {boolean} [visible=true] Visibility. - * @property {import("../extent.js").Extent} [extent] The bounding extent for layer rendering. The layer will not be - * rendered outside of this extent. - * @property {number} [zIndex] The z-index for layer rendering. At rendering time, the layers - * will be ordered, first by Z-index and then by position. When `undefined`, a `zIndex` of 0 is assumed - * for layers that are added to the map's `layers` collection, or `Infinity` when the layer's `setMap()` - * method was used. - * @property {number} [minResolution] The minimum resolution (inclusive) at which this layer will be - * visible. - * @property {number} [maxResolution] The maximum resolution (exclusive) below which this layer will - * be visible. - * @property {number} [minZoom] The minimum view zoom level (exclusive) above which this layer will be - * visible. - * @property {number} [maxZoom] The maximum view zoom level (inclusive) at which this layer will - * be visible. - * @property {number} [minZoom] The minimum view zoom level (exclusive) above which this layer will be - * visible. - * @property {number} [maxZoom] The maximum view zoom level (inclusive) at which this layer will - * be visible. - * @property {Array|import("../Collection.js").default} [layers] Child layers. - */ -/** - * @enum {string} - * @private - */ -var Property$1 = { - LAYERS: 'layers', -}; -/** - * @classdesc - * A {@link module:ol/Collection~Collection} of layers that are handled together. - * - * A generic `change` event is triggered when the group/Collection changes. - * - * @api - */ -var LayerGroup = /** @class */ (function (_super) { - __extends$i(LayerGroup, _super); - /** - * @param {Options=} opt_options Layer options. - */ - function LayerGroup(opt_options) { - var _this = this; - var options = opt_options || {}; - var baseOptions = /** @type {Options} */ (assign({}, options)); - delete baseOptions.layers; - var layers = options.layers; - _this = _super.call(this, baseOptions) || this; - /** - * @private - * @type {Array} - */ - _this.layersListenerKeys_ = []; - /** - * @private - * @type {Object>} - */ - _this.listenerKeys_ = {}; - _this.addEventListener(getChangeEventType(Property$1.LAYERS), _this.handleLayersChanged_); - if (layers) { - if (Array.isArray(layers)) { - layers = new Collection(layers.slice(), { unique: true }); - } - else { - assert(typeof ( /** @type {?} */(layers).getArray) === 'function', 43); // Expected `layers` to be an array or a `Collection` - } - } - else { - layers = new Collection(undefined, { unique: true }); - } - _this.setLayers(layers); - return _this; - } - /** - * @private - */ - LayerGroup.prototype.handleLayerChange_ = function () { - this.changed(); - }; - /** - * @private - */ - LayerGroup.prototype.handleLayersChanged_ = function () { - this.layersListenerKeys_.forEach(unlistenByKey); - this.layersListenerKeys_.length = 0; - var layers = this.getLayers(); - this.layersListenerKeys_.push(listen(layers, CollectionEventType.ADD, this.handleLayersAdd_, this), listen(layers, CollectionEventType.REMOVE, this.handleLayersRemove_, this)); - for (var id in this.listenerKeys_) { - this.listenerKeys_[id].forEach(unlistenByKey); - } - clear(this.listenerKeys_); - var layersArray = layers.getArray(); - for (var i = 0, ii = layersArray.length; i < ii; i++) { - var layer = layersArray[i]; - this.listenerKeys_[getUid(layer)] = [ - listen(layer, ObjectEventType.PROPERTYCHANGE, this.handleLayerChange_, this), - listen(layer, EventType.CHANGE, this.handleLayerChange_, this), - ]; - } - this.changed(); - }; - /** - * @param {import("../Collection.js").CollectionEvent} collectionEvent CollectionEvent. - * @private - */ - LayerGroup.prototype.handleLayersAdd_ = function (collectionEvent) { - var layer = /** @type {import("./Base.js").default} */ (collectionEvent.element); - this.listenerKeys_[getUid(layer)] = [ - listen(layer, ObjectEventType.PROPERTYCHANGE, this.handleLayerChange_, this), - listen(layer, EventType.CHANGE, this.handleLayerChange_, this), - ]; - this.changed(); - }; - /** - * @param {import("../Collection.js").CollectionEvent} collectionEvent CollectionEvent. - * @private - */ - LayerGroup.prototype.handleLayersRemove_ = function (collectionEvent) { - var layer = /** @type {import("./Base.js").default} */ (collectionEvent.element); - var key = getUid(layer); - this.listenerKeys_[key].forEach(unlistenByKey); - delete this.listenerKeys_[key]; - this.changed(); - }; - /** - * Returns the {@link module:ol/Collection collection} of {@link module:ol/layer/Layer~Layer layers} - * in this group. - * @return {!import("../Collection.js").default} Collection of - * {@link module:ol/layer/Base layers} that are part of this group. - * @observable - * @api - */ - LayerGroup.prototype.getLayers = function () { - return /** @type {!import("../Collection.js").default} */ (this.get(Property$1.LAYERS)); - }; - /** - * Set the {@link module:ol/Collection collection} of {@link module:ol/layer/Layer~Layer layers} - * in this group. - * @param {!import("../Collection.js").default} layers Collection of - * {@link module:ol/layer/Base layers} that are part of this group. - * @observable - * @api - */ - LayerGroup.prototype.setLayers = function (layers) { - this.set(Property$1.LAYERS, layers); - }; - /** - * @param {Array=} opt_array Array of layers (to be modified in place). - * @return {Array} Array of layers. - */ - LayerGroup.prototype.getLayersArray = function (opt_array) { - var array = opt_array !== undefined ? opt_array : []; - this.getLayers().forEach(function (layer) { - layer.getLayersArray(array); - }); - return array; - }; - /** - * @param {Array=} opt_states Optional list of layer states (to be modified in place). - * @return {Array} List of layer states. - */ - LayerGroup.prototype.getLayerStatesArray = function (opt_states) { - var states = opt_states !== undefined ? opt_states : []; - var pos = states.length; - this.getLayers().forEach(function (layer) { - layer.getLayerStatesArray(states); - }); - var ownLayerState = this.getLayerState(); - for (var i = pos, ii = states.length; i < ii; i++) { - var layerState = states[i]; - layerState.opacity *= ownLayerState.opacity; - layerState.visible = layerState.visible && ownLayerState.visible; - layerState.maxResolution = Math.min(layerState.maxResolution, ownLayerState.maxResolution); - layerState.minResolution = Math.max(layerState.minResolution, ownLayerState.minResolution); - layerState.minZoom = Math.max(layerState.minZoom, ownLayerState.minZoom); - layerState.maxZoom = Math.min(layerState.maxZoom, ownLayerState.maxZoom); - if (ownLayerState.extent !== undefined) { - if (layerState.extent !== undefined) { - layerState.extent = getIntersection(layerState.extent, ownLayerState.extent); - } - else { - layerState.extent = ownLayerState.extent; - } - } - } - return states; - }; - /** - * @return {import("../source/State.js").default} Source state. - */ - LayerGroup.prototype.getSourceState = function () { - return SourceState.READY; - }; - return LayerGroup; -}(BaseLayer)); - -var __extends$j = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -/** - * @classdesc - * Events emitted as map events are instances of this type. - * See {@link module:ol/PluggableMap~PluggableMap} for which events trigger a map event. - */ -var MapEvent = /** @class */ (function (_super) { - __extends$j(MapEvent, _super); - /** - * @param {string} type Event type. - * @param {import("./PluggableMap.js").default} map Map. - * @param {?import("./PluggableMap.js").FrameState=} opt_frameState Frame state. - */ - function MapEvent(type, map, opt_frameState) { - var _this = _super.call(this, type) || this; - /** - * The map where the event occurred. - * @type {import("./PluggableMap.js").default} - * @api - */ - _this.map = map; - /** - * The frame state at the time of the event. - * @type {?import("./PluggableMap.js").FrameState} - * @api - */ - _this.frameState = opt_frameState !== undefined ? opt_frameState : null; - return _this; - } - return MapEvent; -}(BaseEvent)); - -var __extends$k = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -/** - * @classdesc - * Events emitted as map browser events are instances of this type. - * See {@link module:ol/PluggableMap~PluggableMap} for which events trigger a map browser event. - * @template {UIEvent} EVENT - */ -var MapBrowserEvent = /** @class */ (function (_super) { - __extends$k(MapBrowserEvent, _super); - /** - * @param {string} type Event type. - * @param {import("./PluggableMap.js").default} map Map. - * @param {EVENT} originalEvent Original event. - * @param {boolean=} opt_dragging Is the map currently being dragged? - * @param {?import("./PluggableMap.js").FrameState=} opt_frameState Frame state. - */ - function MapBrowserEvent(type, map, originalEvent, opt_dragging, opt_frameState) { - var _this = _super.call(this, type, map, opt_frameState) || this; - /** - * The original browser event. - * @const - * @type {EVENT} - * @api - */ - _this.originalEvent = originalEvent; - /** - * The map pixel relative to the viewport corresponding to the original browser event. - * @type {?import("./pixel.js").Pixel} - */ - _this.pixel_ = null; - /** - * The coordinate in the user projection corresponding to the original browser event. - * @type {?import("./coordinate.js").Coordinate} - */ - _this.coordinate_ = null; - /** - * Indicates if the map is currently being dragged. Only set for - * `POINTERDRAG` and `POINTERMOVE` events. Default is `false`. - * - * @type {boolean} - * @api - */ - _this.dragging = opt_dragging !== undefined ? opt_dragging : false; - return _this; - } - Object.defineProperty(MapBrowserEvent.prototype, "pixel", { - /** - * The map pixel relative to the viewport corresponding to the original event. - * @type {import("./pixel.js").Pixel} - * @api - */ - get: function () { - if (!this.pixel_) { - this.pixel_ = this.map.getEventPixel(this.originalEvent); - } - return this.pixel_; - }, - set: function (pixel) { - this.pixel_ = pixel; - }, - enumerable: false, - configurable: true - }); - Object.defineProperty(MapBrowserEvent.prototype, "coordinate", { - /** - * The coordinate corresponding to the original browser event. This will be in the user - * projection if one is set. Otherwise it will be in the view projection. - * @type {import("./coordinate.js").Coordinate} - * @api - */ - get: function () { - if (!this.coordinate_) { - this.coordinate_ = this.map.getCoordinateFromPixel(this.pixel); - } - return this.coordinate_; - }, - set: function (coordinate) { - this.coordinate_ = coordinate; - }, - enumerable: false, - configurable: true - }); - /** - * Prevents the default browser action. - * See https://developer.mozilla.org/en-US/docs/Web/API/event.preventDefault. - * @api - */ - MapBrowserEvent.prototype.preventDefault = function () { - _super.prototype.preventDefault.call(this); - this.originalEvent.preventDefault(); - }; - /** - * Prevents further propagation of the current event. - * See https://developer.mozilla.org/en-US/docs/Web/API/event.stopPropagation. - * @api - */ - MapBrowserEvent.prototype.stopPropagation = function () { - _super.prototype.stopPropagation.call(this); - this.originalEvent.stopPropagation(); - }; - return MapBrowserEvent; -}(MapEvent)); - -/** - * @module ol/MapBrowserEventType - */ -/** - * Constants for event names. - * @enum {string} - */ -var MapBrowserEventType = { - /** - * A true single click with no dragging and no double click. Note that this - * event is delayed by 250 ms to ensure that it is not a double click. - * @event module:ol/MapBrowserEvent~MapBrowserEvent#singleclick - * @api - */ - SINGLECLICK: 'singleclick', - /** - * A click with no dragging. A double click will fire two of this. - * @event module:ol/MapBrowserEvent~MapBrowserEvent#click - * @api - */ - CLICK: EventType.CLICK, - /** - * A true double click, with no dragging. - * @event module:ol/MapBrowserEvent~MapBrowserEvent#dblclick - * @api - */ - DBLCLICK: EventType.DBLCLICK, - /** - * Triggered when a pointer is dragged. - * @event module:ol/MapBrowserEvent~MapBrowserEvent#pointerdrag - * @api - */ - POINTERDRAG: 'pointerdrag', - /** - * Triggered when a pointer is moved. Note that on touch devices this is - * triggered when the map is panned, so is not the same as mousemove. - * @event module:ol/MapBrowserEvent~MapBrowserEvent#pointermove - * @api - */ - POINTERMOVE: 'pointermove', - POINTERDOWN: 'pointerdown', - POINTERUP: 'pointerup', - POINTEROVER: 'pointerover', - POINTEROUT: 'pointerout', - POINTERENTER: 'pointerenter', - POINTERLEAVE: 'pointerleave', - POINTERCANCEL: 'pointercancel', -}; - -/** - * @module ol/pointer/EventType - */ -/** - * Constants for event names. - * @enum {string} - */ -var PointerEventType = { - POINTERMOVE: 'pointermove', - POINTERDOWN: 'pointerdown', - POINTERUP: 'pointerup', - POINTEROVER: 'pointerover', - POINTEROUT: 'pointerout', - POINTERENTER: 'pointerenter', - POINTERLEAVE: 'pointerleave', - POINTERCANCEL: 'pointercancel', -}; - -/** - * @module ol/MapBrowserEventHandler - */ -var __extends$l = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -var MapBrowserEventHandler = /** @class */ (function (_super) { - __extends$l(MapBrowserEventHandler, _super); - /** - * @param {import("./PluggableMap.js").default} map The map with the viewport to listen to events on. - * @param {number=} moveTolerance The minimal distance the pointer must travel to trigger a move. - */ - function MapBrowserEventHandler(map, moveTolerance) { - var _this = _super.call(this, map) || this; - /** - * This is the element that we will listen to the real events on. - * @type {import("./PluggableMap.js").default} - * @private - */ - _this.map_ = map; - /** - * @type {any} - * @private - */ - _this.clickTimeoutId_; - /** - * Emulate dblclick and singleclick. Will be true when only one pointer is active. - * @type {boolean} - */ - _this.emulateClicks_ = false; - /** - * @type {boolean} - * @private - */ - _this.dragging_ = false; - /** - * @type {!Array} - * @private - */ - _this.dragListenerKeys_ = []; - /** - * @type {number} - * @private - */ - _this.moveTolerance_ = moveTolerance - ? moveTolerance * DEVICE_PIXEL_RATIO - : DEVICE_PIXEL_RATIO; - /** - * The most recent "down" type event (or null if none have occurred). - * Set on pointerdown. - * @type {PointerEvent} - * @private - */ - _this.down_ = null; - var element = _this.map_.getViewport(); - /** - * @type {number} - * @private - */ - _this.activePointers_ = 0; - /** - * @type {!Object} - * @private - */ - _this.trackedTouches_ = {}; - _this.element_ = element; - /** - * @type {?import("./events.js").EventsKey} - * @private - */ - _this.pointerdownListenerKey_ = listen(element, PointerEventType.POINTERDOWN, _this.handlePointerDown_, _this); - /** - * @type {PointerEvent} - * @private - */ - _this.originalPointerMoveEvent_; - /** - * @type {?import("./events.js").EventsKey} - * @private - */ - _this.relayedListenerKey_ = listen(element, PointerEventType.POINTERMOVE, _this.relayEvent_, _this); - /** - * @private - */ - _this.boundHandleTouchMove_ = _this.handleTouchMove_.bind(_this); - _this.element_.addEventListener(EventType.TOUCHMOVE, _this.boundHandleTouchMove_, PASSIVE_EVENT_LISTENERS ? { passive: false } : false); - return _this; - } - /** - * @param {PointerEvent} pointerEvent Pointer - * event. - * @private - */ - MapBrowserEventHandler.prototype.emulateClick_ = function (pointerEvent) { - var newEvent = new MapBrowserEvent(MapBrowserEventType.CLICK, this.map_, pointerEvent); - this.dispatchEvent(newEvent); - if (this.clickTimeoutId_ !== undefined) { - // double-click - clearTimeout(this.clickTimeoutId_); - this.clickTimeoutId_ = undefined; - newEvent = new MapBrowserEvent(MapBrowserEventType.DBLCLICK, this.map_, pointerEvent); - this.dispatchEvent(newEvent); - } - else { - // click - this.clickTimeoutId_ = setTimeout( - /** @this {MapBrowserEventHandler} */ - function () { - this.clickTimeoutId_ = undefined; - var newEvent = new MapBrowserEvent(MapBrowserEventType.SINGLECLICK, this.map_, pointerEvent); - this.dispatchEvent(newEvent); - }.bind(this), 250); - } - }; - /** - * Keeps track on how many pointers are currently active. - * - * @param {PointerEvent} pointerEvent Pointer - * event. - * @private - */ - MapBrowserEventHandler.prototype.updateActivePointers_ = function (pointerEvent) { - var event = pointerEvent; - if (event.type == MapBrowserEventType.POINTERUP || - event.type == MapBrowserEventType.POINTERCANCEL) { - delete this.trackedTouches_[event.pointerId]; - } - else if (event.type == MapBrowserEventType.POINTERDOWN) { - this.trackedTouches_[event.pointerId] = true; - } - this.activePointers_ = Object.keys(this.trackedTouches_).length; - }; - /** - * @param {PointerEvent} pointerEvent Pointer - * event. - * @private - */ - MapBrowserEventHandler.prototype.handlePointerUp_ = function (pointerEvent) { - this.updateActivePointers_(pointerEvent); - var newEvent = new MapBrowserEvent(MapBrowserEventType.POINTERUP, this.map_, pointerEvent); - this.dispatchEvent(newEvent); - // We emulate click events on left mouse button click, touch contact, and pen - // contact. isMouseActionButton returns true in these cases (evt.button is set - // to 0). - // See http://www.w3.org/TR/pointerevents/#button-states - // We only fire click, singleclick, and doubleclick if nobody has called - // event.stopPropagation() or event.preventDefault(). - if (this.emulateClicks_ && - !newEvent.propagationStopped && - !this.dragging_ && - this.isMouseActionButton_(pointerEvent)) { - this.emulateClick_(this.down_); - } - if (this.activePointers_ === 0) { - this.dragListenerKeys_.forEach(unlistenByKey); - this.dragListenerKeys_.length = 0; - this.dragging_ = false; - this.down_ = null; - } - }; - /** - * @param {PointerEvent} pointerEvent Pointer - * event. - * @return {boolean} If the left mouse button was pressed. - * @private - */ - MapBrowserEventHandler.prototype.isMouseActionButton_ = function (pointerEvent) { - return pointerEvent.button === 0; - }; - /** - * @param {PointerEvent} pointerEvent Pointer - * event. - * @private - */ - MapBrowserEventHandler.prototype.handlePointerDown_ = function (pointerEvent) { - this.emulateClicks_ = this.activePointers_ === 0; - this.updateActivePointers_(pointerEvent); - var newEvent = new MapBrowserEvent(MapBrowserEventType.POINTERDOWN, this.map_, pointerEvent); - this.dispatchEvent(newEvent); - this.down_ = pointerEvent; - if (this.dragListenerKeys_.length === 0) { - var doc = this.map_.getOwnerDocument(); - this.dragListenerKeys_.push(listen(doc, MapBrowserEventType.POINTERMOVE, this.handlePointerMove_, this), listen(doc, MapBrowserEventType.POINTERUP, this.handlePointerUp_, this), - /* Note that the listener for `pointercancel is set up on - * `pointerEventHandler_` and not `documentPointerEventHandler_` like - * the `pointerup` and `pointermove` listeners. - * - * The reason for this is the following: `TouchSource.vacuumTouches_()` - * issues `pointercancel` events, when there was no `touchend` for a - * `touchstart`. Now, let's say a first `touchstart` is registered on - * `pointerEventHandler_`. The `documentPointerEventHandler_` is set up. - * But `documentPointerEventHandler_` doesn't know about the first - * `touchstart`. If there is no `touchend` for the `touchstart`, we can - * only receive a `touchcancel` from `pointerEventHandler_`, because it is - * only registered there. - */ - listen(this.element_, MapBrowserEventType.POINTERCANCEL, this.handlePointerUp_, this)); - if (this.element_.getRootNode && this.element_.getRootNode() !== doc) { - this.dragListenerKeys_.push(listen(this.element_.getRootNode(), MapBrowserEventType.POINTERUP, this.handlePointerUp_, this)); - } - } - }; - /** - * @param {PointerEvent} pointerEvent Pointer - * event. - * @private - */ - MapBrowserEventHandler.prototype.handlePointerMove_ = function (pointerEvent) { - // Between pointerdown and pointerup, pointermove events are triggered. - // To avoid a 'false' touchmove event to be dispatched, we test if the pointer - // moved a significant distance. - if (this.isMoving_(pointerEvent)) { - this.dragging_ = true; - var newEvent = new MapBrowserEvent(MapBrowserEventType.POINTERDRAG, this.map_, pointerEvent, this.dragging_); - this.dispatchEvent(newEvent); - } - }; - /** - * Wrap and relay a pointer event. Note that this requires that the type - * string for the MapBrowserEvent matches the PointerEvent type. - * @param {PointerEvent} pointerEvent Pointer - * event. - * @private - */ - MapBrowserEventHandler.prototype.relayEvent_ = function (pointerEvent) { - this.originalPointerMoveEvent_ = pointerEvent; - var dragging = !!(this.down_ && this.isMoving_(pointerEvent)); - this.dispatchEvent(new MapBrowserEvent(pointerEvent.type, this.map_, pointerEvent, dragging)); - }; - /** - * Flexible handling of a `touch-action: none` css equivalent: because calling - * `preventDefault()` on a `pointermove` event does not stop native page scrolling - * and zooming, we also listen for `touchmove` and call `preventDefault()` on it - * when an interaction (currently `DragPan` handles the event. - * @param {TouchEvent} event Event. - * @private - */ - MapBrowserEventHandler.prototype.handleTouchMove_ = function (event) { - // Due to https://github.com/mpizenberg/elm-pep/issues/2, `this.originalPointerMoveEvent_` - // may not be initialized yet when we get here on a platform without native pointer events. - if (!this.originalPointerMoveEvent_ || - this.originalPointerMoveEvent_.defaultPrevented) { - event.preventDefault(); - } - }; - /** - * @param {PointerEvent} pointerEvent Pointer - * event. - * @return {boolean} Is moving. - * @private - */ - MapBrowserEventHandler.prototype.isMoving_ = function (pointerEvent) { - return (this.dragging_ || - Math.abs(pointerEvent.clientX - this.down_.clientX) > - this.moveTolerance_ || - Math.abs(pointerEvent.clientY - this.down_.clientY) > this.moveTolerance_); - }; - /** - * Clean up. - */ - MapBrowserEventHandler.prototype.disposeInternal = function () { - if (this.relayedListenerKey_) { - unlistenByKey(this.relayedListenerKey_); - this.relayedListenerKey_ = null; - } - this.element_.removeEventListener(EventType.TOUCHMOVE, this.boundHandleTouchMove_); - if (this.pointerdownListenerKey_) { - unlistenByKey(this.pointerdownListenerKey_); - this.pointerdownListenerKey_ = null; - } - this.dragListenerKeys_.forEach(unlistenByKey); - this.dragListenerKeys_.length = 0; - this.element_ = null; - _super.prototype.disposeInternal.call(this); - }; - return MapBrowserEventHandler; -}(Target)); - -/** - * @module ol/MapEventType - */ -/** - * @enum {string} - */ -var MapEventType = { - /** - * Triggered after a map frame is rendered. - * @event module:ol/MapEvent~MapEvent#postrender - * @api - */ - POSTRENDER: 'postrender', - /** - * Triggered when the map starts moving. - * @event module:ol/MapEvent~MapEvent#movestart - * @api - */ - MOVESTART: 'movestart', - /** - * Triggered after the map is moved. - * @event module:ol/MapEvent~MapEvent#moveend - * @api - */ - MOVEEND: 'moveend', -}; - -/** - * @module ol/MapProperty - */ -/** - * @enum {string} - */ -var MapProperty = { - LAYERGROUP: 'layergroup', - SIZE: 'size', - TARGET: 'target', - VIEW: 'view', -}; - -/** - * @module ol/structs/PriorityQueue - */ -/** - * @type {number} - */ -var DROP = Infinity; -/** - * @classdesc - * Priority queue. - * - * The implementation is inspired from the Closure Library's Heap class and - * Python's heapq module. - * - * See http://closure-library.googlecode.com/svn/docs/closure_goog_structs_heap.js.source.html - * and http://hg.python.org/cpython/file/2.7/Lib/heapq.py. - * - * @template T - */ -var PriorityQueue = /** @class */ (function () { - /** - * @param {function(T): number} priorityFunction Priority function. - * @param {function(T): string} keyFunction Key function. - */ - function PriorityQueue(priorityFunction, keyFunction) { - /** - * @type {function(T): number} - * @private - */ - this.priorityFunction_ = priorityFunction; - /** - * @type {function(T): string} - * @private - */ - this.keyFunction_ = keyFunction; - /** - * @type {Array} - * @private - */ - this.elements_ = []; - /** - * @type {Array} - * @private - */ - this.priorities_ = []; - /** - * @type {!Object} - * @private - */ - this.queuedElements_ = {}; - } - /** - * FIXME empty description for jsdoc - */ - PriorityQueue.prototype.clear = function () { - this.elements_.length = 0; - this.priorities_.length = 0; - clear(this.queuedElements_); - }; - /** - * Remove and return the highest-priority element. O(log N). - * @return {T} Element. - */ - PriorityQueue.prototype.dequeue = function () { - var elements = this.elements_; - var priorities = this.priorities_; - var element = elements[0]; - if (elements.length == 1) { - elements.length = 0; - priorities.length = 0; - } - else { - elements[0] = elements.pop(); - priorities[0] = priorities.pop(); - this.siftUp_(0); - } - var elementKey = this.keyFunction_(element); - delete this.queuedElements_[elementKey]; - return element; - }; - /** - * Enqueue an element. O(log N). - * @param {T} element Element. - * @return {boolean} The element was added to the queue. - */ - PriorityQueue.prototype.enqueue = function (element) { - assert(!(this.keyFunction_(element) in this.queuedElements_), 31); // Tried to enqueue an `element` that was already added to the queue - var priority = this.priorityFunction_(element); - if (priority != DROP) { - this.elements_.push(element); - this.priorities_.push(priority); - this.queuedElements_[this.keyFunction_(element)] = true; - this.siftDown_(0, this.elements_.length - 1); - return true; - } - return false; - }; - /** - * @return {number} Count. - */ - PriorityQueue.prototype.getCount = function () { - return this.elements_.length; - }; - /** - * Gets the index of the left child of the node at the given index. - * @param {number} index The index of the node to get the left child for. - * @return {number} The index of the left child. - * @private - */ - PriorityQueue.prototype.getLeftChildIndex_ = function (index) { - return index * 2 + 1; - }; - /** - * Gets the index of the right child of the node at the given index. - * @param {number} index The index of the node to get the right child for. - * @return {number} The index of the right child. - * @private - */ - PriorityQueue.prototype.getRightChildIndex_ = function (index) { - return index * 2 + 2; - }; - /** - * Gets the index of the parent of the node at the given index. - * @param {number} index The index of the node to get the parent for. - * @return {number} The index of the parent. - * @private - */ - PriorityQueue.prototype.getParentIndex_ = function (index) { - return (index - 1) >> 1; - }; - /** - * Make this a heap. O(N). - * @private - */ - PriorityQueue.prototype.heapify_ = function () { - var i; - for (i = (this.elements_.length >> 1) - 1; i >= 0; i--) { - this.siftUp_(i); - } - }; - /** - * @return {boolean} Is empty. - */ - PriorityQueue.prototype.isEmpty = function () { - return this.elements_.length === 0; - }; - /** - * @param {string} key Key. - * @return {boolean} Is key queued. - */ - PriorityQueue.prototype.isKeyQueued = function (key) { - return key in this.queuedElements_; - }; - /** - * @param {T} element Element. - * @return {boolean} Is queued. - */ - PriorityQueue.prototype.isQueued = function (element) { - return this.isKeyQueued(this.keyFunction_(element)); - }; - /** - * @param {number} index The index of the node to move down. - * @private - */ - PriorityQueue.prototype.siftUp_ = function (index) { - var elements = this.elements_; - var priorities = this.priorities_; - var count = elements.length; - var element = elements[index]; - var priority = priorities[index]; - var startIndex = index; - while (index < count >> 1) { - var lIndex = this.getLeftChildIndex_(index); - var rIndex = this.getRightChildIndex_(index); - var smallerChildIndex = rIndex < count && priorities[rIndex] < priorities[lIndex] - ? rIndex - : lIndex; - elements[index] = elements[smallerChildIndex]; - priorities[index] = priorities[smallerChildIndex]; - index = smallerChildIndex; - } - elements[index] = element; - priorities[index] = priority; - this.siftDown_(startIndex, index); - }; - /** - * @param {number} startIndex The index of the root. - * @param {number} index The index of the node to move up. - * @private - */ - PriorityQueue.prototype.siftDown_ = function (startIndex, index) { - var elements = this.elements_; - var priorities = this.priorities_; - var element = elements[index]; - var priority = priorities[index]; - while (index > startIndex) { - var parentIndex = this.getParentIndex_(index); - if (priorities[parentIndex] > priority) { - elements[index] = elements[parentIndex]; - priorities[index] = priorities[parentIndex]; - index = parentIndex; - } - else { - break; - } - } - elements[index] = element; - priorities[index] = priority; - }; - /** - * FIXME empty description for jsdoc - */ - PriorityQueue.prototype.reprioritize = function () { - var priorityFunction = this.priorityFunction_; - var elements = this.elements_; - var priorities = this.priorities_; - var index = 0; - var n = elements.length; - var element, i, priority; - for (i = 0; i < n; ++i) { - element = elements[i]; - priority = priorityFunction(element); - if (priority == DROP) { - delete this.queuedElements_[this.keyFunction_(element)]; - } - else { - priorities[index] = priority; - elements[index++] = element; - } - } - elements.length = index; - priorities.length = index; - this.heapify_(); - }; - return PriorityQueue; -}()); - -/** - * @module ol/TileState - */ -/** - * @enum {number} - */ -var TileState = { - IDLE: 0, - LOADING: 1, - LOADED: 2, - /** - * Indicates that tile loading failed - * @type {number} - */ - ERROR: 3, - EMPTY: 4, -}; - -var __extends$m = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -/** - * @typedef {function(import("./Tile.js").default, string, import("./coordinate.js").Coordinate, number): number} PriorityFunction - */ -var TileQueue = /** @class */ (function (_super) { - __extends$m(TileQueue, _super); - /** - * @param {PriorityFunction} tilePriorityFunction Tile priority function. - * @param {function(): ?} tileChangeCallback Function called on each tile change event. - */ - function TileQueue(tilePriorityFunction, tileChangeCallback) { - var _this = _super.call(this, - /** - * @param {Array} element Element. - * @return {number} Priority. - */ - function (element) { - return tilePriorityFunction.apply(null, element); - }, - /** - * @param {Array} element Element. - * @return {string} Key. - */ - function (element) { - return /** @type {import("./Tile.js").default} */ (element[0]).getKey(); - }) || this; - /** @private */ - _this.boundHandleTileChange_ = _this.handleTileChange.bind(_this); - /** - * @private - * @type {function(): ?} - */ - _this.tileChangeCallback_ = tileChangeCallback; - /** - * @private - * @type {number} - */ - _this.tilesLoading_ = 0; - /** - * @private - * @type {!Object} - */ - _this.tilesLoadingKeys_ = {}; - return _this; - } - /** - * @param {Array} element Element. - * @return {boolean} The element was added to the queue. - */ - TileQueue.prototype.enqueue = function (element) { - var added = _super.prototype.enqueue.call(this, element); - if (added) { - var tile = element[0]; - tile.addEventListener(EventType.CHANGE, this.boundHandleTileChange_); - } - return added; - }; - /** - * @return {number} Number of tiles loading. - */ - TileQueue.prototype.getTilesLoading = function () { - return this.tilesLoading_; - }; - /** - * @param {import("./events/Event.js").default} event Event. - * @protected - */ - TileQueue.prototype.handleTileChange = function (event) { - var tile = /** @type {import("./Tile.js").default} */ (event.target); - var state = tile.getState(); - if ((tile.hifi && state === TileState.LOADED) || - state === TileState.ERROR || - state === TileState.EMPTY) { - tile.removeEventListener(EventType.CHANGE, this.boundHandleTileChange_); - var tileKey = tile.getKey(); - if (tileKey in this.tilesLoadingKeys_) { - delete this.tilesLoadingKeys_[tileKey]; - --this.tilesLoading_; - } - this.tileChangeCallback_(); - } - }; - /** - * @param {number} maxTotalLoading Maximum number tiles to load simultaneously. - * @param {number} maxNewLoads Maximum number of new tiles to load. - */ - TileQueue.prototype.loadMoreTiles = function (maxTotalLoading, maxNewLoads) { - var newLoads = 0; - var state, tile, tileKey; - while (this.tilesLoading_ < maxTotalLoading && - newLoads < maxNewLoads && - this.getCount() > 0) { - tile = /** @type {import("./Tile.js").default} */ (this.dequeue()[0]); - tileKey = tile.getKey(); - state = tile.getState(); - if (state === TileState.IDLE && !(tileKey in this.tilesLoadingKeys_)) { - this.tilesLoadingKeys_[tileKey] = true; - ++this.tilesLoading_; - ++newLoads; - tile.load(); - } - } - }; - return TileQueue; -}(PriorityQueue)); -/** - * @param {import('./PluggableMap.js').FrameState} frameState Frame state. - * @param {import("./Tile.js").default} tile Tile. - * @param {string} tileSourceKey Tile source key. - * @param {import("./coordinate.js").Coordinate} tileCenter Tile center. - * @param {number} tileResolution Tile resolution. - * @return {number} Tile priority. - */ -function getTilePriority(frameState, tile, tileSourceKey, tileCenter, tileResolution) { - // Filter out tiles at higher zoom levels than the current zoom level, or that - // are outside the visible extent. - if (!frameState || !(tileSourceKey in frameState.wantedTiles)) { - return DROP; - } - if (!frameState.wantedTiles[tileSourceKey][tile.getKey()]) { - return DROP; - } - // Prioritize the highest zoom level tiles closest to the focus. - // Tiles at higher zoom levels are prioritized using Math.log(tileResolution). - // Within a zoom level, tiles are prioritized by the distance in pixels between - // the center of the tile and the center of the viewport. The factor of 65536 - // means that the prioritization should behave as desired for tiles up to - // 65536 * Math.log(2) = 45426 pixels from the focus. - var center = frameState.viewState.center; - var deltaX = tileCenter[0] - center[0]; - var deltaY = tileCenter[1] - center[1]; - return (65536 * Math.log(tileResolution) + - Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution); -} - -/** - * @module ol/size - */ -/** - * Determines if a size has a positive area. - * @param {Size} size The size to test. - * @return {boolean} The size has a positive area. - */ -function hasArea(size) { - return size[0] > 0 && size[1] > 0; -} -/** - * Returns a size scaled by a ratio. The result will be an array of integers. - * @param {Size} size Size. - * @param {number} ratio Ratio. - * @param {Size=} opt_size Optional reusable size array. - * @return {Size} The scaled size. - */ -function scale$2(size, ratio, opt_size) { - if (opt_size === undefined) { - opt_size = [0, 0]; - } - opt_size[0] = (size[0] * ratio + 0.5) | 0; - opt_size[1] = (size[1] * ratio + 0.5) | 0; - return opt_size; -} -/** - * Returns an `Size` array for the passed in number (meaning: square) or - * `Size` array. - * (meaning: non-square), - * @param {number|Size} size Width and height. - * @param {Size=} opt_size Optional reusable size array. - * @return {Size} Size. - * @api - */ -function toSize(size, opt_size) { - if (Array.isArray(size)) { - return size; - } - else { - if (opt_size === undefined) { - opt_size = [size, size]; - } - else { - opt_size[0] = size; - opt_size[1] = size; - } - return opt_size; - } -} - -var __extends$n = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -/** - * State of the current frame. Only `pixelRatio`, `time` and `viewState` should - * be used in applications. - * @typedef {Object} FrameState - * @property {number} pixelRatio The pixel ratio of the frame. - * @property {number} time The time when rendering of the frame was requested. - * @property {import("./View.js").State} viewState The state of the current view. - * @property {boolean} animate - * @property {import("./transform.js").Transform} coordinateToPixelTransform - * @property {import("rbush").default} declutterTree - * @property {null|import("./extent.js").Extent} extent - * @property {number} index - * @property {Array} layerStatesArray - * @property {number} layerIndex - * @property {import("./transform.js").Transform} pixelToCoordinateTransform - * @property {Array} postRenderFunctions - * @property {import("./size.js").Size} size - * @property {TileQueue} tileQueue - * @property {!Object>} usedTiles - * @property {Array} viewHints - * @property {!Object>} wantedTiles - */ -/** - * @typedef {function(PluggableMap, ?FrameState): any} PostRenderFunction - */ -/** - * @typedef {Object} AtPixelOptions - * @property {undefined|function(import("./layer/Layer.js").default): boolean} [layerFilter] Layer filter - * function. The filter function will receive one argument, the - * {@link module:ol/layer/Layer layer-candidate} and it should return a boolean value. - * Only layers which are visible and for which this function returns `true` - * will be tested for features. By default, all visible layers will be tested. - * @property {number} [hitTolerance=0] Hit-detection tolerance in css pixels. Pixels - * inside the radius around the given position will be checked for features. - * @property {boolean} [checkWrapped=true] Check-Wrapped Will check for for wrapped geometries inside the range of - * +/- 1 world width. Works only if a projection is used that can be wrapped. - */ -/** - * @typedef {Object} MapOptionsInternal - * @property {Collection} [controls] - * @property {Collection} [interactions] - * @property {HTMLElement|Document} keyboardEventTarget - * @property {Collection} overlays - * @property {Object} values - */ -/** - * Object literal with config options for the map. - * @typedef {Object} MapOptions - * @property {Collection|Array} [controls] - * Controls initially added to the map. If not specified, - * {@link module:ol/control~defaults} is used. - * @property {number} [pixelRatio=window.devicePixelRatio] The ratio between - * physical pixels and device-independent pixels (dips) on the device. - * @property {Collection|Array} [interactions] - * Interactions that are initially added to the map. If not specified, - * {@link module:ol/interaction~defaults} is used. - * @property {HTMLElement|Document|string} [keyboardEventTarget] The element to - * listen to keyboard events on. This determines when the `KeyboardPan` and - * `KeyboardZoom` interactions trigger. For example, if this option is set to - * `document` the keyboard interactions will always trigger. If this option is - * not specified, the element the library listens to keyboard events on is the - * map target (i.e. the user-provided div for the map). If this is not - * `document`, the target element needs to be focused for key events to be - * emitted, requiring that the target element has a `tabindex` attribute. - * @property {Array|Collection|LayerGroup} [layers] - * Layers. If this is not defined, a map with no layers will be rendered. Note - * that layers are rendered in the order supplied, so if you want, for example, - * a vector layer to appear on top of a tile layer, it must come after the tile - * layer. - * @property {number} [maxTilesLoading=16] Maximum number tiles to load - * simultaneously. - * @property {number} [moveTolerance=1] The minimum distance in pixels the - * cursor must move to be detected as a map move event instead of a click. - * Increasing this value can make it easier to click on the map. - * @property {Collection|Array} [overlays] - * Overlays initially added to the map. By default, no overlays are added. - * @property {HTMLElement|string} [target] The container for the map, either the - * element itself or the `id` of the element. If not specified at construction - * time, {@link module:ol/Map~Map#setTarget} must be called for the map to be - * rendered. If passed by element, the container can be in a secondary document. - * @property {View} [view] The map's view. No layer sources will be - * fetched unless this is specified at construction time or through - * {@link module:ol/Map~Map#setView}. - */ -/** - * @fires import("./MapBrowserEvent.js").MapBrowserEvent - * @fires import("./MapEvent.js").MapEvent - * @fires import("./render/Event.js").default#precompose - * @fires import("./render/Event.js").default#postcompose - * @fires import("./render/Event.js").default#rendercomplete - * @api - */ -var PluggableMap = /** @class */ (function (_super) { - __extends$n(PluggableMap, _super); - /** - * @param {MapOptions} options Map options. - */ - function PluggableMap(options) { - var _this = _super.call(this) || this; - var optionsInternal = createOptionsInternal(options); - /** @private */ - _this.boundHandleBrowserEvent_ = _this.handleBrowserEvent.bind(_this); - /** - * @type {number} - * @private - */ - _this.maxTilesLoading_ = - options.maxTilesLoading !== undefined ? options.maxTilesLoading : 16; - /** - * @private - * @type {number} - */ - _this.pixelRatio_ = - options.pixelRatio !== undefined - ? options.pixelRatio - : DEVICE_PIXEL_RATIO; - /** - * @private - * @type {*} - */ - _this.postRenderTimeoutHandle_; - /** - * @private - * @type {number|undefined} - */ - _this.animationDelayKey_; - /** - * @private - */ - _this.animationDelay_ = /** @this {PluggableMap} */ function () { - this.animationDelayKey_ = undefined; - this.renderFrame_(Date.now()); - }.bind(_this); - /** - * @private - * @type {import("./transform.js").Transform} - */ - _this.coordinateToPixelTransform_ = create(); - /** - * @private - * @type {import("./transform.js").Transform} - */ - _this.pixelToCoordinateTransform_ = create(); - /** - * @private - * @type {number} - */ - _this.frameIndex_ = 0; - /** - * @private - * @type {?FrameState} - */ - _this.frameState_ = null; - /** - * The extent at the previous 'moveend' event. - * @private - * @type {import("./extent.js").Extent} - */ - _this.previousExtent_ = null; - /** - * @private - * @type {?import("./events.js").EventsKey} - */ - _this.viewPropertyListenerKey_ = null; - /** - * @private - * @type {?import("./events.js").EventsKey} - */ - _this.viewChangeListenerKey_ = null; - /** - * @private - * @type {?Array} - */ - _this.layerGroupPropertyListenerKeys_ = null; - /** - * @private - * @type {!HTMLElement} - */ - _this.viewport_ = document.createElement('div'); - _this.viewport_.className = - 'ol-viewport' + ('ontouchstart' in window ? ' ol-touch' : ''); - _this.viewport_.style.position = 'relative'; - _this.viewport_.style.overflow = 'hidden'; - _this.viewport_.style.width = '100%'; - _this.viewport_.style.height = '100%'; - /** - * @private - * @type {!HTMLElement} - */ - _this.overlayContainer_ = document.createElement('div'); - _this.overlayContainer_.style.position = 'absolute'; - _this.overlayContainer_.style.zIndex = '0'; - _this.overlayContainer_.style.width = '100%'; - _this.overlayContainer_.style.height = '100%'; - _this.overlayContainer_.style.pointerEvents = 'none'; - _this.overlayContainer_.className = 'ol-overlaycontainer'; - _this.viewport_.appendChild(_this.overlayContainer_); - /** - * @private - * @type {!HTMLElement} - */ - _this.overlayContainerStopEvent_ = document.createElement('div'); - _this.overlayContainerStopEvent_.style.position = 'absolute'; - _this.overlayContainerStopEvent_.style.zIndex = '0'; - _this.overlayContainerStopEvent_.style.width = '100%'; - _this.overlayContainerStopEvent_.style.height = '100%'; - _this.overlayContainerStopEvent_.style.pointerEvents = 'none'; - _this.overlayContainerStopEvent_.className = 'ol-overlaycontainer-stopevent'; - _this.viewport_.appendChild(_this.overlayContainerStopEvent_); - /** - * @private - * @type {MapBrowserEventHandler} - */ - _this.mapBrowserEventHandler_ = null; - /** - * @private - * @type {number} - */ - _this.moveTolerance_ = options.moveTolerance; - /** - * @private - * @type {HTMLElement|Document} - */ - _this.keyboardEventTarget_ = optionsInternal.keyboardEventTarget; - /** - * @private - * @type {?Array} - */ - _this.keyHandlerKeys_ = null; - /** - * @type {Collection} - * @protected - */ - _this.controls = optionsInternal.controls || new Collection(); - /** - * @type {Collection} - * @protected - */ - _this.interactions = optionsInternal.interactions || new Collection(); - /** - * @type {Collection} - * @private - */ - _this.overlays_ = optionsInternal.overlays; - /** - * A lookup of overlays by id. - * @private - * @type {Object} - */ - _this.overlayIdIndex_ = {}; - /** - * @type {import("./renderer/Map.js").default} - * @private - */ - _this.renderer_ = null; - /** - * @type {undefined|function(Event): void} - * @private - */ - _this.handleResize_; - /** - * @private - * @type {!Array} - */ - _this.postRenderFunctions_ = []; - /** - * @private - * @type {TileQueue} - */ - _this.tileQueue_ = new TileQueue(_this.getTilePriority.bind(_this), _this.handleTileChange_.bind(_this)); - _this.addEventListener(getChangeEventType(MapProperty.LAYERGROUP), _this.handleLayerGroupChanged_); - _this.addEventListener(getChangeEventType(MapProperty.VIEW), _this.handleViewChanged_); - _this.addEventListener(getChangeEventType(MapProperty.SIZE), _this.handleSizeChanged_); - _this.addEventListener(getChangeEventType(MapProperty.TARGET), _this.handleTargetChanged_); - // setProperties will trigger the rendering of the map if the map - // is "defined" already. - _this.setProperties(optionsInternal.values); - _this.controls.forEach( - /** - * @param {import("./control/Control.js").default} control Control. - * @this {PluggableMap} - */ - function (control) { - control.setMap(this); - }.bind(_this)); - _this.controls.addEventListener(CollectionEventType.ADD, - /** - * @param {import("./Collection.js").CollectionEvent} event CollectionEvent. - */ - function (event) { - event.element.setMap(this); - }.bind(_this)); - _this.controls.addEventListener(CollectionEventType.REMOVE, - /** - * @param {import("./Collection.js").CollectionEvent} event CollectionEvent. - */ - function (event) { - event.element.setMap(null); - }.bind(_this)); - _this.interactions.forEach( - /** - * @param {import("./interaction/Interaction.js").default} interaction Interaction. - * @this {PluggableMap} - */ - function (interaction) { - interaction.setMap(this); - }.bind(_this)); - _this.interactions.addEventListener(CollectionEventType.ADD, - /** - * @param {import("./Collection.js").CollectionEvent} event CollectionEvent. - */ - function (event) { - event.element.setMap(this); - }.bind(_this)); - _this.interactions.addEventListener(CollectionEventType.REMOVE, - /** - * @param {import("./Collection.js").CollectionEvent} event CollectionEvent. - */ - function (event) { - event.element.setMap(null); - }.bind(_this)); - _this.overlays_.forEach(_this.addOverlayInternal_.bind(_this)); - _this.overlays_.addEventListener(CollectionEventType.ADD, - /** - * @param {import("./Collection.js").CollectionEvent} event CollectionEvent. - */ - function (event) { - this.addOverlayInternal_( - /** @type {import("./Overlay.js").default} */ (event.element)); - }.bind(_this)); - _this.overlays_.addEventListener(CollectionEventType.REMOVE, - /** - * @param {import("./Collection.js").CollectionEvent} event CollectionEvent. - */ - function (event) { - var overlay = /** @type {import("./Overlay.js").default} */ (event.element); - var id = overlay.getId(); - if (id !== undefined) { - delete this.overlayIdIndex_[id.toString()]; - } - event.element.setMap(null); - }.bind(_this)); - return _this; - } - /** - * @abstract - * @return {import("./renderer/Map.js").default} The map renderer - */ - PluggableMap.prototype.createRenderer = function () { - throw new Error('Use a map type that has a createRenderer method'); - }; - /** - * Add the given control to the map. - * @param {import("./control/Control.js").default} control Control. - * @api - */ - PluggableMap.prototype.addControl = function (control) { - this.getControls().push(control); - }; - /** - * Add the given interaction to the map. If you want to add an interaction - * at another point of the collection use `getInteraction()` and the methods - * available on {@link module:ol/Collection~Collection}. This can be used to - * stop the event propagation from the handleEvent function. The interactions - * get to handle the events in the reverse order of this collection. - * @param {import("./interaction/Interaction.js").default} interaction Interaction to add. - * @api - */ - PluggableMap.prototype.addInteraction = function (interaction) { - this.getInteractions().push(interaction); - }; - /** - * Adds the given layer to the top of this map. If you want to add a layer - * elsewhere in the stack, use `getLayers()` and the methods available on - * {@link module:ol/Collection~Collection}. - * @param {import("./layer/Base.js").default} layer Layer. - * @api - */ - PluggableMap.prototype.addLayer = function (layer) { - var layers = this.getLayerGroup().getLayers(); - layers.push(layer); - }; - /** - * Add the given overlay to the map. - * @param {import("./Overlay.js").default} overlay Overlay. - * @api - */ - PluggableMap.prototype.addOverlay = function (overlay) { - this.getOverlays().push(overlay); - }; - /** - * This deals with map's overlay collection changes. - * @param {import("./Overlay.js").default} overlay Overlay. - * @private - */ - PluggableMap.prototype.addOverlayInternal_ = function (overlay) { - var id = overlay.getId(); - if (id !== undefined) { - this.overlayIdIndex_[id.toString()] = overlay; - } - overlay.setMap(this); - }; - /** - * - * Clean up. - */ - PluggableMap.prototype.disposeInternal = function () { - this.setTarget(null); - _super.prototype.disposeInternal.call(this); - }; - /** - * Detect features that intersect a pixel on the viewport, and execute a - * callback with each intersecting feature. Layers included in the detection can - * be configured through the `layerFilter` option in `opt_options`. - * @param {import("./pixel.js").Pixel} pixel Pixel. - * @param {function(import("./Feature.js").FeatureLike, import("./layer/Layer.js").default, import("./geom/SimpleGeometry.js").default): T} callback Feature callback. The callback will be - * called with two arguments. The first argument is one - * {@link module:ol/Feature feature} or - * {@link module:ol/render/Feature render feature} at the pixel, the second is - * the {@link module:ol/layer/Layer layer} of the feature and will be null for - * unmanaged layers. To stop detection, callback functions can return a - * truthy value. - * @param {AtPixelOptions=} opt_options Optional options. - * @return {T|undefined} Callback result, i.e. the return value of last - * callback execution, or the first truthy callback return value. - * @template S,T - * @api - */ - PluggableMap.prototype.forEachFeatureAtPixel = function (pixel, callback, opt_options) { - if (!this.frameState_) { - return; - } - var coordinate = this.getCoordinateFromPixelInternal(pixel); - opt_options = opt_options !== undefined ? opt_options : {}; - var hitTolerance = opt_options.hitTolerance !== undefined ? opt_options.hitTolerance : 0; - var layerFilter = opt_options.layerFilter !== undefined ? opt_options.layerFilter : TRUE; - var checkWrapped = opt_options.checkWrapped !== false; - return this.renderer_.forEachFeatureAtCoordinate(coordinate, this.frameState_, hitTolerance, checkWrapped, callback, null, layerFilter, null); - }; - /** - * Get all features that intersect a pixel on the viewport. - * @param {import("./pixel.js").Pixel} pixel Pixel. - * @param {AtPixelOptions=} opt_options Optional options. - * @return {Array} The detected features or - * an empty array if none were found. - * @api - */ - PluggableMap.prototype.getFeaturesAtPixel = function (pixel, opt_options) { - var features = []; - this.forEachFeatureAtPixel(pixel, function (feature) { - features.push(feature); - }, opt_options); - return features; - }; - /** - * Detect layers that have a color value at a pixel on the viewport, and - * execute a callback with each matching layer. Layers included in the - * detection can be configured through `opt_layerFilter`. - * - * Note: this may give false positives unless the map layers have had different `className` - * properties assigned to them. - * - * @param {import("./pixel.js").Pixel} pixel Pixel. - * @param {function(this: S, import("./layer/Layer.js").default, (Uint8ClampedArray|Uint8Array)): T} callback - * Layer callback. This callback will receive two arguments: first is the - * {@link module:ol/layer/Layer layer}, second argument is an array representing - * [R, G, B, A] pixel values (0 - 255) and will be `null` for layer types - * that do not currently support this argument. To stop detection, callback - * functions can return a truthy value. - * @param {AtPixelOptions=} opt_options Configuration options. - * @return {T|undefined} Callback result, i.e. the return value of last - * callback execution, or the first truthy callback return value. - * @template S,T - * @api - */ - PluggableMap.prototype.forEachLayerAtPixel = function (pixel, callback, opt_options) { - if (!this.frameState_) { - return; - } - var options = opt_options || {}; - var hitTolerance = options.hitTolerance !== undefined ? options.hitTolerance : 0; - var layerFilter = options.layerFilter || TRUE; - return this.renderer_.forEachLayerAtPixel(pixel, this.frameState_, hitTolerance, callback, layerFilter); - }; - /** - * Detect if features intersect a pixel on the viewport. Layers included in the - * detection can be configured through `opt_layerFilter`. - * @param {import("./pixel.js").Pixel} pixel Pixel. - * @param {AtPixelOptions=} opt_options Optional options. - * @return {boolean} Is there a feature at the given pixel? - * @api - */ - PluggableMap.prototype.hasFeatureAtPixel = function (pixel, opt_options) { - if (!this.frameState_) { - return false; - } - var coordinate = this.getCoordinateFromPixelInternal(pixel); - opt_options = opt_options !== undefined ? opt_options : {}; - var layerFilter = opt_options.layerFilter !== undefined ? opt_options.layerFilter : TRUE; - var hitTolerance = opt_options.hitTolerance !== undefined ? opt_options.hitTolerance : 0; - var checkWrapped = opt_options.checkWrapped !== false; - return this.renderer_.hasFeatureAtCoordinate(coordinate, this.frameState_, hitTolerance, checkWrapped, layerFilter, null); - }; - /** - * Returns the coordinate in user projection for a browser event. - * @param {MouseEvent} event Event. - * @return {import("./coordinate.js").Coordinate} Coordinate. - * @api - */ - PluggableMap.prototype.getEventCoordinate = function (event) { - return this.getCoordinateFromPixel(this.getEventPixel(event)); - }; - /** - * Returns the coordinate in view projection for a browser event. - * @param {MouseEvent} event Event. - * @return {import("./coordinate.js").Coordinate} Coordinate. - */ - PluggableMap.prototype.getEventCoordinateInternal = function (event) { - return this.getCoordinateFromPixelInternal(this.getEventPixel(event)); - }; - /** - * Returns the map pixel position for a browser event relative to the viewport. - * @param {UIEvent} event Event. - * @return {import("./pixel.js").Pixel} Pixel. - * @api - */ - PluggableMap.prototype.getEventPixel = function (event) { - var viewportPosition = this.viewport_.getBoundingClientRect(); - var eventPosition = - //FIXME Are we really calling this with a TouchEvent anywhere? - 'changedTouches' in event - ? /** @type {TouchEvent} */ (event).changedTouches[0] - : /** @type {MouseEvent} */ (event); - return [ - eventPosition.clientX - viewportPosition.left, - eventPosition.clientY - viewportPosition.top, - ]; - }; - /** - * Get the target in which this map is rendered. - * Note that this returns what is entered as an option or in setTarget: - * if that was an element, it returns an element; if a string, it returns that. - * @return {HTMLElement|string|undefined} The Element or id of the Element that the - * map is rendered in. - * @observable - * @api - */ - PluggableMap.prototype.getTarget = function () { - return /** @type {HTMLElement|string|undefined} */ (this.get(MapProperty.TARGET)); - }; - /** - * Get the DOM element into which this map is rendered. In contrast to - * `getTarget` this method always return an `Element`, or `null` if the - * map has no target. - * @return {HTMLElement} The element that the map is rendered in. - * @api - */ - PluggableMap.prototype.getTargetElement = function () { - var target = this.getTarget(); - if (target !== undefined) { - return typeof target === 'string' - ? document.getElementById(target) - : target; - } - else { - return null; - } - }; - /** - * Get the coordinate for a given pixel. This returns a coordinate in the - * user projection. - * @param {import("./pixel.js").Pixel} pixel Pixel position in the map viewport. - * @return {import("./coordinate.js").Coordinate} The coordinate for the pixel position. - * @api - */ - PluggableMap.prototype.getCoordinateFromPixel = function (pixel) { - return toUserCoordinate(this.getCoordinateFromPixelInternal(pixel), this.getView().getProjection()); - }; - /** - * Get the coordinate for a given pixel. This returns a coordinate in the - * map view projection. - * @param {import("./pixel.js").Pixel} pixel Pixel position in the map viewport. - * @return {import("./coordinate.js").Coordinate} The coordinate for the pixel position. - */ - PluggableMap.prototype.getCoordinateFromPixelInternal = function (pixel) { - var frameState = this.frameState_; - if (!frameState) { - return null; - } - else { - return apply(frameState.pixelToCoordinateTransform, pixel.slice()); - } - }; - /** - * Get the map controls. Modifying this collection changes the controls - * associated with the map. - * @return {Collection} Controls. - * @api - */ - PluggableMap.prototype.getControls = function () { - return this.controls; - }; - /** - * Get the map overlays. Modifying this collection changes the overlays - * associated with the map. - * @return {Collection} Overlays. - * @api - */ - PluggableMap.prototype.getOverlays = function () { - return this.overlays_; - }; - /** - * Get an overlay by its identifier (the value returned by overlay.getId()). - * Note that the index treats string and numeric identifiers as the same. So - * `map.getOverlayById(2)` will return an overlay with id `'2'` or `2`. - * @param {string|number} id Overlay identifier. - * @return {import("./Overlay.js").default} Overlay. - * @api - */ - PluggableMap.prototype.getOverlayById = function (id) { - var overlay = this.overlayIdIndex_[id.toString()]; - return overlay !== undefined ? overlay : null; - }; - /** - * Get the map interactions. Modifying this collection changes the interactions - * associated with the map. - * - * Interactions are used for e.g. pan, zoom and rotate. - * @return {Collection} Interactions. - * @api - */ - PluggableMap.prototype.getInteractions = function () { - return this.interactions; - }; - /** - * Get the layergroup associated with this map. - * @return {LayerGroup} A layer group containing the layers in this map. - * @observable - * @api - */ - PluggableMap.prototype.getLayerGroup = function () { - return /** @type {LayerGroup} */ (this.get(MapProperty.LAYERGROUP)); - }; - /** - * Get the collection of layers associated with this map. - * @return {!Collection} Layers. - * @api - */ - PluggableMap.prototype.getLayers = function () { - var layers = this.getLayerGroup().getLayers(); - return layers; - }; - /** - * @return {boolean} Layers have sources that are still loading. - */ - PluggableMap.prototype.getLoading = function () { - var layerStatesArray = this.getLayerGroup().getLayerStatesArray(); - for (var i = 0, ii = layerStatesArray.length; i < ii; ++i) { - var layer = layerStatesArray[i].layer; - var source = /** @type {import("./layer/Layer.js").default} */ (layer).getSource(); - if (source && source.loading) { - return true; - } - } - return false; - }; - /** - * Get the pixel for a coordinate. This takes a coordinate in the user - * projection and returns the corresponding pixel. - * @param {import("./coordinate.js").Coordinate} coordinate A map coordinate. - * @return {import("./pixel.js").Pixel} A pixel position in the map viewport. - * @api - */ - PluggableMap.prototype.getPixelFromCoordinate = function (coordinate) { - var viewCoordinate = fromUserCoordinate(coordinate, this.getView().getProjection()); - return this.getPixelFromCoordinateInternal(viewCoordinate); - }; - /** - * Get the pixel for a coordinate. This takes a coordinate in the map view - * projection and returns the corresponding pixel. - * @param {import("./coordinate.js").Coordinate} coordinate A map coordinate. - * @return {import("./pixel.js").Pixel} A pixel position in the map viewport. - */ - PluggableMap.prototype.getPixelFromCoordinateInternal = function (coordinate) { - var frameState = this.frameState_; - if (!frameState) { - return null; - } - else { - return apply(frameState.coordinateToPixelTransform, coordinate.slice(0, 2)); - } - }; - /** - * Get the map renderer. - * @return {import("./renderer/Map.js").default} Renderer - */ - PluggableMap.prototype.getRenderer = function () { - return this.renderer_; - }; - /** - * Get the size of this map. - * @return {import("./size.js").Size|undefined} The size in pixels of the map in the DOM. - * @observable - * @api - */ - PluggableMap.prototype.getSize = function () { - return /** @type {import("./size.js").Size|undefined} */ (this.get(MapProperty.SIZE)); - }; - /** - * Get the view associated with this map. A view manages properties such as - * center and resolution. - * @return {View} The view that controls this map. - * @observable - * @api - */ - PluggableMap.prototype.getView = function () { - return /** @type {View} */ (this.get(MapProperty.VIEW)); - }; - /** - * Get the element that serves as the map viewport. - * @return {HTMLElement} Viewport. - * @api - */ - PluggableMap.prototype.getViewport = function () { - return this.viewport_; - }; - /** - * Get the element that serves as the container for overlays. Elements added to - * this container will let mousedown and touchstart events through to the map, - * so clicks and gestures on an overlay will trigger {@link module:ol/MapBrowserEvent~MapBrowserEvent} - * events. - * @return {!HTMLElement} The map's overlay container. - */ - PluggableMap.prototype.getOverlayContainer = function () { - return this.overlayContainer_; - }; - /** - * Get the element that serves as a container for overlays that don't allow - * event propagation. Elements added to this container won't let mousedown and - * touchstart events through to the map, so clicks and gestures on an overlay - * don't trigger any {@link module:ol/MapBrowserEvent~MapBrowserEvent}. - * @return {!HTMLElement} The map's overlay container that stops events. - */ - PluggableMap.prototype.getOverlayContainerStopEvent = function () { - return this.overlayContainerStopEvent_; - }; - /** - * @return {!Document} The document where the map is displayed. - */ - PluggableMap.prototype.getOwnerDocument = function () { - return this.getTargetElement() - ? this.getTargetElement().ownerDocument - : document; - }; - /** - * @param {import("./Tile.js").default} tile Tile. - * @param {string} tileSourceKey Tile source key. - * @param {import("./coordinate.js").Coordinate} tileCenter Tile center. - * @param {number} tileResolution Tile resolution. - * @return {number} Tile priority. - */ - PluggableMap.prototype.getTilePriority = function (tile, tileSourceKey, tileCenter, tileResolution) { - return getTilePriority(this.frameState_, tile, tileSourceKey, tileCenter, tileResolution); - }; - /** - * @param {UIEvent} browserEvent Browser event. - * @param {string=} opt_type Type. - */ - PluggableMap.prototype.handleBrowserEvent = function (browserEvent, opt_type) { - var type = opt_type || browserEvent.type; - var mapBrowserEvent = new MapBrowserEvent(type, this, browserEvent); - this.handleMapBrowserEvent(mapBrowserEvent); - }; - /** - * @param {MapBrowserEvent} mapBrowserEvent The event to handle. - */ - PluggableMap.prototype.handleMapBrowserEvent = function (mapBrowserEvent) { - if (!this.frameState_) { - // With no view defined, we cannot translate pixels into geographical - // coordinates so interactions cannot be used. - return; - } - var originalEvent = /** @type {PointerEvent} */ (mapBrowserEvent.originalEvent); - var eventType = originalEvent.type; - if (eventType === PointerEventType.POINTERDOWN || - eventType === EventType.WHEEL || - eventType === EventType.KEYDOWN) { - var doc = this.getOwnerDocument(); - var rootNode = this.viewport_.getRootNode - ? this.viewport_.getRootNode() - : doc; - var target = 'host' in rootNode // ShadowRoot - ? /** @type {ShadowRoot} */ (rootNode).elementFromPoint(originalEvent.clientX, originalEvent.clientY) - : /** @type {Node} */ (originalEvent.target); - if ( - // Abort if the target is a child of the container for elements whose events are not meant - // to be handled by map interactions. - this.overlayContainerStopEvent_.contains(target) || - // Abort if the event target is a child of the container that is no longer in the page. - // It's possible for the target to no longer be in the page if it has been removed in an - // event listener, this might happen in a Control that recreates it's content based on - // user interaction either manually or via a render in something like https://reactjs.org/ - !(rootNode === doc ? doc.documentElement : rootNode).contains(target)) { - return; - } - } - mapBrowserEvent.frameState = this.frameState_; - if (this.dispatchEvent(mapBrowserEvent) !== false) { - var interactionsArray = this.getInteractions().getArray().slice(); - for (var i = interactionsArray.length - 1; i >= 0; i--) { - var interaction = interactionsArray[i]; - if (interaction.getMap() !== this || - !interaction.getActive() || - !this.getTargetElement()) { - continue; - } - var cont = interaction.handleEvent(mapBrowserEvent); - if (!cont || mapBrowserEvent.propagationStopped) { - break; - } - } - } - }; - /** - * @protected - */ - PluggableMap.prototype.handlePostRender = function () { - var frameState = this.frameState_; - // Manage the tile queue - // Image loads are expensive and a limited resource, so try to use them - // efficiently: - // * When the view is static we allow a large number of parallel tile loads - // to complete the frame as quickly as possible. - // * When animating or interacting, image loads can cause janks, so we reduce - // the maximum number of loads per frame and limit the number of parallel - // tile loads to remain reactive to view changes and to reduce the chance of - // loading tiles that will quickly disappear from view. - var tileQueue = this.tileQueue_; - if (!tileQueue.isEmpty()) { - var maxTotalLoading = this.maxTilesLoading_; - var maxNewLoads = maxTotalLoading; - if (frameState) { - var hints = frameState.viewHints; - if (hints[ViewHint.ANIMATING] || hints[ViewHint.INTERACTING]) { - var lowOnFrameBudget = !IMAGE_DECODE && Date.now() - frameState.time > 8; - maxTotalLoading = lowOnFrameBudget ? 0 : 8; - maxNewLoads = lowOnFrameBudget ? 0 : 2; - } - } - if (tileQueue.getTilesLoading() < maxTotalLoading) { - tileQueue.reprioritize(); // FIXME only call if view has changed - tileQueue.loadMoreTiles(maxTotalLoading, maxNewLoads); - } - } - if (frameState && - this.hasListener(RenderEventType.RENDERCOMPLETE) && - !frameState.animate && - !this.tileQueue_.getTilesLoading() && - !this.getLoading()) { - this.renderer_.dispatchRenderEvent(RenderEventType.RENDERCOMPLETE, frameState); - } - var postRenderFunctions = this.postRenderFunctions_; - for (var i = 0, ii = postRenderFunctions.length; i < ii; ++i) { - postRenderFunctions[i](this, frameState); - } - postRenderFunctions.length = 0; - }; - /** - * @private - */ - PluggableMap.prototype.handleSizeChanged_ = function () { - if (this.getView() && !this.getView().getAnimating()) { - this.getView().resolveConstraints(0); - } - this.render(); - }; - /** - * @private - */ - PluggableMap.prototype.handleTargetChanged_ = function () { - // target may be undefined, null, a string or an Element. - // If it's a string we convert it to an Element before proceeding. - // If it's not now an Element we remove the viewport from the DOM. - // If it's an Element we append the viewport element to it. - var targetElement; - if (this.getTarget()) { - targetElement = this.getTargetElement(); - } - if (this.mapBrowserEventHandler_) { - for (var i = 0, ii = this.keyHandlerKeys_.length; i < ii; ++i) { - unlistenByKey(this.keyHandlerKeys_[i]); - } - this.keyHandlerKeys_ = null; - this.viewport_.removeEventListener(EventType.CONTEXTMENU, this.boundHandleBrowserEvent_); - this.viewport_.removeEventListener(EventType.WHEEL, this.boundHandleBrowserEvent_); - if (this.handleResize_ !== undefined) { - removeEventListener(EventType.RESIZE, this.handleResize_, false); - this.handleResize_ = undefined; - } - this.mapBrowserEventHandler_.dispose(); - this.mapBrowserEventHandler_ = null; - removeNode(this.viewport_); - } - if (!targetElement) { - if (this.renderer_) { - clearTimeout(this.postRenderTimeoutHandle_); - this.postRenderTimeoutHandle_ = undefined; - this.postRenderFunctions_.length = 0; - this.renderer_.dispose(); - this.renderer_ = null; - } - if (this.animationDelayKey_) { - cancelAnimationFrame(this.animationDelayKey_); - this.animationDelayKey_ = undefined; - } - } - else { - targetElement.appendChild(this.viewport_); - if (!this.renderer_) { - this.renderer_ = this.createRenderer(); - } - this.mapBrowserEventHandler_ = new MapBrowserEventHandler(this, this.moveTolerance_); - for (var key in MapBrowserEventType) { - this.mapBrowserEventHandler_.addEventListener(MapBrowserEventType[key], this.handleMapBrowserEvent.bind(this)); - } - this.viewport_.addEventListener(EventType.CONTEXTMENU, this.boundHandleBrowserEvent_, false); - this.viewport_.addEventListener(EventType.WHEEL, this.boundHandleBrowserEvent_, PASSIVE_EVENT_LISTENERS ? { passive: false } : false); - var keyboardEventTarget = !this.keyboardEventTarget_ - ? targetElement - : this.keyboardEventTarget_; - this.keyHandlerKeys_ = [ - listen(keyboardEventTarget, EventType.KEYDOWN, this.handleBrowserEvent, this), - listen(keyboardEventTarget, EventType.KEYPRESS, this.handleBrowserEvent, this), - ]; - if (!this.handleResize_) { - this.handleResize_ = this.updateSize.bind(this); - window.addEventListener(EventType.RESIZE, this.handleResize_, false); - } - } - this.updateSize(); - // updateSize calls setSize, so no need to call this.render - // ourselves here. - }; - /** - * @private - */ - PluggableMap.prototype.handleTileChange_ = function () { - this.render(); - }; - /** - * @private - */ - PluggableMap.prototype.handleViewPropertyChanged_ = function () { - this.render(); - }; - /** - * @private - */ - PluggableMap.prototype.handleViewChanged_ = function () { - if (this.viewPropertyListenerKey_) { - unlistenByKey(this.viewPropertyListenerKey_); - this.viewPropertyListenerKey_ = null; - } - if (this.viewChangeListenerKey_) { - unlistenByKey(this.viewChangeListenerKey_); - this.viewChangeListenerKey_ = null; - } - var view = this.getView(); - if (view) { - this.updateViewportSize_(); - this.viewPropertyListenerKey_ = listen(view, ObjectEventType.PROPERTYCHANGE, this.handleViewPropertyChanged_, this); - this.viewChangeListenerKey_ = listen(view, EventType.CHANGE, this.handleViewPropertyChanged_, this); - view.resolveConstraints(0); - } - this.render(); - }; - /** - * @private - */ - PluggableMap.prototype.handleLayerGroupChanged_ = function () { - if (this.layerGroupPropertyListenerKeys_) { - this.layerGroupPropertyListenerKeys_.forEach(unlistenByKey); - this.layerGroupPropertyListenerKeys_ = null; - } - var layerGroup = this.getLayerGroup(); - if (layerGroup) { - this.layerGroupPropertyListenerKeys_ = [ - listen(layerGroup, ObjectEventType.PROPERTYCHANGE, this.render, this), - listen(layerGroup, EventType.CHANGE, this.render, this), - ]; - } - this.render(); - }; - /** - * @return {boolean} Is rendered. - */ - PluggableMap.prototype.isRendered = function () { - return !!this.frameState_; - }; - /** - * Requests an immediate render in a synchronous manner. - * @api - */ - PluggableMap.prototype.renderSync = function () { - if (this.animationDelayKey_) { - cancelAnimationFrame(this.animationDelayKey_); - } - this.animationDelay_(); - }; - /** - * Redraws all text after new fonts have loaded - */ - PluggableMap.prototype.redrawText = function () { - var layerStates = this.getLayerGroup().getLayerStatesArray(); - for (var i = 0, ii = layerStates.length; i < ii; ++i) { - var layer = layerStates[i].layer; - if (layer.hasRenderer()) { - layer.getRenderer().handleFontsChanged(); - } - } - }; - /** - * Request a map rendering (at the next animation frame). - * @api - */ - PluggableMap.prototype.render = function () { - if (this.renderer_ && this.animationDelayKey_ === undefined) { - this.animationDelayKey_ = requestAnimationFrame(this.animationDelay_); - } - }; - /** - * Remove the given control from the map. - * @param {import("./control/Control.js").default} control Control. - * @return {import("./control/Control.js").default|undefined} The removed control (or undefined - * if the control was not found). - * @api - */ - PluggableMap.prototype.removeControl = function (control) { - return this.getControls().remove(control); - }; - /** - * Remove the given interaction from the map. - * @param {import("./interaction/Interaction.js").default} interaction Interaction to remove. - * @return {import("./interaction/Interaction.js").default|undefined} The removed interaction (or - * undefined if the interaction was not found). - * @api - */ - PluggableMap.prototype.removeInteraction = function (interaction) { - return this.getInteractions().remove(interaction); - }; - /** - * Removes the given layer from the map. - * @param {import("./layer/Base.js").default} layer Layer. - * @return {import("./layer/Base.js").default|undefined} The removed layer (or undefined if the - * layer was not found). - * @api - */ - PluggableMap.prototype.removeLayer = function (layer) { - var layers = this.getLayerGroup().getLayers(); - return layers.remove(layer); - }; - /** - * Remove the given overlay from the map. - * @param {import("./Overlay.js").default} overlay Overlay. - * @return {import("./Overlay.js").default|undefined} The removed overlay (or undefined - * if the overlay was not found). - * @api - */ - PluggableMap.prototype.removeOverlay = function (overlay) { - return this.getOverlays().remove(overlay); - }; - /** - * @param {number} time Time. - * @private - */ - PluggableMap.prototype.renderFrame_ = function (time) { - var _this = this; - var size = this.getSize(); - var view = this.getView(); - var previousFrameState = this.frameState_; - /** @type {?FrameState} */ - var frameState = null; - if (size !== undefined && hasArea(size) && view && view.isDef()) { - var viewHints = view.getHints(this.frameState_ ? this.frameState_.viewHints : undefined); - var viewState = view.getState(); - frameState = { - animate: false, - coordinateToPixelTransform: this.coordinateToPixelTransform_, - declutterTree: null, - extent: getForViewAndSize(viewState.center, viewState.resolution, viewState.rotation, size), - index: this.frameIndex_++, - layerIndex: 0, - layerStatesArray: this.getLayerGroup().getLayerStatesArray(), - pixelRatio: this.pixelRatio_, - pixelToCoordinateTransform: this.pixelToCoordinateTransform_, - postRenderFunctions: [], - size: size, - tileQueue: this.tileQueue_, - time: time, - usedTiles: {}, - viewState: viewState, - viewHints: viewHints, - wantedTiles: {}, - }; - } - this.frameState_ = frameState; - this.renderer_.renderFrame(frameState); - if (frameState) { - if (frameState.animate) { - this.render(); - } - Array.prototype.push.apply(this.postRenderFunctions_, frameState.postRenderFunctions); - if (previousFrameState) { - var moveStart = !this.previousExtent_ || - (!isEmpty$1(this.previousExtent_) && - !equals$1(frameState.extent, this.previousExtent_)); - if (moveStart) { - this.dispatchEvent(new MapEvent(MapEventType.MOVESTART, this, previousFrameState)); - this.previousExtent_ = createOrUpdateEmpty(this.previousExtent_); - } - } - var idle = this.previousExtent_ && - !frameState.viewHints[ViewHint.ANIMATING] && - !frameState.viewHints[ViewHint.INTERACTING] && - !equals$1(frameState.extent, this.previousExtent_); - if (idle) { - this.dispatchEvent(new MapEvent(MapEventType.MOVEEND, this, frameState)); - clone(frameState.extent, this.previousExtent_); - } - } - this.dispatchEvent(new MapEvent(MapEventType.POSTRENDER, this, frameState)); - if (!this.postRenderTimeoutHandle_) { - this.postRenderTimeoutHandle_ = setTimeout(function () { - _this.postRenderTimeoutHandle_ = undefined; - _this.handlePostRender(); - }, 0); - } - }; - /** - * Sets the layergroup of this map. - * @param {LayerGroup} layerGroup A layer group containing the layers in this map. - * @observable - * @api - */ - PluggableMap.prototype.setLayerGroup = function (layerGroup) { - this.set(MapProperty.LAYERGROUP, layerGroup); - }; - /** - * Set the size of this map. - * @param {import("./size.js").Size|undefined} size The size in pixels of the map in the DOM. - * @observable - * @api - */ - PluggableMap.prototype.setSize = function (size) { - this.set(MapProperty.SIZE, size); - }; - /** - * Set the target element to render this map into. - * @param {HTMLElement|string|undefined} target The Element or id of the Element - * that the map is rendered in. - * @observable - * @api - */ - PluggableMap.prototype.setTarget = function (target) { - this.set(MapProperty.TARGET, target); - }; - /** - * Set the view for this map. - * @param {View} view The view that controls this map. - * @observable - * @api - */ - PluggableMap.prototype.setView = function (view) { - this.set(MapProperty.VIEW, view); - }; - /** - * Force a recalculation of the map viewport size. This should be called when - * third-party code changes the size of the map viewport. - * @api - */ - PluggableMap.prototype.updateSize = function () { - var targetElement = this.getTargetElement(); - if (!targetElement) { - this.setSize(undefined); - } - else { - var computedStyle = getComputedStyle(targetElement); - this.setSize([ - targetElement.offsetWidth - - parseFloat(computedStyle['borderLeftWidth']) - - parseFloat(computedStyle['paddingLeft']) - - parseFloat(computedStyle['paddingRight']) - - parseFloat(computedStyle['borderRightWidth']), - targetElement.offsetHeight - - parseFloat(computedStyle['borderTopWidth']) - - parseFloat(computedStyle['paddingTop']) - - parseFloat(computedStyle['paddingBottom']) - - parseFloat(computedStyle['borderBottomWidth']), - ]); - } - this.updateViewportSize_(); - }; - /** - * Recomputes the viewport size and save it on the view object (if any) - * @private - */ - PluggableMap.prototype.updateViewportSize_ = function () { - var view = this.getView(); - if (view) { - var size = undefined; - var computedStyle = getComputedStyle(this.viewport_); - if (computedStyle.width && computedStyle.height) { - size = [ - parseInt(computedStyle.width, 10), - parseInt(computedStyle.height, 10), - ]; - } - view.setViewportSize(size); - } - }; - return PluggableMap; -}(BaseObject)); -/** - * @param {MapOptions} options Map options. - * @return {MapOptionsInternal} Internal map options. - */ -function createOptionsInternal(options) { - /** - * @type {HTMLElement|Document} - */ - var keyboardEventTarget = null; - if (options.keyboardEventTarget !== undefined) { - keyboardEventTarget = - typeof options.keyboardEventTarget === 'string' - ? document.getElementById(options.keyboardEventTarget) - : options.keyboardEventTarget; - } - /** - * @type {Object} - */ - var values = {}; - var layerGroup = options.layers && - typeof ( /** @type {?} */(options.layers).getLayers) === 'function' - ? /** @type {LayerGroup} */ (options.layers) - : new LayerGroup({ layers: /** @type {Collection} */ (options.layers) }); - values[MapProperty.LAYERGROUP] = layerGroup; - values[MapProperty.TARGET] = options.target; - values[MapProperty.VIEW] = - options.view !== undefined ? options.view : new View(); - var controls; - if (options.controls !== undefined) { - if (Array.isArray(options.controls)) { - controls = new Collection(options.controls.slice()); - } - else { - assert(typeof ( /** @type {?} */(options.controls).getArray) === 'function', 47); // Expected `controls` to be an array or an `import("./Collection.js").Collection` - controls = /** @type {Collection} */ (options.controls); - } - } - var interactions; - if (options.interactions !== undefined) { - if (Array.isArray(options.interactions)) { - interactions = new Collection(options.interactions.slice()); - } - else { - assert(typeof ( /** @type {?} */(options.interactions).getArray) === - 'function', 48); // Expected `interactions` to be an array or an `import("./Collection.js").Collection` - interactions = /** @type {Collection} */ (options.interactions); - } - } - var overlays; - if (options.overlays !== undefined) { - if (Array.isArray(options.overlays)) { - overlays = new Collection(options.overlays.slice()); - } - else { - assert(typeof ( /** @type {?} */(options.overlays).getArray) === 'function', 49); // Expected `overlays` to be an array or an `import("./Collection.js").Collection` - overlays = options.overlays; - } - } - else { - overlays = new Collection(); - } - return { - controls: controls, - interactions: interactions, - keyboardEventTarget: keyboardEventTarget, - overlays: overlays, - values: values, - }; -} - -var __extends$o = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -/** - * @typedef {Object} Options - * @property {HTMLElement} [element] The element is the control's - * container element. This only needs to be specified if you're developing - * a custom control. - * @property {function(import("../MapEvent.js").default):void} [render] Function called when - * the control should be re-rendered. This is called in a `requestAnimationFrame` - * callback. - * @property {HTMLElement|string} [target] Specify a target if you want - * the control to be rendered outside of the map's viewport. - */ -/** - * @classdesc - * A control is a visible widget with a DOM element in a fixed position on the - * screen. They can involve user input (buttons), or be informational only; - * the position is determined using CSS. By default these are placed in the - * container with CSS class name `ol-overlaycontainer-stopevent`, but can use - * any outside DOM element. - * - * This is the base class for controls. You can use it for simple custom - * controls by creating the element with listeners, creating an instance: - * ```js - * var myControl = new Control({element: myElement}); - * ``` - * and then adding this to the map. - * - * The main advantage of having this as a control rather than a simple separate - * DOM element is that preventing propagation is handled for you. Controls - * will also be objects in a {@link module:ol/Collection~Collection}, so you can use their methods. - * - * You can also extend this base for your own control class. See - * examples/custom-controls for an example of how to do this. - * - * @api - */ -var Control = /** @class */ (function (_super) { - __extends$o(Control, _super); - /** - * @param {Options} options Control options. - */ - function Control(options) { - var _this = _super.call(this) || this; - var element = options.element; - if (element && !options.target && !element.style.pointerEvents) { - element.style.pointerEvents = 'auto'; - } - /** - * @protected - * @type {HTMLElement} - */ - _this.element = element ? element : null; - /** - * @private - * @type {HTMLElement} - */ - _this.target_ = null; - /** - * @private - * @type {import("../PluggableMap.js").default} - */ - _this.map_ = null; - /** - * @protected - * @type {!Array} - */ - _this.listenerKeys = []; - if (options.render) { - _this.render = options.render; - } - if (options.target) { - _this.setTarget(options.target); - } - return _this; - } - /** - * Clean up. - */ - Control.prototype.disposeInternal = function () { - removeNode(this.element); - _super.prototype.disposeInternal.call(this); - }; - /** - * Get the map associated with this control. - * @return {import("../PluggableMap.js").default} Map. - * @api - */ - Control.prototype.getMap = function () { - return this.map_; - }; - /** - * Remove the control from its current map and attach it to the new map. - * Subclasses may set up event handlers to get notified about changes to - * the map here. - * @param {import("../PluggableMap.js").default} map Map. - * @api - */ - Control.prototype.setMap = function (map) { - if (this.map_) { - removeNode(this.element); - } - for (var i = 0, ii = this.listenerKeys.length; i < ii; ++i) { - unlistenByKey(this.listenerKeys[i]); - } - this.listenerKeys.length = 0; - this.map_ = map; - if (this.map_) { - var target = this.target_ - ? this.target_ - : map.getOverlayContainerStopEvent(); - target.appendChild(this.element); - if (this.render !== VOID) { - this.listenerKeys.push(listen(map, MapEventType.POSTRENDER, this.render, this)); - } - map.render(); - } - }; - /** - * Renders the control. - * @param {import("../MapEvent.js").default} mapEvent Map event. - * @api - */ - Control.prototype.render = function (mapEvent) { }; - /** - * This function is used to set a target element for the control. It has no - * effect if it is called after the control has been added to the map (i.e. - * after `setMap` is called on the control). If no `target` is set in the - * options passed to the control constructor and if `setTarget` is not called - * then the control is added to the map's overlay container. - * @param {HTMLElement|string} target Target. - * @api - */ - Control.prototype.setTarget = function (target) { - this.target_ = - typeof target === 'string' ? document.getElementById(target) : target; - }; - return Control; -}(BaseObject)); - -var __extends$p = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -/** - * @typedef {Object} Options - * @property {string} [className='ol-attribution'] CSS class name. - * @property {HTMLElement|string} [target] Specify a target if you - * want the control to be rendered outside of the map's - * viewport. - * @property {boolean} [collapsible] Specify if attributions can - * be collapsed. If not specified, sources control this behavior with their - * `attributionsCollapsible` setting. - * @property {boolean} [collapsed=true] Specify if attributions should - * be collapsed at startup. - * @property {string} [tipLabel='Attributions'] Text label to use for the button tip. - * @property {string} [label='i'] Text label to use for the - * collapsed attributions button. - * Instead of text, also an element (e.g. a `span` element) can be used. - * @property {string} [expandClassName=className + '-expand'] CSS class name for the - * collapsed attributions button. - * @property {string|HTMLElement} [collapseLabel='»'] Text label to use - * for the expanded attributions button. - * Instead of text, also an element (e.g. a `span` element) can be used. - * @property {string} [collapseClassName=className + '-collapse'] CSS class name for the - * expanded attributions button. - * @property {function(import("../MapEvent.js").default):void} [render] Function called when - * the control should be re-rendered. This is called in a `requestAnimationFrame` - * callback. - */ -/** - * @classdesc - * Control to show all the attributions associated with the layer sources - * in the map. This control is one of the default controls included in maps. - * By default it will show in the bottom right portion of the map, but this can - * be changed by using a css selector for `.ol-attribution`. - * - * @api - */ -var Attribution = /** @class */ (function (_super) { - __extends$p(Attribution, _super); - /** - * @param {Options=} opt_options Attribution options. - */ - function Attribution(opt_options) { - var _this = this; - var options = opt_options ? opt_options : {}; - _this = _super.call(this, { - element: document.createElement('div'), - render: options.render, - target: options.target, - }) || this; - /** - * @private - * @type {HTMLElement} - */ - _this.ulElement_ = document.createElement('ul'); - /** - * @private - * @type {boolean} - */ - _this.collapsed_ = - options.collapsed !== undefined ? options.collapsed : true; - /** - * @private - * @type {boolean} - */ - _this.userCollapsed_ = _this.collapsed_; - /** - * @private - * @type {boolean} - */ - _this.overrideCollapsible_ = options.collapsible !== undefined; - /** - * @private - * @type {boolean} - */ - _this.collapsible_ = - options.collapsible !== undefined ? options.collapsible : true; - if (!_this.collapsible_) { - _this.collapsed_ = false; - } - var className = options.className !== undefined ? options.className : 'ol-attribution'; - var tipLabel = options.tipLabel !== undefined ? options.tipLabel : 'Attributions'; - var expandClassName = options.expandClassName !== undefined - ? options.expandClassName - : className + '-expand'; - var collapseLabel = options.collapseLabel !== undefined ? options.collapseLabel : '\u00BB'; - var collapseClassName = options.collapseClassName !== undefined - ? options.collapseClassName - : className + '-collpase'; - if (typeof collapseLabel === 'string') { - /** - * @private - * @type {HTMLElement} - */ - _this.collapseLabel_ = document.createElement('span'); - _this.collapseLabel_.textContent = collapseLabel; - _this.collapseLabel_.className = collapseClassName; - } - else { - _this.collapseLabel_ = collapseLabel; - } - var label = options.label !== undefined ? options.label : 'i'; - if (typeof label === 'string') { - /** - * @private - * @type {HTMLElement} - */ - _this.label_ = document.createElement('span'); - _this.label_.textContent = label; - _this.label_.className = expandClassName; - } - else { - _this.label_ = label; - } - var activeLabel = _this.collapsible_ && !_this.collapsed_ ? _this.collapseLabel_ : _this.label_; - var button = document.createElement('button'); - button.setAttribute('type', 'button'); - button.title = tipLabel; - button.appendChild(activeLabel); - button.addEventListener(EventType.CLICK, _this.handleClick_.bind(_this), false); - var cssClasses = className + - ' ' + - CLASS_UNSELECTABLE + - ' ' + - CLASS_CONTROL + - (_this.collapsed_ && _this.collapsible_ ? ' ' + CLASS_COLLAPSED : '') + - (_this.collapsible_ ? '' : ' ol-uncollapsible'); - var element = _this.element; - element.className = cssClasses; - element.appendChild(_this.ulElement_); - element.appendChild(button); - /** - * A list of currently rendered resolutions. - * @type {Array} - * @private - */ - _this.renderedAttributions_ = []; - /** - * @private - * @type {boolean} - */ - _this.renderedVisible_ = true; - return _this; - } - /** - * Collect a list of visible attributions and set the collapsible state. - * @param {import("../PluggableMap.js").FrameState} frameState Frame state. - * @return {Array} Attributions. - * @private - */ - Attribution.prototype.collectSourceAttributions_ = function (frameState) { - /** - * Used to determine if an attribution already exists. - * @type {!Object} - */ - var lookup = {}; - /** - * A list of visible attributions. - * @type {Array} - */ - var visibleAttributions = []; - var collapsible = true; - var layerStatesArray = frameState.layerStatesArray; - for (var i = 0, ii = layerStatesArray.length; i < ii; ++i) { - var layerState = layerStatesArray[i]; - if (!inView(layerState, frameState.viewState)) { - continue; - } - var source = /** @type {import("../layer/Layer.js").default} */ (layerState.layer).getSource(); - if (!source) { - continue; - } - var attributionGetter = source.getAttributions(); - if (!attributionGetter) { - continue; - } - var attributions = attributionGetter(frameState); - if (!attributions) { - continue; - } - collapsible = - collapsible && source.getAttributionsCollapsible() !== false; - if (Array.isArray(attributions)) { - for (var j = 0, jj = attributions.length; j < jj; ++j) { - if (!(attributions[j] in lookup)) { - visibleAttributions.push(attributions[j]); - lookup[attributions[j]] = true; - } - } - } - else { - if (!(attributions in lookup)) { - visibleAttributions.push(attributions); - lookup[attributions] = true; - } - } - } - if (!this.overrideCollapsible_) { - this.setCollapsible(collapsible); - } - return visibleAttributions; - }; - /** - * @private - * @param {?import("../PluggableMap.js").FrameState} frameState Frame state. - */ - Attribution.prototype.updateElement_ = function (frameState) { - if (!frameState) { - if (this.renderedVisible_) { - this.element.style.display = 'none'; - this.renderedVisible_ = false; - } - return; - } - var attributions = this.collectSourceAttributions_(frameState); - var visible = attributions.length > 0; - if (this.renderedVisible_ != visible) { - this.element.style.display = visible ? '' : 'none'; - this.renderedVisible_ = visible; - } - if (equals(attributions, this.renderedAttributions_)) { - return; - } - removeChildren(this.ulElement_); - // append the attributions - for (var i = 0, ii = attributions.length; i < ii; ++i) { - var element = document.createElement('li'); - element.innerHTML = attributions[i]; - this.ulElement_.appendChild(element); - } - this.renderedAttributions_ = attributions; - }; - /** - * @param {MouseEvent} event The event to handle - * @private - */ - Attribution.prototype.handleClick_ = function (event) { - event.preventDefault(); - this.handleToggle_(); - this.userCollapsed_ = this.collapsed_; - }; - /** - * @private - */ - Attribution.prototype.handleToggle_ = function () { - this.element.classList.toggle(CLASS_COLLAPSED); - if (this.collapsed_) { - replaceNode(this.collapseLabel_, this.label_); - } - else { - replaceNode(this.label_, this.collapseLabel_); - } - this.collapsed_ = !this.collapsed_; - }; - /** - * Return `true` if the attribution is collapsible, `false` otherwise. - * @return {boolean} True if the widget is collapsible. - * @api - */ - Attribution.prototype.getCollapsible = function () { - return this.collapsible_; - }; - /** - * Set whether the attribution should be collapsible. - * @param {boolean} collapsible True if the widget is collapsible. - * @api - */ - Attribution.prototype.setCollapsible = function (collapsible) { - if (this.collapsible_ === collapsible) { - return; - } - this.collapsible_ = collapsible; - this.element.classList.toggle('ol-uncollapsible'); - if (this.userCollapsed_) { - this.handleToggle_(); - } - }; - /** - * Collapse or expand the attribution according to the passed parameter. Will - * not do anything if the attribution isn't collapsible or if the current - * collapsed state is already the one requested. - * @param {boolean} collapsed True if the widget is collapsed. - * @api - */ - Attribution.prototype.setCollapsed = function (collapsed) { - this.userCollapsed_ = collapsed; - if (!this.collapsible_ || this.collapsed_ === collapsed) { - return; - } - this.handleToggle_(); - }; - /** - * Return `true` when the attribution is currently collapsed or `false` - * otherwise. - * @return {boolean} True if the widget is collapsed. - * @api - */ - Attribution.prototype.getCollapsed = function () { - return this.collapsed_; - }; - /** - * Update the attribution element. - * @param {import("../MapEvent.js").default} mapEvent Map event. - * @override - */ - Attribution.prototype.render = function (mapEvent) { - this.updateElement_(mapEvent.frameState); - }; - return Attribution; -}(Control)); - -var __extends$q = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -/** - * @typedef {Object} Options - * @property {string} [className='ol-rotate'] CSS class name. - * @property {string|HTMLElement} [label='⇧'] Text label to use for the rotate button. - * Instead of text, also an element (e.g. a `span` element) can be used. - * @property {string} [tipLabel='Reset rotation'] Text label to use for the rotate tip. - * @property {string} [compassClassName='ol-compass'] CSS class name for the compass. - * @property {number} [duration=250] Animation duration in milliseconds. - * @property {boolean} [autoHide=true] Hide the control when rotation is 0. - * @property {function(import("../MapEvent.js").default):void} [render] Function called when the control should - * be re-rendered. This is called in a `requestAnimationFrame` callback. - * @property {function():void} [resetNorth] Function called when the control is clicked. - * This will override the default `resetNorth`. - * @property {HTMLElement|string} [target] Specify a target if you want the control to be - * rendered outside of the map's viewport. - */ -/** - * @classdesc - * A button control to reset rotation to 0. - * To style this control use css selector `.ol-rotate`. A `.ol-hidden` css - * selector is added to the button when the rotation is 0. - * - * @api - */ -var Rotate = /** @class */ (function (_super) { - __extends$q(Rotate, _super); - /** - * @param {Options=} opt_options Rotate options. - */ - function Rotate(opt_options) { - var _this = this; - var options = opt_options ? opt_options : {}; - _this = _super.call(this, { - element: document.createElement('div'), - render: options.render, - target: options.target, - }) || this; - var className = options.className !== undefined ? options.className : 'ol-rotate'; - var label = options.label !== undefined ? options.label : '\u21E7'; - var compassClassName = options.compassClassName !== undefined - ? options.compassClassName - : 'ol-compass'; - /** - * @type {HTMLElement} - * @private - */ - _this.label_ = null; - if (typeof label === 'string') { - _this.label_ = document.createElement('span'); - _this.label_.className = compassClassName; - _this.label_.textContent = label; - } - else { - _this.label_ = label; - _this.label_.classList.add(compassClassName); - } - var tipLabel = options.tipLabel ? options.tipLabel : 'Reset rotation'; - var button = document.createElement('button'); - button.className = className + '-reset'; - button.setAttribute('type', 'button'); - button.title = tipLabel; - button.appendChild(_this.label_); - button.addEventListener(EventType.CLICK, _this.handleClick_.bind(_this), false); - var cssClasses = className + ' ' + CLASS_UNSELECTABLE + ' ' + CLASS_CONTROL; - var element = _this.element; - element.className = cssClasses; - element.appendChild(button); - _this.callResetNorth_ = options.resetNorth ? options.resetNorth : undefined; - /** - * @type {number} - * @private - */ - _this.duration_ = options.duration !== undefined ? options.duration : 250; - /** - * @type {boolean} - * @private - */ - _this.autoHide_ = options.autoHide !== undefined ? options.autoHide : true; - /** - * @private - * @type {number|undefined} - */ - _this.rotation_ = undefined; - if (_this.autoHide_) { - _this.element.classList.add(CLASS_HIDDEN); - } - return _this; - } - /** - * @param {MouseEvent} event The event to handle - * @private - */ - Rotate.prototype.handleClick_ = function (event) { - event.preventDefault(); - if (this.callResetNorth_ !== undefined) { - this.callResetNorth_(); - } - else { - this.resetNorth_(); - } - }; - /** - * @private - */ - Rotate.prototype.resetNorth_ = function () { - var map = this.getMap(); - var view = map.getView(); - if (!view) { - // the map does not have a view, so we can't act - // upon it - return; - } - var rotation = view.getRotation(); - if (rotation !== undefined) { - if (this.duration_ > 0 && rotation % (2 * Math.PI) !== 0) { - view.animate({ - rotation: 0, - duration: this.duration_, - easing: easeOut, - }); - } - else { - view.setRotation(0); - } - } - }; - /** - * Update the rotate control element. - * @param {import("../MapEvent.js").default} mapEvent Map event. - * @override - */ - Rotate.prototype.render = function (mapEvent) { - var frameState = mapEvent.frameState; - if (!frameState) { - return; - } - var rotation = frameState.viewState.rotation; - if (rotation != this.rotation_) { - var transform = 'rotate(' + rotation + 'rad)'; - if (this.autoHide_) { - var contains = this.element.classList.contains(CLASS_HIDDEN); - if (!contains && rotation === 0) { - this.element.classList.add(CLASS_HIDDEN); - } - else if (contains && rotation !== 0) { - this.element.classList.remove(CLASS_HIDDEN); - } - } - this.label_.style.transform = transform; - } - this.rotation_ = rotation; - }; - return Rotate; -}(Control)); - -var __extends$r = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -/** - * @typedef {Object} Options - * @property {number} [duration=250] Animation duration in milliseconds. - * @property {string} [className='ol-zoom'] CSS class name. - * @property {string} [zoomInClassName=className + '-in'] CSS class name for the zoom-in button. - * @property {string} [zoomOutClassName=className + '-out'] CSS class name for the zoom-out button. - * @property {string|HTMLElement} [zoomInLabel='+'] Text label to use for the zoom-in - * button. Instead of text, also an element (e.g. a `span` element) can be used. - * @property {string|HTMLElement} [zoomOutLabel='-'] Text label to use for the zoom-out button. - * Instead of text, also an element (e.g. a `span` element) can be used. - * @property {string} [zoomInTipLabel='Zoom in'] Text label to use for the button tip. - * @property {string} [zoomOutTipLabel='Zoom out'] Text label to use for the button tip. - * @property {number} [delta=1] The zoom delta applied on each click. - * @property {HTMLElement|string} [target] Specify a target if you want the control to be - * rendered outside of the map's viewport. - */ -/** - * @classdesc - * A control with 2 buttons, one for zoom in and one for zoom out. - * This control is one of the default controls of a map. To style this control - * use css selectors `.ol-zoom-in` and `.ol-zoom-out`. - * - * @api - */ -var Zoom = /** @class */ (function (_super) { - __extends$r(Zoom, _super); - /** - * @param {Options=} opt_options Zoom options. - */ - function Zoom(opt_options) { - var _this = this; - var options = opt_options ? opt_options : {}; - _this = _super.call(this, { - element: document.createElement('div'), - target: options.target, - }) || this; - var className = options.className !== undefined ? options.className : 'ol-zoom'; - var delta = options.delta !== undefined ? options.delta : 1; - var zoomInClassName = options.zoomInClassName !== undefined - ? options.zoomInClassName - : className + '-in'; - var zoomOutClassName = options.zoomOutClassName !== undefined - ? options.zoomOutClassName - : className + '-out'; - var zoomInLabel = options.zoomInLabel !== undefined ? options.zoomInLabel : '+'; - var zoomOutLabel = options.zoomOutLabel !== undefined ? options.zoomOutLabel : '\u2212'; - var zoomInTipLabel = options.zoomInTipLabel !== undefined ? options.zoomInTipLabel : 'Zoom in'; - var zoomOutTipLabel = options.zoomOutTipLabel !== undefined - ? options.zoomOutTipLabel - : 'Zoom out'; - var inElement = document.createElement('button'); - inElement.className = zoomInClassName; - inElement.setAttribute('type', 'button'); - inElement.title = zoomInTipLabel; - inElement.appendChild(typeof zoomInLabel === 'string' - ? document.createTextNode(zoomInLabel) - : zoomInLabel); - inElement.addEventListener(EventType.CLICK, _this.handleClick_.bind(_this, delta), false); - var outElement = document.createElement('button'); - outElement.className = zoomOutClassName; - outElement.setAttribute('type', 'button'); - outElement.title = zoomOutTipLabel; - outElement.appendChild(typeof zoomOutLabel === 'string' - ? document.createTextNode(zoomOutLabel) - : zoomOutLabel); - outElement.addEventListener(EventType.CLICK, _this.handleClick_.bind(_this, -delta), false); - var cssClasses = className + ' ' + CLASS_UNSELECTABLE + ' ' + CLASS_CONTROL; - var element = _this.element; - element.className = cssClasses; - element.appendChild(inElement); - element.appendChild(outElement); - /** - * @type {number} - * @private - */ - _this.duration_ = options.duration !== undefined ? options.duration : 250; - return _this; - } - /** - * @param {number} delta Zoom delta. - * @param {MouseEvent} event The event to handle - * @private - */ - Zoom.prototype.handleClick_ = function (delta, event) { - event.preventDefault(); - this.zoomByDelta_(delta); - }; - /** - * @param {number} delta Zoom delta. - * @private - */ - Zoom.prototype.zoomByDelta_ = function (delta) { - var map = this.getMap(); - var view = map.getView(); - if (!view) { - // the map does not have a view, so we can't act - // upon it - return; - } - var currentZoom = view.getZoom(); - if (currentZoom !== undefined) { - var newZoom = view.getConstrainedZoom(currentZoom + delta); - if (this.duration_ > 0) { - if (view.getAnimating()) { - view.cancelAnimations(); - } - view.animate({ - zoom: newZoom, - duration: this.duration_, - easing: easeOut, - }); - } - else { - view.setZoom(newZoom); - } - } - }; - return Zoom; -}(Control)); - -var __extends$s = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -var events = [ - 'fullscreenchange', - 'webkitfullscreenchange', - 'MSFullscreenChange', -]; -/** - * @enum {string} - */ -var FullScreenEventType = { - /** - * Triggered after the map entered fullscreen. - * @event FullScreenEventType#enterfullscreen - * @api - */ - ENTERFULLSCREEN: 'enterfullscreen', - /** - * Triggered after the map leave fullscreen. - * @event FullScreenEventType#leavefullscreen - * @api - */ - LEAVEFULLSCREEN: 'leavefullscreen', -}; -/** - * @typedef {Object} Options - * @property {string} [className='ol-full-screen'] CSS class name. - * @property {string|Text} [label='\u2922'] Text label to use for the button. - * Instead of text, also an element (e.g. a `span` element) can be used. - * @property {string|Text} [labelActive='\u00d7'] Text label to use for the - * button when full-screen is active. - * @property {string} [activeClassName=className + '-true'] CSS class name for the button - * when full-screen is active. - * @property {string} [inactiveClassName=className + '-false'] CSS class name for the button - * when full-screen is inactive. - * Instead of text, also an element (e.g. a `span` element) can be used. - * @property {string} [tipLabel='Toggle full-screen'] Text label to use for the button tip. - * @property {boolean} [keys=false] Full keyboard access. - * @property {HTMLElement|string} [target] Specify a target if you want the - * control to be rendered outside of the map's viewport. - * @property {HTMLElement|string} [source] The element to be displayed - * fullscreen. When not provided, the element containing the map viewport will - * be displayed fullscreen. - */ -/** - * @classdesc - * Provides a button that when clicked fills up the full screen with the map. - * The full screen source element is by default the element containing the map viewport unless - * overridden by providing the `source` option. In which case, the dom - * element introduced using this parameter will be displayed in full screen. - * - * When in full screen mode, a close button is shown to exit full screen mode. - * The [Fullscreen API](http://www.w3.org/TR/fullscreen/) is used to - * toggle the map in full screen mode. - * - * @fires FullScreenEventType#enterfullscreen - * @fires FullScreenEventType#leavefullscreen - * @api - */ -var FullScreen = /** @class */ (function (_super) { - __extends$s(FullScreen, _super); - /** - * @param {Options=} opt_options Options. - */ - function FullScreen(opt_options) { - var _this = this; - var options = opt_options ? opt_options : {}; - _this = _super.call(this, { - element: document.createElement('div'), - target: options.target, - }) || this; - /** - * @private - * @type {string} - */ - _this.cssClassName_ = - options.className !== undefined ? options.className : 'ol-full-screen'; - /** - * @private - * @type {Array} - */ - _this.activeClassName_ = - options.activeClassName !== undefined - ? options.activeClassName.split(' ') - : [_this.cssClassName_ + '-true']; - /** - * @private - * @type {Array} - */ - _this.inactiveClassName_ = - options.inactiveClassName !== undefined - ? options.inactiveClassName.split(' ') - : [_this.cssClassName_ + '-false']; - var label = options.label !== undefined ? options.label : '\u2922'; - /** - * @private - * @type {Text} - */ - _this.labelNode_ = - typeof label === 'string' ? document.createTextNode(label) : label; - var labelActive = options.labelActive !== undefined ? options.labelActive : '\u00d7'; - /** - * @private - * @type {Text} - */ - _this.labelActiveNode_ = - typeof labelActive === 'string' - ? document.createTextNode(labelActive) - : labelActive; - /** - * @private - * @type {HTMLElement} - */ - _this.button_ = document.createElement('button'); - var tipLabel = options.tipLabel ? options.tipLabel : 'Toggle full-screen'; - _this.setClassName_(_this.button_, isFullScreen()); - _this.button_.setAttribute('type', 'button'); - _this.button_.title = tipLabel; - _this.button_.appendChild(_this.labelNode_); - _this.button_.addEventListener(EventType.CLICK, _this.handleClick_.bind(_this), false); - var cssClasses = _this.cssClassName_ + - ' ' + - CLASS_UNSELECTABLE + - ' ' + - CLASS_CONTROL + - ' ' + - (!isFullScreenSupported() ? CLASS_UNSUPPORTED : ''); - var element = _this.element; - element.className = cssClasses; - element.appendChild(_this.button_); - /** - * @private - * @type {boolean} - */ - _this.keys_ = options.keys !== undefined ? options.keys : false; - /** - * @private - * @type {HTMLElement|string|undefined} - */ - _this.source_ = options.source; - return _this; - } - /** - * @param {MouseEvent} event The event to handle - * @private - */ - FullScreen.prototype.handleClick_ = function (event) { - event.preventDefault(); - this.handleFullScreen_(); - }; - /** - * @private - */ - FullScreen.prototype.handleFullScreen_ = function () { - if (!isFullScreenSupported()) { - return; - } - var map = this.getMap(); - if (!map) { - return; - } - if (isFullScreen()) { - exitFullScreen(); - } - else { - var element = void 0; - if (this.source_) { - element = - typeof this.source_ === 'string' - ? document.getElementById(this.source_) - : this.source_; - } - else { - element = map.getTargetElement(); - } - if (this.keys_) { - requestFullScreenWithKeys(element); - } - else { - requestFullScreen(element); - } - } - }; - /** - * @private - */ - FullScreen.prototype.handleFullScreenChange_ = function () { - var map = this.getMap(); - if (isFullScreen()) { - this.setClassName_(this.button_, true); - replaceNode(this.labelActiveNode_, this.labelNode_); - this.dispatchEvent(FullScreenEventType.ENTERFULLSCREEN); - } - else { - this.setClassName_(this.button_, false); - replaceNode(this.labelNode_, this.labelActiveNode_); - this.dispatchEvent(FullScreenEventType.LEAVEFULLSCREEN); - } - if (map) { - map.updateSize(); - } - }; - /** - * @param {HTMLElement} element Target element - * @param {boolean} fullscreen True if fullscreen class name should be active - * @private - */ - FullScreen.prototype.setClassName_ = function (element, fullscreen) { - var _a, _b, _c; - var activeClassName = this.activeClassName_; - var inactiveClassName = this.inactiveClassName_; - var nextClassName = fullscreen ? activeClassName : inactiveClassName; - (_a = element.classList).remove.apply(_a, activeClassName); - (_b = element.classList).remove.apply(_b, inactiveClassName); - (_c = element.classList).add.apply(_c, nextClassName); - }; - /** - * Remove the control from its current map and attach it to the new map. - * Subclasses may set up event handlers to get notified about changes to - * the map here. - * @param {import("../PluggableMap.js").default} map Map. - * @api - */ - FullScreen.prototype.setMap = function (map) { - _super.prototype.setMap.call(this, map); - if (map) { - for (var i = 0, ii = events.length; i < ii; ++i) { - this.listenerKeys.push(listen(document, events[i], this.handleFullScreenChange_, this)); - } - } - }; - return FullScreen; -}(Control)); -/** - * @return {boolean} Fullscreen is supported by the current platform. - */ -function isFullScreenSupported() { - var body = document.body; - return !!(body['webkitRequestFullscreen'] || - (body['msRequestFullscreen'] && document['msFullscreenEnabled']) || - (body.requestFullscreen && document.fullscreenEnabled)); -} -/** - * @return {boolean} Element is currently in fullscreen. - */ -function isFullScreen() { - return !!(document['webkitIsFullScreen'] || - document['msFullscreenElement'] || - document.fullscreenElement); -} -/** - * Request to fullscreen an element. - * @param {HTMLElement} element Element to request fullscreen - */ -function requestFullScreen(element) { - if (element.requestFullscreen) { - element.requestFullscreen(); - } - else if (element['msRequestFullscreen']) { - element['msRequestFullscreen'](); - } - else if (element['webkitRequestFullscreen']) { - element['webkitRequestFullscreen'](); - } -} -/** - * Request to fullscreen an element with keyboard input. - * @param {HTMLElement} element Element to request fullscreen - */ -function requestFullScreenWithKeys(element) { - if (element['webkitRequestFullscreen']) { - element['webkitRequestFullscreen'](); - } - else { - requestFullScreen(element); - } -} -/** - * Exit fullscreen. - */ -function exitFullScreen() { - if (document.exitFullscreen) { - document.exitFullscreen(); - } - else if (document['msExitFullscreen']) { - document['msExitFullscreen'](); - } - else if (document['webkitExitFullscreen']) { - document['webkitExitFullscreen'](); - } -} - -/** - * @module ol/OverlayPositioning - */ -/** - * Overlay position: `'bottom-left'`, `'bottom-center'`, `'bottom-right'`, - * `'center-left'`, `'center-center'`, `'center-right'`, `'top-left'`, - * `'top-center'`, `'top-right'` - * @enum {string} - */ -var OverlayPositioning = { - BOTTOM_LEFT: 'bottom-left', - BOTTOM_CENTER: 'bottom-center', - BOTTOM_RIGHT: 'bottom-right', - CENTER_LEFT: 'center-left', - CENTER_CENTER: 'center-center', - CENTER_RIGHT: 'center-right', - TOP_LEFT: 'top-left', - TOP_CENTER: 'top-center', - TOP_RIGHT: 'top-right', -}; - -var __extends$t = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -/** - * @typedef {Object} Options - * @property {number|string} [id] Set the overlay id. The overlay id can be used - * with the {@link module:ol/Map~Map#getOverlayById} method. - * @property {HTMLElement} [element] The overlay element. - * @property {Array} [offset=[0, 0]] Offsets in pixels used when positioning - * the overlay. The first element in the - * array is the horizontal offset. A positive value shifts the overlay right. - * The second element in the array is the vertical offset. A positive value - * shifts the overlay down. - * @property {import("./coordinate.js").Coordinate} [position] The overlay position - * in map projection. - * @property {import("./OverlayPositioning.js").default} [positioning='top-left'] Defines how - * the overlay is actually positioned with respect to its `position` property. - * Possible values are `'bottom-left'`, `'bottom-center'`, `'bottom-right'`, - * `'center-left'`, `'center-center'`, `'center-right'`, `'top-left'`, - * `'top-center'`, and `'top-right'`. - * @property {boolean} [stopEvent=true] Whether event propagation to the map - * viewport should be stopped. If `true` the overlay is placed in the same - * container as that of the controls (CSS class name - * `ol-overlaycontainer-stopevent`); if `false` it is placed in the container - * with CSS class name specified by the `className` property. - * @property {boolean} [insertFirst=true] Whether the overlay is inserted first - * in the overlay container, or appended. If the overlay is placed in the same - * container as that of the controls (see the `stopEvent` option) you will - * probably set `insertFirst` to `true` so the overlay is displayed below the - * controls. - * @property {PanIntoViewOptions|boolean} [autoPan=false] Pan the map when calling - * `setPosition`, so that the overlay is entirely visible in the current viewport? - * If `true` (deprecated), then `autoPanAnimation` and `autoPanMargin` will be - * used to determine the panning parameters; if an object is supplied then other - * parameters are ignored. - * @property {PanOptions} [autoPanAnimation] The animation options used to pan - * the overlay into view. This animation is only used when `autoPan` is enabled. - * A `duration` and `easing` may be provided to customize the animation. - * Deprecated and ignored if `autoPan` is supplied as an object. - * @property {number} [autoPanMargin=20] The margin (in pixels) between the - * overlay and the borders of the map when autopanning. Deprecated and ignored - * if `autoPan` is supplied as an object. - * @property {PanIntoViewOptions} [autoPanOptions] The options to use for the - * autoPan. This is only used when `autoPan` is enabled and has preference over - * the individual `autoPanMargin` and `autoPanOptions`. - * @property {string} [className='ol-overlay-container ol-selectable'] CSS class - * name. - */ -/** - * @typedef {Object} PanOptions - * @property {number} [duration=1000] The duration of the animation in - * milliseconds. - * @property {function(number):number} [easing] The easing function to use. Can - * be one from {@link module:ol/easing} or a custom function. - * Default is {@link module:ol/easing~inAndOut}. - */ -/** - * @typedef {Object} PanIntoViewOptions - * @property {PanOptions} [animation={}] The animation parameters for the pan - * @property {number} [margin=20] The margin (in pixels) between the - * overlay and the borders of the map when panning into view. - */ -/** - * @enum {string} - * @protected - */ -var Property$2 = { - ELEMENT: 'element', - MAP: 'map', - OFFSET: 'offset', - POSITION: 'position', - POSITIONING: 'positioning', -}; -/** - * @classdesc - * An element to be displayed over the map and attached to a single map - * location. Like {@link module:ol/control/Control~Control}, Overlays are - * visible widgets. Unlike Controls, they are not in a fixed position on the - * screen, but are tied to a geographical coordinate, so panning the map will - * move an Overlay but not a Control. - * - * Example: - * - * import Overlay from 'ol/Overlay'; - * - * var popup = new Overlay({ - * element: document.getElementById('popup') - * }); - * popup.setPosition(coordinate); - * map.addOverlay(popup); - * - * @api - */ -var Overlay = /** @class */ (function (_super) { - __extends$t(Overlay, _super); - /** - * @param {Options} options Overlay options. - */ - function Overlay(options) { - var _this = _super.call(this) || this; - /** - * @protected - * @type {Options} - */ - _this.options = options; - /** - * @protected - * @type {number|string|undefined} - */ - _this.id = options.id; - /** - * @protected - * @type {boolean} - */ - _this.insertFirst = - options.insertFirst !== undefined ? options.insertFirst : true; - /** - * @protected - * @type {boolean} - */ - _this.stopEvent = options.stopEvent !== undefined ? options.stopEvent : true; - /** - * @protected - * @type {HTMLElement} - */ - _this.element = document.createElement('div'); - _this.element.className = - options.className !== undefined - ? options.className - : 'ol-overlay-container ' + CLASS_SELECTABLE; - _this.element.style.position = 'absolute'; - _this.element.style.pointerEvents = 'auto'; - var autoPan = options.autoPan; - if (autoPan && 'object' !== typeof autoPan) { - autoPan = { - animation: options.autoPanAnimation, - margin: options.autoPanMargin, - }; - } - /** - * @protected - * @type {PanIntoViewOptions|false} - */ - _this.autoPan = /** @type {PanIntoViewOptions} */ (autoPan) || false; - /** - * @protected - * @type {{transform_: string, - * visible: boolean}} - */ - _this.rendered = { - transform_: '', - visible: true, - }; - /** - * @protected - * @type {?import("./events.js").EventsKey} - */ - _this.mapPostrenderListenerKey = null; - _this.addEventListener(getChangeEventType(Property$2.ELEMENT), _this.handleElementChanged); - _this.addEventListener(getChangeEventType(Property$2.MAP), _this.handleMapChanged); - _this.addEventListener(getChangeEventType(Property$2.OFFSET), _this.handleOffsetChanged); - _this.addEventListener(getChangeEventType(Property$2.POSITION), _this.handlePositionChanged); - _this.addEventListener(getChangeEventType(Property$2.POSITIONING), _this.handlePositioningChanged); - if (options.element !== undefined) { - _this.setElement(options.element); - } - _this.setOffset(options.offset !== undefined ? options.offset : [0, 0]); - _this.setPositioning(options.positioning !== undefined - ? /** @type {import("./OverlayPositioning.js").default} */ (options.positioning) - : OverlayPositioning.TOP_LEFT); - if (options.position !== undefined) { - _this.setPosition(options.position); - } - return _this; - } - /** - * Get the DOM element of this overlay. - * @return {HTMLElement|undefined} The Element containing the overlay. - * @observable - * @api - */ - Overlay.prototype.getElement = function () { - return /** @type {HTMLElement|undefined} */ (this.get(Property$2.ELEMENT)); - }; - /** - * Get the overlay identifier which is set on constructor. - * @return {number|string|undefined} Id. - * @api - */ - Overlay.prototype.getId = function () { - return this.id; - }; - /** - * Get the map associated with this overlay. - * @return {import("./PluggableMap.js").default|undefined} The map that the - * overlay is part of. - * @observable - * @api - */ - Overlay.prototype.getMap = function () { - return /** @type {import("./PluggableMap.js").default|undefined} */ (this.get(Property$2.MAP)); - }; - /** - * Get the offset of this overlay. - * @return {Array} The offset. - * @observable - * @api - */ - Overlay.prototype.getOffset = function () { - return /** @type {Array} */ (this.get(Property$2.OFFSET)); - }; - /** - * Get the current position of this overlay. - * @return {import("./coordinate.js").Coordinate|undefined} The spatial point that the overlay is - * anchored at. - * @observable - * @api - */ - Overlay.prototype.getPosition = function () { - return /** @type {import("./coordinate.js").Coordinate|undefined} */ (this.get(Property$2.POSITION)); - }; - /** - * Get the current positioning of this overlay. - * @return {import("./OverlayPositioning.js").default} How the overlay is positioned - * relative to its point on the map. - * @observable - * @api - */ - Overlay.prototype.getPositioning = function () { - return /** @type {import("./OverlayPositioning.js").default} */ (this.get(Property$2.POSITIONING)); - }; - /** - * @protected - */ - Overlay.prototype.handleElementChanged = function () { - removeChildren(this.element); - var element = this.getElement(); - if (element) { - this.element.appendChild(element); - } - }; - /** - * @protected - */ - Overlay.prototype.handleMapChanged = function () { - if (this.mapPostrenderListenerKey) { - removeNode(this.element); - unlistenByKey(this.mapPostrenderListenerKey); - this.mapPostrenderListenerKey = null; - } - var map = this.getMap(); - if (map) { - this.mapPostrenderListenerKey = listen(map, MapEventType.POSTRENDER, this.render, this); - this.updatePixelPosition(); - var container = this.stopEvent - ? map.getOverlayContainerStopEvent() - : map.getOverlayContainer(); - if (this.insertFirst) { - container.insertBefore(this.element, container.childNodes[0] || null); - } - else { - container.appendChild(this.element); - } - this.performAutoPan(); - } - }; - /** - * @protected - */ - Overlay.prototype.render = function () { - this.updatePixelPosition(); - }; - /** - * @protected - */ - Overlay.prototype.handleOffsetChanged = function () { - this.updatePixelPosition(); - }; - /** - * @protected - */ - Overlay.prototype.handlePositionChanged = function () { - this.updatePixelPosition(); - this.performAutoPan(); - }; - /** - * @protected - */ - Overlay.prototype.handlePositioningChanged = function () { - this.updatePixelPosition(); - }; - /** - * Set the DOM element to be associated with this overlay. - * @param {HTMLElement|undefined} element The Element containing the overlay. - * @observable - * @api - */ - Overlay.prototype.setElement = function (element) { - this.set(Property$2.ELEMENT, element); - }; - /** - * Set the map to be associated with this overlay. - * @param {import("./PluggableMap.js").default|undefined} map The map that the - * overlay is part of. - * @observable - * @api - */ - Overlay.prototype.setMap = function (map) { - this.set(Property$2.MAP, map); - }; - /** - * Set the offset for this overlay. - * @param {Array} offset Offset. - * @observable - * @api - */ - Overlay.prototype.setOffset = function (offset) { - this.set(Property$2.OFFSET, offset); - }; - /** - * Set the position for this overlay. If the position is `undefined` the - * overlay is hidden. - * @param {import("./coordinate.js").Coordinate|undefined} position The spatial point that the overlay - * is anchored at. - * @observable - * @api - */ - Overlay.prototype.setPosition = function (position) { - this.set(Property$2.POSITION, position); - }; - /** - * Pan the map so that the overlay is entirely visisble in the current viewport - * (if necessary) using the configured autoPan parameters - * @protected - */ - Overlay.prototype.performAutoPan = function () { - if (this.autoPan) { - this.panIntoView(this.autoPan); - } - }; - /** - * Pan the map so that the overlay is entirely visible in the current viewport - * (if necessary). - * @param {PanIntoViewOptions=} opt_panIntoViewOptions Options for the pan action - * @api - */ - Overlay.prototype.panIntoView = function (opt_panIntoViewOptions) { - var map = this.getMap(); - if (!map || !map.getTargetElement() || !this.get(Property$2.POSITION)) { - return; - } - var mapRect = this.getRect(map.getTargetElement(), map.getSize()); - var element = this.getElement(); - var overlayRect = this.getRect(element, [ - outerWidth(element), - outerHeight(element), - ]); - var panIntoViewOptions = opt_panIntoViewOptions || {}; - var myMargin = panIntoViewOptions.margin === undefined ? 20 : panIntoViewOptions.margin; - if (!containsExtent(mapRect, overlayRect)) { - // the overlay is not completely inside the viewport, so pan the map - var offsetLeft = overlayRect[0] - mapRect[0]; - var offsetRight = mapRect[2] - overlayRect[2]; - var offsetTop = overlayRect[1] - mapRect[1]; - var offsetBottom = mapRect[3] - overlayRect[3]; - var delta = [0, 0]; - if (offsetLeft < 0) { - // move map to the left - delta[0] = offsetLeft - myMargin; - } - else if (offsetRight < 0) { - // move map to the right - delta[0] = Math.abs(offsetRight) + myMargin; - } - if (offsetTop < 0) { - // move map up - delta[1] = offsetTop - myMargin; - } - else if (offsetBottom < 0) { - // move map down - delta[1] = Math.abs(offsetBottom) + myMargin; - } - if (delta[0] !== 0 || delta[1] !== 0) { - var center = /** @type {import("./coordinate.js").Coordinate} */ (map - .getView() - .getCenterInternal()); - var centerPx = map.getPixelFromCoordinateInternal(center); - if (!centerPx) { - return; - } - var newCenterPx = [centerPx[0] + delta[0], centerPx[1] + delta[1]]; - var panOptions = panIntoViewOptions.animation || {}; - map.getView().animateInternal({ - center: map.getCoordinateFromPixelInternal(newCenterPx), - duration: panOptions.duration, - easing: panOptions.easing, - }); - } - } - }; - /** - * Get the extent of an element relative to the document - * @param {HTMLElement} element The element. - * @param {import("./size.js").Size} size The size of the element. - * @return {import("./extent.js").Extent} The extent. - * @protected - */ - Overlay.prototype.getRect = function (element, size) { - var box = element.getBoundingClientRect(); - var offsetX = box.left + window.pageXOffset; - var offsetY = box.top + window.pageYOffset; - return [offsetX, offsetY, offsetX + size[0], offsetY + size[1]]; - }; - /** - * Set the positioning for this overlay. - * @param {import("./OverlayPositioning.js").default} positioning how the overlay is - * positioned relative to its point on the map. - * @observable - * @api - */ - Overlay.prototype.setPositioning = function (positioning) { - this.set(Property$2.POSITIONING, positioning); - }; - /** - * Modify the visibility of the element. - * @param {boolean} visible Element visibility. - * @protected - */ - Overlay.prototype.setVisible = function (visible) { - if (this.rendered.visible !== visible) { - this.element.style.display = visible ? '' : 'none'; - this.rendered.visible = visible; - } - }; - /** - * Update pixel position. - * @protected - */ - Overlay.prototype.updatePixelPosition = function () { - var map = this.getMap(); - var position = this.getPosition(); - if (!map || !map.isRendered() || !position) { - this.setVisible(false); - return; - } - var pixel = map.getPixelFromCoordinate(position); - var mapSize = map.getSize(); - this.updateRenderedPosition(pixel, mapSize); - }; - /** - * @param {import("./pixel.js").Pixel} pixel The pixel location. - * @param {import("./size.js").Size|undefined} mapSize The map size. - * @protected - */ - Overlay.prototype.updateRenderedPosition = function (pixel, mapSize) { - var style = this.element.style; - var offset = this.getOffset(); - var positioning = this.getPositioning(); - this.setVisible(true); - var x = Math.round(pixel[0] + offset[0]) + 'px'; - var y = Math.round(pixel[1] + offset[1]) + 'px'; - var posX = '0%'; - var posY = '0%'; - if (positioning == OverlayPositioning.BOTTOM_RIGHT || - positioning == OverlayPositioning.CENTER_RIGHT || - positioning == OverlayPositioning.TOP_RIGHT) { - posX = '-100%'; - } - else if (positioning == OverlayPositioning.BOTTOM_CENTER || - positioning == OverlayPositioning.CENTER_CENTER || - positioning == OverlayPositioning.TOP_CENTER) { - posX = '-50%'; - } - if (positioning == OverlayPositioning.BOTTOM_LEFT || - positioning == OverlayPositioning.BOTTOM_CENTER || - positioning == OverlayPositioning.BOTTOM_RIGHT) { - posY = '-100%'; - } - else if (positioning == OverlayPositioning.CENTER_LEFT || - positioning == OverlayPositioning.CENTER_CENTER || - positioning == OverlayPositioning.CENTER_RIGHT) { - posY = '-50%'; - } - var transform = "translate(" + posX + ", " + posY + ") translate(" + x + ", " + y + ")"; - if (this.rendered.transform_ != transform) { - this.rendered.transform_ = transform; - style.transform = transform; - // @ts-ignore IE9 - style.msTransform = transform; - } - }; - /** - * returns the options this Overlay has been created with - * @return {Options} overlay options - */ - Overlay.prototype.getOptions = function () { - return this.options; - }; - return Overlay; -}(BaseObject)); - -/** - * @module ol/control - */ -/** - * @typedef {Object} DefaultsOptions - * @property {boolean} [attribution=true] Include - * {@link module:ol/control/Attribution~Attribution}. - * @property {import("./control/Attribution.js").Options} [attributionOptions] - * Options for {@link module:ol/control/Attribution~Attribution}. - * @property {boolean} [rotate=true] Include - * {@link module:ol/control/Rotate~Rotate}. - * @property {import("./control/Rotate.js").Options} [rotateOptions] Options - * for {@link module:ol/control/Rotate~Rotate}. - * @property {boolean} [zoom] Include {@link module:ol/control/Zoom~Zoom}. - * @property {import("./control/Zoom.js").Options} [zoomOptions] Options for - * {@link module:ol/control/Zoom~Zoom}. - * @api - */ -/** - * Set of controls included in maps by default. Unless configured otherwise, - * this returns a collection containing an instance of each of the following - * controls: - * * {@link module:ol/control/Zoom~Zoom} - * * {@link module:ol/control/Rotate~Rotate} - * * {@link module:ol/control/Attribution~Attribution} - * - * @param {DefaultsOptions=} opt_options - * Defaults options. - * @return {Collection} - * Controls. - * @api - */ -function defaults(opt_options) { - var options = opt_options ? opt_options : {}; - var controls = new Collection(); - var zoomControl = options.zoom !== undefined ? options.zoom : true; - if (zoomControl) { - controls.push(new Zoom(options.zoomOptions)); - } - var rotateControl = options.rotate !== undefined ? options.rotate : true; - if (rotateControl) { - controls.push(new Rotate(options.rotateOptions)); - } - var attributionControl = options.attribution !== undefined ? options.attribution : true; - if (attributionControl) { - controls.push(new Attribution(options.attributionOptions)); - } - return controls; -} - -/** - * @module ol/interaction/Property - */ -/** - * @enum {string} - */ -var InteractionProperty = { - ACTIVE: 'active', -}; - -var __extends$u = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -/** - * Object literal with config options for interactions. - * @typedef {Object} InteractionOptions - * @property {function(import("../MapBrowserEvent.js").default):boolean} handleEvent - * Method called by the map to notify the interaction that a browser event was - * dispatched to the map. If the function returns a falsy value, propagation of - * the event to other interactions in the map's interactions chain will be - * prevented (this includes functions with no explicit return). The interactions - * are traversed in reverse order of the interactions collection of the map. - */ -/** - * @classdesc - * Abstract base class; normally only used for creating subclasses and not - * instantiated in apps. - * User actions that change the state of the map. Some are similar to controls, - * but are not associated with a DOM element. - * For example, {@link module:ol/interaction/KeyboardZoom~KeyboardZoom} is - * functionally the same as {@link module:ol/control/Zoom~Zoom}, but triggered - * by a keyboard event not a button element event. - * Although interactions do not have a DOM element, some of them do render - * vectors and so are visible on the screen. - * @api - */ -var Interaction = /** @class */ (function (_super) { - __extends$u(Interaction, _super); - /** - * @param {InteractionOptions=} opt_options Options. - */ - function Interaction(opt_options) { - var _this = _super.call(this) || this; - if (opt_options && opt_options.handleEvent) { - _this.handleEvent = opt_options.handleEvent; - } - /** - * @private - * @type {import("../PluggableMap.js").default} - */ - _this.map_ = null; - _this.setActive(true); - return _this; - } - /** - * Return whether the interaction is currently active. - * @return {boolean} `true` if the interaction is active, `false` otherwise. - * @observable - * @api - */ - Interaction.prototype.getActive = function () { - return /** @type {boolean} */ (this.get(InteractionProperty.ACTIVE)); - }; - /** - * Get the map associated with this interaction. - * @return {import("../PluggableMap.js").default} Map. - * @api - */ - Interaction.prototype.getMap = function () { - return this.map_; - }; - /** - * Handles the {@link module:ol/MapBrowserEvent map browser event}. - * @param {import("../MapBrowserEvent.js").default} mapBrowserEvent Map browser event. - * @return {boolean} `false` to stop event propagation. - * @api - */ - Interaction.prototype.handleEvent = function (mapBrowserEvent) { - return true; - }; - /** - * Activate or deactivate the interaction. - * @param {boolean} active Active. - * @observable - * @api - */ - Interaction.prototype.setActive = function (active) { - this.set(InteractionProperty.ACTIVE, active); - }; - /** - * Remove the interaction from its current map and attach it to the new map. - * Subclasses may set up event handlers to get notified about changes to - * the map here. - * @param {import("../PluggableMap.js").default} map Map. - */ - Interaction.prototype.setMap = function (map) { - this.map_ = map; - }; - return Interaction; -}(BaseObject)); -/** - * @param {import("../View.js").default} view View. - * @param {import("../coordinate.js").Coordinate} delta Delta. - * @param {number=} opt_duration Duration. - */ -function pan(view, delta, opt_duration) { - var currentCenter = view.getCenterInternal(); - if (currentCenter) { - var center = [currentCenter[0] + delta[0], currentCenter[1] + delta[1]]; - view.animateInternal({ - duration: opt_duration !== undefined ? opt_duration : 250, - easing: linear, - center: view.getConstrainedCenter(center), - }); - } -} -/** - * @param {import("../View.js").default} view View. - * @param {number} delta Delta from previous zoom level. - * @param {import("../coordinate.js").Coordinate=} opt_anchor Anchor coordinate in the user projection. - * @param {number=} opt_duration Duration. - */ -function zoomByDelta(view, delta, opt_anchor, opt_duration) { - var currentZoom = view.getZoom(); - if (currentZoom === undefined) { - return; - } - var newZoom = view.getConstrainedZoom(currentZoom + delta); - var newResolution = view.getResolutionForZoom(newZoom); - if (view.getAnimating()) { - view.cancelAnimations(); - } - view.animate({ - resolution: newResolution, - anchor: opt_anchor, - duration: opt_duration !== undefined ? opt_duration : 250, - easing: easeOut, - }); -} - -var __extends$v = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -/** - * @typedef {Object} Options - * @property {number} [duration=250] Animation duration in milliseconds. - * @property {number} [delta=1] The zoom delta applied on each double click. - */ -/** - * @classdesc - * Allows the user to zoom by double-clicking on the map. - * @api - */ -var DoubleClickZoom = /** @class */ (function (_super) { - __extends$v(DoubleClickZoom, _super); - /** - * @param {Options=} opt_options Options. - */ - function DoubleClickZoom(opt_options) { - var _this = _super.call(this) || this; - var options = opt_options ? opt_options : {}; - /** - * @private - * @type {number} - */ - _this.delta_ = options.delta ? options.delta : 1; - /** - * @private - * @type {number} - */ - _this.duration_ = options.duration !== undefined ? options.duration : 250; - return _this; - } - /** - * Handles the {@link module:ol/MapBrowserEvent map browser event} (if it was a - * doubleclick) and eventually zooms the map. - * @param {import("../MapBrowserEvent.js").default} mapBrowserEvent Map browser event. - * @return {boolean} `false` to stop event propagation. - */ - DoubleClickZoom.prototype.handleEvent = function (mapBrowserEvent) { - var stopEvent = false; - if (mapBrowserEvent.type == MapBrowserEventType.DBLCLICK) { - var browserEvent = /** @type {MouseEvent} */ (mapBrowserEvent.originalEvent); - var map = mapBrowserEvent.map; - var anchor = mapBrowserEvent.coordinate; - var delta = browserEvent.shiftKey ? -this.delta_ : this.delta_; - var view = map.getView(); - zoomByDelta(view, delta, anchor, this.duration_); - browserEvent.preventDefault(); - stopEvent = true; - } - return !stopEvent; - }; - return DoubleClickZoom; -}(Interaction)); - -var __extends$w = (undefined && undefined.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -/** - * @typedef {Object} Options - * @property {function(import("../MapBrowserEvent.js").default):boolean} [handleDownEvent] - * Function handling "down" events. If the function returns `true` then a drag - * sequence is started. - * @property {function(import("../MapBrowserEvent.js").default):void} [handleDragEvent] - * Function handling "drag" events. This function is called on "move" events - * during a drag sequence. - * @property {function(import("../MapBrowserEvent.js").default):boolean} [handleEvent] - * Method called by the map to notify the interaction that a browser event was - * dispatched to the map. The function may return `false` to prevent the - * propagation of the event to other interactions in the map's interactions - * chain. - * @property {function(import("../MapBrowserEvent.js").default):void} [handleMoveEvent] - * Function handling "move" events. This function is called on "move" events. - * This functions is also called during a drag sequence, so during a drag - * sequence both the `handleDragEvent` function and this function are called. - * If `handleDownEvent` is defined and it returns true this function will not - * be called during a drag sequence. - * @property {function(import("../MapBrowserEvent.js").default):boolean} [handleUpEvent] - * Function handling "up" events. If the function returns `false` then the - * current drag sequence is stopped. - * @property {function(boolean):boolean} [stopDown] - * Should the down event be propagated to other interactions, or should be - * stopped? - */ -/** - * @classdesc - * Base class that calls user-defined functions on `down`, `move` and `up` - * events. This class also manages "drag sequences". - * - * When the `handleDownEvent` user function returns `true` a drag sequence is - * started. During a drag sequence the `handleDragEvent` user function is - * called on `move` events. The drag sequence ends when the `handleUpEvent` - * user function is called and returns `false`. - * @api - */ -var PointerInteraction = /** @class */ (function (_super) { - __extends$w(PointerInteraction, _super); - /** - * @param {Options=} opt_options Options. - */ - function PointerInteraction(opt_options) { - var _this = this; - var options = opt_options ? opt_options : {}; - _this = _super.call(this, - /** @type {import("./Interaction.js").InteractionOptions} */ (options)) || this; - if (options.handleDownEvent) { - _this.handleDownEvent = options.handleDownEvent; - } - if (options.handleDragEvent) { - _this.handleDragEvent = options.handleDragEvent; - } - if (options.handleMoveEvent) { - _this.handleMoveEvent = options.handleMoveEvent; - } - if (options.handleUpEvent) { - _this.handleUpEvent = options.handleUpEvent; - } - if (options.stopDown) { - _this.stopDown = options.stopDown; - } - /** - * @type {boolean} - * @protected - */ - _this.handlingDownUpSequence = false; - /** - * @type {!Object} - * @private - */ - _this.trackedPointers_ = {}; - /** - * @type {Array} - * @protected - */ - _this.targetPointers = []; - return _this; - } - /** - * Returns the current number of pointers involved in the interaction, - * e.g. `2` when two fingers are used. - * @return {number} The number of pointers. - * @api - */ - PointerInteraction.prototype.getPointerCount = function () { - return this.targetPointers.length; - }; - /** - * Handle pointer down events. - * @param {import("../MapBrowserEvent.js").default} mapBrowserEvent Event. - * @return {boolean} If the event was consumed. - * @protected - */ - PointerInteraction.prototype.handleDownEvent = function (mapBrowserEvent) { - return false; - }; - /** - * Handle pointer drag events. - * @param {import("../MapBrowserEvent.js").default} mapBrowserEvent Event. - * @protected - */ - PointerInteraction.prototype.handleDragEvent = function (mapBrowserEvent) { }; - /** - * Handles the {@link module:ol/MapBrowserEvent map browser event} and may call into - * other functions, if event sequences like e.g. 'drag' or 'down-up' etc. are - * detected. - * @param {import("../MapBrowserEvent.js").default} mapBrowserEvent Map browser event. - * @return {boolean} `false` to stop event propagation. - * @api - */ - PointerInteraction.prototype.handleEvent = function (mapBrowserEvent) { - if (!mapBrowserEvent.originalEvent) { - return true; - } - var stopEvent = false; - this.updateTrackedPointers_(mapBrowserEvent); - if (this.handlingDownUpSequence) { - if (mapBrowserEvent.type == MapBrowserEventType.POINTERDRAG) { - this.handleDragEvent(mapBrowserEvent); - // prevent page scrolling during dragging - mapBrowserEvent.originalEvent.preventDefault(); - } - else if (mapBrowserEvent.type == MapBrowserEventType.POINTERUP) { - var handledUp = this.handleUpEvent(mapBrowserEvent); - this.handlingDownUpSequence = - handledUp && this.targetPointers.length > 0; - } - } - else { - if (mapBrowserEvent.type == MapBrowserEventType.POINTERDOWN) { - var handled = this.handleDownEvent(mapBrowserEvent); - this.handlingDownUpSequence = handled; - stopEvent = this.stopDown(handled); - } - else if (mapBrowserEvent.type == MapBrowserEventType.POINTERMOVE) { - this.handleMoveEvent(mapBrowserEvent); - } - } - return !stopEvent; - }; - /** - * Handle pointer move events. - * @param {import("../MapBrowserEvent.js").default} mapBrowserEvent Event. - * @protected - */ - PointerInteraction.prototype.handleMoveEvent = function (mapBrowserEvent) { }; - /** - * Handle pointer up events. - * @param {import("../MapBrowserEvent.js").default} mapBrowserEvent Event. - * @return {boolean} If the event was consumed. - * @protected - */ - PointerInteraction.prototype.handleUpEvent = function (mapBrowserEvent) { - return false; - }; - /** - * This function is used to determine if "down" events should be propagated - * to other interactions or should be stopped. - * @param {boolean} handled Was the event handled by the interaction? - * @return {boolean} Should the `down` event be stopped? - */ - PointerInteraction.prototype.stopDown = function (handled) { - return handled; - }; - /** - * @param {import("../MapBrowserEvent.js").default} mapBrowserEvent Event. - * @private - */ - PointerInteraction.prototype.updateTrackedPointers_ = function (mapBrowserEvent) { - if (isPointerDraggingEvent(mapBrowserEvent)) { - var event_1 = mapBrowserEvent.originalEvent; - var id = event_1.pointerId.toString(); - if (mapBrowserEvent.type == MapBrowserEventType.POINTERUP) { - delete this.trackedPointers_[id]; - } - else if (mapBrowserEvent.type == MapBrowserEventType.POINTERDOWN) { - this.trackedPointers_[id] = event_1; - } - else if (id in this.trackedPointers_) { - // update only when there was a pointerdown event for this pointer - this.trackedPointers_[id] = event_1; - } - this.targetPointers = getValues(this.trackedPointers_); - } - }; - return PointerInteraction; -}(Interaction)); -/** - * @param {Array} pointerEvents List of events. - * @return {import("../pixel.js").Pixel} Centroid pixel. - */ -function centroid(pointerEvents) { - var length = pointerEvents.length; - var clientX = 0; - var clientY = 0; - for (var i = 0; i < length; i++) { - clientX += pointerEvents[i].clientX; - clientY += pointerEvents[i].clientY; - } - return [clientX / length, clientY / length]; -} -/** - * @param {import("../MapBrowserEvent.js").default} mapBrowserEvent Event. - * @return {boolean} Whether the event is a pointerdown, pointerdrag - * or pointerup event. - */ -function isPointerDraggingEvent(mapBrowserEvent) { - var type = mapBrowserEvent.type; - return (type === MapBrowserEventType.POINTERDOWN || - type === MapBrowserEventType.POINTERDRAG || - type === MapBrowserEventType.POINTERUP); -} - -/** - * @module ol/events/condition - */ -/** - * A function that takes an {@link module:ol/MapBrowserEvent} and returns a - * `{boolean}`. If the condition is met, true should be returned. - * - * @typedef {function(this: ?, import("../MapBrowserEvent.js").default): boolean} Condition - */ -/** - * Creates a condition function that passes when all provided conditions pass. - * @param {...Condition} var_args Conditions to check. - * @return {Condition} Condition function. - */ -function all(var_args) { - var conditions = arguments; - /** - * @param {import("../MapBrowserEvent.js").default} event Event. - * @return {boolean} All conditions passed. - */ - return function (event) { - var pass = true; - for (var i = 0, ii = conditions.length; i < ii; ++i) { - pass = pass && conditions[i](event); - if (!pass) { - break; - } - } - return pass; - }; -} -/** - * Return `true` if only the alt-key and shift-key is pressed, `false` otherwise - * (e.g. when additionally the platform-modifier-key is pressed). - * - * @param {import("../MapBrowserEvent.js").default} mapBrowserEvent Map browser event. - * @return {boolean} True if only the alt and shift keys are pressed. - * @api - */ -var altShiftKeysOnly = function (mapBrowserEvent) { - var originalEvent = /** @type {KeyboardEvent|MouseEvent|TouchEvent} */ (mapBrowserEvent.originalEvent); - return (originalEvent.altKey && - !(originalEvent.metaKey || originalEvent.ctrlKey) && - originalEvent.shiftKey); -}; -/** - * Return `true` if the map has the focus. This condition requires a map target - * element with a `tabindex` attribute, e.g. `
`. - * - * @param {import("../MapBrowserEvent.js").default} event Map browser event. - * @return {boolean} The map has the focus. - * @api - */ -var focus = function (event) { - return event.target.getTargetElement().contains(document.activeElement); -}; -/** - * Return `true` if the map has the focus or no 'tabindex' attribute set. - * - * @param {import("../MapBrowserEvent.js").default} event Map browser event. - * @return {boolean} The map container has the focus or no 'tabindex' attribute. - */ -var focusWithTabindex = function (event) { - return event.map.getTargetElement().hasAttribute('tabindex') - ? focus(event) - : true; -}; -/** - * Return always true. - * - * @param {import("../MapBrowserEvent.js").default} mapBrowserEvent Map browser event. - * @return {boolean} True. - * @api - */ -var always = TRUE; -/** - * Return `true` if the event has an "action"-producing mouse button. - * - * By definition, this includes left-click on windows/linux, and left-click - * without the ctrl key on Macs. - * - * @param {import("../MapBrowserEvent.js").default} mapBrowserEvent Map browser event. - * @return {boolean} The result. - */ -var mouseActionButton = function (mapBrowserEvent) { - var originalEvent = /** @type {MouseEvent} */ (mapBrowserEvent.originalEvent); - return originalEvent.button == 0 && !(WEBKIT && MAC && originalEvent.ctrlKey); -}; -/** - * Return `true` if no modifier key (alt-, shift- or platform-modifier-key) is - * pressed. - * - * @param {import("../MapBrowserEvent.js").default} mapBrowserEvent Map browser event. - * @return {boolean} True only if there no modifier keys are pressed. - * @api - */ -var noModifierKeys = function (mapBrowserEvent) { - var originalEvent = /** @type {KeyboardEvent|MouseEvent|TouchEvent} */ (mapBrowserEvent.originalEvent); - return (!originalEvent.altKey && - !(originalEvent.metaKey || originalEvent.ctrlKey) && - !originalEvent.shiftKey); -}; -/** - * Return `true` if only the shift-key is pressed, `false` otherwise (e.g. when - * additionally the alt-key is pressed). - * - * @param {import("../MapBrowserEvent.js").default} mapBrowserEvent Map browser event. - * @return {boolean} True if only the shift key is pressed. - * @api - */ -var shiftKeyOnly = function (mapBrowserEvent) { - var originalEvent = /** @type {KeyboardEvent|MouseEvent|TouchEvent} */ (mapBrowserEvent.originalEvent); - return (!originalEvent.altKey && - !(originalEvent.metaKey || originalEvent.ctrlKey) && - originalEvent.shiftKey); -}; -/** - * Return `true` if the target element is not editable, i.e. not a ``-, - * `