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

/day_23_PDO_&_Session_CRUD/PDO_INSERT_UPDATE_DELETE/fuel/packages/email/classes/email/driver.php

https://gitlab.com/cmtsheikeshadi/first_app_development_project
PHP | 1333 lines | 862 code | 129 blank | 342 comment | 25 complexity | 592ae7f51aeaf984f21986fe47688592 MD5 | raw file
  1. <?php
  2. /**
  3. * Fuel
  4. *
  5. * Fuel is a fast, lightweight, community driven PHP5 framework.
  6. *
  7. * @package Fuel
  8. * @version 1.8
  9. * @author Fuel Development Team
  10. * @license MIT License
  11. * @copyright 2010 - 2016 Fuel Development Team
  12. * @link http://fuelphp.com
  13. */
  14. namespace Email;
  15. abstract class Email_Driver
  16. {
  17. /**
  18. * Driver config
  19. */
  20. protected $config = array();
  21. /**
  22. * To recipients list
  23. */
  24. protected $to = array();
  25. /**
  26. * Cc recipients list
  27. */
  28. protected $cc = array();
  29. /**
  30. * Bcc recipients list
  31. */
  32. protected $bcc = array();
  33. /**
  34. * Reply to list
  35. */
  36. protected $reply_to = array();
  37. /**
  38. * Attachments array
  39. */
  40. protected $attachments = array(
  41. 'inline' => array(),
  42. 'attachment' => array(),
  43. );
  44. /**
  45. * Message body
  46. */
  47. protected $body = '';
  48. /**
  49. * Message alt body
  50. */
  51. protected $alt_body = '';
  52. /**
  53. * Message subject
  54. */
  55. protected $subject = '';
  56. /**
  57. * Invalid addresses
  58. */
  59. protected $invalid_addresses = array();
  60. /**
  61. * Message boundaries
  62. */
  63. protected $boundaries = array();
  64. /**
  65. * Message headers
  66. */
  67. protected $headers = array();
  68. /**
  69. * Custom headers
  70. */
  71. protected $extra_headers = array();
  72. /**
  73. * Pipelining enabled?
  74. */
  75. protected $pipelining = false;
  76. /**
  77. * Mail type
  78. */
  79. protected $type = 'plain';
  80. /**
  81. * Driver constructor
  82. *
  83. * @param array $config driver config
  84. */
  85. public function __construct(array $config)
  86. {
  87. $this->config = $config;
  88. }
  89. /**
  90. * Get a driver config setting.
  91. *
  92. * @param string $key Config key
  93. * @param string $default Default value
  94. *
  95. * @return mixed the config setting value
  96. */
  97. public function get_config($key, $default = null)
  98. {
  99. return \Arr::get($this->config, $key, $default);
  100. }
  101. /**
  102. * Set a driver config setting.
  103. *
  104. * @param string $key the config key
  105. * @param mixed $value the new config value
  106. * @return object $this
  107. */
  108. public function set_config($key, $value)
  109. {
  110. \Arr::set($this->config, $key, $value);
  111. return $this;
  112. }
  113. /**
  114. * Enables or disables driver pipelining.
  115. *
  116. * @param bool $pipelining Whether or not to enable pipelining
  117. *
  118. * @return $this
  119. */
  120. public function pipelining($pipelining = true)
  121. {
  122. $this->pipelining = (bool) $pipelining;
  123. return $this;
  124. }
  125. /**
  126. * Gets the body
  127. *
  128. * @return string the message body
  129. */
  130. public function get_body()
  131. {
  132. return $this->body;
  133. }
  134. /**
  135. * Sets the body
  136. *
  137. * @param string $body The message body
  138. *
  139. * @return $this
  140. */
  141. public function body($body)
  142. {
  143. $this->body = (string) $body;
  144. return $this;
  145. }
  146. /**
  147. * Sets the alt body
  148. *
  149. * @param string $alt_body The message alt body
  150. *
  151. * @return $this
  152. */
  153. public function alt_body($alt_body)
  154. {
  155. $this->alt_body = (string) $alt_body;
  156. return $this;
  157. }
  158. /**
  159. * Sets the mail priority
  160. *
  161. * @param string $priority The message priority
  162. *
  163. * @return $this
  164. */
  165. public function priority($priority)
  166. {
  167. $this->config['priority'] = $priority;
  168. return $this;
  169. }
  170. /**
  171. * Sets the html body and optionally a generated alt body.
  172. *
  173. * @param string $html The body html
  174. * @param bool $generate_alt Whether to generate the alt body, will set is html to true
  175. * @param bool $auto_attach Whether to auto attach inline files
  176. *
  177. * @return $this
  178. */
  179. public function html_body($html, $generate_alt = null, $auto_attach = null)
  180. {
  181. $this->config['is_html'] = true;
  182. // Check settings
  183. $generate_alt = is_bool($generate_alt) ? $generate_alt : $this->config['generate_alt'];
  184. $auto_attach = is_bool($auto_attach) ? $auto_attach : $this->config['auto_attach'];
  185. $remove_html_comments = ! empty($this->config['remove_html_comments']) ? $this->config['remove_html_comments'] : true;
  186. // Remove html comments
  187. if ($remove_html_comments)
  188. {
  189. $html = preg_replace('/<!--(.*)-->/', '', (string) $html);
  190. }
  191. if ($auto_attach)
  192. {
  193. // Auto attach all images
  194. preg_match_all("/(src|background)=\"(.*)\"/Ui", $html, $images);
  195. if ( ! empty($images[2]))
  196. {
  197. foreach ($images[2] as $i => $image_url)
  198. {
  199. // Don't attach absolute urls
  200. if ( ! preg_match('/(^http\:\/\/|^https\:\/\/|^\/\/|^cid\:|^data\:|^#)/Ui', $image_url))
  201. {
  202. $cid = 'cid:'.md5(pathinfo($image_url, PATHINFO_BASENAME));
  203. if ( ! isset($this->attachments['inline'][$cid]))
  204. {
  205. $this->attach($image_url, true, $cid);
  206. }
  207. $html = preg_replace("/".$images[1][$i]."=\"".preg_quote($image_url, '/')."\"/Ui", $images[1][$i]."=\"".$cid."\"", $html);
  208. }
  209. // Deal with relative protocol URI's if needed
  210. elseif ($scheme = $this->get_config('relative_protocol_replacement', false) and strpos($image_url, '//') === 0)
  211. {
  212. $html = preg_replace("/".$images[1][$i]."=\"".preg_quote($image_url, '/')."\"/Ui", $images[1][$i]."=\"".$scheme.substr($image_url, 2)."\"", $html);
  213. }
  214. }
  215. }
  216. }
  217. $this->body = $html;
  218. $generate_alt and $this->alt_body = static::generate_alt($html, $this->config['wordwrap'], $this->config['newline']);
  219. return $this;
  220. }
  221. /**
  222. * Gets the message subject
  223. *
  224. * @return string the message subject
  225. */
  226. public function get_subject()
  227. {
  228. return $this->subject;
  229. }
  230. /**
  231. * Sets the message subject
  232. *
  233. * @param string $subject The message subject
  234. *
  235. * @return $this
  236. */
  237. public function subject($subject)
  238. {
  239. if ($this->config['encode_headers'])
  240. {
  241. $subject = $this->encode_mimeheader((string) $subject);
  242. }
  243. $this->subject = (string) $subject;
  244. return $this;
  245. }
  246. /**
  247. * Gets from address and name
  248. *
  249. * @return array from address and name
  250. */
  251. public function get_from()
  252. {
  253. return $this->config['from'];
  254. }
  255. /**
  256. * Sets the from address and name
  257. *
  258. * @param string $email The from email address
  259. * @param bool|string $name The optional from name
  260. *
  261. * @return $this
  262. */
  263. public function from($email, $name = false)
  264. {
  265. $this->config['from']['email'] = (string) $email;
  266. $this->config['from']['name'] = (is_string($name)) ? $name : false;
  267. if ($this->config['encode_headers'] and $this->config['from']['name'])
  268. {
  269. $this->config['from']['name'] = $this->encode_mimeheader((string) $this->config['from']['name']);
  270. }
  271. return $this;
  272. }
  273. /**
  274. * Gets to recipients list.
  275. *
  276. * @return array to recipients list
  277. */
  278. public function get_to()
  279. {
  280. return $this->to;
  281. }
  282. /**
  283. * Add to the to recipients list.
  284. *
  285. * @param string|array $email Email address or list of email addresses, array(email => name, email)
  286. * @param string|bool $name Recipient name, false, null or empty for no name
  287. *
  288. * @return $this
  289. */
  290. public function to($email, $name = false)
  291. {
  292. static::add_to_list('to', $email, $name);
  293. return $this;
  294. }
  295. /**
  296. * Gets to cc recipients list.
  297. *
  298. * @return array to cc recipients list
  299. */
  300. public function get_cc()
  301. {
  302. return $this->cc;
  303. }
  304. /**
  305. * Add to the cc recipients list.
  306. *
  307. * @param string|array $email Email address or list of email addresses, array(email => name, email)
  308. * @param string|bool $name Recipient name, false, null or empty for no name
  309. *
  310. * @return $this
  311. */
  312. public function cc($email, $name = false)
  313. {
  314. static::add_to_list('cc', $email, $name);
  315. return $this;
  316. }
  317. /**
  318. * Gets to bcc recipients list.
  319. *
  320. * @return array to bcc recipients list
  321. */
  322. public function get_bcc()
  323. {
  324. return $this->bcc;
  325. }
  326. /**
  327. * Add to the bcc recipients list.
  328. *
  329. * @param string|array $email Email address or list of email addresses, array(email => name, email)
  330. * @param string|bool $name Recipient name, false, null or empty for no name
  331. *
  332. * @return $this
  333. */
  334. public function bcc($email, $name = false)
  335. {
  336. static::add_to_list('bcc', $email, $name);
  337. return $this;
  338. }
  339. /**
  340. * Gets to 'reply to' recipients list.
  341. *
  342. * @return array to 'reply to' recipients list
  343. */
  344. public function get_reply_to()
  345. {
  346. return $this->reply_to;
  347. }
  348. /**
  349. * Add to the 'reply to' list.
  350. *
  351. * @param string|array $email Email address or list of email addresses, array(email => name, email)
  352. * @param string|bool $name The name, false, null or empty for no name
  353. *
  354. * @return $this
  355. */
  356. public function reply_to($email, $name = false)
  357. {
  358. static::add_to_list('reply_to', $email, $name);
  359. return $this;
  360. }
  361. /**
  362. * Sets the return-path address
  363. *
  364. * @param string $email The return-path email address
  365. *
  366. * @return $this
  367. */
  368. public function return_path($email)
  369. {
  370. $this->config['return_path'] = (string) $email;
  371. return $this;
  372. }
  373. /**
  374. * Add to a recipients list.
  375. *
  376. * @param string $list List to add to (to, cc, bcc)
  377. * @param string|array $email Email address or list of email addresses, array(email => name, email)
  378. * @param string|bool $name Recipient name, false, null or empty for no name
  379. *
  380. * @return void
  381. */
  382. protected function add_to_list($list, $email, $name = false)
  383. {
  384. if ( ! is_array($email))
  385. {
  386. $email = (is_string($name)) ? array($email => $name) : array($email);
  387. }
  388. foreach ($email as $_email => $name)
  389. {
  390. if (is_numeric($_email))
  391. {
  392. $_email = $name;
  393. $name = false;
  394. }
  395. if ($this->config['encode_headers'] and $name)
  396. {
  397. $name = $this->encode_mimeheader($name);
  398. }
  399. $this->{$list}[$_email] = array(
  400. 'name' => $name,
  401. 'email' => $_email,
  402. );
  403. }
  404. }
  405. /**
  406. * Clear the a recipient list.
  407. *
  408. * @param string|array $list List or array of lists
  409. *
  410. * @return void
  411. */
  412. protected function clear_list($list)
  413. {
  414. is_array($list) or $list = array($list);
  415. foreach ($list as $_list)
  416. {
  417. $this->{$_list} = array();
  418. }
  419. }
  420. /**
  421. * Clear all recipient lists.
  422. *
  423. * @return $this
  424. */
  425. public function clear_recipients()
  426. {
  427. static::clear_list(array('to', 'cc', 'bcc'));
  428. return $this;
  429. }
  430. /**
  431. * Clear all address lists.
  432. *
  433. * @return $this
  434. */
  435. public function clear_addresses()
  436. {
  437. static::clear_list(array('to', 'cc', 'bcc', 'reply_to'));
  438. return $this;
  439. }
  440. /**
  441. * Clear the 'to' recipient list.
  442. *
  443. * @return $this
  444. */
  445. public function clear_to()
  446. {
  447. static::clear_list('to');
  448. return $this;
  449. }
  450. /**
  451. * Clear the 'cc' recipient list.
  452. *
  453. * @return $this
  454. */
  455. public function clear_cc()
  456. {
  457. static::clear_list('cc');
  458. return $this;
  459. }
  460. /**
  461. * Clear the 'bcc' recipient list.
  462. *
  463. * @return $this
  464. */
  465. public function clear_bcc()
  466. {
  467. static::clear_list('bcc');
  468. return $this;
  469. }
  470. /**
  471. * Clear the 'reply to' recipient list.
  472. *
  473. * @return $this
  474. */
  475. public function clear_reply_to()
  476. {
  477. static::clear_list('reply_to');
  478. return $this;
  479. }
  480. /**
  481. * Sets custom headers.
  482. *
  483. * @param string|array $header Header type or array of headers
  484. * @param string $value Header value
  485. * @return $this
  486. */
  487. public function header($header, $value = null)
  488. {
  489. if(is_array($header))
  490. {
  491. foreach($header as $_header => $_value)
  492. {
  493. empty($_value) or $this->extra_headers[$_header] = $_value;
  494. }
  495. }
  496. else
  497. {
  498. empty($value) or $this->extra_headers[$header] = $value;
  499. }
  500. return $this;
  501. }
  502. /**
  503. * Attaches a file to the email. This method will search for the file in the attachment paths set (config/email.php) in the attach_paths array
  504. *
  505. * @param string $file The file to attach
  506. * @param bool $inline Whether to include the file inline
  507. * @param string $cid The content identifier. Used when attaching inline images
  508. * @param string $mime The file's mime-type
  509. * @param string $name The attachment's name
  510. *
  511. * @throws \InvalidAttachmentsException Could not read attachment or attachment is empty
  512. *
  513. * @return $this
  514. */
  515. public function attach($file, $inline = false, $cid = null, $mime = null, $name = null)
  516. {
  517. $file = (array) $file;
  518. // Ensure the attachment name
  519. if ( ! isset($file[1]))
  520. {
  521. $name or $name = pathinfo($file[0], PATHINFO_BASENAME);
  522. $file[] = $name;
  523. }
  524. // Find the attachment.
  525. $file[0] = $this->find_attachment($file[0]);
  526. if (($contents = file_get_contents($file[0])) === false or empty($contents))
  527. {
  528. throw new \InvalidAttachmentsException('Could not read attachment or attachment is empty: '.$file[0]);
  529. }
  530. $disp = ($inline) ? 'inline' : 'attachment';
  531. $cid = empty($cid) ? 'cid:'.md5($file[1]) : trim($cid);
  532. $cid = strpos($cid, 'cid:') === 0 ? $cid : 'cid:'.$cid;
  533. // Fetch the file mime type.
  534. $mime or $mime = static::attachment_mime($file[0]);
  535. $this->attachments[$disp][$cid] = array(
  536. 'file' => $file,
  537. 'contents' => chunk_split(base64_encode($contents), 76, $this->config['newline']),
  538. 'mime' => $mime,
  539. 'disp' => $disp,
  540. 'cid' => $cid,
  541. );
  542. return $this;
  543. }
  544. /**
  545. * Finds an attachment.
  546. *
  547. * @param $file
  548. *
  549. * @throws \AttachmentNotFoundException Email attachment not found
  550. *
  551. * @return string path of the first found attachment
  552. */
  553. protected function find_attachment($file)
  554. {
  555. foreach($this->get_config('attach_paths') as $path)
  556. {
  557. if(is_file($path.$file))
  558. {
  559. return $path.$file;
  560. }
  561. }
  562. // No file found?
  563. throw new \AttachmentNotFoundException('Email attachment not found: '.$file);
  564. }
  565. /**
  566. * Attach a file using string input
  567. *
  568. * @param string $contents File contents
  569. * @param string $filename The files name
  570. * @param string $cid The content identifier. Used when attaching inline images
  571. * @param bool $inline Whether it's an inline attachment
  572. * @param string $mime The file's mime-type
  573. *
  574. * @return $this
  575. */
  576. public function string_attach($contents, $filename, $cid = null, $inline = false, $mime = null)
  577. {
  578. $disp = ($inline) ? 'inline' : 'attachment';
  579. $cid = empty($cid) ? 'cid:'.md5($filename) : trim($cid);
  580. $cid = strpos($cid, 'cid:') === 0 ? $cid : 'cid:'.$cid;
  581. $mime or $mime = static::attachment_mime($filename);
  582. $this->attachments[$disp][$cid] = array(
  583. 'file' => array(1 => $filename),
  584. 'contents' => static::encode_string($contents, 'base64', $this->config['newline']),
  585. 'mime' => $mime,
  586. 'disp' => $disp,
  587. 'cid' => $cid,
  588. );
  589. return $this;
  590. }
  591. /**
  592. * Clear the attachments list.
  593. *
  594. * @return $this
  595. */
  596. public function clear_attachments()
  597. {
  598. $this->attachments = array(
  599. 'inline' => array(),
  600. 'attachment' => array(),
  601. );
  602. return $this;
  603. }
  604. /**
  605. * Get the mimetype for an attachment
  606. *
  607. * @param string $file The path to the attachment
  608. *
  609. * @return $this
  610. */
  611. protected static function attachment_mime($file)
  612. {
  613. static $mimes = false;
  614. if ( ! $mimes)
  615. {
  616. $mimes = \Config::load('mimes');
  617. }
  618. $ext = pathinfo($file, PATHINFO_EXTENSION);
  619. $mime = \Arr::get($mimes, $ext, 'application/octet-stream');
  620. is_array($mime) and $mime = reset($mime);
  621. return $mime;
  622. }
  623. /**
  624. * Validates all the email addresses.
  625. *
  626. * @return bool|array True if all are valid or an array of recipients which failed validation.
  627. */
  628. protected function validate_addresses()
  629. {
  630. $failed = array();
  631. foreach (array('to', 'cc', 'bcc') as $list)
  632. {
  633. foreach ($this->{$list} as $recipient)
  634. {
  635. if ( ! filter_var($recipient['email'], FILTER_VALIDATE_EMAIL))
  636. {
  637. $failed[$list][] = $recipient;
  638. }
  639. }
  640. }
  641. if (count($failed) === 0)
  642. {
  643. return true;
  644. }
  645. return $failed;
  646. }
  647. /**
  648. * Sets unique message boundaries
  649. */
  650. protected function set_boundaries()
  651. {
  652. $uniq_id = md5(uniqid(microtime(true)));
  653. // Message part boundary, (separates message and attachments).
  654. $this->boundaries[0] = 'B1_'.$uniq_id;
  655. // Message body boundary (separates message, alt message)
  656. $this->boundaries[1] = 'B2_'.$uniq_id;
  657. $this->boundaries[2] = 'B3_'.$uniq_id;
  658. }
  659. /**
  660. * Initiates the sending process.
  661. *
  662. * @param bool Whether to validate the addresses, falls back to config setting
  663. *
  664. * @throws \EmailValidationFailedException One or more email addresses did not pass validation
  665. * @throws \FuelException Cannot send without from address/Cannot send without recipients
  666. *
  667. * @return bool
  668. */
  669. public function send($validate = null)
  670. {
  671. if (empty($this->to) and empty($this->cc) and empty($this->bcc))
  672. {
  673. throw new \FuelException('Cannot send email without recipients.');
  674. }
  675. if (($from = $this->config['from']['email']) === false or empty($from))
  676. {
  677. throw new \FuelException('Cannot send without from address.');
  678. }
  679. // Check which validation bool to use
  680. is_bool($validate) or $validate = $this->config['validate'];
  681. // Validate the email addresses if specified
  682. if ($validate and ($failed = $this->validate_addresses()) !== true)
  683. {
  684. $this->invalid_addresses = $failed;
  685. $error_str = '';
  686. foreach($failed as $_list => $_contents)
  687. {
  688. $error_str .= $_list.': '.htmlentities(static::format_addresses($_contents)).'.'.PHP_EOL;
  689. }
  690. throw new \EmailValidationFailedException('One or more email addresses did not pass validation: '.$error_str);
  691. }
  692. // Reset the headers
  693. $this->headers = array();
  694. // Set the email boundaries
  695. $this->set_boundaries();
  696. // Set RFC 822 formatted date
  697. $this->set_header('Date', date('r'));
  698. // Set return path
  699. if ($this->config['return_path'] !== false)
  700. {
  701. $this->set_header('Return-Path', $this->config['return_path']);
  702. }
  703. else
  704. {
  705. $this->set_header('Return-Path', $this->config['from']['email']);
  706. }
  707. if (($this instanceof Email_Driver_Mail) !== true)
  708. {
  709. if ( ! empty($this->to))
  710. {
  711. // Set from
  712. $this->set_header('To', static::format_addresses($this->to));
  713. }
  714. // Set subject
  715. $this->set_header('Subject', $this->subject);
  716. }
  717. $this->set_header('From', static::format_addresses(array($this->config['from'])));
  718. foreach (array('cc' => 'Cc', 'bcc' => 'Bcc', 'reply_to' => 'Reply-To') as $list => $header)
  719. {
  720. if (count($this->{$list}) > 0)
  721. {
  722. $this->set_header($header, static::format_addresses($this->{$list}));
  723. }
  724. }
  725. // Set message id
  726. $this->set_header('Message-ID', $this->get_message_id());
  727. // Set mime version
  728. $this->set_header('MIME-Version', '1.0');
  729. // Set priority
  730. $this->set_header('X-Priority', $this->config['priority']);
  731. // Set mailer useragent
  732. $this->set_header('X-Mailer', $this->config['useragent']);
  733. $newline = $this->config['newline'];
  734. $this->type = $this->get_mail_type();
  735. $encoding = $this->config['encoding'];
  736. $charset = $this->config['charset'];
  737. if ($this->type !== 'plain' and $this->type !== 'html')
  738. {
  739. $this->set_header('Content-Type', $this->get_content_type($this->type, $newline."\tboundary=\"".$this->boundaries[0].'"'));
  740. }
  741. else
  742. {
  743. $this->set_header('Content-Transfer-Encoding', $encoding);
  744. $this->set_header('Content-Type', 'text/'.$this->type.'; charset="'.$this->config['charset'].'"');
  745. }
  746. // Encode messages
  747. $this->body = static::encode_string($this->body, $encoding, $newline);
  748. $this->alt_body = static::encode_string($this->alt_body, $encoding, $newline);
  749. // Set wordwrapping
  750. $wrapping = $this->config['wordwrap'];
  751. $qp_mode = $encoding === 'quoted-printable';
  752. $is_html = (stripos($this->type, 'html') !== false);
  753. // Don't wrap the text when using quoted-printable
  754. if ($wrapping and ! $qp_mode)
  755. {
  756. $this->body = static::wrap_text($this->body, $wrapping, $newline, $is_html);
  757. $this->alt_body = static::wrap_text($this->alt_body, $wrapping, $newline, false);
  758. }
  759. // Send
  760. $this->_send();
  761. return true;
  762. }
  763. /**
  764. * Get the invalid addresses
  765. *
  766. * @return array An array of invalid email addresses
  767. */
  768. public function get_invalid_addresses()
  769. {
  770. return $this->invalid_addresses;
  771. }
  772. /**
  773. * Sets the message headers
  774. *
  775. * @param string $header The header type
  776. * @param string $value The header value
  777. */
  778. protected function set_header($header, $value)
  779. {
  780. empty($value) or $this->headers[$header] = $value;
  781. }
  782. /**
  783. * Gets the header
  784. *
  785. * @param string $header The header name. Will return all headers, if not specified
  786. * @param bool $formatted Adds newline as suffix and colon as prefix, if true
  787. *
  788. * @return string|array Mail header or array of headers
  789. */
  790. protected function get_header($header = null, $formatted = true)
  791. {
  792. if ($header === null)
  793. {
  794. return $this->headers;
  795. }
  796. if (array_key_exists($header, $this->headers))
  797. {
  798. $prefix = ($formatted) ? $header.': ' : '';
  799. $suffix = ($formatted) ? $this->config['newline'] : '';
  800. return $prefix.$this->headers[$header].$suffix;
  801. }
  802. return '';
  803. }
  804. /**
  805. * Encodes a mimeheader.
  806. *
  807. * @param string $header Header to encode
  808. *
  809. * @return string Mimeheader encoded string
  810. */
  811. protected function encode_mimeheader($header)
  812. {
  813. // we need mbstring for this
  814. if ( ! MBSTRING)
  815. {
  816. throw new \RuntimeException('Email requires the multibyte package ("mbstring") package to be installed!');
  817. }
  818. $transfer_encoding = ($this->config['encoding'] === 'quoted-printable') ? 'Q' : 'B' ;
  819. // work around possible bugs with encoding by setting the encoding manually
  820. $current_encoding = mb_internal_encoding();
  821. mb_internal_encoding($this->config['charset']);
  822. $header = mb_encode_mimeheader($header, $this->config['charset'], $transfer_encoding, $this->config['newline']);
  823. mb_internal_encoding($current_encoding);
  824. return $header;
  825. }
  826. /**
  827. * Get the attachment headers
  828. *
  829. */
  830. protected function get_attachment_headers($type, $boundary)
  831. {
  832. $return = '';
  833. $newline = $this->config['newline'];
  834. foreach ($this->attachments[$type] as $attachment)
  835. {
  836. $return .= '--'.$boundary.$newline;
  837. $return .= 'Content-Type: '.$attachment['mime'].'; name="'.$attachment['file'][1].'"'.$newline;
  838. $return .= 'Content-Transfer-Encoding: base64'.$newline;
  839. $type === 'inline' and $return .= 'Content-ID: <'.substr($attachment['cid'], 4).'>'.$newline;
  840. $return .= 'Content-Disposition: '.$type.'; filename="'.$attachment['file'][1].'"'.$newline.$newline;
  841. $return .= $attachment['contents'].$newline.$newline;
  842. }
  843. return $return;
  844. }
  845. /**
  846. * Get a unique message id
  847. *
  848. * @return string The message id
  849. */
  850. protected function get_message_id()
  851. {
  852. $from = $this->config['from']['email'];
  853. return "<".uniqid('').strstr($from, '@').">";
  854. }
  855. /**
  856. * Returns the mail's type
  857. *
  858. * @return string Mail type
  859. */
  860. protected function get_mail_type()
  861. {
  862. $return = $this->config['is_html'] ? 'html' : 'plain' ;
  863. $alt = trim($this->alt_body);
  864. $return .= ($this->config['is_html'] and ! empty($alt)) ? '_alt' : '';
  865. $return .= ($this->config['is_html'] and count($this->attachments['inline'])) ? '_inline' : '';
  866. $return .= (count($this->attachments['attachment'])) ? '_attach' : '';
  867. return $return;
  868. }
  869. /**
  870. * Returns the content type
  871. *
  872. * @param string $mail_type Type of email (plain, html, html_inline, etc…)
  873. * @param $boundary
  874. *
  875. * @throws \FuelException Invalid content-type
  876. *
  877. * @return string Mail content type
  878. */
  879. protected function get_content_type($mail_type, $boundary)
  880. {
  881. $related = $this->config['force_mixed'] ? 'multipart/mixed; ' : 'multipart/related; ';
  882. switch ($mail_type)
  883. {
  884. case 'plain':
  885. return 'text/plain';
  886. case 'plain_attach':
  887. case 'html_attach':
  888. return $related.$boundary;
  889. case 'html':
  890. return 'text/html';
  891. case 'html_alt_attach':
  892. case 'html_alt_inline_attach':
  893. return 'multipart/mixed; '.$boundary;
  894. case 'html_alt_inline':
  895. case 'html_alt':
  896. case 'html_inline':
  897. return 'multipart/alternative; '.$boundary;
  898. default:
  899. throw new \FuelException('Invalid content-type'.$mail_type);
  900. }
  901. }
  902. /**
  903. * Builds the headers and body
  904. *
  905. * @param bool $no_bcc Whether to exclude Bcc headers.
  906. *
  907. * @return array An array containing the headers and the body
  908. */
  909. protected function build_message($no_bcc = false)
  910. {
  911. $newline = $this->config['newline'];
  912. $charset = $this->config['charset'];
  913. $encoding = $this->config['encoding'];
  914. $headers = '';
  915. $parts = array('Date', 'Return-Path', 'From', 'To', 'Cc', 'Bcc', 'Reply-To', 'Subject', 'Message-ID', 'X-Priority', 'X-Mailer', 'MIME-Version', 'Content-Type');
  916. $no_bcc and array_splice($parts, 5, 1);
  917. foreach ($parts as $part)
  918. {
  919. $headers .= $this->get_header($part);
  920. }
  921. foreach ($this->extra_headers as $header => $value)
  922. {
  923. $headers .= $header.': '.$value.$newline;
  924. }
  925. $headers .= $newline;
  926. $body = '';
  927. if ($this->type === 'plain' or $this->type === 'html')
  928. {
  929. $body = $this->body;
  930. }
  931. else
  932. {
  933. switch ($this->type)
  934. {
  935. case 'html_alt':
  936. $body .= '--'.$this->boundaries[0].$newline;
  937. $body .= 'Content-Type: text/plain; charset="'.$charset.'"'.$newline;
  938. $body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline;
  939. $body .= $this->alt_body.$newline.$newline;
  940. $body .= '--'.$this->boundaries[0].$newline;
  941. $body .= 'Content-Type: text/html; charset="'.$charset.'"'.$newline;
  942. $body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline;
  943. $body .= $this->body.$newline.$newline;
  944. $body .= '--'.$this->boundaries[0].'--';
  945. break;
  946. case 'plain_attach':
  947. case 'html_attach':
  948. case 'html_inline':
  949. $body .= '--'.$this->boundaries[0].$newline;
  950. $text_type = (stripos($this->type, 'html') !== false) ? 'html' : 'plain';
  951. $body .= 'Content-Type: text/'.$text_type.'; charset="'.$charset.'"'.$newline;
  952. $body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline;
  953. $body .= $this->body.$newline.$newline;
  954. $attach_type = (stripos($this->type, 'attach') !== false) ? 'attachment' : 'inline';
  955. $body .= $this->get_attachment_headers($attach_type, $this->boundaries[0]);
  956. $body .= '--'.$this->boundaries[0].'--';
  957. break;
  958. case 'html_alt_inline':
  959. $body .= '--'.$this->boundaries[0].$newline;
  960. $body .= 'Content-Type: text/plain'.'; charset="'.$charset.'"'.$newline;
  961. $body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline;
  962. $body .= $this->alt_body.$newline.$newline;
  963. $body .= '--'.$this->boundaries[0].$newline;
  964. $body .= 'Content-Type: multipart/related;'.$newline."\tboundary=\"{$this->boundaries[1]}\"".$newline.$newline;
  965. $body .= '--'.$this->boundaries[1].$newline;
  966. $body .= 'Content-Type: text/html; charset="'.$charset.'"'.$newline;
  967. $body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline;
  968. $body .= $this->body.$newline.$newline;
  969. $body .= $this->get_attachment_headers('inline', $this->boundaries[1]);
  970. $body .= '--'.$this->boundaries[1].'--'.$newline.$newline;
  971. $body .= '--'.$this->boundaries[0].'--';
  972. break;
  973. case 'html_alt_attach':
  974. case 'html_inline_attach':
  975. $body .= '--'.$this->boundaries[0].$newline;
  976. $body .= 'Content-Type: multipart/alternative;'.$newline."\t boundary=\"{$this->boundaries[1]}\"".$newline.$newline;
  977. if (stripos($this->type, 'alt') !== false)
  978. {
  979. $body .= '--'.$this->boundaries[1].$newline;
  980. $body .= 'Content-Type: text/plain; charset="'.$charset.'"'.$newline;
  981. $body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline;
  982. $body .= $this->alt_body.$newline.$newline;
  983. }
  984. $body .= '--'.$this->boundaries[1].$newline;
  985. $body .= 'Content-Type: text/html; charset="'.$charset.'"'.$newline;
  986. $body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline;
  987. $body .= $this->body.$newline.$newline;
  988. if (stripos($this->type, 'inline') !== false)
  989. {
  990. $body .= $this->get_attachment_headers('inline', $this->boundaries[1]);
  991. $body .= $this->alt_body.$newline.$newline;
  992. }
  993. $body .= '--'.$this->boundaries[1].'--'.$newline.$newline;
  994. $body .= $this->get_attachment_headers('attachment', $this->boundaries[0]);
  995. $body .= '--'.$this->boundaries[0].'--';
  996. break;
  997. case 'html_alt_inline_attach':
  998. $body .= '--'.$this->boundaries[0].$newline;
  999. $body .= 'Content-Type: multipart/alternative;'.$newline."\t boundary=\"{$this->boundaries[1]}\"".$newline.$newline;
  1000. $body .= '--'.$this->boundaries[1].$newline;
  1001. $body .= 'Content-Type: text/plain; charset="'.$charset.'"'.$newline;
  1002. $body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline;
  1003. $body .= $this->alt_body.$newline.$newline;
  1004. $body .= '--'.$this->boundaries[1].$newline;
  1005. $body .= 'Content-Type: multipart/related;'.$newline."\t boundary=\"{$this->boundaries[2]}\"".$newline.$newline;
  1006. $body .= '--'.$this->boundaries[2].$newline;
  1007. $body .= 'Content-Type: text/html; charset="'.$charset.'"'.$newline;
  1008. $body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline;
  1009. $body .= $this->body.$newline.$newline;
  1010. $body .= $this->get_attachment_headers('inline', $this->boundaries[2]);
  1011. $body .= $this->alt_body.$newline.$newline;
  1012. $body .= '--'.$this->boundaries[2].'--'.$newline.$newline;
  1013. $body .= '--'.$this->boundaries[1].'--'.$newline.$newline;
  1014. $body .= $this->get_attachment_headers('attachment', $this->boundaries[0]);
  1015. $body .= '--'.$this->boundaries[0].'--';
  1016. break;
  1017. }
  1018. }
  1019. return array(
  1020. 'header' => $headers,
  1021. 'body' => $body,
  1022. );
  1023. }
  1024. /**
  1025. * Wraps the body or alt text
  1026. *
  1027. * @param string $message The text to wrap
  1028. * @param int $length The max line length
  1029. * @param string $newline The newline delimiter
  1030. * @param bool $is_html
  1031. *
  1032. * @internal param string $charset the text charset
  1033. * @internal param bool $qp_mode whether the text is quoted printable encoded
  1034. *
  1035. * @return string
  1036. */
  1037. protected static function wrap_text($message, $length, $newline, $is_html = true)
  1038. {
  1039. $length = ($length > 76) ? 76 : $length;
  1040. $is_html and $message = preg_replace('/[\r\n\t ]+/m', ' ', $message);
  1041. $message = wordwrap($message, $length, $newline, false);
  1042. return $message;
  1043. }
  1044. /**
  1045. * Standardize newlines.
  1046. *
  1047. * @param string $string String to prep
  1048. * @param string $newline The newline delimiter
  1049. *
  1050. * @return string String with standardized newlines
  1051. */
  1052. protected static function prep_newlines($string, $newline = null)
  1053. {
  1054. $newline or $newline = \Config::get('email.defaults.newline', "\n");
  1055. $replace = array(
  1056. "\r\n" => "\n",
  1057. "\n\r" => "\n",
  1058. "\r" => "\n",
  1059. "\n" => $newline,
  1060. );
  1061. foreach ($replace as $from => $to)
  1062. {
  1063. $string = str_replace($from, $to, $string);
  1064. }
  1065. return $string;
  1066. }
  1067. /**
  1068. * Encodes a string in the given encoding.
  1069. *
  1070. * @param string $string String to encode
  1071. * @param string $encoding The charset
  1072. * @param string $newline Newline delimeter
  1073. *
  1074. * @throws \InvalidEmailStringEncoding Encoding is not a supported by encoding method
  1075. *
  1076. * @return string Encoded string
  1077. */
  1078. protected static function encode_string($string, $encoding, $newline = null)
  1079. {
  1080. $newline or $newline = \Config::get('email.defaults.newline', "\n");
  1081. switch ($encoding)
  1082. {
  1083. case 'quoted-printable':
  1084. return quoted_printable_encode($string);
  1085. case '7bit':
  1086. case '8bit':
  1087. return static::prep_newlines(rtrim($string, $newline), $newline);
  1088. case 'base64':
  1089. return chunk_split(base64_encode($string), 76, $newline);
  1090. default:
  1091. throw new \InvalidEmailStringEncoding($encoding.' is not a supported encoding method.');
  1092. }
  1093. }
  1094. /**
  1095. * Returns a formatted string of email addresses.
  1096. *
  1097. * @param array $addresses Array of adresses array(array(name=>name, email=>email));
  1098. *
  1099. * @return string Correctly formatted email addresses
  1100. */
  1101. protected static function format_addresses($addresses)
  1102. {
  1103. $return = array();
  1104. foreach ($addresses as $recipient)
  1105. {
  1106. $recipient['name'] and $recipient['email'] = '"'.$recipient['name'].'" <'.$recipient['email'].'>';
  1107. $return[] = $recipient['email'];
  1108. }
  1109. return join(', ', $return);
  1110. }
  1111. /**
  1112. * Generates an alt body
  1113. *
  1114. * @param string $html html Body to al body generate from
  1115. * @param int $wordwrap Wordwrap length
  1116. * @param string $newline Line separator to use
  1117. *
  1118. * @return string The generated alt body
  1119. */
  1120. protected static function generate_alt($html, $wordwrap, $newline)
  1121. {
  1122. $html = preg_replace('/[ | ]{2,}/m', ' ', $html);
  1123. $html = trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/s', '', $html)));
  1124. $lines = explode($newline, $html);
  1125. $result = array();
  1126. $first_newline = true;
  1127. foreach ($lines as $line)
  1128. {
  1129. $line = trim($line);
  1130. if ( ! empty($line) or $first_newline)
  1131. {
  1132. $first_newline = false;
  1133. $result[] = $line;
  1134. }
  1135. else
  1136. {
  1137. $first_newline = true;
  1138. }
  1139. }
  1140. $html = join($newline, $result);
  1141. if ( ! $wordwrap)
  1142. {
  1143. return $html;
  1144. }
  1145. return wordwrap($html, $wordwrap, $newline, true);
  1146. }
  1147. /**
  1148. * Initiates the sending process.
  1149. *
  1150. * @return bool success boolean
  1151. */
  1152. abstract protected function _send();
  1153. }