Merge branch '2013.10' into 2014.11
[tine20] / tine20 / Calendar / Convert / Event / VCalendar / MacOSX.php
1 <?php
2 /**
3  * Tine 2.0
4  *
5  * @package     Calendar
6  * @subpackage  Convert
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @author      Lars Kneschke <l.kneschke@metaways.de>
9  * @copyright   Copyright (c) 2011-2013 Metaways Infosystems GmbH (http://www.metaways.de)
10  */
11
12 /**
13  * class to convert a Mac OS X VCALENDAR to Tine 2.0 Calendar_Model_Event and back again
14  *
15  * @package     Calendar
16  * @subpackage  Convert
17  */
18 class Calendar_Convert_Event_VCalendar_MacOSX extends Calendar_Convert_Event_VCalendar_Abstract
19 {
20     // DAVKit/4.0.3 (732.2); CalendarStore/4.0.4 (997.7); iCal/4.0.4 (1395.7); Mac OS X/10.6.8 (10K549)
21     // CalendarStore/5.0 (1127); iCal/5.0 (1535); Mac OS X/10.7.1 (11B26)
22     // Mac OS X/10.8 (12A269) CalendarAgent/47 
23     // Mac_OS_X/10.9 (13A603) CalendarAgent/174
24     // Mac+OS+X/10.10 (14A389) CalendarAgent/315"
25     const HEADER_MATCH = '/(?J)((CalendarStore.*Mac OS X\/(?P<version>\S+) )|(^Mac[ _+]OS[ _+]X\/(?P<version>\S+).*CalendarAgent))/';
26     
27     protected $_supportedFields = array(
28         'seq',
29         'dtend',
30         'transp',
31         'class',
32         'description',
33         #'geo',
34         'location',
35         'priority',
36         'summary',
37         'url',
38         'alarms',
39         #'tags',
40         'dtstart',
41         'exdate',
42         'rrule',
43         'recurid',
44         'is_all_day_event',
45         #'rrule_until',
46         'originator_tz',
47     );
48     
49     /**
50      * get attendee array for given contact
51      * 
52      * @param  \Sabre\VObject\Property\ICalendar\CalAddress  $calAddress  the attendee row from the vevent object
53      * @return array
54      */
55     protected function _getAttendee(\Sabre\VObject\Property\ICalendar\CalAddress $calAddress)
56     {
57         $newAttendee = parent::_getAttendee($calAddress);
58
59         // skip implicit organizer attendee.
60         // NOTE: when the organizer edits the event he becomes attendee anyway, see comments in MSEventFacade::update
61
62         // in mavericks iCal adds organiser as attendee without role
63         if (version_compare($this->_version, '10.9', '>=') && version_compare($this->_version, '10.10', '<')) {
64             if (!isset($calAddress['ROLE'])) {
65                 return NULL;
66             }
67         // in yosemite iCal adds organiser with role "chair" but has no roles for other attendee
68         } else if (version_compare($this->_version, '10.10', '>=')) {
69             if (isset($calAddress['ROLE']) && $calAddress['ROLE'] == 'CHAIR') {
70                 return NULL;
71             }
72         }
73         
74         return $newAttendee;
75     }
76
77     /**
78      * add event attendee to VEVENT object
79      *
80      * @param \Sabre\VObject\Component\VEvent $vevent
81      * @param Calendar_Model_Event            $event
82      */
83     protected function _addEventAttendee(\Sabre\VObject\Component\VEvent $vevent, Calendar_Model_Event $event)
84     {
85         parent::_addEventAttendee($vevent, $event);
86
87         if (empty($event->attendee)) {
88             return;
89         }
90
91         // add organizer as CHAIR Attendee if he's no organizer, otherwise yosemite would add an attendee
92         // when editing the event again.
93         // NOTE: when the organizer edits the event he becomes attendee anyway, see comments in MSEventFacade::update
94         if (version_compare($this->_version, '10.10', '>=')) {
95             if (!empty($event->organizer)) {
96                 $organizerContact = $event->resolveOrganizer();
97
98                 if ($organizerContact instanceof Addressbook_Model_Contact) {
99
100                     $organizerAttendee = Calendar_Model_Attender::getAttendee($event->attendee, new Calendar_Model_Attender(array(
101                         'user_id' => $organizerContact->getId(),
102                         'user_type' => Calendar_Model_Attender::USERTYPE_USER
103                     )));
104
105                     if (! $organizerAttendee) {
106                         $parameters = array(
107                             'CN'       => $organizerContact->n_fileas,
108                             'CUTYPE'   => 'INDIVIDUAL',
109                             'PARTSTAT' => 'ACCEPTED',
110                             'ROLE'     => 'CHAIR',
111                         );
112                         $organizerEmail = $organizerContact->email;
113                         if (strpos($organizerEmail, '@') !== false) {
114                             $parameters['EMAIL'] = $organizerEmail;
115                         }
116                         $vevent->add('ATTENDEE', (strpos($organizerEmail, '@') !== false ? 'mailto:' : 'urn:uuid:') . $organizerEmail, $parameters);
117                     }
118                 }
119             }
120         }
121
122     }
123     /**
124      * do version specific magic here
125      *
126      * @param \Sabre\VObject\Component\VCalendar $vcalendar
127      * @return \Sabre\VObject\Component\VCalendar | null
128      */
129     protected function _findMainEvent(\Sabre\VObject\Component\VCalendar $vcalendar)
130     {
131         $return = parent::_findMainEvent($vcalendar);
132
133         // NOTE 10.7 and 10.10 sometimes write access into calendar property
134         if (isset($vcalendar->{'X-CALENDARSERVER-ACCESS'})) {
135             foreach ($vcalendar->VEVENT as $vevent) {
136                 $vevent->{'X-CALENDARSERVER-ACCESS'} = $vcalendar->{'X-CALENDARSERVER-ACCESS'};
137             }
138         }
139
140         return $return;
141     }
142
143     /**
144      * parse VEVENT part of VCALENDAR
145      *
146      * @param  \Sabre\VObject\Component\VEvent  $vevent  the VEVENT to parse
147      * @param  Calendar_Model_Event             $event   the Tine 2.0 event to update
148      * @param  array                            $options
149      */
150     protected function _convertVevent(\Sabre\VObject\Component\VEvent $vevent, Calendar_Model_Event $event, $options)
151     {
152
153         $return = parent::_convertVevent($vevent, $event, $options);
154
155         // NOTE: 10.7 sometimes uses (internal?) int's
156         if (isset($vevent->{'X-CALENDARSERVER-ACCESS'}) && (int) (string) $vevent->{'X-CALENDARSERVER-ACCESS'} > 0) {
157             $event->class = (int) (string) $vevent->{'X-CALENDARSERVER-ACCESS'} == 1 ?
158                 Calendar_Model_Event::CLASS_PUBLIC :
159                 Calendar_Model_Event::CLASS_PRIVATE;
160         }
161
162         // 10.10 sends UNTIL in wrong timezone for all day events
163         if ($event->is_all_day_event && version_compare($this->_version, '10.10', '>=')) {
164             $event->rrule = preg_replace_callback('/UNTIL=([\d :-]{19})(?=;?)/', function($matches) use ($vevent) {
165                 // refetch UNTIL from vevent and drop timepart
166                 preg_match('/UNTIL=([\dTZ]+)(?=;?)/', $vevent->RRULE, $matches);
167                 $dtUntil = Calendar_Convert_Event_VCalendar_Abstract::getUTCDateFromStringInUsertime(substr($matches[1], 0, 8));
168                 return 'UNTIL=' . $dtUntil->format(Tinebase_Record_Abstract::ISO8601LONG);
169             }, $event->rrule);
170         }
171         return $return;
172     }
173
174     /**
175      * iCal supports manged attachments
176      *
177      * @param Calendar_Model_Event          $event
178      * @param Tinebase_Record_RecordSet     $attachments
179      */
180     protected function _manageAttachmentsFromClient($event, $attachments)
181     {
182         $event->attachments = $attachments;
183     }
184 }