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