PageRenderTime 46ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/application/libraries/Imap.php

https://github.com/ushahidi/Ushahidi_Election
PHP | 417 lines | 256 code | 75 blank | 86 comment | 47 complexity | d8adfdeb175426bc91de71e8041205e8 MD5 | raw file
Possible License(s): LGPL-3.0, BSD-3-Clause, LGPL-2.1
  1. <?php defined('SYSPATH') OR die('No direct access allowed.');
  2. /**
  3. * Imap library. Wrapper to read email using IMAP/POP3.
  4. * @package Imap
  5. * @author Ushahidi Team
  6. * @copyright (c) 2009 Ushahidi Team
  7. * @license http://www.ushahidi.com/license.html
  8. */
  9. class Imap_Core {
  10. private $imap_stream;
  11. /**
  12. * Opens an IMAP stream
  13. */
  14. public function __construct()
  15. {
  16. // Set Imap Timeouts
  17. imap_timeout(IMAP_OPENTIMEOUT,90);
  18. imap_timeout(IMAP_READTIMEOUT,90);
  19. // If SSL Enabled
  20. $ssl = Kohana::config('settings.email_ssl') == true ? "/ssl" : "";
  21. // Do not validate certificates (TLS/SSL server)
  22. //$novalidate = strtolower(Kohana::config('settings.email_servertype')) == "imap" ? "/novalidate-cert" : "";
  23. $novalidate = "/novalidate-cert";
  24. // If POP3 Disable TLS
  25. $notls = strtolower(Kohana::config('settings.email_servertype')) == "pop3" ? "/notls" : "";
  26. /*
  27. More Info about above options at:
  28. http://php.net/manual/en/function.imap-open.php
  29. */
  30. $service = "{".Kohana::config('settings.email_host').":"
  31. .Kohana::config('settings.email_port')."/"
  32. .Kohana::config('settings.email_servertype')
  33. .$notls.$ssl.$novalidate."}";
  34. // Check if the host name is valid, if not, set imap_stream as false and return false
  35. if(count(dns_get_record("".Kohana::config('settings.email_host')."")) == 0)
  36. {
  37. $this->imap_stream = false;
  38. return false;
  39. }
  40. if ( $imap_stream = @imap_open($service, Kohana::config('settings.email_username')
  41. ,Kohana::config('settings.email_password')))
  42. {
  43. $this->imap_stream = $imap_stream;
  44. } else {
  45. // We don't usually want to break the entire scheduler process if email settings are off
  46. // so lets return false instead of halting the entire script with a Kohana Exception.
  47. $this->imap_stream = false;
  48. return false;
  49. //throw new Kohana_Exception('imap.imap_stream_not_opened', $throwing_error);
  50. }
  51. }
  52. /**
  53. * Get messages according to a search criteria
  54. *
  55. * @param string search criteria (RFC2060, sec. 6.4.4). Set to "UNSEEN" by default
  56. * NB: Search criteria only affects IMAP mailboxes.
  57. * @param string date format. Set to "Y-m-d H:i:s" by default
  58. * @return mixed array containing messages
  59. */
  60. public function get_messages($search_criteria="UNSEEN",
  61. $date_format="Y-m-d H:i:s")
  62. {
  63. global $htmlmsg,$plainmsg,$attachments;
  64. // If our imap connection failed earlier, return no messages
  65. if($this->imap_stream == false)
  66. {
  67. return array();
  68. }
  69. $no_of_msgs = imap_num_msg($this->imap_stream);
  70. $max_imap_messages = Kohana::config('email.max_imap_messages');
  71. // Check to see if the number of messages we want to sort through is greater than
  72. // the number of messages we want to allow. If there are too many messages, it
  73. // can fail and that's no good.
  74. $msg_to_pull = $no_of_msgs;
  75. //** Disabled this config setting for now - causing issues **
  76. //if($msg_to_pull > $max_imap_messages){
  77. // $msg_to_pull = $max_imap_messages;
  78. //}
  79. $messages = array();
  80. for ($msgno = 1; $msgno <= $msg_to_pull; $msgno++)
  81. {
  82. $header = imap_headerinfo($this->imap_stream, $msgno);
  83. if( ! isset($header->message_id) OR ! isset($header->udate))
  84. {
  85. continue;
  86. }
  87. $message_id = $header->message_id;
  88. $date = date($date_format, $header->udate);
  89. if (isset($header->from))
  90. {
  91. $from = $header->from;
  92. }else{
  93. $from = FALSE;
  94. }
  95. $fromname = "";
  96. $fromaddress = "";
  97. $subject = "";
  98. $body = "";
  99. $attachments = "";
  100. if ($from != FALSE)
  101. {
  102. foreach ($from as $id => $object)
  103. {
  104. if (isset($object->personal))
  105. {
  106. $fromname = $object->personal;
  107. }
  108. if (isset($object->mailbox) AND isset($object->host))
  109. {
  110. $fromaddress = $object->mailbox."@".$object->host;
  111. }
  112. if ($fromname == "")
  113. {
  114. // In case from object doesn't have Name
  115. $fromname = $fromaddress;
  116. }
  117. }
  118. }
  119. if (isset($header->subject))
  120. {
  121. $subject = $this->_mime_decode($header->subject);
  122. }
  123. // Fetch Body
  124. $this->_getmsg($this->imap_stream, $msgno);
  125. if ($htmlmsg)
  126. {
  127. // Convert HTML to Text
  128. $html2text = new Html2Text($htmlmsg);
  129. $htmlmsg = $html2text->get_text();
  130. }
  131. $body = ($plainmsg) ? $plainmsg : $htmlmsg;
  132. // Fetch Attachments
  133. $attachments = $this->_extract_attachments($this->imap_stream, $msgno);
  134. // Convert to valid UTF8
  135. $body = htmlentities($body,NULL,mb_detect_encoding($body, "auto"));
  136. $subject = htmlentities(strip_tags($subject),NULL,'UTF-8');
  137. array_push($messages, array('message_id' => $message_id,
  138. 'date' => $date,
  139. 'from' => $fromname,
  140. 'email' => $fromaddress,
  141. 'subject' => $subject,
  142. 'body' => $body,
  143. 'attachments' => $attachments));
  144. // Mark Message As Read
  145. imap_setflag_full($this->imap_stream, $msgno, "\\Seen");
  146. }
  147. return $messages;
  148. }
  149. /**
  150. * Delete a message
  151. * @param int message number
  152. */
  153. public function delete_message($msg_no)
  154. {
  155. imap_delete($this->imap_stream, $msg_no);
  156. }
  157. /**
  158. * Closes an IMAP stream
  159. */
  160. public function close()
  161. {
  162. @imap_close($this->imap_stream);
  163. }
  164. private function _mime_decode($str)
  165. {
  166. $elements = imap_mime_header_decode($str);
  167. $text = "";
  168. foreach ($elements as $element)
  169. {
  170. // Make sure Arabic characters can be passed through as UTF-8
  171. if(strtoupper($element->charset) == 'WINDOWS-1256'){
  172. $element->text = iconv("windows-1256", "UTF-8", $element->text);
  173. }
  174. $text.= $element->text;
  175. }
  176. return $text;
  177. }
  178. /**
  179. * Extract Attachments from Email
  180. */
  181. private function _extract_attachments($connection, $message_number) {
  182. $attachments = array();
  183. $structure = imap_fetchstructure($connection, $message_number);
  184. if(isset($structure->parts) && count($structure->parts)) {
  185. for($i = 0; $i < count($structure->parts); $i++) {
  186. $attachments[$i] = array(
  187. 'is_attachment' => false,
  188. 'file_name' => '',
  189. 'name' => '',
  190. 'attachment' => ''
  191. );
  192. if($structure->parts[$i]->ifdparameters) {
  193. foreach($structure->parts[$i]->dparameters as $object) {
  194. if(strtolower($object->attribute) == 'filename') {
  195. $attachments[$i]['is_attachment'] = true;
  196. $attachments[$i]['file_name'] = $object->value;
  197. }
  198. }
  199. }
  200. if($structure->parts[$i]->ifparameters) {
  201. foreach($structure->parts[$i]->parameters as $object) {
  202. if(strtolower($object->attribute) == 'name') {
  203. $attachments[$i]['is_attachment'] = true;
  204. $attachments[$i]['name'] = $object->value;
  205. }
  206. }
  207. }
  208. if($attachments[$i]['is_attachment']) {
  209. $attachments[$i]['attachment'] = imap_fetchbody($connection, $message_number, $i+1);
  210. if($structure->parts[$i]->encoding == 3) { // 3 = BASE64
  211. $attachments[$i]['attachment'] = base64_decode($attachments[$i]['attachment']);
  212. }
  213. elseif($structure->parts[$i]->encoding == 4) { // 4 = QUOTED-PRINTABLE
  214. $attachments[$i]['attachment'] = quoted_printable_decode($attachments[$i]['attachment']);
  215. }
  216. }
  217. }
  218. }
  219. $valid_attachments = array();
  220. foreach ($attachments as $attachment)
  221. {
  222. $file_name = $attachment['file_name'];
  223. $file_content = $attachment['attachment'];
  224. $file_type = strrev(substr(strrev($file_name),0,4));
  225. $new_file_name = time()."_".$this->_random_string(10); // Included rand so that files don't overwrite each other
  226. $valid_attachments[] = $this->_save_attachments($file_type, $new_file_name, $file_content);
  227. }
  228. // Remove Nulls
  229. return array_filter($valid_attachments);
  230. }
  231. /**
  232. * Save Attachments to Upload Folder
  233. * Right now we only accept gif, png and jpg files
  234. */
  235. private function _save_attachments($file_type,$file_name,$file_content)
  236. {
  237. if ($file_type == ".gif"
  238. OR $file_type == ".png"
  239. OR $file_type == ".jpg")
  240. {
  241. $attachments = array();
  242. $file = Kohana::config("upload.directory")."/".$file_name.$file_type;
  243. $fp = fopen($file, "w");
  244. fwrite($fp, $file_content);
  245. fclose($fp);
  246. // IMAGE SIZES: 800X600, 400X300, 89X59
  247. // Large size
  248. Image::factory($file)->resize(800,600,Image::AUTO)
  249. ->save(Kohana::config('upload.directory', TRUE).$file_name.$file_type);
  250. // Medium size
  251. Image::factory($file)->resize(400,300,Image::HEIGHT)
  252. ->save(Kohana::config('upload.directory', TRUE).$file_name."_m".$file_type);
  253. // Thumbnail
  254. Image::factory($file)->resize(89,59,Image::HEIGHT)
  255. ->save(Kohana::config('upload.directory', TRUE).$file_name."_t".$file_type);
  256. $attachments[] = array(
  257. $file_name.$file_type,
  258. $file_name."_m".$file_type,
  259. $file_name."_t".$file_type
  260. );
  261. return $attachments;
  262. }
  263. else
  264. {
  265. return null;
  266. }
  267. }
  268. // Random Character String
  269. private function _random_string($length)
  270. {
  271. $random = "";
  272. srand((double)microtime()*1000000);
  273. $char_list = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  274. $char_list .= "abcdefghijklmnopqrstuvwxyz";
  275. $char_list .= "1234567890";
  276. // Add the special characters to $char_list if needed
  277. for($i = 0; $i < $length; $i++)
  278. {
  279. $random .= substr($char_list,(rand()%(strlen($char_list))), 1);
  280. }
  281. return $random;
  282. }
  283. private function _getmsg($mbox,$mid)
  284. {
  285. // input $mbox = IMAP stream, $mid = message id
  286. // output all the following:
  287. global $htmlmsg,$plainmsg,$attachments;
  288. // the message may in $htmlmsg, $plainmsg, or both
  289. $htmlmsg = $plainmsg = '';
  290. $attachments = array();
  291. // HEADER
  292. $h = imap_header($mbox,$mid);
  293. // add code here to get date, from, to, cc, subject...
  294. // BODY
  295. $s = imap_fetchstructure($mbox,$mid);
  296. if (@!$s->parts) // not multipart
  297. $this->_getpart($mbox,$mid,$s,0); // no part-number, so pass 0
  298. else { // multipart: iterate through each part
  299. foreach ($s->parts as $partno0=>$p)
  300. $this->_getpart($mbox,$mid,$p,$partno0+1);
  301. }
  302. }
  303. private function _getpart($mbox,$mid,$p,$partno)
  304. {
  305. // $partno = '1', '2', '2.1', '2.1.3', etc if multipart, 0 if not multipart
  306. global $htmlmsg,$plainmsg,$attachments;
  307. // DECODE DATA
  308. $data = ($partno)?
  309. imap_fetchbody($mbox,$mid,$partno): // multipart
  310. imap_body($mbox,$mid); // not multipart
  311. // Any part may be encoded, even plain text messages, so check everything.
  312. if ($p->encoding==4)
  313. $data = quoted_printable_decode($data);
  314. elseif ($p->encoding==3)
  315. $data = base64_decode($data);
  316. // no need to decode 7-bit, 8-bit, or binary
  317. // TEXT
  318. if ($p->type==0 && $data) {
  319. // Messages may be split in different parts because of inline attachments,
  320. // so append parts together with blank row.
  321. if (strtolower($p->subtype)=='plain')
  322. $plainmsg .= trim($data) ."\n\n";
  323. else
  324. $htmlmsg .= $data ."<br><br>";
  325. }
  326. // EMBEDDED MESSAGE
  327. // Many bounce notifications embed the original message as type 2,
  328. // but AOL uses type 1 (multipart), which is not handled here.
  329. // There are no PHP functions to parse embedded messages,
  330. // so this just appends the raw source to the main message.
  331. elseif ($p->type==2 && $data)
  332. {
  333. $plainmsg .= trim($data) ."\n\n";
  334. }
  335. // SUBPART RECURSION
  336. if (isset($p->parts))
  337. {
  338. foreach ($p->parts as $partno0=>$p2)
  339. $this->_getpart($mbox,$mid,$p2,$partno.'.'.($partno0+1)); // 1.2, 1.2.1, etc.
  340. }
  341. }
  342. } // End Imap