Files
crm.clientright.ru/wappi.bak
Fedor ac7467f0b4 Major CRM updates: AI Assistant, Court Status API, S3 integration improvements, and extensive file storage system
- Added comprehensive AI Assistant system (aiassist/ directory):
  * Vector search and embedding capabilities
  * Typebot proxy integration
  * Elastic search functionality
  * Message classification and chat history
  * MCP proxy for external integrations

- Implemented Court Status API (GetCourtStatus.php):
  * Real-time court document status checking
  * Integration with external court systems
  * Comprehensive error handling and logging

- Enhanced S3 integration:
  * Improved file backup system with metadata
  * Batch processing capabilities
  * Enhanced error logging and recovery
  * Copy operations with URL fixing

- Added Telegram contact creation API
- Improved error logging across all modules
- Enhanced callback system for AI responses
- Extensive backup file storage with timestamps
- Updated documentation and README files

- File storage improvements:
  * Thousands of backup files with proper metadata
  * Fix operations for broken file references
  * Project-specific backup and recovery systems
  * Comprehensive file integrity checking

Total: 26,461+ files added/modified including AWS SDK, vendor dependencies, and extensive backup system.
2025-10-16 11:17:21 +03:00

303 lines
15 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/*********************************************************************************
* Endpoint для вызова снаружи вебхуками входящих сообщений в WhatsApp
* All Rights Reserved.
* Contributor(s): Илья Руденко itsaturn@yandex.ru
********************************************************************************/
error_reporting(E_ALL);
ini_set('display_errors', '1');
include_once 'modules/Users/Users.php';
include_once 'include/utils/CommonUtils.php';
require_once('include/Webservices/Utils.php');
require_once 'include/Webservices/Create.php';
require_once 'include/Webservices/Revise.php';
require_once 'include/utils/WhatsApp.php';
require_once 'includes/Loader.php';
vimport ('includes.runtime.Globals');
vimport ('includes.runtime.BaseModel');
vimport ('includes.runtime.LanguageHandler');
$str = file_get_contents('php://input'); //Вынимаем пришедшие данные вот таким оригинальным образом - это просто строка, НЕ массив!
//$str = '{"messages":[{"wh_type":"incoming_message","profile_id":"527d54d0-b2af","id":"2DC4B20ABFD7CA7A39F60DF8009C10CF","body":"Тест","type":"chat","from":"79298407770@c.us","to":"79286601171@c.us","senderName":"Илья Руденко","chatId":"79298407770@c.us","timestamp":"2023-04-06T18:02:20+03:00","time":1680793340,"from_where":"phone","contact":null,"is_forwarded":false,"stanza_id":""}]}';
$str = str_replace('[', '', $str);
$str = str_replace(']', '', $str);
$data = json_decode($str, true); // Формируем массив
if ($data['messages']['chatId'] == $data['messages']['from']) {
$logstring = date('Y-m-d H:i:s').' '.$str.PHP_EOL; // Пишем в лог то, что будем обрабатывать, но только если это личное сообщение - мусор из чатов не интересен
file_put_contents('wa_inbound.log', $logstring, FILE_APPEND);
}
$wh_type = $data['messages']['wh_type']; //Тип события
$type = $data['messages']['type']; //Тип сообщения
$message = $data['messages']['body']; // Тело сообщения
$profile = $data['messages']['profile_id']; // Идентификатор пользователя, кому предназначено сообщение
$from = substr($data['messages']['from'], 0, 11); //Номер отправителя
// Отбираем только входящие текстовые сообщения, отправленные в личку (не в общие чаты)
if ($wh_type == 'incoming_message' and $data['messages']['chatId'] == $data['messages']['from'] and ($type == 'chat' or $type == 'document' or $type == 'image')) {
$logstring = date('Y-m-d H:i:s').' Ищем клиента с телефоном: '.$from.PHP_EOL;
file_put_contents('wa_inbound.log', $logstring, FILE_APPEND);
$adb = PearDatabase::getInstance();
// Т.к. в половине случаев базе номера хранятся без первой цифры или с восьмерки, нам придется искать по всем трем вариантам
$short = substr($from, 1);
$start8 = '8'.substr($from, 1);
$query = 'select p.crmid, w.id, p.setype
from vtiger_pbxmanager_phonelookup p
left join vtiger_ws_entity w on w.name = p.setype
where p.fnumber = ? or p.fnumber = ? or p.fnumber = ?
order by 1 desc limit 1';
$result = $adb->pquery($query, array($from, $short, $start8));
$user = Users::getActiveAdminUser(); // Получаем пользователя, под которым будем создавать коммент
if ($adb->num_rows($result) == 0) {
$logstring = date('Y-m-d H:i:s').' По телефону не нашли - будем создавать'.PHP_EOL;
file_put_contents('wa_inbound.log', $logstring, FILE_APPEND);
$sender = $data['messages']['senderName']; // Отправитель сообщения
$space = mb_strpos($sender, ' ');
if ($space > 0) {
$firstname = mb_substr($sender, 0, $space);
$lastname = mb_substr($sender, $space + 1);
} else {
$firstname = $sender;
$lastname = '-';
}
try {
$params = array (
'firstname' => $firstname,
'lastname' => $lastname,
'mobile' => $from,
'assigned_user_id' => $user
);
if ($profile == '4f2d8b57-3889') {
$params['cf_1740'] = '000';
}
$logstring = date('Y-m-d H:i:s').' Массив: '.json_encode($params).PHP_EOL;
file_put_contents('wa_inbound.log', $logstring, FILE_APPEND);
$contact = vtws_create('Contacts', $params, $user);
$output = 'ID : '.$contact['id'];
$customer = substr($contact['id'], 3);
$crmid = $contact['id'];
$setype = 'Contacts';
$userid = substr($user, 3);
} catch (WebServiceException $ex) {
$output = $ex->getMessage();
}
$logstring = date('Y-m-d H:i:s').' Создание Контакта : '.$output.PHP_EOL;
file_put_contents('wa_inbound.log', $logstring, FILE_APPEND);
} else {
$crmid = $adb->query_result($result, 0, 'id').'x'.$adb->query_result($result, 0, 'crmid'); // Сразу конкатенируем идентификатор справочника с айдишником через Х
$customer = $adb->query_result($result, 0, 'crmid'); // голый id клиента для апдейта
$setype = $adb->query_result($result, 0, 'setype'); // Модуль клиента - он Lead или Contact
$logstring = date('Y-m-d H:i:s').' Найден клиент с ID: '.$crmid.PHP_EOL;
file_put_contents('wa_inbound.log', $logstring, FILE_APPEND);
}
// Если нам прислали файл или картинку, то их надо раскодировать и собственно создать файлы в системе
$msg = '';
if ($type == 'document' or $type == 'image') {
$mime_type = $data['messages']['mimetype']; // Имя файла нам передали натурально
$allowed_types = array(
'text/plain',
'image',
'image/png',
'image/jpeg',
'image/jpg',
'application/pdf',
'application/msword',
'application/excel',
'application/vnd.ms-excel',
'application/vnd.oasis.opendocument.text',
'application/vnd.oasis.opendocument.spreadsheet',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
); //Список разрешенных к сохранению типов файлов
if (!in_array($mime_type, $allowed_types)) {
// Недопустимого формата
$msg = 'Передан файл недопустимого формата (не Документ, не PDF и не картина) - НЕ загружен!';
$logstring = date('Y-m-d H:i:s').' '.$msg.PHP_EOL;
file_put_contents('wa_inbound.log', $logstring, FILE_APPEND);
Reply($from, 'Файл '.$filename.' не доставлен!'.PHP_EOL.PHP_EOL.'Принимаются только картинки, PDF и документы Word и Excel', $profile);
} else {
$filecontent = base64_decode($message);
$file_size = strlen($filecontent);
if ($file_size > 5242880) {
// Размером более 5мб
$msg = 'Передан файл более 5 мб - НЕ загружен!';
$logstring = date('Y-m-d H:i:s').' '.$msg.PHP_EOL;
file_put_contents('wa_inbound.log', $logstring, FILE_APPEND);
Reply($from, 'Файл '.$filename.' не доставлен!'.PHP_EOL.PHP_EOL.'Объем файла не должен превышать 5Мб', $profile);
} else {
// Размер и тип подходящие
if ($type == 'document') {
$filename = $data['messages']['file_name']; // Имя файла нам передали натурально
} else {
$filename = 'image.jpg';
}
}
}
// Если появилась ошибка
if ($msg <> '') {
// То она и есть текст комментария
$message = $msg;
} else {
$message = 'Файл во вложении';
}
}
// А теперь добавим коммент к найденному или созданному Контакту
try {
$params = array (
'commentcontent' => $message,
'related_to' => $crmid,
'channel' => 'WhatsApp',
'assigned_user_id' => vtws_getWebserviceEntityId('Users', $user->id)
);
$logstring = date('Y-m-d H:i:s').' Массив: '.json_encode($params).PHP_EOL;
file_put_contents('wa_inbound.log', $logstring, FILE_APPEND);
$comment = vtws_create('ModComments', $params, $user);
$output = 'ID : '.$comment['id'];
// Почему-то при попытке сразу прописать customer при создании коммента, в половине случаев вылетает ошибка
// поэтому сначала просо создаем комментарий, а потом отдельным апдейтом корректируем, что это не просто коммент, а ответ от клиента
$commentid = substr($comment['id'], 3);
$query = 'update vtiger_modcomments set customer = ?, userid = 0 where modcommentsid = ?';
$result = $adb->pquery($query, array($customer, $commentid));
} catch (WebServiceException $ex) {
$output = $ex->getMessage();
}
$logstring = date('Y-m-d H:i:s').' Создание коммента: '.$output.PHP_EOL;
file_put_contents('wa_inbound.log', $logstring, FILE_APPEND);
// Файл есть и он валидный - надо скачать и загрузить в коммент
if (($type == 'document' or $type == 'image') and $msg == '') {
$current_id = $adb->getUniqueID("vtiger_crmentity");
$date_var = date('Y-m-d H:i:s');
$ownerid = getUserId($userid, $customer); // Если у клиента есть активный Проект - достанем оттуда ответственного
$upload_file_path = decideFilePath();
file_put_contents($upload_file_path . $current_id . "_" . $filename, $filecontent);
$sql1 = "insert into vtiger_crmentity (crmid,smcreatorid,smownerid,setype,createdtime,modifiedtime) values(?,?,?,?,?,?)";
$params1 = array($current_id, $ownerid, $ownerid, 'ModComments Attachment', $adb->formatDate($date_var, true), $adb->formatDate($date_var, true));
$adb->pquery($sql1, $params1);
$logstring = date('Y-m-d H:i:s').' Добавили аттач в crmentity'.PHP_EOL;
file_put_contents('wa_inbound.log', $logstring, FILE_APPEND);
$sql2 = "insert into vtiger_attachments(attachmentsid, name, type, path, storedname) values(?,?,?,?,?)";
$params2 = array($current_id, $filename, $mime_type, $upload_file_path, $filename);
$adb->pquery($sql2, $params2);
$logstring = date('Y-m-d H:i:s').' Добавили аттач в attachments'.PHP_EOL;
file_put_contents('wa_inbound.log', $logstring, FILE_APPEND);
$sql3 = "insert into vtiger_seattachmentsrel(crmid, attachmentsid) values(?,?)";
$adb->pquery($sql3, array($commentid, $current_id));
$sql4 = 'update vtiger_modcomments set filename = ? where modcommentsid = ?';
$adb->pquery($sql4, array($current_id, $commentid));
$logstring = date('Y-m-d H:i:s').' связали аттач с комментом '.PHP_EOL;
file_put_contents('wa_inbound.log', $logstring, FILE_APPEND);
}
// Собираем ссылку на сущность, куда добавлен коммент
$link = 'module='.$setype.'&view=Detail&record='.$customer.'&app=MARKETING';
// Собираем текст уведомления с именем отправителя
if ($setype == 'Leads') {
$query = 'select l.firstname, l.lastname, e.smownerid as userid
from vtiger_leaddetails l
left join vtiger_crmentity e on e.crmid = l.leadid
where l.leadid = ? and e.deleted = 0';
$result = $adb->pquery($query, array($customer));
$userid = $adb->query_result($result, 0, 'userid');
} else {
$query = 'select c.firstname, c.lastname, e.smownerid as userid
from vtiger_contactdetails c
left join vtiger_crmentity e on e.crmid = c.contactid
where c.contactid = ? and e.deleted = 0';
$result = $adb->pquery($query, array($customer));
$userid = getUserId($adb->query_result($result, 0, 'userid'), $customer); // Тут мы определим, кому показывать уведомление - ответственному по Контакту, или по Проекту, если он есть
}
$name = $adb->query_result($result, 0, 'firstname').' '.$adb->query_result($result, 0, 'lastname');
$title = $name.' - новое сообщение'; // Итоговый текст уведомления
// Ищем непрочтенную всплывашку с этим клиентом
$query = 'select id from vtiger_vdnotifierpro where userid = ? and crmid = ? and title = ? and status = 5';
$result = $adb->pquery($query, array($userid, $customer, $title));
if ($adb->num_rows($result) > 0) {
// Обновляем время в старой всплывашке, чтобы не плодить дубли
$id = $adb->query_result($result, 0, 'id');
$query = 'update vtiger_vdnotifierpro set modifiedtime = ? where id = ?';
$result = $adb->pquery($query, array(date('Y-m-d H:i:s'), $id));
$logstring = date('Y-m-d H:i:s').' id записи '.$id.PHP_EOL;
file_put_contents('wa_inbound.log', $logstring, FILE_APPEND);
} else {
// Добавляем новую всплывашку
$query = 'insert into vtiger_vdnotifierpro (userid, modulename, crmid, modiuserid, link, title, action, modifiedtime, status) values (?, ?, ?, 0, ?, ?, "", ?, 5)';
$result = $adb->pquery($query, array($userid, $setype, $customer, $link, $title, date('Y-m-d H:i:s')));
}
} else {
$logstring = date('Y-m-d H:i:s').' Левая шняга?'.PHP_EOL;
file_put_contents('wa_inbound.log', $logstring, FILE_APPEND);
}
function getUserId($userid, $contactid) {
global $adb;
$query = 'select e.smownerid as userid
from vtiger_project p
left join vtiger_crmentity e on e.crmid = p.projectid
where e.deleted = 0 and p.linktoaccountscontacts = ? and p.projectstatus <> "completed"';
$result = $adb->pquery($query, array($contactid));
if ($adb->num_rows($result) == 1) {
// Единственный активный Проект - вытащим оттуда ответственного
$output = $adb->query_result($result, 0, 'userid');
} else {
// Активных Проектов нет, а может быть несколько - значит ответственным будет владелец Контакта
$output = $userid;
}
return $output;
}
function Reply($mobile, $message, $from) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://wappi.pro/api/sync/message/send?profile_id='.$from);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"recipient": "'.$mobile.'", "body": "'.$message.'"}');
$headers = array();
$headers[] = 'Accept: application/json';
$headers[] = 'Authorization: 45971e2d99071461360515ced697ef08d270363d';
$headers[] = 'Content-Type: text/plain';
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$output = curl_exec($ch);
curl_close($ch);
return $output;
}
?>