| [ Index ] |
PHP Cross Reference of Drupal 6 (gatewave) |
[Summary view] [Print] [Text view]
1 <?php 2 // $Id: mail.inc,v 1.8.2.8 2010/06/02 12:07:24 goba Exp $ 3 4 /** 5 * Compose and optionally send an e-mail message. 6 * 7 * Sending an e-mail works with defining an e-mail template (subject, text 8 * and possibly e-mail headers) and the replacement values to use in the 9 * appropriate places in the template. Processed e-mail templates are 10 * requested from hook_mail() from the module sending the e-mail. Any module 11 * can modify the composed e-mail message array using hook_mail_alter(). 12 * Finally drupal_mail_send() sends the e-mail, which can be reused 13 * if the exact same composed e-mail is to be sent to multiple recipients. 14 * 15 * Finding out what language to send the e-mail with needs some consideration. 16 * If you send e-mail to a user, her preferred language should be fine, so 17 * use user_preferred_language(). If you send email based on form values 18 * filled on the page, there are two additional choices if you are not 19 * sending the e-mail to a user on the site. You can either use the language 20 * used to generate the page ($language global variable) or the site default 21 * language. See language_default(). The former is good if sending e-mail to 22 * the person filling the form, the later is good if you send e-mail to an 23 * address previously set up (like contact addresses in a contact form). 24 * 25 * Taking care of always using the proper language is even more important 26 * when sending e-mails in a row to multiple users. Hook_mail() abstracts 27 * whether the mail text comes from an administrator setting or is 28 * static in the source code. It should also deal with common mail tokens, 29 * only receiving $params which are unique to the actual e-mail at hand. 30 * 31 * An example: 32 * 33 * @code 34 * function example_notify($accounts) { 35 * foreach ($accounts as $account) { 36 * $params['account'] = $account; 37 * // example_mail() will be called based on the first drupal_mail() parameter. 38 * drupal_mail('example', 'notice', $account->mail, user_preferred_language($account), $params); 39 * } 40 * } 41 * 42 * function example_mail($key, &$message, $params) { 43 * $language = $message['language']; 44 * $variables = user_mail_tokens($params['account'], $language); 45 * switch($key) { 46 * case 'notice': 47 * $message['subject'] = t('Notification from !site', $variables, $language->language); 48 * $message['body'][] = t("Dear !username\n\nThere is new content available on the site.", $variables, $language->language); 49 * break; 50 * } 51 * } 52 * @endcode 53 * 54 * @param $module 55 * A module name to invoke hook_mail() on. The {$module}_mail() hook will be 56 * called to complete the $message structure which will already contain common 57 * defaults. 58 * @param $key 59 * A key to identify the e-mail sent. The final e-mail id for e-mail altering 60 * will be {$module}_{$key}. 61 * @param $to 62 * The e-mail address or addresses where the message will be sent to. The 63 * formatting of this string must comply with RFC 2822. Some examples are: 64 * user@example.com 65 * user@example.com, anotheruser@example.com 66 * User <user@example.com> 67 * User <user@example.com>, Another User <anotheruser@example.com> 68 * @param $language 69 * Language object to use to compose the e-mail. 70 * @param $params 71 * Optional parameters to build the e-mail. 72 * @param $from 73 * Sets From to this value, if given. 74 * @param $send 75 * Send the message directly, without calling drupal_mail_send() manually. 76 * @return 77 * The $message array structure containing all details of the 78 * message. If already sent ($send = TRUE), then the 'result' element 79 * will contain the success indicator of the e-mail, failure being already 80 * written to the watchdog. (Success means nothing more than the message being 81 * accepted at php-level, which still doesn't guarantee it to be delivered.) 82 */ 83 function drupal_mail($module, $key, $to, $language, $params = array(), $from = NULL, $send = TRUE) { 84 $default_from = variable_get('site_mail', ini_get('sendmail_from')); 85 86 // Bundle up the variables into a structured array for altering. 87 $message = array( 88 'id' => $module .'_'. $key, 89 'to' => $to, 90 'from' => isset($from) ? $from : $default_from, 91 'language' => $language, 92 'params' => $params, 93 'subject' => '', 94 'body' => array() 95 ); 96 97 // Build the default headers 98 $headers = array( 99 'MIME-Version' => '1.0', 100 'Content-Type' => 'text/plain; charset=UTF-8; format=flowed; delsp=yes', 101 'Content-Transfer-Encoding' => '8Bit', 102 'X-Mailer' => 'Drupal' 103 ); 104 if ($default_from) { 105 // To prevent e-mail from looking like spam, the addresses in the Sender and 106 // Return-Path headers should have a domain authorized to use the originating 107 // SMTP server. Errors-To is redundant, but shouldn't hurt. 108 $headers['From'] = $headers['Sender'] = $headers['Return-Path'] = $headers['Errors-To'] = $default_from; 109 } 110 if ($from) { 111 $headers['From'] = $from; 112 } 113 $message['headers'] = $headers; 114 115 // Build the e-mail (get subject and body, allow additional headers) by 116 // invoking hook_mail() on this module. We cannot use module_invoke() as 117 // we need to have $message by reference in hook_mail(). 118 if (function_exists($function = $module .'_mail')) { 119 $function($key, $message, $params); 120 } 121 122 // Invoke hook_mail_alter() to allow all modules to alter the resulting e-mail. 123 drupal_alter('mail', $message); 124 125 // Concatenate and wrap the e-mail body. 126 $message['body'] = is_array($message['body']) ? drupal_wrap_mail(implode("\n\n", $message['body'])) : drupal_wrap_mail($message['body']); 127 128 // Optionally send e-mail. 129 if ($send) { 130 $message['result'] = drupal_mail_send($message); 131 132 // Log errors 133 if (!$message['result']) { 134 watchdog('mail', 'Error sending e-mail (from %from to %to).', array('%from' => $message['from'], '%to' => $message['to']), WATCHDOG_ERROR); 135 drupal_set_message(t('Unable to send e-mail. Please contact the site administrator if the problem persists.'), 'error'); 136 } 137 } 138 139 return $message; 140 } 141 142 /** 143 * Send an e-mail message, using Drupal variables and default settings. 144 * More information in the <a href="http://php.net/manual/en/function.mail.php"> 145 * PHP function reference for mail()</a>. See drupal_mail() for information on 146 * how $message is composed. 147 * 148 * @param $message 149 * Message array with at least the following elements: 150 * - id 151 * A unique identifier of the e-mail type. Examples: 'contact_user_copy', 152 * 'user_password_reset'. 153 * - to 154 * The mail address or addresses where the message will be sent to. The 155 * formatting of this string must comply with RFC 2822. Some examples are: 156 * user@example.com 157 * user@example.com, anotheruser@example.com 158 * User <user@example.com> 159 * User <user@example.com>, Another User <anotheruser@example.com> 160 * - subject 161 * Subject of the e-mail to be sent. This must not contain any newline 162 * characters, or the mail may not be sent properly. 163 * - body 164 * Message to be sent. Accepts both CRLF and LF line-endings. 165 * E-mail bodies must be wrapped. You can use drupal_wrap_mail() for 166 * smart plain text wrapping. 167 * - headers 168 * Associative array containing all mail headers. 169 * @return 170 * Returns TRUE if the mail was successfully accepted for delivery, 171 * FALSE otherwise. 172 */ 173 function drupal_mail_send($message) { 174 // Allow for a custom mail backend. 175 if (variable_get('smtp_library', '') && file_exists(variable_get('smtp_library', ''))) { 176 include_once './'. variable_get('smtp_library', ''); 177 return drupal_mail_wrapper($message); 178 } 179 else { 180 $mimeheaders = array(); 181 foreach ($message['headers'] as $name => $value) { 182 $mimeheaders[] = $name .': '. mime_header_encode($value); 183 } 184 return mail( 185 $message['to'], 186 mime_header_encode($message['subject']), 187 // Note: e-mail uses CRLF for line-endings, but PHP's API requires LF. 188 // They will appear correctly in the actual e-mail that is sent. 189 str_replace("\r", '', $message['body']), 190 // For headers, PHP's API suggests that we use CRLF normally, 191 // but some MTAs incorrecly replace LF with CRLF. See #234403. 192 join("\n", $mimeheaders) 193 ); 194 } 195 } 196 197 /** 198 * Perform format=flowed soft wrapping for mail (RFC 3676). 199 * 200 * We use delsp=yes wrapping, but only break non-spaced languages when 201 * absolutely necessary to avoid compatibility issues. 202 * 203 * We deliberately use LF rather than CRLF, see drupal_mail(). 204 * 205 * @param $text 206 * The plain text to process. 207 * @param $indent (optional) 208 * A string to indent the text with. Only '>' characters are repeated on 209 * subsequent wrapped lines. Others are replaced by spaces. 210 */ 211 function drupal_wrap_mail($text, $indent = '') { 212 // Convert CRLF into LF. 213 $text = str_replace("\r", '', $text); 214 // See if soft-wrapping is allowed. 215 $clean_indent = _drupal_html_to_text_clean($indent); 216 $soft = strpos($clean_indent, ' ') === FALSE; 217 // Check if the string has line breaks. 218 if (strpos($text, "\n") !== FALSE) { 219 // Remove trailing spaces to make existing breaks hard. 220 $text = preg_replace('/ +\n/m', "\n", $text); 221 // Wrap each line at the needed width. 222 $lines = explode("\n", $text); 223 array_walk($lines, '_drupal_wrap_mail_line', array('soft' => $soft, 'length' => strlen($indent))); 224 $text = implode("\n", $lines); 225 } 226 else { 227 // Wrap this line. 228 _drupal_wrap_mail_line($text, 0, array('soft' => $soft, 'length' => strlen($indent))); 229 } 230 // Empty lines with nothing but spaces. 231 $text = preg_replace('/^ +\n/m', "\n", $text); 232 // Space-stuff special lines. 233 $text = preg_replace('/^(>| |From)/m', ' $1', $text); 234 // Apply indentation. We only include non-'>' indentation on the first line. 235 $text = $indent . substr(preg_replace('/^/m', $clean_indent, $text), strlen($indent)); 236 237 return $text; 238 } 239 240 /** 241 * Transform an HTML string into plain text, preserving the structure of the 242 * markup. Useful for preparing the body of a node to be sent by e-mail. 243 * 244 * The output will be suitable for use as 'format=flowed; delsp=yes' text 245 * (RFC 3676) and can be passed directly to drupal_mail() for sending. 246 * 247 * We deliberately use LF rather than CRLF, see drupal_mail(). 248 * 249 * This function provides suitable alternatives for the following tags: 250 * <a> <em> <i> <strong> <b> <br> <p> <blockquote> <ul> <ol> <li> <dl> <dt> 251 * <dd> <h1> <h2> <h3> <h4> <h5> <h6> <hr> 252 * 253 * @param $string 254 * The string to be transformed. 255 * @param $allowed_tags (optional) 256 * If supplied, a list of tags that will be transformed. If omitted, all 257 * all supported tags are transformed. 258 * @return 259 * The transformed string. 260 */ 261 function drupal_html_to_text($string, $allowed_tags = NULL) { 262 // Cache list of supported tags. 263 static $supported_tags; 264 if (empty($supported_tags)) { 265 $supported_tags = array('a', 'em', 'i', 'strong', 'b', 'br', 'p', 'blockquote', 'ul', 'ol', 'li', 'dl', 'dt', 'dd', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr'); 266 } 267 268 // Make sure only supported tags are kept. 269 $allowed_tags = isset($allowed_tags) ? array_intersect($supported_tags, $allowed_tags) : $supported_tags; 270 271 // Make sure tags, entities and attributes are well-formed and properly nested. 272 $string = _filter_htmlcorrector(filter_xss($string, $allowed_tags)); 273 274 // Apply inline styles. 275 $string = preg_replace('!</?(em|i)((?> +)[^>]*)?>!i', '/', $string); 276 $string = preg_replace('!</?(strong|b)((?> +)[^>]*)?>!i', '*', $string); 277 278 // Replace inline <a> tags with the text of link and a footnote. 279 // 'See <a href="http://drupal.org">the Drupal site</a>' becomes 280 // 'See the Drupal site [1]' with the URL included as a footnote. 281 _drupal_html_to_mail_urls(NULL, TRUE); 282 $pattern = '@(<a[^>]+?href="([^"]*)"[^>]*?>(.+?)</a>)@i'; 283 $string = preg_replace_callback($pattern, '_drupal_html_to_mail_urls', $string); 284 $urls = _drupal_html_to_mail_urls(); 285 $footnotes = ''; 286 if (count($urls)) { 287 $footnotes .= "\n"; 288 for ($i = 0, $max = count($urls); $i < $max; $i++) { 289 $footnotes .= '['. ($i + 1) .'] '. $urls[$i] ."\n"; 290 } 291 } 292 293 // Split tags from text. 294 $split = preg_split('/<([^>]+?)>/', $string, -1, PREG_SPLIT_DELIM_CAPTURE); 295 // Note: PHP ensures the array consists of alternating delimiters and literals 296 // and begins and ends with a literal (inserting $null as required). 297 298 $tag = FALSE; // Odd/even counter (tag or no tag) 299 $casing = NULL; // Case conversion function 300 $output = ''; 301 $indent = array(); // All current indentation string chunks 302 $lists = array(); // Array of counters for opened lists 303 foreach ($split as $value) { 304 $chunk = NULL; // Holds a string ready to be formatted and output. 305 306 // Process HTML tags (but don't output any literally). 307 if ($tag) { 308 list($tagname) = explode(' ', strtolower($value), 2); 309 switch ($tagname) { 310 // List counters 311 case 'ul': 312 array_unshift($lists, '*'); 313 break; 314 case 'ol': 315 array_unshift($lists, 1); 316 break; 317 case '/ul': 318 case '/ol': 319 array_shift($lists); 320 $chunk = ''; // Ensure blank new-line. 321 break; 322 323 // Quotation/list markers, non-fancy headers 324 case 'blockquote': 325 // Format=flowed indentation cannot be mixed with lists. 326 $indent[] = count($lists) ? ' "' : '>'; 327 break; 328 case 'li': 329 $indent[] = is_numeric($lists[0]) ? ' '. $lists[0]++ .') ' : ' * '; 330 break; 331 case 'dd': 332 $indent[] = ' '; 333 break; 334 case 'h3': 335 $indent[] = '.... '; 336 break; 337 case 'h4': 338 $indent[] = '.. '; 339 break; 340 case '/blockquote': 341 if (count($lists)) { 342 // Append closing quote for inline quotes (immediately). 343 $output = rtrim($output, "> \n") ."\"\n"; 344 $chunk = ''; // Ensure blank new-line. 345 } 346 // Fall-through 347 case '/li': 348 case '/dd': 349 array_pop($indent); 350 break; 351 case '/h3': 352 case '/h4': 353 array_pop($indent); 354 case '/h5': 355 case '/h6': 356 $chunk = ''; // Ensure blank new-line. 357 break; 358 359 // Fancy headers 360 case 'h1': 361 $indent[] = '======== '; 362 $casing = 'drupal_strtoupper'; 363 break; 364 case 'h2': 365 $indent[] = '-------- '; 366 $casing = 'drupal_strtoupper'; 367 break; 368 case '/h1': 369 case '/h2': 370 $casing = NULL; 371 // Pad the line with dashes. 372 $output = _drupal_html_to_text_pad($output, ($tagname == '/h1') ? '=' : '-', ' '); 373 array_pop($indent); 374 $chunk = ''; // Ensure blank new-line. 375 break; 376 377 // Horizontal rulers 378 case 'hr': 379 // Insert immediately. 380 $output .= drupal_wrap_mail('', implode('', $indent)) ."\n"; 381 $output = _drupal_html_to_text_pad($output, '-'); 382 break; 383 384 // Paragraphs and definition lists 385 case '/p': 386 case '/dl': 387 $chunk = ''; // Ensure blank new-line. 388 break; 389 } 390 } 391 // Process blocks of text. 392 else { 393 // Convert inline HTML text to plain text. 394 $value = trim(preg_replace('/\s+/', ' ', decode_entities($value))); 395 if (strlen($value)) { 396 $chunk = $value; 397 } 398 } 399 400 // See if there is something waiting to be output. 401 if (isset($chunk)) { 402 // Apply any necessary case conversion. 403 if (isset($casing)) { 404 $chunk = $casing($chunk); 405 } 406 // Format it and apply the current indentation. 407 $output .= drupal_wrap_mail($chunk, implode('', $indent)) ."\n"; 408 // Remove non-quotation markers from indentation. 409 $indent = array_map('_drupal_html_to_text_clean', $indent); 410 } 411 412 $tag = !$tag; 413 } 414 415 return $output . $footnotes; 416 } 417 418 /** 419 * Helper function for array_walk in drupal_wrap_mail(). 420 * 421 * Wraps words on a single line. 422 */ 423 function _drupal_wrap_mail_line(&$line, $key, $values) { 424 // Use soft-breaks only for purely quoted or unindented text. 425 $line = wordwrap($line, 77 - $values['length'], $values['soft'] ? " \n" : "\n"); 426 // Break really long words at the maximum width allowed. 427 $line = wordwrap($line, 996 - $values['length'], $values['soft'] ? " \n" : "\n"); 428 } 429 430 /** 431 * Helper function for drupal_html_to_text(). 432 * 433 * Keeps track of URLs and replaces them with placeholder tokens. 434 */ 435 function _drupal_html_to_mail_urls($match = NULL, $reset = FALSE) { 436 global $base_url, $base_path; 437 static $urls = array(), $regexp; 438 439 if ($reset) { 440 // Reset internal URL list. 441 $urls = array(); 442 } 443 else { 444 if (empty($regexp)) { 445 $regexp = '@^'. preg_quote($base_path, '@') .'@'; 446 } 447 if ($match) { 448 list(, , $url, $label) = $match; 449 // Ensure all URLs are absolute. 450 $urls[] = strpos($url, '://') ? $url : preg_replace($regexp, $base_url .'/', $url); 451 return $label .' ['. count($urls) .']'; 452 } 453 } 454 return $urls; 455 } 456 457 /** 458 * Helper function for drupal_wrap_mail() and drupal_html_to_text(). 459 * 460 * Replace all non-quotation markers from a given piece of indentation with spaces. 461 */ 462 function _drupal_html_to_text_clean($indent) { 463 return preg_replace('/[^>]/', ' ', $indent); 464 } 465 466 /** 467 * Helper function for drupal_html_to_text(). 468 * 469 * Pad the last line with the given character. 470 */ 471 function _drupal_html_to_text_pad($text, $pad, $prefix = '') { 472 // Remove last line break. 473 $text = substr($text, 0, -1); 474 // Calculate needed padding space and add it. 475 if (($p = strrpos($text, "\n")) === FALSE) { 476 $p = -1; 477 } 478 $n = max(0, 79 - (strlen($text) - $p) - strlen($prefix)); 479 // Add prefix and padding, and restore linebreak. 480 return $text . $prefix . str_repeat($pad, $n) ."\n"; 481 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Thu Mar 24 11:18:33 2011 | Cross-referenced by PHPXref 0.7 |