model.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. <?php
  2. /**
  3. * The model file of message 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 Yidong Wang <yidong@cnezsoft.com>
  8. * @package message
  9. * @version $Id$
  10. * @link https://www.zentao.net
  11. */
  12. class messageModel extends model
  13. {
  14. /**
  15. * 获取消息。
  16. * Get messages.
  17. *
  18. * @param string $status all|wait|sended
  19. * @param string $orderBy
  20. * @access public
  21. * @return array
  22. */
  23. public function getMessages($status = 'all', $orderBy = 'createdDate')
  24. {
  25. $now = helper::now();
  26. return $this->dao->select('t1.*')->from(TABLE_NOTIFY)->alias('t1')
  27. ->leftJoin(TABLE_ACTION)->alias('t2')->on("t1.objectType = 'message' AND t1.action = t2.id")
  28. ->where('t1.objectType')->eq('message')
  29. ->andWhere('t1.toList')->eq(",{$this->app->user->account},")
  30. ->beginIF(!empty($status) && $status != 'all')->andWhere('t1.status')->eq($status)->fi()
  31. ->andWhere('(t1.sendTime IS NULL OR t1.sendTime <= "' . $now . '")')
  32. ->orderBy($orderBy)
  33. ->fetchAll('id', false);
  34. }
  35. /**
  36. * 获取对象类型。
  37. * Get object types.
  38. *
  39. * @access public
  40. * @return array
  41. */
  42. public function getObjectTypes()
  43. {
  44. $this->loadModel('action');
  45. $objectTypes = array();
  46. foreach($this->config->message->objectTypes as $objectType => $actions)
  47. {
  48. if(!isset($this->lang->action->objectTypes[$objectType])) continue;
  49. $objectTypes[$objectType] = $this->lang->action->objectTypes[$objectType];
  50. }
  51. return $objectTypes;
  52. }
  53. /**
  54. * 获取对象操作。
  55. * Get object actions.
  56. *
  57. * @access public
  58. * @return array
  59. */
  60. public function getObjectActions()
  61. {
  62. $objectActions = array();
  63. foreach($this->config->message->objectTypes as $objectType => $actions)
  64. {
  65. foreach($actions as $action)
  66. {
  67. if(isset($this->lang->message->label->{$action})) $objectActions[$objectType][$action] = $this->lang->message->label->{$action};
  68. if(isset($this->lang->message->label->{$objectType}) && isset($this->lang->message->label->{$objectType}->{$action})) $objectActions[$objectType][$action] = $this->lang->message->label->{$objectType}->{$action};
  69. }
  70. }
  71. return $objectActions;
  72. }
  73. /**
  74. * 发送消息。
  75. * Send messages.
  76. *
  77. * @param string $objectType
  78. * @param int $objectID
  79. * @param string $actionType
  80. * @param int $actionID
  81. * @param string $actor
  82. * @param string $extra
  83. * @access public
  84. * @return void
  85. */
  86. public function send($objectType, $objectID, $actionType, $actionID, $actor = '', $extra = '')
  87. {
  88. if(commonModel::isTutorialMode()) return;
  89. $objectType = strtolower($objectType);
  90. $messageSetting = $this->config->message->setting;
  91. if(is_string($messageSetting)) $messageSetting = json_decode($messageSetting, true);
  92. /* 如果是业需和用需,则使用它们的发信配置。*/
  93. if($objectType == 'story')
  94. {
  95. $story = $this->loadModel('story')->fetchByID($objectID);
  96. if($story) $objectType = $story->type;
  97. if($story && $story->status == 'draft') return;
  98. }
  99. if(isset($messageSetting['mail']))
  100. {
  101. $actions = $messageSetting['mail']['setting'];
  102. if(isset($actions[$objectType]) && in_array($actionType, $actions[$objectType]))
  103. {
  104. /* If it is an api call, get the request method set by the user. */
  105. global $config;
  106. $requestType = $config->requestType;
  107. if(defined('RUN_MODE') && RUN_MODE == 'api')
  108. {
  109. $configRoot = $this->app->getConfigRoot();
  110. include file_exists($configRoot . 'my.php') ? $configRoot . 'my.php' : $configRoot . 'config.php';
  111. }
  112. if($objectType == 'feedback' || $objectType == 'ticket')
  113. {
  114. $this->loadModel($objectType)->sendmail($objectID, $actionID);
  115. }
  116. else
  117. {
  118. $this->loadModel('mail')->sendmail($objectID, $actionID);
  119. }
  120. if(defined('RUN_MODE') && RUN_MODE == 'api') $config->requestType = $requestType;
  121. }
  122. }
  123. if(isset($messageSetting['webhook']))
  124. {
  125. $actions = $messageSetting['webhook']['setting'];
  126. if(isset($actions[$objectType]) && in_array($actionType, $actions[$objectType])) $this->loadModel('webhook')->send($objectType, $objectID, $actionType, $actionID, $actor);
  127. }
  128. if(isset($messageSetting['message']))
  129. {
  130. $isBuiltinMethod = true;
  131. if($this->config->edition != 'open')
  132. {
  133. $groupID = $this->loadModel('workflowgroup')->getGroupIDByDataID($objectType, $objectID);
  134. $method = $this->loadModel('workflowaction')->getByModuleAndAction($objectType, $this->app->rawMethod, $groupID);
  135. if($method && !$method->buildin) $isBuiltinMethod = false;
  136. }
  137. $actions = $messageSetting['message']['setting'];
  138. if($isBuiltinMethod && isset($actions[$objectType]) && in_array($actionType, $actions[$objectType])) $this->saveNotice($objectType, $objectID, $actionType, $actionID, $actor);
  139. }
  140. }
  141. /**
  142. * 批量保存待办消息。
  143. * Batch save todo notice.
  144. *
  145. * @access public
  146. * @return array
  147. */
  148. public function batchSaveTodoNotice()
  149. {
  150. $todos = $this->getNoticeTodos();
  151. if(empty($todos)) return array();
  152. $account = $this->app->user->account;
  153. $newTodos = array();
  154. foreach($todos as $todo)
  155. {
  156. $notice = new stdclass();
  157. $notice->objectType = 'message';
  158. $notice->action = 0;
  159. $notice->toList = ",{$account},";
  160. $notice->data = $todo->data;
  161. $notice->status = 'wait';
  162. $notice->createdBy = $account;
  163. $notice->createdDate = helper::now();
  164. $this->dao->insert(TABLE_NOTIFY)->data($notice)->exec();
  165. $noticeID = $this->dao->lastInsertID();
  166. $todo->id = $noticeID;
  167. $newTodos[$noticeID] = $todo;
  168. }
  169. return $newTodos;
  170. }
  171. /**
  172. * 存储提示消息。
  173. * Save notice.
  174. *
  175. * @param string $objectType
  176. * @param int $objectID
  177. * @param string $actionType
  178. * @param int $actionID
  179. * @param string $actor
  180. * @access public
  181. * @return bool
  182. */
  183. public function saveNotice($objectType, $objectID, $actionType, $actionID, $actor = '')
  184. {
  185. if(empty($actor)) $actor = $this->app->user->account;
  186. if(empty($actor) || !$objectID) return false;
  187. /* 如果对象类型是瀑布,动作是提交审计或者审计,那么对象类型就是审批。*/
  188. if($objectType == 'waterfall' && strpos(',toaudit,audited,', ",{$actionType},") !== false) $objectType = 'review';
  189. $this->loadModel('action');
  190. $user = $this->loadModel('user')->getById($actor);
  191. $table = $this->config->objectTables[$objectType];
  192. $field = $this->config->action->objectNameFields[$objectType];
  193. $object = $this->dao->select('*')->from($table)->where('id')->eq($objectID)->fetch();
  194. $toList = $this->getToList($object, $objectType, $actionID);
  195. if(empty($toList) || $toList == $actor) return false;
  196. $this->app->loadConfig('mail');
  197. $sysURL = zget($this->config->mail, 'domain', common::getSysURL());
  198. $isonlybody = isInModal();
  199. if($isonlybody) unset($_GET['onlybody']);
  200. $methodNmae = 'view';
  201. $moduleName = $objectType == 'case' ? 'testcase' : $objectType;
  202. if($objectType == 'kanbancard') $moduleName = 'kanban';
  203. if($objectType == 'feedback' && $this->config->vision == 'rnd') $methodNmae = 'adminView';
  204. if($objectType == 'auditplan') $object->title = $this->lang->auditplan->common . ' #' . $object->id;
  205. $space = common::checkNotCN() ? ' ' : '';
  206. $data = ($actor == 'guest' ? 'guest' : $user->realname) . $space . $this->lang->action->label->{$actionType} . $space . $this->lang->action->objectTypes[$objectType];
  207. $dataID = $objectType == 'kanbancard' ? $object->kanban : $objectID;
  208. $url = helper::createLink($moduleName, $methodNmae, "id={$dataID}") . "#app={$this->app->tab}";
  209. $data .= ' ' . html::a((strpos($url, $sysURL) === 0 ? '' : $sysURL) . $url, "[#{$objectID}::{$object->$field}]");
  210. if($objectType == 'aitask' && in_array($actionType, array('finished', 'failed')))
  211. {
  212. $url = helper::createLink('aitask', 'view', "taskID={$objectID}");
  213. $linkUrl = (strpos($url, $sysURL) === 0 ? '' : $sysURL) . $url;
  214. $data = $this->loadModel('aitask')->getNotificationText($object, $objectID, $actionType, 'html', $linkUrl);
  215. }
  216. else
  217. {
  218. $methodNmae = 'view';
  219. $moduleName = $objectType == 'case' ? 'testcase' : $objectType;
  220. if($objectType == 'kanbancard') $moduleName = 'kanban';
  221. if($objectType == 'feedback' && $this->config->vision == 'rnd') $methodNmae = 'adminView';
  222. $space = common::checkNotCN() ? ' ' : '';
  223. $data = ($actor == 'guest' ? 'guest' : $user->realname) . $space . $this->lang->action->label->{$actionType} . $space . $this->lang->action->objectTypes[$objectType];
  224. $dataID = $objectType == 'kanbancard' ? $object->kanban : $objectID;
  225. $url = helper::createLink($moduleName, $methodNmae, "id={$dataID}");
  226. $data .= ' ' . html::a((strpos($url, $sysURL) === 0 ? '' : $sysURL) . $url, "[#{$objectID}::{$object->$field}]");
  227. }
  228. $sendTime = null;
  229. if($objectType == 'aitask' && in_array($actionType, array('finished', 'failed')))
  230. {
  231. $sendTime = helper::now();
  232. if($object && !empty($object->noticeTime) && $object->noticeTime != '1')
  233. {
  234. $today = date('Y-m-d');
  235. $targetTime = $today . ' ' . $object->noticeTime . ':00';
  236. $targetTimestamp = strtotime($targetTime);
  237. $nowTimestamp = time();
  238. $sendTime = $targetTimestamp < $nowTimestamp ? helper::now() : $targetTime;
  239. }
  240. }
  241. if($isonlybody) $_GET['onlybody'] = 'yes';
  242. foreach(explode(',', trim($toList, ',')) as $to)
  243. {
  244. if($to == $actor || empty($to)) continue;
  245. $notify = new stdclass();
  246. $notify->objectType = 'message';
  247. $notify->action = $actionID;
  248. $notify->toList = ",{$to},";
  249. $notify->data = $data;
  250. $notify->status = 'wait';
  251. $notify->createdBy = $actor;
  252. $notify->createdDate = helper::now();
  253. $notify->sendTime = $sendTime;
  254. $this->dao->insert(TABLE_NOTIFY)->data($notify)->exec();
  255. }
  256. return true;
  257. }
  258. /**
  259. * 获取抄送给的人员。
  260. * Get toList.
  261. *
  262. * @param object $object
  263. * @param string $objectType
  264. * @param int $actionID
  265. * @access public
  266. * @return string
  267. */
  268. public function getToList($object, $objectType, $actionID = 0)
  269. {
  270. $toList = '';
  271. $ccList = '';
  272. if($objectType == 'aitask')
  273. {
  274. $toList = !empty($object->assignedTo) ? $object->assignedTo : ($object->createdBy ?? '');
  275. return trim($toList, ',');
  276. }
  277. if(!empty($object->assignedTo)) $toList = $object->assignedTo;
  278. if(empty($toList) && $objectType == 'todo') $toList = $object->account;
  279. if(empty($toList) && $objectType == 'testtask') $toList = $object->owner;
  280. if(empty($toList) && $objectType == 'meeting') $toList = $object->host . $object->participant;
  281. if(empty($toList) && $objectType == 'mr') $toList = $object->createdBy . ',' . $object->assignee;
  282. if(empty($toList) and $objectType == 'demandpool') $toList = trim($object->owner, ',') . ',' . trim($object->reviewer, ',');
  283. if(empty($toList) && in_array($objectType, array('release', 'doc', 'execution')))
  284. {
  285. list($toList, $ccList) = $this->loadModel($objectType)->getToAndCcList($object);
  286. $toList = $toList . ',' . $ccList;
  287. }
  288. if(empty($toList) && $objectType == 'rule' && $actionID)
  289. {
  290. $action = $this->loadModel('action')->getById($actionID);
  291. list($toList, $ccList) = $this->loadModel('rule')->getToAndCcList($object, $action);
  292. $toList = $toList . ',' . $ccList;
  293. }
  294. if($toList == 'closed') $toList = '';
  295. if($objectType == 'feedback' && $object->status == 'replied') $toList = ',' . $object->openedBy . ',';
  296. if(in_array($objectType, array('story', 'epic', 'requirement', 'ticket', 'review', 'deploy', 'task', 'feedback', 'reviewissue', 'bug')) && $actionID)
  297. {
  298. $action = $this->loadModel('action')->getById($actionID);
  299. $toAndCcList = $this->loadModel($objectType)->getToAndCcList($object, $action->action);
  300. if(!empty($toAndCcList)) list($toList, $ccList) = $toAndCcList;
  301. $toList = $toList . ',' . $ccList;
  302. }
  303. if($objectType == 'testtask')
  304. {
  305. $toList = array_merge(explode(',', $toList), explode(',', $object->members));
  306. $toList = array_filter(array_unique($toList));
  307. $toList = implode(',', $toList);
  308. }
  309. if(empty($toList) and $objectType == 'demand' and $this->config->edition == 'ipd')
  310. {
  311. $toList = $object->assignedTo;
  312. $toList .= ',' . str_replace(' ', '', trim($object->mailto, ','));
  313. $toList .= ",$object->createdBy";
  314. $reviewers = $this->loadModel('demand')->getReviewerPairs($object->id, $object->version);
  315. $reviewers = array_keys($reviewers);
  316. if($reviewers) $toList .= ',' . implode(',', $reviewers);
  317. $toList = trim($toList, ',');
  318. }
  319. if(strpos(',opportunity,risk,issue,', ",{$objectType},") !== false) $toList = "{$object->assignedTo},{$object->createdBy}";
  320. /* 非内置工作流使用工作流的toList。 */
  321. if($this->config->edition != 'open')
  322. {
  323. $flow = $this->loadModel('workflow')->getByModule($objectType);
  324. $groupID = $this->loadModel('workflowgroup')->getGroupIDByDataID($objectType, $object->id);
  325. $method = $this->loadModel('workflowaction')->getByModuleAndAction($objectType, $this->app->rawMethod, $groupID);
  326. if(($flow && !$flow->buildin) || ($method && !$method->buildin)) $toList = $this->loadModel('flow')->getToList($flow, $object->id, $method);
  327. }
  328. if($objectType == 'product') $toList = $object->createdBy . ',' . $object->PO;
  329. if($objectType == 'project') $toList = $object->openedBy . ',' . $object->PM;
  330. return trim($toList, ',');
  331. }
  332. /**
  333. * 获取提示待办。
  334. * Get notice todos.
  335. *
  336. * @access public
  337. * @return array
  338. */
  339. public function getNoticeTodos()
  340. {
  341. $todos = $this->loadModel('todo')->getList('today', $this->app->user->account, 'wait');
  342. $notices = array();
  343. $now = helper::now();
  344. $interval = 60;
  345. if($todos)
  346. {
  347. /* Set date array. */
  348. $begins[1] = (int)date('Hi', strtotime($now));
  349. $begins[10] = (int)date('Hi', strtotime("+10 minute {$now}"));
  350. $begins[30] = (int)date('Hi', strtotime("+30 minute {$now}"));
  351. $ends[1] = (int)date('Hi', strtotime("+{$interval} seconds {$now}"));
  352. $ends[10] = (int)date('Hi', strtotime("+10 minute {$interval} seconds {$now}"));
  353. $ends[30] = (int)date('Hi', strtotime("+30 minute {$interval} seconds {$now}"));
  354. foreach($todos as $todo)
  355. {
  356. if(empty($todo->begin)) continue;
  357. $time = (int)str_replace(':', '', $todo->begin);
  358. $lastTime = 0;
  359. if($time > $begins[1] && $time <= $ends[1]) $lastTime = 1;
  360. if($time > $begins[10] && $time <= $ends[10]) $lastTime = 10;
  361. if($time > $begins[30] && $time <= $ends[30]) $lastTime = 30;
  362. /* If the todo needs to be reminded, add it to notices array. */
  363. if($lastTime)
  364. {
  365. $notice = new stdclass();
  366. $notice->id = 'todo' . $todo->id;
  367. $notice->data = $this->lang->todo->common . ' ' . html::a(helper::createLink('todo', 'view', "id={$todo->id}"), "{$todo->begin} {$todo->name}");
  368. $notices[$notice->id] = $notice;
  369. }
  370. }
  371. }
  372. return $notices;
  373. }
  374. /**
  375. * 获取浏览器通知的相关配置信息。
  376. * Get browser message config.
  377. *
  378. * @access public
  379. * @return array
  380. */
  381. public function getBrowserMessageConfig()
  382. {
  383. return array('turnon' => $this->config->message->browser->turnon, 'pollTime' => $this->config->message->browser->pollTime);
  384. }
  385. /**
  386. * 获取未读消息数量。
  387. * Get unread count.
  388. *
  389. * @access public
  390. * @return int
  391. */
  392. public function getUnreadCount()
  393. {
  394. $account = $this->app->user->account;
  395. return $this->dao->select('COUNT(1) as count')->from(TABLE_NOTIFY)->where('toList')->eq(",{$account},")->andWhere('objectType')->eq('message')->andWhere('status')->ne('read')->fetch('count');
  396. }
  397. /**
  398. * 删除过期消息。
  399. * Delete expire messages.
  400. *
  401. * @access public
  402. * @return void
  403. */
  404. public function deleteExpired()
  405. {
  406. $days = (int)$this->config->message->browser->maxDays;
  407. $account = $this->app->user->account;
  408. $expiryDate = date('Y-m-d 00:00:00', time() - 86400 * ($days + 1));
  409. $this->dao->delete()->from(TABLE_NOTIFY)->where('toList')->eq(",{$account},")->andWhere('objectType')->eq('message')->andWhere('createdDate')->lt($expiryDate)->exec();
  410. }
  411. }