model.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695
  1. <?php
  2. /**
  3. * The model file of mail 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 Chunsheng Wang <chunsheng@cnezsoft.com>
  8. * @package mail
  9. * @version $Id: model.php 4750 2013-05-05 00:22:53Z chencongzhi520@gmail.com $
  10. * @link https://www.zentao.net
  11. */
  12. ?>
  13. <?php
  14. class mailModel extends model
  15. {
  16. public static $instance;
  17. public $mta;
  18. public $mtaType;
  19. public $errors = array();
  20. /**
  21. * 初始化
  22. * Construct
  23. *
  24. * @access public
  25. * @return void
  26. */
  27. public function __construct()
  28. {
  29. parent::__construct();
  30. $mta = $this->config->mail->mta;
  31. $this->app->loadClass('phpmailer', $static = true);
  32. $this->setMTA();
  33. }
  34. /**
  35. * Auto detect email config.
  36. *
  37. * @param string $email
  38. * @access public
  39. * @return object
  40. */
  41. public function autoDetect($email)
  42. {
  43. $username = $domain = '';
  44. /* Split the email to username and domain. */
  45. if(strpos($email, '@') !== false) list($username, $domain) = explode('@', $email);
  46. $domain = strtolower($domain);
  47. /*
  48. * 1. try to find config from the providers.
  49. * 2. try to find the mx record to get the domain and then search it in providers.
  50. * 3. try smtp.$domain's 25 and 465 port, if can connect, use smtp.$domain.
  51. */
  52. $config = $this->getConfigFromProvider($domain, $username);
  53. if(!$config) $config = $this->getConfigByMXRR($domain, $username);
  54. if(!$config) $config = $this->getConfigByDetectingSMTP($domain, $username, 25);
  55. if(!$config) $config = $this->getConfigByDetectingSMTP($domain, $username, 465);
  56. if(!$config) $config = new stdclass();
  57. /* Set default values. */
  58. $config->mta = 'smtp';
  59. $config->fromName = '';
  60. $config->password = '';
  61. $config->debug = 1;
  62. $config->charset = 'utf-8';
  63. if(!isset($config->secure)) $config->secure = 0;
  64. if(!isset($config->username)) $config->username = $username;
  65. if(!isset($config->host)) $config->host = '';
  66. if(!isset($config->auth)) $config->auth = 1;
  67. if(!isset($config->port)) $config->port = '25';
  68. return $config;
  69. }
  70. /**
  71. * Try get config from providers.
  72. *
  73. * @param string $domain
  74. * @param string $username
  75. * @access public
  76. * @return object|false
  77. */
  78. public function getConfigFromProvider($domain, $username)
  79. {
  80. if(!isset($this->config->mail->provider[$domain])) return false;
  81. $config = (object)$this->config->mail->provider[$domain];
  82. $config->mta = 'smtp';
  83. $config->username = $username;
  84. $config->auth = 1;
  85. if(!isset($config->port)) $config->port = 25;
  86. if(!isset($config->secure)) $config->secure = 0;
  87. return $config;
  88. }
  89. /**
  90. * Get config by MXRR.
  91. *
  92. * @param string $domain
  93. * @param string $username
  94. * @access public
  95. * @return object|false
  96. */
  97. public function getConfigByMXRR($domain, $username)
  98. {
  99. /* Try to get mx record, under linux, use getmxrr() directly, windows use nslookup. */
  100. $smtpHosts = array();
  101. if(function_exists('getmxrr'))
  102. {
  103. getmxrr($domain, $smtpHosts);
  104. }
  105. elseif(strpos(PHP_OS, 'WIN') !== false)
  106. {
  107. $result = `nslookup -q=mx {$domain} 2>nul`;
  108. $lines = explode("\n", $result);
  109. foreach($lines as $line)
  110. {
  111. if(stripos($line, 'exchanger')) $smtpHosts[] = trim(substr($line, strrpos($line, '=') + 1));
  112. }
  113. }
  114. /* Cycle the smtpHosts and try to find it's config from the provider config. */
  115. foreach($smtpHosts as $smtpHost)
  116. {
  117. /* Get the domain name from the hosts, for example: imxbiz1.qq.com get qq.com. */
  118. $smtpDomain = explode('.', $smtpHost);
  119. array_shift($smtpDomain);
  120. $smtpDomain = strtolower(implode('.', $smtpDomain));
  121. $config = $this->getConfigFromProvider($smtpDomain, $username);
  122. if($config) $config->username = "$username@$domain";
  123. return $config;
  124. }
  125. return false;
  126. }
  127. /**
  128. * Try connect to smtp.$domain's 25 or 465 port and compute the config according to the connection result.
  129. *
  130. * @param string $domain
  131. * @param string $username
  132. * @param int $port
  133. * @access public
  134. * @return object|false
  135. */
  136. public function getConfigByDetectingSMTP($domain, $username, $port)
  137. {
  138. ini_set('default_socket_timeout', 3);
  139. $host = 'smtp.' . $domain;
  140. if(gethostbynamel($host) == false) return false;
  141. $connection = @fsockopen($host, $port);
  142. if(!$connection) return false;
  143. fclose($connection);
  144. $config = new stdclass();
  145. $config->username = $username;
  146. $config->host = $host;
  147. $config->auth = 1;
  148. $config->port = $port;
  149. $config->secure = $port == 465 ? 'ssl' : 0;
  150. return $config;
  151. }
  152. /**
  153. * Set MTA.
  154. *
  155. * @access public
  156. * @return void
  157. */
  158. public function setMTA()
  159. {
  160. if(static::$instance == null) static::$instance = new phpmailer(true);
  161. $this->mta = static::$instance;
  162. $this->mta->CharSet = $this->config->charset;
  163. $funcName = "set{$this->config->mail->mta}";
  164. if(!method_exists($this, $funcName)) $this->app->triggerError("The MTA {$this->config->mail->mta} not supported now.", __FILE__, __LINE__, $exit = true);
  165. $this->$funcName();
  166. return $this->mta;
  167. }
  168. /**
  169. * Set smtp.
  170. *
  171. * @access public
  172. * @return void
  173. */
  174. public function setSMTP()
  175. {
  176. $this->mta->isSMTP();
  177. $this->mta->SMTPDebug = $this->config->mail->smtp->debug;
  178. $this->mta->Host = $this->config->mail->smtp->host;
  179. $this->mta->SMTPAuth = $this->config->mail->smtp->auth;
  180. $this->mta->Username = $this->config->mail->smtp->username;
  181. $this->mta->Password = $this->config->mail->smtp->password;
  182. if(isset($this->config->mail->smtp->charset)) $this->mta->CharSet = $this->config->mail->smtp->charset;
  183. if(isset($this->config->mail->smtp->port)) $this->mta->Port = $this->config->mail->smtp->port;
  184. if(isset($this->config->mail->smtp->secure) and !empty($this->config->mail->smtp->secure))$this->mta->SMTPSecure = strtolower($this->config->mail->smtp->secure);
  185. }
  186. /**
  187. * Gmail.
  188. *
  189. * @access public
  190. * @return void
  191. */
  192. public function setGMail()
  193. {
  194. $this->mta->isSMTP();
  195. $this->mta->SMTPDebug = $this->config->mail->gmail->debug;
  196. $this->mta->Host = 'smtp.gmail.com';
  197. $this->mta->Port = 465;
  198. $this->mta->SMTPSecure = "ssl";
  199. $this->mta->SMTPAuth = true;
  200. $this->mta->Username = $this->config->mail->gmail->username;
  201. $this->mta->Password = $this->config->mail->gmail->password;
  202. }
  203. /**
  204. * Send email
  205. *
  206. * @param string $toList
  207. * @param string $subject
  208. * @param string $body
  209. * @param string $ccList
  210. * @param bool $includeMe
  211. * @param array $emails
  212. * @param bool $forceSync
  213. * @param bool $processUser
  214. * @access public
  215. * @return int|string|bool
  216. */
  217. public function send($toList, $subject, $body = '', $ccList = '', $includeMe = false, $emails = array(), $forceSync = false, $processUser = true)
  218. {
  219. if(!$this->config->mail->turnon) return false;
  220. if(!empty($this->config->mail->async) and !$forceSync) return $this->addQueue($toList, $subject, $body, $ccList, $includeMe, $emails = array());
  221. ob_start();
  222. $images = $this->mailTao->getImages($body);
  223. if($images) $body = $this->mailTao->replaceImageURL($body, $images);
  224. if($processUser) list($toList, $ccList) = $this->mailTao->processToAndCC($toList, $ccList, $includeMe);
  225. /* Get realname and email of users. */
  226. if(empty($emails)) $emails = $this->loadModel('user')->getRealNameAndEmails($toList . ',' . $ccList);
  227. $this->clear();
  228. try
  229. {
  230. /* Add for task #5301. */
  231. if(function_exists('putenv')) putenv('RES_OPTIONS=retrans:1 retry:1 timeout:1 attempts:1');
  232. $this->mta->setFrom($this->config->mail->fromAddress, $this->convertCharset($this->config->mail->fromName));
  233. $this->setSubject($this->convertCharset($subject));
  234. $this->setTO(explode(',', $toList), $emails);
  235. $this->setCC(explode(',', $ccList), $emails);
  236. $this->setBody($this->convertCharset($body));
  237. if($images) $this->setImages($images);
  238. $this->setErrorLang();
  239. $this->mta->send();
  240. }
  241. catch (phpmailerException $e)
  242. {
  243. $mailError = ob_get_contents();
  244. $encoding = mb_detect_encoding($mailError, array('ASCII','UTF-8','GB2312','GBK','BIG5'));
  245. if($encoding != 'UTF-8') $mailError = mb_convert_encoding($mailError, 'utf8', $encoding);
  246. $this->errors[] = nl2br(trim(strip_tags($e->errorMessage())));
  247. $this->errors[] = $mailError;
  248. }
  249. catch (Exception $e)
  250. {
  251. $this->errors[] = trim(strip_tags($e->getMessage()));
  252. }
  253. if($this->config->mail->mta == 'smtp') $this->mta->smtpClose();
  254. /* save errors. */
  255. if($this->isError()) $this->app->saveError(2, implode(' ', $this->errors), __FILE__, __LINE__, true);
  256. $message = ob_get_contents();
  257. ob_end_clean();
  258. return $message;
  259. }
  260. /**
  261. * Set to address
  262. *
  263. * @param array $toList
  264. * @param array $emails
  265. * @access public
  266. * @return void
  267. */
  268. public function setTO($toList, $emails)
  269. {
  270. if(empty($toList)) return;
  271. foreach($toList as $account)
  272. {
  273. if(!isset($emails[$account]) or isset($emails[$account]->sended) or strpos($emails[$account]->email, '@') == false) continue;
  274. $this->mta->addAddress($emails[$account]->email, $this->convertCharset($emails[$account]->realname));
  275. $emails[$account]->sended = true;
  276. }
  277. }
  278. /**
  279. * Set cc.
  280. *
  281. * @param array $ccList
  282. * @param array $emails
  283. * @access public
  284. * @return void
  285. */
  286. public function setCC($ccList, $emails)
  287. {
  288. if(empty($ccList)) return;
  289. foreach($ccList as $account)
  290. {
  291. if(!isset($emails[$account]) or isset($emails[$account]->sended) or strpos($emails[$account]->email, '@') == false) continue;
  292. $this->mta->addCC($emails[$account]->email, $this->convertCharset($emails[$account]->realname));
  293. $emails[$account]->sended = true;
  294. }
  295. }
  296. /**
  297. * Set subject
  298. *
  299. * @param string $subject
  300. * @access public
  301. * @return void
  302. */
  303. public function setSubject($subject)
  304. {
  305. $this->mta->Subject = stripslashes($subject);
  306. }
  307. /**
  308. * Set body.
  309. *
  310. * @param string $body
  311. * @access public
  312. * @return void
  313. */
  314. public function setBody($body)
  315. {
  316. $this->mta->msgHtml($body);
  317. }
  318. /**
  319. * 设置嵌入邮件的图片。
  320. * Set embedded images in the email.
  321. *
  322. * @param array $images
  323. * @access public
  324. * @return void
  325. */
  326. public function setImages($images)
  327. {
  328. $wwwRoot = $this->app->getWwwRoot();
  329. $images = array_filter(array_unique($images));
  330. foreach($images as $image) $this->mta->AddEmbeddedImage($image, basename($image));
  331. }
  332. /**
  333. * Convert charset.
  334. *
  335. * @param string $string
  336. * @access public
  337. * @return string
  338. */
  339. public function convertCharset($string)
  340. {
  341. if(!empty($this->config->mail->smtp->charset) and $this->config->mail->smtp->charset != strtolower($this->config->charset)) return helper::convertEncoding($string, $this->config->charset, $this->config->mail->smtp->charset);
  342. return $string;
  343. }
  344. /**
  345. * Set error lang.
  346. *
  347. * @access public
  348. * @return void
  349. */
  350. public function setErrorLang()
  351. {
  352. $this->mta->SetLanguage($this->app->getClientLang());
  353. }
  354. /**
  355. * Clear.
  356. *
  357. * @access public
  358. * @return void
  359. */
  360. public function clear()
  361. {
  362. $this->mta->clearAllRecipients();
  363. $this->mta->clearAttachments();
  364. }
  365. /**
  366. * Check system if there is a mail at least.
  367. *
  368. * @access public
  369. * @return object|false
  370. */
  371. public function mailExist()
  372. {
  373. return $this->dao->select('email')->from(TABLE_USER)->where('email')->ne('')->fetch();
  374. }
  375. /**
  376. * Is error?
  377. *
  378. * @access public
  379. * @return bool
  380. */
  381. public function isError()
  382. {
  383. return !empty($this->errors);
  384. }
  385. /**
  386. * Get errors.
  387. *
  388. * @access public
  389. * @return array
  390. */
  391. public function getError()
  392. {
  393. $errors = $this->errors;
  394. $this->errors = array();
  395. return $errors;
  396. }
  397. /**
  398. * Add queue.
  399. *
  400. * @param string $toList
  401. * @param string $subject
  402. * @param string $body
  403. * @param string $ccList
  404. * @param bool $includeMe
  405. * @access public
  406. * @return int|bool
  407. */
  408. public function addQueue($toList, $subject, $body = '', $ccList = '', $includeMe = false)
  409. {
  410. list($toList, $ccList) = $this->mailTao->processToAndCC($toList, $ccList, $includeMe);
  411. if(empty($toList) and empty($ccList)) return false;
  412. if(empty($toList) or empty($subject)) return false;
  413. $data = new stdclass();
  414. $data->objectType = 'mail';
  415. $data->toList = $toList;
  416. $data->ccList = $ccList;
  417. $data->subject = $subject;
  418. $data->data = $body;
  419. $data->createdBy = $this->app->user->account;
  420. $data->createdDate = helper::now();
  421. $this->dao->insert(TABLE_NOTIFY)->data($data)->autocheck()->exec();
  422. return $this->dao->lastInsertID();
  423. }
  424. /**
  425. * Get queue.
  426. *
  427. * @param string $status all|wait|fail
  428. * @param string $orderBy
  429. * @param object $pager
  430. * @param bool $mergeByUser
  431. * @access public
  432. * @return array
  433. */
  434. public function getQueue($status = 'all', $orderBy = 'id_desc', $pager = null, $mergeByUser = true)
  435. {
  436. $mails = $this->dao->select('*')->from(TABLE_NOTIFY)
  437. ->where('objectType')->eq('mail')
  438. ->beginIF(!empty($status) && $status != 'all')->andWhere('status')->eq($status)->fi()
  439. ->orderBy($orderBy)
  440. ->page($pager)
  441. ->fetchAll('id', false);
  442. if(!$mergeByUser) return $mails;
  443. /* Group mails by toList and ccList. */
  444. $groupMails = array();
  445. foreach($mails as $mail)
  446. {
  447. $users = $mail->toList . ',' . $mail->ccList;
  448. $groupMails[$users][] = $mail;
  449. }
  450. /* Merge the mails if a group has more than one mail. */
  451. $queue = array();
  452. foreach($groupMails as $groupMail)
  453. {
  454. if(count($groupMail) == 1) $queue[] = reset($groupMail);
  455. if(count($groupMail) > 1) $queue[] = $this->mergeMails($groupMail);
  456. }
  457. return $queue;
  458. }
  459. /**
  460. * Get queue by id.
  461. *
  462. * @param int $queueID
  463. * @access public
  464. * @return object|false
  465. */
  466. public function getQueueById($queueID)
  467. {
  468. return $this->dao->select('*')->from(TABLE_NOTIFY)->where('id')->eq($queueID)->fetch();
  469. }
  470. /**
  471. * Merge mails.
  472. *
  473. * @param array $mails
  474. * @access public
  475. * @return object
  476. */
  477. public function mergeMails($mails = array())
  478. {
  479. if(count($mails) <= 1) return array_shift($mails);
  480. /* Get first and last mail. */
  481. $firstMail = array_shift($mails);
  482. $lastMail = array_pop($mails);
  483. $secondMail = empty($mails) ? '' : reset($mails);
  484. /* Set mail info.*/
  485. $mail = new stdClass();
  486. $mail->status = 'wait';
  487. $mail->merge = true;
  488. $mail->id = $firstMail->id;
  489. $mail->toList = $firstMail->toList;
  490. $mail->ccList = $firstMail->ccList;
  491. $mail->subject = $firstMail->subject;
  492. $mail->data = $firstMail->data;
  493. /* Remove html tail for first mail. */
  494. if(($endPos = strripos($firstMail->data, '</td>')) !== false) $mail->data = substr($firstMail->data, 0, $endPos);
  495. if(empty($mails)) $mail->subject .= '|' . $lastMail->subject;
  496. if(!empty($mails)) $mail->subject .= '|' . $secondMail->subject . '|' . $this->lang->mail->more;
  497. foreach($mails as $middleMail)
  498. {
  499. $mail->id .= ',' . $middleMail->id;
  500. /* Remove html head and tail for middle mails. */
  501. $mailBody = $middleMail->data;
  502. if(($beginPos = strpos($mailBody, '</table>')) !== false) $mailBody = substr($mailBody, $beginPos + 8);
  503. if(($endPos = strripos($mailBody, '</td>')) !== false) $mailBody = substr($mailBody, 0, $endPos);
  504. $mail->data .= $mailBody;
  505. }
  506. /* Remove html head for last mail. */
  507. $mailBody = $lastMail->data;
  508. if(($beginPos = strpos($mailBody, '</table>')) !== false) $mailBody = substr($mailBody, $beginPos + 8);
  509. $mail->id .= ',' . $lastMail->id;
  510. $mail->data .= $mailBody;
  511. return $mail;
  512. }
  513. /**
  514. * Send mail by object.
  515. *
  516. * @param int $objectID
  517. * @param int $actionID
  518. * @access public
  519. * @return void
  520. */
  521. public function sendmail($objectID, $actionID)
  522. {
  523. if(empty($objectID) or empty($actionID)) return;
  524. /* Load module and get vars. */
  525. $action = $this->mailTao->getActionForMail($actionID);
  526. $objectType = $action->objectType;
  527. $object = $this->mailTao->getObjectForMail($objectType, $objectID);
  528. $title = $this->mailTao->getObjectTitle($object, $objectType);
  529. $subject = $this->mailTao->getSubject($objectType, $object, $title, $action->action);
  530. $domain = zget($this->config->mail, 'domain', common::getSysURL());
  531. $domain = rtrim($domain, '/');
  532. if(empty($title)) $action->appendLink = '';
  533. if($title and $action->appendLink) $action->appendLink = html::a($domain . helper::createLink($action->objectType, 'view', "id={$action->appendLink}"), "#{$action->appendLink} {$title}");
  534. if($objectType == 'kanbancard') $objectType = 'kanban';
  535. $addressees = $this->mailTao->getAddressees($objectType, $object, $action);
  536. if(!$addressees) return;
  537. list($toList, $ccList) = $addressees;
  538. /* Send it. */
  539. if($objectType == 'mr')
  540. {
  541. $mailContent = $this->mailTao->getMRMailContent($object, $action->action);
  542. if($action->action == 'compilefail') $this->send($toList, $subject, $mailContent, $ccList);
  543. if($action->action == 'compilepass')
  544. {
  545. $this->send($toList, $subject, $mailContent);
  546. $this->send($ccList, $subject, $this->mailTao->getMRMailContent($object, $action->action, 'cc'));
  547. /* Create a todo item for this MR. */
  548. $this->loadModel('mr')->apiCreateMRTodo($object->hostID, $object->targetProject, $object->mriid);
  549. }
  550. }
  551. else
  552. {
  553. if($objectType == 'review') $this->app->loadLang('baseline');
  554. $emails = array();
  555. $mailContent = $this->mailTao->getMailContent($objectType, $object, $action);
  556. if($objectType == 'ticket') $emails = $this->loadModel('ticket')->getContactEmails($object->id, $toList, $ccList, $action->action == 'closed');
  557. $this->send($toList, $subject, $mailContent, $ccList, false, $emails);
  558. }
  559. if($this->isError()) error_log(implode("\n", $this->getError()));
  560. }
  561. /**
  562. * Get subject.
  563. *
  564. * @param string $objectType
  565. * @param object $object
  566. * @param string $title
  567. * @param string $actionType
  568. * @access public
  569. * @return string
  570. */
  571. public function getSubject($objectType, $object, $title, $actionType)
  572. {
  573. $suffix = '';
  574. $subject = '';
  575. $titleType = 'edit';
  576. if($objectType == 'testtask')
  577. {
  578. $this->app->loadLang('testtask');
  579. if($actionType == 'opened') $titleType = 'create';
  580. if($actionType == 'closed') $titleType = 'close';
  581. return sprintf($this->lang->testtask->mail->{$titleType}->title, $this->app->user->realname, $object->id, $object->name);
  582. }
  583. if($objectType == 'doc')
  584. {
  585. $this->app->loadLang('doc');
  586. if($actionType == 'releaseddoc') $titleType = 'releasedDoc';
  587. return sprintf($this->lang->doc->mail->{$titleType}->title, $this->app->user->realname, $object->id, $object->title);
  588. }
  589. if($objectType == 'story' or $objectType == 'bug') $suffix = empty($object->product) ? '' : ' - ' . $this->loadModel('product')->getById($object->product)->name;
  590. if($objectType == 'task') $suffix = empty($object->execution) ? '' : ' - ' . $this->loadModel('execution')->getById($object->execution)->name;
  591. if($objectType == 'story') $objectType = $object->type; // 业务需求和用户需求的邮件标题不同。
  592. return strtoupper($objectType) . ' #' . $object->id . ' ' . $title . $suffix;
  593. }
  594. /**
  595. * 校验传入项的操作列对应操作按钮是否可点击。
  596. * Check if the item is clickable.
  597. *
  598. * @param object $queue
  599. * @param string $method
  600. * @access public
  601. * @return bool
  602. * @param object $item
  603. */
  604. public function isClickable($item, $method)
  605. {
  606. $hasPriv = common::hasPriv('mail', $method);
  607. if($method == 'resend' && $item->status == 'fail') return $hasPriv;
  608. if($method == 'delete') return $hasPriv;
  609. return false;
  610. }
  611. }