Initial revision
[Packages/TYPO3.CMS.git] / t3lib / class.t3lib_htmlmail.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 1999-2003 Kasper Skårhøj (kasper@typo3.com)
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
18 *
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27 /**
28 * HTML mail class
29 *
30
31 htmlmail.php
32
33
34
35
36
37
38
39
40
41
42 Plain + HTML
43 multipart/alternative (text, html)
44 multipart/alternative (text, html)
45
46
47 Plain + HTML + billede
48 multipart/related (m/a, cids)
49 multipart/alternative (text, html)
50
51 multipart/related (m/a, cids)
52 multipart/alternative (text, html)
53
54
55 plain + attachment
56 multipart/mixed
57
58
59 HTML + Attachment:
60 multipart/mixed (text/html , attachments)
61
62
63 Plain + HTML + Attachments:
64 multipart/mixed (m/a, attachments)
65 multipart/alternative (text, html)
66
67
68
69
70
71 Plain + HTML + billede + attachment
72
73 Calypso and outlook ex.
74 multipart/mixed (m/r, attachments)
75 multipart/related (m/a, cids)
76 multipart/alternative (text, html)
77
78
79 *
80 * @author Kasper Skårhøj <kasper@typo3.com>
81 * @package TYPO3
82 * @subpackage t3lib
83 */
84
85
86 class t3lib_htmlmail {
87 // Headerinfo:
88 var $recipient = "recipient@whatever.com";
89 var $recipient_copy = ""; // This recipient (or list of...) will also receive the mail. Regard it as a copy.
90 var $subject = "This is the subject";
91 var $from_email = "sender@php-mailer.com";
92 var $from_name = "Mr. Sender";
93 var $replyto_email = "reply@mailer.com";
94 var $replyto_name = "Mr. Reply";
95 var $organisation = "Your Company";
96 var $priority = 3; // 1 = highest, 5 = lowest, 3 = normal
97 var $mailer = "PHP mailer"; // X-mailer
98 var $alt_base64=0;
99 var $jumperURL_prefix =""; // This is a prefix that will be added to all links in the mail. Example: 'http://www.mydomain.com/jump?userid=###FIELD_uid###&url='. if used, anything after url= is urlencoded.
100 var $jumperURL_useId=0; // If set, then the array-key of the urls are inserted instead of the url itself. Smart in order to reduce link-length
101 var $mediaList=""; // If set, this is a list of the media-files (index-keys to the array) that should be represented in the html-mail
102 var $http_password="";
103 var $http_username="";
104
105
106 // Internal
107
108 /* This is how the $theParts-array is normally looking
109 var $theParts = Array(
110 "plain" => Array (
111 "content"=> ""
112 ),
113 "html" => Array (
114 "content"=> "",
115 "path" => "",
116 "media" => Array(),
117 "hrefs" => Array()
118 ),
119 "attach" => Array ()
120 );
121 */
122 var $theParts = Array();
123
124 var $messageid = "";
125 var $returnPath = "";
126 var $Xid = "";
127
128 var $headers = "";
129 var $message = "";
130 var $part=0;
131 var $image_fullpath_list = "";
132 var $href_fullpath_list = "";
133
134 var $plain_text_header = "Content-Type: text/plain; charset=iso-8859-1\nContent-Transfer-Encoding: quoted-printable";
135 var $html_text_header = "Content-Type: text/html; charset=iso-8859-1\nContent-Transfer-Encoding: quoted-printable";
136
137 function start () {
138 // Sets the message id
139 $this->messageid = '<'.md5(microtime()).'@domain.tld>';
140 }
141 function useBase64() {
142 $this->plain_text_header = 'Content-Type: text/plain; charset=iso-8859-1'.chr(10).'Content-Transfer-Encoding: base64';
143 $this->html_text_header = 'Content-Type: text/html; charset=iso-8859-1'.chr(10).'Content-Transfer-Encoding: base64';
144 $this->alt_base64=1;
145 }
146 function encodeMsg($content) {
147 return $this->alt_base64 ? $this->makeBase64($content) : $this->quoted_printable($content);
148 }
149 function addPlain ($content) {
150 // Adds plain-text and qp-encodes it
151 $content=$this->substHTTPurlsInPlainText($content);
152 $this->setPlain($this->encodeMsg($content));
153 }
154 function addAttachment($file) {
155 // Adds an attachment to the mail
156 $theArr = $this->getExtendedURL($file); // We fetch the content and the mime-type
157 if ($theArr) {
158 if (!$theArr["content_type"]){$theArr["content_type"]="application/octet-stream";}
159 $temp = $this->split_fileref($file);
160 $theArr["filename"]= (($temp["file"])?$temp["file"]:(strpos(" ".$theArr["content_type"],"htm")?"index.html":"unknown"));
161 $this->theParts["attach"][]=$theArr;
162 return true;
163 } else { return false;}
164 }
165 function addHTML ($file) {
166 // Adds HTML and media, encodes it from a URL or file
167 $status = $this->fetchHTML($file);
168 // debug(md5($status));
169 if (!$status) {return false;}
170 if ($this->extractFramesInfo()) {
171 return "Document was a frameset. Stopped";
172 }
173 $this->extractMediaLinks();
174 $this->extractHyperLinks();
175 $this->fetchHTMLMedia();
176 $this->substMediaNamesInHTML(0); // 0 = relative
177 $this->substHREFsInHTML();
178 $this->setHTML($this->encodeMsg($this->theParts["html"]["content"]));
179 }
180
181 /**
182 * External used to extract HTML-parts
183 */
184 function extractHtmlInit($html,$url) {
185 $this->theParts["html"]["content"]=$html;
186 $this->theParts["html"]["path"]=$url;
187 }
188
189 function send($recipient) {
190 // This function sends the mail to one $recipient
191 if ($recipient) {$this->recipient = $recipient;}
192 $this->setHeaders();
193 $this->setContent();
194 $this->sendTheMail();
195 }
196
197
198 // *****************
199 // Main functions
200 // *****************
201 function setHeaders () {
202 // Clears the header-string and sets the headers based on object-vars.
203 $this->headers = "";
204 // Message_id
205 $this->add_header("Message-ID: ".$this->messageid);
206 // Return path
207 if ($this->returnPath) {
208 $this->add_header("Return-Path: ".$this->returnPath);
209 }
210 // X-id
211 if ($this->Xid) {
212 $this->add_header("X-Typo3MID: ".$this->Xid);
213 }
214
215 // From
216 if ($this->from_email) {
217 if ($this->from_name) {
218 $name = $this->convertName($this->from_name);
219 $this->add_header("From: $name <$this->from_email>");
220 } else {
221 $this->add_header("From: $this->from_email");
222 }
223 }
224 // Reply
225 if ($this->replyto_email) {
226 if ($this->replyto_name) {
227 $name = $this->convertName($this->replyto_name);
228 $this->add_header("Reply-To: $name <$this->replyto_email>");
229 } else {
230 $this->add_header("Reply-To: $this->replyto_email");
231 }
232 }
233 // Organisation
234 if ($this->organisation) {
235 $name = $this->convertName($this->organisation);
236 $this->add_header("Organisation: $name");
237 }
238 // mailer
239 if ($this->mailer) {
240 $this->add_header("X-Mailer: $this->mailer");
241 }
242 // priority
243 if ($this->priority) {
244 $this->add_header("X-Priority: $this->priority");
245 }
246 $this->add_header("Mime-Version: 1.0");
247 }
248 function setRecipient ($recip) {
249 // Sets the recipient(s). If you supply a string, you set one recipient. If you supply an array, every value is added as a recipient.
250 if (is_array($recip)) {
251 $this->recipient = "";
252 while (list($key,) = each($recip)) {
253 $this->recipient .= $recip[$key].",";
254 }
255 $this->recipient = ereg_replace(",$","",$this->recipient);
256 } else {
257 $this->recipient = $recip;
258 }
259 }
260 function getHTMLContentType() {
261 return count($this->theParts["html"]["media"]) ? 'multipart/related;' : 'multipart/alternative;';
262 }
263 function setContent() {
264 // Begins building the message-body
265 $this->message = "";
266 $boundary = $this->getBoundary();
267 // Setting up headers
268 if (count($this->theParts["attach"])) {
269 $this->add_header('Content-Type: multipart/mixed;');
270 $this->add_header(' boundary="'.$boundary.'"');
271 $this->add_message("This is a multi-part message in MIME format.\n");
272 $this->constructMixed($boundary); // Generate (plain/HTML) / attachments
273 } elseif ($this->theParts["html"]["content"]) {
274 $this->add_header('Content-Type: '.$this->getHTMLContentType());
275 $this->add_header(' boundary="'.$boundary.'"');
276 $this->add_message("This is a multi-part message in MIME format.\n");
277 $this->constructHTML($boundary); // Generate plain/HTML mail
278 } else {
279 $this->add_header($this->plain_text_header);
280 $this->add_message($this->getContent("plain")); // Generate plain only
281 }
282 }
283 function constructMixed ($boundary) {
284 // Here (plain/HTML) is combined with the attachments
285 $this->add_message("--".$boundary);
286 // (plain/HTML) is added
287 if ($this->theParts["html"]["content"]) {
288 // HTML and plain
289 $newBoundary = $this->getBoundary();
290 $this->add_message("Content-Type: ".$this->getHTMLContentType());
291 $this->add_message(' boundary="'.$newBoundary.'"');
292 $this->add_message('');
293 $this->constructHTML($newBoundary);
294 } else { // Purely plain
295 $this->add_message($this->plain_text_header);
296 $this->add_message('');
297 $this->add_message($this->getContent("plain"));
298 }
299 // attachments are added
300 if (is_array($this->theParts["attach"])) {
301 reset($this->theParts["attach"]);
302 while(list(,$media)=each($this->theParts["attach"])) {
303 $this->add_message("--".$boundary);
304 $this->add_message("Content-Type: ".$media["content_type"]);
305 $this->add_message(' name="'.$media["filename"].'"');
306 $this->add_message("Content-Transfer-Encoding: base64");
307 $this->add_message("Content-Disposition: attachment;");
308 $this->add_message(' filename="'.$media["filename"].'"');
309 $this->add_message('');
310 $this->add_message($this->makeBase64($media["content"]));
311 }
312 }
313 $this->add_message("--".$boundary."--\n");
314 }
315 function constructHTML ($boundary) {
316 if (count($this->theParts["html"]["media"])) { // If media, then we know, the multipart/related content-type has been set before this function call...
317 $this->add_message("--".$boundary);
318 // HTML has media
319 $newBoundary = $this->getBoundary();
320 $this->add_message("Content-Type: multipart/alternative;");
321 $this->add_message(' boundary="'.$newBoundary.'"');
322 $this->add_message('');
323
324 $this->constructAlternative($newBoundary); // Adding the plaintext/html mix
325
326 $this->constructHTML_media($boundary);
327 $this->add_message("--".$boundary."--\n");
328 } else {
329 $this->constructAlternative($boundary); // Adding the plaintext/html mix, and if no media, then use $boundary instead of $newBoundary
330 }
331 }
332 function constructAlternative($boundary) {
333 // Here plain is combined with HTML
334 $this->add_message("--".$boundary);
335 // plain is added
336 $this->add_message($this->plain_text_header);
337 $this->add_message('');
338 $this->add_message($this->getContent("plain"));
339 $this->add_message("--".$boundary);
340 // html is added
341 $this->add_message($this->html_text_header);
342 $this->add_message('');
343 $this->add_message($this->getContent("html"));
344 $this->add_message("--".$boundary."--\n");
345 }
346 function constructHTML_media ($boundary) {
347 /* // Constructs the HTML-part of message if the HTML contains media
348 $this->add_message("--".$boundary);
349 // htmlcode is added
350 $this->add_message($this->html_text_header);
351 $this->add_message('');
352 $this->add_message($this->getContent("html"));
353
354 OLD stuf...
355
356 */
357 // media is added
358 if (is_array($this->theParts["html"]["media"])) {
359 reset($this->theParts["html"]["media"]);
360 while(list($key,$media)=each($this->theParts["html"]["media"])) {
361 if (!$this->mediaList || t3lib_div::inList($this->mediaList,$key)) {
362 $this->add_message("--".$boundary);
363 $this->add_message("Content-Type: ".$media["ctype"]);
364 $this->add_message("Content-ID: <part".$key.".".$this->messageid.">");
365 $this->add_message("Content-Transfer-Encoding: base64");
366 $this->add_message('');
367 $this->add_message($this->makeBase64($media["content"]));
368 }
369 }
370 }
371 $this->add_message("--".$boundary."--\n");
372 }
373 function sendTheMail () {
374 // Sends the mail.
375 // Requires the recipient, message and headers to be set.
376 #debug(array($this->recipient,$this->subject,$this->message,$this->headers));
377 if (trim($this->recipient) && trim($this->message)) { // && trim($this->headers)
378 mail( $this->recipient,
379 $this->subject,
380 $this->message,
381 $this->headers );
382 // Sending copy:
383 if ($this->recipient_copy) {
384 mail( $this->recipient_copy,
385 $this->subject,
386 $this->message,
387 $this->headers );
388 }
389 // Auto response
390 if ($this->auto_respond_msg) {
391 $theParts = explode("/",$this->auto_respond_msg,2);
392 $theParts[1] = str_replace("/",chr(10),$theParts[1]);
393 mail( $this->from_email,
394 $theParts[0],
395 $theParts[1],
396 "From: ".$this->recipient );
397 }
398 return true;
399 } else {return false;}
400 }
401 function getBoundary() {
402 // Returns boundaries
403 $this->part++;
404 return "----------".uniqid("part_".$this->part."_");
405 }
406 function setPlain ($content) {
407 // Sets the plain-text part. No processing done.
408 $this->theParts["plain"]["content"] = $content;
409 }
410 function setHtml ($content) {
411 // Sets the HTML-part. No processing done.
412 $this->theParts["html"]["content"] = $content;
413 }
414 function add_header ($header) {
415 // Adds a header to the mail. Use this AFTER the setHeaders()-function
416 $this->headers.=$header."\n";
417 }
418 function add_message ($string) {
419 // Adds a line of text to the mail-body. Is normally use internally
420 $this->message.=$string."\n";
421 }
422 function getContent($type) {
423 return $this->theParts[$type]["content"];
424 }
425 function preview() {
426 echo nl2br(HTMLSpecialChars($this->headers));
427 echo "<BR>";
428 echo nl2br(HTMLSpecialChars($this->message));
429 }
430
431
432 // *********************************************************************
433 // ** Functions for acquiring attachments, HTML, analyzing and so on **
434 // *********************************************************************
435 function fetchHTML($file) {
436 // Fetches the HTML-content from either url og local serverfile
437 $this->theParts["html"]["content"] = $this->getURL($file); // Fetches the content of the page
438 if ($this->theParts["html"]["content"]) {
439 $addr = $this->extParseUrl($file);
440 $path = ($addr["scheme"]) ? $addr["scheme"]."://".$addr["host"].(($addr["filepath"])?$addr["filepath"]:"/") : $addr["filepath"];
441 $this->theParts["html"]["path"] = $path;
442 return true;
443 } else {
444 return false;
445 }
446 }
447 function fetchHTMLMedia() {
448 // Fetches the mediafiles which are found by extractMediaLinks()
449 if (is_array($this->theParts["html"]["media"])) {
450 reset ($this->theParts["html"]["media"]);
451 if (count($this->theParts["html"]["media"]) > 0) {
452 while (list($key,$media) = each ($this->theParts["html"]["media"])) {
453 $picdata = $this->getExtendedURL($this->theParts["html"]["media"][$key]["absRef"]); // We fetch the content and the mime-type
454 if (is_array($picdata)) {
455 $this->theParts["html"]["media"][$key]["content"] = $picdata["content"];
456 $this->theParts["html"]["media"][$key]["ctype"] = $picdata["content_type"];
457 }
458 }
459 }
460 }
461 }
462 function extractMediaLinks() {
463 // extracts all media-links from $this->theParts["html"]["content"]
464 $html_code = $this->theParts["html"]["content"];
465 $attribRegex = $this->tag_regex(Array("img","table","td","tr","body","iframe","script","input","embed"));
466 $codepieces = split($attribRegex, $html_code); // Splits the document by the beginning of the above tags
467 $len=strlen($codepieces[0]);
468 $pieces = count($codepieces);
469 for($i=1; $i < $pieces; $i++) {
470 $tag = strtolower(strtok(substr($html_code,$len+1,10)," "));
471 $len+=strlen($tag)+strlen($codepieces[$i])+2;
472 $dummy = eregi("[^>]*", $codepieces[$i], $reg);
473 $attributes = $this->get_tag_attributes($reg[0]); // Fetches the attributes for the tag
474 $imageData=array();
475 $imageData["ref"]=($attributes["src"]) ? $attributes["src"] : $attributes["background"]; // Finds the src or background attribute
476 if ($imageData["ref"]) {
477 $imageData["quotes"]=(substr($codepieces[$i],strpos($codepieces[$i], $imageData["ref"])-1,1)=='"')?'"':''; // Finds out if the value had quotes around it
478 $imageData["subst_str"] = $imageData["quotes"].$imageData["ref"].$imageData["quotes"]; // subst_str is the string to look for, when substituting lateron
479 if ($imageData["ref"] && !strstr($this->image_fullpath_list,"|".$imageData["subst_str"]."|")) {
480 $this->image_fullpath_list.="|".$imageData["subst_str"]."|";
481 $imageData["absRef"] = $this->absRef($imageData["ref"]);
482 $imageData["tag"]=$tag;
483 $imageData["use_jumpurl"]=$attributes["dmailerping"]?1:0;
484 $this->theParts["html"]["media"][]=$imageData;
485 }
486 }
487 }
488 // Extracts stylesheets
489 $attribRegex = $this->tag_regex(Array("link"));
490 $codepieces = split($attribRegex, $html_code); // Splits the document by the beginning of the above tags
491 $pieces = count($codepieces);
492 for($i=1; $i < $pieces; $i++) {
493 $dummy = eregi("[^>]*", $codepieces[$i], $reg);
494 $attributes = $this->get_tag_attributes($reg[0]); // Fetches the attributes for the tag
495 $imageData=array();
496 if (strtolower($attributes["rel"])=="stylesheet" && $attributes["href"]) {
497 $imageData["ref"]=$attributes["href"]; // Finds the src or background attribute
498 $imageData["quotes"]=(substr($codepieces[$i],strpos($codepieces[$i], $imageData["ref"])-1,1)=='"')?'"':''; // Finds out if the value had quotes around it
499 $imageData["subst_str"] = $imageData["quotes"].$imageData["ref"].$imageData["quotes"]; // subst_str is the string to look for, when substituting lateron
500 if ($imageData["ref"] && !strstr($this->image_fullpath_list,"|".$imageData["subst_str"]."|")) {
501 $this->image_fullpath_list.="|".$imageData["subst_str"]."|";
502 $imageData["absRef"] = $this->absRef($imageData["ref"]);
503 $this->theParts["html"]["media"][]=$imageData;
504 }
505 }
506 }
507 // fixes javascript rollovers
508 $codepieces = split(quotemeta(".src"), $html_code);
509 $pieces = count($codepieces);
510 $expr = "^[^".quotemeta("\"").quotemeta("'")."]*";
511 for($i=1; $i < $pieces; $i++) {
512 $temp = $codepieces[$i];
513 $temp = trim(ereg_replace("=","",trim($temp)));
514 ereg ($expr,substr($temp,1,strlen($temp)),$reg);
515 $imageData["ref"] = $reg[0];
516 $imageData["quotes"] = substr($temp,0,1);
517 $imageData["subst_str"] = $imageData["quotes"].$imageData["ref"].$imageData["quotes"]; // subst_str is the string to look for, when substituting lateron
518 $theInfo = $this->split_fileref($imageData["ref"]);
519 switch ($theInfo["fileext"]) {
520 case "gif":
521 case "jpeg":
522 case "jpg":
523 if ($imageData["ref"] && !strstr($this->image_fullpath_list,"|".$imageData["subst_str"]."|")) {
524 $this->image_fullpath_list.="|".$imageData["subst_str"]."|";
525 $imageData["absRef"] = $this->absRef($imageData["ref"]);
526 $this->theParts["html"]["media"][]=$imageData;
527 }
528 break;
529 }
530 }
531 }
532 function extractHyperLinks() {
533 // extracts all hyper-links from $this->theParts["html"]["content"]
534 $html_code = $this->theParts["html"]["content"];
535 $attribRegex = $this->tag_regex(Array("a","form","area"));
536 $codepieces = split($attribRegex, $html_code); // Splits the document by the beginning of the above tags
537 $len=strlen($codepieces[0]);
538 $pieces = count($codepieces);
539 for($i=1; $i < $pieces; $i++) {
540 $tag = strtolower(strtok(substr($html_code,$len+1,10)," "));
541 $len+=strlen($tag)+strlen($codepieces[$i])+2;
542
543 $dummy = eregi("[^>]*", $codepieces[$i], $reg);
544 $attributes = $this->get_tag_attributes($reg[0]); // Fetches the attributes for the tag
545 $hrefData="";
546 if ($attributes["href"]) {$hrefData["ref"]=$attributes["href"];} else {$hrefData["ref"]=$attributes["action"];}
547 if ($hrefData["ref"]) {
548 $hrefData["quotes"]=(substr($codepieces[$i],strpos($codepieces[$i], $hrefData["ref"])-1,1)=='"')?'"':''; // Finds out if the value had quotes around it
549 $hrefData["subst_str"] = $hrefData["quotes"].$hrefData["ref"].$hrefData["quotes"]; // subst_str is the string to look for, when substituting lateron
550 if ($hrefData["ref"] && substr(trim($hrefData["ref"]),0,1)!="#" && !strstr($this->href_fullpath_list,"|".$hrefData["subst_str"]."|")) {
551 $this->href_fullpath_list.="|".$hrefData["subst_str"]."|";
552 $hrefData["absRef"] = $this->absRef($hrefData["ref"]);
553 $hrefData["tag"]=$tag;
554 $this->theParts["html"]["hrefs"][]=$hrefData;
555 }
556 }
557 }
558 // Extracts TYPO3 specific links made by the openPic() JS function
559 $codepieces = explode("onClick=\"openPic('", $html_code);
560 $pieces = count($codepieces);
561 for($i=1; $i < $pieces; $i++) {
562 $showpic_linkArr = explode("'",$codepieces[$i]);
563 $hrefData["ref"]=$showpic_linkArr[0];
564 if ($hrefData["ref"]) {
565 $hrefData["quotes"]="'"; // Finds out if the value had quotes around it
566 $hrefData["subst_str"] = $hrefData["quotes"].$hrefData["ref"].$hrefData["quotes"]; // subst_str is the string to look for, when substituting lateron
567 if ($hrefData["ref"] && !strstr($this->href_fullpath_list,"|".$hrefData["subst_str"]."|")) {
568 $this->href_fullpath_list.="|".$hrefData["subst_str"]."|";
569 $hrefData["absRef"] = $this->absRef($hrefData["ref"]);
570 $this->theParts["html"]["hrefs"][]=$hrefData;
571 }
572 }
573 }
574 }
575 function extractFramesInfo() {
576 // extracts all media-links from $this->theParts["html"]["content"]
577 $html_code = $this->theParts["html"]["content"];
578 if (strpos(" ".$html_code,"<frame ")) {
579 $attribRegex = $this->tag_regex("frame");
580 $codepieces = split($attribRegex, $html_code, 1000000 ); // Splits the document by the beginning of the above tags
581 $pieces = count($codepieces);
582 for($i=1; $i < $pieces; $i++) {
583 $dummy = eregi("[^>]*", $codepieces[$i], $reg);
584 $attributes = $this->get_tag_attributes($reg[0]); // Fetches the attributes for the tag
585 $frame="";
586 $frame["src"]=$attributes["src"];
587 $frame["name"]=$attributes["name"];
588 $frame["absRef"] = $this->absRef($frame["src"]);
589 $theInfo[] = $frame;
590 }
591 return $theInfo;
592 }
593 }
594 function substMediaNamesInHTML($absolute) {
595 // This substitutes the media-references in $this->theParts["html"]["content"]
596 // If $absolute is true, then the refs are substituted with http:// ref's indstead of Content-ID's (cid).
597 if (is_array($this->theParts["html"]["media"])) {
598 reset ($this->theParts["html"]["media"]);
599 while (list($key,$val) = each ($this->theParts["html"]["media"])) {
600 if ($val["use_jumpurl"] && $this->jumperURL_prefix) {
601 $theSubstVal = $this->jumperURL_prefix.rawurlencode($val["absRef"]);
602 } else {
603 $theSubstVal = ($absolute) ? $val["absRef"] : "cid:part".$key.".".$this->messageid;
604 }
605 $this->theParts["html"]["content"] = str_replace(
606 $val["subst_str"],
607 $val["quotes"].$theSubstVal.$val["quotes"],
608 $this->theParts["html"]["content"] );
609 }
610 }
611 if (!$absolute) {
612 $this->fixRollOvers();
613 }
614 }
615 function substHREFsInHTML() {
616 // This substitutes the hrefs in $this->theParts["html"]["content"]
617 if (is_array($this->theParts["html"]["hrefs"])) {
618 reset ($this->theParts["html"]["hrefs"]);
619 while (list($key,$val) = each ($this->theParts["html"]["hrefs"])) {
620 if ($this->jumperURL_prefix && $val["tag"]!="form") { // Form elements cannot use jumpurl!
621 if ($this->jumperURL_useId) {
622 $theSubstVal = $this->jumperURL_prefix.$key;
623 } else {
624 $theSubstVal = $this->jumperURL_prefix.rawurlencode($val["absRef"]);
625 }
626 } else {
627 $theSubstVal = $val["absRef"];
628 }
629 $this->theParts["html"]["content"] = str_replace(
630 $val["subst_str"],
631 $val["quotes"].$theSubstVal.$val["quotes"],
632 $this->theParts["html"]["content"] );
633 }
634 }
635 }
636 function substHTTPurlsInPlainText($content) {
637 // This substitutes the http:// urls in plain text with links
638 if ($this->jumperURL_prefix) {
639 $textpieces = explode("http://", $content);
640 $pieces = count($textpieces);
641 $textstr = $textpieces[0];
642 for($i=1; $i<$pieces; $i++) {
643 $len=strcspn($textpieces[$i],chr(32).chr(9).chr(13).chr(10));
644 if (trim(substr($textstr,-1))=="" && $len) {
645 $lastChar=substr($textpieces[$i],$len-1,1);
646 if (!ereg("[A-Za-z0-9\/#]",$lastChar)) {$len--;} // Included "\/" 3/12
647
648 $parts[0]="http://".substr($textpieces[$i],0,$len);
649 $parts[1]=substr($textpieces[$i],$len);
650
651 if ($this->jumperURL_useId) {
652 $this->theParts["plain"]["link_ids"][$i]=$parts[0];
653 $parts[0] = $this->jumperURL_prefix."-".$i;
654 } else {
655 $parts[0] = $this->jumperURL_prefix.rawurlencode($parts[0]);
656 }
657 // debug($parts);
658 $textstr.=$parts[0].$parts[1];
659 } else {
660 $textstr.='http://'.$textpieces[$i];
661 }
662 }
663 $content = $textstr;
664 // debug(array($textstr));
665 // debug(md5($textstr));
666 // debug(md5($content));
667 }
668 return $content;
669 }
670
671 function fixRollOvers() {
672 // JavaScript rollOvers cannot support graphics inside of mail. If these exists we must let them refer to the absolute url. By the way: Roll-overs seems to work only on some mail-readers and so far I've seen it work on Netscape 4 message-center (but not 4.5!!)
673 $theNewContent = "";
674 $theSplit = explode(".src",$this->theParts["html"]["content"]);
675 if (count($theSplit)>1) {
676 while(list($key,$part)=each($theSplit)) {
677 $sub = substr($part,0,200);
678 if (ereg("cid:part[^ \"']*",$sub,$reg)) {
679 $thePos = strpos($part,$reg[0]); // The position of the string
680 ereg("cid:part([^\.]*).*",$sub,$reg2); // Finds the id of the media...
681 $theSubStr = $this->theParts["html"]["media"][intval($reg2[1])]["absRef"];
682 if ($thePos && $theSubStr) { // ... and substitutes the javaScript rollover image with this instead
683 if (!strpos(" ".$theSubStr,"http://")) {$theSubStr = "http://";} // If the path is NOT and url, the reference is set to nothing
684 $part = substr($part,0,$thePos).$theSubStr.substr($part,$thePos+strlen($reg[0]),strlen($part));
685 }
686 }
687 $theNewContent.= $part.((($key+1)!=count($theSplit))? ".src" : "" );
688 }
689 $this->theParts["html"]["content"]=$theNewContent;
690 }
691 }
692
693
694 // ************************
695 // File and URL-functions
696 // ************************
697 function makeBase64($inputstr) {
698 // Returns base64-encoded content, which is broken every 76 character
699 return chunk_split(base64_encode($inputstr));
700 }
701 function getExtendedURL($url) {
702 // reads the URL or file and determines the Content-type by either guessing or opening a connection to the host
703 $res["content"] = $this->getURL($url);
704 if (!$res["content"]) {return false;}
705 $pathInfo = parse_url($url);
706 $fileInfo = $this->split_fileref($pathInfo["path"]);
707 if ($fileInfo["fileext"] == "gif") {$res["content_type"] = "image/gif";}
708 if ($fileInfo["fileext"] == "jpg" || $fileInfo["fileext"] == "jpeg") {$res["content_type"] = "image/jpeg";}
709 if ($fileInfo["fileext"] == "html" || $fileInfo["fileext"] == "htm") {$res["content_type"] = "text/html";}
710 if ($fileInfo["fileext"] == "swf") {$res["content_type"] = "application/x-shockwave-flash";}
711 if (!$res["content_type"]) {$res["content_type"] = $this->getMimeType($url);}
712 return $res;
713 }
714 function addUserPass($url) {
715 $user=$this->http_username;
716 $pass=$this->http_password;
717 if ($user && $pass && substr($url,0,7)=="http://") {
718 $url = "http://".$user.":".$pass."@".substr($url,7);
719 }
720 return $url;
721 }
722 function getURL($url) {
723 $url = $this->addUserPass($url);
724 // reads a url or file
725 if($fd = @fopen($url,"rb")) {
726 $content = "";
727 while (!feof($fd)) {
728 $content.=fread( $fd, 5000 );
729 }
730 fclose( $fd );
731 return $content;
732 } else {
733 return false;
734 }
735 }
736 function getStrippedURL($url) {
737 // reads a url or file and strips the HTML-tags AND removes all empty lines. This is used to read plain-text out of a HTML-page
738 if($fd = fopen($url,"rb")) {
739 $content = "";
740 while (!feof($fd)) {
741 $line = fgetss($fd, 5000);
742 if (trim($line)) {
743 $content.=trim($line)."\n";
744 }
745 }
746 fclose( $fd );
747 return $content;
748 }
749 }
750 function getMimeType($url) {
751 // Opens a connection to the server and returns the mime-type of the file
752 // takes url only
753 $pathInfo = parse_url($url);
754 if (!$pathInfo["scheme"]) {return false;}
755 $getAdr = ($pathInfo["query"])?$pathInfo["path"]."?".$pathInfo["query"]:$pathInfo["path"];
756 $fp = fsockopen($pathInfo["host"], 80, $errno, $errstr);
757 if(!$fp) {
758 return false;
759 } else {
760 fputs($fp,"GET ".$getAdr." HTTP/1.0\n\n");
761 while(!feof($fp)) {
762 $thePortion= fgets($fp,128);
763 if (eregi("(^Content-Type: )(.*)",trim($thePortion), $reg)) {
764 $res = trim($reg[2]);
765 break;
766 }
767 }
768 fclose($fp);
769 }
770 return $res;
771 }
772 function absRef($ref) {
773 // Returns the absolute address of a link. This is based on $this->theParts["html"]["path"] being the root-address
774 $ref = trim($ref);
775 $urlINFO = parse_url($ref);
776 if ($urlINFO["scheme"]) {
777 return $ref;
778 } elseif (eregi("^/",$ref)){
779 $addr = parse_url($this->theParts["html"]["path"]);
780 return $addr["scheme"]."://".$addr["host"].$ref;
781 } else {
782 return $this->theParts["html"]["path"].$ref; // If the reference is relative, the path is added, in order for us to fetch the content
783 }
784 }
785 function split_fileref($fileref) {
786 // Returns an array with path, filename, filebody, fileext.
787 if ( ereg("(.*/)(.*)$",$fileref,$reg) ) {
788 $info["path"] = $reg[1];
789 $info["file"] = $reg[2];
790 } else {
791 $info["path"] = "";
792 $info["file"] = $fileref;
793 }
794 $reg="";
795 if ( ereg("(.*)\.([^\.]*$)",$info["file"],$reg) ) {
796 $info["filebody"] = $reg[1];
797 $info["fileext"] = strtolower($reg[2]);
798 $info["realFileext"] = $reg[2];
799 } else {
800 $info["filebody"] = $info["file"];
801 $info["fileext"] = "";
802 }
803 return $info;
804 }
805 function extParseUrl($path) {
806 // Returns an array with file or url-information
807 $res = parse_url($path);
808 ereg("(.*/)([^/]*)$",$res["path"],$reg);
809 $res["filepath"]=$reg[1];
810 $res["filename"]=$reg[2];
811 return $res;
812 }
813 function tag_regex($tagArray) {
814 if (!is_array($tagArray)) {
815 $tagArray=Array($tagArray);
816 }
817 $theRegex = "";
818 $c=count($tagArray);
819 while(list(,$tag)=each($tagArray)) {
820 $c--;
821 $theRegex.="<".sql_regcase($tag)."[[:space:]]".(($c)?"|":"");
822 }
823 return $theRegex;
824 }
825 function get_tag_attributes($tag) {
826 // analyses a HTML-tag
827 // $tag is either like this "<TAG OPTION ATTRIB=VALUE>" or this " OPTION ATTRIB=VALUE>" which means you can omit the tag-name
828 // returns an array with the attributes as keys in lower-case
829 // If an attribute is empty (like OPTION) the value of that key is just empty. Check it with is_set();
830 $attributes = Array();
831 $tag = ltrim(eregi_replace ("^<[^ ]*","",trim($tag)));
832 $tagLen = strlen($tag);
833 $safetyCounter = 100;
834 // Find attribute
835 while ($tag) {
836 $value = "";
837 $reg = split("[[:space:]=>]",$tag,2);
838 $attrib = $reg[0];
839
840 $tag = ltrim(substr($tag,strlen($attrib),$tagLen));
841 if (substr($tag,0,1)=="=") {
842 $tag = ltrim(substr($tag,1,$tagLen));
843 if (substr($tag,0,1)=='"') { // Quotes around the value
844 $reg = explode('"',substr($tag,1,$tagLen),2);
845 $tag = ltrim($reg[1]);
846 $value = $reg[0];
847 } else { // No qoutes around value
848 ereg("^([^[:space:]>]*)(.*)",$tag,$reg);
849 $value = trim($reg[1]);
850 $tag = ltrim($reg[2]);
851 if (substr($tag,0,1)==">") {
852 $tag ="";
853 }
854 }
855 }
856 $attributes[strtolower($attrib)]=$value;
857 $safetyCounter--;
858 if ($safetyCounter<0) {break;}
859 }
860 return $attributes;
861 }
862 function quoted_printable($string) {
863 // This functions is buggy. It seems that in the part where the lines are breaked every 76th character, that it fails if the break happens right in a quoted_printable encode character!
864 $newString = "";
865 $theLines = explode(chr(10),$string); // Break lines. Doesn't work with mac eol's which seems to be 13. But 13-10 or 10 will work
866 while (list(,$val)=each($theLines)) {
867 $val = ereg_replace(chr(13)."$","",$val); // removes possible character 13 at the end of line
868
869 $newVal = "";
870 $theValLen = strlen($val);
871 $len = 0;
872 for ($index=0;$index<$theValLen;$index++) {
873 $char = substr($val,$index,1);
874 $ordVal =Ord($char);
875 if ($len>(76-4) || ($len>(66-4)&&$ordVal==32)) {
876 $len=0;
877 $newVal.="=".chr(13).chr(10);
878 }
879 if (($ordVal>=33 && $ordVal<=60) || ($ordVal>=62 && $ordVal<=126) || $ordVal==9 || $ordVal==32) {
880 $newVal.=$char;
881 $len++;
882 } else {
883 $newVal.=sprintf("=%02X",$ordVal);
884 $len+=3;
885 }
886 }
887 $newVal = ereg_replace(chr(32)."$","=20",$newVal); // replaces a possible SPACE-character at the end of a line
888 $newVal = ereg_replace(chr(9)."$","=09",$newVal); // replaces a possible TAB-character at the end of a line
889 $newString.=$newVal.chr(13).chr(10);
890 }
891 return $newString;
892 }
893 function convertName($name) {
894 if (ereg("[^".chr(32)."-".chr(60).chr(62)."-".chr(127)."]",$name)) {
895 return '=?iso-8859-1?B?'.base64_encode($name).'?=';
896 } else {
897 return $name;
898 }
899 }
900 }
901
902
903
904
905
906
907 /*
908
909
910 FROM RFC 1521:
911
912
913 5.1 Quoted-Printable Content-Transfer-Encoding
914 The Quoted-Printable encoding is intended to represent data that largely consists of octets that correspond to printable characters in the ASCII character set. It encodes the data in such a way that the resulting octets are unlikely to be modified by mail transport. If the data being encoded are mostly ASCII text, the encoded form of the data remains largely recognizable by humans. A body which is entirely ASCII may also be encoded in Quoted-Printable to ensure the integrity of the data should the message pass through a character- translating, and/or line-wrapping gateway.
915
916 In this encoding, octets are to be represented as determined by the following rules:
917
918 Rule #1: (General 8-bit representation) Any octet, except those indicating a line break according to the newline convention of the canonical (standard) form of the data being encoded, may be represented by an "=" followed by a two digit hexadecimal representation of the octet's value. The digits of the hexadecimal alphabet, for this purpose, are "0123456789ABCDEF". Uppercase letters must be used when sending hexadecimal data, though a robust implementation may choose to recognize lowercase letters on receipt. Thus, for example, the value 12 (ASCII form feed) can be represented by "=0C", and the value 61 (ASCII EQUAL SIGN) can be represented by "=3D". Except when the following rules allow an alternative encoding, this rule is mandatory.
919
920 Rule #2: (Literal representation) Octets with decimal values of 33 through 60 inclusive, and 62 through 126, inclusive, MAY be represented as the ASCII characters which correspond to those octets (EXCLAMATION POINT through LESS THAN, and GREATER THAN through TILDE, respectively).
921
922 Rule #3: (White Space): Octets with values of 9 and 32 MAY be represented as ASCII TAB (HT) and SPACE characters, respectively, but MUST NOT be so represented at the end of an encoded line. Any TAB (HT) or SPACE characters on an encoded line MUST thus be followed on that line by a printable character. In particular, an
923
924 "=" at the end of an encoded line, indicating a soft line break (see rule #5) may follow one or more TAB (HT) or SPACE characters. It follows that an octet with value 9 or 32 appearing at the end of an encoded line must be represented according to Rule #1. This rule is necessary because some MTAs (Message Transport Agents, programs which transport messages from one user to another, or perform a part of such transfers) are known to pad lines of text with SPACEs, and others are known to remove "white space" characters from the end of a line. Therefore, when decoding a Quoted-Printable body, any trailing white space on a line must be deleted, as it will necessarily have been added by intermediate transport agents.
925
926 Rule #4 (Line Breaks): A line break in a text body, independent of what its representation is following the canonical representation of the data being encoded, must be represented by a (RFC 822) line break, which is a CRLF sequence, in the Quoted-Printable encoding. Since the canonical representation of types other than text do not generally include the representation of line breaks, no hard line breaks (i.e. line breaks that are intended to be meaningful and to be displayed to the user) should occur in the quoted-printable encoding of such types. Of course, occurrences of "=0D", "=0A", "0A=0D" and "=0D=0A" will eventually be encountered. In general, however, base64 is preferred over quoted-printable for binary data.
927
928 Note that many implementations may elect to encode the local representation of various content types directly, as described in Appendix G. In particular, this may apply to plain text material on systems that use newline conventions other than CRLF delimiters. Such an implementation is permissible, but the generation of line breaks must be generalized to account for the case where alternate representations of newline sequences are used.
929
930 Rule #5 (Soft Line Breaks): The Quoted-Printable encoding REQUIRES that encoded lines be no more than 76 characters long. If longer lines are to be encoded with the Quoted-Printable encoding, 'soft' line breaks must be used. An equal sign as the last character on a encoded line indicates such a non-significant ('soft') line break in the encoded text. Thus if the "raw" form of the line is a single unencoded line that says:
931
932 Now's the time for all folk to come to the aid of
933 their country.
934
935 This can be represented, in the Quoted-Printable encoding, as
936
937
938 Now's the time =
939 for all folk to come=
940 to the aid of their country.
941
942 This provides a mechanism with which long lines are encoded in such a way as to be restored by the user agent. The 76 character limit does not count the trailing CRLF, but counts all other characters, including any equal signs.
943
944 Since the hyphen character ("-") is represented as itself in the Quoted-Printable encoding, care must be taken, when encapsulating a quoted-printable encoded body in a multipart entity, to ensure that the encapsulation boundary does not appear anywhere in the encoded body. (A good strategy is to choose a boundary that includes a character sequence such as "=_" which can never appear in a quoted- printable body. See the definition of multipart messages later in this document.)
945
946 NOTE: The quoted-printable encoding represents something of a compromise between readability and reliability in transport. Bodies encoded with the quoted-printable encoding will work reliably over most mail gateways, but may not work perfectly over a few gateways, notably those involving translation into EBCDIC. (In theory, an EBCDIC gateway could decode a quoted-printable body and re-encode it using base64, but such gateways do not yet exist.) A higher level of confidence is offered by the base64 Content-Transfer-Encoding. A way to get reasonably reliable transport through EBCDIC gateways is to also quote the ASCII characters
947
948 !"#$@[\]^`{|}~
949
950 according to rule #1. See Appendix B for more information.
951
952 Because quoted-printable data is generally assumed to be line- oriented, it is to be expected that the representation of the breaks between the lines of quoted printable data may be altered in transport, in the same manner that plain text mail has always been altered in Internet mail when passing between systems with differing newline conventions. If such alterations are likely to constitute a corruption of the data, it is probably more sensible to use the base64 encoding rather than the quoted-printable encoding.
953
954 WARNING TO IMPLEMENTORS: If binary data are encoded in quoted- printable, care must be taken to encode CR and LF characters as "=0D" and "=0A", respectively. In particular, a CRLF sequence in binary data should be encoded as "=0D=0A". Otherwise, if CRLF were represented as a hard line break, it might be incorrectly decoded on
955
956 platforms with different line break conventions.
957
958 For formalists, the syntax of quoted-printable data is described by the following grammar:
959
960
961 quoted-printable := ([*(ptext / SPACE / TAB) ptext] ["="] CRLF)
962 ; Maximum line length of 76 characters excluding CRLF
963
964 ptext := octet /<any ASCII character except "=", SPACE, or TAB>
965 ; characters not listed as "mail-safe" in Appendix B
966 ; are also not recommended.
967
968 octet := "=" 2(DIGIT / "A" / "B" / "C" / "D" / "E" / "F")
969 ; octet must be used for characters > 127, =, SPACE, or TAB,
970 ; and is recommended for any characters not listed in
971 ; Appendix B as "mail-safe".
972
973
974
975
976
977
978
979
980
981
982
983
984
985 */
986
987
988
989 if (defined("TYPO3_MODE") && $TYPO3_CONF_VARS[TYPO3_MODE]["XCLASS"]["t3lib/class.t3lib_htmlmail.php"]) {
990 include_once($TYPO3_CONF_VARS[TYPO3_MODE]["XCLASS"]["t3lib/class.t3lib_htmlmail.php"]);
991 }
992
993 ?>