File indexing completed on 2024-12-22 05:36:50

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_Log
0017  * @subpackage Writer
0018  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0019  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0020  * @version    $Id$
0021  */
0022 
0023 /** Zend_Log_Writer_Abstract */
0024 // require_once 'Zend/Log/Writer/Abstract.php';
0025 
0026 /** Zend_Log_Exception */
0027 // require_once 'Zend/Log/Exception.php';
0028 
0029 /** Zend_Log_Formatter_Simple*/
0030 // require_once 'Zend/Log/Formatter/Simple.php';
0031 
0032 /**
0033  * Class used for writing log messages to email via Zend_Mail.
0034  *
0035  * Allows for emailing log messages at and above a certain level via a
0036  * Zend_Mail object.  Note that this class only sends the email upon
0037  * completion, so any log entries accumulated are sent in a single email.
0038  *
0039  * @category   Zend
0040  * @package    Zend_Log
0041  * @subpackage Writer
0042  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0043  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0044  * @version    $Id$
0045  */
0046 class Zend_Log_Writer_Mail extends Zend_Log_Writer_Abstract
0047 {
0048     /**
0049      * Array of formatted events to include in message body.
0050      *
0051      * @var array
0052      */
0053     protected $_eventsToMail = array();
0054 
0055     /**
0056      * Array of formatted lines for use in an HTML email body; these events
0057      * are formatted with an optional formatter if the caller is using
0058      * Zend_Layout.
0059      *
0060      * @var array
0061      */
0062     protected $_layoutEventsToMail = array();
0063 
0064     /**
0065      * Zend_Mail instance to use
0066      *
0067      * @var Zend_Mail
0068      */
0069     protected $_mail;
0070 
0071     /**
0072      * Zend_Layout instance to use; optional.
0073      *
0074      * @var Zend_Layout
0075      */
0076     protected $_layout;
0077 
0078     /**
0079      * Optional formatter for use when rendering with Zend_Layout.
0080      *
0081      * @var Zend_Log_Formatter_Interface
0082      */
0083     protected $_layoutFormatter;
0084 
0085     /**
0086      * Array keeping track of the number of entries per priority level.
0087      *
0088      * @var array
0089      */
0090     protected $_numEntriesPerPriority = array();
0091 
0092     /**
0093      * Subject prepend text.
0094      *
0095      * Can only be used of the Zend_Mail object has not already had its
0096      * subject line set.  Using this will cause the subject to have the entry
0097      * counts per-priority level appended to it.
0098      *
0099      * @var string|null
0100      */
0101     protected $_subjectPrependText;
0102 
0103     /**
0104      * MethodMap for Zend_Mail's headers
0105      *
0106      * @var array
0107      */
0108     protected static $_methodMapHeaders = array(
0109         'from' => 'setFrom',
0110         'to' => 'addTo',
0111         'cc' => 'addCc',
0112         'bcc' => 'addBcc',
0113     );
0114 
0115     /**
0116      * Class constructor.
0117      *
0118      * Constructs the mail writer; requires a Zend_Mail instance, and takes an
0119      * optional Zend_Layout instance.  If Zend_Layout is being used,
0120      * $this->_layout->events will be set for use in the layout template.
0121      *
0122      * @param  Zend_Mail $mail Mail instance
0123      * @param  Zend_Layout $layout Layout instance; optional
0124      * @return void
0125      */
0126     public function __construct(Zend_Mail $mail, Zend_Layout $layout = null)
0127     {
0128         $this->_mail = $mail;
0129         if (null !== $layout) {
0130             $this->setLayout($layout);
0131         }
0132         $this->_formatter = new Zend_Log_Formatter_Simple();
0133     }
0134 
0135     /**
0136      * Create a new instance of Zend_Log_Writer_Mail
0137      *
0138      * @param  array|Zend_Config $config
0139      * @return Zend_Log_Writer_Mail
0140      */
0141     static public function factory($config)
0142     {
0143         $config = self::_parseConfig($config);
0144         $mail = self::_constructMailFromConfig($config);
0145         $writer = new self($mail);
0146 
0147         if (isset($config['layout']) || isset($config['layoutOptions'])) {
0148             $writer->setLayout($config);
0149         }
0150         if (isset($config['layoutFormatter'])) {
0151             $layoutFormatter = new $config['layoutFormatter'];
0152             $writer->setLayoutFormatter($layoutFormatter);
0153         }
0154         if (isset($config['subjectPrependText'])) {
0155             $writer->setSubjectPrependText($config['subjectPrependText']);
0156         }
0157 
0158         return $writer;
0159     }
0160 
0161     /**
0162      * Set the layout
0163      *
0164      * @param Zend_Layout|array $layout
0165      * @return Zend_Log_Writer_Mail
0166      * @throws Zend_Log_Exception
0167      */
0168     public function setLayout($layout)
0169     {
0170         if (is_array($layout)) {
0171             $layout = $this->_constructLayoutFromConfig($layout);
0172         }
0173 
0174         if (!$layout instanceof Zend_Layout) {
0175             // require_once 'Zend/Log/Exception.php';
0176             throw new Zend_Log_Exception('Mail must be an instance of Zend_Layout or an array');
0177         }
0178         $this->_layout = $layout;
0179 
0180         return $this;
0181     }
0182 
0183     /**
0184      * Construct a Zend_Mail instance based on a configuration array
0185      *
0186      * @param array $config
0187      * @return Zend_Mail
0188      * @throws Zend_Log_Exception
0189      */
0190     protected static function _constructMailFromConfig(array $config)
0191     {
0192         $mailClass = 'Zend_Mail';
0193         if (isset($config['mail'])) {
0194             $mailClass = $config['mail'];
0195         }
0196 
0197         if (!array_key_exists('charset', $config)) {
0198             $config['charset'] = null;
0199         }
0200         $mail = new $mailClass($config['charset']);
0201         if (!$mail instanceof Zend_Mail) {
0202             throw new Zend_Log_Exception($mail . 'must extend Zend_Mail');
0203         }
0204 
0205         if (isset($config['subject'])) {
0206             $mail->setSubject($config['subject']);
0207         }
0208 
0209         $headerAddresses = array_intersect_key($config, self::$_methodMapHeaders);
0210         if (count($headerAddresses)) {
0211             foreach ($headerAddresses as $header => $address) {
0212                 $method = self::$_methodMapHeaders[$header];
0213                 if (is_array($address) && isset($address['name'])
0214                     && !is_numeric($address['name'])
0215                 ) {
0216                     $params = array(
0217                         $address['email'],
0218                         $address['name']
0219                     );
0220                 } else if (is_array($address) && isset($address['email'])) {
0221                     $params = array($address['email']);
0222                 } else {
0223                     $params = array($address);
0224                 }
0225                 call_user_func_array(array($mail, $method), $params);
0226             }
0227         }
0228 
0229         return $mail;
0230     }
0231 
0232     /**
0233      * Construct a Zend_Layout instance based on a configuration array
0234      *
0235      * @param array $config
0236      * @return Zend_Layout
0237      * @throws Zend_Log_Exception
0238      */
0239     protected function _constructLayoutFromConfig(array $config)
0240     {
0241         $config = array_merge(array(
0242             'layout' => 'Zend_Layout',
0243             'layoutOptions' => null
0244         ), $config);
0245 
0246         $layoutClass = $config['layout'];
0247         $layout = new $layoutClass($config['layoutOptions']);
0248         if (!$layout instanceof Zend_Layout) {
0249             throw new Zend_Log_Exception($layout . 'must extend Zend_Layout');
0250         }
0251 
0252         return $layout;
0253     }
0254 
0255     /**
0256      * Places event line into array of lines to be used as message body.
0257      *
0258      * Handles the formatting of both plaintext entries, as well as those
0259      * rendered with Zend_Layout.
0260      *
0261      * @param  array $event Event data
0262      * @return void
0263      */
0264     protected function _write($event)
0265     {
0266         // Track the number of entries per priority level.
0267         if (!isset($this->_numEntriesPerPriority[$event['priorityName']])) {
0268             $this->_numEntriesPerPriority[$event['priorityName']] = 1;
0269         } else {
0270             $this->_numEntriesPerPriority[$event['priorityName']]++;
0271         }
0272 
0273         $formattedEvent = $this->_formatter->format($event);
0274 
0275         // All plaintext events are to use the standard formatter.
0276         $this->_eventsToMail[] = $formattedEvent;
0277 
0278         // If we have a Zend_Layout instance, use a specific formatter for the
0279         // layout if one exists.  Otherwise, just use the event with its
0280         // default format.
0281         if ($this->_layout) {
0282             if ($this->_layoutFormatter) {
0283                 $this->_layoutEventsToMail[] =
0284                     $this->_layoutFormatter->format($event);
0285             } else {
0286                 $this->_layoutEventsToMail[] = $formattedEvent;
0287             }
0288         }
0289     }
0290 
0291     /**
0292      * Gets instance of Zend_Log_Formatter_Instance used for formatting a
0293      * message using Zend_Layout, if applicable.
0294      *
0295      * @return Zend_Log_Formatter_Interface|null The formatter, or null.
0296      */
0297     public function getLayoutFormatter()
0298     {
0299         return $this->_layoutFormatter;
0300     }
0301 
0302     /**
0303      * Sets a specific formatter for use with Zend_Layout events.
0304      *
0305      * Allows use of a second formatter on lines that will be rendered with
0306      * Zend_Layout.  In the event that Zend_Layout is not being used, this
0307      * formatter cannot be set, so an exception will be thrown.
0308      *
0309      * @param  Zend_Log_Formatter_Interface $formatter
0310      * @return Zend_Log_Writer_Mail
0311      * @throws Zend_Log_Exception
0312      */
0313     public function setLayoutFormatter(Zend_Log_Formatter_Interface $formatter)
0314     {
0315         if (!$this->_layout) {
0316             throw new Zend_Log_Exception(
0317                 'cannot set formatter for layout; ' .
0318                     'a Zend_Layout instance is not in use');
0319         }
0320 
0321         $this->_layoutFormatter = $formatter;
0322         return $this;
0323     }
0324 
0325     /**
0326      * Allows caller to have the mail subject dynamically set to contain the
0327      * entry counts per-priority level.
0328      *
0329      * Sets the text for use in the subject, with entry counts per-priority
0330      * level appended to the end.  Since a Zend_Mail subject can only be set
0331      * once, this method cannot be used if the Zend_Mail object already has a
0332      * subject set.
0333      *
0334      * @param  string $subject Subject prepend text.
0335      * @return Zend_Log_Writer_Mail
0336      * @throws Zend_Log_Exception
0337      */
0338     public function setSubjectPrependText($subject)
0339     {
0340         if ($this->_mail->getSubject()) {
0341             throw new Zend_Log_Exception(
0342                 'subject already set on mail; ' .
0343                     'cannot set subject prepend text');
0344         }
0345 
0346         $this->_subjectPrependText = (string) $subject;
0347         return $this;
0348     }
0349 
0350     /**
0351      * Sends mail to recipient(s) if log entries are present.  Note that both
0352      * plaintext and HTML portions of email are handled here.
0353      *
0354      * @return void
0355      */
0356     public function shutdown()
0357     {
0358         // If there are events to mail, use them as message body.  Otherwise,
0359         // there is no mail to be sent.
0360         if (empty($this->_eventsToMail)) {
0361             return;
0362         }
0363 
0364         if ($this->_subjectPrependText !== null) {
0365             // Tack on the summary of entries per-priority to the subject
0366             // line and set it on the Zend_Mail object.
0367             $numEntries = $this->_getFormattedNumEntriesPerPriority();
0368             $this->_mail->setSubject(
0369                 "{$this->_subjectPrependText} ({$numEntries})");
0370         }
0371 
0372 
0373         // Always provide events to mail as plaintext.
0374         $this->_mail->setBodyText(implode('', $this->_eventsToMail));
0375 
0376         // If a Zend_Layout instance is being used, set its "events"
0377         // value to the lines formatted for use with the layout.
0378         if ($this->_layout) {
0379             // Set the required "messages" value for the layout.  Here we
0380             // are assuming that the layout is for use with HTML.
0381             $this->_layout->events =
0382                 implode('', $this->_layoutEventsToMail);
0383 
0384             // If an exception occurs during rendering, convert it to a notice
0385             // so we can avoid an exception thrown without a stack frame.
0386             try {
0387                 $this->_mail->setBodyHtml($this->_layout->render());
0388             } catch (Exception $e) {
0389                 trigger_error(
0390                     "exception occurred when rendering layout; " .
0391                         "unable to set html body for message; " .
0392                         "message = {$e->getMessage()}; " .
0393                         "code = {$e->getCode()}; " .
0394                         "exception class = " . get_class($e),
0395                     E_USER_NOTICE);
0396             }
0397         }
0398 
0399         // Finally, send the mail.  If an exception occurs, convert it into a
0400         // warning-level message so we can avoid an exception thrown without a
0401         // stack frame.
0402         try {
0403             $this->_mail->send();
0404         } catch (Exception $e) {
0405             trigger_error(
0406                 "unable to send log entries via email; " .
0407                     "message = {$e->getMessage()}; " .
0408                     "code = {$e->getCode()}; " .
0409                         "exception class = " . get_class($e),
0410                 E_USER_WARNING);
0411         }
0412     }
0413 
0414     /**
0415      * Gets a string of number of entries per-priority level that occurred, or
0416      * an emptry string if none occurred.
0417      *
0418      * @return string
0419      */
0420     protected function _getFormattedNumEntriesPerPriority()
0421     {
0422         $strings = array();
0423 
0424         foreach ($this->_numEntriesPerPriority as $priority => $numEntries) {
0425             $strings[] = "{$priority}={$numEntries}";
0426         }
0427 
0428         return implode(', ', $strings);
0429     }
0430 }