ソースを参照

[1.11] Merged Zend_Http_UserAgent to trunk

- Merged Zend_Http_UserAgent to trunk, including:
  - Zend_Http_UserAgent component
  - Useragent Zend_Application resource
  - UserAgent view helper
  - TinySrc view helper
- Updated manual to include new documentation
- Updated test suites with new configuration and tests

git-svn-id: http://framework.zend.com/svn/framework/standard/trunk@23058 44c647ce-9c0f-0410-b52a-842ac1e357ba
matthew 15 年 前
コミット
532e9a9e84
58 ファイル変更9724 行追加0 行削除
  1. 30 0
      documentation/manual/en/manual.xml.in
  2. 92 0
      documentation/manual/en/module_specs/Zend_Application-AvailableResources-Useragent.xml
  3. 1 0
      documentation/manual/en/module_specs/Zend_Application-AvailableResources.xml
  4. 643 0
      documentation/manual/en/module_specs/Zend_Http_UserAgent-Device.xml
  5. 83 0
      documentation/manual/en/module_specs/Zend_Http_UserAgent-Features.xml
  6. 154 0
      documentation/manual/en/module_specs/Zend_Http_UserAgent-Features_Wurfl.xml
  7. 150 0
      documentation/manual/en/module_specs/Zend_Http_UserAgent-Storage.xml
  8. 193 0
      documentation/manual/en/module_specs/Zend_Http_UserAgent-Storage_Session.xml
  9. 1058 0
      documentation/manual/en/module_specs/Zend_Http_UserAgent.xml
  10. 439 0
      documentation/manual/en/module_specs/Zend_View-Helpers-TinySrc.xml
  11. 126 0
      documentation/manual/en/module_specs/Zend_View-Helpers-UserAgent.xml
  12. 2 0
      documentation/manual/en/module_specs/Zend_View-Helpers.xml
  13. 72 0
      library/Zend/Application/Resource/Useragent.php
  14. 847 0
      library/Zend/Http/UserAgent.php
  15. 964 0
      library/Zend/Http/UserAgent/AbstractDevice.php
  16. 128 0
      library/Zend/Http/UserAgent/Bot.php
  17. 76 0
      library/Zend/Http/UserAgent/Checker.php
  18. 67 0
      library/Zend/Http/UserAgent/Console.php
  19. 56 0
      library/Zend/Http/UserAgent/Desktop.php
  20. 184 0
      library/Zend/Http/UserAgent/Device.php
  21. 65 0
      library/Zend/Http/UserAgent/Email.php
  22. 36 0
      library/Zend/Http/UserAgent/Exception.php
  23. 39 0
      library/Zend/Http/UserAgent/Features/Adapter.php
  24. 103 0
      library/Zend/Http/UserAgent/Features/Adapter/WurflApi.php
  25. 36 0
      library/Zend/Http/UserAgent/Features/Exception.php
  26. 81 0
      library/Zend/Http/UserAgent/Feed.php
  27. 526 0
      library/Zend/Http/UserAgent/Mobile.php
  28. 70 0
      library/Zend/Http/UserAgent/Offline.php
  29. 81 0
      library/Zend/Http/UserAgent/Probe.php
  30. 79 0
      library/Zend/Http/UserAgent/Spam.php
  31. 65 0
      library/Zend/Http/UserAgent/Storage.php
  32. 37 0
      library/Zend/Http/UserAgent/Storage/Exception.php
  33. 97 0
      library/Zend/Http/UserAgent/Storage/NonPersistent.php
  34. 166 0
      library/Zend/Http/UserAgent/Storage/Session.php
  35. 132 0
      library/Zend/Http/UserAgent/Text.php
  36. 73 0
      library/Zend/Http/UserAgent/Validator.php
  37. 317 0
      library/Zend/View/Helper/TinySrc.php
  38. 76 0
      library/Zend/View/Helper/UserAgent.php
  39. 9 0
      tests/TestConfiguration.php.dist
  40. 2 0
      tests/Zend/Application/Resource/AllTests.php
  41. 130 0
      tests/Zend/Application/Resource/UseragentTest.php
  42. 2 0
      tests/Zend/Http/AllTests.php
  43. 36 0
      tests/Zend/Http/TestAsset/DesktopDevice.php
  44. 38 0
      tests/Zend/Http/TestAsset/Device/Browser/Features/Adapter.php
  45. 36 0
      tests/Zend/Http/TestAsset/Device/Desktop.php
  46. 31 0
      tests/Zend/Http/TestAsset/InvalidDevice.php
  47. 32 0
      tests/Zend/Http/TestAsset/InvalidPluginLoader.php
  48. 94 0
      tests/Zend/Http/TestAsset/PopulatedStorage.php
  49. 39 0
      tests/Zend/Http/TestAsset/TestPluginLoader.php
  50. 807 0
      tests/Zend/Http/UserAgent/AbstractDeviceTest.php
  51. 62 0
      tests/Zend/Http/UserAgent/AllTests.php
  52. 67 0
      tests/Zend/Http/UserAgent/Features/Adapter/WurflApiTest.php
  53. 554 0
      tests/Zend/Http/UserAgentTest.php
  54. 1 0
      tests/Zend/Http/_files/serialized_device.txt
  55. 0 0
      tests/Zend/Http/_files/var/cache/mobile/.placeholder
  56. 101 0
      tests/Zend/Http/index.php
  57. 2 0
      tests/Zend/View/Helper/AllTests.php
  58. 237 0
      tests/Zend/View/Helper/TinySrcTest.php

+ 30 - 0
documentation/manual/en/manual.xml.in

@@ -1105,6 +1105,36 @@
                     <xi:include href="../en/module_specs/Zend_Http_Response.xml" />
                 </xi:fallback>
             </xi:include>
+            <xi:include href="module_specs/Zend_Http_UserAgent.xml">
+                <xi:fallback>
+                    <xi:include href="../en/module_specs/Zend_Http_UserAgent.xml" />
+                </xi:fallback>
+            </xi:include>
+            <xi:include href="module_specs/Zend_Http_UserAgent-Device.xml">
+                <xi:fallback>
+                    <xi:include href="../en/module_specs/Zend_Http_UserAgent-Device.xml" />
+                </xi:fallback>
+            </xi:include>
+            <xi:include href="module_specs/Zend_Http_UserAgent-Features.xml">
+                <xi:fallback>
+                    <xi:include href="../en/module_specs/Zend_Http_UserAgent-Features.xml" />
+                </xi:fallback>
+            </xi:include>
+            <xi:include href="module_specs/Zend_Http_UserAgent-Features_Wurfl.xml">
+                <xi:fallback>
+                    <xi:include href="../en/module_specs/Zend_Http_UserAgent-Features_Wurfl.xml" />
+                </xi:fallback>
+            </xi:include>
+            <xi:include href="module_specs/Zend_Http_UserAgent-Storage.xml">
+                <xi:fallback>
+                    <xi:include href="../en/module_specs/Zend_Http_UserAgent-Storage.xml" />
+                </xi:fallback>
+            </xi:include>
+            <xi:include href="module_specs/Zend_Http_UserAgent-Storage_Session.xml">
+                <xi:fallback>
+                    <xi:include href="../en/module_specs/Zend_Http_UserAgent-Storage_Session.xml" />
+                </xi:fallback>
+            </xi:include>
         </chapter>
 
         <chapter id="zend.infocard">

+ 92 - 0
documentation/manual/en/module_specs/Zend_Application-AvailableResources-Useragent.xml

@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Reviewed: no -->
+<sect2 id="zend.application.available-resources.useragent">
+    <title>Zend_Application_Resource_Useragent</title>
+
+    <sect3 id="zend.application.available-resources.useragent.intro">
+        <title>Overview</title>
+
+        <para>
+            This resource provides the ability to configure and instantiate <link
+                linkened="zend.http.user-agent">Zend_Http_UserAgent</link> for use within your
+            application. 
+        </para>
+    </sect3>
+
+    <sect3 id="zend.application.available-resources.useragent.quick-start">
+        <title>Quick Start</title>
+
+        <para>
+            Using <classname>Zend_Http_UserAgent</classname>, including usage of the application
+            resource, is covered in the <link
+                linkend="zend.http.user-agent.quick-start">UserAgent quick start</link>. Below is a
+            quick summary of typical options you might provide.
+        </para>
+
+        <programlisting language="ini">
+resources.useragent.wurflapi.wurfl_api_version = "1.1"
+resources.useragent.wurflapi.wurfl_lib_dir = APPLICATION_PATH "/../library/Wurfl/1.1"
+resources.useragent.wurflapi.wurfl_config_file = APPLICATION_PATH "/../library/Wurfl/1.1/resources/wurfl-config.php"
+</programlisting>
+    </sect3>
+
+    <sect3 id="zend.application.available-resources.useragent.options">
+        <title>Configuration Options</title>
+
+        <para>
+            Please see the <link linkend="zend.http.user-agent.options">UserAgent options
+                section</link> for details on available options.
+        </para>
+    </sect3>
+
+    <sect3 id="zend.application.available-resources.useragent.methods">
+        <title>Available Methods</title>
+
+        <refentry id="zend.application.available-resources.useragent.methods.init">
+            <refnamediv>
+                <refname>init</refname>
+                <refpurpose>Bootstrap/initialize the resource</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>init</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>init()</title>
+
+                <para>
+                    Called by the bootstrap object to initialize the resource. Calls the
+                    <methodname>getUserAgent()</methodname> method first (and returns the instance
+                    returned by that method); then, if the "view" resource is available, retrieves
+                    it and injects the <classname>UserAgent</classname> instance into the
+                    <classname>UserAgent</classname> view helper.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.application.available-resources.useragent.methods.get-user-agent">
+            <refnamediv>
+                <refname>getUserAgent</refname>
+                <refpurpose>Retrieve a configured UserAgent instance</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>getUserAgent</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>getUserAgent()</title>
+
+                <para>
+                    Instantiates a <classname>Zend_Http_UserAgent</classname> instance, using the
+                    configuration options provided in the application configuration.
+                </para>
+            </refsect1>
+        </refentry>
+    </sect3>
+</sect2>

+ 1 - 0
documentation/manual/en/module_specs/Zend_Application-AvailableResources.xml

@@ -22,5 +22,6 @@
     <xi:include href="Zend_Application-AvailableResources-Router.xml" />
     <xi:include href="Zend_Application-AvailableResources-Session.xml" />
     <xi:include href="Zend_Application-AvailableResources-Translate.xml" />
+    <xi:include href="Zend_Application-AvailableResources-Useragent.xml" />
     <xi:include href="Zend_Application-AvailableResources-View.xml" />
 </sect1>

+ 643 - 0
documentation/manual/en/module_specs/Zend_Http_UserAgent-Device.xml

@@ -0,0 +1,643 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Reviewed: no -->
+<sect1 id="zend.http.user-agent-device">
+    <title>The UserAgent Device Interface</title>
+
+    <sect2 id="zend.http.user-agent-device.intro">
+        <title>Overview</title>
+
+        <para>
+            Once the User-Agent has been parsed and capabilities retrieved from the <link
+                linkend="zend.http.user-agent-features">Features adapter</link>, you will be
+            returned an object that represents the discovered brower device. This interface
+            describes the various capabilities you may now introspect.
+        </para>
+
+        <para>
+            Additionally, the various device classes define algorithms for matching the devices they
+            describe. By implementing this interface, you may provide additional logic around these
+            capabilities.
+        </para>
+    </sect2>
+
+    <sect2 id="zend.http.user-agent-device.quick-start">
+        <title>Quick Start</title>
+
+        <para>
+            The <interfacename>Zend_Http_UserAgent_Device</interfacename> interface defines the
+            following methods.
+        </para>
+
+        <programlisting language="php"><![CDATA[
+interface Zend_Http_UserAgent_Device extends Serializable
+{
+    public function __construct($userAgent = null, array $server = array(), array $config = array());
+    public static function match($userAgent, $server);
+    public function getAllFeatures();
+    public function getAllGroups();
+    public function getBrowser();
+    public function getBrowserVersion();
+    public function getGroup($group);
+    public function getImageFormatSupport();
+    public function getImages();
+    public function getMaxImageHeight();
+    public function getMaxImageWidth();
+    public function getPhysicalScreenHeight();
+    public function getPhysicalScreenWidth();
+    public function getPreferredMarkup();
+    public function getUserAgent();
+    public function getXhtmlSupportLevel();
+    public function hasFlashSupport();
+    public function hasPdfSupport();
+    public function hasPhoneNumber();
+    public function httpsSupport();
+}
+]]></programlisting>
+
+        <para>
+            The static function <methodname>match()</methodname> should be used to determine whether
+            the provided User-Agent and environment (represented by the <varname>$server</varname>
+            variable) match this device. If they do, the <classname>Zend_Http_UserAgent</classname>
+            class will then create an instance of the class, passing it the User-Agent,
+            <varname>$server</varname> array, and any configuration available; at this time, it is
+            expected that the Device class will consult with a features adapter, if present, and
+            populate itself with discovered capabilities.
+        </para>
+
+        <para>
+            In practice, you will likely extend
+            <clasname>Zend_Http_UserAgent_AbstractDevice</clasname>, which provides functionality
+            around discovering capabilities from the User-Agent string itself, as well as
+            discovering and querying a <link linkend="zend.http.user-agent-features">Features
+                adapter</link>. 
+        </para>
+    </sect2>
+
+    <sect2 id="zend.http.user-agent-device.options">
+        <title>Configuration Options</title>
+
+        <para>
+            Configuration options are defined on a per-device basis. The following options are
+            defined in <classname>Zend_Http_UserAgent_AbstractDevice</classname>. Like all options,
+            the "." character represents an additional level of depth to the configuration array.
+        </para>
+
+        <variablelist>
+            <title>Device Options</title>
+
+            <varlistentry>
+                <term>features.classname</term>
+
+                <listitem>
+                    <para>
+                        The name of a <link linkend="zend.http.user-agent-features">Features
+                            adapter</link> class that has either been previously loaded or which is
+                        discoverable via autoloading, or used in conjunction with the
+                        <varname>features.path</varname> option. This class must implement the
+                        <interfacename>Zend_Http_UserAgent_Features_Adapter</interfacename>
+                        interface.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>features.path</term>
+
+                <listitem>
+                    <para>
+                        If provided, the filesystem path to the features adapter class being used.
+                        The path must either be an absolute path or discoverable via the
+                        <varname>include_path</varname>.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+        </variablelist>
+    </sect2>
+
+    <sect2 id="zend.http.user-agent-device.methods">
+        <title>Available Methods</title>
+
+        <refentry id="zend.http.user-agent-device.methods.constructor">
+            <refnamediv>
+                <refname>__construct</refname>
+                <refpurpose>Instantiate a Device instance</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>__construct</methodname>
+                    <methodparam>
+                        <funcparams>$userAgent = null, array $server = array(), array $config = array()</funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>__construct()</title>
+
+                <para>
+                    Expects a User-Agent string, an array representing the HTTP environment, and an
+                    array of configuration values. The values are all optional, as they may be
+                    populated during deserialization.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent-device.methods.match">
+            <refnamediv>
+                <refname>match</refname>
+                <refpurpose>Determine if a User-Agent matches this device</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>match</methodname>
+                    <methodparam>
+                        <funcparams>$userAgent, $server</funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>match()</title>
+
+                <para>
+                    This method is static.
+                </para>
+
+                <para>
+                    Provided the <varname>$userAgent</varname> string and an array representing the
+                    HTTP headers provided in the request, determine if they match the capabilities
+                    of this device. This method should return a boolean.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent-device.methods.get-all-features">
+            <refnamediv>
+                <refname>getAllFeatures</refname>
+                <refpurpose>Get an array of all supported features</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>getAllFeatures</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>getAllFeatures()</title>
+
+                <para>
+                    Returns an array of key/value pairs representing the features supported by this
+                    device instance.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent-device.methods.get-all-groups">
+            <refnamediv>
+                <refname>getAllGroups</refname>
+                <refpurpose>Retrieve all feature groups</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>getAllGroups</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>getAllGroups()</title>
+
+                <para>
+                    Similar to <methodname>getAllFeatures()</methodname>, this retrieves all
+                    features, but grouped by type.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent-device.methods.get-browser">
+            <refnamediv>
+                <refname>getBrowser</refname>
+                <refpurpose>Get the User-Agent browser string</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>getBrowser</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>getBrowser()</title>
+
+                <para>
+                    Returns the browser string as discoverd from the User-Agent string.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent-device.methods.get-browser-version">
+            <refnamediv>
+                <refname>getBrowserVersion</refname>
+                <refpurpose>Determine the browser version</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>getBrowserVersion</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>getBrowserVersion()</title>
+
+                <para>
+                    Retrieve the browser version as discovered from the User-Agent string.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent-device.methods.get-group">
+            <refnamediv>
+                <refname>getGroup</refname>
+                <refpurpose>Get features from a given group</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>getGroup</methodname>
+                    <methodparam>
+                        <funcparams>$group</funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>getGroup()</title>
+
+                <para>
+                    Get all features from a given feature group.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent-device.methods.get-image-format-support">
+            <refnamediv>
+                <refname>getImageFormatSupport</refname>
+                <refpurpose>Retrieve list of supported images</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>getImageFormatSupport</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>getImageFormatSupport()</title>
+
+                <para>
+                    Retrieve a list of supported image types.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent-device.methods.get-images">
+            <refnamediv>
+                <refname>getImages</refname>
+                <refpurpose>Alias for <methodname>getImageFormatSupport()</methodname></refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>getImages</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>getImages()</title>
+
+                <para>
+                    Alias for <methodname>getImageFormatSupport()</methodname>.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent-device.methods.get-max-image-height">
+            <refnamediv>
+                <refname>getMaxImageHeight</refname>
+                <refpurpose>Get maximum supported image height</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>getMaxImageHeight</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>getMaxImageHeight()</title>
+
+                <para>
+                    Retrieve the maximum supported image height for the current device instance.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent-device.methods.get-max-image-width">
+            <refnamediv>
+                <refname>getMaxImageWidth</refname>
+                <refpurpose>Get maximum supported image width</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>getMaxImageWidth</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>getMaxImageWidth()</title>
+
+                <para>
+                    Retrieve the maximum supported image width for the current device instance.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent-device.methods.get-physical-screen-height">
+            <refnamediv>
+                <refname>getPhysicalScreenHeight</refname>
+                <refpurpose>Get the physical screen height for the device</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>getPhysicalScreenHeight</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>getPhysicalScreenHeight()</title>
+
+                <para>
+                    Retrieve the physical screen height for the current device instance.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent-device.methods.get-physical-screen-width">
+            <refnamediv>
+                <refname>getPhysicalScreenWidth</refname>
+                <refpurpose>Get the physical screen width for the device</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>getPhysicalScreenWidth</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>getPhysicalScreenWidth()</title>
+
+                <para>
+                    Retrieve the physical screen width for the current device instance.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent-device.methods.get-preferred-markup">
+            <refnamediv>
+                <refname>getPreferredMarkup</refname>
+                <refpurpose>Determine the preferred markup format</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>getPreferredMarkup</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>getPreferredMarkup()</title>
+
+                <para>
+                    Retrieve the preferred markup format for the current device instance.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent-device.methods.get-user-agent">
+            <refnamediv>
+                <refname>getUserAgent</refname>
+                <refpurpose>Get the User-Agent string</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>getUserAgent</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>getUserAgent()</title>
+
+                <para>
+                    Retrieve the User-Agent string for the current device instance.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent-device.methods.get-xhtml-support-level">
+            <refnamediv>
+                <refname>getXhtmlSupportLevel</refname>
+                <refpurpose>Get the version of XHTML supported</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>getXhtmlSupportLevel</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>getXhtmlSupportLevel()</title>
+
+                <para>
+                    Retrieve the supported X/HTML version for the current device instance.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent-device.methods.has-flash-support">
+            <refnamediv>
+                <refname>hasFlashSupport</refname>
+                <refpurpose>Does the device support Flash?</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>hasFlashSupport</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>hasFlashSupport()</title>
+
+                <para>
+                    Determine if the current device instance supports Flash.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent-device.methods.has-pdf-support">
+            <refnamediv>
+                <refname>hasPdfSupport</refname>
+                <refpurpose>Does the device support PDF?</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>hasPdfSupport</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>hasPdfSupport()</title>
+
+                <para>
+                    Determine if the current device instance supports PDF.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent-device.methods.has-phone-number">
+            <refnamediv>
+                <refname>hasPhoneNumber</refname>
+                <refpurpose>Does the device have a phone number?</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>hasPhoneNumber</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>hasPhoneNumber()</title>
+
+                <para>
+                    Determine if the device has an associated phone number. Note: does not retrieve
+                    the phone number. This can be useful for determining if the device is a mobile
+                    phone versus another wireless capable device.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent-device.methods.https-support">
+            <refnamediv>
+                <refname>httpsSupport</refname>
+                <refpurpose>Does the device support HTTPS?</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>httpsSupport</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>httpsSupport()</title>
+
+                <para>
+                    Determine if the current device instance supports HTTPS. 
+                </para>
+            </refsect1>
+        </refentry>
+    </sect2>
+
+    <sect2 id="zend.http.user-agent-device.examples">
+        <title>Examples</title>
+
+        <example id="zend.http.user-agent-device.examples.support">
+            <title>Determining Supported Features</title>
+
+            <para>
+                You may wish to direct the user to specific areas of your site based on features
+                supported by the device they are using. For instance, if a particular app works only
+                in Flash, you might direct a non-Flash-capable device to a page indicating the
+                application will not work on their device; or for a device not capable of HTTPS, you
+                may indicate certain actions, such as purchases, are not available.
+            </para>
+
+            <programlisting language="php"><![CDATA[
+$userAgent = new Zend_Http_UserAgent();
+$device    = $userAgent->getDevice();
+
+// Redirect to a Flash version of the app:
+if ($device->hasFlashSupport()) {
+    header('Location: /flash/app');
+    exit;
+}
+
+// Determine whether to show a "purchase" link:
+if ($device->httpsSupport()) {
+    echo '<a href="https://store-site.com/purchase">Purchase!</a>';
+} else {
+    echo 'Purchasing is unavailable on this device.';
+}
+]]></programlisting>
+        </example>
+
+        <example id="zend.http.user-agent-device.examples.images">
+            <title>Dynamically Scaling Images</title>
+
+            <para>
+                You may wish to alter the image sizes present in order to achieve a specific design
+                within mobile devices. You may use a variety of methods in the device object to make
+                this happen.
+            </para>
+
+            <programlisting language="php"><![CDATA[
+$userAgent = new Zend_Http_UserAgent();
+$device    = $userAgent->getDevice();
+
+// Get the maximum image width and height
+$maxWidth  = $device->getMaxImageWidth();
+$maxHeight = $device->getMaxImageHeight();
+
+// Create an <img> tag with appropriate sizes
+echo '<img src="/images/foo.png"';
+if ((null !== $maxWidth) && ($maxWidth < 328)) {
+    echo ' width="' . $maxWidth . '"';
+}
+if ((null !== $maxHeight) && ($maxHeight < 400)) {
+    echo ' height="' . $maxHeight . '"';
+}
+echo '/>';
+]]></programlisting>
+
+            <note>
+                <title>Markup- based scaling is not ideal</title>
+
+                <para>
+                    Markup-based scaling such as in the example above is not the best approach, as
+                    it means that the full-sized image is still delivered to the device. A better
+                    approach is to pre-scale your images to a variety of sizes representing the
+                    devices you wish to support, and then using the device capabilities to determine
+                    which image to use.
+                </para>
+
+                <para>
+                    Another approach is to use third-party services. Zend Framework provides one
+                    such capability via the <link
+                        linkend="zend.view.helpers.initial.tiny-src">TinySrc view helper</link>.
+                </para>
+            </note>
+        </example>
+    </sect2>
+</sect1>

+ 83 - 0
documentation/manual/en/module_specs/Zend_Http_UserAgent-Features.xml

@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Reviewed: no -->
+<sect1 id="zend.http.user-agent-features">
+    <title>The UserAgent Features Adapter</title>
+
+    <sect2 id="zend.http.user-agent-features.intro">
+        <title>Overview</title>
+
+        <para>
+            A variety of databases exist that define browser device features and capabilities.
+            <classname>Zend_Http_UserAgent</classname> provides the ability to utilize the database
+            of your choice via a features adapter interface.
+        </para>
+
+        <para>
+            The features adapter is passed a request array that contains the User-Agent string and
+            any other relevant HTTP headers and environment pertinent to detecting the device in
+            use. Additionally, if any adapter-specific configuration might be needed, an additional
+            configuration array will be provided. The adapter must then return an array of device
+            capabilities.
+        </para>
+    </sect2>
+
+    <sect2 id="zend.http.user-agent-features.quick-start">
+        <title>Quick Start</title>
+
+        <para>
+            The adapter interface is quite simple, defining but the single static method
+            <methodname>getFromRequest()</methodname>.
+        </para>
+
+        <programlisting language="php"><![CDATA[
+interface Zend_Http_UserAgent_Features_Adapter
+{
+    /**
+     * Retrieve the browser's features from a given request object ($_SERVER)
+     *
+     * @return array
+     */
+    public static function getFromRequest($request, array $config);
+}
+]]></programlisting>
+    </sect2>
+
+    <sect2 id="zend.http.user-agent-features.options">
+        <title>Configuration Options</title>
+
+        <variablelist>
+            <title>Features Adapter Options</title>
+
+            <para>
+                Options are defined on a per-adapter basis.
+            </para>
+    </sect2>
+
+    <sect2 id="zend.http.user-agent-features.methods">
+        <title>Available Methods</title>
+
+        <refentry id="zend.http.user-agent-features.methods.get-from-request">
+            <refnamediv>
+                <refname>getFromRequest</refname>
+                <refpurpose>Match a User-Agent to its capabilities</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>getFromRequest</methodname>
+                    <methodparam>
+                        <funcparams>array $request, array $config</funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>getFromRequest()</title>
+
+                <para>
+                    Decompose the request in order to return an array of device capabilities.
+                </para>
+            </refsect1>
+        </refentry>
+    </sect2>
+</sect1>

+ 154 - 0
documentation/manual/en/module_specs/Zend_Http_UserAgent-Features_Wurfl.xml

@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Reviewed: no -->
+<sect1 id="zend.http.user-agent-features-wurfl">
+    <title>The WURFL UserAgent Features Adapter</title>
+
+    <sect2 id="zend.http.user-agent-features-wurfl.intro">
+        <title>Overview</title>
+
+        <para>
+            <ulink url="http://wurfl.sourceforge.net/">WURFL</url> (Wireless Universal Resource
+            File) is a database of mobile device capabilities. This class provides a <link
+                linkend="zend.http.user-agent-features">features adapter</link> that utilizes the
+            <acronym>WURFL</acronym> PHP API in order to discover mobile device capabilities to
+            inject into <classname>UserAgent</classname> device instances.
+        </para>
+    </sect2>
+
+    <sect2 id="zend.http.user-agent-features-wurfl.quick-start">
+        <title>Quick Start</title>
+
+        <para>
+            Please see the <link linkend="zend.http.user-agent.quick-start">Zend_Http_UserAgent
+                quick start</link> for an example.
+        </para>
+    </sect2>
+
+    <sect2 id="zend.http.user-agent-features-wurfl.options">
+        <title>Configuration Options</title>
+
+        <variablelist>
+            <title>WURFL API Options</title>
+
+            <varlistentry>
+                <term>wurflapi.wurfl_api_version</term>
+
+                <listitem>
+                    <para>
+                        If using the <acronym>WURFL</acronym> API, use this key to specify which
+                        version you are using; typically, this will be either "1.0" or "1.1".
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>wurflapi.wurfl_lib_dir</term>
+
+                <listitem>
+                    <para>
+                        If using the <acronym>WURFL</acronym> API, use this key to specify in which
+                        directory the library exists.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>wurflapi.wurfl_config_file</term>
+
+                <listitem>
+                    <para>
+                        If using the <acronym>WURFL</acronym> API, use this key to specify the
+                        location of the configuration file you will use; typically, this will be
+                        <filename>resources/wurfl-config.php</filename> within the
+                        <varname>wurfl_lib_dir</varname>.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>wurflapi.wurfl_config_array.wurfl.main-file</term>
+
+                <listitem>
+                    <para>
+                        If using version 1.1 of the <acronym>WURFL</acronym> API, you can omit using
+                        a <varname>wurfl_config_file</varname>, and instead provide an associative
+                        array of configuration values. This particular value indicates the location
+                        of the <filename>wurfl.xml</filename> file containing the actual
+                        <acronym>WURFL</acronym> database.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>wurflapi.wurfl_config_array.wurfl.patches[]</term>
+
+                <listitem>
+                    <para>
+                        If using version 1.1 of the <acronym>WURFL</acronym> API, you can omit using
+                        a <varname>wurfl_config_file</varname>, and instead provide an associative
+                        array of configuration values. This particular value is an array of file
+                        locations containing patchfiles for the <varname>wurfl.main-file</varname>
+                        (which are used to ammend and extend the primary database file).
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>wurflapi.wurfl_config_array.persistence.provider</term>
+
+                <listitem>
+                    <para>
+                        If using version 1.1 of the <acronym>WURFL</acronym> API, you can omit using
+                        a <varname>wurfl_config_file</varname>, and instead provide an associative
+                        array of configuration values. This particular value indicates the type of
+                        persistence provider used when caching discovered capabilities. See the
+                        <acronym>WURFL</acronym> documentation for potential values; "file" is a
+                        known good value.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>wurflapi.wurfl_config_array.persistence.dir</term>
+
+                <listitem>
+                    <para>
+                        If using version 1.1 of the <acronym>WURFL</acronym> API, you can omit using
+                        a <varname>wurfl_config_file</varname>, and instead provide an associative
+                        array of configuration values. This particular value indicates the location
+                        where the persistence provider will cache discovered capabilities.
+                    </para>
+                </listitem>
+            </varlistentry>
+        </variablelist>
+        </variablelist>
+    </sect2>
+
+    <sect2 id="zend.http.user-agent-features-wurfl.methods">
+        <title>Available Methods</title>
+
+        <refentry id="zend.http.user-agent-features.methods.get-from-request">
+            <refnamediv>
+                <refname>getFromRequest</refname>
+                <refpurpose>Match a User-Agent to its capabilities</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>getFromRequest</methodname>
+                    <methodparam>
+                        <funcparams>array $request, array $config</funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>getFromRequest()</title>
+
+                <para>
+                    Decompose the request in order to return an array of device capabilities.
+                </para>
+            </refsect1>
+        </refentry>
+    </sect2>
+</sect1>

+ 150 - 0
documentation/manual/en/module_specs/Zend_Http_UserAgent-Storage.xml

@@ -0,0 +1,150 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Reviewed: no -->
+<sect1 id="zend.http.user-agent-storage">
+    <title>The UserAgent Storage Interface</title>
+
+    <sect2 id="zend.http.user-agent-storage.intro">
+        <title>Overview</title>
+
+        <para>
+            Because discovering and identifying mobile device capabilities can involve a number of
+            resources, it's often useful to identify the capabilities on the first visit, and cache
+            it for subsequent visits.
+        </para>
+
+        <para>
+            The <interfacename>Zend_Http_UserAgent_Storage</interfacename> interface provides a
+            simple definition for defining storage adapters capable of persisting definitions. By
+            default, a <classname>Session</classname> storage adapter is used, which persists the
+            data in a <classname>Zend_Session_Namespace</classname> instance.
+        </para>
+    </sect2>
+
+    <sect2 id="zend.http.user-agent-storage.quick-start">
+        <title>Quick Start</title>
+
+        <para>
+            The interface provides simply the ability to read from, write to, test for, and clear
+            data in the persistence backend.
+        </para>
+
+        <programlisting language="php"><![CDATA[
+interface Zend_Http_UserAgent_Storage
+{
+    public function isEmpty();
+    public function read();
+    public function write($contents);
+    public function clear();
+}
+]]></programlisting>
+
+        <para>
+            By default, the <classname>Zend_Http_UserAgent_Storage_Session</classname> adapter is
+            utilized. That adapter writes to a unique <classname>Zend_Session_Namespace</classname>
+            for the given user.
+        </para>
+    </sect2>
+
+    <sect2 id="zend.http.user-agent-storage.options">
+        <title>Configuration Options</title>
+
+        <para>
+            See the individual storage adapters for configuration options. Most adapters will accept
+            an array or object as an argument to the constructor, and the
+            <classname>UserAgent</classname> class allows passing an array of options.
+        </para>
+    </sect2>
+
+    <sect2 id="zend.http.user-agent-storage.methods">
+        <title>Available Methods</title>
+        <refentry id="zend.view.helpers.initial.tiny-src.methods.is-empty">
+            <refnamediv>
+                <refname>isEmpty</refname>
+                <refpurpose>Test whether or not the storage adapter has an entry</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>isEmpty</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>isEmpty()</title>
+
+                <para>
+                    Test whether ornot the storage adapter has an entry. Returns true if the
+                    storage is currently unpopulated.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.view.helpers.initial.tiny-src.methods.read">
+            <refnamediv>
+                <refname>read</refname>
+                <refpurpose>Read data from storage</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>read</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>read()</title>
+
+                <para>
+                    Reads data from storage; the data will be serialized PHP.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.view.helpers.initial.tiny-src.methods.write">
+            <refnamediv>
+                <refname>write</refname>
+                <refpurpose>Write data to storage</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>write</methodname>
+                    <methodparam>
+                        <funcparams>$contents</funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>write()</title>
+
+                <para>
+                    Write a serialized string to the storage engine.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.view.helpers.initial.tiny-src.methods.clear">
+            <refnamediv>
+                <refname>clear</refname>
+                <refpurpose>Empty the storage</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>clear</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>clear()</title>
+
+                <para>
+                    Should empty the storage; calling <methodname>isEmpty()</methodname> following a
+                    <methodname>clear()</methodname> operation should return
+                    <constant>true</constant>.
+                </para>
+            </refsect1>
+        </refentry>
+    </sect2>
+</sect1>

+ 193 - 0
documentation/manual/en/module_specs/Zend_Http_UserAgent-Storage_Session.xml

@@ -0,0 +1,193 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Reviewed: no -->
+<sect1 id="zend.http.user-agent-storage-session">
+    <title>The Session UserAgent Storage Adapter</title>
+
+    <sect2 id="zend.http.user-agent-storage-session.intro">
+        <title>Overview</title>
+
+        <para>
+            This <link linkend="zend.http.user-agent-storage">storage adapter</link> utilizes
+            <classname>Zend_Session_Namespace</classname> for persisting discovered device
+            capabilities for a given user session.
+        </para>
+    </sect2>
+
+    <sect2 id="zend.http.user-agent-storage-session.quick-start">
+        <title>Quick Start</title>
+
+        <para>
+            This particular storage adapter is used by default with
+            <classname>Zend_Http_UserAgent</classname>. As such, you will benefit from it from the
+            outset without any real configuration necessary.
+        </para>
+
+        <para>
+            You can alter the behavior slightly, however, by altering the namespace used, and the
+            key (or <emphasis>member</emphasis>) in which data is written. You may do so by
+            specifying the <varname>browser_type</varname> (mapped to namespace) and
+            <varname>member</varname> options in your configuration.
+        </para>
+
+        <programlisting language="ini">
+resources.useragent.storage.adapter = "Session"
+resources.useragent.storage.options.browser_type = "all"
+resources.useragent.storage.options.member = "data"
+</programlisting>
+
+        <para>
+            Typically, you will not pass the <varname>browser_type</varname> option, and instead let
+            this be populated by the value discovered by the <classname>UserAgent</classname> class.
+        </para>
+    </sect2>
+
+    <sect2 id="zend.http.user-agent-storage-session.options">
+        <title>Configuration Options</title>
+
+        <variablelist>
+            <title>Session Storage Options</title>
+
+            <varlistentry>
+                <term>browser_type</term>
+
+                <listitem>
+                    <para>
+                        Provide this in order to hardcode the session namespace in which you wish to
+                        store the User-Agent data. By default, the currently discovered browser type
+                        will be used, or, if not provided, the value "Zend_Http_UserAgent".
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>member</term>
+
+                <listitem>
+                    <para>
+                        This is the specific variable member within the session namespace in which
+                        the data will be stored. By default, the value "storage" will be used.
+                    </para>
+                </listitem>
+            </varlistentry>
+        </variablelist>
+    </sect2>
+
+    <sect2 id="zend.http.user-agent-storage-session.methods">
+        <title>Available Methods</title>
+
+        <refentry id="zend.http.user-agent-storage-session.methods.constructor">
+            <refnamediv>
+                <refname>__construct</refname>
+                <refpurpose>Initialize the object</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>__construct</methodname>
+                    <methodparam>
+                        <funcparams>$options = null</funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>__construct()</title>
+
+                <para>
+                    Accepts an array or object containing options. See the <link
+                        linkend="zend.http.user-agent-storage-session.options">configuration options
+                        section</link> for details on the <varname>$options</varname> variable.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent-storage-session.methods.is-empty">
+            <refnamediv>
+                <refname>isEmpty</refname>
+                <refpurpose>Is the storage populated?</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>isEmpty</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>isEmpty()</title>
+
+                <para>
+                    Used to determine whether or not the storage has been populated yet.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent-storage-session.methods.read">
+            <refnamediv>
+                <refname>read</refname>
+                <refpurpose>Get data from storage</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>read</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>read()</title>
+
+                <para>
+                    Retrieve previously stored data from the storage adapter.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent-storage-session.methods.write">
+            <refnamediv>
+                <refname>write</refname>
+                <refpurpose>Write data to storage</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>write</methodname>
+                    <methodparam>
+                        <funcparams>$contents</funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>write()</title>
+
+                <para>
+                    Write data to the storage adapter for later retrieval.
+                    <varname>$contents</varname> should be a string containing the serialized
+                    <classname>UserAgent</classname> object.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent-storage-session.methods.clear">
+            <refnamediv>
+                <refname>clear</refname>
+                <refpurpose>Empty the storage</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>clear</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>clear()</title>
+
+                <para>
+                    Clear the storage of any data.
+                </para>
+            </refsect1>
+        </refentry>
+    </sect2>
+</sect1>

+ 1058 - 0
documentation/manual/en/module_specs/Zend_Http_UserAgent.xml

@@ -0,0 +1,1058 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Reviewed: no -->
+<sect1 id="zend.http.user-agent">
+    <title>Zend_Http_UserAgent</title>
+
+    <sect2 id="zend.http.user-agent.intro">
+        <title>Overview</title>
+
+        <para>
+            With the plethora of mobile devices available on the market, it's increasingly important
+            to be able to identify the capabilities of those devices in order to present content in
+            a an appropriate way. For instance, if the device is not capable of displaying images,
+            you might want to omit them from the markup; alternately, if a device is capable of
+            Flash, you may want to provide a Flash-based user interface.
+        </para>
+
+        <para>
+            The process of identifying a device's capabilities typically first requires knowing the
+            HTTP User Agent, and then comparing that user agent against a database of user agent
+            capabilities. <classname>Zend_Http_UserAgent</classname> was created to provide these
+            capabilities for your applications. It consists of several major features:
+        </para>
+
+        <itemizedlist>
+            <listitem>
+                <para>
+                    The primary <classname>Zend_Http_UserAgent</classname> class, which detects the
+                    User Agent, and gives you a device object, as well as persists the device object
+                    for later retrieval.
+                </para>
+            </listitem>
+
+            <listitem>
+                <para>
+                    A <link linkend="zend.http.user-agent-device">Zend_Http_UserAgent_Device</link>
+                    interface, and a number of implementations that implement it. These objects
+                    utilize a features adatper to discover device capabilities, and then allow you
+                    to introspect those capabilities.
+                </para>
+            </listitem>
+
+            <listitem>
+                <para>
+                    A <link linkend="zend.http.user-agent-features">Zend_Http_UserAgent_Features_Adapter</link>
+                    interface; concrete implementations provide the ability to discover device
+                    capabilities, or features.
+                </para>
+            </listitem>
+
+            <listitem>
+                <para>
+                    A <link linkend="zend.http.user-agent-storage">Zend_Http_UserAgent_Storage</link>
+                    interface, which is used to persist discovered devices for given users, allowing
+                    for faster device capability discovery on subsequent page visits.
+                </para>
+            </listitem>
+
+            <listitem>
+                <para>
+                    A <link linkend="zend.view.helpers.initial.user-agent">view helper</link> that
+                    can be used within your view scripts and layouts to branch display logic based
+                    on device capabilities.
+                </para>
+            </listitem>
+
+            <listitem>
+                <para>
+                    A <classname>Zend_Application</classname> <link
+                        linkend="zend.application.available-resources.useragent">resource</link> for
+                    configuring and instantiating the user agent object, as well as seeding the view
+                    helper with the user agent object instance.
+                </para>
+            </listitem>
+        </itemizedlist>
+
+        <para>
+            At the time of this writing, The <classname>UserAgent</classname> component provides
+            only a single features adapter, one that consumes the <ulink
+                url="http://wurfl.sourceforge.net/">WURFL</url> (Wireless Universal Resource File)
+            PHP API. This database is considered one of the most comprehensive mobile device
+            databases, and coupled with the <classname>UserAgent</classname> component can provide
+            you with all the tools you need to write applications targetting mobile devices with
+            Zend Framework.
+        </para>
+    </sect2>
+
+    <sect2 id="zend.http.user-agent.quick-start">
+        <title>Quick Start</title>
+
+        <para>
+            First, you will need to download the following:
+        </para>
+
+        <itemizedlist>
+            <listitem>
+                <para>
+                    The <acronym>WURFL</acronym> <ulink
+                        url="http://sourceforge.net/projects/wurfl/files/WURFL%20PHP/1.1/wurfl-php-1.1.tar.gz/download">PHP
+                        API</ulink>. This includes the <filename>wurfl.xml</filename> file, which is
+                    the actual <acronym>WURFL</acronym> database.
+                </para>
+            </listitem>
+        </itemizedlist>
+
+        <para>
+            We suggest that you place this library in your "library" directory. Inflating the
+            archive will create a <filename>Wurfl/1.1/</filename> directory.
+        </para>
+
+        <programlisting language="text">
+project
+|-- application
+|-- data
+|-- library
+|   |-- Wurfl
+|   |   |-- 1.1
+|   |   |   |-- resources
+|   |   |       |-- wurfl.xml
+|   |   |       `-- wurfl-config.php
+</programlisting>
+
+        <para>
+            Next, find the file <filename>resources/wurfl-config.php</filename> in your
+            <acronym>WURFL</acronym> library. The <acronym>WURFL</acronym> PHP API is designed to
+            cache data. As such, you will want to set the <varname>['persistence']['dir']</varname>
+            key. We recommend having these point to <filename>APPLICATION_PATH .
+                "/../data/wurfl/</filename>; you will need to create this directory if it does not
+            yet.
+        </para>
+
+        <programlisting language="php"><![CDATA[
+$configuration['persistence']['dir'] = APPLICATION_PATH . '/../data/wurfl';
+]]></programlisting>
+
+        <para>
+            Now, in your <filename>application.ini</filename>, add the following entries:
+        </para>
+
+        <programlisting language="ini">
+resources.useragent.wurflapi.wurfl_api_version = "1.1"
+resources.useragent.wurflapi.wurfl_lib_dir = APPLICATION_PATH "/../library/Wurfl/1.1"
+resources.useragent.wurflapi.wurfl_config_file = APPLICATION_PATH "/../library/Wurfl/1.1/resources/wurfl-config.php"
+</programlisting>
+
+        <para>
+            At this point, everything is setup. The first request will populate the
+            <acronym>WURFL</acronym> cache by parsing the <filename>resources/wurfl.xml</filename>
+            file, and as such may take up to a minute. After that, lookups will be quite fast, and
+            each request will contain detailed information on the user agent.
+        </para>
+
+        <para>
+            You can access this information in a variety of ways. From within the MVC portion of
+            your application, you can access it via the bootstrap. Within plugins, this is done by
+            grabbing the bootstrap from the front controller.
+        </para>
+
+        <programlisting language="php"><![CDATA[
+$bootstrap = Zend_Controller_Front::getInstance()->getParam('bootstrap');
+$userAgent = $bootstrap->getResource('useragent');
+]]></programlisting>
+
+        <para>
+            From your action controller, use <methodname>getInvokeArg()</methodname> to grab the
+            bootstrap, and from there, the user agent object.
+        </para>
+
+        <programlisting language="php"><![CDATA[
+$bootstrap = $this->getInvokeArg('bootstrap');
+$userAgent = $bootstrap->getResource('useragent');
+]]></programlisting>
+
+        <para>
+            Within your view, you can grab it using the <classname>UserAgent</classname> view
+            helper.
+        </para>
+
+        <programlisting language="php"><![CDATA[
+$userAgent = $this->userAgent();
+]]></programlisting>
+
+        <para>
+            Once you have the user agent object, you can query it for different capabilities. As one
+            example, you may want to use an alternate layout script based on the user agent
+            capabilities.
+        </para>
+
+        <programlisting language="php"><![CDATA[
+$width = $userAgent->getDevice()->getPhysicalScreenWidth();
+switch (true) {
+    case ($width <= 128):
+        $layout->setLayout('layout-poor');
+        break;
+    case ($width <= 176):
+        $layout->setLayout('layout-medium');
+        break;
+    case ($width <= 240):
+        $layout->setLayout('layout-high');
+        break;
+    case ($width <= 320):
+        $layout->setLayout('layout-ultra');
+        break;
+    default:
+        // use default
+        break;
+}
+]]></programlisting>
+    </sect2>
+
+    <sect2 id="zend.http.user-agent.options">
+        <title>Configuration Options</title>
+
+        <variablelist>
+            <title>UserAgent Options</title>
+
+            <para>
+                The following options may be passed to the constructor or within your application
+                configuration. A "." indicates another layer of depth in the configuration array; as
+                an example, assigning "wurflapi.wurfl_config_array.wurfl.main-file" as part of a PHP
+                configuration would require the following definition:
+            </para>
+
+            <programlisting language="php"><![CDATA[
+$config = array(
+    'wurflapi' => array(
+        'wurfl_config_array' => array(
+            'wurfl' => array(
+                'main-file' => 'path/to/some/file',
+            ),
+        ),
+    ),
+);
+]]></programlisting>
+
+            <varlistentry>
+                <term>browser_type</term>
+
+                <listitem>
+                    <para>
+                        Used to seed the list of devices the component will search. See also
+                        <varname>identification_sequence</varname>; this value will be prepended to
+                        that list during user agent device discovery.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>http_accept</term>
+
+                <listitem>
+                    <para>
+                        The value of the <varname>Accept</varname> <acronym>HTTP</acronym> header;
+                        used by some user agents to determine capabilities. Set this to seed the
+                        value explicitly.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>identification_sequence</term>
+
+                <listitem>
+                    <para>
+                        A comma-separated list of device types to scan for matches; defaults to
+                        "mobile,desktop".
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>storage.adapter</term>
+
+                <listitem>
+                    <para>
+                        The name of a storage adapter used to persist the device capabilities,
+                        typically within a given user session. The value may either be a fully
+                        qualified class name, or a short name to resolve by the plugin loader for
+                        storage classes. By default, uses "Session" as the value, resolving to
+                        <classname>Zend_Http_UserAgent_Storage_Session</classname>.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>storage.options[]</term>
+
+                <listitem>
+                    <para>
+                        An array of options to pass to the constructor of a storage adapter. By
+                        default, the option <varname>browser_type</varname> will be present.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>plugin_loader.[type] = [class]</term>
+
+                <listitem>
+                    <para>
+                        Plugin loader configuration; allows you to specify a pre-configured
+                        <classname>Zend_Loader_PluginLoader</classname> extension class to use for
+                        one of the plugin loader types managed by <classname>UserAgent</classname>
+                        (currently "storage" and "device". 
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>server[]</term>
+
+                <listitem>
+                    <para>
+                        Typically, you will not set this; this simply allows injection of the
+                        <varname>$_SERVER</varname> superglobal (or a filtered version of it). The
+                        value should be an associative array.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>user_agent</term>
+
+                <listitem>
+                    <para>
+                        The actual <acronym>HTTP</acronym> User-Agent string you wish to try and
+                        match. Typically, this will be auto-discovered from the
+                        <varname>server</varname> array.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>[browser_type].device.classname</term>
+
+                <listitem>
+                    <para>
+                        The device class to use for a given browser type; typically,
+                        <varname>browser_type</varname> will be one of the supported browser
+                        devices, including:
+                    </para>
+
+                    <itemizedlist>
+                        <listitem>Bot</listitem>
+                        <listitem>Checker</listitem>
+                        <listitem>Console</listitem>
+                        <listitem>Desktop</listitem>
+                        <listitem>Email</listitem>
+                        <listitem>Feed</listitem>
+                        <listitem>Mobile</listitem>
+                        <listitem>Offline</listitem>
+                        <listitem>Probe</listitem>
+                        <listitem>Spam</listitem>
+                        <listitem>Text</listitem>
+                        <listitem>Validator</listitem>
+                    </itemizedlist>
+
+                    <para>
+                        The <varname>browser_type</varname> should be normalized to lowercase for
+                        configuration purposes.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>[browser_type].device.path and [browser_type].device.prefix</term>
+
+                <listitem>
+                    <para>
+                        An alternate way to specify the device class for a given browser type is to
+                        assume it is named after the device, and that all device classes are in the
+                        same path sharing the same prefix. Configure the prefix and path using these
+                        keys.
+                    </para>
+
+                    <para>
+                        As an example, the following would look for a class named
+                        "Mobile_Device_Bot" on the path "Mobile/Device/" under the application
+                        library.
+                    </para>
+
+                    <programlisting language="ini">
+resources.useragent.bot.device.path = APPLICATION_PATH '/../library/Mobile/Device"
+resources.useragent.bot.device.prefix = "Mobile_Device"
+</programlisting>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>[browser_type].features.path and [browser_type].features.classname</term>
+
+                <listitem>
+                    <para>
+                        These settings are used to load the features capabilities detection class
+                        for a given browser type. The class will be named using the
+                        <varname>classname</varname> key, and is expected to exist in the file
+                        denoted by the <varname>path</varname> key. The class should implement
+                        <classname>Zend_Http_UserAgent_Features_Adapter</classname>.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>wurflapi.wurfl_api_version</term>
+
+                <listitem>
+                    <para>
+                        If using the <acronym>WURFL</acronym> API, use this key to specify which
+                        version you are using; typically, this will be either "1.0" or "1.1".
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>wurflapi.wurfl_lib_dir</term>
+
+                <listitem>
+                    <para>
+                        If using the <acronym>WURFL</acronym> API, use this key to specify in which
+                        directory the library exists.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>wurflapi.wurfl_config_file</term>
+
+                <listitem>
+                    <para>
+                        If using the <acronym>WURFL</acronym> API, use this key to specify the
+                        location of the configuration file you will use; typically, this will be
+                        <filename>resources/wurfl-config.php</filename> within the
+                        <varname>wurfl_lib_dir</varname>.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>wurflapi.wurfl_config_array.wurfl.main-file</term>
+
+                <listitem>
+                    <para>
+                        If using version 1.1 of the <acronym>WURFL</acronym> API, you can omit using
+                        a <varname>wurfl_config_file</varname>, and instead provide an associative
+                        array of configuration values. This particular value indicates the location
+                        of the <filename>wurfl.xml</filename> file containing the actual
+                        <acronym>WURFL</acronym> database.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>wurflapi.wurfl_config_array.wurfl.patches[]</term>
+
+                <listitem>
+                    <para>
+                        If using version 1.1 of the <acronym>WURFL</acronym> API, you can omit using
+                        a <varname>wurfl_config_file</varname>, and instead provide an associative
+                        array of configuration values. This particular value is an array of file
+                        locations containing patchfiles for the <varname>wurfl.main-file</varname>
+                        (which are used to ammend and extend the primary database file).
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>wurflapi.wurfl_config_array.persistence.provider</term>
+
+                <listitem>
+                    <para>
+                        If using version 1.1 of the <acronym>WURFL</acronym> API, you can omit using
+                        a <varname>wurfl_config_file</varname>, and instead provide an associative
+                        array of configuration values. This particular value indicates the type of
+                        persistence provider used when caching discovered capabilities. See the
+                        <acronym>WURFL</acronym> documentation for potential values; "file" is a
+                        known good value.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>wurflapi.wurfl_config_array.persistence.dir</term>
+
+                <listitem>
+                    <para>
+                        If using version 1.1 of the <acronym>WURFL</acronym> API, you can omit using
+                        a <varname>wurfl_config_file</varname>, and instead provide an associative
+                        array of configuration values. This particular value indicates the location
+                        where the persistence provider will cache discovered capabilities.
+                    </para>
+                </listitem>
+            </varlistentry>
+        </variablelist>
+    </sect2>
+
+    <sect2 id="zend.http.user-agent.methods">
+        <title>Available Methods</title>
+        <refentry id="zend.http.user-agent.methods.constructor">
+            <refnamediv>
+                <refname>__construct</refname>
+                <refpurpose>Instantiate and initialize</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>__construct</methodname>
+                    <methodparam>
+                        <funcparams>$options = null</funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>__construct()</title>
+
+                <para>
+                    The constructor attempts to determine the current User-Agent based on the
+                    options provided, the current request information, and/or previously discovered
+                    information persisted in storage. Once instantiated, the detected device is
+                    immediately available.
+                </para>
+
+                <para>
+                    Please see <link linkend="zend.http.user-agent.options">configuration
+                        options</link> section for details on the <varname>$options</varname> array.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent.methods.serialize">
+            <refnamediv>
+                <refname>serialize</refname>
+                <refpurpose>Serialize the object for persistence</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>serialize</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>serialize()</title>
+
+                <para>
+                    Defined by the <interfacename>Serializable</interfacename> interface, this
+                    method performs logic necessary to determine what within the object should be
+                    serialized when the object is serialized by a storage adapter.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent.methods.unserialize">
+            <refnamediv>
+                <refname>unserialize</refname>
+                <refpurpose>Unserialize the object</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>unserialize</methodname>
+                    <methodparam>
+                        <funcparams>$serialized</funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>unserialize()</title>
+
+                <para>
+                    Defined by the <interfacename>Serializable</interfacename> interface, this
+                    method performs logic necessary to determine how to unserialize a previously
+                    serialized instance.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent.methods.set-options">
+            <refnamediv>
+                <refname>setOptions</refname>
+                <refpurpose>Initialize object state</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>setOptions</methodname>
+                    <methodparam>
+                        <funcparams>$options</funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>setOptions()</title>
+
+                <para>
+                    Initializes object state. Please see the <link
+                        linkend="zend.http.user-agent.options">configuration options</link> section
+                    for information on the <varname>$options</varname> array.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent.methods.get-user-agent">
+            <refnamediv>
+                <refname>getUserAgent</refname>
+                <refpurpose>Get the user agent string</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>getUserAgent</methodname>
+                    <methodparam>
+                        <funcparams></funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>getUserAgent()</title>
+
+                <para>
+                    Retrieve the discovered User-Agent string. Unless set explicitly, this will be
+                    autodiscovered from the server array.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent.methods.set-user-agent">
+            <refnamediv>
+                <refname>setUserAgent</refname>
+                <refpurpose>Set the user agent string</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>setUserAgent</methodname>
+                    <methodparam>
+                        <funcparams>$userAgent</funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>setUserAgent()</title>
+
+                <para>
+                    Set the User-Agent string explicitly. Once <methodname>getDevice()</methodname>
+                    has been called, this property is marked immutable, and calling this method will
+                    raise an exception.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent.methods.get-http-accept">
+            <refnamediv>
+                <refname>getHttpAccept</refname>
+                <refpurpose>Get the HTTP Accept header value</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>getHttpAccept</methodname>
+                    <methodparam>
+                        <funcparams>$httpAccept = null</funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>getHttpAccept()</title>
+
+                <para>
+                    Retrieve the HTTP Accept header value. 
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent.methods.set-http-accept">
+            <refnamediv>
+                <refname>setHttpAccept</refname>
+                <refpurpose>Set the HTTP Accept header value</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>setHttpAccept</methodname>
+                    <methodparam>
+                        <funcparams>$httpAccept</funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>setHttpAccept()</title>
+
+                <para>
+                    Explicitly set the HTTP Accept header value. Once
+                    <methodname>getDevice()</methodname> has been called, this property is marked
+                    immutable, and calling this method will raise an exception.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent.methods.get-storage">
+            <refnamediv>
+                <refname>getStorage</refname>
+                <refpurpose>Get the persistent storage object</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>getStorage</methodname>
+                    <methodparam>
+                        <funcparams>$browser = null</funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>getStorage()</title>
+
+                <para>
+                    Retrieves a persistent storage object for a given browser type.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent.methods.set-storage">
+            <refnamediv>
+                <refname>setStorage</refname>
+                <refpurpose>Set the persistent storage object</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>setStorage</methodname>
+                    <methodparam>
+                        <funcparams>Zend_Http_UserAgent_Storage $storage</funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>setStorage()</title>
+
+                <para>
+                    Use this to explicitly set the peristent storage object. Once
+                    <methodname>getDevice()</methodname> has been called, the storage is marked
+                    immutable (as in: you may not inject a new storage object), and calling this
+                    method will raise an exception.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent.methods.clear-storage">
+            <refnamediv>
+                <refname>clearStorage</refname>
+                <refpurpose>Clear the storage object</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>clearStorage</methodname>
+                    <methodparam>
+                        <funcparams>$browser = null</funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>clearStorage()</title>
+
+                <para>
+                    Clears any information in the persistent storage object.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent.methods.get-config">
+            <refnamediv>
+                <refname>getConfig</refname>
+                <refpurpose></refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>getConfig</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>getConfig()</title>
+
+                <para>
+                    Retrieve configuration parameters.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent.methods.get-device">
+            <refnamediv>
+                <refname>getDevice</refname>
+                <refpurpose>Get the device object</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>getDevice</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>getDevice()</title>
+
+                <para>
+                    Use this method to get the User-Agent Device object; this is the object that
+                    will contain the various discovered device capabilities.
+                </para>
+
+                <para>
+                    Discovery of the User-Agent device occurs in this method. Once the device has
+                    been retrieved, the server array, browser type, user agent, http accept, and
+                    storage properties are marked as immutable.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent.methods.get-browser-type">
+            <refnamediv>
+                <refname>getBrowserType</refname>
+                <refpurpose></refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>getBrowserType</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>getBrowserType()</title>
+
+                <para>
+                    Retrieve the discovered browser type; usually one of:
+                </para>
+
+                <itemizedlist>
+                    <listitem>Bot</listitem>
+                    <listitem>Checker</listitem>
+                    <listitem>Console</listitem>
+                    <listitem>Desktop</listitem>
+                    <listitem>Email</listitem>
+                    <listitem>Feed</listitem>
+                    <listitem>Mobile</listitem>
+                    <listitem>Offline</listitem>
+                    <listitem>Probe</listitem>
+                    <listitem>Spam</listitem>
+                    <listitem>Text</listitem>
+                    <listitem>Validator</listitem>
+                </itemizedlist>
+
+                <para>
+                    Unless explicitly set, the browser type is unknown until
+                    <methodname>getDevice()</methodname> has been called.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent.methods.set-browser-type">
+            <refnamediv>
+                <refname>setBrowserType</refname>
+                <refpurpose>Set a browser type to prepend to the identification sequence</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>setBrowserType</methodname>
+                    <methodparam>
+                        <funcparams>$browserType</funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>setBrowserType()</title>
+
+                <para>
+                    Explicitly set the browser type to prepend to the identification sequence. Once
+                    <methodname>getDevice()</methodname> has been called, the browser type is marked
+                    immutable, and calling this method will raise an exception.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent.methods.get-server">
+            <refnamediv>
+                <refname>getServer</refname>
+                <refpurpose>Retrieve the server array</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>getServer</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>getServer()</title>
+
+                <para>
+                    Retrieve the array of HTTP headers and environment variables used to perform
+                    device discovery. If the array has not yet been set, it is seeded with the
+                    <varname>$_SERVER</varname> superglobal.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent.methods.set-server">
+            <refnamediv>
+                <refname>setServer</refname>
+                <refpurpose>Set the server array</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>setServer</methodname>
+                    <methodparam>
+                        <funcparams>$server</funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>setServer()</title>
+
+                <para>
+                    Explicitly set the "server" array of HTTP headers and environment variables to
+                    use during device discovery. Once <methodname>getDevice()</methodname> has been
+                    called, the server array is marked immutable, and calling this method will raise
+                    an exception.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent.methods.get-server-value">
+            <refnamediv>
+                <refname>getServerValue</refname>
+                <refpurpose></refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>getServerValue</methodname>
+                    <methodparam>
+                        <funcparams>$key</funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>getServerValue()</title>
+
+                <para>
+                    Retrieve a single value from the server array by key.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent.methods.set-server-value">
+            <refnamediv>
+                <refname>setServerValue</refname>
+                <refpurpose></refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>setServerValue</methodname>
+                    <methodparam>
+                        <funcparams>$key, $value</funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>setServerValue()</title>
+
+                <para>
+                    Overwrite or define a value in the internal server array. Once
+                    <methodname>getDevice()</methodname> has been called, the server array is marked
+                    immutable, and calling this method will raise an exception.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent.methods.set-plugin-loader">
+            <refnamediv>
+                <refname>setPluginLoader</refname>
+                <refpurpose>Set a specific plugin loader instance</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>setPluginLoader</methodname>
+                    <methodparam>
+                        <funcparams>$type, $loader</funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>setPluginLoader()</title>
+
+                <para>
+                    <varname>$type</varname> may be one of "device" or "storage; the former is used
+                    when attempting to find device classes, the latter for finding storage classes.
+                    <varname>$loader</varname> may be a
+                    <classname>Zend_Loader_PluginLoader</classname> instance, or a string name
+                    containing the classname of a <classname>Zend_Loader_PluginLoader</classname>
+                    extension class.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.http.user-agent.methods.get-plugin-loader">
+            <refnamediv>
+                <refname>getPluginLoader</refname>
+                <refpurpose>Retrieve a plugin loader</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>getPluginLoader</methodname>
+                    <methodparam>
+                        <funcparams>$type</funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>getPluginLoader()</title>
+
+                <para>
+                    Retrieves either the "device" or "storage" plugin loader instance.
+                </para>
+            </refsect1>
+        </refentry>
+    </sect2>
+
+    <sect2 id="zend.http.user-agent.examples">
+        <title>Examples</title>
+
+        <para>
+            Please see the <link linkend="zend.http.user-agent.quick-start">quick start</link> for
+            examples at this time.
+        </para>
+    </sect2>
+</sect1>

+ 439 - 0
documentation/manual/en/module_specs/Zend_View-Helpers-TinySrc.xml

@@ -0,0 +1,439 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Reviewed: no -->
+<sect3 id="zend.view.helpers.initial.tiny-src">
+    <title>TinySrc Helper</title>
+
+    <sect4 id="zend.view.helpers.initial.tiny-src.intro">
+        <title>Overview</title>
+
+        <para>
+            <url ulink="http://tinysrc.net/">tinysrc.net</url> provides an API for automatic scaling
+            and image format conversion for use with mobile devices. The API is quite simple: you
+            simply create a standard HTML image tag, but append your image URL to a URL on the
+            tinysrc.net domain:
+        </para>
+
+        <programlisting language="html"><![CDATA[
+<img src="http://i.tinysrc.net/http://yourdomain.com/images/foo.jpg" />
+]]></programlisting>
+
+        <para>
+            Their service then sizes the image appropriately for the device requesting it.
+        </para>
+
+        <para>
+            You can control a number of aspects regarding image display, including:
+        </para>
+
+        <itemizedlist>
+            <listitem>
+                <para>
+                    <emphasis>Image dimensions</emphasis>. You may specify a width and optional
+                    height. These dimensions can be in absolute pixels, or use one of the adaptive
+                    mechanisms tinysrc.net offers. One is <emphasis>subtractive</emphasis>;
+                    prepending a dimension with a minus ("-") indicates that the image should fill
+                    the maximum physical dimensions, <emphasis>minus</emphasis> the value given in
+                    pixels. The other is <emphasis>percentage</emphasis> based; prepending a
+                    dimension with an "x" tells the service to size that dimension by that
+                    percentage -- e.g., "x20" indicates "20%".
+                </para>
+            </listitem>
+
+            <listitem>
+                <para>
+                    <emphasis>Image format</emphasis>. By default, tinysrc.net autodiscovers the
+                    format. Internally, it supports only <acronym>JPEG</acronym> or
+                    <acronym>PNG</acronym>, and autoconverts <acronym>GIF</acronym> to
+                    <acronym>PNG</acronym>. You can specifically request that it should convert an
+                    image to either <acronym>PNG</acronym> or <acronym>JPEG</acronym>, however.
+                </para>
+            </listitem>
+        </itemizedlist>
+
+        <para>
+            The <classname>TinySrc</classname> view helper provides functionality around the
+            tinysrc.net API, and gives you the ability to:
+        </para>
+
+        <itemizedlist>
+            <listitem>
+                <para>
+                    selectively enable or disable whether it returns just the tinysrc.net URL or a
+                    fully-populated HTML <acronym>img</acronym> tag (enabled by default);
+                </para>
+            </listitem>
+
+            <listitem>
+                <para>
+                    specify default values for image format as well as width and height;
+                </para>
+            </listitem>
+
+            <listitem>
+                <para>
+                    specify a default value for the base URL used (uses the <link
+                        linkend="zend.view.helpers.initial.baseurl">BaseUrl</link> and <link
+                        linkend="zend.view.helpers.initial.serverurl">ServerUrl</link> view helpers
+                    by default);
+                </para>
+            </listitem>
+
+            <listitem>
+                <para>
+                    override the default options on a per-image basis, via passed in options.
+                </para>
+            </listitem>
+        </itemizedlist>
+    </sect4>
+
+    <sect4 id="zend.view.helpers.initial.tiny-src.quick-start">
+        <title>Quick Start</title>
+
+        <para>
+            The most basic usage is simply to pass the path to an image, relative to your document
+            root or base URL, to create the appropriate image tag:
+        </para>
+
+        <programlisting language="php"><![CDATA[
+<?php echo $this->tinySrc('/images/foo.png'); ?>
+]]></programlisting>
+
+        <para>
+            You may specify default values for the base URL, conversion format, dimensions, and
+            whether or not to create an <acronym>img</acronym> tag by default:
+        </para>
+
+        <programlisting language="php"><![CDATA[
+<?php $this->tinySrc()
+           ->setBaseUrl('http://example.com/foo/')
+           ->setCreateTag(false)                // disable tag creation
+           ->setDefaultFormat('png')            // convert images to PNG
+           ->setDefaultDimensions('-5', 'x20'); // width should be 5 less than screen width;
+                                                // height should be 20% of total screen height
+?>
+]]></programlisting>
+
+        <para>
+            Finally, you can also pass in values as an array of options, passed as the second
+            parameter:
+        </para>
+
+        <programlisting language="php"><![CDATA[
+<?php echo $this->tinySrc('/images/foo.png', array(
+    'format' => 'jpg', // convert to JPEG
+    'width'  => 'x50', // 1/2 screen width
+); ?>
+]]></programlisting>
+    </sect4>
+
+    <sect4 id="zend.view.helpers.initial.tiny-src.options">
+        <title>Configuration Options</title>
+
+        <variablelist>
+            <title>TinySrc Helper Options</title>
+
+            <para>
+                The following options may be passed to the <varname>$options</varname> (second)
+                argument of the helper.
+            </para>
+
+            <varlistentry>
+                <term>base_url</term>
+
+                <listitem>
+                    <para>
+                        The base URL, including scheme, host, and optionally port and/or path; this
+                        value will be prepended to the image path provided in the first argument. By
+                        default, this uses the <classname>BaseUrl</classname> and
+                        <classname>ServerUrl</classname> view helpers to determine the value.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>create_tag</term>
+
+                <listitem>
+                    <para>
+                        A boolean value indicating whether or not the helper should return an HTML
+                        <acronym>img</acronym> tag, or simply the tinysrc.net URL. By default, this
+                        flag is enabled.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>format</term>
+
+                <listitem>
+                    <para>
+                        Should be one of the values "png" or "jpeg". If specified, this value will
+                        be used to indicate the image conversion format. If not specified, the
+                        default format will be used, or the format will be auto-determined based on
+                        the image itself.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>width</term>
+
+                <listitem>
+                    <para>
+                        This should be either <constant>null</constant>, or an integer (optionally
+                        prefixed by "x" or "-"). If specified, this value will be used to determine
+                        the converted image width. If null, neither a width nor a height value will
+                        be used. If not specified, the default dimensions will be used.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term>height</term>
+
+                <listitem>
+                    <para>
+                        This should be either <constant>null</constant>, or an integer (optionally
+                        prefixed by "x" or "-"). If specified, this value will be used to determine
+                        the converted image height. If null, no height value will be used. If not
+                        specified, the default height will be used.
+                    </para>
+                </listitem>
+            </varlistentry>
+        </variablelist>
+
+        <para>
+            Any other options provided will be used as attributes to the HTML <acronym>img</acronym>
+            tag (if created).
+        </para>
+    </sect4>
+
+    <sect4 id="zend.view.helpers.initial.tiny-src.methods">
+        <title>Available Methods</title>
+
+        <refentry id="zend.view.helpers.initial.tiny-src.methods.tiny-src">
+            <refnamediv>
+                <refname>tinySrc</refname>
+                <refpurpose>Return the helper instance, the tinysrc.net URL, or an image tag</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>tinySrc</methodname>
+                    <methodparam>
+                        <funcparams>$image = null, array $options = array()</funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>tinySrc()</title>
+
+                <para>
+                    Called with no arguments, returns the helper instance. This is useful for
+                    configuring the helper.
+                </para>
+
+                <para>
+                    If the <varname>$image</varname> argument is provided, it will either create and
+                    return the tinysrc.net URL for the image, or an image tag containing that URL as
+                    the source, depending on the status of the "create tag" flag (either the default
+                    value, or the value passed via <varname>$options</varname>).
+                </para>
+
+                <para>
+                    See the <link linkend="zend.view.helpers.initial.tiny-src.options">configuration
+                        section</link> for details on the <varname>$options</varname> array.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.view.helpers.initial.tiny-src.methods.set-base-url">
+            <refnamediv>
+                <refname>setBaseUrl</refname>
+                <refpurpose>Set the base URL to prepend to images</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>setBaseUrl</methodname>
+                    <methodparam>
+                        <funcparams>$url</funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>setBaseUrl()</title>
+
+                <para>
+                    Use this method to manually specify the base URL to prepend to the
+                    <varname>$image</varname> argument of the <methodname>tinySrc()</methodname>
+                    method.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.view.helpers.initial.tiny-src.methods.get-base-url">
+            <refnamediv>
+                <refname>getBaseUrl</refname>
+                <refpurpose></refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>getBaseUrl</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>getBaseUrl()</title>
+
+                <para>
+                    Retrieve the base URL for prepending to image URLs. By default, autodiscovers
+                    this from the <classname>BaseUrl</classname> and
+                    <classname>ServerUrl</classname> view helpers.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.view.helpers.initial.tiny-src.methods.set-default-format">
+            <refnamediv>
+                <refname>setDefaultFormat</refname>
+                <refpurpose></refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>setDefaultFormat</methodname>
+                    <methodparam>
+                        <funcparams>$format = null</funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>setDefaultFormat()</title>
+
+                <para>
+                    Specifiy the default image conversion format. If none provided, the value is
+                    cleared. Otherwise, expects either "png" or "jpeg".
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.view.helpers.initial.tiny-src.methods.set-default-dimensions">
+            <refnamediv>
+                <refname>setDefaultDimensions</refname>
+                <refpurpose></refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>setDefaultDimensions</methodname>
+                    <methodparam>
+                        <funcparams>$width = null, $height = null</funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>setDefaultDimensions()</title>
+
+                <para>
+                    Set the default dimensions for image conversion. If no <varname>$width</varname>
+                    is specified, an empty value is provided for all dimensions (setting the height
+                    requires a width as well). Passing no value for the height will set only a
+                    width. Dimensions should be specified as either pixel dimensions, or:
+                </para>
+
+                <itemizedlist>
+                    <listitem>
+                        <para>
+                            A pixel value, preceded by a "-" sign. This will indicate the width
+                            should take the entire screen size, minus the number of pixels
+                            specified.
+                        </para>
+                    </listitem>
+
+                    <listitem>
+                        <para>
+                            A percentage of the total screen dimensions, expressed as "x" followed
+                            by the percentage: "x20" is equivalent to 20%. 
+                        </para>
+                    </listitem>
+                </itemizedlist>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.view.helpers.initial.tiny-src.methods.set-create-tag">
+            <refnamediv>
+                <refname>setCreateTag</refname>
+                <refpurpose>Indicate whether or not to create an image tag by default</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>setCreateTag</methodname>
+                    <methodparam>
+                        <funcparams>$flag</funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>setCreateTag()</title>
+
+                <para>
+                    Indicate whether the <methodname>tinySrc()</methodname> method should create an
+                    HTML image tag. If boolean <constant>false</constant>, only a tinysrc.net URL
+                    will be returned.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.view.helpers.initial.tiny-src.methods.create-tag">
+            <refnamediv>
+                <refname>createTag</refname>
+                <refpurpose>Determine status of "create tag" flag</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>createTag</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>createTag()</title>
+
+                <para>
+                    Returns the status of the "create tag" flag.
+                </para>
+            </refsect1>
+        </refentry>
+    </sect4>
+
+    <sect4 id="zend.view.helpers.initial.tiny-src.examples">
+        <title>Examples</title>
+
+        <example id="zend.view.helpers.initial.tiny-src.examples.url">
+            <title>Returning only a tinysrc.net URL</title>
+
+            <para>
+                You may want to return only a tinysrc.net URL. To do this, you have two options:
+                make this the default behavior, or specify in your <varname>$options</varname> not
+                to create a tag.
+            </para>
+
+            <programlisting language="php"><![CDATA[
+// Specifying default behavior:
+$this->tinySrc()->setCreateTag(false);
+echo $this->tinySrc('image.jpg');
+
+// Per-image:
+echo $this->tinySrc('image.jpg', array('create_tag' => false));
+]]></programlisting>
+        </example>
+    </sect4>
+</sect1>

+ 126 - 0
documentation/manual/en/module_specs/Zend_View-Helpers-UserAgent.xml

@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Reviewed: no -->
+<sect3 id="zend.view.helpers.initial.user-agent">
+    <title>UserAgent View Helper</title>
+
+    <sect4 id="zend.view.helpers.initial.user-agent.intro">
+        <title>Overview</title>
+
+        <para>
+            This view helper provides the ability to inject and later retrieve a
+            <classname>Zend_Http_UserAgent</classname> instance for use in branching display logic
+            based on device capabilities.
+        </para>
+    </sect4>
+
+    <sect4 id="zend.view.helpers.initial.user-agent.quick-start">
+        <title>Quick Start</title>
+
+        <para>
+            In most cases, you can simply retrieve the User-Agent and related device by calling the
+            helper. If the <classname>UserAgent</classname> was configured in the <link
+                linkend="zend.application.application.available-resources.useragent">
+            bootstrap</link>, that instance will be injected already in the helper; otherwise, it
+            will instantiate one for you.
+        </para>
+
+        <programlisting language="php"><![CDATA[
+<?php if ($this->userAgent()->getDevice()->hasFlash()): ?>
+    <object ...></object>
+<?php endif ?>
+]]></programlisting>
+
+        <para>
+            If you initialize the <classname>UserAgent</classname> object manually, you can still
+            inject it into the helper, in one of two ways.
+        </para>
+
+        <programlisting language="php"><![CDATA[
+// Pull the helper from the view, and inject:
+$helper = $view->getHelper('userAgent');
+$helper->setUserAgent($userAgent);
+
+// Pass the UserAgent to the helper:
+$view->userAgent($userAgent);
+]]></programlisting>
+    </sect4>
+
+    <sect4 id="zend.view.helpers.initial.user-agent.methods">
+        <title>Available Methods</title>
+
+        <refentry id="zend.view.helpers.initial.user-agent.methods.user-agent">
+            <refnamediv>
+                <refname>userAgent</refname>
+                <refpurpose>Set and/or retrieve the UserAgent instance</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>userAgent</methodname>
+                    <methodparam>
+                        <funcparams>Zend_Http_UserAgent $userAgent = null</funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>userAgent()</title>
+
+                <para>
+                    Use this method to set or retrieve the <classname>UserAgent</classname>
+                    instance. Passing an instance will set it; passing no arguments will retrieve
+                    it. If no previous instance has been registered, one will be lazy-loaded using
+                    defaults.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.view.helpers.initial.user-agent.methods.set-user-agent">
+            <refnamediv>
+                <refname>setUserAgent</refname>
+                <refpurpose>Set the UserAgent instance</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>setUserAgent</methodname>
+                    <methodparam>
+                        <funcparams>Zend_Http_UserAgent $userAgent</funcparams>
+                    </methodparam>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>setUserAgent()</title>
+
+                <para>
+                    If you have an instance of the helper -- for instance, by calling the view
+                    object's <methodname>getHelper()</methodname> method -- you may use this method
+                    to set the <classname>UserAgent</classname> instance.
+                </para>
+            </refsect1>
+        </refentry>
+
+        <refentry id="zend.view.helpers.initial.user-agent.methods.get-user-agent">
+            <refnamediv>
+                <refname>getUserAgent</refname>
+                <refpurpose>Retrieve the UserAgent instance</refpurpose>
+            </refnamediv>
+
+            <refsynopsisdiv>
+                <methodsynopsis>
+                    <methodname>getUserAgent</methodname>
+                </methodsynopsis>
+            </refsynopsisdiv>
+
+            <refsect1>
+                <title>getUserAgent()</title>
+
+                <para>
+                    Retrieves the <classname>UserAgent</classname> instance; if none is registered,
+                    it will lazy-load one using default values.
+                </para>
+            </refsect1>
+        </refentry>
+    </sect4>
+</sect3>

+ 2 - 0
documentation/manual/en/module_specs/Zend_View-Helpers.xml

@@ -405,7 +405,9 @@ echo $this->formCheckbox('foo',
         <xi:include href="Zend_View-Helpers-InlineScript.xml" />
         <xi:include href="Zend_View-Helpers-Json.xml" />
         <xi:include href="Zend_View-Helpers-Navigation.xml" />
+        <xi:include href="Zend_View-Helpers-TinySrc.xml" />
         <xi:include href="Zend_View-Helpers-Translate.xml" />
+        <xi:include href="Zend_View-Helpers-UserAgent.xml" />
     </sect2>
 
     <sect2 id="zend.view.helpers.paths">

+ 72 - 0
library/Zend/Application/Resource/Useragent.php

@@ -0,0 +1,72 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Application
+ * @subpackage Resource
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @category   Zend
+ * @package    Zend_Application
+ * @subpackage Resource
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Application_Resource_UserAgent extends Zend_Application_Resource_ResourceAbstract 
+{
+    /**
+     * @var Zend_Http_UserAgent
+     */
+	protected $_userAgent;
+	
+    /**
+     * Intialize resource
+     * 
+     * @return Zend_Http_UserAgent
+     */
+    public function init() 
+    {
+		$userAgent = $this->getUserAgent();
+
+        // Optionally seed the UserAgent view helper
+        $bootstrap = $this->getBootstrap();
+        if ($bootstrap->hasResource('view') || $bootstrap->hasPluginResource('view')) {
+            $bootstrap->bootstrap('view');
+            $view = $bootstrap->getResource('view');
+            if (null !== $view) {
+                $view->userAgent($userAgent);
+            }
+        }
+
+        return $userAgent;
+	}
+	
+    /**
+     * Get UserAgent instance
+     * 
+     * @return Zend_Http_UserAgent
+     */
+    public function getUserAgent() 
+    {
+		if (null === $this->_userAgent) {
+			$options = $this->getOptions();
+			$this->_userAgent = new Zend_Http_UserAgent($options);
+		}
+		
+		return $this->_userAgent;
+	}
+}

+ 847 - 0
library/Zend/Http/UserAgent.php

@@ -0,0 +1,847 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http_UserAgent
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * Lists of User Agent chains for testing :
+ *
+ * - http://www.useragentstring.com/layout/useragentstring.php
+ * - http://user-agent-string.info/list-of-ua
+ * - http://www.user-agents.org/allagents.xml
+ * - http://en.wikipedia.org/wiki/List_of_user_agents_for_mobile_phones
+ * - http://www.mobilemultimedia.be/fr/
+ *
+ * @category   Zend
+ * @package    Zend_Http_UserAgent
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Http_UserAgent implements Serializable
+{
+    /**
+     * 'desktop' by default if the sequence return false for each item or is empty
+     */
+    const DEFAULT_IDENTIFICATION_SEQUENCE = 'mobile,desktop';
+
+    /**
+     * Default persitent storage adapter : Session or NonPersitent
+     */
+    const DEFAULT_PERSISTENT_STORAGE_ADAPTER = 'Session';
+
+    /**
+     * 'desktop' by default if the sequence return false for each item
+     */
+    const DEFAULT_BROWSER_TYPE = 'desktop';
+
+    /**
+     * Default User Agent chain to prevent empty value 
+     */
+    const DEFAULT_HTTP_USER_AGENT = 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)';
+
+    /**
+     * Default Http Accept param to prevent empty value 
+     */
+    const DEFAULT_HTTP_ACCEPT = "application/xhtml+xml";
+
+    /**
+     * Default markup language 
+     */
+    const DEFAULT_MARKUP_LANGUAGE = "xhtml";
+
+    /**
+     * Browser type
+     *
+     * @var string
+     */
+    protected $_browserType;
+
+    /**
+     * Browser type class
+     *
+     * Map of browser types to classes.
+     *
+     * @var array
+     */
+    protected $_browserTypeClass = array();
+
+    /**
+     * Array to store config
+     *
+     * Default values are provided to ensure specific keys are present at 
+     * instantiation.
+     * 
+     * @var array
+     */
+    protected $_config = array(
+        'identification_sequence' => 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());
+    }
+}

+ 964 - 0
library/Zend/Http/UserAgent/AbstractDevice.php

@@ -0,0 +1,964 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once 'Zend/Http/UserAgent/Device.php';
+
+/**
+ * Abstract Class to define a browser device.
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+abstract class Zend_Http_UserAgent_AbstractDevice
+    implements Zend_Http_UserAgent_Device
+{
+    /**
+     * Browser signature
+     *
+     * @var string
+     */
+    protected $_browser = '';
+
+    /**
+     * Browser version
+     *
+     * @var string
+     */
+    protected $_browserVersion = '';
+
+    /**
+     * Configuration
+     * 
+     * @var array
+     */
+    protected $_config;
+
+    /**
+     * User Agent chain
+     *
+     * @var string
+     */
+    protected $_userAgent;
+
+    /**
+     * Server variable
+     * 
+     * @var array
+     */
+    protected $_server;
+
+    /**
+     * Image types
+     * 
+     * @var array
+     */
+    protected $_images = array(
+        'jpeg', 
+        'gif', 
+        'png', 
+        'pjpeg', 
+        'x-png', 
+        'bmp',
+    );
+
+    /**
+     * Browser/Device features
+     *
+     * @var array
+     */
+    protected $_aFeatures = array();
+
+    /**
+     * Constructor
+     *
+     * @param  null|string|array $userAgent If array, restores from serialized version
+     * @param  array $server 
+     * @param  array $config 
+     * @return void
+     */
+    public function __construct($userAgent = null, array $server = array(), array $config = array())
+    {
+        if (is_array($userAgent)) {
+            // Restoring from serialized array
+            $this->_restoreFromArray($userAgent);
+        } else {
+            // Constructing new object
+            $this->setUserAgent($userAgent);
+            $this->_server    = $server;
+            $this->_config    = $config;
+            $this->_getDefaultFeatures();
+            $this->_defineFeatures();
+        }
+    }
+
+    /**
+     * Serialize object
+     * 
+     * @return string
+     */
+    public function serialize()
+    {
+        $spec = array(
+            '_aFeatures'      => $this->_aFeatures,
+            '_browser'        => $this->_browser,
+            '_browserVersion' => $this->_browserVersion,
+            '_userAgent'      => $this->_userAgent,
+            '_images'         => $this->_images,
+        );
+        return serialize($spec);
+    }
+
+    /**
+     * Unserialize
+     * 
+     * @param  string $serialized 
+     * @return void
+     */
+    public function unserialize($serialized)
+    {
+        $spec = unserialize($serialized);
+        $this->_restoreFromArray($spec);
+    }
+
+    /**
+     * Restore object state from array
+     * 
+     * @param  array $spec 
+     * @return void
+     */
+    protected function _restoreFromArray(array $spec)
+    {
+        foreach ($spec as $key => $value) {
+            if (property_exists($this, $key)) {
+                $this->{$key} = $value;
+            }
+        }
+    }
+
+    /**
+     * Look for features
+     *
+     * @return array|null
+     */
+    protected function _defineFeatures()
+    {
+        $features = $this->_loadFeaturesAdapter();
+
+        if (is_array($features)) {
+            $this->_aFeatures = array_merge($this->_aFeatures, $features);
+        }
+        
+        return $this->_aFeatures;
+    }
+
+    /**
+     * Gets the browser type identifier
+     *
+     * @return string
+     */
+    abstract public function getType();
+
+    /**
+     * Check a feature for the current browser/device.
+     *
+     * @param  string $feature The feature to check.
+     * @return bool
+     */
+    public function hasFeature($feature)
+    {
+        return (!empty($this->_aFeatures[$feature]));
+    }
+
+    /**
+     * Gets the value of the current browser/device feature
+     *
+     * @param  string $feature Feature to search
+     * @return string|null
+     */
+    public function getFeature($feature)
+    {
+        if ($this->hasFeature($feature)) {
+            return $this->_aFeatures[$feature];
+        }
+    }
+
+    /**
+     * Set a feature for the current browser/device.
+     *
+     * @param  string $feature The feature to set.
+     * @param  string $value (option) feature value.
+     * @param  string $group (option) Group to associate with the feature
+     * @return Zend_Http_UserAgent_AbstractDevice
+     */
+    public function setFeature($feature, $value = false, $group = '')
+    {
+        $this->_aFeatures[$feature] = $value;
+        if (!empty($group)) {
+            $this->setGroup($group, $feature);
+        }
+        return $this;
+    }
+
+    /**
+     * Affects a feature to a group
+     *
+     * @param  string $group Group name
+     * @param  string $feature Feature name
+     * @return Zend_Http_UserAgent_AbstractDevice
+     */
+    public function setGroup($group, $feature)
+    {
+        if (!isset($this->_aGroup[$group])) {
+            $this->_aGroup[$group] = array();
+        }
+        if (!in_array($feature, $this->_aGroup[$group])) {
+            $this->_aGroup[$group][] = $feature;
+        }
+        return $this;
+    }
+
+    /**
+     * Gets an array of features associated to a group
+     *
+     * @param  string $group Group param
+     * @return array
+     */
+    public function getGroup($group)
+    {
+        return $this->_aGroup[$group];
+    }
+
+    /**
+     * Gets all the browser/device features
+     *
+     * @return array
+     */
+    public function getAllFeatures()
+    {
+        return $this->_aFeatures;
+    }
+
+    /**
+     * Gets all the browser/device features' groups
+     *
+     * @return array
+     */
+    public function getAllGroups()
+    {
+        return $this->_aGroup;
+    }
+
+    /**
+     * Sets all the standard features extracted from the User Agent chain and $this->_server
+     * vars
+     *
+     * @return void
+     */
+    protected function _getDefaultFeatures()
+    {
+        $server = array();
+        
+        // gets info from user agent chain 
+        $uaExtract = $this->extractFromUserAgent($this->getUserAgent());
+        
+        if (is_array($uaExtract)) {
+            foreach ($uaExtract as $key => $info) {
+                $this->setFeature($key, $info, 'product_info');
+            }
+        }
+        
+        if (isset($uaExtract['browser_name'])) {
+            $this->_browser = $uaExtract['browser_name'];
+        }
+        if (isset($uaExtract['browser_version'])) {
+            $this->_browserVersion = $uaExtract['browser_version'];
+        }
+        if (isset($uaExtract['device_os'])) {
+            $this->device_os = $uaExtract['device_os_name'];
+        }
+        
+        /* browser & device info */
+        $this->setFeature('is_wireless_device', false, 'product_info');
+        $this->setFeature('is_mobile', false, 'product_info');
+        $this->setFeature('is_desktop', false, 'product_info');
+        $this->setFeature('is_tablet', false, 'product_info');
+        $this->setFeature('is_bot', false, 'product_info');
+        $this->setFeature('is_email', false, 'product_info');
+        $this->setFeature('is_text', false, 'product_info');
+        $this->setFeature('device_claims_web_support', false, 'product_info');
+        
+        $this->setFeature('is_' . strtolower($this->getType()), true, 'product_info');
+        
+        /* sets the browser name */
+        if (isset($this->list) && empty($this->_browser)) {
+            $lowerUserAgent = strtolower($this->getUserAgent());
+            foreach ($this->list as $browser_signature) {
+                if (strpos($lowerUserAgent, $browser_signature) !== false) {
+                    $this->_browser = strtolower($browser_signature);
+                    $this->setFeature('browser_name', $this->_browser, 'product_info');
+                }
+            }
+        }
+        
+        /* sets the client IP */
+        if (isset($this->_server['remote_addr'])) {
+            $this->setFeature('client_ip', $this->_server['remote_addr'], 'product_info');
+        } elseif (isset($this->_server['http_x_forwarded_for'])) {
+            $this->setFeature('client_ip', $this->_server['http_x_forwarded_for'], 'product_info');
+        } elseif (isset($this->_server['http_client_ip'])) {
+            $this->setFeature('client_ip', $this->_server['http_client_ip'], 'product_info');
+        }
+        
+        /* sets the server infos */
+        if (isset($this->_server['server_software'])) {
+            if (strpos($this->_server['server_software'], 'Apache') !== false || strpos($this->_server['server_software'], 'LiteSpeed') !== false) {
+                $server['version'] = 1;
+                if (strpos($this->_server['server_software'], 'Apache/2') !== false) {
+                    $server['version'] = 2;
+                }
+                $server['server'] = 'apache';
+            }
+            
+            if (strpos($this->_server['server_software'], 'Microsoft-IIS') !== false) {
+                $server['server'] = 'iis';
+            }
+            
+            if (strpos($this->_server['server_software'], 'Unix') !== false) {
+                $server['os'] = 'unix';
+                if (isset($_ENV['MACHTYPE'])) {
+                    if (strpos($_ENV['MACHTYPE'], 'linux') !== false) {
+                        $server['os'] = 'linux';
+                    }
+                }
+            } elseif (strpos($this->_server['server_software'], 'Win') !== false) {
+                $server['os'] = 'windows';
+            }
+            
+            if (preg_match('/Apache\/([0-9\.]*)/', $this->_server['server_software'], $arr)) {
+                if ($arr[1]) {
+                    $server['version'] = $arr[1];
+                    $server['server']  = 'apache';
+                }
+            }
+        }
+        
+        $this->setFeature('php_version', phpversion(), 'server_info');
+        if (isset($server['server'])) {
+            $this->setFeature('server_os', $server['server'], 'server_info');
+        }
+        if (isset($server['version'])) {
+            $this->setFeature('server_os_version', $server['version'], 'server_info');
+        }
+        if (isset($this->_server['http_accept'])) {
+            $this->setFeature('server_http_accept', $this->_server['http_accept'], 'server_info');
+        }
+        if (isset($this->_server['http_accept_language'])) {
+            $this->setFeature('server_http_accept_language', $this->_server['http_accept_language'], 'server_info');
+        }
+        if (isset($this->_server['server_addr'])) {
+            $this->setFeature('server_ip', $this->_server['server_addr'], 'server_info');
+        }
+        if (isset($this->_server['server_name'])) {
+            $this->setFeature('server_name', $this->_server['server_name'], 'server_info');
+        }
+    }
+
+    /**
+     * Extract and sets informations from the User Agent chain
+     *
+     * @param  string $userAgent User Agent chain
+     * @return array
+     */
+    public static function extractFromUserAgent($userAgent)
+    {
+        $userAgent = trim($userAgent);
+        
+        /**
+         * @see http://www.texsoft.it/index.php?c=software&m=sw.php.useragent&l=it
+         */
+        $pattern =  "(([^/\s]*)(/(\S*))?)(\s*\[[a-zA-Z][a-zA-Z]\])?\s*(\\((([^()]|(\\([^()]*\\)))*)\\))?\s*";
+        preg_match("#^$pattern#", $userAgent, $match);
+
+        $comment = array();
+        if (isset($match[7])) {
+            $comment = explode(';', $match[7]);
+        }
+        
+        // second part if exists
+        $end = substr($userAgent, strlen($match[0]));
+        if (!empty($end)) {
+            $result['others']['full'] = $end;
+        }
+        
+        $match2 = array();
+        if (isset($result['others'])) {
+            preg_match_all('/(([^\/\s]*)(\/)?([^\/\(\)\s]*)?)(\s\((([^\)]*)*)\))?/i', $result['others']['full'], $match2);
+        }
+        $result['user_agent']   = trim($match[1]);
+        $result['product_name'] = isset($match[2]) ? trim($match[2]) : '';
+        $result['browser_name'] = $result['product_name'];
+        if (isset($match[4]) && trim($match[4])) {
+            $result['product_version'] = trim($match[4]);
+            $result['browser_version'] = trim($match[4]);
+        }
+        if (count($comment) && !empty($comment[0])) {
+            $result['comment']['full']     = trim($match[7]);
+            $result['comment']['detail']   = $comment;
+            $result['compatibility_flag']  = trim($comment[0]);
+            if (isset($comment[1])) {
+                $result['browser_token']   = trim($comment[1]);
+            }
+            if (isset($comment[2])) {
+                $result['device_os_token'] = trim($comment[2]);
+            }
+        }
+        if (empty($result['device_os_token']) && !empty($result['compatibility_flag'])) {
+            // some browsers do not have a platform token
+            $result['device_os_token'] = $result['compatibility_flag'];
+        }
+        if ($match2) {
+            $i = 0;
+            $max = count($match2[0]);
+            for ($i = 0; $i < $max; $i ++) {
+                if (!empty($match2[0][$i])) {
+                    $result['others']['detail'][] = array(
+                        $match2[0][$i], 
+                        $match2[2][$i], 
+                        $match2[4][$i],
+                    );
+                }
+            }
+        }
+        
+        /** Security level */
+        $security = array(
+            'N' => 'no security', 
+            'U' => 'strong security', 
+            'I' => 'weak security',
+        );
+        if (!empty($result['browser_token'])) {
+            if (isset($security[$result['browser_token']])) {
+                $result['security_level'] = $security[$result['browser_token']];
+                unset($result['browser_token']);
+            }
+        }
+        
+        $product = strtolower($result['browser_name']);
+        
+        // Mozilla : true && false
+        $compatibleOrIe = false;
+        if (isset($result['compatibility_flag']) && isset($result['comment'])) {
+            $compatibleOrIe = ($result['compatibility_flag'] == 'compatible' || strpos($result['comment']['full'], "MSIE") !== false);
+        }
+        if ($product == 'mozilla' && $compatibleOrIe) {
+            if (!empty($result['browser_token'])) {
+                // Classic Mozilla chain
+                preg_match_all('/([^\/\s].*)(\/|\s)(.*)/i', $result['browser_token'], $real);
+            } else {
+                // MSIE specific chain with 'Windows' compatibility flag
+                foreach ($result['comment']['detail'] as $v) {
+                    if (strpos($v, 'MSIE') !== false) {
+                        $real[0][1]               = trim($v);
+                        $result['browser_engine'] = "MSIE";
+                        $real[1][0]               = "Internet Explorer";
+                        $temp                     = explode(' ', trim($v));
+                        $real[3][0]               = $temp[1];
+                    
+                    }
+                    if (strpos($v, 'Win') !== false) {
+                        $result['device_os_token'] = trim($v);
+                    }
+                }
+            }
+            
+            if (!empty($real[0])) {
+                $result['browser_name']    = $real[1][0];
+                $result['browser_version'] = $real[3][0];
+            } else {
+                $result['browser_name']    = $result['browser_token'];
+                $result['browser_version'] = '??';
+            }
+        } elseif ($product == 'mozilla' && $result['browser_version'] < 5.0) {
+            // handles the real Mozilla (or old Netscape if version < 5.0)
+            $result['browser_name'] = 'Netscape';
+        }
+        
+        /** windows */
+        if ($result['browser_name'] == 'MSIE') {
+            $result['browser_engine'] = 'MSIE';
+            $result['browser_name']   = 'Internet Explorer';
+        }
+        if (isset($result['device_os_token'])) {
+            if (strpos($result['device_os_token'], 'Win') !== false) {
+                
+                $windows = array(
+                    'Windows NT 6.1'          => 'Windows 7', 
+                    'Windows NT 6.0'          => 'Windows Vista', 
+                    'Windows NT 5.2'          => 'Windows Server 2003', 
+                    'Windows NT 5.1'          => 'Windows XP', 
+                    'Windows NT 5.01'         => 'Windows 2000 SP1', 
+                    'Windows NT 5.0'          => 'Windows 2000', 
+                    'Windows NT 4.0'          => 'Microsoft Windows NT 4.0', 
+                    'WinNT'                   => 'Microsoft Windows NT 4.0', 
+                    'Windows 98; Win 9x 4.90' => 'Windows Me', 
+                    'Windows 98'              => 'Windows 98', 
+                    'Win98'                   => 'Windows 98', 
+                    'Windows 95'              => 'Windows 95', 
+                    'Win95'                   => 'Windows 95', 
+                    'Windows CE'              => 'Windows CE',
+                );
+                if (isset($windows[$result['device_os_token']])) {
+                    $result['device_os_name'] = $windows[$result['device_os_token']];
+                } else {
+                    $result['device_os_name'] = $result['device_os_token'];
+                }
+            }
+        }
+        
+        // iphone 
+        $apple_device = array(
+            'iPhone', 
+            'iPod', 
+            'iPad',
+        );
+        if (isset($result['compatibility_flag'])) {
+            if (in_array($result['compatibility_flag'], $apple_device)) {
+                $result['device']           = strtolower($result['compatibility_flag']);
+                $result['device_os_token']  = 'iPhone OS';
+                $result['browser_language'] = trim($comment[3]);
+                $result['browser_version']  = $result['others']['detail'][1][2];
+                if (!empty($result['others']['detail'][2])) {
+                    $result['firmware'] = $result['others']['detail'][2][2];
+                }
+                if (!empty($result['others']['detail'][3])) {
+                    $result['browser_name']  = $result['others']['detail'][3][1];
+                    $result['browser_build'] = $result['others']['detail'][3][2];
+                }
+            }
+        }
+        
+        // Safari
+        if (isset($result['others'])) {
+            if ($result['others']['detail'][0][1] == 'AppleWebKit') {
+                $result['browser_engine'] = 'AppleWebKit';
+                if ($result['others']['detail'][1][1] == 'Version') {
+                    $result['browser_version'] = $result['others']['detail'][1][2];
+                } else {
+                    $result['browser_version'] = $result['others']['detail'][count($result['others']['detail']) - 1][2];
+                }
+                $result['browser_language'] = trim($comment[3]);
+                
+                $last = $result['others']['detail'][count($result['others']['detail']) - 1][1];
+                
+                if (empty($result['others']['detail'][2][1]) || $result['others']['detail'][2][1] == 'Safari') {
+                    $result['browser_name']    = ($result['others']['detail'][1][1] && $result['others']['detail'][1][1] != 'Version' ? $result['others']['detail'][1][1] : 'Safari');
+                    $result['browser_version'] = ($result['others']['detail'][1][2] ? $result['others']['detail'][1][2] : $result['others']['detail'][0][2]);
+                } else {
+                    $result['browser_name']    = $result['others']['detail'][2][1];
+                    $result['browser_version'] = $result['others']['detail'][2][2];
+                    
+                    // mobile version
+                    if ($result['browser_name'] == 'Mobile') {
+                        $result['browser_name'] = 'Safari ' . $result['browser_name'];
+                        if ($result['others']['detail'][1][1] == 'Version') {
+                            $result['browser_version'] = $result['others']['detail'][1][2];
+                        }
+                    }
+                }
+                
+                // For Safari < 2.2, AppleWebKit version gives the Safari version
+                if (strpos($result['browser_version'], '.') > 2 || (int) $result['browser_version'] > 20) {
+                    $temp = explode('.', $result['browser_version']);
+                    $build = (int) $temp[0];
+                    $awkVersion = array(
+                        48  => '0.8', 
+                        73  => '0.9', 
+                        85  => '1.0', 
+                        103 => '1.1', 
+                        124 => '1.2', 
+                        300 => '1.3', 
+                        400 => '2.0',
+                    );
+                    foreach ($awkVersion as $k => $v) {
+                        if ($build >= $k) {
+                            $result['browser_version'] = $v;
+                        }
+                    }
+                }
+            }
+            
+            // Gecko (Firefox or compatible)
+            if ($result['others']['detail'][0][1] == 'Gecko') {
+                $searchRV = true;
+                if (!empty($result['others']['detail'][1][1]) && !empty($result['others']['detail'][count($result['others']['detail']) - 1][2]) || strpos(strtolower($result['others']['full']), 'opera') !== false) {
+                    $searchRV = false;
+                    $result['browser_engine'] = $result['others']['detail'][0][1];
+                    
+                    // the name of the application is at the end indepenently 
+                    // of quantity of information in $result['others']['detail']
+                    $last = count($result['others']['detail']) - 1;
+                    
+                    // exception : if the version of the last information is 
+                    // empty we take the previous one
+                    if (empty($result['others']['detail'][$last][2])) {
+                        $last --;
+                    }
+                    
+                    // exception : if the last one is 'Red Hat' or 'Debian' => 
+                    // use rv: to find browser_version */
+                    if (in_array($result['others']['detail'][$last][1], array(
+                        'Debian', 
+                        'Hat',
+                    ))) {
+                        $searchRV = true;
+                    }
+                    $result['browser_name']    = $result['others']['detail'][$last][1];
+                    $result['browser_version'] = $result['others']['detail'][$last][2];
+                    if (isset($comment[4])) {
+                        $result['browser_build'] = trim($comment[4]);
+                    }
+                    $result['browser_language'] = trim($comment[3]);
+                    
+                    // Netscape
+                    if ($result['browser_name'] == 'Navigator' || $result['browser_name'] == 'Netscape6') {
+                        $result['browser_name'] = 'Netscape';
+                    }
+                }
+                if ($searchRV) {
+                    // Mozilla alone : the version is identified by rv:
+                    $result['browser_name'] = 'Mozilla';
+                    if (isset($result['comment']['detail'])) {
+                        foreach ($result['comment']['detail'] as $rv) {
+                            if (strpos($rv, 'rv:') !== false) {
+                                $result['browser_version'] = trim(str_replace('rv:', '', $rv));
+                            }
+                        }
+                    }
+                }
+            }
+            
+            // Netscape
+            if ($result['others']['detail'][0][1] == 'Netscape') {
+                $result['browser_name']    = 'Netscape';
+                $result['browser_version'] = $result['others']['detail'][0][2];
+            }
+            
+            // Opera 
+            // Opera: engine Presto
+            if ($result['others']['detail'][0][1] == 'Presto') {
+                $result['browser_engine'] = 'Presto';
+                if (!empty($result['others']['detail'][1][2])) {
+                    $result['browser_version'] = $result['others']['detail'][1][2];
+                }
+            }
+            
+            // UA ends with 'Opera X.XX'
+            if ($result['others']['detail'][0][1] == 'Opera') {
+                $result['browser_name']    = $result['others']['detail'][0][1];
+                $result['browser_version'] = $result['others']['detail'][1][1];
+            }
+            
+            // Opera Mini
+            if (isset($result["browser_token"])) {
+                if (strpos($result["browser_token"], 'Opera Mini') !== false) {
+                    $result['browser_name'] = 'Opera Mini';
+                }
+            }
+            
+            // Symbian
+            if ($result['others']['detail'][0][1] == 'SymbianOS') {
+                $result['device_os_token'] = 'SymbianOS';
+            }
+        }
+        
+        // UA ends with 'Opera X.XX'
+        if (isset($result['browser_name']) && isset($result['browser_engine'])) {
+            if ($result['browser_name'] == 'Opera' && $result['browser_engine'] == 'Gecko' && empty($result['browser_version'])) {
+                $result['browser_version'] = $result['others']['detail'][count($result['others']['detail']) - 1][1];
+            }
+        }
+        
+        // cleanup
+        if (isset($result['browser_version']) && isset($result['browser_build'])) {
+            if ($result['browser_version'] == $result['browser_build']) {
+                unset($result['browser_build']);
+            }
+        }
+        
+        // compatibility
+        $compatibility['AppleWebKit'] = 'Safari';
+        $compatibility['Gecko']       = 'Firefox';
+        $compatibility['MSIE']        = 'Internet Explorer';
+        $compatibility['Presto']      = 'Opera';
+        if (!empty($result['browser_engine'])) {
+            if (isset($compatibility[$result['browser_engine']])) {
+                $result['browser_compatibility'] = $compatibility[$result['browser_engine']];
+            }
+        }
+        
+        ksort($result);
+        return $result;
+    }
+
+    /**
+     * Loads the Features Adapter if it's defined in the $config array
+     * Otherwise, nothing is done
+     *
+     * @param  string $browserType Browser type
+     * @return array
+     */
+    protected function _loadFeaturesAdapter()
+    {
+        $config      = $this->_config;
+        $browserType = $this->getType();
+        if (!isset($config[$browserType]) || !isset($config[$browserType]['features'])) {
+            return array();
+        }
+        $config = $config[$browserType]['features'];
+
+        if (empty($config['classname'])) {
+            require_once 'Zend/Http/UserAgent/Exception.php';
+            throw new Zend_Http_UserAgent_Exception('The ' . $this->getType() . ' features adapter must have a "classname" config parameter defined');
+        }
+
+        $className = $config['classname'];
+        if (!class_exists($className)) {
+            if (isset($config['path'])) {
+                $path = $config['path'];
+            } else {
+                require_once 'Zend/Http/UserAgent/Exception.php';
+                throw new Zend_Http_UserAgent_Exception('The ' . $this->getType() . ' features adapter must have a "path" config parameter defined');
+            }
+            
+            if (false === include_once ($path)) {
+                require_once 'Zend/Http/UserAgent/Exception.php';
+                throw new Zend_Http_UserAgent_Exception('The ' . $this->getType() . ' features adapter path that does not exist');
+            }
+        }
+        
+        return call_user_func(array($className, 'getFromRequest'), $this->_server, $this->_config);
+    }
+
+    /**
+     * Retrieve image format support
+     *
+     * @return array
+     */
+    public function getImageFormatSupport()
+    {
+        return $this->_images;
+    }
+
+    /**
+     * Get maximum image height supported by this device
+     *
+     * @return int
+     */
+    public function getMaxImageHeight()
+    {
+        return null;
+    }
+
+    /**
+     * Get maximum image width supported by this device
+     *
+     * @return int
+     */
+    public function getMaxImageWidth()
+    {
+        return null;
+    }
+
+    /**
+     * Get physical screen height of this device
+     *
+     * @return int
+     */
+    public function getPhysicalScreenHeight()
+    {
+        return null;
+    }
+
+    /**
+     * Get physical screen width of this device
+     *
+     * @return int
+     */
+    public function getPhysicalScreenWidth()
+    {
+        return null;
+    }
+
+    /**
+     * Get preferred markup type
+     *
+     * @return string
+     */
+    public function getPreferredMarkup()
+    {
+        return 'xhtml';
+    }
+
+    /**
+     * Get supported X/HTML version
+     *
+     * @return int
+     */
+    public function getXhtmlSupportLevel()
+    {
+        return 4;
+    }
+
+    /**
+     * Does the device support Flash?
+     *
+     * @return bool
+     */
+    public function hasFlashSupport()
+    {
+        return true;
+    }
+
+    /**
+     * Does the device support PDF?
+     *
+     * @return bool
+     */
+    public function hasPdfSupport()
+    {
+        return true;
+    }
+
+    /**
+     * Does the device have a phone number associated with it?
+     *
+     * @return bool
+     */
+    public function hasPhoneNumber()
+    {
+        return false;
+    }
+
+    /**
+     * Does the device support HTTPS?
+     *
+     * @return bool
+     */
+    public function httpsSupport()
+    {
+        return true;
+    }
+
+    /**
+     * Get the browser type
+     *
+     * @return string
+     */
+    public function getBrowser()
+    {
+        return $this->_browser;
+    }
+
+    /**
+     * Get the browser version
+     *
+     * @return string
+     */
+    public function getBrowserVersion()
+    {
+        return $this->_browserVersion;
+    }
+
+    /**
+     * Get the user agent string
+     *
+     * @return string
+     */
+    public function getUserAgent()
+    {
+        return $this->_userAgent;
+    }
+
+    /**
+     * @return the $_images
+     */
+    public function getImages()
+    {
+        return $this->_images;
+    }
+
+    /**
+     * @param string $browser
+     */
+    public function setBrowser($browser)
+    {
+        $this->_browser = $browser;
+    }
+
+    /**
+     * @param string $browserVersion
+     */
+    public function setBrowserVersion($browserVersion)
+    {
+        $this->_browserVersion = $browserVersion;
+    }
+
+    /**
+     * @param string $userAgent
+     */
+    public function setUserAgent($userAgent)
+    {
+        $this->_userAgent = $userAgent;
+        return $this;
+    }
+
+    /**
+     * @param array $_images
+     */
+    public function setImages($_images)
+    {
+        $this->_images = $_images;
+    }
+
+    /**
+     * Match a user agent string against a list of signatures
+     * 
+     * @param  string $userAgent 
+     * @param  array $signatures 
+     * @return bool
+     */
+    protected static function _matchAgentAgainstSignatures($userAgent, $signatures)
+    {
+        $userAgent = strtolower($userAgent);
+        foreach ($signatures as $signature) {
+            if (!empty($signature)) {
+                if (strpos($userAgent, $signature) !== false) {
+                    // Browser signature was found in user agent string
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+}

+ 128 - 0
library/Zend/Http/UserAgent/Bot.php

@@ -0,0 +1,128 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+require_once 'Zend/Http/UserAgent/AbstractDevice.php';
+
+/**
+ * Bot browser type matcher
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+class Zend_Http_UserAgent_Bot extends Zend_Http_UserAgent_AbstractDevice
+{
+
+    /**
+     * User Agent Signatures
+     *
+     * @var array
+     */
+    protected static $_uaSignatures = array(
+        // The most common ones.
+        'googlebot', 
+        'msnbot', 
+        'slurp', 
+        'yahoo',
+
+        // The rest, alphabetically.
+        'alexa', 
+        'appie', 
+        'archiver', 
+        'ask jeeves', 
+        'baiduspider', 
+        'bot', 
+        'crawl', 
+        'crawler', 
+        'curl', 
+        'eventbox', 
+        'facebookexternal', 
+        'fast', 
+        'feedfetcher-google', 
+        'firefly', 
+        'froogle', 
+        'gigabot', 
+        'girafabot', 
+        'google', 
+        'infoseek', 
+        'inktomi', 
+        'java', 
+        'larbin',
+        'looksmart', 
+        'mechanize', 
+        'mediapartners-google', 
+        'monitor', 
+        'nambu', 
+        'nationaldirectory', 
+        'novarra', 
+        'pear', 
+        'perl', 
+        'python', 
+        'rabaz', 
+        'radian', 
+        'rankivabot', 
+        'scooter', 
+        'sogou web spider', 
+        'spade', 
+        'sphere', 
+        'spider', 
+        'technoratisnoop', 
+        'tecnoseek', 
+        'teoma', 
+        'toolbar', 
+        'transcoder', 
+        'twitt', 
+        'url_spider_sql', 
+        'webalta crawler', 
+        'webbug', 
+        'webfindbot', 
+        'wordpress', 
+        'www.galaxy.com', 
+        'yahoo! searchmonkey', 
+        'yahoo! slurp', 
+        'yandex', 
+        'zyborg',
+    );
+
+    /**
+     * Comparison of the UserAgent chain and browser signatures
+     *
+     * @param  string $userAgent User Agent chain
+     * @param  array $server $_SERVER like param
+     * @return bool
+     */
+    public static function match($userAgent, $server)
+    {
+        return self::_matchAgentAgainstSignatures($userAgent, self::$_uaSignatures);
+    }
+
+    /**
+     * Gives the current browser type
+     *
+     * @return string
+     */
+    public function getType()
+    {
+        return 'bot';
+    }
+}

+ 76 - 0
library/Zend/Http/UserAgent/Checker.php

@@ -0,0 +1,76 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+require_once 'Zend/Http/UserAgent/Desktop.php';
+
+/**
+ * Checker browser type matcher
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+class Zend_Http_UserAgent_Checker extends Zend_Http_UserAgent_Desktop
+{
+
+    /**
+     * User Agent Signatures
+     *
+     * @var array
+     */
+    protected static $_uaSignatures = array(
+        'abilogic',
+        'checklink', 
+        'checker', 
+        'linksmanager', 
+        'mojoo', 
+        'notifixious', 
+        'ploetz', 
+        'zeller', 
+        'sitebar', 
+        'xenu', 
+        'sleuth',
+    );
+
+    /**
+     * Comparison of the UserAgent chain and User Agent signatures
+     *
+     * @param string $userAgent User Agent chain
+     * @param  array $server $_SERVER like param
+     * @return bool
+     */
+    public static function match($userAgent, $server)
+    {
+        return self::_matchAgentAgainstSignatures($userAgent, self::$_uaSignatures);
+    }
+
+    /**
+     * Gives the current browser type
+     *
+     * @return string
+     */
+    public function getType()
+    {
+        return 'bot';
+    }
+}

+ 67 - 0
library/Zend/Http/UserAgent/Console.php

@@ -0,0 +1,67 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once 'Zend/Http/UserAgent/Desktop.php';
+
+/**
+ * Console browser type matcher
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Http_UserAgent_Console extends Zend_Http_UserAgent_Desktop
+{
+    /**
+     * User Agent Signatures
+     *
+     * @var array
+     */
+    protected static $_uaSignatures = array(
+        'playstation', 
+        'wii', 
+        'libnup',
+    );
+
+    /**
+     * Comparison of the UserAgent chain and User Agent signatures
+     *
+     * @param string $userAgent User Agent chain
+     * @param  array $server $_SERVER like param
+     * @return bool
+     */
+    public static function match($userAgent, $server)
+    {
+        return self::_matchAgentAgainstSignatures($userAgent, self::$_uaSignatures);
+    }
+
+    /**
+     * Gives the current browser type
+     *
+     * @return string
+     */
+    public function getType()
+    {
+        return 'console';
+    }
+}

+ 56 - 0
library/Zend/Http/UserAgent/Desktop.php

@@ -0,0 +1,56 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once 'Zend/Http/UserAgent/AbstractDevice.php';
+
+/**
+ * Desktop browser type matcher
+ *
+ * @category   Zend
+ * @package    Zend_Browser
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Http_UserAgent_Desktop extends Zend_Http_UserAgent_AbstractDevice
+{
+
+    /**
+     * Used by default : must be always true
+     *
+     * @param string $userAgent User Agent chain
+     * @param  array $server $_SERVER like param
+     * @return bool
+     */
+    public static function match($userAgent, $server)
+    {
+        return true;
+    }
+
+    /**
+     * Gives the current browser type
+     *
+     * @return string
+     */
+    public function getType()
+    {
+        return 'desktop';
+    }
+}

+ 184 - 0
library/Zend/Http/UserAgent/Device.php

@@ -0,0 +1,184 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * Interface defining a browser device type.
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+interface Zend_Http_UserAgent_Device extends Serializable
+{
+    /**
+     * Constructor
+     *
+     * Allows injecting user agent, server array, and/or config array. If an 
+     * array is provided for the first argument, the assumption should be that
+     * the device object is being seeded with cached values from serialization.
+     * 
+     * @param  null|string|array $userAgent 
+     * @param  array $server 
+     * @param  array $config 
+     * @return void
+     */
+    public function __construct($userAgent = null, array $server = array(), array $config = array());
+
+    /**
+     * Attempt to match the user agent
+     *
+     * Return either an array of browser signature strings, or a boolean.
+     * 
+     * @param  string $userAgent 
+     * @param  array $server 
+     * @return bool|array
+     */
+    public static function match($userAgent, $server);
+
+    /**
+     * Get all browser/device features
+     * 
+     * @return array
+     */
+    public function getAllFeatures();
+
+    /**
+     * Get all of the browser/device's features' groups
+     * 
+     * @return void
+     */
+    public function getAllGroups();
+
+    /**
+     * Get the browser type
+     * 
+     * @return string
+     */
+    public function getBrowser();
+
+    /**
+     * Retrurn the browser version
+     * 
+     * @return string
+     */
+    public function getBrowserVersion();
+
+    /**
+     * Get an array of features associated with a group
+     * 
+     * @param  string $group 
+     * @return array
+     */
+    public function getGroup($group);
+
+    /**
+     * Retrieve image format support
+     * 
+     * @return array
+     */
+    public function getImageFormatSupport();
+
+    /**
+     * Get image types
+     * 
+     * @return array
+     */
+    public function getImages();
+
+    /**
+     * Get the maximum image height supported by this device
+     * 
+     * @return int
+     */
+    public function getMaxImageHeight();
+
+    /**
+     * Get the maximum image width supported by this device
+     * 
+     * @return int
+     */
+    public function getMaxImageWidth();
+
+    /**
+     * Get the physical screen height of this device
+     * 
+     * @return int
+     */
+    public function getPhysicalScreenHeight();
+
+    /**
+     * Get the physical screen width of this device
+     * 
+     * @return int
+     */
+    public function getPhysicalScreenWidth();
+
+    /**
+     * Get the preferred markup type
+     * 
+     * @return string
+     */
+    public function getPreferredMarkup();
+
+    /**
+     * Get the user agent string
+     * 
+     * @return string
+     */
+    public function getUserAgent();
+
+    /**
+     * Get supported X/HTML version
+     * 
+     * @return int
+     */
+    public function getXhtmlSupportLevel();
+
+    /**
+     * Does the device support Flash?
+     * 
+     * @return bool
+     */
+    public function hasFlashSupport();
+
+    /**
+     * Does the device support PDF?
+     * 
+     * @return bool
+     */
+    public function hasPdfSupport();
+
+    /**
+     * Does the device have a phone number associated with it?
+     * 
+     * @return bool
+     */
+    public function hasPhoneNumber();
+
+    /**
+     * Does the device support HTTPS?
+     *
+     * @return bool
+     */
+    public function httpsSupport();
+}

+ 65 - 0
library/Zend/Http/UserAgent/Email.php

@@ -0,0 +1,65 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once 'Zend/Http/UserAgent/Desktop.php';
+
+/**
+ * Email browser type matcher
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Http_UserAgent_Email extends Zend_Http_UserAgent_Desktop
+{
+    /**
+     * User Agent Signatures
+     *
+     * @var array
+     */
+    protected static $_uaSignatures = array(
+        'thunderbird',
+    );
+
+    /**
+     * Comparison of the UserAgent chain and User Agent signatures
+     *
+     * @param string $userAgent User Agent chain
+     * @param  array $server $_SERVER like param
+     * @return bool
+     */
+    public static function match($userAgent, $server)
+    {
+        return self::_matchAgentAgainstSignatures($userAgent, self::$_uaSignatures);
+    }
+
+    /**
+     * Gives the current browser type
+     *
+     * @return string
+     */
+    public function getType()
+    {
+        return 'email';
+    }
+}

+ 36 - 0
library/Zend/Http/UserAgent/Exception.php

@@ -0,0 +1,36 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * Zend_Exception
+ */
+require_once 'Zend/Exception.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Http_UserAgent_Exception extends Zend_Exception
+{
+}

+ 39 - 0
library/Zend/Http/UserAgent/Features/Adapter.php

@@ -0,0 +1,39 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * The interface required by all Zend_Browser_Features Adapter classes to implement. 
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+interface Zend_Http_UserAgent_Features_Adapter
+{
+    /**
+     * Retrieve the browser's features from a given request object ($_SERVER)
+     *
+     * @return array
+     */
+    public static function getFromRequest($request, array $config);
+}

+ 103 - 0
library/Zend/Http/UserAgent/Features/Adapter/WurflApi.php

@@ -0,0 +1,103 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * Zend_Http_UserAgent_Features_Adapter_Interface
+ */
+require_once 'Zend/Http/UserAgent/Features/Adapter.php';
+
+/**
+ * Features adapter build with the official WURFL PHP API
+ * See installation instruction here : http://wurfl.sourceforge.net/nphp/ 
+ * Download : http://sourceforge.net/projects/wurfl/files/WURFL PHP/1.1/wurfl-php-1.1.tar.gz/download
+ *
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Http_UserAgent_Features_Adapter_WurflApi
+    implements Zend_Http_UserAgent_Features_Adapter 
+{
+    const DEFAULT_API_VERSION = '1.1';
+
+    /**
+     * Get features from request
+     *
+     * @param  array $request $_SERVER variable
+     * @return array
+     */
+    public static function getFromRequest($request, array $config)
+    {
+        if (!isset($config['wurflapi'])) {
+            require_once 'Zend/Http/UserAgent/Features/Exception.php';
+            throw new Zend_Http_UserAgent_Features_Exception('"wurflapi" configuration is not defined');
+        }
+
+        $config = $config['wurflapi'];
+        
+        if (empty($config['wurfl_lib_dir'])) {
+            require_once 'Zend/Http/UserAgent/Features/Exception.php';
+            throw new Zend_Http_UserAgent_Features_Exception('The "wurfl_lib_dir" parameter is not defined');
+        }
+        if (empty($config['wurfl_config_file']) && empty($config['wurfl_config_array'])) {
+            require_once 'Zend/Http/UserAgent/Features/Exception.php';
+            throw new Zend_Http_UserAgent_Features_Exception('The "wurfl_config_file" parameter is not defined');
+        }
+        
+        if (empty($config['wurfl_api_version'])) {
+            $config['wurfl_api_version'] = self::DEFAULT_API_VERSION;
+        }
+        
+        switch ($config['wurfl_api_version']) {
+            case '1.0':
+                // Zend_Http_UserAgent::$config['wurfl_config_file'] must be an XML file
+                require_once ($config['wurfl_lib_dir'] . 'WURFLManagerProvider.php');
+                $wurflManager = WURFL_WURFLManagerProvider::getWURFLManager(Zend_Http_UserAgent::$config['wurfl_config_file']);
+                break;
+            case '1.1':
+                require_once ($config['wurfl_lib_dir'] . 'Application.php');
+                if (!empty($config['wurfl_config_file'])) {
+                    $wurflConfig = WURFL_Configuration_ConfigFactory::create($config['wurfl_config_file']);
+                } elseif (!empty($config['wurfl_config_array'])) {
+                    $c            = $config['wurfl_config_array'];
+                    $wurflConfig  = new WURFL_Configuration_InMemoryConfig();
+                    $wurflConfig->wurflFile($c['wurfl']['main-file'])
+                                ->wurflPatch($c['wurfl']['patches'])
+                                ->persistence($c['persistence']['provider'], $c['persistence']['dir']);
+                }
+                
+                $wurflManagerFactory = new WURFL_WURFLManagerFactory($wurflConfig);
+                $wurflManager = $wurflManagerFactory->create();
+                break;
+            default:
+                require_once 'Zend/Http/UserAgent/Features/Exception.php';
+                throw new Zend_Http_UserAgent_Features_Exception(sprintf(
+                    'Unknown API version "%s"', 
+                    $config['wurfl_api_version']
+                ));
+        }
+
+        $device   = $wurflManager->getDeviceForHttpRequest(array_change_key_case($request, CASE_UPPER));
+        $features = $device->getAllCapabilities();
+        return $features;
+    }
+}

+ 36 - 0
library/Zend/Http/UserAgent/Features/Exception.php

@@ -0,0 +1,36 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @see Zend_Browser_Exception
+ */
+require_once 'Zend/Http/UserAgent/Exception.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Http_UserAgent_Features_Exception extends Zend_Http_UserAgent_Exception
+{
+}

+ 81 - 0
library/Zend/Http/UserAgent/Feed.php

@@ -0,0 +1,81 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once 'Zend/Http/UserAgent/AbstractDevice.php';
+
+/**
+ * Feed browser type matcher
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Http_UserAgent_Feed extends Zend_Http_UserAgent_AbstractDevice
+{
+    /**
+     * User Agent Signatures
+     *
+     * @var array
+     */
+    protected static $_uaSignatures = array(
+        'bloglines', 
+        'everyfeed', 
+        'feedfetcher', 
+        'gregarius',
+    );
+
+    /**
+     * Comparison of the UserAgent chain and User Agent signatures
+     *
+     * @param  string $userAgent User Agent chain
+     * @param  array $server $_SERVER like param
+     * @return bool
+     */
+    public static function match($userAgent, $server)
+    {
+        return self::_matchAgentAgainstSignatures($userAgent, self::$_uaSignatures);
+    }
+
+    /**
+     * Gives the current browser type
+     *
+     * @return string
+     */
+    public function getType()
+    {
+        return 'feed';
+    }
+
+    /**
+     * Look for features
+     *
+     * @return string
+     */
+    protected function _defineFeatures()
+    {
+        $this->setFeature('iframes',    false, 'product_capability');
+        $this->setFeature('frames',     false, 'product_capability');
+        $this->setFeature('javascript', false, 'product_capability');
+        return parent::_defineFeatures();
+    }
+}

+ 526 - 0
library/Zend/Http/UserAgent/Mobile.php

@@ -0,0 +1,526 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once 'Zend/Http/UserAgent/AbstractDevice.php';
+
+/**
+ * Mobile browser type matcher
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Http_UserAgent_Mobile extends Zend_Http_UserAgent_AbstractDevice
+{
+
+    const DEFAULT_FEATURES_ADAPTER_CLASSNAME = 'Zend_Http_UserAgent_Features_Adapter_WurflApi';
+
+    const DEFAULT_FEATURES_ADAPTER_PATH = 'Zend/Http/UserAgent/Features/Adapter/WurflApi.php';
+
+    /**
+     * User Agent Signatures
+     *
+     * @var array
+     */
+    protected static $_uaSignatures = array(
+        'iphone', 
+        'ipod', 
+        'ipad', 
+        'android', 
+        'blackberry', 
+        'opera mini', 
+        'opera mobi', 
+        'palm', 
+        'palmos', 
+        'elaine', 
+        'windows ce', 
+        ' ppc', 
+        '_mms', 
+        'ahong', 
+        'archos', 
+        'armv', 
+        'astel', 
+        'avantgo', 
+        'benq', 
+        'blazer', 
+        'brew', 
+        'com2', 
+        'compal', 
+        'danger', 
+        'pocket', 
+        'docomo', 
+        'epoc', 
+        'ericsson', 
+        'eudoraweb', 
+        'hiptop', 
+        'htc-', 
+        'htc_', 
+        'iemobile', 
+        'ipad', 
+        'iris', 
+        'j-phone', 
+        'kddi', 
+        'kindle', 
+        'lg ', 
+        'lg-', 
+        'lg/', 
+        'lg;lx', 
+        'lge vx', 
+        'lge', 
+        'lge-', 
+        'lge-cx', 
+        'lge-lx', 
+        'lge-mx', 
+        'linux armv', 
+        'maemo', 
+        'midp', 
+        'mini 9.5', 
+        'minimo', 
+        'mob-x', 
+        'mobi', 
+        'mobile', 
+        'mobilephone', 
+        'mot 24', 
+        'mot-', 
+        'motorola', 
+        'n410', 
+        'netfront', 
+        'nintendo wii', 
+        'nintendo', 
+        'nitro', 
+        'nokia', 
+        'novarra-vision', 
+        'nuvifone', 
+        'openweb', 
+        'opwv', 
+        'palmsource', 
+        'pdxgw', 
+        'phone', 
+        'playstation', 
+        'polaris', 
+        'portalmmm', 
+        'qt embedded', 
+        'reqwirelessweb', 
+        'sagem', 
+        'sam-r', 
+        'samsu', 
+        'samsung', 
+        'sec-', 
+        'sec-sgh', 
+        'semc-browser', 
+        'series60', 
+        'series70', 
+        'series80', 
+        'series90', 
+        'sharp', 
+        'sie-m', 
+        'sie-s', 
+        'smartphone', 
+        'sony cmd', 
+        'sonyericsson', 
+        'sprint', 
+        'spv', 
+        'symbian os', 
+        'symbian', 
+        'symbianos', 
+        'telco', 
+        'teleca', 
+        'treo', 
+        'up.browser', 
+        'up.link', 
+        'vodafone', 
+        'vodaphone', 
+        'webos', 
+        'webpro', 
+        'windows phone os 7', 
+        'wireless', 
+        'wm5 pie', 
+        'wms pie', 
+        'xiino',
+    );
+
+    /**
+     * @var array
+     */
+    protected static $_haTerms = array(
+        'midp', 
+        'wml', 
+        'vnd.rim', 
+        'vnd.wap',
+    );
+
+    /**
+     * first 4 letters of mobile User Agent chains
+     * 
+     * @var array
+     */
+    protected static $_uaBegin = array(
+        'w3c ', 
+        'acs-', 
+        'alav', 
+        'alca', 
+        'amoi', 
+        'audi', 
+        'avan', 
+        'benq', 
+        'bird', 
+        'blac', 
+        'blaz', 
+        'brew', 
+        'cell', 
+        'cldc', 
+        'cmd-', 
+        'dang', 
+        'doco', 
+        'eric', 
+        'hipt', 
+        'inno', 
+        'ipaq', 
+        'java', 
+        'jigs', 
+        'kddi', 
+        'keji', 
+        'leno', 
+        'lg-c', 
+        'lg-d', 
+        'lg-g', 
+        'lge-', 
+        'maui', 
+        'maxo', 
+        'midp', 
+        'mits', 
+        'mmef', 
+        'mobi', 
+        'mot-', 
+        'moto', 
+        'mwbp', 
+        'nec-', 
+        'newt', 
+        'noki', 
+        'oper', 
+        'palm', 
+        'pana', 
+        'pant', 
+        'phil', 
+        'play', 
+        'port', 
+        'prox', 
+        'qwap', 
+        'sage', 
+        'sams', 
+        'sany', 
+        'sch-', 
+        'sec-', 
+        'send', 
+        'seri', 
+        'sgh-', 
+        'shar', 
+        'sie-', 
+        'siem', 
+        'smal', 
+        'smar', 
+        'sony', 
+        'sph-', 
+        'symb', 
+        't-mo', 
+        'teli', 
+        'tim-', 
+        'tosh', 
+        'tsm-', 
+        'upg1', 
+        'upsi', 
+        'vk-v', 
+        'voda', 
+        'wap-', 
+        'wapa', 
+        'wapi', 
+        'wapp', 
+        'wapr', 
+        'webc', 
+        'winw', 
+        'winw', 
+        'xda', 
+        'xda-',
+    );
+
+    /**
+     * Comparison of the UserAgent chain and User Agent signatures
+     *
+     * @param  string $userAgent User Agent chain
+     * @param  array $server $_SERVER like param
+     * @return bool
+     */
+    public static function match($userAgent, $server)
+    {
+        //  To have a quick identification, try light-weight tests first
+        if (isset($server['all_http'])) {
+            if (strpos(strtolower(str_replace(' ', '', $server['all_http'])), 'operam') !== false) {
+                // Opera Mini or Opera Mobi
+                return true;
+            }
+        }
+        if (isset($server['http_x_wap_profile']) || isset($server['http_profile'])) {
+            return true;
+        }
+
+        if (self::_matchAgentAgainstSignatures($userAgent, self::$_haTerms)) {
+            return true;
+        }
+
+        if (self::userAgentStart($userAgent)) {
+            return true;
+        }
+
+        if (self::_matchAgentAgainstSignatures($userAgent, self::$_uaSignatures)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Retrieve beginning clause of user agent
+     * 
+     * @param  string $userAgent
+     * @return string
+     */
+    public static function userAgentStart($userAgent)
+    {
+        
+        $mobile_ua = strtolower(substr($userAgent, 0, 4));
+        
+        return (in_array($mobile_ua, self::$_uaBegin));
+    }
+
+    /**
+     * Constructor
+     *
+     * @return void
+     */
+    public function __construct($userAgent = null, array $server = array(), array $config = array())
+    {
+        // For mobile detection, an adapter must be defined
+        if (empty($config['mobile']['features'])) {
+            $config['mobile']['features']['path']      = self::DEFAULT_FEATURES_ADAPTER_PATH;
+            $config['mobile']['features']['classname'] = self::DEFAULT_FEATURES_ADAPTER_CLASSNAME;
+        }
+        parent::__construct($userAgent, $server, $config);
+    }
+
+    /**
+     * Gives the current browser type
+     *
+     * @return string
+     */
+    public function getType()
+    {
+        return 'mobile';
+    }
+
+    /**
+     * Look for features
+     *
+     * @return string
+     */
+    protected function _defineFeatures()
+    {
+        $this->setFeature('is_wireless_device', false, 'product_info');
+        
+        parent::_defineFeatures();
+        
+        if (!empty($this->_aFeatures["mobile_browser"])) {
+            $this->setFeature("browser_name", $this->_aFeatures["mobile_browser"]);
+        }
+        if (!empty($this->_aFeatures["mobile_browser_version"])) {
+            $this->setFeature("browser_version", $this->_aFeatures["mobile_browser_version"]);
+        }
+        
+        // markup
+        if ($this->getFeature('device_os') == 'iPhone OS' 
+            || $this->getFeature('device_os_token') == 'iPhone OS'
+        ) {
+            $this->setFeature('markup', 'iphone');
+        } else {
+            $this->setFeature('markup', $this->getMarkupLanguage($this->getFeature('preferred_markup')));
+        }
+        
+        // image format
+        $this->_images = array();
+        
+        if ($this->getFeature('png')) {
+            $this->_images[] = 'png';
+        }
+        if ($this->getFeature('jpg')) {
+            $this->_images[] = 'jpg';
+        }
+        if ($this->getFeature('gif')) {
+            $this->_images[] = 'gif';
+        }
+        if ($this->getFeature('wbmp')) {
+            $this->_images[] = 'wbmp';
+        }
+        
+        return $this->_aFeatures;
+    }
+
+    /**
+     * Determine markup language expected
+     *
+     * @access public
+     * @return __TYPE__
+     */
+    public function getMarkupLanguage($preferredMarkup = null)
+    {
+        $return = '';
+        switch ($preferredMarkup) {
+            case 'wml_1_1':
+            case 'wml_1_2':
+            case 'wml_1_3':
+                $return = 'wml'; //text/vnd.wap.wml encoding="ISO-8859-15"
+            case 'html_wi_imode_compact_generic':
+            case 'html_wi_imode_html_1':
+            case 'html_wi_imode_html_2':
+            case 'html_wi_imode_html_3':
+            case 'html_wi_imode_html_4':
+            case 'html_wi_imode_html_5':
+                $return = 'chtml'; //text/html
+            case 'html_wi_oma_xhtmlmp_1_0': //application/vnd.wap.xhtml+xml
+            case 'html_wi_w3_xhtmlbasic': //application/xhtml+xml DTD XHTML Basic 1.0
+                $return = 'xhtml';
+            case 'html_web_3_2': //text/html DTD Html 3.2 Final
+            case 'html_web_4_0': //text/html DTD Html 4.01 Transitional
+                $return = '';
+        }
+        return $return;
+    }
+
+    /**
+     * Determine image format support
+     *
+     * @return array
+     */
+    public function getImageFormatSupport()
+    {
+        return $this->_images;
+    }
+
+    /**
+     * Determine maximum image height supported
+     *
+     * @return int
+     */
+    public function getMaxImageHeight()
+    {
+        return $this->getFeature('max_image_height');
+    }
+
+    /**
+     * Determine maximum image width supported
+     *
+     * @return int
+     */
+    public function getMaxImageWidth()
+    {
+        return $this->getFeature('max_image_width');
+    }
+
+    /**
+     * Determine physical screen height
+     *
+     * @return int
+     */
+    public function getPhysicalScreenHeight()
+    {
+        return $this->getFeature('physical_screen_height');
+    }
+
+    /**
+     * Determine physical screen width
+     *
+     * @return int
+     */
+    public function getPhysicalScreenWidth()
+    {
+        return $this->getFeature('physical_screen_width');
+    }
+
+    /**
+     * Determine preferred markup
+     *
+     * @return string
+     */
+    public function getPreferredMarkup()
+    {
+        return $this->getFeature("markup");
+    }
+
+    /**
+     * Determine X/HTML support level
+     *
+     * @return int
+     */
+    public function getXhtmlSupportLevel()
+    {
+        return $this->getFeature('xhtml_support_level');
+    }
+
+    /**
+     * Does the device support Flash?
+     *
+     * @return bool
+     */
+    public function hasFlashSupport()
+    {
+        return $this->getFeature('fl_browser');
+    }
+
+    /**
+     * Does the device support PDF?
+     *
+     * @return bool
+     */
+    public function hasPdfSupport()
+    {
+        return $this->getFeature('pdf_support');
+    }
+
+    /**
+     * Does the device have an associated phone number?
+     *
+     * @return bool
+     */
+    public function hasPhoneNumber()
+    {
+        return $this->getFeature('can_assign_phone_number');
+    }
+
+    /**
+     * Does the device support HTTPS?
+     *
+     * @return bool
+     */
+    public function httpsSupport()
+    {
+        return ($this->getFeature('https_support') == 'supported');
+    }
+}

+ 70 - 0
library/Zend/Http/UserAgent/Offline.php

@@ -0,0 +1,70 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once 'Zend/Http/UserAgent/Desktop.php';
+
+/**
+ * Offline browser type matcher
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Http_UserAgent_Offline extends Zend_Http_UserAgent_Desktop
+{
+    /**
+     * User Agent Signatures
+     *
+     * @var array
+     */
+    protected static $_uaSignatures = array(
+        'wget', 
+        'webzip', 
+        'webcopier', 
+        'downloader', 
+        'superbot', 
+        'offline',
+    );
+
+    /**
+     * Comparison of the UserAgent chain and User Agent signatures
+     *
+     * @param  string $userAgent User Agent chain
+     * @param  array $server $_SERVER like param
+     * @return bool
+     */
+    public static function match($userAgent, $server)
+    {
+        return self::_matchAgentAgainstSignatures($userAgent, self::$_uaSignatures);
+    }
+
+    /**
+     * Gives the current browser type
+     *
+     * @return string
+     */
+    public function getType()
+    {
+        return 'offline';
+    }
+}

+ 81 - 0
library/Zend/Http/UserAgent/Probe.php

@@ -0,0 +1,81 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once 'Zend/Http/UserAgent/AbstractDevice.php';
+
+/**
+ * Probe browser type matcher
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Http_UserAgent_Probe extends Zend_Http_UserAgent_AbstractDevice
+{
+    /**
+     * User Agent Signatures
+     *
+     * @var array
+     */
+    protected static $_uaSignatures = array(
+        'witbe', 
+        'netvigie',
+    );
+    
+    /**
+     * Comparison of the UserAgent chain and User Agent signatures
+     *
+     * @param string $userAgent User Agent chain
+     * @param  array $server $_SERVER like param
+     * @return bool
+     */
+    public static function match($userAgent, $server)
+    {
+        return self::_matchAgentAgainstSignatures($userAgent, self::$_uaSignatures);
+    }
+
+    
+    /**
+     * Gives the current browser type
+     *
+     * @return string
+     */
+    public function getType()
+    {
+        return 'probe';
+    }
+
+    /**
+     * Look for features
+     *
+     * @return string
+     */
+    protected function _defineFeatures()
+    {
+        $this->setFeature('images', false, 'product_capability');
+        $this->setFeature('iframes', false, 'product_capability');
+        $this->setFeature('frames', false, 'product_capability');
+        $this->setFeature('javascript', false, 'product_capability');
+        return parent::_defineFeatures();
+    }
+}

+ 79 - 0
library/Zend/Http/UserAgent/Spam.php

@@ -0,0 +1,79 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once 'Zend/Http/UserAgent/AbstractDevice.php';
+
+/**
+ * Spam browser type matcher
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Http_UserAgent_Spam extends Zend_Http_UserAgent_AbstractDevice
+{
+    /**
+     * @todo User Agent Signatures
+     *
+     * @var array
+     */
+    protected static $_uaSignatures = array(
+        '',
+    );
+
+    /**
+     * Comparison of the UserAgent chain and User Agent signatures
+     *
+     * @param  string $userAgent User Agent chain
+     * @param  array $server $_SERVER like param
+     * @return bool
+     */
+    public static function match($userAgent, $server)
+    {
+        return self::_matchAgentAgainstSignatures($userAgent, self::$_uaSignatures);
+    }
+
+    /**
+     * Gives the current browser type
+     *
+     * @return string
+     */
+    public function getType()
+    {
+        return 'spam';
+    }
+
+    /**
+     * Look for features
+     *
+     * @return string
+     */
+    protected function _defineFeatures()
+    {
+        $this->setFeature('images', false, 'product_capability');
+        $this->setFeature('iframes', false, 'product_capability');
+        $this->setFeature('frames', false, 'product_capability');
+        $this->setFeature('javascript', false, 'product_capability');
+        return parent::_defineFeatures();
+    }
+}

+ 65 - 0
library/Zend/Http/UserAgent/Storage.php

@@ -0,0 +1,65 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+interface Zend_Http_UserAgent_Storage
+{
+    /**
+     * Returns true if and only if storage is empty
+     *
+     * @throws Zend_Http_UserAgent_Storage_Exception If it is impossible to determine whether storage is empty
+     * @return boolean
+     */
+    public function isEmpty();
+
+    /**
+     * Returns the contents of storage associated to the key parameter
+     *
+     * Behavior is undefined when storage is empty.
+     *
+     * @throws Zend_Http_UserAgent_Storage_Exception If reading contents from storage is impossible
+     * @return mixed
+     */
+    public function read();
+
+    /**
+     * Writes $contents associated to the key parameter to storage
+     *
+     * @param  mixed $contents
+     * @throws Zend_Http_UserAgent_Storage_Exception If writing $contents to storage is impossible
+     * @return void
+     */
+    public function write($contents);
+
+    /**
+     * Clears contents from storage
+     *
+     * @throws Zend_Http_UserAgent_Storage_Exception If clearing contents from storage is impossible
+     * @return void
+     */
+    public function clear();
+}

+ 37 - 0
library/Zend/Http/UserAgent/Storage/Exception.php

@@ -0,0 +1,37 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+
+/**
+ * @see Zend_Http_UserAgent_Exception
+ */
+require_once 'Zend/Http/UserAgent/Exception.php';
+
+
+/**
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Http_UserAgent_Storage_Exception extends Zend_Http_UserAgent_Exception
+{
+}

+ 97 - 0
library/Zend/Http/UserAgent/Storage/NonPersistent.php

@@ -0,0 +1,97 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id: NonPersistent.php 20096 2010-01-06 02:05:09Z bkarwin $
+ */
+
+
+/**
+ * @see Zend_Http_UserAgent_Storage_Interface
+ */
+require_once 'Zend/Http/UserAgent/Storage.php';
+
+
+/**
+ * Non-Persistent Browser Storage
+ *
+ * Since HTTP Browserentication happens again on each request, this will always be
+ * re-populated. So there's no need to use sessions, this simple value class
+ * will hold the data for rest of the current request.
+ *
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Http_UserAgent_Storage_NonPersistent 
+    implements Zend_Http_UserAgent_Storage
+{
+    /**
+     * Holds the actual Browser data
+     * @var mixed
+     */
+    protected $_data;
+
+    /**
+     * Returns true if and only if storage is empty
+     *
+     * @throws Zend_Http_UserAgent_Storage_Exception If it is impossible to determine whether storage is empty
+     * @return boolean
+     */
+    public function isEmpty()
+    {
+        return empty($this->_data);
+    }
+
+    /**
+     * Returns the contents of storage
+     *
+     * Behavior is undefined when storage is empty.
+     *
+     * @throws Zend_Http_UserAgent_Storage_Exception If reading contents from storage is impossible
+     * @return mixed
+     */
+    public function read()
+    {
+        return $this->_data;
+    }
+
+    /**
+     * Writes $contents to storage
+     *
+     * @param  mixed $contents
+     * @throws Zend_Http_UserAgent_Storage_Exception If writing $contents to storage is impossible
+     * @return void
+     */
+    public function write($contents)
+    {
+        $this->_data = $contents;
+    }
+
+    /**
+     * Clears contents from storage
+     *
+     * @throws Zend_Http_UserAgent_Storage_Exception If clearing contents from storage is impossible
+     * @return void
+     */
+    public function clear()
+    {
+        $this->_data = null;
+    }
+}

+ 166 - 0
library/Zend/Http/UserAgent/Storage/Session.php

@@ -0,0 +1,166 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @see Zend_Http_UserAgent_Storage
+ */
+require_once 'Zend/Http/UserAgent/Storage.php';
+
+/**
+ * @see Zend_Session_Namespace
+ */
+require_once 'Zend/Session/Namespace.php';
+
+/**
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Http_UserAgent_Storage_Session implements Zend_Http_UserAgent_Storage 
+{
+    /**
+     * Default session namespace
+     */
+    const NAMESPACE_DEFAULT = 'Zend_Http_UserAgent';
+    
+    /**
+     * Default session object member name
+     */
+    const MEMBER_DEFAULT = 'storage';
+    
+    /**
+     * Object to proxy $_SESSION storage
+     *
+     * @var Zend_Session_Namespace
+     */
+    protected $_session;
+    
+    /**
+     * Session namespace
+     *
+     * @var mixed
+     */
+    protected $_namespace;
+    
+    /**
+     * Session object member
+     *
+     * @var mixed
+     */
+    protected $_member;
+    
+    /**
+     * Sets session storage options and initializes session namespace object
+     *
+     * Expects options to contain 0 or more of the following keys:
+     * - browser_type -- maps to "namespace" internally
+     * - member
+     *
+     * @param  null|array|object $options
+     * @return void
+     * @throws Zend_Http_UserAgent_Storage_Exception on invalid $options argument
+     */
+    public function __construct($options = null) 
+    {
+        if (is_object($options) && method_exists($options, 'toArray')) {
+            $options = $options->toArray();
+        } elseif (is_object($options)) {
+            $options = (array) $options;
+        }
+        if (null !== $options && !is_array($options)) {
+            require_once 'Zend/Http/UserAgent/Storage/Exception.php';
+            throw new Zend_Http_UserAgent_Storage_Exception(sprintf(
+                'Expected array or object options; "%s" provided',
+                gettype($options)
+            ));
+        }
+
+        // add '.' to prevent the message ''Session namespace must not start with a number'
+        $this->_namespace = '.' 
+                          . (isset($options['browser_type']) 
+                             ? $options['browser_type'] 
+                             : self::NAMESPACE_DEFAULT);
+        $this->_member    = isset($options['member']) ? $options['member'] : self::MEMBER_DEFAULT;
+        $this->_session   = new Zend_Session_Namespace($this->_namespace);
+    }
+    
+    /**
+     * Returns the session namespace name
+     *
+     * @return string
+     */
+    public function getNamespace() 
+    {
+        return $this->_namespace;
+    }
+    
+    /**
+     * Returns the name of the session object member
+     *
+     * @return string
+     */
+    public function getMember() 
+    {
+        return $this->_member;
+    }
+    
+    /**
+     * Defined by Zend_Http_UserAgent_Storage
+     *
+     * @return boolean
+     */
+    public function isEmpty() 
+    {
+        return empty($this->_session->{$this->_member});
+    }
+    
+    /**
+     * Defined by Zend_Http_UserAgent_Storage
+     *
+     * @return mixed
+     */
+    public function read() 
+    {
+        return $this->_session->{$this->_member};
+    }
+    
+    /**
+     * Defined by Zend_Http_UserAgent_Storage
+     *
+     * @param  mixed $contents
+     * @return void
+     */
+    public function write($content) 
+    {
+        $this->_session->{$this->_member} = $content;
+    }
+    
+    /**
+     * Defined by Zend_Http_UserAgent_Storage
+     *
+     * @return void
+     */
+    public function clear() 
+    {
+        unset($this->_session->{$this->_member});
+    }
+}

+ 132 - 0
library/Zend/Http/UserAgent/Text.php

@@ -0,0 +1,132 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once 'Zend/Http/UserAgent/AbstractDevice.php';
+
+/**
+ * Text browser type matcher
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Http_UserAgent_Text extends Zend_Http_UserAgent_AbstractDevice
+{
+    /**
+     * User Agent Signatures
+     *
+     * @var array
+     */
+    protected static $_uaSignatures = array(
+        'lynx', 
+        'retawq', 
+        'w3m',
+    );
+
+    /**
+     * Comparison of the UserAgent chain and User Agent signatures
+     *
+     * @param string $userAgent User Agent chain
+     * @param  array $server $_SERVER like param
+     * @return bool
+     */
+    public static function match($userAgent, $server)
+    {
+        return self::_matchAgentAgainstSignatures($userAgent, self::$_uaSignatures);
+    }
+
+    /**
+     * Gives the current browser type
+     *
+     * @return string
+     */
+    public function getType()
+    {
+        return 'text';
+    }
+
+    /**
+     * Look for features
+     *
+     * @return string
+     */
+    protected function _defineFeatures()
+    {
+        $this->setFeature('images', false, 'product_capability');
+        $this->setFeature('iframes', false, 'product_capability');
+        $this->setFeature('frames', false, 'product_capability');
+        $this->setFeature('javascript', false, 'product_capability');
+        return parent::_defineFeatures();
+    }
+
+    /**
+     * Determine supported image formats
+     *
+     * @return null
+     */
+    public function getImageFormatSupport()
+    {
+        return null;
+    }
+
+    /**
+     * Get preferred markup format
+     *
+     * @return string
+     */
+    public function getPreferredMarkup()
+    {
+        return 'xhtml';
+    }
+
+    /**
+     * Get supported X/HTML markup level
+     *
+     * @return int
+     */
+    public function getXhtmlSupportLevel()
+    {
+        return 1;
+    }
+
+    /**
+     * Does the device support Flash?
+     *
+     * @return bool
+     */
+    public function hasFlashSupport()
+    {
+
+        return false;
+    }
+
+    /**
+     * Does the device support PDF?
+     *
+     * @return bool
+     */
+    public function hasPdfSupport()
+    {
+        return false;
+    }
+}

+ 73 - 0
library/Zend/Http/UserAgent/Validator.php

@@ -0,0 +1,73 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once 'Zend/Http/UserAgent/Desktop.php';
+
+/**
+ * Validator browser type matcher
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Http_UserAgent_Validator extends Zend_Http_UserAgent_Desktop
+{
+    /**
+     * User Agent Signatures
+     *
+     * @var array
+     */
+    protected static $_uaSignatures = array(
+        'htmlvalidator', 
+        'csscheck', 
+        'cynthia', 
+        'htmlparser', 
+        'validator', 
+        'jfouffa', 
+        'jigsaw', 
+        'w3c_validator', 
+        'wdg_validator',
+    );
+
+    /**
+     * Comparison of the UserAgent chain and User Agent signatures
+     *
+     * @param string $userAgent User Agent chain
+     * @param  array $server $_SERVER like param
+     * @return bool
+     */
+    public static function match($userAgent, $server)
+    {
+        return self::_matchAgentAgainstSignatures($userAgent, self::$_uaSignatures);
+    }
+
+    /**
+     * Gives the current browser type
+     *
+     * @return string
+     */
+    public function getType()
+    {
+        return 'validator';
+    }
+}

+ 317 - 0
library/Zend/View/Helper/TinySrc.php

@@ -0,0 +1,317 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_View
+ * @subpackage Helper
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/** Zend_View_Helper_HtmlElement */
+require_once 'Zend/View/Helper/HtmlElement.php';
+
+/**
+ * Helper for generating urls and/or image tags for use with tinysrc.net
+ *
+ * tinysrc.net provides an API for generating scaled, browser device-specific 
+ * images. In essence, you pass the API the URL to an image on your own server,
+ * and tinysrc.net then provides the appropriate image based on the device that
+ * accesses it.
+ *
+ * Additionally, tinysrc.net allows you to specify additional configuration via 
+ * the API:
+ *
+ * - image size. You may define this as:
+ *   - explicit size
+ *   - subtractive size (size of screen minus specified number of pixels)
+ *   - percentage size (percentage of screen size))
+ * - image format. This will convert the image to the given format; allowed 
+ *   values are "png" or "jpeg". By default, gif images are converted to png.
+ *
+ * This helper allows you to specify all configuration options, as well as:
+ *
+ * - whether or not to generate the full image tag (or just the URL)
+ * - base url to images (which should include the protocol, server, and 
+ *   optionally port and base path)
+ *
+ * @see        http://tinysrc.net/
+ * @package    Zend_View
+ * @subpackage Helper
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_View_Helper_TinySrc extends Zend_View_Helper_HtmlElement
+{
+    const TINYSRC_BASE = 'http://i.tinysrc.mobi';
+
+    /**
+     * @var string Base URL for images
+     */
+    protected $_baseUrl;
+
+    /**
+     * @var bool Whether or not to create an image tag
+     */
+    protected $_createTagFlag = true;
+
+    /**
+     * @var string Default width and height
+     */
+    protected $_dimensions = '';
+
+    /**
+     * Default options
+     *
+     * Used when determining what options were passed, and needing to merge 
+     * them with default options.
+     * 
+     * @var array
+     */
+    protected $_defaultOptions = array(
+        'base_url'   => null,
+        'format'     => null,
+        'width'      => false,
+        'height'     => false,
+        'create_tag' => true,
+    );
+
+    /**
+     * @var string Default image format to use
+     */
+    protected $_format = '';
+
+    /**
+     * Generate a link or image tag pointing to tinysrc.net
+     * 
+     * @param mixed $image 
+     * @param array $options 
+     * @return void
+     */
+    public function tinySrc($image = null, array $options = array())
+    {
+        if (null === $image) {
+            return $this;
+        }
+
+        $defaultOptions = $this->_defaultOptions;
+        $defaultOptions['create_tag'] = $this->createTag();
+        $options = array_merge($defaultOptions, $options);
+
+        $url = '/' . $this->_mergeBaseUrl($options) . ltrim($image, '/');
+
+        $src = self::TINYSRC_BASE 
+             . $this->_mergeFormat($options) 
+             . $this->_mergeDimensions($options)
+             . $url;
+
+        if (!$options['create_tag']) {
+            return $src;
+        }
+
+        foreach (array_keys($this->_defaultOptions) as $key) {
+            switch ($key) {
+                case 'width':
+                case 'height':
+                    if (!is_int($options[$key]) || !is_numeric($options[$key]) || $options[$key] < 0) {
+                        unset($options[$key]);
+                    }
+                    break;
+                default:
+                    unset($options[$key]);
+                    break;
+            }
+        }
+
+        $options['src'] = $src;
+
+        $tag = '<img' . $this->_htmlAttribs($options) . $this->getClosingBracket();
+        return $tag;
+    }
+
+    /**
+     * Set base URL for images
+     * 
+     * @param  string $url 
+     * @return Zend_View_Helper_TinySrc
+     */
+    public function setBaseUrl($url)
+    {
+        $this->_baseUrl = rtrim($url, '/') . '/';
+        return $this;
+    }
+
+    /**
+     * Get base URL for images
+     *
+     * If none already set, uses the ServerUrl and BaseUrl view helpers to
+     * determine the base URL to images.
+     * 
+     * @return string
+     */
+    public function getBaseUrl()
+    {
+        if (null === $this->_baseUrl) {
+            $this->setBaseUrl($this->view->serverUrl($this->view->baseUrl()));
+        }
+        return $this->_baseUrl;
+    }
+
+    /**
+     * Set default image format
+     *
+     * If set, this will set the default format to use on all images.
+     * 
+     * @param  null|string $format 
+     * @return Zend_View_Helper_TinySrc
+     * @throws Zend_View_Exception
+     */
+    public function setDefaultFormat($format = null)
+    {
+        if (null === $format) {
+            $this->_format = '';
+            return $this;
+        }
+
+        $format = strtolower($format);
+        if (!in_array($format, array('png', 'jpeg'))) {
+            require_once 'Zend/View/Exception.php';
+            throw new Zend_View_Exception('Invalid format; must be one of "jpeg" or "png"');
+        }
+        $this->_format = "/$format";
+        return $this;
+    }
+
+    /**
+     * Set default dimensions
+     *
+     * If null is specified for width, default dimensions will be cleared. If 
+     * only width is specified, only width will be used. If either dimension
+     * fails validation, an exception is raised.
+     * 
+     * @param  null|int|string $width 
+     * @param  null|int|string $height 
+     * @return Zend_View_Helper_TinySrc
+     * @throws Zend_View_Exception
+     */
+    public function setDefaultDimensions($width = null, $height = null)
+    {
+        if (null === $width) {
+            $this->_dimensions = '';
+            return $this;
+        }
+
+        if (!$this->_validateDimension($width)) {
+            require_once 'Zend/View/Exception.php';
+            throw new Zend_View_Exception('Invalid dimension; must be an integer, optionally preceded by "-" or "x"');
+        }
+
+        $this->_dimensions = "/$width";
+        if (null === $height) {
+            return $this;
+        }
+
+        if (!$this->_validateDimension($height)) {
+            require_once 'Zend/View/Exception.php';
+            throw new Zend_View_Exception('Invalid dimension; must be an integer, optionally preceded by "-" or "x"');
+        }
+        $this->_dimensions .= "/$height";
+        return $this;
+    }
+
+    /**
+     * Set state of "create tag" flag
+     * 
+     * @param  bool $flag 
+     * @return Zend_View_Helper_TinySrc
+     */
+    public function setCreateTag($flag)
+    {
+        $this->_createTagFlag = (bool) $flag;
+        return $this;
+    }
+
+    /**
+     * Should the helper create an image tag?
+     * 
+     * @return bool
+     */
+    public function createTag()
+    {
+        return $this->_createTagFlag;
+    }
+
+    /**
+     * Validate a dimension
+     *
+     * Dimensions may be integers, optionally preceded by '-' or 'x'.
+     * 
+     * @param  string $dim 
+     * @return bool
+     */
+    protected function _validateDimension($dim)
+    {
+        if (!is_scalar($dim) || is_bool($dim)) {
+            return false;
+        }
+        return preg_match('/^(-|x)?\d+$/', (string) $dim);
+    }
+
+    /**
+     * Determine whether to use default base URL, or base URL from options
+     * 
+     * @param  array $options 
+     * @return string
+     */
+    protected function _mergeBaseUrl(array $options)
+    {
+        if (null === $options['base_url']) {
+            return $this->getBaseUrl();
+        }
+        return rtrim($options['base_url'], '/') . '/';
+    }
+
+    /**
+     * Determine whether to use default format or format provided in options.
+     * 
+     * @param  array $options 
+     * @return string
+     */
+    protected function _mergeFormat(array $options) 
+    {
+        if (in_array($options['format'], array('png', 'jpeg'))) {
+            return '/' . $options['format'];
+        }
+        return $this->_format;
+    }
+
+    /**
+     * Determine whether to use default dimensions, or those passed in options.
+     * 
+     * @param  array $options 
+     * @return string
+     */
+    protected function _mergeDimensions(array $options)
+    {
+        if (!$this->_validateDimension($options['width'])) {
+            return $this->_dimensions;
+        }
+        $dimensions = '/' . $options['width'];
+        if (!$this->_validateDimension($options['height'])) {
+            return $dimensions;
+        }
+        $dimensions .= '/' . $options['height'];
+        return $dimensions;
+    }
+}

+ 76 - 0
library/Zend/View/Helper/UserAgent.php

@@ -0,0 +1,76 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_View
+ * @subpackage Helper
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/** Zend_View_Helper_Abstract */
+require_once 'Zend/View/Helper/Abstract.php';
+
+/**
+ * Helper for interacting with UserAgent instance
+ *
+ * @package    Zend_View
+ * @subpackage Helper
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_View_Helper_UserAgent extends Zend_View_Helper_Abstract
+{
+    /**
+     * Helper method: retrieve or set UserAgent instance
+     * 
+     * @param  null|Zend_Http_UserAgent $userAgent 
+     * @return Zend_Http_UserAgent
+     */
+    public function userAgent(Zend_Http_UserAgent $userAgent = null)
+    {
+        if (null !== $userAgent) {
+            $this->setUserAgent($userAgent);
+        }
+        return $this->getUserAgent();
+    }
+
+    /**
+     * Set UserAgent instance
+     * 
+     * @param  Zend_Http_UserAgent $userAgent 
+     * @return Zend_View_Helper_UserAgent
+     */
+    public function setUserAgent(Zend_Http_UserAgent $userAgent)
+    {
+        $this->_userAgent = $userAgent;
+        return $this;
+    }
+
+    /**
+     * Retrieve UserAgent instance
+     *
+     * If none set, instantiates one using no configuration
+     * 
+     * @return Zend_Http_UserAgent
+     */
+    public function getUserAgent()
+    {
+        if (null === $this->_userAgent) {
+            require_once 'Zend/Http/UserAgent.php';
+            $this->setUserAgent(new Zend_Http_UserAgent());
+        }
+        return $this->_userAgent;
+    }
+}

+ 9 - 0
tests/TestConfiguration.php.dist

@@ -325,6 +325,15 @@ define('TESTS_ZEND_HTTP_CLIENT_HTTP_PROXY_USER', '');
 define('TESTS_ZEND_HTTP_CLIENT_HTTP_PROXY_PASS', '');
 
 /**
+ * Zend_Http_UserAgent tests
+ *
+ * Location of WURFL library and config file, for testing mobile device 
+ * detection.
+ */
+define('TESTS_ZEND_HTTP_USERAGENT_WURFL_LIB_DIR', false);
+define('TESTS_ZEND_HTTP_USERAGENT_WURFL_CONFIG_FILE', false);
+
+/**
  * Zend_Loader_Autoloader multi-version support tests
  *
  * ENABLED:      whether or not to run the multi-version tests

+ 2 - 0
tests/Zend/Application/Resource/AllTests.php

@@ -39,6 +39,7 @@ require_once 'Zend/Application/Resource/ModulesTest.php';
 require_once 'Zend/Application/Resource/MultidbTest.php';
 require_once 'Zend/Application/Resource/NavigationTest.php';
 require_once 'Zend/Application/Resource/SessionTest.php';
+require_once 'Zend/Application/Resource/UseragentTest.php';
 require_once 'Zend/Application/Resource/ViewTest.php';
 
 /**
@@ -74,6 +75,7 @@ class Zend_Application_Resource_AllTests
         $suite->addTestSuite('Zend_Application_Resource_MultidbTest');
         $suite->addTestSuite('Zend_Application_Resource_NavigationTest');
         $suite->addTestSuite('Zend_Application_Resource_SessionTest');
+        $suite->addTestSuite('Zend_Application_Resource_UseragentTest');
         $suite->addTestSuite('Zend_Application_Resource_ViewTest');
 
         return $suite;

+ 130 - 0
tests/Zend/Application/Resource/UseragentTest.php

@@ -0,0 +1,130 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Application
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+if (!defined('PHPUnit_MAIN_METHOD')) {
+    define('PHPUnit_MAIN_METHOD', 'Zend_Application_Resource_UseragentTest::main');
+}
+
+/**
+ * Test helper
+ */
+require_once dirname(__FILE__) . '/../../../TestHelper.php';
+
+/**
+ * Zend_Loader_Autoloader
+ */
+require_once 'Zend/Loader/Autoloader.php';
+
+require_once 'Zend/Application/Resource/ResourceAbstract.php';
+require_once 'Zend/Application/Resource/Useragent.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Application
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @group      Zend_Application
+ */
+class Zend_Application_Resource_UseragentTest extends PHPUnit_Framework_TestCase
+{
+    public static function main()
+    {
+        $suite  = new PHPUnit_Framework_TestSuite(__CLASS__);
+        $result = PHPUnit_TextUI_TestRunner::run($suite);
+    }
+
+    public function setUp()
+    {
+        // Store original autoloaders
+        $this->loaders = spl_autoload_functions();
+        if (!is_array($this->loaders)) {
+            // spl_autoload_functions does not return empty array when no
+            // autoloaders registered...
+            $this->loaders = array();
+        }
+
+        Zend_Loader_Autoloader::resetInstance();
+        $this->autoloader = Zend_Loader_Autoloader::getInstance();
+
+        $this->application = new Zend_Application('testing');
+
+        require_once dirname(__FILE__) . '/../_files/ZfAppBootstrap.php';
+        $this->bootstrap = new ZfAppBootstrap($this->application);
+
+        Zend_Controller_Action_HelperBroker::resetHelpers();
+    }
+
+    public function tearDown()
+    {
+        // Restore original autoloaders
+        $loaders = spl_autoload_functions();
+        foreach ($loaders as $loader) {
+            spl_autoload_unregister($loader);
+        }
+
+        foreach ($this->loaders as $loader) {
+            spl_autoload_register($loader);
+        }
+
+        // Reset autoloader instance so it doesn't affect other tests
+        Zend_Loader_Autoloader::resetInstance();
+    }
+
+    public function testInitializationInitializesUserAgentObject()
+    {
+        $resource = new Zend_Application_Resource_Useragent(array());
+        $resource->setBootstrap($this->bootstrap);
+        $resource->init();
+        $this->assertTrue($resource->getUserAgent() instanceof Zend_Http_UserAgent);
+    }
+
+    public function testOptionsPassedToResourceAreUsedToSetUserAgentState()
+    {
+        $options = array(
+            'storage' => array('adapter' => 'NonPersistent'),
+        );
+        $resource = new Zend_Application_Resource_Useragent($options);
+        $resource->setBootstrap($this->bootstrap);
+        $resource->init();
+        $ua      = $resource->getUserAgent();
+        $storage = $ua->getStorage();
+        $this->assertType('Zend_Http_UserAgent_Storage_NonPersistent', $storage);
+    }
+
+    public function testInjectsUserAgentIntoViewHelperWhenViewResourcePresent()
+    {
+        $this->bootstrap->registerPluginResource('view', array());
+        $resource = new Zend_Application_Resource_Useragent(array());
+        $resource->setBootstrap($this->bootstrap);
+        $resource->init();
+
+        $view   = $this->bootstrap->getResource('view');
+        $helper = $view->getHelper('userAgent');
+
+        $expected = $resource->getUserAgent();
+        $this->assertSame($expected, $helper->getUserAgent());
+    }
+}
+
+if (PHPUnit_MAIN_METHOD == 'Zend_Application_Resource_UseragentTest::main') {
+    Zend_Application_Resource_UseragentTest::main();
+}

+ 2 - 0
tests/Zend/Http/AllTests.php

@@ -30,6 +30,7 @@ require_once 'Zend/Http/ResponseTest.php';
 require_once 'Zend/Http/CookieTest.php';
 require_once 'Zend/Http/CookieJarTest.php';
 require_once 'Zend/Http/Client/AllTests.php';
+require_once 'Zend/Http/UserAgent/AllTests.php';
 
 /**
  * @category   Zend
@@ -54,6 +55,7 @@ class Zend_Http_AllTests
         $suite->addTestSuite('Zend_Http_CookieTest');
         $suite->addTestSuite('Zend_Http_CookieJarTest');
         $suite->addTest(Zend_Http_Client_AllTests::suite());
+        $suite->addTest(Zend_Http_UserAgent_AllTests::suite());
 
         return $suite;
     }

+ 36 - 0
tests/Zend/Http/TestAsset/DesktopDevice.php

@@ -0,0 +1,36 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http_UserAgent
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * Zend_Http_UserAgent
+ */
+require_once 'Zend/Http/UserAgent/Desktop.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Http_UserAgent
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Http_TestAsset_DesktopDevice extends Zend_Http_UserAgent_Desktop
+{
+}

+ 38 - 0
tests/Zend/Http/TestAsset/Device/Browser/Features/Adapter.php

@@ -0,0 +1,38 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http_UserAgent
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id: JsonTest.php 12081 2008-10-22 19:07:55Z norm2782 $
+ */
+
+require_once 'Zend/Http/UserAgent/Features/Adapter.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Http_UserAgent
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Device_Browser_Features_Adapter implements Zend_Http_UserAgent_Features_Adapter
+{
+    public static function getFromRequest($request, array $config)
+    {
+        return array();
+    }
+}

+ 36 - 0
tests/Zend/Http/TestAsset/Device/Desktop.php

@@ -0,0 +1,36 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http_UserAgent
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * Zend_Http_UserAgent
+ */
+require_once 'Zend/Http/UserAgent/Desktop.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Http_UserAgent
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Http_TestAsset_Device_Desktop extends Zend_Http_UserAgent_Desktop
+{
+}

+ 31 - 0
tests/Zend/Http/TestAsset/InvalidDevice.php

@@ -0,0 +1,31 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http_UserAgent
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @category   Zend
+ * @package    Zend_Http_UserAgent
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Http_TestAsset_InvalidDevice
+{
+}

+ 32 - 0
tests/Zend/Http/TestAsset/InvalidPluginLoader.php

@@ -0,0 +1,32 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http_UserAgent
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id: JsonTest.php 12081 2008-10-22 19:07:55Z norm2782 $
+ */
+
+/**
+ * @category   Zend
+ * @package    Zend_Http_UserAgent
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Http_TestAsset_InvalidPluginLoader
+{
+}

+ 94 - 0
tests/Zend/Http/TestAsset/PopulatedStorage.php

@@ -0,0 +1,94 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @see Zend_Http_UserAgent_Storage_Interface
+ */
+require_once 'Zend/Http/UserAgent/Storage.php';
+
+/**
+ * Non-Persistent Browser Storage
+ *
+ * Since HTTP Browserentication happens again on each request, this will always be
+ * re-populated. So there's no need to use sessions, this simple value class
+ * will hold the data for rest of the current request.
+ *
+ * @package    Zend_Http
+ * @subpackage UserAgent
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Http_TestAsset_PopulatedStorage 
+    implements Zend_Http_UserAgent_Storage
+{
+    /**
+     * Holds the actual Browser data
+     * @var mixed
+     */
+    protected $_data = 'a:6:{s:12:"browser_type";s:7:"desktop";s:6:"config";a:4:{s:23:"identification_sequence";s:14:"mobile,desktop";s:26:"persistent_storage_adapter";s:13:"NonPersistent";s:8:"wurflapi";a:2:{s:13:"wurfl_lib_dir";s:63:"/home/matthew/git/zf-standard/tests/Zend/Http/_files/Wurfl/1.1/";s:17:"wurfl_config_file";s:85:"/home/matthew/git/zf-standard/tests/Zend/Http/_files/Wurfl/resources/wurfl-config.php";}s:7:"desktop";a:1:{s:7:"matcher";a:1:{s:9:"classname";s:33:"Zend_Http_TestAsset_DesktopDevice";}}}s:12:"device_class";s:33:"Zend_Http_TestAsset_DesktopDevice";s:6:"device";s:793:"a:5:{s:10:"_aFeatures";a:19:{s:12:"browser_name";s:7:"desktop";s:12:"product_name";s:7:"desktop";s:10:"user_agent";s:7:"desktop";s:18:"is_wireless_device";b:0;s:9:"is_mobile";b:0;s:10:"is_desktop";b:1;s:9:"is_tablet";b:0;s:6:"is_bot";b:0;s:8:"is_email";b:0;s:7:"is_text";b:0;s:25:"device_claims_web_support";b:0;s:9:"client_ip";s:9:"127.0.0.1";s:11:"php_version";s:5:"5.3.1";s:9:"server_os";s:6:"apache";s:17:"server_os_version";s:6:"2.2.12";s:18:"server_http_accept";s:3:"*/*";s:27:"server_http_accept_language";s:5:"fr-FR";s:9:"server_ip";s:9:"127.0.0.1";s:11:"server_name";s:8:"zfmobile";}s:8:"_browser";s:7:"desktop";s:15:"_browserVersion";s:0:"";s:10:"_userAgent";s:7:"desktop";s:7:"_images";a:6:{i:0;s:4:"jpeg";i:1;s:3:"gif";i:2;s:3:"png";i:3;s:5:"pjpeg";i:4;s:5:"x-png";i:5;s:3:"bmp";}}";s:10:"user_agent";s:7:"desktop";s:11:"http_accept";s:3:"*/*";}';
+
+    /**
+     * Returns true if and only if storage is empty
+     *
+     * @throws Zend_Http_UserAgent_Storage_Exception If it is impossible to determine whether storage is empty
+     * @return boolean
+     */
+    public function isEmpty()
+    {
+        return empty($this->_data);
+    }
+
+    /**
+     * Returns the contents of storage
+     *
+     * Behavior is undefined when storage is empty.
+     *
+     * @throws Zend_Http_UserAgent_Storage_Exception If reading contents from storage is impossible
+     * @return mixed
+     */
+    public function read()
+    {
+        return $this->_data;
+    }
+
+    /**
+     * Writes $contents to storage
+     *
+     * @param  mixed $contents
+     * @throws Zend_Http_UserAgent_Storage_Exception If writing $contents to storage is impossible
+     * @return void
+     */
+    public function write($contents)
+    {
+        $this->_data = $contents;
+    }
+
+    /**
+     * Clears contents from storage
+     *
+     * @throws Zend_Http_UserAgent_Storage_Exception If clearing contents from storage is impossible
+     * @return void
+     */
+    public function clear()
+    {
+        $this->_data = null;
+    }
+}

+ 39 - 0
tests/Zend/Http/TestAsset/TestPluginLoader.php

@@ -0,0 +1,39 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http_UserAgent
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id: JsonTest.php 12081 2008-10-22 19:07:55Z norm2782 $
+ */
+
+require_once 'Zend/Loader/PluginLoader.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Http_UserAgent
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Http_TestAsset_TestPluginLoader extends Zend_Loader_PluginLoader
+{
+    public function __construct(array $prefixToPaths = array(), $staticRegistryName = null)
+    {
+        parent::__construct($prefixToPaths, $staticRegistryName);
+        $this->addPrefixPath('Zend_Http_TestAsset_Device', dirname(__FILE__) . '/Device');
+    }
+}

+ 807 - 0
tests/Zend/Http/UserAgent/AbstractDeviceTest.php

@@ -0,0 +1,807 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http_UserAgent
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once dirname(__FILE__) . '/../../../TestHelper.php';
+
+/**
+ * Zend_Http_UserAgent
+ */
+require_once 'Zend/Http/UserAgent.php';
+require_once 'Zend/Http/UserAgent/AbstractDevice.php';
+require_once 'Zend/Http/UserAgent/Bot.php';
+require_once 'Zend/Http/UserAgent/Checker.php';
+require_once 'Zend/Http/UserAgent/Console.php';
+require_once 'Zend/Http/UserAgent/Desktop.php';
+require_once 'Zend/Http/UserAgent/Email.php';
+require_once 'Zend/Http/UserAgent/Feed.php';
+require_once 'Zend/Http/UserAgent/Mobile.php';
+require_once 'Zend/Http/UserAgent/Offline.php';
+require_once 'Zend/Http/UserAgent/Probe.php';
+require_once 'Zend/Http/UserAgent/Spam.php';
+require_once 'Zend/Http/UserAgent/Text.php';
+require_once 'Zend/Http/UserAgent/Validator.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Http_UserAgent
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Http_UserAgent_AbstractDeviceTest extends PHPUnit_Framework_TestCase
+{
+    public function testUserAgentSafari()
+    {
+        $userAgent = 'Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/533.17.8 (KHTML, like Gecko) Version/5.0.1 Safari/533.17.8';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('AppleWebKit', $extract['browser_engine']);
+        $this->assertEquals('Safari', $extract['browser_name']);
+        $this->assertEquals('5.0.1', $extract['browser_version']);
+        $this->assertEquals('Windows Server 2003', $extract['device_os_name']);
+        
+        $userAgent = 'Mozilla/5.0 (iPad; U; CPU iPhone OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B314 Safari/531.21.10gin_lib.cc';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('AppleWebKit', $extract['browser_engine']);
+        $this->assertEquals('Safari Mobile', $extract['browser_name']);
+        $this->assertEquals('4.0.4', $extract['browser_version']);
+        $this->assertEquals('iPhone OS', $extract['device_os_token']);
+        
+        $userAgent = 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10_4_11; hu-hu) AppleWebKit/531.21.8 (KHTML, like Gecko) Version/4.0.4 Safari/531.21.10';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('AppleWebKit', $extract['browser_engine']);
+        $this->assertEquals('Safari', $extract['browser_name']);
+        $this->assertEquals('4.0.4', $extract['browser_version']);
+        $this->assertEquals('PPC Mac OS X 10_4_11', $extract['device_os_token']);
+        
+        $userAgent = 'Mozilla/5.0 (iPad; U; CPU iPhone OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B314';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('AppleWebKit', $extract['browser_engine']);
+        $this->assertEquals('Safari Mobile', $extract['browser_name']);
+        $this->assertEquals('4.0.4', $extract['browser_version']);
+        $this->assertEquals('iPhone OS', $extract['device_os_token']);
+        
+        $userAgent = 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X; pt-pt) AppleWebKit/418.9.1 (KHTML, like Gecko) Safari/419.3';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('AppleWebKit', $extract['browser_engine']);
+        $this->assertEquals('Safari', $extract['browser_name']);
+        $this->assertEquals('2.0', $extract['browser_version']);
+        $this->assertEquals('PPC Mac OS X', $extract['device_os_token']);
+        
+        $userAgent = 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-ch) AppleWebKit/312.1.1 (KHTML, like Gecko) Safari/312';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Safari', $extract['browser_name']);
+        $this->assertEquals('1.3', $extract['browser_version']);
+        
+        $userAgent = 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/312.5.2 (KHTML, like Gecko) Safari/312.3.3';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Safari', $extract['browser_name']);
+        $this->assertEquals('1.3', $extract['browser_version']);
+        
+        $userAgent = 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/85.7 (KHTML, like Gecko) Safari/85.5';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Safari', $extract['browser_name']);
+        $this->assertEquals('1.0', $extract['browser_version']);
+        
+        $userAgent = 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-gb) AppleWebKit/85.8.5 (KHTML, like Gecko) Safari/85.8.1';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Safari', $extract['browser_name']);
+        $this->assertEquals('1.0', $extract['browser_version']);
+        
+        $userAgent = 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/124 (KHTML, like Gecko) Safari/125';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Safari', $extract['browser_name']);
+        $this->assertEquals('1.2', $extract['browser_version']);
+        
+        $userAgent = 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/418.9 (KHTML, like Gecko) Safari/419.3';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Safari', $extract['browser_name']);
+        $this->assertEquals('2.0', $extract['browser_version']);
+        
+        $userAgent = 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/412.6.2 (KHTML, like Gecko) Safari/412.2.2';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Safari', $extract['browser_name']);
+        $this->assertEquals('2.0', $extract['browser_version']);
+    }
+
+    public function testUserAgentInternetExplorer()
+    {
+        $userAgent = 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('MSIE', $extract['browser_engine']);
+        $this->assertEquals('Internet Explorer', $extract['browser_name']);
+        $this->assertEquals('9.0', $extract['browser_version']);
+        $this->assertEquals('Windows 7', $extract['device_os_name']);
+        
+        $userAgent = 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.2; FDM; OfficeLiveConnector.1.4; OfficeLivePatch.1.3; .NET CLR 1.1.4322)';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('MSIE', $extract['browser_engine']);
+        $this->assertEquals('Internet Explorer', $extract['browser_name']);
+        $this->assertEquals('8.0', $extract['browser_version']);
+        $this->assertEquals('Windows 7', $extract['device_os_name']);
+        
+        $userAgent = 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('MSIE', $extract['browser_engine']);
+        $this->assertEquals('Internet Explorer', $extract['browser_name']);
+        $this->assertEquals('7.0', $extract['browser_version']);
+        $this->assertEquals('Windows 7', $extract['device_os_name']);
+        
+        $userAgent = 'Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 6.0; en-US)';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('MSIE', $extract['browser_engine']);
+        $this->assertEquals('Internet Explorer', $extract['browser_name']);
+        $this->assertEquals('7.0', $extract['browser_version']);
+        $this->assertEquals('Windows Vista', $extract['device_os_name']);
+        
+        $userAgent = 'Mozilla/4.0 (Windows; MSIE 7.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('MSIE', $extract['browser_engine']);
+        $this->assertEquals('Internet Explorer', $extract['browser_name']);
+        $this->assertEquals('7.0', $extract['browser_version']);
+        $this->assertEquals('Windows XP', $extract['device_os_name']);
+        
+        $userAgent = 'Mozilla/45.0 (compatible; MSIE 6.0; Windows NT 5.1)';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('MSIE', $extract['browser_engine']);
+        $this->assertEquals('Internet Explorer', $extract['browser_name']);
+        $this->assertEquals('6.0', $extract['browser_version']);
+        $this->assertEquals('Windows XP', $extract['device_os_name']);
+        
+        $userAgent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.2; .NET CLR 1.1.4322)';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('MSIE', $extract['browser_engine']);
+        $this->assertEquals('Internet Explorer', $extract['browser_name']);
+        $this->assertEquals('5.5', $extract['browser_version']);
+        $this->assertEquals('Windows Server 2003', $extract['device_os_name']);
+        
+        $userAgent = 'Mozilla/4.0 (compatible; MSIE 5.12; Mac_PowerPC)';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('MSIE', $extract['browser_engine']);
+        $this->assertEquals('Internet Explorer', $extract['browser_name']);
+        $this->assertEquals('5.12', $extract['browser_version']);
+        $this->assertEquals('Mac_PowerPC', $extract['device_os_token']);
+        
+        $userAgent = 'Mozilla/4.0 (compatible; MSIE 4.5; Windows 98; )';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('MSIE', $extract['browser_engine']);
+        $this->assertEquals('Internet Explorer', $extract['browser_name']);
+        $this->assertEquals('4.5', $extract['browser_version']);
+        $this->assertEquals('Windows 98', $extract['device_os_name']);
+    }
+
+    public function testUserAgentFirefox()
+    {
+        $userAgent = 'Mozilla/5.0 (Windows; U; Windows NT 6.1; ru; rv:1.9.2.3) Gecko/20100401 Firefox/4.0 (.NET CLR 3.5.30729)';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Firefox', $extract['browser_name']);
+        $this->assertEquals('4.0', $extract['browser_version']);
+        $this->assertEquals('Windows 7', $extract['device_os_name']);
+        
+        $userAgent = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; nb-NO; rv:1.9.2.4) Gecko/20100611 Firefox/3.6.4 (.NET CLR 3.5.30729)';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Firefox', $extract['browser_name']);
+        $this->assertEquals('3.6.4', $extract['browser_version']);
+        $this->assertEquals('Windows XP', $extract['device_os_name']);
+        
+        $userAgent = 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.6) Gecko/2009020518 Ubuntu/9.04 (jaunty) Firefox/3.0.6';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Firefox', $extract['browser_name']);
+        $this->assertEquals('3.0.6', $extract['browser_version']);
+        $this->assertEquals('Linux i686', $extract['device_os_token']);
+        
+        $userAgent = 'Mozilla/5.0 (X11; U; Linux i686 (x86_64); en-US; rv:1.8.1.9) Gecko/20071025 Firefox/2.0.0.9';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Firefox', $extract['browser_name']);
+        $this->assertEquals('2.0.0.9', $extract['browser_version']);
+        $this->assertEquals('Linux i686 (x86_64)', $extract['device_os_token']);
+        
+        $userAgent = 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.8) Gecko/20071019 Fedora/2.0.0.8-1.fc7 Firefox/2.0.0.8';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Firefox', $extract['browser_name']);
+        $this->assertEquals('2.0.0.8', $extract['browser_version']);
+        $this->assertEquals('Linux i686', $extract['device_os_token']);
+        
+        $userAgent = 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.1) Gecko/20060313 Debian/1.5.dfsg+1.5.0.1-4 Firefox/1.5.0.1';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Firefox', $extract['browser_name']);
+        $this->assertEquals('1.5.0.1', $extract['browser_version']);
+        $this->assertEquals('Linux i686', $extract['device_os_token']);
+    }
+
+    public function testUserAgentMozilla()
+    {
+        $userAgent = 'Mozilla/5.0 (Windows; U; Windows NT 6.1; it; rv:2.0b4) Gecko/20100818';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Mozilla', $extract['browser_name']);
+        $this->assertEquals('2.0b4', $extract['browser_version']);
+        
+        $userAgent = 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en; rv:1.8.1.4pre) Gecko/20070521 Camino/1.6a1pre';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Camino', $extract['browser_name']);
+        $this->assertEquals('1.6a1pre', $extract['browser_version']);
+        
+        $userAgent = 'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.0.7) Gecko/2009021910 MEGAUPLOAD 1.0';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Mozilla', $extract['browser_name']);
+        $this->assertEquals('1.9.0.7', $extract['browser_version']);
+        
+        $userAgent = 'Mozilla/5.0 (X11; U; Linux i686; pl-PL; rv:1.9.0.6) Gecko/2009020911';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Mozilla', $extract['browser_name']);
+        $this->assertEquals('1.9.0.6', $extract['browser_version']);
+        
+        $userAgent = 'Mozilla/5.001 (X11; U; Linux i686; rv:1.8.1.6; de-ch) Gecko/25250101 (ubuntu-feisty)';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Mozilla', $extract['browser_name']);
+        $this->assertEquals('1.8.1.6', $extract['browser_version']);
+        
+        $userAgent = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.18) Gecko/20081029';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Mozilla', $extract['browser_name']);
+        $this->assertEquals('1.8.1.18', $extract['browser_version']);
+        
+        $userAgent = 'Mozilla/5.0 (X11; U; Linux i586; de-AT; rv:1.4) Gecko/20030908 Debian/1.4-4';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Mozilla', $extract['browser_name']);
+        $this->assertEquals('1.4', $extract['browser_version']);
+    }
+
+    public function testUserAgentChrome()
+    {
+        $userAgent = 'Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/534.4 (KHTML, like Gecko) Chrome/6.0.481.0 Safari/534.4';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('AppleWebKit', $extract['browser_engine']);
+        $this->assertEquals('Chrome', $extract['browser_name']);
+        $this->assertEquals('6.0.481.0', $extract['browser_version']);
+        $this->assertEquals('Windows Server 2003', $extract['device_os_name']);
+        
+        $userAgent = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.514.0 Safari/534.7';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('AppleWebKit', $extract['browser_engine']);
+        $this->assertEquals('Chrome', $extract['browser_name']);
+        $this->assertEquals('7.0.514.0', $extract['browser_version']);
+        $this->assertEquals('Windows XP', $extract['device_os_name']);
+        
+        $userAgent = 'Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.1 (KHTML, like Gecko) Chrome/6.0.416.0 Safari/534.1';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('AppleWebKit', $extract['browser_engine']);
+        $this->assertEquals('Chrome', $extract['browser_name']);
+        $this->assertEquals('6.0.416.0', $extract['browser_version']);
+        $this->assertEquals('Linux i686', $extract['device_os_token']);
+    }
+
+    public function testUserAgentNetscape()
+    {
+        $userAgent = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.9pre) Gecko/20071102 Firefox/2.0.0.9 Navigator/9.0.0.3';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Gecko', $extract['browser_engine']);
+        $this->assertEquals('Netscape', $extract['browser_name']);
+        $this->assertEquals('9.0.0.3', $extract['browser_version']);
+        $this->assertEquals('Windows XP', $extract['device_os_name']);
+        
+        $userAgent = 'Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.7.2) Gecko/20050208 Netscape/7.20';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Gecko', $extract['browser_engine']);
+        $this->assertEquals('Netscape', $extract['browser_name']);
+        $this->assertEquals('7.20', $extract['browser_version']);
+        $this->assertEquals('Windows 2000', $extract['device_os_name']);
+        
+        $userAgent = 'Mozilla/5.0 (Windows; U; Windows NT 5.0; fr-FR; rv:0.9.4) Gecko/20011128 Netscape6/6.2.1';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Gecko', $extract['browser_engine']);
+        $this->assertEquals('Netscape', $extract['browser_name']);
+        $this->assertEquals('6.2.1', $extract['browser_version']);
+        $this->assertEquals('Windows 2000', $extract['device_os_name']);
+        
+        $userAgent = 'Mozilla/4.79 [en] (X11; U; SunOS 5.7 sun4u)';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Netscape', $extract['browser_name']);
+        $this->assertEquals('4.79', $extract['browser_version']);
+        $this->assertEquals('SunOS 5.7 sun4u', $extract['device_os_token']);
+        
+        $userAgent = 'Mozilla/4.04 [fr] (Macintosh; I; PPC, Nav)';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Netscape', $extract['browser_name']);
+        $this->assertEquals('4.04', $extract['browser_version']);
+        $this->assertEquals('Macintosh', $extract['compatibility_flag']);
+    }
+
+    public function testUserAgentOpera()
+    {
+        $userAgent = 'Opera/9.99 (Windows NT 5.1; U; pl) Presto/9.9.9';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Presto', $extract['browser_engine']);
+        $this->assertEquals('Opera', $extract['browser_name']);
+        $this->assertEquals('9.99', $extract['browser_version']);
+        
+        $userAgent = 'Opera/9.80 (J2ME/MIDP; Opera Mini/5.0 (Windows; U; Windows NT 5.1; en) AppleWebKit/886; U; en) Presto/2.4.15';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Presto', $extract['browser_engine']);
+        $this->assertEquals('opera mini', strtolower($extract['browser_name']));
+        
+        $userAgent = 'Opera/9.70 (Linux ppc64 ; U; en) Presto/2.2.1';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Presto', $extract['browser_engine']);
+        $this->assertEquals('Opera', $extract['browser_name']);
+        $this->assertEquals('9.70', $extract['browser_version']);
+        
+        $userAgent = 'Mozilla/5.0 (Windows NT 5.1; U; en-GB; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.61';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Gecko', $extract['browser_engine']);
+        $this->assertEquals('Opera', $extract['browser_name']);
+        $this->assertEquals('9.61', $extract['browser_version']);
+        
+        $userAgent = 'Mozilla/4.0 (compatible; MSIE 6.0; X11; Linux x86_64; en) Opera 9.60';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('MSIE', $extract['browser_engine']);
+        $this->assertEquals('Opera', $extract['browser_name']);
+        $this->assertEquals('9.60', $extract['browser_version']);
+        
+        $userAgent = 'Opera/9.52 (Windows NT 6.0; U; Opera/9.52 (X11; Linux x86_64; U); en)';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Opera', $extract['browser_name']);
+        $this->assertEquals('9.52', $extract['browser_version']);
+        
+        $userAgent = 'Opera/9.20 (Windows NT 6.0; U; de)';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Opera', $extract['browser_name']);
+        $this->assertEquals('9.20', $extract['browser_version']);
+        
+        $userAgent = 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; ru) Opera 8.54';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('MSIE', $extract['browser_engine']);
+        $this->assertEquals('Opera', $extract['browser_name']);
+        $this->assertEquals('8.54', $extract['browser_version']);
+        
+        $userAgent = 'Opera/9.80 (Windows NT 5.1; U; zh-cn) Presto/2.2.15 Version/10.00';
+        $extract = Zend_Http_UserAgent_Desktop::extractFromUserAgent($userAgent);
+        $this->assertEquals('Presto', $extract['browser_engine']);
+        $this->assertEquals('Opera', $extract['browser_name']);
+        $this->assertEquals('10.00', $extract['browser_version']);
+    }
+
+    /** 
+     * examples from http://en.wikipedia.org/wiki/List_of_user_agents_for_mobile_phones
+     */
+    public function testMatchMobile()
+    {
+        $userAgent = 'Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleW1ebKit/420.1 (KHTML, like Gecko) Version/3.0 Mobile/4A102 Safari/419.3';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = '8500: HTC-8500/1.2 Mozilla/4.0 (compatible; MSIE 5.5; Windows CE; PPC; 240x320) ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = '8500: HTC-8500/1.2 Mozilla/4.0 (compatible; MSIE 6.0; Windows CE; IEMobile 7.6) UP.Link/6.3.1.17.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Alcatel OT-708: Alcatel-OT-708/1.0 Profile/MIDP-2.0 Configuration/CLDC-1.1 ObigoInternetBrowser/Q03C ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Android SDK 1.5r3: Mozilla/5.0 (Linux; U; Android 1.5; de-; sdk Build/CUPCAKE) AppleWebkit/528.5+ (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Apple iPad: Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B367 Safari/531.21.10 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Apple iPhone OS 4: Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_0 like Mac OS X; en-us) AppleWebKit/532.9 (KHTML, like Gecko) Version/4.0.5 Mobile/8A293 Safari/6531.22.7 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Apple iPhone: Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us) AppleWebKit/420.1 (KHTML, like Gecko) Version/3.0 Mobile/1A542a Safari/419.3 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'BlackBerry 7100i: BlackBerry7100i/4.1.0 Profile/MIDP-2.0 Configuration/CLDC-1.1 VendorID/103 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'BlackBerry 7130e: BlackBerry7130e/4.1.0 Profile/MIDP-2.0 Configuration/CLDC-1.1 VendorID/104 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'BlackBerry 7230: BlackBerry7230/3.7.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'BlackBerry 7250: BlackBerry7250/4.0.0 Profile/MIDP-2.0 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'BlackBerry 7520: BlackBerry7520/4.0.0 Profile/MIDP-2.0 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'BlackBerry 7730: BlackBerry7730/3.7.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'BlackBerry 8100: Mozilla/4.0 BlackBerry8100/4.2.0 Profile/MIDP-2.0 Configuration/CLDC-1.1 VendorID/100 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'BlackBerry 8130: BlackBerry8130/4.3.0 Profile/MIDP-2.0 Configuration/CLDC-1.1 VendorID/109 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'BlackBerry 8310: BlackBerry8310/4.2.2 Profile/MIDP-2.0 Configuration/CLDC-1.1 VendorID/121 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'BlackBerry 8320: BlackBerry8320/4.3.1 Profile/MIDP-2.0 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'BlackBerry 8700: BlackBerry8700/4.1.0 Profile/MIDP-2.0 Configuration/CLDC-1.1 VendorID/100 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'BlackBerry 8703e: BlackBerry8703e/4.1.0 Profile/MIDP-2.0 Configuration/CLDC-1.1 VendorID/105 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'BlackBerry 8820: BlackBerry8820/4.2.2 Profile/MIDP-2.0 Configuration/CLDC-1.1 VendorID/102 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'BlackBerry 8830: BlackBerry8830/4.2.2 Profile/MIDP-2.0 Configuration/CLOC-1.1 VendorID/105 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'BlackBerry 8900: BlackBerry8900/4.5.1.231 Profile/MIDP-2.0 Configuration/CLDC-1.1 VendorID/100 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'BlackBerry 9000: BlackBerry9000/4.6.0.65 Profile/MIDP-2.0 Configuration/CLDC-1.1 VendorID/102 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'BlackBerry 9530: BlackBerry9530/4.7.0.167 Profile/MIDP-2.0 Configuration/CLDC-1.1 VendorID/102 UP.Link/6.3.1.20.0';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'BlackBerry 9630 Tour BlackBerry9630/4.7.1.40 Profile/MIDP-2.0 Configuration/CLDC-1.1 VendorID/104 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'BlackBerry 9700 Bold: BlackBerry9700/5.0.0.423 Profile/MIDP-2.1 Configuration/CLDC-1.1 VendorID/100 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'BlackBerry 9800 Torch: Mozilla/5.0 (BlackBerry; U; BlackBerry 9800; en) AppleWebKit/534.1+ (KHTML, Like Gecko) Version/6.0.0.141 Mobile Safari/534.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'BlackBerry9530/5.0.0.328 Profile/MIDP-2.1 Configuration/CLDC-1.1 VendorID/105 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Desire: Mozilla/5.0 (Linux; U; Android 2.1-update1; fr-fr; desire_A8181 Build/ERE27) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'EF81: SIE-EF81/58 UP.Browser/7.0.0.1.181 (GUI) MMP/2.0 Profile/MIDP-2.0 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Espresso: Mozilla/5.0 (Linux; U; Android 2.1-update1; en-us; T-Mobile_Espresso Build/ERE27) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'EVO 4G: Mozilla/5.0 (Linux; U; Android 2.1-update1; en-us; Sprint APA9292KT Build/ERE27) AppleWebKit/530.17 (KHTML, like Gecko) ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Hero: Mozilla/5.0 (Linux; U; Android 1.5; en-za; HTC Hero Build/CUPCAKE) AppleWebKit/528.5+ (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'iPod Touch: Mozilla/5.0 (iPod; U; CPU iPhone OS 3_1_1 like Mac OS X; en-us) AppleWebKit/528.18 (KHTML, like Gecko) Mobile/7C145 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Legend: Mozilla/5.0 (Linux; U; Android 2.1; fr-fr; HTC Legend 1.32.163.1 Build/ERD79) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG B2050: LG-B2050 MIC/WAP2.0 MIDP-2.0/CLDC-1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG C1100: LG-C1100 MIC/WAP2.0 MIDP-2.0/CLDC-1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG CU720: LG-CU720/V1.0|Obigo/Q05A Profile/MIDP-2.0 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG CU8080: LGE-CU8080/1.0 UP.Browser/4.1.26l ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG G1800: LG-G1800 MIC/WAP2.0 MIDP-2.0/CLDC-1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG G210: LG-G210/SW100/WAP2.0 Profile/MIDP-2.0 Configuration/CLDC-1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG G220: LG-G220/V100/WAP2.0 Profile/MIDP-2.0 Configuration/CLDC-1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG G232: LG-G232/V100/WAP2.0 Profile/MIDP-2.0 Configuration/CLDC-1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG G262: LG-G262/V100/WAP2.0 Profile/MIDP-2.0 Configuration/CLDC-1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG G5200: LG-G5200 AU/4.10 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG G5600: LG-G5600 MIC/WAP2.0 MIDP-2.0/CLDC-1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG G610: LG-G610 V100 AU/4.10 Profile/MIDP-1.0 Configuration/CLDC-1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG G622: LG-G622/V100/WAP2.0 Profile/MIDP-2.0 Configuration/CLDC-1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG G650: LG-G650 V100 AU/4.10 Profile/MIDP-1.0 Configuration/CLDC-1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG G660: LG-G660/V100/WAP2.0 Profile/MIDP-2.0 Configuration/CLDC-1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG G672: LG-G672/V100/WAP2.0 Profile/MIDP-2.0 Configuration/CLDC-1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG G682: LG-G682 /V100/WAP2.0 Profile/MIDP-2.0 Configuration/CLDC-1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG G688: LG-G688 MIC/V100/WAP2.0 MIDP-2.0/CLDC-1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG G7000: LG-G7000 AU/4.10 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG G7050: LG-G7050 UP.Browser/6.2.2 (GUI) MMP/1.0 Profile/MIDP-1.0 Configuration/CLDC-1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG G7100: LG-G7100 AU/4.10 Profile/MIDP-1.0 Configuration/CLDC-1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG G7200: LG-G7200 UP.Browser/6.2.2 (GUI) MMP/1.0 Profile/MIDP-1.0 Configuration/CLDC-1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG G822: LG-G822/SW100/WAP2.0 Profile/MIDP-2.0 Configuration/CLDC-1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG G850: LG-G850 V100 UP.Browser/6.2.2 (GUI) MMP/1.0 Profile/MIDP-1.0 Configuration/CLDC-1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG G920: LG-G920/V122/WAP2.0 Profile/MIDP-1.0 Configuration/CLDC-1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG G922: LG-G922 Obigo/WAP2.0 MIDP-2.0/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG G932: LG-G932 UP.Browser/6.2.3(GUI)MMP/1.0 Profile/MIDP-2.0 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG KP500: LG-KP500 Teleca/WAP2.0 MIDP-2.0/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG KS360: LG-KS360 Teleca/WAP2.0 MIDP-2.0/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG L1100: LG-L1100 UP.Browser/6.2.2 (GUI) MMP/1.0 Profile/MIDP-1.0 Configuration/CLDC-1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG MX8700: LGE-MX8700/1.0 UP.Browser/6.2.3.2 (GUI) MMP/2.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG T5100: LG-T5100 UP.Browser/6.2.3 (GUI) MMP/1.0 Profile/MIDP-1.0 Configuration/CLDC-1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG U8120: LG/U8120/v1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG U8130: LG/U8130/v1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG U8138: LG/U8138/v2.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG U8180: LG/U8180/v1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG U880: LG/U880/v1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'LG VX9100: LGE-VX9100/1.0 UP.Browser/6.2.3.2 (GUI) MMP/2.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Magic: Mozilla/5.0 (Linux; U; Android 1.5; en-dk; HTC Magic Build/CUPCAKE) AppleWebKit/528.5+ (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Motorola CLIQ: Mozilla/5.0 (Linux; U; Android 1.5; en-us; MB200 Build/CUPCAKE) AppleWebKit/528.5+ (KHTML, like Gecko) Version/3.1.2 Mob ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Motorola Droid V2.01: Mozilla/5.0 (Linux; U; Android 2.0.1; en-us; Droid Build/ESD56) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Motorola Droid X Mozilla/5.0 (Linux; U; Android 2.1-update1; en-us; DROIDX Build/VZW) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17 480X854 motorola DROIDX ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Motorola E398: MOT-E398/0E.20.59R MIB/2.2.1 Profile/MIDP-2.0 Configuration/CLDC-1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Motorola EM30: MOT-EM30/R6716_G_71.01.24R Mozilla/5.0 (compatible; OSS/1.0; Chameleon; Linux) BER/2.0 Profile/MIDP-2.0 Configuration/CLDC-1.1 [es-co] ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Motorola KRZR K1: MOT-K1/08.03.08R MIB/BER2.2 Profile/MIDP-2.0 Configuration/CLDC-1.1 EGE/1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Motorola Motoroi(XT720) V2.01: Mozilla/5.0 (Linux; U; Android 2.0.1; ko-kr; XT720 Build/STSKT_N_79.11.31R) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Motorola Motoroi(XT720) V2.1-update1: Mozilla/5.0 (Linux; U; Android 2.1-update1; ko-kr; XT720 Build/STSKT_N_79.11.33R) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Motorola RAZR V3: MOT-V3/0E.42.0ER MIB/2.2.1 Profile/MIDP-2.0 Configuration/CLDC-1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Motorola RAZR V3r: MOT-V3r/08.BD.43R MIB/2.2.1 Profile/MIDP-2.0 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Motorola RAZR V3xx: MOT-RAZRV3xx/96.64.21P BER2.2 Mozilla/4.0 (compatible; MSIE 6.0; 11003002) Profile/MIDP-2.0 Configuration/CLDC-1.1 Opera 8.00 [en] UP.Link/6.3.0.0.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Motorola RAZR2 V8: motorazrV8/R601_G_80.42.0FRP Mozilla/4.0 (compatible; MSIE 6.0 Linux; Motorola V8;nnn) Profile/MIDP-2.0 Configuration/CLDC-1.1 Opera 8.50[yy] ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Motorola RAZR2 V9: MOT-MOTORAZRV9/4 BER2.2 Mozilla/4.0 (compatible; MSIE 6.0; 14003181) Profile/MIDP-2.0 Configuration/CLDC-1.1 Op! era 8.00 [en] UP.Link/6.3.0.0.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Motorola RAZR2 V9x: MOT-MOTORAZRV9x/9E.03.15R BER2.2 Mozilla/4.0 (compatible; MSIE 6.0; 13003337) Profile/MIDP-2.0 Configuration/CLDC-1.1 Opera 8.60 [en] UP.Link/6.3.0.0.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Motorola ROKR E8: Mozilla/5.0 (compatible; OSS/1.0; Chameleon; Linux) MOT-E8/R6713_G_71.02.07R BER/2.0 Profile/MIDP-2.0 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Motorola ROKR Z6: MOTOROKR Z6/R60_G_80.xx.yyl Mozilla/4.0 (compatible; MSIE 6.0 Linux; MOTOROKRZ6;nnn) Profile/MIDP-2.0 Configuration/CLDC-1.1 Opera 8.50[yy] ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Motorola SLVR L6: MOT-L6/0A.52.2BR MIB/2.2.1 Profile/MIDP-2.0 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Motorola SLVR L7: MOT-L7/NA.ACR_RB MIB/2.2.1 Profile/MIDP-2.0 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Motorola U9: Mozilla/5.0 (compatible; OSS/1.0; Chameleon; Linux) MOT-U9/R6632_G_81.11.29R BER/2.0 Profile/MIDP-2.0 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Motorola Z9: MOT-MOTOZ9/9E.01.03R BER2.2 Mozilla/4.0 (compatible; MSIE 6.0; 11003002) Profile/MIDP-2.0 Configuration/CLDC-1.1 Opera 8.60 [en] UP.Link/6.3.0.0.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Motorola ZN5: MOT-ZN5/R6637_G_81.03.05R Mozilla/4.0 (compatible; OSS/1.0; Linux MOTOZINE ZN5) Profile/MIDP-2.0 Configuration/CLDC-1.1 Symphony 1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Mozilla/5.0 (SymbianOS/9.1; U; en-us) AppleWebKit/413 (KHTML, like Gecko) Safari/413 es65';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Nexus One: Mozilla/5.0 (Linux; U; Android 2.2; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Nokia 2610: Nokia2610/2.0 (07.04a) Profile/MIDP-2.0 Configuration/CLDC-1.1 UP.Link/6.3.1.20.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Nokia 5300: Nokia5300/2.0 (05.51) Profile/MIDP-2.0 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Nokia 5530: Mozilla/5.0 (SymbianOS/9.4; U; Series60/5.0 Nokia5530c-2/10.0.050; Profile MIDP-2.1 Configuration/CLDC-1.1) AppleWebKit/525 (KHTML, like Gecko) Safari/525 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Nokia 5630: Mozilla/5.0 (SymbianOS/9.3; U; Series60/3.2 Nokia5630d-1/012.020; Profile MIDP-2.1 Configuration/CLDC-1.1) AppleWebKit/413 (KHTML, like Gecko) Safari/413 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Nokia 5800: Mozilla/5.0 (SymbianOS/9.4; U; Series60/5.0 Nokia5800d-1/31.0.101; Profile MIDP-2.1 Configuration/CLDC-1.1) AppleWebKit/413 (KHTML, like Gecko) Safari/413 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Nokia 6030: Nokia6030/2.0 (y3.44) Profile/MIDP-2.0 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Nokia 6230i: Nokia6230i/2.0 (03.40) Profile/MIDP-2.0 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Nokia 6280: Nokia6280/2.0 (03.60) Profile/MIDP-2.0 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Nokia 6650: Nokia6650d-1bh/ATT.2.15 Mozilla/5.0 (SymbianOS/9.3; U; [en]; Series60/3.2; Profile/MIDP-2.1 Configuration/CLDC-1.1) AppleWebKit/413 (KHTML, like Gecko) Safari/413 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Nokia E51-1: Mozilla/5.0 (SymbianOS/9.2; U; Series60/3.1 NokiaE51-1/220.34.37; Profile/MIDP-2.0 Configuration/CLDC-1.1) AppleWebKit/413 (KHTML, like Gecko) Safari/413 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Nokia E71x: NokiaE71x/ATT.03.11.1 Mozilla/5.0 SymbianOS/9.3; U; [en]; Series60/3.2; Profile/MIDP-2.1 Configuration/CLDC-1.1 AppleWebKit/413 KHTML, like Gecko) Safari/413 UP.Link/6.3.0.0.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Nokia N70: NokiaN70-1/5.0616.2.0.3 Series60/2.8 Profile/MIDP-2.0 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Nokia N75: NokiaN75-3/3.0 (1.0635.0.0.6); SymbianOS/9.1 Series60/3.0 Profile/MIDP-2.0 Configuration/CLDC-1.1) UP.Link/6.3.0.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Nokia N78: Mozilla/5.0 (SymbianOS/9.3; U; Series60/3.2 NokiaN78-1/12.046; Profile/MIDP-2.1 Configuration/CLDC-1.1) AppleWebKit/413 (KHTML, like Gecko) Safari/413 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Nokia N80: NokiaN80-1/3.0(4.0632.0.10) Series60/3.0 Profile/MIDP-2.0 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Nokia N90: NokiaN90-1/5.0607.7.3 Series60/2.8 Profile/MIDP-2.0 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Nokia N900: Mozilla/5.0 (X11; U; Linux armv7l; en-GB; rv:1.9.2b6pre) Gecko/20100318 Firefox/3.5 Maemo Browser 1.7.4.8 RX-51 N900 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Nokia N95: Mozilla/5.0 (SymbianOS/9.2; U; Series60/3.1 NokiaN95/11.0.026; Profile MIDP-2.0 Configuration/CLDC-1.1) AppleWebKit/413 (KHTML, like Gecko) Safari/413 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Nokia N97-3: Mozilla/5.0 (SymbianOS/9.4; Series60/5.0 NokiaN97-3/21.2.045; Profile/MIDP-2.1 Configuration/CLDC-1.1;) AppleWebKit/525 (KHTML, like Gecko) BrowserNG/7.1.4 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'nokia_e65 (partial string)';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Note that Nokia Symbian phones may have two different user-agent strings, one for the classical WAP like:';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Opera Mini on Samsung Z720: Opera/9.50 (J2ME/MIDP; Opera Mini/4.1.11355/542; U; en) ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'P3450: Mozilla/4.0 (compatible; MSIE 6.0; Windows CE; IEMobile 6.12) PPC; 240x320; HTC P3450; OpVer 23.116.1.611 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'P3650: HTC_P3650 Mozilla/4.0 (compatible; MSIE 6.0; Windows CE; IEMobile 7.6) ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Pixi: Mozilla/5.0 (webOS/Palm webOS 1.2.9; U; en-US) AppleWebKit/525.27.1 (KHTML, like Gecko) Version/1.0 Safari/525.27.1 Pixi/1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Pre: Mozilla/5.0 (webOS/1.4.0; U; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Version/1.0 Safari/532.2 Pre/1.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'S68: SIE-S68/36 UP.Browser/7.1.0.e.18 (GUI) MMP/2.0 Profile/MIDP-2.0 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'S710: Mozilla/4.0 (compatible; MSIE 6.0; Windows CE; IEMobile 7.6) SP; 240x320; HTC_S710/1.0 ... ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Samsung A737: SAMSUNG-SGH-A737/1.0 SHP/VPP/R5 NetFront/3.3 SMM-MMS/1.2.0 profile/MIDP-2.0 configuration/CLDC-1.1 UP.Link/6.3.0.0.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Samsung A737: SAMSUNG-SGH-A737/UCGI3 SHP/VPP/R5 NetFront/3.4 SMM-MMS/1.2.0 profile/MIDP-2.0 configuration/CLDC-1.1 UP.Link/6.3.1.17.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Samsung A767: SAMSUNG-SGH-A767/A767UCHG2 SHP/VPP/R5 NetFront/3.4 SMM-MMS/1.2.0 profile/MIDP-2.0 configuration/CLDC-1.1 UP.Link/6.3.0.0.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Samsung A867: SAMSUNG-SGH-A867/A867UCHG5 SHP/VPP/R5 NetFront/3.4 SMM-MMS/1.2.0 profile/MIDP-2.0 configuration/CLDC-1.1 UP.Link/6.3.0.0.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Samsung A877: SAMSUNG-SGH-A877/A877UCHK1 SHP/VPP/R5 NetFront/3.5 SMM-MMS/1.2.0 profile/MIDP-2.1 configuration/CLDC-1.1 UP.Link/6.3.0.0.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Samsung Captivate (Galaxy for AT&T): Mozilla/5.0 (Linux; U; Android 2.1-update1; en-us; SAMSUNG-SGH-I897/I897UCJF6 Build/ECLAIR) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Samsung D600: SAMSUNG-SGH-D600/1.0 Profile/MIDP-2.0 Configuration/CLDC-1.1 UP.Browser/6.2.3.3.c.1.101 (GUI) MMP/2.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Samsung GT-S5230: SAMSUNG-GT-S5230/S523MXEIL2 SHP/VPP/R5 Jasmine/1.0 Nextreaming SMM-MMS/1.2.0 profile/MIDP-2.1 configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Samsung i617: SAMSUNG-SGH-I617/1.0 Mozilla/4.0 (compatible; MSIE 6.0; Windows CE; IEMobile 6.12) UP.Link/6.3.0.0.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Samsung i7500 Galaxy: Mozilla/5.0 (Linux; U; Android 1.5; de-de; Galaxy Build/CUPCAKE) AppleWebkit/528.5+ (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Samsung i9000 aka Galaxy S : Mozilla/5.0 (Linux; U; Android 2.1-update1; fr-fr; GT-I9000 Build/ECLAIR) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Samsung SGH-E250: SAMSUNG-SGH-E250/1.0 Profile/MIDP-2.0 Configuration/CLDC-1.1 UP.Browser/6.2.3.3.c.1.101 (GUI) MMP/2.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Samsung SGH-i900 Omnia: SAMSUNG-SGH-i900/1.0 Opera 9.5 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Samsung SGH-i907: SAMSUNG-SGH-i907/UCHI5 Mozilla/4.0 (compatible; MSIE 6.0; Windows CE; IEMobile 7.11) ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Samsung SGH-T919: SAMSUNG-SGH-T919/919UVHL3SHP/VPP/R5NetFront/3.5SMM-MMS/1.2.0profile/MIDP-2.1configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Samsung SGH-U600: SEC-SGHU600/1.0 NetFront/3.2 Profile ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Samsung SGH-U900: SAMSUNG-SGH-U900-Vodafone/U900BUHD6 SHP/VPP/R5 NetFront/3.4 Qtv5.3 SMM-MMS/1.2.0 profile/MIDP-2.0 configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Samsung Z720: SAMSUNG-SGH-Z720/1.0 SHP/VPR/R5 NetFront/3.3 SMM-MMS/1.2.0 profile/MIDP-2.0 configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Smart: HTC_Smart_F3188 Mozilla/5.0 (like Gecko) Obigo/Q7 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'SonyEricsson C901: SonyEricssonC901/R1EA Browser/NetFront/3.4 Profile/MIDP-2.1 Configuration/CLDC-1.1 JavaPlatform/JP-8.4.2 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'SonyEricsson C905: SonyEricssonC905/R1FA Browser/NetFront/3.4 Profile/MIDP-2.1 Configuration/CLDC-1.1 JavaPlatform/JP-8.4.3 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'SonyEricsson C905a: SonyEricssonC905a/R1FA Browser/NetFront/3.4 Profile/MIDP-2.1 Configuration/CLDC-1.1 JavaPlatform/JP-8.4.3 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'SonyEricsson K510i: SonyEricssonK510i/R4CJ Browser/NetFront/3.3 Profile/MIDP-2.0 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'SonyEricsson K550i: SonyEricssonK550i/R8BA Browser/NetFront/3.3 Profile/MIDP-2.0 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'SonyEricsson K600i: SonyEricssonK600i/R2BA Browser/SEMC-Browser/4.2 Profile/MIDP-2.0 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'SonyEricsson K610i: SonyEricssonK610i/R1CB Browser/NetFront/3.3 Profile/MIDP-2.0 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'SonyEricsson K630i: SonyEricssonK630i/R1CA Browser/NetFront/3.4 Profile/MIDP-2.1 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'SonyEricsson K700: SonyEricssonK700/R1A Profile/MIDP-1.0 MIDP-2.0 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'SonyEricsson K750i: SonyEricssonK750i/R1CA Browser/SEMC-Browser/4.2 Profile/MIDP-2.0 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'SonyEricsson K790i: SonyEricssonK790i/R8BF Browser/NetFront/3.3 Profile/MIDP-2.0 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'SonyEricsson K800i: SonyEricssonK800i/R8BF Browser/NetFront/3.3 Profile/MIDP-2.0 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'SonyEricsson W800i: SonyEricssonW800i/R1AA Browser/SEMC-Browser/4.2 Profile/MIDP-2.0 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'SonyEricsson W810i: SonyEricssonW810i/MIDP-2.0 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'SonyEricsson W900i: SonyEricssonW900i/R5AH Browser/NetFront/3.3 Profile/MIDP-2.0 Configuration/CLDC-1.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'SonyEricsson W995i: SonyEricssonW995/R1DB Browser/NetFront/3.4 Profile/MIDP-2.1 Configuration/CLDC-1.1 JavaPlatform/JP-8.4.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'SonyEricsson X10: Mozilla/5.0 (Linux; U; Android 1.6; es-es; SonyEricssonX10i Build/R1FA016) AppleWebKit/528.5+ (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'SonyEricsson Z500a: SonyEricssonZ500a/R1A SEMC-Browser/4.0.1 Profile/MIDP-2.0 Configuration/CLDC-1.1 UP.Link/6.3.1.20.0 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Tattoo: Mozilla/5.0 (Linux; U; Android 1.6; en-us; HTC_TATTOO_A3288 Build/DRC79) AppleWebKit/528.5+ (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'The string that is presented depends on the APN settings that are used for browsing (WAP/ISP). As the traditional browser string does not usually give any clues as to the type of device, the user-agent alone is not a guaranteed method of identifying Nokia devices. However, when the traditional browser user-agent is used, Nokia devices also provide the x-Device-User-Agent header, which contains the device specific user-agent.';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'Treo 650: Mozilla/4.0 (compatible; MSIE 6.0; Windows 98; PalmSource/hspr-H102; Blazer/4.0) 16;320x320 ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        $userAgent = 'V8301: ZTE-V8301/MB6801_V1_Z1_VN_F1BPa101 Profile/MIDP-2.0 Configuration/CLDC-1.1 Obigo/Q03C ';
+        $this->assertTrue(Zend_Http_UserAgent_Mobile::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+    
+    }
+
+    public function testMatchBot()
+    {
+        $userAgent = 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)';
+        $this->assertTrue(Zend_Http_UserAgent_Bot::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        
+        $userAgent = 'Googlebot/2.1 (+http://www.googlebot.com/bot.html)';
+        $this->assertTrue(Zend_Http_UserAgent_Bot::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        
+        $userAgent = 'Mozilla/5.0 (compatible; Yahoo! Slurp; http://help.yahoo.com/help/us/ysearch/slurp)';
+        $this->assertTrue(Zend_Http_UserAgent_Bot::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        
+        $userAgent = 'YahooSeeker/1.2 (compatible; Mozilla 4.0; MSIE 5.5; yahooseeker at yahoo-inc dot com ; http://help.yahoo.com/help/us/shop/merchant/)';
+        $this->assertTrue(Zend_Http_UserAgent_Bot::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        
+        $userAgent = 'Mozilla/4.0 compatible ZyBorg/1.0 (wn.zyborg@looksmart.net; http://www.WISEnutbot.com)';
+        $this->assertTrue(Zend_Http_UserAgent_Bot::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+    }
+
+    public function testMatchChecker()
+    {
+        $userAgent = 'Mozilla/5.0 (compatible; AbiLogicBot/1.0; +http://www.abilogic.com/bot.html)';
+        $this->assertTrue(Zend_Http_UserAgent_Checker::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        
+        $userAgent = 'W3C-checklink/4.5 [4.160] libwww-perl/5.823';
+        $this->assertTrue(Zend_Http_UserAgent_Checker::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+    }
+
+    public function testMatchConsole()
+    {
+        $userAgent = 'Mozilla/5.0 (PLAYSTATION 3; 1.10)';
+        $this->assertTrue(Zend_Http_UserAgent_Console::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        
+        $userAgent = 'Opera/9.30 (Nintendo Wii; U; ; 2071; Wii Shop Channel/1.0; en)';
+        $this->assertTrue(Zend_Http_UserAgent_Console::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+    }
+
+    public function testMatchEmail()
+    {
+        $userAgent = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.1.9) Gecko/20100317 Lightning/1.0b1 Thunderbird/3.0.4';
+        $this->assertTrue(Zend_Http_UserAgent_Email::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+    }
+
+    public function testMatchFeed()
+    {
+        $userAgent = 'Bloglines/3.0-rho (http://www.bloglines.com; 3 subscribers)';
+        $this->assertTrue(Zend_Http_UserAgent_Feed::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+    }
+
+    public function testMatchOffline()
+    {
+        $userAgent = 'Offline Explorer/2.5';
+        $this->assertTrue(Zend_Http_UserAgent_Offline::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        
+        $userAgent = 'Wget/1.9.1';
+        $this->assertTrue(Zend_Http_UserAgent_Offline::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        
+        $userAgent = 'WebCopier v3.2a';
+        $this->assertTrue(Zend_Http_UserAgent_Offline::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+    }
+
+    public function _testMatchProbe()
+    {
+        $this->markTestIncomplete();
+    }
+
+    public function _testMatchSpam()
+    {
+        $this->markTestIncomplete();
+    }
+
+    public function testMatchText()
+    {
+        $userAgent = 'Lynx/2.8.6rel.4 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/0.9.8k';
+        $this->assertTrue(Zend_Http_UserAgent_Text::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+        
+        $userAgent = 'w3m/0.5.1+cvs-1.968';
+        $this->assertTrue(Zend_Http_UserAgent_Text::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+    }
+
+    public function testMatchValidator()
+    {
+        $userAgent = 'CSE HTML Validator Lite Online (http://online.htmlvalidator.com/php/onlinevallite.php)';
+        $this->assertTrue(Zend_Http_UserAgent_Validator::match($userAgent,array('HTTP_USER_AGENT'=>$userAgent)));
+    }
+}

+ 62 - 0
tests/Zend/Http/UserAgent/AllTests.php

@@ -0,0 +1,62 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+
+require_once dirname(__FILE__) . '/../../../TestHelper.php';
+
+if (!defined('PHPUnit_MAIN_METHOD')) {
+    define('PHPUnit_MAIN_METHOD', 'Zend_Http_UserAgent_AllTests::main');
+}
+
+require_once 'Zend/Http/UserAgentTest.php';
+require_once 'Zend/Http/UserAgent/AbstractDeviceTest.php';
+require_once 'Zend/Http/UserAgent/Features/Adapter/WurflApiTest.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @group      Zend_Http
+ */
+class Zend_Http_AllTests
+{
+    public static function main()
+    {
+        PHPUnit_TextUI_TestRunner::run(self::suite());
+    }
+
+    public static function suite()
+    {
+        $suite = new PHPUnit_Framework_TestSuite('Zend Framework - Zend_Http - UserAgent');
+
+        $suite->addTestSuite('Zend_Http_UserAgentTest');
+        $suite->addTestSuite('Zend_Http_UserAgent_AbstractDeviceTest');
+        $suite->addTestSuite('Zend_Http_UserAgent_Features_Adapter_WurflApiTest');
+
+        return $suite;
+    }
+}
+
+if (PHPUnit_MAIN_METHOD == 'Zend_Http_UserAgent_AllTests::main') {
+    Zend_Http_UserAgent_AllTests::main();
+}

+ 67 - 0
tests/Zend/Http/UserAgent/Features/Adapter/WurflApiTest.php

@@ -0,0 +1,67 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http_UserAgent
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+require_once dirname(__FILE__) . '/../../../../../TestHelper.php';
+
+/**
+ * Zend_Http_UserAgent
+ */
+require_once 'Zend/Http/UserAgent.php';
+require_once 'Zend/Http/UserAgent/Features/Adapter/WurflApi.php';
+
+/** to generate the cache */
+set_time_limit(0);
+
+/**
+ * @category   Zend
+ * @package    Zend_Http_UserAgent
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Http_UserAgent_Features_Adapter_WurflApiTest extends PHPUnit_Framework_TestCase
+{
+
+    public function setUp()
+    {
+        if (!constant('TESTS_ZEND_HTTP_USERAGENT_WURFL_LIB_DIR')
+            || !constant('TESTS_ZEND_HTTP_USERAGENT_WURFL_CONFIG_FILE')
+        ) {
+            $this->markTestSkipped('Requires WURFL library');
+        }
+        $this->config['wurflapi']['wurfl_lib_dir']     = constant('TESTS_ZEND_HTTP_USERAGENT_WURFL_LIB_DIR');
+        $this->config['wurflapi']['wurfl_config_file'] = constant('TESTS_ZEND_HTTP_USERAGENT_WURFL_CONFIG_FILE');
+    }
+
+    public function testGetFromRequest()
+    {
+        $request['http_user_agent'] = 'Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleW1ebKit/420.1 (KHTML, like Gecko) Version/3.0 Mobile/4A102 Safari/419.3';
+        $wurfl = Zend_Http_UserAgent_Features_Adapter_WurflApi::getFromRequest($request, $this->config);
+        $this->assertEquals('Safari', $wurfl['mobile_browser']);
+        $this->assertEquals('iPhone OS', $wurfl['device_os']);
+        $this->assertEquals('1.0', $wurfl['device_os_version']);
+        $this->assertEquals('true', $wurfl['has_qwerty_keyboard']);
+        $this->assertEquals('touchscreen', $wurfl['pointing_method']);
+        $this->assertEquals('false', $wurfl['is_tablet']);
+        $this->assertEquals('iPhone', $wurfl['model_name']);
+        $this->assertEquals('Apple', $wurfl['brand_name']);
+    }
+}

+ 554 - 0
tests/Zend/Http/UserAgentTest.php

@@ -0,0 +1,554 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http_UserAgent
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id: JsonTest.php 12081 2008-10-22 19:07:55Z norm2782 $
+ */
+
+require_once dirname ( __FILE__ ) . '/../../TestHelper.php';
+
+require_once 'Zend/Config.php';
+require_once 'Zend/Http/UserAgent.php';
+require_once 'Zend/Http/UserAgent/Mobile.php';
+require_once 'Zend/Http/UserAgent/Storage/NonPersistent.php';
+
+require_once dirname(__FILE__) . '/TestAsset/TestPluginLoader.php';
+require_once dirname(__FILE__) . '/TestAsset/DesktopDevice.php';
+require_once dirname(__FILE__) . '/TestAsset/InvalidDevice.php';
+require_once dirname(__FILE__) . '/TestAsset/PopulatedStorage.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Http_UserAgent
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Http_UserAgentTest extends PHPUnit_Framework_TestCase
+{
+
+    public function setUp()
+    {
+        $this->server                         = array();
+        $this->server['os']                   = 'Windows_NT';
+        $this->server['http_accept']          = '*/*';
+        $this->server['http_accept_language'] = 'fr-FR';
+        $this->server['http_accept_encoding'] = 'gzip, deflate';
+        $this->server['http_user_agent']      = 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)';
+        $this->server['http_host']            = 'zfmobile';
+        $this->server['http_connection']      = 'Keep-Alive';
+        $this->server['http_cookie']          = 'ZDEDebuggerPresent=php,phtml,php3';
+        $this->server['server_signature']     = '';
+        $this->server['server_software']      = 'Apache/2.2.12 (Win32) mod_ssl/2.2.12 OpenSSL/0.9.8k';
+        $this->server['server_name']          = 'zfmobile';
+        $this->server['server_addr']          = '127.0.0.1';
+        $this->server['server_port']          = '80';
+        $this->server['remote_addr']          = '127.0.0.1';
+        $this->server['server_protocol']      = 'HTTP/1.1';
+        
+        $this->config                         = array(
+            'server' => &$this->server,
+            'storage'               => array(
+                'adapter'           => 'NonPersistent',
+            ),
+            'wurflapi'              => array(
+                'wurfl_lib_dir'     => constant('TESTS_ZEND_HTTP_USERAGENT_WURFL_LIB_DIR'),
+                'wurfl_config_file' => constant('TESTS_ZEND_HTTP_USERAGENT_WURFL_CONFIG_FILE'),
+            ),
+        );
+    }
+
+    public function testMatchUserAgentSimple()
+    {
+        $config = $this->config;
+        $config['server']['server_software'] = 'Apache/2';
+        $userAgent = new Zend_Http_UserAgent($config);
+        $device    = $userAgent->getDevice();
+        
+        $this->assertEquals('desktop', $userAgent->getBrowserType());
+        $this->assertEquals('Internet Explorer', $device->getFeature('browser_name'));
+        $this->assertEquals('7.0', $device->getFeature('browser_version'));
+        $this->assertEquals('Internet Explorer', $device->getFeature('browser_compatibility'));
+        $this->assertEquals('MSIE', $device->getFeature('browser_engine'));
+        $this->assertEquals('Windows XP', $device->getFeature('device_os_name'));
+        $this->assertEquals('Windows NT 5.1', $device->getFeature('device_os_token'));
+        $this->assertEquals('apache', $device->getFeature('server_os'));
+        $this->assertEquals('2', $device->getFeature('server_os_version'));
+    }
+
+    public function testMatchUserAgentServer()
+    {
+        $config = $this->config;
+        $config['server']['os']              = 'Windows_NT';
+        $config['server']['http_accept']     = '*/*';
+        $config['server']['http_user_agent'] = 'Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 5.1)';
+        $config['server']['server_software'] = 'Apache/99';
+        $config['user_agent']                = $config['server']["http_user_agent"];
+        $userAgent = new Zend_Http_UserAgent($config);
+        $device    = $userAgent->getDevice();
+
+        $this->assertEquals('desktop', $userAgent->getBrowserType());
+        $this->assertEquals('Internet Explorer', $device->getFeature('browser_name'));
+        $this->assertEquals('9.0', $device->getFeature('browser_version'));
+        $this->assertEquals('Internet Explorer', $device->getFeature('browser_compatibility'));
+        $this->assertEquals('MSIE', $device->getFeature('browser_engine'));
+        $this->assertEquals('Windows XP', $device->getFeature('device_os_name'));
+        $this->assertEquals('Windows NT 5.1', $device->getFeature('device_os_token'));
+        $this->assertEquals('apache', $device->getFeature('server_os'));
+        $this->assertEquals('99', $device->getFeature('server_os_version'));
+    }
+
+    public function testUserAgentDefineIdentificationSequence()
+    {
+        if (!constant('TESTS_ZEND_HTTP_USERAGENT_WURFL_LIB_DIR')) {
+            $this->markTestSkipped('Depends on WURFL support');
+        }
+        $config = $this->config;
+        $config['user_agent'] = 'Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleW1ebKit/420.1 (KHTML, like Gecko) Version/3.0 Mobile/4A102 Safari/419.3';
+
+        $userAgent = new Zend_Http_UserAgent($config);
+        $device    = $userAgent->getDevice();
+        $this->assertType('Zend_Http_UserAgent_Mobile', $device);
+        $this->assertEquals('mobile', $userAgent->getBrowserType());
+        $this->assertEquals('Safari', $userAgent->getDevice()->getFeature('mobile_browser'));
+        $this->assertEquals('iPhone OS', $userAgent->getDevice()->getFeature('device_os'));
+        $this->assertEquals('true', $userAgent->getDevice()->getFeature('has_qwerty_keyboard'));
+        $this->assertEquals('touchscreen', $userAgent->getDevice()->getFeature('pointing_method'));
+        $this->assertEquals('false', $userAgent->getDevice()->getFeature('is_tablet'));
+        $this->assertEquals('iPhone', $userAgent->getDevice()->getFeature('model_name'));
+        $this->assertEquals('Apple', $userAgent->getDevice()->getFeature('brand_name'));
+    }
+
+    public function testUserAgentDefineStorage()
+    {
+        $config = array(
+            'storage' => array('adapter' => 'NonPersistent'),
+            'server'  => $this->server,
+        );
+        $oUserAgent      = new Zend_Http_UserAgent($config);
+        $browser         = $oUserAgent->getUserAgent();
+        $this->assertType('Zend_Http_UserAgent_Storage_NonPersistent', $oUserAgent->getStorage($browser));
+    }
+
+    public function testUserAgentFeatureAdapter()
+    {
+        $config = $this->config;
+        $config['mobile']['features']['path']      = dirname(__FILE__) . '/TestAsset/Device/Browser/Features/Adapter.php';
+        $config['mobile']['features']['classname'] = 'Device_Browser_Features_Adapter';
+        $config['user_agent'] = 'Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleW1ebKit/420.1 (KHTML, like Gecko) Version/3.0 Mobile/4A102 Safari/419.3';
+
+        $userAgent = new Zend_Http_UserAgent($config);
+
+        $config = $userAgent->getConfig();
+        $this->assertContains('Device/Browser/Features/Adapter.php', $config['mobile']['features']['path']);
+    }
+
+    public function testSetDefaultConfigAlone()
+    {
+        $config['server'] = $this->server;
+        $userAgent = new Zend_Http_UserAgent($config);
+        $config = $userAgent->getConfig();
+        $this->assertEquals(Zend_Http_UserAgent::DEFAULT_IDENTIFICATION_SEQUENCE, $config['identification_sequence']);
+        $this->assertEquals(Zend_Http_UserAgent::DEFAULT_PERSISTENT_STORAGE_ADAPTER, $config['storage']['adapter']);
+    }
+
+    public function testSetDefaultConfigStorage()
+    {
+        $config     = array('identification_sequence' => 'Test');
+        $oUserAgent = new Zend_Http_UserAgent($config);
+
+        $test = $oUserAgent->getConfig();
+        $this->assertEquals('Test', $test['identification_sequence']);
+        $this->assertEquals(Zend_Http_UserAgent::DEFAULT_PERSISTENT_STORAGE_ADAPTER, $test['storage']['adapter']);
+    }
+
+    public function testSetDefaultConfigBoth()
+    {
+        $config = array(
+            'identification_sequence'    => 'Test',
+            'storage' => array('adapter' => 'NonPersistent'),
+        );
+        $oUserAgent = new Zend_Http_UserAgent($config);
+        $test       = $oUserAgent->getConfig();
+        $this->assertEquals('Test', $test['identification_sequence']);
+        $this->assertEquals('NonPersistent', $test['storage']['adapter']);
+    }
+
+    public function testDeviceClassNameMatchesBrowserTypeIfUserAgentMatches()
+    {
+        if (!constant('TESTS_ZEND_HTTP_USERAGENT_WURFL_LIB_DIR')) {
+            $this->markTestSkipped('Depends on WURFL support');
+        }
+        $this->config['browser_type'] = 'MoBiLe';
+        $this->config['user_agent']   = 'Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleW1ebKit/420.1 (KHTML, like Gecko) Version/3.0 Mobile/4A102 Safari/419.3';
+        $userAgent = new Zend_Http_UserAgent($this->config);
+        $className = get_class($userAgent->getDevice());
+        $this->assertEquals('Zend_Http_UserAgent_Mobile', $className);
+    }
+
+    public function testDeviceClassNameMatchesDesktopTypeIfUserAgentDoesNotMatch()
+    {
+        $config = array(
+            'browser_type' => 'MoBiLe',
+        );
+        $userAgent = new Zend_Http_UserAgent($config);
+        $className = get_class($userAgent->getDevice());
+        $this->assertEquals('Zend_Http_UserAgent_Desktop', $className);
+    }
+
+    public function testUserAgentFromServerSuperglobalWhenNotProvided()
+    {
+        $_SERVER['HTTP_USER_AGENT'] = 'UserAgentTest2';
+        $ua = new Zend_Http_UserAgent();
+        $this->assertEquals('UserAgentTest2', $ua->getServerValue('http_user_agent'));
+        $this->assertEquals('UserAgentTest2', $ua->getUserAgent());
+    }
+
+    public function testAllowsPassingUserAgentDirectly()
+    {
+        $this->config['user_agent'] = 'UserAgentTest2';
+        $ua = new Zend_Http_UserAgent($this->config);
+        $this->assertEquals('UserAgentTest2', $ua->getUserAgent());
+    }
+
+    public function testAllowsSettingUserAgentManually()
+    {
+        $ua = new Zend_Http_UserAgent();
+        $ua->setUserAgent('userAgentTest');
+        $this->assertEquals('userAgentTest', $ua->getServerValue('HTTP_USER_AGENT'));
+        $this->assertEquals('userAgentTest', $ua->getUserAgent());
+    }
+
+    public function testUsesHttpAcceptConstantValueByDefault()
+    {
+        unset($this->server['http_accept']);
+        $ua = new Zend_Http_UserAgent();
+        $this->assertEquals(Zend_Http_UserAgent::DEFAULT_HTTP_ACCEPT, $ua->getHttpAccept());
+        $this->assertEquals(Zend_Http_UserAgent::DEFAULT_HTTP_ACCEPT, $ua->getServerValue('HTTP_ACCEPT'));
+    }
+
+    public function testUsesServerHttpAcceptValueWhenPresent()
+    {
+        $_SERVER['HTTP_ACCEPT'] = 'HttpAcceptTest2';
+        $ua = new Zend_Http_UserAgent();
+        $this->assertEquals('HttpAcceptTest2', $ua->getHttpAccept());
+        $this->assertEquals('HttpAcceptTest2', $ua->getServerValue('HTTP_ACCEPT'));
+    }
+
+    public function testAllowsPassingHttpAcceptValueViaConfiguration()
+    {
+        $this->config['http_accept'] = 'HttpAcceptTest';
+        $ua = new Zend_Http_UserAgent($this->config);
+        $this->assertEquals('HttpAcceptTest', $ua->getHttpAccept());
+        $this->assertEquals('HttpAcceptTest', $ua->getServerValue('HTTP_ACCEPT'));
+    }
+
+    public function testAllowsSettingHttpAcceptManually()
+    {
+        $ua = new Zend_Http_UserAgent();
+        $ua->setHttpAccept('httpAcceptTest');
+        $this->assertEquals('httpAcceptTest', $ua->getHttpAccept());
+        $this->assertEquals('httpAcceptTest', $ua->getServerValue('HTTP_ACCEPT'));
+    }
+
+    public function testCanSetConfigWithConfigObject()
+    {
+        $config = new Zend_Config($this->config);
+        $ua     = new Zend_Http_UserAgent($config);
+        $test   = $ua->getConfig();
+        $this->assertEquals($config->storage->adapter, $test['storage']['adapter']);
+    }
+
+    public function testCanSetConfigWithTraversableObject()
+    {
+        $config = new ArrayObject($this->config);
+        $ua     = new Zend_Http_UserAgent($config);
+        $test   = $ua->getConfig();
+        $this->assertEquals($config['storage'], $test['storage']);
+    }
+
+    public function invalidConfigs()
+    {
+        return array(
+            array(true),
+            array(1),
+            array(1.0),
+            array(new stdClass),
+        );
+    }
+
+    /**
+     * @dataProvider invalidConfigs
+     */
+    public function testSettingConfigWithInvalidTypeRaisesException($arg)
+    {
+        $this->setExpectedException('Zend_Http_UserAgent_Exception', 'expected array');
+        $ua = new Zend_Http_UserAgent($arg);
+    }
+
+    public function testAllowsSettingServerWithArrayObject()
+    {
+        $server = new ArrayObject($this->server);
+        $ua = new Zend_Http_UserAgent(array('server' => $server));
+        $this->assertEquals($server['os'], $ua->getServerValue('os'));
+    }
+
+    public function testAllowsSettingServerWithTraversableObject()
+    {
+        $server = new ArrayIterator($this->server);
+        $ua = new Zend_Http_UserAgent(array('server' => $server));
+        $this->assertEquals($this->server['os'], $ua->getServerValue('os'));
+    }
+
+    /**
+     * @dataProvider invalidConfigs
+     */
+    public function testSettingServerWithInvalidTypeRaisesException($arg)
+    {
+        $this->setExpectedException('Zend_Http_UserAgent_Exception', 'array or object implementing Traversable');
+        $ua = new Zend_Http_UserAgent(array('server' => $arg));
+    }
+
+    public function testAllowsSettingPluginLoaderUsingClassname()
+    {
+        $ua = new Zend_Http_UserAgent();
+        $ua->setPluginLoader('device', 'Zend_Http_TestAsset_TestPluginLoader');
+        $loader = $ua->getPluginLoader('device');
+        $this->assertType('Zend_Http_TestAsset_TestPluginLoader', $loader);
+    }
+
+    public function testSpecifyingInvalidPluginLoaderClassNameRaisesException()
+    {
+        $ua = new Zend_Http_UserAgent();
+        $this->setExpectedException('Zend_Http_UserAgent_Exception', 'extending Zend_Loader_PluginLoader');
+        $ua->setPluginLoader('device', 'Zend_Http_TestAsset_InvalidPluginLoader');
+    }
+
+    public function invalidLoaders()
+    {
+        return array(
+            array(true),
+            array(1),
+            array(1.0),
+            array(array()),
+        );
+    }
+
+    /**
+     * @dataProvider invalidLoaders
+     */
+    public function testSpecifyingInvalidTypeToPluginLoaderRaisesException($arg)
+    {
+        $ua = new Zend_Http_UserAgent();
+        $this->setExpectedException('Zend_Http_UserAgent_Exception', 'class or object');
+        $ua->setPluginLoader('device', $arg);
+    }
+
+    public function testSpecifyingNonPluginLoaderObjectRaisesException()
+    {
+        $ua = new Zend_Http_UserAgent();
+        $this->setExpectedException('Zend_Http_UserAgent_Exception', 'extending Zend_Loader_PluginLoader');
+        $ua->setPluginLoader('device', $this);
+    }
+
+    public function testSpecifyingInvalidTypeWhenSettingPluginLoaderRaisesException()
+    {
+        $ua = new Zend_Http_UserAgent();
+        $this->setExpectedException('Zend_Http_UserAgent_Exception', 'plugin loader type');
+        $ua->setPluginLoader('__bogus__', new Zend_Loader_PluginLoader());
+    }
+
+    public function testAllowsSpecifyingPluginLoadersViaConfiguration()
+    {
+        $this->config['plugin_loader'] = array(
+            'device'  => 'Zend_Http_TestAsset_TestPluginLoader',
+            'storage' => 'Zend_Http_TestAsset_TestPluginLoader',
+        );
+        $ua = new Zend_Http_UserAgent($this->config);
+        $deviceLoader = $ua->getPluginLoader('device');
+        $this->assertType('Zend_Http_TestAsset_TestPluginLoader', $deviceLoader);
+        $storageLoader = $ua->getPluginLoader('storage');
+        $this->assertType('Zend_Http_TestAsset_TestPluginLoader', $storageLoader);
+        $this->assertNotSame($deviceLoader, $storageLoader);
+    }
+
+    public function testAllowsSpecifyingCustomDeviceClassesViaConfiguration()
+    {
+        $this->config['desktop'] = array(
+            'device' => array(
+                'classname' => 'Zend_Http_TestAsset_DesktopDevice',
+            ),
+        );
+        $this->config['user_agent'] = 'desktop';
+        $ua     = new Zend_Http_UserAgent($this->config);
+        $device = $ua->getDevice();
+        $this->assertType('Zend_Http_TestAsset_DesktopDevice', $device);
+    }
+
+    public function testAllowsSpecifyingCustomDeviceViaPrefixPath()
+    {
+        $this->config['desktop'] = array(
+            'device' => array(
+                'path'   => dirname(__FILE__) . '/TestAsset/Device',
+                'prefix' => 'Zend_Http_TestAsset_Device',
+            ),
+        );
+        $this->config['user_agent'] = 'desktop';
+        $ua     = new Zend_Http_UserAgent($this->config);
+        $device = $ua->getDevice();
+        $this->assertType('Zend_Http_TestAsset_Device_Desktop', $device);
+    }
+
+    public function testShouldRaiseExceptionOnInvalidDeviceClass()
+    {
+        $this->config['desktop'] = array(
+            'device' => array(
+                'classname' => 'Zend_Http_TestAsset_InvalidDevice',
+            ),
+        );
+        $this->config['user_agent'] = 'desktop';
+
+        $ua     = new Zend_Http_UserAgent($this->config);
+        $this->setExpectedException('Zend_Http_UserAgent_Exception', 'Zend_Http_UserAgent_Device');
+        $ua->getDevice();
+    }
+
+    public function testStorageContainsSerializedUserAgent()
+    {
+        $this->config['desktop'] = array(
+            'device' => array(
+                'classname' => 'Zend_Http_TestAsset_DesktopDevice',
+            ),
+        );
+        $this->config['user_agent'] = 'desktop';
+        $ua         = new Zend_Http_UserAgent($this->config);
+
+        // prime storage by retrieving device
+        $device     = $ua->getDevice();
+        $storage    = $ua->getStorage();
+        $serialized = $storage->read();
+
+        $test       = unserialize($serialized);
+        $this->assertEquals($ua->getBrowserType(), $test['browser_type']);
+        $this->assertEquals($ua->getConfig(), $test['config']);
+        $this->assertEquals('Zend_Http_TestAsset_DesktopDevice', $test['device_class']);
+        $this->assertEquals($ua->getUserAgent(), $test['user_agent']);
+        $this->assertEquals($ua->getHttpAccept(), $test['http_accept']);
+
+        $test   = unserialize($test['device']);
+        $this->assertEquals($device->getAllFeatures(), $test['_aFeatures']);
+        $this->assertEquals($device->getBrowser(), $test['_browser']);
+        $this->assertEquals($device->getBrowserVersion(), $test['_browserVersion']);
+        $this->assertEquals($device->getUserAgent(), $test['_userAgent']);
+        $this->assertEquals($device->getImages(), $test['_images']);
+    }
+
+    public function testCanPopulateFromStorage()
+    {
+        $this->config['storage']['adapter'] = 'Zend_Http_TestAsset_PopulatedStorage';
+        $this->config['user_agent'] = 'desktop';
+        $ua         = new Zend_Http_UserAgent($this->config);
+        $storage    = $ua->getStorage();
+        $this->assertType('Zend_Http_TestAsset_PopulatedStorage', $storage);
+        $device = $ua->getDevice();
+        $this->assertType('Zend_Http_TestAsset_DesktopDevice', $device);
+    }
+
+    public function testCanClearStorage()
+    {
+        $this->config['desktop'] = array(
+            'device' => array(
+                'classname' => 'Zend_Http_TestAsset_DesktopDevice',
+            ),
+        );
+        $this->config['user_agent'] = 'desktop';
+        $ua         = new Zend_Http_UserAgent($this->config);
+
+        // Prime storage by retrieving device
+        $device     = $ua->getDevice();
+        $storage    = $ua->getStorage();
+        $this->assertType('Zend_Http_UserAgent_Storage', $storage);
+        $this->assertFalse($storage->isEmpty());
+        $ua->clearStorage();
+        $this->assertTrue($storage->isEmpty());
+    }
+
+    public function testServerIsImmutableOnceDeviceRetrieved()
+    {
+        $config = $this->config;
+        $userAgent = new Zend_Http_UserAgent($config);
+        $device    = $userAgent->getDevice();
+
+        $this->setExpectedException('Zend_Http_UserAgent_Exception', 'immutable');
+        $userAgent->setServerValue('HTTP_ACCEPT', 'application/json');
+    }
+
+    public function testBrowserTypeIsImmutableOnceDeviceRetrieved()
+    {
+        $config = $this->config;
+        $userAgent = new Zend_Http_UserAgent($config);
+        $device    = $userAgent->getDevice();
+
+        $this->setExpectedException('Zend_Http_UserAgent_Exception', 'immutable');
+        $userAgent->setBrowserType('mobile');
+    }
+
+    public function testHttpAcceptIsImmutableOnceDeviceRetrieved()
+    {
+        $config = $this->config;
+        $userAgent = new Zend_Http_UserAgent($config);
+        $device    = $userAgent->getDevice();
+
+        $this->setExpectedException('Zend_Http_UserAgent_Exception', 'immutable');
+        $userAgent->setHttpAccept('application/json');
+    }
+
+    public function testUserAgentIsImmutableOnceDeviceIsRetrieved()
+    {
+        $config = $this->config;
+        $userAgent = new Zend_Http_UserAgent($config);
+        $device    = $userAgent->getDevice();
+
+        $this->setExpectedException('Zend_Http_UserAgent_Exception', 'immutable');
+        $userAgent->setUserAgent('userAgentTest');
+    }
+
+    public function testStorageIsImmutableOnceDeviceIsRetrieved()
+    {
+        $config = $this->config;
+        $userAgent = new Zend_Http_UserAgent($config);
+        $device    = $userAgent->getDevice();
+
+        $this->setExpectedException('Zend_Http_UserAgent_Exception', 'immutable');
+        $userAgent->setStorage(new Zend_Http_UserAgent_Storage_NonPersistent());
+    }
+
+    public function testAllowsPassingStorageConfigurationOptions()
+    {
+        $config = $this->config;
+        $config['storage']['adapter'] = 'Session';
+        $config['storage']['options'] = array(
+            'browser_type' => 'foobar',
+            'member'       => 'data',
+        );
+        $userAgent = new Zend_Http_UserAgent($config);
+        $storage   = $userAgent->getStorage();
+        $this->assertEquals('.foobar', $storage->getNamespace());
+        $this->assertEquals('data', $storage->getMember());
+    }
+}

+ 1 - 0
tests/Zend/Http/_files/serialized_device.txt

@@ -0,0 +1 @@
+a:6:{s:12:"browser_type";s:7:"desktop";s:6:"config";a:4:{s:23:"identification_sequence";s:14:"mobile,desktop";s:26:"persistent_storage_adapter";s:13:"NonPersistent";s:8:"wurflapi";a:2:{s:13:"wurfl_lib_dir";s:63:"/home/matthew/git/zf-standard/tests/Zend/Http/_files/Wurfl/1.1/";s:17:"wurfl_config_file";s:85:"/home/matthew/git/zf-standard/tests/Zend/Http/_files/Wurfl/resources/wurfl-config.php";}s:7:"desktop";a:1:{s:7:"matcher";a:1:{s:9:"classname";s:33:"Zend_Http_TestAsset_DesktopDevice";}}}s:12:"device_class";s:33:"Zend_Http_TestAsset_DesktopDevice";s:6:"device";s:793:"a:5:{s:10:"_aFeatures";a:19:{s:12:"browser_name";s:7:"desktop";s:12:"product_name";s:7:"desktop";s:10:"user_agent";s:7:"desktop";s:18:"is_wireless_device";b:0;s:9:"is_mobile";b:0;s:10:"is_desktop";b:1;s:9:"is_tablet";b:0;s:6:"is_bot";b:0;s:8:"is_email";b:0;s:7:"is_text";b:0;s:25:"device_claims_web_support";b:0;s:9:"client_ip";s:9:"127.0.0.1";s:11:"php_version";s:5:"5.3.1";s:9:"server_os";s:6:"apache";s:17:"server_os_version";s:6:"2.2.12";s:18:"server_http_accept";s:3:"*/*";s:27:"server_http_accept_language";s:5:"fr-FR";s:9:"server_ip";s:9:"127.0.0.1";s:11:"server_name";s:8:"zfmobile";}s:8:"_browser";s:7:"desktop";s:15:"_browserVersion";s:0:"";s:10:"_userAgent";s:7:"desktop";s:7:"_images";a:6:{i:0;s:4:"jpeg";i:1;s:3:"gif";i:2;s:3:"png";i:3;s:5:"pjpeg";i:4;s:5:"x-png";i:5;s:3:"bmp";}}";s:10:"user_agent";s:7:"desktop";s:11:"http_accept";s:3:"*/*";}

+ 0 - 0
tests/Zend/Http/_files/var/cache/mobile/.placeholder


+ 101 - 0
tests/Zend/Http/index.php

@@ -0,0 +1,101 @@
+<?php
+
+require_once 'Zend/Loader/Autoloader.php';
+Zend_Loader_Autoloader::getInstance();
+$autoloader = Zend_Loader_Autoloader::getInstance();
+
+error_reporting(E_ALL);
+set_time_limit(0);
+
+$config['config']['wurflapi']['wurfl_lib_dir'] = dirname(__FILE__) . '/_files/Wurfl/1.1/';
+$config['config']['wurflapi']['wurfl_config_file'] = dirname(__FILE__) . '/_files/Wurfl/resources/wurfl-config.php';
+$config['server'] = $_SERVER;
+
+if (!empty($_GET['userAgent'])) {
+    $config['server']['http_user_agent'] = $_GET['userAgent'];
+} else {
+    $_GET['userAgent'] = $_SERVER['HTTP_USER_AGENT'];
+}
+
+if (!empty($_GET['sequence'])) {
+    $config['config']['identification_sequence'] = $_GET['sequence'];
+}
+$oUserAgent = new Zend_Http_UserAgent($config);
+
+//$oUserAgent = Zend_Http_UserAgent::getInstance ();
+
+
+function printBrowserDetails($browser)
+{
+    $device = $browser->getDevice();
+    //Zend_Debug::dump($device->getAllFeatures());
+    if (isset($device)) {
+        print "<b>General informations</b>";
+        print "<ul>";
+        print "<li>Browser Type: " . $browser->getBrowserType() . "</li>";
+        print "<li>Browser Name: " . $device->getFeature('browser_name') . "</li>";
+        print "<li>Browser Version: " . $device->getFeature('browser_version') . "</li>";
+        print "<li>Browser Compatibility: " . $device->getFeature('browser_compatibility') . "</li>";
+        print "<li>Browser Engine: " . $device->getFeature('browser_engine') . "</li>";
+        print "<li>Device OS Name: " . $device->getFeature('device_os_name') . "</li>";
+        print "<li>Device OS token: " . $device->getFeature('device_os_token') . "</li>";
+        print "<li>Server Os: " . $device->getFeature('server_os') . "</li>";
+        print "<li>Server OS Version: " . $device->getFeature('server_os_version') . "</li>";
+        print "</ul>";
+        
+        $wurfl = $device->getFeature("mobile_browser");
+        if (!$wurfl) {
+            print "<b>no WURFL identification</b>";
+        } else {
+            print "<b>WURFL capabilities :</b>";
+            print "<ul>";
+            print "<li>WURFL ID: " . (isset($device->id) ? $device->id : "") . "</li>";
+            print "<li>Mobile browser: " . $device->getFeature("mobile_browser") . "</li>";
+            print "<li>Mobile browser version: " . $device->getFeature("mobile_browser_version") . "</li>";
+            print "<li>Device Brand Name: " . $device->getFeature("brand_name") . "</li>";
+            print "<li>Device Model Name: " . $device->getFeature('model_name') . "</li>";
+            print "<li>Device OS: " . $device->getFeature('device_os') . "</li>";
+            print "<li>Xhtml Preferred Markup:" . $device->getFeature('preferred_markup') . "</li>";
+            print "<li>Resolution Width:" . $device->getFeature('resolution_width') . "</li>";
+            print "<li>Resolution Height:" . $device->getFeature('resolution_height') . "</li>";
+            print "<li>MP3:" . $device->getFeature('mp3') . "</li>";
+            print "</ul>";
+        }
+        
+        print "<br /><br />";
+        print "<b>Full</b>";
+        Zend_Debug::dump($device->getAllFeatures());
+    }
+
+}
+
+?>
+
+<div id="content">
+
+<p><b>Query by providing the user agent:</b></p>
+<p>look at <a target="_blank"
+	href="http://www.useragentstring.com/pages/useragentstring.php">http://www.useragentstring.com/pages/useragentstring.php</a></p>
+<p>For mobile, look at <a target="_blank"
+	href="http://www.mobilemultimedia.be/">http://www.mobilemultimedia.be/</a></p>
+<fieldset>
+<form method="get">
+<div>Sequence : <select name="sequence">
+	<option value="">(standard)</option>
+	<option value="mobile, text, desktop">mobile, text, desktop</option>
+	<option value="bot, validator, checker, console, offline, email, text">bot,
+	validator, checker, console, offline, email, text</option>
+</select> (DON'T FORGET TO CLEAN SESSION COOKIE)<br />
+User Agent : <input type="text" name="userAgent" size="40"
+	value="<?=htmlentities($_GET['userAgent'])?>" /> <br />
+<input type="submit" /></div>
+</form>
+</fieldset>
+
+<?php
+if ($oUserAgent) {
+    printBrowserDetails($oUserAgent);
+}
+?>
+
+</div>

+ 2 - 0
tests/Zend/View/Helper/AllTests.php

@@ -70,6 +70,7 @@ require_once 'Zend/View/Helper/Placeholder/ContainerTest.php';
 require_once 'Zend/View/Helper/Placeholder/RegistryTest.php';
 require_once 'Zend/View/Helper/Placeholder/StandaloneContainerTest.php';
 require_once 'Zend/View/Helper/ServerUrlTest.php';
+require_once 'Zend/View/Helper/TinySrcTest.php';
 require_once 'Zend/View/Helper/TranslateTest.php';
 require_once 'Zend/View/Helper/UrlTest.php';
 
@@ -138,6 +139,7 @@ class Zend_View_Helper_AllTests
         $suite->addTestSuite('Zend_View_Helper_Placeholder_RegistryTest');
         $suite->addTestSuite('Zend_View_Helper_Placeholder_StandaloneContainerTest');
         $suite->addTestSuite('Zend_View_Helper_ServerUrlTest');
+        $suite->addTestSuite('Zend_View_Helper_TinySrcTest');
         $suite->addTestSuite('Zend_View_Helper_TranslateTest');
         $suite->addTestSuite('Zend_View_Helper_UrlTest');
 

+ 237 - 0
tests/Zend/View/Helper/TinySrcTest.php

@@ -0,0 +1,237 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_View
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+// Call Zend_View_Helper_TinySrcTest::main() if this source file is executed directly.
+if (!defined('PHPUnit_MAIN_METHOD')) {
+    define('PHPUnit_MAIN_METHOD', 'Zend_View_Helper_TinySrcTest::main');
+}
+require_once dirname(__FILE__) . '/../../../TestHelper.php';
+
+/**
+ * @see Zend_View_Helper_TinySrc
+ */
+require_once 'Zend/View/Helper/TinySrc.php';
+
+/**
+ * @see Zend_View
+ */
+require_once 'Zend/View.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_View
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @group      Zend_View
+ * @group      Zend_View_Helper
+ */
+class Zend_View_Helper_TinySrcTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * @var Zend_View_Helper_TinySrc
+     */
+    public $helper;
+
+    /**
+     * @var Zend_View
+     */
+    public $view;
+
+    /**
+     * Main
+     */
+    public static function main()
+    {
+        $suite  = new PHPUnit_Framework_TestSuite(__CLASS__);
+        $result = PHPUnit_TextUI_TestRunner::run($suite);
+    }
+
+    /**
+     * Prepares the environment before running a test.
+     */
+    protected function setUp()
+    {
+        $this->helper = new Zend_View_Helper_TinySrc();
+        $this->view   = new Zend_View();
+        $this->view->doctype()->setDoctype(strtoupper("XHTML1_STRICT"));
+        $this->helper->setView($this->view);
+    }
+
+    public function testCallingHelperMethodWithNoArgumentsReturnsHelperInstance()
+    {
+        $test = $this->helper->tinySrc();
+        $this->assertSame($this->helper, $test);
+    }
+
+    public function testHelperUsesServerAndBaseUrlFromHelpersByDefault()
+    {
+        $base   = $this->view->getHelper('baseUrl');
+        $base->setBaseUrl('/foo/bar');
+
+        $server = $this->view->getHelper('serverUrl');
+        $server->setScheme('https')
+               ->setHost('example.com:8080');
+
+        $test = $this->helper->getBaseUrl();
+        $this->assertEquals('https://example.com:8080/foo/bar/', $test);
+    }
+
+    public function testAllowsSettingDefaultFormat()
+    {
+        $this->helper->setDefaultFormat('png')
+                     ->setBaseUrl('http://example.com');
+        $image = $this->helper->tinySrc('foo.jpg');
+        $this->assertContains('/png/', $image);
+    }
+
+    public function testSettingInvalidDefaultFormatRaisesException()
+    {
+        $this->setExpectedException('Zend_View_Exception', 'Invalid format');
+        $this->helper->setDefaultFormat('gif');
+    }
+
+    public function testPassingNullValueToDefaultFormatClearsSetFormat()
+    {
+        $this->helper->setDefaultFormat('png')
+                     ->setBaseUrl('http://example.com');
+        $this->helper->setDefaultFormat(null);
+        $image = $this->helper->tinySrc('foo.jpg');
+        $this->assertNotContains('/png/', $image);
+    }
+
+    public function testAllowsPassingDefaultWidth()
+    {
+        $this->helper->setBaseUrl('http://example.com')
+                     ->setDefaultDimensions('-5');
+        $image = $this->helper->tinySrc('foo.jpg');
+        $this->assertContains('/-5/', $image);
+    }
+
+    /**
+     * @dataProvider invalidDimensions
+     */
+    public function testRaisesExceptionOnInvalidDefaultWidthValue($dim)
+    {
+        $this->setExpectedException('Zend_View_Exception', 'Invalid dimension');
+        $this->helper->setDefaultDimensions($dim);
+    }
+
+    public function testAllowsPassingDefaultWidthAndHeight()
+    {
+        $this->helper->setBaseUrl('http://example.com')
+                     ->setDefaultDimensions('5', 'x20');
+        $image = $this->helper->tinySrc('foo.jpg');
+        $this->assertContains('/5/x20/', $image);
+    }
+
+    /**
+     * @dataProvider invalidDimensions
+     */
+    public function testRaisesExceptionOnInvalidDefaultHeightValue($dim)
+    {
+        $this->setExpectedException('Zend_View_Exception', 'Invalid dimension');
+        $this->helper->setDefaultDimensions('10', $dim);
+    }
+
+    public function testPassingNullAsDefaultWidthValueClearsBothWidthAndHeight()
+    {
+        $this->helper->setBaseUrl('http://example.com')
+                     ->setDefaultDimensions('5', 'x20');
+        $this->helper->setDefaultDimensions(null, 'x20');
+        $image = $this->helper->tinySrc('foo.jpg');
+        $this->assertNotContains('/5/x20/', $image);
+        $this->assertNotContains('/5/', $image);
+        $this->assertNotContains('/x20/', $image);
+    }
+
+    public function testPassingNullAsDefaultHeightValueClearsHeight()
+    {
+        $this->helper->setBaseUrl('http://example.com')
+                     ->setDefaultDimensions('5', 'x20');
+        $this->helper->setDefaultDimensions('5');
+        $image = $this->helper->tinySrc('foo.jpg');
+        $this->assertNotContains('/5/x20/', $image);
+        $this->assertContains('/5/', $image);
+    }
+
+    public function testCreatesImageTagByDefault()
+    {
+        $this->helper->setBaseUrl('http://example.com');
+        $image = $this->helper->tinySrc('foo.jpg');
+        $this->assertContains('<img src="', $image);
+    }
+
+    public function testImageTagObeysDoctype()
+    {
+        $this->view->doctype('XHTML1_STRICT');
+        $this->helper->setBaseUrl('http://example.com');
+        $image = $this->helper->tinySrc('foo.jpg');
+        $this->assertContains('/>', $image);
+    }
+
+    public function testAllowsSpecifyingTagCreation()
+    {
+        $this->helper->setCreateTag(false);
+        $this->helper->setBaseUrl('http://example.com');
+        $image = $this->helper->tinySrc('foo.jpg');
+        $this->assertNotContains('<img src="', $image);
+    }
+
+    public function testPassingOptionsToHelperMethodOverridesDefaults()
+    {
+        $this->helper->setBaseUrl('http://example.com')
+                     ->setCreateTag(false)
+                     ->setDefaultDimensions(320, 480);
+        $image = $this->helper->tinySrc('foo.jpg', array(
+            'base_url'   => 'https://example.org:8080/public',
+            'format'     => 'png',
+            'width'      => 160,
+            'height'     => null,
+            'create_tag' => true,
+        ));
+        $this->assertContains('<img width="160" src="http://i.tinysrc.mobi/png/160/https://example.org:8080/public/foo.jpg"', $image);
+    }
+
+    public function testUnknownOptionsPassedToHelperMethodAreTreatedAsImageAttributes()
+    {
+        $this->helper->setBaseUrl('http://example.com');
+        $image = $this->helper->tinySrc('foo.jpg', array(
+            'alt'   => 'Alt text for image',
+        ));
+        $this->assertContains('alt="Alt text for image"', $image);
+    }
+
+    public function invalidDimensions()
+    {
+        return array(
+            array('foo'),
+            array(true),
+            array(array()),
+            array(new stdClass),
+        );
+    }
+}
+
+// Call Zend_View_Helper_TinySrcTest::main() if this source file is executed directly.
+if (PHPUnit_MAIN_METHOD == 'Zend_View_Helper_TinySrcTest::main') {
+    Zend_View_Helper_TinySrcTest::main();
+}