caldav update: checks if record belongs to other application
[tine20] / tine20 / Calendar / Import / Ical.php
1 <?php
2 /**
3  * Tine 2.0
4  * 
5  * @package     Calendar
6  * @subpackage  Import
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @author      Cornelius Weiss <c.weiss@metaways.de>
9  * @copyright   Copyright (c) 2010-2013 Metaways Infosystems GmbH (http://www.metaways.de)
10  * 
11  * @todo        use more functionality of Tinebase_Import_Abstract (import() and other fns)
12  */
13
14 /**
15  * Calendar_Import_Ical
16  * 
17  * @package     Calendar
18  * @subpackage  Import
19  * 
20  * @see for german holidays http://www.sunbird-kalender.de/extension/kalender/
21  */
22 class Calendar_Import_Ical extends Tinebase_Import_Abstract
23 {
24     /**
25      * config options
26      * 
27      * @var array
28      */
29     protected $_options = array(
30         /**
31          * force update of existing events 
32          * @var boolean
33          */
34         'updateExisting'        => TRUE,
35         /**
36          * updates exiting events if sequence number is higher
37          * @var boolean
38          */
39         'forceUpdateExisting'   => FALSE,
40         /**
41          * container the events should be imported in
42          * @var string
43          */
44         'importContainerId'     => NULL,
45     );
46     
47     /**
48      * default timezone from VCALENDAR. If not present, users default tz will be taken
49      * @var string
50      */
51     protected $_defaultTimezoneId;
52     
53     /**
54      * maps tine20 propertynames to ical propertynames
55      * @var array
56      */
57     protected $_eventPropertyMap = array(
58         'summary'               => 'SUMMARY',
59         'description'           => 'DESCRIPTION',
60         'class'                 => 'CLASS',
61         'transp'                => 'TRANSP',
62         'seq'                   => 'SEQUENCE',
63         'uid'                   => 'UID',
64         'dtstart'               => 'DTSTART',
65         'dtend'                 => 'DTEND',
66         'rrule'                 => 'RRULE',
67 //        '' => 'DTSTAMP',
68         'creation_time'         => 'CREATED',
69         'last_modified_time'    => 'LAST-MODIFIED',
70     );
71     
72     /**
73      * creates a new importer from an importexport definition
74      * 
75      * @param  Tinebase_Model_ImportExportDefinition $_definition
76      * @param  array                                 $_options
77      * @return Calendar_Import_Ical
78      */
79     public static function createFromDefinition(Tinebase_Model_ImportExportDefinition $_definition, array $_options = array())
80     {
81         return new Calendar_Import_Ical(self::getOptionsArrayFromDefinition($_definition, $_options));
82     }
83     
84     /**
85      * import the data
86      *
87      * @param  stream $_resource 
88      * @param array $_clientRecordData
89      * @return array : 
90      *  'results'           => Tinebase_Record_RecordSet, // for dryrun only
91      *  'totalcount'        => int,
92      *  'failcount'         => int,
93      *  'duplicatecount'    => int,
94      *  
95      *  @throws Calendar_Exception_IcalParser
96      *  
97      *  @see 0008334: use vcalendar converter for ics import
98      */
99     public function import($_resource = NULL, $_clientRecordData = array())
100     {
101         if (! $this->_options['importContainerId']) {
102             throw new Tinebase_Exception_InvalidArgument('you need to define a importContainerId');
103         }
104         
105         $converter = Calendar_Convert_Event_VCalendar_Factory::factory(Calendar_Convert_Event_VCalendar_Factory::CLIENT_GENERIC);
106         
107         try {
108             $events = $converter->toTine20RecordSet($_resource);
109         } catch (Exception $e) {
110             Tinebase_Exception::log($e);
111             $isce = new Calendar_Exception_IcalParser();
112             $isce->setParseError($e);
113             throw $isce;
114         }
115         
116         $events->container_id = $this->_options['importContainerId'];
117         
118         $cc = Calendar_Controller_MSEventFacade::getInstance();
119         $sendNotifications = Calendar_Controller_Event::getInstance()->sendNotifications(FALSE);
120         
121         // search uid's and remove already existing -> only in import cal?
122         $existingEventsFilter = new Calendar_Model_EventFilter(array(
123             array('field' => 'container_id', 'operator' => 'in', 'value' => array($this->_options['importContainerId'])),
124             array('field' => 'uid', 'operator' => 'in', 'value' => array_unique($events->uid)),
125         ));
126         $existingEvents = $cc->search($existingEventsFilter, NULL);
127         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . ' ' . __LINE__ . ' ' 
128                 . ' Found ' . count($existingEvents) . ' existing events');
129         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . ' ' . __LINE__ . ' '
130                 . ' Filter: ' . print_r($existingEventsFilter->toArray(), true));
131         
132         // insert one by one in a single transaction
133         $existingEvents->addIndices(array('uid'));
134         foreach ($events as $event) {
135             $existingEvent = $existingEvents->find('uid', $event->uid);
136             try {
137                 if (! $existingEvent) {
138                     $cc->create($event, FALSE);
139                     $this->_importResult['totalcount'] += 1;
140                 } else if ($this->_options['forceUpdateExisting'] || ($this->_options['updateExisting'] && $event->seq > $existingEvent->seq)) {
141                     $event->id = $existingEvent->getId();
142                     $event->last_modified_time = ($existingEvent->last_modified_time instanceof Tinebase_DateTime) ? clone $existingEvent->last_modified_time : NULL;
143                     $cc->update($event, FALSE);
144                     $this->_importResult['totalcount'] += 1;
145                 } else {
146                     $this->_importResult['duplicatecount'] += 1;
147                 }
148             } catch (Exception $e) {
149                 if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . ' ' . __LINE__
150                         . ' Import failed for Event ' . $event->summary);
151                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . ' ' . __LINE__
152                         . ' ' . print_r($event->toArray(), TRUE));
153                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . ' ' . __LINE__
154                         . ' ' . $e);
155                 $this->_importResult['failcount'] += 1;
156             }
157         }
158         Calendar_Controller_Event::getInstance()->sendNotifications($sendNotifications);
159         
160         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . ' ' . __LINE__ . ' '
161                 . ' totalcount: ' . $this->_importResult['totalcount']
162                 . ' / duplicates: ' . $this->_importResult['duplicatecount']
163                 . ' / fails: ' . $this->_importResult['failcount']);
164         
165         return $this->_importResult;
166     }
167 }