Part.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  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. * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com)
  18. * @license http://framework.zend.com/license/new-bsd New BSD License
  19. * @version $Id$
  20. */
  21. /**
  22. * @see Zend_Mime_Decode
  23. */
  24. require_once 'Zend/Mime/Decode.php';
  25. /**
  26. * @see Zend_Mail_Part_Interface
  27. */
  28. require_once 'Zend/Mail/Part/Interface.php';
  29. /**
  30. * @category Zend
  31. * @package Zend_Mail
  32. * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com)
  33. * @license http://framework.zend.com/license/new-bsd New BSD License
  34. */
  35. class Zend_Mail_Part implements RecursiveIterator, Zend_Mail_Part_Interface
  36. {
  37. /**
  38. * headers of part as array
  39. * @var null|array
  40. */
  41. protected $_headers;
  42. /**
  43. * raw part body
  44. * @var null|string
  45. */
  46. protected $_content;
  47. /**
  48. * toplines as fetched with headers
  49. * @var string
  50. */
  51. protected $_topLines = '';
  52. /**
  53. * parts of multipart message
  54. * @var array
  55. */
  56. protected $_parts = array();
  57. /**
  58. * count of parts of a multipart message
  59. * @var null|int
  60. */
  61. protected $_countParts;
  62. /**
  63. * current position of iterator
  64. * @var int
  65. */
  66. protected $_iterationPos = 1;
  67. /**
  68. * mail handler, if late fetch is active
  69. * @var null|Zend_Mail_Storage_Abstract
  70. */
  71. protected $_mail;
  72. /**
  73. * message number for mail handler
  74. * @var int
  75. */
  76. protected $_messageNum = 0;
  77. /**
  78. * Class to use when creating message parts
  79. * @var string
  80. */
  81. protected $_partClass;
  82. /**
  83. * Public constructor
  84. *
  85. * Zend_Mail_Part supports different sources for content. The possible params are:
  86. * - handler a instance of Zend_Mail_Storage_Abstract for late fetch
  87. * - id number of message for handler
  88. * - raw raw content with header and body as string
  89. * - headers headers as array (name => value) or string, if a content part is found it's used as toplines
  90. * - noToplines ignore content found after headers in param 'headers'
  91. * - content content as string
  92. *
  93. * @param array $params full message with or without headers
  94. * @throws Zend_Mail_Exception
  95. */
  96. public function __construct(array $params)
  97. {
  98. if (isset($params['handler'])) {
  99. if (!$params['handler'] instanceof Zend_Mail_Storage_Abstract) {
  100. /**
  101. * @see Zend_Mail_Exception
  102. */
  103. require_once 'Zend/Mail/Exception.php';
  104. throw new Zend_Mail_Exception('handler is not a valid mail handler');
  105. }
  106. if (!isset($params['id'])) {
  107. /**
  108. * @see Zend_Mail_Exception
  109. */
  110. require_once 'Zend/Mail/Exception.php';
  111. throw new Zend_Mail_Exception('need a message id with a handler');
  112. }
  113. $this->_mail = $params['handler'];
  114. $this->_messageNum = $params['id'];
  115. }
  116. if (isset($params['partclass'])) {
  117. $this->setPartClass($params['partclass']);
  118. }
  119. if (isset($params['raw'])) {
  120. Zend_Mime_Decode::splitMessage($params['raw'], $this->_headers, $this->_content);
  121. } else if (isset($params['headers'])) {
  122. if (is_array($params['headers'])) {
  123. $this->_headers = $params['headers'];
  124. } else {
  125. if (!empty($params['noToplines'])) {
  126. Zend_Mime_Decode::splitMessage($params['headers'], $this->_headers, $null);
  127. } else {
  128. Zend_Mime_Decode::splitMessage($params['headers'], $this->_headers, $this->_topLines);
  129. }
  130. }
  131. if (isset($params['content'])) {
  132. $this->_content = $params['content'];
  133. }
  134. }
  135. }
  136. /**
  137. * Set name pf class used to encapsulate message parts
  138. * @param string $class
  139. * @return Zend_Mail_Part
  140. */
  141. public function setPartClass($class)
  142. {
  143. if ( !class_exists($class) ) {
  144. /**
  145. * @see Zend_Mail_Exception
  146. */
  147. require_once 'Zend/Mail/Exception.php';
  148. throw new Zend_Mail_Exception("Class '{$class}' does not exist");
  149. }
  150. if ( !is_subclass_of($class, 'Zend_Mail_Part_Interface') ) {
  151. /**
  152. * @see Zend_Mail_Exception
  153. */
  154. require_once 'Zend/Mail/Exception.php';
  155. throw new Zend_Mail_Exception("Class '{$class}' must implement Zend_Mail_Part_Interface");
  156. }
  157. $this->_partClass = $class;
  158. return $this;
  159. }
  160. /**
  161. * Retrieve the class name used to encapsulate message parts
  162. * @return string
  163. */
  164. public function getPartClass()
  165. {
  166. if ( !$this->_partClass ) {
  167. $this->_partClass = __CLASS__;
  168. }
  169. return $this->_partClass;
  170. }
  171. /**
  172. * Check if part is a multipart message
  173. *
  174. * @return bool if part is multipart
  175. */
  176. public function isMultipart()
  177. {
  178. try {
  179. return stripos($this->contentType, 'multipart/') === 0;
  180. } catch(Zend_Mail_Exception $e) {
  181. return false;
  182. }
  183. }
  184. /**
  185. * Body of part
  186. *
  187. * If part is multipart the raw content of this part with all sub parts is returned
  188. *
  189. * @return string body
  190. * @throws Zend_Mail_Exception
  191. */
  192. public function getContent()
  193. {
  194. if ($this->_content !== null) {
  195. return $this->_content;
  196. }
  197. if ($this->_mail) {
  198. return $this->_mail->getRawContent($this->_messageNum);
  199. } else {
  200. /**
  201. * @see Zend_Mail_Exception
  202. */
  203. require_once 'Zend/Mail/Exception.php';
  204. throw new Zend_Mail_Exception('no content');
  205. }
  206. }
  207. /**
  208. * Return size of part
  209. *
  210. * Quite simple implemented currently (not decoding). Handle with care.
  211. *
  212. * @return int size
  213. */
  214. public function getSize() {
  215. return strlen($this->getContent());
  216. }
  217. /**
  218. * Cache content and split in parts if multipart
  219. *
  220. * @return null
  221. * @throws Zend_Mail_Exception
  222. */
  223. protected function _cacheContent()
  224. {
  225. // caching content if we can't fetch parts
  226. if ($this->_content === null && $this->_mail) {
  227. $this->_content = $this->_mail->getRawContent($this->_messageNum);
  228. }
  229. if (!$this->isMultipart()) {
  230. return;
  231. }
  232. // split content in parts
  233. $boundary = $this->getHeaderField('content-type', 'boundary');
  234. if (!$boundary) {
  235. /**
  236. * @see Zend_Mail_Exception
  237. */
  238. require_once 'Zend/Mail/Exception.php';
  239. throw new Zend_Mail_Exception('no boundary found in content type to split message');
  240. }
  241. $parts = Zend_Mime_Decode::splitMessageStruct($this->_content, $boundary);
  242. if ($parts === null) {
  243. return;
  244. }
  245. $partClass = $this->getPartClass();
  246. $counter = 1;
  247. foreach ($parts as $part) {
  248. $this->_parts[$counter++] = new $partClass(array('headers' => $part['header'], 'content' => $part['body']));
  249. }
  250. }
  251. /**
  252. * Get part of multipart message
  253. *
  254. * @param int $num number of part starting with 1 for first part
  255. * @return Zend_Mail_Part wanted part
  256. * @throws Zend_Mail_Exception
  257. */
  258. public function getPart($num)
  259. {
  260. if (isset($this->_parts[$num])) {
  261. return $this->_parts[$num];
  262. }
  263. if (!$this->_mail && $this->_content === null) {
  264. /**
  265. * @see Zend_Mail_Exception
  266. */
  267. require_once 'Zend/Mail/Exception.php';
  268. throw new Zend_Mail_Exception('part not found');
  269. }
  270. if ($this->_mail && $this->_mail->hasFetchPart) {
  271. // TODO: fetch part
  272. // return
  273. }
  274. $this->_cacheContent();
  275. if (!isset($this->_parts[$num])) {
  276. /**
  277. * @see Zend_Mail_Exception
  278. */
  279. require_once 'Zend/Mail/Exception.php';
  280. throw new Zend_Mail_Exception('part not found');
  281. }
  282. return $this->_parts[$num];
  283. }
  284. /**
  285. * Count parts of a multipart part
  286. *
  287. * @return int number of sub-parts
  288. */
  289. public function countParts()
  290. {
  291. if ($this->_countParts) {
  292. return $this->_countParts;
  293. }
  294. $this->_countParts = count($this->_parts);
  295. if ($this->_countParts) {
  296. return $this->_countParts;
  297. }
  298. if ($this->_mail && $this->_mail->hasFetchPart) {
  299. // TODO: fetch part
  300. // return
  301. }
  302. $this->_cacheContent();
  303. $this->_countParts = count($this->_parts);
  304. return $this->_countParts;
  305. }
  306. /**
  307. * Get all headers
  308. *
  309. * The returned headers are as saved internally. All names are lowercased. The value is a string or an array
  310. * if a header with the same name occurs more than once.
  311. *
  312. * @return array headers as array(name => value)
  313. */
  314. public function getHeaders()
  315. {
  316. if ($this->_headers === null) {
  317. if (!$this->_mail) {
  318. $this->_headers = array();
  319. } else {
  320. $part = $this->_mail->getRawHeader($this->_messageNum);
  321. Zend_Mime_Decode::splitMessage($part, $this->_headers, $null);
  322. }
  323. }
  324. return $this->_headers;
  325. }
  326. /**
  327. * Get a header in specificed format
  328. *
  329. * Internally headers that occur more than once are saved as array, all other as string. If $format
  330. * is set to string implode is used to concat the values (with Zend_Mime::LINEEND as delim).
  331. *
  332. * @param string $name name of header, matches case-insensitive, but camel-case is replaced with dashes
  333. * @param string $format change type of return value to 'string' or 'array'
  334. * @return string|array value of header in wanted or internal format
  335. * @throws Zend_Mail_Exception
  336. */
  337. public function getHeader($name, $format = null)
  338. {
  339. if ($this->_headers === null) {
  340. $this->getHeaders();
  341. }
  342. $lowerName = strtolower($name);
  343. if ($this->headerExists($name) == false) {
  344. $lowerName = strtolower(preg_replace('%([a-z])([A-Z])%', '\1-\2', $name));
  345. if($this->headerExists($lowerName) == false) {
  346. /**
  347. * @see Zend_Mail_Exception
  348. */
  349. require_once 'Zend/Mail/Exception.php';
  350. throw new Zend_Mail_Exception("no Header with Name $name or $lowerName found");
  351. }
  352. }
  353. $name = $lowerName;
  354. $header = $this->_headers[$name];
  355. switch ($format) {
  356. case 'string':
  357. if (is_array($header)) {
  358. $header = implode(Zend_Mime::LINEEND, $header);
  359. }
  360. break;
  361. case 'array':
  362. $header = (array)$header;
  363. default:
  364. // do nothing
  365. }
  366. return $header;
  367. }
  368. /**
  369. * Check wheater the Mail part has a specific header.
  370. *
  371. * @param string $name
  372. * @return boolean
  373. */
  374. public function headerExists($name)
  375. {
  376. $name = strtolower($name);
  377. if(isset($this->_headers[$name])) {
  378. return true;
  379. } else {
  380. return false;
  381. }
  382. }
  383. /**
  384. * Get a specific field from a header like content type or all fields as array
  385. *
  386. * If the header occurs more than once, only the value from the first header
  387. * is returned.
  388. *
  389. * Throws a Zend_Mail_Exception if the requested header does not exist. If
  390. * the specific header field does not exist, returns null.
  391. *
  392. * @param string $name name of header, like in getHeader()
  393. * @param string $wantedPart the wanted part, default is first, if null an array with all parts is returned
  394. * @param string $firstName key name for the first part
  395. * @return string|array wanted part or all parts as array($firstName => firstPart, partname => value)
  396. * @throws Zend_Exception, Zend_Mail_Exception
  397. */
  398. public function getHeaderField($name, $wantedPart = 0, $firstName = 0) {
  399. return Zend_Mime_Decode::splitHeaderField(current($this->getHeader($name, 'array')), $wantedPart, $firstName);
  400. }
  401. /**
  402. * Getter for mail headers - name is matched in lowercase
  403. *
  404. * This getter is short for Zend_Mail_Part::getHeader($name, 'string')
  405. *
  406. * @see Zend_Mail_Part::getHeader()
  407. *
  408. * @param string $name header name
  409. * @return string value of header
  410. * @throws Zend_Mail_Exception
  411. */
  412. public function __get($name)
  413. {
  414. return $this->getHeader($name, 'string');
  415. }
  416. /**
  417. * Isset magic method proxy to hasHeader
  418. *
  419. * This method is short syntax for Zend_Mail_Part::hasHeader($name);
  420. *
  421. * @see Zend_Mail_Part::hasHeader
  422. *
  423. * @param string
  424. * @return boolean
  425. */
  426. public function __isset($name)
  427. {
  428. return $this->headerExists($name);
  429. }
  430. /**
  431. * magic method to get content of part
  432. *
  433. * @return string content
  434. */
  435. public function __toString()
  436. {
  437. return $this->getContent();
  438. }
  439. /**
  440. * implements RecursiveIterator::hasChildren()
  441. *
  442. * @return bool current element has children/is multipart
  443. */
  444. public function hasChildren()
  445. {
  446. $current = $this->current();
  447. return $current && $current instanceof Zend_Mail_Part && $current->isMultipart();
  448. }
  449. /**
  450. * implements RecursiveIterator::getChildren()
  451. *
  452. * @return Zend_Mail_Part same as self::current()
  453. */
  454. public function getChildren()
  455. {
  456. return $this->current();
  457. }
  458. /**
  459. * implements Iterator::valid()
  460. *
  461. * @return bool check if there's a current element
  462. */
  463. public function valid()
  464. {
  465. if ($this->_countParts === null) {
  466. $this->countParts();
  467. }
  468. return $this->_iterationPos && $this->_iterationPos <= $this->_countParts;
  469. }
  470. /**
  471. * implements Iterator::next()
  472. *
  473. * @return null
  474. */
  475. public function next()
  476. {
  477. ++$this->_iterationPos;
  478. }
  479. /**
  480. * implements Iterator::key()
  481. *
  482. * @return string key/number of current part
  483. */
  484. public function key()
  485. {
  486. return $this->_iterationPos;
  487. }
  488. /**
  489. * implements Iterator::current()
  490. *
  491. * @return Zend_Mail_Part current part
  492. */
  493. public function current()
  494. {
  495. return $this->getPart($this->_iterationPos);
  496. }
  497. /**
  498. * implements Iterator::rewind()
  499. *
  500. * @return null
  501. */
  502. public function rewind()
  503. {
  504. $this->countParts();
  505. $this->_iterationPos = 1;
  506. }
  507. }