' * - record (array): Pair of name+email, e.g. ['name' => 'Full Name', 'email' => 'user@example.com'] * - records: (array) List of records, keyed sequentially. */ trait AddressingTrait { /** * The primary email recipient (single address). * * @var string|null * Ex: '"Foo Bar" ' * Ex: ['name' => "Foo Bar", "email" => "foo.bar@example.com"] * * The "To:" address is mapped to the "envelope" scope. The existing * envelope format treats this as a pair of fields [toName,toEmail]. * Consequently, we only support one "To:" address, and it uses a * special import/export method. */ protected $to; /** * The email sender (single address). * * @var string|null * Ex: '"Foo Bar" ' * @scope envelope */ protected $from; /** * The email sender's Reply-To (single address). * * @var string|null * Ex: '"Foo Bar" ' * @scope envelope */ protected $replyTo; /** * Additional recipients (multiple addresses). * * @var string|null * Ex: '"Foo Bar" , "Whiz Bang" ' * Ex: [['name' => 'Foo Bar', 'email' => 'foo.bar@example.com'], ['name' => 'Whiz Bang', 'email' => 'whiz.bang@example.com']] * @scope envelope */ protected $cc; /** * Additional recipients (multiple addresses). * * @var string|null * Ex: '"Foo Bar" , "Whiz Bang" ' * Ex: [['name' => 'Foo Bar', 'email' => 'foo.bar@example.com'], ['name' => 'Whiz Bang', 'email' => 'whiz.bang@example.com']] * @scope envelope */ protected $bcc; /** * Get the list of "To:" addresses. * * Note: This returns only * * @param string $format * Ex: 'rfc822', 'records', 'record' * @return array|string * Ex: '"Foo Bar" ' * Ex: ['name' => 'Foo Bar', 'email' => 'foo.bar@example.com'] */ public function getTo($format = ADDRESS_EXPORT_FMT) { return $this->formatAddress($format, $this->to); } /** * Get the "From:" address. * * @param string $format * Ex: 'rfc822', 'records', 'record' * @return array|string * The "From" address. If none set, this will be empty ([]). * Ex: '"Foo Bar" ' * Ex: ['name' => 'Foo Bar', 'email' => 'foo.bar@example.com'] */ public function getFrom($format = ADDRESS_EXPORT_FMT) { return $this->formatAddress($format, $this->from); } /** * Get the "Reply-To:" address. * * @param string $format * Ex: 'rfc822', 'records', 'record' * @return array|string * The "From" address. If none set, this will be empty ([]). * Ex: '"Foo Bar" ' * Ex: ['name' => 'Foo Bar', 'email' => 'foo.bar@example.com'] */ public function getReplyTo($format = ADDRESS_EXPORT_FMT) { return $this->formatAddress($format, $this->replyTo); } /** * Get the list of "Cc:" addresses. * * @param string $format * Ex: 'rfc822', 'records', 'record' * @return array|string * List of addresses. * Ex: 'First , second@example.com' * Ex: [['name' => 'First', 'email' => 'first@example.com'], ['email' => 'second@example.com']] */ public function getCc($format = ADDRESS_EXPORT_FMT) { return $this->formatAddress($format, $this->cc); } /** * Get the list of "Bcc:" addresses. * * @param string $format * Ex: 'rfc822', 'records', 'record' * @return array|string * List of addresses. * Ex: 'First , second@example.com' * Ex: [['name' => 'First', 'email' => 'first@example.com'], ['email' => 'second@example.com']] */ public function getBcc($format = ADDRESS_EXPORT_FMT) { return $this->formatAddress($format, $this->bcc); } /** * @param string|array $address * Ex: '"Foo Bar" ' * Ex: ['name' => "Foo Bar", "email" => "foo.bar@example.com"] * @return $this */ public function setFrom($address) { $this->from = $this->formatAddress(ADDRESS_STORAGE_FMT, $address); return $this; } /** * @param string|array $address * Ex: '"Foo Bar" ' * Ex: ['name' => "Foo Bar", "email" => "foo.bar@example.com"] * @return $this */ public function setTo($address) { $this->to = $this->formatAddress(ADDRESS_STORAGE_FMT, $address); return $this; } /** * @param string|array $address * Ex: '"Foo Bar" ' * Ex: ['name' => "Foo Bar", "email" => "foo.bar@example.com"] * @return $this */ public function setReplyTo($address) { $this->replyTo = $this->formatAddress(ADDRESS_STORAGE_FMT, $address); return $this; } /** * Set the "CC:" list. * * @param string|array $address * Ex: '"Foo Bar" ' * Ex: ['name' => "Foo Bar", "email" => "foo.bar@example.com"] * Ex: [['email' => 'first@example.com'], ['email' => 'second@example.com']] * @return $this */ public function setCc($address) { $this->cc = $this->formatAddress(ADDRESS_STORAGE_FMT, $address); return $this; } /** * Set the "BCC:" list. * * @param string|array $address * Ex: '"Foo Bar" ' * Ex: ['name' => "Foo Bar", "email" => "foo.bar@example.com"] * Ex: [['email' => 'first@example.com'], ['email' => 'second@example.com']] * @return $this */ public function setBcc($address) { $this->bcc = $this->formatAddress(ADDRESS_STORAGE_FMT, $address); return $this; } /** * Add another "CC:" address. * * @param string|array $address * Ex: '"Foo Bar" ' * Ex: ['name' => "Foo Bar", "email" => "foo.bar@example.com"] * Ex: [['email' => 'first@example.com'], ['email' => 'second@example.com']] * @return $this */ public function addCc($address) { return $this->setCc(array_merge( $this->getCc('records'), $this->formatAddress('records', $address) )); } /** * Add another "BCC:" address. * * @param string|array $address * Ex: '"Foo Bar" ' * Ex: ['name' => "Foo Bar", "email" => "foo.bar@example.com"] * Ex: [['email' => 'first@example.com'], ['email' => 'second@example.com']] * @return $this */ public function addBcc($address) { return $this->setBcc(array_merge( $this->getBcc('records'), $this->formatAddress('records', $address) )); } /** * Plugin to `WorkflowMessageInterface::import()` and handle toEmail/toName. * * @param array $values * @see \Civi\WorkflowMessage\Traits\ReflectiveWorkflowTrait::import */ protected function importExtraEnvelope_toAddress(array &$values): void { if (array_key_exists('toEmail', $values) || array_key_exists('toName', $values)) { $this->setTo(['name' => $values['toName'] ?? NULL, 'email' => $values['toEmail'] ?? NULL]); unset($values['toName']); unset($values['toEmail']); } } /** * Plugin to `WorkflowMessageInterface::export()` and handle toEmail/toName. * * @param array $values * @see \Civi\WorkflowMessage\Traits\ReflectiveWorkflowTrait::export */ protected function exportExtraEnvelope_toAddress(array &$values): void { $addr = $this->getTo('record'); $values['toName'] = $addr['name'] ?? NULL; $values['toEmail'] = $addr['email'] ?? NULL; } /** * Convert an address to the desired format. * * @param string $newFormat * Ex: 'rfc822', 'records', 'record' * @param array|string $mixed * @return array|string|null */ private function formatAddress($newFormat, $mixed) { if ($mixed === NULL) { return NULL; } $oldFormat = is_string($mixed) ? 'rfc822' : (array_key_exists('email', $mixed) ? 'record' : 'records'); if ($oldFormat === $newFormat) { return $mixed; } $recordToObj = function (?array $record) { return new \ezcMailAddress($record['email'], $record['name'] ?? ''); }; $objToRecord = function (?\ezcMailAddress $addr) { return is_null($addr) ? NULL : ['email' => $addr->email, 'name' => $addr->name]; }; // Convert $mixed to intermediate format (ezcMailAddress[] $objects) and then to final format. /** @var \ezcMailAddress[] $objects */ switch ($oldFormat) { case 'rfc822': $objects = \ezcMailTools::parseEmailAddresses($mixed); break; case 'record': $objects = [$recordToObj($mixed)]; break; case 'records': $objects = array_map($recordToObj, $mixed); break; default: throw new \RuntimeException("Unrecognized source format: $oldFormat"); } switch ($newFormat) { case 'rfc822': // We use `implode(map(composeEmailAddress))` instead of `composeEmailAddresses` because the latter has header-line-wrapping. return implode(', ', array_map(['ezcMailTools', 'composeEmailAddress'], $objects)); case 'record': if (count($objects) > 1) { throw new \RuntimeException("Cannot convert email addresses to record format. Too many addresses."); } return $objToRecord($objects[0] ?? NULL); case 'records': return array_map($objToRecord, $objects); default: throw new \RuntimeException("Unrecognized output format: $newFormat"); } } }