From 177f33a8d6170b99853817094b23cd90da285941 Mon Sep 17 00:00:00 2001 From: enured <enis.nuredini@uzh.ch> Date: Thu, 28 Dec 2023 12:59:48 +0100 Subject: [PATCH] B17462: Improved chat search and chat communication. refs #17462 --- extension/Classes/Core/AbstractBuildForm.php | 19 ++++++-- extension/Classes/Core/Constants.php | 3 ++ extension/Classes/Core/Form/Chat.php | 32 ++++++++----- extension/Classes/Core/Save.php | 10 ++-- javascript/src/Helper/qfqChat.js | 48 ++++++++++---------- 5 files changed, 68 insertions(+), 44 deletions(-) diff --git a/extension/Classes/Core/AbstractBuildForm.php b/extension/Classes/Core/AbstractBuildForm.php index d98ab07b5..7e950bb29 100644 --- a/extension/Classes/Core/AbstractBuildForm.php +++ b/extension/Classes/Core/AbstractBuildForm.php @@ -3139,6 +3139,10 @@ abstract class AbstractBuildForm { $chatConfigJson = json_encode($chatConfig, JSON_UNESCAPED_SLASHES); $websocketUrl = $this->store::getVar(SYSTEM_WEBSOCKET_URL, STORE_SYSTEM) ?? ''; + if ($chatConfig['pIdCreator'] == 0 || empty($chatConfig['pIdCreator']) || empty($chatConfig['username'])) { + throw new \UserFormException("Missing or empty pIdCreator/username parameter.", ERROR_MISSING_CHAT_DATA); + } + // Part 1: Build fieldset for chat $fieldsetAttribute = ''; $fieldsetAttribute .= Support::doAttribute('id', $formElement[FE_HTML_ID]); @@ -3261,11 +3265,20 @@ abstract class AbstractBuildForm { $wrapSetupClass = $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_CLASS] ?? ''; // Return last message id as value - $columnId = array_column($result, $dbColumnNames['id']); - $value = max($columnId); + if (!empty($result)) { + $columnId = array_column($result, $dbColumnNames[COLUMN_ID]); + $recordId = array_column($result, $dbColumnNames['rId']); + $createdDate = array_column($result, $dbColumnNames[COLUMN_CREATED]); + + $value = max($columnId); + $recordId = max($recordId); + $createdDate = max($createdDate); + } $json = $this->getFormElementForJson($htmlFormElementName, $value, $formElement, $wrapSetupClass); - $json['chat'] = $chatJson; + $json['rId'] = $recordId ?? 0; + $json[COLUMN_CREATED] = $createdDate ?? ''; + $json[FE_TYPE_CHAT] = $chatJson; // Concat chat html elements and return return $chatFieldsetStart . $chatHead . $chatContent . $chatTail . $inputHtml . $chatFieldsetEnd; diff --git a/extension/Classes/Core/Constants.php b/extension/Classes/Core/Constants.php index ead78e131..de0100ab1 100644 --- a/extension/Classes/Core/Constants.php +++ b/extension/Classes/Core/Constants.php @@ -382,6 +382,9 @@ const ERROR_ENCRYPT_CLASS = 3111; const ERROR_MSG_TOO_BIG = "** Data removed: too big **"; +// Chat +const ERROR_MISSING_CHAT_DATA = 3200; + // // Store Names: Identifier // diff --git a/extension/Classes/Core/Form/Chat.php b/extension/Classes/Core/Form/Chat.php index 1b5648c9a..d8552af82 100644 --- a/extension/Classes/Core/Form/Chat.php +++ b/extension/Classes/Core/Form/Chat.php @@ -31,14 +31,14 @@ class Chat implements MessageComponentInterface { $this->clientInfo = []; } - public function onOpen(ConnectionInterface $conn) { + public function onOpen(ConnectionInterface $conn): void { $this->clients->attach($conn); $this->clientConnections[$conn->resourceId] = $conn; $timestamp = date("Y-m-d H:i:s"); Logger::logMessage($timestamp . " - " . "New connection! ({$conn->resourceId})", $this->logFile); } - public function onMessage(ConnectionInterface $from, $msg) { + public function onMessage(ConnectionInterface $from, $msg): void { $data = json_decode($msg); // Catch heartbeat connection keeping @@ -56,10 +56,11 @@ class Chat implements MessageComponentInterface { $timestamp = date("Y-m-d H:i:s"); Logger::logMessage($timestamp . " - " . "Connection configured! ({$from->resourceId}) Config: " . $msg, $this->logFile); - $from->send('Current ID: '. $from->resourceId . ', Receiver IDs: ' . json_encode($this->clientInfo[$from->resourceId]['receiverIds'])); return; } + $this->refreshRecordId($from, $data); + // Refresh receiverIds if there exist a new client connection $this->updateReceiverIds($from); @@ -67,7 +68,6 @@ class Chat implements MessageComponentInterface { // send message to client with given receiverId foreach ($this->clientInfo[$from->resourceId]['receiverIds'] as $receiverId) { if (isset($this->clientConnections[$receiverId])) { - $this->clientConnections[$receiverId]->send('Sent from: '. $from->resourceId); $messages = $this->getMessages($data, $from, $this->clientInfo[$receiverId]); $this->clientConnections[$receiverId]->send(json_encode($messages, JSON_UNESCAPED_SLASHES)); } @@ -75,7 +75,7 @@ class Chat implements MessageComponentInterface { } } - public function onClose(ConnectionInterface $conn) { + public function onClose(ConnectionInterface $conn): void { $this->clients->detach($conn); unset($this->clientConnections[$conn->resourceId]); unset($this->clientInfo[$conn->resourceId]); @@ -83,13 +83,13 @@ class Chat implements MessageComponentInterface { Logger::logMessage($timestamp . " - " . "Connection {$conn->resourceId} has disconnected", $this->logFile); } - public function onError(ConnectionInterface $conn, \Exception $e) { + public function onError(ConnectionInterface $conn, \Exception $e): void { $timestamp = date("Y-m-d H:i:s"); Logger::logMessage($timestamp . " - " . "An error has occurred: {$e->getMessage()}", $this->logFile); $conn->close(); } - private function updateReceiverIds($from) { + private function updateReceiverIds($from): void { if (!isset($this->clientInfo[$from->resourceId]['receiverIds'])) { $this->clientInfo[$from->resourceId]['receiverIds'] = array(); } @@ -101,19 +101,23 @@ class Chat implements MessageComponentInterface { $fromCreator = $this->clientInfo[$from->resourceId]['pIdCreator'] ?? null; $clientCreator = $this->clientInfo[$client->resourceId]['pIdCreator'] ?? null; + $fromRecordId = $this->clientInfo[$from->resourceId]['recordId'] ?? null; + $clientRecordId = $this->clientInfo[$client->resourceId]['recordId'] ?? null; + $isDifferentClient = $from !== $client; $isNotInReceiverIds = !in_array($client->resourceId, $this->clientInfo[$from->resourceId]['receiverIds']); $isValidGroupList = !empty($fromGroupList) && $fromGroupList === $clientGroupList; $isValidCreator = empty($fromGroupList) && $fromCreator === $clientCreator; + $isValidRecordId = $fromRecordId === $clientRecordId; - if ($isDifferentClient && $isNotInReceiverIds && ($isValidGroupList || $isValidCreator)) { + if ($isDifferentClient && $isValidRecordId && $isNotInReceiverIds && ($isValidGroupList || $isValidCreator)) { $this->clientInfo[$from->resourceId]['receiverIds'][] = $client->resourceId; } } } - private function getMessages($data, $from, $client) { + private function getMessages($data, $from, $client): array { $chatJson = array(); $chatJson[$data->messageId]['bubbleClass'] = 'chat-left-bubble'; $chatJson[$data->messageId]['username'] = $this->clientInfo[$from->resourceId]['username']; @@ -123,12 +127,18 @@ class Chat implements MessageComponentInterface { $chatJson[$data->messageId]['username'] = ''; } - // current date - $timestamp = time(); + // given date + $timestamp = strtotime($data->created); $chatJson[$data->messageId]['title'] = date('d.m.Y H:i', $timestamp); $chatJson[$data->messageId]['message'] = $data->value; $chatJson[$data->messageId]['chatTime'] = date('H:i', $timestamp); return $chatJson; } + + private function refreshRecordId($from, $data): void { + if ($this->clientInfo[$from->resourceId]['recordId'] == 0 && $data->rId) { + $this->clientInfo[$from->resourceId]['recordId'] = $data->rId; + } + } } \ No newline at end of file diff --git a/extension/Classes/Core/Save.php b/extension/Classes/Core/Save.php index c311a6400..04ba93cac 100644 --- a/extension/Classes/Core/Save.php +++ b/extension/Classes/Core/Save.php @@ -417,13 +417,13 @@ class Save { $feColumnTypes = array(); $feColumnTypeChat = array(); foreach ($this->feSpecNative as $fe) { - if ($fe['type'] === FE_TYPE_CHAT && $formValues[$fe['name']] !== '') { - $feColumnTypeChat[$fe['name']]['value'] = $formValues[$fe['name']]; - $feColumnTypeChat[$fe['name']]['fe'] = $fe; + if ($fe[FE_TYPE] === FE_TYPE_CHAT && $formValues[$fe[FE_NAME]] !== '') { + $feColumnTypeChat[$fe[FE_NAME]][FE_VALUE] = $formValues[$fe[FE_NAME]]; + $feColumnTypeChat[$fe[FE_NAME]]['fe'] = $fe; continue; } - $feColumnTypes[$fe['name']] = $fe['type']; + $feColumnTypes[$fe[FE_NAME]] = $fe[FE_TYPE]; } // Get htmlAllow parameters of all formValues and store in $feSpecsTags @@ -539,7 +539,7 @@ class Save { foreach ($feColumnTypeChat as $fe => $data) { $chatConfig = Support::createChatConfig($data['fe'], $this->store); $chatParams = [ - 'recordId' => $chatConfig['recordId'], + 'recordId' => $chatConfig['recordId'] != 0 ? $chatConfig['recordId'] : $recordId, 'pIdCreator' => $this->evaluate->parse($chatConfig['pIdCreator'], ROW_REGULAR), 'xGrIdStatus' => $this->evaluate->parse($chatConfig['xGrIdStatus'], ROW_REGULAR), 'grIdGroupList' => $this->evaluate->parse($chatConfig['grIdGroupList'], ROW_REGULAR), diff --git a/javascript/src/Helper/qfqChat.js b/javascript/src/Helper/qfqChat.js index 1bca72d26..a77a056e6 100644 --- a/javascript/src/Helper/qfqChat.js +++ b/javascript/src/Helper/qfqChat.js @@ -78,23 +78,20 @@ QfqNS.Helper = QfqNS.Helper || {}; this.activateSearchBtn.addEventListener('click', () => { if (this.chatSearch.style.display === 'block') { // If visible, start the hiding process - this.resetFilter(''); + this.searchInput.value = ''; + this.handleSearch(); this.chatSearch.style.opacity = '0'; - this.chatSearch.addEventListener('transitionend', function() { - this.chatSearch.style.display = 'none'; - }.bind(this), { once: true }); + this.chatSearch.style.display = 'none'; } else { // If hidden, show the div this.chatSearch.style.display = 'block'; - requestAnimationFrame(function() { - this.chatSearch.style.opacity = '1'; - }.bind(this)); + this.chatSearch.style.opacity = '1'; } }); document.addEventListener("visibilitychange", function() { if (this.chatRefresh) { - this.chatMessages.scrollTop = this.chatMessages.scrollHeight; + this.scrollToBottom(); this.chatRefresh = false; } }.bind(this)); @@ -108,11 +105,10 @@ QfqNS.Helper = QfqNS.Helper || {}; this.form.on('form.submit.successful', (obj) => { if (this.connection) { let elementName = this.elementName; - let newMessageId = qfqChat.getValue(obj.data, elementName); + let messageData = qfqChat.getMessageData(obj.data, elementName); - let messageData = {value: this.chatInput.value, messageId: newMessageId}; - this.connection.send(JSON.stringify(messageData)); - console.log('send data: ' + JSON.stringify(messageData)); + let messageDataToSend = {value: this.chatInput.value, messageId: messageData.messageId, rId: messageData.rId, created: messageData.created}; + this.connection.send(JSON.stringify(messageDataToSend)); } }); @@ -134,10 +130,10 @@ QfqNS.Helper = QfqNS.Helper || {}; data: chatJsonConfig }; - let keepConnection = {type: "heartbeat"}; - this.connection.send(JSON.stringify(chatConfig)); - // Send heartbeat message every 30 seconds + + let keepConnection = {type: "heartbeat"}; + // Send heartbeat message every 30 seconds to keep connection, some server configs doesn't allow longer connections setInterval(() => { this.connection.send(JSON.stringify(keepConnection)); }, 30000); @@ -300,8 +296,6 @@ QfqNS.Helper = QfqNS.Helper || {}; for (var key in chatItem) { if (chatItem.hasOwnProperty(key) && !messageIdsSet.has(parseInt(key, 10))) { // Process the elements and create not existing chat messages - console.log("Processing: ", chatItem[key]); - let chatContainerElement = this.createNewMessage(key, chatItem[key]); // First clean input field element.nextElementSibling.value = ''; @@ -351,16 +345,20 @@ QfqNS.Helper = QfqNS.Helper || {}; return chatContainerElement; }; - qfqChat.getValue = function (obj, elementName) { - let returnValue = null; + qfqChat.getMessageData = function (obj, elementName) { + let returnMessageData = {}; - obj["form-update"].forEach(item => { - if(item["form-element"] === elementName) { - returnValue = item.value; - } - }); + if (obj["form-update"] !== undefined) { + obj["form-update"].forEach(item => { + if(item["form-element"] === elementName) { + returnMessageData.messageId = item.value; + returnMessageData.rId = item.rId; + returnMessageData.created = item.created + } + }); + } - return returnValue; + return returnMessageData; }; n.qfqChat = qfqChat; -- GitLab