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