diff --git a/extension/Classes/Core/AbstractBuildForm.php b/extension/Classes/Core/AbstractBuildForm.php index 7e950bb2941a347a531249d4ee36f19f494a6b92..ecb21a67bd421915b378d1cc3563d8964c627021 100644 --- a/extension/Classes/Core/AbstractBuildForm.php +++ b/extension/Classes/Core/AbstractBuildForm.php @@ -9,6 +9,7 @@ namespace IMATHUZH\Qfq\Core; use IMATHUZH\Qfq\Core\Database\Database; +use IMATHUZH\Qfq\Core\Form\Chat; use IMATHUZH\Qfq\Core\Form\Checkbox; use IMATHUZH\Qfq\Core\Form\FormAsFile; use IMATHUZH\Qfq\Core\Form\TypeAhead; @@ -3135,14 +3136,18 @@ abstract class AbstractBuildForm { * @throws \UserReportException */ public function buildChat(array $formElement, $htmlFormElementName, $value, array &$json, $mode = FORM_LOAD) { - $chatConfig = Support::createChatConfig($formElement, $this->store); + $chatConfig = Chat::createChatConfig($formElement, $this->store); $chatConfigJson = json_encode($chatConfig, JSON_UNESCAPED_SLASHES); $websocketUrl = $this->store::getVar(SYSTEM_WEBSOCKET_URL, STORE_SYSTEM) ?? ''; + $placeholder = ''; if ($chatConfig['pIdCreator'] == 0 || empty($chatConfig['pIdCreator']) || empty($chatConfig['username'])) { throw new \UserFormException("Missing or empty pIdCreator/username parameter.", ERROR_MISSING_CHAT_DATA); } + // Set readonly if grIdGroupList is set but is not found in grIdCreatorGroupList + Chat::checkAccess($chatConfig['grIdGroupList'], $chatConfig['grIdCreatorGroupList'], $formElement, $placeholder); + // Part 1: Build fieldset for chat $fieldsetAttribute = ''; $fieldsetAttribute .= Support::doAttribute('id', $formElement[FE_HTML_ID]); @@ -3152,6 +3157,8 @@ abstract class AbstractBuildForm { $fieldsetAttribute .= Support::doAttribute('class', $formElement[F_FE_FIELDSET_CLASS] . ' qfq-chat'); $fieldsetAttribute .= Support::doAttribute(ATTRIBUTE_DATA_REFERENCE, $formElement[FE_DATA_REFERENCE]); + $fieldsetAttribute .= HelperFormElement::getAttributeFeMode($formElement[FE_MODE], false); + $chatFieldsetStart = '<fieldset ' . $fieldsetAttribute . '>'; if ($formElement[FE_LABEL] !== '') { @@ -3176,7 +3183,7 @@ abstract class AbstractBuildForm { }, ['id', 'rId', 'pIdCreator', 'message', 'username', 'created'])) . ' FROM ' . $chatConfig['tableName'] . ' WHERE ' . $dbColumnNames['rId'] . ' = ? AND (' - . '(LENGTH(?) = 0 AND ' . $dbColumnNames['pIdCreator'] . ' = ?) ' + . '(LENGTH(?) = 0 AND ' . $dbColumnNames['pIdCreator'] . ' = ? AND ' . $dbColumnNames['grIdGroupList'] . ' = "") ' . 'OR (LENGTH(?) > 0 AND (find_match_in_lists('. $dbColumnNames['grIdGroupList'] . ', ?) = 1 ' . 'AND find_match_in_lists(' . $dbColumnNames['grIdGroupList'] . ', ?)))) ' . 'ORDER BY ' . $dbColumnNames['created']; @@ -3260,7 +3267,17 @@ abstract class AbstractBuildForm { $inputAttribute .= Support::doAttribute('maxlength', $formElement[FE_MAX_LENGTH], false); } - $inputHtml = "$inputHtml $inputAttribute>$textarea"; + if (empty($formElement[F_FE_DATA_MATCH_ERROR])) { + $formElement[F_FE_DATA_REQUIRED_ERROR] = F_FE_DATA_REQUIRED_ERROR_DEFAULT; + } + + if ($formElement[FE_MODE] == FE_MODE_REQUIRED) { + $inputAttribute .= Support::doAttribute(F_FE_DATA_REQUIRED_ERROR, $formElement[F_FE_DATA_REQUIRED_ERROR]); + } + + $inputAttribute .= HelperFormElement::getAttributeFeMode($formElement[FE_MODE], false); + + $inputHtml = "$inputHtml $placeholder $inputAttribute>$textarea"; $wrapSetupClass = $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_CLASS] ?? ''; diff --git a/extension/Classes/Core/Constants.php b/extension/Classes/Core/Constants.php index de0100ab1da04bd5053c6fefecc8844ce95fa62b..f498003662384bfebcd7b9807b3a94000f643ee4 100644 --- a/extension/Classes/Core/Constants.php +++ b/extension/Classes/Core/Constants.php @@ -384,6 +384,7 @@ const ERROR_MSG_TOO_BIG = "** Data removed: too big **"; // Chat const ERROR_MISSING_CHAT_DATA = 3200; +const ERROR_INVALID_CHAT_DATA = 3201; // // Store Names: Identifier diff --git a/extension/Classes/Core/Form/Chat.php b/extension/Classes/Core/Form/Chat.php index d8552af82f1e1439680119a09f9e9f012d2b4836..b707ee69e1dc31edcb7318725da0e1e1e3b54180 100644 --- a/extension/Classes/Core/Form/Chat.php +++ b/extension/Classes/Core/Form/Chat.php @@ -10,7 +10,10 @@ namespace IMATHUZH\Qfq\Core\Form; use IMATHUZH\Qfq\Core\Helper\Logger; +use IMATHUZH\Qfq\Core\Helper\OnArray; +use IMATHUZH\Qfq\Core\Helper\OnString; use IMATHUZH\Qfq\Core\Helper\Path; +use IMATHUZH\Qfq\Core\Store\Store; use Ratchet\MessageComponentInterface; use Ratchet\ConnectionInterface; @@ -136,6 +139,81 @@ class Chat implements MessageComponentInterface { return $chatJson; } + /** + * @param array $formElement + * @param Store $store + * @return array + * @throws \CodeException + * @throws \DbException + * @throws \UserFormException + * @throws \UserReportException + */ + public static function createChatConfig(array $formElement, Store $store) { + $chatConfig = array(); + + // [apId:rId,pId:pIdCreator,grIdStatus:xGrIdStatus,grIdGroup:grIdGroupList,...] + $chatConfig['dbColumnNames'] = array ( + 'id' => 'id', + 'rId' => 'rId', + 'pIdCreator' => 'pIdCreator', + 'xGrIdStatus' => 'xGrIdStatus', + 'grIdGroupList' => 'grIdGroupList', + 'message' => 'message', + 'username' => 'username', + 'created' => 'created' + + ); + + $chatConfig['columnMap'] = $formElement['columnMap'] ?? '[]'; + + OnArray::mapColumns($chatConfig['dbColumnNames'], $chatConfig['columnMap']); + $chatConfig['tableName'] = $formElement['tableName'] ?? 'Chat'; + + $chatConfig['recordId'] = $formElement['recordId'] ?? $store::getVar(SIP_RECORD_ID, STORE_SIP . STORE_ZERO); + $chatConfig['pIdCreator'] = $formElement['pIdCreator'] ?? 0; + $chatConfig['username'] = $formElement['username'] ?? ''; + $chatConfig['grIdCreatorGroupList'] = $formElement['grIdCreatorGroupList'] ?? ''; + $chatConfig['grIdGroupList'] = $formElement['grIdGroupList'] ?? ''; + $chatConfig['xGrIdStatus'] = $formElement['xGrIdStatus'] ?? 0; + $chatConfig['disabledFlag'] = $formElement['disabledFlag'] ?? ''; + + $chatConfig['formParam'] = $formElement['formParam'] ?? ''; + $chatConfig['customJson'] = $formElement['customJson'] ?? ''; + $chatConfig['threadId'] = $formElement['threadId'] ?? 0; + $chatConfig['submitReference'] = $formElement['submitReference'] ?? 0; + + return $chatConfig; + } + + public static function existsInGroup($groupList1, $groupList2): bool { + $bool = false; + + // Convert strings to arrays + $groupArray1 = explode(",", $groupList1); + $groupArray2 = explode(",", $groupList2); + + $matchingIds = array_intersect($groupArray1, $groupArray2); + + if (!empty($matchingIds)) { + $bool = true; + } + + return $bool; + } + + public static function checkAccess($groupList1, $groupList2, &$fe, &$placeholder = ''): void { + if (!empty($groupList1)) { + if (!OnString::isValidNumberList($groupList1) || !OnString::isValidNumberList($groupList2)) { + throw new \UserFormException("Invalid grIdGroupList or grIdCreatorGroupList parameter.", ERROR_INVALID_CHAT_DATA); + } + + if (!Chat::existsInGroup($groupList1, $groupList2)) { + $placeholder = 'placeholder="No access for this chat."'; + $fe[FE_MODE] = $fe[FE_MODE] === FE_MODE_HIDDEN ? $fe[FE_MODE] : FE_MODE_READONLY; + } + } + } + private function refreshRecordId($from, $data): void { if ($this->clientInfo[$from->resourceId]['recordId'] == 0 && $data->rId) { $this->clientInfo[$from->resourceId]['recordId'] = $data->rId; diff --git a/extension/Classes/Core/Helper/OnString.php b/extension/Classes/Core/Helper/OnString.php index 87c099be40a5747bcaa64576a117903e7b74ba97..fcdba5cd5e10e19a696b3f40b9150d940ed96ac2 100644 --- a/extension/Classes/Core/Helper/OnString.php +++ b/extension/Classes/Core/Helper/OnString.php @@ -865,5 +865,10 @@ class OnString { return $arrNew; } + + public static function isValidNumberList($string): bool { + // Regex pattern: Empty string or numbers, commas, and optional spaces + return preg_match('/^[\d,\s]*$/', $string) || $string === ""; + } } diff --git a/extension/Classes/Core/Helper/Support.php b/extension/Classes/Core/Helper/Support.php index 8cc2b874f349b17b21d7faa2775da15552a87b75..4dbcbc2f731b6f795ba2eef7f436bb0b12ff699d 100644 --- a/extension/Classes/Core/Helper/Support.php +++ b/extension/Classes/Core/Helper/Support.php @@ -1714,50 +1714,4 @@ class Support { throw new \CodeException("Invalid wget configuration. Maybe wget command is incorrect or not supported.", ERROR_INVALID_WGET_CMD); } } - - /** - * @param array $formElement - * @param Store $store - * @return array - * @throws \CodeException - * @throws \DbException - * @throws \UserFormException - * @throws \UserReportException - */ - public static function createChatConfig(array $formElement, Store $store) { - $chatConfig = array(); - - // [apId:rId,pId:pIdCreator,grIdStatus:xGrIdStatus,grIdGroup:grIdGroupList,...] - $chatConfig['dbColumnNames'] = array ( - 'id' => 'id', - 'rId' => 'rId', - 'pIdCreator' => 'pIdCreator', - 'xGrIdStatus' => 'xGrIdStatus', - 'grIdGroupList' => 'grIdGroupList', - 'message' => 'message', - 'username' => 'username', - 'created' => 'created' - - ); - - $chatConfig['columnMap'] = $formElement['columnMap'] ?? '[]'; - - OnArray::mapColumns($chatConfig['dbColumnNames'], $chatConfig['columnMap']); - $chatConfig['tableName'] = $formElement['tableName'] ?? 'Chat'; - - $chatConfig['recordId'] = $formElement['recordId'] ?? $store::getVar(SIP_RECORD_ID, STORE_SIP . STORE_ZERO); - $chatConfig['pIdCreator'] = $formElement['pIdCreator'] ?? 0; - $chatConfig['username'] = $formElement['username'] ?? ''; - $chatConfig['grIdCreatorGroupList'] = $formElement['grIdCreatorGroupList'] ?? ''; - $chatConfig['grIdGroupList'] = $formElement['grIdGroupList'] ?? ''; - $chatConfig['xGrIdStatus'] = $formElement['xGrIdStatus'] ?? 0; - $chatConfig['disabledFlag'] = $formElement['disabledFlag'] ?? ''; - - $chatConfig['formParam'] = $formElement['formParam'] ?? ''; - $chatConfig['customJson'] = $formElement['customJson'] ?? ''; - $chatConfig['threadId'] = $formElement['threadId'] ?? 0; - $chatConfig['submitReference'] = $formElement['submitReference'] ?? 0; - - return $chatConfig; - } } \ No newline at end of file diff --git a/extension/Classes/Core/Save.php b/extension/Classes/Core/Save.php index 04ba93cac3fa4bae517fe6694460e07ac45dbf7c..1eaf599fd8778ba7dc4ab496f2ff451a1f1687c7 100644 --- a/extension/Classes/Core/Save.php +++ b/extension/Classes/Core/Save.php @@ -11,6 +11,7 @@ namespace IMATHUZH\Qfq\Core; use HTMLPurifier; use IMATHUZH\Qfq\Core\Database\Database; use IMATHUZH\Qfq\Core\Exception\Thrower; +use IMATHUZH\Qfq\Core\Form\Chat; use IMATHUZH\Qfq\Core\Form\FormAction; use IMATHUZH\Qfq\Core\Helper\EncryptDecrypt; use IMATHUZH\Qfq\Core\Helper\HelperFile; @@ -417,8 +418,15 @@ class Save { $feColumnTypes = array(); $feColumnTypeChat = array(); foreach ($this->feSpecNative as $fe) { - if ($fe[FE_TYPE] === FE_TYPE_CHAT && $formValues[$fe[FE_NAME]] !== '') { + if ($fe[FE_TYPE] === FE_TYPE_CHAT && isset($formValues[$fe[FE_NAME]]) && $formValues[$fe[FE_NAME]] !== '') { $feColumnTypeChat[$fe[FE_NAME]][FE_VALUE] = $formValues[$fe[FE_NAME]]; + + // Check for access and change fe mode dynamically + $groupList1 = isset($fe['grIdGroupList']) ? $this->evaluate->parse($fe['grIdGroupList'], ROW_REGULAR) : ''; + $groupList2 = isset($fe['grIdCreatorGroupList']) ? $this->evaluate->parse($fe['grIdCreatorGroupList'], ROW_REGULAR) : ''; + $fe['grIdGroupList'] = $groupList1; + Chat::checkAccess($groupList1, $groupList2, $fe); + $feColumnTypeChat[$fe[FE_NAME]]['fe'] = $fe; continue; } @@ -537,12 +545,16 @@ class Save { // Handle save for FE type chat if (!empty($feColumnTypeChat)) { foreach ($feColumnTypeChat as $fe => $data) { - $chatConfig = Support::createChatConfig($data['fe'], $this->store); + if ($data['fe'][FE_MODE] === FE_MODE_HIDDEN || $data['fe'][FE_MODE] === FE_MODE_READONLY) { + continue; + } + + $chatConfig = Chat::createChatConfig($data['fe'], $this->store); $chatParams = [ '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), + 'grIdGroupList' => $chatConfig['grIdGroupList'], 'username' => $this->evaluate->parse($chatConfig['username'], ROW_REGULAR) ]; diff --git a/javascript/src/Helper/qfqChat.js b/javascript/src/Helper/qfqChat.js index a77a056e606e50d09a4413f8e8436ffb75d0d043..64a2187283b5e22614e0fe19a983f80db78b79ca 100644 --- a/javascript/src/Helper/qfqChat.js +++ b/javascript/src/Helper/qfqChat.js @@ -292,6 +292,7 @@ QfqNS.Helper = QfqNS.Helper || {}; var messageIds = Array.from(element.querySelectorAll('.chat-messages div[data-message-id]')).map(el => parseInt(el.getAttribute('data-message-id'), 10)); var messageIdsSet = new Set(messageIds); let messageElement = element.querySelector('.chat-messages'); + let noMessageBanner = element.querySelector('.chat-no-message'); for (var key in chatItem) { if (chatItem.hasOwnProperty(key) && !messageIdsSet.has(parseInt(key, 10))) { @@ -300,6 +301,11 @@ QfqNS.Helper = QfqNS.Helper || {}; // First clean input field element.nextElementSibling.value = ''; + // Check if no message banner exists, clear it + if (noMessageBanner !== undefined && noMessageBanner !== null && noMessageBanner.style.display !== 'none') { + noMessageBanner.style.display = 'none'; + } + // append new container to existing chat messageElement.appendChild(chatContainerElement); }