7 * @license http://www.gnu.org/licenses/agpl.html AGPL3
8 * @copyright Copyright (c) 2014 Metaways Infosystems GmbH (http://www.metaways.de)
9 * @author Cornelius Weiß <c.weiss@metaways.de>
11 class Calendar_Setup_Update_Release8 extends Setup_Update_Abstract
15 * - move ack & snooze time from attendee to alarm
17 public function update_0()
19 // find all events with ack or snooze times set
20 $eventIds = $this->_db->query(
21 "SELECT DISTINCT " . $this->_db->quoteIdentifier('cal_event_id') .
22 " FROM " . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . "cal_attendee") .
23 " WHERE " . $this->_db->quoteIdentifier("alarm_ack_time") . " IS NOT NULL OR ". $this->_db->quoteIdentifier("alarm_snooze_time") . " IS NOT NULL"
24 )->fetchAll(Zend_Db::FETCH_ASSOC);
26 $attendeeBE = new Calendar_Backend_Sql_Attendee();
27 $alarmBE = Tinebase_Alarm::getInstance();
29 foreach ($eventIds as $eventId) {
30 $eventId = $eventId['cal_event_id'];
32 $attendeeFilter = new Tinebase_Model_Filter_FilterGroup();
33 $attendeeFilter->addFilter(new Tinebase_Model_Filter_Text('cal_event_id', 'equals', $eventId));
34 $attendees = $attendeeBE->search($attendeeFilter);
36 $alarms = $alarmBE->search(new Tinebase_Model_AlarmFilter(array(
37 array('field' => 'model', 'operator' => 'equals', 'value' =>'Calendar_Model_Event'),
38 array('field' => 'record_id', 'operator' => 'equals', 'value' => $eventId)
41 foreach ($alarms as $alarm) {
42 foreach ($attendees as $attendee) {
43 if ($attendee->alarm_ack_time instanceof Tinebase_DateTime) {
44 $alarm->setOption("acknowledged-{$attendee->user_id}", $attendee->alarm_ack_time->format(Tinebase_Record_Abstract::ISO8601LONG));
46 if ($attendee->alarm_snooze_time instanceof Tinebase_DateTime) {
47 $alarm->setOption("snoozed-{$attendee->user_id}", $attendee->alarm_snooze_time->format(Tinebase_Record_Abstract::ISO8601LONG));
51 $alarmBE->update($alarm);
55 // delte ack & snooze from attendee
56 $this->_backend->dropCol('cal_attendee', 'alarm_ack_time');
57 $this->_backend->dropCol('cal_attendee', 'alarm_snooze_time');
59 $this->setTableVersion('cal_attendee', 5);
60 $this->setApplicationVersion('Calendar', '8.1');
67 public function update_1()
69 $eventBE = new Tinebase_Backend_Sql(array(
70 'modelName' => 'Calendar_Model_Event',
71 'tableName' => 'cal_events',
72 'modlogActive' => false
74 // find all events without organizer
75 $eventIds = $this->_db->query(
76 "SELECT " . $this->_db->quoteIdentifier('id') .
77 " FROM " . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . "cal_events") .
78 " WHERE " . $this->_db->quoteIdentifier("organizer") . " IS NULL OR " .
79 $this->_db->quoteIdentifier("organizer") . " = ''"
80 )->fetchAll(Zend_Db::FETCH_ASSOC);
82 foreach ($eventIds as $eventId) {
83 $event = $eventBE->get($eventId['id']);
84 $event->organizer = (string) Tinebase_User::getInstance()->getFullUserById($event->created_by)->contact_id;
87 $this->_db->quoteInto($this->_db->quoteIdentifier('id') . ' = ?', $eventId),
91 $this->_db->update(SQL_TABLE_PREFIX . "cal_events", $event->toArray(), $where);
92 } catch (Tinebase_Exception_Record_Validation $terv) {
93 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
94 . ' Could not fix invalid event record: ' . print_r($event->toArray(), true));
95 Tinebase_Exception::log($terv);
99 // find all events CONFIDENTIAL class
100 $eventIds = $this->_db->query(
101 "SELECT " . $this->_db->quoteIdentifier('id') .
102 " FROM " . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . "cal_events") .
103 " WHERE " . $this->_db->quoteIdentifier("class") . " = 'CONFIDENTIAL'"
104 )->fetchAll(Zend_Db::FETCH_ASSOC);
106 foreach ($eventIds as $eventId) {
107 $event = $eventBE->get($eventId['id']);
108 $class = Calendar_Model_Event::CLASS_PRIVATE;
109 $event->class = (string) $class;
112 $this->_db->quoteInto($this->_db->quoteIdentifier('id') . ' = ?', $eventId),
116 $this->_db->update(SQL_TABLE_PREFIX . "cal_events", $event->toArray(), $where);
117 } catch (Tinebase_Exception_Record_Validation $terv) {
118 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
119 . ' Could not fix invalid event record: ' . print_r($event->toArray(), true));
120 Tinebase_Exception::log($terv);
123 $this->setApplicationVersion('Calendar', '8.2');
128 * - normalize all rrules
130 public function update_2()
132 // find all events with rrule
133 $eventIds = $this->_db->query(
134 "SELECT " . $this->_db->quoteIdentifier('id') .
135 " FROM " . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . "cal_events") .
136 " WHERE " . $this->_db->quoteIdentifier("rrule") . " IS NOT NULL"
137 )->fetchAll(Zend_Db::FETCH_ASSOC);
139 // NOTE: we need a generic sql BE to circumvent calendar specific acl issues
140 $eventBE = new Tinebase_Backend_Sql(array(
141 'modelName' => 'Calendar_Model_Event',
142 'tableName' => 'cal_events',
143 'modlogActive' => false
146 foreach ($eventIds as $eventId) {
147 $event = $eventBE->get($eventId['id']);
148 $oldRruleString = (string) $event->rrule;
149 $rrule = Calendar_Model_Rrule::getRruleFromString($oldRruleString);
150 $rrule->normalize($event);
152 if ($oldRruleString != (string) $rrule) {
153 $event->rrule = (string) $rrule;
155 $eventBE->update($event);
156 } catch (Tinebase_Exception_Record_Validation $terv) {
157 Tinebase_Exception::log($terv, null, $event->toArray());
158 } catch (Tinebase_Exception_UnexpectedValue $teuv) {
159 Tinebase_Exception::log($teuv, null, $event->toArray());
164 $this->setApplicationVersion('Calendar', '8.3');
172 public function update_3()
174 if (! $this->_backend->columnExists('etag', 'cal_events')) {
175 $declaration = new Setup_Backend_Schema_Field_Xml('
181 $this->_backend->addCol('cal_events', $declaration);
183 $declaration = new Setup_Backend_Schema_Index_Xml('
190 $this->_backend->addIndex('cal_events', $declaration);
193 $this->setTableVersion('cal_events', 7);
194 $this->setApplicationVersion('Calendar', '8.4');
198 * - update import / export
200 public function update_4()
202 Setup_Controller::getInstance()->createImportExportDefinitions(Tinebase_Application::getInstance()->getApplicationByName('Calendar'));
203 $this->setTableVersion('cal_events', 7);
204 $this->setApplicationVersion('Calendar', '8.5');
208 * adds external_seq col
210 * @see 0009890: improve external event invitation support
212 public function update_5()
214 if (! $this->_backend->columnExists('external_seq', 'cal_events')) {
216 <name>external_seq</name>
218 <notnull>true</notnull>
222 $declaration = new Setup_Backend_Schema_Field_Xml($seqCol);
223 $this->_backend->addCol('cal_events', $declaration);
226 $this->setTableVersion('cal_events', '8');
227 $this->setApplicationVersion('Calendar', '8.6');
233 * @see 0010214: improve calendar performance / yearly base events
235 * TODO re-enable this when it is fixed for postgresql
236 * @see 0011194: only drop index if it exists
238 public function update_6()
240 // $declaration = new Setup_Backend_Schema_Index_Xml('
242 // <name>rrule</name>
244 // <name>rrule</name>
248 // $this->_backend->addIndex('cal_events', $declaration);
249 // } catch (Zend_Db_Statement_Exception $e) {
250 // Tinebase_Exception::log($e);
253 $this->setTableVersion('cal_events', '9');
254 $this->setApplicationVersion('Calendar', '8.7');
258 * repair missing displaycontainer_id
260 public function update_7()
262 $allUser = $this->_db->query(
263 "SELECT " . $this->_db->quoteIdentifier('id') . "," . $this->_db->quoteIdentifier('contact_id') .
264 " FROM " . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . "accounts") .
265 " WHERE " . $this->_db->quoteIdentifier("contact_id") . " IS NOT NULL"
266 )->fetchAll(Zend_Db::FETCH_ASSOC);
268 $contactUserMap = array();
269 foreach ($allUser as $id => $user) {
270 $contactUserMap[$user['contact_id']] = $user['id'];
273 // find all user/groupmember attendees with missing displaycontainer
274 $attendees = $this->_db->query(
275 "SELECT DISTINCT" . $this->_db->quoteIdentifier('user_type') . "," . $this->_db->quoteIdentifier('user_id') .
276 " FROM " . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . "cal_attendee") .
277 " WHERE " . $this->_db->quoteIdentifier("displaycontainer_id") . " IS NULL" .
278 " AND " . $this->_db->quoteIdentifier("user_type") . $this->_db->quoteInto(" IN (?)", array('user', 'groupmemeber')) .
279 " AND " . $this->_db->quoteIdentifier("user_id") . $this->_db->quoteInto(" IN (?)", array_keys($contactUserMap))
280 )->fetchAll(Zend_Db::FETCH_ASSOC);
282 // find all user/groupmember attendees with missing displaycontainer
283 $attendees = array_merge($attendees, $this->_db->query(
284 "SELECT DISTINCT" . $this->_db->quoteIdentifier('user_type') . "," . $this->_db->quoteIdentifier('user_id') .
285 " FROM " . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . "cal_attendee") .
286 " WHERE " . $this->_db->quoteIdentifier("displaycontainer_id") . " IS NULL" .
287 " AND " . $this->_db->quoteIdentifier("user_type") . $this->_db->quoteInto(" IN (?)", array('resource'))
288 )->fetchAll(Zend_Db::FETCH_ASSOC));
290 $resources = $this->_db->query(
291 "SELECT " . $this->_db->quoteIdentifier('id') . "," . $this->_db->quoteIdentifier('container_id') .
292 " FROM " . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . "cal_resources")
293 )->fetchAll(Zend_Db::FETCH_ASSOC);
295 $resourceContainerMap = array();
296 foreach ($resources as $resource) {
297 $resourceContainerMap[$resource['id']] = $resource['container_id'];
300 foreach ($attendees as $attendee) {
301 //find out displaycontainer
302 if ($attendee['user_type'] != 'resource') {
303 $userAccountId = $contactUserMap[$attendee['user_id']];
305 $attendee['displaycontainerId'] = Calendar_Controller_Event::getDefaultDisplayContainerId($userAccountId);
306 } catch (Tinebase_Exception_NotFound $tenf) {
307 Setup_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . " Could not find user with id " . $attendee['user_id']);
311 $attendee['displaycontainerId'] = $resourceContainerMap[$attendee['user_id']];
314 // update displaycontainer
316 "UPDATE" . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . "cal_attendee") .
317 " SET " . $this->_db->quoteIdentifier("displaycontainer_id") . " = " . $this->_db->quote($attendee['displaycontainerId']) .
318 " WHERE " . $this->_db->quoteIdentifier("user_type") . " = " . $this->_db->quote($attendee['user_type']) .
319 " AND " . $this->_db->quoteIdentifier("user_id") . " = " . $this->_db->quote($attendee['user_id'])
323 $this->setApplicationVersion('Calendar', '8.8');
327 * identify base event via new base_event_id field instead of UID
329 public function update_8()
331 /* find possibly broken events
332 SELECT group_concat(id), uid, count(id) as cnt from tine20_cal_events
333 WHERE rrule IS NOT NULL
338 $declaration = new Setup_Backend_Schema_Field_Xml('
340 <name>base_event_id</name>
344 $this->_backend->addCol('cal_events', $declaration);
346 $declaration = new Setup_Backend_Schema_Index_Xml('
348 <name>base_event_id</name>
350 <name>base_event_id</name>
353 $this->_backend->addIndex('cal_events', $declaration);
355 // find all events with rrule
356 $events = $this->_db->query(
357 "SELECT " . $this->_db->quoteIdentifier('id') .
358 ', ' . $this->_db->quoteIdentifier('uid') .
359 ', ' . $this->_db->quoteIdentifier('container_id') .
360 ', ' . $this->_db->quoteIdentifier('created_by') .
361 " FROM " . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . "cal_events") .
362 " WHERE " . $this->_db->quoteIdentifier("rrule") . " IS NOT NULL" .
363 " AND " . $this->_db->quoteIdentifier("is_deleted") . " = " . $this->_db->quote(0, Zend_Db::INT_TYPE)
364 )->fetchAll(Zend_Db::FETCH_ASSOC);
366 // update all exdates in same container
367 foreach($events as $event) {
369 "UPDATE " . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . "cal_events") .
370 " SET " . $this->_db->quoteIdentifier('base_event_id') . ' = ' . $this->_db->quote($event['id']) .
371 " WHERE " . $this->_db->quoteIdentifier('uid') . ' = ' . $this->_db->quote($event['uid']) .
372 " AND " . $this->_db->quoteIdentifier("container_id") . ' = ' . $this->_db->quote($event['container_id']) .
373 " AND " . $this->_db->quoteIdentifier("recurid") . " IS NOT NULL" .
374 " AND " . $this->_db->quoteIdentifier("is_deleted") . " = " . $this->_db->quote(0, Zend_Db::INT_TYPE)
378 // find all container move exdates
379 $danglingExdates = $this->_db->query(
380 "SELECT " . $this->_db->quoteIdentifier('uid') .
381 ', ' . $this->_db->quoteIdentifier('id') .
382 ', ' . $this->_db->quoteIdentifier('created_by') .
383 " FROM " . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . "cal_events") .
384 " WHERE " . $this->_db->quoteIdentifier("recurid") . " IS NOT NULL" .
385 " AND " . $this->_db->quoteIdentifier("base_event_id") . " IS NULL" .
386 " AND " . $this->_db->quoteIdentifier("is_deleted") . " = " . $this->_db->quote(0, Zend_Db::INT_TYPE)
387 )->fetchAll(Zend_Db::FETCH_ASSOC);
389 // try to match by creator
390 foreach ($danglingExdates as $exdate) {
391 $possibleBaseEvents = array();
392 $matches = array_filter($events, function ($event) use ($exdate, $possibleBaseEvents) {
393 if ($event['uid'] == $exdate['uid']) {
394 $possibleBaseEvents[] = $event;
395 return $event['created_by'] == $exdate['created_by'];
400 switch(count($matches)) {
403 if (count($possibleBaseEvents) == 0) {
404 // garbage? exdate without any base event
405 Setup_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . " dangling exdate with id {$exdate['id']}");
408 Setup_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . " no match for exdate with id {$exdate['id']}");
409 $baseEvent = current($possibleBaseEvents);
413 $baseEvent = current($matches);
416 // to much matches :-(
417 Setup_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . " multiple matches for exdate with id {$exdate['id']}");
418 $baseEvent = current($matches);
422 "UPDATE " . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . "cal_events") .
423 " SET " . $this->_db->quoteIdentifier('base_event_id') . ' = ' . $this->_db->quote($baseEvent['id']) .
424 " WHERE " . $this->_db->quoteIdentifier('id') . ' = ' . $this->_db->quote($exdate['id'])
428 $this->setTableVersion('cal_events', '10');
429 $this->setApplicationVersion('Calendar', '8.9');
433 * @see 0011266: increase size of event fields summary and location
435 public function update_9()
437 $fieldsToChange = array('location', 'summary');
439 foreach ($fieldsToChange as $name) {
441 <name>' . $name . '</name>
443 <length>1024</length>
446 $declaration = new Setup_Backend_Schema_Field_Xml($seqCol);
447 $this->_backend->alterCol('cal_events', $declaration);
450 $this->setTableVersion('cal_events', 11);
451 $this->setApplicationVersion('Calendar', '8.10');