1<?php 2 3/** 4 * @see https://github.com/laminas/laminas-log for the canonical source repository 5 * @copyright https://github.com/laminas/laminas-log/blob/master/COPYRIGHT.md 6 * @license https://github.com/laminas/laminas-log/blob/master/LICENSE.md New BSD License 7 */ 8 9namespace Laminas\Log\Writer; 10 11use Laminas\Log\Exception; 12use Laminas\Log\Formatter\Simple as SimpleFormatter; 13use Laminas\Mail\Message as MailMessage; 14use Laminas\Mail\MessageFactory as MailMessageFactory; 15use Laminas\Mail\Transport; 16use Laminas\Mail\Transport\Exception as TransportException; 17use Traversable; 18 19/** 20 * Class used for writing log messages to email via Laminas\Mail. 21 * 22 * Allows for emailing log messages at and above a certain level via a 23 * Laminas\Mail\Message object. Note that this class only sends the email upon 24 * completion, so any log entries accumulated are sent in a single email. 25 * The email is sent using a Laminas\Mail\Transport\TransportInterface object 26 * (Sendmail is default). 27 */ 28class Mail extends AbstractWriter 29{ 30 /** 31 * Array of formatted events to include in message body. 32 * 33 * @var array 34 */ 35 protected $eventsToMail = []; 36 37 /** 38 * Mail message instance to use 39 * 40 * @var MailMessage 41 */ 42 protected $mail; 43 44 /** 45 * Mail transport instance to use; optional. 46 * 47 * @var Transport\TransportInterface 48 */ 49 protected $transport; 50 51 /** 52 * Array keeping track of the number of entries per priority level. 53 * 54 * @var array 55 */ 56 protected $numEntriesPerPriority = []; 57 58 /** 59 * Subject prepend text. 60 * 61 * Can only be used of the Laminas\Mail object has not already had its 62 * subject line set. Using this will cause the subject to have the entry 63 * counts per-priority level appended to it. 64 * 65 * @var string|null 66 */ 67 protected $subjectPrependText; 68 69 /** 70 * Constructor 71 * 72 * @param MailMessage|array|Traversable $mail 73 * @param Transport\TransportInterface $transport Optional 74 * @throws Exception\InvalidArgumentException 75 */ 76 public function __construct($mail, Transport\TransportInterface $transport = null) 77 { 78 if ($mail instanceof Traversable) { 79 $mail = iterator_to_array($mail); 80 } 81 82 if (is_array($mail)) { 83 parent::__construct($mail); 84 if (isset($mail['subject_prepend_text'])) { 85 $this->setSubjectPrependText($mail['subject_prepend_text']); 86 } 87 $transport = isset($mail['transport']) ? $mail['transport'] : null; 88 $mail = isset($mail['mail']) ? $mail['mail'] : null; 89 if (is_array($mail)) { 90 $mail = MailMessageFactory::getInstance($mail); 91 } 92 if (is_array($transport)) { 93 $transport = Transport\Factory::create($transport); 94 } 95 } 96 97 // Ensure we have a valid mail message 98 if (! $mail instanceof MailMessage) { 99 throw new Exception\InvalidArgumentException(sprintf( 100 'Mail parameter of type %s is invalid; must be of type Laminas\Mail\Message', 101 (is_object($mail) ? get_class($mail) : gettype($mail)) 102 )); 103 } 104 $this->mail = $mail; 105 106 // Ensure we have a valid mail transport 107 if (null === $transport) { 108 $transport = new Transport\Sendmail(); 109 } 110 if (! $transport instanceof Transport\TransportInterface) { 111 throw new Exception\InvalidArgumentException(sprintf( 112 'Transport parameter of type %s is invalid; must be of type Laminas\Mail\Transport\TransportInterface', 113 (is_object($transport) ? get_class($transport) : gettype($transport)) 114 )); 115 } 116 $this->setTransport($transport); 117 118 if ($this->formatter === null) { 119 $this->formatter = new SimpleFormatter(); 120 } 121 } 122 123 /** 124 * Set the transport message 125 * 126 * @param Transport\TransportInterface $transport 127 * @return Mail 128 */ 129 public function setTransport(Transport\TransportInterface $transport) 130 { 131 $this->transport = $transport; 132 return $this; 133 } 134 135 /** 136 * Places event line into array of lines to be used as message body. 137 * 138 * @param array $event Event data 139 */ 140 protected function doWrite(array $event) 141 { 142 // Track the number of entries per priority level. 143 if (! isset($this->numEntriesPerPriority[$event['priorityName']])) { 144 $this->numEntriesPerPriority[$event['priorityName']] = 1; 145 } else { 146 $this->numEntriesPerPriority[$event['priorityName']]++; 147 } 148 149 // All plaintext events are to use the standard formatter. 150 $this->eventsToMail[] = $this->formatter->format($event); 151 } 152 153 /** 154 * Allows caller to have the mail subject dynamically set to contain the 155 * entry counts per-priority level. 156 * 157 * Sets the text for use in the subject, with entry counts per-priority 158 * level appended to the end. Since a Laminas\Mail\Message subject can only be set 159 * once, this method cannot be used if the Laminas\Mail\Message object already has a 160 * subject set. 161 * 162 * @param string $subject Subject prepend text 163 * @return Mail 164 */ 165 public function setSubjectPrependText($subject) 166 { 167 $this->subjectPrependText = (string) $subject; 168 return $this; 169 } 170 171 /** 172 * Sends mail to recipient(s) if log entries are present. Note that both 173 * plaintext and HTML portions of email are handled here. 174 */ 175 public function shutdown() 176 { 177 // If there are events to mail, use them as message body. Otherwise, 178 // there is no mail to be sent. 179 if (empty($this->eventsToMail)) { 180 return; 181 } 182 183 if ($this->subjectPrependText !== null) { 184 // Tack on the summary of entries per-priority to the subject 185 // line and set it on the Laminas\Mail object. 186 $numEntries = $this->getFormattedNumEntriesPerPriority(); 187 $this->mail->setSubject("{$this->subjectPrependText} ({$numEntries})"); 188 } 189 190 // Always provide events to mail as plaintext. 191 $this->mail->setBody(implode(PHP_EOL, $this->eventsToMail)); 192 193 // Finally, send the mail. If an exception occurs, convert it into a 194 // warning-level message so we can avoid an exception thrown without a 195 // stack frame. 196 try { 197 $this->transport->send($this->mail); 198 } catch (TransportException\ExceptionInterface $e) { 199 trigger_error( 200 "unable to send log entries via email; " . 201 "message = {$e->getMessage()}; " . 202 "code = {$e->getCode()}; " . 203 "exception class = " . get_class($e), 204 E_USER_WARNING 205 ); 206 } 207 } 208 209 /** 210 * Gets a string of number of entries per-priority level that occurred, or 211 * an empty string if none occurred. 212 * 213 * @return string 214 */ 215 protected function getFormattedNumEntriesPerPriority() 216 { 217 $strings = []; 218 219 foreach ($this->numEntriesPerPriority as $priority => $numEntries) { 220 $strings[] = "{$priority}={$numEntries}"; 221 } 222 223 return implode(', ', $strings); 224 } 225} 226