From 8802be4a59e272cfd6f32feb5e5d794f0b6b6c34 Mon Sep 17 00:00:00 2001 From: Marvin Beym Date: Wed, 21 Jan 2026 07:56:40 +0100 Subject: [PATCH] Show confirmation modal on contact unlink --- .../classes/class.ilBuddySystemGUI.php | 40 +++++++ .../ILIAS/Contact/resources/buddy_system.js | 113 ++++++++++++++---- lang/ilias_de.lang | 1 + lang/ilias_en.lang | 1 + 4 files changed, 129 insertions(+), 26 deletions(-) diff --git a/components/ILIAS/Contact/BuddySystem/classes/class.ilBuddySystemGUI.php b/components/ILIAS/Contact/BuddySystem/classes/class.ilBuddySystemGUI.php index 22f987d63da3..e957d7815cda 100755 --- a/components/ILIAS/Contact/BuddySystem/classes/class.ilBuddySystemGUI.php +++ b/components/ILIAS/Contact/BuddySystem/classes/class.ilBuddySystemGUI.php @@ -18,7 +18,11 @@ declare(strict_types=1); +use ILIAS\Filesystem\Stream\Streams; +use ILIAS\HTTP\Response\ResponseHeader; use ILIAS\HTTP\Services; +use ILIAS\UI\Factory as UIFactory; +use ILIAS\UI\Renderer as UIRenderer; /** * Class ilBuddySystemGUI @@ -39,6 +43,8 @@ class ilBuddySystemGUI protected ilLanguage $lng; protected Services $http; private readonly ilGlobalTemplateInterface $main_tpl; + private UIFactory $ui_factory; + private UIRenderer $ui_renderer; public function __construct() { @@ -49,6 +55,8 @@ public function __construct() $this->ctrl = $DIC['ilCtrl']; $this->user = $DIC['ilUser']; $this->lng = $DIC['lng']; + $this->ui_factory = $DIC->ui()->factory(); + $this->ui_renderer = $DIC->ui()->renderer(); $this->buddyList = ilBuddyList::getInstanceByGlobalUser(); $this->stateFactory = ilBuddySystemRelationStateFactory::getInstance(); @@ -68,8 +76,14 @@ public static function initializeFrontend(ilGlobalTemplateInterface $page): void $DIC->language()->loadLanguageModule('buddysystem'); $page->addJavaScript('./assets/js/buddy_system.js'); + $page->addJavaScript('./assets/js/modal.min.js'); $config = new stdClass(); + $config->async_get_unlink_modal_confirmation_html = $DIC->ctrl()->getLinkTargetByClass([ + ilUIPluginRouterGUI::class, + self::class + ], 'asyncGetUnlinkModalConfirmationHtml', '', true, false); + $config->http_post_url = $DIC->ctrl()->getFormActionByClass([ ilUIPluginRouterGUI::class, self::class @@ -87,6 +101,32 @@ public static function initializeFrontend(ilGlobalTemplateInterface $page): void } } + public function asyncGetUnlinkModalConfirmationHtmlCommand(): never + { + $confirmation_modal = $this->ui_factory->modal()->interruptive( + $this->lng->txt('confirmation'), + $this->lng->txt('buddy_confirm_unlink'), + '' + ) + ->withActionButtonLabel($this->lng->txt('confirm')); + + $this->http->saveResponse( + $this->http->response()->withBody( + Streams::ofString( + json_encode([ + "html" => $this->ui_renderer->renderAsync($confirmation_modal), + "signals" => [ + "show" => $confirmation_modal->getShowSignal()->getId(), + "close" => $confirmation_modal->getCloseSignal()->getId() + ] + ], JSON_THROW_ON_ERROR) + ) + )->withHeader(ResponseHeader::CONTENT_TYPE, 'application/json') + ); + $this->http->sendResponse(); + $this->http->close(); + } + /** * @throws RuntimeException */ diff --git a/components/ILIAS/Contact/resources/buddy_system.js b/components/ILIAS/Contact/resources/buddy_system.js index 9e2e02bc0ed2..2d956bccb0a7 100755 --- a/components/ILIAS/Contact/resources/buddy_system.js +++ b/components/ILIAS/Contact/resources/buddy_system.js @@ -25,6 +25,15 @@ const BuddySystemButton = { config: {}, + unlinkConfirmationModal: { + signals: { + show: '', + close: '', + }, + }, + unlinkModalAbortController: null, + unlinkModal: null, + unlinkModalReady: false, setConfig(config) { this.config = config; @@ -40,32 +49,84 @@ const triggerButton = e.target.closest(triggerSelector); const container = triggerButton.closest(`.${this.config.bnt_class}`); - if (triggerButton.dataset.submitted === 'true') return Promise.resolve(); - - const values = new FormData(); - values.append('usr_id', container.dataset.buddyId); - values.append('action', triggerButton.dataset.action); - values.append(`cmd[${BuddySystem.config.transition_state_cmd}]`, 1); - - return disableButtons(container) - .then(() => fetch(BuddySystem.config.http_post_url, { - method: 'POST', - headers: { Accept: 'application/json' }, - body: values, - })) - .then((response) => { - if (!response.ok) throw new Error('Request failed'); - return response.json(); - }) - .then((data) => processResponse(container, data)) - .then(() => { - container.querySelector(toggleSelector)?.focus(); - }) - .catch((error) => { - console.error(error); - enableButtons(container); - container.querySelector(toggleSelector)?.focus(); - }); + + const widgetClickAction = () => { + if (triggerButton.dataset.submitted === 'true') return Promise.resolve(); + + const values = new FormData(); + values.append('usr_id', container.dataset.buddyId); + values.append('action', triggerButton.dataset.action); + values.append(`cmd[${BuddySystem.config.transition_state_cmd}]`, 1); + + return disableButtons(container) + .then(() => fetch(BuddySystem.config.http_post_url, { + method: 'POST', + headers: { Accept: 'application/json' }, + body: values, + })) + .then((response) => { + if (!response.ok) throw new Error('Request failed'); + return response.json(); + }) + .then((data) => processResponse(container, data)) + .then(() => { + container.querySelector(toggleSelector)?.focus(); + }) + .catch((error) => { + console.error(error); + enableButtons(container); + container.querySelector(toggleSelector)?.focus(); + }); + }; + + if (triggerButton.dataset.action === 'unlink') { + return showUnlinkConfirmationModal().then(() => widgetClickAction()); + } + + return widgetClickAction(); + }; + + const showUnlinkConfirmationModal = () => { + const ensureModal = () => { + if (this.unlinkModalReady) { + return Promise.resolve(); + } + + return fetch(BuddySystem.config.async_get_unlink_modal_confirmation_html) + .then((response) => { + if (!response.ok) throw new Error('Request failed'); + return response.json(); + }) + .then((data) => { + const wrapper = document.createElement('div'); + const modalFragment = document.createRange().createContextualFragment(data.html); + wrapper.appendChild(modalFragment); + document.body.append(wrapper); + + this.unlinkModal = wrapper.querySelector('dialog'); + this.unlinkConfirmationModal.signals.show = data.signals.show; + this.unlinkConfirmationModal.signals.close = data.signals.close; + this.unlinkModalReady = true; + }); + }; + + return ensureModal().then(() => new Promise((resolve) => { + if (this.unlinkModalAbortController) { + this.unlinkModalAbortController.abort(); + } + + this.unlinkModalAbortController = new AbortController(); + + const submitButton = this.unlinkModal.querySelector('input[type="submit"]'); + + submitButton.addEventListener('click', (event) => { + event.preventDefault(); + global.jQuery(document).trigger(this.unlinkConfirmationModal.signals.close, {}); + resolve(); + }, { signal: this.unlinkModalAbortController.signal }); + + global.jQuery(document).trigger(this.unlinkConfirmationModal.signals.show, {}); + })); }; const disableButtons = (container) => new Promise((resolve) => { diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang index 5118fa48df87..99092a7ec74f 100644 --- a/lang/ilias_de.lang +++ b/lang/ilias_de.lang @@ -2769,6 +2769,7 @@ buddysystem#:#buddy_bs_state_requested_p#:#Angefragt buddysystem#:#buddy_bs_state_unlinked#:#Nicht vernetzt buddysystem#:#buddy_bs_state_unlinked_a#:#Nicht vernetzt buddysystem#:#buddy_bs_state_unlinked_p#:#Nicht vernetzt +buddysystem#:#buddy_confirm_unlink#:#Möchten Sie die Verbindung zu diesem Kontakt wirklich aufheben? buddysystem#:#buddy_enable#:#Aktiviere „Benutzerkontakte“ buddysystem#:#buddy_enable_info#:#Falls aktiviert, können Benutzer im System über Kontaktanfragen miteinander in Verbindung treten. Eine zusätzliche persönliche Einstellung ermöglicht jedem Benutzer, Kontaktaufnahmen zuzulassen oder zu verhindern. buddysystem#:#buddy_handle_contact_request#:#Kontaktanfrage diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index 8f4c14f6a05e..c9a75e1da5a5 100755 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -2770,6 +2770,7 @@ buddysystem#:#buddy_bs_state_requested_p#:#Requested buddysystem#:#buddy_bs_state_unlinked#:#Unlinked buddysystem#:#buddy_bs_state_unlinked_a#:#Unlinked buddysystem#:#buddy_bs_state_unlinked_p#:#Unlinked +buddysystem#:#buddy_confirm_unlink#:#Do you really want to disconnect from this contact? buddysystem#:#buddy_enable#:#Activate ‘Contacts’ buddysystem#:#buddy_enable_info#:#If enabled, users are allowed to contact each other by initiating contact requests. Additionally, there is a personal user setting to allow or prevent being contacted. buddysystem#:#buddy_handle_contact_request#:#Contact Request