PageRenderTime 44ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/library/Zend/Mail/Storage/Mbox.php

https://github.com/sidealice/zf2
PHP | 407 lines | 178 code | 46 blank | 183 comment | 31 complexity | 226f635ec7dc5345148286a6f0fbff6a 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-2011 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\Exception;
  27. /**
  28. * @uses \Zend\Mail\Message\File
  29. * @uses \Zend\Mail\Storage\AbstractStorage
  30. * @uses \Zend\Mail\Storage\Exception
  31. * @category Zend
  32. * @package Zend_Mail
  33. * @subpackage Storage
  34. * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
  35. * @license http://framework.zend.com/license/new-bsd New BSD License
  36. */
  37. class Mbox extends AbstractStorage
  38. {
  39. /**
  40. * file handle to mbox file
  41. * @var null|resource
  42. */
  43. protected $_fh;
  44. /**
  45. * filename of mbox file for __wakeup
  46. * @var string
  47. */
  48. protected $_filename;
  49. /**
  50. * modification date of mbox file for __wakeup
  51. * @var int
  52. */
  53. protected $_filemtime;
  54. /**
  55. * start and end position of messages as array('start' => start, 'seperator' => headersep, 'end' => end)
  56. * @var array
  57. */
  58. protected $_positions;
  59. /**
  60. * used message class, change it in an extened class to extend the returned message class
  61. * @var string
  62. */
  63. protected $_messageClass = '\Zend\Mail\Message\File';
  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()
  71. {
  72. return count($this->_positions);
  73. }
  74. /**
  75. * Get a list of messages with number and size
  76. *
  77. * @param int|null $id number of message or null for all messages
  78. * @return int|array size of given message of list with all messages as array(num => size)
  79. */
  80. public function getSize($id = 0)
  81. {
  82. if ($id) {
  83. $pos = $this->_positions[$id - 1];
  84. return $pos['end'] - $pos['start'];
  85. }
  86. $result = array();
  87. foreach ($this->_positions as $num => $pos) {
  88. $result[$num + 1] = $pos['end'] - $pos['start'];
  89. }
  90. return $result;
  91. }
  92. /**
  93. * Get positions for mail message or throw exeption if id is invalid
  94. *
  95. * @param int $id number of message
  96. * @return array positions as in _positions
  97. * @throws \Zend\Mail\Storage\Exception
  98. */
  99. protected function _getPos($id)
  100. {
  101. if (!isset($this->_positions[$id - 1])) {
  102. throw new Exception\InvalidArgumentException('id does not exist');
  103. }
  104. return $this->_positions[$id - 1];
  105. }
  106. /**
  107. * Fetch a message
  108. *
  109. * @param int $id number of message
  110. * @return \Zend\Mail\Message\File
  111. * @throws \Zend\Mail\Storage\Exception
  112. */
  113. public function getMessage($id)
  114. {
  115. // TODO that's ugly, would be better to let the message class decide
  116. if (strtolower($this->_messageClass) == '\zend\mail\message\file' || is_subclass_of($this->_messageClass, '\Zend\Mail\Message\File')) {
  117. // TODO top/body lines
  118. $messagePos = $this->_getPos($id);
  119. return new $this->_messageClass(array('file' => $this->_fh, 'startPos' => $messagePos['start'],
  120. 'endPos' => $messagePos['end']));
  121. }
  122. $bodyLines = 0; // TODO: need a way to change that
  123. $message = $this->getRawHeader($id);
  124. // file pointer is after headers now
  125. if ($bodyLines) {
  126. $message .= "\n";
  127. while ($bodyLines-- && ftell($this->_fh) < $this->_positions[$id - 1]['end']) {
  128. $message .= fgets($this->_fh);
  129. }
  130. }
  131. return new $this->_messageClass(array('handler' => $this, 'id' => $id, 'headers' => $message));
  132. }
  133. /*
  134. * Get raw header of message or part
  135. *
  136. * @param int $id number of message
  137. * @param null|array|string $part path to part or null for messsage header
  138. * @param int $topLines include this many lines with header (after an empty line)
  139. * @return string raw header
  140. * @throws \Zend\Mail\Protocol\Exception
  141. * @throws \Zend\Mail\Storage\Exception
  142. */
  143. public function getRawHeader($id, $part = null, $topLines = 0)
  144. {
  145. if ($part !== null) {
  146. // TODO: implement
  147. throw new Exception\RuntimeException('not implemented');
  148. }
  149. $messagePos = $this->_getPos($id);
  150. // TODO: toplines
  151. return stream_get_contents($this->_fh, $messagePos['separator'] - $messagePos['start'], $messagePos['start']);
  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 messsage content
  158. * @return string raw content
  159. * @throws \Zend\Mail\Protocol\Exception
  160. * @throws \Zend\Mail\Storage\Exception
  161. */
  162. public function getRawContent($id, $part = null)
  163. {
  164. if ($part !== null) {
  165. // TODO: implement
  166. throw new Exception\RuntimeException('not implemented');
  167. }
  168. $messagePos = $this->_getPos($id);
  169. return stream_get_contents($this->_fh, $messagePos['end'] - $messagePos['separator'], $messagePos['separator']);
  170. }
  171. /**
  172. * Create instance with parameters
  173. * Supported parameters are:
  174. * - filename filename of mbox file
  175. *
  176. * @param $params array mail reader specific parameters
  177. * @throws \Zend\Mail\Storage\Exception
  178. */
  179. public function __construct($params)
  180. {
  181. if (is_array($params)) {
  182. $params = (object)$params;
  183. }
  184. if (!isset($params->filename)) {
  185. throw new Exception\InvalidArgumentException('no valid filename given in params');
  186. }
  187. $this->_openMboxFile($params->filename);
  188. $this->_has['top'] = true;
  189. $this->_has['uniqueid'] = false;
  190. }
  191. /**
  192. * check if given file is a mbox file
  193. *
  194. * if $file is a resource its file pointer is moved after the first line
  195. *
  196. * @param resource|string $file stream resource of name of file
  197. * @param bool $fileIsString file is string or resource
  198. * @return bool file is mbox file
  199. */
  200. protected function _isMboxFile($file, $fileIsString = true)
  201. {
  202. if ($fileIsString) {
  203. $file = @fopen($file, 'r');
  204. if (!$file) {
  205. return false;
  206. }
  207. } else {
  208. fseek($file, 0);
  209. }
  210. $result = false;
  211. $line = fgets($file);
  212. if (strpos($line, 'From ') === 0) {
  213. $result = true;
  214. }
  215. if ($fileIsString) {
  216. @fclose($file);
  217. }
  218. return $result;
  219. }
  220. /**
  221. * open given file as current mbox file
  222. *
  223. * @param string $filename filename of mbox file
  224. * @return null
  225. * @throws \Zend\Mail\Storage\Exception
  226. */
  227. protected function _openMboxFile($filename)
  228. {
  229. if ($this->_fh) {
  230. $this->close();
  231. }
  232. $this->_fh = @fopen($filename, 'r');
  233. if (!$this->_fh) {
  234. throw new Exception\RuntimeException('cannot open mbox file');
  235. }
  236. $this->_filename = $filename;
  237. $this->_filemtime = filemtime($this->_filename);
  238. if (!$this->_isMboxFile($this->_fh, false)) {
  239. @fclose($this->_fh);
  240. throw new Exception\InvalidArgumentException('file is not a valid mbox format');
  241. }
  242. $messagePos = array('start' => ftell($this->_fh), 'separator' => 0, 'end' => 0);
  243. while (($line = fgets($this->_fh)) !== false) {
  244. if (strpos($line, 'From ') === 0) {
  245. $messagePos['end'] = ftell($this->_fh) - strlen($line) - 2; // + newline
  246. if (!$messagePos['separator']) {
  247. $messagePos['separator'] = $messagePos['end'];
  248. }
  249. $this->_positions[] = $messagePos;
  250. $messagePos = array('start' => ftell($this->_fh), 'separator' => 0, 'end' => 0);
  251. }
  252. if (!$messagePos['separator'] && !trim($line)) {
  253. $messagePos['separator'] = ftell($this->_fh);
  254. }
  255. }
  256. $messagePos['end'] = ftell($this->_fh);
  257. if (!$messagePos['separator']) {
  258. $messagePos['separator'] = $messagePos['end'];
  259. }
  260. $this->_positions[] = $messagePos;
  261. }
  262. /**
  263. * Close resource for mail lib. If you need to control, when the resource
  264. * is closed. Otherwise the destructor would call this.
  265. *
  266. * @return void
  267. */
  268. public function close()
  269. {
  270. @fclose($this->_fh);
  271. $this->_positions = array();
  272. }
  273. /**
  274. * Waste some CPU cycles doing nothing.
  275. *
  276. * @return void
  277. */
  278. public function noop()
  279. {
  280. return true;
  281. }
  282. /**
  283. * stub for not supported message deletion
  284. *
  285. * @return null
  286. * @throws \Zend\Mail\Storage\Exception
  287. */
  288. public function removeMessage($id)
  289. {
  290. throw new Exception\RuntimeException('mbox is read-only');
  291. }
  292. /**
  293. * get unique id for one or all messages
  294. *
  295. * Mbox does not support unique ids (yet) - it's always the same as the message number.
  296. * That shouldn't be a problem, because we can't change mbox files. Therefor the message
  297. * number is save enough.
  298. *
  299. * @param int|null $id message number
  300. * @return array|string message number for given message or all messages as array
  301. * @throws \Zend\Mail\Storage\Exception
  302. */
  303. public function getUniqueId($id = null)
  304. {
  305. if ($id) {
  306. // check if id exists
  307. $this->_getPos($id);
  308. return $id;
  309. }
  310. $range = range(1, $this->countMessages());
  311. return array_combine($range, $range);
  312. }
  313. /**
  314. * get a message number from a unique id
  315. *
  316. * I.e. if you have a webmailer that supports deleting messages you should use unique ids
  317. * as parameter and use this method to translate it to message number right before calling removeMessage()
  318. *
  319. * @param string $id unique id
  320. * @return int message number
  321. * @throws \Zend\Mail\Storage\Exception
  322. */
  323. public function getNumberByUniqueId($id)
  324. {
  325. // check if id exists
  326. $this->_getPos($id);
  327. return $id;
  328. }
  329. /**
  330. * magic method for serialize()
  331. *
  332. * with this method you can cache the mbox class
  333. *
  334. * @return array name of variables
  335. */
  336. public function __sleep()
  337. {
  338. return array('_filename', '_positions', '_filemtime');
  339. }
  340. /**
  341. * magic method for unserialize()
  342. *
  343. * with this method you can cache the mbox class
  344. * for cache validation the mtime of the mbox file is used
  345. *
  346. * @return null
  347. * @throws \Zend\Mail\Storage\Exception
  348. */
  349. public function __wakeup()
  350. {
  351. if ($this->_filemtime != @filemtime($this->_filename)) {
  352. $this->close();
  353. $this->_openMboxFile($this->_filename);
  354. } else {
  355. $this->_fh = @fopen($this->_filename, 'r');
  356. if (!$this->_fh) {
  357. throw new Exception\RuntimeException('cannot open mbox file');
  358. }
  359. }
  360. }
  361. }