| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577 | <?php/** * @file * Common mail functions for sending e-mail. Originally written by Gerhard. * *   Allie Micka <allie at pajunas dot com> *//** * Attempts to RFC822-compliant headers for the mail message or its MIME parts. * * @todo Could use some enhancement and stress testing. * * @param array $headers *   An array of headers. * * @return string *   A string containing the headers. */function mimemail_rfc_headers($headers) {  $header = '';  $crlf = variable_get('mimemail_crlf', MAIL_LINE_ENDINGS);  foreach ($headers as $key => $value) {    $key = trim($key);    // Collapse spaces and get rid of newline characters.    $value = preg_replace('/(\s+|\n|\r|^\s|\s$)/', ' ', $value);    // Fold headers if they're too long.    // A CRLF may be inserted before any WSP.    // @see http://tools.ietf.org/html/rfc2822#section-2.2.3    if (drupal_strlen($value) > 60) {      // If there's a semicolon, use that to separate.      if (count($array = preg_split('/;\s*/', $value)) > 1) {        $value = trim(join(";$crlf ", $array));      }      else {        $value = wordwrap($value, 50, "$crlf ", FALSE);      }    }    $header .= $key . ": " . $value . $crlf;  }  return trim($header);}/** * Gives useful defaults for standard email headers. * * @param array $headers *   Message headers. * @param string $from *   The address of the sender. * * @return array *   Overwrited headers. */function mimemail_headers($headers, $from = NULL) {  $default_from = variable_get('site_mail', ini_get('sendmail_from'));  // Overwrite standard headers.  if ($from) {    if (!isset($headers['From']) || $headers['From'] == $default_from) {      $headers['From'] = $from;    }    if (!isset($headers['Sender']) || $headers['Sender'] == $default_from) {      $headers['Sender'] = $from;    }    // This may not work. The MTA may rewrite the Return-Path.    if (!isset($headers['Return-Path']) || $headers['Return-Path'] == $default_from) {      // According to IANA the current longest TLD is 23 characters.      if (preg_match('/[a-z\d\-\.\+_]+@(?:[a-z\d\-]+\.)+[a-z\d]{2,23}/i', $from, $matches)) {        $headers['Return-Path'] = "<$matches[0]>";      }    }  }  // Convert From header if it is an array.  if (is_array($headers['From'])) {    $headers['From'] = mimemail_address($headers['From']);  }  // Run all headers through mime_header_encode() to convert non-ascii  // characters to an rfc compliant string, similar to drupal_mail().  foreach ($headers as $key => $value) {    // According to RFC 2047 addresses MUST NOT be encoded.    if ($key !== 'From' && $key !== 'Sender') {      $headers[$key] = mime_header_encode($value);    }  }  return $headers;}/** * Extracts links to local images from HTML documents. * * @param string $html *   A string containing the HTML source of the message. * * @return array *   An array containing the document body and the extracted files like the following. *     array( *       array( *         'name' => document name *         'content' => html text, local image urls replaced by Content-IDs, *         'Content-Type' => 'text/html; charset=utf-8') *       array( *         'name' => file name, *         'file' => reference to local file, *         'Content-ID' => generated Content-ID, *         'Content-Type' => derived using mime_content_type if available, educated guess otherwise *        ) *     ) */function mimemail_extract_files($html) {  $pattern = '/(<link[^>]+href=[\'"]?|<object[^>]+codebase=[\'"]?|@import |[\s]src=[\'"]?)([^\'>"]+)([\'"]?)/mis';  $content = preg_replace_callback($pattern, '_mimemail_replace_files', $html);  $encoding = '8Bit';  $body = explode("\n", $content);  foreach ($body as $line) {    if (drupal_strlen($line) > 998) {      $encoding = 'base64';      break;    }  }  if ($encoding == 'base64') {    $content = rtrim(chunk_split(base64_encode($content)));  }  $document = array(array(    'Content-Type' => "text/html; charset=utf-8",    'Content-Transfer-Encoding' => $encoding,    'content' => $content,  ));  $files = _mimemail_file();  return array_merge($document, $files);}/** * Callback function for preg_replace_callback(). */function _mimemail_replace_files($matches) {  return stripslashes($matches[1]) . _mimemail_file($matches[2]) . stripslashes($matches[3]);}/** * Helper function to extract local files. * * @param string $url *   (optional) The URI or the absolute URL to the file. * @param string $content *   (optional) The actual file content. * @param string $name *   (optional) The file name. * @param string $type *   (optional) The file type. * @param string $disposition *   (optional) The content disposition. Defaults to inline. * * @return *   The Content-ID and/or an array of the files on success or the URL on failure. */function _mimemail_file($url = NULL, $content = NULL, $name = '', $type = '', $disposition = 'inline') {  static $files = array();  static $ids = array();  if ($url) {    $image = preg_match('!\.(png|gif|jpg|jpeg)$!i', $url);    $linkonly = variable_get('mimemail_linkonly', 0);    // The file exists on the server as-is. Allows for non-web-accessible files.    if (@is_file($url) && $image && !$linkonly) {      $file = $url;    }    else {      $url = _mimemail_url($url, 'TRUE');      // The $url is absolute, we're done here.      $scheme = file_uri_scheme($url);      if ($scheme == 'http' || $scheme == 'https' || preg_match('!mailto:!', $url)) {        return $url;      }      // The $url is a non-local URI that needs to be converted to a URL.      else {        $file = (drupal_realpath($url)) ? drupal_realpath($url) : file_create_url($url);      }    }  }  // We have the actual content.  elseif ($content) {    $file = $content;  }  if (isset($file)) {    $is_file = @is_file($file);    if ($is_file) {      $access = user_access('send arbitrary files');      $in_public_path = strpos(@drupal_realpath($file), drupal_realpath('public://')) === 0;      if (!$in_public_path && !$access) {        return $url;      }    }    if (!$name) {      $name = $is_file ? basename($file) : 'attachment.dat';    }    if (!$type) {      $type = $is_file ? file_get_mimetype($file) : file_get_mimetype($name);    }    $id = md5($file) .'@'. $_SERVER['HTTP_HOST'];    // Prevent duplicate items.    if (isset($ids[$id])) {      return 'cid:'. $ids[$id];    }    $new_file = array(      'name' => $name,      'file' => $file,      'Content-ID' => $id,      'Content-Disposition' => $disposition,      'Content-Type' => $type,    );    $files[] = $new_file;    $ids[$id] = $id;    return 'cid:' . $id;  }  // The $file does not exist and no $content, return the $url if possible.  elseif ($url) {    return $url;  }  $ret = $files;  $files = array();  $ids = array();  return $ret;}/** * Build a multipart body. * * @param array $parts *   An associative array containing the parts to be included: *   - name: A string containing the name of the attachment. *   - content: A string containing textual content. *   - file: A string containing file content. *   - Content-Type: A string containing the content type of either file or content. Mandatory *     for content, optional for file. If not present, it will be derived from file the file if *     mime_content_type is available. If not, application/octet-stream is used. *   - Content-Disposition: (optional) A string containing the disposition. Defaults to inline. *   - Content-Transfer-Encoding: (optional) Base64 is assumed for files, 8bit for other content. *   - Content-ID: (optional) for in-mail references to attachements. *   Name is mandatory, one of content and file is required, they are mutually exclusive. * @param string $content_type *   (optional) A string containing the content-type for the combined message. Defaults to *   multipart/mixed. * * @return array *   An associative array containing the following elements: *   - body: A string containing the MIME-encoded multipart body of a mail. *   - headers: An array that includes some headers for the mail to be sent. */function mimemail_multipart_body($parts, $content_type = 'multipart/mixed; charset=utf-8', $sub_part = FALSE) {  // Control variable to avoid boundary collision.  static $part_num = 0;  $boundary = sha1(uniqid($_SERVER['REQUEST_TIME'], TRUE)) . $part_num++;  $body = '';  $headers = array(    'Content-Type' => "$content_type; boundary=\"$boundary\"",  );  if (!$sub_part) {    $headers['MIME-Version'] = '1.0';    $body = "This is a multi-part message in MIME format.\n";  }  foreach ($parts as $part) {    $part_headers = array();    if (isset($part['Content-ID'])) {      $part_headers['Content-ID'] = '<' . $part['Content-ID'] . '>';    }    if (isset($part['Content-Type'])) {      $part_headers['Content-Type'] = $part['Content-Type'];    }    if (isset($part['Content-Disposition'])) {      $part_headers['Content-Disposition'] = $part['Content-Disposition'];    }    elseif (strpos($part['Content-Type'], 'multipart/alternative') === FALSE) {      $part_headers['Content-Disposition'] = 'inline';    }    if (isset($part['Content-Transfer-Encoding'])) {      $part_headers['Content-Transfer-Encoding'] = $part['Content-Transfer-Encoding'];    }    // Mail content provided as a string.    if (isset($part['content']) && $part['content']) {      if (!isset($part['Content-Transfer-Encoding'])) {        $part_headers['Content-Transfer-Encoding'] = '8bit';      }      $part_body = $part['content'];      if (isset($part['name'])) {        $part_headers['Content-Type'] .= '; name="' . $part['name'] . '"';        $part_headers['Content-Disposition'] .= '; filename="' . $part['name'] . '"';      }    // Mail content references in a filename.    }    else {      if (!isset($part['Content-Transfer-Encoding'])) {        $part_headers['Content-Transfer-Encoding'] = 'base64';      }      if (!isset($part['Content-Type'])) {        $part['Content-Type'] = file_get_mimetype($part['file']);      }      if (isset($part['name'])) {        $part_headers['Content-Type'] .= '; name="' . $part['name'] . '"';        $part_headers['Content-Disposition'] .= '; filename="' . $part['name'] . '"';      }      if (isset($part['file'])) {        $file = (@is_file($part['file'])) ? file_get_contents($part['file']) : $part['file'];        $part_body = chunk_split(base64_encode($file), 76, variable_get('mimemail_crlf', "\n"));      }    }    $body .= "\n--$boundary\n";    $body .= mimemail_rfc_headers($part_headers) . "\n\n";    $body .= isset($part_body) ? $part_body : '';  }  $body .= "\n--$boundary--\n";  return array('headers' => $headers, 'body' => $body);}/** * Callback for preg_replace_callback(). */function _mimemail_expand_links($matches) {  return $matches[1] . _mimemail_url($matches[2]);}/** * Generate a multipart message body with a text alternative for some HTML text. * * @param string $body *   The HTML message body. * @param string $subject *   The message subject. * @param boolean $plain *   (optional) Whether the recipient prefers plaintext-only messages. Defaults to FALSE. * @param string $plaintext *   (optional) The plaintext message body. * @param array $attachments *   (optional) The files to be attached to the message. * * @return array *   An associative array containing the following elements: *   - body: A string containing the MIME-encoded multipart body of a mail. *   - headers: An array that includes some headers for the mail to be sent. * * The first mime part is a multipart/alternative containing mime-encoded sub-parts for * HTML and plaintext. Each subsequent part is the required image or attachment. */function mimemail_html_body($body, $subject, $plain = FALSE, $plaintext = NULL, $attachments = array()) {  if (empty($plaintext)) {    // @todo Remove once filter_xss() can handle direct descendant selectors in inline CSS.    // @see http://drupal.org/node/1116930    // @see http://drupal.org/node/370903    // Pull out the message body.    preg_match('|<body.*?</body>|mis', $body, $matches);    $plaintext = drupal_html_to_text($matches[0]);  }  if ($plain) {    // Plain mail without attachment.    if (empty($attachments)) {      $content_type = 'text/plain';      return array(        'body' => $plaintext,        'headers' => array('Content-Type' => 'text/plain; charset=utf-8'),      );    }    // Plain mail with attachement.    else {      $content_type = 'multipart/mixed';      $parts = array(array(      'content' => $plaintext,        'Content-Type' => 'text/plain; charset=utf-8',      ));    }  }  else {    $content_type = 'multipart/mixed';    $plaintext_part = array('Content-Type' => 'text/plain; charset=utf-8', 'content' => $plaintext);    // Expand all local links.    $pattern = '/(<a[^>]+href=")([^"]*)/mi';    $body = preg_replace_callback($pattern, '_mimemail_expand_links', $body);    $mime_parts = mimemail_extract_files($body);    $content = array($plaintext_part, array_shift($mime_parts));    $content = mimemail_multipart_body($content, 'multipart/alternative', TRUE);    $parts = array(array('Content-Type' => $content['headers']['Content-Type'], 'content' => $content['body']));    if ($mime_parts) {      $parts = array_merge($parts, $mime_parts);      $content = mimemail_multipart_body($parts, 'multipart/related; type="multipart/alternative"', TRUE);      $parts = array(array('Content-Type' => $content['headers']['Content-Type'], 'content' => $content['body']));    }  }  if (is_array($attachments) && !empty($attachments)) {    foreach ($attachments as $a) {      $a = (object) $a;      $path = isset($a->uri) ? $a->uri : (isset($a->filepath) ? $a->filepath : NULL);      $content = isset($a->filecontent) ? $a->filecontent : NULL;      $name = isset($a->filename) ? $a->filename : NULL;      $type = isset($a->filemime) ? $a->filemime : NULL;      _mimemail_file($path, $content, $name, $type, 'attachment');      $parts = array_merge($parts, _mimemail_file());    }  }  return mimemail_multipart_body($parts, $content_type);}/** * Helper function to format URLs. * * @param string $url *   The file path. * @param boolean $to_embed *   (optional) Wheter the URL is used to embed the file. Defaults to NULL. * * @return string *   A processed URL. */function _mimemail_url($url, $to_embed = NULL) {  global $base_url;  $url = urldecode($url);  $to_link = variable_get('mimemail_linkonly', 0);  $is_image = preg_match('!\.(png|gif|jpg|jpeg)!i', $url);  $is_absolute = file_uri_scheme($url) != FALSE || preg_match('!(mailto|callto|tel)\:!', $url);  if (!$to_embed) {    if ($is_absolute) {      return str_replace(' ', '%20', $url);    }  }  else {    $url = preg_replace('!^' . base_path() . '!', '', $url, 1);    if ($is_image) {      // Remove security token from URL, this allows for styled image embedding.      // @see https://drupal.org/drupal-7.20-release-notes      $url = preg_replace('/\\?itok=.*$/', '', $url);      if ($to_link) {        // Exclude images from embedding if needed.        $url = file_create_url($url);        $url = str_replace(' ', '%20', $url);      }    }    return $url;  }  $url = str_replace('?q=', '', $url);  @list($url, $fragment) = explode('#', $url, 2);  @list($path, $query) = explode('?', $url, 2);  // If we're dealing with an intra-document reference, return it.  if (empty($path)) {    return '#' . $fragment;  }  // Get a list of enabled languages.  $languages = language_list('enabled');  $languages = $languages[1];  // Default language settings.  $prefix = '';  $language = language_default();  // Check for language prefix.  $path = trim($path, '/');  $args = explode('/', $path);  foreach ($languages as $lang) {    if (!empty($args) && $args[0] == $lang->prefix) {      $prefix = array_shift($args);      $language = $lang;      $path = implode('/', $args);      break;    }  }  $options = array(    'query' => ($query) ? drupal_get_query_array($query) : array(),    'fragment' => $fragment,    'absolute' => TRUE,    'language' => $language,    'prefix' => $prefix,  );  $url = url($path, $options);  // If url() added a ?q= where there should not be one, remove it.  if (preg_match('!^\?q=*!', $url)) {    $url = preg_replace('!\?q=!', '', $url);  }  $url = str_replace('+', '%2B', $url);  return $url;}/** * Formats an address string. * * @todo Could use some enhancement and stress testing. * * @param mixed $address *   A user object, a text email address or an array containing name, mail. * @param boolean $simplify *   Determines if the address needs to be simplified. Defaults to FALSE. * * @return string *   A formatted address string or FALSE. */function mimemail_address($address, $simplify = FALSE) {  if (is_array($address)) {    // It's an array containing 'mail' and/or 'name'.    if (isset($address['mail'])) {      $output = '';      if (empty($address['name']) || $simplify) {        return $address['mail'];      }      else {        return '"' . addslashes(mime_header_encode($address['name'])) . '" <' . $address['mail'] . '>';      }    }    // It's an array of address items.    $addresses = array();    foreach ($address as $a) {      $addresses[] = mimemail_address($a);    }    return $addresses;  }  // It's a user object.  if (is_object($address) && isset($address->mail)) {    if (empty($address->name) || $simplify) {      return $address->mail;    }    else {      return '"' . addslashes(mime_header_encode($address->name)) . '" <' . $address->mail . '>';    }  }  // It's formatted or unformatted string.  // @todo: shouldn't assume it's valid - should try to re-parse  if (is_string($address)) {    return $address;  }  return FALSE;}
 |