_defaultHeader['Content-Type'] = 'application/x-www-form-urlencoded'; $this->_integrationID = $integId; $this->_logID = $logID; $this->_logResponse = new UtilApiResponse(); $this->_entryID = $entryID; $this->_apiEndPoint = $apiEndPoint; } public function sendMessages($data) { $insertRecordEndpoint = $this->_apiEndPoint . '/sendMessage'; return HttpHelper::post($insertRecordEndpoint, $data, $this->_defaultHeader); } public function executeRecordApi($integrationDetails, $fieldValues, $formID, $entryID) { // Without $formID, ${bf_all_data} and the repeater tags resolve to ''. $msg = FieldValueHandler::replaceFieldWithValue($integrationDetails->body, $fieldValues, $formID); $messagesBody = self::normalizeMessageBody($msg, $integrationDetails->parse_mode); $type = 'insert'; $files = self::collectAttachments($integrationDetails, $fieldValues); $responses = []; // Ride along as the caption when it fits, so users still get one notification. $caption = ''; if (!empty($files) && '' !== trim($messagesBody) && mb_strlen($messagesBody) <= self::CAPTION_LIMIT) { $caption = $messagesBody; $messagesBody = ''; } if ('' !== trim($messagesBody)) { $responses[] = $this->sendMessages([ 'chat_id' => $integrationDetails->chat_id, 'text' => $messagesBody, 'parse_mode' => $integrationDetails->parse_mode, ]); } if (!empty($files)) { $filesApiHelper = new FilesApiHelper($formID, $entryID); // ten items max per group, and incompatible types cannot share one $batches = $filesApiHelper->buildMediaBatches(array_values($files)); if (empty($batches)) { // the body was folded into the caption above — don't lose it too if ('' !== trim($caption)) { $responses[] = $this->sendMessages([ 'chat_id' => $integrationDetails->chat_id, 'text' => $caption, 'parse_mode' => $integrationDetails->parse_mode, ]); } $responses[] = new WP_Error('TELEGRAM_FILE_NOT_FOUND', __('None of the Telegram attachments could be read.', 'bit-form')); } foreach ($batches as $index => $batch) { // first send only, or the body repeats once per batch $batchCaption = 0 === $index ? $caption : ''; if (1 === count($batch)) { // uploadFiles() picks sendPhoto/sendAudio/sendVideo/sendDocument by MIME $responses[] = $filesApiHelper->uploadFiles($this->_apiEndPoint, [ 'chat_id' => $integrationDetails->chat_id, 'parse_mode' => $integrationDetails->parse_mode, 'caption' => $batchCaption, 'photo' => $batch[0]['url'], ]); continue; } $responses[] = $filesApiHelper->uploadMultipleFiles($this->_apiEndPoint, [ 'chat_id' => $integrationDetails->chat_id, 'parse_mode' => $integrationDetails->parse_mode, 'caption' => $batchCaption, 'media' => $batch, ]); } } if (empty($responses)) { $responses[] = new WP_Error('TELEGRAM_EMPTY_REQUEST', __('Telegram integration has nothing to send: message body and attachments are both empty.', 'bit-form')); } $entryDetails = [ 'formId' => $formID, 'entryId' => $entryID, 'fieldValues' => $fieldValues ]; $failure = null; $decoded = []; foreach ($responses as $response) { $response = self::decodeResponse($response); $decoded[] = $response; if (is_null($failure) && !self::isSuccessful($response)) { $failure = $response; } } $recordApiResponse = 1 === count($decoded) ? $decoded[0] : $decoded; if (is_null($failure)) { $this->_logResponse->apiResponse($this->_logID, $this->_integrationID, ['type' => 'record', 'type_name' => $type], 'success', $recordApiResponse, $entryDetails); return $recordApiResponse; } $this->_logResponse->apiResponse($this->_logID, $this->_integrationID, ['type' => 'record', 'type_name' => $type], 'error', self::describeFailure($failure), $entryDetails); return $failure instanceof WP_Error ? $failure : $recordApiResponse; } /** * Collect the mapped attachment file URLs out of the submitted values. * * @return array */ private static function collectAttachments($integrationDetails, $fieldValues) { if (empty($integrationDetails->actions->attachments)) { return []; } $attachment = (array) $integrationDetails->actions->attachments; $files = []; foreach ($fieldValues as $fieldKey => $fieldValue) { if (!in_array($fieldKey, $attachment)) { continue; } if (is_array($fieldValue)) { $files = array_merge($files, $fieldValue); } elseif (!empty($fieldValue)) { $files[] = $fieldValue; } } return array_values(array_filter($files, function ($file) { return is_string($file) && '' !== $file; })); } /** * Telegram's HTML parse mode allows only a small tag whitelist;
/
are not
* in it and fail the call with "Unsupported start tag".
*
* @return string
*/
private static function normalizeMessageBody($msg, $parseMode)
{
if (!is_string($msg)) {
return '';
}
$msg = self::normalizeAnchors($msg);
$msg = self::flattenTableMarkup($msg);
$msg = self::flattenListMarkup($msg);
$msg = preg_replace('#
#i', "\n", $msg);
$msg = preg_replace('#
]*>#i', "\n\n", $msg); $msg = preg_replace('#?p[^>]*>#i', '', $msg); if ('HTML' !== strtoupper((string) $parseMode)) { return self::tidyWhitespace(wp_strip_all_tags($msg)); } return self::tidyWhitespace(wp_kses($msg, [ 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [], 'ins' => [], 's' => [], 'strike' => [], 'del' => [], 'a' => ['href' => []], 'code' => ['class' => []], 'pre' => [], 'span' => ['class' => []], 'tg-spoiler' => [], 'blockquote' => [], ])); } /** * FieldValueHandler::anchorMarkup() wraps list values in an , so a checkbox * option becomes href="Option 2" and Telegram fails the message on the bad protocol. * * @param string $msg * * @return string */ private static function normalizeAnchors($msg) { if (false === stripos($msg, ']*href\s*=\s*([\'"])(.*?)\1[^>]*>(.*?)#is', function ($matches) { $href = trim(html_entity_decode($matches[2], ENT_QUOTES)); $text = $matches[3]; if (!preg_match('#^(?:https?://|tg://|mailto:)#i', $href)) { return $text; } return '' . $text . ''; }, $msg ); } /** * ${bf_all_data} renders an HTML