0004934: Ensure that container does exists
[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         'container_id'     => NULL,
45         
46         /**
47          * Model to be used for import
48          * @var string
49          */
50         'model' => 'Calendar_Model_Event'
51     );
52     
53     /**
54      * default timezone from VCALENDAR. If not present, users default tz will be taken
55      * @var string
56      */
57     protected $_defaultTimezoneId;
58     
59     /**
60      * maps tine20 propertynames to ical propertynames
61      * @var array
62      */
63     protected $_eventPropertyMap = array(
64         'summary'               => 'SUMMARY',
65         'description'           => 'DESCRIPTION',
66         'class'                 => 'CLASS',
67         'transp'                => 'TRANSP',
68         'seq'                   => 'SEQUENCE',
69         'uid'                   => 'UID',
70         'dtstart'               => 'DTSTART',
71         'dtend'                 => 'DTEND',
72         'rrule'                 => 'RRULE',
73         'creation_time'         => 'CREATED',
74         'last_modified_time'    => 'LAST-MODIFIED',
75     );
76     
77     /**
78      * creates a new importer from an importexport definition
79      * 
80      * @param  Tinebase_Model_ImportExportDefinition $_definition
81      * @param  array                                 $_options
82      * @return Calendar_Import_Ical
83      */
84     public static function createFromDefinition(Tinebase_Model_ImportExportDefinition $_definition, array $_options = array())
85     {
86         return new Calendar_Import_Ical(self::getOptionsArrayFromDefinition($_definition, $_options));
87     }
88     
89     /**
90      * import the data
91      *
92      * @param  stream $_resource 
93      * @param array $_clientRecordData
94      * @return array : 
95      *  'results'           => Tinebase_Record_RecordSet, // for dryrun only
96      *  'totalcount'        => int,
97      *  'failcount'         => int,
98      *  'duplicatecount'    => int,
99      *  
100      *  @throws Calendar_Exception_IcalParser
101      *  
102      *  @see 0008334: use vcalendar converter for ics import
103      */
104     public function import($_resource = NULL, $_clientRecordData = array())
105     {
106         $this->_initImportResult();
107         
108         if (! $this->_options['container_id']) {
109             throw new Tinebase_Exception_InvalidArgument('you need to define a container_id');
110         }
111         
112         $converter = Calendar_Convert_Event_VCalendar_Factory::factory(Calendar_Convert_Event_VCalendar_Factory::CLIENT_GENERIC);
113         
114         try {
115             $events = $converter->toTine20RecordSet($_resource);
116         } catch (Exception $e) {
117             Tinebase_Exception::log($e);
118             $isce = new Calendar_Exception_IcalParser();
119             $isce->setParseError($e);
120             throw $isce;
121         }
122         
123         
124         try {
125             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . ' ' . __LINE__ . ' ' 
126                 . ' Trying to find container with ID ' . print_r($this->_options['container_id'], true));
127             $container = Tinebase_Container::getInstance()->getContainerById($this->_options['container_id']);
128         } catch (Tinebase_Exception_InvalidArgument $e) {
129             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . ' ' . __LINE__ . ' ' 
130                 . ' Could not found container with Id ' . print_r($this->_options['container_id'], true) . ' assuming this is a container name.' );
131                          
132             $container = new Tinebase_Model_Container(array(
133                 'name'              => $this->_options['container_id'],
134                 'type'              => Tinebase_Model_Container::TYPE_PERSONAL,
135                 'backend'           => Tinebase_User::SQL,
136                 'color'             => '#ffffff',
137                 'application_id'    => Tinebase_Application::getInstance()->getApplicationByName('Calendar')->getId(),
138                 'owner_id'          => Tinebase_Core::getUser()->getId(),
139                 'model'             => 'Calendar_Model_Event',
140             ));
141             $container = Tinebase_Container::getInstance()->addContainer($container);
142         }
143         
144         try {
145             $this->_options['container_id'] = $container->getId();
146         } catch (Exception $ex) {
147             throw new Tinebase_Exception_NotFound('Could not find container by ID: ' . $this->_options['container_id']);
148         }
149         
150         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . ' ' . __LINE__ . ' ' 
151             . ' Import into calendar: ' . print_r($this->_options['container_id'], true));
152         
153         $cc = Calendar_Controller_MSEventFacade::getInstance();
154         $sendNotifications = Calendar_Controller_Event::getInstance()->sendNotifications(FALSE);
155         
156         // search uid's and remove already existing -> only in import cal?
157         $existingEventsFilter = new Calendar_Model_EventFilter(array(
158             array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_options['container_id']),
159             array('field' => 'uid', 'operator' => 'in', 'value' => array_unique($events->uid)),
160         ));
161         $existingEvents = $cc->search($existingEventsFilter);
162         
163         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . ' ' . __LINE__ . ' ' 
164                 . ' Found ' . count($existingEvents) . ' existing events');
165         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . ' ' . __LINE__ . ' '
166                 . ' Filter: ' . print_r($existingEventsFilter->toArray(), true));
167         
168         // insert one by one in a single transaction
169         $existingEvents->addIndices(array('uid'));
170         foreach ($events as $event) {
171             $existingEvent = $existingEvents->find('uid', $event->uid);
172             try {
173                 if (! $existingEvent) {
174                     $event->container_id = $this->_options['container_id'];
175                     $event = $cc->create($event, FALSE);
176                     $this->_importResult['totalcount'] += 1;
177                     $this->_importResult['results']->addRecord($event);
178                 } else if ($this->_options['forceUpdateExisting'] || ($this->_options['updateExisting'] && $event->seq > $existingEvent->seq)) {
179                     $event->id = $existingEvent->getId();
180                     $event->last_modified_time = ($existingEvent->last_modified_time instanceof Tinebase_DateTime) ? clone $existingEvent->last_modified_time : NULL;
181                     $cc->update($event, FALSE);
182                     $this->_importResult['results']->addRecord($event);
183                     $this->_importResult['totalcount'] += 1;
184                 } else {
185                     $this->_importResult['duplicatecount'] += 1;
186                 }
187             } catch (Exception $e) {
188                 if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . ' ' . __LINE__
189                         . ' Import failed for Event ' . $event->summary);
190                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . ' ' . __LINE__
191                         . ' ' . print_r($event->toArray(), TRUE));
192                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . ' ' . __LINE__
193                         . ' ' . $e);
194                 $this->_importResult['failcount'] += 1;
195             }
196         }
197         Calendar_Controller_Event::getInstance()->sendNotifications($sendNotifications);
198         
199         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . ' ' . __LINE__ . ' '
200                 . ' totalcount: ' . $this->_importResult['totalcount']
201                 . ' / duplicates: ' . $this->_importResult['duplicatecount']
202                 . ' / fails: ' . $this->_importResult['failcount']);
203         
204         return $this->_importResult;
205     }
206 }