PageRenderTime 29ms CodeModel.GetById 1ms RepoModel.GetById 0ms app.codeStats 0ms

/core/emails/libs/email_socket.php

https://github.com/dakota/infinitas
PHP | 505 lines | 191 code | 60 blank | 254 comment | 26 complexity | 61021cbcd65b6d650f8bab12a69a0a38 MD5 | raw file
  1. <?php
  2. //App::import('Libs', 'Libs.BaseSocket');
  3. App::import('CakeSocket');
  4. /**
  5. * @base Email socket is a low level class for communicating directly with mail servers
  6. *
  7. * This class will connect and handle all data communications to the mail server
  8. * via a "driver". The common drivers are pop3, IMAP and SMTP.
  9. *
  10. * @code
  11. * // example ways to connect to a server (using pop3, all drivers are the same)
  12. * App::import('Libs', 'Emails.Pop3'); // use pop3
  13. * $connection = array(
  14. * 'host' => 'mail.server.com',
  15. * 'username' => 'user@server.com',
  16. * 'password' => 'password',
  17. * 'port' => 110,
  18. * 'ssl' => false,
  19. * 'timeout' => 30,
  20. * 'connection' => 'pop3'
  21. * );
  22. *
  23. * // connect #1
  24. * $EmailSocket = new Pop3Socket($connection);
  25. * $EmailSocket->login();
  26. *
  27. * // connect #2
  28. * $EmailSocket = new Pop3Socket();
  29. * $EmailSocket->set($connection)->login();
  30. *
  31. * // connect #3
  32. * $EmailSocket = new Pop3Socket();
  33. * $EmailSocket->set($connection);
  34. * $EmailSocket->login();
  35. *
  36. * // connect #3
  37. * $EmailSocket = new Pop3Socket();
  38. * $EmailSocket->set('host', $connection['host']);
  39. * ...
  40. * $EmailSocket->login();
  41. *
  42. * // connect #4
  43. * $EmailSocket = new Pop3Socket();
  44. * $EmailSocket->host = $connection['host'];
  45. * ...
  46. * $EmailSocket->login();
  47. * @endcode
  48. */
  49. abstract class EmailSocket extends CakeSocket{
  50. public $ParseMail;
  51. public $config;
  52. public $eol;
  53. public $mailStats = array(
  54. 'totalCount' => 0,
  55. 'totalSize' => 0,
  56. 'totalSizeReadable' => 0
  57. );
  58. public $mailList = array();
  59. public $description = 'Email Socket Interface';
  60. private $__connectionOptions = array(
  61. 'type' => 'imap',
  62. 'host' => '',
  63. 'username' => '',
  64. 'password' => '',
  65. 'port' => null,
  66. 'ssl' => true,
  67. 'request' => '',
  68. 'timeout' => 30,
  69. 'mail_box' => ''
  70. );
  71. /**
  72. * @brief a unique key per server so that some commands do not need to be run all the time
  73. */
  74. private $__cacheKey;
  75. private $__errors = array();
  76. private $__logs = array();
  77. protected $_response = array();
  78. /**
  79. * @brief a list of capabilities that the server has available.
  80. *
  81. * @property _capabilities
  82. */
  83. protected $_capabilities = array();
  84. protected $_mailboxes = array();
  85. public function __construct($connection = array()){
  86. parent::__construct($connection);
  87. App::import('Libs', 'Emails.ParseMail');
  88. $this->ParseMail = new ParseMail();
  89. $this->eol = "\r\n";
  90. }
  91. /**
  92. * @brief allow method chaning for setting values
  93. *
  94. * The set method is able to take an array of values or normal name, value
  95. * params so you are able to connect in a number of ways. The following
  96. * things all do the same thing.
  97. *
  98. * @li $Imap->set(array('name' => 'value', ....));
  99. * @li $Imap->set('name', 'value')->set('name2' => 'value2')
  100. * @li $Imap->name = 'value'
  101. *
  102. * If you are passing an array of options there the $value param is not used
  103. *
  104. * @param <type> $name
  105. * @param <type> $value
  106. * @return ImapSocket
  107. */
  108. public function set($name, $value = false){
  109. if(!is_array($name)){
  110. $this->{$name} = $value;
  111. }
  112. else{
  113. foreach($name as $key => $value){
  114. $this->set($key, $value);
  115. }
  116. }
  117. return $this;
  118. }
  119. /**
  120. * @brief Check if there is a connection to the mail server
  121. *
  122. * @access public
  123. *
  124. * @return bool true if connected, false if not
  125. */
  126. public function isConnected(){
  127. return $this->connected;
  128. }
  129. /**
  130. * @brief set up the connection for the socket
  131. *
  132. * This method will connect to the server and wait for the welcome
  133. * message. It is the responsibility of the driver to authenticate
  134. * if that is needed.
  135. *
  136. * @return bool was the login correct of not
  137. */
  138. public function login(){
  139. parent::connect();
  140. if(!parent::read(1024, 'isOk')){
  141. $this->error('Server not responding, exiting');
  142. return false;
  143. }
  144. if(!$this->_getCapabilities()){
  145. $this->error('Unable to get the sockets capabilities');
  146. }
  147. return true;
  148. }
  149. /**
  150. * @brief Read data directly from the socket that is open
  151. *
  152. * This is the low level interface to the socket class for reading data
  153. * from the mail server. You are able to pass a call back method that
  154. * the data will be sent to for formatting.
  155. *
  156. * If there are problems reading from the mail server bool false will be
  157. * returned. Other returns will be raw text or the callback method that
  158. * is set.
  159. *
  160. * It should be the duty of the callback method to make sure it has all the
  161. * data that is required and request more using EmailSocket::read() if
  162. * it needs more.
  163. *
  164. * @param int $size the size of data to read upto
  165. * @param string $method a callback method to trigger
  166. * @access public
  167. *
  168. * @return bool|string|array false on fail, string for raw and
  169. * string|array depending on the callbacks
  170. */
  171. public function read($size = 1024, $method = false){
  172. $data = parent::read($size);
  173. if($data){
  174. if($method && is_callable(array($this, $method))){
  175. $method = '_' . $method;
  176. return $this->{$method}($data);
  177. }
  178. return $data;
  179. }
  180. $this->error('Stream timed out, no data');
  181. return false;
  182. }
  183. public function write($data, $method = false, $size = 1024){
  184. $didWrite = parent::write($data . $this->eol);
  185. if($didWrite && $size > 0){
  186. if($method && is_callable(array($this, '_' . $method))){
  187. $data = parent::read($size, $method);
  188. $method = '_' . $method;
  189. return $this->{$method}($data);
  190. }
  191. return parent::read($size);
  192. }
  193. return $didWrite;
  194. }
  195. protected function _getSize($data){
  196. if(!$this->_isOk($data)){
  197. return 0;
  198. }
  199. /*if(strstr($data, ' messages ')){
  200. $data = explode('(', $data, 2);
  201. $data = explode(' ', isset($data[1]) ? $data[1] : '');
  202. return isset($data[0]) ? $data[0] : false;
  203. }*/
  204. $data = explode(' ', $data, 3);
  205. return (isset($data[1]) && (int)$data[1] > 0) ? $data[1] : 0;
  206. }
  207. protected function _isOk($data){
  208. return substr($data, 1, 2) == 'OK';
  209. }
  210. /**
  211. * @brief get the last response from the server
  212. *
  213. * @return <type>
  214. */
  215. public function lastResponse(){
  216. $index = count($this->_response) - 1;
  217. if($index < 0 || !isset($this->_response[$index])){
  218. return false;
  219. }
  220. return $this->_response[$index];
  221. }
  222. /**
  223. * @brief Check if the server has the capability passed in
  224. *
  225. * @param string $capability check the drivers for a list of capabilities
  226. *
  227. * @return bool it does or does not
  228. */
  229. public function hasCapability($capability){
  230. return isset($this->_capabilities[$capability]);
  231. }
  232. /**
  233. * @brief Explode the data into an array and remove the new lines
  234. *
  235. * the data will be returned as array('response_code' => array('data', 'goes', 'here'))
  236. *
  237. * @param string $data the data from the read
  238. * @return array formatted data without the response
  239. */
  240. public function _cleanData($data){
  241. if($data === false || empty($data)){
  242. return false;
  243. }
  244. $list = array_filter(explode("\r\n", str_replace('.', '', $data)));
  245. unset($data);
  246. $response = array_shift($list);
  247. return array($response => $list);
  248. }
  249. /**
  250. * @brief Generate a unique cache key for the server that is being used.
  251. */
  252. private function __cacheKey(){
  253. if(!$this->__cacheKey){
  254. $this->__cacheKey = sha1(serialize(array($this->config['host'], $this->config['type'], $this->config['port'], $this->config['ssl'])));
  255. }
  256. return $this->__cacheKey;
  257. }
  258. /**
  259. * @brief wrapper for Cache::read()
  260. *
  261. * @link http://api13.cakephp.org/class/cache#method-Cacheread
  262. *
  263. * @param string $key the name of the cache file
  264. * @param string $config the config to use when reading
  265. * @access public
  266. *
  267. * @return mixed false for nothing, any data if there was
  268. */
  269. public function readCache($key, $config = 'emails'){
  270. return Cache::read($this->__cacheKey() . '.' . $key, $config);
  271. }
  272. /**
  273. * @brief wrapper for Cache::write()
  274. *
  275. * @link http://api13.cakephp.org/class/cache#method-Cachewrite
  276. *
  277. * @param string $key the name of the cache file
  278. * @param array $data the data that should be written to cache
  279. * @param string $config the config to use when writing
  280. * @access public
  281. *
  282. * @return bool true on write, false for error.
  283. */
  284. public function writeCache($key, $data, $config = 'emails'){
  285. return Cache::write($this->__cacheKey() . '.' . $key, $data, $config);
  286. }
  287. /**
  288. * @brief attempt to log the user out of the mail server
  289. *
  290. * This is part of the cleanup process, if the driver has done authentication
  291. * you should use this method to logout of the session.
  292. *
  293. * @return <type>
  294. */
  295. abstract public function logout();
  296. /**
  297. * @brief Get some basic stats for the email account
  298. *
  299. * This method should be cached per user and can be used as a way to
  300. * check if there is any new mail. A hash of the number of mails + the
  301. * total size could be used.
  302. *
  303. * @return bool return true, or false on error
  304. */
  305. abstract protected function _getStats();
  306. /**
  307. * @brief Get a list of the mail numbers and the sizes for later use
  308. *
  309. * @return bool true if found, false if not
  310. */
  311. abstract protected function _getList();
  312. /**
  313. * @brief Get the details of what the server has available
  314. */
  315. abstract protected function _getCapabilities();
  316. /**
  317. * @brief Get a list of possible mail boxes
  318. *
  319. * This should be cached per user
  320. *
  321. * @access abstract
  322. *
  323. * @return bool|array false on error, or array of mailboxes
  324. */
  325. abstract protected function _getMailboxes($ref = '', $wildcard = '*');
  326. /**
  327. * @brief Just returns a +OK response for keep-alive
  328. *
  329. * @link http://www.apps.ietf.org/rfc/rfc1939.html#page-9
  330. *
  331. * @access public
  332. *
  333. * @return bool true if server is still available, false when connection is lost
  334. */
  335. abstract public function noop();
  336. /**
  337. * @brief Sort of undelete
  338. *
  339. * will reset mails that were set to delete.
  340. *
  341. * @link http://www.apps.ietf.org/rfc/rfc1939.html#page-9
  342. *
  343. * @access public
  344. *
  345. * @return bool
  346. */
  347. abstract public function undoDeletes();
  348. /**
  349. * @brief record an error
  350. *
  351. * @param string $text the error message
  352. * @access public
  353. *
  354. * @return bool true
  355. */
  356. public function error($text){
  357. $this->__errors[] = $text;
  358. return true;
  359. }
  360. /**
  361. * @brief Log the raw data that the server is returning
  362. *
  363. * @param string $text the raw logs
  364. * @param integer $size the size of the packet
  365. * @access public
  366. *
  367. * @return bool true
  368. */
  369. public function log($text, $size = 0){
  370. $size = ($size) ? $size : strlen($text);
  371. $this->__logs[] = array(
  372. 'data' => substr($text, 0, -2),
  373. 'size' => $size
  374. );
  375. unset($text, $size);
  376. return true;
  377. }
  378. /**
  379. * @brief Get any errors that occured during the connection
  380. *
  381. * @access public
  382. *
  383. * @return array the errors
  384. */
  385. public function errors(){
  386. if(empty($this->__errors)){
  387. return false;
  388. }
  389. return $this->__errors;
  390. }
  391. /**
  392. * @brief public method to get the raw logs
  393. *
  394. * @access public
  395. *
  396. * @return array the logs
  397. */
  398. public function logs(){
  399. if(empty($this->__logs)){
  400. return false;
  401. }
  402. return $this->__logs;
  403. }
  404. /**
  405. * @brief set the connection details
  406. *
  407. * @param string $name
  408. * @param string|integer $value
  409. * @access public
  410. *
  411. * @return ImapInterface
  412. */
  413. public function __set($name, $value = null){
  414. if(!in_array($name, $this->__connectionOptions)){
  415. $this->_errors[] = sprintf('You may not set the %s property', $name);
  416. return false;
  417. }
  418. switch($name){
  419. case 'port':
  420. if((int)$value == 0){
  421. $this->_errors[] = sprintf('The port "%s" is not valid', $value);
  422. return false;
  423. }
  424. break;
  425. case 'ssl':
  426. if($value){
  427. $this->request = array('uri' => array('scheme' => 'https'));
  428. return;
  429. }
  430. break;
  431. case 'host':
  432. case 'username':
  433. if(!is_string($value)){
  434. $this->_errors[] = sprintf('%s should be a string, "%s" is not valid', $name, gettype($value));
  435. return false;
  436. }
  437. if(empty($value)){
  438. $this->_errors[] = '%s should not be empty';
  439. return false;
  440. }
  441. break;
  442. }
  443. $this->config[$name] = $value;
  444. }
  445. }