PageRenderTime 50ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/library/Zend/Mail/Storage/Maildir.php

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