Message.php 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419
  1. <?php
  2. /*
  3. * File: Message.php
  4. * Category: -
  5. * Author: M. Goldenbaum
  6. * Created: 19.01.17 22:21
  7. * Updated: -
  8. *
  9. * Description:
  10. * -
  11. */
  12. namespace Webklex\PHPIMAP;
  13. use ReflectionClass;
  14. use ReflectionException;
  15. use Webklex\PHPIMAP\Exceptions\InvalidMessageDateException;
  16. use Webklex\PHPIMAP\Exceptions\MaskNotFoundException;
  17. use Webklex\PHPIMAP\Exceptions\MessageContentFetchingException;
  18. use Webklex\PHPIMAP\Exceptions\MessageFlagException;
  19. use Webklex\PHPIMAP\Exceptions\MessageHeaderFetchingException;
  20. use Webklex\PHPIMAP\Exceptions\MethodNotFoundException;
  21. use Webklex\PHPIMAP\Support\AttachmentCollection;
  22. use Webklex\PHPIMAP\Support\FlagCollection;
  23. use Webklex\PHPIMAP\Support\Masks\MessageMask;
  24. use Illuminate\Support\Str;
  25. use Webklex\PHPIMAP\Support\MessageCollection;
  26. use Webklex\PHPIMAP\Traits\HasEvents;
  27. /**
  28. * Class Message
  29. *
  30. * @package Webklex\PHPIMAP
  31. *
  32. * @property integer msglist
  33. * @property integer uid
  34. * @property integer msgn
  35. * @property Attribute subject
  36. * @property Attribute message_id
  37. * @property Attribute message_no
  38. * @property Attribute references
  39. * @property Attribute date
  40. * @property Attribute from
  41. * @property Attribute to
  42. * @property Attribute cc
  43. * @property Attribute bcc
  44. * @property Attribute reply_to
  45. * @property Attribute in_reply_to
  46. * @property Attribute sender
  47. *
  48. * @method integer getMsglist()
  49. * @method integer setMsglist($msglist)
  50. * @method integer getUid()
  51. * @method integer getMsgn()
  52. * @method Attribute getPriority()
  53. * @method Attribute getSubject()
  54. * @method Attribute getMessageId()
  55. * @method Attribute getMessageNo()
  56. * @method Attribute getReferences()
  57. * @method Attribute getDate()
  58. * @method Attribute getFrom()
  59. * @method Attribute getTo()
  60. * @method Attribute getCc()
  61. * @method Attribute getBcc()
  62. * @method Attribute getReplyTo()
  63. * @method Attribute getInReplyTo()
  64. * @method Attribute getSender()
  65. */
  66. class Message {
  67. use HasEvents;
  68. /**
  69. * Client instance
  70. *
  71. * @var Client
  72. */
  73. private $client = Client::class;
  74. /**
  75. * Default mask
  76. *
  77. * @var string $mask
  78. */
  79. protected $mask = MessageMask::class;
  80. /**
  81. * Used config
  82. *
  83. * @var array $config
  84. */
  85. protected $config = [];
  86. /**
  87. * Attribute holder
  88. *
  89. * @var Attribute[]|mixed[] $attributes
  90. */
  91. protected $attributes = [];
  92. /**
  93. * The message folder path
  94. *
  95. * @var string $folder_path
  96. */
  97. protected $folder_path;
  98. /**
  99. * Fetch body options
  100. *
  101. * @var integer
  102. */
  103. public $fetch_options = null;
  104. /**
  105. * @var integer
  106. */
  107. protected $sequence = IMAP::NIL;
  108. /**
  109. * Fetch body options
  110. *
  111. * @var bool
  112. */
  113. public $fetch_body = null;
  114. /**
  115. * Fetch flags options
  116. *
  117. * @var bool
  118. */
  119. public $fetch_flags = null;
  120. /**
  121. * @var Header $header
  122. */
  123. public $header = null;
  124. /**
  125. * Raw message body
  126. *
  127. * @var null|string $raw_body
  128. */
  129. public $raw_body = null;
  130. /**
  131. * Message structure
  132. *
  133. * @var Structure $structure
  134. */
  135. protected $structure = null;
  136. /**
  137. * Message body components
  138. *
  139. * @var array $bodies
  140. */
  141. public $bodies = [];
  142. /** @var AttachmentCollection $attachments */
  143. public $attachments;
  144. /** @var FlagCollection $flags */
  145. public $flags;
  146. /**
  147. * A list of all available and supported flags
  148. *
  149. * @var array $available_flags
  150. */
  151. private $available_flags = null;
  152. /**
  153. * Message constructor.
  154. * @param integer $uid
  155. * @param integer|null $msglist
  156. * @param Client $client
  157. * @param integer|null $fetch_options
  158. * @param boolean $fetch_body
  159. * @param boolean $fetch_flags
  160. * @param integer $sequence
  161. *
  162. * @throws Exceptions\ConnectionFailedException
  163. * @throws InvalidMessageDateException
  164. * @throws Exceptions\RuntimeException
  165. * @throws MessageHeaderFetchingException
  166. * @throws MessageContentFetchingException
  167. * @throws Exceptions\EventNotFoundException
  168. * @throws MessageFlagException
  169. * @throws Exceptions\MessageNotFoundException
  170. */
  171. public function __construct($uid, $msglist, Client $client, $fetch_options = null, $fetch_body = false, $fetch_flags = false, $sequence = null) {
  172. $this->boot();
  173. $default_mask = $client->getDefaultMessageMask();
  174. if($default_mask != null) {
  175. $this->mask = $default_mask;
  176. }
  177. $this->events["message"] = $client->getDefaultEvents("message");
  178. $this->events["flag"] = $client->getDefaultEvents("flag");
  179. $this->folder_path = $client->getFolderPath();
  180. $this->setSequence($sequence);
  181. $this->setFetchOption($fetch_options);
  182. $this->setFetchBodyOption($fetch_body);
  183. $this->setFetchFlagsOption($fetch_flags);
  184. $this->client = $client;
  185. $this->client->openFolder($this->folder_path);
  186. $this->setSequenceId($uid, $msglist);
  187. if ($this->fetch_options == IMAP::FT_PEEK) {
  188. $this->parseFlags();
  189. }
  190. $this->parseHeader();
  191. if ($this->getFetchBodyOption() === true) {
  192. $this->parseBody();
  193. }
  194. if ($this->getFetchFlagsOption() === true && $this->fetch_options !== IMAP::FT_PEEK) {
  195. $this->parseFlags();
  196. }
  197. }
  198. /**
  199. * Create a new instance without fetching the message header and providing them raw instead
  200. * @param int $uid
  201. * @param int|null $msglist
  202. * @param Client $client
  203. * @param string $raw_header
  204. * @param string $raw_body
  205. * @param array $raw_flags
  206. * @param null $fetch_options
  207. * @param null $sequence
  208. *
  209. * @return Message
  210. * @throws Exceptions\ConnectionFailedException
  211. * @throws Exceptions\EventNotFoundException
  212. * @throws InvalidMessageDateException
  213. * @throws MessageContentFetchingException
  214. * @throws ReflectionException
  215. * @throws MessageFlagException
  216. * @throws Exceptions\RuntimeException
  217. * @throws Exceptions\MessageNotFoundException
  218. */
  219. public static function make($uid, $msglist, Client $client, $raw_header, $raw_body, $raw_flags, $fetch_options = null, $sequence = null){
  220. $reflection = new ReflectionClass(self::class);
  221. /** @var self $instance */
  222. $instance = $reflection->newInstanceWithoutConstructor();
  223. $instance->boot();
  224. $default_mask = $client->getDefaultMessageMask();
  225. if($default_mask != null) {
  226. $instance->setMask($default_mask);
  227. }
  228. $instance->setEvents([
  229. "message" => $client->getDefaultEvents("message"),
  230. "flag" => $client->getDefaultEvents("flag"),
  231. ]);
  232. $instance->setFolderPath($client->getFolderPath());
  233. $instance->setSequence($sequence);
  234. $instance->setFetchOption($fetch_options);
  235. $instance->setClient($client);
  236. $instance->setSequenceId($uid, $msglist);
  237. $instance->parseRawHeader($raw_header);
  238. $instance->parseRawFlags($raw_flags);
  239. $instance->parseRawBody($raw_body);
  240. $instance->peek();
  241. return $instance;
  242. }
  243. /**
  244. * Boot a new instance
  245. */
  246. public function boot(){
  247. $this->attributes = [];
  248. $this->config = ClientManager::get('options');
  249. $this->available_flags = ClientManager::get('flags');
  250. $this->attachments = AttachmentCollection::make([]);
  251. $this->flags = FlagCollection::make([]);
  252. }
  253. /**
  254. * Call dynamic attribute setter and getter methods
  255. * @param string $method
  256. * @param array $arguments
  257. *
  258. * @return mixed
  259. * @throws MethodNotFoundException
  260. */
  261. public function __call($method, $arguments) {
  262. if(strtolower(substr($method, 0, 3)) === 'get') {
  263. $name = Str::snake(substr($method, 3));
  264. return $this->get($name);
  265. }elseif (strtolower(substr($method, 0, 3)) === 'set') {
  266. $name = Str::snake(substr($method, 3));
  267. if(in_array($name, array_keys($this->attributes))) {
  268. return $this->__set($name, array_pop($arguments));
  269. }
  270. }
  271. throw new MethodNotFoundException("Method ".self::class.'::'.$method.'() is not supported');
  272. }
  273. /**
  274. * Magic setter
  275. * @param $name
  276. * @param $value
  277. *
  278. * @return mixed
  279. */
  280. public function __set($name, $value) {
  281. $this->attributes[$name] = $value;
  282. return $this->attributes[$name];
  283. }
  284. /**
  285. * Magic getter
  286. * @param $name
  287. *
  288. * @return Attribute|mixed|null
  289. */
  290. public function __get($name) {
  291. return $this->get($name);
  292. }
  293. /**
  294. * Get an available message or message header attribute
  295. * @param $name
  296. *
  297. * @return Attribute|mixed|null
  298. */
  299. public function get($name) {
  300. if(isset($this->attributes[$name])) {
  301. return $this->attributes[$name];
  302. }
  303. return $this->header->get($name);
  304. }
  305. /**
  306. * Check if the Message has a text body
  307. *
  308. * @return bool
  309. */
  310. public function hasTextBody() {
  311. return isset($this->bodies['text']);
  312. }
  313. /**
  314. * Get the Message text body
  315. *
  316. * @return mixed
  317. */
  318. public function getTextBody() {
  319. if (!isset($this->bodies['text'])) {
  320. return null;
  321. }
  322. return $this->bodies['text'];
  323. }
  324. /**
  325. * Check if the Message has a html body
  326. *
  327. * @return bool
  328. */
  329. public function hasHTMLBody() {
  330. return isset($this->bodies['html']);
  331. }
  332. /**
  333. * Get the Message html body
  334. *
  335. * @return string|null
  336. */
  337. public function getHTMLBody() {
  338. if (!isset($this->bodies['html'])) {
  339. return null;
  340. }
  341. return $this->bodies['html'];
  342. }
  343. /**
  344. * Parse all defined headers
  345. *
  346. * @throws Exceptions\ConnectionFailedException
  347. * @throws Exceptions\RuntimeException
  348. * @throws InvalidMessageDateException
  349. * @throws MessageHeaderFetchingException
  350. */
  351. private function parseHeader() {
  352. $sequence_id = $this->getSequenceId();
  353. $headers = $this->client->getConnection()->headers([$sequence_id], "RFC822", $this->sequence === IMAP::ST_UID);
  354. if (!isset($headers[$sequence_id])) {
  355. throw new MessageHeaderFetchingException("no headers found", 0);
  356. }
  357. $this->parseRawHeader($headers[$sequence_id]);
  358. }
  359. /**
  360. * @param string $raw_header
  361. *
  362. * @throws InvalidMessageDateException
  363. */
  364. public function parseRawHeader($raw_header){
  365. $this->header = new Header($raw_header);
  366. }
  367. /**
  368. * Parse additional raw flags
  369. * @param array $raw_flags
  370. */
  371. public function parseRawFlags($raw_flags) {
  372. $this->flags = FlagCollection::make([]);
  373. foreach($raw_flags as $flag) {
  374. if (strpos($flag, "\\") === 0){
  375. $flag = substr($flag, 1);
  376. }
  377. $flag_key = strtolower($flag);
  378. if ($this->available_flags === null || in_array($flag_key, $this->available_flags)) {
  379. $this->flags->put($flag_key, $flag);
  380. }
  381. }
  382. }
  383. /**
  384. * Parse additional flags
  385. *
  386. * @return void
  387. * @throws Exceptions\ConnectionFailedException
  388. * @throws MessageFlagException
  389. * @throws Exceptions\RuntimeException
  390. */
  391. private function parseFlags() {
  392. $this->client->openFolder($this->folder_path);
  393. $this->flags = FlagCollection::make([]);
  394. $sequence_id = $this->getSequenceId();
  395. try {
  396. $flags = $this->client->getConnection()->flags([$sequence_id], $this->sequence === IMAP::ST_UID);
  397. } catch (Exceptions\RuntimeException $e) {
  398. throw new MessageFlagException("flag could not be fetched", 0, $e);
  399. }
  400. if (isset($flags[$sequence_id])) {
  401. $this->parseRawFlags($flags[$sequence_id]);
  402. }
  403. }
  404. /**
  405. * Parse the Message body
  406. *
  407. * @return $this
  408. * @throws Exceptions\ConnectionFailedException
  409. * @throws Exceptions\MessageContentFetchingException
  410. * @throws InvalidMessageDateException
  411. * @throws Exceptions\EventNotFoundException
  412. * @throws MessageFlagException
  413. * @throws Exceptions\RuntimeException
  414. */
  415. public function parseBody() {
  416. $this->client->openFolder($this->folder_path);
  417. $sequence_id = $this->getSequenceId();
  418. try {
  419. $contents = $this->client->getConnection()->content([$sequence_id], "RFC822", $this->sequence === IMAP::ST_UID);
  420. } catch (Exceptions\RuntimeException $e) {
  421. throw new MessageContentFetchingException("failed to fetch content", 0);
  422. }
  423. if (!isset($contents[$sequence_id])) {
  424. throw new MessageContentFetchingException("no content found", 0);
  425. }
  426. $content = $contents[$sequence_id];
  427. $body = $this->parseRawBody($content);
  428. $this->peek();
  429. return $body;
  430. }
  431. /**
  432. * Handle auto "Seen" flag handling
  433. *
  434. * @throws Exceptions\ConnectionFailedException
  435. * @throws Exceptions\EventNotFoundException
  436. * @throws MessageFlagException
  437. * @throws Exceptions\RuntimeException
  438. */
  439. public function peek(){
  440. if ($this->fetch_options == IMAP::FT_PEEK) {
  441. if ($this->getFlags()->get("seen") == null) {
  442. $this->unsetFlag("Seen");
  443. }
  444. }elseif ($this->getFlags()->get("seen") != null) {
  445. $this->setFlag("Seen");
  446. }
  447. }
  448. /**
  449. * Parse a given message body
  450. * @param string $raw_body
  451. *
  452. * @return $this
  453. * @throws Exceptions\ConnectionFailedException
  454. * @throws InvalidMessageDateException
  455. * @throws MessageContentFetchingException
  456. * @throws Exceptions\RuntimeException
  457. */
  458. public function parseRawBody($raw_body) {
  459. $this->structure = new Structure($raw_body, $this->header);
  460. $this->fetchStructure($this->structure);
  461. return $this;
  462. }
  463. /**
  464. * Fetch the Message structure
  465. * @param Structure $structure
  466. *
  467. * @throws Exceptions\ConnectionFailedException
  468. * @throws Exceptions\RuntimeException
  469. */
  470. private function fetchStructure($structure) {
  471. $this->client->openFolder($this->folder_path);
  472. foreach ($structure->parts as $part) {
  473. $this->fetchPart($part);
  474. }
  475. }
  476. /**
  477. * Fetch a given part
  478. * @param Part $part
  479. */
  480. private function fetchPart(Part $part) {
  481. if ($part->isAttachment()) {
  482. $this->fetchAttachment($part);
  483. }else{
  484. $encoding = $this->getEncoding($part);
  485. $content = $this->decodeString($part->content, $part->encoding);
  486. // We don't need to do convertEncoding() if charset is ASCII (us-ascii):
  487. // ASCII is a subset of UTF-8, so all ASCII files are already UTF-8 encoded
  488. // https://stackoverflow.com/a/11303410
  489. //
  490. // us-ascii is the same as ASCII:
  491. // ASCII is the traditional name for the encoding system; the Internet Assigned Numbers Authority (IANA)
  492. // prefers the updated name US-ASCII, which clarifies that this system was developed in the US and
  493. // based on the typographical symbols predominantly in use there.
  494. // https://en.wikipedia.org/wiki/ASCII
  495. //
  496. // convertEncoding() function basically means convertToUtf8(), so when we convert ASCII string into UTF-8 it gets broken.
  497. if ($encoding != 'us-ascii') {
  498. $content = $this->convertEncoding($content, $encoding);
  499. }
  500. $subtype = strtolower($part->subtype);
  501. $subtype = $subtype == "plain" || $subtype == "" ? "text" : $subtype;
  502. if (isset($this->bodies[$subtype])) {
  503. $this->bodies[$subtype] .= "\n".$content;
  504. }else{
  505. $this->bodies[$subtype] = $content;
  506. }
  507. }
  508. }
  509. /**
  510. * Fetch the Message attachment
  511. * @param Part $part
  512. */
  513. protected function fetchAttachment($part) {
  514. $oAttachment = new Attachment($this, $part);
  515. if ($oAttachment->getName() !== null && $oAttachment->getSize() > 0) {
  516. if ($oAttachment->getId() !== null) {
  517. $this->attachments->put($oAttachment->getId(), $oAttachment);
  518. } else {
  519. $this->attachments->push($oAttachment);
  520. }
  521. }
  522. }
  523. /**
  524. * Fail proof setter for $fetch_option
  525. * @param $option
  526. *
  527. * @return $this
  528. */
  529. public function setFetchOption($option) {
  530. if (is_long($option) === true) {
  531. $this->fetch_options = $option;
  532. } elseif (is_null($option) === true) {
  533. $config = ClientManager::get('options.fetch', IMAP::FT_UID);
  534. $this->fetch_options = is_long($config) ? $config : 1;
  535. }
  536. return $this;
  537. }
  538. /**
  539. * Set the sequence type
  540. * @param int $sequence
  541. *
  542. * @return $this
  543. */
  544. public function setSequence($sequence) {
  545. if (is_long($sequence)) {
  546. $this->sequence = $sequence;
  547. } elseif (is_null($sequence)) {
  548. $config = ClientManager::get('options.sequence', IMAP::ST_MSGN);
  549. $this->sequence = is_long($config) ? $config : IMAP::ST_MSGN;
  550. }
  551. return $this;
  552. }
  553. /**
  554. * Fail proof setter for $fetch_body
  555. * @param $option
  556. *
  557. * @return $this
  558. */
  559. public function setFetchBodyOption($option) {
  560. if (is_bool($option)) {
  561. $this->fetch_body = $option;
  562. } elseif (is_null($option)) {
  563. $config = ClientManager::get('options.fetch_body', true);
  564. $this->fetch_body = is_bool($config) ? $config : true;
  565. }
  566. return $this;
  567. }
  568. /**
  569. * Fail proof setter for $fetch_flags
  570. * @param $option
  571. *
  572. * @return $this
  573. */
  574. public function setFetchFlagsOption($option) {
  575. if (is_bool($option)) {
  576. $this->fetch_flags = $option;
  577. } elseif (is_null($option)) {
  578. $config = ClientManager::get('options.fetch_flags', true);
  579. $this->fetch_flags = is_bool($config) ? $config : true;
  580. }
  581. return $this;
  582. }
  583. /**
  584. * Decode a given string
  585. * @param $string
  586. * @param $encoding
  587. *
  588. * @return string
  589. */
  590. public function decodeString($string, $encoding) {
  591. switch ($encoding) {
  592. case IMAP::MESSAGE_ENC_BINARY:
  593. if (extension_loaded('imap')) {
  594. return base64_decode(\imap_binary($string));
  595. }
  596. return base64_decode($string);
  597. case IMAP::MESSAGE_ENC_BASE64:
  598. return base64_decode($string);
  599. case IMAP::MESSAGE_ENC_QUOTED_PRINTABLE:
  600. return quoted_printable_decode($string);
  601. case IMAP::MESSAGE_ENC_8BIT:
  602. case IMAP::MESSAGE_ENC_7BIT:
  603. case IMAP::MESSAGE_ENC_OTHER:
  604. default:
  605. return $string;
  606. }
  607. }
  608. /**
  609. * Convert the encoding
  610. * @param $str
  611. * @param string $from
  612. * @param string $to
  613. *
  614. * @return mixed|string
  615. */
  616. public function convertEncoding($str, $from = "ISO-8859-2", $to = "UTF-8") {
  617. $from = EncodingAliases::get($from);
  618. $to = EncodingAliases::get($to);
  619. if ($from === $to) {
  620. return $str;
  621. }
  622. // We don't need to do convertEncoding() if charset is ASCII (us-ascii):
  623. // ASCII is a subset of UTF-8, so all ASCII files are already UTF-8 encoded
  624. // https://stackoverflow.com/a/11303410
  625. //
  626. // us-ascii is the same as ASCII:
  627. // ASCII is the traditional name for the encoding system; the Internet Assigned Numbers Authority (IANA)
  628. // prefers the updated name US-ASCII, which clarifies that this system was developed in the US and
  629. // based on the typographical symbols predominantly in use there.
  630. // https://en.wikipedia.org/wiki/ASCII
  631. //
  632. // convertEncoding() function basically means convertToUtf8(), so when we convert ASCII string into UTF-8 it gets broken.
  633. if (strtolower($from) == 'us-ascii' && $to == 'UTF-8') {
  634. return $str;
  635. }
  636. if (function_exists('iconv') && $from != 'UTF-7' && $to != 'UTF-7') {
  637. return @iconv($from, $to.'//IGNORE', $str);
  638. } else {
  639. if (!$from) {
  640. return mb_convert_encoding($str, $to);
  641. }
  642. return mb_convert_encoding($str, $to, $from);
  643. }
  644. }
  645. /**
  646. * Get the encoding of a given abject
  647. * @param object|string $structure
  648. *
  649. * @return string
  650. */
  651. public function getEncoding($structure) {
  652. if (property_exists($structure, 'parameters')) {
  653. foreach ($structure->parameters as $parameter) {
  654. if (strtolower($parameter->attribute) == "charset") {
  655. return EncodingAliases::get($parameter->value);
  656. }
  657. }
  658. }elseif (property_exists($structure, 'charset')){
  659. return EncodingAliases::get($structure->charset);
  660. }elseif (is_string($structure) === true){
  661. return mb_detect_encoding($structure);
  662. }
  663. return 'UTF-8';
  664. }
  665. /**
  666. * Get the messages folder
  667. *
  668. * @return mixed
  669. * @throws Exceptions\ConnectionFailedException
  670. * @throws Exceptions\FolderFetchingException
  671. * @throws Exceptions\RuntimeException
  672. */
  673. public function getFolder(){
  674. return $this->client->getFolderByPath($this->folder_path);
  675. }
  676. /**
  677. * Create a message thread based on the current message
  678. * @param Folder|null $sent_folder
  679. * @param MessageCollection|null $thread
  680. * @param Folder|null $folder
  681. *
  682. * @return MessageCollection|null
  683. * @throws Exceptions\ConnectionFailedException
  684. * @throws Exceptions\FolderFetchingException
  685. * @throws Exceptions\GetMessagesFailedException
  686. * @throws Exceptions\RuntimeException
  687. */
  688. public function thread($sent_folder = null, &$thread = null, $folder = null){
  689. $thread = $thread ? $thread : MessageCollection::make([]);
  690. $folder = $folder ? $folder : $this->getFolder();
  691. $sent_folder = $sent_folder ? $sent_folder : $this->client->getFolderByPath(ClientManager::get("options.common_folders.sent", "INBOX/Sent"));
  692. /** @var Message $message */
  693. foreach($thread as $message) {
  694. if ($message->message_id->first() == $this->message_id->first()) {
  695. return $thread;
  696. }
  697. }
  698. $thread->push($this);
  699. $this->fetchThreadByInReplyTo($thread, $this->message_id, $folder, $folder, $sent_folder);
  700. $this->fetchThreadByInReplyTo($thread, $this->message_id, $sent_folder, $folder, $sent_folder);
  701. if (is_array($this->in_reply_to)) {
  702. foreach($this->in_reply_to as $in_reply_to) {
  703. $this->fetchThreadByMessageId($thread, $in_reply_to, $folder, $folder, $sent_folder);
  704. $this->fetchThreadByMessageId($thread, $in_reply_to, $sent_folder, $folder, $sent_folder);
  705. }
  706. }
  707. return $thread;
  708. }
  709. /**
  710. * Fetch a partial thread by message id
  711. * @param MessageCollection $thread
  712. * @param string $in_reply_to
  713. * @param Folder $primary_folder
  714. * @param Folder $secondary_folder
  715. * @param Folder $sent_folder
  716. *
  717. * @throws Exceptions\ConnectionFailedException
  718. * @throws Exceptions\GetMessagesFailedException
  719. * @throws Exceptions\RuntimeException
  720. */
  721. protected function fetchThreadByInReplyTo(&$thread, $in_reply_to, $primary_folder, $secondary_folder, $sent_folder){
  722. $primary_folder->query()->inReplyTo($in_reply_to)
  723. ->setFetchBody($this->getFetchBodyOption())
  724. ->leaveUnread()->get()->each(function($message) use(&$thread, $secondary_folder, $sent_folder){
  725. /** @var Message $message */
  726. $message->thread($sent_folder, $thread, $secondary_folder);
  727. });
  728. }
  729. /**
  730. * Fetch a partial thread by message id
  731. * @param MessageCollection $thread
  732. * @param string $message_id
  733. * @param Folder $primary_folder
  734. * @param Folder $secondary_folder
  735. * @param Folder $sent_folder
  736. *
  737. * @throws Exceptions\ConnectionFailedException
  738. * @throws Exceptions\GetMessagesFailedException
  739. * @throws Exceptions\RuntimeException
  740. */
  741. protected function fetchThreadByMessageId(&$thread, $message_id, $primary_folder, $secondary_folder, $sent_folder){
  742. $primary_folder->query()->messageId($message_id)
  743. ->setFetchBody($this->getFetchBodyOption())
  744. ->leaveUnread()->get()->each(function($message) use(&$thread, $secondary_folder, $sent_folder){
  745. /** @var Message $message */
  746. $message->thread($sent_folder, $thread, $secondary_folder);
  747. });
  748. }
  749. /**
  750. * Copy the current Messages to a mailbox
  751. * @param string $folder_path
  752. * @param boolean $expunge
  753. *
  754. * @return null|Message
  755. * @throws Exceptions\ConnectionFailedException
  756. * @throws Exceptions\FolderFetchingException
  757. * @throws Exceptions\RuntimeException
  758. * @throws InvalidMessageDateException
  759. * @throws MessageContentFetchingException
  760. * @throws MessageHeaderFetchingException
  761. * @throws Exceptions\EventNotFoundException
  762. * @throws MessageFlagException
  763. * @throws Exceptions\MessageNotFoundException
  764. */
  765. public function copy($folder_path, $expunge = false) {
  766. $this->client->openFolder($folder_path);
  767. $status = $this->client->getConnection()->examineFolder($folder_path);
  768. if (isset($status["uidnext"])) {
  769. $next_uid = $status["uidnext"];
  770. /** @var Folder $folder */
  771. $folder = $this->client->getFolderByPath($folder_path);
  772. $this->client->openFolder($this->folder_path);
  773. if ($this->client->getConnection()->copyMessage($folder->path, $this->getSequenceId(), null, $this->sequence === IMAP::ST_UID) == true) {
  774. return $this->fetchNewMail($folder, $next_uid, "copied", $expunge);
  775. }
  776. }
  777. return null;
  778. }
  779. /**
  780. * Move the current Messages to a mailbox
  781. * @param string $folder_path
  782. * @param boolean $expunge
  783. *
  784. * @return Message|null
  785. * @throws Exceptions\ConnectionFailedException
  786. * @throws Exceptions\FolderFetchingException
  787. * @throws Exceptions\RuntimeException
  788. * @throws InvalidMessageDateException
  789. * @throws MessageContentFetchingException
  790. * @throws MessageHeaderFetchingException
  791. * @throws Exceptions\EventNotFoundException
  792. * @throws MessageFlagException
  793. * @throws Exceptions\MessageNotFoundException
  794. */
  795. public function move($folder_path, $expunge = false) {
  796. $this->client->openFolder($folder_path);
  797. $status = $this->client->getConnection()->examineFolder($folder_path);
  798. if (isset($status["uidnext"])) {
  799. $next_uid = $status["uidnext"];
  800. /** @var Folder $folder */
  801. $folder = $this->client->getFolderByPath($folder_path);
  802. $this->client->openFolder($this->folder_path);
  803. if ($this->client->getConnection()->moveMessage($folder->path, $this->getSequenceId(), null, $this->sequence === IMAP::ST_UID) == true) {
  804. return $this->fetchNewMail($folder, $next_uid, "moved", $expunge);
  805. }
  806. }
  807. return null;
  808. }
  809. /**
  810. * Fetch a new message and fire a given event
  811. * @param Folder $folder
  812. * @param int $next_uid
  813. * @param string $event
  814. * @param boolean $expunge
  815. *
  816. * @return mixed
  817. * @throws Exceptions\ConnectionFailedException
  818. * @throws Exceptions\EventNotFoundException
  819. * @throws Exceptions\MessageNotFoundException
  820. * @throws Exceptions\RuntimeException
  821. * @throws InvalidMessageDateException
  822. * @throws MessageContentFetchingException
  823. * @throws MessageFlagException
  824. * @throws MessageHeaderFetchingException
  825. */
  826. protected function fetchNewMail($folder, $next_uid, $event, $expunge){
  827. if($expunge) $this->client->expunge();
  828. $this->client->openFolder($folder->path);
  829. if ($this->sequence === IMAP::ST_UID) {
  830. $sequence_id = $next_uid;
  831. }else{
  832. $sequence_id = $this->client->getConnection()->getMessageNumber($next_uid);
  833. }
  834. $message = $folder->query()->getMessage($sequence_id, null, $this->sequence);
  835. $event = $this->getEvent("message", $event);
  836. $event::dispatch($this, $message);
  837. return $message;
  838. }
  839. /**
  840. * Delete the current Message
  841. * @param bool $expunge
  842. *
  843. * @return bool
  844. * @throws Exceptions\ConnectionFailedException
  845. * @throws Exceptions\EventNotFoundException
  846. * @throws MessageFlagException
  847. * @throws Exceptions\RuntimeException
  848. */
  849. public function delete($expunge = true) {
  850. $status = $this->setFlag("Deleted");
  851. if($expunge) $this->client->expunge();
  852. $event = $this->getEvent("message", "deleted");
  853. $event::dispatch($this);
  854. return $status;
  855. }
  856. /**
  857. * Restore a deleted Message
  858. * @param boolean $expunge
  859. *
  860. * @return bool
  861. * @throws Exceptions\ConnectionFailedException
  862. * @throws Exceptions\EventNotFoundException
  863. * @throws MessageFlagException
  864. * @throws Exceptions\RuntimeException
  865. */
  866. public function restore($expunge = true) {
  867. $status = $this->unsetFlag("Deleted");
  868. if($expunge) $this->client->expunge();
  869. $event = $this->getEvent("message", "restored");
  870. $event::dispatch($this);
  871. return $status;
  872. }
  873. /**
  874. * Set a given flag
  875. * @param string|array $flag
  876. *
  877. * @return bool
  878. * @throws Exceptions\ConnectionFailedException
  879. * @throws MessageFlagException
  880. * @throws Exceptions\EventNotFoundException
  881. * @throws Exceptions\RuntimeException
  882. */
  883. public function setFlag($flag) {
  884. $this->client->openFolder($this->folder_path);
  885. $flag = "\\".trim(is_array($flag) ? implode(" \\", $flag) : $flag);
  886. $sequence_id = $this->getSequenceId();
  887. try {
  888. $status = $this->client->getConnection()->store([$flag], $sequence_id, $sequence_id, "+", true, $this->sequence === IMAP::ST_UID);
  889. } catch (Exceptions\RuntimeException $e) {
  890. throw new MessageFlagException("flag could not be set", 0, $e);
  891. }
  892. $this->parseFlags();
  893. $event = $this->getEvent("flag", "new");
  894. $event::dispatch($this, $flag);
  895. return $status;
  896. }
  897. /**
  898. * Unset a given flag
  899. * @param string|array $flag
  900. *
  901. * @return bool
  902. * @throws Exceptions\ConnectionFailedException
  903. * @throws Exceptions\EventNotFoundException
  904. * @throws MessageFlagException
  905. * @throws Exceptions\RuntimeException
  906. */
  907. public function unsetFlag($flag) {
  908. $this->client->openFolder($this->folder_path);
  909. $flag = "\\".trim(is_array($flag) ? implode(" \\", $flag) : $flag);
  910. $sequence_id = $this->getSequenceId();
  911. try {
  912. $status = $this->client->getConnection()->store([$flag], $sequence_id, $sequence_id, "-", true, $this->sequence === IMAP::ST_UID);
  913. } catch (Exceptions\RuntimeException $e) {
  914. throw new MessageFlagException("flag could not be removed", 0, $e);
  915. }
  916. $this->parseFlags();
  917. $event = $this->getEvent("flag", "deleted");
  918. $event::dispatch($this, $flag);
  919. return $status;
  920. }
  921. /**
  922. * Set a given flag
  923. * @param string|array $flag
  924. *
  925. * @return bool
  926. * @throws Exceptions\ConnectionFailedException
  927. * @throws MessageFlagException
  928. * @throws Exceptions\EventNotFoundException
  929. * @throws Exceptions\RuntimeException
  930. */
  931. public function addFlag($flag) {
  932. return $this->setFlag($flag);
  933. }
  934. /**
  935. * Unset a given flag
  936. * @param string|array $flag
  937. *
  938. * @return bool
  939. * @throws Exceptions\ConnectionFailedException
  940. * @throws Exceptions\EventNotFoundException
  941. * @throws MessageFlagException
  942. * @throws Exceptions\RuntimeException
  943. */
  944. public function removeFlag($flag) {
  945. return $this->unsetFlag($flag);
  946. }
  947. /**
  948. * Get all message attachments.
  949. *
  950. * @return AttachmentCollection
  951. */
  952. public function getAttachments() {
  953. return $this->attachments;
  954. }
  955. /**
  956. * Get all message attachments.
  957. *
  958. * @return AttachmentCollection
  959. */
  960. public function attachments(){
  961. return $this->getAttachments();
  962. }
  963. /**
  964. * Checks if there are any attachments present
  965. *
  966. * @return boolean
  967. */
  968. public function hasAttachments() {
  969. return $this->attachments->isEmpty() === false;
  970. }
  971. /**
  972. * Get the raw body
  973. *
  974. * @return string
  975. * @throws Exceptions\ConnectionFailedException
  976. * @throws Exceptions\RuntimeException
  977. */
  978. public function getRawBody() {
  979. if ($this->raw_body === null) {
  980. $this->client->openFolder($this->folder_path);
  981. $this->raw_body = $this->structure->raw;
  982. }
  983. return $this->raw_body;
  984. }
  985. /**
  986. * Get the message header
  987. *
  988. * @return Header
  989. */
  990. public function getHeader() {
  991. return $this->header;
  992. }
  993. /**
  994. * Get the current client
  995. *
  996. * @return Client
  997. */
  998. public function getClient() {
  999. return $this->client;
  1000. }
  1001. /**
  1002. * Get the used fetch option
  1003. *
  1004. * @return integer
  1005. */
  1006. public function getFetchOptions() {
  1007. return $this->fetch_options;
  1008. }
  1009. /**
  1010. * Get the used fetch body option
  1011. *
  1012. * @return boolean
  1013. */
  1014. public function getFetchBodyOption() {
  1015. return $this->fetch_body;
  1016. }
  1017. /**
  1018. * Get the used fetch flags option
  1019. *
  1020. * @return boolean
  1021. */
  1022. public function getFetchFlagsOption() {
  1023. return $this->fetch_flags;
  1024. }
  1025. /**
  1026. * Get all available bodies
  1027. *
  1028. * @return array
  1029. */
  1030. public function getBodies() {
  1031. return $this->bodies;
  1032. }
  1033. /**
  1034. * Get all set flags
  1035. *
  1036. * @return FlagCollection
  1037. */
  1038. public function getFlags() {
  1039. return $this->flags;
  1040. }
  1041. /**
  1042. * Get all set flags
  1043. *
  1044. * @return FlagCollection
  1045. */
  1046. public function flags(){
  1047. return $this->getFlags();
  1048. }
  1049. /**
  1050. * Get the fetched structure
  1051. *
  1052. * @return Structure|null
  1053. */
  1054. public function getStructure(){
  1055. return $this->structure;
  1056. }
  1057. /**
  1058. * Check if a message matches an other by comparing basic attributes
  1059. *
  1060. * @param null|Message $message
  1061. * @return boolean
  1062. */
  1063. public function is(Message $message = null) {
  1064. if (is_null($message)) {
  1065. return false;
  1066. }
  1067. return $this->uid == $message->uid
  1068. && $this->message_id->first() == $message->message_id->first()
  1069. && $this->subject->first() == $message->subject->first()
  1070. && $this->date->toDate()->eq($message->date);
  1071. }
  1072. /**
  1073. * Get all message attributes
  1074. *
  1075. * @return array
  1076. */
  1077. public function getAttributes(){
  1078. return array_merge($this->attributes, $this->header->getAttributes());
  1079. }
  1080. /**
  1081. * Set the message mask
  1082. * @param $mask
  1083. *
  1084. * @return $this
  1085. */
  1086. public function setMask($mask){
  1087. if(class_exists($mask)){
  1088. $this->mask = $mask;
  1089. }
  1090. return $this;
  1091. }
  1092. /**
  1093. * Get the used message mask
  1094. *
  1095. * @return string
  1096. */
  1097. public function getMask(){
  1098. return $this->mask;
  1099. }
  1100. /**
  1101. * Get a masked instance by providing a mask name
  1102. * @param string|null $mask
  1103. *
  1104. * @return mixed
  1105. * @throws MaskNotFoundException
  1106. */
  1107. public function mask($mask = null){
  1108. $mask = $mask !== null ? $mask : $this->mask;
  1109. if(class_exists($mask)){
  1110. return new $mask($this);
  1111. }
  1112. throw new MaskNotFoundException("Unknown mask provided: ".$mask);
  1113. }
  1114. /**
  1115. * Get the message path aka folder path
  1116. *
  1117. * @return string
  1118. */
  1119. public function getFolderPath(){
  1120. return $this->folder_path;
  1121. }
  1122. /**
  1123. * Set the message path aka folder path
  1124. * @param $folder_path
  1125. *
  1126. * @return $this
  1127. */
  1128. public function setFolderPath($folder_path){
  1129. $this->folder_path = $folder_path;
  1130. return $this;
  1131. }
  1132. /**
  1133. * Set the config
  1134. * @param $config
  1135. *
  1136. * @return $this
  1137. */
  1138. public function setConfig($config){
  1139. $this->config = $config;
  1140. return $this;
  1141. }
  1142. /**
  1143. * Set the available flags
  1144. * @param $available_flags
  1145. *
  1146. * @return $this
  1147. */
  1148. public function setAvailableFlags($available_flags){
  1149. $this->available_flags = $available_flags;
  1150. return $this;
  1151. }
  1152. /**
  1153. * Set the attachment collection
  1154. * @param $attachments
  1155. *
  1156. * @return $this
  1157. */
  1158. public function setAttachments($attachments){
  1159. $this->attachments = $attachments;
  1160. return $this;
  1161. }
  1162. /**
  1163. * Set the flag collection
  1164. * @param $flags
  1165. *
  1166. * @return $this
  1167. */
  1168. public function setFlags($flags){
  1169. $this->flags = $flags;
  1170. return $this;
  1171. }
  1172. /**
  1173. * Set the client
  1174. * @param $client
  1175. *
  1176. * @return $this
  1177. * @throws Exceptions\RuntimeException
  1178. * @throws Exceptions\ConnectionFailedException
  1179. */
  1180. public function setClient($client){
  1181. $this->client = $client;
  1182. $this->client->openFolder($this->folder_path);
  1183. return $this;
  1184. }
  1185. /**
  1186. * Set the message number
  1187. * @param int $uid
  1188. *
  1189. * @return $this
  1190. * @throws Exceptions\MessageNotFoundException
  1191. * @throws Exceptions\ConnectionFailedException
  1192. */
  1193. public function setUid($uid){
  1194. $this->uid = $uid;
  1195. $this->msgn = $this->client->getConnection()->getMessageNumber($this->uid);
  1196. $this->msglist = null;
  1197. return $this;
  1198. }
  1199. /**
  1200. * Set the message number
  1201. * @param $msgn
  1202. * @param int|null $msglist
  1203. *
  1204. * @return $this
  1205. * @throws Exceptions\MessageNotFoundException
  1206. * @throws Exceptions\ConnectionFailedException
  1207. */
  1208. public function setMsgn($msgn, $msglist = null){
  1209. $this->msgn = $msgn;
  1210. $this->msglist = $msglist;
  1211. $this->uid = $this->client->getConnection()->getUid($this->msgn);
  1212. return $this;
  1213. }
  1214. /**
  1215. * Get the current sequence type
  1216. *
  1217. * @return int
  1218. */
  1219. public function getSequence(){
  1220. return $this->sequence;
  1221. }
  1222. /**
  1223. * Set the sequence type
  1224. *
  1225. * @return int
  1226. */
  1227. public function getSequenceId(){
  1228. return $this->sequence === IMAP::ST_UID ? $this->uid : $this->msgn;
  1229. }
  1230. /**
  1231. * Set the sequence id
  1232. * @param $uid
  1233. * @param int|null $msglist
  1234. *
  1235. * @throws Exceptions\ConnectionFailedException
  1236. * @throws Exceptions\MessageNotFoundException
  1237. */
  1238. public function setSequenceId($uid, $msglist = null){
  1239. if ($this->getSequence() === IMAP::ST_UID) {
  1240. $this->setUid($uid);
  1241. $this->setMsglist($msglist);
  1242. }else{
  1243. $this->setMsgn($uid, $msglist);
  1244. }
  1245. }
  1246. }