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 = ''; $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 .= ' '.$this->root.$user.'/'.$calendar.'/'.$event['id'].'.ics '; $stream .= IcalEvent::toString($event); $stream .=' '.$event['updated'].' HTTP/1.1 200 OK '; } $stream .=''; 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 = ' '.$this->root.' '.$this->root.$user.' '.$this->root.$user.' '.$lastUpdate.' HTTP/1.1 200 OK '; } //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 = ' '.$this->root.$user.'/'.$calendar.'/ HTTP/1.1 200 OK HTTP/1.1 404 Not Found '; foreach($events as $event){ $stream .= ' '.$this->root.$user.'/'.$calendar.'/'.$event['id'].'.ics text/calendar; charset=utf-8 '.$event['updated'].' HTTP/1.1 200 OK '; } $stream .= ''; } 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'); 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 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 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'); 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; } } ?>