diff --git a/lib/filter/mapistore.php b/lib/filter/mapistore.php index 78f2cf2..7fb7529 100644 --- a/lib/filter/mapistore.php +++ b/lib/filter/mapistore.php @@ -1,531 +1,532 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ class kolab_api_filter_mapistore extends kolab_api_filter { protected $input; protected $attrs_filter; // Common properties [MS-OXCMSG] protected static $map = array( // 'PidTagAccess' => '', // 'PidTagAccessLevel' => '', // 0 - read-only, 1 - modify // 'PidTagChangeKey' => '', 'PidTagCreationTime' => 'creation-date', // PtypTime, UTC 'PidTagLastModificationTime' => 'last-modification-date', // PtypTime, UTC // 'PidTagLastModifierName' => '', // 'PidTagObjectType' => '', // @TODO // 'PidTagHasAttachments' => '', // @TODO // 'PidTagRecordKey' => '', // 'PidTagSearchKey' => '', // 'PidLidCategories' => 'categories', // @TODO ); /** * Modify request path * * @param array (Exploded) request path */ public function path(&$path) { // handle differences between OpenChange API and Kolab API // here we do only very basic modifications, just to be able // to select apprioprate api action class if ($path[0] == 'calendars') { $path[0] = 'events'; } } /** * Executed before every api action * * @param kolab_api_input Request */ public function input(&$input) { $this->input = $input; // handle differences between OpenChange API and Kolab API switch ($input->action) { case 'folders': // in OpenChange folders/1/folders means get all folders if ($input->method == 'GET' && $input->path[0] === '1' && $input->path[1] == 'folders') { $input->path = array(); $type = 'folder'; } // in OpenChange folders/0/folders means get the hierarchy of the NON IPM Subtree // we should ignore/send empty request else if ($input->method == 'GET' && $input->path[0] === '0' && $input->path[1] == 'folders') { // @TODO throw new kolab_api_exception(kolab_api_exception::NOT_FOUND); } else if ($input->path[1] == 'messages') { $input->path[1] = 'objects'; if ($input->args['properties']) { $type = $input->api->backend->folder_type($input->path[0]); list($type, ) = explode('.', $type); } } else if ($input->path[1] == 'deletemessages') { $input->path[1] = 'deleteobjects'; } // properties filter, map MAPI attribute names to Kolab attributes if ($type && $input->args['properties']) { $this->attrs_filter = explode(',', $this->input->args['properties']); $properties = $this->attributes_filter($this->attrs_filter, $type); $input->args['properties'] = implode(',', $properties); } break; case 'notes': // Notes do not have attachments in Exchange if ($input->path[1] === 'attachments' || count($this->path) > 2) { throw new kolab_api_exception(kolab_api_exception::NOT_FOUND); } break; } $common_action = !in_array($input->action, array('folders', 'info')); // convert / to // or /// if ($common_action && ($uid = $input->path[0])) { list($folder, $msg, $attach) = self::uid_decode($uid); $path = array($folder, $msg); if ($attach) { $path[] = $attach; } array_splice($input->path, 0, 1, $path); } // convert parent_id into path on object create request if ($input->method == 'POST' && $common_action && !count($input->path)) { $data = $input->input(null, true); if ($data['parent_id']) { $input->path[0] = $data['parent_id']; } else { throw new kolab_api_exception(kolab_api_exception::INVALID_REQUEST); } } } /** * Executed when parsing request body * * @param string Request data * @param string Expected object type + * @param string Original object data (set on update requests) */ - public function input_body(&$data, $type = null) + public function input_body(&$data, $type = null, $original_object = null) { $input = $this->input; // handle differences between OpenChange API and Kolab API // Note: input->path is already modified by input() and path() above switch ($input->action) { case 'folders': // folders//deletemessages input if ($input->path[1] == 'deleteobjects') { // Kolab API expects just a list of identifiers, I.e.: // [{"id": "1"}, {"id": "2"}] => ["1", "2"] foreach ((array) $data as $idx => $element) { $data[$idx] = $element['id']; } } break; } switch ($type) { case 'attachment': case 'event': case 'note': case 'task': case 'contact': case 'mail': case 'folder': $model = $this->get_model_class($type); - $data = $model->input($data); + $data = $model->input($data, $original_object); break; } } /** * Apply filter on output data * * @param array Result data * @param string Object type * @param array Context (folder_uid, object_uid, object) * @param array Optional attributes filter */ public function output(&$result, $type, $context = null, $attrs_filter = array()) { // handle differences between OpenChange API and Kolab API $model = $this->get_model_class($type); if (!empty($this->attrs_filter)) { $attrs_filter = array_combine($this->attrs_filter, $this->attrs_filter); } else if (!empty($attrs_filter)) { $attrs_filter = $this->attributes_filter($attrs_filter, $type, true); $attrs_filter = array_combine($attrs_filter, $attrs_filter); } foreach ($result as $idx => $data) { if ($filtered = $model->output($data, $context)) { // apply properties filter (again) if (!empty($attrs_filter)) { $filtered = array_intersect_key($filtered, $attrs_filter); } $result[$idx] = $filtered; } else { unset($result[$idx]); $unset = true; } } if ($unset) { $result = array_values($result); } } /** * Executed for response headers * * @param array Response headers */ public function headers(&$headers) { // handle differences between OpenChange API and Kolab API foreach ($headers as $name => $value) { switch ($name) { case 'X-Count': $headers['X-mapistore-rowcount'] = $value; unset($headers[$name]); break; } } } /** * Executed for empty response status * * @param int Status code */ public function send_status(&$status) { // handle differences between OpenChange API and Kolab API } /** * Extracts data from kolab data array */ public static function get_kolab_value($data, $name) { $name_items = explode('.', $name); $count = count($name_items); $value = $data[$name_items[0]]; for ($i = 1; $i < $count; $i++) { if (!is_array($value)) { return null; } list($key, $num) = explode(':', $name_items[$i]); $value = $value[$key]; if ($num !== null && $value !== null) { $value = is_array($value) ? $value[$num] : null; } } return $value; } /** * Sets specified kolab data item */ public static function set_kolab_value(&$data, $name, $value) { $name_items = explode('.', $name); $count = count($name_items); $element = &$data; if ($count > 1) { for ($i = 0; $i < $count - 1; $i++) { $key = $name_items[$i]; if (!array_key_exists($key, $element)) { $element[$key] = array(); } $element = &$element[$key]; } } $element[$name_items[$count - 1]] = $value; } /** * Converts kolab identifiers describind the object into * MAPI identifier that can be easily used in URL. * * @param string Folder UID * @param string Object UID * @param string Optional attachment identifier * * @return string Object identifier */ public static function uid_encode($folder_uid, $msg_uid, $attach_id = null) { $result = array($folder_uid, $msg_uid); if ($attach_id) { $result[] = $attach_id; } $result = array_map(array('kolab_api_filter_mapistore', 'uid_encode_item'), $result); return implode('.', $result); } /** * Converts back the MAPI identifier into kolab folder/object/attachment IDs * * @param string Object identifier * * @return array Object identifiers */ public static function uid_decode($uid) { $result = explode('.', $uid); $result = array_map(array('kolab_api_filter_mapistore', 'uid_decode_item'), $result); return $result; } /** * Encodes UID element */ protected static function uid_encode_item($str) { $fn = function($match) { return '_' . ord($match[1]); }; $str = preg_replace_callback('/([^0-9a-zA-Z-])/', $fn, $str); return $str; } /** * Decodes UID element */ protected static function uid_decode_item($str) { $fn = function($match) { return chr($match[1]); }; $str = preg_replace_callback('/_([0-9]{2})/', $fn, $str); return $str; } /** * Parse common properties in object data (convert into MAPI format) */ public static function parse_common_props(&$result, $data, $context = array()) { if (empty($context)) { // @TODO: throw exception? return; } if ($data['uid'] && $context['folder_uid']) { $result['id'] = self::uid_encode($context['folder_uid'], $data['uid']); } if ($context['folder_uid']) { $result['parent_id'] = $context['folder_uid']; } foreach (self::$map as $mapi_idx => $kolab_idx) { if (!isset($result[$mapi_idx]) && ($value = $data[$kolab_idx]) !== null) { switch ($kolab_idx) { case 'creation-date': case 'last-modification-date': $result[$mapi_idx] = self::date_php2mapi($value, true); break; } } } } /** * Convert common properties into kolab format */ public static function convert_common_props(&$result, $data) { // @TODO: id, parent_id? foreach (self::$map as $mapi_idx => $kolab_idx) { if (array_key_exists($mapi_idx, $data) && !array_key_exists($kolab_idx, $result)) { $value = $data[$mapi_idx]; switch ($mapi_idx) { case 'PidTagCreationTime': case 'PidTagLastModificationTime': if ($value) { $dt = self::date_mapi2php($value); $result[$kolab_idx] = $dt->format('Y-m-d\TH:i:s\Z'); } break; } } } } /** * Filter property names */ public function attributes_filter($attrs, $type = null, $reverse = false) { $map = self::$map; $result = array(); if ($type) { $model = $this->get_model_class($type); $map = array_merge($map, $model->map()); } // add some special common attributes $map['PidTagMessageClass'] = 'PidTagMessageClass'; $map['collection'] = 'collection'; $map['id'] = 'uid'; foreach ($attrs as $attr) { if ($reverse) { if ($name = array_search($attr, $map)) { $result[] = $name; } } else if ($name = $map[$attr]) { $result[] = $name; } } return $result; } /** * Return instance of model class object */ protected function get_model_class($type) { $class = "kolab_api_filter_mapistore_$type"; return new $class($this); } /** * Convert DateTime object to MAPI date format */ public function date_php2mapi($date, $utc = true, $time = null) { // convert string to DateTime if (!is_object($date) && !empty($date)) { // convert date to datetime on 00:00:00 if (preg_match('/^([0-9]{4})-?([0-9]{2})-?([0-9]{2})$/', $date, $m)) { $date = $m[1] . '-' . $m[2] . '-' . $m[3] . 'T00:00:00+00:00'; } $date = new DateTime($date); } if (!is_object($date)) { return; } if ($utc) { $date->setTimezone(new DateTimeZone('UTC')); } if (!empty($time)) { $date->setTime((int) $time['hour'], (int) $time['minute'], (int) $time['second']); } // MAPI PTypTime is 64-bit integer representing the number // of 100-nanosecond intervals since January 1, 1601. // Note: probably does not work on 32-bit systems return ($date->format('U') + 11644473600) * 10000000; } /** * Convert date-time from MAPI format to DateTime */ public function date_mapi2php($date, $utc = true) { // Note: probably does not work on 32-bit systems $seconds = intval($date / 10000000) - 11644473600; // assumes we're working with dates after 1970-01-01 return new DateTime('@' . $seconds); } /** * Parse categories according to [MS-OXCICAL 2.1.3.1.1.20.3] * * @param array Categories * * @return array Categories */ public static function parse_categories($categories) { if (!is_array($categories)) { return; } $result = array(); foreach ($categories as $idx => $val) { $val = preg_replace('/(\x3B|\x2C|\x06\x1B|\xFE\x54|\xFF\x1B)/', '', $val); $val = preg_replace('/\s+/', ' ', $val); $val = trim($val); $len = mb_strlen($val); if ($len) { if ($len > 255) { $val = mb_substr($val, 0, 255); } $result[mb_strtolower($val)] = $val; } } return array_values($result); } } diff --git a/lib/filter/mapistore/attachment.php b/lib/filter/mapistore/attachment.php index d44689d..34ed7c4 100644 --- a/lib/filter/mapistore/attachment.php +++ b/lib/filter/mapistore/attachment.php @@ -1,183 +1,184 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ class kolab_api_filter_mapistore_attachment { protected $map = array( // general props (read-only) 'PidTagAccessLevel' => '', // 0 - read-only, 1 - modify 'PidTagObjectType' => '', 'PidTagRecordKey' => '', // PtypBinary // other props 'PidTagLastModificationTime' => '', 'PidTagCreationTime' => '', 'PidTagDisplayName' => 'filename', 'PidTagAttachSize' => 'size', 'PidTagAttachNumber' => '', // @TODO: unique attachment index within a message 'PidTagAttachDataBinary' => '', // PtypBinary 'PidTagAttachDataObject' => '', // PtypBinary 'PidTagAttachMethod' => '', 'PidTagAttachLongFilename' => 'filename', // filename with extension 'PidTagAttachFilename' => '', // filename in 8.3 form 'PidTagAttachExtension' => '', 'PidTagAttachLongPathname' => '', 'PidTagAttachPathname' => '', 'PidTagAttachTag' => '', // PtypBinary 'PidTagRenderingPosition' => '', 'PidTagAttachRendering' => '', // PtypBinary 'PidTagAttachFlags' => '', 'PidTagAttachTransportName' => '', 'PidTagAttachEncoding' => '', // PtypBinary 'PidTagAttachAdditionalInformation' => '', // PtypBinary 'PidTagAttachmentLinkId' => '', 'PidTagAttachmentFlags' => '', 'PidTagAttachmentHidden' => '', // PtypBoolean 'PidTagTextAttachmentChars' => 'charset', // MIME props 'PidTagAttachMimeTag' => 'mimetype', 'PidTagAttachContentId' => 'content-id', 'PidTagAttachContentLocation' => 'content-location', 'PidTagAttachContentBase' => '', 'PidTagAttachPayloadClass' => '', 'PidTagAttachPayloadProviderGuidString' => '', 'PidNameAttachmentMacContentType' => '', 'PidNameAttachmentMacInfo' => '', // PtypBinary ); /** * Methods for PidTagAttachMethod */ protected $methods = array( 'afNone' => 0x00000001, 'afByValue' => 0x00000001, 'afByReference' => 0x00000002, 'afByReferenceOnly' => 0x00000004, 'afEmbeddedMessage' => 0x00000005, 'afStorage' => 0x00000006, ); /** * 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( 'PidTagObjectType' => 0x00000007, // mapistore REST API specific properties 'collection' => 'attachments', ); // @TODO: Set PidTagAccessLevel depending if a message is in Drafts or not // or the attachment belongs to a kolab object in writeable folder? foreach ($this->map as $mapi_idx => $kolab_idx) { if (empty($kolab_idx)) { continue; } $value = kolab_api_filter_mapistore::get_kolab_value($data, $kolab_idx); if ($value === null) { continue; } $result[$mapi_idx] = $value; } if (($pos = strrpos($data['filename'], '.')) !== false) { $result['PidTagAttachExtension'] = substr($data['filename'], $pos + 1); } // Store attachment body in base64 // @TODO: shouldn't we do this only in attachment.info request? $result['PidTagAttachDataBinary'] = $this->attachment_body($context['object'], $data, true); $result['PidTagAttachMethod'] = $this->methods['afByValue']; kolab_api_filter_mapistore::parse_common_props($result, $data, $context); $result['id'] = kolab_api_filter_mapistore::uid_encode($context['folder_uid'], $context['object_uid'], $data['id']); 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) + 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]; $result[$kolab_idx] = $value; } return $result; } /** * Returns the attributes names mapping */ public function map() { $map = array_filter($this->map); $map['PidTagAttachExtension'] = 'filename'; return $map; } /** * Get attachment body */ protected function attachment_body($object, $attachment, $encoded = false) { if (empty($object)) { return; } $api = kolab_api::get_instance(); $body = $api->backend->attachment_get($object, $attachment['id']); return $encoded ? base64_encode($body) : $body; } } diff --git a/lib/filter/mapistore/contact.php b/lib/filter/mapistore/contact.php index 47c2153..0d3183c 100644 --- a/lib/filter/mapistore/contact.php +++ b/lib/filter/mapistore/contact.php @@ -1,545 +1,569 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ class kolab_api_filter_mapistore_contact { protected $map = array( 'PidTagNickname' => 'nickname', 'PidTagGeneration' => 'n.suffix', 'PidTagDisplayNamePrefix' => 'n.prefix', 'PidTagSurname' => 'n.surname', 'PidTagMiddleName' => 'n.additional', 'PidTagGivenName' => 'n.given', 'PidTagInitials' => '', // @TODO: X-? 'PidTagDisplayName' => 'fn', 'PidTagPrimaryFaxNumber' => '', // @TODO: X-? 'PidTagBusinessFaxNumber' => '', // @TODO: X-? 'PidTagHomeFaxNumber' => '', 'PidTagHomeAddressStreet' => '', 'PidTagHomeAddressCity' => '', 'PidTagHomeAddressStateOrProvince' => '', 'PidTagHomeAddressPostalCode' => '', 'PidTagHomeAddressCountry' => '', 'PidLidHomeAddressCountryCode' => '', 'PidTagHomeAddressPostOfficeBox' => '', 'PidLidHomeAddress' => '', // @TODO: ? 'PidLidWorkAddressStreet' => '', 'PidLidWorkAddressCity' => '', 'PidLidWorkAddressState' => '', 'PidLidWorkAddressPostalCode' => '', 'PidLidWorkAddressCountry' => '', 'PidLidWorkAddressCountryCode' => '', 'PidLidWorkAddressPostOfficeBox' => '', 'PidLidWorkAddress' => '', // @TODO: ? 'PidTagOtherAddressStreet' => '', 'PidTagOtherAddressCity' => '', 'PidTagOtherAddressStateOrProvince' => '', 'PidTagOtherAddressPostalCode' => '', 'PidTagOtherAddressCountry' => '', 'PidLidOtherAddressCountryCode' => '', 'PidTagOtherAddressPostOfficeBox' => '', 'PidLidOtherAddress' => '', // @TODO: ? 'PidTagStreetAddress' => '', // @TODO: ? 'PidTagLocality' => '', // @TODO: ? 'PidTagStateOrProvince' => '', // @TODO: ? 'PidTagPostalCode' => '', // @TODO: ? 'PidTagCountry' => '', // @TODO: ? 'PidLidAddressCountryCode' => '', // @TODO: ? 'PidTagPostOfficeBox' => '', // @TODO: ? 'PidTagPostalAddress' => '', // @TODO: ? 'PidTagPagerTelephoneNumber' => '', 'PidTagCallbackTelephoneNumber' => '', 'PidTagBusinessTelephoneNumber' => '', 'PidTagHomeTelephoneNumber' => '', 'PidTagPrimaryTelephoneNumber' => '', 'PidTagBusiness2TelephoneNumber' => '', 'PidTagMobileTelephoneNumber' => '', 'PidTagRadioTelephoneNumber' => '', 'PidTagCarTelephoneNumber' => '', 'PidTagOtherTelephoneNumber' => '', 'PidTagAssistantTelephoneNumber' => '', 'PidTagHome2TelephoneNumber' => '', // @TODO: X-? 'PidTagTelecommunicationsDeviceForDeafTelephoneNumber' => '', // @TODO: X-? 'PidTagCompanyMainTelephoneNumber' => '', // @TODO: X-? 'PidTagTelexNumber' => '', 'PidTagIsdnNumber' => '', 'PidTagBirthday' => 'bday', // PtypTime 'PidTagWeddingAnniversary' => 'anniversary', // PtypTime 'PidTagTitle' => 'title', 'PidTagCompanyName' => '', 'PidTagDepartmentName' => '', 'PidTagOfficeLocation' => '', // @TODO: X-? 'PidTagManagerName' => '', 'PidTagAssistant' => '', 'PidTagProfession' => 'group.role', 'PidLidHasPicture' => '', // bool, more about photo attachments in MS-OXOCNTC 'PidTagHobbies' => '', // @TODO: X-? 'PidTagSpouseName' => '', 'PidTagLanguage' => 'lang', 'PidTagLocation' => '', // @TODO: X-? 'PidLidInstantMessagingAddress' => 'impp', 'PidTagOrganizationalIdNumber' => '', // @TODO: X-? 'PidTagCustomerId' => '', // @TODO: X-? 'PidTagGovernmentIdNumber' => '', // @TODO: X-? 'PidTagPersonalHomePage' => 'url', 'PidTagBusinessHomePage' => '', // @TODO: X-? 'PidTagFtpSite' => '', // @TODO: X-? 'PidLidFreeBusyLocation' => 'fburl', 'PidTagChildrenNames' => '', // PtypMultipleString 'PidTagGender' => 'gender', 'PidTagUserX509Certificate' => 'key', // PtypMultipleBinary 'PidTagMessageClass' => '', // IPM.Contact, IPM.DistList 'PidTagBody' => 'note', 'PidTagLastModificationTime' => 'rev', /* // distribution lists 'PidTag' => '', 'PidTag' => '', 'PidTag' => '', 'PidTag' => '', 'PidTag' => '', 'PidTag' => '', 'PidTag' => '', 'PidTag' => '', */ ); protected $gender_map = array( 0 => '', 1 => 'F', 2 => 'M', ); protected $phone_map = array( 'PidTagPagerTelephoneNumber' => 'pager', 'PidTagBusinessTelephoneNumber' => 'work', 'PidTagHomeTelephoneNumber' => 'home', 'PidTagMobileTelephoneNumber' => 'cell', 'PidTagCarTelephoneNumber' => 'x-car', 'PidTagOtherTelephoneNumber' => 'textphone', 'PidTagBusinessFaxNumber' => 'faxwork', 'PidTagHomeFaxNumber' => 'faxhome', ); protected $email_map = array( 'PidLidEmail1EmailAddress' => 'home', 'PidLidEmail2EmailAddress' => 'work', 'PidLidEmail3EmailAddress' => 'other', ); /** * 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.Contact', // mapistore REST API specific properties 'collection' => 'contacts', ); foreach ($this->map as $mapi_idx => $kolab_idx) { if (empty($kolab_idx)) { continue; } $value = kolab_api_filter_mapistore::get_kolab_value($data, $kolab_idx); if ($value === null) { continue; } switch ($mapi_idx) { case 'PidTagGender': $value = (int) array_search($value, $this->gender_map); break; case 'PidTagPersonalHomePage': case 'PidLidInstantMessagingAddress': if (is_array($value)) { $value = $value[0]; } break; case 'PidTagBirthday': case 'PidTagWeddingAnniversary': case 'PidTagLastModificationTime': $value = kolab_api_filter_mapistore::date_php2mapi($value, false); break; case 'PidTagUserX509Certificate': foreach ((array) $value as $val) { if ($val && preg_match('|^data:application/pkcs7-mime;base64,|i', $val, $m)) { $result[$mapi_idx] = substr($val, strlen($m[0])); continue 3; } } $value = null; break; + + case 'PidTagTitle': + if (is_array($value)) { + $value = $value[0]; + } + break; } if ($value === null) { continue; } $result[$mapi_idx] = $value; } // contact photo attachment [MS-OXVCARD 2.1.3.2.4] if (!empty($data['photo'])) { // @TODO: check if photo is one of .bmp, .gif, .jpeg, .png // Photo in MAPI is handled as attachment // Set PidTagAttachmentContactPhoto=true on attachment object $result['PidLidHasPicture'] = true; } // Organization/Department $organization = $data['group']['org']; if (is_array($organization)) { $result['PidTagCompanyName'] = $organization[0]; $result['PidTagDepartmentName'] = $organization[1]; } else if ($organization !== null) { $result['PidTagCompanyName'] = $organization; } // Manager/Assistant $related = $data['group']['related']; if ($related && $related['parameters']) { $related = array($related); } foreach ((array) $related as $rel) { $type = $rel['parameters']['type']; if ($type == 'x-manager') { $result['PidTagManagerName'] = $rel['text']; } else if ($type == 'x-assistant') { $result['PidTagAssistant'] = $rel['text']; } } // Children, Spouse foreach ((array) $data['related'] as $rel) { $type = $rel['parameters']['type']; if ($type == 'child') { $result['PidTagChildrensNames'][] = $rel['text']; } else if ($type == 'spouse') { $result['PidTagSpouseName'] = $rel['text']; } } // Emails $email_map = array_flip($this->email_map); foreach ((array) $data['email'] as $email) { $type = is_array($email) ? $email['parameters']['type'] : 'other'; $key = $email_map[$type] ?: $email_map['other']; $result[$key] = is_array($email) ? $email['text'] : $email; } // Phone(s) $phone_map = array_flip($this->phone_map); $phones = $data['tel']; if ($phones && $phones['parameters']) { $phones = array($phones); } foreach ((array) $phones as $phone) { $type = implode('', (array)$phone['parameters']['type']); if ($phone['text'] && ($idx = $phone_map[$type])) { $result[$idx] = $phone['text']; } } // Addresses(s) $addresses = $data['adr']; if ($addresses && $addresses['parameters']) { $addresses = array($addresses); } foreach ((array) $addresses as $addr) { $type = $addr['parameters']['type']; $address = null; if ($type == 'home') { $address = array( 'PidTagHomeAddressStreet' => $addr['street'], 'PidTagHomeAddressCity' => $addr['locality'], 'PidTagHomeAddressStateOrProvince' => $addr['region'], 'PidTagHomeAddressPostalCode' => $addr['code'], 'PidTagHomeAddressCountry' => $addr['country'], 'PidTagHomeAddressPostOfficeBox' => $addr['pobox'], ); } else if ($type == 'work') { $address = array( 'PidLidWorkAddressStreet' => $addr['street'], 'PidLidWorkAddressCity' => $addr['locality'], 'PidLidWorkAddressState' => $addr['region'], 'PidLidWorkAddressPostalCode' => $addr['code'], 'PidLidWorkAddressCountry' => $addr['country'], 'PidLidWorkAddressPostOfficeBox' => $addr['pobox'], ); } if (!empty($address)) { $result = array_merge($result, array_filter($address)); } } $other_adr_map = array( 'street' => 'PidTagOtherAddressStreet', 'locality' => 'PidTagOtherAddressCity', 'region' => 'PidTagOtherAddressStateOrProvince', 'code' => 'PidTagOtherAddressPostalCode', 'country' => 'PidTagOtherAddressCountry', 'pobox' => 'PidTagOtherAddressPostOfficeBox', ); foreach ((array) $data['group']['adr'] as $idx => $value) { if ($value && ($key = $other_adr_map[$idx])) { $result[$key] = $value; } } 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) + 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 'PidTagBirthday': case 'PidTagWeddingAnniversary': if ($value) { $value = kolab_api_filter_mapistore::date_mapi2php($value); $value = $value->format('Y-m-d'); } break; case 'PidTagLastModificationTime': if ($value) { $value = kolab_api_filter_mapistore::date_mapi2php($value); $value = $value->format('Y-m-d\TH:i:s\Z'); } break; case 'PidTagGender': $value = $this->gender_map[(int)$value]; break; case 'PidTagUserX509Certificate': if (!empty($value)) { $value = array('data:application/pkcs7-mime;base64,' . $value); } break; case 'PidTagPersonalHomePage': case 'PidLidInstantMessagingAddress': if (!empty($value)) { $value = array($value); } break; } kolab_api_filter_mapistore::set_kolab_value($result, $kolab_idx, $value); } // MS-OXVCARD 2.1.3.2.1 if (!empty($data['PidTagNormalizedSubject']) && empty($data['PidTagDisplayName'])) { $result['fn'] = $data['PidTagNormalizedSubject']; } // Organization/Department if ($data['PidTagCompanyName']) { $result['group']['org'][] = $data['PidTagCompanyName']; } if (!empty($data['PidTagDepartmentName'])) { $result['group']['org'][] = $data['PidTagDepartmentName']; } // Manager if ($data['PidTagManagerName']) { $result['group']['related'][] = array( 'parameters' => array('type' => 'x-manager'), 'text' => $data['PidTagManagerName'], ); } // Assistant if ($data['PidTagAssistant']) { $result['group']['related'][] = array( 'parameters' => array('type' => 'x-assistant'), 'text' => $data['PidTagAssistant'], ); } // Spouse if ($data['PidTagSpouseName']) { $result['related'][] = array( 'parameters' => array('type' => 'spouse'), 'text' => $data['PidTagSpouseName'], ); } // Children foreach ((array) $data['PidTagChildrensNames'] as $child) { $result['related'][] = array( 'parameters' => array('type' => 'child'), 'text' => $child, ); } // Emails foreach ($this->email_map as $mapi_idx => $type) { if ($email = $data[$mapi_idx]) { $result['email'][] = array( 'parameters' => array('type' => $type), 'text' => $email, ); } } // Phone(s) foreach ($this->phone_map as $mapi_idx => $type) { - if ($tel = $data[$mapi_idx]) { - if (preg_match('/^fax(work|home)$/', $type, $m)) { - $type = array('fax', $m[1]); + if (array_key_exists($mapi_idx, $data)) { + // first remove the old phone... + if (!empty($object['tel'])) { + foreach ($object['tel'] as $idx => $phone) { + $pt = implode('', (array) $phone['parameters']['type']); + if ($pt == $type) { + unset($object['tel'][$idx]); + } + } } - $result['tel'][] = array( - 'parameters' => array('type' => $type), - 'text' => $tel, - ); + if ($tel = $data[$mapi_idx]) { + if (preg_match('/^fax(work|home)$/', $type, $m)) { + $type = array('fax', $m[1]); + } + + // and add it to the list + $result['tel'][] = array( + 'parameters' => array('type' => $type), + 'text' => $tel, + ); + } } } + if (!empty($object['tel'])) { + $result['tel'] = array_merge((array) $result['tel'], (array) $object['tel']); + } + // Home address $address = array(); $adr_map = array( 'PidTagHomeAddressStreet' => 'street', 'PidTagHomeAddressCity' => 'locality', 'PidTagHomeAddressStateOrProvince' => 'region', 'PidTagHomeAddressPostalCode' => 'code', 'PidTagHomeAddressCountry' => 'country', 'PidTagHomeAddressPostOfficeBox' => 'pobox', ); foreach ($adr_map as $mapi_idx => $idx) { if ($adr = $data[$mapi_idx]) { $address[$idx] = $adr; } } if (!empty($address)) { $type = array('parameters' => array('type' => 'home')); $result['adr'][] = array_merge($address, $type); } // Work address $address = array(); $adr_map = array( 'PidLidWorkAddressStreet' => 'street', 'PidLidWorkAddressCity' => 'locality', 'PidLidWorkAddressState' => 'region', 'PidLidWorkAddressPostalCode' => 'code', 'PidLidWorkAddressCountry' => 'country', 'PidLidWorkAddressPostOfficeBox' => 'pobox', ); foreach ($adr_map as $mapi_idx => $idx) { if ($adr = $data[$mapi_idx]) { $address[$idx] = $adr; } } if (!empty($address)) { $type = array('parameters' => array('type' => 'work')); $result['adr'][] = array_merge($address, $type); } // Office address $address = array(); $adr_map = array( 'PidTagOtherAddressStreet' => 'street', 'PidTagOtherAddressCity' => 'locality', 'PidTagOtherAddressStateOrProvince' => 'region', 'PidTagOtherAddressPostalCode' => 'code', 'PidTagOtherAddressCountry' => 'country', 'PidTagOtherAddressPostOfficeBox' => 'pobox', ); foreach ($adr_map as $mapi_idx => $idx) { if ($adr = $data[$mapi_idx]) { $address[$idx] = $adr; } } if (!empty($address)) { $result['group']['adr'] = array_merge($address, $type); } 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/event.php b/lib/filter/mapistore/event.php index fb80287..7c4a1fd 100644 --- a/lib/filter/mapistore/event.php +++ b/lib/filter/mapistore/event.php @@ -1,591 +1,592 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ class kolab_api_filter_mapistore_event { protected $map = array( // common properties [MS-OXOCAL] 'PidLidAppointmentSequence' => 'sequence', 'PidLidBusyStatus' => '', // @TODO: X-MICROSOFT-CDO-BUSYSTATUS 'PidLidAppointmentAuxiliaryFlags' => '', 'PidLidLocation' => 'location', 'PidLidAppointmentStartWhole' => 'dtstart', 'PidLidAppointmentEndWhole' => 'dtend', 'PidLidAppointmentDuration' => '', 'PidNameKeywords' => 'categories', 'PidLidAppointmentSubType' => '', 'PidLidAppointmentStateFlags' => '', 'PidLidResponseStatus' => '', 'PidLidRecurring' => '', 'PidLidIsRecurring' => '', 'PidLidClipStart' => '', 'PidLidClipEnd' => '', 'PidLidAllAttendeesString' => '', 'PidLidToAttendeesString' => '', 'PidLidCCAttendeesString' => '', 'PidLidNonSendableTo' => '', 'PidLidNonSendableCc' => '', 'PidLidNonSendableBcc' => '', 'PidLidNonSendToTrackStatus' => '', 'PidLidNonSendCcTrackStatus' => '', 'PidLidNonSendBccTrackStatus' => '', 'PidLidAppointmentUnsendableRecipients' => '', 'PidLidAppointmentNotAllowPropose' => '', // @TODO: X-MICROSOFT-CDO-DISALLOW-COUNTER 'PidLidGlobalObjectId' => '', 'PidLidCleanGlobalObjectId' => '', 'PidTagOwnerAppointmentId' => '', // @TODO: X-MICROSOFT-CDO-OWNERAPPTID 'PidTagStartDate' => '', 'PidTagEndDate' => '', 'PidLidCommonStart' => '', 'PidLidCommonEnd' => '', 'PidLidOwnerCriticalChange' => '', // @TODO: X-MICROSOFT-CDO-CRITICAL-CHANGE 'PidLidIsException' => '', 'PidTagResponseRequested' => '', 'PidTagReplyRequested' => '', 'PidLidTimeZoneStruct' => '', 'PidLidTimeZoneDescription' => '', 'PidLidAppointmentTimeZoneDefinitionRecur' => '', 'PidLidAppointmentTimeZoneDefinitionStartDisplay' => '', 'PidLidAppointmentTimeZoneDefinitionEndDisplay' => '', 'PidLidAppointmentRecur' => '', 'PidLidRecurrenceType' => '', 'PidLidRecurrencePattern' => '', 'PidLidLinkedTaskItems' => '', 'PidLidMeetingWorkspaceUrl' => '', 'PidTagIconIndex' => '', 'PidLidAppointmentColor' => '', 'PidLidAppointmentReplyTime' => '', // @TODO: X-MICROSOFT-CDO-REPLYTIME 'PidLidIntendedBusyStatus' => '', // @TODO: X-MICROSOFT-CDO-INTENDEDSTATUS // calendar object properties [MS-OXOCAL] 'PidTagMessageClass' => '', 'PidLidSideEffects' => '', 'PidLidFExceptionAttendees' => '', 'PidLidClientIntent' => '', // common props [MS-OXCMSG] 'PidTagSubject' => 'summary', 'PidTagBody' => 'description', 'PidTagHtml' => '', // @TODO: (?) 'PidTagNativeBody' => '', 'PidTagBodyHtml' => '', 'PidTagRtfCompressed' => '', 'PidTagInternetCodepage' => '', 'PidTagContentId' => '', 'PidTagBodyContentLocation' => '', 'PidTagImportance' => 'priority', 'PidTagSensitivity' => 'class', 'PidLidPrivate' => '', 'PidTagCreationTime' => 'created', 'PidTagLastModificationTime' => 'dtstamp', // reminder properties [MS-OXORMDR] ); protected $recipient_track_status_map = array( 'TENTATIVE' => 0x00000002, 'ACCEPTED' => 0x00000003, 'DECLINED' => 0x00000004, ); protected $recipient_type_map = array( 'NON-PARTICIPANT' => 0x00000004, 'OPT-PARTICIPANT' => 0x00000002, 'REQ-PARTICIPANT' => 0x00000002, 'CHAIR' => 0x00000001, ); /** * Message importance for PidTagImportance as defined in [MS-OXCMSG] */ protected $importance = array( 0 => 0x00000000, 1 => 0x00000002, 2 => 0x00000002, 3 => 0x00000002, 4 => 0x00000002, 5 => 0x00000001, 6 => 0x00000000, 7 => 0x00000000, 8 => 0x00000000, 9 => 0x00000000, ); /** * Message sesnitivity for PidTagSensitivity as defined in [MS-OXCMSG] */ protected $sensitivity = array( 'public' => 0x00000000, 'personal' => 0x00000001, 'private' => 0x00000002, 'confidential' => 0x00000003, ); /** * Mapping of weekdays */ protected $recurrence_day_map = array( 'SU' => 0x00000000, 'MO' => 0x00000001, 'TU' => 0x00000002, 'WE' => 0x00000003, 'TH' => 0x00000004, 'FR' => 0x00000005, 'SA' => 0x00000006, 'BYDAY-SU' => 0x00000001, 'BYDAY-MO' => 0x00000002, 'BYDAY-TU' => 0x00000004, 'BYDAY-WE' => 0x00000008, 'BYDAY-TH' => 0x00000010, 'BYDAY-FR' => 0x00000020, 'BYDAY-SA' => 0x00000040, ); /** * 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.Appointment', // mapistore REST API specific properties 'collection' => 'calendars', ); foreach ($this->map as $mapi_idx => $kolab_idx) { if (empty($kolab_idx)) { continue; } $value = kolab_api_filter_mapistore::get_kolab_value($data, $kolab_idx); if ($value === null) { continue; } switch ($mapi_idx) { case 'PidNameKeywords': $value = kolab_api_filter_mapistore::parse_categories((array) $value); break; case 'PidTagSensitivity': $value = (int) $this->sensitivity[strtolower($value)]; break; case 'PidTagCreationTime': case 'PidTagLastModificationTime': $value = kolab_api_filter_mapistore::date_php2mapi($value, true); break; case 'PidTagImportance': $value = (int) $this->importance[(int) $value]; break; case 'PidLidAppointmentStartWhole': case 'PidLidAppointmentEndWhole': $dt = kolab_api_input_json::to_datetime($value); $value = kolab_api_filter_mapistore::date_php2mapi($dt, true); // PidLidAppointmentTimeZoneDefinitionStartDisplay // PidLidAppointmentTimeZoneDefinitionEndDisplay // this is all-day event if ($dt->_dateonly) { $result['PidLidAppointmentSubType'] = 0x00000001; } break; } $result[$mapi_idx] = $value; } // Organizer if (!empty($data['organizer'])) { $this->add_attendee_to_result($data['organizer'], $result, true); } // Attendees [MS-OXCICAL 2.1.3.1.1.20.2] foreach ((array) $data['attendee'] as $attendee) { $this->add_attendee_to_result($attendee, $result); } // Alarms (MAPI supports only one) foreach ((array) $data['valarm'] as $alarm) { if ($alarm['properties'] && $alarm['properties']['action'] == 'DISPLAY' && ($duration = $alarm['properties']['trigger']['duration']) && ($delta = self::reminder_duration_to_delta($duration)) ) { $result['PidLidReminderDelta'] = $delta; $result['PidLidReminderSet'] = true; // PidLidReminderTime // PidLidReminderSignalTime break; } } // Recurrence rule if (!empty($data['rrule']) && !empty($data['rrule']['recur'])) { if ($rule = $this->recurrence_from_kolab($data['rrule']['recur'])) { $result['PidLidAppointmentRecur'] = $rule; } } // @TODO: PidLidAppointmentDuration // @TODO: exceptions, resources 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) + 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 'PidTagImportance': $map = array( 0x00000002 => 1, 0x00000001 => 5, 0x00000000 => 9, ); $value = (int) $map[(int) $value]; break; case 'PidTagSensitivity': $map = array_flip($this->sensitivity); $value = $map[$value]; break; case 'PidTagCreationTime': case 'PidTagLastModificationTime': if ($value) { $value = kolab_api_filter_mapistore::date_mapi2php($value); $value = $value->format('Y-m-d\TH:i:s\Z'); } break; case 'PidLidAppointmentStartWhole': case 'PidLidAppointmentEndWhole': if ($value) { $value = kolab_api_filter_mapistore::date_mapi2php($value); $format = $data['PidLidAppointmentSubType'] ? 'Y-m-d' : 'Y-m-d\TH:i:s\Z'; $value = $value->format($format); } break; } $result[$kolab_idx] = $value; } // Alarms (MAPI supports only one, DISPLAY) if ($data['PidLidReminderSet'] && ($delta = $data['PidLidReminderDelta'])) { $duration = self::reminder_delta_to_duration($delta); $alarm = array( 'action' => 'DISPLAY', 'trigger' => array('duration' => $duration), 'description' => 'Reminder', ); $result['valarm'] = array(array('properties' => $alarm)); } else if (array_key_exists('PidLidReminderSet', $data) || array_key_exists('PidLidReminderDelta', $data)) { $result['valarm'] = array(); } // @TODO: PidLidAppointmentDuration (?) // @TODO: exceptions, resources, recurrence, attendees kolab_api_filter_mapistore::convert_common_props($result, $data); return $result; } /** * Returns the attributes names mapping */ public function map() { $map = array_filter($this->map); // @TODO: add properties that are not in the map $map['PidLidAppointmentRecur'] = 'rrule'; return $map; } /** * Setting PidTagRecipientType according to [MS-OXCICAL 2.1.3.1.1.20.2] */ protected function to_recipient_type($cutype, $role) { if ($cutype && in_array($cutype, array('RESOURCE', 'ROOM'))) { return 0x00000003; } if ($role && ($type = $this->recipient_type_map[$role])) { return $type; } return 0x00000001; } /** * Convert Kolab 'attendee' specification into MAPI recipient * and add it to the result */ protected function add_attendee_to_result($attendee, &$result, $is_organizer = false) { $email = $attendee['cal-address']; $params = (array) $attendee['parameters']; // parse mailto string if (strpos($email, 'mailto:') === 0) { $email = urldecode(substr($email, 7)); } $emails = rcube_mime::decode_address_list($email, 1); if (!empty($email)) { $email = $emails[key($emails)]; $recipient = array( 'PidTagAddressType' => 'SMTP', 'PidTagDisplayName' => $params['cn'] ?: $email['name'], 'PidTagDisplayType' => 0, 'PidTagEmailAddress' => $email['mailto'], ); if ($is_organizer) { $recipient['PidTagRecipientFlags'] = 0x00000003; $recipient['PidTagRecipientType'] = 0x00000001; } else { $recipient['PidTagRecipientFlags'] = 0x00000001; $recipient['PidTagRecipientTrackStatus'] = (int) $this->recipient_track_status_map[$params['partstat']]; $recipient['PidTagRecipientType'] = $this->to_recipient_type($params['cutype'], $params['role']); } // PidTagEntryId // PidTagRecipientEntryId $recipient['PidTagRecipientDisplayName'] = $recipient['PidTagDisplayName']; $result['recipients'][] = $recipient; if (strtoupper($params['rsvp']) == 'TRUE') { $result['PidTagReplyRequested'] = true; $result['PidTagResponseRequested'] = true; } } } /** * Convert PidLidReminderDelta value into xCal duration */ protected static function reminder_delta_to_duration($delta) { if ($delta == 0x5AE980E1) { $delta = 15; } $delta = (int) $delta; return "-PT{$delta}M"; } /** * Convert Kolab alarm duration into PidLidReminderDelta */ protected static function reminder_duration_to_delta($duration) { if ($duration && preg_match('/^-[PT]*([0-9]+)([WDHMS])$/', $duration, $matches)) { $value = intval($matches[1]); switch ($matches[2]) { case 'S': $value = intval(round($value/60)); break; case 'H': $value *= 60; break; case 'D': $value *= 24 * 60; break; case 'W': $value *= 7 * 24 * 60; break; } return $value; } } /** * Convert Kolab recurrence specification into MAPI properties */ protected function recurrence_from_kolab($rule) { $result = array( 'ReaderVersion' => 0x3004, 'WriterVersion' => 0x3004, 'ReaderVersion2' => 0x00003006, 'WriterVersion2' => 0x00003009, 'Period' => $rule['interval'] ? $rule['interval'] : 1, 'SlidingFlag' => 0, 'FirstDOW' => $this->day2bitmask($rule['wkst']), 'StartTimeOffset' => 60 * intval($rule['byhour']) + intval($rule['byminute']), 'EndType' => 0x00002021, 'OccurenceCount' => 0x0000000A, 'EndDate' => 0x5AE980DF, // @TODO 'CalendarType' => 0x0001, // EndTimeOffset // FirstDateTime // ExceptionCount, ExceptionInfo // ReservedBlocks, ExtendedExceptions // DeletedInstanceCount, DeletedInstanceDates // ModifiedInstanceCount, ModifiedInstanceDates ); switch ($rule['freq']) { case 'DAILY': $result['RecurFrequency'] = 0x200A; $result['PatternType'] = 0x000; $result['Period'] *= 1440; break; case 'WEEKLY': $result['RecurFrequency'] = 0x200B; $result['PatternType'] = 0x0001; $result['PatternTypeSpecific.Week.Sa-Su'] = $this->day2bitmask($rule['byday'], 'BYDAY-'); break; case 'MONTHLY': $result['RecurFrequency'] = 0x200C; $result['PatternType'] = 0x0002; if (!empty($rule['bymonthday'])) { // @TODO: MAPI doesn't support multi-valued month days $month_day = array_shift(explode(',', $rule['bymonthday'])); $result['PatternTypeSpecific.Month.Day'] = $month_day == -1 ? 0x0000001F : $month_day; } else { $result['PatternTypeSpecific.MonthNth.Sa-Su'] = $this->day2bitmask($rule['byday'], 'BYDAY-');; if (!empty($rule['bysetpos'])) { $result['PatternTypeSpecific.MonthNth.N'] = $rule['bysetpos'] == -1 ? 0x00000005 : $rule['bysetpos']; } } break; case 'YEARLY': $result['RecurFrequency'] = 0x200D; $result['PatternType'] = 0x0002; $result['Period'] *= 12; // @TODO: MAPI doesn't support multi-valued months $month = array_shift(explode(',', $rule['bymonth'])); // @TODO: StartDate/EndDate if (!empty($rule['bymonthday'])) { // @TODO: MAPI doesn't support multi-valued month days $month_day = array_shift(explode(',', $rule['bymonthday'])); $result['PatternTypeSpecific.Month.Day'] = $month_day == -1 ? 0x0000001F : $month_day; } else { $result['PatternType'] = 0x0003; $result['PatternTypeSpecific.MonthNth.Sa-Su'] = $this->day2bitmask($rule['byday'], 'BYDAY-');; if (!empty($rule['bysetpos'])) { $result['PatternTypeSpecific.MonthNth.N'] = $rule['bysetpos'] == -1 ? 0x00000005 : $rule['bysetpos']; } } break; } if (!empty($rule['until'])) { // $recurrence['until'] = self::date_from_kolab($rule['until']); // @TODO: calculate OccurenceCount? $result['EndType'] = 0x00002023; } else if (!empty($rule['count'])) { $result['EndType'] = 0x00002022; $result['OccurenceCount'] = $rule['count']; } return $result; } /** * Converts string of days (TU,TH) to bitmask used by MAPI * * @param string $days * * @return int */ protected function day2bitmask($days, $prefix = '') { $days = explode(',', $days); $result = 0; foreach ($days as $day) { $result = $result + $this->recurrence_day_map[$prefix.$day]; } return $result; } /** * Convert bitmask used by MAPI to string of days (TU,TH) * * @param int $days * * @return string */ protected function bitmask2day($days) { $days_arr = array(); foreach ($this->recurrence_day_map as $day => $bit) { if ($days & $bit) { $days_arr[] = preg_replace('/^[A-Z-]+/', '', $day); } } $result = implode(',', $days_arr); return $result; } } diff --git a/lib/filter/mapistore/folder.php b/lib/filter/mapistore/folder.php index cefeb90..1fe369c 100644 --- a/lib/filter/mapistore/folder.php +++ b/lib/filter/mapistore/folder.php @@ -1,166 +1,167 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ class kolab_api_filter_mapistore_folder { protected $map = array( // read-only properties 'PidTagAccess' => '', 'PidTagChangeKey' => '', 'PidTagCreationTime' => '', // @TODO: store in folder annotation? 'PidTagLastodificationTime' => '', 'PidTagContentCount' => '', 'PidTagContentUnreadCount' => '', 'PidTagDeletedOn' => '', // 'PidTagAddressbookEntryId' => '', 'PidTagFolderId' => '', // PtypInteger64 'PidTagHierarchyChangeNumber' => '', // number of subfolders 'PidTagMessageSize' => '', // size of all messages 'PidTagMessageSizeExtended' => '', 'PidTagSubfolders' => '', // bool 'PidTagLocalCommitTime' => '', // last change time in UTC 'PidTagLocalCommitTimeMax' => '', 'PidTagDeletedCountTotal' => '', // read-write properties 'PidTagAttributeHidden' => '', // bool 'PidTagComment' => '', // @TODO: store in folder annotation? 'PidTagContainerClass' => 'type', // IPF.* 'PidTagContainerHierarchy' => '', 'PidTagDisplayName' => 'name', 'PidTagFolderAssociatedContents' => '', 'PidTagFolderType' => '', // 0 - namespace roots, 1 - other, 2 - virtual/search folders 'PidTagRights' => '', 'PidTagAccessControlListData' => '', // see [MS-OXCPERM] ); protected $type_map = array( '' => 'IPF.Note', 'mail' => 'IPF.Note', 'task' => 'IPF.Task', 'note' => 'IPF.StickyNote', 'event' => 'IPF.Appointment', 'journal' => 'IPF.Journal', 'contact' => 'IPF.Contact', ); /** * Convert Kolab to MAPI * * @param array Data * @param array Context (folder_uid, object_uid, object) * * @return array Data */ public function output($data, $context = null) { list($type, ) = explode('.', $data['type']); $type = $this->type_map[(string)$type]; // skip folders of unsupported type if (empty($type)) { return; } // skip folders that are not subfolders of the specified folder, // in list-mode MAPI always requests for one-level of the hierarchy (?) if ($api->input->path[1] == 'folders') { $api = kolab_api::get_instance(); $parent = !empty($api->input->path) ? $api->input->path[0] : ''; if ($data['parent'] != $parent) { return; } } $result = array( // mapistore properties 'id' => $data['uid'], // MAPI properties 'PidTagFolderType' => 1, 'PidTagDisplayName' => $data['name'], 'PidTagContainerClass' => $type, ); if ($data['parent']) { $result['parent_id'] = $data['parent']; } $result = array_filter($result, function($v) { return $v !== null; }); 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) + public function input($data, $object = null) { $result = array(); // mapistore properties if ($data['id']) { $result['uid'] = $data['id']; } if ($data['parent_id']) { $result['parent'] = $data['parent_id']; } // MAPI properties if ($data['PidTagDisplayName']) { $result['name'] = $data['PidTagDisplayName']; } if ($data['PidTagContainerClass']) { // @TODO: what if folder is already a *.default or *.sentitems, etc. // we should keep the subtype intact $map = array_flip($this->type_map); $result['type'] = $map[$data['PidTagContainerClass']]; } return $result; } /** * Returns the attributes names mapping */ public function map() { $map = array_filter($this->map); $map['parent_id'] = 'parent'; $map['PidTagContainerClass'] = 'type'; $map['PidTagFolderType'] = 'PidTagFolderType'; return $map; } } diff --git a/lib/filter/mapistore/info.php b/lib/filter/mapistore/info.php index d74001a..9abb435 100644 --- a/lib/filter/mapistore/info.php +++ b/lib/filter/mapistore/info.php @@ -1,86 +1,87 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ class kolab_api_filter_mapistore_info { protected $map = array( 'name' => 'name', 'version' => 'version', 'capabilities' => 'capabilities', // @TODO ); /** * 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(); foreach ($this->map as $mapi_idx => $kolab_idx) { if (empty($kolab_idx)) { continue; } $value = $data[$kolab_idx]; if ($value === null) { continue; } $result[$mapi_idx] = $value; } $result = array_filter($result, function($v) { return $v !== null; }); 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) + public function input($data, $object = null) { return null; } /** * Returns the attributes names mapping */ public function map() { $map = array_filter($this->map); return $map; } } diff --git a/lib/filter/mapistore/mail.php b/lib/filter/mapistore/mail.php index 23a09ed..143a3fa 100644 --- a/lib/filter/mapistore/mail.php +++ b/lib/filter/mapistore/mail.php @@ -1,295 +1,296 @@ | +--------------------------------------------------------------------------+ | 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; } 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) + 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/note.php b/lib/filter/mapistore/note.php index f3f8569..5414716 100644 --- a/lib/filter/mapistore/note.php +++ b/lib/filter/mapistore/note.php @@ -1,141 +1,142 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ class kolab_api_filter_mapistore_note { protected $map = array( // note specific props [MS-OXNOTE] 'PidLidNoteColor' => '', // @TODO: X-? 'PidLidNoteHeight' => '', // @TODO: X-? 'PidLidNoteWidth' => '', // @TODO: X-? 'PidLidNoteX' => '', // @TODO: X-? 'PidLidNoteY' => '', // @TODO: X-? // common props [MS-OXCMSG] 'PidTagBody' => 'description', 'PidTagHtml' => '', // @TODO: (?) 'PidTagMessageClass' => '', 'PidTagSubject' => 'summary', 'PidTagNormalizedSubject' => '', // @TODO: abbreviated note body 'PidTagIconIndex' => '', // @TODO: depends on PidLidNoteColor ); protected $color_map = array( '0000FF' => 0x00000000, // blue '008000' => 0x00000001, // green 'FFC0CB' => 0x00000002, // pink 'FFFF00' => 0x00000003, // yellow 'FFFFFF' => 0x00000004, // white ); /** * 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.StickyNote', // notes do not have attachments in MAPI // 'PidTagHasAttachments' => 0, // mapistore REST API specific properties 'collection' => 'notes', ); 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; } break; */ } if ($value === null) { continue; } $result[$mapi_idx] = $value; } 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) + 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]; $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/task.php b/lib/filter/mapistore/task.php index a661204..a7c808b 100644 --- a/lib/filter/mapistore/task.php +++ b/lib/filter/mapistore/task.php @@ -1,250 +1,251 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ class kolab_api_filter_mapistore_task { protected $map = array( // task specific props [MS-OXOTASK] 'PidLidTaskMode' => '', // ignored 'PidLidTaskStatus' => '', 'PidLidPercentComplete' => 'percent-complete', 'PidLidTaskStartDate' => 'dtstart', // PtypTime 'PidLidTaskDueDate' => 'due', // PtypTime 'PidLidTaskResetReminder' => '', // @TODO 'PidLidTaskAccepted' => '', // @TODO 'PidLidTaskDeadOcuurence' => '', // @TODO 'PidLidTaskDateCompleted' => '', // @TODO: X-? 'PidLidTaskLastUpdate' => '', // PtypTime 'PidLidTaskActualEffort' => '', // @TODO: X-? 'PidLidTaskEstimatedEffort' => '', // @TODO: X-? 'PidLidTaskVersion' => '', 'PidLidTaskState' => '', 'PidLidTaskRecurrence' => '', // @TODO 'PidLidTaskAssigners' => '', 'PidLidTaskStatusOnComplete' => '', 'PidLidTaskHistory' => '', // @TODO: ? 'PidLidTaskUpdates' => '', 'PidLidTaskComplete' => '', 'PidLidTaskFCreator' => '', 'PidLidTaskOwner' => '', // @TODO 'PidLidTaskMultipleRecipients' => '', 'PidLidTaskAssigner' => '', 'PidLidTaskLastUser' => '', 'PidLidTaskOrdinal' => '', 'PidLidTaskLastDelegate' => '', 'PidLidTaskFRecurring' => '', 'PidLidTaskOwnership' => '', // @TODO 'PidLidTaskAcceptanceState' => '', 'PidLidTaskFFixOffline' => '', 'PidLidTaskGlobalId' => '', // @TODO 'PidLidTaskCustomFlags' => '', // ignored 'PidLidTaskRole' => '', // ignored 'PidLidTaskNoCompute' => '', // ignored 'PidLidTeamTask' => '', // ignored // common props [MS-OXCMSG] 'PidTagSubject' => 'summary', 'PidTagBody' => 'description', 'PidTagHtml' => '', // @TODO: (?) 'PidTagNativeBody' => '', 'PidTagBodyHtml' => '', 'PidTagRtfCompressed' => '', 'PidTagInternetCodepage' => '', 'PidTagContentId' => '', 'PidTagBodyContentLocation' => '', 'PidTagMessageClass' => '', 'PidLidCommonStart' => 'dtstart', 'PidLidCommonEnd' => 'due', 'PidTagIconIndex' => '', // @TODO 'PidTagCreationTime' => 'created', // PtypTime, UTC 'PidTagLastModificationTime' => 'dtstamp', // PtypTime, UTC ); /** * Values for PidLidTaskStatus property */ protected $status_map = array( 'none' => 0x00000000, // PidLidPercentComplete = 0 'in-progress' => 0x00000001, // PidLidPercentComplete > 0 and PidLidPercentComplete < 1 'complete' => 0x00000002, // PidLidPercentComplete = 1 'waiting' => 0x00000003, 'deferred' => 0x00000004, ); /** * Values for PidLidTaskHistory property */ protected $history_map = array( 'none' => 0x00000000, 'accepted' => 0x00000001, 'rejected' => 0x00000002, 'changed' => 0x00000003, 'due-changed' => 0x00000004, 'assigned' => 0x00000005, ); /** * 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.Task', // mapistore REST API specific properties 'collection' => 'tasks', ); 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 ($mapi_idx) { case 'PidLidPercentComplete': $value /= 100; break; case 'PidLidTaskStartDate': case 'PidLidTaskDueDate': $value = kolab_api_filter_mapistore::date_php2mapi($value, false, array('hour' => 0)); break; case 'PidLidCommonStart': case 'PidLidCommonEnd': $value = kolab_api_filter_mapistore::date_php2mapi($value, true); break; // case 'PidLidTaskLastUpdate': case 'PidTagCreationTime': case 'PidTagLastModificationTime': $value = kolab_api_filter_mapistore::date_php2mapi($value, true); break; } if ($value === null) { continue; } $result[$mapi_idx] = $value; } // set status $percent = $result['PidLidPercentComplete']; if ($precent == 1) { $result['PidLidTaskStatus'] = $this->status_map['complete']; // PidLidTaskDateCompleted } else if ($precent > 0) { $result['PidLidTaskStatus'] = $this->status_map['in-progress']; } else { $result['PidLidTaskStatus'] = $this->status_map['none']; } // @TODO: recurrence 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) + public function input($data, $object = null) { $result = array(); foreach ($this->map as $mapi_idx => $kolab_idx) { if (empty($kolab_idx)) { continue; } $value = $data[$mapi_idx]; if ($value === null) { continue; } switch ($mapi_idx) { case 'PidLidPercentComplete': $value = intval($value * 100); break; case 'PidLidTaskStartDate': case 'PidLidTaskDueDate': if (intval($value) !== 0x5AE980E0) { $value = kolab_api_filter_mapistore::date_mapi2php($value); $value = $value->format('Y-m-d'); } break; case 'PidLidCommonStart': case 'PidLidCommonEnd': // $value = kolab_api_filter_mapistore::date_mapi2php($value, true); break; case 'PidTagCreationTime': case 'PidTagLastModificationTime': if ($value) { $value = kolab_api_filter_mapistore::date_mapi2php($value); $value = $value->format('Y-m-d\TH:i:s\Z'); } break; } $result[$kolab_idx] = $value; } if ($data['PidLidTaskComplete']) { $result['status'] = 'COMPLETED'; } 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/input/json.php b/lib/input/json.php index 4947900..20a3807 100644 --- a/lib/input/json.php +++ b/lib/input/json.php @@ -1,121 +1,127 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ class kolab_api_input_json extends kolab_api_input { /** * Get request data (JSON) * * @param string Expected object type * @param bool Disable filters application + * @param array Original object data (set on update requests) * * @return array Request data */ - public function input($type = null, $disable_filters = false) + public function input($type = null, $disable_filters = false, $original = null) { if ($this->input_body === null) { $data = file_get_contents('php://input'); $data = trim($data); $data = json_decode($data, true); $this->input_body = $data; } if (!$disable_filters) { if ($this->filter) { - $this->filter->input_body($this->input_body, $type); + if (!empty($original)) { + // convert object data into API format + $data = $this->api->get_object_data($original, $type); + } + + $this->filter->input_body($this->input_body, $type, $data); } // convert input to internal kolab_storage format if ($type) { $class = "kolab_api_input_json_$type"; $model = new $class; - $model->input($this->input_body); + $model->input($this->input_body, $original); } } return $this->input_body; } /** * Convert xCard/xCal date and date-time into internal DateTime * * @param array|string Date or Date-Time * * @return DateTime */ public static function to_datetime($input) { if (empty($input)) { return; } if (is_array($input)) { if ($input['date-time']) { if ($input['parameters']['tzid']) { $tzid = str_replace('/kolab.org/', '', $input['parameters']['tzid']); } else { $tzid = 'UTC'; } $datetime = $input['date-time']; try { $timezone = new DateTimeZone($tzid); } catch (Exception $e) {} } else if ($input['timestamp']) { $datetime = $input['timestamp']; } else if ($input['date']) { $datetime = $input['date']; $is_date = true; } else { return; } } else { $datetime = $input; $is_date = preg_match('/^[0-9]{4}-?[0-9]{2}-?[0-9]{2}$/', $input); } try { $dt = new DateTime($datetime, $timezone ?: new DateTimeZone('UTC')); } catch (Exception $e) { return; } if ($is_date) { $dt->_dateonly = true; $dt->setTime(0, 0, 0); } return $dt; } } diff --git a/lib/input/json/attachment.php b/lib/input/json/attachment.php index 7694742..80e7530 100644 --- a/lib/input/json/attachment.php +++ b/lib/input/json/attachment.php @@ -1,65 +1,71 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ class kolab_api_input_json_attachment { // map xml/json attributes into internal format protected $field_map = array( ); /** - * Convert note input array into an array that can - * be handled by kolab_storage_folder::save() + * Convert attachment input array into an array that can + * be handled by the API * * @param array Request body + * @param array Original object data (on update) */ - public function input(&$data) + public function input(&$data, $original = null) { if (empty($data) || !is_array($data)) { throw new kolab_api_exception(kolab_api_exception::INVALID_REQUEST); } foreach ($this->field_map as $kolab => $api) { - $value = $data[$api]; - if ($value === null) { + if (!array_key_exists($api, $data)) { continue; } + $value = $data[$api]; + switch ($kolab) { case 'sensitivity': // $value = strtolower($value); break; } $result[$kolab] = $value; } if (empty($result)) { throw new kolab_api_exception(kolab_api_exception::INVALID_REQUEST); } + if (!empty($original)) { + $result = array_merge($original, $result); + } + $data = $result; } } diff --git a/lib/input/json/contact.php b/lib/input/json/contact.php index 9efc60b..c7d86f8 100644 --- a/lib/input/json/contact.php +++ b/lib/input/json/contact.php @@ -1,274 +1,336 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ class kolab_api_input_json_contact { // map xml/json attributes into internal (kolab_format) protected $field_map = array( // 'created' => 'creation-date', 'changed' => 'rev', 'categories' => 'categories', 'kind' => 'kind', // not supported by kolab_format_contact 'freebusyurl' => 'fburl', 'notes' => 'note', 'name' => 'fn', 'jobtitle' => 'title', 'nickname' => 'nickname', 'birthday' => 'bday', 'anniversary' => 'anniversary', 'photo' => 'photo', 'gender' => 'gender', 'im' => 'impp', 'lang' => 'lang', 'geo' => 'geo', // not supported by kolab_format_contact 'x-crypto' => 'x-crypto', // not supported by kolab_format_contact // the rest of properties is handled separately ); protected $address_props = array( // 'parameters', // 'pobox', // 'ext', 'street', 'locality', 'region', 'code', 'country' ); protected $gendermap = array( 'M' => 'male', 'F' => 'female', ); /** * Convert contact input array into an array that can * be handled by kolab_storage_folder::save() * * @param array Request body + * @param array Original object data (on update) */ - public function input(&$data) + public function input(&$data, $original = null) { if (empty($data) || !is_array($data)) { throw new kolab_api_exception(kolab_api_exception::INVALID_REQUEST); } foreach ($this->field_map as $kolab => $api) { if (!array_key_exists($api, $data)) { continue; } $value = $data[$api]; switch ($kolab) { case 'gender': $value = $this->gendermap[$value]; break; case 'created': case 'changed': case 'birthday': case 'anniversary': $value = kolab_api_input_json::to_datetime($value); break; + + case 'photo': + if (preg_match('/^(data:image\/[a-z]+;base64,).+$/i', $value, $m)) { + $value = base64_decode(substr($value, strlen($m[1]))); + } + else { + continue 2; + } + break; + + case 'jobtitle': + $value = (array) $value; + break; } $result[$kolab] = $value; } // contact name properties - if (!empty($data['n'])) { + if (array_key_exists('n', $data)) { $name_attrs = array( 'surname' => 'surname', 'firstname' => 'given', 'middlename' => 'additional', 'prefix' => 'prefix', 'suffix' => 'suffix', ); foreach ($name_attrs as $kolab => $api) { - if ($data['n'][$api] !== null) { - $result[$kolab] = $data['n'][$api]; - } + $result[$kolab] = $data['n'][$api]; } } // contact additional properties - if (!empty($data['group'])) { + if (array_key_exists('group', $data)) { + $group_changed = true; + $result['organization'] = null; + $result['department'] = null; + $result['profession'] = null; + $result['manager'] = null; + $result['assistant'] = null; + foreach ((array) $data['group'] as $idx => $entry) { - if (empty($entry)) { - continue; - } // organization, department if ($idx == 'org') { if (is_array($entry) && count($entry) > 1) { $result['organization'] = $entry[0]; $result['department'] = $entry[1]; } else { $result['organization'] = is_array($entry) ? $entry[0] : $entry; } } // profession else if ($idx == 'role') { $result['profession'] = $entry; } // manager, assistant else if ($idx == 'related') { foreach ((array) $entry as $item) { if ($item['text'] !== null) { foreach (array('manager', 'assistant') as $i) { if ($item['parameters']['type'] == "x-$i") { $result[$i] = $item['text']; } } } } } // office address else if ($idx == 'adr') { $address = array('type' => 'office'); foreach ($this->address_props as $prop) { if ($entry[$prop] !== null) { $address[$prop] = $entry[$prop]; } } $result['address'][] = $address; } } } // website url if (array_key_exists('url', $data)) { $result['website'] = array(); + foreach ((array) $data['url'] as $url) { if (is_array($url) && $url['url']) { $result['website'][] = array( 'url' => $url['url'], 'type' => $url['parameters']['type'], ); } else if ($url) { $result['website'][] = array('url' => $url); } } } // home and work address - foreach ((array) $data['adr'] as $addr) { - $address = array('type' => $addr['parameters']['type']); - foreach ($this->address_props as $prop) { - if ($addr[$prop] !== null) { - $address[$prop] = $addr[$prop]; + if (array_key_exists('adr', $data)) { + $adr_changed = true; + + foreach ((array) $data['adr'] as $addr) { + $address = array('type' => $addr['parameters']['type']); + foreach ($this->address_props as $prop) { + if ($addr[$prop] !== null) { + $address[$prop] = $addr[$prop]; + } } - } - $result['address'][] = $address; + $result['address'][] = $address; + } } // spouse, children - if (!empty($data['related'])) { + if (array_key_exists('related', $data)) { + $result['spouse'] = null; + $result['children'] = array(); + foreach ($data['related'] as $entry) { if (isset($entry['text'])) { $type = $entry['parameters']['type']; if ($type == 'spouse') { $result['spouse'] = $entry['text']; } else if ($type == 'child') { $result['children'][] = $entry['text']; } } } } // phone numbers if (array_key_exists('tel', $data)) { + $result['phone'] = array(); + foreach ((array) $data['tel'] as $phone) { if (!empty($phone) && isset($phone['text'])) { $type = implode('', (array) $phone['parameters']['type']); $aliases = array( 'faxhome' => 'homefax', 'faxwork' => 'workfax', 'x-car' => 'car', 'textphone' => 'other', 'cell' => 'mobile', 'voice' => 'main', ); $result['phone'][] = array( 'type' => $aliases[$type] ?: $type, 'number' => $phone['text'], ); } } } // email addresses if (array_key_exists('email', $data)) { + $result['email'] = array(); + foreach ($data['email'] as $email) { if (!empty($email)) { if (!is_array($email)) { $result['email'][] = array( 'type' => 'other', 'address' => $email, ); } else if (isset($email['text'])) { $type = implode('', (array) $email['parameters']['type']); $result['email'][] = array( 'type' => $type ? $type : 'other', 'address' => $email['text'], ); } } } } // PGP or S/MIME key if (array_key_exists('key', $data)) { $key_types = array( 'pgp-keys' => 'pgppublickey', 'pkcs7-mime' => 'pkcs7publickey', ); + foreach ($key_types as $type) { + $result[$type] = null; + } + foreach ((array) $data['key'] as $key) { if (preg_match('#^data:application/(pgp-keys|pkcs7-mime);base64,#', $key, $m)) { $result[$key_types[$m[1]]] = base64_decode(substr($key, strlen($m[0]))); } } } // @TODO: x-custom fields - // @TODO: what contact properties should we require? + // @TODO: which contact properties should we require? if (empty($result)) { throw new kolab_api_exception(kolab_api_exception::INVALID_REQUEST); } + if (!empty($original)) { + // fix addresses merging... + $addresses = (array) $original['address']; + + // unset office address + if ($group_changed) { + foreach ($addresses as $idx => $adr) { + if ($adr['type'] == 'office') { + unset($addresses[$idx]); + } + } + } + + // unset other addresses + if ($adr_changed) { + foreach ($addresses as $idx => $adr) { + if ($adr['type'] != 'office') { + unset($addresses[$idx]); + } + } + } + + // merge old and new addresses + if (isset($result['address']) && !empty($addresses)) { + $result['address'] = array_merge($result['address'], $addresses); + } + + $result = array_merge($original, $result); + } + $data = $result; } } diff --git a/lib/input/json/event.php b/lib/input/json/event.php index 9ef7099..1999eea 100644 --- a/lib/input/json/event.php +++ b/lib/input/json/event.php @@ -1,111 +1,116 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ class kolab_api_input_json_event { // map xml/json attributes into internal (kolab_format) protected $field_map = array( 'description' => 'description', 'title' => 'summary', 'sensitivity' => 'class', 'sequence' => 'sequence', 'categories' => 'categories', 'created' => 'created', 'changed' => 'dtstamp', 'attendees' => 'attendee', 'organizer' => 'organizer', 'recurrence' => 'rrule', 'start' => 'dtstart', 'end' => 'dtend', 'valarms' => 'valarms', 'location' => 'location', 'priority' => 'priority', 'status' => 'status', 'url' => 'url', ); /** * Convert event input array into an array that can * be handled by kolab_storage_folder::save() * * @param array Request body + * @param array Original object data (on update) */ - public function input(&$data) + public function input(&$data, $original = null) { if (empty($data) || !is_array($data)) { throw new kolab_api_exception(kolab_api_exception::INVALID_REQUEST); } - // require at least 'dtstart' property - if (empty($data['dtstart'])) { + // require at least 'dtstart' property for new objects + if (empty($original) && empty($data['dtstart'])) { throw new kolab_api_exception(kolab_api_exception::INVALID_REQUEST); } foreach ($this->field_map as $kolab => $api) { if (!array_key_exists($api, $data)) { continue; } $value = $data[$api]; switch ($kolab) { case 'sensitivity': if ($value) { $value = strtolower($value); } break; case 'url': if (is_array($value)) { $value = $value[0]; } break; case 'created': case 'changed': case 'start': case 'end': $value = kolab_api_input_json::to_datetime($value); break; } $result[$kolab] = $value; } // @TODO: attendees // @TODO: organizer // @TODO: recurrence // @TODO: exceptions // @TOOD: alarms // @TODO: x-custom fields // @TODO: should we require event summary/title? if (empty($result)) { throw new kolab_api_exception(kolab_api_exception::INVALID_REQUEST); } + if (!empty($original)) { + $result = array_merge($original, $result); + } + $data = $result; } } diff --git a/lib/input/json/folder.php b/lib/input/json/folder.php index bfed1cc..6077566 100644 --- a/lib/input/json/folder.php +++ b/lib/input/json/folder.php @@ -1,47 +1,48 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ class kolab_api_input_json_folder { /** * Convert folder input array into internal format * * @param array Request body + * @param array Original object data (on update) */ - public function input(&$data) + public function input(&$data, $original = null) { // folder objects don't need to be converted // we do some sanity check only if (empty($data) || !is_array($data)) { throw new kolab_api_exception(kolab_api_exception::INVALID_REQUEST); } /* if (!isset($data['name']) || !strlen($data['name'])) { throw new kolab_api_exception(kolab_api_exception::INVALID_REQUEST); } */ } } diff --git a/lib/input/json/mail.php b/lib/input/json/mail.php index bbc0406..b8b446f 100644 --- a/lib/input/json/mail.php +++ b/lib/input/json/mail.php @@ -1,66 +1,71 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ class kolab_api_input_json_mail { // map xml/json attributes into internal format protected $field_map = array( ); /** * Convert note input array into an array that can * be handled by kolab_storage_folder::save() * * @param array Request body + * @param array Original object data (on update) */ - public function input(&$data) + public function input(&$data, $original = null) { if (empty($data) || !is_array($data)) { throw new kolab_api_exception(kolab_api_exception::INVALID_REQUEST); } foreach ($this->field_map as $kolab => $api) { if (!array_key_exists($api, $data)) { continue; } $value = $data[$api]; switch ($kolab) { case 'sensitivity': // $value = strtolower($value); break; } $result[$kolab] = $value; } if (empty($result)) { throw new kolab_api_exception(kolab_api_exception::INVALID_REQUEST); } + if (!empty($original)) { + $result = array_merge($original, $result); + } + $data = $result; } } diff --git a/lib/input/json/note.php b/lib/input/json/note.php index 548c5f0..0d7926d 100644 --- a/lib/input/json/note.php +++ b/lib/input/json/note.php @@ -1,80 +1,87 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ class kolab_api_input_json_note { // map xml/json attributes into internal (kolab_format) protected $field_map = array( 'description' => 'description', 'title' => 'summary', 'sensitivity' => 'classification', 'categories' => 'categories', 'created' => 'creation-date', 'changed' => 'last-modification-date', ); /** * Convert note input array into an array that can * be handled by kolab_storage_folder::save() * * @param array Request body + * @param array Original object data (on update) */ - public function input(&$data) + public function input(&$data, $original = null) { if (empty($data) || !is_array($data)) { throw new kolab_api_exception(kolab_api_exception::INVALID_REQUEST); } foreach ($this->field_map as $kolab => $api) { if (!array_key_exists($api, $data)) { continue; } $value = $data[$api]; switch ($kolab) { case 'sensitivity': - $value = strtolower($value); + if ($value) { + $value = strtolower($value); + } break; case 'created': case 'changed': $value = kolab_api_input_json::to_datetime($value); break; } $result[$kolab] = $value; } // @TODO: x-custom fields // @TODO: should we require note summary/title? if (empty($result)) { throw new kolab_api_exception(kolab_api_exception::INVALID_REQUEST); } + if (!empty($original)) { + $result = array_merge($original, $result); + } + $data = $result; } } diff --git a/lib/input/json/task.php b/lib/input/json/task.php index 5a062a0..360b6a1 100644 --- a/lib/input/json/task.php +++ b/lib/input/json/task.php @@ -1,97 +1,109 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ class kolab_api_input_json_task { // map xml/json attributes into internal (kolab_format) protected $field_map = array( 'description' => 'description', 'title' => 'summary', 'sensitivity' => 'class', 'sequence' => 'sequence', 'categories' => 'categories', 'created' => 'created', 'changed' => 'dtstamp', 'complete' => 'percent-complete', 'status' => 'status', - 'start' => 'start', + 'start' => 'dtstart', 'due' => 'due', 'parent_id' => 'related-to', 'location' => 'location', 'priority' => 'priority', 'url' => 'url', ); /** * Convert task input array into an array that can * be handled by kolab_storage_folder::save() * * @param array Request body + * @param array Original object data (on update) */ - public function input(&$data) + public function input(&$data, $original = null) { if (empty($data) || !is_array($data)) { throw new kolab_api_exception(kolab_api_exception::INVALID_REQUEST); } foreach ($this->field_map as $kolab => $api) { if (!array_key_exists($api, $data)) { continue; } $value = $data[$api]; switch ($kolab) { case 'sensitivity': if ($value) { $value = strtolower($value); } break; + case 'parent_id': + // kolab_format_task supports only one parent + if (is_array($value)) { + $value = $value[0]; + } + break; + case 'created': case 'changed': case 'start': case 'due': $value = kolab_api_input_json::to_datetime($value); break; } $result[$kolab] = $value; } // @TOOD: categories // @TODO: attendees // @TODO: organizer // @TODO: recurrence // @TOOD: alarms // @TODO: x-custom fields if (empty($result)) { throw new kolab_api_exception(kolab_api_exception::INVALID_REQUEST); } + if (!empty($original)) { + $result = array_merge($original, $result); + } + $data = $result; } } diff --git a/lib/kolab_api.php b/lib/kolab_api.php index b49c1a3..ea93628 100644 --- a/lib/kolab_api.php +++ b/lib/kolab_api.php @@ -1,406 +1,417 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ class kolab_api extends rcube { const APP_NAME = 'Kolab REST API'; const VERSION = '0.1'; public $backend; public $filter; public $input; public $output; protected $model; /** * This implements the 'singleton' design pattern * * @return kolab_api The one and only instance */ public static function get_instance() { if (!self::$instance || !is_a(self::$instance, 'kolab_api')) { $path = kolab_api_input::request_path(); $request = array_shift($path); $class = 'kolab_api_' . $request; if (!$request || !class_exists($class)) { throw new kolab_api_exception(kolab_api_exception::NOT_FOUND, array( 'code' => 500, 'line' => __LINE__, 'file' => __FILE__, 'message' => "Invalid request method: $request" )); } self::$instance = new $class(); self::$instance->startup(); } return self::$instance; } /** * Initial startup function * to register session, create database and imap connections */ protected function startup() { $this->init(self::INIT_WITH_DB | self::INIT_WITH_PLUGINS); // Get list of plugins // WARNING: We can use only plugins that are prepared for this // e.g. are not using output or rcmail objects or // doesn't throw errors when using them $plugins = (array) $this->config->get('kolab_api_plugins', array('kolab_auth')); $plugins = array_unique(array_merge($plugins, array('libkolab'))); // this way we're compatible with Roundcube Framework 1.2 // we can't use load_plugins() here foreach ($plugins as $plugin) { $this->plugins->load_plugin($plugin, true); } /* // start session $this->session_init(); // create user object $this->set_user(new rcube_user($_SESSION['user_id'])); */ } /** * Exception handler * * @param kolab_api_exception Exception */ public static function exception_handler($exception) { $code = $exception->getCode(); $message = $exception->getMessage(); if ($code == 401) { header('WWW-Authenticate: Basic realm="' . self::APP_NAME .'"'); } if (!$exception instanceof kolab_api_exception) { rcube::raise_error($exception, true, false); } header("HTTP/1.1 $code $message"); exit; } /** * Program execution handler */ protected function initialize_handler() { // Handle request input $this->input = kolab_api_input::factory($this); // Get input/output filter $this->filter = $this->input->filter; // Initialize backend $this->backend = kolab_api_backend::get_instance(); // Check authentication $this->authenticate(); // Filter the input, we want this after authentication if ($this->filter) { $this->filter->input($this->input); } // set response output class $this->output = kolab_api_output::factory($this); } /** * Script shutdown handler */ public function shutdown() { parent::shutdown(); // write performance stats to logs/console if ($this->config->get('devel_mode')) { if (function_exists('memory_get_peak_usage')) $mem = memory_get_peak_usage(); else if (function_exists('memory_get_usage')) $mem = memory_get_usage(); $log = trim(kolab_api_input::request_uri() . ($mem ? sprintf(' [%.1f MB]', $mem/1024/1024) : '')); if (defined('KOLAB_API_START')) { rcube::print_timer(KOLAB_API_START, $log); } else { rcube::console($log); } } } /** * Authentication request handler (HTTP Auth) */ protected function authenticate() { if (!empty($_SERVER['PHP_AUTH_USER'])) { $username = $_SERVER['PHP_AUTH_USER']; $password = $_SERVER['PHP_AUTH_PW']; } // when used with (f)cgi no PHP_AUTH* variables are available without defining a special rewrite rule else if (!isset($_SERVER['PHP_AUTH_USER'])) { // "Basic didhfiefdhfu4fjfjdsa34drsdfterrde..." if (isset($_SERVER['REMOTE_USER'])) { $basicAuthData = base64_decode(substr($_SERVER['REMOTE_USER'], 6)); } else if (isset($_SERVER['REDIRECT_REMOTE_USER'])) { $basicAuthData = base64_decode(substr($_SERVER['REDIRECT_REMOTE_USER'], 6)); } else if (isset($_SERVER['Authorization'])) { $basicAuthData = base64_decode(substr($_SERVER['Authorization'], 6)); } else if (isset($_SERVER['HTTP_AUTHORIZATION'])) { $basicAuthData = base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)); } if (isset($basicAuthData) && !empty($basicAuthData)) { list($username, $password) = explode(':', $basicAuthData); } } if (!empty($username)) { $result = $this->backend->authenticate($username, $password); } if (empty($result)) { throw new kolab_api_exception(kolab_api_exception::UNAUTHORIZED); } } - /** - * Merge new properties into the object on update - */ - protected function merge_data($new_props, $object) - { - foreach ($new_props as $idx => $value) { - $object[$idx] = $value; - } - - return $object; - } - /** * Handle API request */ public function run() { $this->initialize_handler(); $path = $this->input->path; $method = $this->input->method; if (!$path[1] && $path[0] && $method == 'POST') { $this->api_object_create(); } else if ($path[1]) { switch (strtolower($path[2])) { case 'attachments': if ($method == 'HEAD') { $this->api_object_count_attachments(); } else if ($method == 'GET') { $this->api_object_list_attachments(); } break; case '': if ($method == 'GET') { $this->api_object_info(); } else if ($method == 'PUT') { $this->api_object_update(); } else if ($method == 'HEAD') { $this->api_object_exists(); } else if ($method == 'DELETE') { $this->api_object_delete(); } } } throw new kolab_api_exception(kolab_api_exception::NOT_FOUND); } /** * Fetch object info */ protected function api_object_info() { $folder = $this->input->path[0]; $uid = $this->input->path[1]; $object = $this->backend->object_get($folder, $uid); $context = array('folder_uid' => $folder, 'object' => $object); $this->output->send($object, $this->model, $context); } /** - * Create a object + * Create an object */ protected function api_object_create() { $folder = $this->input->path[0]; $input = $this->input->input($this->model); $context = array('folder_uid' => $folder); $uid = $this->backend->object_create($folder, $input, $this->model); $this->output->send(array('uid' => $uid), $this->model, $context, array('uid')); } /** * Update specified object */ protected function api_object_update() { $folder = $this->input->path[0]; $uid = $this->input->path[1]; $object = $this->backend->object_get($folder, $uid); - $input = $this->input->input($this->model); - // merge old object's data with new properties - $object = $this->merge_data($input, $object); + // convert object data into API format + $data = $this->get_object_data($object, $this->model); - $this->backend->object_update($folder, $object, $this->model); + // parse input and merge with current data (result is in kolab_format) + $input = $this->input->input($this->model, false, $object); + + // update object on the backend + $this->backend->object_update($folder, $input, $this->model); // $this->output->send(array('uid' => $uid), $this->model, $folder); $this->output->send_status(kolab_api_output::STATUS_EMPTY); } /** * Check if specified object exists */ protected function api_object_exists() { $folder = $this->input->path[0]; $uid = $this->input->path[1]; $object = $this->backend->object_get($folder, $uid); $this->output->send_status(kolab_api_output::STATUS_OK); } /** * Remove specified object */ protected function api_object_delete() { $folder = $this->input->path[0]; $uid = $this->input->path[1]; $object = $this->backend->object_get($folder, $uid); $this->backend->objects_delete($folder, array($uid)); $this->output->send_status(kolab_api_output::STATUS_EMPTY); } /** * Count object attachments */ protected function api_object_count_attachments() { $folder = $this->input->path[0]; $uid = $this->input->path[1]; $object = $this->backend->object_get($folder, $uid); $context = array( 'folder_uid' => $folder, 'object_uid' => $uid, 'object' => $object ); $count = !empty($object['_attachments']) ? count($object['_attachments']) : 0; $this->output->headers(array('X-Count' => $count)); $this->output->send_status(kolab_api_output::STATUS_OK); } /** * List object attachments */ protected function api_object_list_attachments() { $folder = $this->input->path[0]; $uid = $this->input->path[1]; $object = $this->backend->object_get($folder, $uid); $props = $this->input->args['properties'] ? explode(',', $this->input->args['properties']) : null; $context = array( 'folder_uid' => $folder, 'object_uid' => $uid, 'object' => $object ); // @TODO: currently Kolab format (libkolabxml) allows attachments // in events, tasks and notes. We should support them also in contacts $list = $this->get_object_attachments($object); $this->output->send($list, 'attachment-list', $context, $props); } /** * Extract attachments from the object, depending if it's * Kolab object or email message */ protected function get_object_attachments($object) { // this is a kolab_format object data if (is_array($object)) { $list = (array) $object['_attachments']; foreach ($list as $idx => $att) { $attachment = new rcube_message_part; $attachment->mime_id = $att['id']; $attachment->filename = $att['name']; $attachment->mimetype = $att['mimetype']; $attachment->size = $att['size']; $attachment->disposition = 'attachment'; $list[$idx] = $attachment; } } // this is rcube_message(_header) else { $list = (array) $object->attachments; } return $list; } + + /** + * Convert kolab_format object into API format + * + * @param array Object data in kolab_format + * @param string Object type + * + * @return array Object data in API format + */ + public function get_object_data($object, $type) + { + $output = $this->output; + + if (!$this->output instanceof kolab_api_output_json) { + $class = "kolab_api_output_json"; + $output = new $class($this); + } + + return $output->convert($object, $type); + } } diff --git a/lib/kolab_api_filter.php b/lib/kolab_api_filter.php index 55d482e..07edae5 100644 --- a/lib/kolab_api_filter.php +++ b/lib/kolab_api_filter.php @@ -1,73 +1,74 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ abstract class kolab_api_filter { /** * Modify initial request path * * @param array (Exploded) request path */ abstract function path(&$path); /** * Executed before every api action * * @param kolab_api_input Request data */ abstract function input(&$input); /** * Executed when parsing request body * * @param array Request body * @param string Object type + * @param array Original object data (set on update requests) */ - abstract function input_body(&$data, $type); + abstract function input_body(&$data, $type, $original = null); /** * Apply filter on output data * * @param array Result data * @param string Object type * @param array Context (folder_uid, object_uid, object) * @param array Optional attributes filter */ abstract function output(&$output, $type, $context, $attrs_filter = array()); /** * Executed for response headers * * @param array Response headers */ abstract function headers(&$headers); /** * Executed for empty response status * * @param int Status code */ abstract function send_status(&$status); } diff --git a/lib/kolab_api_input.php b/lib/kolab_api_input.php index 7708b9f..bac8d6e 100644 --- a/lib/kolab_api_input.php +++ b/lib/kolab_api_input.php @@ -1,136 +1,137 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ abstract class kolab_api_input { public $method; public $action; public $path = array(); public $args = array(); public $supports = array(); public $api; public $filter; public $input_body; /** * Factory method to create input object * according to the API request input * * @param kolab_api The API * * @return kolab_api_input Output object */ public static function factory($api) { // default mode $mode = 'json'; $class = "kolab_api_input_$mode"; return new $class($api); } /** * Object constructor */ public function __construct($api) { $this->api = $api; $this->method = $_SERVER['REQUEST_METHOD']; if ($this->method == 'POST' && !empty($_SERVER['HTTP_X_HTTP_METHOD'])) { $this->method = $_SERVER['HTTP_X_HTTP_METHOD']; } $this->path = self::request_path($this->filter); // remove first argument - action name $this->action = array_shift($this->path); if ($this->api->config->get('kolab_api_debug')) { rcube::console($this->method . ': ' . self::request_uri()); // @TODO: log request input data for PUT/POST } $accept_header = strtolower(rcube_utils::request_header('Accept')); list($this->supports,) = explode(';', $accept_header); $this->supports = explode(',', $this->supports); // store GET arguments $this->args = $_GET; unset($this->args['api']); unset($this->args['request']); } /** * Parse request arguments * * @return array Request arguments */ public static function request_path(&$filter = null) { $api = (string) $_GET['api']; $path = explode('/', trim((string) $_GET['request'], ' /')); // map api specific request to Kolab API if ($api && class_exists("kolab_api_filter_$api")) { $class = "kolab_api_filter_$api"; $filter = new $class; $filter->path($path); } foreach ($path as $idx => $value) { $path[$idx] = strip_tags($value); } $path[0] = strtolower($path[0]); return $path; } /** * Return request URI (for logging) */ public static function request_uri() { $url = trim((string) $_GET['request'], ' /'); list($uri, $params) = explode('?', $_SERVER['REQUEST_URI']); if ($params) { $url .= '?' . $params; } return $url; } /** * Get request data (JSON) * * @param string Expected object type * @param bool Disable filters application + * @param array Original object data (set on update requests) * * @return array Request data */ - abstract function input($type = null, $disable_filters = false); + abstract function input($type = null, $disable_filters = false, $original = null); } diff --git a/lib/output/json.php b/lib/output/json.php index 10bbba1..1c2f9e1 100644 --- a/lib/output/json.php +++ b/lib/output/json.php @@ -1,219 +1,237 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ class kolab_api_output_json extends kolab_api_output { /** * Send successful response * * @param mixed Response data * @param string Data type * @param array Context (folder_uid, object_uid, object) * @param array Optional attributes filter */ public function send($data, $type, $context = null, $attrs_filter = array()) { // Set output type $this->headers(array('Content-Type' => "application/json; charset=utf-8")); list($type, $mode) = explode('-', $type); if ($mode != 'list') { $data = array($data); } $class = "kolab_api_output_json_$type"; $model = new $class($this); $result = array(); $debug = $this->api->config->get('kolab_api_debug'); foreach ($data as $idx => $item) { if ($element = $model->element($item, $attrs_filter)) { $result[] = $element; } else { unset($data[$idx]); } } // apply output filter if ($this->api->filter) { $this->api->filter->output($result, $type, $context, $attrs_filter); } // generate JSON output $opts = $debug && defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0; $result = json_encode($result, $opts); if ($mode != 'list') { $result = trim($result, '[]'); } if ($debug) { rcube::console($result); } // send JSON output echo $result; exit; } + /** + * Convert object data into JSON API format + * + * @param array Object data + * @param string Object type + * + * @return array Object data in JSON API format + */ + public function convert($data, $type) + { + $class = "kolab_api_output_json_$type"; + $model = new $class($this); + + return $model->element($data); + } + /** * Convert (part of) kolab_format object into an array * * @param array Kolab object * @param string Object type * @param string Data element name * @param array Optional list of return properties * * @return array Object data */ public function object_to_array($object, $type, $element, $properties = array(), $array_elements = array()) { // load old object to preserve data we don't understand/process if (is_object($object['_formatobj'])) { $format = $object['_formatobj']; } // create new kolab_format instance if (!$format) { $format = kolab_format::factory($type, kolab_storage::$version); if (PEAR::isError($format)) { return; } $format->set($object); } $xml = $format->write(kolab_storage::$version); if (empty($xml) || !$format->is_valid() || !$format->uid) { return; } // The simplest way of "normalizing object properties // is to use its XML representation $doc = new DOMDocument(); // LIBXML_NOBLANKS is required for xml_to_array() below $doc->loadXML($xml, LIBXML_NOBLANKS); $node = $doc->getElementsByTagName($element)->item(0); $node = $this->xml_to_array($node); $node = array_filter($node); unset($node['prodid']); if (!empty($properties)) { $node = array_intersect_key($node, array_combine($properties, $properties)); } // force some elements to be arrays - self::parse_array_result($node, $array_elements); + if (!empty($array_elements)) { + self::parse_array_result($node, $array_elements); + } return $node; } /** * Convert XML element into an array * This is intended to use with Kolab XML format * * @param DOMElement XML element * * @return mixed Conversion result */ public function xml_to_array($node) { $children = $node->childNodes; if (!$children->length) { return; } if ($children->length == 1) { if ($node->firstChild->nodeType == XML_TEXT_NODE || !$node->firstChild->childNodes->length ) { return (string) $node->textContent; } if ($node->firstChild->nodeType == XML_ELEMENT_NODE && $node->firstChild->childNodes->length == 1 && $node->firstChild->firstChild->nodeType == XML_TEXT_NODE ) { switch ($node->firstChild->nodeName) { case 'integer': return (int) $node->textContent; case 'date-time': case 'timestamp': case 'date': case 'text': default: return (string) $node->textContent; } } } $result = array(); foreach ($children as $child) { $value = $child->nodeType == XML_TEXT_NODE ? $child->nodeValue : $this->xml_to_array($child); if (!isset($result[$child->nodeName])) { $result[$child->nodeName] = $value; } else { if (!is_array($result[$child->nodeName]) || !isset($result[$child->nodeName][0])) { $result[$child->nodeName] = array($result[$child->nodeName]); } $result[$child->nodeName][] = $value; } } if (is_array($result['text']) && count($result) == 1) { $result = $result['text']; } return $result; } public static function parse_array_result(&$data, $array_elements = array()) { foreach ($array_elements as $key) { $items = explode('/', $key); if (count($items) > 1 && !empty($data[$items[0]])) { $key = array_shift($items); self::parse_array_result($data[$key], array(implode('/', $items))); } else if (!empty($data[$key]) && (!is_array($data[$key]) || !array_key_exists(0, $data[$key]))) { $data[$key] = array($data[$key]); } } } } diff --git a/lib/output/json/contact.php b/lib/output/json/contact.php index 1b74e11..dd1c232 100644 --- a/lib/output/json/contact.php +++ b/lib/output/json/contact.php @@ -1,77 +1,79 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ class kolab_api_output_json_contact { protected $output; protected $array_elements = array( // 'group', // @TODO 'adr', 'related', 'url', 'lang', 'tel', 'impp', 'email', 'geo', 'key', + 'title', + 'categories', 'member', //dist-list 'x-custom', ); /** * Object constructor * * @param kolab_api_output Output object */ public function __construct($output) { $this->output = $output; } /** * Convert data into an array * * @param array Data * @param array Optional attributes filter * * @return array Data */ public function element($data, $attrs_filter = array()) { // partial data if (count($data) == 1) { $attrs_filter = array(key($data)); } $result = $this->output->object_to_array($data, 'contact', 'vcard', $attrs_filter, $this->array_elements); if ($result['uid'] && strpos($result['uid'], 'urn:uuid:') === 0) { $result['uid'] = substr($result['uid'], 9); } return $result; } } diff --git a/lib/output/json/note.php b/lib/output/json/note.php index 1ba79f3..0d8906e 100644 --- a/lib/output/json/note.php +++ b/lib/output/json/note.php @@ -1,63 +1,64 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ class kolab_api_output_json_note { protected $output; protected $array_elements = array( 'attachment', + 'categories', 'x-custom', ); /** * Object constructor * * @param kolab_api_output Output object */ public function __construct($output) { $this->output = $output; } /** * Convert data into an array * * @param array Data * @param array Optional attributes filter * * @return array Data */ public function element($data, $attrs_filter = array()) { // partial data if (count($data) == 1) { $attrs_filter = array(key($data)); } $result = $this->output->object_to_array($data, 'note', 'note', $attrs_filter, $this->array_elements); return $result; } } diff --git a/lib/output/json/task.php b/lib/output/json/task.php index 0b41828..618d344 100644 --- a/lib/output/json/task.php +++ b/lib/output/json/task.php @@ -1,66 +1,76 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ class kolab_api_output_json_task { protected $output; protected $array_elements = array( 'attach', 'attendee', 'related-to', 'x-custom', - 'components/valarm', + 'categories', + 'valarm', ); /** * Object constructor * * @param kolab_api_output Output object */ public function __construct($output) { $this->output = $output; } /** * Convert data into an array * * @param array Data * @param array Optional attributes filter * * @return array Data */ public function element($data, $attrs_filter = array()) { - $result = $this->output->object_to_array($data, 'task', 'vtodo', null, $this->array_elements); + $result = $this->output->object_to_array($data, 'task', 'vtodo'); if (!empty($attrs_filter)) { $result['properties'] = array_intersect_key($result['properties'], array_combine($attrs_filter, $attrs_filter)); } - return $result['properties']; + // add 'components' to the result + if (!empty($result['components'])) { + $result['properties'] += (array) $result['components']; + } + + $result = $result['properties']; + + kolab_api_output_json::parse_array_result($result, $this->array_elements); + + return $result; } } diff --git a/tests/API/Contacts.php b/tests/API/Contacts.php index 5f74237..1142218 100644 --- a/tests/API/Contacts.php +++ b/tests/API/Contacts.php @@ -1,187 +1,187 @@ get('folders/' . md5('Contacts') . '/objects'); $code = self::$api->response_code(); $body = self::$api->response_body(); $body = json_decode($body, true); $this->assertEquals(200, $code); $this->assertSame('a-b-c-d', $body[0]['uid']); $this->assertSame('displname', $body[0]['fn']); } /** * Test contact existence */ function test_contact_exists() { self::$api->head('contacts/' . md5('Contacts') . '/a-b-c-d'); $code = self::$api->response_code(); $body = self::$api->response_body(); $this->assertEquals(200, $code); $this->assertSame('', $body); // and non-existing contact self::$api->head('contacts/' . md5('Contacts') . '/12345'); $code = self::$api->response_code(); $body = self::$api->response_body(); $this->assertEquals(404, $code); $this->assertSame('', $body); } /** * Test contact info */ function test_contact_info() { self::$api->get('contacts/' . md5('Contacts') . '/a-b-c-d'); $code = self::$api->response_code(); $body = self::$api->response_body(); $body = json_decode($body, true); $this->assertEquals(200, $code); $this->assertSame('a-b-c-d', $body['uid']); $this->assertSame('displname', $body['fn']); } /** * Test contact create */ function test_contact_create() { $post = json_encode(array( 'n' => array( 'surname' => 'lastname', ), 'note' => 'Test description', )); self::$api->post('contacts/' . md5('Contacts'), array(), $post); $code = self::$api->response_code(); $body = self::$api->response_body(); $body = json_decode($body, true); $this->assertEquals(200, $code); $this->assertCount(1, $body); $this->assertTrue(!empty($body['uid'])); // folder does not exists $post = json_encode(array( 'n' => array( 'surname' => 'lastname', ), 'note' => 'Test description', )); self::$api->post('contacts/' . md5('non-existing'), array(), $post); $code = self::$api->response_code(); $this->assertEquals(404, $code); // invalid object data $post = json_encode(array( 'test' => 'Test summary 2', )); self::$api->post('contacts/' . md5('Contacts'), array(), $post); $code = self::$api->response_code(); $this->assertEquals(422, $code); } /** * Test contact update */ function test_contact_update() { - // @TODO: test modification of all supported properties $post = json_encode(array( - 'note' => 'Modified notes', + 'note' => 'note1', )); + self::$api->put('contacts/' . md5('Contacts') . '/a-b-c-d', array(), $post); $code = self::$api->response_code(); $body = self::$api->response_body(); $this->assertEquals(204, $code); $this->assertSame('', $body); self::$api->get('contacts/' . md5('Contacts') . '/a-b-c-d'); $body = self::$api->response_body(); $body = json_decode($body, true); - $this->assertSame('Modified notes', $body['note']); + $this->assertSame('note1', $body['note']); } /** * Test contact delete */ function test_contact_delete() { // delete existing contact self::$api->delete('contacts/' . md5('Contacts') . '/a-b-c-d'); $code = self::$api->response_code(); $body = self::$api->response_body(); $this->assertEquals(204, $code); $this->assertSame('', $body); // and non-existing contact self::$api->delete('contacts/' . md5('Contacts') . '/12345'); $code = self::$api->response_code(); $body = self::$api->response_body(); $this->assertEquals(404, $code); $this->assertSame('', $body); } /** * Test counting task attachments */ function test_count_attachments() { // @TODO } /** * Test listing task attachments */ function test_list_attachments() { // @TODO } } diff --git a/tests/API/Notes.php b/tests/API/Notes.php index e24314e..d75ce49 100644 --- a/tests/API/Notes.php +++ b/tests/API/Notes.php @@ -1,185 +1,212 @@ get('folders/' . md5('Notes') . '/objects'); $code = self::$api->response_code(); $body = self::$api->response_body(); $body = json_decode($body, true); $this->assertEquals(200, $code); $this->assertSame(2, count($body)); $this->assertSame('1-1-1-1', $body[0]['uid']); $this->assertSame('test', $body[0]['summary']); $this->assertSame('2-2-2-2', $body[1]['uid']); $this->assertSame('wwww', $body[1]['summary']); } /** * Test note existence */ function test_note_exists() { self::$api->head('notes/' . md5('Notes') . '/1-1-1-1'); $code = self::$api->response_code(); $body = self::$api->response_body(); $this->assertEquals(200, $code); $this->assertSame('', $body); // and non-existing note self::$api->head('notes/' . md5('Notes') . '/12345'); $code = self::$api->response_code(); $body = self::$api->response_body(); $this->assertEquals(404, $code); $this->assertSame('', $body); } /** * Test note info */ function test_note_info() { self::$api->get('notes/' . md5('Notes') . '/1-1-1-1'); $code = self::$api->response_code(); $body = self::$api->response_body(); $body = json_decode($body, true); $this->assertEquals(200, $code); $this->assertSame('1-1-1-1', $body['uid']); $this->assertSame('test', $body['summary']); } /** * Test note create */ function test_note_create() { $post = json_encode(array( 'summary' => 'Test summary', 'description' => 'Test description' )); self::$api->post('notes/' . md5('Notes'), array(), $post); $code = self::$api->response_code(); $body = self::$api->response_body(); $body = json_decode($body, true); $this->assertEquals(200, $code); $this->assertCount(1, $body); $this->assertTrue(!empty($body['uid'])); // folder does not exists $post = json_encode(array( 'summary' => 'Test summary 2', )); self::$api->post('notes/' . md5('non-existing'), array(), $post); $code = self::$api->response_code(); $this->assertEquals(404, $code); // invalid object data $post = json_encode(array( 'test' => 'Test summary 2', )); self::$api->post('notes/' . md5('Notes'), array(), $post); $code = self::$api->response_code(); $this->assertEquals(422, $code); } /** * Test note update */ function test_note_update() { $post = json_encode(array( - 'summary' => 'Modified summary', - 'description' => 'Modified description' + 'summary' => 'Modified summary', + 'description' => 'Modified description', + 'classification' => 'PRIVATE', + 'categories' => array('test'), + 'unknown' => 'test' )); self::$api->put('notes/' . md5('Notes') . '/1-1-1-1', array(), $post); $code = self::$api->response_code(); $body = self::$api->response_body(); $this->assertEquals(204, $code); $this->assertSame('', $body); self::$api->get('notes/' . md5('Notes') . '/1-1-1-1'); $body = self::$api->response_body(); $body = json_decode($body, true); $this->assertSame('Modified summary', $body['summary']); $this->assertSame('Modified description', $body['description']); + $this->assertSame('PRIVATE', $body['classification']); + $this->assertSame(array('test'), $body['categories']); + $this->assertSame(null, $body['unknown']); + + // test unsetting some data + $post = json_encode(array( + 'description' => null, + 'categories' => null, + )); + self::$api->put('notes/' . md5('Notes') . '/1-1-1-1', array(), $post); + + $code = self::$api->response_code(); + $body = self::$api->response_body(); + + $this->assertEquals(204, $code); + $this->assertSame('', $body); + + self::$api->get('notes/' . md5('Notes') . '/1-1-1-1'); + + $body = self::$api->response_body(); + $body = json_decode($body, true); + + $this->assertSame(null, $body['description']); + $this->assertSame(null, $body['categories']); } /** * Test note delete */ function test_note_delete() { // delete existing note self::$api->delete('notes/' . md5('Notes') . '/1-1-1-1'); $code = self::$api->response_code(); $body = self::$api->response_body(); $this->assertEquals(204, $code); $this->assertSame('', $body); // and non-existing note self::$api->delete('notes/' . md5('Notes') . '/12345'); $code = self::$api->response_code(); $body = self::$api->response_body(); $this->assertEquals(404, $code); $this->assertSame('', $body); } /** * Test counting task attachments */ function test_count_attachments() { // @TODO } /** * Test listing task attachments */ function test_list_attachments() { // @TODO } } diff --git a/tests/API/Tasks.php b/tests/API/Tasks.php index d141a4d..2cb26b6 100644 --- a/tests/API/Tasks.php +++ b/tests/API/Tasks.php @@ -1,205 +1,234 @@ get('folders/' . md5('Tasks') . '/objects'); $code = self::$api->response_code(); $body = self::$api->response_body(); $body = json_decode($body, true); $this->assertEquals(200, $code); $this->assertSame(2, count($body)); $this->assertSame('10-10-10-10', $body[0]['uid']); $this->assertSame('task title', $body[0]['summary']); $this->assertSame('20-20-20-20', $body[1]['uid']); $this->assertSame('task', $body[1]['summary']); } /** * Test task existence */ function test_task_exists() { self::$api->head('tasks/' . md5('Tasks') . '/10-10-10-10'); $code = self::$api->response_code(); $body = self::$api->response_body(); $this->assertEquals(200, $code); $this->assertSame('', $body); // and non-existing task self::$api->head('tasks/' . md5('Tasks') . '/12345'); $code = self::$api->response_code(); $body = self::$api->response_body(); $this->assertEquals(404, $code); $this->assertSame('', $body); } /** * Test task info */ function test_task_info() { self::$api->get('tasks/' . md5('Tasks') . '/10-10-10-10'); $code = self::$api->response_code(); $body = self::$api->response_body(); $body = json_decode($body, true); $this->assertEquals(200, $code); $this->assertSame('10-10-10-10', $body['uid']); $this->assertSame('task title', $body['summary']); } /** * Test task create */ function test_task_create() { $post = json_encode(array( 'summary' => 'Test summary', 'description' => 'Test description' )); self::$api->post('tasks/' . md5('Tasks'), array(), $post); $code = self::$api->response_code(); $body = self::$api->response_body(); $body = json_decode($body, true); $this->assertEquals(200, $code); $this->assertCount(1, $body); $this->assertTrue(!empty($body['uid'])); // folder does not exists $post = json_encode(array( 'summary' => 'Test summary 2', )); self::$api->post('tasks/' . md5('non-existing'), array(), $post); $code = self::$api->response_code(); $this->assertEquals(404, $code); // invalid object data $post = json_encode(array( 'test' => 'Test summary 2', )); self::$api->post('tasks/' . md5('Tasks'), array(), $post); $code = self::$api->response_code(); $this->assertEquals(422, $code); } /** * Test task update */ function test_task_update() { $post = json_encode(array( - 'summary' => 'Modified summary', - 'description' => 'Modified description' + 'summary' => 'modified summary', + 'description' => 'modified description', + 'class' => 'PRIVATE', + 'dtstart' => '2014-01-10', )); self::$api->put('tasks/' . md5('Tasks') . '/10-10-10-10', array(), $post); $code = self::$api->response_code(); $body = self::$api->response_body(); $this->assertEquals(204, $code); $this->assertSame('', $body); self::$api->get('tasks/' . md5('Tasks') . '/10-10-10-10'); $body = self::$api->response_body(); $body = json_decode($body, true); - $this->assertSame('Modified summary', $body['summary']); - $this->assertSame('Modified description', $body['description']); + $this->assertSame('modified summary', $body['summary']); + $this->assertSame('modified description', $body['description']); + $this->assertSame('PRIVATE', $body['class']); + + // test unsetting some properties + $post = json_encode(array( + 'due' => null, + 'related-to' => null, + 'location' => null, + 'unknown' => 'test', + )); + self::$api->put('tasks/' . md5('Tasks') . '/10-10-10-10', array(), $post); + + $code = self::$api->response_code(); + $body = self::$api->response_body(); + + $this->assertEquals(204, $code); + $this->assertSame('', $body); + + self::$api->get('tasks/' . md5('Tasks') . '/10-10-10-10'); + + $body = self::$api->response_body(); + $body = json_decode($body, true); + + $this->assertSame('2014-01-10', $body['dtstart']); + $this->assertSame(null, $body['due']); + $this->assertSame(null, $body['related-to']); + $this->assertSame(null, $body['location']); + $this->assertSame(null, $body['unknown']); } /** * Test task delete */ function test_task_delete() { // delete existing task self::$api->delete('tasks/' . md5('Tasks') . '/20-20-20-20'); $code = self::$api->response_code(); $body = self::$api->response_body(); $this->assertEquals(204, $code); $this->assertSame('', $body); // and non-existing task self::$api->delete('tasks/' . md5('Tasks') . '/12345'); $code = self::$api->response_code(); $body = self::$api->response_body(); $this->assertEquals(404, $code); $this->assertSame('', $body); } /** * Test counting task attachments */ function test_count_attachments() { self::$api->head('tasks/' . md5('Tasks') . '/10-10-10-10/attachments'); $code = self::$api->response_code(); $body = self::$api->response_body(); $count = self::$api->response_header('X-Count'); $this->assertEquals(200, $code); $this->assertSame('', $body); $this->assertSame(1, (int) $count); } /** * Test listing task attachments */ function test_list_attachments() { self::$api->get('tasks/' . md5('Tasks') . '/10-10-10-10/attachments'); $code = self::$api->response_code(); $body = self::$api->response_body(); $body = json_decode($body, true); $this->assertEquals(200, $code); $this->assertCount(1, $body); $this->assertSame('3', $body[0]['id']); $this->assertSame('text/plain', $body[0]['mimetype']); $this->assertSame('test.txt', $body[0]['filename']); $this->assertSame('attachment', $body[0]['disposition']); $this->assertSame(4, $body[0]['size']); } } diff --git a/tests/Mapistore/Contacts.php b/tests/Mapistore/Contacts.php index 3c6a015..d31cf00 100644 --- a/tests/Mapistore/Contacts.php +++ b/tests/Mapistore/Contacts.php @@ -1,234 +1,231 @@ get('folders/' . md5('Contacts') . '/messages'); $code = self::$api->response_code(); $body = self::$api->response_body(); $body = json_decode($body, true); $this->assertEquals(200, $code); $this->assertSame(kolab_api_filter_mapistore::uid_encode(md5('Contacts'), 'a-b-c-d'), $body[0]['id']); $this->assertSame(md5('Contacts'), $body[0]['parent_id']); $this->assertSame('contacts', $body[0]['collection']); $this->assertSame('IPM.Contact', $body[0]['PidTagMessageClass']); $this->assertSame('displname', $body[0]['PidTagDisplayName']); } /** * Test contact existence */ function test_contact_exists() { self::$api->head('contacts/' . kolab_api_filter_mapistore::uid_encode(md5('Contacts'), 'a-b-c-d')); $code = self::$api->response_code(); $body = self::$api->response_body(); $this->assertEquals(200, $code); $this->assertSame('', $body); // and non-existing contact self::$api->get('contacts/' . kolab_api_filter_mapistore::uid_encode(md5('Contacts'), '12345')); $code = self::$api->response_code(); $body = self::$api->response_body(); $this->assertEquals(404, $code); $this->assertSame('', $body); } /** * Test contact info */ function test_contact_info() { self::$api->get('contacts/' . kolab_api_filter_mapistore::uid_encode(md5('Contacts'), 'a-b-c-d')); $code = self::$api->response_code(); $body = self::$api->response_body(); $body = json_decode($body, true); $this->assertEquals(200, $code); $this->assertSame(kolab_api_filter_mapistore::uid_encode(md5('Contacts'), 'a-b-c-d'), $body['id']); $this->assertSame(md5('Contacts'), $body['parent_id']); $this->assertSame('contacts', $body['collection']); $this->assertSame('IPM.Contact', $body['PidTagMessageClass']); $this->assertSame('displname', $body['PidTagDisplayName']); } /** * Test contact create */ function test_contact_create() { $post = json_encode(array( 'parent_id' => md5('Contacts'), 'PidTagSurname' => 'lastname', 'PidTagTitle' => 'Test title', )); self::$api->post('contacts', array(), $post); $code = self::$api->response_code(); $body = self::$api->response_body(); $body = json_decode($body, true); $this->assertEquals(200, $code); $this->assertCount(1, $body); $this->assertTrue(!empty($body['id'])); // folder does not exists $post = json_encode(array( 'parent_id' => md5('non-existing'), 'PidTagSurname' => 'lastname', 'PidTagTitle' => 'Test title', )); self::$api->post('contacts', array(), $post); $code = self::$api->response_code(); $this->assertEquals(404, $code); // invalid object data $post = json_encode(array( 'parent_id' => md5('Contacts'), 'test' => 'Test summary 2', )); self::$api->post('contacts', array(), $post); $code = self::$api->response_code(); $this->assertEquals(422, $code); } /** * Test contact update */ function test_contact_update() { // @TODO: most of this should probably be in unit-tests not functional-tests $post = array( 'PidTagTitle' => 'Title', 'PidTagNickname' => 'Nickname', -// @TODO -// 'PidTagBirthday' => kolab_api_filter_mapistore::date_php2mapi('20150330'), -// 'PidTagWeddingAnniversary' => kolab_api_filter_mapistore::date_php2mapi('20150301'), 'PidTagDisplayName' => 'DisplayName', 'PidTagSurname' => 'Surname', 'PidTagGivenName' => 'GivenName', 'PidTagMiddleName' => 'MiddleName', 'PidTagDisplayNamePrefix' => 'Prefix', 'PidTagGeneration' => 'Generation', 'PidTagBody' => 'Body', 'PidLidFreeBusyLocation' => 'FreeBusyLocation', 'PidTagCompanyName' => 'CompanyName', 'PidTagDepartmentName' => 'Department', 'PidTagProfession' => 'Profession', 'PidTagManagerName' => 'Manager', 'PidTagAssistant' => 'Assistant', 'PidTagPersonalHomePage' => 'HomePage', 'PidTagOtherAddressStreet' => 'OtherStreet', 'PidTagOtherAddressCity' => 'OtherCity', 'PidTagOtherAddressStateOrProvince' => 'OtherState', 'PidTagOtherAddressPostalCode' => 'OtherCode', 'PidTagOtherAddressCountry' => 'OtherCountry', // 'PidTagOtherAddressPostOfficeBox' => 'OtherBox', 'PidTagHomeAddressStreet' => 'HomeStreet', 'PidTagHomeAddressCity' => 'HomeCity', 'PidTagHomeAddressStateOrProvince' => 'HomeState', 'PidTagHomeAddressPostalCode' => 'HomeCode', 'PidTagHomeAddressCountry' => 'HomeCountry', // 'PidTagHomeAddressPostOfficeBox' => 'HomeBox', 'PidLidWorkAddressStreet' => 'WorkStreet', 'PidLidWorkAddressCity' => 'WorkCity', 'PidLidWorkAddressState' => 'WorkState', 'PidLidWorkAddressPostalCode' => 'WorkCode', 'PidLidWorkAddressCountry' => 'WorkCountry', // 'PidLidWorkAddressPostOfficeBox' => 'WorkBox', 'PidTagGender' => 1, 'PidTagSpouseName' => 'Spouse', 'PidTagChildrensNames' => array('child'), 'PidTagHomeTelephoneNumber' => 'HomeNumber', 'PidTagBusinessTelephoneNumber' => 'BusinessNumber', 'PidTagHomeFaxNumber' => 'HomeFax', 'PidTagBusinessFaxNumber' => 'BusinessFax', 'PidTagMobileTelephoneNumber' => 'Mobile', 'PidTagPagerTelephoneNumber' => 'Pager', 'PidTagCarTelephoneNumber' => 'Car', 'PidTagOtherTelephoneNumber' => 'OtherNumber', 'PidLidInstantMessagingAddress' => 'IM', 'PidLidEmail1EmailAddress' => 'email1@domain.tld', 'PidLidEmail2EmailAddress' => 'email1@domain.tld', 'PidLidEmail3EmailAddress' => 'email1@domain.tld', 'PidTagUserX509Certificate' => base64_encode('Certificate'), ); self::$api->put('contacts/' . kolab_api_filter_mapistore::uid_encode(md5('Contacts'), 'a-b-c-d'), array(), json_encode($post)); $code = self::$api->response_code(); $body = self::$api->response_body(); $this->assertEquals(204, $code); $this->assertSame('', $body); self::$api->get('contacts/' . kolab_api_filter_mapistore::uid_encode(md5('Contacts'), 'a-b-c-d')); $body = self::$api->response_body(); $body = json_decode($body, true); foreach ($post as $idx => $value) { $this->assertSame($value, $body[$idx], "Test for update of $idx"); } } /** * Test contact delete */ function test_contact_delete() { // delete existing contact self::$api->delete('contacts/' . kolab_api_filter_mapistore::uid_encode(md5('Contacts'), 'a-b-c-d')); $code = self::$api->response_code(); $body = self::$api->response_body(); $this->assertEquals(204, $code); $this->assertSame('', $body); // and non-existing contact self::$api->get('contacts/' . kolab_api_filter_mapistore::uid_encode(md5('Contacts'), '12345')); $code = self::$api->response_code(); $body = self::$api->response_body(); $this->assertEquals(404, $code); $this->assertSame('', $body); } } diff --git a/tests/Unit/Filter/Mapistore/Attachment.php b/tests/Unit/Filter/Mapistore/Attachment.php index 2fe5cfe..fa13631 100644 --- a/tests/Unit/Filter/Mapistore/Attachment.php +++ b/tests/Unit/Filter/Mapistore/Attachment.php @@ -1,81 +1,90 @@ 'folder', 'object_uid' => 'msg'); $attach = array( 'id' => 'id', 'mimetype' => 'mimetype', 'size' => 100, 'filename' => 'filename.txt', 'disposition' => 'disposition', 'content-id' => 'content-id', 'content-location' => 'content-location', ); $result = $api->output($attach, $context); $this->assertSame(kolab_api_filter_mapistore::uid_encode('folder', 'msg', 'id'), $result['id']); $this->assertSame('attachments', $result['collection']); $this->assertSame('mimetype', $result['PidTagAttachMimeTag']); $this->assertSame(100, $result['PidTagAttachSize']); $this->assertSame('filename.txt', $result['PidTagDisplayName']); $this->assertSame('txt', $result['PidTagAttachExtension']); $this->assertSame('content-id', $result['PidTagAttachContentId']); $this->assertSame('content-location', $result['PidTagAttachContentLocation']); $this->assertSame(1, $result['PidTagAttachMethod']); $this->assertSame(7, $result['PidTagObjectType']); $this->assertSame(null, $result['PidTagAttachDataBinary']); // @TODO: test attachment body handling } /** * Test input method */ function test_input() { $api = new kolab_api_filter_mapistore_attachment; $data = array( 'id' => 'id', 'PidTagAttachMimeTag' => 'mimetype', 'PidTagAttachSize' => 100, 'PidTagDisplayName' => 'filename.txt', 'PidTagAttachContentId' => 'content-id', 'PidTagAttachContentLocation' => 'content-location', ); $result = $api->input($data); // $this->assertSame(kolab_api_filter_mapistore::uid_encode('folder', 'msg', 'id'), $result['id']); $this->assertSame('mimetype', $result['mimetype']); $this->assertSame(100, $result['size']); $this->assertSame('filename.txt', $result['filename']); $this->assertSame('content-id', $result['content-id']); $this->assertSame('content-location', $result['content-location']); } + /** + * Test input method with merge + */ + function test_input2() + { + // @TODO + $this->markTestIncomplete('TODO'); + } + /** * Test map method */ function test_map() { $api = new kolab_api_filter_mapistore_attachment; $map = $api->map(); $this->assertInternalType('array', $map); $this->assertTrue(!empty($map)); } } diff --git a/tests/Unit/Filter/Mapistore/Contact.php b/tests/Unit/Filter/Mapistore/Contact.php index e12dbec..9cee825 100644 --- a/tests/Unit/Filter/Mapistore/Contact.php +++ b/tests/Unit/Filter/Mapistore/Contact.php @@ -1,250 +1,397 @@ output($data, $context); $this->assertSame(kolab_api_filter_mapistore::uid_encode(md5('Contacts'), 'a-b-c-d'), $result['id']); $this->assertSame(md5('Contacts'), $result['parent_id']); // $this->assertSame(kolab_api_filter_mapistore::date_php2mapi('20150421T145607Z'), $result['PidTagLastModificationTime']); $this->assertSame(kolab_api_filter_mapistore::date_php2mapi('20150330', false), $result['PidTagBirthday']); $this->assertSame(kolab_api_filter_mapistore::date_php2mapi('20150301', false), $result['PidTagWeddingAnniversary']); $this->assertSame('displname', $result['PidTagDisplayName']); $this->assertSame('last', $result['PidTagSurname']); $this->assertSame('test', $result['PidTagGivenName']); $this->assertSame('middlename', $result['PidTagMiddleName']); $this->assertSame('prefx', $result['PidTagDisplayNamePrefix']); $this->assertSame('suff', $result['PidTagGeneration']); $this->assertSame('dsfsdfsdfsdf sdfsdfsdf sdfsdfsfd', $result['PidTagBody']); $this->assertSame('free-busy url', $result['PidLidFreeBusyLocation']); $this->assertSame('title', $result['PidTagTitle']); $this->assertSame('Org', $result['PidTagCompanyName']); $this->assertSame('dept', $result['PidTagDepartmentName']); $this->assertSame('profeion', $result['PidTagProfession']); $this->assertSame('manager name', $result['PidTagManagerName']); $this->assertSame('assist', $result['PidTagAssistant']); $this->assertSame('website', $result['PidTagPersonalHomePage']); $this->assertSame('office street', $result['PidTagOtherAddressStreet']); $this->assertSame('office city', $result['PidTagOtherAddressCity']); $this->assertSame('office state', $result['PidTagOtherAddressStateOrProvince']); $this->assertSame('office zip', $result['PidTagOtherAddressPostalCode']); $this->assertSame('office country', $result['PidTagOtherAddressCountry']); // $this->assertSame('office pobox', $result['PidTagOtherAddressPostOfficeBox']); $this->assertSame('home street', $result['PidTagHomeAddressStreet']); $this->assertSame('home city', $result['PidTagHomeAddressCity']); $this->assertSame('home state', $result['PidTagHomeAddressStateOrProvince']); $this->assertSame('home zip', $result['PidTagHomeAddressPostalCode']); $this->assertSame('home country', $result['PidTagHomeAddressCountry']); // $this->assertSame('home pobox', $result['PidTagHomeAddressPostOfficeBox']); $this->assertSame('work street', $result['PidLidWorkAddressStreet']); $this->assertSame('work city', $result['PidLidWorkAddressCity']); $this->assertSame('work state', $result['PidLidWorkAddressState']); $this->assertSame('work zip', $result['PidLidWorkAddressPostalCode']); $this->assertSame('work country', $result['PidLidWorkAddressCountry']); // $this->assertSame('work pobox', $result['PidLidWorkAddressPostOfficeBox']); $this->assertSame('nick', $result['PidTagNickname']); $this->assertSame(2, $result['PidTagGender']); $this->assertSame('spouse', $result['PidTagSpouseName']); $this->assertSame(array('children', 'children2'), $result['PidTagChildrensNames']); $this->assertSame('home phone', $result['PidTagHomeTelephoneNumber']); $this->assertSame('work phone', $result['PidTagBusinessTelephoneNumber']); $this->assertSame('home fax', $result['PidTagHomeFaxNumber']); $this->assertSame('work fax', $result['PidTagBusinessFaxNumber']); $this->assertSame('mobile', $result['PidTagMobileTelephoneNumber']); $this->assertSame('pager', $result['PidTagPagerTelephoneNumber']); $this->assertSame('car phone', $result['PidTagCarTelephoneNumber']); $this->assertSame('other phone', $result['PidTagOtherTelephoneNumber']); $this->assertSame('im gg', $result['PidLidInstantMessagingAddress']); $this->assertSame('test@mail.ru', $result['PidLidEmail1EmailAddress']); $this->assertSame('work@email.pl', $result['PidLidEmail2EmailAddress']); $this->assertSame('other@email.pl', $result['PidLidEmail3EmailAddress']); $this->assertRegExp('/^cy9.*/', $result['PidTagUserX509Certificate']); // $this->assertRegExp('|^data:application/pgp-keys;base64,|', $result['key'][0]); // $this->assertRegExp('|^data:image/jpeg;base64,|', $result['photo']); // $this->assertSame('individual', $result['kind']); } /** * Test input method */ function test_input() { $api = new kolab_api_filter_mapistore_contact; $data = array( 'id' => kolab_api_filter_mapistore::uid_encode(md5('Contacts'), 'a-b-c-d'), 'parent_id' => md5('Contacts'), // 'PidTagLastModificationTime' => kolab_api_filter_mapistore::date_php2mapi('20150421T145607Z'), 'PidTagBirthday' => kolab_api_filter_mapistore::date_php2mapi('20150330', true), 'PidTagWeddingAnniversary' => kolab_api_filter_mapistore::date_php2mapi('20150301', true), 'PidTagDisplayName' => 'displname', 'PidTagSurname' => 'last', 'PidTagGivenName' => 'test', 'PidTagMiddleName' => 'middlename', 'PidTagDisplayNamePrefix' => 'prefx', 'PidTagGeneration' => 'suff', 'PidTagBody' => 'dsfsdfsdfsdf sdfsdfsdf sdfsdfsfd', 'PidLidFreeBusyLocation' => 'free-busy url', 'PidTagTitle' => 'title', 'PidTagCompanyName' => 'Org', 'PidTagDepartmentName' => 'dept', 'PidTagProfession' => 'profeion', 'PidTagManagerName' => 'manager name', 'PidTagAssistant' => 'assist', 'PidTagPersonalHomePage' => 'website', 'PidTagOtherAddressStreet' => 'office street', 'PidTagOtherAddressCity' => 'office city', 'PidTagOtherAddressStateOrProvince' => 'office state', 'PidTagOtherAddressPostalCode' => 'office zip', 'PidTagOtherAddressCountry' => 'office country', // 'PidTagOtherAddressPostOfficeBox' => 'office pobox', 'PidTagHomeAddressStreet' => 'home street', 'PidTagHomeAddressCity' => 'home city', 'PidTagHomeAddressStateOrProvince' => 'home state', 'PidTagHomeAddressPostalCode' => 'home zip', 'PidTagHomeAddressCountry' => 'home country', // 'PidTagHomeAddressPostOfficeBox' => 'home pobox', 'PidLidWorkAddressStreet' => 'work street', 'PidLidWorkAddressCity' => 'work city', 'PidLidWorkAddressState' => 'work state', 'PidLidWorkAddressPostalCode' => 'work zip', 'PidLidWorkAddressCountry' => 'work country', // 'PidLidWorkAddressPostOfficeBox' => 'work pobox', 'PidTagNickname' => 'nick', 'PidTagGender' => 2, 'PidTagSpouseName' => 'spouse', 'PidTagChildrensNames' => array('children', 'children2'), 'PidTagHomeTelephoneNumber' => 'home phone', 'PidTagBusinessTelephoneNumber' => 'work phone', 'PidTagHomeFaxNumber' => 'home fax', 'PidTagBusinessFaxNumber' => 'work fax', 'PidTagMobileTelephoneNumber' => 'mobile', 'PidTagPagerTelephoneNumber' => 'pager', 'PidTagCarTelephoneNumber' => 'car phone', 'PidTagOtherTelephoneNumber' => 'other phone', 'PidLidInstantMessagingAddress' => 'im gg', 'PidLidEmail1EmailAddress' => 'test@mail.ru', 'PidLidEmail2EmailAddress' => 'work@email.pl', 'PidLidEmail3EmailAddress' => 'other@email.pl', 'PidTagUserX509Certificate' => '1234567890', ); $result = $api->input($data); // $this->assertSame('a-b-c-d', $result['uid']); // $this->assertSame('20150420T141533Z', $result['rev']); // $this->assertSame('individual', $result['kind']); $this->assertSame('displname', $result['fn']); $this->assertSame('last', $result['n']['surname']); $this->assertSame('test', $result['n']['given']); $this->assertSame('middlename', $result['n']['additional']); $this->assertSame('prefx', $result['n']['prefix']); $this->assertSame('suff', $result['n']['suffix']); $this->assertSame('dsfsdfsdfsdf sdfsdfsdf sdfsdfsfd', $result['note']); $this->assertSame('free-busy url', $result['fburl']); $this->assertSame('title', $result['title']); $this->assertSame('Org', $result['group']['org'][0]); $this->assertSame('dept', $result['group']['org'][1]); $this->assertSame('profeion', $result['group']['role']); $this->assertSame('x-manager', $result['group']['related'][0]['parameters']['type']); $this->assertSame('manager name', $result['group']['related'][0]['text']); $this->assertSame('x-assistant', $result['group']['related'][1]['parameters']['type']); $this->assertSame('assist', $result['group']['related'][1]['text']); // $this->assertSame('', $result['group']['adr']['pobox']); $this->assertSame('office street', $result['group']['adr']['street']); $this->assertSame('office city', $result['group']['adr']['locality']); $this->assertSame('office state', $result['group']['adr']['region']); $this->assertSame('office zip', $result['group']['adr']['code']); $this->assertSame('office country', $result['group']['adr']['country']); $this->assertSame(array('website'), $result['url']); $this->assertSame('home', $result['adr'][0]['parameters']['type']); $this->assertSame('home street', $result['adr'][0]['street']); $this->assertSame('home city', $result['adr'][0]['locality']); $this->assertSame('home state', $result['adr'][0]['region']); $this->assertSame('home zip', $result['adr'][0]['code']); $this->assertSame('home country', $result['adr'][0]['country']); $this->assertSame('work', $result['adr'][1]['parameters']['type']); $this->assertSame('work street', $result['adr'][1]['street']); $this->assertSame('work city', $result['adr'][1]['locality']); $this->assertSame('work state', $result['adr'][1]['region']); $this->assertSame('work zip', $result['adr'][1]['code']); $this->assertSame('work country', $result['adr'][1]['country']); $this->assertSame('nick', $result['nickname']); $this->assertSame('spouse', $result['related'][0]['parameters']['type']); $this->assertSame('spouse', $result['related'][0]['text']); $this->assertSame('child', $result['related'][1]['parameters']['type']); $this->assertSame('children', $result['related'][1]['text']); $this->assertSame('child', $result['related'][2]['parameters']['type']); $this->assertSame('children2', $result['related'][2]['text']); $this->assertSame('2015-03-30', $result['bday']); // ? $this->assertSame('2015-03-01', $result['anniversary']); // ? $this->assertSame('M', $result['gender']); $this->assertSame(array('im gg'), $result['impp']); $this->assertSame('home', $result['email'][0]['parameters']['type']); $this->assertSame('test@mail.ru', $result['email'][0]['text']); $this->assertSame('work', $result['email'][1]['parameters']['type']); $this->assertSame('work@email.pl', $result['email'][1]['text']); $this->assertSame('other', $result['email'][2]['parameters']['type']); $this->assertSame('other@email.pl', $result['email'][2]['text']); $this->assertRegExp('|^data:application/pkcs7-mime;base64,|', $result['key'][0]); // $this->assertRegExp('|^data:application/pgp-keys;base64,|', $result['key'][1]); // $this->assertRegExp('|^data:image/jpeg;base64,|', $result['photo']); $phones = array( 'home' => 'home phone', 'work' => 'work phone', 'faxhome' => 'home fax', 'faxwork' => 'work fax', 'cell' => 'mobile', 'pager' => 'pager', 'x-car' => 'car phone', 'textphone' => 'other phone', ); foreach ($result['tel'] as $tel) { $type = implode('', (array)$tel['parameters']['type']); $text = $tel['text']; if (!empty($phones[$type]) && $phones[$type] == $text) { unset($phones[$type]); } } $this->assertCount(8, $result['tel']); $this->assertCount(0, $phones); - // @TODO: test mappings on object update + self::$original = $result; + } + + /** + * Test input method with merge + */ + function test_input2() + { + $api = new kolab_api_filter_mapistore_contact; + $data = array( + 'id' => kolab_api_filter_mapistore::uid_encode(md5('Contacts'), 'a-b-c-d'), + 'parent_id' => md5('Contacts'), +// 'PidTagLastModificationTime' => kolab_api_filter_mapistore::date_php2mapi('20150421T145607Z'), + 'PidTagBirthday' => kolab_api_filter_mapistore::date_php2mapi('20150430', true), + 'PidTagWeddingAnniversary' => kolab_api_filter_mapistore::date_php2mapi('20150401', true), + 'PidTagDisplayName' => 'displname1', + 'PidTagSurname' => 'last1', + 'PidTagGivenName' => 'test1', + 'PidTagMiddleName' => 'middlename1', + 'PidTagDisplayNamePrefix' => 'prefx1', + 'PidTagGeneration' => 'suff1', + 'PidTagBody' => 'body1', + 'PidLidFreeBusyLocation' => 'free-busy url1', + 'PidTagTitle' => 'title1', + 'PidTagCompanyName' => 'Org1', + 'PidTagDepartmentName' => 'dept1', + 'PidTagProfession' => 'profeion1', + 'PidTagManagerName' => 'manager name1', + 'PidTagAssistant' => 'assist1', + 'PidTagPersonalHomePage' => 'website1', + + 'PidTagOtherAddressStreet' => 'office street1', + 'PidTagOtherAddressCity' => 'office city1', + 'PidTagOtherAddressStateOrProvince' => 'office state1', + 'PidTagOtherAddressPostalCode' => 'office zip1', + 'PidTagOtherAddressCountry' => 'office country1', + 'PidTagHomeAddressStreet' => 'home street1', + 'PidTagHomeAddressCity' => 'home city1', + 'PidTagHomeAddressStateOrProvince' => 'home state1', + 'PidTagHomeAddressPostalCode' => 'home zip1', + 'PidTagHomeAddressCountry' => 'home country1', + 'PidLidWorkAddressStreet' => 'work street1', + 'PidLidWorkAddressCity' => 'work city1', + 'PidLidWorkAddressState' => 'work state1', + 'PidLidWorkAddressPostalCode' => 'work zip1', + 'PidLidWorkAddressCountry' => 'work country1', + + 'PidTagNickname' => 'nick1', + 'PidTagGender' => 1, + 'PidTagSpouseName' => 'spouse1', + 'PidTagChildrensNames' => array('children10', 'children20'), + + 'PidTagHomeTelephoneNumber' => 'home phone1', + 'PidTagBusinessTelephoneNumber' => null, + 'PidTagHomeFaxNumber' => 'home fax1', + 'PidTagBusinessFaxNumber' => 'work fax1', + 'PidTagMobileTelephoneNumber' => 'mobile1', + 'PidTagPagerTelephoneNumber' => 'pager1', + 'PidTagOtherTelephoneNumber' => 'other phone1', + 'PidLidInstantMessagingAddress' => 'im gg1', + + 'PidLidEmail1EmailAddress' => 'test@mail.ru', + 'PidLidEmail2EmailAddress' => 'work@email.pl', + 'PidTagUserX509Certificate' => '12345678901', + ); + + $result = $api->input($data, self::$original); + +// $this->assertSame('a-b-c-d', $result['uid']); +// $this->assertSame('20150420T141533Z', $result['rev']); +// $this->assertSame('individual', $result['kind']); + $this->assertSame('displname1', $result['fn']); + $this->assertSame('last1', $result['n']['surname']); + $this->assertSame('test1', $result['n']['given']); + $this->assertSame('middlename1', $result['n']['additional']); + $this->assertSame('prefx1', $result['n']['prefix']); + $this->assertSame('suff1', $result['n']['suffix']); + $this->assertSame('body1', $result['note']); + $this->assertSame('free-busy url1', $result['fburl']); + $this->assertSame('title1', $result['title']); + $this->assertSame('Org1', $result['group']['org'][0]); + $this->assertSame('dept1', $result['group']['org'][1]); + $this->assertSame('profeion1', $result['group']['role']); + $this->assertSame('x-manager', $result['group']['related'][0]['parameters']['type']); + $this->assertSame('manager name1', $result['group']['related'][0]['text']); + $this->assertSame('x-assistant', $result['group']['related'][1]['parameters']['type']); + $this->assertSame('assist1', $result['group']['related'][1]['text']); + $this->assertSame('office street1', $result['group']['adr']['street']); + $this->assertSame('office city1', $result['group']['adr']['locality']); + $this->assertSame('office state1', $result['group']['adr']['region']); + $this->assertSame('office zip1', $result['group']['adr']['code']); + $this->assertSame('office country1', $result['group']['adr']['country']); + $this->assertSame(array('website1'), $result['url']); + $this->assertSame('home', $result['adr'][0]['parameters']['type']); + $this->assertSame('home street1', $result['adr'][0]['street']); + $this->assertSame('home city1', $result['adr'][0]['locality']); + $this->assertSame('home state1', $result['adr'][0]['region']); + $this->assertSame('home zip1', $result['adr'][0]['code']); + $this->assertSame('home country1', $result['adr'][0]['country']); + $this->assertSame('work', $result['adr'][1]['parameters']['type']); + $this->assertSame('work street1', $result['adr'][1]['street']); + $this->assertSame('work city1', $result['adr'][1]['locality']); + $this->assertSame('work state1', $result['adr'][1]['region']); + $this->assertSame('work zip1', $result['adr'][1]['code']); + $this->assertSame('work country1', $result['adr'][1]['country']); + $this->assertSame('nick1', $result['nickname']); + $this->assertSame('spouse', $result['related'][0]['parameters']['type']); + $this->assertSame('spouse1', $result['related'][0]['text']); + $this->assertSame('child', $result['related'][1]['parameters']['type']); + $this->assertSame('children10', $result['related'][1]['text']); + $this->assertSame('child', $result['related'][2]['parameters']['type']); + $this->assertSame('children20', $result['related'][2]['text']); + $this->assertSame('2015-04-30', $result['bday']); // ? + $this->assertSame('2015-04-01', $result['anniversary']); // ? + $this->assertSame('F', $result['gender']); + $this->assertSame(array('im gg1'), $result['impp']); + $this->assertSame('home', $result['email'][0]['parameters']['type']); + $this->assertSame('test@mail.ru', $result['email'][0]['text']); + $this->assertSame('work', $result['email'][1]['parameters']['type']); + $this->assertSame('work@email.pl', $result['email'][1]['text']); + $this->assertSame(null, $result['email'][2]); + $this->assertRegExp('|^data:application/pkcs7-mime;base64,|', $result['key'][0]); +// $this->assertRegExp('|^data:application/pgp-keys;base64,|', $result['key'][1]); +// $this->assertRegExp('|^data:image/jpeg;base64,|', $result['photo']); + + $phones = array( + 'home' => 'home phone1', + 'faxhome' => 'home fax1', + 'faxwork' => 'work fax1', + 'cell' => 'mobile1', + 'pager' => 'pager1', + 'x-car' => 'car phone', + 'textphone' => 'other phone1', + ); + foreach ($result['tel'] as $tel) { + $type = implode('', (array)$tel['parameters']['type']); + $text = $tel['text']; + + if (!empty($phones[$type]) && $phones[$type] == $text) { + unset($phones[$type]); + } + } + + $this->assertCount(7, $result['tel']); + $this->assertCount(0, $phones); + + // @TODO: updating some deep items (e.g. adr); } /** * Test map method */ function test_map() { $api = new kolab_api_filter_mapistore_contact; $map = $api->map(); $this->assertInternalType('array', $map); $this->assertTrue(!empty($map)); } } diff --git a/tests/Unit/Filter/Mapistore/Event.php b/tests/Unit/Filter/Mapistore/Event.php index 843e65e..ef3f922 100644 --- a/tests/Unit/Filter/Mapistore/Event.php +++ b/tests/Unit/Filter/Mapistore/Event.php @@ -1,131 +1,176 @@ output($data, $context); $this->assertSame(kolab_api_filter_mapistore::uid_encode(md5('Calendar'), '100-100-100-100'), $result['id']); $this->assertSame('calendars', $result['collection']); $this->assertSame('IPM.Appointment', $result['PidTagMessageClass']); $this->assertSame(kolab_api_filter_mapistore::date_php2mapi('2015-05-14T13:03:33Z'), $result['PidTagCreationTime']); $this->assertSame(kolab_api_filter_mapistore::date_php2mapi('2015-05-14T13:50:18Z'), $result['PidTagLastModificationTime']); $this->assertSame(2, $result['PidLidAppointmentSequence']); $this->assertSame(3, $result['PidTagSensitivity']); $this->assertSame('Work', $result['PidNameKeywords'][0]); /* $this->assertSame('/kolab.org/Europe/Berlin', $result['dtstart']['parameters']['tzid']); $this->assertSame('2015-05-15T10:00:00', $result['dtstart']['date-time']); $this->assertSame('/kolab.org/Europe/Berlin', $result['dtend']['parameters']['tzid']); $this->assertSame('2015-05-15T10:30:00', $result['dtend']['date-time']); $this->assertSame('https://some.url', $result['url']); */ $this->assertSame('Summary', $result['PidTagSubject']); $this->assertSame('Description', $result['PidTagBody']); $this->assertSame(2, $result['PidTagImportance']); $this->assertSame('Location', $result['PidLidLocation']); $this->assertSame('German, Mark', $result['recipients'][0]['PidTagDisplayName']); $this->assertSame('mark.german@example.org', $result['recipients'][0]['PidTagEmailAddress']); $this->assertSame(1, $result['recipients'][0]['PidTagRecipientType']); $this->assertSame('Manager, Jane', $result['recipients'][1]['PidTagDisplayName']); $this->assertSame(2, $result['recipients'][1]['PidTagRecipientType']); $this->assertSame('jane.manager@example.org', $result['recipients'][1]['PidTagEmailAddress']); $this->assertSame(15, $result['PidLidReminderDelta']); $this->assertSame(true, $result['PidLidReminderSet']); $data = kolab_api_tests::get_data('101-101-101-101', md5('Calendar'), 'event', 'json', $context); $result = $api->output($data, $context); $this->assertSame(kolab_api_filter_mapistore::uid_encode(md5('Calendar'), '101-101-101-101'), $result['id']); $this->assertSame('calendars', $result['collection']); $this->assertSame('IPM.Appointment', $result['PidTagMessageClass']); $this->assertSame(0, $result['PidTagSensitivity']); $this->assertSame(kolab_api_filter_mapistore::date_php2mapi('2015-05-15T00:00:00Z'), $result['PidLidAppointmentStartWhole']); $this->assertSame(kolab_api_filter_mapistore::date_php2mapi('2015-05-15T00:00:00Z'), $result['PidLidAppointmentEndWhole']); $this->assertSame(1, $result['PidLidAppointmentSubType']); $this->assertSame(1, $result['PidLidAppointmentRecur']['Period']); $this->assertSame(0x200B, $result['PidLidAppointmentRecur']['RecurFrequency']); $this->assertSame(1, $result['PidLidAppointmentRecur']['PatternType']); $this->assertSame(2, $result['PidLidAppointmentRecur']['PatternTypeSpecific.Week.Sa-Su']); } /** * Test input method */ function test_input() { $api = new kolab_api_filter_mapistore_event; $data = array( 'PidTagCreationTime' => kolab_api_filter_mapistore::date_php2mapi('2015-05-14T13:03:33Z'), 'PidTagLastModificationTime' => kolab_api_filter_mapistore::date_php2mapi('2015-05-14T13:50:18Z'), 'PidLidAppointmentSequence' => 10, 'PidTagSensitivity' => 3, 'PidNameKeywords' => array('work'), 'PidTagSubject' => 'subject', 'PidTagBody' => 'body', 'PidTagImportance' => 2, 'PidLidLocation' => 'location', 'PidLidAppointmentStartWhole' => kolab_api_filter_mapistore::date_php2mapi('2015-05-14T13:03:33Z'), 'PidLidAppointmentEndWhole' => kolab_api_filter_mapistore::date_php2mapi('2015-05-14T16:00:00Z'), 'PidLidReminderDelta' => 15, 'PidLidReminderSet' => true, ); $result = $api->input($data); $this->assertSame('subject', $result['summary']); $this->assertSame('body', $result['description']); $this->assertSame(10, $result['sequence']); $this->assertSame('confidential', $result['class']); $this->assertSame(array('work'), $result['categories']); $this->assertSame('location', $result['location']); $this->assertSame(1, $result['priority']); $this->assertSame('2015-05-14T13:03:33Z', $result['created']); $this->assertSame('2015-05-14T13:50:18Z', $result['dtstamp']); $this->assertSame('2015-05-14T13:03:33Z', $result['dtstart']); $this->assertSame('2015-05-14T16:00:00Z', $result['dtend']); $this->assertSame('DISPLAY', $result['valarm'][0]['properties']['action']); $this->assertSame('Reminder', $result['valarm'][0]['properties']['description']); $this->assertSame('-PT15M', $result['valarm'][0]['properties']['trigger']['duration']); + self::$original = $result; + // @TODO: recurrence, alarms, attendees $data = array( // all-day event 'PidLidAppointmentStartWhole' => kolab_api_filter_mapistore::date_php2mapi('2015-05-14T00:00:00Z'), 'PidLidAppointmentEndWhole' => kolab_api_filter_mapistore::date_php2mapi('2015-05-14T00:00:00Z'), 'PidLidAppointmentSubType' => 1, 'PidLidReminderSet' => false, ); $result = $api->input($data); $this->assertSame('2015-05-14', $result['dtstart']); $this->assertSame('2015-05-14', $result['dtend']); $this->assertSame(array(), $result['valarm']); } + /** + * Test input method with merge + */ + function test_input2() + { + $api = new kolab_api_filter_mapistore_event; + $data = array( +// 'PidTagCreationTime' => kolab_api_filter_mapistore::date_php2mapi('2015-05-14T13:03:33Z'), +// 'PidTagLastModificationTime' => kolab_api_filter_mapistore::date_php2mapi('2015-05-14T13:50:18Z'), + 'PidLidAppointmentSequence' => 20, + 'PidTagSensitivity' => 2, + 'PidNameKeywords' => array('work1'), + 'PidTagSubject' => 'subject1', + 'PidTagBody' => 'body1', + 'PidTagImportance' => 1, + 'PidLidLocation' => 'location1', + 'PidLidAppointmentStartWhole' => kolab_api_filter_mapistore::date_php2mapi('2015-05-15T13:03:33Z'), + 'PidLidAppointmentEndWhole' => kolab_api_filter_mapistore::date_php2mapi('2015-05-15T16:00:00Z'), + 'PidLidReminderDelta' => 25, + 'PidLidReminderSet' => true, + ); + + $result = $api->input($data, self::$original); + + $this->assertSame('subject1', $result['summary']); + $this->assertSame('body1', $result['description']); + $this->assertSame(20, $result['sequence']); + $this->assertSame('private', $result['class']); + $this->assertSame(array('work1'), $result['categories']); + $this->assertSame('location1', $result['location']); + $this->assertSame(5, $result['priority']); +// $this->assertSame('2015-05-14T13:03:33Z', $result['created']); +// $this->assertSame('2015-05-14T13:50:18Z', $result['dtstamp']); + $this->assertSame('2015-05-15T13:03:33Z', $result['dtstart']); + $this->assertSame('2015-05-15T16:00:00Z', $result['dtend']); + $this->assertSame('DISPLAY', $result['valarm'][0]['properties']['action']); + $this->assertSame('Reminder', $result['valarm'][0]['properties']['description']); + $this->assertSame('-PT25M', $result['valarm'][0]['properties']['trigger']['duration']); + + // @TODO: recurrence, alarms, attendees + } + /** * Test map method */ function test_map() { $api = new kolab_api_filter_mapistore_event; $map = $api->map(); $this->assertInternalType('array', $map); $this->assertTrue(!empty($map)); } } diff --git a/tests/Unit/Filter/Mapistore/Folder.php b/tests/Unit/Filter/Mapistore/Folder.php index db482b2..837c610 100644 --- a/tests/Unit/Filter/Mapistore/Folder.php +++ b/tests/Unit/Filter/Mapistore/Folder.php @@ -1,79 +1,103 @@ 'test-uid', 'parent' => 'parent', 'name' => 'folder name', ); $result = $api->output($data, $context); $this->assertSame('test-uid', $result['id']); $this->assertSame('parent', $result['parent_id']); $this->assertSame('folder name', $result['PidTagDisplayName']); $this->assertSame(1, $result['PidTagFolderType']); $types = array( '' => 'IPF.Note', 'mail.inbox' => 'IPF.Note', 'task.default' => 'IPF.Task', 'note.default' => 'IPF.StickyNote', 'event.default' => 'IPF.Appointment', 'journal.default' => 'IPF.Journal', 'contact.default' => 'IPF.Contact', ); foreach ($types as $type => $exp) { $data = array('uid' => 'test', 'type' => $type); $result = $api->output($data, $context); $this->assertSame($exp, $result['PidTagContainerClass']); } } /** * Test input method */ function test_input() { $api = new kolab_api_filter_mapistore_folder; $data = array( 'id' => 'test-uid', 'parent_id' => 'parent', 'PidTagDisplayName' => 'folder name', 'PidTagContainerClass' => 'IPF.Contact', ); $result = $api->input($data); $this->assertSame('test-uid', $result['uid']); $this->assertSame('parent', $result['parent']); $this->assertSame('folder name', $result['name']); $this->assertSame('contact', $result['type']); + + self::$original = $result; + } + + /** + * Test input method with merge + */ + function test_input2() + { + $api = new kolab_api_filter_mapistore_folder; + $data = array( +// 'id' => 'test-uid', +// 'parent_id' => 'parent', + 'PidTagDisplayName' => 'folder name1', + 'PidTagContainerClass' => 'IPF.Appointment', + ); + + $result = $api->input($data, self::$original); + +// $this->assertSame('test-uid', $result['uid']); +// $this->assertSame('parent', $result['parent']); + $this->assertSame('folder name1', $result['name']); + $this->assertSame('event', $result['type']); } /** * Test map method */ function test_map() { $api = new kolab_api_filter_mapistore_folder; $map = $api->map(); $this->assertInternalType('array', $map); $this->assertTrue(!empty($map)); } } diff --git a/tests/Unit/Filter/Mapistore/Info.php b/tests/Unit/Filter/Mapistore/Info.php index 97092a6..884513e 100644 --- a/tests/Unit/Filter/Mapistore/Info.php +++ b/tests/Unit/Filter/Mapistore/Info.php @@ -1,55 +1,63 @@ 'test1', 'version' => 'version1', ); $result = $api->output($data, $context); $this->assertSame('test1', $result['name']); $this->assertSame('version1', $result['version']); } /** * Test input method */ function test_input() { $api = new kolab_api_filter_mapistore_info; $data = array( 'name' => 'test1', 'version' => 'version1', ); $result = $api->input($data); $this->assertSame(null, $result); } + /** + * Test input method with merge + */ + function test_input2() + { + // there's nothing to test here + } + /** * Test map method */ function test_map() { $api = new kolab_api_filter_mapistore_info; $map = $api->map(); $this->assertInternalType('array', $map); $this->assertTrue(!empty($map)); } } diff --git a/tests/Unit/Filter/Mapistore/Mail.php b/tests/Unit/Filter/Mapistore/Mail.php index 6cded8d..46f438e 100644 --- a/tests/Unit/Filter/Mapistore/Mail.php +++ b/tests/Unit/Filter/Mapistore/Mail.php @@ -1,64 +1,73 @@ output($data, $context); $this->assertSame(kolab_api_filter_mapistore::uid_encode(md5('INBOX'), '1'), $result['id']); $this->assertSame(md5('INBOX'), $result['parent_id']); $this->assertSame('mails', $result['collection']); $this->assertSame('IPM.Note', $result['PidTagMessageClass']); $this->assertSame('"test" wurde aktualisiert', $result['PidTagSubject']); $this->assertSame(624, $result['PidTagMessageSize']); $this->assertCount(1, $result['recipients']); $this->assertSame('mark.german@example.org', $result['recipients'][0]['PidTagSmtpAddress']); $this->assertSame(1, $result['recipients'][0]['PidTagRecipientType']); $data = kolab_api_tests::get_data('2', md5('INBOX'), 'mail', 'json', $context); $result = $api->output($data, $context); $this->assertSame(kolab_api_filter_mapistore::uid_encode(md5('INBOX'), '2'), $result['id']); $this->assertSame(md5('INBOX'), $result['parent_id']); $this->assertSame('IPM.Note', $result['PidTagMessageClass']); $this->assertSame('Re: dsda', $result['PidTagSubject']); $this->assertSame(807, $result['PidTagMessageSize']); $this->assertCount(1, $result['recipients']); $this->assertSame('mark.german@example.org', $result['recipients'][0]['PidTagSmtpAddress']); $this->assertSame('German, Mark', $result['recipients'][0]['PidTagDisplayName']); $this->assertSame(1, $result['recipients'][0]['PidTagRecipientType']); } /** * Test input method */ function test_input() { // @TODO $this->markTestIncomplete('TODO'); } + /** + * Test input method with merge + */ + function test_input2() + { + // @TODO + $this->markTestIncomplete('TODO'); + } + /** * Test map method */ function test_map() { $api = new kolab_api_filter_mapistore_mail; $map = $api->map(); $this->assertInternalType('array', $map); $this->assertTrue(!empty($map)); } } diff --git a/tests/Unit/Filter/Mapistore/Note.php b/tests/Unit/Filter/Mapistore/Note.php index 9abb4d4..5fb8fa5 100644 --- a/tests/Unit/Filter/Mapistore/Note.php +++ b/tests/Unit/Filter/Mapistore/Note.php @@ -1,65 +1,103 @@ output($data, $context); $this->assertSame(kolab_api_filter_mapistore::uid_encode(md5('Notes'), '1-1-1-1'), $result['id']); $this->assertSame(md5('Notes'), $result['parent_id']); $this->assertSame(kolab_api_filter_mapistore::date_php2mapi('2015-01-20T11:44:59Z'), $result['PidTagCreationTime']); $this->assertSame(kolab_api_filter_mapistore::date_php2mapi('2015-01-22T11:30:17Z'), $result['PidTagLastModificationTime']); // $this->assertSame('PUBLIC', $result['classification']); $this->assertSame('test', $result['PidTagSubject']); $this->assertRegexp('//', $result['PidTagBody']); } /** * Test input method */ function test_input() { $api = new kolab_api_filter_mapistore_note; $data = array( 'id' => kolab_api_filter_mapistore::uid_encode(md5('Notes'), '1-1-1-1'), 'parent_id' => md5('Notes'), 'PidTagCreationTime' => kolab_api_filter_mapistore::date_php2mapi('2015-01-20T11:44:59Z'), 'PidTagLastModificationTime' => kolab_api_filter_mapistore::date_php2mapi('2015-01-22T11:30:17Z'), 'PidTagSubject' => 'subject', 'PidTagBody' => 'body', ); $result = $api->input($data); // $this->assertSame(kolab_api_filter_mapistore::uid_encode(md5('Notes'), '1-1-1-1'), $result['uid']); // $this->assertSame(md5('Notes'), $result['parent']); $this->assertSame('2015-01-20T11:44:59Z', $result['creation-date']); $this->assertSame('2015-01-22T11:30:17Z', $result['last-modification-date']); $this->assertSame('subject', $result['summary']); $this->assertSame('body', $result['description']); + + self::$original = $result; + } + + /** + * Test input method with merge + */ + function test_input2() + { + $api = new kolab_api_filter_mapistore_note; + $data = array( + 'id' => kolab_api_filter_mapistore::uid_encode(md5('Notes'), '1-1-1-1'), + 'parent_id' => md5('Notes'), +// 'PidTagCreationTime' => kolab_api_filter_mapistore::date_php2mapi('2015-01-20T11:44:59Z'), +// 'PidTagLastModificationTime' => kolab_api_filter_mapistore::date_php2mapi('2015-01-22T11:30:17Z'), + 'PidTagSubject' => 'subject1', + 'PidTagBody' => 'body1', + ); + + $result = $api->input($data); + +// $this->assertSame(kolab_api_filter_mapistore::uid_encode(md5('Notes'), '1-1-1-1'), $result['uid']); +// $this->assertSame(md5('Notes'), $result['parent']); +// $this->assertSame('2015-01-20T11:44:59Z', $result['creation-date']); +// $this->assertSame('2015-01-22T11:30:17Z', $result['last-modification-date']); + $this->assertSame('subject1', $result['summary']); + $this->assertSame('body1', $result['description']); + + // test unsetting values + $api = new kolab_api_filter_mapistore_note; + $data = array( + 'PidTagSubject' => '', + ); + + $result = $api->input($data, self::$original); + + $this->assertSame('', $result['summary']); } /** * Test map method */ function test_map() { $api = new kolab_api_filter_mapistore_note; $map = $api->map(); $this->assertInternalType('array', $map); $this->assertTrue(!empty($map)); } } diff --git a/tests/Unit/Filter/Mapistore/Task.php b/tests/Unit/Filter/Mapistore/Task.php index df8896e..afb35ed 100644 --- a/tests/Unit/Filter/Mapistore/Task.php +++ b/tests/Unit/Filter/Mapistore/Task.php @@ -1,104 +1,138 @@ output($data, $context); $this->assertSame(kolab_api_filter_mapistore::uid_encode(md5('Tasks'), '10-10-10-10'), $result['id']); $this->assertSame(md5('Tasks'), $result['parent_id']); $this->assertSame('IPM.Task', $result['PidTagMessageClass']); $this->assertSame('tasks', $result['collection']); $this->assertSame('task title', $result['PidTagSubject']); $this->assertSame("task description\nsecond line", $result['PidTagBody']); $this->assertSame(0.56, $result['PidLidPercentComplete']); $this->assertSame(kolab_api_filter_mapistore::date_php2mapi('2015-04-20T14:22:18Z', true), $result['PidTagLastModificationTime']); $this->assertSame(kolab_api_filter_mapistore::date_php2mapi('2015-04-20T14:22:18Z', true), $result['PidTagCreationTime']); /* $this->assertSame('German, Mark', $result['organizer']['parameters']['cn']); $this->assertSame('mailto:%3Cmark.german%40example.org%3E', $result['organizer']['cal-address']); */ $data = kolab_api_tests::get_data('20-20-20-20', md5('Tasks'), 'task', 'json', $context); $result = $api->output($data, $context); $this->assertSame(kolab_api_filter_mapistore::uid_encode(md5('Tasks'), '20-20-20-20'), $result['id']); $this->assertSame(md5('Tasks'), $result['parent_id']); $this->assertSame('IPM.Task', $result['PidTagMessageClass']); $this->assertSame('tasks', $result['collection']); $this->assertSame('task', $result['PidTagSubject']); $this->assertSame(kolab_api_filter_mapistore::date_php2mapi('2015-04-20', true), $result['PidLidTaskStartDate']); $this->assertSame(kolab_api_filter_mapistore::date_php2mapi('2015-04-27', true), $result['PidLidTaskDueDate']); /* $this->assertSame('DAILY', $result['rrule']['recur']); $this->assertSame('NEEDS-ACTION', $result['status']); $this->assertSame('Manager, Jane', $result['attendee']['parameters']['cn']); $this->assertSame('NEEDS-ACTION', $result['attendee']['parameters']['partstat']); $this->assertSame('REQ-PARTICIPANT', $result['attendee']['parameters']['role']); $this->assertSame('true', $result['attendee']['parameters']['rsvp']); $this->assertSame('mailto:%3Cjane.manager%40example.org%3E', $result['attendee']['cal-address']); */ } /** * Test input method */ function test_input() { $api = new kolab_api_filter_mapistore_task; $data = array( 'id' => kolab_api_filter_mapistore::uid_encode(md5('Tasks'), '10-10-10-10'), 'parent_id' => md5('Tasks'), 'PidTagCreationTime' => kolab_api_filter_mapistore::date_php2mapi('2015-01-20T11:44:59Z'), 'PidTagLastModificationTime' => kolab_api_filter_mapistore::date_php2mapi('2015-01-22T11:30:17Z'), 'PidTagMessageClass' => 'IPM.Task', 'PidTagSubject' => 'subject', 'PidLidPercentComplete' => 0.56, 'PidTagBody' => 'body', 'PidLidTaskStartDate' => kolab_api_filter_mapistore::date_php2mapi('2015-04-20', true), 'PidLidTaskDueDate' => kolab_api_filter_mapistore::date_php2mapi('2015-04-27', true), ); $result = $api->input($data); + self::$original = $result; + $this->assertSame('subject', $result['summary']); $this->assertSame('body', $result['description']); $this->assertSame(56, $result['percent-complete']); $this->assertSame('2015-01-20T11:44:59Z', $result['created']); $this->assertSame('2015-01-22T11:30:17Z', $result['dtstamp']); $this->assertSame('2015-04-20', $result['dtstart']); $this->assertSame('2015-04-27', $result['due']); $data = array( 'PidLidTaskComplete' => true, ); $result = $api->input($data); $this->assertSame('COMPLETED', $result['status']); + } + + /** + * Test input method with merge + */ + function test_input2() + { + $api = new kolab_api_filter_mapistore_task; + $data = array( +// 'id' => kolab_api_filter_mapistore::uid_encode(md5('Tasks'), '10-10-10-10'), +// 'parent_id' => md5('Tasks'), + 'PidTagCreationTime' => kolab_api_filter_mapistore::date_php2mapi('2015-01-20T12:44:59Z'), + 'PidTagLastModificationTime' => kolab_api_filter_mapistore::date_php2mapi('2015-01-22T12:30:17Z'), +// 'PidTagMessageClass' => 'IPM.Task', + 'PidTagSubject' => 'subject1', + 'PidLidPercentComplete' => 0.66, + 'PidTagBody' => 'body1', + 'PidLidTaskStartDate' => kolab_api_filter_mapistore::date_php2mapi('2015-04-21', true), + 'PidLidTaskDueDate' => kolab_api_filter_mapistore::date_php2mapi('2015-04-28', true), + ); + + $result = $api->input($data, self::$original); + + self::$original = $result; + $this->assertSame('subject1', $result['summary']); + $this->assertSame('body1', $result['description']); + $this->assertSame(66, $result['percent-complete']); + $this->assertSame('2015-01-20T12:44:59Z', $result['created']); + $this->assertSame('2015-01-22T12:30:17Z', $result['dtstamp']); + $this->assertSame('2015-04-21', $result['dtstart']); + $this->assertSame('2015-04-28', $result['due']); } /** * Test map method */ function test_map() { $api = new kolab_api_filter_mapistore_task; $map = $api->map(); $this->assertInternalType('array', $map); $this->assertTrue(!empty($map)); } } diff --git a/tests/Unit/Input/Json/Attachment.php b/tests/Unit/Input/Json/Attachment.php index 06dab70..ab41523 100644 --- a/tests/Unit/Input/Json/Attachment.php +++ b/tests/Unit/Input/Json/Attachment.php @@ -1,46 +1,55 @@ input($data); } /** * Test expected exception in input method * * @expectedException kolab_api_exception * @expectedExceptionCode 422 */ function test_input_exception2() { $input = new kolab_api_input_json_attachment; $data = 'test'; $input->input($data); } /** * Test input method (convert JSON to internal format) */ function test_input() { // @TODO $this->markTestIncomplete('TODO'); } + + /** + * Test input method with merging + */ + function test_input2() + { + // @TODO + $this->markTestIncomplete('TODO'); + } } diff --git a/tests/Unit/Input/Json/Contact.php b/tests/Unit/Input/Json/Contact.php index 02df247..67fca36 100644 --- a/tests/Unit/Input/Json/Contact.php +++ b/tests/Unit/Input/Json/Contact.php @@ -1,250 +1,383 @@ input($data); } /** * Test expected exception in input method * * @expectedException kolab_api_exception * @expectedExceptionCode 422 */ function test_input_exception2() { $input = new kolab_api_input_json_contact; $data = 'test'; $input->input($data); } /** * Test input method (convert JSON to internal format) */ function test_input() { $input = new kolab_api_input_json_contact; $data = array( 'note' => 'note', 'categories' => array('test'), 'kind' => 'individual', 'rev' => '2015-04-21T00:00:00Z', 'bday' => '2014-01-01', 'anniversary' => '2014-02-01', 'fn' => 'name', 'nickname' => 'nick', 'n' => array( 'surname' => 'surname', 'given' => 'given', 'additional' => 'middle', 'prefix' => 'prefix', 'suffix' => 'suffix', ), 'title' => array('title'), 'group' => array( 'org' => array('Org', 'dept'), 'role' => 'profession', 'related' => array( array( 'text' => 'manager name', 'parameters' => array('type' => 'x-manager'), ), array( 'text' => 'assist', 'parameters' => array('type' => 'x-assistant'), ), ), 'adr' => array( 'street' => 'office street', 'locality' => 'office city', 'region' => 'office state', 'code' => 'office zip', 'country' => 'office country', // 'parameters' => '', // 'pobox' => '', // 'ext' => '', ), ), 'adr' => array( array( 'parameters' => array('type' => 'home'), 'street' => 'home street', 'locality' => 'home city', 'region' => 'home state', 'code' => 'home zip', 'country' => 'home country', ), array( 'parameters' => array('type' => 'work'), 'street' => 'work street', 'locality' => 'work city', 'region' => 'work state', 'code' => 'work zip', 'country' => 'work country', ), ), 'related' => array( array('text' => 'spouse', 'parameters' => array('type' => 'spouse')), array('text' => 'child1', 'parameters' => array('type' => 'child')), array('text' => 'child2', 'parameters' => array('type' => 'child')), ), - 'photo' => 'photo', + 'photo' => 'data:image/jpeg;base64,cGhvdG8x', 'gender' => 'M', 'lang' => array('lang'), 'tel' => array( array( 'text' => 'home phone', 'parameters' => array('type' => 'home'), ), array( 'text' => 'work phone', 'parameters' => array('type' => 'work'), ), array( 'text' => 'home fax', 'parameters' => array('type' => array('fax', 'home')), ), array( 'text' => 'work fax', 'parameters' => array('type' => array('fax', 'work')), ), array( 'text' => 'mobile', 'parameters' => array('type' => 'cell'), ), array( 'text' => 'pager', 'parameters' => array('type' => 'pager'), ), array( 'text' => 'car phone', 'parameters' => array('type' => 'x-car'), ), array( 'text' => 'other phone', 'parameters' => array('type' => 'textphone'), ), ), 'impp' => array('impp'), 'email' => array( array( 'text' => 'test@mail.ru', 'parameters' => array('type' => 'home'), ), array( 'text' => 'work@email.pl', 'parameters' => array('type' => 'work'), ), 'other@email.pl', ), // 'geo' => array(), 'key' => array( 'data:application/pgp-keys;base64,' . base64_encode('1'), 'data:application/pkcs7-mime;base64,' . base64_encode('2'), ), // 'x-crypto' => array(), 'fburl' => 'freebusyurl', 'url' => array('url'), ); $input->input($data); $this->assertSame('note', $data['notes']); $this->assertSame(array('test'), $data['categories']); $this->assertSame('individual', $data['kind']); $this->assertSame('name', $data['name']); $this->assertSame('nick', $data['nickname']); $this->assertSame(array('title'), $data['jobtitle']); $this->assertSame('freebusyurl', $data['freebusyurl']); $this->assertSame('male', $data['gender']); - $this->assertSame('photo', $data['photo']); + $this->assertSame('photo1', $data['photo']); $this->assertSame(array('lang'), $data['lang']); $this->assertSame(array('impp'), $data['im']); $this->assertSame(array(array('url' => 'url')), $data['website']); // 'n' $this->assertSame('surname', $data['surname']); $this->assertSame('given', $data['firstname']); $this->assertSame('middle', $data['middlename']); $this->assertSame('prefix', $data['prefix']); $this->assertSame('suffix', $data['suffix']); // 'email' $this->assertSame('home', $data['email'][0]['type']); $this->assertSame('test@mail.ru', $data['email'][0]['address']); $this->assertSame('work', $data['email'][1]['type']); $this->assertSame('work@email.pl', $data['email'][1]['address']); $this->assertSame('other@email.pl', $data['email'][2]['address']); // 'tel' $this->assertSame('home', $data['phone'][0]['type']); $this->assertSame('home phone', $data['phone'][0]['number']); $this->assertSame('work', $data['phone'][1]['type']); $this->assertSame('work phone', $data['phone'][1]['number']); $this->assertSame('homefax', $data['phone'][2]['type']); $this->assertSame('home fax', $data['phone'][2]['number']); $this->assertSame('workfax', $data['phone'][3]['type']); $this->assertSame('work fax', $data['phone'][3]['number']); $this->assertSame('mobile', $data['phone'][4]['type']); $this->assertSame('mobile', $data['phone'][4]['number']); $this->assertSame('pager', $data['phone'][5]['type']); $this->assertSame('pager', $data['phone'][5]['number']); $this->assertSame('car', $data['phone'][6]['type']); $this->assertSame('car phone', $data['phone'][6]['number']); $this->assertSame('other', $data['phone'][7]['type']); $this->assertSame('other phone', $data['phone'][7]['number']); // 'related' $this->assertSame('spouse', $data['spouse']); $this->assertSame(array('child1', 'child2'), $data['children']); // 'group' $this->assertSame('Org', $data['organization']); $this->assertSame('dept', $data['department']); $this->assertSame('profession', $data['profession']); $this->assertSame('manager name', $data['manager']); $this->assertSame('assist', $data['assistant']); $this->assertSame('office street', $data['address'][0]['street']); $this->assertSame('office city', $data['address'][0]['locality']); $this->assertSame('office state', $data['address'][0]['region']); $this->assertSame('office zip', $data['address'][0]['code']); $this->assertSame('office country', $data['address'][0]['country']); $this->assertSame('office', $data['address'][0]['type']); // $this->assertSame('', $data['address'][0]['parameters']); // $this->assertSame('', $data['address'][0]['pobox']); // $this->assertSame('', $data['address'][0]['ext']); // 'adr' $this->assertSame('home', $data['address'][1]['type']); $this->assertSame('home street', $data['address'][1]['street']); $this->assertSame('home city', $data['address'][1]['locality']); $this->assertSame('home state', $data['address'][1]['region']); $this->assertSame('home zip', $data['address'][1]['code']); $this->assertSame('home country', $data['address'][1]['country']); $this->assertSame('work', $data['address'][2]['type']); $this->assertSame('work street', $data['address'][2]['street']); $this->assertSame('work city', $data['address'][2]['locality']); $this->assertSame('work state', $data['address'][2]['region']); $this->assertSame('work zip', $data['address'][2]['code']); $this->assertSame('work country', $data['address'][2]['country']); // 'key' $this->assertSame('1', $data['pgppublickey']); $this->assertSame('2', $data['pkcs7publickey']); // $this->assertSame(kolab_api_input_json::to_datetime('2015-04-20T14:22:18Z')->format('c'), $data['created']->format('c')); $this->assertSame(kolab_api_input_json::to_datetime('2015-04-21T00:00:00Z')->format('c'), $data['changed']->format('c')); $this->assertSame(kolab_api_input_json::to_datetime('2014-01-01')->format('c'), $data['birthday']->format('c')); $this->assertSame(kolab_api_input_json::to_datetime('2014-02-01')->format('c'), $data['anniversary']->format('c')); + + // for test_input2() below + self::$original = $data; + } + + /** + * Test input method with merge + */ + function test_input2() + { + $input = new kolab_api_input_json_contact; + $data = array( + 'note' => 'note1', + 'categories' => array('test1'), + 'kind' => 'individual', +// 'rev' => '2015-04-21T00:00:00Z', + 'bday' => '2014-01-11', + 'anniversary' => '2014-02-11', + 'fn' => 'name1', + 'nickname' => 'nick1', + 'n' => array( + 'surname' => 'surname1', + 'given' => 'given1', + 'additional' => 'middle1', + 'prefix' => null, + 'suffix' => 'suffix1', + ), + 'title' => array('title1'), + 'group' => array( + 'org' => array('Org1', 'dept1'), + 'role' => 'profession1', + 'adr' => array( + 'street' => 'office street1', + 'locality' => 'office city1', + 'region' => null, + 'code' => 'office zip1', + 'country' => 'office country1', + ), + ), + 'adr' => array( + array( + 'parameters' => array('type' => 'home'), + 'street' => 'home street1', + 'locality' => 'home city1', + 'region' => 'home state1', + 'code' => null, + 'country' => 'home country1', + ), + ), + 'related' => array( + array('text' => 'spouse1', 'parameters' => array('type' => 'spouse')), + array('text' => 'child11', 'parameters' => array('type' => 'child')), + array('text' => 'child21', 'parameters' => array('type' => 'child')), + ), + 'photo' => 'data:image/jpeg;base64,cGhvdG8x', + 'gender' => 'F', + 'lang' => array('lang1'), + 'tel' => array( + array( + 'text' => 'home phone1', + 'parameters' => array('type' => 'home'), + ), + ), + 'impp' => array('impp1'), + 'email' => array('other@email.pl'), +// 'geo' => array(), + 'key' => array( + 'data:application/pgp-keys;base64,' . base64_encode('1'), + ), +// 'x-crypto' => array(), + 'fburl' => 'freebusyurl1', + 'url' => array('url1'), + ); + + $input->input($data, self::$original); + + $this->assertSame('note1', $data['notes']); + $this->assertSame(array('test1'), $data['categories']); + $this->assertSame('individual', $data['kind']); + $this->assertSame('name1', $data['name']); + $this->assertSame('nick1', $data['nickname']); + $this->assertSame(array('title1'), $data['jobtitle']); + $this->assertSame('freebusyurl1', $data['freebusyurl']); + $this->assertSame('female', $data['gender']); + $this->assertSame('photo1', $data['photo']); + $this->assertSame(array('lang1'), $data['lang']); + $this->assertSame(array('impp1'), $data['im']); + $this->assertSame(array(array('url' => 'url1')), $data['website']); + // 'n' + $this->assertSame('surname1', $data['surname']); + $this->assertSame('given1', $data['firstname']); + $this->assertSame('middle1', $data['middlename']); + $this->assertSame(null, $data['prefix']); + $this->assertSame('suffix1', $data['suffix']); + // 'email' + $this->assertSame('other@email.pl', $data['email'][0]['address']); + $this->assertCount(1, $data['email']); + // 'tel' + $this->assertSame('home', $data['phone'][0]['type']); + $this->assertSame('home phone1', $data['phone'][0]['number']); + $this->assertCount(1, $data['phone']); + // 'related' + $this->assertSame('spouse1', $data['spouse']); + $this->assertSame(array('child11', 'child21'), $data['children']); + // 'group' + $this->assertSame('Org1', $data['organization']); + $this->assertSame('dept1', $data['department']); + $this->assertSame('profession1', $data['profession']); + $this->assertSame(null, $data['manager']); + $this->assertSame(null, $data['assistant']); + + $this->assertSame('office', $data['address'][0]['type']); + $this->assertSame('office street1', $data['address'][0]['street']); + $this->assertSame('office city1', $data['address'][0]['locality']); + $this->assertSame(null, $data['address'][0]['region']); + $this->assertSame('office zip1', $data['address'][0]['code']); + $this->assertSame('office country1', $data['address'][0]['country']); + // 'adr' + $this->assertSame('home', $data['address'][1]['type']); + $this->assertSame('home street1', $data['address'][1]['street']); + $this->assertSame('home city1', $data['address'][1]['locality']); + $this->assertSame('home state1', $data['address'][1]['region']); + $this->assertSame(null, $data['address'][1]['code']); + $this->assertSame('home country1', $data['address'][1]['country']); + $this->assertCount(2, $data['address']); + // 'key' + $this->assertSame('1', $data['pgppublickey']); + $this->assertSame(null, $data['pkcs7publickey']); + + $this->assertSame(kolab_api_input_json::to_datetime('2014-01-11')->format('c'), $data['birthday']->format('c')); + $this->assertSame(kolab_api_input_json::to_datetime('2014-02-11')->format('c'), $data['anniversary']->format('c')); } } diff --git a/tests/Unit/Input/Json/Event.php b/tests/Unit/Input/Json/Event.php index e80dd2a..d29315c 100644 --- a/tests/Unit/Input/Json/Event.php +++ b/tests/Unit/Input/Json/Event.php @@ -1,91 +1,132 @@ input($data); } /** * Test expected exception in input method * * @expectedException kolab_api_exception * @expectedExceptionCode 422 */ function test_input_exception2() { $input = new kolab_api_input_json_event; $data = 'test'; $input->input($data); } /** * Test expected exception in input method * * @expectedException kolab_api_exception * @expectedExceptionCode 422 */ function test_input_exception3() { $input = new kolab_api_input_json_event; $data = array('test' => 'test'); // 'dtstamp' field is required $input->input($data); } /** * Test input method (convert JSON to internal format) */ function test_input() { $input = new kolab_api_input_json_event; $data = array( 'description' => 'description', 'summary' => 'summary', 'sequence' => 10, 'class' => 'PUBLIC', 'categories' => array('test'), 'created' => '2015-04-20T14:22:18Z', 'dtstamp' => '2015-04-21T00:00:00Z', 'status' => 'NEEDS-ACTION', 'dtstart' => '2014-01-01', 'dtend' => '2014-02-01', 'location' => null, 'priority' => 1, 'url' => 'url', ); $input->input($data); $this->assertSame('description', $data['description']); $this->assertSame('summary', $data['title']); $this->assertSame('public', $data['sensitivity']); $this->assertSame(10, $data['sequence']); $this->assertSame(array('test'), $data['categories']); $this->assertSame(null, $data['location']); $this->assertSame(1, $data['priority']); $this->assertSame('url', $data['url']); $this->assertSame(kolab_api_input_json::to_datetime('2015-04-20T14:22:18Z')->format('c'), $data['created']->format('c')); $this->assertSame(kolab_api_input_json::to_datetime('2015-04-21T00:00:00Z')->format('c'), $data['changed']->format('c')); $this->assertSame(kolab_api_input_json::to_datetime('2014-01-01')->format('c'), $data['start']->format('c')); $this->assertSame(kolab_api_input_json::to_datetime('2014-02-01')->format('c'), $data['end']->format('c')); + + self::$original = $data; + } + + /** + * Test input method with merging + */ + function test_input2() + { + $input = new kolab_api_input_json_event; + $data = array( + 'description' => 'description1', + 'summary' => 'summary1', + 'sequence' => 20, + 'class' => 'PRIVATE', + 'categories' => array('test1'), +// 'created' => '2015-04-20T14:22:18Z', +// 'dtstamp' => '2015-04-21T00:00:00Z', +// 'status' => 'IN-PROCESS', + 'dtstart' => '2014-01-11', + 'dtend' => '2014-02-11', + 'location' => 'location1', + 'priority' => 2, + 'url' => 'url1', + ); + + $input->input($data, self::$original); + + $this->assertSame('description1', $data['description']); + $this->assertSame('summary1', $data['title']); + $this->assertSame('private', $data['sensitivity']); + $this->assertSame(20, $data['sequence']); + $this->assertSame(array('test1'), $data['categories']); + $this->assertSame('location1', $data['location']); + $this->assertSame(2, $data['priority']); + $this->assertSame('url1', $data['url']); +// $this->assertSame(kolab_api_input_json::to_datetime('2015-04-20T14:22:18Z')->format('c'), $data['created']->format('c')); +// $this->assertSame(kolab_api_input_json::to_datetime('2015-04-21T00:00:00Z')->format('c'), $data['changed']->format('c')); + $this->assertSame(kolab_api_input_json::to_datetime('2014-01-11')->format('c'), $data['start']->format('c')); + $this->assertSame(kolab_api_input_json::to_datetime('2014-02-11')->format('c'), $data['end']->format('c')); } } diff --git a/tests/Unit/Input/Json/Folder.php b/tests/Unit/Input/Json/Folder.php index 7686851..5cdb07b 100644 --- a/tests/Unit/Input/Json/Folder.php +++ b/tests/Unit/Input/Json/Folder.php @@ -1,59 +1,68 @@ input($data); } /** * Test expected exception in input method * * @expectedException kolab_api_exception * @expectedExceptionCode 422 */ function test_input_exception2() { $input = new kolab_api_input_json_folder; $data = 'test'; $input->input($data); } /** * Test input method (convert JSON to internal format) */ function test_input() { $input = new kolab_api_input_json_folder; $data = array( 'name' => 'name', 'parent' => 'parent', 'type' => 'mail', 'subscribed' => true, ); $input->input($data); $this->assertSame('name', $data['name']); $this->assertSame('parent', $data['parent']); $this->assertSame('mail', $data['type']); $this->assertSame(true, $data['subscribed']); } + + /** + * Test input method with merging (?) + */ + function test_input2() + { + // @TODO + $this->markTestIncomplete('TODO'); + } } diff --git a/tests/Unit/Input/Json/Mail.php b/tests/Unit/Input/Json/Mail.php index d68320a..61f741d 100644 --- a/tests/Unit/Input/Json/Mail.php +++ b/tests/Unit/Input/Json/Mail.php @@ -1,47 +1,56 @@ input($data); } /** * Test expected exception in input method * * @expectedException kolab_api_exception * @expectedExceptionCode 422 */ function test_input_exception2() { $input = new kolab_api_input_json_mail; $data = 'test'; $input->input($data); } /** * Test input method (convert JSON to internal format) */ function test_input() { // @TODO $this->markTestIncomplete('TODO'); } + + /** + * Test input method with merging (?) + */ + function test_input2() + { + // @TODO + $this->markTestIncomplete('TODO'); + } } diff --git a/tests/Unit/Input/Json/Note.php b/tests/Unit/Input/Json/Note.php index 96db6d6..570c50c 100644 --- a/tests/Unit/Input/Json/Note.php +++ b/tests/Unit/Input/Json/Note.php @@ -1,63 +1,92 @@ input($data); } /** * Test expected exception in input method * * @expectedException kolab_api_exception * @expectedExceptionCode 422 */ function test_input_exception2() { $input = new kolab_api_input_json_note; $data = 'test'; $input->input($data); } /** * Test input method (convert JSON to internal format) */ function test_input() { $input = new kolab_api_input_json_note; $data = array( 'description' => 'description', 'summary' => 'summary', 'classification' => 'PUBLIC', 'categories' => array('test'), 'creation-date' => '2015-04-20T14:22:18Z', 'last-modification-date' => '2015-04-21T00:00:00Z', ); $input->input($data); $this->assertSame('description', $data['description']); $this->assertSame('summary', $data['title']); $this->assertSame('public', $data['sensitivity']); $this->assertSame(array('test'), $data['categories']); $this->assertSame(kolab_api_input_json::to_datetime('2015-04-20T14:22:18Z')->format('c'), $data['created']->format('c')); $this->assertSame(kolab_api_input_json::to_datetime('2015-04-21T00:00:00Z')->format('c'), $data['changed']->format('c')); + + // for test_input2() below + self::$original = $data; + } + + /** + * Test input method with merging + */ + function test_input2() + { + $input = new kolab_api_input_json_note; + $data = array( + 'description' => 'description1', + 'summary' => 'summary1', + 'classification' => 'PRIVATE', + 'categories' => array('test1'), +// 'creation-date' => '2015-04-20T14:22:18Z', +// 'last-modification-date' => '2015-04-21T00:00:00Z', + ); + + $input->input($data, self::$original); + + $this->assertSame('description1', $data['description']); + $this->assertSame('summary1', $data['title']); + $this->assertSame('private', $data['sensitivity']); + $this->assertSame(array('test1'), $data['categories']); +// $this->assertSame(kolab_api_input_json::to_datetime('2015-04-20T14:22:18Z')->format('c'), $data['created']->format('c')); +// $this->assertSame(kolab_api_input_json::to_datetime('2015-04-21T00:00:00Z')->format('c'), $data['changed']->format('c')); } } diff --git a/tests/Unit/Input/Json/Task.php b/tests/Unit/Input/Json/Task.php index 365746f..d1541fb 100644 --- a/tests/Unit/Input/Json/Task.php +++ b/tests/Unit/Input/Json/Task.php @@ -1,80 +1,126 @@ input($data); } /** * Test expected exception in input method * * @expectedException kolab_api_exception * @expectedExceptionCode 422 */ function test_input_exception2() { $input = new kolab_api_input_json_task; $data = 'test'; $input->input($data); } /** * Test input method (convert JSON to internal format) */ function test_input() { $input = new kolab_api_input_json_task; $data = array( 'description' => 'description', 'summary' => 'summary', 'sequence' => 10, 'class' => 'PUBLIC', 'categories' => array('test'), 'created' => '2015-04-20T14:22:18Z', 'dtstamp' => '2015-04-21T00:00:00Z', 'percent-complete' => 50, 'status' => 'NEEDS-ACTION', - 'start' => '2014-01-01', + 'dtstart' => '2014-01-01', 'due' => '2014-02-01', 'related-to' => 'parent', 'location' => null, 'priority' => 1, 'url' => 'url', ); $input->input($data); $this->assertSame('description', $data['description']); $this->assertSame('summary', $data['title']); $this->assertSame('public', $data['sensitivity']); $this->assertSame(10, $data['sequence']); $this->assertSame(array('test'), $data['categories']); $this->assertSame(50, $data['complete']); $this->assertSame('parent', $data['parent_id']); $this->assertSame(null, $data['location']); $this->assertSame(1, $data['priority']); $this->assertSame('url', $data['url']); $this->assertSame(kolab_api_input_json::to_datetime('2015-04-20T14:22:18Z')->format('c'), $data['created']->format('c')); $this->assertSame(kolab_api_input_json::to_datetime('2015-04-21T00:00:00Z')->format('c'), $data['changed']->format('c')); $this->assertSame(kolab_api_input_json::to_datetime('2014-01-01')->format('c'), $data['start']->format('c')); $this->assertSame(kolab_api_input_json::to_datetime('2014-02-01')->format('c'), $data['due']->format('c')); + + // for test_input2() below + self::$original = $data; + } + + /** + * Test input method with merging + */ + function test_input2() + { + $input = new kolab_api_input_json_task; + $data = array( + 'description' => 'description1', + 'summary' => 'summary1', + 'sequence' => 20, + 'class' => 'PRIVATE', + 'categories' => array('test1'), +// 'created' => '2015-04-20T14:22:18Z', +// 'dtstamp' => '2015-04-21T00:00:00Z', + 'percent-complete' => 55, + 'status' => 'NEEDS-ACTION', + 'dtstart' => '2014-01-11', + 'due' => '2014-02-11', + 'related-to' => 'parent1', + 'location' => 'location1', + 'priority' => 2, + 'url' => 'url1', + ); + + $input->input($data, self::$original); + + $this->assertSame('description1', $data['description']); + $this->assertSame('summary1', $data['title']); + $this->assertSame('private', $data['sensitivity']); + $this->assertSame(20, $data['sequence']); + $this->assertSame(array('test1'), $data['categories']); + $this->assertSame(55, $data['complete']); + $this->assertSame('parent1', $data['parent_id']); + $this->assertSame('location1', $data['location']); + $this->assertSame(2, $data['priority']); + $this->assertSame('url1', $data['url']); +// $this->assertSame(kolab_api_input_json::to_datetime('2015-04-20T14:22:18Z')->format('c'), $data['created']->format('c')); +// $this->assertSame(kolab_api_input_json::to_datetime('2015-04-21T00:00:00Z')->format('c'), $data['changed']->format('c')); + $this->assertSame(kolab_api_input_json::to_datetime('2014-01-11')->format('c'), $data['start']->format('c')); + $this->assertSame(kolab_api_input_json::to_datetime('2014-02-11')->format('c'), $data['due']->format('c')); } } diff --git a/tests/Unit/Output/Json/Contact.php b/tests/Unit/Output/Json/Contact.php index c04bc7e..788cc5c 100644 --- a/tests/Unit/Output/Json/Contact.php +++ b/tests/Unit/Output/Json/Contact.php @@ -1,99 +1,99 @@ element($object); $this->assertSame('a-b-c-d', $result['uid']); // $this->assertSame('3.1.0', $result['x-kolab-version']); // $this->assertSame('20150420T141533Z', $result['rev']); $this->assertSame('individual', $result['kind']); $this->assertSame('displname', $result['fn']); $this->assertSame('last', $result['n']['surname']); $this->assertSame('test', $result['n']['given']); $this->assertSame('middlename', $result['n']['additional']); $this->assertSame('prefx', $result['n']['prefix']); $this->assertSame('suff', $result['n']['suffix']); $this->assertSame('dsfsdfsdfsdf sdfsdfsdf sdfsdfsfd', $result['note']); $this->assertSame('free-busy url', $result['fburl']); - $this->assertSame('title', $result['title']); + $this->assertSame(array('title'), $result['title']); $this->assertSame('Org', $result['group']['org'][0]); $this->assertSame('dept', $result['group']['org'][1]); $this->assertSame('profeion', $result['group']['role']); $this->assertSame('x-manager', $result['group']['related'][0]['parameters']['type']); $this->assertSame('manager name', $result['group']['related'][0]['text']); $this->assertSame('x-assistant', $result['group']['related'][1]['parameters']['type']); $this->assertSame('assist', $result['group']['related'][1]['text']); // $this->assertSame('', $result['group']['adr']['parameters']); // $this->assertSame('', $result['group']['adr']['pobox']); // $this->assertSame('', $result['group']['adr']['ext']); $this->assertSame('office street', $result['group']['adr']['street']); $this->assertSame('office city', $result['group']['adr']['locality']); $this->assertSame('office state', $result['group']['adr']['region']); $this->assertSame('office zip', $result['group']['adr']['code']); $this->assertSame('office country', $result['group']['adr']['country']); $this->assertSame('website', $result['url'][0]); $this->assertSame('home', $result['adr'][0]['parameters']['type']); $this->assertSame('home street', $result['adr'][0]['street']); $this->assertSame('home city', $result['adr'][0]['locality']); $this->assertSame('home state', $result['adr'][0]['region']); $this->assertSame('home zip', $result['adr'][0]['code']); $this->assertSame('home country', $result['adr'][0]['country']); $this->assertSame('work', $result['adr'][1]['parameters']['type']); $this->assertSame('work street', $result['adr'][1]['street']); $this->assertSame('work city', $result['adr'][1]['locality']); $this->assertSame('work state', $result['adr'][1]['region']); $this->assertSame('work zip', $result['adr'][1]['code']); $this->assertSame('work country', $result['adr'][1]['country']); $this->assertSame('nick', $result['nickname']); $this->assertSame('spouse', $result['related'][0]['parameters']['type']); $this->assertSame('spouse', $result['related'][0]['text']); $this->assertSame('child', $result['related'][1]['parameters']['type']); $this->assertSame('children', $result['related'][1]['text']); $this->assertSame('child', $result['related'][2]['parameters']['type']); $this->assertSame('children2', $result['related'][2]['text']); $this->assertSame('20150330', $result['bday']); // ? $this->assertSame('20150301', $result['anniversary']); // ? $this->assertRegExp('|^data:image/jpeg;base64,|', $result['photo']); $this->assertSame('M', $result['gender']); $this->assertSame('home', $result['tel'][0]['parameters']['type']); $this->assertSame('home phone', $result['tel'][0]['text']); $this->assertSame('work', $result['tel'][1]['parameters']['type']); $this->assertSame('work phone', $result['tel'][1]['text']); $this->assertSame('fax', $result['tel'][2]['parameters']['type'][0]); $this->assertSame('home', $result['tel'][2]['parameters']['type'][1]); $this->assertSame('home fax', $result['tel'][2]['text']); $this->assertSame('fax', $result['tel'][3]['parameters']['type'][0]); $this->assertSame('work', $result['tel'][3]['parameters']['type'][1]); $this->assertSame('work fax', $result['tel'][3]['text']); $this->assertSame('cell', $result['tel'][4]['parameters']['type']); $this->assertSame('mobile', $result['tel'][4]['text']); $this->assertSame('pager', $result['tel'][5]['parameters']['type']); $this->assertSame('pager', $result['tel'][5]['text']); $this->assertSame('x-car', $result['tel'][6]['parameters']['type']); $this->assertSame('car phone', $result['tel'][6]['text']); $this->assertSame('textphone', $result['tel'][7]['parameters']['type']); $this->assertSame('other phone', $result['tel'][7]['text']); $this->assertSame('im gg', $result['impp'][0]); $this->assertSame('home', $result['email'][0]['parameters']['type']); $this->assertSame('test@mail.ru', $result['email'][0]['text']); $this->assertSame('work', $result['email'][1]['parameters']['type']); $this->assertSame('work@email.pl', $result['email'][1]['text']); $this->assertSame('other@email.pl', $result['email'][2]); $this->assertRegExp('|^data:application/pgp-keys;base64,|', $result['key'][0]); $this->assertRegExp('|^data:application/pkcs7-mime;base64,|', $result['key'][1]); } } diff --git a/tests/Unit/Output/Json/Task.php b/tests/Unit/Output/Json/Task.php index 1758596..47a3674 100644 --- a/tests/Unit/Output/Json/Task.php +++ b/tests/Unit/Output/Json/Task.php @@ -1,51 +1,51 @@ element($object); $this->assertSame('10-10-10-10', $result['uid']); $this->assertSame('task title', $result['summary']); $this->assertSame('2015-04-20T14:22:18Z', $result['created']); $this->assertSame('2015-04-20T14:22:18Z', $result['dtstamp']); $this->assertSame(0, $result['sequence']); $this->assertSame('PUBLIC', $result['class']); $this->assertSame("task description\nsecond line", $result['description']); $this->assertSame(56, $result['percent-complete']); $this->assertSame('German, Mark', $result['organizer']['parameters']['cn']); $this->assertSame('mailto:%3Cmark.german%40example.org%3E', $result['organizer']['cal-address']); - $this->assertSame('text/plain', $result['attach']['parameters']['fmttype']); - $this->assertSame('test.txt', $result['attach']['parameters']['x-label']); - $this->assertSame('cid:test.1429539738.5833.txt', $result['attach']['uri']); + $this->assertSame('text/plain', $result['attach'][0]['parameters']['fmttype']); + $this->assertSame('test.txt', $result['attach'][0]['parameters']['x-label']); + $this->assertSame('cid:test.1429539738.5833.txt', $result['attach'][0]['uri']); $object = kolab_api_tests::get_data('20-20-20-20', md5('Tasks'), 'task', null, $context); $result = $output->element($object); $this->assertSame('20-20-20-20', $result['uid']); $this->assertSame('task', $result['summary']); $this->assertSame('2015-04-20', $result['dtstart']); $this->assertSame('2015-04-27', $result['due']); $this->assertSame('DAILY', $result['rrule']['recur']); $this->assertSame('NEEDS-ACTION', $result['status']); - $this->assertSame('Manager, Jane', $result['attendee']['parameters']['cn']); - $this->assertSame('NEEDS-ACTION', $result['attendee']['parameters']['partstat']); - $this->assertSame('REQ-PARTICIPANT', $result['attendee']['parameters']['role']); - $this->assertSame('true', $result['attendee']['parameters']['rsvp']); - $this->assertSame('mailto:%3Cjane.manager%40example.org%3E', $result['attendee']['cal-address']); + $this->assertSame('Manager, Jane', $result['attendee'][0]['parameters']['cn']); + $this->assertSame('NEEDS-ACTION', $result['attendee'][0]['parameters']['partstat']); + $this->assertSame('REQ-PARTICIPANT', $result['attendee'][0]['parameters']['role']); + $this->assertSame('true', $result['attendee'][0]['parameters']['rsvp']); + $this->assertSame('mailto:%3Cjane.manager%40example.org%3E', $result['attendee'][0]['cal-address']); } }