diff --git a/lib/filter/mapistore/mail.php b/lib/filter/mapistore/mail.php index 143a3fa..09d36e7 100644 --- a/lib/filter/mapistore/mail.php +++ b/lib/filter/mapistore/mail.php @@ -1,296 +1,289 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ class kolab_api_filter_mapistore_mail { protected $map = array( 'PidTagMessageClass' => '', 'PidTagHasAttachments' => '', 'PidTagMessageCodepage' => '', 'PidTagMessageLocaleId' => '', 'PidTagMessageFlags' => '', 'PidTagMessageSize' => 'size', // size in bytes 'PidTagMessageStatus' => '', 'PidTagSubjectPrefix' => '', 'PidTagNormalizedSubject' => '', 'PidTagImportance' => '', 'PidTagPriority' => 'priority', 'PidTagSensitivity' => '', 'PidLidSmartNoAttach' => '', 'PidLidPrivate' => '', 'PidLidSideEffects' => '', 'PidNameKeywords' => '', // PtypMultipleString 'PidLidCommonStart' => '', 'PidLidCommonEnd' => '', 'PidTagAutoForwarded' => '', 'PidTagAutoForwardComment' => '', 'PidTagCategories' => '', // PtypMultipleString 'PidLidClassification' => '', 'PidLidClassificationDescription' => '', 'PidLidClassified' => '', 'PidTagInternetReferences' => '', 'PidLidInfoPathFormName' => '', 'PidTagMimeSkeleton' => '', 'PidTagTnefCorrelationKey' => '', 'PidTagAddressBookDisplayName' => '', 'PidTagCreatorEntryId' => '', 'PidTagLastModifierEntryId' => '', 'PidLidAgingDontAgeMe' => '', 'PidLidCurrentVersion' => '', 'PidLidCurrentVersionName' => '', 'PidTagAlternateRecipientAllowed' => '', 'PidTagResponsibility' => '', 'PidTagRowid' => '', 'PidTagHasNamedProperties' => '', 'PidTagRecipientOrder' => '', 'PidNameContentBase' => '', 'PidNameAcceptLanguage' => '', 'PidTagPurportedSenderDomain' => '', 'PidTagStoreEntryId' => '', 'PidTagTrustSender' => '', 'PidTagSubject' => 'subject', 'PidTagMessageRecipients' => '', 'PidNameContentClass' => '', 'PidTagLocalCommitTime' => '', 'PidNameContentType' => '', 'PidTagCreatorName' => '', 'PidTagMessageAttachments' => '', 'PidTagRead' => '', 'PidTagRecipientDisplayName' => '', 'PidTagRecipientEntryId' => '', // body properties 'PidTagBody' => '', 'PidTagNativeBody' => '', 'PidTagBodyHtml' => '', 'PidTagRtfCompressed' => '', 'PidTagRtfInSync' => '', 'PidTagInternetCodepage' => '', 'PidTagBodyContentId' => '', 'PidTagBodyContentLocation' => '', 'PidTagHtml' => '', // contact linking properties 'PidLidContactLinkEntry' => '', 'PidLidContacts' => '', 'PidLidContactLinkName' => '', 'PidLidContactLinkSearchKey' => '', // retention and archive properties 'PidTagArchiveTag' => '', 'PidTagPolicyTag' => '', 'PidTagRetentionPeriod' => '', 'PidTagStartDateEtc' => '', 'PidTagRetentionDate' => '', 'PidTagRetentionFlags' => '', 'PidTagArchivePeriod' => '', 'PidTagArchiveDate' => '', // MS-OXOMSG properties 'PidTagInternetMessageId' => 'message-id', 'PidTagInReplyToId' => 'in-reply-to', ); /** * Message flags for PidTagMessageFlags as defined in [MS-OXCMSG] */ protected $flags = array( 'mfRead' => 0x00000001, 'mfUnsent' => 0x00000008, 'mfResend' => 0x00000080, 'mfUnmodified' => 0x00000002, 'mfSubmitted' => 0x00000004, 'mfHasAttach' => 0x00000010, 'mfFromMe' => 0x00000020, 'mfFAI' => 0x00000040, 'mfNotifyRead' => 0x00000100, 'mfNotifyUnread' => 0x00000200, 'mfEventRead' => 0x00000400, 'mfInternet' => 0x00002000, 'mfUntrusted' => 0x00008000, ); /** * Message status for PidTagMessageStatus as defined in [MS-OXCMSG] */ protected $status = array( 'msRemoteDownload' => 0x00001000, 'msInConflict' => 0x00000800, 'msRemoteDelete' => 0x00002000, ); /** * Message importance for PidTagImportance as defined in [MS-OXCMSG] */ protected $importance = array( 'low' => 0x00000000, 'normal' => 0x00000001, 'high' => 0x00000002, ); /** * Message priority for PidTagPriority as defined in [MS-OXCMSG] */ protected $priority = array( 'urgent' => 0x00000001, 'normal' => 0x00000000, 'not-urgent' => 0xFFFFFFFF, ); /** * Message sesnitivity for PidTagSensitivity as defined in [MS-OXCMSG] */ protected $sensitivity = array( 'normal' => 0x00000000, 'personal' => 0x00000001, 'private' => 0x00000002, 'confidential' => 0x00000003, ); /** * Recipient type for PidTagRecipientType as defined in [MS-OXOMSG] */ protected $recipient_types = array( 'to' => 0x00000001, 'cc' => 0x00000002, 'bcc' => 0x00000003, ); /** * Convert Kolab to MAPI * * @param array Data * @param array Context (folder_uid, object_uid, object) * * @return array Data */ public function output($data, $context = null) { $result = array( 'PidTagMessageClass' => 'IPM.Note', // mapistore REST API specific properties 'collection' => 'mails', ); foreach ($this->map as $mapi_idx => $kolab_idx) { if (empty($kolab_idx)) { continue; } $value = kolab_api_filter_mapistore::get_kolab_value($data, $kolab_idx); switch ($kolab_idx) { -/* - Color property is not supported by Kolab format anymore (see #2918) - case 'color': - if ($value) { - $value = strtoupper($value); - $value = str_replace('#', '', $value); - $value = isset($this->color_map[$value]) ? $this->color_map[$value] : null; - } + case 'PidTagInternetMessageId': + $value = trim($value, '<>'); break; -*/ } if ($value === null) { continue; } $result[$mapi_idx] = $value; } // Recipients (To, Cc, Bcc) foreach (array('to', 'cc', 'bcc') as $idx) { foreach ((array) $data[$idx] as $address) { // @TODO: PidTagEntryId, PidTagEmailAddress if ($address['address']) { $recipient = array( 'PidTagSmtpAddress' => $address['address'], 'PidTagAddressType' => 'EX', 'PidTagRecipientType' => $this->recipient_types[$idx], ); if ($address['name']) { $recipient['PidTagDisplayName'] = $address['name']; } $result['recipients'][] = $recipient; } } } kolab_api_filter_mapistore::parse_common_props($result, $data, $context); return $result; } /** * Convert from MAPI to Kolab * * @param array Data * @param array Data of the object that is being updated * * @return array Data */ public function input($data, $object = null) { $result = array(); foreach ($this->map as $mapi_idx => $kolab_idx) { if (empty($kolab_idx)) { continue; } if (!array_key_exists($mapi_idx, $data)) { continue; } $value = $data[$mapi_idx]; switch ($mapi_idx) { /* case 'PidLidNoteColor': if (strlen($value)) { $map = array_flip($this->color_map); $value = isset($map[(int) $value]) ? $map[(int) $value] : ''; } break; */ } $result[$kolab_idx] = $value; } kolab_api_filter_mapistore::convert_common_props($result, $data); return $result; } /** * Returns the attributes names mapping */ public function map() { $map = array_filter($this->map); return $map; } } diff --git a/lib/filter/mapistore/structure.php b/lib/filter/mapistore/structure.php new file mode 100644 index 0000000..b3b4faa --- /dev/null +++ b/lib/filter/mapistore/structure.php @@ -0,0 +1,315 @@ + | + +--------------------------------------------------------------------------+ + | Author: Aleksander Machniak | + +--------------------------------------------------------------------------+ +*/ + +/** + * MAPI structures handler + */ +class kolab_api_filter_mapistore_structure +{ + protected $structure = array(); + protected $data = array(); + protected $lengths = array( + 'BYTE' => 1, + 'WORD' => 2, + 'LONG' => 4, + 'ULONG' => 4, + 'SYSTEMTIME' => 16, + ); + + /** + * Class constructor + * + * @param array Structure data properties + */ + public function __construct($data = array()) + { + if (!empty($data)) { + $this->data = $data; + } + } + + /** + * Convert binary input into internal structure + * + * @param string $input Binary representation of the structure + * @param bool $base64 Set to TRUE if the input is base64-encoded + * @param object $parent Parent structure + * + * @return int Number of bytes read from the binary input + */ + public function input($input, $base64 = false, $parent = null) + { + if ($base64) { + $input = base64_decode($input); + } + + $input_length = strlen($input); + $position = 0; + $counter = 0; + + foreach ($this->structure as $idx => $struct) { + $length = 0; + $class = null; + $is_array = false; + $count = 1; + + switch ($struct['type']) { + case 'EMPTY': + continue 2; + + case 'STRING': + $length = $struct['length'] ?: (int) $this->data[$struct['counter']]; + break; + + case 'WSTRING': + $length = $struct['length'] ?: ((int) $this->data[$struct['counter']]) * 2; + break; + + case 'BYTE': + case 'WORD': + case 'LONG': + case 'ULONG': + case 'SYSTEMTIME': + default: + if (preg_match('/^(LONG|ULONG|WORD|BYTE)\[([0-9]*)\]$/', $struct['type'], $m)) { + $is_array = true; + $count = $m[2] ? $m[2] : (int) $this->data[$struct['counter']]; + $struct['type'] = $m[1]; + $length = $this->lengths[$struct['type']]; + } + else if (preg_match('/^(\[?)(kolab_api_[a-z_]+)\]?$/', $struct['type'], $m)) { + $length = 0; + $class = $m[2]; + $is_array = !empty($m[1]); + $count = $is_array ? (int) $this->data[$struct['counter']] : 1; + } + else { + $length = $this->lengths[$struct['type']]; + } + } + + if ($length && $position >= $input_length) { + throw new kolab_api_exception(kolab_api_exception::SERVER_ERROR, array( + 'line' => __LINE__, + 'file' => __FILE__, + 'message' => 'Invalid MAPI structure for ' . get_class($this) + )); + } + + for ($i = 0; $i < $count; $i++) { + if ($length) { + $value = substr($input, $position, $length); + $position += $length; + } + else { + $value = null; + } + + switch ($struct['type']) { + case 'WSTRING': + $value = rcube_charset::convert($value, 'UTF-16LE', RCUBE_CHARSET); + // no-break + + case 'STRING': + break; + + case 'BYTE': + $value = ord($value); + break; + + case 'WORD': + $unpack = unpack('v', $value); + $value = $unpack[1]; + break; + + case 'LONG': + $unpack = unpack('l', $value); + $value = $unpack[1]; + break; + + case 'ULONG': + $unpack = unpack('V', $value); + $value = $unpack[1]; + break; + + case 'SYSTEMTIME': + $structure = new kolab_api_filter_mapistore_structure_systemtime; + $structure->input($value, false, $this, $is_array ? $i : null); + $value = $structure; + break; + + default: + $structure = new $class; + $position += $structure->input(substr($input, $position), false, $this, $is_array ? $i : null); + $value = $structure; + } + + if ($value !== null) { + if ($is_array) { + $this->data[$idx][] = $value; + } + else { + $this->data[$idx] = $value; + } + } + } + } + + return $position; + } + + /** + * Convert internal structure into binary string + * + * @param bool $base64 Enables base64 encoding of the output + * + * @return string Binary representation of the structure + */ + public function output($base64 = false) + { + $output = ''; + + foreach ($this->structure as $idx => $struct) { + $value = array_key_exists($idx, $this->data) ? $this->data[$idx] : $struct['default']; + + switch ($struct['type']) { + case 'EMPTY': + break; + + case 'WSTRING': + $value = rcube_charset::convert($value, RCUBE_CHARSET, 'UTF-16LE'); + // no-break + + case 'STRING': + $output .= $value; + break; + + case 'BYTE': + $output .= chr((int) $value); + break; + + case 'WORD': + $output .= pack('v', $value); + break; + + case 'LONG': + $output .= pack('l', $value); + break; + + case 'ULONG': + $output .= pack('V', $value); + break; + + case 'SYSTEMTIME': + if ($value instanceof kolab_api_filter_mapistore_structure_systemtime) { + $output .= $value->output(); + } + else { + $output .= pack('llll', 0, 0, 0, 0); + } + break; + + default: + if (preg_match('/^(LONG|WORD|ULONG|BYTE)\[([0-9]*)\]$/', $struct['type'], $m)) { + $count = $m[2] ? $m[2] : count((array) $value); + for ($x = 0; $x < $count; $x++) { + switch ($m[1]) { + case 'BYTE': + $output .= chr((int) $value[$x]); + break; + + case 'WORD': + $output .= pack('v', $value[$x]); + break; + + case 'LONG': + $output .= pack('l', $value[$x]); + break; + + case 'ULONG': + $output .= pack('V', $value[$x]); + break; + } + } + } + else if (preg_match('/^\[?(kolab_api_[a-z_]+)\]?$/', $struct['type'], $m)) { + $type = $m[1]; + if (!is_array($value)) { + $value = array($value); + } + + foreach ($value as $v) { + if (!($v instanceof $type)) { + throw new kolab_api_exception(kolab_api_exception::SERVER_ERROR, array( + 'line' => __LINE__, + 'file' => __FILE__, + 'message' => "Expected object of type $type" + )); + } + + $output .= $v->output(); + } + } + } + } + + if ($base64) { + $output = base64_encode($output); + } + + return $output; + } + + /** + * Sets class data item + */ + public function __set($name, $value) + { + if (!array_key_exists($name, $this->structure)) { + throw new kolab_api_exception(kolab_api_exception::SERVER_ERROR, array( + 'line' => __LINE__, + 'file' => __FILE__, + 'message' => 'Invalid member of MAPI structure: ' . get_class($this) . '::' . $name + )); + } + + $this->data[$name] = $value; + } + + /** + * Gets class data item + */ + public function __get($name) + { + if (!array_key_exists($name, $this->structure)) { + throw new kolab_api_exception(kolab_api_exception::SERVER_ERROR, array( + 'line' => __LINE__, + 'file' => __FILE__, + 'message' => 'Invalid member of MAPI structure: ' . get_class($this) . '::' . $name + )); + } + + return $this->data[$name]; + } +} diff --git a/lib/filter/mapistore/structure/appointmentrecurrencepattern.php b/lib/filter/mapistore/structure/appointmentrecurrencepattern.php new file mode 100644 index 0000000..1c7cfe4 --- /dev/null +++ b/lib/filter/mapistore/structure/appointmentrecurrencepattern.php @@ -0,0 +1,68 @@ + | + +--------------------------------------------------------------------------+ + | Author: Aleksander Machniak | + +--------------------------------------------------------------------------+ +*/ + +/** + * AppointmentRecurrencePattern structure definition according to MS-OXOCAL 2.2.1.44.5 + */ +class kolab_api_filter_mapistore_structure_appointmentrecurrencepattern extends kolab_api_filter_mapistore_structure +{ + protected $structure = array( + 'RecurrencePattern' => array('type' => 'kolab_api_filter_mapistore_structure_recurrencepattern'), + 'ReaderVersion' => array('type' => 'ULONG', 'default' => 0x00003006), + 'WriterVersion' => array('type' => 'ULONG', 'default' => 0x00003009), + 'StartTimeOffset' => array('type' => 'ULONG'), + 'EndTimeOffset' => array('type' => 'ULONG'), + 'ExceptionCount' => array('type' => 'WORD'), + 'ExceptionInfo' => array('type' => '[kolab_api_filter_mapistore_structure_exceptioninfo]', 'counter' => 'ExceptionCount'), + 'ReservedBlock1Size' => array('type' => 'ULONG', 'default' => 0), + 'ReservedBlock1' => array('type' => 'STRING', 'counter' => 'ReservedBlock1Size'), + 'ExtendedException' => array('type' => '[kolab_api_filter_mapistore_structure_extendedexception]', 'counter' => 'ExceptionCount'), + 'ReservedBlock2Size' => array('type' => 'ULONG', 'default' => 0), + 'ReservedBlock2' => array('type' => 'STRING', 'counter' => 'ReservedBlock2Size'), + ); + + /** + * Convert internal structure into binary string + * + * @param bool $base64 Enables base64 encoding of the output + * + * @return string Binary representation of the structure + */ + public function output($base64 = false) + { + if (count($this->data['ExceptionInfo']) != count($this->ExtendedException)) { + throw new kolab_api_exception(kolab_api_exception::SERVER_ERROR, array( + 'line' => __LINE__, + 'file' => __FILE__, + 'message' => 'ExceptionInfo and ExtendedException need to be of the same size' + )); + } + + $this->data['ExceptionCount'] = count($this->data['ExceptionInfo']); + $this->data['ReservedBlock1Size'] = strlen($this->data['ReservedBlock1']); + $this->data['ReservedBlock2Size'] = strlen($this->data['ReservedBlock2']); + + return parent::output($base64); + } +} diff --git a/lib/filter/mapistore/structure/changehighlight.php b/lib/filter/mapistore/structure/changehighlight.php new file mode 100644 index 0000000..75fed18 --- /dev/null +++ b/lib/filter/mapistore/structure/changehighlight.php @@ -0,0 +1,72 @@ + | + +--------------------------------------------------------------------------+ + | Author: Aleksander Machniak | + +--------------------------------------------------------------------------+ +*/ + +/** + * ChangeHighlight structure definition according to MS-OXOCAL 2.2.1.44.3 + */ +class kolab_api_filter_mapistore_structure_changehighlight extends kolab_api_filter_mapistore_structure +{ + protected $structure = array( + 'ChangeHighlightSize' => array('type' => 'ULONG'), + 'ChangeHighlightValue' => array('type' => 'ULONG'), + 'Reserved' => array('type' => 'STRING'), + ); + + /** + * Convert binary input into internal structure + * + * @param string $input Binary representation of the structure + * @param bool $base64 Set to TRUE if the input is base64-encoded + * + * @return int Number of bytes read from the binary input + */ + public function input($input, $base64 = false) + { + if ($base64) { + $input = base64_decode($input); + } + + // Read size + $unpack = unpack('V', substr($input, 0, 4)); + $value = $unpack[1]; + + $this->structure['Reserved']['length'] = $value - 4; + + return parent::input($input, false); + } + + /** + * Convert internal structure into binary string + * + * @param bool $base64 Enables base64 encoding of the output + * + * @return string Binary representation of the structure + */ + public function output($base64 = false) + { + $this->data['ChangeHighlightSize'] = strlen($this->data['Reserved']) + 4; + + return parent::output($base64); + } +} diff --git a/lib/filter/mapistore/structure/exceptioninfo.php b/lib/filter/mapistore/structure/exceptioninfo.php new file mode 100644 index 0000000..536d131 --- /dev/null +++ b/lib/filter/mapistore/structure/exceptioninfo.php @@ -0,0 +1,200 @@ + | + +--------------------------------------------------------------------------+ + | Author: Aleksander Machniak | + +--------------------------------------------------------------------------+ +*/ + +/** + * ExceptionInfo structure definition according to MS-OXOCAL 2.2.1.44.2 + */ +class kolab_api_filter_mapistore_structure_exceptioninfo extends kolab_api_filter_mapistore_structure +{ + protected $structure = array( + 'StartDateTime' => array('type' => 'ULONG'), + 'EndDateTime' => array('type' => 'ULONG'), + 'OriginalStartDate' => array('type' => 'ULONG'), + 'OverrideFlags' => array('type' => 'WORD'), + 'SubjectLength' => array('type' => 'WORD'), + 'SubjectLength2' => array('type' => 'WORD'), + 'Subject' => array('type' => 'STRING', 'counter' => 'SubjectLength2'), + 'MeetingType' => array('type' => 'ULONG'), + 'ReminderDelta' => array('type' => 'ULONG'), + 'ReminderSet' => array('type' => 'ULONG'), + 'LocationLength' => array('type' => 'WORD'), + 'LocationLength2' => array('type' => 'WORD'), + 'Location' => array('type' => 'STRING', 'counter' => 'LocationLength2'), + 'BusyStatus' => array('type' => 'ULONG'), + 'Attachment' => array('type' => 'ULONG'), + 'SubType' => array('type' => 'ULONG'), + 'AppointmentColor' => array('type' => 'ULONG'), + ); + + const OVERRIDEFLAGS_ARO_SUBJECT = 0x0001; + const OVERRIDEFLAGS_ARO_MEETINGTYPE = 0x0002; + const OVERRIDEFLAGS_ARO_REMINDERDELTA = 0x0004; + const OVERRIDEFLAGS_ARO_REMINDER = 0x0008; + const OVERRIDEFLAGS_ARO_LOCATION = 0x0010; + const OVERRIDEFLAGS_ARO_BUSYSTATUS = 0x0020; + const OVERRIDEFLAGS_ARO_ATTACHMENT = 0x0040; + const OVERRIDEFLAGS_ARO_SUBTYPE = 0x0080; + const OVERRIDEFLAGS_ARO_APPTCOLOR = 0x0100; + const OVERRIDEFLAGS_ARO_EXCEPTIONAL_BODY = 0x0200; + + /** + * Convert binary input into internal structure + * + * @param string $input Binary representation of the structure + * @param bool $base64 Set to TRUE if the input is base64-encoded + * + * @return int Number of bytes read from the binary input + */ + public function input($input, $base64 = false) + { + if ($base64) { + $input = base64_decode($input); + } + + // Read OverrideFlags + $unpack = unpack('v', substr($input, 12, 2)); + $value = $unpack[1]; + + $this->data['OverrideFlags'] = $value; + + // modify structure according to OverrideFlags + $this->set_structure(); + + return parent::input($input, false); + } + + /** + * Convert internal structure into binary string + * + * @param bool $base64 Enables base64 encoding of the output + * + * @return string Binary representation of the structure + */ + public function output($base64 = false) + { + $flags = 0; + + if ($this->data['Subject'] !== null) { + $flags += self::OVERRIDEFLAGS_ARO_SUBJECT; + $length = strlen($this->data['Subject']); + + $this->data['SubjectLength2'] = $length; + $this->data['SubjectLength'] = $length + 1; + } + + if ($this->data['Location'] !== null) { + $flags += self::OVERRIDEFLAGS_ARO_LOCATION; + $length = strlen($this->data['Location']); + + $this->data['LocationLength2'] = $length; + $this->data['LocationLength'] = $length + 1; + } + + if ($this->data['MeetingType'] !== null) { + $flags += self::OVERRIDEFLAGS_ARO_MEETINGTYPE; + } + + if ($this->data['ReminderDelta'] !== null) { + $flags += self::OVERRIDEFLAGS_ARO_REMINDERDELTA; + } + + if ($this->data['ReminderSet'] !== null) { + $flags += self::OVERRIDEFLAGS_ARO_REMINDER; + } + + if ($this->data['BusyStatus'] !== null) { + $flags += self::OVERRIDEFLAGS_ARO_BUSYSTATUS; + } + + if ($this->data['Attachment'] !== null) { + $flags += self::OVERRIDEFLAGS_ARO_ATTACHMENT; + } + + if ($this->data['SubType'] !== null) { + $flags += self::OVERRIDEFLAGS_ARO_SUBTYPE; + } + + if ($this->data['AppointmentColor'] !== null) { + $flags += self::OVERRIDEFLAGS_ARO_APPTCOLOR; + } + + $this->data['OverrideFlags'] = $flags; + + $this->set_structure(); + return parent::output($base64); + } + + /** + * Modify the structure according to OverrideFlags + */ + protected function set_structure() + { + $flags = $this->data['OverrideFlags']; + + // Enable/Disable structure fields according to OverrideFlags + if (!($flags & self::OVERRIDEFLAGS_ARO_SUBJECT)) { + $this->structure['Subject']['type'] = 'EMPTY'; + $this->structure['SubjectLength']['type'] = 'EMPTY'; + $this->structure['SubjectLength2']['type'] = 'EMPTY'; + } + + if (!($flags & self::OVERRIDEFLAGS_ARO_MEETINGTYPE)) { + $this->structure['MeetingType']['type'] = 'EMPTY'; + } + + if (!($flags & self::OVERRIDEFLAGS_ARO_REMINDERDELTA)) { + $this->structure['ReminderDelta']['type'] = 'EMPTY'; + } + + if (!($flags & self::OVERRIDEFLAGS_ARO_REMINDER)) { + $this->structure['ReminderSet']['type'] = 'EMPTY'; + } + + if (!($flags & self::OVERRIDEFLAGS_ARO_LOCATION)) { + $this->structure['Location']['type'] = 'EMPTY'; + $this->structure['LocationLength']['type'] = 'EMPTY'; + $this->structure['LocationLength2']['type'] = 'EMPTY'; + } + + if (!($flags & self::OVERRIDEFLAGS_ARO_BUSYSTATUS)) { + $this->structure['BusyStatus']['type'] = 'EMPTY'; + } + + if (!($flags & self::OVERRIDEFLAGS_ARO_ATTACHMENT)) { + $this->structure['Attachment']['type'] = 'EMPTY'; + } + + if (!($flags & self::OVERRIDEFLAGS_ARO_SUBTYPE)) { + $this->structure['SubType']['type'] = 'EMPTY'; + } + + if (!($flags & self::OVERRIDEFLAGS_ARO_APPTCOLOR)) { + $this->structure['AppointmentColor']['type'] = 'EMPTY'; + } + + if ($flags & self::OVERRIDEFLAGS_ARO_EXCEPTIONAL_BODY) { + // @TODO + } + } +} diff --git a/lib/filter/mapistore/structure/extendedexception.php b/lib/filter/mapistore/structure/extendedexception.php new file mode 100644 index 0000000..8c831a3 --- /dev/null +++ b/lib/filter/mapistore/structure/extendedexception.php @@ -0,0 +1,129 @@ + | + +--------------------------------------------------------------------------+ + | Author: Aleksander Machniak | + +--------------------------------------------------------------------------+ +*/ + +/** + * ExtendedException structure definition according to MS-OXOCAL 2.2.1.44.4 + */ +class kolab_api_filter_mapistore_structure_extendedexception extends kolab_api_filter_mapistore_structure +{ + protected $parent; + protected $structure = array( + 'ChangeHighlight' => array('type' => 'kolab_api_filter_mapistore_structure_changehighlight'), + 'ReservedBlockEE1Size' => array('type' => 'ULONG', 'default' => 0), + 'ReservedBlockEE1' => array('type' => 'STRING', 'counter' => 'ReservedBlockEE1Size'), + 'StartDateTime' => array('type' => 'ULONG'), + 'EndDateTime' => array('type' => 'ULONG'), + 'OriginalStartDate' => array('type' => 'ULONG'), + 'WideCharSubjectLength' => array('type' => 'WORD',), + 'WideCharSubject' => array('type' => 'WSTRING', 'counter' => 'WideCharSubjectLength'), + 'WideCharLocationLength' => array('type' => 'WORD'), + 'WideCharLocation' => array('type' => 'WSTRING', 'counter' => 'WideCharLocationLength'), + 'ReservedBlockEE2Size' => array('type' => 'ULONG', 'default' => 0), + 'ReservedBlockEE2' => array('type' => 'STRING', 'counter' => 'ReservedBlockEE2Size'), + ); + + /** + * Convert binary input into internal structure + * + * @param string $input Binary representation of the structure + * @param bool $base64 Set to TRUE if the input is base64-encoded + * @param object $parent Parent structure + * @param int $index Index in the parent property array + * + * @return int Number of bytes read from the binary input + */ + public function input($input, $base64 = false, $parent = null, $index = null) + { + if ($base64) { + $input = base64_decode($input); + } + + // read OverrideFlags from matching ExceptionInfo + if (empty($parent) || $index === null || !array_key_exists($index, (array) $parent->ExceptionInfo)) { + throw new kolab_api_exception(kolab_api_exception::SERVER_ERROR, array( + 'line' => __LINE__, + 'file' => __FILE__, + 'message' => 'Missing ExceptionInfo structure for ' . get_class($this) + )); + } + + $flags = $parent->ExceptionInfo[$index]->OverrideFlags; + + if (!($flags & kolab_api_filter_mapistore_structure_exceptioninfo::OVERRIDEFLAGS_ARO_SUBJECT)) { + $no_subject = true; + $this->structure['WideCharSubject']['type'] = 'EMPTY'; + $this->structure['WideCharSubjectLength']['type'] = 'EMPTY'; + } + + if (!($flags & kolab_api_filter_mapistore_structure_exceptioninfo::OVERRIDEFLAGS_ARO_LOCATION)) { + $no_location = true; + $this->structure['WideCharLocation']['type'] = 'EMPTY'; + $this->structure['WideCharLocationLength']['type'] = 'EMPTY'; + } + + if ($no_subject && $no_location) { + $this->structure['StartDateTime']['type'] = 'EMPTY'; + $this->structure['EndDateTime']['type'] = 'EMPTY'; + $this->structure['OriginalStartDate']['type'] = 'EMPTY'; + } + + return parent::input($input, false); + } + + /** + * Convert internal structure into binary string + * + * @param bool $base64 Enables base64 encoding of the output + * + * @return string Binary representation of the structure + */ + public function output($base64 = false) + { + if ($this->data['WideCharSubject'] !== null) { + $got_subject = true; + $this->data['WideCharSubjectLength'] = mb_strlen($this->data['WideCharSubject']); + } + else { + $this->structure['WideCharSubjectLength']['type'] = 'EMPTY'; + $this->structure['WideCharSubject']['type'] = 'EMPTY'; + } + + if ($this->data['WideCharLocation'] !== null) { + $got_location = true; + $this->data['WideCharLocationLength'] = mb_strlen($this->data['WideCharLocation']); + } + else { + $this->structure['WideCharLocationLength']['type'] = 'EMPTY'; + $this->structure['WideCharLocation']['type'] = 'EMPTY'; + } + + if (!$got_subject && !$got_location) { + $this->structure['StartDateTime']['type'] = 'EMPTY'; + $this->structure['EndDateTime']['type'] = 'EMPTY'; + $this->structure['OriginalStartDate']['type'] = 'EMPTY'; + } + + return parent::output($base64); + } +} diff --git a/lib/filter/mapistore/structure/recipientrow.php b/lib/filter/mapistore/structure/recipientrow.php new file mode 100644 index 0000000..7b7fe8e --- /dev/null +++ b/lib/filter/mapistore/structure/recipientrow.php @@ -0,0 +1,55 @@ + | + +--------------------------------------------------------------------------+ + | Author: Aleksander Machniak | + +--------------------------------------------------------------------------+ +*/ + +/** + * RecipientRow structure definition according to MS-OXCDATA 2.8.3 + */ +class kolab_api_filter_mapistore_structure_recipientrow extends kolab_api_filter_mapistore_structure +{ + protected $structure = array( + 'RecipientFlags' => array('type' => 'kolab_api_filter_mapistore_structure_recipientflags'), + 'AddressPrefixUsed' => array('type' => 'BYTE'), + 'DisplayType' => array('type' => 'WORD'), + 'X500DN' => array('type' => 'STRING'), + 'EntryIdSize' => array('type' => 'WORD', 'default' => 0), + 'EntryId' => array('type' => 'STRING'), + 'SearchKeySize' => array('type' => 'WORD'), + 'SearchKey' => array(), + 'AddressType' => array(), + 'EmailAddress' => array(), + 'DisplayName' => array(), + 'SimpleDisplayName' => array(), + 'TransmittableDisplayName' => array(), + 'RecipientColumnCount' => array('type' => 'WORD'), + 'RecipientProperties' => array('type' => '[kolab_api_filter_mapistore_structure_propertyrow]'), + ); + + const DISPLAYTYPE_USER = 0x00; + const DISPLAYTYPE_LIST = 0x01; + const DISPLAYTYPE_FORUM = 0x02; + const DISPLAYTYPE_AGENT = 0x03; + const DISPLAYTYPE_ABOOK = 0x04; + const DISPLAYTYPE_PRIVATE = 0x05; + const DISPLAYTYPE_ABOOK_REMOTE = 0x06; +} diff --git a/lib/filter/mapistore/structure/recurrencepattern.php b/lib/filter/mapistore/structure/recurrencepattern.php new file mode 100644 index 0000000..463d925 --- /dev/null +++ b/lib/filter/mapistore/structure/recurrencepattern.php @@ -0,0 +1,167 @@ + | + +--------------------------------------------------------------------------+ + | Author: Aleksander Machniak | + +--------------------------------------------------------------------------+ +*/ + +/** + * RecurrencePattern structure definition according to MS-OXOCAL 2.2.1.44.1 + */ +class kolab_api_filter_mapistore_structure_recurrencepattern extends kolab_api_filter_mapistore_structure +{ + protected $structure = array( + 'ReaderVersion' => array('type' => 'WORD', 'default' => 0x3004), + 'WriterVersion' => array('type' => 'WORD', 'default' => 0x3004), + 'RecurFrequency' => array('type' => 'WORD'), + 'PatternType' => array('type' => 'WORD'), + 'CalendarType' => array('type' => 'WORD'), + 'FirstDateTime' => array('type' => 'ULONG'), + 'Period' => array('type' => 'ULONG'), + 'SlidingFlag' => array('type' => 'ULONG', 'default' => 0), + 'PatternTypeSpecific' => array('type' => 'EMPTY'), // default for PatternType=0 + 'EndType' => array('type' => 'ULONG'), + 'OccurrenceCount' => array('type' => 'ULONG', 'default' => 0x0000000A), + 'FirstDOW' => array('type' => 'ULONG', 'default' => 0), + 'DeletedInstanceCount' => array('type' => 'ULONG', 'default' => 0), + 'DeletedInstanceDates' => array('type' => 'ULONG[]', 'counter' => 'DeletedInstanceCount'), + 'ModifiedInstanceCount' => array('type' => 'ULONG', 'default' => 0), + 'ModifiedInstanceDates' => array('type' => 'ULONG[]', 'counter' => 'ModifiedInstanceCount'), + 'StartDate' => array('type' => 'ULONG'), + 'EndDate' => array('type' => 'ULONG'), + ); + + const RECURFREQUENCY_DAILY = 0x200A; + const RECURFREQUENCY_WEEKLY = 0x200B; + const RECURFREQUENCY_MONTHLY = 0x200C; + const RECURFREQUENCY_YEARLY = 0x200D; + + const PATTERNTYPE_DAY = 0x0000; + const PATTERNTYPE_WEEK = 0x0001; + const PATTERNTYPE_MONTH = 0x0002; + const PATTERNTYPE_MONTHEND = 0x0004; + const PATTERNTYPE_MONTHNTH = 0x0003; + const PATTERNTYPE_HJMONTH = 0x000A; + const PATTERNTYPE_HJMONTHNTH = 0x000B; + const PATTERNTYPE_HJMONTHEND = 0x000C; + + const CALENDARTYPE_GREGORIAN = 0x0001; + const CALENDARTYPE_GREGORIAN_US = 0x0002; + const CALENDARTYPE_JAPAN = 0x0003; + const CALENDARTYPE_TAIWAN = 0x0004; + const CALENDARTYPE_KOREA = 0x0005; + const CALENDARTYPE_HIJRI = 0x0006; + const CALENDARTYPE_THAI = 0x0007; + const CALENDARTYPE_HEBREW = 0x0008; + const CALENDARTYPE_GREGORIAN_ME_FRENCH = 0x0009; + const CALENDARTYPE_GREGORIAN_ARABIC = 0x000A; + const CALENDARTYPE_GREGORIAN_XLIT_ENGLISH = 0x000B; + const CALENDARTYPE_GREGORIAN_XLIT_FRENCH = 0x000C; + const CALENDARTYPE_LUNAR_JAPANESE = 0x000E; + const CALENDARTYPE_CHINESE_LUNAR = 0x000F; + const CALENDARTYPE_SAKA = 0x0010; + const CALENDARTYPE_LUNAR_ETO_CHN = 0x0011; + const CALENDARTYPE_LUNAR_ETO_KOR = 0x0012; + const CALENDARTYPE_LUNAR_ROKUYOU = 0x0013; + const CALENDARTYPE_LUNAR_KOREAN = 0x0014; + const CALENDARTYPE_UMALQURA = 0x0017; + + const ENDTYPE_AFTER = 0x00002021; + const ENDTYPE_NOCC = 0x00002022; + const ENDTYPE_NEVER = 0x00002023; // can be 0xffffffff + + const FIRSTDOW_SUNDAY = 0x00000000; + const FIRSTDOW_MONDAY = 0x00000001; + const FIRSTDOW_TUESDAY = 0x00000002; + const FIRSTDOW_WEDNESDAY = 0x00000003; + const FIRSTDOW_THURSDAY = 0x00000004; + const FIRSTDOW_FRIDAY = 0x00000005; + const FIRSTDOW_SATURDAY = 0x00000006; + + /** + * Convert binary input into internal structure + * + * @param string $input Binary representation of the structure + * @param bool $base64 Set to TRUE if the input is base64-encoded + * + * @return int Number of bytes read from the binary input + */ + public function input($input, $base64 = false) + { + if ($base64) { + $input = base64_decode($input); + } + + // Read PatternType + $unpack = unpack('v', substr($input, 6, 2)); + $value = $unpack[1]; + + $this->data['PatternType'] = $value; + + // modify structure according to PatternType + $this->set_structure(); + + return parent::input($input, false); + } + + /** + * Convert internal structure into binary string + * + * @param bool $base64 Enables base64 encoding of the output + * + * @return string Binary representation of the structure + */ + public function output($base64 = false) + { + $this->set_structure(); + + $this->data['DeletedInstanceCount'] = count($this->data['DeletedInstanceDates']); + $this->data['ModifiedInstanceCount'] = count($this->data['ModifiedInstanceDates']); + + return parent::output($base64); + } + + /** + * Modify the structure according to PatternType + */ + protected function set_structure() + { + // Set PatternTypeSpecific field type according to PatternType + switch ($this->data['PatternType']) { + case self::PATTERNTYPE_WEEK: + case self::PATTERNTYPE_MONTH: + case self::PATTERNTYPE_MONTHEND: + case self::PATTERNTYPE_HJMONTH: + case self::PATTERNTYPE_HJMONTHEND: + $this->structure['PatternTypeSpecific']['type'] = 'ULONG'; + break; + + case self::PATTERNTYPE_MONTHNTH: + case self::PATTERNTYPE_HJMONTHNTH: + $this->structure['PatternTypeSpecific']['type'] = 'ULONG[2]'; + break; + + case self::PATTERNTYPE_DAY: + default: + $this->structure['PatternTypeSpecific']['type'] = 'EMPTY'; + break; + } + } +} diff --git a/lib/filter/mapistore/structure/systemtime.php b/lib/filter/mapistore/structure/systemtime.php new file mode 100644 index 0000000..f53107d --- /dev/null +++ b/lib/filter/mapistore/structure/systemtime.php @@ -0,0 +1,40 @@ + | + +--------------------------------------------------------------------------+ + | Author: Aleksander Machniak | + +--------------------------------------------------------------------------+ +*/ + +/** + * Systemtime structure definition according to MS-DTYP 2.3.13 + */ +class kolab_api_filter_mapistore_structure_systemtime extends kolab_api_filter_mapistore_structure +{ + protected $structure = array( + 'WYear' => array('type' => 'WORD'), + 'WMonth' => array('type' => 'WORD'), + 'WDayOfWeek' => array('type' => 'WORD'), + 'WDay' => array('type' => 'WORD'), + 'WHour' => array('type' => 'WORD'), + 'WMinute' => array('type' => 'WORD'), + 'WSecond' => array('type' => 'WORD'), + 'WMilliseconds' => array('type' => 'WORD'), + ); +} diff --git a/lib/filter/mapistore/structure/timezonestruct.php b/lib/filter/mapistore/structure/timezonestruct.php new file mode 100644 index 0000000..14911f6 --- /dev/null +++ b/lib/filter/mapistore/structure/timezonestruct.php @@ -0,0 +1,39 @@ + | + +--------------------------------------------------------------------------+ + | Author: Aleksander Machniak | + +--------------------------------------------------------------------------+ +*/ + +/** + * TimeZoneStruct structure definition according to MS-OXOCAL 2.2.1.39 + */ +class kolab_api_filter_mapistore_structure_timezonestruct extends kolab_api_filter_mapistore_structure +{ + protected $structure = array( + 'IBias' => array('type' => 'LONG'), + 'IStandardBias' => array('type' => 'LONG'), + 'IDaylightBias' => array('type' => 'LONG'), + 'WStandardYear' => array('type' => 'WORD'), + 'StStandardDate' => array('type' => 'SYSTEMTIME'), + 'WDaylightYear' => array('type' => 'WORD'), + 'StDaylightDate' => array('type' => 'SYSTEMTIME'), + ); +} diff --git a/lib/filter/mapistore/structure/tzrule.php b/lib/filter/mapistore/structure/tzrule.php new file mode 100644 index 0000000..56fb041 --- /dev/null +++ b/lib/filter/mapistore/structure/tzrule.php @@ -0,0 +1,46 @@ + | + +--------------------------------------------------------------------------+ + | Author: Aleksander Machniak | + +--------------------------------------------------------------------------+ +*/ + +/** + * TZRule structure definition according to MS-OXOCAL 2.2.1.41.1 + */ +class kolab_api_filter_mapistore_structure_tzrule extends kolab_api_filter_mapistore_structure +{ + protected $structure = array( + 'MajorVersion' => array('type' => 'BYTE', 'default' => 0x01), + 'MinorVersion' => array('type' => 'BYTE', 'default' => 0x01), + 'Reserved' => array('type' => 'WORD', 'default' => 0x003E), + 'Flags' => array('type' => 'WORD', 'default' => 0), + 'WYear' => array('type' => 'WORD'), + 'X' => array('type' => 'BYTE[14]', 'default' => 0), + 'IBias' => array('type' => 'LONG'), + 'IStandardBias' => array('type' => 'LONG'), + 'IDaylightBias' => array('type' => 'LONG'), + 'StStandardDate' => array('type' => 'SYSTEMTIME'), + 'StDaylightDate' => array('type' => 'SYSTEMTIME'), + ); + + const FLAG_RECUR_CURRENT = 1; + const FLAG_EFFECTIVE = 2; +} diff --git a/tests/Unit/Filter/Mapistore/Structure/Appointmentrecurrencepattern.php b/tests/Unit/Filter/Mapistore/Structure/Appointmentrecurrencepattern.php new file mode 100644 index 0000000..7cbac22 --- /dev/null +++ b/tests/Unit/Filter/Mapistore/Structure/Appointmentrecurrencepattern.php @@ -0,0 +1,121 @@ + '', // taken from recurrencepattern test + 'ReaderVersion' => '06300000', + 'WriterVersion' => '09300000', + 'StartTimeOffset' => '58020000', + 'EndTimeOffset' => '76020000', + 'ExceptionCount' => '0100', + 'ExceptionInfo' => '', // taken from exceptioninfo test + 'ReservedBlock1Size' => '00000000', + 'ExtendedException' => '', // taken from extendedexception test + 'ReservedBlock2Size' => '00000000', + ); + + + /** + * Test output method + */ + function test_output() + { + $structure = new kolab_api_filter_mapistore_structure_appointmentrecurrencepattern; + $exceptioninfo = new kolab_api_filter_mapistore_structure_exceptioninfo; + $recurrencepattern = new kolab_api_filter_mapistore_structure_recurrencepattern; + $extendedexception = new kolab_api_filter_mapistore_structure_extendedexception; + $highlight = new kolab_api_filter_mapistore_structure_changehighlight; + + $highlight->ChangeHighlightValue = 4; + $extendedexception->ChangeHighlight = $highlight; + $extendedexception->StartDateTime = 0x0CBC9934; + $extendedexception->EndDateTime = 0x0CBC9952; + $extendedexception->OriginalStartDate = 0x0CBC98F8; + $extendedexception->WideCharSubject = 'Simple Recurrence with exceptions'; + $extendedexception->WideCharLocation = '34/4141'; + + $recurrencepattern->RecurFrequency = 0x200b; + $recurrencepattern->PatternType = 1; + $recurrencepattern->CalendarType = 0; + $recurrencepattern->FirstDateTime = 0x000021C0; + $recurrencepattern->Period = 1; + $recurrencepattern->SlidingFlag = 0; + $recurrencepattern->PatternTypeSpecific = 0x00000032; + $recurrencepattern->EndType = 0x00002022; + $recurrencepattern->OccurrenceCount = 12; + $recurrencepattern->FirstDOW = 0; + $recurrencepattern->DeletedInstanceDates = array(0x0CBC96A0); + $recurrencepattern->ModifiedInstanceDates = array(0x0CBC96A0); + $recurrencepattern->StartDate = 213655680; + $recurrencepattern->EndDate = 0x0CBCAD20; + + $exceptioninfo->StartDateTime = 0x0CBC9934; + $exceptioninfo->EndDateTime = 0x0CBC9952; + $exceptioninfo->OriginalStartDate = 0x0CBC98F8; + $exceptioninfo->Subject = 'Simple Recurrence with exceptions'; + $exceptioninfo->Location = '34/4141'; + + $structure->StartTimeOffset = 600; + $structure->EndTimeOffset = 630; + $structure->ExceptionInfo = array($exceptioninfo); + $structure->RecurrencePattern = $recurrencepattern; + $structure->ExtendedException = array($extendedexception); + + $result = $structure->output(); + + require_once __DIR__ . '/Recurrencepattern.php'; + require_once __DIR__ . '/Exceptioninfo.php'; + require_once __DIR__ . '/Extendedexception.php'; + + $expected = self::$sample; + $expected['RecurrencePattern'] = implode('', KolabApiFilterMapistoreStructureRecurrencepattern::$sample); + $expected['ExceptionInfo'] = implode('', KolabApiFilterMapistoreStructureExceptioninfo::$sample); + $expected['ExtendedException'] = implode('', KolabApiFilterMapistoreStructureExtendedexception::$sample); + + $this->assertSame(strtoupper(bin2hex($result)), implode('', $expected)); + } + + /** + * Test input method + */ + function test_input() + { + $structure = new kolab_api_filter_mapistore_structure_appointmentrecurrencepattern; + + require_once __DIR__ . '/Recurrencepattern.php'; + require_once __DIR__ . '/Exceptioninfo.php'; + require_once __DIR__ . '/Extendedexception.php'; + + $in = self::$sample; + $in['RecurrencePattern'] = implode('', KolabApiFilterMapistoreStructureRecurrencepattern::$sample); + $in['ExceptionInfo'] = implode('', KolabApiFilterMapistoreStructureExceptioninfo::$sample); + $in['ExtendedException'] = implode('', KolabApiFilterMapistoreStructureExtendedexception::$sample); + + // convert input data into binary format + $in = pack("H*" , implode('', $in)); + $len = strlen($in); + + $result = $structure->input($in); + + $this->assertSame($len, $result); + $this->assertInstanceOf('kolab_api_filter_mapistore_structure_recurrencepattern', $structure->RecurrencePattern); + $this->assertSame(0x00003006, $structure->ReaderVersion); + $this->assertSame(0x00003009, $structure->WriterVersion); + $this->assertSame(600, $structure->StartTimeOffset); + $this->assertSame(630, $structure->EndTimeOffset); + $this->assertSame(1, $structure->ExceptionCount); + $this->assertInternalType('array', $structure->ExceptionInfo); + $this->assertInstanceOf('kolab_api_filter_mapistore_structure_exceptioninfo', $structure->ExceptionInfo[0]); + $this->assertInternalType('array', $structure->ExtendedException); + $this->assertInstanceOf('kolab_api_filter_mapistore_structure_extendedexception', $structure->ExtendedException[0]); + $this->assertSame(0, $structure->ReservedBlock1Size); + $this->assertSame(0, $structure->ReservedBlock2Size); + } +} diff --git a/tests/Unit/Filter/Mapistore/Structure/Changehighlight.php b/tests/Unit/Filter/Mapistore/Structure/Changehighlight.php new file mode 100644 index 0000000..21058fb --- /dev/null +++ b/tests/Unit/Filter/Mapistore/Structure/Changehighlight.php @@ -0,0 +1,49 @@ + '04000000', + 'ChangeHighlightValue' => '04000000', + ); + + + /** + * Test output method + */ + function test_output() + { + $structure = new kolab_api_filter_mapistore_structure_changehighlight; + + $structure->ChangeHighlightValue = 4; + + $result = $structure->output(); + + $this->assertSame(strtoupper(bin2hex($result)), implode('', self::$sample)); + } + + /** + * Test input method + */ + function test_input() + { + $structure = new kolab_api_filter_mapistore_structure_changehighlight; + + // convert input data into binary format + $in = pack("H*" , implode('', self::$sample)); + $len = strlen($in); + + $result = $structure->input($in); + + $this->assertSame($len, $result); + $this->assertSame(4, $structure->ChangeHighlightSize); + $this->assertSame(4, $structure->ChangeHighlightValue); + $this->assertSame(null, $structure->Reserved); + } +} diff --git a/tests/Unit/Filter/Mapistore/Structure/Exceptioninfo.php b/tests/Unit/Filter/Mapistore/Structure/Exceptioninfo.php new file mode 100644 index 0000000..d1026da --- /dev/null +++ b/tests/Unit/Filter/Mapistore/Structure/Exceptioninfo.php @@ -0,0 +1,68 @@ + '3499BC0C', + 'EndDateTime' => '5299BC0C', + 'OriginalStartDate' => 'F898BC0C', + 'OverrideFlags' => '1100', + 'SubjectLength' => '2200', + 'SubjectLength2' => '2100', + 'Subject' => '53696D706C6520526563757272656E6365207769746820657863657074696F6E73', + 'LocationLength' => '0800', + 'LocationLength2' => '0700', + 'Location' => '33342F34313431', + ); + + + /** + * Test output method + */ + function test_output() + { + $structure = new kolab_api_filter_mapistore_structure_exceptioninfo; + + $structure->StartDateTime = 0x0CBC9934; + $structure->EndDateTime = 0x0CBC9952; + $structure->OriginalStartDate = 0x0CBC98F8; + $structure->Subject = 'Simple Recurrence with exceptions'; + $structure->Location = '34/4141'; + + $result = $structure->output(); + + $this->assertSame(strtoupper(bin2hex($result)), implode('', self::$sample)); + } + + /** + * Test input method + */ + function test_input() + { + $structure = new kolab_api_filter_mapistore_structure_exceptioninfo; + + // convert input data into binary format + $in = pack("H*" , implode('', self::$sample)); + $len = strlen($in); + + $result = $structure->input($in); + + $this->assertSame($len, $result); + $this->assertSame(0x0CBC9934, $structure->StartDateTime); + $this->assertSame(0x0CBC9952, $structure->EndDateTime); + $this->assertSame(0x0CBC98F8, $structure->OriginalStartDate); + $this->assertSame(0x0011, $structure->OverrideFlags); + $this->assertSame(34, $structure->SubjectLength); + $this->assertSame(33, $structure->SubjectLength2); + $this->assertSame('Simple Recurrence with exceptions', $structure->Subject); + $this->assertSame(8, $structure->LocationLength); + $this->assertSame(7, $structure->LocationLength2); + $this->assertSame('34/4141', $structure->Location); + } +} diff --git a/tests/Unit/Filter/Mapistore/Structure/Extendedexception.php b/tests/Unit/Filter/Mapistore/Structure/Extendedexception.php new file mode 100644 index 0000000..58b6479 --- /dev/null +++ b/tests/Unit/Filter/Mapistore/Structure/Extendedexception.php @@ -0,0 +1,76 @@ + '0400000004000000', + 'ReservedBlockEE1Size' => '00000000', + 'StartDateTime' => '3499BC0C', + 'EndDateTime' => '5299BC0C', + 'OriginalStartDate' => 'F898BC0C', + 'WideCharSubjectLength' => '2100', + 'WideCharSubject' => '530069006D0070006C006500200052006500630075007200720065006E006300650020007700690074006800200065007800630065007000740069006F006E007300', + 'WideCharLocationLength' => '0700', + 'WideCharLocation' => '330034002F003400310034003100', + 'ReservedBlockEE2Size' => '00000000', + ); + + + /** + * Test output method + */ + function test_output() + { + $structure = new kolab_api_filter_mapistore_structure_extendedexception; + $highlight = new kolab_api_filter_mapistore_structure_changehighlight; + + $highlight->ChangeHighlightValue = 4; + $structure->ChangeHighlight = $highlight; + $structure->StartDateTime = 0x0CBC9934; + $structure->EndDateTime = 0x0CBC9952; + $structure->OriginalStartDate = 0x0CBC98F8; + $structure->WideCharSubject = 'Simple Recurrence with exceptions'; + $structure->WideCharLocation = '34/4141'; + + $result = $structure->output(); + + $this->assertSame(strtoupper(bin2hex($result)), implode('', self::$sample)); + } + + /** + * Test input method + */ + function test_input() + { + $structure = new kolab_api_filter_mapistore_structure_extendedexception; + $parent = new kolab_api_filter_mapistore_structure_appointmentrecurrencepattern; + $exception = new kolab_api_filter_mapistore_structure_exceptioninfo; + + $exception->OverrideFlags = 17; + $parent->ExceptionInfo = array($exception); + + // convert input data into binary format + $in = pack("H*" , implode('', self::$sample)); + $len = strlen($in); + + $result = $structure->input($in, false, $parent, 0); + + $this->assertSame($len, $result); + $this->assertInstanceOf('kolab_api_filter_mapistore_structure_changehighlight', $structure->ChangeHighlight); + $this->assertSame(0, $structure->ReservedBlockEE1Size); + $this->assertSame(0x0CBC9934, $structure->StartDateTime); + $this->assertSame(0x0CBC9952, $structure->EndDateTime); + $this->assertSame(0x0CBC98F8, $structure->OriginalStartDate); + $this->assertSame(33, $structure->WideCharSubjectLength); + $this->assertSame('Simple Recurrence with exceptions', $structure->WideCharSubject); + $this->assertSame(7, $structure->WideCharLocationLength); + $this->assertSame('34/4141', $structure->WideCharLocation); + $this->assertSame(0, $structure->ReservedBlockEE2Size); + } +} diff --git a/tests/Unit/Filter/Mapistore/Structure/Recipientrow.php b/tests/Unit/Filter/Mapistore/Structure/Recipientrow.php new file mode 100644 index 0000000..1b980af --- /dev/null +++ b/tests/Unit/Filter/Mapistore/Structure/Recipientrow.php @@ -0,0 +1,31 @@ +markTestIncomplete('TODO'); + } + + /** + * Test input method + */ + function test_input() + { + // @TODO + $this->markTestIncomplete('TODO'); + } +} diff --git a/tests/Unit/Filter/Mapistore/Structure/Recurrencepattern.php b/tests/Unit/Filter/Mapistore/Structure/Recurrencepattern.php new file mode 100644 index 0000000..4e3cdac --- /dev/null +++ b/tests/Unit/Filter/Mapistore/Structure/Recurrencepattern.php @@ -0,0 +1,93 @@ + '0430', + 'WriterVersion' => '0430', + 'RecurFrequency' => '0B20', + 'PatternType' => '0100', + 'CalendarType' => '0000', + 'FirstDateTime' => 'C0210000', + 'Period' => '01000000', + 'SlidingFlag' => '00000000', + 'PatternTypeSpecific' => '32000000', + 'EndType' => '22200000', + 'OccurrenceCount' => '0C000000', + 'FirstDOW' => '00000000', + 'DeletedInstanceCount' => '01000000', + 'DeletedInstanceDates' => 'A096BC0C', + 'ModifiedInstanceCount' => '01000000', + 'ModifiedInstanceDates' => 'A096BC0C', + 'StartDate' => '8020BC0C', + 'EndDate' => '20ADBC0C', + ); + + + /** + * Test output method + */ + function test_output() + { + $structure = new kolab_api_filter_mapistore_structure_recurrencepattern; + + $structure->RecurFrequency = 0x200b; + $structure->PatternType = 1; + $structure->CalendarType = 0; + $structure->FirstDateTime = 0x000021C0; + $structure->Period = 1; + $structure->SlidingFlag = 0; + $structure->PatternTypeSpecific = 0x00000032; + $structure->EndType = 0x00002022; + $structure->OccurrenceCount = 12; + $structure->FirstDOW = 0; + $structure->DeletedInstanceDates = array(0x0CBC96A0); + $structure->ModifiedInstanceDates = array(0x0CBC96A0); + $structure->StartDate = 213655680; + $structure->EndDate = 0x0CBCAD20; + + $result = $structure->output(); + + $this->assertSame(strtoupper(bin2hex($result)), implode('', self::$sample)); + } + + /** + * Test input method + */ + function test_input() + { + $structure = new kolab_api_filter_mapistore_structure_recurrencepattern; + + // convert input data into binary format + $in = pack("H*" , implode('', self::$sample)); + $len = strlen($in); + + $result = $structure->input($in); + + $this->assertSame($len, $result); + $this->assertSame(0x3004, $structure->ReaderVersion); + $this->assertSame(0x3004, $structure->WriterVersion); + $this->assertSame(0x200b, $structure->RecurFrequency); + $this->assertSame(0x0001, $structure->PatternType); + $this->assertSame(0, $structure->CalendarType); + $this->assertSame(0x000021C0, $structure->FirstDateTime); + $this->assertSame(0x0001, $structure->Period); + $this->assertSame(0, $structure->SlidingFlag); + $this->assertSame(0x00000032, $structure->PatternTypeSpecific); + $this->assertSame(0x00002022, $structure->EndType); + $this->assertSame(12, $structure->OccurrenceCount); + $this->assertSame(0, $structure->FirstDOW); + $this->assertSame(1, $structure->DeletedInstanceCount); + $this->assertSame(array(0x0CBC96A0), $structure->DeletedInstanceDates); + $this->assertSame(1, $structure->ModifiedInstanceCount); + $this->assertSame(array(0x0CBC96A0), $structure->ModifiedInstanceDates); + $this->assertSame(213655680, $structure->StartDate); + $this->assertSame(0x0CBCAD20, $structure->EndDate); + } +} diff --git a/tests/Unit/Filter/Mapistore/Structure/Systemtime.php b/tests/Unit/Filter/Mapistore/Structure/Systemtime.php new file mode 100644 index 0000000..42aea38 --- /dev/null +++ b/tests/Unit/Filter/Mapistore/Structure/Systemtime.php @@ -0,0 +1,31 @@ +markTestIncomplete('TODO'); + } + + /** + * Test input method + */ + function test_input() + { + // @TODO + $this->markTestIncomplete('TODO'); + } +} diff --git a/tests/Unit/Filter/Mapistore/Structure/Timezonestruct.php b/tests/Unit/Filter/Mapistore/Structure/Timezonestruct.php new file mode 100644 index 0000000..d1946f1 --- /dev/null +++ b/tests/Unit/Filter/Mapistore/Structure/Timezonestruct.php @@ -0,0 +1,91 @@ + 'E0010000', + 'IStandardBias' => '00000000', + 'IDaylightBias' => 'C4FFFFFF', + 'WStandardYear' => '0000', + 'StStandardDate' => '00000B00000001000200000000000000', + 'WDaylightYear' => '0000', + 'StDaylightDate' => '00000300000002000200000000000000', + ); + + + /** + * Test output method + */ + function test_output() + { + $structure = new kolab_api_filter_mapistore_structure_timezonestruct; + $structure->IBias = 480; + $structure->IStandardBias = 0; + $structure->IDaylightBias = -60; + $structure->WStandardYear = 0; + $structure->WDaylightYear = 0; + $structure->StStandardDate = new kolab_api_filter_mapistore_structure_systemtime(array( + 'WYear' => 0, + 'WMonth' => 11, + 'WDayOfWeek' => 0, + 'WDay' => 1, + 'WHour' => 2, + )); + $structure->StDaylightDate = new kolab_api_filter_mapistore_structure_systemtime(array( + 'WYear' => 0, + 'WMonth' => 3, + 'WDayOfWeek' => 0, + 'WDay' => 2, + 'WHour' => 2, + )); + + $result = $structure->output(); + + $this->assertSame(strtoupper(bin2hex($result)), implode('', self::$sample)); + } + + /** + * Test input method + */ + function test_input() + { + $structure = new kolab_api_filter_mapistore_structure_timezonestruct; + + // convert input data into binary format + $in = pack("H*" , implode('', self::$sample)); + $len = strlen($in); + + $result = $structure->input($in); + + $this->assertSame($len, $result); + $this->assertSame(480, $structure->IBias); + $this->assertSame(0, $structure->IStandardBias); + $this->assertSame(-60, $structure->IDaylightBias); + $this->assertSame(0, $structure->WStandardYear); + $this->assertSame(0, $structure->WDaylightYear); + $this->assertInstanceOf('kolab_api_filter_mapistore_structure_systemtime', $structure->StStandardDate); + $this->assertInstanceOf('kolab_api_filter_mapistore_structure_systemtime', $structure->StDaylightDate); + $this->assertSame(0, $structure->StStandardDate->WYear); + $this->assertSame(11, $structure->StStandardDate->WMonth); + $this->assertSame(0, $structure->StStandardDate->WDayOfWeek); + $this->assertSame(1, $structure->StStandardDate->WDay); + $this->assertSame(2, $structure->StStandardDate->WHour); + $this->assertSame(0, $structure->StStandardDate->WMinute); + $this->assertSame(0, $structure->StStandardDate->WSecond); + $this->assertSame(0, $structure->StStandardDate->WMilliseconds); + $this->assertSame(0, $structure->StDaylightDate->WYear); + $this->assertSame(3, $structure->StDaylightDate->WMonth); + $this->assertSame(0, $structure->StDaylightDate->WDayOfWeek); + $this->assertSame(2, $structure->StDaylightDate->WDay); + $this->assertSame(2, $structure->StDaylightDate->WHour); + $this->assertSame(0, $structure->StDaylightDate->WMinute); + $this->assertSame(0, $structure->StDaylightDate->WSecond); + $this->assertSame(0, $structure->StDaylightDate->WMilliseconds); + } +} diff --git a/tests/Unit/Filter/Mapistore/Structure/Tzrule.php b/tests/Unit/Filter/Mapistore/Structure/Tzrule.php new file mode 100644 index 0000000..b8053b2 --- /dev/null +++ b/tests/Unit/Filter/Mapistore/Structure/Tzrule.php @@ -0,0 +1,31 @@ +markTestIncomplete('TODO'); + } + + /** + * Test input method + */ + function test_input() + { + // @TODO + $this->markTestIncomplete('TODO'); + } +} diff --git a/tests/phpunit.xml b/tests/phpunit.xml index d642823..292f3bd 100644 --- a/tests/phpunit.xml +++ b/tests/phpunit.xml @@ -1,55 +1,64 @@ Unit/Output/Json.php Unit/Output/Json/Attachment.php Unit/Output/Json/Contact.php Unit/Output/Json/Event.php Unit/Output/Json/Folder.php Unit/Output/Json/Info.php Unit/Output/Json/Mail.php Unit/Output/Json/Note.php Unit/Output/Json/Task.php Unit/Input/Json.php Unit/Input/Json/Attachment.php Unit/Input/Json/Contact.php Unit/Input/Json/Event.php Unit/Input/Json/Folder.php Unit/Input/Json/Mail.php Unit/Input/Json/Note.php Unit/Input/Json/Folder.php Unit/Input/Json/Task.php Unit/Filter/Mapistore.php + Unit/Filter/Mapistore/Structure/Appointmentrecurrencepattern.php + Unit/Filter/Mapistore/Structure/Changehighlight.php + Unit/Filter/Mapistore/Structure/Exceptioninfo.php + Unit/Filter/Mapistore/Structure/Extendedexception.php + Unit/Filter/Mapistore/Structure/Recipientrow.php + Unit/Filter/Mapistore/Structure/Recurrencepattern.php + Unit/Filter/Mapistore/Structure/Systemtime.php + Unit/Filter/Mapistore/Structure/Timezonestruct.php + Unit/Filter/Mapistore/Structure/Tzrule.php Unit/Filter/Mapistore/Attachment.php Unit/Filter/Mapistore/Contact.php Unit/Filter/Mapistore/Event.php Unit/Filter/Mapistore/Folder.php Unit/Filter/Mapistore/Info.php Unit/Filter/Mapistore/Mail.php Unit/Filter/Mapistore/Note.php Unit/Filter/Mapistore/Task.php API/Folders.php API/Attachments.php API/Contacts.php API/Events.php API/Info.php API/Mails.php API/Notes.php API/Tasks.php Mapistore/Folders.php Mapistore/Attachments.php Mapistore/Contacts.php Mapistore/Events.php Mapistore/Info.php Mapistore/Mails.php Mapistore/Notes.php Mapistore/Tasks.php