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

/phpmyfaq/inc/Mail.php

https://github.com/cyrke/phpMyFAQ
PHP | 977 lines | 445 code | 110 blank | 422 comment | 54 complexity | b23afdeabd1961c2350407c102bf8c75 MD5 | raw file
Possible License(s): LGPL-2.1, LGPL-3.0, MPL-2.0-no-copyleft-exception
  1. <?php
  2. /**
  3. * MUA (Mail User Agent) implementation.
  4. *
  5. * PHP Version 5.3
  6. *
  7. * This Source Code Form is subject to the terms of the Mozilla Public License,
  8. * v. 2.0. If a copy of the MPL was not distributed with this file, You can
  9. * obtain one at http://mozilla.org/MPL/2.0/.
  10. *
  11. * @category phpMyFAQ
  12. * @package Mail
  13. * @author Matteo Scaramuccia <matteo@phpmyfaq.de>
  14. * @author Thorsten Rinne <thorsten@phpmyfaq.de>
  15. * @copyright 2009-2012 phpMyFAQ Team
  16. * @license http://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
  17. * @link http://www.phpmyfaq.de
  18. * @since 2009-09-11
  19. */
  20. if (!defined('IS_VALID_PHPMYFAQ')) {
  21. exit();
  22. }
  23. /**
  24. * Mail
  25. *
  26. * @category phpMyFAQ
  27. * @package Mail
  28. * @author Matteo Scaramuccia <matteo@phpmyfaq.de>
  29. * @author Thorsten Rinne <thorsten@phpmyfaq.de>
  30. * @copyright 2009-2012 phpMyFAQ Team
  31. * @license http://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
  32. * @link http://www.phpmyfaq.de
  33. * @since 2009-09-11
  34. */
  35. class PMF_Mail
  36. {
  37. /**
  38. * Type of the used MUA. Possible values:
  39. * - built-in.
  40. *
  41. * @var string $agent
  42. */
  43. public $agent;
  44. /**
  45. * Attached filed.
  46. *
  47. * @var mixed $attachments
  48. */
  49. public $attachments;
  50. /**
  51. * Body of the e-mail.
  52. *
  53. * @var string $body
  54. */
  55. public $body = '';
  56. /**
  57. * Boundary.
  58. *
  59. * @var string $boundary
  60. */
  61. public $boundary = '----------';
  62. /**
  63. * Charset.
  64. *
  65. * @var string $charset
  66. */
  67. public $charset = 'utf-8';
  68. /**
  69. * Content disposition.
  70. *
  71. * @var string $contentDisposition
  72. */
  73. public $contentDisposition = 'inline';
  74. /**
  75. * Content type.
  76. *
  77. * @var string $contentType
  78. */
  79. public $contentType = 'text/plain';
  80. /**
  81. * Content transfer encoding.
  82. *
  83. * @var string $contentTransferEncoding
  84. */
  85. public $contentTransferEncoding = '8bit';
  86. /**
  87. * The one and only valid End Of Line sequence as per RFC 2822:
  88. * carriage-return followed by line-feed.
  89. *
  90. * @var string $eol
  91. */
  92. public $eol = "\r\n";
  93. /**
  94. * Headers of the e-mail.
  95. *
  96. * @var string $headers
  97. */
  98. public $headers;
  99. /**
  100. * Message of the e-mail: HTML text allowed.
  101. *
  102. * @var string $message
  103. */
  104. public $message;
  105. /**
  106. * Alternate message of the e-mail: only plain text allowed.
  107. *
  108. * @var string $messageAlt
  109. */
  110. public $messageAlt;
  111. /**
  112. * Message-ID of the e-mail.
  113. *
  114. * @var string $messageId
  115. */
  116. public $messageId;
  117. /**
  118. * Priorities: 1 (Highest), 2 (High), 3 (Normal), 4 (Low), 5 (Lowest).
  119. *
  120. * @var mixed $priorities
  121. */
  122. public $priorities = array(
  123. 1 => 'Highest',
  124. 2 => 'High',
  125. 3 => 'Normal',
  126. 4 => 'Low',
  127. 5 => 'Lowest');
  128. /**
  129. * Priority of the e-mail: 1 (Highest), 2 (High), 3 (Normal), 4 (Low), 5 (Lowest).
  130. *
  131. * @var int $priority
  132. * @see priorities
  133. */
  134. public $priority;
  135. /**
  136. * Subject of the e-mail.
  137. *
  138. * @var string $subject
  139. */
  140. public $subject;
  141. /**
  142. * Recipients of the e-mail as <BCC>.
  143. *
  144. * @var mixed $_bcc
  145. */
  146. private $_bcc;
  147. /**
  148. * Recipients of the e-mail as <CC>.
  149. *
  150. * @var mixed $_cc
  151. */
  152. private $_cc;
  153. /**
  154. * Recipients of the e-mail as <From>.
  155. *
  156. * @var mixed $_from
  157. */
  158. private $_from;
  159. /**
  160. * Mailer string.
  161. *
  162. * @var string $_mailer
  163. */
  164. private $_mailer;
  165. /**
  166. * Recipient of the optional notification.
  167. *
  168. * @var mixed $_notifyTo
  169. */
  170. private $_notifyTo;
  171. /**
  172. * Recipient of the e-mail as <Reply-To>.
  173. *
  174. * @var mixed $_replyTo
  175. */
  176. private $_replyTo;
  177. /**
  178. * Recipient of the e-mail as <Return-Path>.
  179. *
  180. * @var mixed $_returnPath
  181. */
  182. private $_returnPath;
  183. /**
  184. * Recipient of the e-mail as <Sender>.
  185. *
  186. * @var mixed $_sender
  187. */
  188. private $_sender;
  189. /**
  190. * Recipients of the e-mail as <TO:>.
  191. *
  192. * @var mixed $_to
  193. */
  194. private $_to;
  195. /**
  196. * @var PMF_Configuration
  197. */
  198. private $_config;
  199. /*
  200. * Default constructor.
  201. * Note: any email will be sent from the PMF administrator, use unsetFrom
  202. * before using setFrom.
  203. *
  204. * @param Configuration $config
  205. */
  206. function __construct(PMF_Configuration $config)
  207. {
  208. // Set default value for public properties
  209. $this->agent = 'built-in';
  210. $this->attachments = array();
  211. $this->boundary = self::createBoundary();
  212. $this->headers = array();
  213. $this->message = '';
  214. $this->messageAlt = '';
  215. $this->messageId = '<'.$_SERVER['REQUEST_TIME'] . '.'. md5(microtime()) . '@' . self::getServerName() . '>';
  216. $this->priority = 3; // 3 -> Normal
  217. $this->subject = '';
  218. // Set default value for private properties
  219. $this->_config = $config;
  220. $this->_bcc = array();
  221. $this->_cc = array();
  222. $this->_from = array();
  223. $this->_mailer = 'phpMyFAQ on PHP/' . PHP_VERSION;
  224. $this->_notifyTo = array();
  225. $this->_replyTo = array();
  226. $this->_returnPath = array();
  227. $this->_sender = array();
  228. $this->_to = array();
  229. // Set phpMyFAQ related data
  230. $this->_mailer = 'phpMyFAQ/' . $this->_config->get('main.currentVersion');
  231. $this->setFrom($this->_config->get('main.administrationMail'));
  232. }
  233. /**
  234. * Add an e-mail address to an array.
  235. *
  236. * @param array $target Target array.
  237. * @param string $targetAlias Alias Target alias.
  238. * @param string $address User e-mail address.
  239. * @param string $name User name (optional).
  240. *
  241. * @return bool True if successful, false otherwise.
  242. *
  243. * @todo Enhance error handling using exceptions
  244. */
  245. private function _addEmailTo(&$target, $targetAlias, $address, $name = null)
  246. {
  247. // Sanity check
  248. if (!self::validateEmail($address)) {
  249. trigger_error(
  250. "<b>Mail Class</b>: $address is not a valid e-mail address!",
  251. E_USER_ERROR
  252. );
  253. return false;
  254. }
  255. // Don't allow duplicated addresses
  256. if (array_key_exists($address, $target)) {
  257. trigger_error(
  258. "<b>Mail Class</b>: $address has been already added in '$targetAlias'!",
  259. E_USER_WARNING
  260. );
  261. return false;
  262. }
  263. if (!empty($name)) {
  264. // Remove CR and LF characters to prevent header injection
  265. $name = str_replace(array("\n", "\r"), '', $name);
  266. if (function_exists('mb_encode_mimeheader')) {
  267. // Encode any special characters in the displayed name
  268. $name = mb_encode_mimeheader($name);
  269. }
  270. // Wrap the displayed name in quotes (to fix problems with commas etc),
  271. // and escape any existing quotes
  272. $name = '"' . str_replace('"', '\"', $name) . '"';
  273. }
  274. // Add the e-mail address into the target array
  275. $target[$address] = $name;
  276. // On Windows, when using PHP built-in mail drop any name, just use the e-mail address
  277. if (('WIN' === strtoupper(substr(PHP_OS, 0, 3))) && ('built-in' == $this->agent)) {
  278. $target[$address] = null;
  279. }
  280. return true;
  281. }
  282. /**
  283. * Create the body of the email.
  284. *
  285. * @return void
  286. */
  287. private function _createBody()
  288. {
  289. $lines = array();
  290. $mainBoundary = $this->boundary;
  291. // Cleanup body
  292. $this->body = array();
  293. // Add lines
  294. if (strpos($this->contentType, 'multipart') !== false) {
  295. $lines[] = 'This is a multi-part message in MIME format.';
  296. $lines[] = '';
  297. }
  298. if (in_array($this->contentType,
  299. array(
  300. 'multipart/mixed',
  301. 'multipart/related'
  302. )
  303. )
  304. ) {
  305. $lines[] = '--'.$mainBoundary;
  306. $this->boundary = "--=alternative=".self::createBoundary();
  307. $lines[] = 'Content-Type: multipart/alternative; boundary="'.$this->boundary.'"';
  308. $lines[] = '';
  309. }
  310. if (strpos($this->contentType, 'multipart') !== false) {
  311. // At least we have messageAlt and message
  312. if (!empty($this->messageAlt)) {
  313. // 1/2. messageAlt, supposed as plain text
  314. $lines[] = '--'.$this->boundary;
  315. $lines[] = 'Content-Type: text/plain; charset="'.$this->charset.'"';
  316. $lines[] = 'Content-Transfer-Encoding: '.$this->contentTransferEncoding;
  317. $lines[] = '';
  318. $lines[] = self::wrapLines(PMF_Utils::resolveMarkers($this->messageAlt, $this->_config));
  319. $lines[] = '';
  320. }
  321. // 2/2. message, supposed as, potentially, HTML
  322. $lines[] = '--'.$this->boundary;
  323. $lines[] = 'Content-Type: text/html; charset="'.$this->charset.'"';
  324. $lines[] = 'Content-Transfer-Encoding: '.$this->contentTransferEncoding;
  325. $lines[] = '';
  326. $lines[] = self::wrapLines($this->message);
  327. // Close the boundary delimiter
  328. $lines[] = '--'.$this->boundary.'--';
  329. } else {
  330. $lines[] = self::wrapLines($this->message);
  331. }
  332. if (in_array($this->contentType,
  333. array(
  334. 'multipart/mixed',
  335. 'multipart/related'
  336. )
  337. )
  338. ) {
  339. // Back to the main boundary
  340. $this->boundary = $mainBoundary;
  341. // Add the attachments
  342. foreach ($this->attachments as $attachment) {
  343. $lines[] = '--'.$this->boundary;
  344. $lines[] = 'Content-Type: '.$attachment['mimetype'].'; name="'.$attachment['name'].'"';
  345. $lines[] = 'Content-Transfer-Encoding: base64';
  346. if ('inline' == $attachment['disposition']) {
  347. $lines[] = 'Content-ID: <'.$attachment['cid'].'>';
  348. }
  349. $lines[] = 'Content-Disposition: '.$attachment['disposition'].'; filename="'.$attachment['name'].'"';
  350. $lines[] = '';
  351. $lines[] = chunk_split(base64_encode(file_get_contents($attachment['path'])));
  352. }
  353. // Close the boundary delimiter
  354. $lines[] = '--'.$this->boundary.'--';
  355. }
  356. // Create the final body
  357. $this->body = '';
  358. foreach ($lines as $line) {
  359. $this->body .= $line.$this->eol;
  360. }
  361. }
  362. /**
  363. * Create the headers of the email.
  364. *
  365. * @return void
  366. */
  367. private function _createHeaders()
  368. {
  369. // Cleanup headers
  370. $this->headers = array();
  371. // Check if the message consists of just a "plain" single item
  372. if (false === strpos($this->contentType, 'multipart')) {
  373. // Content-Disposition: inline
  374. $this->headers['Content-Disposition'] = $this->contentDisposition;
  375. // Content-Type
  376. $this->headers['Content-Type'] = $this->contentType.'; format=flowed; charset="'.$this->charset.'"';
  377. // Content-Transfer-Encoding: 7bit
  378. $this->headers['Content-Transfer-Encoding'] = '7bit';
  379. } else {
  380. // Content-Type
  381. $this->headers['Content-Type'] = $this->contentType.'; boundary="'.$this->boundary.'"';
  382. }
  383. // Date
  384. $this->headers['Date'] = self::getDate(self::getTime());
  385. // Disposition-Notification-To, RFC 3798
  386. $notifyTos = array();
  387. foreach($this->_notifyTo as $address => $name) {
  388. $notifyTos[] = (empty($name) ? '' : $name.' ').'<'.$address.'>';
  389. }
  390. $notifyTo = implode(',', $notifyTos);
  391. if (!empty($notifyTo)) {
  392. $this->headers['Disposition-Notification-To'] = $notifyTo;
  393. }
  394. // From
  395. foreach ($this->_from as $address => $name) {
  396. $this->headers['From'] = (empty($name) ? '' : $name.' ').'<'.$address.'>';
  397. }
  398. // Message-Id
  399. $this->headers['Message-ID'] = $this->messageId;
  400. // MIME-Version: 1.0
  401. $this->headers['MIME-Version'] = '1.0';
  402. // Reply-To
  403. $this->headers['Reply-To'] = $this->headers['From'];
  404. foreach($this->_replyTo as $address => $name) {
  405. $this->headers['Reply-To'] = (empty($name) ? '' : $name.' ').'<'.$address.'>';
  406. }
  407. // Return-Path
  408. foreach($this->_from as $address => $name) {
  409. $this->headers['Return-Path'] = '<'.$address.'>';
  410. }
  411. foreach($this->_returnPath as $address => $name) {
  412. $this->headers['Return-Path'] = '<'.$address.'>';
  413. }
  414. // Sender
  415. $this->headers['Sender'] = $this->headers['From'];
  416. foreach($this->_sender as $address => $name) {
  417. $this->headers['Sender'] = (empty($name) ? '' : $name.' ').'<'.$address.'>';
  418. }
  419. // Subject. Note: it must be RFC 2047 compliant
  420. // TODO: wrap mb_encode_mimeheader() to add other content encodings
  421. $this->headers['Subject'] = PMF_Utils::resolveMarkers(
  422. html_entity_decode($this->subject, ENT_COMPAT, 'UTF-8'),
  423. $this->_config
  424. );
  425. // X-Mailer
  426. $this->headers['X-Mailer'] = $this->_mailer;
  427. // X-MSMail-Priority
  428. if (isset($this->priorities[(int)$this->priority])) {
  429. $this->headers['X-MSMail-Priority'] = $this->priorities[(int)$this->priority];
  430. }
  431. // X-Originating-IP
  432. if (isset($_SERVER['REMOTE_ADDR'])) {
  433. $this->headers['X-Originating-IP'] = $_SERVER['REMOTE_ADDR'];
  434. }
  435. // X-Priority
  436. $this->headers['X-Priority'] = $this->priority;
  437. }
  438. /**
  439. * Set just one e-mail address into an array.
  440. *
  441. * @param array $target Target array.
  442. * @param string $targetAlias Alias Target alias.
  443. * @param string $address User e-mail address.
  444. * @param string $name User name (optional).
  445. *
  446. * @return bool True if successful, false otherwise.
  447. */
  448. private function _setEmailTo(&$target, $targetAlias, $address, $name = null)
  449. {
  450. // Check for the permitted number of items into the $target array
  451. if (count($target) > 0) {
  452. $keys = array_keys($target);
  453. trigger_error(
  454. "<b>Mail Class</b>: a valid e-mail address, $keys[0], has been already added as '$targetAlias'!",
  455. E_USER_ERROR
  456. );
  457. return false;
  458. }
  459. return $this->_addEmailTo($target, $targetAlias, $address, $name);
  460. }
  461. /**
  462. * Add an attachment.
  463. *
  464. * @param string $path File path.
  465. * @param string $name File name. Defaults to the basename.
  466. * @param string $mimetype File MIME type. Defaults to 'application/octet-stream'.
  467. * @param string $disposition Attachment disposition. Defaults to 'attachment'.
  468. * @param string $cid Content ID, required when disposition is 'inline'. Defaults to ''.
  469. * @return bool True if successful, false otherwise.
  470. */
  471. public function addAttachment($path, $name = null, $mimetype = 'application/octet-stream', $disposition = 'attachment', $cid = '')
  472. {
  473. if (!file_exists($path)) {
  474. // File not found
  475. return false;
  476. } else if (('inline' == $disposition) && empty($cid)) {
  477. // Content ID is required
  478. return false;
  479. } else {
  480. if (empty($name)) {
  481. $name = basename($path);
  482. }
  483. $this->attachments[] = array(
  484. "cid" => $cid,
  485. "disposition" => $disposition,
  486. "mimetype" => $mimetype,
  487. "name" => $name,
  488. "path" => $path
  489. );
  490. return true;
  491. }
  492. }
  493. /**
  494. * Add a recipient as <BCC>.
  495. *
  496. * @param string $address User e-mail address.
  497. * @param string $name User name (optional).
  498. * @return bool True if successful, false otherwise.
  499. */
  500. public function addBcc($address, $name = null)
  501. {
  502. return $this->_addEmailTo($this->_bcc, 'Bcc', $address, $name);
  503. }
  504. /**
  505. * Add a recipient as <CC>.
  506. *
  507. * @param string $address User e-mail address.
  508. * @param string $name User name (optional).
  509. * @return bool True if successful, false otherwise.
  510. */
  511. public function addCc($address, $name = null)
  512. {
  513. return $this->_addEmailTo($this->_cc, 'Cc', $address, $name);
  514. }
  515. /**
  516. * Add an address to send a notification to.
  517. *
  518. * @param string $address User e-mail address.
  519. * @param string $name User name (optional).
  520. * @return bool True if successful, false otherwise.
  521. */
  522. public function addNotificationTo($address, $name = null)
  523. {
  524. return $this->_addEmailTo($this->_notifyTo, 'Disposition-Notification-To', $address, $name);
  525. }
  526. /**
  527. * Add a recipient as <TO>.
  528. *
  529. * @param string $address User e-mail address.
  530. * @param string $name User name (optional).
  531. * @return bool True if successful, false otherwise.
  532. */
  533. public function addTo($address, $name = null)
  534. {
  535. return $this->_addEmailTo($this->_to, 'To', $address, $name);
  536. }
  537. /**
  538. * Create a string to be used as a valid boundary value.
  539. *
  540. * @static
  541. * @return string The boundary value.
  542. */
  543. public static function createBoundary()
  544. {
  545. return '-----' .md5(microtime());
  546. }
  547. /**
  548. * Returns the given text being sure that any CR or LF has been fixed
  549. * according with RFC 2822 EOL setting.
  550. *
  551. * @param string $text Text with a mixed usage of CR, LF, CRLF.
  552. * @return string The fixed text.
  553. * @see eol
  554. */
  555. public function fixEOL($text)
  556. {
  557. // Assure that anything among CRLF, CR will be replaced with just LF
  558. $text = str_replace(
  559. array(
  560. "\r\n",// CRLF
  561. "\r", // CR
  562. "\n",// LF
  563. ),
  564. "\n", // LF
  565. $text
  566. );
  567. // Set any LF to the RFC 2822 EOL
  568. $text = str_replace("\n", $this->eol, $text);
  569. return $text;
  570. }
  571. /**
  572. * Returns the date according with RFC 2822.
  573. *
  574. * @static
  575. * @param string $date Unix timestamp.
  576. * @return string The RFC 2822 date if successful, false otherwise.
  577. */
  578. public static function getDate($date)
  579. {
  580. $rfc2822Date = date('r', $date);
  581. return $rfc2822Date;
  582. }
  583. /**
  584. * Returns the Unix timestamp with preference to the Page Request time.
  585. *
  586. * @static
  587. * @return int Unix timestamp.
  588. */
  589. public static function getTime()
  590. {
  591. if (isset($_SERVER['REQUEST_TIME'])) {
  592. return $_SERVER['REQUEST_TIME'];
  593. }
  594. return time();
  595. }
  596. /**
  597. * Get the instance of the class implementing the MUA for the given type.
  598. *
  599. * @static
  600. * @param string $mua Type of the MUA.
  601. *
  602. * @return mixed The class instance if successful, false otherwise.
  603. */
  604. public static function getMUA($mua)
  605. {
  606. $impl = ucfirst(
  607. str_replace(
  608. '-',
  609. '',
  610. $mua
  611. )
  612. );
  613. $class = 'PMF_Mail_'.$impl;
  614. return new $class;
  615. }
  616. /**
  617. * Returns the server name.
  618. *
  619. * @static
  620. * @return string The server name.
  621. */
  622. public static function getServerName()
  623. {
  624. $hostname = 'localhost.localdomain';
  625. if (isset($_SERVER['HTTP_HOST'])) {
  626. $hostname = $_SERVER['HTTP_HOST'];
  627. } else if (isset($_SERVER['SERVER_NAME'])) {
  628. $hostname = $_SERVER['SERVER_NAME'];
  629. }
  630. return $hostname;
  631. }
  632. /**
  633. * Send the e-mail according with the current settings.
  634. *
  635. * @return bool True if successful, false otherwise.
  636. *
  637. * @todo Enhance error handling using exceptions
  638. */
  639. public function send()
  640. {
  641. // Sanity check
  642. if (count($this->_to) + count($this->_cc) + count($this->_bcc) < 1) {
  643. trigger_error(
  644. "<b>Mail Class</b>: you need at least to set one recipient among TO, CC and BCC!",
  645. E_USER_ERROR
  646. );
  647. return false;
  648. }
  649. // Has any alternative message been provided?
  650. if (!empty($this->messageAlt)) {
  651. $this->contentType = 'multipart/alternative';
  652. }
  653. // Has any attachment been provided?
  654. if (!empty($this->attachments)) {
  655. $this->contentType = 'multipart/mixed';
  656. }
  657. // Has any in-line attachment been provided?
  658. $hasInlineAttachments = false;
  659. $idx = 0;
  660. while (!$hasInlineAttachments && ($idx < count($this->attachments))) {
  661. $hasInlineAttachments = ('inline' == $this->attachments[$idx]['disposition']);
  662. $idx++;
  663. }
  664. if ($hasInlineAttachments) {
  665. $this->contentType = 'multipart/related';
  666. }
  667. // A valid MUA needs to implement the PMF_Mail_IMUA interface
  668. // i.e. we must prepare recipients, headers, body for the send() method
  669. // Prepare the recipients
  670. $recipients = '';
  671. $to = array();
  672. foreach($this->_to as $address => $name) {
  673. $to[] = (empty($name) ? '' : $name.' ').'<'.$address.'>';
  674. }
  675. $recipients = implode(',', $to);
  676. // Check for the need of undisclosed recipients outlook-like <TO:>
  677. if (empty($recipients) && (0 == count($this->_cc))) {
  678. $recipients = '<Undisclosed-Recipient:;>';
  679. }
  680. // Prepare the headers
  681. $this->_createHeaders();
  682. // Prepare the body
  683. $this->_createBody();
  684. // Send the email adopting to the given MUA
  685. $sent = false;
  686. $mua = self::getMUA($this->agent);
  687. switch($this->agent) {
  688. case 'built-in':
  689. $sent = $mua->send($recipients, $this->headers, $this->body);
  690. break;
  691. default:
  692. trigger_error("<b>Mail Class</b>: $this->agent has no implementation!", E_USER_ERROR);
  693. $sent = false;
  694. }
  695. return $sent;
  696. }
  697. /**
  698. * Set the "From" address.
  699. *
  700. * @param string $address User e-mail address.
  701. * @param string $name User name (optional).
  702. * @return bool True if successful, false otherwise.
  703. */
  704. public function setFrom($address, $name = null)
  705. {
  706. return $this->_setEmailTo($this->_from, 'From', $address, $name);
  707. }
  708. /**
  709. * Set an HTML message providing also a plain text alternative message,
  710. * if not already set using the $messageAlt property.
  711. * Besides it is possible to put resources as inline attachments
  712. *
  713. * @param string $message HTML message.
  714. * @param bool $sanitize Strip out potentially unsecured HTML tags. Defaults to false.
  715. * @param bool $inline Add images as inline attachments. Defaults to false.
  716. *
  717. * @return void
  718. */
  719. public function setHTMLMessage($message, $sanitize = false, $inline = false)
  720. {
  721. // No Javascript at all
  722. // 1/2. <script blahblahblah>blahblahblah</tag>
  723. $message = PMF_String::preg_replace(
  724. '/(<script[^>]*>.*<\/script>)|<script[^\/]*\/>|<script[^\/]*>/is',
  725. '',
  726. $message
  727. );
  728. // Cleanup potentially dangerous HTML tags:
  729. if ($sanitize) {
  730. // 1/2. <tag blahblahblah>blahblahblah</tag>
  731. $message = PMF_String::preg_replace(
  732. '/<(applet|embed|head|meta|object|style|title)[^>]*>.*<\/\\1>/is',
  733. '',
  734. $message
  735. );
  736. // 2/2. <tag blahblahblah />
  737. $message = PMF_String::preg_replace(
  738. '/<(applet|embed|head|meta|object|style|title)[^\/]*\/>/is',
  739. '',
  740. $message
  741. );
  742. }
  743. if ($inline) {
  744. trigger_error(
  745. "<b>Mail Class</b>: inline option is not implemented yet.",
  746. E_USER_ERROR
  747. );
  748. }
  749. // Set the HTML text as the main message
  750. $this->message = trim($message);
  751. // If no alternative text has been provided yet, use just
  752. // the HTML message stripping any HTML tag
  753. if (empty($this->messageAlt)) {
  754. $this->messageAlt = trim(strip_tags($this->message));
  755. }
  756. }
  757. /**
  758. * Set the "Reply-to" address.
  759. *
  760. * @param string $address User e-mail address.
  761. * @param string $name User name (optional).
  762. * @return bool True if successful, false otherwise.
  763. */
  764. public function setReplyTo($address, $name = null)
  765. {
  766. return $this->_setEmailTo($this->_replyTo, 'Reply-To', $address, $name);
  767. }
  768. /**
  769. * Set the "Return-Path" address.
  770. *
  771. * @param string $address User e-mail address.
  772. * @return bool True if successful, false otherwise.
  773. */
  774. public function setReturnPath($address)
  775. {
  776. return $this->_setEmailTo($this->_returnPath, 'Return-Path', $address);
  777. }
  778. /**
  779. * Set the "Sender" address.
  780. *
  781. * @param string $address User e-mail address.
  782. * @param string $name User name (optional).
  783. * @return bool True if successful, false otherwise.
  784. */
  785. public function setSender($address, $name = null)
  786. {
  787. return $this->_setEmailTo($this->_sender, 'Sender', $address, $name);
  788. }
  789. /**
  790. * Remove any previous "From" address.
  791. *
  792. * @return bool True if successful, false otherwise.
  793. */
  794. public function unsetFrom()
  795. {
  796. $this->_from = array();
  797. return true;
  798. }
  799. /**
  800. * Validate an address as an e-mail address.
  801. *
  802. * @param string $address E-Mail address
  803. *
  804. * @return bool True if the given address is a valid e-mail address, false otherwise.
  805. */
  806. public static function validateEmail($address)
  807. {
  808. if (empty($address)) {
  809. return false;
  810. }
  811. if (PMF_String::strpos($address, '\0') !== false) {
  812. return false;
  813. }
  814. $unsafe = array ("\r", "\n");
  815. if ($address !== str_replace($unsafe, '', $address)) {
  816. return false;
  817. }
  818. if (false === filter_var($address, FILTER_VALIDATE_EMAIL)) {
  819. return false;
  820. }
  821. return true;
  822. }
  823. /**
  824. * Wraps the lines contained into the given message.
  825. *
  826. * @param string $message Message.
  827. * @param integer $width Column width. Defaults to 72.
  828. * @param boolean $cut Cutting a word is allowed. Defaults to false.
  829. *
  830. * @return string The given message, wrapped as requested.
  831. */
  832. public function wrapLines($message, $width = 72, $cut = false)
  833. {
  834. $message = $this->fixEOL($message);
  835. if (PMF_String::strpos(strtolower($this->charset), 'utf') !== false) {
  836. // PHP wordwrap() is not safe with multibyte UTF chars
  837. return $message;
  838. } else {
  839. $lines = explode($this->eol, $message);
  840. $wrapped = '';
  841. foreach ($lines as $value) {
  842. $wrapped .= (empty($wrapped) ? '' : $this->eol);
  843. $wrapped .= wordwrap($value, $width, $this->eol, $cut);
  844. }
  845. return $wrapped;
  846. }
  847. }
  848. /**
  849. * If the email spam protection has been activated from the general
  850. * phpMyFAQ configuration this method converts an email address e.g.
  851. * from "user@example.org" to "user_AT_example_DOT_org". Otherwise
  852. * it will return the plain email address.
  853. *
  854. * @param string $email E-mail address
  855. * @static
  856. *
  857. * @return string
  858. */
  859. public function safeEmail($email)
  860. {
  861. if ($this->_config->get('spam.enableSafeEmail')) {
  862. return str_replace ( array ('@', '.' ), array ('_AT_', '_DOT_' ), $email );
  863. } else {
  864. return $email;
  865. }
  866. }
  867. }