diff --git a/extension/Classes/Core/AbstractBuildForm.php b/extension/Classes/Core/AbstractBuildForm.php index 1e4843695de2f1391b34f5272cbdafb6c6778175..25046bb50297d53b230fa785075efb3cafff26ea 100644 --- a/extension/Classes/Core/AbstractBuildForm.php +++ b/extension/Classes/Core/AbstractBuildForm.php @@ -3119,8 +3119,8 @@ abstract class AbstractBuildForm { public function buildChat(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) { $chatConfig = Support::createChatConfig($formElement, $this->store); $chatConfigJson = json_encode($chatConfig, JSON_UNESCAPED_SLASHES); + $websocketUrl = $this->store::getVar(SYSTEM_WEBSOCKET_URL, STORE_SYSTEM) ?? ''; - $baseUrl = $this->store::getVar(SYSTEM_BASE_URL, STORE_SYSTEM . STORE_EMPTY); // Part 1: Build fieldset for chat $fieldsetAttribute = ''; $fieldsetAttribute .= Support::doAttribute('id', $formElement[FE_HTML_ID]); @@ -3142,6 +3142,7 @@ abstract class AbstractBuildForm { $chatWindowAttribute = Support::doAttribute('name', $htmlFormElementName . '-chat'); $chatWindowAttribute .= Support::doAttribute(ATTRIBUTE_DATA_REFERENCE, $formElement[FE_DATA_REFERENCE] . '-chat'); $chatWindowAttribute .= Support::doAttribute('data-chat-config', $chatConfigJson); + $chatWindowAttribute .= Support::doAttribute('data-websocket-url', $websocketUrl); $chatHead = '<div class="qfq-chat-window" '. $chatWindowAttribute . '><span class="fas fa-search chat-search-activate"></span><div class="chat-search"><input type="text" class="chat-search-input qfq-skip-dirty" placeholder="Search..." ' . $chatConfig['disabledFlag'] . '><button class="chat-search-btn qfq-skip-dirty" ' . $chatConfig['disabledFlag'] . '>Search</button><span class="chat-search-info"></span></div><div class="chat-messages"><div class="fas fa-arrow-circle-up chat-top-symbol"></div>'; $chatTail = '</div></div>'; diff --git a/extension/Classes/Core/Constants.php b/extension/Classes/Core/Constants.php index 4ac4ec52f103d2bf0e538bb00db025e0afb855c0..5901dead4cd55555a283e3b9d75851632a1f9914 100644 --- a/extension/Classes/Core/Constants.php +++ b/extension/Classes/Core/Constants.php @@ -794,7 +794,7 @@ const SYSTEM_QFQ_PROJECT_PATH = 'qfqProjectPath'; const SYSTEM_DO_NOT_LOG_COLUMN = 'doNotLogColumn'; const SYSTEM_PROTECTED_FOLDER_CHECK = 'protectedFolderCheck'; const SYSTEM_WEBSOCKET_PORT = 'websocketPort'; - +const SYSTEM_WEBSOCKET_URL = 'websocketUrl'; const EXTRA_ENABLE_SWITCH = 'enableSwitch'; const EXTRA_COLUMN_NAME_AlIAS_SLUG = 'columnNameAliasSlug'; const EXTRA_FORM_SUBMIT_LOG_ID = 'formSubmitLogId'; diff --git a/extension/Classes/Core/Form/Chat.php b/extension/Classes/Core/Form/Chat.php index 5d04da1d08dc5825d44099f5c90461723fcfa532..1b5648c9a1e6a75e001aaf177f50606489477fbb 100644 --- a/extension/Classes/Core/Form/Chat.php +++ b/extension/Classes/Core/Form/Chat.php @@ -78,6 +78,7 @@ class Chat implements MessageComponentInterface { public function onClose(ConnectionInterface $conn) { $this->clients->detach($conn); unset($this->clientConnections[$conn->resourceId]); + unset($this->clientInfo[$conn->resourceId]); $timestamp = date("Y-m-d H:i:s"); Logger::logMessage($timestamp . " - " . "Connection {$conn->resourceId} has disconnected", $this->logFile); } @@ -94,7 +95,19 @@ class Chat implements MessageComponentInterface { } foreach ($this->clients as $client) { - if ($from !== $client && !in_array($client->resourceId, $this->clientInfo[$from->resourceId]['receiverIds']) && $this->clientInfo[$from->resourceId]['grIdGroupList'] === $this->clientInfo[$client->resourceId]['grIdGroupList']) { + $fromGroupList = $this->clientInfo[$from->resourceId]['grIdGroupList'] ?? null; + $clientGroupList = $this->clientInfo[$client->resourceId]['grIdGroupList'] ?? null; + + $fromCreator = $this->clientInfo[$from->resourceId]['pIdCreator'] ?? null; + $clientCreator = $this->clientInfo[$client->resourceId]['pIdCreator'] ?? null; + + $isDifferentClient = $from !== $client; + $isNotInReceiverIds = !in_array($client->resourceId, $this->clientInfo[$from->resourceId]['receiverIds']); + + $isValidGroupList = !empty($fromGroupList) && $fromGroupList === $clientGroupList; + $isValidCreator = empty($fromGroupList) && $fromCreator === $clientCreator; + + if ($isDifferentClient && $isNotInReceiverIds && ($isValidGroupList || $isValidCreator)) { $this->clientInfo[$from->resourceId]['receiverIds'][] = $client->resourceId; } } diff --git a/extension/ext_conf_template.txt b/extension/ext_conf_template.txt index 2958e50696b58ecb287a18fb476f8886f0d6e905..3f6ac04240ed4fcd44f9f5559784b1d1354f82b4 100644 --- a/extension/ext_conf_template.txt +++ b/extension/ext_conf_template.txt @@ -22,6 +22,9 @@ reportAsFileAutoExport = # cat=config/config; type=string; label=Websocket server port:Default is empty and no active websocket. If set: Websocket server is running and ready for chat implementation. websocketPort = +# cat=config/config; type=string; label=Websocket server url:Default is empty and no active websocket. +websocketUrl = + # cat=graphics/config; type=string; label=Command 'inkscape':Default is 'inkscape'. Will be used to convert SVG to images (png). An empty string disables `inkscape`. If it is not available, `convert` will be used instead. cmdInkscape = inkscape diff --git a/javascript/src/Helper/qfqChat.js b/javascript/src/Helper/qfqChat.js index d0d8b3859753359d3a3076ed5c2f69f9897fe48a..32abb060ad3aa99fb29849c8ea42c45fa47079e7 100644 --- a/javascript/src/Helper/qfqChat.js +++ b/javascript/src/Helper/qfqChat.js @@ -46,6 +46,7 @@ QfqNS.Helper = QfqNS.Helper || {}; this.topBtn = this.chatWindow.querySelector(".chat-top-symbol"); this.activateSearchBtn = this.chatWindow.querySelector(".chat-search-activate"); this.chatInput = this.chatWindow.nextElementSibling; + this.websocketUrl = this.chatWindow.getAttribute('data-websocket-url'); this.currentSearchIndex = 0; this.searchResults = []; this.lastSearchTerm = ''; @@ -98,6 +99,12 @@ QfqNS.Helper = QfqNS.Helper || {}; } }.bind(this)); + window.addEventListener("beforeunload", () => { + if (this.connection && this.connection.readyState === WebSocket.OPEN) { + this.connection.close(); + } + }); + this.form.on('form.submit.successful', (obj) => { let elementName = this.elementName; let newMessageId = qfqChat.getValue(obj.data, elementName); @@ -113,51 +120,55 @@ QfqNS.Helper = QfqNS.Helper || {}; this.scrollToBottom(); // Build up websocket connection - this.connection = new WebSocket('ws://webwork20:46301/ws'); - this.connection.onopen = function(e) { - console.log("Connection established!"); - let chatJsonConfigString = this.chatWindow.getAttribute('data-chat-config'); - let chatJsonConfig = JSON.parse(chatJsonConfigString); - - let chatConfig = { - type: "config", - data: chatJsonConfig - }; - - let keepConnection = {type: "heartbeat"}; - - this.connection.send(JSON.stringify(chatConfig)); - // Send heartbeat message every 30 seconds - setInterval(function() { - this.connection.send(JSON.stringify(keepConnection)); - }.bind(this), 30000); - }.bind(this); - - this.connection.onmessage = function(e) { - try { - // Try to parse the data as JSON - let decodedData = JSON.parse(e.data); - let entriesArray = Object.entries(decodedData); - // If the parsing succeeds and it's an array with more than one element - if (entriesArray.length > 0) { - let element = this.chatWindow; - qfqChat.refreshChat(element, decodedData); - - // Set flag if users tab is not active. - if (document.visibilityState !== 'visible') { - this.chatRefresh = true; + if (this.websocketUrl !== '') { + this.connection = new WebSocket(this.websocketUrl); + this.connection.onopen = function(e) { + console.log("Connection established!"); + let chatJsonConfigString = this.chatWindow.getAttribute('data-chat-config'); + let chatJsonConfig = JSON.parse(chatJsonConfigString); + + let chatConfig = { + type: "config", + data: chatJsonConfig + }; + + let keepConnection = {type: "heartbeat"}; + + this.connection.send(JSON.stringify(chatConfig)); + // Send heartbeat message every 30 seconds + setInterval(function() { + this.connection.send(JSON.stringify(keepConnection)); + }.bind(this), 30000); + }.bind(this); + + this.connection.onmessage = function(e) { + try { + // Try to parse the data as JSON + let decodedData = JSON.parse(e.data); + let entriesArray = Object.entries(decodedData); + // If the parsing succeeds and it's an array with more than one element + if (entriesArray.length > 0) { + let element = this.chatWindow; + qfqChat.refreshChat(element, decodedData); + + // Set flag if users tab is not active. + if (document.visibilityState !== 'visible') { + this.chatRefresh = true; + } } - } - } catch (error) {} - }.bind(this); + } catch (error) {} + }.bind(this); - this.connection.onerror = function(e) { - console.error("Connection error!", e); - }; + this.connection.onerror = function(e) { + console.error("Connection error!", e); + }; - this.connection.onclose = function(e) { - console.log("Connection closed!", e); - }; + this.connection.onclose = function(e) { + console.log("Connection closed!", e); + }; + } else { + console.log("No websocket url found. Set in qfq config if needed."); + } } scrollToBottom() {