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