simple-imap.class.php 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. <?php
  2. /**
  3. * Insipid
  4. * Personal web-bookmark-system
  5. *
  6. * Copyright 2016-2023 Johannes Keßler
  7. *
  8. * Development starting from 2011: Johannes Keßler
  9. * https://www.bananas-playground.net/projekt/insipid/
  10. *
  11. * creator:
  12. * Luke Reeves <luke@neuro-tech.net>
  13. *
  14. * simple IMAP SSL/TLS email connection based on the imap PHP functions
  15. * the code supports SSL/TLS and IMAP only
  16. *
  17. * This program is free software: you can redistribute it and/or modify
  18. * it under the terms of the GNU General Public License as published by
  19. * the Free Software Foundation, either version 3 of the License, or
  20. * (at your option) any later version.
  21. *
  22. * This program is distributed in the hope that it will be useful,
  23. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  24. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  25. * GNU General Public License for more details.
  26. *
  27. * You should have received a copy of the GNU General Public License
  28. * along with this program. If not, see http://www.gnu.org/licenses/gpl-3.0.
  29. *
  30. */
  31. /**
  32. * Class SimpleImap
  33. * read and manage email messages over imap. Sending not included. Use PHPMailer instead.
  34. */
  35. class SimpleImap {
  36. private IMAP\Connection $_connection;
  37. private string $_server = EMAIL_SERVER;
  38. private string $_user = EMAIL_SERVER_USER;
  39. private string $_pass = EMAIL_SERVER_PASS;
  40. private int $_port = EMAIL_SERVER_PORT_IMAP;
  41. private string $_mailbox = EMAIL_SERVER_MAILBOX;
  42. private string $_connectionstring = '';
  43. /**
  44. * SimpleImap constructor.
  45. */
  46. function __construct() {
  47. # create the mailboxstring
  48. $this->_connectionstring = '{'.$this->_server.':'.$this->_port.'/imap/ssl/novalidate-cert}';
  49. }
  50. /**
  51. *
  52. */
  53. function __destruct() {
  54. //imap_close($this->_connection);
  55. }
  56. /**
  57. * connect to the e-mail server
  58. * with this code SSL/TLS only
  59. *
  60. * @see http://ca.php.net/manual/en/function.imap-open.php
  61. * @throws Exception
  62. */
  63. public function connect(): void {
  64. if(empty($this->_server)) {
  65. throw new Exception('Missing EMAIL_SERVER');
  66. }
  67. if(empty($this->_port)) {
  68. throw new Exception('Missing EMAIL_SERVER_PORT');
  69. }
  70. if(empty($this->_user)) {
  71. throw new Exception('Missing EMAIL_SERVER_USER');
  72. }
  73. # create the connection
  74. $this->_connection = imap_open($this->_connectionstring.$this->_mailbox, $this->_user, $this->_pass);
  75. if(!$this->_connection) {
  76. throw new Exception('Failed IMAP connection: '.var_export(imap_last_error(),true));
  77. }
  78. }
  79. /**
  80. * process the given mailbox and check for the special messages
  81. * return the body and headers from the found message
  82. *
  83. * @param string $subjectmarker
  84. * @return array emailId => array(body, header);
  85. * @throws Exception
  86. */
  87. function messageWithValidSubject(string $subjectmarker): array {
  88. $ret = array();
  89. $messagecount = imap_num_msg($this->_connection);
  90. if($messagecount === false) {
  91. throw new Exception('Can not read the messages in given mailbox');
  92. }
  93. $processedmessagescount = 0;
  94. for($i = 1; $i <= $messagecount; $i++) {
  95. $subject = $this->_extractSubject($i);
  96. if(!empty($subject)) {
  97. # check the special stuff
  98. $markerextract = substr($subject, 0, strlen($subjectmarker));
  99. if($markerextract == $subjectmarker) {
  100. $processedmessagescount++;
  101. # valid message
  102. # get the body
  103. $ret[$i]['body'] = $this->_extractBody($i);
  104. $ret[$i]['header'] = $this->emailHeaders($i);
  105. $ret[$i]['header_rfc822'] = $this->emailHeaders_rfc822($i);
  106. $ret[$i]['header_array'] = $this->emailHeadersAsArray($i);
  107. # @see https://www.php.net/manual/en/function.imap-uid.php
  108. $ret[$i]['uid'] = imap_uid($this->_connection,$i);
  109. }
  110. }
  111. }
  112. # log messages processed to all messages
  113. Summoner::sysLog("INFO Read ".$messagecount." messages");
  114. Summoner::sysLog("INFO Processed ".$processedmessagescount." messages");
  115. return $ret;
  116. }
  117. /**
  118. * the current status about the mail connection and INBOX
  119. * kinda debug only
  120. *
  121. * @see http://ca.php.net/manual/en/function.imap-status.php
  122. */
  123. public function mailboxStatus(): void {
  124. if($this->_connection !== false) {
  125. $status = imap_status($this->_connection, $this->_connectionstring.$this->_mailbox, SA_ALL);
  126. if(DEBUG === true) {
  127. Summoner::sysLog("messages " . $status->messages);
  128. Summoner::sysLog("recent " . $status->recent);
  129. Summoner::sysLog("unseen " . $status->unseen);
  130. Summoner::sysLog("uidnext " . $status->uidnext);
  131. Summoner::sysLog("uidvalidity " . $status->uidvalidity);
  132. }
  133. $list = imap_getmailboxes($this->_connection, $this->_connectionstring, "*");
  134. if (is_array($list)) {
  135. foreach ($list as $key => $val) {
  136. echo "($key) ";
  137. echo imap_utf7_decode($val->name) . ",";
  138. echo "'" . $val->delimiter . "',";
  139. echo $val->attributes . "<br />\n";
  140. }
  141. } else {
  142. Summoner::sysLog("ERROR imap_getmailboxes failed: ".Summoner::cleanForLog(imap_last_error()));
  143. }
  144. }
  145. }
  146. /**
  147. * This function causes a fetch of the complete, unfiltered RFC2822 format header of the specified message.
  148. *
  149. * @param integer $messagenum
  150. * @return string
  151. */
  152. public function emailHeaders(int $messagenum): string {
  153. return imap_fetchheader($this->_connection, $messagenum);
  154. }
  155. /**
  156. * return the email headers by given emailid
  157. *
  158. * @param integer $messagenum
  159. * @return object
  160. */
  161. public function emailHeaders_rfc822(int $messagenum): object {
  162. return imap_rfc822_parse_headers($this->emailHeaders($messagenum));
  163. }
  164. /**
  165. * Email headers parsed as an array
  166. *
  167. * @param integer $messagenum
  168. * @return array
  169. */
  170. public function emailHeadersAsArray(int $messagenum): array {
  171. preg_match_all('/([^: ]+): (.+?(?:\r\n\s(?:.+?))*)\r\n/m', $this->emailHeaders($messagenum), $matches );
  172. return array_combine( $matches[1], $matches[2]);
  173. }
  174. /**
  175. * Move given message to given folder
  176. *
  177. * @param integer $messageUid This is the message Uid as an int
  178. * @param string $folder This is the target folder. Default is EMAIL_ARCHIVE_FOLDER
  179. */
  180. public function moveMessage(int $messageUid, string $folder=EMAIL_ARCHIVE_FOLDER): void {
  181. if(!empty($messageUid) && !empty($folder)) {
  182. $messageUid = (string)$messageUid;
  183. imap_setflag_full($this->_connection,$messageUid,"\SEEN", ST_UID);
  184. imap_mail_move($this->_connection, $messageUid, $folder,CP_UID);
  185. imap_expunge($this->_connection);
  186. }
  187. }
  188. /**
  189. * extract the subject from the email headers and decode
  190. * A subject can be split into multiple parts...
  191. *
  192. * @param int $messagenum
  193. * @return string
  194. */
  195. private function _extractSubject(int $messagenum): string {
  196. $ret = '';
  197. $headerinfo = $this->emailHeaders_rfc822($messagenum);
  198. $subjectArr = imap_mime_header_decode($headerinfo->subject);
  199. foreach ($subjectArr as $el) {
  200. $ret .= $el->text;
  201. }
  202. return $ret;
  203. }
  204. /**
  205. * extract the body of the given message
  206. *
  207. * @see http://php.net/manual/en/function.imap-fetchstructure.php
  208. *
  209. * @param int $messagenum
  210. * @return string
  211. */
  212. private function _extractBody(int $messagenum): string {
  213. $ret = '';
  214. $emailstructure = imap_fetchstructure($this->_connection, $messagenum);
  215. # simple or multipart?
  216. if(isset($emailstructure->parts)) {
  217. exit("multipart todo");
  218. }
  219. else {
  220. $body = imap_body($this->_connection, $messagenum);
  221. }
  222. # encoding
  223. switch ($emailstructure->encoding) {
  224. case ENC8BIT: # 1 8BIT
  225. $ret = quoted_printable_decode(imap_8bit($body));
  226. break;
  227. case ENCBINARY: # 2 BINARY
  228. $ret = imap_binary($body);
  229. break;
  230. case ENCBASE64: # 3 BASE64
  231. $ret = imap_base64($body);
  232. break;
  233. case ENCQUOTEDPRINTABLE: # 4 QUOTED-PRINTABLE
  234. $ret = quoted_printable_decode($body);
  235. break;
  236. case ENC7BIT: # 0 7BIT
  237. // imap_qprint($body); does throws notices
  238. $ret = quoted_printable_decode($body);
  239. break;
  240. case ENCOTHER: # 5 OTHER
  241. default: # UNKNOWN
  242. $ret = $body;
  243. }
  244. return $ret;
  245. }
  246. /**
  247. * close the imap connection
  248. */
  249. function close(): void {
  250. imap_close($this->_connection);
  251. }
  252. }