PageRenderTime 52ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/app/classes/Zend/Mail/Storage/Maildir.php

https://gitlab.com/jalon/doadoronline
PHP | 408 lines | 226 code | 51 blank | 131 comment | 45 complexity | 75fbf7fe2eee66c2b0e53b0e3ac5892d MD5 | raw file
  1. <?php
  2. /**
  3. * Zend Framework (http://framework.zend.com/)
  4. *
  5. * @link http://github.com/zendframework/zf2 for the canonical source repository
  6. * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
  7. * @license http://framework.zend.com/license/new-bsd New BSD License
  8. */
  9. namespace Zend\Mail\Storage;
  10. use Zend\Mail;
  11. use Zend\Stdlib\ErrorHandler;
  12. class Maildir extends AbstractStorage
  13. {
  14. /**
  15. * used message class, change it in an extended class to extend the returned message class
  16. * @var string
  17. */
  18. protected $messageClass = '\Zend\Mail\Storage\Message\File';
  19. /**
  20. * data of found message files in maildir dir
  21. * @var array
  22. */
  23. protected $files = array();
  24. /**
  25. * known flag chars in filenames
  26. *
  27. * This list has to be in alphabetical order for setFlags()
  28. *
  29. * @var array
  30. */
  31. protected static $knownFlags = array('D' => Mail\Storage::FLAG_DRAFT,
  32. 'F' => Mail\Storage::FLAG_FLAGGED,
  33. 'P' => Mail\Storage::FLAG_PASSED,
  34. 'R' => Mail\Storage::FLAG_ANSWERED,
  35. 'S' => Mail\Storage::FLAG_SEEN,
  36. 'T' => Mail\Storage::FLAG_DELETED);
  37. // TODO: getFlags($id) for fast access if headers are not needed (i.e. just setting flags)?
  38. /**
  39. * Count messages all messages in current box
  40. *
  41. * @param mixed $flags
  42. * @return int number of messages
  43. */
  44. public function countMessages($flags = null)
  45. {
  46. if ($flags === null) {
  47. return count($this->files);
  48. }
  49. $count = 0;
  50. if (!is_array($flags)) {
  51. foreach ($this->files as $file) {
  52. if (isset($file['flaglookup'][$flags])) {
  53. ++$count;
  54. }
  55. }
  56. return $count;
  57. }
  58. $flags = array_flip($flags);
  59. foreach ($this->files as $file) {
  60. foreach ($flags as $flag => $v) {
  61. if (!isset($file['flaglookup'][$flag])) {
  62. continue 2;
  63. }
  64. }
  65. ++$count;
  66. }
  67. return $count;
  68. }
  69. /**
  70. * Get one or all fields from file structure. Also checks if message is valid
  71. *
  72. * @param int $id message number
  73. * @param string|null $field wanted field
  74. * @throws Exception\InvalidArgumentException
  75. * @return string|array wanted field or all fields as array
  76. */
  77. protected function _getFileData($id, $field = null)
  78. {
  79. if (!isset($this->files[$id - 1])) {
  80. throw new Exception\InvalidArgumentException('id does not exist');
  81. }
  82. if (!$field) {
  83. return $this->files[$id - 1];
  84. }
  85. if (!isset($this->files[$id - 1][$field])) {
  86. throw new Exception\InvalidArgumentException('field does not exist');
  87. }
  88. return $this->files[$id - 1][$field];
  89. }
  90. /**
  91. * Get a list of messages with number and size
  92. *
  93. * @param int|null $id number of message or null for all messages
  94. * @return int|array size of given message of list with all messages as array(num => size)
  95. */
  96. public function getSize($id = null)
  97. {
  98. if ($id !== null) {
  99. $filedata = $this->_getFileData($id);
  100. return isset($filedata['size']) ? $filedata['size'] : filesize($filedata['filename']);
  101. }
  102. $result = array();
  103. foreach ($this->files as $num => $data) {
  104. $result[$num + 1] = isset($data['size']) ? $data['size'] : filesize($data['filename']);
  105. }
  106. return $result;
  107. }
  108. /**
  109. * Fetch a message
  110. *
  111. * @param int $id number of message
  112. * @return \Zend\Mail\Storage\Message\File
  113. * @throws \Zend\Mail\Storage\Exception\ExceptionInterface
  114. */
  115. public function getMessage($id)
  116. {
  117. // TODO that's ugly, would be better to let the message class decide
  118. if (strtolower($this->messageClass) == '\zend\mail\storage\message\file'
  119. || is_subclass_of($this->messageClass, '\Zend\Mail\Storage\Message\File')) {
  120. return new $this->messageClass(array('file' => $this->_getFileData($id, 'filename'),
  121. 'flags' => $this->_getFileData($id, 'flags')));
  122. }
  123. return new $this->messageClass(array('handler' => $this, 'id' => $id, 'headers' => $this->getRawHeader($id),
  124. 'flags' => $this->_getFileData($id, 'flags')));
  125. }
  126. /*
  127. * Get raw header of message or part
  128. *
  129. * @param int $id number of message
  130. * @param null|array|string $part path to part or null for message header
  131. * @param int $topLines include this many lines with header (after an empty line)
  132. * @throws Exception\RuntimeException
  133. * @return string raw header
  134. */
  135. public function getRawHeader($id, $part = null, $topLines = 0)
  136. {
  137. if ($part !== null) {
  138. // TODO: implement
  139. throw new Exception\RuntimeException('not implemented');
  140. }
  141. $fh = fopen($this->_getFileData($id, 'filename'), 'r');
  142. $content = '';
  143. while (!feof($fh)) {
  144. $line = fgets($fh);
  145. if (!trim($line)) {
  146. break;
  147. }
  148. $content .= $line;
  149. }
  150. fclose($fh);
  151. return $content;
  152. }
  153. /*
  154. * Get raw content of message or part
  155. *
  156. * @param int $id number of message
  157. * @param null|array|string $part path to part or null for message content
  158. * @throws Exception\RuntimeException
  159. * @return string raw content
  160. */
  161. public function getRawContent($id, $part = null)
  162. {
  163. if ($part !== null) {
  164. // TODO: implement
  165. throw new Exception\RuntimeException('not implemented');
  166. }
  167. $fh = fopen($this->_getFileData($id, 'filename'), 'r');
  168. while (!feof($fh)) {
  169. $line = fgets($fh);
  170. if (!trim($line)) {
  171. break;
  172. }
  173. }
  174. $content = stream_get_contents($fh);
  175. fclose($fh);
  176. return $content;
  177. }
  178. /**
  179. * Create instance with parameters
  180. * Supported parameters are:
  181. * - dirname dirname of mbox file
  182. *
  183. * @param $params array mail reader specific parameters
  184. * @throws Exception\InvalidArgumentException
  185. */
  186. public function __construct($params)
  187. {
  188. if (is_array($params)) {
  189. $params = (object) $params;
  190. }
  191. if (!isset($params->dirname) || !is_dir($params->dirname)) {
  192. throw new Exception\InvalidArgumentException('no valid dirname given in params');
  193. }
  194. if (!$this->_isMaildir($params->dirname)) {
  195. throw new Exception\InvalidArgumentException('invalid maildir given');
  196. }
  197. $this->has['top'] = true;
  198. $this->has['flags'] = true;
  199. $this->_openMaildir($params->dirname);
  200. }
  201. /**
  202. * check if a given dir is a valid maildir
  203. *
  204. * @param string $dirname name of dir
  205. * @return bool dir is valid maildir
  206. */
  207. protected function _isMaildir($dirname)
  208. {
  209. if (file_exists($dirname . '/new') && !is_dir($dirname . '/new')) {
  210. return false;
  211. }
  212. if (file_exists($dirname . '/tmp') && !is_dir($dirname . '/tmp')) {
  213. return false;
  214. }
  215. return is_dir($dirname . '/cur');
  216. }
  217. /**
  218. * open given dir as current maildir
  219. *
  220. * @param string $dirname name of maildir
  221. * @throws Exception\RuntimeException
  222. */
  223. protected function _openMaildir($dirname)
  224. {
  225. if ($this->files) {
  226. $this->close();
  227. }
  228. ErrorHandler::start(E_WARNING);
  229. $dh = opendir($dirname . '/cur/');
  230. $error = ErrorHandler::stop();
  231. if (!$dh) {
  232. throw new Exception\RuntimeException('cannot open maildir', 0, $error);
  233. }
  234. $this->_getMaildirFiles($dh, $dirname . '/cur/');
  235. closedir($dh);
  236. ErrorHandler::start(E_WARNING);
  237. $dh = opendir($dirname . '/new/');
  238. $error = ErrorHandler::stop();
  239. if ($dh) {
  240. $this->_getMaildirFiles($dh, $dirname . '/new/', array(Mail\Storage::FLAG_RECENT));
  241. closedir($dh);
  242. } elseif (file_exists($dirname . '/new/')) {
  243. throw new Exception\RuntimeException('cannot read recent mails in maildir', 0, $error);
  244. }
  245. }
  246. /**
  247. * find all files in opened dir handle and add to maildir files
  248. *
  249. * @param resource $dh dir handle used for search
  250. * @param string $dirname dirname of dir in $dh
  251. * @param array $defaultFlags default flags for given dir
  252. */
  253. protected function _getMaildirFiles($dh, $dirname, $defaultFlags = array())
  254. {
  255. while (($entry = readdir($dh)) !== false) {
  256. if ($entry[0] == '.' || !is_file($dirname . $entry)) {
  257. continue;
  258. }
  259. ErrorHandler::start(E_NOTICE);
  260. list($uniq, $info) = explode(':', $entry, 2);
  261. list(, $size) = explode(',', $uniq, 2);
  262. ErrorHandler::stop();
  263. if ($size && $size[0] == 'S' && $size[1] == '=') {
  264. $size = substr($size, 2);
  265. }
  266. if (!ctype_digit($size)) {
  267. $size = null;
  268. }
  269. ErrorHandler::start(E_NOTICE);
  270. list($version, $flags) = explode(',', $info, 2);
  271. ErrorHandler::stop();
  272. if ($version != 2) {
  273. $flags = '';
  274. }
  275. $namedFlags = $defaultFlags;
  276. $length = strlen($flags);
  277. for ($i = 0; $i < $length; ++$i) {
  278. $flag = $flags[$i];
  279. $namedFlags[$flag] = isset(static::$knownFlags[$flag]) ? static::$knownFlags[$flag] : $flag;
  280. }
  281. $data = array('uniq' => $uniq,
  282. 'flags' => $namedFlags,
  283. 'flaglookup' => array_flip($namedFlags),
  284. 'filename' => $dirname . $entry);
  285. if ($size !== null) {
  286. $data['size'] = (int) $size;
  287. }
  288. $this->files[] = $data;
  289. }
  290. }
  291. /**
  292. * Close resource for mail lib. If you need to control, when the resource
  293. * is closed. Otherwise the destructor would call this.
  294. *
  295. */
  296. public function close()
  297. {
  298. $this->files = array();
  299. }
  300. /**
  301. * Waste some CPU cycles doing nothing.
  302. *
  303. * @return bool always return true
  304. */
  305. public function noop()
  306. {
  307. return true;
  308. }
  309. /**
  310. * stub for not supported message deletion
  311. *
  312. * @param $id
  313. * @throws Exception\RuntimeException
  314. */
  315. public function removeMessage($id)
  316. {
  317. throw new Exception\RuntimeException('maildir is (currently) read-only');
  318. }
  319. /**
  320. * get unique id for one or all messages
  321. *
  322. * if storage does not support unique ids it's the same as the message number
  323. *
  324. * @param int|null $id message number
  325. * @return array|string message number for given message or all messages as array
  326. */
  327. public function getUniqueId($id = null)
  328. {
  329. if ($id) {
  330. return $this->_getFileData($id, 'uniq');
  331. }
  332. $ids = array();
  333. foreach ($this->files as $num => $file) {
  334. $ids[$num + 1] = $file['uniq'];
  335. }
  336. return $ids;
  337. }
  338. /**
  339. * get a message number from a unique id
  340. *
  341. * I.e. if you have a webmailer that supports deleting messages you should use unique ids
  342. * as parameter and use this method to translate it to message number right before calling removeMessage()
  343. *
  344. * @param string $id unique id
  345. * @throws Exception\InvalidArgumentException
  346. * @return int message number
  347. */
  348. public function getNumberByUniqueId($id)
  349. {
  350. foreach ($this->files as $num => $file) {
  351. if ($file['uniq'] == $id) {
  352. return $num + 1;
  353. }
  354. }
  355. throw new Exception\InvalidArgumentException('unique id not found');
  356. }
  357. }