PageRenderTime 45ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/horde/framework/Horde/Mail/Rfc822/List.php

https://gitlab.com/unofficial-mirrors/moodle
PHP | 518 lines | 321 code | 71 blank | 126 comment | 48 complexity | bb2af5e2abb4f4e4d51268f42a88d104 MD5 | raw file
  1. <?php
  2. /**
  3. * Copyright 2012-2017 Horde LLC (http://www.horde.org/)
  4. *
  5. * See the enclosed file COPYING for license information (BSD). If you
  6. * did not receive this file, see http://www.horde.org/licenses/bsd.
  7. *
  8. * @category Horde
  9. * @copyright 2012-2017 Horde LLC
  10. * @license http://www.horde.org/licenses/bsd New BSD License
  11. * @package Mail
  12. */
  13. /**
  14. * Container object for a collection of RFC 822 elements.
  15. *
  16. * @author Michael Slusarz <slusarz@horde.org>
  17. * @category Horde
  18. * @copyright 2012-2017 Horde LLC
  19. * @license http://www.horde.org/licenses/bsd New BSD License
  20. * @package Mail
  21. *
  22. * @property-read array $addresses The list of all addresses (address
  23. * w/personal parts).
  24. * @property-read array $bare_addresses The list of all addresses (mail@host).
  25. * @property-read array $bare_addresses_idn The list of all addresses
  26. * (mail@host; IDN encoded).
  27. * (@since 2.1.0)
  28. * @property-read array $base_addresses The list of ONLY base addresses
  29. * (Address objects).
  30. * @property-read array $raw_addresses The list of all addresses (Address
  31. * objects).
  32. */
  33. class Horde_Mail_Rfc822_List
  34. extends Horde_Mail_Rfc822_Object
  35. implements ArrayAccess, Countable, SeekableIterator, Serializable
  36. {
  37. /** Filter masks. */
  38. const HIDE_GROUPS = 1;
  39. const BASE_ELEMENTS = 2;
  40. /**
  41. * List data.
  42. *
  43. * @var array
  44. */
  45. protected $_data = array();
  46. /**
  47. * Current Iterator filter.
  48. *
  49. * @var array
  50. */
  51. protected $_filter = array();
  52. /**
  53. * Current Iterator pointer.
  54. *
  55. * @var array
  56. */
  57. protected $_ptr;
  58. /**
  59. * Constructor.
  60. *
  61. * @param mixed $obs Address data to store in this object.
  62. */
  63. public function __construct($obs = null)
  64. {
  65. if (!is_null($obs)) {
  66. $this->add($obs);
  67. }
  68. }
  69. /**
  70. */
  71. public function __get($name)
  72. {
  73. switch ($name) {
  74. case 'addresses':
  75. case 'bare_addresses':
  76. case 'bare_addresses_idn':
  77. case 'base_addresses':
  78. case 'raw_addresses':
  79. $old = $this->_filter;
  80. $mask = ($name == 'base_addresses')
  81. ? self::BASE_ELEMENTS
  82. : self::HIDE_GROUPS;
  83. $this->setIteratorFilter($mask, empty($old['filter']) ? null : $old['filter']);
  84. $out = array();
  85. foreach ($this as $val) {
  86. switch ($name) {
  87. case 'addresses':
  88. $out[] = strval($val);
  89. break;
  90. case 'bare_addresses':
  91. $out[] = $val->bare_address;
  92. break;
  93. case 'bare_addresses_idn':
  94. $out[] = $val->bare_address_idn;
  95. break;
  96. case 'base_addresses':
  97. case 'raw_addresses':
  98. $out[] = clone $val;
  99. break;
  100. }
  101. }
  102. $this->_filter = $old;
  103. return $out;
  104. }
  105. }
  106. /**
  107. * Add objects to the container.
  108. *
  109. * @param mixed $obs Address data to store in this object.
  110. */
  111. public function add($obs)
  112. {
  113. foreach ($this->_normalize($obs) as $val) {
  114. $this->_data[] = $val;
  115. }
  116. }
  117. /**
  118. * Remove addresses from the container. This method ignores Group objects.
  119. *
  120. * @param mixed $obs Addresses to remove.
  121. */
  122. public function remove($obs)
  123. {
  124. $old = $this->_filter;
  125. $this->setIteratorFilter(self::HIDE_GROUPS | self::BASE_ELEMENTS);
  126. foreach ($this->_normalize($obs) as $val) {
  127. $remove = array();
  128. foreach ($this as $key => $val2) {
  129. if ($val2->match($val)) {
  130. $remove[] = $key;
  131. }
  132. }
  133. foreach (array_reverse($remove) as $key) {
  134. unset($this[$key]);
  135. }
  136. }
  137. $this->_filter = $old;
  138. }
  139. /**
  140. * Removes duplicate addresses from list. This method ignores Group
  141. * objects.
  142. */
  143. public function unique()
  144. {
  145. $exist = $remove = array();
  146. $old = $this->_filter;
  147. $this->setIteratorFilter(self::HIDE_GROUPS | self::BASE_ELEMENTS);
  148. // For duplicates, we use the first address that contains personal
  149. // information.
  150. foreach ($this as $key => $val) {
  151. $bare = $val->bare_address;
  152. if (isset($exist[$bare])) {
  153. if (($exist[$bare] == -1) || is_null($val->personal)) {
  154. $remove[] = $key;
  155. } else {
  156. $remove[] = $exist[$bare];
  157. $exist[$bare] = -1;
  158. }
  159. } else {
  160. $exist[$bare] = is_null($val->personal)
  161. ? $key
  162. : -1;
  163. }
  164. }
  165. foreach (array_reverse($remove) as $key) {
  166. unset($this[$key]);
  167. }
  168. $this->_filter = $old;
  169. }
  170. /**
  171. * Group count.
  172. *
  173. * @return integer The number of groups in the list.
  174. */
  175. public function groupCount()
  176. {
  177. $ret = 0;
  178. foreach ($this->_data as $val) {
  179. if ($val instanceof Horde_Mail_Rfc822_Group) {
  180. ++$ret;
  181. }
  182. }
  183. return $ret;
  184. }
  185. /**
  186. * Set the Iterator filter.
  187. *
  188. * @param integer $mask Filter masks.
  189. * @param mixed $filter An e-mail, or as list of e-mails, to filter by.
  190. */
  191. public function setIteratorFilter($mask = 0, $filter = null)
  192. {
  193. $this->_filter = array();
  194. if ($mask) {
  195. $this->_filter['mask'] = $mask;
  196. }
  197. if (!is_null($filter)) {
  198. $rfc822 = new Horde_Mail_Rfc822();
  199. $this->_filter['filter'] = $rfc822->parseAddressList($filter);
  200. }
  201. }
  202. /**
  203. */
  204. protected function _writeAddress($opts)
  205. {
  206. $out = array();
  207. foreach ($this->_data as $val) {
  208. $out[] = $val->writeAddress($opts);
  209. }
  210. return implode(', ', $out);
  211. }
  212. /**
  213. */
  214. public function match($ob)
  215. {
  216. if (!($ob instanceof Horde_Mail_Rfc822_List)) {
  217. $ob = new Horde_Mail_Rfc822_List($ob);
  218. }
  219. $a = $this->bare_addresses;
  220. sort($a);
  221. $b = $ob->bare_addresses;
  222. sort($b);
  223. return ($a == $b);
  224. }
  225. /**
  226. * Does this list contain the given e-mail address?
  227. *
  228. * @param mixed $address An e-mail address.
  229. *
  230. * @return boolean True if the e-mail address is contained in the list.
  231. */
  232. public function contains($address)
  233. {
  234. $ob = new Horde_Mail_Rfc822_Address($address);
  235. foreach ($this->raw_addresses as $val) {
  236. if ($val->match($ob)) {
  237. return true;
  238. }
  239. }
  240. return false;
  241. }
  242. /**
  243. * Convenience method to return the first element in a list.
  244. *
  245. * Useful since it allows chaining; older PHP versions did not allow array
  246. * access dereferencing from the results of a function call.
  247. *
  248. * @since 2.5.0
  249. *
  250. * @return Horde_Mail_Rfc822_Object Rfc822 object, or null if no object.
  251. */
  252. public function first()
  253. {
  254. return $this[0];
  255. }
  256. /**
  257. * Normalize objects to add to list.
  258. *
  259. * @param mixed $obs Address data to store in this object.
  260. *
  261. * @return array Entries to add.
  262. */
  263. protected function _normalize($obs)
  264. {
  265. $add = array();
  266. if (!($obs instanceof Horde_Mail_Rfc822_List) &&
  267. !is_array($obs)) {
  268. $obs = array($obs);
  269. }
  270. foreach ($obs as $val) {
  271. if (is_string($val)) {
  272. $rfc822 = new Horde_Mail_Rfc822();
  273. $val = $rfc822->parseAddressList($val);
  274. }
  275. if ($val instanceof Horde_Mail_Rfc822_List) {
  276. $val->setIteratorFilter(self::BASE_ELEMENTS);
  277. foreach ($val as $val2) {
  278. $add[] = $val2;
  279. }
  280. } elseif ($val instanceof Horde_Mail_Rfc822_Object) {
  281. $add[] = $val;
  282. }
  283. }
  284. return $add;
  285. }
  286. /* ArrayAccess methods. */
  287. /**
  288. */
  289. public function offsetExists($offset)
  290. {
  291. return !is_null($this[$offset]);
  292. }
  293. /**
  294. */
  295. public function offsetGet($offset)
  296. {
  297. try {
  298. $this->seek($offset);
  299. return $this->current();
  300. } catch (OutOfBoundsException $e) {
  301. return null;
  302. }
  303. }
  304. /**
  305. */
  306. public function offsetSet($offset, $value)
  307. {
  308. if ($ob = $this[$offset]) {
  309. if (is_null($this->_ptr['subidx'])) {
  310. $tmp = $this->_normalize($value);
  311. if (isset($tmp[0])) {
  312. $this->_data[$this->_ptr['idx']] = $tmp[0];
  313. }
  314. } else {
  315. $ob[$offset] = $value;
  316. }
  317. $this->_ptr = null;
  318. }
  319. }
  320. /**
  321. */
  322. public function offsetUnset($offset)
  323. {
  324. if ($ob = $this[$offset]) {
  325. if (is_null($this->_ptr['subidx'])) {
  326. unset($this->_data[$this->_ptr['idx']]);
  327. $this->_data = array_values($this->_data);
  328. } else {
  329. unset($ob->addresses[$this->_ptr['subidx']]);
  330. }
  331. $this->_ptr = null;
  332. }
  333. }
  334. /* Countable methods. */
  335. /**
  336. * Address count.
  337. *
  338. * @return integer The number of addresses.
  339. */
  340. public function count()
  341. {
  342. return count($this->addresses);
  343. }
  344. /* Iterator methods. */
  345. public function current()
  346. {
  347. if (!$this->valid()) {
  348. return null;
  349. }
  350. $ob = $this->_data[$this->_ptr['idx']];
  351. return is_null($this->_ptr['subidx'])
  352. ? $ob
  353. : $ob->addresses[$this->_ptr['subidx']];
  354. }
  355. public function key()
  356. {
  357. return $this->_ptr['key'];
  358. }
  359. public function next()
  360. {
  361. if (is_null($this->_ptr['subidx'])) {
  362. $curr = $this->current();
  363. if (($curr instanceof Horde_Mail_Rfc822_Group) && count($curr)) {
  364. $this->_ptr['subidx'] = 0;
  365. } else {
  366. ++$this->_ptr['idx'];
  367. }
  368. $curr = $this->current();
  369. } elseif (!($curr = $this->_data[$this->_ptr['idx']]->addresses[++$this->_ptr['subidx']])) {
  370. $this->_ptr['subidx'] = null;
  371. ++$this->_ptr['idx'];
  372. $curr = $this->current();
  373. }
  374. if (!is_null($curr)) {
  375. if (!empty($this->_filter) && $this->_iteratorFilter($curr)) {
  376. $this->next();
  377. } else {
  378. ++$this->_ptr['key'];
  379. }
  380. }
  381. }
  382. public function rewind()
  383. {
  384. $this->_ptr = array(
  385. 'idx' => 0,
  386. 'key' => 0,
  387. 'subidx' => null
  388. );
  389. if ($this->valid() &&
  390. !empty($this->_filter) &&
  391. $this->_iteratorFilter($this->current())) {
  392. $this->next();
  393. $this->_ptr['key'] = 0;
  394. }
  395. }
  396. public function valid()
  397. {
  398. return (!empty($this->_ptr) && isset($this->_data[$this->_ptr['idx']]));
  399. }
  400. public function seek($position)
  401. {
  402. if (!$this->valid() ||
  403. ($position < $this->_ptr['key'])) {
  404. $this->rewind();
  405. }
  406. for ($i = $this->_ptr['key']; ; ++$i) {
  407. if ($i == $position) {
  408. return;
  409. }
  410. $this->next();
  411. if (!$this->valid()) {
  412. throw new OutOfBoundsException('Position not found.');
  413. }
  414. }
  415. }
  416. protected function _iteratorFilter($ob)
  417. {
  418. if (!empty($this->_filter['mask'])) {
  419. if (($this->_filter['mask'] & self::HIDE_GROUPS) &&
  420. ($ob instanceof Horde_Mail_Rfc822_Group)) {
  421. return true;
  422. }
  423. if (($this->_filter['mask'] & self::BASE_ELEMENTS) &&
  424. !is_null($this->_ptr['subidx'])) {
  425. return true;
  426. }
  427. }
  428. if (!empty($this->_filter['filter']) &&
  429. ($ob instanceof Horde_Mail_Rfc822_Address)) {
  430. foreach ($this->_filter['filter'] as $val) {
  431. if ($ob->match($val)) {
  432. return true;
  433. }
  434. }
  435. }
  436. return false;
  437. }
  438. /* Serializable methods. */
  439. public function serialize()
  440. {
  441. return serialize($this->_data);
  442. }
  443. public function unserialize($data)
  444. {
  445. $this->_data = unserialize($data);
  446. }
  447. }