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