Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 39 and 401]

   1  <?php
   2  /**
   3   * Copyright 2007-2017 Horde LLC (http://www.horde.org/)
   4   *
   5   * See the enclosed file LICENSE for license information (LGPL). If you
   6   * did not receive this file, see http://www.horde.org/licenses/lgpl21.
   7   *
   8   * @category  Horde
   9   * @copyright 2007-2017 Horde LLC
  10   * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
  11   * @package   Mime
  12   */
  13  
  14  /**
  15   * The Horde_Mime_Mail:: class wraps around the various MIME library classes
  16   * to provide a simple interface for creating and sending MIME messages.
  17   *
  18   * All content has to be passed UTF-8 encoded. The charset parameters is used
  19   * for the generated message only.
  20   *
  21   * @author    Jan Schneider <jan@horde.org>
  22   * @category  Horde
  23   * @copyright 2007-2017 Horde LLC
  24   * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
  25   * @package   Mime
  26   */
  27  class Horde_Mime_Mail
  28  {
  29      /**
  30       * The message headers.
  31       *
  32       * @var Horde_Mime_Headers
  33       */
  34      protected $_headers;
  35  
  36      /**
  37       * The base MIME part.
  38       *
  39       * @var Horde_Mime_Part
  40       */
  41      protected $_base;
  42  
  43      /**
  44       * The main body part.
  45       *
  46       * @var Horde_Mime_Part
  47       */
  48      protected $_body;
  49  
  50      /**
  51       * The main HTML body part.
  52       *
  53       * @var Horde_Mime_Part
  54       */
  55      protected $_htmlBody;
  56  
  57      /**
  58       * The message recipients.
  59       *
  60       * @var Horde_Mail_Rfc822_List
  61       */
  62      protected $_recipients;
  63  
  64      /**
  65       * Bcc recipients.
  66       *
  67       * @var string
  68       */
  69      protected $_bcc;
  70  
  71      /**
  72       * All MIME parts except the main body part.
  73       *
  74       * @var array
  75       */
  76      protected $_parts = array();
  77  
  78      /**
  79       * The Mail driver name.
  80       *
  81       * @link http://pear.php.net/Mail
  82       * @var string
  83       */
  84      protected $_mailer_driver = 'smtp';
  85  
  86      /**
  87       * The charset to use for the message.
  88       *
  89       * @var string
  90       */
  91      protected $_charset = 'UTF-8';
  92  
  93      /**
  94       * The Mail driver parameters.
  95       *
  96       * @link http://pear.php.net/Mail
  97       * @var array
  98       */
  99      protected $_mailer_params = array();
 100  
 101      /**
 102       * Constructor.
 103       *
 104       * @param array $params  A hash with basic message information. 'charset'
 105       *                       is the character set of the message.  'body' is
 106       *                       the message body. All other parameters are
 107       *                       assumed to be message headers.
 108       *
 109       * @throws Horde_Mime_Exception
 110       */
 111      public function __construct($params = array())
 112      {
 113          /* Set SERVER_NAME. */
 114          if (!isset($_SERVER['SERVER_NAME'])) {
 115              $_SERVER['SERVER_NAME'] = php_uname('n');
 116          }
 117  
 118          $this->_headers = new Horde_Mime_Headers();
 119  
 120          if (isset($params['charset'])) {
 121              $this->_charset = $params['charset'];
 122              unset($params['charset']);
 123          }
 124  
 125          if (isset($params['body'])) {
 126              $this->setBody($params['body'], $this->_charset);
 127              unset($params['body']);
 128          }
 129  
 130          $this->addHeaders($params);
 131  
 132          $this->clearRecipients();
 133      }
 134  
 135      /**
 136       * Adds several message headers at once.
 137       *
 138       * @param array $header    Hash with header names as keys and header
 139       *                         contents as values.
 140       *
 141       * @throws Horde_Mime_Exception
 142       */
 143      public function addHeaders($headers = array())
 144      {
 145          foreach ($headers as $header => $value) {
 146              $this->addHeader($header, $value);
 147          }
 148      }
 149  
 150      /**
 151       * Adds a message header.
 152       *
 153       * @param string $header      The header name.
 154       * @param string $value       The header value.
 155       * @param boolean $overwrite  If true, an existing header of the same name
 156       *                            is being overwritten; if false, multiple
 157       *                            headers are added; if null, the correct
 158       *                            behaviour is automatically chosen depending
 159       *                            on the header name.
 160       *
 161       * @throws Horde_Mime_Exception
 162       */
 163      public function addHeader($header, $value, $overwrite = null)
 164      {
 165          $lc_header = Horde_String::lower($header);
 166  
 167          if (is_null($overwrite) &&
 168              in_array($lc_header, $this->_headers->singleFields(true))) {
 169              $overwrite = true;
 170          }
 171  
 172          if ($overwrite) {
 173              $this->_headers->removeHeader($header);
 174          }
 175  
 176          if ($lc_header === 'bcc') {
 177              $this->_bcc = $value;
 178          } else {
 179              $this->_headers->addHeader($header, $value);
 180          }
 181      }
 182  
 183      /**
 184       * Add a Horde_Mime_Headers_Element object to the current header list.
 185       *
 186       * @since 2.5.0
 187       *
 188       * @param Horde_Mime_Headers_Element $ob  Header object to add.
 189       *
 190       * @throws InvalidArgumentException
 191       */
 192      public function addHeaderOb(Horde_Mime_Headers_Element $ob)
 193      {
 194          $this->_headers->addHeaderOb($ob, true);
 195      }
 196  
 197      /**
 198       * Removes a message header.
 199       *
 200       * @param string $header  The header name.
 201       */
 202      public function removeHeader($header)
 203      {
 204          if (Horde_String::lower($header) === 'bcc') {
 205              unset($this->_bcc);
 206          } else {
 207              $this->_headers->removeHeader($header);
 208          }
 209      }
 210  
 211      /**
 212       * Sets the message body text.
 213       *
 214       * @param string $body             The message content.
 215       * @param string $charset          The character set of the message.
 216       * @param boolean|integer $wrap    If true, wrap the message at column 76;
 217       *                                 If an integer wrap the message at that
 218       *                                 column. Don't use wrapping if sending
 219       *                                 flowed messages.
 220       */
 221      public function setBody($body, $charset = null, $wrap = false)
 222      {
 223          if (!$charset) {
 224              $charset = $this->_charset;
 225          }
 226          $body = Horde_String::convertCharset($body, 'UTF-8', $charset);
 227          if ($wrap) {
 228              $body = Horde_String::wrap($body, $wrap === true ? 76 : $wrap);
 229          }
 230          $this->_body = new Horde_Mime_Part();
 231          $this->_body->setType('text/plain');
 232          $this->_body->setCharset($charset);
 233          $this->_body->setContents($body);
 234          $this->_base = null;
 235      }
 236  
 237      /**
 238       * Sets the HTML message body text.
 239       *
 240       * @param string $body          The message content.
 241       * @param string $charset       The character set of the message.
 242       * @param boolean $alternative  If true, a multipart/alternative message is
 243       *                              created and the text/plain part is
 244       *                              generated automatically. If false, a
 245       *                              text/html message is generated.
 246       */
 247      public function setHtmlBody($body, $charset = null, $alternative = true)
 248      {
 249          if (!$charset) {
 250              $charset = $this->_charset;
 251          }
 252          $this->_htmlBody = new Horde_Mime_Part();
 253          $this->_htmlBody->setType('text/html');
 254          $this->_htmlBody->setCharset($charset);
 255          $this->_htmlBody->setContents($body);
 256          if ($alternative) {
 257              $this->setBody(Horde_Text_Filter::filter($body, 'Html2text', array('charset' => $charset, 'wrap' => false)), $charset);
 258          }
 259          $this->_base = null;
 260      }
 261  
 262      /**
 263       * Adds a message part.
 264       *
 265       * @param string $mime_type    The content type of the part.
 266       * @param string $content      The content of the part.
 267       * @param string $charset      The character set of the part.
 268       * @param string $disposition  The content disposition of the part.
 269       *
 270       * @return integer  The part number.
 271       */
 272      public function addPart($mime_type, $content, $charset = 'us-ascii',
 273                              $disposition = null)
 274      {
 275          $part = new Horde_Mime_Part();
 276          $part->setType($mime_type);
 277          $part->setCharset($charset);
 278          $part->setDisposition($disposition);
 279          $part->setContents($content);
 280          return $this->addMimePart($part);
 281      }
 282  
 283      /**
 284       * Adds a MIME message part.
 285       *
 286       * @param Horde_Mime_Part $part  A Horde_Mime_Part object.
 287       *
 288       * @return integer  The part number.
 289       */
 290      public function addMimePart($part)
 291      {
 292          $this->_parts[] = $part;
 293          return count($this->_parts) - 1;
 294      }
 295  
 296      /**
 297       * Sets the base MIME part.
 298       *
 299       * If the base part is set, any text bodies will be ignored when building
 300       * the message.
 301       *
 302       * @param Horde_Mime_Part $part  A Horde_Mime_Part object.
 303       */
 304      public function setBasePart($part)
 305      {
 306          $this->_base = $part;
 307      }
 308  
 309      /**
 310       * Adds an attachment.
 311       *
 312       * @param string $file     The path to the file.
 313       * @param string $name     The file name to use for the attachment.
 314       * @param string $type     The content type of the file.
 315       * @param string $charset  The character set of the part (only relevant for
 316       *                         text parts.
 317       *
 318       * @return integer  The part number.
 319       */
 320      public function addAttachment($file, $name = null, $type = null,
 321                                    $charset = 'us-ascii')
 322      {
 323          if (empty($name)) {
 324              $name = basename($file);
 325          }
 326  
 327          if (empty($type)) {
 328              $type = Horde_Mime_Magic::filenameToMime($file, false);
 329          }
 330  
 331          $num = $this->addPart($type, file_get_contents($file), $charset, 'attachment');
 332          $this->_parts[$num]->setName($name);
 333          return $num;
 334      }
 335  
 336      /**
 337       * Removes a message part.
 338       *
 339       * @param integer $part  The part number.
 340       */
 341      public function removePart($part)
 342      {
 343          if (isset($this->_parts[$part])) {
 344              unset($this->_parts[$part]);
 345          }
 346      }
 347  
 348      /**
 349       * Removes all (additional) message parts but leaves the body parts
 350       * untouched.
 351       */
 352      public function clearParts()
 353      {
 354          $this->_parts = array();
 355      }
 356  
 357      /**
 358       * Adds message recipients.
 359       *
 360       * Recipients specified by To:, Cc:, or Bcc: headers are added
 361       * automatically.
 362       *
 363       * @param string|array  List of recipients, either as a comma separated
 364       *                      list or as an array of email addresses.
 365       *
 366       * @throws Horde_Mime_Exception
 367       */
 368      public function addRecipients($recipients)
 369      {
 370          $this->_recipients->add($recipients);
 371      }
 372  
 373      /**
 374       * Removes message recipients.
 375       *
 376       * @param string|array  List of recipients, either as a comma separated
 377       *                      list or as an array of email addresses.
 378       *
 379       * @throws Horde_Mime_Exception
 380       */
 381      public function removeRecipients($recipients)
 382      {
 383          $this->_recipients->remove($recipients);
 384      }
 385  
 386      /**
 387       * Removes all message recipients.
 388       */
 389      public function clearRecipients()
 390      {
 391          $this->_recipients = new Horde_Mail_Rfc822_List();
 392      }
 393  
 394      /**
 395       * Sends this message.
 396       *
 397       * @param Horde_Mail_Transport $mailer A Horde_Mail_Transport object.
 398       * @param boolean $resend              If true, the message id and date are re-used;
 399       *                                     If false, they will be updated.
 400       * @param boolean $flowed              Send message in flowed text format.
 401       *
 402       * @throws Horde_Mime_Exception
 403       */
 404      public function send($mailer, $resend = false, $flowed = true)
 405      {
 406          /* Add mandatory headers if missing. */
 407          if (!$resend || !isset($this->_headers['Message-ID'])) {
 408              $this->_headers->addHeaderOb(
 409                  Horde_Mime_Headers_MessageId::create()
 410              );
 411          }
 412          if (!isset($this->_headers['User-Agent'])) {
 413              $this->_headers->addHeaderOb(
 414                  Horde_Mime_Headers_UserAgent::create()
 415              );
 416          }
 417          if (!$resend || !isset($this->_headers['Date'])) {
 418              $this->_headers->addHeaderOb(Horde_Mime_Headers_Date::create());
 419          }
 420  
 421          if (isset($this->_base)) {
 422              $basepart = $this->_base;
 423          } else {
 424              /* Send in flowed format. */
 425              if ($flowed && !empty($this->_body)) {
 426                  $flowed = new Horde_Text_Flowed($this->_body->getContents(), $this->_body->getCharset());
 427                  $flowed->setDelSp(true);
 428                  $this->_body->setContentTypeParameter('format', 'flowed');
 429                  $this->_body->setContentTypeParameter('DelSp', 'Yes');
 430                  $this->_body->setContents($flowed->toFlowed());
 431              }
 432  
 433              /* Build mime message. */
 434              $body = new Horde_Mime_Part();
 435              if (!empty($this->_body) && !empty($this->_htmlBody)) {
 436                  $body->setType('multipart/alternative');
 437                  $this->_body->setDescription(Horde_Mime_Translation::t("Plaintext Version of Message"));
 438                  $body[] = $this->_body;
 439                  $this->_htmlBody->setDescription(Horde_Mime_Translation::t("HTML Version of Message"));
 440                  $body[] = $this->_htmlBody;
 441              } elseif (!empty($this->_htmlBody)) {
 442                  $body = $this->_htmlBody;
 443              } elseif (!empty($this->_body)) {
 444                  $body = $this->_body;
 445              }
 446              if (count($this->_parts)) {
 447                  $basepart = new Horde_Mime_Part();
 448                  $basepart->setType('multipart/mixed');
 449                  $basepart->isBasePart(true);
 450                  if ($body) {
 451                      $basepart[] = $body;
 452                  }
 453                  foreach ($this->_parts as $mime_part) {
 454                      $basepart[] = $mime_part;
 455                  }
 456              } else {
 457                  $basepart = $body;
 458                  $basepart->isBasePart(true);
 459              }
 460          }
 461          $basepart->setHeaderCharset($this->_charset);
 462  
 463          /* Build recipients. */
 464          $recipients = clone $this->_recipients;
 465          foreach (array('to', 'cc') as $header) {
 466              if ($h = $this->_headers[$header]) {
 467                  $recipients->add($h->getAddressList());
 468              }
 469          }
 470          if ($this->_bcc) {
 471              $recipients->add($this->_bcc);
 472          }
 473  
 474          /* Trick Horde_Mime_Part into re-generating the message headers. */
 475          $this->_headers->removeHeader('MIME-Version');
 476  
 477          /* Send message. */
 478          $recipients->unique();
 479          $basepart->send($recipients->writeAddress(), $this->_headers, $mailer);
 480  
 481          /* Remember the basepart */
 482          $this->_base = $basepart;
 483      }
 484  
 485      /**
 486       * Get the raw email data sent by this object.
 487       *
 488       * @param  boolean $stream  If true, return a stream resource, otherwise
 489       *                          a string is returned.
 490       *
 491       * @return resource|string  The raw email data.
 492       * @since 2.4.0
 493       */
 494      public function getRaw($stream = true)
 495      {
 496          if ($stream) {
 497              $hdr = new Horde_Stream();
 498              $hdr->add($this->_headers->toString(), true);
 499              return Horde_Stream_Wrapper_Combine::getStream(
 500                  array($hdr->stream,
 501                        $this->getBasePart()->toString(
 502                          array('stream' => true, 'encode' => Horde_Mime_Part::ENCODE_7BIT | Horde_Mime_Part::ENCODE_8BIT | Horde_Mime_Part::ENCODE_BINARY))
 503                  )
 504              );
 505          }
 506  
 507          return $this->_headers->toString() . $this->getBasePart()->toString();
 508      }
 509  
 510      /**
 511       * Return the base MIME part.
 512       *
 513       * @return Horde_Mime_Part
 514       */
 515      public function getBasePart()
 516      {
 517          if (empty($this->_base)) {
 518              throw new Horde_Mail_Exception('No base part set.');
 519          }
 520  
 521          return $this->_base;
 522      }
 523  
 524  }