PageRenderTime 68ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 1ms

/modules/InboundEmail/InboundEmail.php

https://bitbucket.org/cviolette/sugarcrm
PHP | 6480 lines | 4383 code | 783 blank | 1314 comment | 812 complexity | 4ed6b6cb6b0602abf3a6f2907774d78b MD5 | raw file
Possible License(s): LGPL-2.1, MPL-2.0-no-copyleft-exception, BSD-3-Clause

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
  3. /*********************************************************************************
  4. * SugarCRM Community Edition is a customer relationship management program developed by
  5. * SugarCRM, Inc. Copyright (C) 2004-2012 SugarCRM Inc.
  6. *
  7. * This program is free software; you can redistribute it and/or modify it under
  8. * the terms of the GNU Affero General Public License version 3 as published by the
  9. * Free Software Foundation with the addition of the following permission added
  10. * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
  11. * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
  12. * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
  13. *
  14. * This program is distributed in the hope that it will be useful, but WITHOUT
  15. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  16. * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
  17. * details.
  18. *
  19. * You should have received a copy of the GNU Affero General Public License along with
  20. * this program; if not, see http://www.gnu.org/licenses or write to the Free
  21. * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  22. * 02110-1301 USA.
  23. *
  24. * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
  25. * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
  26. *
  27. * The interactive user interfaces in modified source and object code versions
  28. * of this program must display Appropriate Legal Notices, as required under
  29. * Section 5 of the GNU Affero General Public License version 3.
  30. *
  31. * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
  32. * these Appropriate Legal Notices must retain the display of the "Powered by
  33. * SugarCRM" logo. If the display of the logo is not reasonably feasible for
  34. * technical reasons, the Appropriate Legal Notices must display the words
  35. * "Powered by SugarCRM".
  36. ********************************************************************************/
  37. require_once('include/OutboundEmail/OutboundEmail.php');
  38. function this_callback($str) {
  39. foreach($str as $match) {
  40. $ret .= chr(hexdec(str_replace("%","",$match)));
  41. }
  42. return $ret;
  43. }
  44. /**
  45. * Stub for certain interactions;
  46. */
  47. class temp {
  48. var $name;
  49. }
  50. class InboundEmail extends SugarBean {
  51. // module specific
  52. var $conn;
  53. var $purifier; // HTMLPurifier object placeholder
  54. var $email;
  55. // fields
  56. var $id;
  57. var $deleted;
  58. var $date_entered;
  59. var $date_modified;
  60. var $modified_user_id;
  61. var $created_by;
  62. var $created_by_name;
  63. var $modified_by_name;
  64. var $name;
  65. var $status;
  66. var $server_url;
  67. var $email_user;
  68. var $email_password;
  69. var $port;
  70. var $service;
  71. var $mailbox;
  72. var $mailboxarray;
  73. var $delete_seen;
  74. var $mailbox_type;
  75. var $template_id;
  76. var $stored_options;
  77. var $group_id;
  78. var $is_personal;
  79. var $groupfolder_id;
  80. // email 2.0
  81. var $pop3socket;
  82. var $outboundInstance; // id to outbound_email instance
  83. var $autoImport;
  84. var $iconFlagged = "F";
  85. var $iconDraft = "D";
  86. var $iconAnswered = "A";
  87. var $iconDeleted = "del";
  88. var $isAutoImport = false;
  89. var $smarty;
  90. var $attachmentCount = 0;
  91. var $tempAttachment = array();
  92. var $unsafeChars = array("&", "!", "'", '"', '\\', '/', '<', '>', '|', '$',);
  93. var $currentCache;
  94. var $defaultSort = 'date';
  95. var $defaultDirection = "DESC";
  96. var $hrSort = array(
  97. 0 => 'flagged',
  98. 1 => 'status',
  99. 2 => 'from',
  100. 3 => 'subj',
  101. 4 => 'date',
  102. );
  103. var $hrSortLocal = array(
  104. 'flagged' => 'flagged',
  105. 'status' => 'answered',
  106. 'from' => 'fromaddr',
  107. 'subject' => 'subject',
  108. 'date' => 'senddate',
  109. );
  110. // default attributes
  111. var $transferEncoding = array(0 => '7BIT',
  112. 1 => '8BIT',
  113. 2 => 'BINARY',
  114. 3 => 'BASE64',
  115. 4 => 'QUOTED-PRINTABLE',
  116. 5 => 'OTHER'
  117. );
  118. // object attributes
  119. var $compoundMessageId; // concatenation of messageID and deliveredToEmail
  120. var $serverConnectString;
  121. var $disable_row_level_security = true;
  122. var $InboundEmailCachePath;
  123. var $InboundEmailCacheFile = 'InboundEmail.cache.php';
  124. var $object_name = 'InboundEmail';
  125. var $module_dir = 'InboundEmail';
  126. var $table_name = 'inbound_email';
  127. var $new_schema = true;
  128. var $process_save_dates = true;
  129. var $order_by;
  130. var $db;
  131. var $dbManager;
  132. var $field_defs;
  133. var $column_fields;
  134. var $required_fields = array('name' => 'name',
  135. 'server_url' => 'server_url',
  136. 'mailbox' => 'mailbox',
  137. 'user' => 'user',
  138. 'port' => 'port',
  139. );
  140. var $imageTypes = array("JPG", "JPEG", "GIF", "PNG");
  141. var $inlineImages = array(); // temporary space to store ID of inlined images
  142. var $defaultEmailNumAutoreplies24Hours = 10;
  143. var $maxEmailNumAutoreplies24Hours = 10;
  144. // custom ListView attributes
  145. var $mailbox_type_name;
  146. var $global_personal_string;
  147. // service attributes
  148. var $tls;
  149. var $ca;
  150. var $ssl;
  151. var $protocol;
  152. var $keyForUsersDefaultIEAccount = 'defaultIEAccount';
  153. // prefix to use when importing inlinge images in emails
  154. public $imagePrefix;
  155. /**
  156. * Sole constructor
  157. */
  158. function InboundEmail() {
  159. $this->InboundEmailCachePath = sugar_cached('modules/InboundEmail');
  160. $this->EmailCachePath = sugar_cached('modules/Emails');
  161. parent::SugarBean();
  162. if(function_exists("imap_timeout")) {
  163. /*
  164. * 1: Open
  165. * 2: Read
  166. * 3: Write
  167. * 4: Close
  168. */
  169. imap_timeout(1, 60);
  170. imap_timeout(2, 60);
  171. imap_timeout(3, 60);
  172. }
  173. $this->smarty = new Sugar_Smarty();
  174. $this->overview = new Overview();
  175. $this->imagePrefix = "{$GLOBALS['sugar_config']['site_url']}/cache/images/";
  176. }
  177. /**
  178. * retrieves I-E bean
  179. * @param string id
  180. * @return object Bean
  181. */
  182. function retrieve($id, $encode=true, $deleted=true) {
  183. $ret = parent::retrieve($id,$encode,$deleted);
  184. // if I-E bean exist
  185. if (!is_null($ret)) {
  186. $this->email_password = blowfishDecode(blowfishGetKey('InboundEmail'), $this->email_password);
  187. $this->retrieveMailBoxFolders();
  188. }
  189. return $ret;
  190. }
  191. /**
  192. * wraps SugarBean->save()
  193. * @param string ID of saved bean
  194. */
  195. function save($check_notify=false) {
  196. // generate cache table for email 2.0
  197. $multiDImArray = $this->generateMultiDimArrayFromFlatArray(explode(",", $this->mailbox), $this->retrieveDelimiter());
  198. $raw = $this->generateFlatArrayFromMultiDimArray($multiDImArray, $this->retrieveDelimiter());
  199. sort($raw);
  200. //_pp(explode(",", $this->mailbox));
  201. //_ppd($raw);
  202. $raw = $this->filterMailBoxFromRaw(explode(",", $this->mailbox), $raw);
  203. $this->mailbox = implode(",", $raw);
  204. if(!empty($this->email_password)) {
  205. $this->email_password = blowfishEncode(blowfishGetKey('InboundEmail'), $this->email_password);
  206. }
  207. $ret = parent::save($check_notify);
  208. return $ret;
  209. }
  210. function filterMailBoxFromRaw($mailboxArray, $rawArray) {
  211. $newArray = array_intersect($mailboxArray, $rawArray);
  212. sort($newArray);
  213. return $newArray;
  214. } // fn
  215. /**
  216. * Overrides SugarBean's mark_deleted() to drop the related cache table
  217. * @param string $id GUID of I-E instance
  218. */
  219. function mark_deleted($id) {
  220. parent::mark_deleted($id);
  221. $q = "update inbound_email set groupfolder_id = null WHERE id = '{$id}'";
  222. $r = $this->db->query($q);
  223. $this->deleteCache();
  224. }
  225. /**
  226. * Mark cached email answered (replied)
  227. * @param string $mailid (uid for imap, message_id for pop3)
  228. */
  229. function mark_answered($mailid, $type = 'smtp') {
  230. switch ($type) {
  231. case 'smtp' :
  232. $q = "update email_cache set answered = 1 WHERE imap_uid = $mailid and ie_id = '{$this->id}'";
  233. $this->db->query($q);
  234. break;
  235. case 'pop3' :
  236. $q = "update email_cache set answered = 1 WHERE message_id = '$mailid' and ie_id = '{$this->id}'";
  237. $this->db->query($q);
  238. break;
  239. }
  240. }
  241. /**
  242. * Renames an IMAP mailbox
  243. * @param string $newName
  244. */
  245. function renameFolder($oldName, $newName) {
  246. //$this->mailbox = "INBOX"
  247. $this->connectMailserver();
  248. $oldConnect = $this->getConnectString('', $oldName);
  249. $newConnect = $this->getConnectString('', $newName);
  250. if(!imap_renamemailbox($this->conn, $oldConnect , $newConnect)) {
  251. $GLOBALS['log']->debug("***INBOUNDEMAIL: failed to rename mailbox [ {$oldConnect} ] to [ {$newConnect} ]");
  252. } else {
  253. $this->mailbox = str_replace($oldName, $newName, $this->mailbox);
  254. $this->save();
  255. $sessionFoldersString = $this->getSessionInboundFoldersString($this->server_url, $this->email_user, $this->port, $this->protocol);
  256. $sessionFoldersString = str_replace($oldName, $newName, $sessionFoldersString);
  257. $this->setSessionInboundFoldersString($this->server_url, $this->email_user, $this->port, $this->protocol, $sessionFoldersString);
  258. }
  259. }
  260. ///////////////////////////////////////////////////////////////////////////
  261. //// CUSTOM LOGIC HOOKS
  262. /**
  263. * Called from $this->getMessageText()
  264. * Allows upgrade-safe custom processing of message text.
  265. *
  266. * To use:
  267. * 1. Create a directory path: ./custom/modules/InboundEmail if it does not exist
  268. * 2. Create a file in the ./custom/InboundEmail/ folder called "getMessageText.php"
  269. * 3. Define a function named "custom_getMessageText()" that takes a string as an argument and returns a string
  270. *
  271. * @param string $msgPart
  272. * @return string
  273. */
  274. function customGetMessageText($msgPart) {
  275. $custom = "custom/modules/InboundEmail/getMessageText.php";
  276. if(file_exists($custom)) {
  277. include_once($custom);
  278. if(function_exists("custom_getMessageText")) {
  279. $GLOBALS['log']->debug("*** INBOUND EMAIL-CUSTOM_LOGIC: calling custom_getMessageText()");
  280. $msgPart = custom_getMessageText($msgPart);
  281. }
  282. }
  283. return $msgPart;
  284. }
  285. //// END CUSTOM LOGIC HOOKS
  286. ///////////////////////////////////////////////////////////////////////////
  287. ///////////////////////////////////////////////////////////////////////////
  288. //// EMAIL 2.0 SPECIFIC
  289. /**
  290. * constructs a nicely formatted version of raw source
  291. * @param int $uid UID of email
  292. * @return string
  293. */
  294. function getFormattedRawSource($uid) {
  295. global $app_strings;
  296. //if($this->protocol == 'pop3') {
  297. //$raw = $app_strings['LBL_EMAIL_VIEW_UNSUPPORTED'];
  298. //} else {
  299. if (empty($this->id)) {
  300. $q = "SELECT raw_source FROM emails_text WHERE email_id = '{$uid}'";
  301. $r = $this->db->query($q);
  302. $a = $this->db->fetchByAssoc($r);
  303. $ret = array();
  304. $raw = $this->convertToUtf8($a['raw_source']);
  305. if (empty($raw)) {
  306. $raw = $app_strings['LBL_EMAIL_ERROR_VIEW_RAW_SOURCE'];
  307. }
  308. } else {
  309. if ($this->isPop3Protocol()) {
  310. $uid = $this->getCorrectMessageNoForPop3($uid);
  311. }
  312. $raw = imap_fetchheader($this->conn, $uid, FT_UID+FT_PREFETCHTEXT);
  313. $raw .= $this->convertToUtf8(imap_body($this->conn, $uid, FT_UID));
  314. } // else
  315. $raw = to_html($raw);
  316. $raw = nl2br($raw);
  317. //}
  318. return $raw;
  319. }
  320. /**
  321. * helper method to convert text to utf-8 if necessary
  322. *
  323. * @param string $input text
  324. * @return string output text
  325. */
  326. function convertToUtf8($input)
  327. {
  328. $charset = $GLOBALS['locale']->detectCharset($input, true);
  329. // we haven't a clue due to missing package?, just return as itself
  330. if ($charset === FALSE) {
  331. return $input;
  332. }
  333. // convert if we can or must
  334. return $this->handleCharsetTranslation($input, $charset);
  335. }
  336. /**
  337. * constructs a nicely formatted version of email headers.
  338. * @param int $uid
  339. * @return string
  340. */
  341. function getFormattedHeaders($uid) {
  342. global $app_strings;
  343. //if($this->protocol == 'pop3') {
  344. // $header = $app_strings['LBL_EMAIL_VIEW_UNSUPPORTED'];
  345. //} else {
  346. if ($this->isPop3Protocol()) {
  347. $uid = $this->getCorrectMessageNoForPop3($uid);
  348. }
  349. $headers = imap_fetchheader($this->conn, $uid, FT_UID);
  350. $lines = explode("\n", $headers);
  351. $header = "<table cellspacing='0' cellpadding='2' border='0' width='100%'>";
  352. foreach($lines as $line) {
  353. $line = trim($line);
  354. if(!empty($line)) {
  355. $key = trim(substr($line, 0, strpos($line, ":")));
  356. $key = strip_tags($key);
  357. $value = trim(substr($line, strpos($line, ":") + 1));
  358. $value = to_html($value);
  359. $header .= "<tr>";
  360. $header .= "<td class='displayEmailLabel' NOWRAP><b>{$key}</b>&nbsp;</td>";
  361. $header .= "<td class='displayEmailValueWhite'>{$value}&nbsp;</td>";
  362. $header .= "</tr>";
  363. }
  364. }
  365. $header .= "</table>";
  366. //}
  367. return $header;
  368. }
  369. /**
  370. * Empties Trash folders
  371. */
  372. function emptyTrash() {
  373. global $sugar_config;
  374. $this->mailbox = $this->get_stored_options("trashFolder");
  375. if (empty($this->mailbox)) {
  376. $this->mailbox = 'INBOX.Trash';
  377. }
  378. $this->connectMailserver();
  379. $uids = imap_search($this->conn, "ALL", SE_UID);
  380. foreach($uids as $uid) {
  381. if(!imap_delete($this->conn, $uid, FT_UID)) {
  382. $lastError = imap_last_error();
  383. $GLOBALS['log']->warn("INBOUNDEMAIL: emptyTrash() Could not delete message [ {$uid} ] from [ {$this->mailbox} ]. IMAP_ERROR [ {$lastError} ]");
  384. }
  385. }
  386. // remove local cache file
  387. $q = "DELETE FROM email_cache WHERE mbox = '{$this->mailbox}' AND ie_id = '{$this->id}'";
  388. $r = $this->db->query($q);
  389. }
  390. /**
  391. * Fetches a timestamp
  392. */
  393. function getCacheTimestamp($mbox) {
  394. $key = $this->db->quote("{$this->id}_{$mbox}");
  395. $q = "SELECT ie_timestamp FROM inbound_email_cache_ts WHERE id = '{$key}'";
  396. $r = $this->db->query($q);
  397. $a = $this->db->fetchByAssoc($r);
  398. if(empty($a)) {
  399. return -1;
  400. }
  401. return $a['ie_timestamp'];
  402. }
  403. /**
  404. * sets the cache timestamp
  405. * @param string mbox
  406. */
  407. function setCacheTimestamp($mbox) {
  408. $key = $this->db->quote("{$this->id}_{$mbox}");
  409. $ts = mktime();
  410. $tsOld = $this->getCacheTimestamp($mbox);
  411. if($tsOld < 0) {
  412. $q = "INSERT INTO inbound_email_cache_ts (id, ie_timestamp) VALUES ('{$key}', {$ts})";
  413. } else {
  414. $q = "UPDATE inbound_email_cache_ts SET ie_timestamp = {$ts} WHERE id = '{$key}'";
  415. }
  416. $r = $this->db->query($q, true);
  417. $GLOBALS['log']->info("INBOUNDEMAIL-CACHE: setting timestamp query [ {$q} ]");
  418. }
  419. /**
  420. * Gets a count of all rows that are flagged seen = 0
  421. * @param string $mbox
  422. * @return int
  423. */
  424. function getCacheUnreadCount($mbox) {
  425. $q = "SELECT count(*) c FROM email_cache WHERE mbox = '{$mbox}' AND seen = 0 AND ie_id = '{$this->id}'";
  426. $r = $this->db->query($q);
  427. $a = $this->db->fetchByAssoc($r);
  428. return $a['c'];
  429. }
  430. /**
  431. * Returns total number of emails for a mailbox
  432. * @param string mbox
  433. * @return int
  434. */
  435. function getCacheCount($mbox) {
  436. $q = "SELECT count(*) c FROM email_cache WHERE mbox = '{$mbox}' AND ie_id = '{$this->id}'";
  437. $r = $this->db->query($q);
  438. $a = $this->db->fetchByAssoc($r);
  439. return $a['c'];
  440. }
  441. function getCacheUnread($mbox) {
  442. $q = "SELECT count(*) c FROM email_cache WHERE mbox = '{$mbox}' AND ie_id = '{$this->id}' AND seen = '0'";
  443. $r = $this->db->query($q);
  444. $a = $this->db->fetchByAssoc($r);
  445. return $a['c'];
  446. }
  447. /**
  448. * Deletes all rows for a given instance
  449. */
  450. function deleteCache() {
  451. $q = "DELETE FROM email_cache WHERE ie_id = '{$this->id}'";
  452. $GLOBALS['log']->info("INBOUNDEMAIL: deleting cache using query [ {$q} ]");
  453. $r = $this->db->query($q);
  454. }
  455. /**
  456. * Deletes all the pop3 data which has been deleted from server
  457. */
  458. function deletePop3Cache() {
  459. global $sugar_config;
  460. $UIDLs = $this->pop3_getUIDL();
  461. $cacheUIDLs = $this->pop3_getCacheUidls();
  462. foreach($cacheUIDLs as $msgNo => $msgId) {
  463. if (!in_array($msgId, $UIDLs)) {
  464. $md5msgIds = md5($msgId);
  465. $file = "{$this->EmailCachePath}/{$this->id}/messages/INBOX{$md5msgIds}.PHP";
  466. $GLOBALS['log']->debug("INBOUNDEMAIL: deleting file [ {$file} ] ");
  467. if(file_exists($file)) {
  468. if(!unlink($file)) {
  469. $GLOBALS['log']->debug("INBOUNDEMAIL: Could not delete [ {$file} ] ");
  470. } // if
  471. } // if
  472. $q = "DELETE from email_cache where imap_uid = {$msgNo} AND msgno = {$msgNo} AND ie_id = '{$this->id}' AND message_id = '{$msgId}'";
  473. $r = $this->db->query($q);
  474. } // if
  475. } // for
  476. } // fn
  477. /**
  478. * Retrieves cached headers
  479. * @return array
  480. */
  481. function getCacheValueForUIDs($mbox, $UIDs) {
  482. if (!is_array($UIDs) || empty($UIDs)) {
  483. return array();
  484. }
  485. $q = "SELECT * FROM email_cache WHERE ie_id = '{$this->id}' AND mbox = '{$mbox}' AND ";
  486. $startIndex = 0;
  487. $endIndex = 5;
  488. $slicedArray = array_slice($UIDs, $startIndex ,$endIndex);
  489. $columnName = ($this->isPop3Protocol() ? "message_id" : "imap_uid");
  490. $ret = array(
  491. 'timestamp' => $this->getCacheTimestamp($mbox),
  492. 'uids' => array(),
  493. 'retArr' => array(),
  494. );
  495. while (!empty($slicedArray)) {
  496. $messageIdString = implode(',', $slicedArray);
  497. $GLOBALS['log']->debug("sliced array = {$messageIdString}");
  498. $extraWhere = "{$columnName} IN (";
  499. $i = 0;
  500. foreach($slicedArray as $UID) {
  501. if($i != 0) {
  502. $extraWhere = $extraWhere . ",";
  503. } // if
  504. $i++;
  505. $extraWhere = "{$extraWhere} '{$UID}'";
  506. } // foreach
  507. $newQuery = $q . $extraWhere . ")";
  508. $r = $this->db->query($newQuery);
  509. while($a = $this->db->fetchByAssoc($r)) {
  510. if (isset($a['uid'])) {
  511. if ($this->isPop3Protocol()) {
  512. $ret['uids'][] = $a['message_id'];
  513. } else {
  514. $ret['uids'][] = $a['uid'];
  515. }
  516. }
  517. $overview = new Overview();
  518. foreach($a as $k => $v) {
  519. $k=strtolower($k);
  520. switch($k) {
  521. case "imap_uid":
  522. $overview->imap_uid = $v;
  523. if ($this->isPop3Protocol()) {
  524. $overview->uid = $a['message_id'];
  525. } else {
  526. $overview->uid = $v;
  527. }
  528. break;
  529. case "toaddr":
  530. $overview->to = from_html($v);
  531. break;
  532. case "fromaddr":
  533. $overview->from = from_html($v);
  534. break;
  535. case "mailsize":
  536. $overview->size = $v;
  537. break;
  538. case "senddate":
  539. $overview->date = $v;
  540. break;
  541. default:
  542. $overview->$k = from_html($v);
  543. break;
  544. } // switch
  545. } // foreach
  546. $ret['retArr'][] = $overview;
  547. } // while
  548. $startIndex = $startIndex + $endIndex;
  549. $slicedArray = array_slice($UIDs, $startIndex ,$endIndex);
  550. $messageIdString = implode(',', $slicedArray);
  551. $GLOBALS['log']->debug("sliced array = {$messageIdString}");
  552. } // while
  553. return $ret;
  554. }
  555. /**
  556. * Retrieves cached headers
  557. * @return array
  558. */
  559. function getCacheValue($mbox, $limit = 20, $page = 1, $sort='', $direction='') {
  560. // try optimizing this call as we don't want repeat queries
  561. if(!empty($this->currentCache)) {
  562. return $this->currentCache;
  563. }
  564. $sort = (empty($sort)) ? $this->defaultSort : $sort;
  565. $direction = (empty($direction)) ? $this->defaultDirection : $direction;
  566. $order = " ORDER BY {$this->hrSortLocal[$sort]} {$direction}";
  567. $q = "SELECT * FROM email_cache WHERE ie_id = '{$this->id}' AND mbox = '{$mbox}' {$order}";
  568. if(!empty($limit)) {
  569. $start = ( $page - 1 ) * $limit;
  570. $r = $this->db->limitQuery($q, $start, $limit);
  571. } else {
  572. $r = $this->db->query($q);
  573. }
  574. $ret = array(
  575. 'timestamp' => $this->getCacheTimestamp($mbox),
  576. 'uids' => array(),
  577. 'retArr' => array(),
  578. );
  579. while($a = $this->db->fetchByAssoc($r)) {
  580. if (isset($a['uid'])) {
  581. if ($this->isPop3Protocol()) {
  582. $ret['uids'][] = $a['message_id'];
  583. } else {
  584. $ret['uids'][] = $a['uid'];
  585. }
  586. }
  587. $overview = new Overview();
  588. foreach($a as $k => $v) {
  589. $k=strtolower($k);
  590. switch($k) {
  591. case "imap_uid":
  592. $overview->imap_uid = $v;
  593. if ($this->isPop3Protocol()) {
  594. $overview->uid = $a['message_id'];
  595. } else {
  596. $overview->uid = $v;
  597. }
  598. break;
  599. case "toaddr":
  600. $overview->to = from_html($v);
  601. break;
  602. case "fromaddr":
  603. $overview->from = from_html($v);
  604. break;
  605. case "mailsize":
  606. $overview->size = $v;
  607. break;
  608. case "senddate":
  609. $overview->date = $v;
  610. break;
  611. default:
  612. $overview->$k = from_html($v);
  613. break;
  614. }
  615. }
  616. $ret['retArr'][] = $overview;
  617. }
  618. $this->currentCache = $ret;
  619. return $ret;
  620. }
  621. /**
  622. * Sets cache values
  623. */
  624. function setCacheValue($mbox, $insert, $update=array(), $remove=array()) {
  625. if(empty($mbox)) {
  626. return;
  627. }
  628. global $timedate;
  629. // reset in-memory cache
  630. $this->currentCache = null;
  631. $table = 'email_cache';
  632. $where = "WHERE ie_id = '{$this->id}' AND mbox = '{$mbox}'";
  633. // handle removed rows
  634. if(!empty($remove)) {
  635. $removeIds = '';
  636. foreach($remove as $overview) {
  637. if(!empty($removeIds)) {
  638. $removeIds .= ",";
  639. }
  640. $removeIds .= "'{$overview->imap_uid}'";
  641. }
  642. $q = "DELETE FROM {$table} {$where} AND imap_uid IN ({$removeIds})";
  643. $GLOBALS['log']->info("INBOUNDEMAIL-CACHE: delete query [ {$q} ]");
  644. $r = $this->db->query($q, true, $q);
  645. }
  646. // handle insert rows
  647. if(!empty($insert)) {
  648. $q = "SELECT imap_uid FROM {$table} {$where}";
  649. $GLOBALS['log']->info("INBOUNDEMAIL-CACHE: filter UIDs query [ {$q} ]");
  650. $r = $this->db->query($q);
  651. $uids = array();
  652. while($a = $this->db->fetchByAssoc($r)) {
  653. $uids[] = $a['imap_uid'];
  654. }
  655. $count = count($uids);
  656. $GLOBALS['log']->info("INBOUNDEMAIL-CACHE: found [ {$count} ] UIDs to filter against");
  657. $tmp = '';
  658. foreach($uids as $uid) {
  659. if(!empty($tmp))
  660. $tmp .= ", ";
  661. $tmp .= "{$uid}";
  662. }
  663. $GLOBALS['log']->info("INBOUNDEMAIL-CACHE: filter UIDs: [ {$tmp} ]");
  664. $cols = "";
  665. foreach($this->overview->fieldDefs as $colDef) {
  666. if(!empty($cols))
  667. $cols .= ",";
  668. $cols .= "{$colDef['name']}";
  669. }
  670. foreach($insert as $overview) {
  671. if(in_array($overview->imap_uid, $uids))
  672. {
  673. // fixing bug #49543: setting 'mbox' property for the following updating of other items in this box
  674. if (!isset($overview->mbox))
  675. {
  676. $overview->mbox = $mbox;
  677. }
  678. $update[] = $overview;
  679. continue;
  680. }
  681. $values = '';
  682. foreach($this->overview->fieldDefs as $colDef) {
  683. if(!empty($values)) {
  684. $values .= ", ";
  685. }
  686. // trim values for Oracle/MSSql
  687. if( isset($colDef['len']) && !empty($colDef['len']) &&
  688. isset($colDef['type']) && !empty($colDef['type']) &&
  689. $colDef['type'] == 'varchar'
  690. )
  691. {
  692. if (isset($overview->$colDef['name']))
  693. {
  694. $overview->$colDef['name'] = substr($overview->$colDef['name'], 0, $colDef['len']);
  695. }
  696. }
  697. switch($colDef['name']) {
  698. case "imap_uid":
  699. if(isset($overview->uid) && !empty($overview->uid)) {
  700. $this->imap_uid = $overview->uid;
  701. }
  702. $values .= "'{$this->imap_uid}'";
  703. break;
  704. case "ie_id":
  705. $values .= "'{$this->id}'";
  706. break;
  707. case "toaddr":
  708. $values .= $this->db->quoted($overview->to);
  709. break;
  710. case "fromaddr":
  711. $values .= $this->db->quoted($overview->from);
  712. break;
  713. case "message_id" :
  714. $values .= $this->db->quoted($overview->message_id);
  715. break;
  716. case "mailsize":
  717. $values .= $overview->size;
  718. break;
  719. case "senddate":
  720. $conv=$timedate->fromString($overview->date);
  721. if (!empty($conv)) {
  722. $values .= $this->db->quoted($conv->asDb());
  723. } else {
  724. $values .= "NULL";
  725. }
  726. break;
  727. case "mbox":
  728. $values .= "'{$mbox}'";
  729. break;
  730. default:
  731. $overview->$colDef['name'] = SugarCleaner::cleanHtml(from_html($overview->$colDef['name']));
  732. $values .= $this->db->quoted($overview->$colDef['name']);
  733. break;
  734. }
  735. }
  736. $q = "INSERT INTO {$table} ({$cols}) VALUES ({$values})";
  737. $GLOBALS['log']->info("INBOUNDEMAIL-CACHE: insert query [ {$q} ]");
  738. $r = $this->db->query($q, true, $q);
  739. }
  740. }
  741. // handle update rows
  742. if(!empty($update)) {
  743. $cols = "";
  744. foreach($this->overview->fieldDefs as $colDef) {
  745. if(!empty($cols))
  746. $cols .= ",";
  747. $cols .= "{$colDef['name']}";
  748. }
  749. foreach($update as $overview) {
  750. $q = "UPDATE {$table} SET ";
  751. $set = '';
  752. foreach($this->overview->fieldDefs as $colDef) {
  753. switch($colDef['name']) {
  754. case "toaddr":
  755. case "fromaddr":
  756. case "mailsize":
  757. case "senddate":
  758. case "mbox":
  759. case "ie_id":
  760. break;
  761. default:
  762. if(!empty($set))
  763. {
  764. $set .= ",";
  765. }
  766. $value = '';
  767. if (isset($overview->$colDef['name']))
  768. {
  769. $value = $this->db->quoted($overview->$colDef['name']);
  770. }
  771. else
  772. {
  773. $value = $this->db->quoted($value);
  774. }
  775. $set .= "{$colDef['name']} = " . $value;
  776. break;
  777. }
  778. }
  779. $q .= $set . " WHERE ie_id = '{$this->id}' AND mbox = '{$overview->mbox}' AND imap_uid = '{$overview->imap_uid}'";
  780. $GLOBALS['log']->info("INBOUNDEMAIL-CACHE: update query [ {$q} ]");
  781. $r = $this->db->query($q, true, $q);
  782. }
  783. }
  784. }
  785. /**
  786. * Opens a socket connection to the pop3 server
  787. * @return bool
  788. */
  789. function pop3_open() {
  790. if(!is_resource($this->pop3socket)) {
  791. $GLOBALS['log']->info("*** INBOUNDEMAIL: opening socket connection");
  792. $exServ = explode('::', $this->service);
  793. $socket = ($exServ[2] == 'ssl') ? "ssl://" : "tcp://";
  794. $socket .= $this->server_url;
  795. $this->pop3socket = fsockopen($socket, $this->port);
  796. } else {
  797. $GLOBALS['log']->info("*** INBOUNDEMAIL: REUSING socket connection");
  798. return true;
  799. }
  800. if(!is_resource($this->pop3socket)) {
  801. $GLOBALS['log']->debug("*** INBOUNDEMAIL: unable to open socket connection");
  802. return false;
  803. }
  804. // clear buffer
  805. $ret = trim(fgets($this->pop3socket, 1024));
  806. $GLOBALS['log']->info("*** INBOUNDEMAIL: got socket connection [ {$ret} ]");
  807. return true;
  808. }
  809. /**
  810. * Closes connections and runs clean-up routines
  811. */
  812. function pop3_cleanUp() {
  813. $GLOBALS['log']->info("*** INBOUNDEMAIL: cleaning up socket connection");
  814. fputs($this->pop3socket, "QUIT\r\n");
  815. $buf = fgets($this->pop3socket, 1024);
  816. fclose($this->pop3socket);
  817. }
  818. /**
  819. * sends a command down to the POP3 server
  820. * @param string command
  821. * @param string args
  822. * @param bool return
  823. * @return string
  824. */
  825. function pop3_sendCommand($command, $args='', $return=true) {
  826. $command .= " {$args}";
  827. $command = trim($command);
  828. $GLOBALS['log']->info("*** INBOUNDEMAIL: pop3_sendCommand() SEND [ {$command} ]");
  829. $command .= "\r\n";
  830. fputs($this->pop3socket, $command);
  831. if($return) {
  832. $ret = trim(fgets($this->pop3socket, 1024));
  833. $GLOBALS['log']->info("*** INBOUNDEMAIL: pop3_sendCommand() RECEIVE [ {$ret} ]");
  834. return $ret;
  835. }
  836. }
  837. function getPop3NewMessagesToDownload() {
  838. $pop3UIDL = $this->pop3_getUIDL();
  839. $cacheUIDLs = $this->pop3_getCacheUidls();
  840. // new email cache values we should deal with
  841. $diff = array_diff_assoc($pop3UIDL, $cacheUIDLs);
  842. // this is msgNo to UIDL array
  843. $diff = $this->pop3_shiftCache($diff, $cacheUIDLs);
  844. // get all the keys which are msgnos;
  845. return array_keys($diff);
  846. }
  847. function getPop3NewMessagesToDownloadForCron() {
  848. $pop3UIDL = $this->pop3_getUIDL();
  849. $cacheUIDLs = $this->pop3_getCacheUidls();
  850. // new email cache values we should deal with
  851. $diff = array_diff_assoc($pop3UIDL, $cacheUIDLs);
  852. // this is msgNo to UIDL array
  853. $diff = $this->pop3_shiftCache($diff, $cacheUIDLs);
  854. // insert data into email_cache
  855. if ($this->groupfolder_id != null && $this->groupfolder_id != "" && $this->isPop3Protocol()) {
  856. $searchResults = array_keys($diff);
  857. $concatResults = implode(",", $searchResults);
  858. if ($this->connectMailserver() == 'true') {
  859. $fetchedOverviews = imap_fetch_overview($this->conn, $concatResults);
  860. // clean up cache entry
  861. foreach($fetchedOverviews as $k => $overview) {
  862. $overview->message_id = trim($diff[$overview->msgno]);
  863. $fetchedOverviews[$k] = $overview;
  864. }
  865. $this->updateOverviewCacheFile($fetchedOverviews);
  866. }
  867. } // if
  868. return $diff;
  869. }
  870. /**
  871. * This method returns all the UIDL for this account. This should be called if the protocol is pop3
  872. * @return array od messageno to UIDL array
  873. */
  874. function pop3_getUIDL() {
  875. $UIDLs = array();
  876. if($this->pop3_open()) {
  877. // authenticate
  878. $this->pop3_sendCommand("USER", $this->email_user);
  879. $this->pop3_sendCommand("PASS", $this->email_password);
  880. // get UIDLs
  881. $this->pop3_sendCommand("UIDL", '', false); // leave socket buffer alone until the while()
  882. fgets($this->pop3socket, 1024); // handle "OK+";
  883. $UIDLs = array();
  884. $buf = '!';
  885. if(is_resource($this->pop3socket)) {
  886. while(!feof($this->pop3socket)) {
  887. $buf = fgets($this->pop3socket, 1024); // 8kb max buffer - shouldn't be more than 80 chars via pop3...
  888. //_pp(trim($buf));
  889. if(trim($buf) == '.') {
  890. $GLOBALS['log']->debug("*** GOT '.'");
  891. break;
  892. }
  893. // format is [msgNo] [UIDL]
  894. $exUidl = explode(" ", $buf);
  895. $UIDLs[$exUidl[0]] = trim($exUidl[1]);
  896. } // while
  897. } // if
  898. $this->pop3_cleanUp();
  899. } // if
  900. return $UIDLs;
  901. } // fn
  902. /**
  903. * Special handler for POP3 boxes. Standard IMAP commands are useless.
  904. * This will fetch only partial emails for POP3 and hence needs to be call again and again based on status it returns
  905. */
  906. function pop3_checkPartialEmail($synch = false) {
  907. require_once('include/utils/array_utils.php');
  908. global $current_user;
  909. global $sugar_config;
  910. $cacheDataExists = false;
  911. $diff = array();
  912. $results = array();
  913. $cacheFilePath = clean_path("{$this->EmailCachePath}/{$this->id}/folders/MsgNOToUIDLData.php");
  914. if(file_exists($cacheFilePath)) {
  915. $cacheDataExists = true;
  916. if($fh = @fopen($cacheFilePath, "rb")) {
  917. $data = "";
  918. $chunksize = 1*(1024*1024); // how many bytes per chunk
  919. while(!feof($fh)) {
  920. $buf = fgets($fh, $chunksize); // 8kb max buffer - shouldn't be more than 80 chars via pop3...
  921. $data = $data . $buf;
  922. flush();
  923. } // while
  924. fclose($fh);
  925. $diff = unserialize($data);
  926. if (!empty($diff)) {
  927. if (count($diff)> 50) {
  928. $newDiff = array_slice($diff, 50, count($diff), true);
  929. } else {
  930. $newDiff=array();
  931. }
  932. $results = array_slice(array_keys($diff), 0 ,50);
  933. $data = serialize($newDiff);
  934. if($fh = @fopen($cacheFilePath, "w")) {
  935. fputs($fh, $data);
  936. fclose($fh);
  937. } // if
  938. }
  939. } // if
  940. } // if
  941. if (!$cacheDataExists) {
  942. if ($synch) {
  943. $this->deletePop3Cache();
  944. }
  945. $UIDLs = $this->pop3_getUIDL();
  946. if(count($UIDLs) > 0) {
  947. // get cached UIDLs
  948. $cacheUIDLs = $this->pop3_getCacheUidls();
  949. // new email cache values we should deal with
  950. $diff = array_diff_assoc($UIDLs, $cacheUIDLs);
  951. $diff = $this->pop3_shiftCache($diff, $cacheUIDLs);
  952. require_once('modules/Emails/EmailUI.php');
  953. EmailUI::preflightEmailCache("{$this->EmailCachePath}/{$this->id}");
  954. if (count($diff)> 50) {
  955. $newDiff = array_slice($diff, 50, count($diff), true);
  956. } else {
  957. $newDiff=array();
  958. }
  959. $results = array_slice(array_keys($diff), 0 ,50);
  960. $data = serialize($newDiff);
  961. if($fh = @fopen($cacheFilePath, "w")) {
  962. fputs($fh, $data);
  963. fclose($fh);
  964. } // if
  965. } else {
  966. $GLOBALS['log']->debug("*** INBOUNDEMAIL: could not open socket connection to POP3 server");
  967. return "could not open socket connection to POP3 server";
  968. } // else
  969. } // if
  970. // build up msgNo request
  971. if(count($diff) > 0) {
  972. // remove dirty cache entries
  973. $startingNo = 0;
  974. if (isset($_REQUEST['currentCount']) && $_REQUEST['currentCount'] > -1) {
  975. $startingNo = $_REQUEST['currentCount'];
  976. }
  977. $this->mailbox = 'INBOX';
  978. $this->connectMailserver();
  979. //$searchResults = array_keys($diff);
  980. //$fetchedOverviews = array();
  981. //$chunkArraySerachResults = array_chunk($searchResults, 50);
  982. $concatResults = implode(",", $results);
  983. $GLOBALS['log']->info('$$$$ '.$concatResults);
  984. $GLOBALS['log']->info("[EMAIL] Start POP3 fetch overview on mailbox [{$this->mailbox}] for user [{$current_user->user_name}] on 50 data");
  985. $fetchedOverviews = imap_fetch_overview($this->conn, $concatResults);
  986. $GLOBALS['log']->info("[EMAIL] End POP3 fetch overview on mailbox [{$this->mailbox}] for user [{$current_user->user_name}] on "
  987. . sizeof($fetchedOverviews) . " data");
  988. // clean up cache entry
  989. foreach($fetchedOverviews as $k => $overview) {
  990. $overview->message_id = trim($diff[$overview->msgno]);
  991. $fetchedOverviews[$k] = $overview;
  992. }
  993. $GLOBALS['log']->info("[EMAIL] Start updating overview cache for pop3 mailbox [{$this->mailbox}] for user [{$current_user->user_name}]");
  994. $this->updateOverviewCacheFile($fetchedOverviews);
  995. $GLOBALS['log']->info("[EMAIL] Start updating overview cache for pop3 mailbox [{$this->mailbox}] for user [{$current_user->user_name}]");
  996. return array('status' => "In Progress", 'mbox' => $this->mailbox, 'count'=> (count($results) + $startingNo), 'totalcount' => count($diff), 'ieid' => $this->id);
  997. } // if
  998. unlink($cacheFilePath);
  999. return array('status' => "done");
  1000. }
  1001. /**
  1002. * Special handler for POP3 boxes. Standard IMAP commands are useless.
  1003. */
  1004. function pop3_checkEmail() {
  1005. if($this->pop3_open()) {
  1006. // authenticate
  1007. $this->pop3_sendCommand("USER", $this->email_user);
  1008. $this->pop3_sendCommand("PASS", $this->email_password);
  1009. // get UIDLs
  1010. $this->pop3_sendCommand("UIDL", '', false); // leave socket buffer alone until the while()
  1011. fgets($this->pop3socket, 1024); // handle "OK+";
  1012. $UIDLs = array();
  1013. $buf = '!';
  1014. if(is_resource($this->pop3socket)) {
  1015. while(!feof($this->pop3socket)) {
  1016. $buf = fgets($this->pop3socket, 1024); // 8kb max buffer - shouldn't be more than 80 chars via pop3...
  1017. //_pp(trim($buf));
  1018. if(trim($buf) == '.') {
  1019. $GLOBALS['log']->debug("*** GOT '.'");
  1020. break;
  1021. }
  1022. // format is [msgNo] [UIDL]
  1023. $exUidl = explode(" ", $buf);
  1024. $UIDLs[$exUidl[0]] = trim($exUidl[1]);
  1025. }
  1026. }
  1027. $this->pop3_cleanUp();
  1028. // get cached UIDLs
  1029. $cacheUIDLs = $this->pop3_getCacheUidls();
  1030. // _pp($UIDLs);_pp($cacheUIDLs);
  1031. // new email cache values we should deal with
  1032. $diff = array_diff_assoc($UIDLs, $cacheUIDLs);
  1033. // remove dirty cache entries
  1034. $diff = $this->pop3_shiftCache($diff, $cacheUIDLs);
  1035. // build up msgNo request
  1036. if(!empty($diff)) {
  1037. $this->mailbox = 'INBOX';
  1038. $this->connectMailserver();
  1039. $searchResults = array_keys($diff);
  1040. $concatResults = implode(",", $searchResults);
  1041. $fetchedOverviews = imap_fetch_overview($this->conn, $concatResults);
  1042. // clean up cache entry
  1043. foreach($fetchedOverviews as $k => $overview) {
  1044. $overview->message_id = trim($diff[$overview->msgno]);
  1045. $fetchedOverviews[$k] = $overview;
  1046. }
  1047. $this->updateOverviewCacheFile($fetchedOverviews);
  1048. }
  1049. } else {
  1050. $GLOBALS['log']->debug("*** INBOUNDEMAIL: could not open socket connection to POP3 server");
  1051. return false;
  1052. }
  1053. }
  1054. /**
  1055. * Iterates through msgno and message_id to remove dirty cache entries
  1056. * @param array diff
  1057. */
  1058. function pop3_shiftCache($diff, $cacheUIDLs) {
  1059. $msgNos = "";
  1060. $msgIds = "";
  1061. $newArray = array();
  1062. foreach($diff as $msgNo => $msgId) {
  1063. if (in_array($msgId, $cacheUIDLs)) {
  1064. $q1 = "UPDATE email_cache SET imap_uid = {$msgNo}, msgno = {$msgNo} WHERE ie_id = '{$this->id}' AND message_id = '{$msgId}'";
  1065. $this->db->query($q1);
  1066. } else {
  1067. $newArray[$msgNo] = $msgId;
  1068. }
  1069. }
  1070. return $newArray;
  1071. /*
  1072. foreach($diff as $msgNo => $msgId) {
  1073. if(!empty($msgNos)) {
  1074. $msgNos .= ", ";
  1075. }
  1076. if(!empty($msgIds)) {
  1077. $msgIds .= ", ";
  1078. }
  1079. $msgNos .= $msgNo;
  1080. $msgIds .= "'{$msgId}'";
  1081. }
  1082. if(!empty($msgNos)) {
  1083. $q1 = "DELETE FROM email_cache WHERE ie_id = '{$this->id}' AND msgno IN ({$msgNos})";
  1084. $this->db->query($q1);
  1085. }
  1086. if(!empty($msgIds)) {
  1087. $q2 = "DELETE FROM email_cache WHERE ie_id = '{$this->id}' AND message_id IN ({$msgIds})";
  1088. $this->db->query($q2);
  1089. }
  1090. */
  1091. }
  1092. /**
  1093. * retrieves cached uidl values.
  1094. * When dealing with POP3 accounts, the message_id column in email_cache will contain the UIDL.
  1095. * @return array
  1096. */
  1097. function pop3_getCacheUidls() {
  1098. $q = "SELECT msgno, message_id FROM email_cache WHERE ie_id = '{$this->id}'";
  1099. $r = $this->db->query($q);
  1100. $ret = array();
  1101. while($a = $this->db->fetchByAssoc($r)) {
  1102. $ret[$a['msgno']] = $a['message_id'];
  1103. }
  1104. return $ret;
  1105. }
  1106. /**
  1107. * This function is used by cron job for group mailbox without group folder
  1108. * @param string $msgno for pop
  1109. * @param string $uid for imap
  1110. */
  1111. function getMessagesInEmailCache($msgno, $uid) {
  1112. $fetchedOverviews = array();
  1113. if ($this->isPop3Protocol()) {
  1114. $fetchedOverviews = imap_fetch_overview($this->conn, $msgno);
  1115. foreach($fetchedOverviews as $k => $overview) {
  1116. $overview->message_id = $uid;
  1117. $fetchedOverviews[$k] = $overview;
  1118. }
  1119. } else {
  1120. $fetchedOverviews = imap_fetch_overview($this->conn, $uid, FT_UID);
  1121. } // else
  1122. $this->updateOverviewCacheFile($fetchedOverviews);
  1123. } // fn
  1124. /**
  1125. * Checks email (local caching too) for one mailbox
  1126. * @param string $mailbox IMAP Mailbox path
  1127. * @param bool $prefetch Flag to prefetch email body on check
  1128. */
  1129. function checkEmailOneMailbox($mailbox, $prefetch=true, $synchronize=false) {
  1130. global $sugar_config;
  1131. global $current_user;
  1132. global $app_strings;
  1133. $result = 1;
  1134. $GLOBALS['log']->info("INBOUNDEMAIL: checking mailbox [ {$mailbox} ]");
  1135. $this->mailbox = $mailbox;
  1136. $this->connectMailserver();
  1137. $checkTime = '';
  1138. $shouldProcessRules = true;
  1139. $timestamp = $this->getCacheTimestamp($mailbox);
  1140. if($timestamp > 0) {
  1141. $checkTime = date('r', $timestamp);
  1142. }
  1143. /* first time through, process ALL emails */
  1144. if(empty($checkTime) || $synchronize) {
  1145. // do not process rules for the first time or sunchronize
  1146. $shouldProcessRules = false;
  1147. $criteria = "ALL UNDELETED";
  1148. $prefetch = false; // do NOT prefetch emails on a brand new account - timeouts happen.
  1149. $GLOBALS['log']->info("INBOUNDEMAIL: new account detected - not prefetching email bodies.");
  1150. } else {
  1151. $criteria = "SINCE \"{$checkTime}\" UNDELETED"; // not using UNSEEN
  1152. }
  1153. $this->setCacheTimestamp($mailbox);
  1154. $GLOBALS['log']->info("[EMAIL] Performing IMAP search using criteria [{$criteria}] on mailbox [{$mailbox}] for user [{$current_user->user_name}]");
  1155. $searchResults = imap_search($this->conn, $criteria, SE_UID);
  1156. $GLOBALS['log']->info("[EMAIL] Done IMAP search on mailbox [{$mailbox}] for user [{$current_user->user_name}]. Result count = ".count($searchResults));
  1157. if(!empty($searchResults)) {
  1158. $concatResults = implode(",", $searchResults);
  1159. $GLOBALS['log']->info("[EMAIL] Start IMAP fetch overview on mailbox [{$mailbox}] for user [{$current_user->user_name}]");
  1160. $fetchedOverview = imap_fetch_overview($this->conn, $concatResults, FT_UID);
  1161. $GLOBALS['log']->info("[EMAIL] Done IMAP fetch overview on mailbox [{$mailbox}] for user [{$current_user->user_name}]");
  1162. $GLOBALS['log']->info("[EMAIL] Start updating overview cache for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
  1163. $this->updateOverviewCacheFile($fetchedOverview);
  1164. $GLOBALS['log']->info("[EMAIL] Done updating overview cache for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
  1165. // prefetch emails
  1166. if($prefetch == true) {
  1167. $GLOBALS['log']->info("[EMAIL] Start fetching emails for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
  1168. if(!$this->fetchCheckedEmails($fetchedOverview))
  1169. {
  1170. $result = 0;
  1171. }
  1172. $GLOBALS['log']->info("[EMAIL] Done fetching emails for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
  1173. }
  1174. } else {
  1175. $GLOBALS['log']->info("INBOUNDEMAIL: no results for mailbox [ {$mailbox} ]");
  1176. $result = 1;
  1177. }
  1178. /**
  1179. * To handle the use case where an external client is also connected, deleting emails, we need to clear our
  1180. * local cache of all emails with the "DELETED" flag
  1181. */
  1182. $criteria = 'DELETED';
  1183. $criteria .= (!empty($checkTime)) ? " SINCE \"{$checkTime}\"" : "";
  1184. $GLOBALS['log']->info("INBOUNDEMAIL: checking for deleted emails using [ {$criteria} ]");
  1185. $trashFolder = $this->get_stored_options("trashFolder");
  1186. if (empty($trashFolder)) {
  1187. $trashFolder = "INBOX.Trash";
  1188. }
  1189. if($this->mailbox != $trashFolder) {
  1190. $searchResults = imap_search($this->conn, $criteria, SE_UID);
  1191. if(!empty($searchResults)) {
  1192. $uids = implode($app_strings['LBL_EMAIL_DELIMITER'], $searchResults);
  1193. $GLOBALS['log']->info("INBOUNDEMAIL: removing UIDs found deleted [ {$uids} ]");
  1194. $this->getOverviewsFromCacheFile($uids, $mailbox, true);
  1195. }
  1196. }
  1197. return $result;
  1198. }
  1199. /**
  1200. * Checks email (local caching too) for one mailbox
  1201. * @param string $mailbox IMAP Mailbox path
  1202. * @param bool $prefetch Flag to prefetch email body on check
  1203. */
  1204. function checkEmailOneMailboxPartial($mailbox, $prefetch=true, $synchronize=false, $start = 0, $max = -1) {
  1205. global $sugar_config;
  1206. global $current_user;
  1207. global $app_strings;
  1208. $GLOBALS['log']->info("INBOUNDEMAIL: checking mailbox [ {$mailbox} ]");
  1209. $this->mailbox = $mailbox;
  1210. $this->connectMailserver();
  1211. $checkTime = '';
  1212. $shouldProcessRules = true;
  1213. $timestamp = $this->getCacheTimestamp($mailbox);
  1214. if($timestamp > 0) {
  1215. $checkTime = date('r', $timestamp);
  1216. }
  1217. /* first time through, process ALL emails */
  1218. if(empty($checkTime) || $synchronize) {
  1219. // do not process rules for the first time or sunchronize
  1220. $shouldProcessRules = false;
  1221. $criteria = "ALL UNDELETED";
  1222. $prefetch = false; // do NOT prefetch emails on a brand new account - timeouts happen.
  1223. $GLOBALS['log']->info("INBOUNDEMAIL: new account detected - not prefetching email bodies.");
  1224. } else {
  1225. $criteria = "SINCE \"{$checkTime}\" UNDELETED"; // not using UNSEEN
  1226. }
  1227. $this->setCacheTimestamp($mailbox);
  1228. $GLOBALS['log']->info("[EMAIL] Performing IMAP search using criteria [{$criteria}] on mailbox [{$mailbox}] for user [{$current_user->user_name}]");
  1229. $searchResults = $this->getCachedIMAPSearch($criteria);
  1230. if(!empty($searchResults)) {
  1231. $total = sizeof($searchResults);
  1232. $searchResults = array_slice($searchResults, $start, $max);
  1233. $GLOBALS['log']->info("INBOUNDEMAIL: there are $total messages in [{$mailbox}], we are on $start");
  1234. $GLOBALS['log']->info("INBOUNDEMAIL: getting the next " . sizeof($searchResults) . " messages");
  1235. $concatResults = implode(",", $searchResults);
  1236. $GLOBALS['log']->info("INBOUNDEMAIL: Start IMAP fetch overview on mailbox [{$mailbox}] for user [{$current_user->user_name}]");
  1237. $fetchedOverview = imap_fetch_overview($this->conn, $concatResults, FT_UID);
  1238. $GLOBALS['log']->info("INBOUNDEMAIL: Done IMAP fetch overview on mailbox [{$mailbox}] for user [{$current_user->user_name}]");
  1239. $GLOBALS['log']->info("INBOUNDEMAIL: Start updating overview cache for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
  1240. $this->updateOverviewCacheFile($fetchedOverview);
  1241. $GLOBALS['log']->info("INBOUNDEMAIL: Done updating overview cache for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
  1242. // prefetch emails
  1243. if($prefetch == true) {
  1244. $GLOBALS['log']->info("INBOUNDEMAIL: Start fetching emails for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
  1245. $this->fetchCheckedEmails($fetchedOverview);
  1246. $GLOBALS['log']->info("INBOUNDEMAIL: Done fetching emails for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
  1247. }
  1248. $status = ($total > $start + sizeof($searchResults)) ? 'continue' : 'done';
  1249. $ret = array('status' => $status, 'count' => $start + sizeof($searchResults), 'mbox' => $mailbox, 'totalcount' => $total);
  1250. $GLOBALS['log']->info("INBOUNDEMAIL: $status : Downloaded " . $start + sizeof($searchResults) . "messages of $total");
  1251. } else {
  1252. $GLOBALS['log']->info("INBOUNDEMAIL: no results for mailbox [ {$mailbox} ]");
  1253. $ret = array('status' =>'done');
  1254. }
  1255. if ($ret['status'] == 'done') {
  1256. //Remove the cached search if we are done with this mailbox
  1257. $cacheFilePath = clean_path("{$this->EmailCachePath}/{$this->id}/folders/SearchData.php");
  1258. unlink($cacheFilePath);
  1259. /**
  1260. * To handle the use case where an external client is also connected, deleting emails, we need to clear our
  1261. * local cache of all emails with the "DELETED" flag
  1262. */
  1263. $criteria = 'DELETED';
  1264. $criteria .= (!empty($checkTime)) ? " SINCE \"{$checkTime}\"" : "";
  1265. $GLOBALS['log']->info("INBOUNDEMAIL: checking for deleted emails using [ {$criteria} ]");
  1266. $trashFolder = $this->get_stored_options("trashFolder");
  1267. if (empty($trashFolder)) {
  1268. $trashFolder = "INBOX.Trash";
  1269. }
  1270. if($this->mailbox != $trashFolder) {
  1271. $searchResults = imap_search($this->conn, $criteria, SE_UID);
  1272. if(!empty($searchResults)) {
  1273. $uids = implode($app_strings['LBL_EMAIL_DELIMITER'], $searchResults);
  1274. $GLOBALS['log']->info("INBOUNDEMAIL: removing UIDs found deleted [ {$uids} ]");
  1275. $this->getOverviewsFromCacheFile($uids, $mailbox, true);
  1276. }
  1277. }
  1278. }
  1279. return $ret;
  1280. }
  1281. function getCachedIMAPSearch($criteria) {
  1282. global $current_user;
  1283. global $sugar_config;
  1284. $cacheDataExists = false;
  1285. $diff = array();
  1286. $results = array();
  1287. $cacheFolderPath = clean_path("{$this->EmailCachePath}/{$this->id}/folders");
  1288. if (!file_exists($cacheFolderPath)) {
  1289. mkdir_recursive($cacheFolderPath);
  1290. }
  1291. $cacheFilePath = $cacheFolderPath . '/SearchData.php';
  1292. $GLOBALS['log']->info("INBOUNDEMAIL: Cache path is $cacheFilePath");
  1293. if(file_exists($cacheFilePath)) {
  1294. $cacheDataExists = true;
  1295. if($fh = @fopen($cacheFilePath, "rb")) {
  1296. $data = "";
  1297. $chunksize = 1*(1024*1024); // how many bytes per chunk
  1298. while(!feof($fh)) {
  1299. $buf = fgets($fh, $chunksize); // 8kb max buffer - shouldn't be more than 80 chars via pop3...
  1300. $data = $data . $buf;
  1301. flush();
  1302. } // while
  1303. fclose($fh);
  1304. $results = unserialize($data);
  1305. } // if
  1306. } // if
  1307. if (!$cacheDataExists) {
  1308. $searchResults = imap_search($this->conn, $criteria, SE_UID);
  1309. if(count($searchResults) > 0) {
  1310. $results = $searchResults;
  1311. $data = serialize($searchResults);
  1312. if($fh = @fopen($cacheFilePath, "w")) {
  1313. fputs($fh, $data);
  1314. fclose($fh);
  1315. } // if
  1316. }
  1317. } // if
  1318. return $results;
  1319. }
  1320. function checkEmailIMAPPartial($prefetch=true, $synch = false) {
  1321. $GLOBALS['log']->info("*****************INBOUNDEMAIL: at IMAP check partial");
  1322. global $sugar_config;
  1323. $result = $this->connectMailserver();
  1324. if ($result == 'false')
  1325. {
  1326. return array(
  1327. 'status' => 'error',
  1328. 'message' => 'Email server is down'
  1329. );
  1330. }
  1331. $mailboxes = $this->getMailboxes(true);
  1332. if (!in_array('INBOX', $mailboxes)) {
  1333. $mailboxes[] = 'INBOX';
  1334. }
  1335. sort($mailboxes);
  1336. if (isset($_REQUEST['mbox']) && !empty($_REQUEST['mbox']) && isset($_REQUEST['currentCount'])) {
  1337. $GLOBALS['log']->info("INBOUNDEMAIL: Picking up from where we left off");
  1338. $mbox = $_REQUEST['mbox'];
  1339. $count = $_REQUEST['currentCount'];
  1340. } else {
  1341. if ($synch) {
  1342. $GLOBALS['log']->info("INBOUNDEMAIL: Cleaning out the cache");
  1343. $this->cleanOutCache();
  1344. }
  1345. $mbox = $mailboxes[0];
  1346. $count = 0;
  1347. }
  1348. $GLOBALS['log']->info("INBOUNDEMAIL:found " . sizeof($mailboxes) . " Mailboxes");
  1349. $index = array_search($mbox, $mailboxes) + 1;
  1350. $ret = $this->checkEmailOneMailboxPartial($mbox, $prefetch, $synch, $count, 100);
  1351. while($ret['status'] == 'done' && $index < sizeof($mailboxes)) {
  1352. if ($ret['count'] > 100) {
  1353. $ret['mbox'] = $mailboxes[$index];
  1354. $ret['status'] = 'continue';
  1355. return $ret;
  1356. }
  1357. $GLOBALS['log']->info("INBOUNDEMAIL: checking account [ $index => $mbox : $count]");
  1358. $mbox = $mailboxes[$index];
  1359. $ret = $this->checkEmailOneMailboxPartial($mbox, $prefetch, $synch, 0, 100);
  1360. $index++;
  1361. }
  1362. return $ret;
  1363. }
  1364. function checkEmail2_meta() {
  1365. global $sugar_config;
  1366. $this->connectMailserver();
  1367. $mailboxes = $this->getMailboxes(true);
  1368. $mailboxes[] = 'INBOX';
  1369. sort($mailboxes);
  1370. $GLOBALS['log']->info("INBOUNDEMAIL: checking account [ {$this->name} ]");
  1371. $mailboxes_meta = array();
  1372. foreach($mailboxes as $mailbox) {
  1373. $mailboxes_meta[$mailbox] = $this->getMailboxProcessCount($mailbox);
  1374. }
  1375. $ret = array();
  1376. $ret['mailboxes'] = $mailboxes_meta;
  1377. foreach($mailboxes_

Large files files are truncated, but you can click here to view the full file