Overview

Namespaces

  • Charcoal
    • Email
      • Script
      • ServiceProvider

Classes

  • Charcoal\Email\Email
  • Charcoal\Email\EmailConfig
  • Charcoal\Email\EmailLog
  • Charcoal\Email\EmailQueueItem
  • Charcoal\Email\EmailQueueManager
  • Charcoal\Email\GenericEmailTemplate
  • Charcoal\Email\Script\ProcessQueueScript
  • Charcoal\Email\ServiceProvider\EmailServiceProvider

Interfaces

  • Charcoal\Email\EmailInterface

Traits

  • Charcoal\Email\EmailAwareTrait
  • Overview
  • Namespace
  • Class
   1: <?php
   2: 
   3: namespace Charcoal\Email;
   4: 
   5: // Dependencies from `PHP`
   6: use \Exception;
   7: use \InvalidArgumentException;
   8: 
   9: // PSR-3 (logger) dependencies
  10: use \Psr\Log\LoggerAwareInterface;
  11: use \Psr\Log\LoggerAwareTrait;
  12: 
  13: // From `phpmailer/phpmailer`
  14: use \PHPMailer\PHPMailer\PHPMailer;
  15: 
  16: // Module `charcoal-config` dependencies
  17: use \Charcoal\Config\ConfigurableInterface;
  18: use \Charcoal\Config\ConfigurableTrait;
  19: 
  20: // Module `charcoal-factory` dependencies
  21: use \Charcoal\Factory\FactoryInterface;
  22: 
  23: // Module `charcoal-view` dependencies
  24: use \Charcoal\View\GenericView;
  25: use \Charcoal\View\ViewableInterface;
  26: use \Charcoal\View\ViewableTrait;
  27: 
  28: // Module `charcoal-queue` dependencies
  29: use \Charcoal\Queue\QueueableInterface;
  30: use \Charcoal\Queue\QueueableTrait;
  31: 
  32: // Intra module (`charcoal-email`) dependencies
  33: use \Charcoal\Email\EmailInterface;
  34: use \Charcoal\Email\EmailConfig;
  35: use \Charcoal\Email\EmailLog;
  36: 
  37: /**
  38:  * Default implementation of the `EmailInterface`.
  39:  */
  40: class Email implements
  41:     ConfigurableInterface,
  42:     EmailInterface,
  43:     LoggerAwareInterface,
  44:     QueueableInterface,
  45:     ViewableInterface
  46: {
  47:     use ConfigurableTrait;
  48:     use LoggerAwareTrait;
  49:     use QueueableTrait;
  50:     use ViewableTrait;
  51:     use EmailAwareTrait;
  52: 
  53:     /**
  54:      * The campaign ID.
  55:      *
  56:      * @var string $campaign
  57:      */
  58:     private $campaign;
  59: 
  60:     /**
  61:      * The recipient email address(es).
  62:      *
  63:      * @var array $to
  64:      */
  65:     private $to = [];
  66: 
  67:     /**
  68:      * The CC recipient email address(es).
  69:      *
  70:      * @var array $cc
  71:      */
  72:     private $cc = [];
  73: 
  74:     /**
  75:      * The BCC recipient email address(es).
  76:      *
  77:      * @var array $bcc
  78:      */
  79:     private $bcc = [];
  80: 
  81:     /**
  82:      * The sender's email address.
  83:      *
  84:      * @var string $from
  85:      */
  86:     private $from;
  87: 
  88:     /**
  89:      * The email address to reply to the message.
  90:      *
  91:      * @var string $replyTo
  92:      */
  93:     private $replyTo;
  94: 
  95:     /**
  96:      * The email subject.
  97:      *
  98:      * @var string $subject
  99:      */
 100:     private $subject;
 101: 
 102:     /**
 103:      * The HTML message body.
 104:      *
 105:      * @var string $msgHtml
 106:      */
 107:     private $msgHtml;
 108: 
 109:     /**
 110:      * The plain-text message body.
 111:      *
 112:      * @var string $msgTxt
 113:      */
 114:     private $msgTxt;
 115: 
 116:     /**
 117:      * @var array $attachments
 118:      */
 119:     private $attachments = [];
 120: 
 121:     /**
 122:      * Whether the email should be logged.
 123:      *
 124:      * @var boolean $log
 125:      */
 126:     private $log;
 127: 
 128:     /**
 129:      * Whether the email should be tracked.
 130:      *
 131:      * @var boolean $track
 132:      */
 133:     private $track;
 134: 
 135:     /**
 136:      * The data to pass onto the view controller.
 137:      *
 138:      * @var array $templateData
 139:      */
 140:     private $templateData = [];
 141: 
 142:     /**
 143:      * @var PHPMailer $phpMailer PHP Mailer instance.
 144:      */
 145:     private $phpMailer;
 146: 
 147:     /**
 148:      * @var FactoryInterface $templateFactory
 149:      */
 150:     private $templateFactory;
 151: 
 152:     /**
 153:      * @var FactoryInterface $queueItemFactory
 154:      */
 155:     private $queueItemFactory;
 156: 
 157:     /**
 158:      * @var FactoryInterface $logFactory
 159:      */
 160:     private $logFactory;
 161: 
 162:     /**
 163:      * Construct a new Email object.
 164:      *
 165:      * @param array $data Dependencies and settings.
 166:      */
 167:     public function __construct(array $data)
 168:     {
 169:         $this->phpMailer = new PHPMailer(true);
 170:         $this->setLogger($data['logger']);
 171:         $this->setView($data['view']);
 172:         $this->setConfig($data['config']);
 173:         $this->setTemplateFactory($data['template_factory']);
 174:         $this->setQueueItemFactory($data['queue_item_factory']);
 175:         $this->setLogFactory($data['log_factory']);
 176:     }
 177: 
 178:     /**
 179:      * @param FactoryInterface $factory The factory to use to create email template objects.
 180:      * @return Email Chainable
 181:      */
 182:     protected function setTemplateFactory(FactoryInterface $factory)
 183:     {
 184:         $this->templateFactory = $factory;
 185:         return $this;
 186:     }
 187: 
 188:     /**
 189:      * @return FactoryInterface
 190:      */
 191:     protected function templateFactory()
 192:     {
 193:         return $this->templateFactory;
 194:     }
 195: 
 196:     /**
 197:      * @param FactoryInterface $factory The factory to use to create email queue item objects.
 198:      * @return Email Chainable
 199:      */
 200:     protected function setQueueItemFactory(FactoryInterface $factory)
 201:     {
 202:         $this->queueItemFactory = $factory;
 203:         return $this;
 204:     }
 205: 
 206:     /**
 207:      * @return FactoryInterface
 208:      */
 209:     protected function queueItemFactory()
 210:     {
 211:         return $this->queueItemFactory;
 212:     }
 213: 
 214:     /**
 215:      * @param FactoryInterface $factory The factory to use to create log objects.
 216:      * @return Email Chainable
 217:      */
 218:     protected function setLogFactory(FactoryInterface $factory)
 219:     {
 220:         $this->logFactory = $factory;
 221:         return $this;
 222:     }
 223: 
 224:     /**
 225:      * @return FactoryInterface
 226:      */
 227:     protected function logFactory()
 228:     {
 229:         return $this->logFactory;
 230:     }
 231: 
 232:     /**
 233:      * Set the email's data.
 234:      *
 235:      * @param array $data The data to set.
 236:      * @return Email Chainable
 237:      */
 238:     public function setData(array $data)
 239:     {
 240:         foreach ($data as $prop => $val) {
 241:             $func = [$this, $this->setter($prop)];
 242:             if (is_callable($func)) {
 243:                 call_user_func($func, $val);
 244:             } else {
 245:                 $this->{$prop} = $val;
 246:             }
 247:         }
 248: 
 249:         return $this;
 250:     }
 251: 
 252:     /**
 253:      * Set the campaign ID.
 254:      *
 255:      * @param  string $campaign The campaign identifier.
 256:      * @throws InvalidArgumentException If the campaign is invalid.
 257:      * @return EmailInterface Chainable
 258:      */
 259:     public function setCampaign($campaign)
 260:     {
 261:         if (!is_string($campaign)) {
 262:             throw new InvalidArgumentException(
 263:                 'Campaign must be a string'
 264:             );
 265:         }
 266: 
 267:         $this->campaign = $campaign;
 268: 
 269:         return $this;
 270:     }
 271: 
 272:     /**
 273:      * Get the campaign identifier.
 274:      *
 275:      * If it has not been explicitely set, it will be auto-generated (with uniqid).
 276:      *
 277:      * @return string
 278:      */
 279:     public function campaign()
 280:     {
 281:         if ($this->campaign === null) {
 282:             $this->campaign = $this->generateCampaign();
 283:         }
 284: 
 285:         return $this->campaign;
 286:     }
 287: 
 288:     /**
 289:      * Generates a unique identifier ideal for a campaign ID.
 290:      *
 291:      * @return string
 292:      */
 293:     protected function generateCampaign()
 294:     {
 295:         return uniqid();
 296:     }
 297: 
 298:     /**
 299:      * Set the recipient email address(es).
 300:      *
 301:      * @param string|array $email The recipient email address(es).
 302:      * @throws InvalidArgumentException If the email address is invalid.
 303:      * @return EmailInterface Chainable
 304:      */
 305:     public function setTo($email)
 306:     {
 307:         if (is_string($email)) {
 308:             $email = [ $email ];
 309:         }
 310: 
 311:         if (!is_array($email)) {
 312:             throw new InvalidArgumentException(
 313:                 'Must be an array of recipients.'
 314:             );
 315:         }
 316: 
 317:         $this->to = [];
 318: 
 319:         // At this point, `$email` can be an _email array_ or an _array of emails_...
 320:         if (isset($email['email'])) {
 321:             // Means we're not dealing with multiple emails
 322:             $this->addTo($email);
 323:         } else {
 324:             foreach ($email as $recipient) {
 325:                 $this->addTo($recipient);
 326:             }
 327:         }
 328: 
 329:         return $this;
 330:     }
 331: 
 332:     /**
 333:      * Add a recipient email address.
 334:      *
 335:      * @param  mixed $email The recipient email address to add.
 336:      * @throws InvalidArgumentException If the email address is invalid.
 337:      * @return EmailInterface Chainable
 338:      */
 339:     public function addTo($email)
 340:     {
 341:         if (is_string($email)) {
 342:             $this->to[] = $email;
 343:         } elseif (is_array($email)) {
 344:             $this->to[] = $this->emailFromArray($email);
 345:         } else {
 346:             throw new InvalidArgumentException(
 347:                 'Can not set to: email must be an array or a string'
 348:             );
 349:         }
 350: 
 351:         return $this;
 352:     }
 353: 
 354:     /**
 355:      * Get the recipient's email address.
 356:      *
 357:      * @return string[]
 358:      */
 359:     public function to()
 360:     {
 361:         return $this->to;
 362:     }
 363: 
 364:     /**
 365:      * Set the carbon copy (CC) recipient email address(es).
 366:      *
 367:      * @param string|array $email The CC recipient email address(es).
 368:      * @throws InvalidArgumentException If the email address is invalid.
 369:      * @return EmailInterface Chainable
 370:      */
 371:     public function setCc($email)
 372:     {
 373:         if (is_string($email)) {
 374:             $email = [ $email ];
 375:         }
 376: 
 377:         if (!is_array($email)) {
 378:             throw new InvalidArgumentException(
 379:                 'Must be an array of CC recipients.'
 380:             );
 381:         }
 382: 
 383:         $this->cc = [];
 384: 
 385:         // At this point, `$email` can be an _email array_ or an _array of emails_...
 386:         if (isset($email['email'])) {
 387:             // Means we're not dealing with multiple emails
 388:             $this->addCc($email);
 389:         } else {
 390:             foreach ($email as $recipient) {
 391:                 $this->addCc($recipient);
 392:             }
 393:         }
 394: 
 395:         return $this;
 396:     }
 397: 
 398:     /**
 399:      * Add a CC recipient email address.
 400:      *
 401:      * @param mixed $email The CC recipient email address to add.
 402:      * @throws InvalidArgumentException If the email address is invalid.
 403:      * @return EmailInterface Chainable
 404:      */
 405:     public function addCc($email)
 406:     {
 407:         if (is_string($email)) {
 408:             $this->cc[] = $email;
 409:         } elseif (is_array($email)) {
 410:             $this->cc[] = $this->emailFromArray($email);
 411:         } else {
 412:             throw new InvalidArgumentException(
 413:                 'Can not set to: email must be an array or a string'
 414:             );
 415:         }
 416: 
 417:         return $this;
 418:     }
 419: 
 420:     /**
 421:      * Get the CC recipient's email address.
 422:      *
 423:      * @return string[]
 424:      */
 425:     public function cc()
 426:     {
 427:         return $this->cc;
 428:     }
 429: 
 430:     /**
 431:      * Set the blind carbon copy (BCC) recipient email address(es).
 432:      *
 433:      * @param string|array $email The BCC recipient email address(es).
 434:      * @throws InvalidArgumentException If the email address is invalid.
 435:      * @return EmailInterface Chainable
 436:      */
 437:     public function setBcc($email)
 438:     {
 439:         if (is_string($email)) {
 440:             // Means we have a straight email
 441:             $email = [ $email ];
 442:         }
 443: 
 444:         if (!is_array($email)) {
 445:             throw new InvalidArgumentException(
 446:                 'Must be an array of BCC recipients.'
 447:             );
 448:         }
 449: 
 450:         $this->bcc = [];
 451: 
 452:         // At this point, `$email` can be an _email array_ or an _array of emails_...
 453:         if (isset($email['email'])) {
 454:             // Means we're not dealing with multiple emails
 455:             $this->addBcc($email);
 456:         } else {
 457:             foreach ($email as $recipient) {
 458:                 $this->addBcc($recipient);
 459:             }
 460:         }
 461: 
 462:         return $this;
 463:     }
 464: 
 465:     /**
 466:      * Add a BCC recipient email address.
 467:      *
 468:      * @param mixed $email The BCC recipient email address to add.
 469:      * @throws InvalidArgumentException If the email address is invalid.
 470:      * @return EmailInterface Chainable
 471:      */
 472:     public function addBcc($email)
 473:     {
 474:         if (is_string($email)) {
 475:             $this->bcc[] = $email;
 476:         } elseif (is_array($email)) {
 477:             $this->bcc[] = $this->emailFromArray($email);
 478:         } else {
 479:             throw new InvalidArgumentException(
 480:                 'Can not set to: email must be an array or a string'
 481:             );
 482:         }
 483: 
 484:         return $this;
 485:     }
 486: 
 487:     /**
 488:      * Get the BCC recipient's email address.
 489:      *
 490:      * @return string[]
 491:      */
 492:     public function bcc()
 493:     {
 494:         return $this->bcc;
 495:     }
 496: 
 497:     /**
 498:      * Set the sender's email address.
 499:      *
 500:      * @param  string|array $email An email address.
 501:      * @throws InvalidArgumentException If the email is not a string or an array.
 502:      * @return EmailInterface Chainable
 503:      * @todo   Implement optional "Sender" field.
 504:      */
 505:     public function setFrom($email)
 506:     {
 507:         if (is_array($email)) {
 508:             $this->from = $this->emailFromArray($email);
 509:         } elseif (is_string($email)) {
 510:             $this->from = $email;
 511:         } else {
 512:             throw new InvalidArgumentException(
 513:                 'Can not set from: email must be an array or a string'
 514:             );
 515:         }
 516: 
 517:         return $this;
 518:     }
 519: 
 520:     /**
 521:      * Get the sender's email address.
 522:      *
 523:      * @return string
 524:      */
 525:     public function from()
 526:     {
 527:         if ($this->from === null) {
 528:             $this->setFrom($this->config()->defaultFrom());
 529:         }
 530: 
 531:         return $this->from;
 532:     }
 533: 
 534:     /**
 535:      * Set email address to reply to the message.
 536:      *
 537:      * @param  mixed $email The sender's "Reply-To" email address.
 538:      * @throws InvalidArgumentException If the email is not a string or an array.
 539:      * @return EmailInterface Chainable
 540:      */
 541:     public function setReplyTo($email)
 542:     {
 543:         if (is_array($email)) {
 544:             $this->replyTo = $this->emailFromArray($email);
 545:         } elseif (is_string($email)) {
 546:             $this->replyTo = $email;
 547:         } else {
 548:             throw new InvalidArgumentException(
 549:                 'Can not set reply-to: email must be an array or a string'
 550:             );
 551:         }
 552: 
 553:         return $this;
 554:     }
 555: 
 556:     /**
 557:      * Get email address to reply to the message.
 558:      *
 559:      * @return string
 560:      */
 561:     public function replyTo()
 562:     {
 563:         if ($this->replyTo === null) {
 564:             $this->replyTo = $this->config()->defaultReplyTo();
 565:         }
 566: 
 567:         return $this->replyTo;
 568:     }
 569: 
 570:     /**
 571:      * Set the email subject.
 572:      *
 573:      * @param  string $subject The email subject.
 574:      * @throws InvalidArgumentException If the subject is not a string.
 575:      * @return EmailInterface Chainable
 576:      */
 577:     public function setSubject($subject)
 578:     {
 579:         if (!is_string($subject)) {
 580:             throw new InvalidArgumentException(
 581:                 'Subject needs to be a string'
 582:             );
 583:         }
 584: 
 585:         $this->subject = $subject;
 586: 
 587:         return $this;
 588:     }
 589: 
 590:     /**
 591:      * Get the email subject.
 592:      *
 593:      * @return string The emails' subject.
 594:      */
 595:     public function subject()
 596:     {
 597:         return $this->subject;
 598:     }
 599: 
 600:     /**
 601:      * Set the email's HTML message body.
 602:      *
 603:      * @param  string $body The HTML message body.
 604:      * @throws InvalidArgumentException If the message is not a string.
 605:      * @return EmailInterface Chainable
 606:      */
 607:     public function setMsgHtml($body)
 608:     {
 609:         if (!is_string($body)) {
 610:             throw new InvalidArgumentException(
 611:                 'HTML message needs to be a string'
 612:             );
 613:         }
 614: 
 615:         $this->msgHtml = $body;
 616: 
 617:         return $this;
 618:     }
 619: 
 620:     /**
 621:      * Get the email's HTML message body.
 622:      *
 623:      * If the message is not explitely set, it will be
 624:      * auto-generated from a template view.
 625:      *
 626:      * @return string
 627:      */
 628:     public function msgHtml()
 629:     {
 630:         if ($this->msgHtml === null) {
 631:             $this->msgHtml = $this->generateMsgHtml();
 632:         }
 633:         return $this->msgHtml;
 634:     }
 635: 
 636:     /**
 637:      * Get the email's HTML message from the template, if applicable.
 638:      *
 639:      * @see    ViewableInterface::renderTemplate()
 640:      * @return string
 641:      */
 642:     protected function generateMsgHtml()
 643:     {
 644:         $templateIdent = $this->templateIdent();
 645: 
 646:         if (!$templateIdent) {
 647:             $message = '';
 648:         } else {
 649:             $message = $this->renderTemplate($templateIdent);
 650:         }
 651: 
 652:         return $message;
 653:     }
 654: 
 655:     /**
 656:      * Set the email's plain-text message body.
 657:      *
 658:      * @param string $body The message's text body.
 659:      * @throws InvalidArgumentException If the parameter is invalid.
 660:      * @return EmailInterface Chainable
 661:      */
 662:     public function setMsgTxt($body)
 663:     {
 664:         if (!is_string($body)) {
 665:             throw new InvalidArgumentException(
 666:                 'Plan-text message needs to be a string'
 667:             );
 668:         }
 669: 
 670:         $this->msgTxt = $body;
 671: 
 672:         return $this;
 673:     }
 674: 
 675:     /**
 676:      * Get the email's plain-text message body.
 677:      *
 678:      * If the plain-text message is not explitely set,
 679:      * it will be auto-generated from the HTML message.
 680:      *
 681:      * @return string
 682:      */
 683:     public function msgTxt()
 684:     {
 685:         if ($this->msgTxt === null) {
 686:             $this->msgTxt = $this->stripHtml($this->msgHtml());
 687:         }
 688: 
 689:         return $this->msgTxt;
 690:     }
 691: 
 692:     /**
 693:      * Convert an HTML string to plain-text.
 694:      *
 695:      * @param string $html The HTML string to convert.
 696:      * @return string The resulting plain-text string.
 697:      */
 698:     protected function stripHtml($html)
 699:     {
 700:         $str = html_entity_decode($html);
 701: 
 702:         // Strip HTML (Replace br with newline, remove "invisible" tags and strip other tags)
 703:         $str = preg_replace('#<br[^>]*?>#siu', "\n", $str);
 704:         $str = preg_replace(
 705:             [
 706:                 '#<applet[^>]*?.*?</applet>#siu',
 707:                 '#<embed[^>]*?.*?</embed>#siu',
 708:                 '#<head[^>]*?>.*?</head>#siu',
 709:                 '#<noframes[^>]*?.*?</noframes>#siu',
 710:                 '#<noscript[^>]*?.*?</noscript>#siu',
 711:                 '#<noembed[^>]*?.*?</noembed>#siu',
 712:                 '#<object[^>]*?.*?</object>#siu',
 713:                 '#<script[^>]*?.*?</script>#siu',
 714:                 '#<style[^>]*?>.*?</style>#siu'
 715:             ],
 716:             '',
 717:             $str
 718:         );
 719:         $str = strip_tags($str);
 720: 
 721:         // Trim whitespace
 722:         $str = str_replace("\t", '', $str);
 723:         $str = preg_replace('#\n\r|\r\n#', "\n", $str);
 724:         $str = preg_replace('#\n{3,}#', "\n\n", $str);
 725:         $str = preg_replace('/ {2,}/', ' ', $str);
 726:         $str = implode("\n", array_map('trim', explode("\n", $str)));
 727:         $str = trim($str)."\n";
 728:         return $str;
 729:     }
 730: 
 731:     /**
 732:      * Set the email's attachments.
 733:      *
 734:      * @param  array $attachments The file attachments.
 735:      * @return EmailInterface Chainable
 736:      */
 737:     public function setAttachments(array $attachments)
 738:     {
 739:         foreach ($attachments as $att) {
 740:             $this->addAttachment($att);
 741:         }
 742: 
 743:         return $this;
 744:     }
 745: 
 746:     /**
 747:      * Add an attachment to the email.
 748:      *
 749:      * @param  mixed $attachment A single file attachment.
 750:      * @return EmailInterface Chainable
 751:      */
 752:     public function addAttachment($attachment)
 753:     {
 754:         $this->attachments[] = $attachment;
 755:         return $this;
 756:     }
 757: 
 758:     /**
 759:      * Get the email's attachments.
 760:      *
 761:      * @return array
 762:      */
 763:     public function attachments()
 764:     {
 765:         return $this->attachments;
 766:     }
 767: 
 768:     /**
 769:      * Enable or disable logging for this particular email.
 770:      *
 771:      * @param  boolean $log The log flag.
 772:      * @return EmailInterface Chainable
 773:      */
 774:     public function setLog($log)
 775:     {
 776:         $this->log = !!$log;
 777:         return $this;
 778:     }
 779: 
 780:     /**
 781:      * Determine if logging is enabled for this particular email.
 782:      *
 783:      * @return boolean
 784:      */
 785:     public function log()
 786:     {
 787:         if ($this->log === null) {
 788:             $this->log = $this->config()->defaultLog();
 789:         }
 790:         return $this->log;
 791:     }
 792: 
 793:     /**
 794:      * Enable or disable tracking for this particular email.
 795:      *
 796:      * @param boolean $track The track flag.
 797:      * @return EmailInterface Chainable
 798:      */
 799:     public function setTrack($track)
 800:     {
 801:         $this->track = !!$track;
 802:         return $this;
 803:     }
 804: 
 805:     /**
 806:      * Determine if tracking is enabled for this particular email.
 807:      *
 808:      * @return boolean
 809:      */
 810:     public function track()
 811:     {
 812:         if ($this->track === null) {
 813:             $this->track = $this->config()->defaultTrack();
 814:         }
 815:         return $this->track;
 816:     }
 817: 
 818:     /**
 819:      * Send the email to all recipients
 820:      *
 821:      * @return boolean Success / Failure.
 822:      * @todo Implement methods and property for toggling rich-text vs. plain-text
 823:      *       emails (`$mail->isHTML(true)`).
 824:      */
 825:     public function send()
 826:     {
 827:         $this->logger->debug(
 828:             'Attempting to send an email',
 829:             $this->to()
 830:         );
 831: 
 832:         $mail = $this->phpMailer;
 833: 
 834:         try {
 835:             $this->setSmtpOptions($mail);
 836: 
 837:             $mail->CharSet = 'UTF-8';
 838: 
 839:             // Setting reply-to field, if required.
 840:             $replyTo = $this->replyTo();
 841:             if ($replyTo) {
 842:                 $replyArr = $this->emailToArray($replyTo);
 843:                 $mail->addReplyTo($replyArr['email'], $replyArr['name']);
 844:             }
 845: 
 846:             // Setting from (sender) field.
 847:             $from = $this->from();
 848:             $fromArr = $this->emailToArray($from);
 849:             $mail->setFrom($fromArr['email'], $fromArr['name']);
 850: 
 851:             // Setting to (recipients) field(s).
 852:             $to = $this->to();
 853:             foreach ($to as $recipient) {
 854:                 $toArr = $this->emailToArray($recipient);
 855:                 $mail->addAddress($toArr['email'], $toArr['name']);
 856:             }
 857: 
 858:             // Setting cc (carbon-copy) field(s).
 859:             $cc = $this->cc();
 860:             foreach ($cc as $ccRecipient) {
 861:                 $ccArr = $this->emailToArray($ccRecipient);
 862:                 $mail->addCC($ccArr['email'], $ccArr['name']);
 863:             }
 864: 
 865:             // Setting bcc (black-carbon-copy) field(s).
 866:             $bcc = $this->bcc();
 867:             foreach ($bcc as $bccRecipient) {
 868:                 $bccArr = $this->emailToArray($bccRecipient);
 869:                 $mail->addBCC($bccArr['email'], $cc['name']);
 870:             }
 871: 
 872:             // Setting attachment(s), if required.
 873:             $attachments = $this->attachments();
 874:             foreach ($attachments as $att) {
 875:                 $mail->addAttachment($att);
 876:             }
 877: 
 878:             $mail->isHTML(true);
 879: 
 880:             $mail->Subject = $this->subject();
 881:             $mail->Body    = $this->msgHtml();
 882:             $mail->AltBody = $this->msgTxt();
 883: 
 884:             $ret = $mail->send();
 885: 
 886:             $this->logSend($ret, $mail);
 887: 
 888:             return $ret;
 889:         } catch (Exception $e) {
 890:             $this->logger->error(
 891:                 sprintf('Error sending email: %s', $e->getMessage())
 892:             );
 893:         }
 894:     }
 895: 
 896:     /**
 897:      * Set the SMTP's options for PHPMailer.
 898:      *
 899:      * @param PHPMailer $mail The PHPMailer to setup.
 900:      * @return void
 901:      */
 902:     public function setSmtpOptions(PHPMailer $mail)
 903:     {
 904:         $config = $this->config();
 905:         if (!$config['smtp']) {
 906:             return;
 907:         }
 908: 
 909:         $this->logger->debug(
 910:             sprintf('Using SMTP "%s" server to send email', $config['smtp_hostname'])
 911:         );
 912: 
 913:         $mail->IsSMTP();
 914:         $mail->Host       = $config['smtp_hostname'];
 915:         $mail->Port       = $config['smtp_port'];
 916:         $mail->SMTPAuth   = $config['smtp_auth'];
 917:         $mail->Username   = $config['smtp_username'];
 918:         $mail->Password   = $config['smtp_password'];
 919:         $mail->SMTPSecure = $config['smtp_security'];
 920:     }
 921: 
 922:     /**
 923:      * Enqueue the email for each recipient.
 924:      *
 925:      * @param mixed $ts A date/time to initiate the queue processing.
 926:      * @return boolean Success / Failure.
 927:      */
 928:     public function queue($ts = null)
 929:     {
 930:         $recipients = $this->to();
 931:         $author     = $this->from();
 932:         $subject    = $this->subject();
 933:         $msgHtml    = $this->msgHtml();
 934:         $msgTxt     = $this->msgTxt();
 935:         $campaign   = $this->campaign();
 936:         $queueId    = $this->queueId();
 937: 
 938:         foreach ($recipients as $to) {
 939:             $queueItem = $this->queueItemFactory()->create('charcoal/email/email-queue-item');
 940: 
 941:             $queueItem->setTo($to);
 942:             $queueItem->setFrom($author);
 943:             $queueItem->setSubject($subject);
 944:             $queueItem->setMsgHtml($msgHtml);
 945:             $queueItem->setMsgTxt($msgTxt);
 946:             $queueItem->setCampaign($campaign);
 947:             $queueItem->setProcessingDate($ts);
 948:             $queueItem->setQueueId($queueId);
 949: 
 950:             $res = $queueItem->save();
 951:         }
 952: 
 953:         return true;
 954:     }
 955: 
 956:     /**
 957:      * Log the send event for each recipient.
 958:      *
 959:      * @param  boolean $result Success or failure.
 960:      * @param  mixed   $mailer The raw mailer.
 961:      * @return void
 962:      */
 963:     protected function logSend($result, $mailer)
 964:     {
 965:         if ($this->log() === false) {
 966:             return;
 967:         }
 968: 
 969:         if (!$result) {
 970:             $this->logger->error('Email could not be sent.');
 971:         } else {
 972:             $this->logger->debug(
 973:                 sprintf('Email "%s" sent successfully.', $this->subject()),
 974:                 $this->to()
 975:             );
 976:         }
 977: 
 978:         $recipients = array_merge(
 979:             $this->to(),
 980:             $this->cc(),
 981:             $this->bcc()
 982:         );
 983: 
 984:         foreach ($recipients as $to) {
 985:             $log = $this->logFactory()->create('charcoal/email/email-log');
 986: 
 987:             $log->setType('email');
 988:             $log->setAction('send');
 989: 
 990:             $log->setRawResponse($mailer);
 991: 
 992:             $log->setMessageId($mailer->getLastMessageId());
 993:             $log->setCampaign($this->campaign());
 994: 
 995:             $log->setSendDate('now');
 996: 
 997:             $log->setFrom($mailer->From);
 998:             $log->setTo($to);
 999:             $log->setSubject($this->subject());
1000: 
1001:             $log->save();
1002:         }
1003:     }
1004: 
1005:     /**
1006:      * Log the queue event.
1007:      *
1008:      * @return void
1009:      * @todo Implement log qeueing.
1010:      */
1011:     protected function logQueue()
1012:     {
1013:     }
1014: 
1015:     /**
1016:      * Set the template data for the view.
1017:      *
1018:      * @param array $data The template data.
1019:      * @return Email Chainable
1020:      */
1021:     public function setTemplateData(array $data)
1022:     {
1023:         $this->templateData = $data;
1024:         return $this;
1025:     }
1026: 
1027:     /**
1028:      * Get the template data for the view.
1029:      *
1030:      * @return array
1031:      */
1032:     public function templateData()
1033:     {
1034:         return $this->templateData;
1035:     }
1036: 
1037:     /**
1038:      * Get the custom view controller for rendering
1039:      * the email's HTML message.
1040:      *
1041:      * Unlike typical `ViewableInterface` objects, the view controller is not
1042:      * the email itself but an external "email" template.
1043:      *
1044:      * @see    ViewableInterface::viewController()
1045:      * @return TemplateInterface|array
1046:      */
1047:     public function viewController()
1048:     {
1049:         $templateIdent = $this->templateIdent();
1050: 
1051:         if (!$templateIdent) {
1052:             return [];
1053:         }
1054: 
1055:         $templateFactory = clone($this->templateFactory());
1056:         $templateFactory->setDefaultClass('charcoal/email/generic-email');
1057:         $template = $templateFactory->create($templateIdent);
1058: 
1059:         $template->setData($this->templateData());
1060: 
1061:         return $template;
1062:     }
1063: 
1064:     /**
1065:      * Allow an object to define how the key getter are called.
1066:      *
1067:      * @param string $key The key to get the getter from.
1068:      * @return string The getter method name, for a given key.
1069:      */
1070:     protected function getter($key)
1071:     {
1072:         $getter = $key;
1073:         return $this->camelize($getter);
1074:     }
1075: 
1076:     /**
1077:      * Allow an object to define how the key setter are called.
1078:      *
1079:      * @param string $key The key to get the setter from.
1080:      * @return string The setter method name, for a given key.
1081:      */
1082:     protected function setter($key)
1083:     {
1084:         $setter = 'set_'.$key;
1085:         return $this->camelize($setter);
1086:     }
1087: 
1088:     /**
1089:      * Transform a snake_case string to camelCase.
1090:      *
1091:      * @param string $str The snake_case string to camelize.
1092:      * @return string The camelCase string.
1093:      */
1094:     private function camelize($str)
1095:     {
1096:         return lcfirst(implode('', array_map('ucfirst', explode('_', $str))));
1097:     }
1098: 
1099:     /**
1100:      * Temporary hack to fulfills the Configurable Interface.
1101:      *
1102:      * @return EmailConfig
1103:      */
1104:     public function createConfig()
1105:     {
1106:         // This should really be avoided.
1107:         $this->logger->warning('AbstractEmail::createConfig() was called, but should not.');
1108:         return new \Charcoal\Email\EmailConfig();
1109:     }
1110: }
1111: 
API documentation generated by ApiGen