lib/ext

Aleksander Machniak machniak at kolabsys.com
Mon Mar 10 13:02:16 CET 2014


 lib/ext/Syncroton/Command/AutoDiscover.php      |  126 ++++++++++++++++++++++++
 lib/ext/Syncroton/Command/Options.php           |    6 -
 lib/ext/Syncroton/Command/Sync.php              |    5 
 lib/ext/Syncroton/Command/Wbxml.php             |    4 
 lib/ext/Syncroton/Data/AData.php                |   68 +++++++-----
 lib/ext/Syncroton/Model/Account.php             |   76 ++++++++++++++
 lib/ext/Syncroton/Model/EmailMeetingRequest.php |   98 ++++++++++++++++++
 lib/ext/Syncroton/Model/EmailRecurrence.php     |   74 ++++++++++++++
 8 files changed, 421 insertions(+), 36 deletions(-)

New commits:
commit cb7725c6fc35ee5fcd48292db11584bb977a0c09
Author: Aleksander Machniak <machniak at kolabsys.com>
Date:   Mon Mar 10 13:01:32 2014 +0100

    Update Syncroton lib with fix for Bug #2664

diff --git a/lib/ext/Syncroton/Command/AutoDiscover.php b/lib/ext/Syncroton/Command/AutoDiscover.php
new file mode 100644
index 0000000..c2415a1
--- /dev/null
+++ b/lib/ext/Syncroton/Command/AutoDiscover.php
@@ -0,0 +1,126 @@
+<?php
+/**
+ * Syncroton
+ *
+ * @package     Syncroton
+ * @subpackage  Command
+ * @license     http://www.tine20.org/licenses/lgpl.html LGPL Version 3
+ * @copyright   Copyright (c) 2013-2013 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Lars Kneschke <l.kneschke at metaways.de>
+ */
+
+/**
+ * class to handle AutoDiscover command
+ *
+ * @package     Syncroton
+ * @subpackage  Command
+ */
+class Syncroton_Command_AutoDiscover implements Syncroton_Command_ICommand
+{
+    /**
+     * the domDocucment containing the xml request from the client
+     *
+     * @var DOMDocument
+     */
+    protected $requestBody;
+    
+    protected $emailAddress;
+    
+    public    $mobileSyncUrl;
+    
+    public    $certEnrollUrl;
+    
+    /**
+     * constructor of this class
+     * 
+     * @param DOMDocument              $_requestBody
+     * @param Syncroton_Model_IDevice  $_device
+     * @param string                   $_policyKey
+     */
+    public function __construct($requestBody, Syncroton_Model_IDevice $device = null, $policyKey = null)
+    {
+        $this->requestBody = $requestBody;
+    }
+    
+    /**
+     * process the incoming data 
+     */
+    public function handle()
+    {
+        $xpath = new DomXPath($this->requestBody);
+        $xpath->registerNamespace('2006', 'http://schemas.microsoft.com/exchange/autodiscover/mobilesync/requestschema/2006');
+        
+        $nodes = $xpath->query('//2006:Autodiscover/2006:Request/2006:EMailAddress');
+        if ($nodes->length === 0) {
+            throw new Syncroton_Exception();
+        }
+        
+        $this->emailAddress = $nodes->item(0)->nodeValue;
+    }
+    
+    /**
+     * create the response
+     * 
+     * @return DOMDocument
+     */
+    public function getResponse()
+    {
+        // Creates an instance of the DOMImplementation class
+        $imp = new DOMImplementation();
+        
+        // Creates a DOMDocument instance
+        $document = $imp->createDocument("http://schemas.microsoft.com/exchange/autodiscover/mobilesync/requestschema/2006", 'Autodiscover');
+        $document->xmlVersion   = '1.0';
+        $document->encoding     = 'UTF-8';
+        $document->formatOutput = false;
+        
+        $response = $document->documentElement->appendChild($document->createElement('Response'));
+        
+        $user = $response->appendChild($document->createElement('User'));
+        $user->appendChild($document->createElement('EMailAddress', $this->emailAddress));
+        
+        $settings = $document->createElement('Settings');
+        
+        if (!empty($this->mobileSyncUrl)) {
+            $server   = $document->createElement('Server');
+            
+            $server->appendChild($document->createElement('Type', 'MobileSync'));
+            
+            $server->appendChild($document->createElement('Url',  $this->mobileSyncUrl));
+            $server->appendChild($document->createElement('Name', $this->mobileSyncUrl));
+            
+            $settings->appendChild($server);
+        }
+        
+        if (!empty($this->certEnrollUrl)) {
+            $server   = $document->createElement('Server');
+            
+            $server->appendChild($document->createElement('Type', 'CertEnroll'));
+            
+            $server->appendChild($document->createElement('Url',  $this->certEnrollUrl));
+            $server->appendChild($document->createElement('Name'));
+            $server->appendChild($document->createElement('ServerData', 'CertEnrollTemplate'));
+            
+            $settings->appendChild($server);
+        }
+        
+        if ($settings->hasChildNodes()) {
+            $action   = $response->appendChild($document->createElement('Action'));
+            $action->appendChild($settings);
+        }
+        
+        return $document;
+    }
+    
+    /**
+     * return headers of command
+     * 
+     * @return array list of headers
+     */
+    public function getHeaders()
+    {
+        return array(
+            'Content-Type'  => 'text/xml;charset=utf-8'
+        );
+    }
+}
diff --git a/lib/ext/Syncroton/Command/Options.php b/lib/ext/Syncroton/Command/Options.php
index f126cc5..52f33ad 100644
--- a/lib/ext/Syncroton/Command/Options.php
+++ b/lib/ext/Syncroton/Command/Options.php
@@ -19,7 +19,7 @@ class Syncroton_Command_Options
 {
     /**
      * this function generates the response for the client
-     * 
+     *
      * @return void
      */
     public function getHeaders()
@@ -28,7 +28,7 @@ class Syncroton_Command_Options
         return array(
             'MS-Server-ActiveSync'  => '14.00.0536.000',
             'MS-ASProtocolVersions' => '2.5,12.0,12.1,14.0,14.1',
-            'MS-ASProtocolCommands' => 'CreateCollection,DeleteCollection,FolderCreate,FolderDelete,FolderSync,FolderUpdate,GetAttachment,GetHierarchy,GetItemEstimate,ItemOperations,MeetingResponse,MoveCollection,MoveItems,Provision,ResolveRecipients,Ping,SendMail,Search,Settings,SmartForward,SmartReply,Sync,ValidateCert'
+            'MS-ASProtocolCommands' => 'FolderCreate,FolderDelete,FolderSync,FolderUpdate,GetAttachment,GetItemEstimate,ItemOperations,MeetingResponse,MoveItems,Provision,ResolveRecipients,Ping,SendMail,Search,Settings,SmartForward,SmartReply,Sync,ValidateCert'
         );
-    }    
+    }
 }
diff --git a/lib/ext/Syncroton/Command/Sync.php b/lib/ext/Syncroton/Command/Sync.php
index 45ac895..8feb0ef 100644
--- a/lib/ext/Syncroton/Command/Sync.php
+++ b/lib/ext/Syncroton/Command/Sync.php
@@ -415,6 +415,9 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
         $collections = $this->_outputDom->createElementNS('uri:AirSync', 'Collections');
 
         $totalChanges = 0;
+
+        // Detect devices that do not support empty Sync reponse
+        $emptySyncSupported = !preg_match('/(meego|nokian800)/i', $this->_device->useragent);
         
         // continue only if there are changes or no time is left
         if ($this->_heartbeatInterval > 0) {
@@ -890,7 +893,7 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
                 if ($this->_logger instanceof Zend_Log)
                     $this->_logger->info(__METHOD__ . '::' . __LINE__ . " current synckey is ". $collectionData->syncState->counter);
                 
-                if ($collection->childNodes->length > 4 || $collectionData->syncState->counter != $collectionData->syncKey) {
+                if (!$emptySyncSupported || $collection->childNodes->length > 4 || $collectionData->syncState->counter != $collectionData->syncKey) {
                      $collections->appendChild($collection);
                 }
             }
diff --git a/lib/ext/Syncroton/Command/Wbxml.php b/lib/ext/Syncroton/Command/Wbxml.php
index a252d25..feb20a8 100644
--- a/lib/ext/Syncroton/Command/Wbxml.php
+++ b/lib/ext/Syncroton/Command/Wbxml.php
@@ -61,14 +61,14 @@ abstract class Syncroton_Command_Wbxml implements Syncroton_Command_ICommand
      * @var DOMDocument
      */
     protected $_outputDom;
-
+    
     /**
      * the domDocucment containing the xml request from the client
      *
      * @var DOMDocument
      */
     protected $_requestBody;
-        
+    
     /**
      * the default namespace
      *
diff --git a/lib/ext/Syncroton/Data/AData.php b/lib/ext/Syncroton/Data/AData.php
index 447d138..7867237 100644
--- a/lib/ext/Syncroton/Data/AData.php
+++ b/lib/ext/Syncroton/Data/AData.php
@@ -17,17 +17,12 @@
  */
 abstract class Syncroton_Data_AData implements Syncroton_Data_IData
 {
-    const LONGID_DELIMITER = "\xe2\x87\x94"; # UTF8 ⇔
+    const LONGID_DELIMITER = "\xe2\x87\x94"; # UTF-8 character ⇔
     
     /**
-     * used by unit tests only to simulated added folders
+     * @var DateTime
      */
-    public static $changedEntries = array();
-    
-    /**
-     * used by unit tests only to simulated exhausted memory
-     */
-    public static $exhaustedEntries = array();
+    protected $_timeStamp;
     
     /**
      * the constructor
@@ -38,7 +33,7 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
     public function __construct(Syncroton_Model_IDevice $_device, DateTime $_timeStamp)
     {
         $this->_device      = $_device;
-        $this->_timestamp   = $_timeStamp;
+        $this->_timeStamp   = $_timeStamp;
         $this->_db          = Syncroton_Registry::getDatabase();
         $this->_tablePrefix = 'Syncroton_';
         $this->_ownerId     = '1234';
@@ -87,12 +82,12 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
         $id = !empty($folder->serverId) ? $folder->serverId : sha1(mt_rand(). microtime());
         
         $this->_db->insert($this->_tablePrefix . 'data_folder', array(
-            'id'            => $id,
-            'type'          => $folder->type,
+            'id'            => $id,
+            'type'          => $folder->type,
             'name'          => $folder->displayName,
             'owner_id'      => $this->_ownerId,
             'parent_id'     => $folder->parentId,
-            'creation_time' => $this->_timestamp->format('Y-m-d H:i:s')
+            'creation_time' => $this->_timeStamp->format("Y-m-d H:i:s")
         ));
         
         return $this->getFolder($id);
@@ -107,10 +102,11 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
         $id = sha1(mt_rand(). microtime());
     
         $this->_db->insert($this->_tablePrefix . 'data', array(
-            'id'        => $id,
-            'class'     => get_class($_entry),
-            'folder_id' => $_folderId,
-            'data'      => serialize($_entry)
+            'id'            => $id,
+            'class'         => get_class($_entry),
+            'folder_id'     => $_folderId,
+            'creation_time' => $this->_timeStamp->format("Y-m-d H:i:s"),
+            'data'          => serialize($_entry)
         ));
     
         return $id;
@@ -187,11 +183,25 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
      */
     public function getChangedEntries($_folderId, DateTime $_startTimeStamp, DateTime $_endTimeStamp = NULL, $filterType = NULL)
     {
-        if (!isset(Syncroton_Data_AData::$changedEntries[get_class($this)])) {
-            return array();
-        } else {
-            return Syncroton_Data_AData::$changedEntries[get_class($this)];
+        $folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->id : $_folderId;
+    
+        $select = $this->_db->select()
+            ->from($this->_tablePrefix . 'data', array('id'))
+            ->where('folder_id = ?', $_folderId)
+            ->where('last_modified_time > ?', $_startTimeStamp->format("Y-m-d H:i:s"));
+        
+        if ($_endTimeStamp instanceof DateTime) {
+            $select->where('last_modified_time < ?', $_endTimeStamp->format("Y-m-d H:i:s"));
+        }
+        
+        $ids = array();
+        
+        $stmt = $this->_db->query($select);
+        while ($id = $stmt->fetchColumn()) {
+            $ids[] = $id;
         }
+        
+        return $ids;
     }
     
     /**
@@ -230,7 +240,7 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
     
     /**
      * @param  Syncroton_Model_IFolder|string  $_folderId
-     * @param  string                        $_filter
+     * @param  string                          $_filter
      * @return array
      */
     public function getServerEntries($_folderId, $_filter)
@@ -281,15 +291,12 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
      * @see Syncroton_Data_IData::getEntry()
      */
     public function getEntry(Syncroton_Model_SyncCollection $collection, $serverId)
-    {
-        if (isset(self::$exhaustedEntries[get_class($this)]) && is_array(self::$exhaustedEntries[get_class($this)]) && in_array($serverId, self::$exhaustedEntries[get_class($this)])) {
-            throw new Syncroton_Exception_MemoryExhausted('memory exchausted for ' . $serverId);
-        } 
+    {
         $select = $this->_db->select()
             ->from($this->_tablePrefix . 'data', array('data'))
             ->where('id = ?', $serverId);
         
-        $stmt = $this->_db->query($select);
+        $stmt  = $this->_db->query($select);
         $entry = $stmt->fetchColumn();
 
         if ($entry === false) {
@@ -328,10 +335,11 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
      * @see Syncroton_Data_IData::updateEntry()
      */
     public function updateEntry($_folderId, $_serverId, Syncroton_Model_IEntry $_entry)
-    {
+    {
         $this->_db->update($this->_tablePrefix . 'data', array(
-            'folder_id' => $_folderId,
-            'data'      => serialize($_entry)
+            'folder_id'          => $_folderId,
+            'last_modified_time' => $this->_timeStamp->format("Y-m-d H:i:s"),
+            'data'               => serialize($_entry)
         ), array(
             'id = ?' => $_serverId
         ));
@@ -346,7 +354,7 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
         $this->_db->update($this->_tablePrefix . 'data_folder', array(
             'name'               => $folder->displayName,
             'parent_id'          => $folder->parentId,
-            'last_modified_time' => $this->_timestamp->format('Y-m-d H:i:s')
+            'last_modified_time' => $this->_timeStamp->format("Y-m-d H:i:s"),
         ), array(
             'id = ?'       => $folder->serverId,
             'owner_id = ?' => $this->_ownerId
diff --git a/lib/ext/Syncroton/Model/Account.php b/lib/ext/Syncroton/Model/Account.php
new file mode 100644
index 0000000..0ed9d65
--- /dev/null
+++ b/lib/ext/Syncroton/Model/Account.php
@@ -0,0 +1,76 @@
+<?php
+/**
+ * Syncroton
+ *
+ * @package     Syncroton
+ * @subpackage  Model
+ * @license     http://www.tine20.org/licenses/lgpl.html LGPL Version 3
+ * @copyright   Copyright (c) 2012-2014 Kolab Systems AG
+ * @author      Aleksander Machniak <machniak at kolabsys.com>
+ */
+
+/**
+ * class to handle (Settings/UserInformation/Get/Accounts/) Account element
+ *
+ * @package     Syncroton
+ * @subpackage  Model
+ * @property    string  accountId
+ * @property    string  accountName
+ * @property    string  userDisplayName
+ * @property    bool    sendDisabled
+ * @property    string  primaryAddress
+ * @property    array   addresses
+ */
+class Syncroton_Model_Account extends Syncroton_Model_AXMLEntry
+{
+    protected $_xmlBaseElement = 'Account';
+
+    protected $_properties = array(
+        'Settings' => array(
+            'accountId'       => array('type' => 'string'),
+            'accountName'     => array('type' => 'string'),
+            'userDisplayName' => array('type' => 'string'),
+            'sendDisabled'    => array('type' => 'number'),
+//            'emailAddresses'  => array('type' => 'container'),
+        ),
+        'Internal' => array(
+            'primaryAddress' => array('type' => 'string'),
+            'addresses'      => array('type' => 'array'),
+        ),
+    );
+
+    /**
+     * (non-PHPdoc)
+     * @see Syncroton_Model_AXMLEntry::appendXML()
+     */
+    public function appendXML(DOMElement $_domParent, Syncroton_Model_IDevice $device)
+    {
+        parent::appendXML($_domParent, $device);
+
+        $nameSpace = 'uri:Settings';
+        $document  = $_domParent->ownerDocument;
+
+        // handle EmailAddresses element
+        $list = $document->createElementNS($nameSpace, 'EmailAddresses');
+
+        if (!empty($this->_elements['primaryAddress'])) {
+            $element = $document->createElementNS($nameSpace, 'PrimarySmtpAddress', $this->_elements['primaryAddress']);
+            $list->appendChild($element);
+        }
+
+        foreach ((array)$this->_elements['addresses'] as $address) {
+            // skip empty values
+            if (empty($address)) {
+                continue;
+            }
+
+            $element = $document->createElementNS($nameSpace, 'SMTPAddress', $address);
+            $list->appendChild($element);
+        }
+
+        if ($list->hasChildNodes()) {
+            $_domParent->appendChild($list);
+        }
+    }
+
+}
diff --git a/lib/ext/Syncroton/Model/EmailMeetingRequest.php b/lib/ext/Syncroton/Model/EmailMeetingRequest.php
new file mode 100644
index 0000000..2786be4
--- /dev/null
+++ b/lib/ext/Syncroton/Model/EmailMeetingRequest.php
@@ -0,0 +1,98 @@
+<?php
+/**
+ * Syncroton
+ *
+ * @package     Syncroton
+ * @subpackage  Model
+ * @license     http://www.tine20.org/licenses/lgpl.html LGPL Version 3
+ * @copyright   Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Lars Kneschke <l.kneschke at metaways.de>
+ */
+
+/**
+ * class to handle Email:MeetingRequest
+ *
+ * @package     Syncroton
+ * @subpackage  Model
+ * @property    bool     AllDayEvent
+ * @property    int      BusyStatus
+ * @property    int      DisallowNewTimeProposal
+ * @property    DateTime DtStamp
+ * @property    DateTime EndTime
+ * @property    string   GlobalObjId
+ * @property    int      InstanceType
+ * @property    int      MeetingMessageType
+ * @property    string   Organizer
+ * @property    string   RecurrenceId
+ * @property    array    Recurrences
+ * @property    int      Reminder
+ * @property    int      ResponseRequested
+ * @property    int      Sensitivity
+ * @property    DateTime StartTime
+ * @property    string   Timezone
+ */
+class Syncroton_Model_EmailMeetingRequest extends Syncroton_Model_AXMLEntry
+{
+    /**
+     * busy status constants
+     */
+    const BUSY_STATUS_FREE      = 0;
+    const BUSY_STATUS_TENATTIVE = 1;
+    const BUSY_STATUS_BUSY      = 2;
+    const BUSY_STATUS_OUT       = 3;
+
+    /**
+     * sensitivity constants
+     */
+    const SENSITIVITY_NORMAL       = 0;
+    const SENSITIVITY_PERSONAL     = 1;
+    const SENSITIVITY_PRIVATE      = 2;
+    const SENSITIVITY_CONFIDENTIAL = 3;
+
+    /**
+     * instanceType constants
+     */
+    const TYPE_NORMAL              = 0;
+    const TYPE_RECURRING_MASTER    = 1;
+    const TYPE_RECURRING_SINGLE    = 2;
+    const TYPE_RECURRING_EXCEPTION = 3;
+
+    /**
+     * messageType constants
+     */
+    const MESSAGE_TYPE_NORMAL      = 0;
+    const MESSAGE_TYPE_REQUEST     = 1;
+    const MESSAGE_TYPE_FULL_UPDATE = 2;
+    const MESSAGE_TYPE_INFO_UPDATE = 3;
+    const MESSAGE_TYPE_OUTDATED    = 4;
+    const MESSAGE_TYPE_COPY        = 5;
+    const MESSAGE_TYPE_DELEGATED   = 6;
+
+    protected $_dateTimeFormat = "Ymd\THis\Z";
+
+    protected $_xmlBaseElement = 'MeetingRequest';
+
+    protected $_properties = array(
+        'Email' => array(
+            'allDayEvent'               => array('type' => 'number'),
+            'busyStatus'                => array('type' => 'number'),
+            'disallowNewTimeProposal'   => array('type' => 'number'),
+            'dtStamp'                   => array('type' => 'datetime'),
+            'endTime'                   => array('type' => 'datetime'),
+            'globalObjId'               => array('type' => 'string'),
+            'instanceType'              => array('type' => 'datetime'),
+            'location'                  => array('type' => 'string'),
+            'organizer'                 => array('type' => 'string'), //e-mail address
+            'recurrenceId'              => array('type' => 'datetime'),
+            'recurrences'               => array('type' => 'container'),
+            'reminder'                  => array('type' => 'number'),
+            'responseRequested'         => array('type' => 'number'),
+            'sensitivity'               => array('type' => 'number'),
+            'startTime'                 => array('type' => 'datetime'),
+            'timeZone'                  => array('type' => 'timezone'),
+        ),
+        'Email2' => array(
+            'meetingMessageType'        => array('type' => 'number'),
+        ),
+    );
+}
diff --git a/lib/ext/Syncroton/Model/EmailRecurrence.php b/lib/ext/Syncroton/Model/EmailRecurrence.php
new file mode 100644
index 0000000..b90cf19
--- /dev/null
+++ b/lib/ext/Syncroton/Model/EmailRecurrence.php
@@ -0,0 +1,74 @@
+<?php
+/**
+ * Syncroton
+ *
+ * @package     Syncroton
+ * @subpackage  Model
+ * @license     http://www.tine20.org/licenses/lgpl.html LGPL Version 3
+ * @copyright   Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Lars Kneschke <l.kneschke at metaways.de>
+ */
+
+/**
+ * class to handle Email::Recurrence
+ *
+ * @package     Syncroton
+ * @subpackage  Model
+ * @property    int       CalendarType
+ * @property    int       DayOfMonth
+ * @property    int       DayOfWeek
+ * @property    int       FirstDayOfWeek
+ * @property    int       Interval
+ * @property    int       IsLeapMonth
+ * @property    int       MonthOfYear
+ * @property    int       Occurrences
+ * @property    int       Type
+ * @property    DateTime  Until
+ * @property    int       WeekOfMonth
+ */
+
+class Syncroton_Model_EmailRecurrence extends Syncroton_Model_AXMLEntry
+{
+    protected $_xmlBaseElement = 'Recurrence';
+
+    /**
+     * recur types
+     */
+    const TYPE_DAILY          = 0;     // Recurs daily
+    const TYPE_WEEKLY         = 1;     // Recurs weekly
+    const TYPE_MONTHLY        = 3;     // Recurs monthly
+    const TYPE_MONTHLY_DAYN   = 2;     // Recurs monthly on the nth day
+    const TYPE_YEARLY         = 5;     // Recurs yearly on the nth day of the nth month each year
+    const TYPE_YEARLY_DAYN    = 6;     // Recurs yearly on the nth day of the week of the nth month
+
+    /**
+     * day of week constants
+     */
+    const RECUR_DOW_SUNDAY      = 1;
+    const RECUR_DOW_MONDAY      = 2;
+    const RECUR_DOW_TUESDAY     = 4;
+    const RECUR_DOW_WEDNESDAY   = 8;
+    const RECUR_DOW_THURSDAY    = 16;
+    const RECUR_DOW_FRIDAY      = 32;
+    const RECUR_DOW_SATURDAY    = 64;
+
+    protected $_dateTimeFormat = "Ymd\THis\Z";
+
+    protected $_properties = array(
+        'Email' => array(
+            'dayOfMonth'     => array('type' => 'number'),
+            'dayOfWeek'      => array('type' => 'number'),
+            'interval'       => array('type' => 'number'), // 1 or 2
+            'monthOfYear'    => array('type' => 'number'),
+            'occurrences'    => array('type' => 'number'),
+            'type'           => array('type' => 'number'),
+            'until'          => array('type' => 'datetime'),
+            'weekOfMonth'    => array('type' => 'number'),
+        ),
+        'Email2' => array(
+            'calendarType'   => array('type' => 'number'),
+            'firstDayOfWeek' => array('type' => 'number'),
+            'isLeapMonth'    => array('type' => 'number'),
+        )
+    );
+}




More information about the commits mailing list