model.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878
  1. <?php
  2. /**
  3. * The model file of webhook module of ZenTaoPMS.
  4. *
  5. * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
  6. * @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
  7. * @author Gang Liu <liugang@cnezsoft.com>
  8. * @package webhook
  9. * @version $Id$
  10. * @link https://www.zentao.net
  11. */
  12. class webhookModel extends model
  13. {
  14. /**
  15. * 获取单个webhook。
  16. * Get a webhook by id.
  17. *
  18. * @param int $id
  19. * @access public
  20. * @return object
  21. */
  22. public function getByID($id)
  23. {
  24. return $this->dao->select('*')->from(TABLE_WEBHOOK)->where('id')->eq($id)->fetch();
  25. }
  26. /**
  27. * 获取某种类型的webhook。
  28. * Get a webhook by type.
  29. *
  30. * @param string $type
  31. * @param int $id
  32. * @access public
  33. * @return object
  34. */
  35. public function getByType($type, $id = 0)
  36. {
  37. return $this->dao->select('*')->from(TABLE_WEBHOOK)->where('type')->eq($type)
  38. ->andWhere('deleted')->eq('0')
  39. ->beginIF($id)->andWhere('id')->eq($id)->fi()
  40. ->fetch();
  41. }
  42. /**
  43. * 获取已经绑定的用户。
  44. * Get a webhook by type.
  45. *
  46. * @param int $webhookID
  47. * @param string $webhookType
  48. * @param string $openID
  49. * @access public
  50. * @return object
  51. */
  52. public function getBindAccount($webhookID, $webhookType, $openID)
  53. {
  54. return $this->dao->select('account')->from(TABLE_OAUTH)
  55. ->where('providerID')->eq($webhookID)
  56. ->andWhere('providerType')->eq($webhookType)
  57. ->andWhere('openID')->eq($openID)
  58. ->fetch('account');
  59. }
  60. /**
  61. * 获取webhook列表。
  62. * Get webhook list.
  63. *
  64. * @param string $orderBy
  65. * @param object $pager
  66. * @access public
  67. * @return array
  68. */
  69. public function getList($orderBy = 'id_desc', $pager = null)
  70. {
  71. return $this->dao->select('*,products,executions')->from(TABLE_WEBHOOK)
  72. ->where('deleted')->eq('0')
  73. ->orderBy($orderBy)
  74. ->page($pager)
  75. ->fetchAll('id', false);
  76. }
  77. /**
  78. * 获取日志列表。
  79. * Get log list of a webhook.
  80. *
  81. * @param int $id
  82. * @param string $orderBy
  83. * @param object $pager
  84. * @access public
  85. * @return array
  86. */
  87. public function getLogList($id, $orderBy = 'date_desc', $pager = null)
  88. {
  89. $logs = $this->dao->select('*')->from(TABLE_LOG)
  90. ->where('objectType')->eq('webhook')
  91. ->andWhere('objectID')->eq($id)
  92. ->orderBy($orderBy)
  93. ->page($pager)
  94. ->fetchAll('id', false);
  95. $actions = array();
  96. foreach($logs as $log) $actions[] = $log->action;
  97. $this->loadModel('action');
  98. $actions = $this->dao->select('*')->from(TABLE_ACTION)->where('id')->in($actions)->fetchAll('id');
  99. $users = $this->loadModel('user')->getPairs('noletter');
  100. foreach($logs as $log)
  101. {
  102. if(!isset($actions[$log->action]))
  103. {
  104. $log->action = '';
  105. continue;
  106. }
  107. $action = $actions[$log->action];
  108. $data = json_decode($log->data);
  109. $object = $this->dao->select('*')->from($this->config->objectTables[$action->objectType])->where('id')->eq($action->objectID)->fetch();
  110. $field = zget($this->config->action->objectNameFields, $action->objectType, $action->objectType);
  111. if(empty($data)) continue;
  112. if(!is_object($object))
  113. {
  114. $object = new stdclass;
  115. $object->$field = '';
  116. }
  117. $log->action = $this->webhookTao->getActionText($data, $action, $object, $users);
  118. $log->actionURL = $this->getViewLink($action->objectType, $action->objectID);
  119. $log->module = $action->objectType;
  120. $log->moduleID = $action->objectID;
  121. $log->dialog = $action->objectType == 'todo' ? 1 : 0;
  122. }
  123. return $logs;
  124. }
  125. /**
  126. * 获取通知信息。
  127. * Get saved data list.
  128. *
  129. * @access public
  130. * @return array
  131. */
  132. public function getDataList()
  133. {
  134. $now = helper::now();
  135. $dataList = $this->dao->select('*')->from(TABLE_NOTIFY)
  136. ->where('status')->eq('wait')
  137. ->andWhere('objectType')->eq('webhook')
  138. ->andWhere('(sendTime IS NULL OR sendTime <= "' . $now . '")')
  139. ->orderBy('id')
  140. ->fetchAll('id', false);
  141. $threeHoursAgo = date('Y-m-d H:i:s', time() - 3 * 3600);
  142. $dataList += $this->dao->select('*')->from(TABLE_NOTIFY)
  143. ->where('status')->eq('senting')
  144. ->andWhere('objectType')->eq('webhook')
  145. ->andWhere('(sendTime IS NULL OR (sendTime <= "' . $now . '" AND sendTime >= "' . $threeHoursAgo . '"))')
  146. ->orderBy('id')
  147. ->fetchAll('id', false);
  148. return $dataList;
  149. }
  150. /**
  151. * 获取绑定用户。
  152. * Get bind users.
  153. *
  154. * @param int $webhookID
  155. * @param array|string $users
  156. * @access public
  157. * @return array
  158. */
  159. public function getBoundUsers($webhookID, $users = array())
  160. {
  161. return $this->dao->select('*')->from(TABLE_OAUTH)->where('providerType')->eq('webhook')
  162. ->andWhere('providerID')->eq($webhookID)
  163. ->beginIF($users)->andWhere('account')->in($users)->fi()
  164. ->fetchPairs('account', 'openID');
  165. }
  166. /**
  167. * 创建webhook。
  168. * Create a webhook.
  169. *
  170. * @param object $webhook
  171. *
  172. * @access public
  173. * @return int|false
  174. */
  175. public function create($webhook)
  176. {
  177. $webhook->createdBy = $this->app->user->account;
  178. $webhook->createdDate = helper::now();
  179. $webhook->domain = trim($webhook->domain, '/');
  180. $webhook->params = $this->post->params ? implode(',', $this->post->params) . ',text' : 'text';
  181. if($webhook->type == 'dinguser')
  182. {
  183. $webhook = $this->webhookTao->getDingdingSecret($webhook);
  184. }
  185. elseif($webhook->type == 'wechatuser')
  186. {
  187. $webhook = $this->webhookTao->getWeixinSecret($webhook);
  188. }
  189. elseif($webhook->type == 'feishuuser')
  190. {
  191. $webhook = $this->webhookTao->getFeishuSecret($webhook);
  192. }
  193. if(!empty($webhook->url) && !preg_match('/^http(s)?:\/\//', $webhook->url)) dao::$errors['url'] = $this->lang->webhook->error->url;
  194. if(dao::isError()) return false;
  195. $this->dao->insert(TABLE_WEBHOOK)->data($webhook, 'agentId,appKey,appSecret,wechatCorpId,wechatCorpSecret,wechatAgentId,feishuAppId,feishuAppSecret')
  196. ->batchCheck($this->config->webhook->create->requiredFields, 'notempty')
  197. ->autoCheck()
  198. ->exec();
  199. return $this->dao->lastInsertID();
  200. }
  201. /**
  202. * 编辑webhook。
  203. * Update a webhook.
  204. *
  205. * @param int $id
  206. * @param object $webhook
  207. * @access public
  208. * @return bool
  209. */
  210. public function update($id, $webhook)
  211. {
  212. $webhook->editedBy = $this->app->user->account;
  213. $webhook->editedDate = helper::now();
  214. $webhook->domain = trim($webhook->domain, '/');
  215. $webhook->params = $this->post->params ? implode(',', $this->post->params) : 'text';
  216. if(strpos($webhook->params, 'text') === false) $webhook->params .= ',text';
  217. if($webhook->type == 'dinguser')
  218. {
  219. $webhook = $this->webhookTao->getDingdingSecret($webhook);
  220. }
  221. elseif($webhook->type == 'wechatuser')
  222. {
  223. $webhook = $this->webhookTao->getWeixinSecret($webhook);
  224. }
  225. elseif($webhook->type == 'feishuuser')
  226. {
  227. $webhook = $this->webhookTao->getFeishuSecret($webhook);
  228. }
  229. if(!empty($webhook->url) && !preg_match('/^http(s)?:\/\//', $webhook->url)) dao::$errors['url'] = $this->lang->webhook->error->url;
  230. if(dao::isError()) return false;
  231. $this->dao->update(TABLE_WEBHOOK)->data($webhook, 'agentId,appKey,appSecret,wechatCorpId,wechatCorpSecret,wechatAgentId,feishuAppId,feishuAppSecret')
  232. ->batchCheck($this->config->webhook->edit->requiredFields, 'notempty')
  233. ->autoCheck()
  234. ->where('id')->eq($id)
  235. ->exec();
  236. return !dao::isError();
  237. }
  238. /**
  239. * 绑定钉钉、飞书、企业微信的用户到禅道。
  240. * Bind users of dingding, feishu, weixin to zentao.
  241. *
  242. * @param int $webhookID
  243. * @access public
  244. * @return bool
  245. */
  246. public function bind($webhookID)
  247. {
  248. $userList = $this->post->userid;
  249. if(!$userList) return false;
  250. $this->dao->delete()->from(TABLE_OAUTH)
  251. ->where('providerType')->eq('webhook')
  252. ->andWhere('providerID')->eq($webhookID)
  253. ->andWhere('account')->in(array_keys($userList))
  254. ->exec();
  255. foreach($userList as $account => $userid)
  256. {
  257. if(empty($userid)) continue;
  258. $oauth = new stdclass();
  259. $oauth->account = $account;
  260. $oauth->openID = $userid;
  261. $oauth->providerType = 'webhook';
  262. $oauth->providerID = $webhookID;
  263. $this->dao->insert(TABLE_OAUTH)->data($oauth)->exec();
  264. }
  265. return !dao::isError();
  266. }
  267. /**
  268. * 发送数据。
  269. * Send data.
  270. *
  271. * @param string $objectType
  272. * @param int $objectID
  273. * @param string $actionType
  274. * @param int $actionID
  275. * @param string $actor
  276. * @access public
  277. * @return bool
  278. */
  279. public function send($objectType, $objectID, $actionType, $actionID, $actor = '')
  280. {
  281. static $webhooks = array();
  282. if(!$webhooks) $webhooks = $this->getList();
  283. if(!$webhooks) return true;
  284. /* 如果对象类型是瀑布,动作是提交审计或者审计,那么对象类型就是审批。*/
  285. if($objectType == 'waterfall' && strpos(',toaudit,audited,', ",{$actionType},") !== false) $objectType = 'review';
  286. $aitaskObject = null;
  287. if($objectType == 'aitask' && in_array($actionType, array('finished', 'failed')))
  288. {
  289. $aitaskObject = $this->dao->select('*')->from(TABLE_AI_TASK)->where('id')->eq($objectID)->fetch();
  290. }
  291. foreach($webhooks as $id => $webhook)
  292. {
  293. $postData = $this->buildData($objectType, $objectID, $actionType, $actionID, $webhook);
  294. if(!$postData) continue;
  295. $needDelay = false;
  296. if($aitaskObject && !empty($aitaskObject->noticeTime) && $aitaskObject->noticeTime != '1')
  297. {
  298. $needDelay = true;
  299. }
  300. if($webhook->sendType == 'async' || $needDelay)
  301. {
  302. if($webhook->type == 'dinguser')
  303. {
  304. $openIdList = $this->getOpenIdList($webhook->id, $actionID);
  305. if(empty($openIdList)) continue;
  306. }
  307. $this->saveData($id, $actionID, $postData, $actor, $aitaskObject);
  308. continue;
  309. }
  310. $result = $this->fetchHook($webhook, $postData, $actionID);
  311. if(!empty($result)) $this->saveLog($webhook, $actionID, $postData, (string)$result);
  312. }
  313. return !dao::isError();
  314. }
  315. /**
  316. * Build data.
  317. *
  318. * @param string $objectType
  319. * @param int $objectID
  320. * @param string $actionType
  321. * @param int $actionID
  322. * @param object $webhook
  323. * @access public
  324. * @return string
  325. */
  326. public function buildData($objectType, $objectID, $actionType, $actionID, $webhook)
  327. {
  328. /* Validate data. */
  329. if(!isset($this->lang->action->label)) $this->loadModel('action');
  330. if(!isset($this->lang->action->label->$actionType)) return false;
  331. if(empty($this->config->objectTables[$objectType])) return false;
  332. $action = $this->dao->select('*')->from(TABLE_ACTION)->where('id')->eq($actionID)->fetch();
  333. if(!$action) return false;
  334. if($webhook->products)
  335. {
  336. $webhookProducts = explode(',', trim($webhook->products, ','));
  337. $actionProduct = explode(',', trim($action->product, ','));
  338. $intersect = array_intersect($webhookProducts, $actionProduct);
  339. if(!$intersect) return false;
  340. }
  341. if($webhook->executions && strpos(",$webhook->executions,", ",$action->execution,") === false) return false;
  342. static $users = array();
  343. if(empty($users)) $users = $this->loadModel('user')->getList();
  344. $object = $this->dao->select('*')->from($this->config->objectTables[$objectType])->where('id')->eq($objectID)->fetch();
  345. if(!$object) return false;
  346. $host = empty($webhook->domain) ? common::getSysURL() : $webhook->domain;
  347. $viewLink = $this->getViewLink($objectType == 'kanbancard' ? 'kanban' : $objectType, $objectType == 'kanbancard' ? $object->kanban : $objectID);
  348. if($objectType == 'aitask' && in_array($actionType, array('finished', 'failed')))
  349. {
  350. $text = $this->loadModel('aitask')->getNotificationText($object, $objectID, $actionType, 'markdown', '', $viewLink, $host);
  351. $title = $text;
  352. }
  353. else
  354. {
  355. $field = $this->config->action->objectNameFields[$objectType];
  356. $objectTypeName = ($objectType == 'story' and $object->type == 'requirement') ? $this->lang->action->objectTypes['requirement'] : $this->lang->action->objectTypes[$objectType];
  357. $title = $this->app->user->realname . $this->lang->action->label->$actionType . $objectTypeName;
  358. $host = (defined('RUN_MODE') and RUN_MODE == 'api') ? '' : $host;
  359. $text = $title . ' ' . "[#{$objectID}::{$object->$field}](" . $host . $viewLink . ")";
  360. }
  361. $action->text = $text;
  362. $mobile = '';
  363. $email = '';
  364. if(in_array($objectType, $this->config->webhook->needAssignTypes) && !empty($object->assignedTo))
  365. {
  366. $assignedTo = $this->loadModel('user')->getById($object->assignedTo);
  367. if($assignedTo)
  368. {
  369. $mobile = $assignedTo->mobile;
  370. $email = $assignedTo->email;
  371. }
  372. }
  373. return $this->getDataByType($webhook, $action, $title, $text, $mobile, $email, $objectType, $objectID);
  374. }
  375. /**
  376. * 根据webhook类型获取数据。
  377. * Get data by type.
  378. *
  379. * @param object $webhook
  380. * @param object $action
  381. * @param string $title
  382. * @param string $text
  383. * @param string $mobile
  384. * @param string $email
  385. * @param string $objectType
  386. * @param int $objectID
  387. * @access public
  388. * @return string
  389. */
  390. public function getDataByType($webhook, $action, $title, $text, $mobile, $email, $objectType, $objectID)
  391. {
  392. if($webhook->type == 'dinggroup' or $webhook->type == 'dinguser')
  393. {
  394. $data = $this->getDingdingData($title, $text, $webhook->type == 'dinguser' ? '' : $mobile);
  395. }
  396. elseif($webhook->type == 'bearychat')
  397. {
  398. $data = $this->getBearychatData($text, $mobile, $email, $objectType, $objectID);
  399. }
  400. elseif($webhook->type == 'wechatgroup' or $webhook->type == 'wechatuser')
  401. {
  402. $data = $this->getWeixinData($text, $mobile);
  403. }
  404. elseif($webhook->type == 'feishuuser' or $webhook->type == 'feishugroup')
  405. {
  406. $data = $this->getFeishuData($title, $text);
  407. }
  408. else
  409. {
  410. $data = new stdclass();
  411. foreach(explode(',', $webhook->params) as $param) $data->$param = $action->$param;
  412. }
  413. return json_encode($data);
  414. }
  415. /**
  416. * 获取对象详情链接。
  417. * Get view link.
  418. *
  419. * @param string $objectType
  420. * @param int $objectID
  421. * @access public
  422. * @return string
  423. */
  424. public function getViewLink($objectType, $objectID)
  425. {
  426. $oldOnlyBody = '';
  427. $tab = '';
  428. if(isset($_GET['onlybody']) and $_GET['onlybody'] == 'yes')
  429. {
  430. $oldOnlyBody = 'yes';
  431. unset($_GET['onlybody']);
  432. }
  433. if($objectType == 'case') $objectType = 'testcase';
  434. if($objectType == 'kanbancard')
  435. {
  436. $objectType = 'kanban';
  437. $objectID = $this->dao->select('kanban')->from(TABLE_KANBANCARD)->where('id')->eq($objectID)->fetch('kanban');
  438. }
  439. if($objectType == 'meeting')
  440. {
  441. $meeting = $this->dao->findById($objectID)->from(TABLE_MEETING)->fetch();
  442. $tab = $meeting->project ? '#app=project' : '#app=my';
  443. }
  444. if($objectType == 'aitask')
  445. {
  446. $viewLink = helper::createLink('aitask', 'view', "taskID={$objectID}", 'html') . $tab;
  447. if($oldOnlyBody) $_GET['onlybody'] = $oldOnlyBody;
  448. return $viewLink;
  449. }
  450. $viewLink = helper::createLink($objectType, 'view', "id=$objectID", 'html') . $tab;
  451. if($oldOnlyBody) $_GET['onlybody'] = $oldOnlyBody;
  452. return $viewLink;
  453. }
  454. /**
  455. * 获取发送钉钉的数据。
  456. * Get hook data for dingding.
  457. *
  458. * @param string $title
  459. * @param string $text
  460. * @param string $mobile
  461. * @access public
  462. * @return object
  463. */
  464. public function getDingdingData($title, $text, $mobile)
  465. {
  466. if($mobile) $text .= " @{$mobile}";
  467. $markdown = new stdclass();
  468. $markdown->title = $title;
  469. $markdown->text = $text;
  470. $data = new stdclass();
  471. $data->msgtype = 'markdown';
  472. $data->markdown = $markdown;
  473. if($mobile)
  474. {
  475. $at = new stdclass();
  476. $at->atMobiles = array($mobile);
  477. $at->isAtAll = false;
  478. $data->at = $at;
  479. }
  480. return $data;
  481. }
  482. /**
  483. * 获取发送bearychat的数据。
  484. * Get hook data for bearychat.
  485. *
  486. * @param string $text
  487. * @param string $mobile
  488. * @param string $email
  489. * @param string $objectType
  490. * @param int $objectID
  491. * @access public
  492. * @return object
  493. */
  494. public function getBearychatData($text, $mobile, $email, $objectType, $objectID)
  495. {
  496. $data = new stdclass();
  497. $data->text = $text;
  498. $data->markdown = 'true';
  499. if($mobile)
  500. {
  501. $data->user = $mobile;
  502. }
  503. elseif($email)
  504. {
  505. $data->user = $email;
  506. }
  507. else
  508. {
  509. $data->user = $this->app->user->account;
  510. }
  511. if(!empty($_FILES['files']['name'][0]))
  512. {
  513. $this->loadModel('file');
  514. $files = $this->dao->select('*')->from(TABLE_FILE)->where('objectType')->eq($objectType)->andWhere('objectID')->eq($objectID)->orderBy('addedDate_desc,id_desc')->limit(count($_FILES['files']['name']))->fetchAll();
  515. if($files)
  516. {
  517. foreach($files as $file)
  518. {
  519. $attachment = array();
  520. $attachment['title'] = $file->title;
  521. $attachment['images'][]['url'] = common::getSysURL() . $this->file->webPath . $file->pathname;
  522. $data->attachments[] = $attachment;
  523. }
  524. }
  525. }
  526. return $data;
  527. }
  528. /**
  529. * 获取发送微信的数据。
  530. * Get weixin send data.
  531. *
  532. * @param string $text
  533. * @param string $mobile
  534. * @access public
  535. * @return object
  536. */
  537. public function getWeixinData($text, $mobile = '')
  538. {
  539. $data = new stdclass();
  540. $data->msgtype = 'markdown';
  541. $markdown = new stdclass();
  542. $markdown->content = $text;
  543. if($mobile)
  544. {
  545. $data->msgtype = 'text';
  546. $markdown->mentioned_mobile_list = array($mobile);
  547. }
  548. $data->{$data->msgtype} = $markdown;
  549. return $data;
  550. }
  551. /**
  552. * 获取发送飞书的数据。
  553. * Get feishu send data.
  554. *
  555. * @param string $title
  556. * @param string $text
  557. * @access public
  558. * @return object
  559. */
  560. public function getFeishuData($title, $text)
  561. {
  562. $data = new stdclass();
  563. $data->msg_type = 'interactive';
  564. $data->card = array();
  565. $data->card['header'] = array();
  566. $data->card['elements'] = array();
  567. $data->card['elements'][] = array('tag' => 'markdown', 'content' => $text);
  568. $data->card['header']['title'] = array('tag' => 'plain_text', 'content' => (string)$title);
  569. $data->card['header']['template'] = 'blue';
  570. return $data;
  571. }
  572. /**
  573. * 获取openID列表。
  574. * Get openID list.
  575. *
  576. * @param int $webhookID
  577. * @param int $actionID
  578. * @param string|array $toList
  579. * @access public
  580. * @return string
  581. */
  582. public function getOpenIdList($webhookID, $actionID, $toList = '')
  583. {
  584. if($toList)
  585. {
  586. $openIdList = $this->getBoundUsers($webhookID, $toList);
  587. $openIdList = implode(',', $openIdList);
  588. return $openIdList;
  589. }
  590. if(empty($actionID)) return false;
  591. $action = $this->dao->select('*')->from(TABLE_ACTION)->where('id')->eq($actionID)->fetch();
  592. $table = zget($this->config->objectTables, $action->objectType, '');
  593. if(empty($table)) return false;
  594. $object = $this->dao->select('*')->from($table)->where('id')->eq($action->objectID)->fetch();
  595. $toList = $this->loadModel('message')->getToList((object)$object, $action->objectType, $actionID);
  596. if(!empty($object->mailto)) $toList .= ',' . $object->mailto;
  597. if(empty($toList)) return false;
  598. /* Remove duplicate people. */
  599. if($action->objectType == 'release')
  600. {
  601. $toList = array_unique(explode(',', $toList));
  602. $toList = implode(',', $toList);
  603. }
  604. $toList = str_replace(",{$this->app->user->account},", ',', ",$toList,");
  605. $openIdList = $this->getBoundUsers($webhookID, $toList);
  606. $openIdList = implode(',', $openIdList);
  607. return $openIdList;
  608. }
  609. /**
  610. * 请求发送数据的接口。
  611. * Post hook data.
  612. *
  613. * @param object $webhook
  614. * @param string $sendData
  615. * @param int $actionID
  616. * @param string|array $appendUser
  617. * @access public
  618. * @return int
  619. */
  620. public function fetchHook($webhook, $sendData, $actionID = 0, $appendUser = '')
  621. {
  622. if(!extension_loaded('curl')) return print(helper::jsonEncode($this->lang->webhook->error->curl));
  623. if(in_array($webhook->type, array('dinguser', 'wechatuser', 'feishuuser'))) return $this->sendToUser($webhook, $sendData, $actionID, $appendUser);
  624. $contentType = "Content-Type: {$webhook->contentType};charset=utf-8";
  625. if($webhook->type == 'dinggroup' or $webhook->type == 'wechatgroup' or $webhook->type == 'feishugroup') $contentType = "Content-Type: application/json";
  626. $header[] = $contentType;
  627. $url = $webhook->url;
  628. if($webhook->type == 'dinggroup' and $webhook->secret)
  629. {
  630. $timestamp = time() * 1000;
  631. $sign = $timestamp . "\n" . $webhook->secret;
  632. $sign = urlencode(base64_encode(hash_hmac('sha256', $sign, $webhook->secret, true)));
  633. $url .= "&timestamp={$timestamp}&sign={$sign}";
  634. }
  635. if($webhook->type == 'feishugroup' and $webhook->secret)
  636. {
  637. $timestamp = time();
  638. $sign = $timestamp . "\n" . $webhook->secret;
  639. $sign = base64_encode(hash_hmac('sha256', '', $sign, true));
  640. $content = json_decode($sendData);
  641. $content->timestamp = $timestamp;
  642. $content->sign = $sign;
  643. $sendData = json_encode($content);
  644. }
  645. $ch = curl_init();
  646. curl_setopt($ch, CURLOPT_URL, $url);
  647. curl_setopt($ch, CURLOPT_POST, 1);
  648. curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
  649. curl_setopt($ch, CURLOPT_TIMEOUT, 30);
  650. curl_setopt($ch, CURLOPT_NOSIGNAL, 1);
  651. curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
  652. curl_setopt($ch, CURLOPT_POSTFIELDS, $sendData);
  653. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  654. curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
  655. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
  656. $result = curl_exec($ch);
  657. $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  658. $error = curl_error($ch);
  659. curl_close($ch);
  660. if($error) return $error;
  661. if($result) return $result;
  662. return $httpCode;
  663. }
  664. /**
  665. * 发送消息到钉钉、飞书、企业微信的用户。
  666. * Send to dingding, feishu, weixin users.
  667. *
  668. * @param object $webhook
  669. * @param string $sendData
  670. * @param int $actionID
  671. * @param string|array $appendUser
  672. * @access public
  673. * @return string|false
  674. */
  675. public function sendToUser($webhook, $sendData, $actionID, $appendUser)
  676. {
  677. if(is_string($webhook->secret)) $webhook->secret = json_decode($webhook->secret);
  678. $openIdList = $this->getOpenIdList($webhook->id, $actionID, $appendUser);
  679. if(empty($openIdList)) return false;
  680. if($webhook->type == 'dinguser')
  681. {
  682. $this->app->loadClass('dingapi', true);
  683. $dingapi = new dingapi($webhook->secret->appKey, $webhook->secret->appSecret, $webhook->secret->agentId);
  684. $result = $dingapi->send($openIdList, $sendData);
  685. return json_encode($result);
  686. }
  687. elseif($webhook->type == 'wechatuser')
  688. {
  689. $this->app->loadClass('wechatapi', true);
  690. $wechatapi = new wechatapi($webhook->secret->appKey, $webhook->secret->appSecret, $webhook->secret->agentId);
  691. $result = $wechatapi->send($openIdList, $sendData);
  692. return json_encode($result);
  693. }
  694. elseif($webhook->type == 'feishuuser')
  695. {
  696. $this->app->loadClass('feishuapi', true);
  697. $feishuapi = new feishuapi($webhook->secret->appId, $webhook->secret->appSecret);
  698. $result = $feishuapi->send($openIdList, $sendData);
  699. return json_encode($result);
  700. }
  701. }
  702. /**
  703. * 保存发送的通知。
  704. * Save datas.
  705. *
  706. * @param int $webhookID
  707. * @param int $actionID
  708. * @param string $data
  709. * @param string $actor
  710. * @access public
  711. * @return bool
  712. * @param object $aitaskObject
  713. */
  714. public function saveData($webhookID, $actionID, $data, $actor = '', $aitaskObject = null)
  715. {
  716. if(empty($actor)) $actor = $this->app->user->account;
  717. $sendTime = helper::now();
  718. $object = $aitaskObject;
  719. if(!$object)
  720. {
  721. $action = $this->loadModel('action')->getById($actionID);
  722. if($action && $action->objectType == 'aitask' && in_array($action->action, array('finished', 'failed')))
  723. {
  724. $object = $this->dao->select('*')->from(TABLE_AI_TASK)->where('id')->eq($action->objectID)->fetch();
  725. }
  726. }
  727. if($object && !empty($object->noticeTime) && $object->noticeTime != '1')
  728. {
  729. $today = date('Y-m-d');
  730. $targetTime = $today . ' ' . $object->noticeTime . ':00';
  731. $targetTimestamp = strtotime($targetTime);
  732. $nowTimestamp = time();
  733. $sendTime = $targetTimestamp < $nowTimestamp ? helper::now() : $targetTime;
  734. }
  735. $webhookData = new stdclass();
  736. $webhookData->objectType = 'webhook';
  737. $webhookData->objectID = $webhookID;
  738. $webhookData->action = $actionID;
  739. $webhookData->data = $data;
  740. $webhookData->status = 'wait';
  741. $webhookData->createdBy = $actor;
  742. $webhookData->createdDate = helper::now();
  743. $webhookData->sendTime = $sendTime;
  744. $this->dao->insert(TABLE_NOTIFY)->data($webhookData)->exec();
  745. return !dao::isError();
  746. }
  747. /**
  748. * 保存发送日志。
  749. * Save log.
  750. *
  751. * @param object $webhook
  752. * @param int $actionID
  753. * @param string $data
  754. * @param string|int $result
  755. * @access public
  756. * @return bool
  757. */
  758. public function saveLog($webhook, $actionID, $data, $result)
  759. {
  760. $log = new stdclass();
  761. $log->objectType = 'webhook';
  762. $log->objectID = $webhook->id;
  763. $log->action = $actionID;
  764. $log->date = helper::now();
  765. $log->url = $webhook->url;
  766. $log->contentType = $webhook->contentType;
  767. $log->data = $data;
  768. $log->result = (string)$result;
  769. $this->dao->insert(TABLE_LOG)->data($log)->exec();
  770. return !dao::isError();
  771. }
  772. /**
  773. * 设置消息发送状态。
  774. * Set sent status.
  775. *
  776. * @param array|int|string $idList
  777. * @param string $status
  778. * @param string $time
  779. * @access public
  780. * @return void
  781. */
  782. public function setSentStatus($idList, $status, $time = '')
  783. {
  784. $this->dao->update(TABLE_NOTIFY)
  785. ->set('status')->eq($status)
  786. ->beginIF(!empty($time))->set('sendTime')->eq($time)->fi()
  787. ->where('id')->in($idList)
  788. ->exec();
  789. }
  790. }