<?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($errstr0$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($exceptionPEAR_LOG_ALERT);
    }
}
?>