6c4c9e035fd9788da02dac3569a32cda8c8db327
[tine20] / tine20 / Calendar / Frontend / Json.php
1 <?php
2 /**
3  * Tine 2.0
4  * 
5  * @package     Calendar
6  * @subpackage  Frontend
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @author      Cornelius Weiss <c.weiss@metaways.de>
9  * @copyright   Copyright (c) 2007-2013 Metaways Infosystems GmbH (http://www.metaways.de)
10  */
11
12 /**
13  * json interface for calendar
14  * 
15  * @package     Calendar
16  * @subpackage  Frontend
17  */
18 class Calendar_Frontend_Json extends Tinebase_Frontend_Json_Abstract
19 {
20     /**
21      * app name
22      * 
23      * @var string
24      */
25     protected $_applicationName = 'Calendar';
26     
27     /**
28      * creates an exception instance of a recurring event
29      *
30      * NOTE: deleting persistent exceptions is done via a normal delete action
31      *       and handled in the controller
32      * 
33      * @param  array       $recordData
34      * @param  bool        $deleteInstance
35      * @param  bool        $deleteAllFollowing
36      * @param  bool        $checkBusyConflicts
37      * @return array       exception Event | updated baseEvent
38      * 
39      * @todo replace $_allFollowing param with $range
40      * @deprecated replace with create/update/delete
41      */
42     public function createRecurException($recordData, $deleteInstance, $deleteAllFollowing, $checkBusyConflicts = FALSE)
43     {
44         $event = new Calendar_Model_Event(array(), TRUE);
45         $event->setFromJsonInUsersTimezone($recordData);
46
47         /** @noinspection PhpDeprecationInspection */
48         $returnEvent = Calendar_Controller_Event::getInstance()->createRecurException($event, $deleteInstance, $deleteAllFollowing, $checkBusyConflicts);
49         
50         return $this->getEvent($returnEvent->getId());
51     }
52     
53     /**
54      * deletes existing events
55      *
56      * @param array $ids
57      * @param string $range
58      * @return string
59      */
60     public function deleteEvents($ids, $range = Calendar_Model_Event::RANGE_THIS)
61     {
62         return $this->_delete($ids, Calendar_Controller_Event::getInstance(), array($range));
63     }
64     
65     /**
66      * deletes existing resources
67      *
68      * @param array $ids
69      * @return string
70      */
71     public function deleteResources($ids)
72     {
73         return $this->_delete($ids, Calendar_Controller_Resource::getInstance());
74     }
75     
76     /**
77      * deletes a recur series
78      *
79      * @param  array $recordData
80      * @return array
81      */
82     public function deleteRecurSeries($recordData)
83     {
84         $event = new Calendar_Model_Event(array(), TRUE);
85         $event->setFromJsonInUsersTimezone($recordData);
86         
87         Calendar_Controller_Event::getInstance()->deleteRecurSeries($event);
88         return array('success' => true);
89     }
90     
91     /**
92      * Return a single event
93      *
94      * @param   string $id
95      * @return  array record data
96      */
97     public function getEvent($id)
98     {
99         return $this->_get($id, Calendar_Controller_Event::getInstance());
100     }
101     
102     /**
103      * Returns registry data of the calendar.
104      *
105      * @return mixed array 'variable name' => 'data'
106      * 
107      * @todo move exception handling (no default calender found) to another place?
108      */
109     public function getRegistryData()
110     {
111         $defaultCalendarId = Tinebase_Core::getPreference('Calendar')->getValue(Calendar_Preference::DEFAULTCALENDAR);
112         try {
113             $defaultCalendarArray = Tinebase_Container::getInstance()->getContainerById($defaultCalendarId)->toArray();
114             $defaultCalendarArray['account_grants'] = Tinebase_Container::getInstance()->getGrantsOfAccount(Tinebase_Core::getUser(), $defaultCalendarId)->toArray();
115             if ($defaultCalendarArray['type'] != Tinebase_Model_Container::TYPE_SHARED) {
116                 $defaultCalendarArray['ownerContact'] = Addressbook_Controller_Contact::getInstance()->getContactByUserId($defaultCalendarArray['owner_id'])->toArray();
117             }
118         } catch (Exception $e) {
119             // remove default cal pref
120             Tinebase_Core::getPreference('Calendar')->deleteUserPref(Calendar_Preference::DEFAULTCALENDAR);
121             $defaultCalendarArray = array();
122         }
123         
124         $importDefinitions = $this->_getImportDefinitions();
125         $allCalendarResources = Calendar_Controller_Resource::getInstance()->getAll()->toArray();
126         
127         $registryData = array(
128             'defaultContainer'          => $defaultCalendarArray,
129             'defaultImportDefinition'   => $importDefinitions['default'],
130             'importDefinitions'         => $importDefinitions,
131             'calendarResources'         => $allCalendarResources
132         );
133         
134         return $registryData;
135     }
136     
137     /**
138      * get default addressbook
139      * 
140      * @return array
141      */
142     public function getDefaultCalendar() 
143    {
144         $defaultCalendar = Calendar_Controller_Event::getInstance()->getDefaultCalendar();
145         $defaultCalendarArray = $defaultCalendar->toArray();
146         $defaultCalendarArray['account_grants'] = Tinebase_Container::getInstance()->getGrantsOfAccount(Tinebase_Core::getUser(), $defaultCalendar->getId())->toArray();
147         Tinebase_Core::getLogger()->notice(print_r($defaultCalendar, true));
148         return $defaultCalendarArray;
149     }
150     
151     /**
152      * import contacts
153      * 
154      * @param string $tempFileId to import
155      * @param string $definitionId
156      * @param array $importOptions
157      * @param array $clientRecordData
158      * @return array
159      */
160     public function importEvents($tempFileId, $definitionId, $importOptions, $clientRecordData = array())
161     {
162         return $this->_import($tempFileId, $definitionId, $importOptions, $clientRecordData);
163     }
164     
165     /**
166      * creates a scheduled import
167      * 
168      * @param string $remoteUrl
169      * @param string $interval
170      * @param string $importOptions
171      * @return array
172      */
173     public function importRemoteEvents($remoteUrl, $interval, $importOptions)
174     {
175         // Determine which plugin should be used to import
176         switch ($importOptions['sourceType']) {
177             case 'remote_caldav':
178                 $plugin = 'Calendar_Import_CalDAV';
179                 break;
180             default:
181                 $plugin = 'Calendar_Import_Ical';
182         }
183
184         $record = Tinebase_Controller_ScheduledImport::getInstance()->create( new Tinebase_Model_Import(array(
185             'source'            => $remoteUrl,
186             'sourcetype'        => Tinebase_Model_Import::SOURCETYPE_REMOTE,
187             'interval'          => $interval,
188             'options'           => array_replace($importOptions, array(
189                 'plugin' => $plugin,
190                 'importFileByScheduler' => $importOptions['sourceType'] != 'remote_caldav',
191             )),
192             'model'             => 'Calendar_Model_Event',
193             'application_id'    => Tinebase_Application::getInstance()->getApplicationByName('Calendar')->getId(),
194         ), true));
195
196         $result = $this->_recordToJson($record);
197
198         return $result;
199     }
200     
201     /**
202      * get addressbook import definitions
203      * 
204      * @return array
205      * 
206      * @todo generalize this
207      */
208     protected function _getImportDefinitions()
209     {
210         $filter = new Tinebase_Model_ImportExportDefinitionFilter(array(
211             array('field' => 'application_id',  'operator' => 'equals', 'value' => Tinebase_Application::getInstance()->getApplicationByName('Calendar')->getId()),
212             array('field' => 'type',            'operator' => 'equals', 'value' => 'import'),
213         ));
214         
215         $definitionConverter = new Tinebase_Convert_ImportExportDefinition_Json();
216         
217         try {
218             $importDefinitions = Tinebase_ImportExportDefinition::getInstance()->search($filter);
219             $defaultDefinition = $this->_getDefaultImportDefinition($importDefinitions);
220             $result = array(
221                 'results'               => $definitionConverter->fromTine20RecordSet($importDefinitions),
222                 'totalcount'            => count($importDefinitions),
223                 'default'               => ($defaultDefinition) ? $definitionConverter->fromTine20Model($defaultDefinition) : array(),
224             );
225         } catch (Exception $e) {
226             Tinebase_Exception::log($e);
227             $result = array(
228                 array(
229                     'results'               => array(),
230                     'totalcount'            => 0,
231                     'default'               => array(),
232                 )
233             );
234         }
235         
236         return $result;
237     }
238     
239     /**
240      * get default definition
241      * 
242      * @param Tinebase_Record_RecordSet $_importDefinitions
243      * @return Tinebase_Model_ImportExportDefinition
244      * 
245      * @todo generalize this
246      */
247     protected function _getDefaultImportDefinition($_importDefinitions)
248     {
249         try {
250             $defaultDefinition = Tinebase_ImportExportDefinition::getInstance()->getByName('cal_import_ical');
251         } catch (Tinebase_Exception_NotFound $tenf) {
252             if (count($_importDefinitions) > 0) {
253                 $defaultDefinition = $_importDefinitions->getFirstRecord();
254             } else {
255                 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' No import definitions found for Calendar');
256                 $defaultDefinition = NULL;
257             }
258         }
259         
260         return $defaultDefinition;
261     }
262     
263     /**
264      * Return a single resouece
265      *
266      * @param   string $id
267      * @return  array record data
268      */
269     public function getResource($id)
270     {
271         return $this->_get($id, Calendar_Controller_Resource::getInstance());
272     }
273
274     /**
275      * @param array $_event
276      *   attendee to find free timeslot for
277      *   dtstart, dtend -> to calculate duration
278      *   rrule optional
279      * @param array $_options
280      *  'from'         datetime (optional, defaults event->dtstart) from where to start searching
281      *  'until'        datetime (optional, defaults 2 years) until when to giveup searching
282      *  'constraints'  array    (optional, defaults 8-20 'FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR') array of timespecs to limit the search with
283      *     timespec:
284      *       dtstart,
285      *       dtend,
286      *       rrule ... for example "work days" -> 'FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR'
287      * @return array
288      */
289     public function searchFreeTime($_event, $_options)
290     {
291         $eventRecord = new Calendar_Model_Event(array(), TRUE);
292         $eventRecord->setFromJsonInUsersTimezone($_event);
293
294         $records = Calendar_Controller_Event::getInstance()->searchFreeTime($eventRecord, $_options);
295
296         $records->attendee = array();
297         $result = $this->_multipleRecordsToJson($records, null, null);
298
299         return array(
300             'results'       => $result,
301             'totalcount'    => count($result),
302             'filter'        => array(),
303         );
304     }
305     
306     /**
307      * Search for events matching given arguments
308      *
309      * @param  array $filter
310      * @param  array $paging
311      * @return array
312      */
313     public function searchEvents($filter, $paging)
314     {
315         $controller = Calendar_Controller_Event::getInstance();
316         
317         $decodedPagination = $this->_prepareParameter($paging);
318         $pagination = new Tinebase_Model_Pagination($decodedPagination);
319         $clientFilter = $filter = $this->_decodeFilter($filter, 'Calendar_Model_EventFilter');
320
321         // find out if fixed calendars should be used
322         $fixedCalendars = Calendar_Config::getInstance()->get(Calendar_Config::FIXED_CALENDARS);
323         $useFixedCalendars = is_array($fixedCalendars) && ! empty($fixedCalendars);
324         
325         $periodFilter = $filter->getFilter('period');
326         
327         // add period filter per default to prevent endless search
328         if (! $periodFilter) {
329             $periodFilter = $this->_getDefaultPeriodFilter();
330             // periodFilter will be added to fixed filter when using fixed calendars
331             if (! $useFixedCalendars) {
332                 $filter->addFilter($periodFilter);
333             }
334         }
335         
336         // add fixed calendar on demand
337         if ($useFixedCalendars) {
338             $fixed = new Calendar_Model_EventFilter(array(), 'AND');
339             $fixed->addFilter( new Tinebase_Model_Filter_Text('container_id', 'in', $fixedCalendars));
340             
341             $fixed->addFilter($periodFilter);
342             
343             $og = new Calendar_Model_EventFilter(array(), 'OR');
344             $og->addFilterGroup($fixed);
345             $og->addFilterGroup($clientFilter);
346             
347             $filter = new Calendar_Model_EventFilter(array(), 'AND');
348             $filter->addFilterGroup($og);
349         }
350         
351         $records = $controller->search($filter, $pagination, FALSE);
352         
353         $result = $this->_multipleRecordsToJson($records, $clientFilter, $pagination);
354         
355         return array(
356             'results'       => $result,
357             'totalcount'    => count($result),
358             'filter'        => $clientFilter->toArray(TRUE),
359         );
360     }
361     
362     /**
363      * get default period filter
364      * 
365      * @return Calendar_Model_PeriodFilter
366      */
367     protected function _getDefaultPeriodFilter()
368     {
369         $now = Tinebase_DateTime::now()->setTime(0,0,0);
370         
371         $from = $now->getClone()->subMonth(Calendar_Config::getInstance()->get(Calendar_Config::MAX_JSON_DEFAULT_FILTER_PERIOD_FROM, 0));
372         $until = $now->getClone()->addMonth(Calendar_Config::getInstance()->get(Calendar_Config::MAX_JSON_DEFAULT_FILTER_PERIOD_UNTIL, 1));
373         $periodFilter = new Calendar_Model_PeriodFilter(array(
374             'field' => 'period',
375             'operator' => 'within',
376             'value' => array("from" => $from, "until" => $until)
377         ));
378         
379         return $periodFilter;
380     }
381     
382     /**
383      * Search for resources matching given arguments
384      *
385      * @param  array $filter
386      * @param  array $paging
387      * @return array
388      */
389     public function searchResources($filter, $paging)
390     {
391         return $this->_search($filter, $paging, Calendar_Controller_Resource::getInstance(), 'Calendar_Model_ResourceFilter', true);
392     }
393     
394     /**
395      * creates/updates an event / recur
396      *
397      * @param   array   $recordData
398      * @param   bool    $checkBusyConflicts
399      * @param   string  $range
400      * @return  array   created/updated event
401      */
402     public function saveEvent($recordData, $checkBusyConflicts = FALSE, $range = Calendar_Model_Event::RANGE_THIS)
403     {
404         return $this->_save($recordData, Calendar_Controller_Event::getInstance(), 'Event', 'id', array($checkBusyConflicts, $range));
405     }
406     
407     /**
408      * creates/updates a Resource
409      *
410      * @param   array   $recordData
411      * @return  array   created/updated Resource
412      */
413     public function saveResource($recordData)
414     {
415         $recordData['grants'] = new Tinebase_Record_RecordSet('Tinebase_Model_Grants', $recordData['grants']);
416         if(array_key_exists ('max_number_of_people', $recordData) && $recordData['max_number_of_people'] == '') {
417            $recordData['max_number_of_people'] = null;
418         }
419         
420         return $this->_save($recordData, Calendar_Controller_Resource::getInstance(), 'Resource');
421     }
422     
423     /**
424      * sets attendee status for an attender on the given event
425      * 
426      * NOTE: for recur events we implicitly create an exceptions on demand
427      *
428      * @param  array         $eventData
429      * @param  array         $attenderData
430      * @param  string        $authKey
431      * @return array         complete event
432      */
433     public function setAttenderStatus($eventData, $attenderData, $authKey)
434     {
435         $event    = new Calendar_Model_Event($eventData);
436         $attender = new Calendar_Model_Attender($attenderData);
437         
438         Calendar_Controller_Event::getInstance()->attenderStatusUpdate($event, $attender, $authKey);
439         
440         return $this->getEvent($event->getId());
441     }
442     
443     /**
444      * updated a recur series
445      *
446      * @param  array $recordData
447      * @param  bool  $checkBusyConflicts
448      * @noparamyet  JSONstring $returnPeriod NOT IMPLEMENTED YET
449      * @return array 
450      */
451     public function updateRecurSeries($recordData, $checkBusyConflicts = FALSE /*, $returnPeriod*/)
452     {
453         $recurInstance = new Calendar_Model_Event(array(), TRUE);
454         $recurInstance->setFromJsonInUsersTimezone($recordData);
455         
456         //if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(print_r($recurInstance->toArray(), true));
457         
458         $baseEvent = Calendar_Controller_Event::getInstance()->updateRecurSeries($recurInstance, $checkBusyConflicts);
459         
460         return $this->getEvent($baseEvent->getId());
461     }
462     
463     /**
464      * prepares an iMIP (RFC 6047) Message
465      * 
466      * @param array|Calendar_Model_iMIP $iMIP
467      * @return array prepared iMIP part
468      */
469     public function iMIPPrepare($iMIP)
470     {
471         $iMIPMessage = $iMIP instanceof Calendar_Model_iMIP ? $iMIP : new Calendar_Model_iMIP($iMIP);
472         $iMIPFrontend = new Calendar_Frontend_iMIP();
473         
474         $iMIPMessage->preconditionsChecked = FALSE;
475         $iMIPFrontend->prepareComponent($iMIPMessage);
476         $iMIPMessage->setTimezone(Tinebase_Core::getUserTimezone());
477         return $iMIPMessage->toArray();
478     }
479     
480     /**
481      * process an iMIP (RFC 6047) Message
482      * 
483      * @param array  $iMIP
484      * @param string $status
485      * @return array prepared iMIP part
486      */
487     public function iMIPProcess($iMIP, $status=null)
488     {
489         $iMIPMessage = new Calendar_Model_iMIP($iMIP);
490         $iMIPFrontend = new Calendar_Frontend_iMIP();
491         
492         $iMIPFrontend->process($iMIPMessage, $status);
493         
494         return $this->iMIPPrepare($iMIPMessage);
495     }
496
497     /**
498      * @param array $attendee
499      * @param array $event
500      * @param array $ignoreUIDs
501      * @return array
502      */
503     public function getFreeBusyInfo($attendee, $event, $ignoreUIDs = array())
504     {
505         $attendee = new Tinebase_Record_RecordSet('Calendar_Model_Attender', $attendee);
506         $calendarController = Calendar_Controller_Event::getInstance();
507         $eventRecord = new Calendar_Model_Event(array(), TRUE);
508         $eventRecord->setFromJsonInUsersTimezone($event);
509
510         $periods = $calendarController->getBlockingPeriods($eventRecord, array(
511             'from'  => $eventRecord->dtstart,
512             'until' => $eventRecord->dtstart->getClone()->addMonth(2)
513         ));
514
515         $fbInfo = $calendarController->getFreeBusyInfo($periods, $attendee, $ignoreUIDs);
516
517         return $fbInfo->toArray();
518     }
519
520     /**
521      * @param array $filter
522      * @param array $paging
523      * @param array $event
524      * @param array $ignoreUIDs
525      * @return array
526      */
527     public function searchAttenders($filter, $paging, $event, $ignoreUIDs)
528     {
529         $filters = array();
530         foreach($filter as $f) {
531             switch($f['field']) {
532                 case 'query':
533                     $filters['query'] = $f;
534                     break;
535                 default:
536                     $filters[$f['field']] = $f['value'];
537                     break;
538             }
539         }
540
541         $result = array();
542         $addressBookFE = new Addressbook_Frontend_Json();
543
544         if (!isset($filters['type']) || in_array(Calendar_Model_Attender::USERTYPE_USER, $filters['type'])) {
545             $contactFilter = array(array('condition' => 'OR', 'filters' => array(
546                 $filters['query'],
547                 array('field' => 'path', 'operator' => 'contains', 'value' => $filters['query']['value'])
548             )));
549             if (isset($filters['userFilter'])) {
550                 $contactFilter[] = $filters['userFilter'];
551             }
552             $contactPaging = $paging;
553             $contactPaging['sort'] = 'type';
554             $result[Calendar_Model_Attender::USERTYPE_USER] = $addressBookFE->searchContacts($contactFilter, $contactPaging);
555         }
556
557         if (!isset($filters['type']) || in_array(Calendar_Model_Attender::USERTYPE_GROUP, $filters['type'])) {
558             $groupFilter = array(array('condition' => 'OR', 'filters' => array(
559                 $filters['query'],
560                 array('field' => 'path', 'operator' => 'contains', 'value' => $filters['query']['value'])
561             )));
562             if (isset($filters['groupFilter'])) {
563                 $groupFilter[] = $filters['groupFilter'];
564             }
565             $groupFilter[] = array('field' => 'type', 'operator' => 'contains', 'value' => 'group');
566             $result[Calendar_Model_Attender::USERTYPE_GROUP] = $addressBookFE->searchLists($groupFilter, $paging);
567         }
568
569         if (!isset($filters['type']) || in_array(Calendar_Model_Attender::USERTYPE_RESOURCE, $filters['type'])) {
570             $resourceFilter = array($filters['query']);
571             if (isset($filters['resourceFilter'])) {
572                 $resourceFilter[] = $filters['resourceFilter'];
573             }
574             $result[Calendar_Model_Attender::USERTYPE_RESOURCE] = $this->searchResources($resourceFilter, $paging);
575         }
576
577         if (empty($event)) {
578             $result['freeBusyInfo'] = array();
579         } else {
580             $attendee = array();
581             foreach ($result as $type => $res) {
582                 foreach ($res['results'] as $r) {
583                     $attendee[] = array(
584                         'user_id' => $r['id'],
585                         'user_type' => $type
586                     );
587                 }
588             }
589
590             $result['freeBusyInfo'] = $this->getFreeBusyInfo($attendee, $event, $ignoreUIDs);
591         }
592
593         return $result;
594     }
595 }