Namespaces

  • Latte
    • Loaders
    • Macros
    • Runtime
  • Nette
    • Application
      • Responses
      • Routers
      • UI
    • Bridges
      • ApplicationDI
      • ApplicationLatte
      • ApplicationTracy
      • CacheDI
      • CacheLatte
      • DatabaseDI
      • DatabaseTracy
      • DITracy
      • FormsDI
      • FormsLatte
      • Framework
      • HttpDI
      • HttpTracy
      • MailDI
      • ReflectionDI
      • SecurityDI
      • SecurityTracy
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Conventions
      • Drivers
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
      • Traits
    • Reflection
    • Security
    • Tokenizer
    • Utils
  • Tracy
    • Bridges
      • Nette
  • none

Classes

  • FallbackMailer
  • Message
  • MimePart
  • SendmailMailer
  • SmtpMailer

Interfaces

  • IMailer

Exceptions

  • FallbackMailerException
  • SendException
  • SmtpException
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  • Other releases
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (https://nette.org)
  5:  * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
  6:  */
  7: 
  8: namespace Nette\Mail;
  9: 
 10: use Nette;
 11: use Nette\Utils\Strings;
 12: 
 13: 
 14: /**
 15:  * MIME message part.
 16:  *
 17:  * @property   string $body
 18:  */
 19: class MimePart
 20: {
 21:     use Nette\SmartObject;
 22: 
 23:     /** encoding */
 24:     const ENCODING_BASE64 = 'base64',
 25:         ENCODING_7BIT = '7bit',
 26:         ENCODING_8BIT = '8bit',
 27:         ENCODING_QUOTED_PRINTABLE = 'quoted-printable';
 28: 
 29:     /** @internal */
 30:     const EOL = "\r\n";
 31: 
 32:     const LINE_LENGTH = 76;
 33: 
 34:     /** @var array */
 35:     private $headers = [];
 36: 
 37:     /** @var array */
 38:     private $parts = [];
 39: 
 40:     /** @var string */
 41:     private $body = '';
 42: 
 43: 
 44:     /**
 45:      * Sets a header.
 46:      * @param  string
 47:      * @param  string|array  value or pair email => name
 48:      * @param  bool
 49:      * @return static
 50:      */
 51:     public function setHeader($name, $value, $append = false)
 52:     {
 53:         if (!$name || preg_match('#[^a-z0-9-]#i', $name)) {
 54:             throw new Nette\InvalidArgumentException("Header name must be non-empty alphanumeric string, '$name' given.");
 55:         }
 56: 
 57:         if ($value == null) { // intentionally ==
 58:             if (!$append) {
 59:                 unset($this->headers[$name]);
 60:             }
 61: 
 62:         } elseif (is_array($value)) { // email
 63:             $tmp = &$this->headers[$name];
 64:             if (!$append || !is_array($tmp)) {
 65:                 $tmp = [];
 66:             }
 67: 
 68:             foreach ($value as $email => $recipient) {
 69:                 if ($recipient === null) {
 70:                     // continue
 71:                 } elseif (!Strings::checkEncoding($recipient)) {
 72:                     Nette\Utils\Validators::assert($recipient, 'unicode', "header '$name'");
 73:                 } elseif (preg_match('#[\r\n]#', $recipient)) {
 74:                     throw new Nette\InvalidArgumentException('Name must not contain line separator.');
 75:                 }
 76:                 Nette\Utils\Validators::assert($email, 'email', "header '$name'");
 77:                 $tmp[$email] = $recipient;
 78:             }
 79: 
 80:         } else {
 81:             $value = (string) $value;
 82:             if (!Strings::checkEncoding($value)) {
 83:                 throw new Nette\InvalidArgumentException('Header is not valid UTF-8 string.');
 84:             }
 85:             $this->headers[$name] = preg_replace('#[\r\n]+#', ' ', $value);
 86:         }
 87:         return $this;
 88:     }
 89: 
 90: 
 91:     /**
 92:      * Returns a header.
 93:      * @param  string
 94:      * @return mixed
 95:      */
 96:     public function getHeader($name)
 97:     {
 98:         return isset($this->headers[$name]) ? $this->headers[$name] : null;
 99:     }
100: 
101: 
102:     /**
103:      * Removes a header.
104:      * @param  string
105:      * @return static
106:      */
107:     public function clearHeader($name)
108:     {
109:         unset($this->headers[$name]);
110:         return $this;
111:     }
112: 
113: 
114:     /**
115:      * Returns an encoded header.
116:      * @param  string
117:      * @return string|null
118:      */
119:     public function getEncodedHeader($name)
120:     {
121:         $offset = strlen($name) + 2; // colon + space
122: 
123:         if (!isset($this->headers[$name])) {
124:             return null;
125: 
126:         } elseif (is_array($this->headers[$name])) {
127:             $s = '';
128:             foreach ($this->headers[$name] as $email => $name) {
129:                 if ($name != null) { // intentionally ==
130:                     $s .= self::encodeHeader($name, $offset, true);
131:                     $email = " <$email>";
132:                 }
133:                 $s .= self::append($email . ',', $offset);
134:             }
135:             return ltrim(substr($s, 0, -1)); // last comma
136: 
137:         } elseif (preg_match('#^(\S+; (?:file)?name=)"(.*)"\z#', $this->headers[$name], $m)) { // Content-Disposition
138:             $offset += strlen($m[1]);
139:             return $m[1] . '"' . self::encodeHeader($m[2], $offset) . '"';
140: 
141:         } else {
142:             return ltrim(self::encodeHeader($this->headers[$name], $offset));
143:         }
144:     }
145: 
146: 
147:     /**
148:      * Returns all headers.
149:      * @return array
150:      */
151:     public function getHeaders()
152:     {
153:         return $this->headers;
154:     }
155: 
156: 
157:     /**
158:      * Sets Content-Type header.
159:      * @param  string
160:      * @param  string
161:      * @return static
162:      */
163:     public function setContentType($contentType, $charset = null)
164:     {
165:         $this->setHeader('Content-Type', $contentType . ($charset ? "; charset=$charset" : ''));
166:         return $this;
167:     }
168: 
169: 
170:     /**
171:      * Sets Content-Transfer-Encoding header.
172:      * @param  string
173:      * @return static
174:      */
175:     public function setEncoding($encoding)
176:     {
177:         $this->setHeader('Content-Transfer-Encoding', $encoding);
178:         return $this;
179:     }
180: 
181: 
182:     /**
183:      * Returns Content-Transfer-Encoding header.
184:      * @return string
185:      */
186:     public function getEncoding()
187:     {
188:         return $this->getHeader('Content-Transfer-Encoding');
189:     }
190: 
191: 
192:     /**
193:      * Adds or creates new multipart.
194:      * @return self
195:      */
196:     public function addPart(self $part = null)
197:     {
198:         return $this->parts[] = $part === null ? new self : $part;
199:     }
200: 
201: 
202:     /**
203:      * Sets textual body.
204:      * @param  string
205:      * @return static
206:      */
207:     public function setBody($body)
208:     {
209:         $this->body = (string) $body;
210:         return $this;
211:     }
212: 
213: 
214:     /**
215:      * Gets textual body.
216:      * @return string
217:      */
218:     public function getBody()
219:     {
220:         return $this->body;
221:     }
222: 
223: 
224:     /********************* building ****************d*g**/
225: 
226: 
227:     /**
228:      * Returns encoded message.
229:      * @return string
230:      */
231:     public function getEncodedMessage()
232:     {
233:         $output = '';
234:         $boundary = '--------' . Nette\Utils\Random::generate();
235: 
236:         foreach ($this->headers as $name => $value) {
237:             $output .= $name . ': ' . $this->getEncodedHeader($name);
238:             if ($this->parts && $name === 'Content-Type') {
239:                 $output .= ';' . self::EOL . "\tboundary=\"$boundary\"";
240:             }
241:             $output .= self::EOL;
242:         }
243:         $output .= self::EOL;
244: 
245:         $body = $this->body;
246:         if ($body !== '') {
247:             switch ($this->getEncoding()) {
248:                 case self::ENCODING_QUOTED_PRINTABLE:
249:                     $output .= quoted_printable_encode($body);
250:                     break;
251: 
252:                 case self::ENCODING_BASE64:
253:                     $output .= rtrim(chunk_split(base64_encode($body), self::LINE_LENGTH, self::EOL));
254:                     break;
255: 
256:                 case self::ENCODING_7BIT:
257:                     $body = preg_replace('#[\x80-\xFF]+#', '', $body);
258:                     // break omitted
259: 
260:                 case self::ENCODING_8BIT:
261:                     $body = str_replace(["\x00", "\r"], '', $body);
262:                     $body = str_replace("\n", self::EOL, $body);
263:                     $output .= $body;
264:                     break;
265: 
266:                 default:
267:                     throw new Nette\InvalidStateException('Unknown encoding.');
268:             }
269:         }
270: 
271:         if ($this->parts) {
272:             if (substr($output, -strlen(self::EOL)) !== self::EOL) {
273:                 $output .= self::EOL;
274:             }
275:             foreach ($this->parts as $part) {
276:                 $output .= '--' . $boundary . self::EOL . $part->getEncodedMessage() . self::EOL;
277:             }
278:             $output .= '--' . $boundary . '--';
279:         }
280: 
281:         return $output;
282:     }
283: 
284: 
285:     /********************* QuotedPrintable helpers ****************d*g**/
286: 
287: 
288:     /**
289:      * Converts a 8 bit header to a string.
290:      * @param  string
291:      * @param  int
292:      * @param  bool
293:      * @return string
294:      */
295:     private static function encodeHeader($s, &$offset = 0, $quotes = false)
296:     {
297:         if (strspn($s, "!\"#$%&\'()*+,-./0123456789:;<>@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^`abcdefghijklmnopqrstuvwxyz{|}~=? _\r\n\t") === strlen($s)) {
298:             if ($quotes && preg_match('#[^ a-zA-Z0-9!\#$%&\'*+/?^_`{|}~-]#', $s)) { // RFC 2822 atext except =
299:                 return self::append('"' . addcslashes($s, '"\\') . '"', $offset);
300:             }
301:             return self::append($s, $offset);
302:         }
303: 
304:         $o = '';
305:         if ($offset >= 55) { // maximum for iconv_mime_encode
306:             $o = self::EOL . "\t";
307:             $offset = 1;
308:         }
309: 
310:         $s = iconv_mime_encode(str_repeat(' ', $old = $offset), $s, [
311:             'scheme' => 'B', // Q is broken
312:             'input-charset' => 'UTF-8',
313:             'output-charset' => 'UTF-8',
314:         ]);
315: 
316:         $offset = strlen($s) - strrpos($s, "\n");
317:         $s = str_replace("\n ", "\n\t", substr($s, $old + 2)); // adds ': '
318:         return $o . $s;
319:     }
320: 
321: 
322:     private static function append($s, &$offset = 0)
323:     {
324:         if ($offset + strlen($s) > self::LINE_LENGTH) {
325:             $offset = 1;
326:             $s = self::EOL . "\t" . $s;
327:         }
328:         $offset += strlen($s);
329:         return $s;
330:     }
331: }
332: 
Nette 2.4-20180918 API API documentation generated by ApiGen 2.8.0