Add support for searching the GAL via activesync devices
authorMichael J. Rubinsky <mrubinsk@horde.org>
Thu, 6 May 2010 20:58:40 +0000 (16:58 -0400)
committerMichael J. Rubinsky <mrubinsk@horde.org>
Thu, 6 May 2010 20:59:10 +0000 (16:59 -0400)
framework/ActiveSync/lib/Horde/ActiveSync.php
framework/ActiveSync/lib/Horde/ActiveSync/Driver/Base.php
framework/ActiveSync/lib/Horde/ActiveSync/Driver/Horde.php
framework/ActiveSync/lib/Horde/ActiveSync/Driver/Horde/Connector/Registry.php
framework/ActiveSync/lib/Horde/ActiveSync/Request/Search.php [new file with mode: 0644]
framework/ActiveSync/package.xml

index d6b8c66..fa68ee8 100644 (file)
@@ -150,43 +150,6 @@ define("SYNC_VALIDATECERT_CERTIFICATECHAIN","ValidateCert:CertificateChain");
 define("SYNC_VALIDATECERT_CHECKCRL","ValidateCert:CheckCRL");
 define("SYNC_VALIDATECERT_STATUS","ValidateCert:Status");
 
-//Search
-define("SYNC_SEARCH_SEARCH", "Search:Search");
-define("SYNC_SEARCH_STORE", "Search:Store");
-define("SYNC_SEARCH_NAME", "Search:Name");
-define("SYNC_SEARCH_QUERY", "Search:Query");
-define("SYNC_SEARCH_OPTIONS", "Search:Options");
-define("SYNC_SEARCH_RANGE", "Search:Range");
-define("SYNC_SEARCH_STATUS", "Search:Status");
-define("SYNC_SEARCH_RESPONSE", "Search:Response");
-define("SYNC_SEARCH_RESULT", "Search:Result");
-define("SYNC_SEARCH_PROPERTIES", "Search:Properties");
-define("SYNC_SEARCH_TOTAL", "Search:Total");
-define("SYNC_SEARCH_EQUALTO", "Search:EqualTo");
-define("SYNC_SEARCH_VALUE", "Search:Value");
-define("SYNC_SEARCH_AND", "Search:And");
-define("SYNC_SEARCH_OR", "Search:Or");
-define("SYNC_SEARCH_FREETEXT", "Search:FreeText");
-define("SYNC_SEARCH_DEEPTRAVERSAL", "Search:DeepTraversal");
-define("SYNC_SEARCH_LONGID", "Search:LongId");
-define("SYNC_SEARCH_REBUILDRESULTS", "Search:RebuildResults");
-define("SYNC_SEARCH_LESSTHAN", "Search:LessThan");
-define("SYNC_SEARCH_GREATERTHAN", "Search:GreaterThan");
-define("SYNC_SEARCH_SCHEMA", "Search:Schema");
-define("SYNC_SEARCH_SUPPORTED", "Search:Supported");
-
-//GAL
-define("SYNC_GAL_DISPLAYNAME", "GAL:DisplayName");
-define("SYNC_GAL_PHONE", "GAL:Phone");
-define("SYNC_GAL_OFFICE", "GAL:Office");
-define("SYNC_GAL_TITLE", "GAL:Title");
-define("SYNC_GAL_COMPANY", "GAL:Company");
-define("SYNC_GAL_ALIAS", "GAL:Alias");
-define("SYNC_GAL_FIRSTNAME", "GAL:FirstName");
-define("SYNC_GAL_LASTNAME", "GAL:LastName");
-define("SYNC_GAL_HOMEPHONE", "GAL:HomePhone");
-define("SYNC_GAL_MOBILEPHONE", "GAL:MobilePhone");
-define("SYNC_GAL_EMAILADDRESS", "GAL:EmailAddress");
 
 /**
  * Main ActiveSync class. Entry point for performing all ActiveSync operations
@@ -279,7 +242,6 @@ class Horde_ActiveSync
     const FLAG_NEWMESSAGE = 'NewMessage';
 
     /* Folder types */
-    // Other constants
     const FOLDER_TYPE_OTHER =  1;
     const FOLDER_TYPE_INBOX =  2;
     const FOLDER_TYPE_DRAFTS =  3;
@@ -301,15 +263,30 @@ class Horde_ActiveSync
     const FOLDER_TYPE_RECIPIENT_CACHE =  19;
     const FOLDER_TYPE_DUMMY =  '__dummy.Folder.Id__';
 
+    /** Origin of changes **/
     const CHANGE_ORIGIN_PIM = 0;
     const CHANGE_ORIGIN_SERVER = 1;
     const CHANGE_ORIGIN_NA = 3;
 
+    /** Remote wipe **/
     const RWSTATUS_NA = 0;
     const RWSTATUS_OK = 1;
     const RWSTATUS_PENDING = 2;
     const RWSTATUS_WIPED = 3;
 
+    /** GAL **/
+    const GAL_DISPLAYNAME = 'GAL:DisplayName';
+    const GAL_PHONE = 'GAL:Phone';
+    const GAL_OFFICE = 'GAL:Office';
+    const GAL_TITLE = 'GAL:Title';
+    const GAL_COMPANY = 'GAL:Company';
+    const GAL_ALIAS = 'GAL:Alias';
+    const GAL_FIRSTNAME = 'GAL:FirstName';
+    const GAL_LASTNAME = 'GAL:LastName';
+    const GAL_HOMEPHONE = 'GAL:HomePhone';
+    const GAL_MOBILEPHONE = 'GAL:MobilePhone';
+    const GAL_EMAILADDRESS = 'GAL:EmailAddress';
+
     /**
      * Logger
      *
@@ -850,137 +827,6 @@ class Horde_ActiveSync
     }
 
     /**
-     * @param $devid
-     * @param $protocolversion
-     * @return unknown_type
-     */
-    public function handleSearch($devid, $protocolversion)
-    {
-        $searchrange = '0';
-        if (!$this->_decoder->getElementStartTag(SYNC_SEARCH_SEARCH)) {
-            return false;
-        }
-
-        if (!$this->_decoder->getElementStartTag(SYNC_SEARCH_STORE)) {
-            return false;
-        }
-
-        if (!$this->_decoder->getElementStartTag(SYNC_SEARCH_NAME)) {
-            return false;
-        }
-        $searchname = $this->_decoder->getElementContent();
-        if (!$this->_decoder->getElementEndTag()) {
-            return false;
-        }
-
-        if (!$this->_decoder->getElementStartTag(SYNC_SEARCH_QUERY)) {
-            return false;
-        }
-        $searchquery = $this->_decoder->getElementContent();
-        if (!$this->_decoder->getElementEndTag()) {
-            return false;
-        }
-
-        if ($this->_decoder->getElementStartTag(SYNC_SEARCH_OPTIONS)) {
-            while(1) {
-                if ($this->_decoder->getElementStartTag(SYNC_SEARCH_RANGE)) {
-                    $searchrange = $this->_decoder->getElementContent();
-                    if (!$this->_decoder->getElementEndTag()) {
-                        return false;
-                    }
-                }
-                $e = $this->_decoder->peek();
-                if ($e[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_ENDTAG) {
-                    $this->_decoder->getElementEndTag();
-                    break;
-                }
-            }
-        }
-        if (!$this->_decoder->getElementEndTag()) {//store
-            return false;
-        }
-
-        if (!$this->_decoder->getElementEndTag()) {//search
-            return false;
-        }
-
-        if (strtoupper($searchname) != "GAL") {
-            $this->_logger->err('Searchtype ' . $searchname . 'is not supported');
-            return false;
-        }
-        //get search results from backend
-        $rows = $this->_driver->getSearchResults($searchquery, $searchrange);
-
-        $this->_encoder->startWBXML();
-        $this->_encoder->startTag(SYNC_SEARCH_SEARCH);
-
-            $this->_encoder->startTag(SYNC_SEARCH_STATUS);
-            $this->_encoder->content(1);
-            $this->_encoder->endTag();
-
-            $this->_encoder->startTag(SYNC_SEARCH_RESPONSE);
-                $this->_encoder->startTag(SYNC_SEARCH_STORE);
-
-                    $this->_encoder->startTag(SYNC_SEARCH_STATUS);
-                    $this->_encoder->content(1);
-                    $this->_encoder->endTag();
-
-                    if (is_array($rows) && !empty($rows)) {
-                        $searchrange = $rows['range'];
-                        unset($rows['range']);
-                        foreach ($rows as $u) {
-                            $this->_encoder->startTag(SYNC_SEARCH_RESULT);
-                                $this->_encoder->startTag(SYNC_SEARCH_PROPERTIES);
-
-                                    $this->_encoder->startTag(SYNC_GAL_DISPLAYNAME);
-                                    $this->_encoder->content($u["fullname"]);
-                                    $this->_encoder->endTag();
-
-                                    $this->_encoder->startTag(SYNC_GAL_PHONE);
-                                    $this->_encoder->content($u["businessphone"]);
-                                    $this->_encoder->endTag();
-
-                                    $this->_encoder->startTag(SYNC_GAL_ALIAS);
-                                    $this->_encoder->content($u["username"]);
-                                    $this->_encoder->endTag();
-
-                                    //it's not possible not get first and last name of an user
-                                    //from the gab and user functions, so we just set fullname
-                                    //to lastname and leave firstname empty because nokia needs
-                                    //first and lastname in order to display the search result
-                                    $this->_encoder->startTag(SYNC_GAL_FIRSTNAME);
-                                    $this->_encoder->content("");
-                                    $this->_encoder->endTag();
-
-                                    $this->_encoder->startTag(SYNC_GAL_LASTNAME);
-                                    $this->_encoder->content($u["fullname"]);
-                                    $this->_encoder->endTag();
-
-                                    $this->_encoder->startTag(SYNC_GAL_EMAILADDRESS);
-                                    $this->_encoder->content($u["emailaddress"]);
-                                    $this->_encoder->endTag();
-
-                                $this->_encoder->endTag();//result
-                            $this->_encoder->endTag();//properties
-                        }
-                        $this->_encoder->startTag(SYNC_SEARCH_RANGE);
-                        $this->_encoder->content($searchrange);
-                        $this->_encoder->endTag();
-
-                        $this->_encoder->startTag(SYNC_SEARCH_TOTAL);
-                        $this->_encoder->content(count($rows));
-                        $this->_encoder->endTag();
-                    }
-
-                $this->_encoder->endTag();//store
-            $this->_encoder->endTag();//response
-        $this->_encoder->endTag();//search
-
-
-        return true;
-    }
-
-    /**
      * The heart of the server. Dispatch a request to the request object to
      * handle.
      *
index 718cd1f..9d40865 100644 (file)
@@ -519,11 +519,12 @@ abstract class Horde_ActiveSync_Driver_Base
     /**
      * Returns array of items which contain contact information
      *
-     * @param string $searchquery
+     * @param string $query
+     * @param string $range
      *
      * @return array
      */
-    public function getSearchResults($searchquery)
+    public function getSearchResults($query, $range)
     {
         throw new Horde_ActiveSync_Exception('getSearchResults not implemented.');
     }
index f914819..9d6a9c5 100644 (file)
@@ -574,14 +574,49 @@ class Horde_ActiveSync_Driver_Horde extends Horde_ActiveSync_Driver_Base
     /**
      * Returns array of items which contain contact information
      *
-     * @param string $searchquery
+     * @param string $query  The text string to match against any textual ANR
+     *                       (Automatic Name Resolution) properties. Exchange's
+     *                       searchable ANR properties are currently:
+     *                       firstname, lastname, alias, displayname, email
+     * @param string $range  The range to return (for example, 1-50).
      *
-     * @return array
+     * @return array with 'rows' and 'range' keys
      */
-    public function getSearchResults($searchquery)
+    public function getSearchResults($query, $range)
     {
-        $this->_logger->err('getSearchResults not yet implemented');
-        return array();
+        /* Get results */
+        $return = array('rows' => array(),
+                        'range' => $range);
+        try {
+            $results = $this->_connector->contacts_search($query);
+        } catch (Horde_ActiveSync_Exception $e) {
+            $this->_logger->err($e->getMessage());
+            return $return;
+        }
+
+        /* Honor range */
+        $count = count($results);
+
+        $this->_logger->info('Horde::getSearchResults found ' . $count . ' matches.');
+
+        preg_match('/(.*)\-(.*)/', $range, $matches);
+               $return_count = $matches[2] - $matches[1];
+        $rows = array_slice($results, $matches[1], $return_count + 1, true);
+        $rows = array_pop($rows);
+        foreach ($rows as $row) {
+            $return['rows'][] = array(
+                Horde_ActiveSync::GAL_ALIAS => $row['alias'],
+                Horde_ActiveSync::GAL_DISPLAYNAME => $row['name'],
+                Horde_ActiveSync::GAL_EMAILADDRESS => $row['email'],
+                Horde_ActiveSync::GAL_FIRSTNAME => $row['firstname'],
+                Horde_ActiveSync::GAL_LASTNAME => $row['lastname'],
+                Horde_ActiveSync::GAL_COMPANY => $row['company'],
+                Horde_ActiveSync::GAL_HOMEPHONE => $row['homePhone'],
+                Horde_ActiveSync::GAL_PHONE => $row['workPhone']
+            );
+        }
+
+        return $return;
     }
 
     /**
index 2672402..b9a1ca0 100644 (file)
@@ -227,6 +227,7 @@ class Horde_ActiveSync_Driver_Horde_Connector_Registry
      * @param string $action    The action to check for (add, modify, delete)
      * @param integer $from_ts  The timestamp to start checking from
      * @param integer $to_ts    The ending timestamp
+     *
      * @return array  An array of event uids
      */
     public function contacts_listBy($action, $from_ts, $to_ts)
@@ -234,6 +235,23 @@ class Horde_ActiveSync_Driver_Horde_Connector_Registry
         return $this->_registry->contacts->listBy($action, $from_ts, null, $to_ts);
     }
 
+    public function contacts_search($query)
+    {
+        $gal = $this->contacts_getGal();
+        $fields = array($gal => array('firstname', 'lastname', 'alias', 'name', 'email'));
+        return $this->_registry->contacts->search(array($query), array($gal), $fields, true, true);
+    }
+
+    /**
+     * Get the GAL source uid.
+     *
+     * @return string | boolean
+     */
+    public function contacts_getGal()
+    {
+        return $this->_registry->contacts->getGalUid();
+    }
+
     /**
      * List all tasks in the user's default tasklist.
      *
diff --git a/framework/ActiveSync/lib/Horde/ActiveSync/Request/Search.php b/framework/ActiveSync/lib/Horde/ActiveSync/Request/Search.php
new file mode 100644 (file)
index 0000000..979eb52
--- /dev/null
@@ -0,0 +1,208 @@
+<?php
+/**
+ * ActiveSync Handler for Search requests
+ *
+ * Copyright 2009 - 2010 The Horde Project (http://www.horde.org)
+ *
+ * @author Michael J. Rubinsky <mrubinsk@horde.org>
+ * @package Horde_ActiveSync
+ */
+/**
+ * Zarafa Deutschland GmbH, www.zarafaserver.de
+ * This file is distributed under GPL v2.
+ * Consult LICENSE file for details
+ */
+class Horde_ActiveSync_Request_Search extends Horde_ActiveSync_Request_Base
+{
+
+    /** Search code page **/
+    const SEARCH_SEARCH = 'Search:Search';
+    const SEARCH_STORE = 'Search:Store';
+    const SEARCH_NAME = 'Search:Name';
+    const SEARCH_QUERY = 'Search:Query';
+    const SEARCH_OPTIONS = 'Search:Options';
+    const SEARCH_RANGE = 'Search:Range';
+    const SEARCH_STATUS = 'Search:Status';
+    const SEARCH_RESPONSE = 'Search:Response';
+    const SEARCH_RESULT = 'Search:Result';
+    const SEARCH_PROPERTIES = 'Search:Properties';
+    const SEARCH_TOTAL = 'Search:Total';
+    const SEARCH_EQUALTO = 'Search:EqualTo';
+    const SEARCH_VALUE = 'Search:Value';
+    const SEARCH_AND = 'Search:And';
+    const SEARCH_OR = 'Search:Or';
+    const SEARCH_FREETEXT = 'Search:FreeText';
+    const SEARCH_DEEPTRAVERSAL = 'Search:DeepTraversal';
+    const SEARCH_LONGID = 'Search:LongId';
+    const SEARCH_REBUILDRESULTS = 'Search:RebuildResults';
+    const SEARCH_LESSTHAN = 'Search:LessThan';
+    const SEARCH_GREATERTHAN = 'Search:GreaterThan';
+    const SEARCH_SCHEMA = 'Search:Schema';
+    const SEARCH_SUPPORTED = 'Search:Supported';
+
+    /** Search Status **/
+    const SEARCH_STATUS_SUCCESS = 1;
+    const SEARCH_STATUS_ERROR = 3;
+
+    /** Store Status **/
+    const STORE_STATUS_SUCCESS = 1;
+    const STORE_STATUS_PROTERR = 2;
+    const STORE_STATUS_SERVERERR = 3;
+    const STORE_STATUS_BADLINK = 4;
+    const STORE_STATUS_NOTFOUND = 6;
+    const STORE_STATUS_CONNECTIONERR = 7;
+    const STORE_STATUS_COMPLEX = 8;
+
+
+    /**
+     * Handle request
+     *
+     * @return boolean
+     */
+    public function handle()
+    {
+        parent::handle();
+        $this->_logger->info('[' . $this->_device->id . '] Beginning SEARCH');
+
+        $searchrange = '0';
+        $search_status = self::SEARCH_STATUS_SUCCESS;
+        $store_status = self::STORE_STATUS_SUCCESS;
+
+        if (!$this->_decoder->getElementStartTag(self::SEARCH_SEARCH) ||
+            !$this->_decoder->getElementStartTag(self::SEARCH_STORE) ||
+            !$this->_decoder->getElementStartTag(self::SEARCH_NAME)) {
+
+            $search_status = self::SEARCH_STATUS_ERROR;
+        }
+
+        /* The type of search, we only support GAL right now */
+        $searchname = $this->_decoder->getElementContent();
+        if (!$this->_decoder->getElementEndTag()) {
+            $search_status = self::SEARCH_STATUS_ERROR;
+            $store_status = self::STORE_STATUS_PROTERR;
+        }
+
+        /* The search query */
+        if (!$this->_decoder->getElementStartTag(self::SEARCH_QUERY)) {
+            $search_status = self::SEARCH_STATUS_ERROR;
+            $store_status = self::STORE_STATUS_PROTERR;
+        }
+        $searchquery = $this->_decoder->getElementContent();
+        if (!$this->_decoder->getElementEndTag()) {
+            $search_status = self::SEARCH_STATUS_ERROR;
+            $store_status = self::STORE_STATUS_PROTERR;
+        }
+
+        /* Range */
+        if ($this->_decoder->getElementStartTag(self::SEARCH_OPTIONS)) {
+            while(1) {
+                if ($this->_decoder->getElementStartTag(self::SEARCH_RANGE)) {
+                    $searchrange = $this->_decoder->getElementContent();
+                    if (!$this->_decoder->getElementEndTag()) {
+                        $search_status = self::SEARCH_STATUS_ERROR;
+                        $store_status = self::STORE_STATUS_PROTERR;
+                    }
+                }
+                $e = $this->_decoder->peek();
+                if ($e[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_ENDTAG) {
+                    $this->_decoder->getElementEndTag();
+                    break;
+                }
+            }
+        }
+
+        /* Close the store container */
+        if (!$this->_decoder->getElementEndTag()) {//store
+            $search_status = self::SEARCH_STATUS_ERROR;
+            $store_status = self::STORE_STATUS_PROTERR;
+        }
+
+        /* Close the search container */
+        if (!$this->_decoder->getElementEndTag()) {//search
+            $search_status = self::SEARCH_STATUS_ERROR;
+            $store_status = self::STORE_STATUS_PROTERR;
+        }
+
+        /* We only support the GAL */
+        if (strtoupper($searchname) != "GAL") {
+            $this->_logger->debug('Searchtype ' . $searchname . 'is not supported');
+            $store_status = self::STORE_STATUS_COMPLEX;
+        }
+
+        /* Get search results from backend */
+        $rows = $this->_driver->getSearchResults($searchquery, $searchrange);
+
+        /* Send output */
+        $this->_encoder->startWBXML();
+        $this->_encoder->startTag(self::SEARCH_SEARCH);
+
+        $this->_encoder->startTag(self::SEARCH_STATUS);
+        $this->_encoder->content($search_status);
+        $this->_encoder->endTag();
+
+        $this->_encoder->startTag(self::SEARCH_RESPONSE);
+        $this->_encoder->startTag(self::SEARCH_STORE);
+
+        $this->_encoder->startTag(self::SEARCH_STATUS);
+        $this->_encoder->content($store_status);
+        $this->_encoder->endTag();
+
+        $searchrange = $rows['range'];
+
+        /* Build the results */
+        foreach ($rows['rows'] as $u) {
+            $this->_encoder->startTag(self::SEARCH_RESULT);
+            $this->_encoder->startTag(self::SEARCH_PROPERTIES);
+
+            $this->_encoder->startTag(Horde_ActiveSync::GAL_DISPLAYNAME);
+            $this->_encoder->content($u[Horde_ActiveSync::GAL_DISPLAYNAME]);
+            $this->_encoder->endTag();
+
+            $this->_encoder->startTag(Horde_ActiveSync::GAL_PHONE);
+            $this->_encoder->content($u[Horde_ActiveSync::GAL_PHONE]);
+            $this->_encoder->endTag();
+
+            $this->_encoder->startTag(Horde_ActiveSync::GAL_ALIAS);
+            $this->_encoder->content($u[Horde_ActiveSync::GAL_ALIAS]);
+            $this->_encoder->endTag();
+
+            $this->_encoder->startTag(Horde_ActiveSync::GAL_FIRSTNAME);
+            $this->_encoder->content($u[Horde_ActiveSync::GAL_FIRSTNAME]);
+            $this->_encoder->endTag();
+
+            $this->_encoder->startTag(Horde_ActiveSync::GAL_LASTNAME);
+            $this->_encoder->content($u[Horde_ActiveSync::GAL_LASTNAME]);
+            $this->_encoder->endTag();
+
+            $this->_encoder->startTag(Horde_ActiveSync::GAL_EMAILADDRESS);
+            $this->_encoder->content($u[Horde_ActiveSync::GAL_EMAILADDRESS]);
+            $this->_encoder->endTag();
+
+            $this->_encoder->startTag(Horde_ActiveSync::GAL_HOMEPHONE);
+            $this->_encoder->content($u[Horde_ActiveSync::GAL_HOMEPHONE]);
+            $this->_encoder->endTag();
+
+            $this->_encoder->startTag(Horde_ActiveSync::GAL_COMPANY);
+            $this->_encoder->content($u[Horde_ActiveSync::GAL_COMPANY]);
+            $this->_encoder->endTag();
+
+            $this->_encoder->endTag();//result
+            $this->_encoder->endTag();//properties
+
+            $this->_encoder->startTag(self::SEARCH_RANGE);
+            $this->_encoder->content($searchrange);
+            $this->_encoder->endTag();
+
+            $this->_encoder->startTag(self::SEARCH_TOTAL);
+            $this->_encoder->content(count($rows));
+            $this->_encoder->endTag();
+        }
+
+        $this->_encoder->endTag();//store
+        $this->_encoder->endTag();//response
+        $this->_encoder->endTag();//search
+
+        return true;
+    }
+
+}
\ No newline at end of file
index 9d16796..36d4d35 100644 (file)
@@ -71,6 +71,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
          <file name="Provision.php" role="php" />
          <file name="GetItemEstimate.php" role="php" />
          <file name="SendMail.php" role="php" />
+         <file name="Search.php" role="php" />
        </dir>
        <file name="Exception.php" role="php" />
        <file name="Wbxml.php" role="php" />
@@ -123,6 +124,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
    <install name="lib/Horde/ActiveSync/Request/Provision.php" as="Horde/ActiveSync/Request/Provision.php" />
    <install name="lib/Horde/ActiveSync/Request/GetItemEstimate.php" as="Horde/ActiveSync/Request/GetItemEstimate.php" />
    <install name="lib/Horde/ActiveSync/Request/SendMail.php" as="Horde/ActiveSync/Request/SendMail.php" />
+   <install name="lib/Horde/ActiveSync/Request/Search.php" as="Horde/ActiveSync/Request/Search.php" />
    <install name="lib/Horde/ActiveSync/Exception.php" as="Horde/ActiveSync/Exception.php" />
    <install name="lib/Horde/ActiveSync/Wbxml.php" as="Horde/ActiveSync/Wbxml.php" />
    <install name="lib/Horde/ActiveSync/Timezone.php" as="Horde/ActiveSync/Timezone.php" />