diff -r 5b37998e522e -r 162c1de6545a web/lib/Zend/Wildfire/Plugin/FirePhp.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/lib/Zend/Wildfire/Plugin/FirePhp.php Fri Mar 11 15:05:35 2011 +0100 @@ -0,0 +1,816 @@ + 1, /* The offset in the trace which identifies the source of the message */ + 'maxTraceDepth' => 99, /* Maximum depth for stack traces */ + 'maxObjectDepth' => 10, /* The maximum depth to traverse objects when encoding */ + 'maxArrayDepth' => 20, /* The maximum depth to traverse nested arrays when encoding */ + 'includeLineNumbers' => true /* Whether to include line and file info for each message */ + ); + + /** + * Filters used to exclude object members when encoding + * @var array + */ + protected $_objectFilters = array(); + + /** + * A stack of objects used during encoding to detect recursion + * @var array + */ + protected $_objectStack = array(); + + /** + * Create singleton instance. + * + * @param string $class OPTIONAL Subclass of Zend_Wildfire_Plugin_FirePhp + * @return Zend_Wildfire_Plugin_FirePhp Returns the singleton Zend_Wildfire_Plugin_FirePhp instance + * @throws Zend_Wildfire_Exception + */ + public static function init($class = null) + { + if (self::$_instance !== null) { + require_once 'Zend/Wildfire/Exception.php'; + throw new Zend_Wildfire_Exception('Singleton instance of Zend_Wildfire_Plugin_FirePhp already exists!'); + } + if ($class !== null) { + if (!is_string($class)) { + require_once 'Zend/Wildfire/Exception.php'; + throw new Zend_Wildfire_Exception('Third argument is not a class string'); + } + + if (!class_exists($class)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($class); + } + self::$_instance = new $class(); + if (!self::$_instance instanceof Zend_Wildfire_Plugin_FirePhp) { + self::$_instance = null; + require_once 'Zend/Wildfire/Exception.php'; + throw new Zend_Wildfire_Exception('Invalid class to third argument. Must be subclass of Zend_Wildfire_Plugin_FirePhp.'); + } + } else { + self::$_instance = new self(); + } + + return self::$_instance; + } + + /** + * Constructor + * @return void + */ + protected function __construct() + { + $this->_channel = Zend_Wildfire_Channel_HttpHeaders::getInstance(); + $this->_channel->getProtocol(self::PROTOCOL_URI)->registerPlugin($this); + } + + /** + * Get or create singleton instance + * + * @param $skipCreate boolean True if an instance should not be created + * @return Zend_Wildfire_Plugin_FirePhp + */ + public static function getInstance($skipCreate=false) + { + if (self::$_instance===null && $skipCreate!==true) { + return self::init(); + } + return self::$_instance; + } + + /** + * Destroys the singleton instance + * + * Primarily used for testing. + * + * @return void + */ + public static function destroyInstance() + { + self::$_instance = null; + } + + /** + * Enable or disable sending of messages to user-agent. + * If disabled all headers to be sent will be removed. + * + * @param boolean $enabled Set to TRUE to enable sending of messages. + * @return boolean The previous value. + */ + public function setEnabled($enabled) + { + $previous = $this->_enabled; + $this->_enabled = $enabled; + if (!$this->_enabled) { + $this->_messages = array(); + $this->_channel->getProtocol(self::PROTOCOL_URI)->clearMessages($this); + } + return $previous; + } + + /** + * Determine if logging to user-agent is enabled. + * + * @return boolean Returns TRUE if logging is enabled. + */ + public function getEnabled() + { + return $this->_enabled; + } + + /** + * Set a single option + * + * @param string $key The name of the option + * @param mixed $value The value of the option + * @return mixed The previous value of the option + */ + public function setOption($key, $value) + { + if (!array_key_exists($key,$this->_options)) { + throw new Zend_Wildfire_Exception('Option with name "'.$key.'" does not exist!'); + } + $previous = $this->_options[$key]; + $this->_options[$key] = $value; + return $previous; + } + + /** + * Retrieve a single option + * + * @param string $key The name of the option + * @return mixed The value of the option + */ + public function getOption($key) + { + if (!array_key_exists($key,$this->_options)) { + throw new Zend_Wildfire_Exception('Option with name "'.$key.'" does not exist!'); + } + return $this->_options[$key]; + } + + /** + * Retrieve all options + * + * @return array All options + */ + public function getOptions() + { + return $this->_options; + } + + /** + * Specify a filter to be used when encoding an object + * + * Filters are used to exclude object members. + * + * @param string $Class The class name of the object + * @param array $Filter An array of members to exclude + * @return void + */ + public function setObjectFilter($class, $filter) { + $this->_objectFilters[$class] = $filter; + } + + /** + * Starts a group in the Firebug Console + * + * @param string $title The title of the group + * @return TRUE if the group instruction was added to the response headers or buffered. + */ + public static function group($title) + { + return self::send(null, $title, self::GROUP_START); + } + + /** + * Ends a group in the Firebug Console + * + * @return TRUE if the group instruction was added to the response headers or buffered. + */ + public static function groupEnd() + { + return self::send(null, null, self::GROUP_END); + } + + /** + * Logs variables to the Firebug Console + * via HTTP response headers and the FirePHP Firefox Extension. + * + * @param mixed $var The variable to log. + * @param string $label OPTIONAL Label to prepend to the log event. + * @param string $style OPTIONAL Style of the log event. + * @param array $options OPTIONAL Options to change how messages are processed and sent + * @return boolean Returns TRUE if the variable was added to the response headers or buffered. + * @throws Zend_Wildfire_Exception + */ + public static function send($var, $label=null, $style=null, $options=array()) + { + $firephp = self::getInstance(); + + if (!$firephp->getEnabled()) { + return false; + } + + if ($var instanceof Zend_Wildfire_Plugin_FirePhp_Message) { + + if ($var->getBuffered()) { + if (!in_array($var, self::$_instance->_messages)) { + self::$_instance->_messages[] = $var; + } + return true; + } + + if ($var->getDestroy()) { + return false; + } + + $style = $var->getStyle(); + $label = $var->getLabel(); + $options = $var->getOptions(); + $var = $var->getMessage(); + } + + if (!self::$_instance->_channel->isReady()) { + return false; + } + + foreach ($options as $name => $value) { + if ($value===null) { + unset($options[$name]); + } + } + $options = array_merge($firephp->getOptions(), $options); + + $trace = null; + + $skipFinalEncode = false; + + $meta = array(); + $meta['Type'] = $style; + + if ($var instanceof Exception) { + + $eTrace = $var->getTrace(); + $eTrace = array_splice($eTrace, 0, $options['maxTraceDepth']); + + $var = array('Class'=>get_class($var), + 'Message'=>$var->getMessage(), + 'File'=>$var->getFile(), + 'Line'=>$var->getLine(), + 'Type'=>'throw', + 'Trace'=>$firephp->_encodeTrace($eTrace)); + + $meta['Type'] = self::EXCEPTION; + + $skipFinalEncode = true; + + } else + if ($meta['Type']==self::TRACE) { + + if (!$label && $var) { + $label = $var; + $var = null; + } + + if (!$trace) { + $trace = $firephp->_getStackTrace(array_merge($options, + array('maxTraceDepth'=>$options['maxTraceDepth']+1))); + } + + $var = array('Class'=>$trace[0]['class'], + 'Type'=>$trace[0]['type'], + 'Function'=>$trace[0]['function'], + 'Message'=>$label, + 'File'=>isset($trace[0]['file'])?$trace[0]['file']:'', + 'Line'=>isset($trace[0]['line'])?$trace[0]['line']:'', + 'Args'=>isset($trace[0]['args'])?$firephp->_encodeObject($trace[0]['args']):'', + 'Trace'=>$firephp->_encodeTrace(array_splice($trace,1))); + + $skipFinalEncode = true; + + } else + if ($meta['Type']==self::TABLE) { + + $var = $firephp->_encodeTable($var); + + $skipFinalEncode = true; + + } else { + if ($meta['Type']===null) { + $meta['Type'] = self::LOG; + } + } + + if ($label!=null) { + $meta['Label'] = $label; + } + + switch ($meta['Type']) { + case self::LOG: + case self::INFO: + case self::WARN: + case self::ERROR: + case self::EXCEPTION: + case self::TRACE: + case self::TABLE: + case self::DUMP: + case self::GROUP_START: + case self::GROUP_END: + break; + default: + require_once 'Zend/Wildfire/Exception.php'; + throw new Zend_Wildfire_Exception('Log style "'.$meta['Type'].'" not recognized!'); + break; + } + + if ($meta['Type'] != self::DUMP && $options['includeLineNumbers']) { + if (!isset($meta['File']) || !isset($meta['Line'])) { + + if (!$trace) { + $trace = $firephp->_getStackTrace(array_merge($options, + array('maxTraceDepth'=>$options['maxTraceDepth']+1))); + } + + $meta['File'] = isset($trace[0]['file'])?$trace[0]['file']:''; + $meta['Line'] = isset($trace[0]['line'])?$trace[0]['line']:''; + + } + } else { + unset($meta['File']); + unset($meta['Line']); + } + + if ($meta['Type'] == self::DUMP) { + + return $firephp->_recordMessage(self::STRUCTURE_URI_DUMP, + array('key'=>$meta['Label'], + 'data'=>$var), + $skipFinalEncode); + + } else { + + return $firephp->_recordMessage(self::STRUCTURE_URI_FIREBUGCONSOLE, + array('data'=>$var, + 'meta'=>$meta), + $skipFinalEncode); + } + } + + /** + * Gets a stack trace + * + * @param array $options Options to change how the stack trace is returned + * @return array The stack trace + */ + protected function _getStackTrace($options) + { + $trace = debug_backtrace(); + + $trace = array_splice($trace, $options['traceOffset']); + + if (!count($trace)) { + return $trace; + } + + if (isset($options['fixZendLogOffsetIfApplicable']) && $options['fixZendLogOffsetIfApplicable']) { + if (count($trace) >=3 && + isset($trace[0]['file']) && substr($trace[0]['file'], -7, 7)=='Log.php' && + isset($trace[1]['function']) && $trace[1]['function']=='__call') { + + $trace = array_splice($trace, 2); + } + } + + return array_splice($trace, 0, $options['maxTraceDepth']); + } + + /** + * Record a message with the given data in the given structure + * + * @param string $structure The structure to be used for the data + * @param array $data The data to be recorded + * @param boolean $skipEncode TRUE if variable encoding should be skipped + * @return boolean Returns TRUE if message was recorded + * @throws Zend_Wildfire_Exception + */ + protected function _recordMessage($structure, $data, $skipEncode=false) + { + switch($structure) { + + case self::STRUCTURE_URI_DUMP: + + if (!isset($data['key'])) { + require_once 'Zend/Wildfire/Exception.php'; + throw new Zend_Wildfire_Exception('You must supply a key.'); + } + if (!array_key_exists('data',$data)) { + require_once 'Zend/Wildfire/Exception.php'; + throw new Zend_Wildfire_Exception('You must supply data.'); + } + + $value = $data['data']; + if (!$skipEncode) { + $value = $this->_encodeObject($data['data']); + } + + return $this->_channel->getProtocol(self::PROTOCOL_URI)-> + recordMessage($this, + $structure, + array($data['key']=>$value)); + + case self::STRUCTURE_URI_FIREBUGCONSOLE: + + if (!isset($data['meta']) || + !is_array($data['meta']) || + !array_key_exists('Type',$data['meta'])) { + + require_once 'Zend/Wildfire/Exception.php'; + throw new Zend_Wildfire_Exception('You must supply a "Type" in the meta information.'); + } + if (!array_key_exists('data',$data)) { + require_once 'Zend/Wildfire/Exception.php'; + throw new Zend_Wildfire_Exception('You must supply data.'); + } + + $value = $data['data']; + if (!$skipEncode) { + $value = $this->_encodeObject($data['data']); + } + + return $this->_channel->getProtocol(self::PROTOCOL_URI)-> + recordMessage($this, + $structure, + array($data['meta'], + $value)); + + default: + require_once 'Zend/Wildfire/Exception.php'; + throw new Zend_Wildfire_Exception('Structure of name "'.$structure.'" is not recognized.'); + break; + } + return false; + } + + /** + * Encodes a table by encoding each row and column with _encodeObject() + * + * @param array $Table The table to be encoded + * @return array + */ + protected function _encodeTable($table) + { + if (!$table) { + return $table; + } + for ($i=0 ; $i_encodeObject($table[$i][$j]); + } + } + } + return $table; + } + + /** + * Encodes a trace by encoding all "args" with _encodeObject() + * + * @param array $Trace The trace to be encoded + * @return array The encoded trace + */ + protected function _encodeTrace($trace) + { + if (!$trace) { + return $trace; + } + for ($i=0 ; $i_encodeObject($trace[$i]['args']); + } + } + return $trace; + } + + /** + * Encode an object by generating an array containing all object members. + * + * All private and protected members are included. Some meta info about + * the object class is added. + * + * @param mixed $object The object/array/value to be encoded + * @return array The encoded object + */ + protected function _encodeObject($object, $objectDepth = 1, $arrayDepth = 1) + { + $return = array(); + + if (is_resource($object)) { + + return '** '.(string)$object.' **'; + + } else + if (is_object($object)) { + + if ($objectDepth > $this->_options['maxObjectDepth']) { + return '** Max Object Depth ('.$this->_options['maxObjectDepth'].') **'; + } + + foreach ($this->_objectStack as $refVal) { + if ($refVal === $object) { + return '** Recursion ('.get_class($object).') **'; + } + } + array_push($this->_objectStack, $object); + + $return['__className'] = $class = get_class($object); + + $reflectionClass = new ReflectionClass($class); + $properties = array(); + foreach ( $reflectionClass->getProperties() as $property) { + $properties[$property->getName()] = $property; + } + + $members = (array)$object; + + foreach ($properties as $just_name => $property) { + + $name = $raw_name = $just_name; + + if ($property->isStatic()) { + $name = 'static:'.$name; + } + if ($property->isPublic()) { + $name = 'public:'.$name; + } else + if ($property->isPrivate()) { + $name = 'private:'.$name; + $raw_name = "\0".$class."\0".$raw_name; + } else + if ($property->isProtected()) { + $name = 'protected:'.$name; + $raw_name = "\0".'*'."\0".$raw_name; + } + + if (!(isset($this->_objectFilters[$class]) + && is_array($this->_objectFilters[$class]) + && in_array($just_name,$this->_objectFilters[$class]))) { + + if (array_key_exists($raw_name,$members) + && !$property->isStatic()) { + + $return[$name] = $this->_encodeObject($members[$raw_name], $objectDepth + 1, 1); + + } else { + if (method_exists($property,'setAccessible')) { + $property->setAccessible(true); + $return[$name] = $this->_encodeObject($property->getValue($object), $objectDepth + 1, 1); + } else + if ($property->isPublic()) { + $return[$name] = $this->_encodeObject($property->getValue($object), $objectDepth + 1, 1); + } else { + $return[$name] = '** Need PHP 5.3 to get value **'; + } + } + } else { + $return[$name] = '** Excluded by Filter **'; + } + } + + // Include all members that are not defined in the class + // but exist in the object + foreach($members as $just_name => $value) { + + $name = $raw_name = $just_name; + + if ($name{0} == "\0") { + $parts = explode("\0", $name); + $name = $parts[2]; + } + if (!isset($properties[$name])) { + $name = 'undeclared:'.$name; + + if (!(isset($this->objectFilters[$class]) + && is_array($this->objectFilters[$class]) + && in_array($just_name,$this->objectFilters[$class]))) { + + $return[$name] = $this->_encodeObject($value, $objectDepth + 1, 1); + } else { + $return[$name] = '** Excluded by Filter **'; + } + } + } + + array_pop($this->_objectStack); + + } elseif (is_array($object)) { + + if ($arrayDepth > $this->_options['maxArrayDepth']) { + return '** Max Array Depth ('.$this->_options['maxArrayDepth'].') **'; + } + + foreach ($object as $key => $val) { + + // Encoding the $GLOBALS PHP array causes an infinite loop + // if the recursion is not reset here as it contains + // a reference to itself. This is the only way I have come up + // with to stop infinite recursion in this case. + if ($key=='GLOBALS' + && is_array($val) + && array_key_exists('GLOBALS',$val)) { + + $val['GLOBALS'] = '** Recursion (GLOBALS) **'; + } + $return[$key] = $this->_encodeObject($val, 1, $arrayDepth + 1); + } + } else { + return $object; + } + return $return; + } + + /* + * Zend_Wildfire_Plugin_Interface + */ + + /** + * Get the unique indentifier for this plugin. + * + * @return string Returns the URI of the plugin. + */ + public function getUri() + { + return self::PLUGIN_URI; + } + + /** + * Flush any buffered data. + * + * @param string $protocolUri The URI of the protocol that should be flushed to + * @return void + */ + public function flushMessages($protocolUri) + { + if (!$this->_messages || $protocolUri!=self::PROTOCOL_URI) { + return; + } + + foreach( $this->_messages as $message ) { + if (!$message->getDestroy()) { + $this->send($message->getMessage(), + $message->getLabel(), + $message->getStyle(), + $message->getOptions()); + } + } + + $this->_messages = array(); + } +}