CakePHP
  • Documentation
    • Book
    • API
    • Videos
    • Reporting Security Issues
    • Privacy Policy
    • Logos & Trademarks
  • Business Solutions
  • Swag
  • Road Trip
  • Team
  • Community
    • Community
    • Get Involved
    • Issues (GitHub)
    • Bakery
    • Featured Resources
    • Training
    • Meetups
    • My CakePHP
    • CakeFest
    • Newsletter
    • Linkedin
    • YouTube
    • Facebook
    • Twitter
    • Mastodon
    • Help & Support
    • Forum
    • Stack Overflow
    • Slack
    • Paid Support
CakePHP

C CakePHP 2.10 API

  • Overview
  • Tree
  • Deprecated
  • Version:
    • 2.10
      • 4.2
      • 4.1
      • 4.0
      • 3.9
      • 3.8
      • 3.7
      • 3.6
      • 3.5
      • 3.4
      • 3.3
      • 3.2
      • 3.1
      • 3.0
      • 2.10
      • 2.9
      • 2.8
      • 2.7
      • 2.6
      • 2.5
      • 2.4
      • 2.3
      • 2.2
      • 2.1
      • 2.0
      • 1.3
      • 1.2

Packages

  • Cake
    • Cache
      • Engine
    • Configure
    • Console
      • Command
        • Task
    • Controller
      • Component
        • Acl
        • Auth
    • Core
    • Error
    • Event
    • I18n
    • Log
      • Engine
    • Model
      • Behavior
      • Datasource
        • Database
        • Session
      • Validator
    • Network
      • Email
      • Http
    • Routing
      • Filter
      • Route
    • TestSuite
      • Coverage
      • Fixture
      • Reporter
    • Utility
    • View
      • Helper
  • None

Classes

  • AbstractTransport
  • CakeEmail
  • DebugTransport
  • MailTransport
  • SmtpTransport
   1: <?php
   2: /**
   3:  * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
   4:  * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
   5:  *
   6:  * Licensed under The MIT License
   7:  * For full copyright and license information, please see the LICENSE.txt
   8:  * Redistributions of files must retain the above copyright notice.
   9:  *
  10:  * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  11:  * @link          https://cakephp.org CakePHP(tm) Project
  12:  * @package       Cake.Network.Email
  13:  * @since         CakePHP(tm) v 2.0.0
  14:  * @license       https://opensource.org/licenses/mit-license.php MIT License
  15:  */
  16: 
  17: App::uses('Multibyte', 'I18n');
  18: App::uses('AbstractTransport', 'Network/Email');
  19: App::uses('File', 'Utility');
  20: App::uses('CakeText', 'Utility');
  21: App::uses('View', 'View');
  22: 
  23: /**
  24:  * CakePHP email class.
  25:  *
  26:  * This class is used for handling Internet Message Format based
  27:  * based on the standard outlined in http://www.rfc-editor.org/rfc/rfc2822.txt
  28:  *
  29:  * @package       Cake.Network.Email
  30:  */
  31: class CakeEmail {
  32: 
  33: /**
  34:  * Default X-Mailer
  35:  *
  36:  * @var string
  37:  */
  38:     const EMAIL_CLIENT = 'CakePHP Email';
  39: 
  40: /**
  41:  * Line length - no should more - RFC 2822 - 2.1.1
  42:  *
  43:  * @var int
  44:  */
  45:     const LINE_LENGTH_SHOULD = 78;
  46: 
  47: /**
  48:  * Line length - no must more - RFC 2822 - 2.1.1
  49:  *
  50:  * @var int
  51:  */
  52:     const LINE_LENGTH_MUST = 998;
  53: 
  54: /**
  55:  * Type of message - HTML
  56:  *
  57:  * @var string
  58:  */
  59:     const MESSAGE_HTML = 'html';
  60: 
  61: /**
  62:  * Type of message - TEXT
  63:  *
  64:  * @var string
  65:  */
  66:     const MESSAGE_TEXT = 'text';
  67: 
  68: /**
  69:  * Holds the regex pattern for email validation
  70:  *
  71:  * @var string
  72:  */
  73:     const EMAIL_PATTERN = '/^((?:[\p{L}0-9.!#$%&\'*+\/=?^_`{|}~-]+)*@[\p{L}0-9-_.]+)$/ui';
  74: 
  75: /**
  76:  * Recipient of the email
  77:  *
  78:  * @var array
  79:  */
  80:     protected $_to = array();
  81: 
  82: /**
  83:  * The mail which the email is sent from
  84:  *
  85:  * @var array
  86:  */
  87:     protected $_from = array();
  88: 
  89: /**
  90:  * The sender email
  91:  *
  92:  * @var array
  93:  */
  94:     protected $_sender = array();
  95: 
  96: /**
  97:  * The email the recipient will reply to
  98:  *
  99:  * @var array
 100:  */
 101:     protected $_replyTo = array();
 102: 
 103: /**
 104:  * The read receipt email
 105:  *
 106:  * @var array
 107:  */
 108:     protected $_readReceipt = array();
 109: 
 110: /**
 111:  * The mail that will be used in case of any errors like
 112:  * - Remote mailserver down
 113:  * - Remote user has exceeded his quota
 114:  * - Unknown user
 115:  *
 116:  * @var array
 117:  */
 118:     protected $_returnPath = array();
 119: 
 120: /**
 121:  * Carbon Copy
 122:  *
 123:  * List of email's that should receive a copy of the email.
 124:  * The Recipient WILL be able to see this list
 125:  *
 126:  * @var array
 127:  */
 128:     protected $_cc = array();
 129: 
 130: /**
 131:  * Blind Carbon Copy
 132:  *
 133:  * List of email's that should receive a copy of the email.
 134:  * The Recipient WILL NOT be able to see this list
 135:  *
 136:  * @var array
 137:  */
 138:     protected $_bcc = array();
 139: 
 140: /**
 141:  * Message ID
 142:  *
 143:  * @var bool|string
 144:  */
 145:     protected $_messageId = true;
 146: 
 147: /**
 148:  * Domain for messageId generation.
 149:  * Needs to be manually set for CLI mailing as env('HTTP_HOST') is empty
 150:  *
 151:  * @var string
 152:  */
 153:     protected $_domain = null;
 154: 
 155: /**
 156:  * The subject of the email
 157:  *
 158:  * @var string
 159:  */
 160:     protected $_subject = '';
 161: 
 162: /**
 163:  * Associative array of a user defined headers
 164:  * Keys will be prefixed 'X-' as per RFC2822 Section 4.7.5
 165:  *
 166:  * @var array
 167:  */
 168:     protected $_headers = array();
 169: 
 170: /**
 171:  * Layout for the View
 172:  *
 173:  * @var string
 174:  */
 175:     protected $_layout = 'default';
 176: 
 177: /**
 178:  * Template for the view
 179:  *
 180:  * @var string
 181:  */
 182:     protected $_template = '';
 183: 
 184: /**
 185:  * View for render
 186:  *
 187:  * @var string
 188:  */
 189:     protected $_viewRender = 'View';
 190: 
 191: /**
 192:  * Vars to sent to render
 193:  *
 194:  * @var array
 195:  */
 196:     protected $_viewVars = array();
 197: 
 198: /**
 199:  * Theme for the View
 200:  *
 201:  * @var array
 202:  */
 203:     protected $_theme = null;
 204: 
 205: /**
 206:  * Helpers to be used in the render
 207:  *
 208:  * @var array
 209:  */
 210:     protected $_helpers = array('Html');
 211: 
 212: /**
 213:  * Text message
 214:  *
 215:  * @var string
 216:  */
 217:     protected $_textMessage = '';
 218: 
 219: /**
 220:  * Html message
 221:  *
 222:  * @var string
 223:  */
 224:     protected $_htmlMessage = '';
 225: 
 226: /**
 227:  * Final message to send
 228:  *
 229:  * @var array
 230:  */
 231:     protected $_message = array();
 232: 
 233: /**
 234:  * Available formats to be sent.
 235:  *
 236:  * @var array
 237:  */
 238:     protected $_emailFormatAvailable = array('text', 'html', 'both');
 239: 
 240: /**
 241:  * What format should the email be sent in
 242:  *
 243:  * @var string
 244:  */
 245:     protected $_emailFormat = 'text';
 246: 
 247: /**
 248:  * What method should the email be sent
 249:  *
 250:  * @var string
 251:  */
 252:     protected $_transportName = 'Mail';
 253: 
 254: /**
 255:  * Instance of transport class
 256:  *
 257:  * @var AbstractTransport
 258:  */
 259:     protected $_transportClass = null;
 260: 
 261: /**
 262:  * Charset the email body is sent in
 263:  *
 264:  * @var string
 265:  */
 266:     public $charset = 'utf-8';
 267: 
 268: /**
 269:  * Charset the email header is sent in
 270:  * If null, the $charset property will be used as default
 271:  *
 272:  * @var string
 273:  */
 274:     public $headerCharset = null;
 275: 
 276: /**
 277:  * The application wide charset, used to encode headers and body
 278:  *
 279:  * @var string
 280:  */
 281:     protected $_appCharset = null;
 282: 
 283: /**
 284:  * List of files that should be attached to the email.
 285:  *
 286:  * Only absolute paths
 287:  *
 288:  * @var array
 289:  */
 290:     protected $_attachments = array();
 291: 
 292: /**
 293:  * If set, boundary to use for multipart mime messages
 294:  *
 295:  * @var string
 296:  */
 297:     protected $_boundary = null;
 298: 
 299: /**
 300:  * Configuration to transport
 301:  *
 302:  * @var string|array
 303:  */
 304:     protected $_config = array();
 305: 
 306: /**
 307:  * 8Bit character sets
 308:  *
 309:  * @var array
 310:  */
 311:     protected $_charset8bit = array('UTF-8', 'SHIFT_JIS');
 312: 
 313: /**
 314:  * Define Content-Type charset name
 315:  *
 316:  * @var array
 317:  */
 318:     protected $_contentTypeCharset = array(
 319:         'ISO-2022-JP-MS' => 'ISO-2022-JP'
 320:     );
 321: 
 322: /**
 323:  * Regex for email validation
 324:  *
 325:  * If null, filter_var() will be used. Use the emailPattern() method
 326:  * to set a custom pattern.'
 327:  *
 328:  * @var string
 329:  */
 330:     protected $_emailPattern = self::EMAIL_PATTERN;
 331: 
 332: /**
 333:  * The class name used for email configuration.
 334:  *
 335:  * @var string
 336:  */
 337:     protected $_configClass = 'EmailConfig';
 338: 
 339: /**
 340:  * An instance of the EmailConfig class can be set here
 341:  *
 342:  * @var EmailConfig
 343:  */
 344:     protected $_configInstance;
 345: 
 346: /**
 347:  * Constructor
 348:  *
 349:  * @param array|string $config Array of configs, or string to load configs from email.php
 350:  */
 351:     public function __construct($config = null) {
 352:         $this->_appCharset = Configure::read('App.encoding');
 353:         if ($this->_appCharset !== null) {
 354:             $this->charset = $this->_appCharset;
 355:         }
 356:         $this->_domain = preg_replace('/\:\d+$/', '', env('HTTP_HOST'));
 357:         if (empty($this->_domain)) {
 358:             $this->_domain = php_uname('n');
 359:         }
 360: 
 361:         if ($config) {
 362:             $this->config($config);
 363:         } elseif (config('email') && class_exists($this->_configClass)) {
 364:             $this->_configInstance = new $this->_configClass();
 365:             if (isset($this->_configInstance->default)) {
 366:                 $this->config('default');
 367:             }
 368:         }
 369:         if (empty($this->headerCharset)) {
 370:             $this->headerCharset = $this->charset;
 371:         }
 372:     }
 373: 
 374: /**
 375:  * From
 376:  *
 377:  * @param string|array $email Null to get, String with email,
 378:  *   Array with email as key, name as value or email as value (without name)
 379:  * @param string $name Name
 380:  * @return array|CakeEmail
 381:  * @throws SocketException
 382:  */
 383:     public function from($email = null, $name = null) {
 384:         if ($email === null) {
 385:             return $this->_from;
 386:         }
 387:         return $this->_setEmailSingle('_from', $email, $name, __d('cake_dev', 'From requires only 1 email address.'));
 388:     }
 389: 
 390: /**
 391:  * Sender
 392:  *
 393:  * @param string|array $email Null to get, String with email,
 394:  *   Array with email as key, name as value or email as value (without name)
 395:  * @param string $name Name
 396:  * @return array|CakeEmail
 397:  * @throws SocketException
 398:  */
 399:     public function sender($email = null, $name = null) {
 400:         if ($email === null) {
 401:             return $this->_sender;
 402:         }
 403:         return $this->_setEmailSingle('_sender', $email, $name, __d('cake_dev', 'Sender requires only 1 email address.'));
 404:     }
 405: 
 406: /**
 407:  * Reply-To
 408:  *
 409:  * @param string|array $email Null to get, String with email,
 410:  *   Array with email as key, name as value or email as value (without name)
 411:  * @param string $name Name
 412:  * @return array|CakeEmail
 413:  * @throws SocketException
 414:  */
 415:     public function replyTo($email = null, $name = null) {
 416:         if ($email === null) {
 417:             return $this->_replyTo;
 418:         }
 419:         return $this->_setEmailSingle('_replyTo', $email, $name, __d('cake_dev', 'Reply-To requires only 1 email address.'));
 420:     }
 421: 
 422: /**
 423:  * Read Receipt (Disposition-Notification-To header)
 424:  *
 425:  * @param string|array $email Null to get, String with email,
 426:  *   Array with email as key, name as value or email as value (without name)
 427:  * @param string $name Name
 428:  * @return array|CakeEmail
 429:  * @throws SocketException
 430:  */
 431:     public function readReceipt($email = null, $name = null) {
 432:         if ($email === null) {
 433:             return $this->_readReceipt;
 434:         }
 435:         return $this->_setEmailSingle('_readReceipt', $email, $name, __d('cake_dev', 'Disposition-Notification-To requires only 1 email address.'));
 436:     }
 437: 
 438: /**
 439:  * Return Path
 440:  *
 441:  * @param string|array $email Null to get, String with email,
 442:  *   Array with email as key, name as value or email as value (without name)
 443:  * @param string $name Name
 444:  * @return array|CakeEmail
 445:  * @throws SocketException
 446:  */
 447:     public function returnPath($email = null, $name = null) {
 448:         if ($email === null) {
 449:             return $this->_returnPath;
 450:         }
 451:         return $this->_setEmailSingle('_returnPath', $email, $name, __d('cake_dev', 'Return-Path requires only 1 email address.'));
 452:     }
 453: 
 454: /**
 455:  * To
 456:  *
 457:  * @param string|array $email Null to get, String with email,
 458:  *   Array with email as key, name as value or email as value (without name)
 459:  * @param string $name Name
 460:  * @return array|self
 461:  */
 462:     public function to($email = null, $name = null) {
 463:         if ($email === null) {
 464:             return $this->_to;
 465:         }
 466:         return $this->_setEmail('_to', $email, $name);
 467:     }
 468: 
 469: /**
 470:  * Add To
 471:  *
 472:  * @param string|array $email Null to get, String with email,
 473:  *   Array with email as key, name as value or email as value (without name)
 474:  * @param string $name Name
 475:  * @return self
 476:  */
 477:     public function addTo($email, $name = null) {
 478:         return $this->_addEmail('_to', $email, $name);
 479:     }
 480: 
 481: /**
 482:  * Cc
 483:  *
 484:  * @param string|array $email Null to get, String with email,
 485:  *   Array with email as key, name as value or email as value (without name)
 486:  * @param string $name Name
 487:  * @return array|self
 488:  */
 489:     public function cc($email = null, $name = null) {
 490:         if ($email === null) {
 491:             return $this->_cc;
 492:         }
 493:         return $this->_setEmail('_cc', $email, $name);
 494:     }
 495: 
 496: /**
 497:  * Add Cc
 498:  *
 499:  * @param string|array $email Null to get, String with email,
 500:  *   Array with email as key, name as value or email as value (without name)
 501:  * @param string $name Name
 502:  * @return self
 503:  */
 504:     public function addCc($email, $name = null) {
 505:         return $this->_addEmail('_cc', $email, $name);
 506:     }
 507: 
 508: /**
 509:  * Bcc
 510:  *
 511:  * @param string|array $email Null to get, String with email,
 512:  *   Array with email as key, name as value or email as value (without name)
 513:  * @param string $name Name
 514:  * @return array|self
 515:  */
 516:     public function bcc($email = null, $name = null) {
 517:         if ($email === null) {
 518:             return $this->_bcc;
 519:         }
 520:         return $this->_setEmail('_bcc', $email, $name);
 521:     }
 522: 
 523: /**
 524:  * Add Bcc
 525:  *
 526:  * @param string|array $email Null to get, String with email,
 527:  *   Array with email as key, name as value or email as value (without name)
 528:  * @param string $name Name
 529:  * @return self
 530:  */
 531:     public function addBcc($email, $name = null) {
 532:         return $this->_addEmail('_bcc', $email, $name);
 533:     }
 534: 
 535: /**
 536:  * Charset setter/getter
 537:  *
 538:  * @param string $charset Character set.
 539:  * @return string this->charset
 540:  */
 541:     public function charset($charset = null) {
 542:         if ($charset === null) {
 543:             return $this->charset;
 544:         }
 545:         $this->charset = $charset;
 546:         if (empty($this->headerCharset)) {
 547:             $this->headerCharset = $charset;
 548:         }
 549:         return $this->charset;
 550:     }
 551: 
 552: /**
 553:  * HeaderCharset setter/getter
 554:  *
 555:  * @param string $charset Character set.
 556:  * @return string this->charset
 557:  */
 558:     public function headerCharset($charset = null) {
 559:         if ($charset === null) {
 560:             return $this->headerCharset;
 561:         }
 562:         return $this->headerCharset = $charset;
 563:     }
 564: 
 565: /**
 566:  * EmailPattern setter/getter
 567:  *
 568:  * @param string|bool|null $regex The pattern to use for email address validation,
 569:  *   null to unset the pattern and make use of filter_var() instead, false or
 570:  *   nothing to return the current value
 571:  * @return string|self
 572:  */
 573:     public function emailPattern($regex = false) {
 574:         if ($regex === false) {
 575:             return $this->_emailPattern;
 576:         }
 577:         $this->_emailPattern = $regex;
 578:         return $this;
 579:     }
 580: 
 581: /**
 582:  * Set email
 583:  *
 584:  * @param string $varName Property name
 585:  * @param string|array $email String with email,
 586:  *   Array with email as key, name as value or email as value (without name)
 587:  * @param string $name Name
 588:  * @return self
 589:  */
 590:     protected function _setEmail($varName, $email, $name) {
 591:         if (!is_array($email)) {
 592:             $this->_validateEmail($email, $varName);
 593:             if ($name === null) {
 594:                 $name = $email;
 595:             }
 596:             $this->{$varName} = array($email => $name);
 597:             return $this;
 598:         }
 599:         $list = array();
 600:         foreach ($email as $key => $value) {
 601:             if (is_int($key)) {
 602:                 $key = $value;
 603:             }
 604:             $this->_validateEmail($key, $varName);
 605:             $list[$key] = $value;
 606:         }
 607:         $this->{$varName} = $list;
 608:         return $this;
 609:     }
 610: 
 611: /**
 612:  * Validate email address
 613:  *
 614:  * @param string $email Email address to validate
 615:  * @param string $context Which property was set
 616:  * @return void
 617:  * @throws SocketException If email address does not validate
 618:  */
 619:     protected function _validateEmail($email, $context) {
 620:         if ($this->_emailPattern === null) {
 621:             if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
 622:                 return;
 623:             }
 624:         } elseif (preg_match($this->_emailPattern, $email)) {
 625:             return;
 626:         }
 627:         if ($email == '') {
 628:             throw new SocketException(__d('cake_dev', 'The email set for "%s" is empty.', $context));
 629:         }
 630:         throw new SocketException(__d('cake_dev', 'Invalid email set for "%s". You passed "%s".', $context, $email));
 631:     }
 632: 
 633: /**
 634:  * Set only 1 email
 635:  *
 636:  * @param string $varName Property name
 637:  * @param string|array $email String with email,
 638:  *   Array with email as key, name as value or email as value (without name)
 639:  * @param string $name Name
 640:  * @param string $throwMessage Exception message
 641:  * @return self
 642:  * @throws SocketException
 643:  */
 644:     protected function _setEmailSingle($varName, $email, $name, $throwMessage) {
 645:         $current = $this->{$varName};
 646:         $this->_setEmail($varName, $email, $name);
 647:         if (count($this->{$varName}) !== 1) {
 648:             $this->{$varName} = $current;
 649:             throw new SocketException($throwMessage);
 650:         }
 651:         return $this;
 652:     }
 653: 
 654: /**
 655:  * Add email
 656:  *
 657:  * @param string $varName Property name
 658:  * @param string|array $email String with email,
 659:  *   Array with email as key, name as value or email as value (without name)
 660:  * @param string $name Name
 661:  * @return self
 662:  * @throws SocketException
 663:  */
 664:     protected function _addEmail($varName, $email, $name) {
 665:         if (!is_array($email)) {
 666:             $this->_validateEmail($email, $varName);
 667:             if ($name === null) {
 668:                 $name = $email;
 669:             }
 670:             $this->{$varName}[$email] = $name;
 671:             return $this;
 672:         }
 673:         $list = array();
 674:         foreach ($email as $key => $value) {
 675:             if (is_int($key)) {
 676:                 $key = $value;
 677:             }
 678:             $this->_validateEmail($key, $varName);
 679:             $list[$key] = $value;
 680:         }
 681:         $this->{$varName} = array_merge($this->{$varName}, $list);
 682:         return $this;
 683:     }
 684: 
 685: /**
 686:  * Get/Set Subject.
 687:  *
 688:  * @param string $subject Subject string.
 689:  * @return string|self
 690:  */
 691:     public function subject($subject = null) {
 692:         if ($subject === null) {
 693:             return $this->_subject;
 694:         }
 695:         $this->_subject = $this->_encode((string)$subject);
 696:         return $this;
 697:     }
 698: 
 699: /**
 700:  * Sets headers for the message
 701:  *
 702:  * @param array $headers Associative array containing headers to be set.
 703:  * @return self
 704:  * @throws SocketException
 705:  */
 706:     public function setHeaders($headers) {
 707:         if (!is_array($headers)) {
 708:             throw new SocketException(__d('cake_dev', '$headers should be an array.'));
 709:         }
 710:         $this->_headers = $headers;
 711:         return $this;
 712:     }
 713: 
 714: /**
 715:  * Add header for the message
 716:  *
 717:  * @param array $headers Headers to set.
 718:  * @return self
 719:  * @throws SocketException
 720:  */
 721:     public function addHeaders($headers) {
 722:         if (!is_array($headers)) {
 723:             throw new SocketException(__d('cake_dev', '$headers should be an array.'));
 724:         }
 725:         $this->_headers = array_merge($this->_headers, $headers);
 726:         return $this;
 727:     }
 728: 
 729: /**
 730:  * Get list of headers
 731:  *
 732:  * ### Includes:
 733:  *
 734:  * - `from`
 735:  * - `replyTo`
 736:  * - `readReceipt`
 737:  * - `returnPath`
 738:  * - `to`
 739:  * - `cc`
 740:  * - `bcc`
 741:  * - `subject`
 742:  *
 743:  * @param array $include List of headers.
 744:  * @return array
 745:  */
 746:     public function getHeaders($include = array()) {
 747:         if ($include == array_values($include)) {
 748:             $include = array_fill_keys($include, true);
 749:         }
 750:         $defaults = array_fill_keys(
 751:             array(
 752:                 'from', 'sender', 'replyTo', 'readReceipt', 'returnPath',
 753:                 'to', 'cc', 'bcc', 'subject'),
 754:             false
 755:         );
 756:         $include += $defaults;
 757: 
 758:         $headers = array();
 759:         $relation = array(
 760:             'from' => 'From',
 761:             'replyTo' => 'Reply-To',
 762:             'readReceipt' => 'Disposition-Notification-To',
 763:             'returnPath' => 'Return-Path'
 764:         );
 765:         foreach ($relation as $var => $header) {
 766:             if ($include[$var]) {
 767:                 $var = '_' . $var;
 768:                 $headers[$header] = current($this->_formatAddress($this->{$var}));
 769:             }
 770:         }
 771:         if ($include['sender']) {
 772:             if (key($this->_sender) === key($this->_from)) {
 773:                 $headers['Sender'] = '';
 774:             } else {
 775:                 $headers['Sender'] = current($this->_formatAddress($this->_sender));
 776:             }
 777:         }
 778: 
 779:         foreach (array('to', 'cc', 'bcc') as $var) {
 780:             if ($include[$var]) {
 781:                 $classVar = '_' . $var;
 782:                 $headers[ucfirst($var)] = implode(', ', $this->_formatAddress($this->{$classVar}));
 783:             }
 784:         }
 785: 
 786:         $headers += $this->_headers;
 787:         if (!isset($headers['X-Mailer'])) {
 788:             $headers['X-Mailer'] = static::EMAIL_CLIENT;
 789:         }
 790:         if (!isset($headers['Date'])) {
 791:             $headers['Date'] = date(DATE_RFC2822);
 792:         }
 793:         if ($this->_messageId !== false) {
 794:             if ($this->_messageId === true) {
 795:                 $headers['Message-ID'] = '<' . str_replace('-', '', CakeText::uuid()) . '@' . $this->_domain . '>';
 796:             } else {
 797:                 $headers['Message-ID'] = $this->_messageId;
 798:             }
 799:         }
 800: 
 801:         if ($include['subject']) {
 802:             $headers['Subject'] = $this->_subject;
 803:         }
 804: 
 805:         $headers['MIME-Version'] = '1.0';
 806:         if (!empty($this->_attachments)) {
 807:             $headers['Content-Type'] = 'multipart/mixed; boundary="' . $this->_boundary . '"';
 808:         } elseif ($this->_emailFormat === 'both') {
 809:             $headers['Content-Type'] = 'multipart/alternative; boundary="' . $this->_boundary . '"';
 810:         } elseif ($this->_emailFormat === 'text') {
 811:             $headers['Content-Type'] = 'text/plain; charset=' . $this->_getContentTypeCharset();
 812:         } elseif ($this->_emailFormat === 'html') {
 813:             $headers['Content-Type'] = 'text/html; charset=' . $this->_getContentTypeCharset();
 814:         }
 815:         $headers['Content-Transfer-Encoding'] = $this->_getContentTransferEncoding();
 816: 
 817:         return $headers;
 818:     }
 819: 
 820: /**
 821:  * Format addresses
 822:  *
 823:  * If the address contains non alphanumeric/whitespace characters, it will
 824:  * be quoted as characters like `:` and `,` are known to cause issues
 825:  * in address header fields.
 826:  *
 827:  * @param array $address Addresses to format.
 828:  * @return array
 829:  */
 830:     protected function _formatAddress($address) {
 831:         $return = array();
 832:         foreach ($address as $email => $alias) {
 833:             if ($email === $alias) {
 834:                 $return[] = $email;
 835:             } else {
 836:                 $encoded = $this->_encode($alias);
 837:                 if (
 838:                     $encoded === $alias && preg_match('/[^a-z0-9 ]/i', $encoded) ||
 839:                     strpos($encoded, ',') !== false
 840:                 ) {
 841:                     $encoded = '"' . str_replace('"', '\"', $encoded) . '"';
 842:                 }
 843:                 $return[] = sprintf('%s <%s>', $encoded, $email);
 844:             }
 845:         }
 846:         return $return;
 847:     }
 848: 
 849: /**
 850:  * Template and layout
 851:  *
 852:  * @param bool|string $template Template name or null to not use
 853:  * @param bool|string $layout Layout name or null to not use
 854:  * @return array|self
 855:  */
 856:     public function template($template = false, $layout = false) {
 857:         if ($template === false) {
 858:             return array(
 859:                 'template' => $this->_template,
 860:                 'layout' => $this->_layout
 861:             );
 862:         }
 863:         $this->_template = $template;
 864:         if ($layout !== false) {
 865:             $this->_layout = $layout;
 866:         }
 867:         return $this;
 868:     }
 869: 
 870: /**
 871:  * View class for render
 872:  *
 873:  * @param string $viewClass View class name.
 874:  * @return string|self
 875:  */
 876:     public function viewRender($viewClass = null) {
 877:         if ($viewClass === null) {
 878:             return $this->_viewRender;
 879:         }
 880:         $this->_viewRender = $viewClass;
 881:         return $this;
 882:     }
 883: 
 884: /**
 885:  * Variables to be set on render
 886:  *
 887:  * @param array $viewVars Variables to set for view.
 888:  * @return array|self
 889:  */
 890:     public function viewVars($viewVars = null) {
 891:         if ($viewVars === null) {
 892:             return $this->_viewVars;
 893:         }
 894:         $this->_viewVars = array_merge($this->_viewVars, (array)$viewVars);
 895:         return $this;
 896:     }
 897: 
 898: /**
 899:  * Theme to use when rendering
 900:  *
 901:  * @param string $theme Theme name.
 902:  * @return string|self
 903:  */
 904:     public function theme($theme = null) {
 905:         if ($theme === null) {
 906:             return $this->_theme;
 907:         }
 908:         $this->_theme = $theme;
 909:         return $this;
 910:     }
 911: 
 912: /**
 913:  * Helpers to be used in render
 914:  *
 915:  * @param array $helpers Helpers list.
 916:  * @return array|self
 917:  */
 918:     public function helpers($helpers = null) {
 919:         if ($helpers === null) {
 920:             return $this->_helpers;
 921:         }
 922:         $this->_helpers = (array)$helpers;
 923:         return $this;
 924:     }
 925: 
 926: /**
 927:  * Email format
 928:  *
 929:  * @param string $format Formatting string.
 930:  * @return string|self
 931:  * @throws SocketException
 932:  */
 933:     public function emailFormat($format = null) {
 934:         if ($format === null) {
 935:             return $this->_emailFormat;
 936:         }
 937:         if (!in_array($format, $this->_emailFormatAvailable)) {
 938:             throw new SocketException(__d('cake_dev', 'Format not available.'));
 939:         }
 940:         $this->_emailFormat = $format;
 941:         return $this;
 942:     }
 943: 
 944: /**
 945:  * Transport name
 946:  *
 947:  * @param string $name Transport name.
 948:  * @return string|self
 949:  */
 950:     public function transport($name = null) {
 951:         if ($name === null) {
 952:             return $this->_transportName;
 953:         }
 954:         $this->_transportName = (string)$name;
 955:         $this->_transportClass = null;
 956:         return $this;
 957:     }
 958: 
 959: /**
 960:  * Return the transport class
 961:  *
 962:  * @return AbstractTransport
 963:  * @throws SocketException
 964:  */
 965:     public function transportClass() {
 966:         if ($this->_transportClass) {
 967:             return $this->_transportClass;
 968:         }
 969:         list($plugin, $transportClassname) = pluginSplit($this->_transportName, true);
 970:         $transportClassname .= 'Transport';
 971:         App::uses($transportClassname, $plugin . 'Network/Email');
 972:         if (!class_exists($transportClassname)) {
 973:             throw new SocketException(__d('cake_dev', 'Class "%s" not found.', $transportClassname));
 974:         } elseif (!method_exists($transportClassname, 'send')) {
 975:             throw new SocketException(__d('cake_dev', 'The "%s" does not have a %s method.', $transportClassname, 'send()'));
 976:         }
 977: 
 978:         return $this->_transportClass = new $transportClassname();
 979:     }
 980: 
 981: /**
 982:  * Message-ID
 983:  *
 984:  * @param bool|string $message True to generate a new Message-ID, False to ignore (not send in email), String to set as Message-ID
 985:  * @return bool|string|self
 986:  * @throws SocketException
 987:  */
 988:     public function messageId($message = null) {
 989:         if ($message === null) {
 990:             return $this->_messageId;
 991:         }
 992:         if (is_bool($message)) {
 993:             $this->_messageId = $message;
 994:         } else {
 995:             if (!preg_match('/^\<.+@.+\>$/', $message)) {
 996:                 throw new SocketException(__d('cake_dev', 'Invalid format for Message-ID. The text should be something like "<[email protected]>"'));
 997:             }
 998:             $this->_messageId = $message;
 999:         }
1000:         return $this;
1001:     }
1002: 
1003: /**
1004:  * Domain as top level (the part after @)
1005:  *
1006:  * @param string $domain Manually set the domain for CLI mailing
1007:  * @return string|self
1008:  */
1009:     public function domain($domain = null) {
1010:         if ($domain === null) {
1011:             return $this->_domain;
1012:         }
1013:         $this->_domain = $domain;
1014:         return $this;
1015:     }
1016: 
1017: /**
1018:  * Add attachments to the email message
1019:  *
1020:  * Attachments can be defined in a few forms depending on how much control you need:
1021:  *
1022:  * Attach a single file:
1023:  *
1024:  * ```
1025:  * $email->attachments('path/to/file');
1026:  * ```
1027:  *
1028:  * Attach a file with a different filename:
1029:  *
1030:  * ```
1031:  * $email->attachments(array('custom_name.txt' => 'path/to/file.txt'));
1032:  * ```
1033:  *
1034:  * Attach a file and specify additional properties:
1035:  *
1036:  * ```
1037:  * $email->attachments(array('custom_name.png' => array(
1038:  *      'file' => 'path/to/file',
1039:  *      'mimetype' => 'image/png',
1040:  *      'contentId' => 'abc123',
1041:  *      'contentDisposition' => false
1042:  * ));
1043:  * ```
1044:  *
1045:  * Attach a file from string and specify additional properties:
1046:  *
1047:  * ```
1048:  * $email->attachments(array('custom_name.png' => array(
1049:  *      'data' => file_get_contents('path/to/file'),
1050:  *      'mimetype' => 'image/png'
1051:  * ));
1052:  * ```
1053:  *
1054:  * The `contentId` key allows you to specify an inline attachment. In your email text, you
1055:  * can use `<img src="cid:abc123" />` to display the image inline.
1056:  *
1057:  * The `contentDisposition` key allows you to disable the `Content-Disposition` header, this can improve
1058:  * attachment compatibility with outlook email clients.
1059:  *
1060:  * @param string|array $attachments String with the filename or array with filenames
1061:  * @return array|self Either the array of attachments when getting or $this when setting.
1062:  * @throws SocketException
1063:  */
1064:     public function attachments($attachments = null) {
1065:         if ($attachments === null) {
1066:             return $this->_attachments;
1067:         }
1068:         $attach = array();
1069:         foreach ((array)$attachments as $name => $fileInfo) {
1070:             if (!is_array($fileInfo)) {
1071:                 $fileInfo = array('file' => $fileInfo);
1072:             }
1073:             if (!isset($fileInfo['file'])) {
1074:                 if (!isset($fileInfo['data'])) {
1075:                     throw new SocketException(__d('cake_dev', 'No file or data specified.'));
1076:                 }
1077:                 if (is_int($name)) {
1078:                     throw new SocketException(__d('cake_dev', 'No filename specified.'));
1079:                 }
1080:                 $fileInfo['data'] = chunk_split(base64_encode($fileInfo['data']), 76, "\r\n");
1081:             } else {
1082:                 $fileName = $fileInfo['file'];
1083:                 $fileInfo['file'] = realpath($fileInfo['file']);
1084:                 if ($fileInfo['file'] === false || !file_exists($fileInfo['file'])) {
1085:                     throw new SocketException(__d('cake_dev', 'File not found: "%s"', $fileName));
1086:                 }
1087:                 if (is_int($name)) {
1088:                     $name = basename($fileInfo['file']);
1089:                 }
1090:             }
1091:             if (!isset($fileInfo['mimetype']) && isset($fileInfo['file']) && function_exists('mime_content_type')) {
1092:                 $fileInfo['mimetype'] = mime_content_type($fileInfo['file']);
1093:             }
1094:             if (!isset($fileInfo['mimetype'])) {
1095:                 $fileInfo['mimetype'] = 'application/octet-stream';
1096:             }
1097:             $attach[$name] = $fileInfo;
1098:         }
1099:         $this->_attachments = $attach;
1100:         return $this;
1101:     }
1102: 
1103: /**
1104:  * Add attachments
1105:  *
1106:  * @param string|array $attachments String with the filename or array with filenames
1107:  * @return self
1108:  * @throws SocketException
1109:  * @see CakeEmail::attachments()
1110:  */
1111:     public function addAttachments($attachments) {
1112:         $current = $this->_attachments;
1113:         $this->attachments($attachments);
1114:         $this->_attachments = array_merge($current, $this->_attachments);
1115:         return $this;
1116:     }
1117: 
1118: /**
1119:  * Get generated message (used by transport classes)
1120:  *
1121:  * @param string $type Use MESSAGE_* constants or null to return the full message as array
1122:  * @return string|array String if have type, array if type is null
1123:  */
1124:     public function message($type = null) {
1125:         switch ($type) {
1126:             case static::MESSAGE_HTML:
1127:                 return $this->_htmlMessage;
1128:             case static::MESSAGE_TEXT:
1129:                 return $this->_textMessage;
1130:         }
1131:         return $this->_message;
1132:     }
1133: 
1134: /**
1135:  * Configuration to use when send email
1136:  *
1137:  * ### Usage
1138:  *
1139:  * Load configuration from `app/Config/email.php`:
1140:  *
1141:  * `$email->config('default');`
1142:  *
1143:  * Merge an array of configuration into the instance:
1144:  *
1145:  * `$email->config(array('to' => '[email protected]'));`
1146:  *
1147:  * @param string|array $config String with configuration name (from email.php), array with config or null to return current config
1148:  * @return string|array|self
1149:  */
1150:     public function config($config = null) {
1151:         if ($config === null) {
1152:             return $this->_config;
1153:         }
1154:         if (!is_array($config)) {
1155:             $config = (string)$config;
1156:         }
1157: 
1158:         $this->_applyConfig($config);
1159:         return $this;
1160:     }
1161: 
1162: /**
1163:  * Send an email using the specified content, template and layout
1164:  *
1165:  * @param string|array $content String with message or array with messages
1166:  * @return array
1167:  * @throws SocketException
1168:  */
1169:     public function send($content = null) {
1170:         if (empty($this->_from)) {
1171:             throw new SocketException(__d('cake_dev', 'From is not specified.'));
1172:         }
1173:         if (empty($this->_to) && empty($this->_cc) && empty($this->_bcc)) {
1174:             throw new SocketException(__d('cake_dev', 'You need to specify at least one destination for to, cc or bcc.'));
1175:         }
1176: 
1177:         if (is_array($content)) {
1178:             $content = implode("\n", $content) . "\n";
1179:         }
1180: 
1181:         $this->_message = $this->_render($this->_wrap($content));
1182: 
1183:         $contents = $this->transportClass()->send($this);
1184:         if (!empty($this->_config['log'])) {
1185:             $config = array(
1186:                 'level' => LOG_DEBUG,
1187:                 'scope' => 'email'
1188:             );
1189:             if ($this->_config['log'] !== true) {
1190:                 if (!is_array($this->_config['log'])) {
1191:                     $this->_config['log'] = array('level' => $this->_config['log']);
1192:                 }
1193:                 $config = $this->_config['log'] + $config;
1194:             }
1195:             CakeLog::write(
1196:                 $config['level'],
1197:                 PHP_EOL . $contents['headers'] . PHP_EOL . PHP_EOL . $contents['message'],
1198:                 $config['scope']
1199:             );
1200:         }
1201:         return $contents;
1202:     }
1203: 
1204: /**
1205:  * Static method to fast create an instance of CakeEmail
1206:  *
1207:  * @param string|array $to Address to send (see CakeEmail::to()). If null, will try to use 'to' from transport config
1208:  * @param string $subject String of subject or null to use 'subject' from transport config
1209:  * @param string|array $message String with message or array with variables to be used in render
1210:  * @param string|array $transportConfig String to use config from EmailConfig or array with configs
1211:  * @param bool $send Send the email or just return the instance pre-configured
1212:  * @return self Instance of CakeEmail
1213:  * @throws SocketException
1214:  */
1215:     public static function deliver($to = null, $subject = null, $message = null, $transportConfig = 'fast', $send = true) {
1216:         $class = get_called_class();
1217:         /** @var CakeEmail $instance */
1218:         $instance = new $class($transportConfig);
1219:         if ($to !== null) {
1220:             $instance->to($to);
1221:         }
1222:         if ($subject !== null) {
1223:             $instance->subject($subject);
1224:         }
1225:         if (is_array($message)) {
1226:             $instance->viewVars($message);
1227:             $message = null;
1228:         } elseif ($message === null && array_key_exists('message', $config = $instance->config())) {
1229:             $message = $config['message'];
1230:         }
1231: 
1232:         if ($send === true) {
1233:             $instance->send($message);
1234:         }
1235: 
1236:         return $instance;
1237:     }
1238: 
1239: /**
1240:  * Apply the config to an instance
1241:  *
1242:  * @param array $config Configuration options.
1243:  * @return void
1244:  * @throws ConfigureException When configuration file cannot be found, or is missing
1245:  *   the named config.
1246:  */
1247:     protected function _applyConfig($config) {
1248:         if (is_string($config)) {
1249:             if (!$this->_configInstance) {
1250:                 if (!class_exists($this->_configClass) && !config('email')) {
1251:                     throw new ConfigureException(__d('cake_dev', '%s not found.', CONFIG . 'email.php'));
1252:                 }
1253:                 $this->_configInstance = new $this->_configClass();
1254:             }
1255:             if (!isset($this->_configInstance->{$config})) {
1256:                 throw new ConfigureException(__d('cake_dev', 'Unknown email configuration "%s".', $config));
1257:             }
1258:             $config = $this->_configInstance->{$config};
1259:         }
1260:         $this->_config = $config + $this->_config;
1261:         if (!empty($config['charset'])) {
1262:             $this->charset = $config['charset'];
1263:         }
1264:         if (!empty($config['headerCharset'])) {
1265:             $this->headerCharset = $config['headerCharset'];
1266:         }
1267:         if (empty($this->headerCharset)) {
1268:             $this->headerCharset = $this->charset;
1269:         }
1270:         $simpleMethods = array(
1271:             'from', 'sender', 'to', 'replyTo', 'readReceipt', 'returnPath', 'cc', 'bcc',
1272:             'messageId', 'domain', 'subject', 'viewRender', 'viewVars', 'attachments',
1273:             'transport', 'emailFormat', 'theme', 'helpers', 'emailPattern'
1274:         );
1275:         foreach ($simpleMethods as $method) {
1276:             if (isset($config[$method])) {
1277:                 $this->$method($config[$method]);
1278:                 unset($config[$method]);
1279:             }
1280:         }
1281:         if (isset($config['headers'])) {
1282:             $this->setHeaders($config['headers']);
1283:             unset($config['headers']);
1284:         }
1285: 
1286:         if (array_key_exists('template', $config)) {
1287:             $this->_template = $config['template'];
1288:         }
1289:         if (array_key_exists('layout', $config)) {
1290:             $this->_layout = $config['layout'];
1291:         }
1292: 
1293:         $this->transportClass()->config($config);
1294:     }
1295: 
1296: /**
1297:  * Reset all CakeEmail internal variables to be able to send out a new email.
1298:  *
1299:  * @return self
1300:  */
1301:     public function reset() {
1302:         $this->_to = array();
1303:         $this->_from = array();
1304:         $this->_sender = array();
1305:         $this->_replyTo = array();
1306:         $this->_readReceipt = array();
1307:         $this->_returnPath = array();
1308:         $this->_cc = array();
1309:         $this->_bcc = array();
1310:         $this->_messageId = true;
1311:         $this->_subject = '';
1312:         $this->_headers = array();
1313:         $this->_layout = 'default';
1314:         $this->_template = '';
1315:         $this->_viewRender = 'View';
1316:         $this->_viewVars = array();
1317:         $this->_theme = null;
1318:         $this->_helpers = array('Html');
1319:         $this->_textMessage = '';
1320:         $this->_htmlMessage = '';
1321:         $this->_message = '';
1322:         $this->_emailFormat = 'text';
1323:         $this->_transportName = 'Mail';
1324:         $this->_transportClass = null;
1325:         $this->charset = 'utf-8';
1326:         $this->headerCharset = null;
1327:         $this->_attachments = array();
1328:         $this->_config = array();
1329:         $this->_emailPattern = static::EMAIL_PATTERN;
1330:         return $this;
1331:     }
1332: 
1333: /**
1334:  * Encode the specified string using the current charset
1335:  *
1336:  * @param string $text String to encode
1337:  * @return string Encoded string
1338:  */
1339:     protected function _encode($text) {
1340:         $internalEncoding = function_exists('mb_internal_encoding');
1341:         if ($internalEncoding) {
1342:             $restore = mb_internal_encoding();
1343:             mb_internal_encoding($this->_appCharset);
1344:         }
1345:         if (empty($this->headerCharset)) {
1346:             $this->headerCharset = $this->charset;
1347:         }
1348:         $return = mb_encode_mimeheader($text, $this->headerCharset, 'B');
1349:         if ($internalEncoding) {
1350:             mb_internal_encoding($restore);
1351:         }
1352:         return $return;
1353:     }
1354: 
1355: /**
1356:  * Translates a string for one charset to another if the App.encoding value
1357:  * differs and the mb_convert_encoding function exists
1358:  *
1359:  * @param string $text The text to be converted
1360:  * @param string $charset the target encoding
1361:  * @return string
1362:  */
1363:     protected function _encodeString($text, $charset) {
1364:         if ($this->_appCharset === $charset || !function_exists('mb_convert_encoding')) {
1365:             return $text;
1366:         }
1367:         return mb_convert_encoding($text, $charset, $this->_appCharset);
1368:     }
1369: 
1370: /**
1371:  * Wrap the message to follow the RFC 2822 - 2.1.1
1372:  *
1373:  * @param string $message Message to wrap
1374:  * @param int $wrapLength The line length
1375:  * @return array Wrapped message
1376:  */
1377:     protected function _wrap($message, $wrapLength = CakeEmail::LINE_LENGTH_MUST) {
1378:         if (strlen($message) === 0) {
1379:             return array('');
1380:         }
1381:         $message = str_replace(array("\r\n", "\r"), "\n", $message);
1382:         $lines = explode("\n", $message);
1383:         $formatted = array();
1384:         $cut = ($wrapLength == CakeEmail::LINE_LENGTH_MUST);
1385: 
1386:         foreach ($lines as $line) {
1387:             if (empty($line) && $line !== '0') {
1388:                 $formatted[] = '';
1389:                 continue;
1390:             }
1391:             if (strlen($line) < $wrapLength) {
1392:                 $formatted[] = $line;
1393:                 continue;
1394:             }
1395:             if (!preg_match('/<[a-z]+.*>/i', $line)) {
1396:                 $formatted = array_merge(
1397:                     $formatted,
1398:                     explode("\n", wordwrap($line, $wrapLength, "\n", $cut))
1399:                 );
1400:                 continue;
1401:             }
1402: 
1403:             $tagOpen = false;
1404:             $tmpLine = $tag = '';
1405:             $tmpLineLength = 0;
1406:             for ($i = 0, $count = strlen($line); $i < $count; $i++) {
1407:                 $char = $line[$i];
1408:                 if ($tagOpen) {
1409:                     $tag .= $char;
1410:                     if ($char === '>') {
1411:                         $tagLength = strlen($tag);
1412:                         if ($tagLength + $tmpLineLength < $wrapLength) {
1413:                             $tmpLine .= $tag;
1414:                             $tmpLineLength += $tagLength;
1415:                         } else {
1416:                             if ($tmpLineLength > 0) {
1417:                                 $formatted = array_merge(
1418:                                     $formatted,
1419:                                     explode("\n", wordwrap(trim($tmpLine), $wrapLength, "\n", $cut))
1420:                                 );
1421:                                 $tmpLine = '';
1422:                                 $tmpLineLength = 0;
1423:                             }
1424:                             if ($tagLength > $wrapLength) {
1425:                                 $formatted[] = $tag;
1426:                             } else {
1427:                                 $tmpLine = $tag;
1428:                                 $tmpLineLength = $tagLength;
1429:                             }
1430:                         }
1431:                         $tag = '';
1432:                         $tagOpen = false;
1433:                     }
1434:                     continue;
1435:                 }
1436:                 if ($char === '<') {
1437:                     $tagOpen = true;
1438:                     $tag = '<';
1439:                     continue;
1440:                 }
1441:                 if ($char === ' ' && $tmpLineLength >= $wrapLength) {
1442:                     $formatted[] = $tmpLine;
1443:                     $tmpLineLength = 0;
1444:                     continue;
1445:                 }
1446:                 $tmpLine .= $char;
1447:                 $tmpLineLength++;
1448:                 if ($tmpLineLength === $wrapLength) {
1449:                     $nextChar = isset($line[$i + 1]) ? $line[$i + 1] : '';
1450:                     if ($nextChar === ' ' || $nextChar === '<') {
1451:                         $formatted[] = trim($tmpLine);
1452:                         $tmpLine = '';
1453:                         $tmpLineLength = 0;
1454:                         if ($nextChar === ' ') {
1455:                             $i++;
1456:                         }
1457:                     } else {
1458:                         $lastSpace = strrpos($tmpLine, ' ');
1459:                         if ($lastSpace === false) {
1460:                             continue;
1461:                         }
1462:                         $formatted[] = trim(substr($tmpLine, 0, $lastSpace));
1463:                         $tmpLine = substr($tmpLine, $lastSpace + 1);
1464: 
1465:                         $tmpLineLength = strlen($tmpLine);
1466:                     }
1467:                 }
1468:             }
1469:             if (!empty($tmpLine)) {
1470:                 $formatted[] = $tmpLine;
1471:             }
1472:         }
1473:         $formatted[] = '';
1474:         return $formatted;
1475:     }
1476: 
1477: /**
1478:  * Create unique boundary identifier
1479:  *
1480:  * @return void
1481:  */
1482:     protected function _createBoundary() {
1483:         if (!empty($this->_attachments) || $this->_emailFormat === 'both') {
1484:             $this->_boundary = md5(uniqid(time()));
1485:         }
1486:     }
1487: 
1488: /**
1489:  * Attach non-embedded files by adding file contents inside boundaries.
1490:  *
1491:  * @param string $boundary Boundary to use. If null, will default to $this->_boundary
1492:  * @return array An array of lines to add to the message
1493:  */
1494:     protected function _attachFiles($boundary = null) {
1495:         if ($boundary === null) {
1496:             $boundary = $this->_boundary;
1497:         }
1498: 
1499:         $msg = array();
1500:         foreach ($this->_attachments as $filename => $fileInfo) {
1501:             if (!empty($fileInfo['contentId'])) {
1502:                 continue;
1503:             }
1504:             $data = isset($fileInfo['data']) ? $fileInfo['data'] : $this->_readFile($fileInfo['file']);
1505: 
1506:             $msg[] = '--' . $boundary;
1507:             $msg[] = 'Content-Type: ' . $fileInfo['mimetype'];
1508:             $msg[] = 'Content-Transfer-Encoding: base64';
1509:             if (!isset($fileInfo['contentDisposition']) ||
1510:                 $fileInfo['contentDisposition']
1511:             ) {
1512:                 $msg[] = 'Content-Disposition: attachment; filename="' . $filename . '"';
1513:             }
1514:             $msg[] = '';
1515:             $msg[] = $data;
1516:             $msg[] = '';
1517:         }
1518:         return $msg;
1519:     }
1520: 
1521: /**
1522:  * Read the file contents and return a base64 version of the file contents.
1523:  *
1524:  * @param string $path The absolute path to the file to read.
1525:  * @return string File contents in base64 encoding
1526:  */
1527:     protected function _readFile($path) {
1528:         $File = new File($path);
1529:         return chunk_split(base64_encode($File->read()));
1530:     }
1531: 
1532: /**
1533:  * Attach inline/embedded files to the message.
1534:  *
1535:  * @param string $boundary Boundary to use. If null, will default to $this->_boundary
1536:  * @return array An array of lines to add to the message
1537:  */
1538:     protected function _attachInlineFiles($boundary = null) {
1539:         if ($boundary === null) {
1540:             $boundary = $this->_boundary;
1541:         }
1542: 
1543:         $msg = array();
1544:         foreach ($this->_attachments as $filename => $fileInfo) {
1545:             if (empty($fileInfo['contentId'])) {
1546:                 continue;
1547:             }
1548:             $data = isset($fileInfo['data']) ? $fileInfo['data'] : $this->_readFile($fileInfo['file']);
1549: 
1550:             $msg[] = '--' . $boundary;
1551:             $msg[] = 'Content-Type: ' . $fileInfo['mimetype'];
1552:             $msg[] = 'Content-Transfer-Encoding: base64';
1553:             $msg[] = 'Content-ID: <' . $fileInfo['contentId'] . '>';
1554:             $msg[] = 'Content-Disposition: inline; filename="' . $filename . '"';
1555:             $msg[] = '';
1556:             $msg[] = $data;
1557:             $msg[] = '';
1558:         }
1559:         return $msg;
1560:     }
1561: 
1562: /**
1563:  * Render the body of the email.
1564:  *
1565:  * @param array $content Content to render
1566:  * @return array Email body ready to be sent
1567:  */
1568:     protected function _render($content) {
1569:         $this->_textMessage = $this->_htmlMessage = '';
1570: 
1571:         $content = implode("\n", $content);
1572:         $rendered = $this->_renderTemplates($content);
1573: 
1574:         $this->_createBoundary();
1575:         $msg = array();
1576: 
1577:         $contentIds = array_filter((array)Hash::extract($this->_attachments, '{s}.contentId'));
1578:         $hasInlineAttachments = count($contentIds) > 0;
1579:         $hasAttachments = !empty($this->_attachments);
1580:         $hasMultipleTypes = count($rendered) > 1;
1581:         $multiPart = ($hasAttachments || $hasMultipleTypes);
1582: 
1583:         $boundary = $relBoundary = $textBoundary = $this->_boundary;
1584: 
1585:         if ($hasInlineAttachments) {
1586:             $msg[] = '--' . $boundary;
1587:             $msg[] = 'Content-Type: multipart/related; boundary="rel-' . $boundary . '"';
1588:             $msg[] = '';
1589:             $relBoundary = $textBoundary = 'rel-' . $boundary;
1590:         }
1591: 
1592:         if ($hasMultipleTypes && $hasAttachments) {
1593:             $msg[] = '--' . $relBoundary;
1594:             $msg[] = 'Content-Type: multipart/alternative; boundary="alt-' . $boundary . '"';
1595:             $msg[] = '';
1596:             $textBoundary = 'alt-' . $boundary;
1597:         }
1598: 
1599:         if (isset($rendered['text'])) {
1600:             if ($multiPart) {
1601:                 $msg[] = '--' . $textBoundary;
1602:                 $msg[] = 'Content-Type: text/plain; charset=' . $this->_getContentTypeCharset();
1603:                 $msg[] = 'Content-Transfer-Encoding: ' . $this->_getContentTransferEncoding();
1604:                 $msg[] = '';
1605:             }
1606:             $this->_textMessage = $rendered['text'];
1607:             $content = explode("\n", $this->_textMessage);
1608:             $msg = array_merge($msg, $content);
1609:             $msg[] = '';
1610:         }
1611: 
1612:         if (isset($rendered['html'])) {
1613:             if ($multiPart) {
1614:                 $msg[] = '--' . $textBoundary;
1615:                 $msg[] = 'Content-Type: text/html; charset=' . $this->_getContentTypeCharset();
1616:                 $msg[] = 'Content-Transfer-Encoding: ' . $this->_getContentTransferEncoding();
1617:                 $msg[] = '';
1618:             }
1619:             $this->_htmlMessage = $rendered['html'];
1620:             $content = explode("\n", $this->_htmlMessage);
1621:             $msg = array_merge($msg, $content);
1622:             $msg[] = '';
1623:         }
1624: 
1625:         if ($textBoundary !== $relBoundary) {
1626:             $msg[] = '--' . $textBoundary . '--';
1627:             $msg[] = '';
1628:         }
1629: 
1630:         if ($hasInlineAttachments) {
1631:             $attachments = $this->_attachInlineFiles($relBoundary);
1632:             $msg = array_merge($msg, $attachments);
1633:             $msg[] = '';
1634:             $msg[] = '--' . $relBoundary . '--';
1635:             $msg[] = '';
1636:         }
1637: 
1638:         if ($hasAttachments) {
1639:             $attachments = $this->_attachFiles($boundary);
1640:             $msg = array_merge($msg, $attachments);
1641:         }
1642:         if ($hasAttachments || $hasMultipleTypes) {
1643:             $msg[] = '';
1644:             $msg[] = '--' . $boundary . '--';
1645:             $msg[] = '';
1646:         }
1647:         return $msg;
1648:     }
1649: 
1650: /**
1651:  * Gets the text body types that are in this email message
1652:  *
1653:  * @return array Array of types. Valid types are 'text' and 'html'
1654:  */
1655:     protected function _getTypes() {
1656:         $types = array($this->_emailFormat);
1657:         if ($this->_emailFormat === 'both') {
1658:             $types = array('html', 'text');
1659:         }
1660:         return $types;
1661:     }
1662: 
1663: /**
1664:  * Build and set all the view properties needed to render the templated emails.
1665:  * If there is no template set, the $content will be returned in a hash
1666:  * of the text content types for the email.
1667:  *
1668:  * @param string $content The content passed in from send() in most cases.
1669:  * @return array The rendered content with html and text keys.
1670:  */
1671:     protected function _renderTemplates($content) {
1672:         $types = $this->_getTypes();
1673:         $rendered = array();
1674:         if (empty($this->_template)) {
1675:             foreach ($types as $type) {
1676:                 $rendered[$type] = $this->_encodeString($content, $this->charset);
1677:             }
1678:             return $rendered;
1679:         }
1680:         $viewClass = $this->_viewRender;
1681:         if ($viewClass !== 'View') {
1682:             list($plugin, $viewClass) = pluginSplit($viewClass, true);
1683:             $viewClass .= 'View';
1684:             App::uses($viewClass, $plugin . 'View');
1685:         }
1686: 
1687:         /** @var View $View */
1688:         $View = new $viewClass(null);
1689:         $View->viewVars = $this->_viewVars;
1690:         $View->helpers = $this->_helpers;
1691: 
1692:         if ($this->_theme) {
1693:             $View->theme = $this->_theme;
1694:         }
1695: 
1696:         $View->loadHelpers();
1697: 
1698:         list($templatePlugin, $template) = pluginSplit($this->_template);
1699:         list($layoutPlugin, $layout) = pluginSplit($this->_layout);
1700:         if ($templatePlugin) {
1701:             $View->plugin = $templatePlugin;
1702:         } elseif ($layoutPlugin) {
1703:             $View->plugin = $layoutPlugin;
1704:         }
1705: 
1706:         if ($View->get('content') === null) {
1707:             $View->set('content', $content);
1708:         }
1709: 
1710:         // Convert null to false, as View needs false to disable
1711:         // the layout.
1712:         if ($this->_layout === null) {
1713:             $this->_layout = false;
1714:         }
1715: 
1716:         foreach ($types as $type) {
1717:             $View->hasRendered = false;
1718:             $View->viewPath = $View->layoutPath = 'Emails' . DS . $type;
1719: 
1720:             $render = $View->render($this->_template, $this->_layout);
1721:             $render = str_replace(array("\r\n", "\r"), "\n", $render);
1722:             $rendered[$type] = $this->_encodeString($render, $this->charset);
1723:         }
1724: 
1725:         foreach ($rendered as $type => $content) {
1726:             $rendered[$type] = $this->_wrap($content);
1727:             $rendered[$type] = implode("\n", $rendered[$type]);
1728:             $rendered[$type] = rtrim($rendered[$type], "\n");
1729:         }
1730:         return $rendered;
1731:     }
1732: 
1733: /**
1734:  * Return the Content-Transfer Encoding value based on the set charset
1735:  *
1736:  * @return string
1737:  */
1738:     protected function _getContentTransferEncoding() {
1739:         $charset = strtoupper($this->charset);
1740:         if (in_array($charset, $this->_charset8bit)) {
1741:             return '8bit';
1742:         }
1743:         return '7bit';
1744:     }
1745: 
1746: /**
1747:  * Return charset value for Content-Type.
1748:  *
1749:  * Checks fallback/compatibility types which include workarounds
1750:  * for legacy japanese character sets.
1751:  *
1752:  * @return string
1753:  */
1754:     protected function _getContentTypeCharset() {
1755:         $charset = strtoupper($this->charset);
1756:         if (array_key_exists($charset, $this->_contentTypeCharset)) {
1757:             return strtoupper($this->_contentTypeCharset[$charset]);
1758:         }
1759:         return strtoupper($this->charset);
1760:     }
1761: 
1762: }
1763: 
OpenHub
Rackspace
Rackspace
  • Business Solutions
  • Showcase
  • Documentation
  • Book
  • API
  • Videos
  • Reporting Security Issues
  • Privacy Policy
  • Logos & Trademarks
  • Community
  • Get Involved
  • Issues (GitHub)
  • Bakery
  • Featured Resources
  • Training
  • Meetups
  • My CakePHP
  • CakeFest
  • Newsletter
  • Linkedin
  • YouTube
  • Facebook
  • Twitter
  • Mastodon
  • Help & Support
  • Forum
  • Stack Overflow
  • Slack
  • Paid Support

Generated using CakePHP API Docs