123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404 |
- <?php
- /**
- * Classe de gestion du serveur Caldav
- * Les méthodes suivantes sont utilisables comme des callbacks a définir avant le start();
- * calendarLastUpdate(user,calendar) : doit retourner le timestamp de la derniere modification sur le calendrier ciblé (le client s'en sert pour la gestion des performances)
- * searchEvents(user,calendar) : doit retourner un tableau d'évenements sur le calendrier ciblé
- * saveEvent(user,calendar,event,infos) : doit effectuer une sauvegarde de l'évenement ciblé
- * deleteEvent(user,calendar,event) : doit effectuer une supression de l'évenement ciblé
- * @author valentin carruesco
- * @category Planning
- * @license cc by nc sa
- */
- class CalDavServer{
- public $root,$searchEvents,$deleteEvent,$saveEvent,$calendarLastUpdate,$log;
- public function start(){
-
- $server = $_SERVER;
- $request = $_REQUEST;
- $method = strtoupper($server['REQUEST_METHOD']);
- $body = stream_get_contents(fopen('php://input','r+'));
- $user = 'default';
- $calendar = 'default';
-
- $target = str_replace($this->root,'',$server['REQUEST_URI']);
- $infos = explode('/',$target);
- if(isset($infos[0]))$user = $infos[0];
- if(isset($infos[1])) $calendar = $infos[1];
- $logFunction = $this->log;
- if(isset($logFunction)) $logFunction('notice','receive : '.$method.', body : '.PHP_EOL.$body.PHP_EOL.PHP_EOL.' --- target : '.$server['REQUEST_URI'].PHP_EOL.PHP_EOL);
-
- switch($method) {
- //Le client veux la liste des évenements de $calendar pour le user $user au format ical
- case 'REPORT':
- $stream = '<?xml version="1.0" encoding="utf-8"?><d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/">';
- $callback = $this->searchEvents;
- $events = $callback($user,$calendar);
- if(isset($logFunction)) $logFunction('notice','fetch '.count($events).' events, parsing to xml ...'.PHP_EOL.PHP_EOL);
- foreach($events as $event){
- $stream .= '<d:response>
- <d:href>'.$this->root.$user.'/'.$calendar.'/'.$event['id'].'.ics</d:href>
- <d:propstat>
- <d:prop>
- <cal:calendar-data>';
- $stream .= IcalEvent::toString($event);
- $stream .='</cal:calendar-data>
- <d:getetag>'.$event['updated'].'</d:getetag>
- </d:prop>
- <d:status>HTTP/1.1 200 OK</d:status>
- </d:propstat>
- </d:response>';
- }
- $stream .='</d:multistatus>';
- if(isset($logFunction)) $logFunction('notice','return stream : '.$stream.' '.PHP_EOL.PHP_EOL,true);
- header('HTTP/1.1 207 Multi-Status');
- echo $stream;
- exit();
- break;
- case 'PROPFIND':
- //Le client souhaite avoir le ctag (timestamp de derniere mise a jour du calendrier ciblé), si ce ctag est égal au dernier ctag fournis, le client ne met pas a jour les events
- if(strpos($body, 'getctag')!==false) {
- $callback = $this->calendarLastUpdate;
- $lastUpdate = $callback($user,$calendar);
- $stream = '<?xml version="1.0" encoding="utf-8"?>
- <d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/">
- <d:response>
- <d:href>'.$this->root.'</d:href>
- <d:propstat>
- <d:prop>
- <d:current-user-principal>
- <d:href>'.$this->root.$user.'</d:href>
- </d:current-user-principal>
- <d:owner>
- <d:href>'.$this->root.$user.'</d:href>
- </d:owner>
- <cal:supported-calendar-component-set>
- <cal:comp name="VEVENT"/>
- <cal:comp name="VTODO"/>
- </cal:supported-calendar-component-set>
- <cs:getctag>'.$lastUpdate.'</cs:getctag>
- <d:resourcetype>
- <d:collection/>
- <cal:calendar/>
- </d:resourcetype>
- <d:supported-report-set>
- <d:supported-report>
- <d:report>
- <d:expand-property/>
- </d:report>
- </d:supported-report>
- <d:supported-report>
- <d:report>
- <d:principal-property-search/>
- </d:report>
- </d:supported-report>
- <d:supported-report>
- <d:report>
- <d:principal-search-property-set/>
- </d:report>
- </d:supported-report>
- <d:supported-report>
- <d:report>
- <cal:calendar-multiget/>
- </d:report>
- </d:supported-report>
- <d:supported-report>
- <d:report>
- <cal:calendar-query/>
- </d:report>
- </d:supported-report>
- <d:supported-report>
- <d:report>
- <cal:free-busy-query/>
- </d:report>
- </d:supported-report>
- </d:supported-report-set>
- </d:prop>
- <d:status>HTTP/1.1 200 OK</d:status>
- </d:propstat>
- </d:response>
- </d:multistatus>';
- }
- //pour le calenderier ciblé, le client souhaite voir tous les etag (tag de derniere maj d'un évenement) pour voir lesquels il doit demander au serveur
- if(strpos($body, 'getetag')!==false) {
-
- $f = $this->searchEvents;
- $events = $f($user,$calendar);
- $stream = '<?xml version="1.0" encoding="utf-8"?>
- <d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/">
- <d:response>
- <d:href>'.$this->root.$user.'/'.$calendar.'/</d:href>
- <d:propstat>
- <d:prop>
- <d:resourcetype>
- <d:collection/>
- <cal:calendar/>
- </d:resourcetype>
- </d:prop>
- <d:status>HTTP/1.1 200 OK</d:status>
- </d:propstat>
- <d:propstat>
- <d:prop>
- <d:getcontenttype/>
- <d:getetag/>
- </d:prop>
- <d:status>HTTP/1.1 404 Not Found</d:status>
- </d:propstat>
- </d:response>';
- foreach($events as $event){
- $stream .= '<d:response>
- <d:href>'.$this->root.$user.'/'.$calendar.'/'.$event['id'].'.ics</d:href>
- <d:propstat>
- <d:prop>
- <d:getcontenttype>text/calendar; charset=utf-8</d:getcontenttype>
- <d:resourcetype/>
- <d:getetag>'.$event['updated'].'</d:getetag>
- </d:prop>
- <d:status>HTTP/1.1 200 OK</d:status>
- </d:propstat>
- </d:response>';
- }
- $stream .= '</d:multistatus>';
- }
- if(isset($logFunction)) $logFunction('notice','return stream : '.$stream.' '.PHP_EOL.PHP_EOL,true);
- header('HTTP/1.1 207 Multi-Status');
- echo $stream;
- exit();
- break;
- //Récuperation d'un fichier (ou dossier ?)
- case 'GET':
- header('HTTP/1.1 200 Ok');
- break;
- //Envois d'un evenement
- case 'PUT':
- header('HTTP/1.1 201 Created');
- $path = $_SERVER['REQUEST_URI'];
- $pathinfos = explode('/',$path);
- preg_match('|([0-9]*)\.ics|i',$server['REQUEST_URI'],$m);
-
- $callback = $this->saveEvent;
- $events = $callback($user,$calendar,$m[1],IcalEvent::fromString($body));
- if(isset($logFunction)) $logFunction('notice','event saved '.$user.'/'.$calendar.', id: '.$m[1].PHP_EOL.PHP_EOL);
- break;
- //Supression dossier/fichier
- case 'DELETE':
- //header('HTTP/1.1 501 Method not implemented');
- header('HTTP/1.1 200 Ok');
- $callback = $this->deleteEvent;
- preg_match('|([0-9]*)\.ics|i',$server['REQUEST_URI'],$m);
- try{
- if(!isset($m[1])) throw new Exception("Event id non récuperable: ".$server['REQUEST_URI']);
-
- $callback($user,$calendar,$m[1]);
- if(isset($logFunction)) $logFunction('notice','event deleted '.$user.'/'.$calendar.', id: '.$m[1].PHP_EOL.PHP_EOL);
- header('HTTP/1.1 200 Ok');
- }catch(Exeption $e){
- if(isset($logFunction)) $logFunction('error','unable to delete event: '.$e->getMessage().' '.PHP_EOL.PHP_EOL);
- header('HTTP/1.1 403 Forbidden');
- }
- /*
- 200 => 'Ok',
- 201 => 'Created',
- 204 => 'No Content',
- 207 => 'Multi-Status',
- 403 => 'Forbidden',
- 404 => 'Not Found',
- 409 => 'Conflict',
- 415 => 'Unsupported Media Type',
- 500 => 'Internal Server Error',
- 501 => 'Method not implemented',
- );
- return 'HTTP/1.1 ' . $code . ' ' . $msg[$code];
- */
- break;
- //Déplacement/renommage dossier/fichier
- case 'MOVE':
- //header('HTTP/1.1 501 Method not implemented');
- header('HTTP/1.1 200 Ok');
- break;
- //The OPTIONS method allows an http client to find out what HTTP methods are supported on a specific url.
- case 'OPTIONS':
- header('Allows: options get head post delete trace propfind proppatch copy mkcol put');
- //header('Allows: options get post delete trace propfind proppatch copy mkcol put');
- break;
- case 'HEAD':
- header('HTTP/1.1 200 Ok');
- //header('HTTP/1.1 501 Method not implemented');
- break;
- case 'POST':
- header('HTTP/1.1 501 Method not implemented');
- break;
- case 'TRACE':
- header('HTTP/1.1 501 Method not implemented');
- break;
- //Updates properties of a resource or collection.
- case 'PROPPATCH':
- header('HTTP/1.1 501 Method not implemented');
- break;
- //Copie d'un élement vers un nouvel emplacement
- case 'COPY':
- //header('HTTP/1.1 501 Method not implemented');
- header('HTTP/1.1 200 Ok');
- break;
- //Verouillage d'un élement
- case 'LOCK':
- header('HTTP/1.1 501 Method not implemented');
- break;
- //Déverouillage d'un élement
- case 'UNLOCK':
- header('HTTP/1.1 501 Method not implemented');
- break;
- }
- }
- }
- class IcalEvent{
- public $title,$description,$start,$end,$frequency,$location,$categories,$alarms,$ics,$uid;
-
- public static function fromString($ical){
- $event = new self();
- $lines = array();
- foreach(explode("\n",$ical) as $line):
- $columns = explode(":",trim($line));
- if(!isset($columns[1])) continue;
- $key = $columns[0];
- $value = $columns[1];
-
- $keyvalues = explode(';',$key);
- $key = array_shift($keyvalues);
- //Ne prendre que la premiere description
- if($key=='DESCRIPTION' && isset($lines['DESCRIPTION'])) continue;
- $lines[$key] = $value;
- endforeach;
-
- if(isset($lines['CATEGORIES'])) $event->categories = $lines['CATEGORIES'];
- if(isset($lines['SUMMARY'])) $event->title = $lines['SUMMARY'];
- if(isset($lines['DESCRIPTION'])) $event->description = $lines['DESCRIPTION'];
- if(isset($lines['DTSTART'])) $event->start = strtotime($lines['DTSTART']);
- if(isset($lines['DTEND'])) $event->end = strtotime($lines['DTEND']);
- if(isset($lines['RRULE'])) $event->frequency = $lines['RRULE'];
- if(isset($lines['LOCATION'])) $event->location = $lines['LOCATION'];
- if(isset($lines['UID'])) $event->uid = $lines['UID'];
- if(isset($lines['TRIGGER'])) $event->alarms = $lines['TRIGGER'];
- return $event;
- }
- public static function toString($event){
- $ics = 'BEGIN:VCALENDAR'."\n";
- $ics .= 'PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN'."\n";
- $ics .= 'VERSION:2.0'."\n";
- $ics .= 'BEGIN:VTIMEZONE'."\n";
- $ics .= 'TZID:Europe/Paris'."\n";
- $ics .= 'BEGIN:DAYLIGHT'."\n";
- $ics .= 'TZOFFSETFROM:+0100'."\n";
- $ics .= 'TZOFFSETTO:+0200'."\n";
- $ics .= 'TZNAME:CEST'."\n";
- $ics .= 'END:DAYLIGHT'."\n";
- $ics .= 'BEGIN:STANDARD'."\n";
- $ics .= 'TZOFFSETFROM:+0200'."\n";
- $ics .= 'TZOFFSETTO:+0100'."\n";
- $ics .= 'TZNAME:CET'."\n";
- $ics .= 'DTSTART:19701025T030000'."\n";
- $ics .= 'END:STANDARD'."\n";
- $ics .= 'END:VTIMEZONE'."\n";
- $ics .= 'BEGIN:VEVENT'."\n";
- $ics .= 'DTSTART:19700329T020000'."\n";
- if(isset($event['recurrence']) && $event['recurrence']!='')
- $ics .= 'RRULE:'.$event['recurrence']."\n";
- $ics .= 'CREATED:'.date('Ymd',$event['created']).'T'.date('His',$event['created']).'Z'."\n";
- $ics .= 'LAST-MODIFIED:'.date('Ymd',$event['updated']).'T'.date('His',$event['updated']).'Z'."\n";
- $ics .= 'DTSTAMP:20181209T222306Z'."\n";
- $ics .= 'UID:'.$event['id']."\n";
- if(!empty($event['label']))
- $ics .= 'SUMMARY:'.$event['label']."\n";
- if(!empty($event['type']))
- $ics .= 'CATEGORIES:'.$event['type']."\n";
- if(!empty($event['description']))
- $ics .= 'DESCRIPTION:'.str_replace(array(PHP_EOL,"\r","\n"),'\n',$event['description'])." \n";
- if($event['street'].$event['zip'].$event['city']!='')
- $ics .= 'LOCATION:'.$event['street'].' '.$event['zip'].' '.$event['city']."\n";
- $ics .= 'DTSTART;TZID=Europe/Paris:'.date('Ymd',$event['startDate']).'T'.date('His',$event['startDate'])."\n";
- $ics .= 'DTEND;TZID=Europe/Paris:'.date('Ymd',$event['endDate']).'T'.date('His',$event['endDate'])."\n";
- $ics .= 'TRANSP:OPAQUE'."\n"; //TRANSP : Définit si la ressource affectée à l'événement est rendu indisponible (OPAQUE, TRANSPARENT)
- if($event['notificationNumber']!='0'){
- if($event['notificationUnity'] == 'minut') $notification= 'PT'.$event['notificationNumber'].'M';
- if($event['notificationUnity'] == 'hour') $notification = 'PT'.$event['notificationNumber'].'H';
- if($event['notificationUnity'] == 'day') $notification = 'P'.$event['notificationNumber'].'D';
- $ics .= 'BEGIN:VALARM'."\n";
- $ics .= 'ACTION:DISPLAY'."\n";
- $ics .= 'TRIGGER;VALUE=DURATION:-'.$notification.''."\n";
- $ics .= 'DESCRIPTION:Rappel erp'."\n";
- $ics .= 'END:VALARM'."\n";
- }
- $ics .= 'STATUS:CONFIRMED'."\n"; //STATUS : Statut de l'événement (TENTATIVE, CONFIRMED, CANCELLED)
-
- $ics .= 'END:VEVENT'."\n";
- $ics .= 'END:VCALENDAR'."\n";
- return $ics;
- }
- }
- ?>
|