diff --git a/wire/config.php b/wire/config.php index 860285cf..b62e71cd 100644 --- a/wire/config.php +++ b/wire/config.php @@ -1011,6 +1011,28 @@ $config->substituteModules = array( 'InputfieldTinyMCE' => 'InputfieldCKEditor' ); +/** + * WireMail module(s) default settings + * + * Note you can add any other properties to the wireMail array that are supported by WireMail settings + * like we’ve done with from, fromName and headers here. Any values set here become defaults for the + * WireMail module. + * + * #property string module Name of WireMail module to use or blank to auto-detect. (default='') + * #property string from Default from email address, when none provided at runtime. (default=$config->adminEmail) + * #property string fromName Default from name string, when none provided at runtime. (default='') + * #property array headers Default additional headers to send in email, key=value. (default=[]) + * + * @var array + * + */ +$config->wireMail = array( + 'module' => '', + 'from' => '', + 'fromName' => '', + 'headers' => array(), +); + /** * PageList default settings * diff --git a/wire/core/Config.php b/wire/core/Config.php index b1dc86de..5d70c2f1 100644 --- a/wire/core/Config.php +++ b/wire/core/Config.php @@ -119,6 +119,7 @@ * @property string $moduleServiceURL URL where the modules web service can be accessed #pw-group-modules * @property string $moduleServiceKey API key for modules web service #pw-group-modules * @property bool $moduleCompile Allow use of compiled modules? #pw-group-modules + * @property array $wireMail Default WireMail module settings. #pw-group-modules * * @property array $substituteModules Associative array with names of substitutute modules for when requested module doesn't exist #pw-group-modules * @property array $logs Additional core logs to keep #pw-group-admin diff --git a/wire/core/WireMail.php b/wire/core/WireMail.php index 944d4b00..5a6a9321 100644 --- a/wire/core/WireMail.php +++ b/wire/core/WireMail.php @@ -50,17 +50,21 @@ * ~~~~~ * #pw-body * - * @method int send() - * @property array $to - * @property array $toName - * @property string $from - * @property string $fromName - * @property string $subject - * @property string $body - * @property string $bodyHTML - * @property array $header - * @property array $param - * @property array $attachments Array of file attachments (if populated) #pw-advanced + * @method int send() Send email. + * + * @property array $to To email address. + * @property array $toName Optional person’s name to accompany “to” email address + * @property string $from From email address. + * @property string $fromName Optional person’s name to accompany “from” email address. + * @property string $replyTo Reply-to email address (where supported). #pw-advanced + * @property string $replyToName Optional person’s name to accompany “reply-to” email address. #pw-advanced + * @property string $subject Subject line of email. + * @property string $body Plain text body of email. + * @property string $bodyHTML HTML body of email. + * @property array $header Associative array of additional headers. + * @property array $headers Alias of $header + * @property array $param Associative array of aditional params (likely not applicable to most WireMail modules). + * @property array $attachments Array of file attachments (if populated and where supported) #pw-advanced * */ @@ -75,6 +79,8 @@ class WireMail extends WireData implements WireMailInterface { 'toName' => array(), // to names - associative: indexed by 'to' email address, may be blank/null for any email 'from' => '', 'fromName' => '', + 'replyTo' => '', + 'replyToName' => '', 'subject' => '', 'body' => '', 'bodyHTML' => '', @@ -85,18 +91,37 @@ class WireMail extends WireData implements WireMailInterface { public function __construct() { $this->mail['header']['X-Mailer'] = "ProcessWire/" . $this->className(); + parent::__construct(); } - public function __get($key) { + public function get($key) { + if($key === 'headers') $key = 'header'; if(array_key_exists($key, $this->mail)) return $this->mail[$key]; - return parent::__get($key); + return parent::get($key); } - - public function __set($key, $value) { - if(array_key_exists($key, $this->mail)) $this->$key($value); // function call - else parent::__set($key, $value); + + public function set($key, $value) { + if($key === 'headers' || $key === 'header') { + if(is_array($value)) $this->headers($value); + } else if(array_key_exists($key, $this->mail)) { + $this->$key($value); // function call + } else { + parent::set($key, $value); + } + return $this; } + + public function __get($key) { return $this->get($key); } + public function __set($key, $value) { return $this->set($key, $value); } + /** + * Sanitize an email address or throw WireException if invalid + * + * @param string $email + * @return string + * @throws WireException + * + */ protected function sanitizeEmail($email) { $email = strtolower(trim($email)); $clean = $this->wire('sanitizer')->email($email); @@ -107,6 +132,13 @@ class WireMail extends WireData implements WireMailInterface { return $clean; } + /** + * Sanitize string for use in a email header + * + * @param string $header + * @return string + * + */ protected function sanitizeHeader($header) { return $this->wire('sanitizer')->emailHeader($header); } @@ -219,7 +251,7 @@ class WireMail extends WireData implements WireMailInterface { * * This sets the 'to name' for whatever the last added 'to' email address was. * - * @param string The 'to' name + * @param string $name The 'to' name * @return $this * @throws WireException if you attempt to set a toName before a to email. * @@ -233,7 +265,7 @@ class WireMail extends WireData implements WireMailInterface { } /** - * Set the email from address + * Set the email 'from' address and optionally name * * @param string $email Must be a single email address or "User Name " string. * @param string|null An optional FROM name (same as setting/calling fromName) @@ -242,8 +274,12 @@ class WireMail extends WireData implements WireMailInterface { * */ public function from($email, $name = null) { - if(is_null($name)) list($email, $name) = $this->extractEmailAndName($email); - if($name) $this->mail['fromName'] = $this->sanitizeHeader($name); + if(is_null($name)) { + list($email, $name) = $this->extractEmailAndName($email); + } else { + $email = $this->sanitizeEmail($email); + } + if($name) $this->fromName($name); $this->mail['from'] = $email; return $this; } @@ -254,7 +290,7 @@ class WireMail extends WireData implements WireMailInterface { * It is preferable to do this with the from() method, but this is provided to ensure that * all properties can be set with direct access, i.e. $mailer->fromName = 'User Name'; * - * @param string The 'from' name + * @param string $name The 'from' name * @return $this * */ @@ -263,6 +299,42 @@ class WireMail extends WireData implements WireMailInterface { return $this; } + /** + * Set the 'reply-to' email address and optionally name (where supported) + * + * @param string $email Must be a single email address or "User Name " string. + * @param string|null An optional Reply-To name (same as setting/calling replyToName method) + * @return $this + * @throws WireException if provided email was invalid + * + */ + public function replyTo($email, $name = null) { + if(is_null($name)) { + list($email, $name) = $this->extractEmailAndName($email); + } else { + $email = $this->sanitizeEmail($email); + } + if($name) $this->mail['replyToName'] = $this->sanitizeHeader($name); + $this->mail['replyTo'] = $email; + if(empty($name)) $name = $this->mail['replyToName']; + if(strlen($name)) $email = $this->bundleEmailAndName($email, $name); + $this->header('Reply-To', $email); + return $this; + } + + /** + * Set the 'reply-to' name (where supported) + * + * @param string $name + * @return $this + * + */ + public function replyToName($name) { + if(strlen($this->mail['replyTo'])) return $this->replyTo($this->mail['replyTo'], $name); + $this->mail['replyToName'] = $this->sanitizeHeader($name); + return $this; + } + /** * Set the email subject * @@ -319,14 +391,18 @@ class WireMail extends WireData implements WireMailInterface { * * #pw-advanced * - * @param string $key Header name - * @param string $value Header value + * @param string|array $key Header name + * @param string $value Header value or specify null to unset * @return $this * */ public function header($key, $value) { if(is_null($value)) { - unset($this->mail['header'][$key]); + if(is_array($key)) { + $this->headers($key); + } else { + unset($this->mail['header'][$key]); + } } else { $k = $this->wire('sanitizer')->name($this->sanitizeHeader($key)); // ensure consistent capitalization for all header keys @@ -338,6 +414,20 @@ class WireMail extends WireData implements WireMailInterface { return $this; } + /** + * Set multiple email headers using associative array + * + * @param array $headers + * @return $this + * + */ + public function headers(array $headers) { + foreach($headers as $key => $value) { + $this->header($key, $value); + } + return $this; + } + /** * Set any email param * diff --git a/wire/core/WireMailTools.php b/wire/core/WireMailTools.php index 35cad53f..5111417d 100644 --- a/wire/core/WireMailTools.php +++ b/wire/core/WireMailTools.php @@ -43,20 +43,43 @@ class WireMailTools extends Wire { */ public function ___new() { + /** @var WireMail|null $mail */ $mail = null; + + /** @var Modules $modules */ $modules = $this->wire('modules'); + + $settings = $this->wire('config')->wireMail; + if(!is_array($settings)) $settings = array(); + + // see if a WireMail module is specified in $config + if(isset($settings['module'])) { + $mail = $modules->get($settings['module']); + unset($settings['module']); + } - // attempt to locate an installed module that overrides WireMail - foreach($modules as $module) { - $parents = wireClassParents("$module"); - if(in_array('WireMail', $parents) && $modules->isInstalled("$module")) { - $mail = $modules->get("$module"); - break; + if(!$mail) { + // attempt to locate an installed module that overrides WireMail + foreach($modules->findByPrefix('WireMail') as $module) { + $parents = wireClassParents("$module"); + if(in_array('WireMail', $parents) && $modules->isInstalled("$module")) { + $mail = $modules->get("$module"); + break; + } + } + } + + // if no module found, default to WireMail base class + if(!$mail) { + $mail = $this->wire(new WireMail()); + } + + // if anything left in settings, apply as a default setting + if(!empty($settings)) { + foreach($settings as $key => $value) { + $mail->set($key, $value); } } - // if no module found, default to WireMail - if(is_null($mail)) $mail = $this->wire(new WireMail()); - /** @var WireMail $mail */ // reset just in case module was not singular $mail->to();