<?php
/**
 * Log:: driver to write messages into Growl client
 *
 * PHP versions 4 and 5
 *
 * @category  Logging
 * @package   Log
 * @author    Laurent Laville <pear@laurent-laville.org>
 * @copyright 2009 Laurent Laville
 * @license   http://www.opensource.org/licenses/bsd-license.php  New BSD License
 * @version   CVS: $Id:$
 * @link      http://pear.php.net/package/Log
 */
require_once 'Net/Growl.php';
define('GROWL_PRIORITY_LOW', -2);
define('GROWL_PRIORITY_MODERATE', -1);
define('GROWL_PRIORITY_NORMAL', 0);
define('GROWL_PRIORITY_HIGH', 1);
define('GROWL_PRIORITY_EMERGENCY', 2);
/**
 * The Log_growl class is a concrete implementation of the Log::
 * abstract class which writes message into Growl client listener.
 *
 * http://growl.info/
 * http://www.growlforwindows.com
 *
 * @category  Logging
 * @package   Log
 * @author    Laurent Laville <pear@laurent-laville.org>
 * @copyright 2009 Laurent Laville
 * @license   http://www.opensource.org/licenses/bsd-license.php  New BSD License
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Log
 */
class Log_growl extends Log
{
    /**
     * String containing the format of a log line.
     * @var string
     * @access private
     */
    var $_lineFormat = '%1$s %2$s [%3$s] %4$s';
    /**
     * String containing the timestamp format.  It will be passed directly to
     * strftime().  Note that the timestamp string will generated using the
     * current locale.
     *
     * @var string
     * @access private
     */
    var $_timeFormat = '%b %d %H:%M:%S';
    /**
     * Flag whether to throw PHP errors that have been converted to ErrorException
     *
     * @var boolean
     * @access protected
     */
    var $throwErrorExceptions;
    /**
     * Previous error handler defined by set_error_handler if any
     *
     * @var mixed
     * @access protected
     */
    var $oldErrorHandler;
    /**
     * Instance of PEAR::Net_Growl
     *
     * @var object
     * @access protected
     */
    var $growl;
    /**
     * Constructs a new Log_growl object.
     *
     * @param string $name  Ignored.
     * @param string $ident The identity string.
     * @param array  $conf  The configuration array.
     * @param int    $level Log messages up to and including this level.
     *
     * @access public
     */
    function Log_growl($name = '', $ident = 'PHP', $conf = array(),
        $level = PEAR_LOG_DEBUG
    ) {
        $this->_id    = md5(microtime());
        $this->_ident = $ident;
        $this->_mask  = Log::UPTO($level);
        if (!empty($conf['lineFormat'])) {
            $this->_lineFormat = str_replace(
                array_keys($this->_formatMap),
                array_values($this->_formatMap),
                $conf['lineFormat']
            );
        }
        if (!empty($conf['timeFormat'])) {
            $this->_timeFormat = $conf['timeFormat'];
        }
        if (isset($conf['application'])) {
            $applicationName = $conf['application'];
        } else {
            $applicationName = 'PEAR Log_growl';
        }
        $applicationName = str_replace(':', ' ', $applicationName);
        if (isset($conf['notifications'])) {
            $notifications = $conf['notifications'];
        } else {
            $notifications = array('All events');
        }
        if (isset($conf['password'])) {
            $password = $conf['password'];
        } else {
            $password = '';
        }
        $options = array();
        if (isset($conf['host'])) {
            $options['host'] = $conf['host'];
        }
        if (isset($conf['port'])) {
            $options['port'] = $conf['port'];
        }
        if (isset($conf['protocol'])) {
            $options['protocol'] = $conf['protocol'];
        }
        $this->growl =& Net_Growl::singleton(
            $applicationName, $notifications, $password, $options
        );
    }
    /**
     * Sends $message to Growl client listener. Also, passes the message
     * along to any Log_observer instances that are observing this Log.
     *
     * @param mixed  $message  String or object containing the message to log.
     * @param string $priority The priority of the message.  Valid values are:
     *                         PEAR_LOG_EMERG, PEAR_LOG_ALERT,
     *                         PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING,
     *                         PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG.
     *
     * @return boolean  True on success or false on failure.
     * @access public
     */
    function log($message, $priority = null)
    {
        static $growlpriorities;
        if (!isset($growlpriorities)) {
            $growlpriorities = array(
                GROWL_PRIORITY_LOW,
                GROWL_PRIORITY_MODERATE,
                GROWL_PRIORITY_NORMAL,
                GROWL_PRIORITY_HIGH,
                GROWL_PRIORITY_EMERGENCY
            );
        }
        /* If a priority hasn't been specified, use the default value. */
        if ($priority === null) {
            $priority = $this->_priority;
        }
        /* Abort early if the priority is above the maximum logging level. */
        if (!$this->_isMasked($priority)) {
            return false;
        }
        /* Extract valid Growl components from $message parameter */
        if (is_array($message)) {
            if (isset($message['notification'])
                && is_string($message['notification'])
            ) {
                $notification = $message['notification'];
            } else {
                $notification = 'All events';
            }
            if (isset($message['message'])) {
                $object = $message['message'];
            } else {
                $object = null;
            }
            if (isset($message['title'])
                && is_string($message['title'])
            ) {
                $title = $message['title'];
            } else {
                $title = 'Log Growl';
            }
            $options = array();
            if (isset($message['priority'])
                && in_array($message['priority'], $growlpriorities)
            ) {
                $options['priority'] = $message['priority'];
            }
            if (isset($message['sticky'])
                && is_bool($message['sticky'])
            ) {
                $options['sticky'] = $message['sticky'];
            }
        } elseif (is_scalar($message)) {
            $title        = 'Log Growl';
            $object       = $message;
            $notification = 'All events';
            $options      = array();
        } elseif ($message instanceof Exception) {
            $title        = get_class($message);
            $object       = $message->getMessage();
            $notification = 'All events';
            $options      = array(
                'priority' => GROWL_PRIORITY_EMERGENCY,
                'sticky'   => true
            );
        } else {
            return false;
        }
        if (!(isset($object) && is_string($object))) {
            return false;
        }
        /* If message Growl priority is not defined, 
           used the mapped PEAR Log priority instead */
        if (!isset($options['priority'])) {
            switch ($priority) {
            case PEAR_LOG_EMERG:
            case PEAR_LOG_ALERT:
            case PEAR_LOG_CRIT:
                $options['priority'] = GROWL_PRIORITY_EMERGENCY;
                break;
            case PEAR_LOG_ERR:
            case PEAR_LOG_WARNING:
                $options['priority'] = GROWL_PRIORITY_HIGH;
                break;
            case PEAR_LOG_NOTICE:
            case PEAR_LOG_INFO:
            case PEAR_LOG_DEBUG:
                $options['priority'] = GROWL_PRIORITY_NORMAL;
                break;
            }
        }
        if (!isset($options['sticky'])) {
            $options['sticky'] = false;
        }
        /* Extract the string representation of the message. */
        $message = $this->_extractMessage($object);
        /* The return/line feed character sequence is used
            as the end-of-line delimiter in the GNTP protocol. */
        $message = str_replace("\r", "", $message);
        /* Build the string containing the complete log line. */
        $line = $this->_format(
            $this->_lineFormat, 
            strftime($this->_timeFormat), $priority, $message
        );
        $success = $this->growl->notify($notification, $title, $line, $options);
        /* Notify observers about this log message. */
        $this->_announce(array('priority' => $priority, 'message' => $message));
        return $success;
    }
    /**
     * Register Growl driver as your error handler
     *
     * @param bool $throwErrorExceptions Convert PHP errors to ErrorException
     * @param bool $replaceOldHandler    Replace or chain with previous error handler
     *
     * @return mixed Returns a string containing the previously defined error handler
     *               (if any), or NULL on error. If the previous handler was
     *               a class method, this function will return an indexed array
     *               with the class and the method name.
     * @access public
     */
    function registerErrorHandler($throwErrorExceptions = false,
        $replaceOldHandler = true
    ) {
        $this->throwErrorExceptions = $throwErrorExceptions;
        $old_error_handler     = set_error_handler(array($this, 'errorHandler'));
        $this->oldErrorHandler = ($replaceOldHandler ? null : $old_error_handler);
        return $old_error_handler;
    }
    /**
     * Log Growl's error handler
     *
     * @param int    $errno      contains the level of the error raised
     * @param string $errstr     contains the error message
     * @param string $errfile    contains the filename that the error was raised in
     * @param int    $errline    contains the line number the error was raised at
     * @param array  $errcontext contain an array of every variable that existed
     *                           in the scope the error was triggered in
     *
     * @return void
     * @access public
     */
    function errorHandler($errno, $errstr, $errfile, $errline, $errcontext)
    {
        // Only catch errors we are asking for
        if ((error_reporting() & $errno) == 0) {
            return;
        }
        /* Map the PHP error to a Log priority. */
        switch ($errno) {
        case E_WARNING:
        case E_USER_WARNING:
            $growlpriority = GROWL_PRIORITY_NORMAL;
            $priority = PEAR_LOG_WARNING;
            $sticky = false;
            break;
        case E_NOTICE:
        case E_USER_NOTICE:
        case E_STRICT:
            $growlpriority = GROWL_PRIORITY_NORMAL;
            $priority = PEAR_LOG_NOTICE;
            $sticky = false;
            break;
        case E_ERROR:
        case E_USER_ERROR:
            $growlpriority = GROWL_PRIORITY_HIGH;
            $priority = PEAR_LOG_ERR;
            $sticky = true;
            break;
        default:
            $growlpriority = GROWL_PRIORITY_NORMAL;
            $priority = PEAR_LOG_INFO;
            $sticky = false;
        }
        if ($this->throwErrorExceptions) {
            throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
        } else {
            $this->log(
                array(
                    'message' => $errstr
                                . ' in ' . $errfile
                                . ' at line ' . $errline,
                    'priority' => $growlpriority,
                    'sticky'   => $sticky
                ),
                $priority
            );
            if (is_callable($this->oldErrorHandler)) {
                // chained with previous error handler defined
                call_user_func_array(
                    $this->oldErrorHandler,
                    array($errno, $errstr, $errfile, $errline)
                );
            }
        }
    }
    /**
     * Register Log Growl as your exception handler
     *
     * @return string|null Returns the name of the previously defined
     *                     exception handler, or NULL on error. If no  previous
     *                     handler was defined, NULL is also returned.
     * @access public
     */
    function registerExceptionHandler()
    {
        return set_exception_handler(array($this, 'exceptionHandler'));
    }
    /**
     * Log Growl's exception handler
     *
     * @param exception $exception contains the exception raised
     *
     * @return void
     * @access public
     */
    function exceptionHandler($exception)
    {
        header('HTTP/1.1 500 Internal Server Error');
        $this->log($exception, PEAR_LOG_ALERT);
    }
}
?>