diff -r 5b37998e522e -r 162c1de6545a web/lib/Zend/Http/UserAgent.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/lib/Zend/Http/UserAgent.php Fri Mar 11 15:05:35 2011 +0100 @@ -0,0 +1,847 @@ + self::DEFAULT_IDENTIFICATION_SEQUENCE, + 'storage' => array( + 'adapter' => self::DEFAULT_PERSISTENT_STORAGE_ADAPTER, + ), + ); + + /** + * Identified device + * + * @var Zend_Http_UserAgent_Device + */ + protected $_device; + + /** + * Whether or not this instance is immutable. + * + * If true, none of the following may be modified: + * - $_server + * - $_browserType + * - User-Agent (defined in $_server) + * - HTTP Accept value (defined in $_server) + * - $_storage + * + * @var bool + */ + protected $_immutable = false; + + /** + * Plugin loaders + * @var array + */ + protected $_loaders = array(); + + /** + * Valid plugin loader types + * @var array + */ + protected $_loaderTypes = array('storage', 'device'); + + /** + * Trace of items matched to identify the browser type + * + * @var array + */ + protected $_matchLog = array(); + + /** + * Server variable + * + * @var array + */ + protected $_server; + + /** + * Persistent storage handler + * + * @var Zend_Http_UserAgent_Storage + */ + protected $_storage; + + /** + * Constructor + * + * @param null|array|Zend_Config|ArrayAccess $options + * @return void + */ + public function __construct($options = null) + { + if (null !== $options) { + $this->setOptions($options); + } + } + + /** + * Serialized representation of the object + * + * @return string + */ + public function serialize() + { + $spec = array( + 'browser_type' => $this->_browserType, + 'config' => $this->_config, + 'device_class' => get_class($this->_device), + 'device' => $this->_device->serialize(), + 'user_agent' => $this->getServerValue('http_user_agent'), + 'http_accept' => $this->getServerValue('http_accept'), + ); + return serialize($spec); + } + + /** + * Unserialize a previous representation of the object + * + * @param string $serialized + * @return void + */ + public function unserialize($serialized) + { + $spec = unserialize($serialized); + + $this->setOptions($spec); + + // Determine device class and ensure the class is loaded + $deviceClass = $spec['device_class']; + if (!class_exists($deviceClass)) { + $this->_getUserAgentDevice($this->getBrowserType()); + } + + // Get device specification and instantiate + $deviceSpec = unserialize($spec['device']); + $deviceSpec['_config'] = $this->getConfig(); + $deviceSpec['_server'] = $this->getServer(); + $this->_device = new $deviceClass($deviceSpec); + } + + /** + * Configure instance + * + * @param array|Zend_Config|ArrayAccess $options + * @return Zend_Http_UserAgent + */ + public function setOptions($options) + { + if ($options instanceof Zend_Config) { + $options = $options->toArray(); + } + + if (!is_array($options) + && !$options instanceof ArrayAccess + && !$options instanceof Traversable + ) { + require_once 'Zend/Http/UserAgent/Exception.php'; + throw new Zend_Http_UserAgent_Exception(sprintf( + 'Invalid argument; expected array, Zend_Config object, or object implementing ArrayAccess and Traversable; received %s', + (is_object($options) ? get_class($options) : gettype($options)) + )); + } + + // Set $_SERVER first + if (isset($options['server'])) { + $this->setServer($options['server']); + unset($options['server']); + } + + // Get plugin loaders sorted + if (isset($options['plugin_loader'])) { + $plConfig = $options['plugin_loader']; + if (is_array($plConfig) || $plConfig instanceof Traversable) { + foreach ($plConfig as $type => $class) { + $this->setPluginLoader($type, $class); + } + } + unset($plConfig, $options['plugin_loader']); + } + + // And then loop through the remaining options + $config = array(); + foreach ($options as $key => $value) { + switch (strtolower($key)) { + case 'browser_type': + $this->setBrowserType($value); + break; + case 'http_accept': + $this->setHttpAccept($value); + break; + case 'user_agent': + $this->setUserAgent($value); + break; + default: + // Cache remaining options for $_config + $config[$key] = $value; + break; + } + } + $this->setConfig($config); + + return $this; + } + + /** + * Comparison of the UserAgent chain and browser signatures. + * + * The comparison is case-insensitive : the browser signatures must be in lower + * case + * + * @param string $deviceClass Name of class against which a match will be attempted + * @return bool + */ + protected function _match($deviceClass) + { + // Validate device class + $r = new ReflectionClass($deviceClass); + if (!$r->implementsInterface('Zend_Http_UserAgent_Device')) { + throw new Zend_Http_UserAgent_Exception(sprintf( + 'Invalid device class provided ("%s"); must implement Zend_Http_UserAgent_Device', + $deviceClass + )); + } + + $userAgent = $this->getUserAgent(); + + // Call match method on device class + return call_user_func( + array($deviceClass, 'match'), + $userAgent, + $this->getServer() + ); + } + + /** + * Loads class for a user agent device + * + * @param string $browserType Browser type + * @return string + * @throws Zend_Loader_PluginLoader_Exception if unable to load UA device + */ + protected function _getUserAgentDevice($browserType) + { + $browserType = strtolower($browserType); + if (isset($this->_browserTypeClass[$browserType])) { + return $this->_browserTypeClass[$browserType]; + } + + if (isset($this->_config[$browserType]) + && isset($this->_config[$browserType]['device']) + ) { + $deviceConfig = $this->_config[$browserType]['device']; + if (is_array($deviceConfig) && isset($deviceConfig['classname'])) { + $device = (string) $deviceConfig['classname']; + if (!class_exists($device)) { + require_once 'Zend/Http/UserAgent/Exception.php'; + throw new Zend_Http_UserAgent_Exception(sprintf( + 'Invalid classname "%s" provided in device configuration for browser type "%s"', + $device, + $browserType + )); + } + } elseif (is_array($deviceConfig) && isset($deviceConfig['path'])) { + $loader = $this->getPluginLoader('device'); + $path = $deviceConfig['path']; + $prefix = isset($deviceConfig['prefix']) ? $deviceConfig['prefix'] : 'Zend_Http_UserAgent'; + $loader->addPrefixPath($prefix, $path); + + $device = $loader->load($browserType); + } else { + $loader = $this->getPluginLoader('device'); + $device = $loader->load($browserType); + } + } else { + $loader = $this->getPluginLoader('device'); + $device = $loader->load($browserType); + } + + $this->_browserTypeClass[$browserType] = $device; + + return $device; + } + + /** + * Returns the User Agent value + * + * If $userAgent param is null, the value of $_server['HTTP_USER_AGENT'] is + * returned. + * + * @return string + */ + public function getUserAgent() + { + if (null === ($ua = $this->getServerValue('http_user_agent'))) { + $ua = self::DEFAULT_HTTP_USER_AGENT; + $this->setUserAgent($ua); + } + + return $ua; + } + + /** + * Force or replace the UA chain in $_server variable + * + * @param string $userAgent Forced UserAgent chain + * @return Zend_Http_UserAgent + */ + public function setUserAgent($userAgent) + { + $this->setServerValue('http_user_agent', $userAgent); + return $this; + } + + /** + * Returns the HTTP Accept server param + * + * @param string $httpAccept (option) forced HTTP Accept chain + * @return string + */ + public function getHttpAccept($httpAccept = null) + { + if (null === ($accept = $this->getServerValue('http_accept'))) { + $accept = self::DEFAULT_HTTP_ACCEPT; + $this->setHttpAccept($accept); + } + return $accept; + } + + /** + * Force or replace the HTTP_ACCEPT chain in self::$_server variable + * + * @param string $httpAccept Forced HTTP Accept chain + * @return Zend_Http_UserAgent + */ + public function setHttpAccept($httpAccept) + { + $this->setServerValue('http_accept', $httpAccept); + return $this; + } + + /** + * Returns the persistent storage handler + * + * Session storage is used by default unless a different storage adapter + * has been set via the "persistent_storage_adapter" key. That key should + * contain either a fully qualified class name, or a short name that + * resolves via the plugin loader. + * + * @param string $browser Browser identifier (User Agent chain) + * @return Zend_Http_UserAgent_Storage + */ + public function getStorage($browser = null) + { + if (null === $browser) { + $browser = $this->getUserAgent(); + } + if (null === $this->_storage) { + $config = $this->_config['storage']; + $adapter = $config['adapter']; + if (!class_exists($adapter)) { + $loader = $this->getPluginLoader('storage'); + $adapter = $loader->load($adapter); + $loader = $this->getPluginLoader('storage'); + } + $options = array('browser_type' => $browser); + if (isset($config['options'])) { + $options = array_merge($options, $config['options']); + } + $this->setStorage(new $adapter($options)); + } + return $this->_storage; + } + + /** + * Sets the persistent storage handler + * + * @param Zend_Http_UserAgent_Storage $storage + * @return Zend_Http_UserAgent + */ + public function setStorage(Zend_Http_UserAgent_Storage $storage) + { + if ($this->_immutable) { + require_once 'Zend/Http/UserAgent/Exception.php'; + throw new Zend_Http_UserAgent_Exception( + 'The User-Agent device object has already been retrieved; the storage object is now immutable' + ); + } + + $this->_storage = $storage; + return $this; + } + + /** + * Clean the persistent storage + * + * @param string $browser Browser identifier (User Agent chain) + * @return void + */ + public function clearStorage($browser = null) + { + $this->getStorage($browser)->clear(); + } + + /** + * Get user configuration + * + * @return array + */ + public function getConfig() + { + return $this->_config; + } + + /** + * Config parameters is an Array or a Zend_Config object + * + * The allowed parameters are : + * - the identification sequence (can be empty) => desktop browser type is the + * default browser type returned + * $config['identification_sequence'] : ',' separated browser types + * - the persistent storage adapter + * $config['persistent_storage_adapter'] = "Session" or "NonPersistent" + * - to add or replace a browser type device + * $config[(type)]['device']['path'] + * $config[(type)]['device']['classname'] + * - to add or replace a browser type features adapter + * $config[(type)]['features']['path'] + * $config[(type)]['features']['classname'] + * + * @param mixed $config (option) Config array + * @return Zend_Http_UserAgent + */ + public function setConfig($config = array()) + { + if ($config instanceof Zend_Config) { + $config = $config->toArray(); + } + + // Verify that Config parameters are in an array. + if (!is_array($config) && !$config instanceof Traversable) { + require_once 'Zend/Http/UserAgent/Exception.php'; + throw new Zend_Http_UserAgent_Exception(sprintf( + 'Config parameters must be in an array or a Traversable object; received "%s"', + (is_object($config) ? get_class($config) : gettype($config)) + )); + } + + if ($config instanceof Traversable) { + $tmp = array(); + foreach ($config as $key => $value) { + $tmp[$key] = $value; + } + $config = $tmp; + unset($tmp); + } + + $this->_config = array_merge($this->_config, $config); + return $this; + } + + /** + * @return the $device + */ + public function getDevice() + { + if (null !== $this->_device) { + return $this->_device; + } + + $userAgent = $this->getUserAgent(); + + // search an existing identification in the session + $storage = $this->getStorage($userAgent); + + if (!$storage->isEmpty()) { + // If the user agent and features are already existing, the + // Zend_Http_UserAgent object is serialized in the session + $object = $storage->read(); + $this->unserialize($object); + } else { + // Otherwise, the identification is made and stored in the session. + // Find the browser type: + $this->setBrowserType($this->_matchUserAgent()); + $this->_createDevice(); + + // put the result in storage: + $this->getStorage($userAgent) + ->write($this->serialize()); + } + + // Mark the object as immutable + $this->_immutable = true; + + // Return the device instance + return $this->_device; + } + + /** + * Retrieve the browser type + * + * @return string $browserType + */ + public function getBrowserType() + { + return $this->_browserType; + } + + /** + * Set the browser "type" + * + * @param string $browserType + * @return Zend_Http_UserAgent + */ + public function setBrowserType($browserType) + { + if ($this->_immutable) { + require_once 'Zend/Http/UserAgent/Exception.php'; + throw new Zend_Http_UserAgent_Exception( + 'The User-Agent device object has already been retrieved; the browser type is now immutable' + ); + } + + $this->_browserType = $browserType; + return $this; + } + + /** + * Retrieve the "$_SERVER" array + * + * Basically, the $_SERVER array or an equivalent container storing the + * data that will be introspected. + * + * If the value has not been previously set, it sets itself from the + * $_SERVER superglobal. + * + * @return array + */ + public function getServer() + { + if (null === $this->_server) { + $this->setServer($_SERVER); + } + return $this->_server; + } + + /** + * Retrieve the "$_SERVER" array + * + * Basically, the $_SERVER array or an equivalent container storing the + * data that will be introspected. + * + * @param array|ArrayAccess $server + * @return void + * @throws Zend_Http_UserAgent_Exception on invalid parameter + */ + public function setServer($server) + { + if ($this->_immutable) { + require_once 'Zend/Http/UserAgent/Exception.php'; + throw new Zend_Http_UserAgent_Exception( + 'The User-Agent device object has already been retrieved; the server array is now immutable' + ); + } + + if (!is_array($server) && !$server instanceof Traversable) { + require_once 'Zend/Http/UserAgent/Exception.php'; + throw new Zend_Http_UserAgent_Exception(sprintf( + 'Expected an array or object implementing Traversable; received %s', + (is_object($server) ? get_class($server) : gettype($server)) + )); + } + + // Get an array if we don't have one + if ($server instanceof ArrayObject) { + $server = $server->getArrayCopy(); + } elseif ($server instanceof Traversable) { + $tmp = array(); + foreach ($server as $key => $value) { + $tmp[$key] = $value; + } + $server = $tmp; + unset($tmp); + } + + // Normalize key case + $server = array_change_key_case($server, CASE_LOWER); + + $this->_server = $server; + return $this; + } + + /** + * Retrieve a server value + * + * @param string $key + * @return mixed + */ + public function getServerValue($key) + { + $key = strtolower($key); + $server = $this->getServer(); + $return = null; + if (isset($server[$key])) { + $return = $server[$key]; + } + unset($server); + return $return; + } + + /** + * Set a server value + * + * @param string|int|float $key + * @param mixed $value + * @return void + */ + public function setServerValue($key, $value) + { + if ($this->_immutable) { + require_once 'Zend/Http/UserAgent/Exception.php'; + throw new Zend_Http_UserAgent_Exception( + 'The User-Agent device object has already been retrieved; the server array is now immutable' + ); + } + + $server = $this->getServer(); // ensure it's been initialized + $key = strtolower($key); + $this->_server[$key] = $value; + return $this; + } + + /** + * Set plugin loader + * + * @param string $type Type of plugin loader; one of 'storage', (?) + * @param string|Zend_Loader_PluginLoader $loader + * @return Zend_Http_UserAgent + */ + public function setPluginLoader($type, $loader) + { + $type = $this->_validateLoaderType($type); + + if (is_string($loader)) { + if (!class_exists($loader)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($loader); + } + $loader = new $loader(); + } elseif (!is_object($loader)) { + require_once 'Zend/Http/UserAgent/Exception.php'; + throw new Zend_Http_UserAgent_Exception(sprintf( + 'Expected a plugin loader class or object; received %s', + gettype($loader) + )); + } + if (!$loader instanceof Zend_Loader_PluginLoader) { + require_once 'Zend/Http/UserAgent/Exception.php'; + throw new Zend_Http_UserAgent_Exception(sprintf( + 'Expected an object extending Zend_Loader_PluginLoader; received %s', + get_class($loader) + )); + } + + $basePrefix = 'Zend_Http_UserAgent_'; + $basePath = 'Zend/Http/UserAgent/'; + switch ($type) { + case 'storage': + $prefix = $basePrefix . 'Storage'; + $path = $basePath . 'Storage'; + break; + case 'device': + $prefix = $basePrefix; + $path = $basePath; + break; + } + $loader->addPrefixPath($prefix, $path); + $this->_loaders[$type] = $loader; + return $this; + } + + /** + * Get a plugin loader + * + * @param string $type A valid plugin loader type; see {@link $_loaderTypes} + * @return Zend_Loader_PluginLoader + */ + public function getPluginLoader($type) + { + $type = $this->_validateLoaderType($type); + if (!isset($this->_loaders[$type])) { + require_once 'Zend/Loader/PluginLoader.php'; + $this->setPluginLoader($type, new Zend_Loader_PluginLoader()); + } + return $this->_loaders[$type]; + } + + /** + * Validate a plugin loader type + * + * Verifies that it is in {@link $_loaderTypes}, and returns a normalized + * version of the type. + * + * @param string $type + * @return string + * @throws Zend_Http_UserAgent_Exception on invalid type + */ + protected function _validateLoaderType($type) + { + $type = strtolower($type); + if (!in_array($type, $this->_loaderTypes)) { + $types = implode(', ', $this->_loaderTypes); + + require_once 'Zend/Http/UserAgent/Exception.php'; + throw new Zend_Http_UserAgent_Exception(sprintf( + 'Expected one of "%s" for plugin loader type; received "%s"', + $types, + (string) $type + )); + } + return $type; + } + + /** + * Run the identification sequence to match the right browser type according to the + * user agent + * + * @return Zend_Http_UserAgent_Result + */ + protected function _matchUserAgent() + { + $type = self::DEFAULT_BROWSER_TYPE; + + // If we have no identification sequence, just return the default type + if (empty($this->_config['identification_sequence'])) { + return $type; + } + + // Get sequence against which to match + $sequence = explode(',', $this->_config['identification_sequence']); + + // If a browser type is already configured, push that to the front of the list + if (null !== ($browserType = $this->getBrowserType())) { + array_unshift($sequence, $browserType); + } + + // Append the default browser type to the list if not alread in the list + if (!in_array($type, $sequence)) { + $sequence[] = $type; + } + + // Test each type until we find a match + foreach ($sequence as $browserType) { + $browserType = trim($browserType); + $className = $this->_getUserAgentDevice($browserType); + + // Attempt to match this device class + if ($this->_match($className)) { + $type = $browserType; + $this->_browserTypeClass[$type] = $className; + break; + } + } + + return $type; + } + + /** + * Creates device object instance + * + * @return void + */ + protected function _createDevice() + { + $browserType = $this->getBrowserType(); + $classname = $this->_getUserAgentDevice($browserType); + $this->_device = new $classname($this->getUserAgent(), $this->getServer(), $this->getConfig()); + } +}