* @package message * @version $Id$ * @link https://www.zentao.net */ class messageModel extends model { /** * 获取消息。 * Get messages. * * @param string $status all|wait|sended * @param string $orderBy * @access public * @return array */ public function getMessages(string $status = 'all', string $orderBy = 'createdDate'): array { $now = helper::now(); return $this->dao->select('t1.*')->from(TABLE_NOTIFY)->alias('t1') ->leftJoin(TABLE_ACTION)->alias('t2')->on("t1.objectType = 'message' AND t1.action = t2.id") ->where('t1.objectType')->eq('message') ->andWhere('t1.toList')->eq(",{$this->app->user->account},") ->beginIF(!empty($status) && $status != 'all')->andWhere('t1.status')->eq($status)->fi() ->andWhere('(t1.sendTime IS NULL OR t1.sendTime <= "' . $now . '")') ->orderBy($orderBy) ->fetchAll('id', false); } /** * 获取对象类型。 * Get object types. * * @access public * @return array */ public function getObjectTypes(): array { $this->loadModel('action'); $objectTypes = array(); foreach($this->config->message->objectTypes as $objectType => $actions) { if(!isset($this->lang->action->objectTypes[$objectType])) continue; $objectTypes[$objectType] = $this->lang->action->objectTypes[$objectType]; } return $objectTypes; } /** * 获取对象操作。 * Get object actions. * * @access public * @return array */ public function getObjectActions(): array { $objectActions = array(); foreach($this->config->message->objectTypes as $objectType => $actions) { foreach($actions as $action) { if(isset($this->lang->message->label->{$action})) $objectActions[$objectType][$action] = $this->lang->message->label->{$action}; if(isset($this->lang->message->label->{$objectType}) && isset($this->lang->message->label->{$objectType}->{$action})) $objectActions[$objectType][$action] = $this->lang->message->label->{$objectType}->{$action}; } } return $objectActions; } /** * 发送消息。 * Send messages. * * @param string $objectType * @param int $objectID * @param string $actionType * @param int $actionID * @param string $actor * @param string $extra * @access public * @return void */ public function send(string $objectType, int $objectID, string $actionType, int $actionID, string $actor = '', string $extra = ''): void { if(commonModel::isTutorialMode()) return; $objectType = strtolower($objectType); $messageSetting = $this->config->message->setting; if(is_string($messageSetting)) $messageSetting = json_decode($messageSetting, true); /* 如果是业需和用需,则使用它们的发信配置。*/ if($objectType == 'story') { $story = $this->loadModel('story')->fetchByID($objectID); if($story) $objectType = $story->type; if($story && $story->status == 'draft') return; } if(isset($messageSetting['mail'])) { $actions = $messageSetting['mail']['setting']; if(isset($actions[$objectType]) && in_array($actionType, $actions[$objectType])) { /* If it is an api call, get the request method set by the user. */ global $config; $requestType = $config->requestType; if(defined('RUN_MODE') && RUN_MODE == 'api') { $configRoot = $this->app->getConfigRoot(); include file_exists($configRoot . 'my.php') ? $configRoot . 'my.php' : $configRoot . 'config.php'; } if($objectType == 'feedback' || $objectType == 'ticket') { $this->loadModel($objectType)->sendmail($objectID, $actionID); } else { $this->loadModel('mail')->sendmail($objectID, $actionID); } if(defined('RUN_MODE') && RUN_MODE == 'api') $config->requestType = $requestType; } } if(isset($messageSetting['webhook'])) { $actions = $messageSetting['webhook']['setting']; if(isset($actions[$objectType]) && in_array($actionType, $actions[$objectType])) $this->loadModel('webhook')->send($objectType, $objectID, $actionType, $actionID, $actor); } if(isset($messageSetting['message'])) { $isBuiltinMethod = true; if($this->config->edition != 'open') { $groupID = $this->loadModel('workflowgroup')->getGroupIDByDataID($objectType, $objectID); $method = $this->loadModel('workflowaction')->getByModuleAndAction($objectType, $this->app->rawMethod, $groupID); if($method && !$method->buildin) $isBuiltinMethod = false; } $actions = $messageSetting['message']['setting']; if($isBuiltinMethod && isset($actions[$objectType]) && in_array($actionType, $actions[$objectType])) $this->saveNotice($objectType, $objectID, $actionType, $actionID, $actor); } } /** * 批量保存待办消息。 * Batch save todo notice. * * @access public * @return array */ public function batchSaveTodoNotice(): array { $todos = $this->getNoticeTodos(); if(empty($todos)) return array(); $account = $this->app->user->account; $newTodos = array(); foreach($todos as $todo) { $notice = new stdclass(); $notice->objectType = 'message'; $notice->action = 0; $notice->toList = ",{$account},"; $notice->data = $todo->data; $notice->status = 'wait'; $notice->createdBy = $account; $notice->createdDate = helper::now(); $this->dao->insert(TABLE_NOTIFY)->data($notice)->exec(); $noticeID = $this->dao->lastInsertID(); $todo->id = $noticeID; $newTodos[$noticeID] = $todo; } return $newTodos; } /** * 存储提示消息。 * Save notice. * * @param string $objectType * @param int $objectID * @param string $actionType * @param int $actionID * @param string $actor * @access public * @return bool */ public function saveNotice(string $objectType, int $objectID, string $actionType, int $actionID, string $actor = ''): bool { if(empty($actor)) $actor = $this->app->user->account; if(empty($actor) || !$objectID) return false; /* 如果对象类型是瀑布,动作是提交审计或者审计,那么对象类型就是审批。*/ if($objectType == 'waterfall' && strpos(',toaudit,audited,', ",{$actionType},") !== false) $objectType = 'review'; $this->loadModel('action'); $user = $this->loadModel('user')->getById($actor); $table = $this->config->objectTables[$objectType]; $field = $this->config->action->objectNameFields[$objectType]; $object = $this->dao->select('*')->from($table)->where('id')->eq($objectID)->fetch(); $toList = $this->getToList($object, $objectType, $actionID); if(empty($toList) || $toList == $actor) return false; $this->app->loadConfig('mail'); $sysURL = zget($this->config->mail, 'domain', common::getSysURL()); $isonlybody = isInModal(); if($isonlybody) unset($_GET['onlybody']); $methodNmae = 'view'; $moduleName = $objectType == 'case' ? 'testcase' : $objectType; if($objectType == 'kanbancard') $moduleName = 'kanban'; if($objectType == 'feedback' && $this->config->vision == 'rnd') $methodNmae = 'adminView'; if($objectType == 'auditplan') $object->title = $this->lang->auditplan->common . ' #' . $object->id; $space = common::checkNotCN() ? ' ' : ''; $data = ($actor == 'guest' ? 'guest' : $user->realname) . $space . $this->lang->action->label->{$actionType} . $space . $this->lang->action->objectTypes[$objectType]; $dataID = $objectType == 'kanbancard' ? $object->kanban : $objectID; $url = helper::createLink($moduleName, $methodNmae, "id={$dataID}") . "#app={$this->app->tab}"; $data .= ' ' . html::a((strpos($url, $sysURL) === 0 ? '' : $sysURL) . $url, "[#{$objectID}::{$object->$field}]"); if($objectType == 'aitask' && in_array($actionType, array('finished', 'failed'))) { $url = helper::createLink('aitask', 'view', "taskID={$objectID}"); $linkUrl = (strpos($url, $sysURL) === 0 ? '' : $sysURL) . $url; $data = $this->loadModel('aitask')->getNotificationText($object, $objectID, $actionType, 'html', $linkUrl); } else { $methodNmae = 'view'; $moduleName = $objectType == 'case' ? 'testcase' : $objectType; if($objectType == 'kanbancard') $moduleName = 'kanban'; if($objectType == 'feedback' && $this->config->vision == 'rnd') $methodNmae = 'adminView'; $space = common::checkNotCN() ? ' ' : ''; $data = ($actor == 'guest' ? 'guest' : $user->realname) . $space . $this->lang->action->label->{$actionType} . $space . $this->lang->action->objectTypes[$objectType]; $dataID = $objectType == 'kanbancard' ? $object->kanban : $objectID; $url = helper::createLink($moduleName, $methodNmae, "id={$dataID}"); $data .= ' ' . html::a((strpos($url, $sysURL) === 0 ? '' : $sysURL) . $url, "[#{$objectID}::{$object->$field}]"); } $sendTime = null; if($objectType == 'aitask' && in_array($actionType, array('finished', 'failed'))) { $sendTime = helper::now(); if($object && !empty($object->noticeTime) && $object->noticeTime != '1') { $today = date('Y-m-d'); $targetTime = $today . ' ' . $object->noticeTime . ':00'; $targetTimestamp = strtotime($targetTime); $nowTimestamp = time(); $sendTime = $targetTimestamp < $nowTimestamp ? helper::now() : $targetTime; } } if($isonlybody) $_GET['onlybody'] = 'yes'; foreach(explode(',', trim($toList, ',')) as $to) { if($to == $actor || empty($to)) continue; $notify = new stdclass(); $notify->objectType = 'message'; $notify->action = $actionID; $notify->toList = ",{$to},"; $notify->data = $data; $notify->status = 'wait'; $notify->createdBy = $actor; $notify->createdDate = helper::now(); $notify->sendTime = $sendTime; $this->dao->insert(TABLE_NOTIFY)->data($notify)->exec(); } return true; } /** * 获取抄送给的人员。 * Get toList. * * @param object $object * @param string $objectType * @param int $actionID * @access public * @return string */ public function getToList(object $object, string $objectType, int $actionID = 0): string { $toList = ''; $ccList = ''; if($objectType == 'aitask') { $toList = !empty($object->assignedTo) ? $object->assignedTo : ($object->createdBy ?? ''); return trim($toList, ','); } if(!empty($object->assignedTo)) $toList = $object->assignedTo; if(empty($toList) && $objectType == 'todo') $toList = $object->account; if(empty($toList) && $objectType == 'testtask') $toList = $object->owner; if(empty($toList) && $objectType == 'meeting') $toList = $object->host . $object->participant; if(empty($toList) && $objectType == 'mr') $toList = $object->createdBy . ',' . $object->assignee; if(empty($toList) and $objectType == 'demandpool') $toList = trim($object->owner, ',') . ',' . trim($object->reviewer, ','); if(empty($toList) && in_array($objectType, array('release', 'doc', 'execution'))) { list($toList, $ccList) = $this->loadModel($objectType)->getToAndCcList($object); $toList = $toList . ',' . $ccList; } if(empty($toList) && $objectType == 'rule' && $actionID) { $action = $this->loadModel('action')->getById($actionID); list($toList, $ccList) = $this->loadModel('rule')->getToAndCcList($object, $action); $toList = $toList . ',' . $ccList; } if($toList == 'closed') $toList = ''; if($objectType == 'feedback' && $object->status == 'replied') $toList = ',' . $object->openedBy . ','; if(in_array($objectType, array('story', 'epic', 'requirement', 'ticket', 'review', 'deploy', 'task', 'feedback', 'reviewissue', 'bug')) && $actionID) { $action = $this->loadModel('action')->getById($actionID); $toAndCcList = $this->loadModel($objectType)->getToAndCcList($object, $action->action); if(!empty($toAndCcList)) list($toList, $ccList) = $toAndCcList; $toList = $toList . ',' . $ccList; } if($objectType == 'testtask') { $toList = array_merge(explode(',', $toList), explode(',', $object->members)); $toList = array_filter(array_unique($toList)); $toList = implode(',', $toList); } if(empty($toList) and $objectType == 'demand' and $this->config->edition == 'ipd') { $toList = $object->assignedTo; $toList .= ',' . str_replace(' ', '', trim($object->mailto, ',')); $toList .= ",$object->createdBy"; $reviewers = $this->loadModel('demand')->getReviewerPairs($object->id, $object->version); $reviewers = array_keys($reviewers); if($reviewers) $toList .= ',' . implode(',', $reviewers); $toList = trim($toList, ','); } if(strpos(',opportunity,risk,issue,', ",{$objectType},") !== false) $toList = "{$object->assignedTo},{$object->createdBy}"; /* 非内置工作流使用工作流的toList。 */ if($this->config->edition != 'open') { $flow = $this->loadModel('workflow')->getByModule($objectType); $groupID = $this->loadModel('workflowgroup')->getGroupIDByDataID($objectType, $object->id); $method = $this->loadModel('workflowaction')->getByModuleAndAction($objectType, $this->app->rawMethod, $groupID); if(($flow && !$flow->buildin) || ($method && !$method->buildin)) $toList = $this->loadModel('flow')->getToList($flow, $object->id, $method); } if($objectType == 'product') $toList = $object->createdBy . ',' . $object->PO; if($objectType == 'project') $toList = $object->openedBy . ',' . $object->PM; return trim($toList, ','); } /** * 获取提示待办。 * Get notice todos. * * @access public * @return array */ public function getNoticeTodos(): array { $todos = $this->loadModel('todo')->getList('today', $this->app->user->account, 'wait'); $notices = array(); $now = helper::now(); $interval = 60; if($todos) { /* Set date array. */ $begins[1] = (int)date('Hi', strtotime($now)); $begins[10] = (int)date('Hi', strtotime("+10 minute {$now}")); $begins[30] = (int)date('Hi', strtotime("+30 minute {$now}")); $ends[1] = (int)date('Hi', strtotime("+{$interval} seconds {$now}")); $ends[10] = (int)date('Hi', strtotime("+10 minute {$interval} seconds {$now}")); $ends[30] = (int)date('Hi', strtotime("+30 minute {$interval} seconds {$now}")); foreach($todos as $todo) { if(empty($todo->begin)) continue; $time = (int)str_replace(':', '', $todo->begin); $lastTime = 0; if($time > $begins[1] && $time <= $ends[1]) $lastTime = 1; if($time > $begins[10] && $time <= $ends[10]) $lastTime = 10; if($time > $begins[30] && $time <= $ends[30]) $lastTime = 30; /* If the todo needs to be reminded, add it to notices array. */ if($lastTime) { $notice = new stdclass(); $notice->id = 'todo' . $todo->id; $notice->data = $this->lang->todo->common . ' ' . html::a(helper::createLink('todo', 'view', "id={$todo->id}"), "{$todo->begin} {$todo->name}"); $notices[$notice->id] = $notice; } } } return $notices; } /** * 获取浏览器通知的相关配置信息。 * Get browser message config. * * @access public * @return array */ public function getBrowserMessageConfig(): array { return array('turnon' => $this->config->message->browser->turnon, 'pollTime' => $this->config->message->browser->pollTime); } /** * 获取未读消息数量。 * Get unread count. * * @access public * @return int */ public function getUnreadCount(): int { $account = $this->app->user->account; 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'); } /** * 删除过期消息。 * Delete expire messages. * * @access public * @return void */ public function deleteExpired(): void { $days = (int)$this->config->message->browser->maxDays; $account = $this->app->user->account; $expiryDate = date('Y-m-d 00:00:00', time() - 86400 * ($days + 1)); $this->dao->delete()->from(TABLE_NOTIFY)->where('toList')->eq(",{$account},")->andWhere('objectType')->eq('message')->andWhere('createdDate')->lt($expiryDate)->exec(); } }