Source for file MailMimePart.php

Documentation is available at MailMimePart.php

  1. 1: <?php
  2. 2:  
  3. 3: /**
  4. 4:  * Nette Framework
  5. 5:  *
  6. 6:  * Copyright (c) 2004, 2009 David Grudl (http://davidgrudl.com)
  7. 7:  *
  8. 8:  * This source file is subject to the "Nette license" that is bundled
  9. 9:  * with this package in the file license.txt.
  10. 10:  *
  11. 11:  * For more information please see http://nettephp.com
  12. 12:  *
  13. 13:  * @copyright  Copyright (c) 2004, 2009 David Grudl
  14. 14:  * @license    http://nettephp.com/license  Nette license
  15. 15:  * @link       http://nettephp.com
  16. 16:  * @category   Nette
  17. 17:  * @package    Nette\Mail
  18. 18:  */
  19. 19:  
  20. 20:  
  21. 21:  
  22. 22: require_once dirname(__FILE__'/../Object.php';
  23. 23:  
  24. 24:  
  25. 25:  
  26. 26: /**
  27. 27:  * MIME message part.
  28. 28:  *
  29. 29:  * @author     David Grudl
  30. 30:  * @copyright  Copyright (c) 2004, 2009 David Grudl
  31. 31:  * @package    Nette\Mail
  32. 32:  *
  33. 33:  * @property   string $encoding 
  34. 34:  * @property   string $body 
  35. 35:  * @property-read array $headers 
  36. 36:  */
  37. 37: class MailMimePart extends Object
  38. 38: {
  39. 39:     /**#@+ encoding */
  40. 40:     const ENCODING_BASE64 = 'base64';
  41. 41:     const ENCODING_7BIT = '7bit';
  42. 42:     const ENCODING_8BIT = '8bit';
  43. 43:     const ENCODING_QUOTED_PRINTABLE = 'quoted-printable';
  44. 44:     /**#@-*/
  45. 45:  
  46. 46:     /**#@+ @ignore internal */
  47. 47:     const EOL "\r\n";
  48. 48:     const LINE_LENGTH 76;
  49. 49:     /**#@-*/
  50. 50:  
  51. 51:     /** @var array */
  52. 52:     private $headers array();
  53. 53:  
  54. 54:     /** @var array */
  55. 55:     private $parts array();
  56. 56:  
  57. 57:     /** @var string */
  58. 58:     private $body;
  59. 59:  
  60. 60:  
  61. 61:  
  62. 62:     /**
  63. 63:      * Sets a header.
  64. 64:      * @param  string 
  65. 65:      * @param  string|array value or pair email => name
  66. 66:      * @param  bool 
  67. 67:      * @return MailMimePart  provides a fluent interface
  68. 68:      */
  69. 69:     public function setHeader($name$value$append FALSE)
  70. 70:     {
  71. 71:         if (!$name || preg_match('#[^a-z0-9-]#i'$name)) {
  72. 72:             throw new InvalidArgumentException("Header name must be non-empty alphanumeric string, '$name' given.");
  73. 73:         }
  74. 74:  
  75. 75:         if ($value == NULL// intentionally ==
  76. 76:             if (!$append{
  77. 77:                 unset($this->headers[$name]);
  78. 78:             }
  79. 79:  
  80. 80:         elseif (is_array($value)) // email
  81. 81:             $tmp $this->headers[$name];
  82. 82:             if (!$append || !is_array($tmp)) {
  83. 83:                 $tmp array();
  84. 84:             }
  85. 85:  
  86. 86:             foreach ($value as $email => $name{
  87. 87:                 if (!preg_match('#^[^@",\s]+@[^@",\s]+\.[a-z]{2,10}$#i'$email)) {
  88. 88:                     throw new InvalidArgumentException("Email address '$email' is not valid.");
  89. 89:                 }
  90. 90:  
  91. 91:                 if (preg_match('#[\r\n]#'$name)) {
  92. 92:                     throw new InvalidArgumentException("Name cannot contain the line separator.");
  93. 93:                 }
  94. 94:                 $tmp[$email$name;
  95. 95:             }
  96. 96:  
  97. 97:         else {
  98. 98:             $this->headers[$namepreg_replace('#[\r\n]+#'' '$value);
  99. 99:         }
  100. 100:         return $this;
  101. 101:     }
  102. 102:  
  103. 103:  
  104. 104:  
  105. 105:     /**
  106. 106:      * Returns a header.
  107. 107:      * @param  string 
  108. 108:      * @return mixed 
  109. 109:      */
  110. 110:     public function getHeader($name)
  111. 111:     {
  112. 112:         return isset($this->headers[$name]$this->headers[$nameNULL;
  113. 113:     }
  114. 114:  
  115. 115:  
  116. 116:  
  117. 117:     /**
  118. 118:      * Removes a header.
  119. 119:      * @param  string 
  120. 120:      * @return MailMimePart  provides a fluent interface
  121. 121:      */
  122. 122:     public function clearHeader($name)
  123. 123:     {
  124. 124:         unset($this->headers[$name]);
  125. 125:         return $this;
  126. 126:     }
  127. 127:  
  128. 128:  
  129. 129:  
  130. 130:     /**
  131. 131:      * Returns an encoded header.
  132. 132:      * @param  string 
  133. 133:      * @param  string 
  134. 134:      * @return string 
  135. 135:      */
  136. 136:     public function getEncodedHeader($name$charset 'UTF-8')
  137. 137:     {
  138. 138:         $len strlen($name2;
  139. 139:  
  140. 140:         if (!isset($this->headers[$name])) {
  141. 141:             return NULL;
  142. 142:  
  143. 143:         elseif (is_array($this->headers[$name])) {
  144. 144:             $s '';
  145. 145:             foreach ($this->headers[$nameas $email => $name{
  146. 146:                 if ($name != NULL// intentionally ==
  147. 147:                     $s .= self::encodeQuotedPrintableHeader(
  148. 148:                         strspn($name'.,;<@>()[]"=?''"' addcslashes($name'"\\''"' $name,
  149. 149:                         $charset$len
  150. 150:                     );
  151. 151:                     $email " <$email>";
  152. 152:                 }
  153. 153:                 if ($len strlen($emailself::LINE_LENGTH{
  154. 154:                     $s .= self::EOL "\t";
  155. 155:                     $len 1;
  156. 156:                 }
  157. 157:                 $s .= "$email,";
  158. 158:                 $len += strlen($email1;
  159. 159:             }
  160. 160:             return substr($s0-1);
  161. 161:  
  162. 162:         else {
  163. 163:             return self::encodeQuotedPrintableHeader($this->headers[$name]$charset$len);
  164. 164:         }
  165. 165:     }
  166. 166:  
  167. 167:  
  168. 168:  
  169. 169:     /**
  170. 170:      * Returns all headers.
  171. 171:      * @return array 
  172. 172:      */
  173. 173:     public function getHeaders()
  174. 174:     {
  175. 175:         return $this->headers;
  176. 176:     }
  177. 177:  
  178. 178:  
  179. 179:  
  180. 180:     /**
  181. 181:      * Sets Content-Type header.
  182. 182:      * @param  string 
  183. 183:      * @param  string 
  184. 184:      * @return MailMimePart  provides a fluent interface
  185. 185:      */
  186. 186:     public function setContentType($contentType$charset NULL)
  187. 187:     {
  188. 188:         $this->setHeader('Content-Type'$contentType ($charset "; charset=$charset''));
  189. 189:         return $this;
  190. 190:     }
  191. 191:  
  192. 192:  
  193. 193:  
  194. 194:     /**
  195. 195:      * Sets Content-Transfer-Encoding header.
  196. 196:      * @param  string 
  197. 197:      * @return MailMimePart  provides a fluent interface
  198. 198:      */
  199. 199:     public function setEncoding($encoding)
  200. 200:     {
  201. 201:         $this->setHeader('Content-Transfer-Encoding'$encoding);
  202. 202:         return $this;
  203. 203:     }
  204. 204:  
  205. 205:  
  206. 206:  
  207. 207:     /**
  208. 208:      * Returns Content-Transfer-Encoding header.
  209. 209:      * @return string 
  210. 210:      */
  211. 211:     public function getEncoding()
  212. 212:     {
  213. 213:         return $this->getHeader('Content-Transfer-Encoding');
  214. 214:     }
  215. 215:  
  216. 216:  
  217. 217:  
  218. 218:     /**
  219. 219:      * Adds or creates new multipart.
  220. 220:      * @param  MailMimePart 
  221. 221:      * @return MailMimePart 
  222. 222:      */
  223. 223:     public function addPart(MailMimePart $part NULL)
  224. 224:     {
  225. 225:         return $this->parts[$part === NULL new self $part;
  226. 226:     }
  227. 227:  
  228. 228:  
  229. 229:  
  230. 230:     /**
  231. 231:      * Sets textual body.
  232. 232:      * @param  mixed 
  233. 233:      * @return MailMimePart  provides a fluent interface
  234. 234:      */
  235. 235:     public function setBody($body)
  236. 236:     {
  237. 237:         $this->body $body;
  238. 238:         return $this;
  239. 239:     }
  240. 240:  
  241. 241:  
  242. 242:  
  243. 243:     /**
  244. 244:      * Gets textual body.
  245. 245:      * @return mixed 
  246. 246:      */
  247. 247:     public function getBody()
  248. 248:     {
  249. 249:         return $this->body;
  250. 250:     }
  251. 251:  
  252. 252:  
  253. 253:  
  254. 254:     /********************* building ****************d*g**/
  255. 255:  
  256. 256:  
  257. 257:  
  258. 258:     /**
  259. 259:      * Returns encoded message.
  260. 260:      * @return string 
  261. 261:      */
  262. 262:     public function generateMessage()
  263. 263:     {
  264. 264:         $output '';
  265. 265:         $boundary '--------' md5(uniqid(''TRUE));
  266. 266:  
  267. 267:         foreach ($this->headers as $name => $value{
  268. 268:             $output .= $name ': ' $this->getEncodedHeader($name);
  269. 269:             if ($this->parts && $name === 'Content-Type'{
  270. 270:                 $output .= ';' self::EOL "\tboundary=\"$boundary\"";
  271. 271:             }
  272. 272:             $output .= self::EOL;
  273. 273:         }
  274. 274:         $output .= self::EOL;
  275. 275:  
  276. 276:         $body = (string) $this->body;
  277. 277:         if ($body !== ''{
  278. 278:             switch ($this->getEncoding()) {
  279. 279:             case self::ENCODING_QUOTED_PRINTABLE:
  280. 280:                 $output .= function_exists('quoted_printable_encode'quoted_printable_encode($bodyself::encodeQuotedPrintable($body);
  281. 281:                 break;
  282. 282:  
  283. 283:             case self::ENCODING_BASE64:
  284. 284:                 $output .= rtrim(chunk_split(base64_encode($body)self::LINE_LENGTHself::EOL));
  285. 285:                 break;
  286. 286:  
  287. 287:             case self::ENCODING_7BIT:
  288. 288:                 $body preg_replace('#[\x80-\xFF]+#'''$body);
  289. 289:                 // break intentionally omitted
  290. 290:  
  291. 291:             case self::ENCODING_8BIT:
  292. 292:                 $body str_replace(array("\x00""\r")''$body);
  293. 293:                 $body str_replace("\n"self::EOL$body);
  294. 294:                 $output .= $body;
  295. 295:                 break;
  296. 296:  
  297. 297:             default:
  298. 298:                 throw new InvalidStateException('Unknown encoding');
  299. 299:             }
  300. 300:         }
  301. 301:  
  302. 302:         if ($this->parts{
  303. 303:             if (substr($output-strlen(self::EOL)) !== self::EOL$output .= self::EOL;
  304. 304:             foreach ($this->parts as $part{
  305. 305:                 $output .= '--' $boundary self::EOL $part->generateMessage(self::EOL;
  306. 306:             }
  307. 307:             $output .= '--' $boundary.'--';
  308. 308:         }
  309. 309:  
  310. 310:         return $output;
  311. 311:     }
  312. 312:  
  313. 313:  
  314. 314:  
  315. 315:     /********************* QuotedPrintable helpers ****************d*g**/
  316. 316:  
  317. 317:  
  318. 318:  
  319. 319:     /**
  320. 320:      * Converts a 8 bit header to a quoted-printable string.
  321. 321:      * @param  string 
  322. 322:      * @param  string 
  323. 323:      * @param  int 
  324. 324:      * @return string 
  325. 325:      */
  326. 326:     private static function encodeQuotedPrintableHeader($s$charset 'UTF-8'$len 0)
  327. 327:     {
  328. 328:         $range '!"#$%&\'()*+,-./0123456789:;<>@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^`abcdefghijklmnopqrstuvwxyz{|}'// \x21-\x7E without \x3D \x3F \x5F
  329. 329:  
  330. 330:         if (strspn($s$range "=? _\r\n\t"=== strlen($s)) {
  331. 331:             return $s;
  332. 332:         }
  333. 333:  
  334. 334:         $prefix "=?$charset?Q?";
  335. 335:         $pos 0;
  336. 336:         $len += strlen($prefix);
  337. 337:         $o $prefix;
  338. 338:         $size strlen($s);
  339. 339:         while ($pos $size{
  340. 340:             if ($l strspn($s$range$pos)) {
  341. 341:                 while ($len $l self::LINE_LENGTH 2// 2 = length of suffix ?=
  342. 342:                     $lx self::LINE_LENGTH $len 2;
  343. 343:                     $o .= substr($s$pos$lx'?=' self::EOL "\t" $prefix;
  344. 344:                     $pos += $lx;
  345. 345:                     $l -= $lx;
  346. 346:                     $len strlen($prefix1;
  347. 347:                 }
  348. 348:                 $o .= substr($s$pos$l);
  349. 349:                 $len += $l;
  350. 350:                 $pos += $l;
  351. 351:  
  352. 352:             else {
  353. 353:                 $len += 3;
  354. 354:                 // \xC0 tests UTF-8 character boudnary; 9 is reserved space for 4bytes UTF-8 character
  355. 355:                 if (($s[$pos"\xC0"!== "\x80" && $len self::LINE_LENGTH 9{
  356. 356:                     $o .= '?=' self::EOL "\t" $prefix;
  357. 357:                     $len strlen($prefix3;
  358. 358:                 }
  359. 359:                 $o .= '=' strtoupper(bin2hex($s[$pos]));
  360. 360:                 $pos++;
  361. 361:             }
  362. 362:         }
  363. 363:         return $o '?=';
  364. 364:     }
  365. 365:  
  366. 366:  
  367. 367:  
  368. 368:     /**
  369. 369:      * Converts a 8 bit string to a quoted-printable string.
  370. 370:      * @param  string 
  371. 371:      * @return string 
  372. 372:      */
  373. 373:     public static function encodeQuotedPrintable($s)
  374. 374:     {
  375. 375:         $range '!"#$%&\'()*+,-./0123456789:;<>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}'// \x21-\x7E without \x3D
  376. 376:         $pos 0;
  377. 377:         $len 0;
  378. 378:         $o '';
  379. 379:         $size strlen($s);
  380. 380:         while ($pos $size{
  381. 381:             if ($l strspn($s$range$pos)) {
  382. 382:                 while ($len $l self::LINE_LENGTH 1// 1 = length of suffix =
  383. 383:                     $lx self::LINE_LENGTH $len 1;
  384. 384:                     $o .= substr($s$pos$lx'=' self::EOL;
  385. 385:                     $pos += $lx;
  386. 386:                     $l -= $lx;
  387. 387:                     $len 0;
  388. 388:                 }
  389. 389:                 $o .= substr($s$pos$l);
  390. 390:                 $len += $l;
  391. 391:                 $pos += $l;
  392. 392:  
  393. 393:             else {
  394. 394:                 $len += 3;
  395. 395:                 if ($len self::LINE_LENGTH 1{
  396. 396:                     $o .= '=' self::EOL;
  397. 397:                     $len 3;
  398. 398:                 }
  399. 399:                 $o .= '=' strtoupper(bin2hex($s[$pos]));
  400. 400:                 $pos++;
  401. 401:             }
  402. 402:         }
  403. 403:         return rtrim($o'=' self::EOL);
  404. 404:     }
  405. 405: