ninegate/src/ninegate-1.0/src/Cadoles/PortalBundle/Command/SynchroICSCommand.php

719 lines
29 KiB
PHP

<?php
namespace Cadoles\PortalBundle\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpKernel\KernelInterface;
use Doctrine\DBAL\Connection as DBALConnection;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Validator\Constraints\DateTime;
use it\thecsea\simple_caldav_client\SimpleCalDAVClient;
use it\thecsea\simple_caldav_client\CalDAVException;
use Cadoles\PortalBundle\Entity\Calendarevent;
class CalFileParser {
private $_base_path = './';
private $_file_name = '';
private $_output = 'array';
private $DTfields = array('DTSTART', 'DTEND', 'DTSTAMP', 'CREATED', 'EXDATE', 'LAST-MODIFIED');
private $_user_timezone = null;
private $_file_timezone = null;
private $ctx;
function __construct() {
$this->_default_output = $this->_output;
$this->ctx = stream_context_create(array('http'=>array('timeout' => 5)));
}
public function set_base_path($path) {
if (isset($path)) {
$this->_base_path = $path;
}
}
public function set_file_name($filename) {
if (!empty($filename)) {
$this->_file_name = $filename;
}
}
public function set_output($output) {
if (!empty($output)) {
$this->_output = $output;
}
}
public function set_timezone($timezone) {
if (!empty($timezone)) {
$this->_user_timezone = $timezone;
}
}
public function get_base_path() {
return $this->_base_path;
}
public function get_file_name() {
return $this->_file_name;
}
public function get_output() {
return $this->_output;
}
/**
* Read File
*
* @param string $file
* @return string
*
* @example
* read_file('schedule.vcal')
* read_file('../2011-08/'schedule.vcal');
* read_file('http://michaelencode.com/example.vcal');
*/
public function read_file($file = '', $options = array()) {
if (empty($file)) {
$file = $this->_file_name;
}
// check to see if file path is a url
if (preg_match('/^(http|https):/', $file) === 1) {
return $this->read_remote_file($file,$options);
}
//empty base path if file starts with forward-slash
if (substr($file, 0, 1) === '/') {
$this->set_base_path('');
}
if (!empty($file) && file_exists($this->_base_path . $file)) {
try {
@$file_contents = file_get_contents($this->_base_path . $file,false,$this->ctx);
return $file_contents;
}
catch (Exception $e) {
return false;
}
} else {
return false;
}
}
/**
* Read Remote File
* @param $file
* @return bool|string
*/
public function read_remote_file($file, $options=array()) {
if (!empty($file)) {
if(isset($options["proxy"])) {
$aContext = array(
'http' => array(
'proxy' => 'http://'.$options["proxy"],
'request_fulluri' => true,
'timeout' => 5
),
);
$cxContext = stream_context_create($aContext);
try {
@$data = file_get_contents($file, False, $cxContext);
}
catch (Exception $e) {
return false;
}
}
else {
try {
@$data = file_get_contents($file,false,$this->ctx);
}
catch (Exception $e) {
return false;
}
}
if ($data !== false) {
return $data;
}
}
return false;
}
/**
* Parse
* Parses iCal or vCal file and returns data of a type that is specified
* @param string $file
* @param string $output
* @return mixed|string
*/
public function parse($file = '', $output = '', $options = array()) {
$file_contents = $this->read_file($file,$options);
if ($file_contents === false) {
return 'Error: File Could not be read';
}
if (empty($output)) {
$output = $this->_output;
}
if (empty($output)) {
$output = $this->_default_output;
}
$events_arr = array();
// fetch timezone to create datetime object
if (preg_match('/X-WR-TIMEZONE:(.+)/i', $file_contents, $timezone) === 1) {
$this->_file_timezone = trim($timezone[1]);
if ($this->_user_timezone == null) {
$this->_user_timezone = $this->_file_timezone;
}
} else {
$this->_file_timezone = $this->_user_timezone;
}
// tell user if setting timezone is necessary
if ($this->_user_timezone == null) {
return 'Error: no timezone set or found';
}
//put contains between start and end of VEVENT into array called $events
preg_match_all('/(BEGIN:VEVENT.*?END:VEVENT)/si', $file_contents, $events);
if (!empty($events)) {
foreach ($events[0] as $event_str) {
//remove begin and end "tags"
$event_str = trim(str_replace(array('BEGIN:VEVENT','END:VEVENT'),'',$event_str));
//convert string of entire event into an array with elements containing string of 'key:value'
$event_key_pairs = $this->convert_event_string_to_array($event_str);
//convert array of 'key:value' strings to an array of key => values
$events_arr[] = $this->convert_key_value_strings($event_key_pairs);
}
}
$this->_output = $this->_default_output;
return $this->output($events_arr, $output);
}
/**
* Output
* outputs data in the format specified
*
* @param $events_arr
* @param string $output
* @return mixed
*/
private function output($events_arr, $output = 'array') {
switch ($output) {
case 'json' :
return json_encode($events_arr);
break;
default :
return $events_arr;
break;
}
}
/**
* Convert event string to array
* accepts a string of calendar event data and produces array of 'key:value' strings
* See convert_key_value_strings() to convert strings to
* @param string $event_str
* @return array
*/
private function convert_event_string_to_array($event_str = '') {
if (!empty($event_str)) {
//replace new lines with a custom delimiter
$event_str = preg_replace("/[\r\n]/", "%%" ,$event_str);
// take care of line wrapping
$event_str = preg_replace("/%%%% /", "" ,$event_str);
if (strpos(substr($event_str, 2), '%%') == '0') { //if this code is executed, then file consisted of one line causing previous tactic to fail
$tmp_piece = explode(':',$event_str);
$num_pieces = count($tmp_piece);
$event_str = '';
foreach ($tmp_piece as $key => $item_str) {
if ($key != ($num_pieces -1) ) {
//split at spaces
$tmp_pieces = preg_split('/\s/',$item_str);
//get the last whole word in the string [item]
$last_word = end($tmp_pieces);
//adds delimiter to front and back of item string, and also between each new key
$item_str = trim(str_replace(array($last_word,' %%' . $last_word),array('%%' . $last_word . ':', '%%' . $last_word), $item_str));
}
//build the event string back together, piece by piece
$event_str .= trim($item_str);
}
}
//perform some house cleaning just in case
$event_str = str_replace('%%%%','%%', $event_str);
if (substr($event_str, 0, 2) == '%%') {
$event_str = substr($event_str, 2);
}
//break string into array elements at custom delimiter
$return = explode('%%',$event_str);
} else {
$return = array();
}
return $return;
}
/**
* Parse Key Value String
* accepts an array of strings in the format of 'key:value' and returns an array of keys and values
* @param array $event_key_pairs
* @return array
*/
private function convert_key_value_strings($event_key_pairs = array()) {
$event = array();
$event_alarm = array();
$event_alarms = array();
$inside_alarm = false;
if (!empty($event_key_pairs)) {
foreach ($event_key_pairs as $line) {
if (empty($line)) continue;
$line_data = explode(':', $line, 2);
$key = trim((isset($line_data[0])) ? $line_data[0] : "");
$value = trim((isset($line_data[1])) ? $line_data[1] : "");
// we are parsing an alarm for this event
if ($key == "BEGIN" && $value == "VALARM") {
$inside_alarm = true;
$event_alarm = array();
continue;
}
// we finished parsing an alarm for this event
if ($key == "END" && $value == "VALARM") {
$inside_alarm = false;
$event_alarms[] = $event_alarm;
continue;
}
// autoconvert datetime fields to DateTime object
$date_key = (strstr($key,";")) ? strstr($key,";", true) : $key;
$date_format = (strstr($key,";")) ? strstr($key,";") : ";VLAUE=DATE-TIME";
if (in_array($date_key, $this->DTfields)) {
// set date key without format
$key = $date_key;
$timezone = $this->_file_timezone;
// found time zone in date format info
if (strstr($date_format,"TZID")) $timezone = substr($date_format, 5);
$timezone=str_replace("=","",$timezone);
// process all dates if there are more then one and comma seperated
$processed_value = array();
foreach(explode(",", $value) AS $date_value) {
// this is simply a date
if ($date_format == ";VALUE=DATE") $date_value .= "T000000";
// date-time in UTC
if (substr($date_value, -1) == "Z") $timezone = "UTC";
// format date
$date = \DateTime::createFromFormat('Ymd\THis', str_replace('Z', '', $date_value), new \DateTimeZone($timezone));
if ($date !== false) $date->setTimezone(new \DateTimeZone($this->_user_timezone));
if ($date !== false) $processed_value[] = $date;
}
// we have more then one date value then return it as an array
if (count($processed_value) > 1) {
$value = $processed_value;
} else {
if ($date !== false) $value = $date;
}
}
// check if current key was already set
// if this is the case then add value data and turn it into an array
$value_current_key = false;
if ($inside_alarm) {
if (isset($event_alarm[$key])) $value_current_key = $event_alarm[$key];
} else {
if (isset($event[$key])) $value_current_key = $event[$key];
}
// this current key already has data add more
if ($value_current_key !== false) {
// check if data is array and merge
if (is_array($value_current_key)) {
if (is_array($value)) {
$value = array_merge($value_current_key, $value);
} else {
$value = array_merge($value_current_key, array($value));
}
} else {
if (is_array($value)) {
$value = array_merge(array($value_current_key), $value);
} else {
$value = array($value_current_key, $value);
}
}
}
if ($inside_alarm) {
$event_alarm[$key] = $value;
} else {
$event[$key] = $value;
}
}
}
// add alarm data
$event["VALARM"] = $event_alarms;
// unescape every element if string.
return array_map(function($value) {
return (is_string($value) ? stripcslashes($value) : $value);
}, $event);
}
}
class SynchroICSCommand extends Command
{
private $container;
private $em;
private $output;
private $filesystem;
private $rootlog;
protected function configure()
{
$this
->setName('Portal:SynchroICS')
->setDescription('Synchronize external ICS URL')
->setHelp('Synchronize external ICS URL')
->addArgument('cronid', InputArgument::OPTIONAL, 'ID Cron Job')
->addArgument('lastchance', InputArgument::OPTIONAL, 'Lastchance to run the cron')
->addArgument('idcalendar', InputArgument::OPTIONAL, 'ID Calendar to synchronize')
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->container = $this->getApplication()->getKernel()->getContainer();
$this->em = $this->container->get('doctrine')->getEntityManager();
$this->output = $output;
$this->filesystem = new Filesystem();
$this->rootlog = $this->container->get('kernel')->getRootDir()."/../var/logs/";
$alias = $this->container->getParameter('alias');
$idcalendar = $input->getArgument('idcalendar');
$PROXYserver = $this->em->getRepository("CadolesCoreBundle:Config")->findOneBy(["id"=>"PROXYserver"])->getValue();
$PROXYport = $this->em->getRepository("CadolesCoreBundle:Config")->findOneBy(["id"=>"PROXYport"])->getValue();
$this->writelnred('');
$this->writelnred('== Portal:SynchroICS');
$this->writelnred('==========================================================================================================');
$now=new \DateTime('now');
$fgdebug = false;
if($idcalendar=="")
$calendars=$this->em->getRepository("CadolesPortalBundle:Calendar")->findAll();
else
$calendars=$this->em->getRepository("CadolesPortalBundle:Calendar")->findBy(["id"=>$idcalendar]);
foreach($calendars as $calendar) {
switch($calendar->getType()) {
// ICS
case 1:
$this->writeln($calendar->getId()." = ICS = ".$calendar->getUrl());
$client = new CalFileParser();
$PROXYactivate = $this->em->getRepository("CadolesCoreBundle:Config")->findOneBy(["id"=>"PROXYactivate"])->getValue();
try {
if($PROXYactivate)
$cal = $client->parse($calendar->getUrl(), '', array("proxy"=>$PROXYserver.":".$PROXYport));
else
$cal = $client->parse($calendar->getUrl());
if(!is_array($cal)) {
$this->writelnred("Impossible de récupérer le calendrier");
$this->writeln("");
break;
}
$tbuid=array();
foreach($cal as $event) {
$event["SUMMARY"] = (isset($event["SUMMARY"])?$event["SUMMARY"]:"");
$event["DESCRIPTION"] = (isset($event["DESCRIPTION"])?$event["DESCRIPTION"]:"");
$event["DTSTART"] = (isset($event["DTSTART"])?$event["DTSTART"]:"");
$event["DTEND"] = (isset($event["DTEND"])?$event["DTEND"]:"");
$event["ALLDAY"] = (isset($event["ALLDAY"])?$event["ALLDAY"]:"");
$event["SEQUENCE"] = (isset($event["SEQUENCE"])?$event["SEQUENCE"]:"");
$event["UID"] = (isset($event["UID"])?$event["UID"]:"");
if($fgdebug) {
echo "<br>";
echo "<br>SUMMARY = ".$event["SUMMARY"];
echo "<br>DESCRIPTION = ".$event["DESCRIPTION"];
echo "<br>DTSTART = ".$event["DTSTART"]->format('d/m/Y H:i:s');
echo "<br>DTEND = ".$event["DTEND"]->format('d/m/Y H:i:s');
echo "<br>ALLDAY = ".$event["ALLDAY"];
echo "<br>SEQUENCE = ".$event["SEQUENCE"];
echo "<br>UID = ".$event["UID"];
}
if($event["UID"]!="") {
// On sauvegarde l'uid
array_push($tbuid,$event["UID"]);
// Allday ?
$allday=0;
if($event["ALLDAY"]==1||$event["SEQUENCE"]>=1)
$allday=1;
// Cas particulier
if($allday==0&&$event["SEQUENCE"]==0) {
if($event["DTSTART"]->format('H:i:s')=='00:00:00'&&$event["DTEND"]->format('H:i:s')=='00:00:00')
$allday=1;
}
// On recherche l'uid dans le calendrier
$toset=false;
$calendarevent=$this->em->getRepository("CadolesPortalBundle:Calendarevent")->findOneBy(["uid"=>$event["UID"]]);
if($calendarevent) {
if($event["SUMMARY"]!=$calendarevent->getName()||$event["DESCRIPTION"]!=$calendarevent->getDescription()||$event["DTSTART"]!=$calendarevent->getStart()||$event["DTEND"]!=$calendarevent->getEnd()||$allday!=$calendarevent->getAllday()) {
$toset=true;
}
}
else {
$toset=true;
$calendarevent=new Calendarevent();
}
if($toset) {
$calendarevent->setName($event["SUMMARY"]);
$calendarevent->setDescription($event["DESCRIPTION"]);
$calendarevent->setStart($event["DTSTART"]);
$calendarevent->setEnd($event["DTEND"]);
$calendarevent->setAllday($allday);
$calendarevent->setUid($event["UID"]);
$calendarevent->setCalendar($calendar);
$this->em->persist($calendarevent);
$this->em->flush();
}
}
}
// Purge des évènements plus présent
$calendarevents=$this->em->getRepository("CadolesPortalBundle:Calendarevent")->findBy(["calendar"=>$calendar]);
foreach($calendarevents as $calendarevent) {
if(!in_array($calendarevent->getUid(),$tbuid)) {
$this->em->remove($calendarevent);
$this->em->flush();
}
}
}
catch (Exception $e) {
$this->writelnred($e->__toString());
}
break;
// CalDav
case 2:
// Création du client calDAV
$this->writeln("");
$this->writeln($calendar->getId()." = CalDAV = ".$calendar->getUrl());
// Récupération du mode proxy
$PROXYactivate = $this->em->getRepository("CadolesCoreBundle:Config")->findOneBy(["id"=>"PROXYactivate"])->getValue();
// En mode proxy on tente la connection via le proxy
if($PROXYactivate) {
$client = new SimpleCalDAVClient();
try {
@$client->connect($calendar->getUrl(), $calendar->getLogin(), $calendar->getPasswordDecrypt(),['proxy_host'=>$PROXYserver.":".$PROXYport,'timeout'=>5]);
}
catch (CalDAVException $e) {
// Si KO on tente la meme connection mais sans le proxy
$PROXYactivate = false;
$this->writeln("Tentative de connection sans proxy");
}
}
if(!$PROXYactivate) {
$client = new SimpleCalDAVClient();
try {
@$client->connect($calendar->getUrl(), $calendar->getLogin(), $calendar->getPasswordDecrypt(),['timeout'=>5]);
}
catch (CalDAVException $e) {
$this->writelnred("");
$this->writelnred("KO = ".$e->getMessage());
break;
}
}
$this->writeln("Connected");
$tbuid=array();
$arrayOfCalendars = $client->findCalendars();
foreach($arrayOfCalendars as $calcalendar) {
$this->writeln($calcalendar->getDisplayName());
if($calcalendar->getDisplayName()!=$calendar->getCaldavname()) continue;
$client->setCalendar($calcalendar);
$events = $client->getEvents();
foreach ($events as $calevent) {
$event=$this->icsToArray($calevent->getData())[2];
foreach($event as $key => $value) {
if (strpos($key, 'DTSTART') === 0) $event["DTSTART"]=new \datetime($value);
if (strpos($key, 'DTEND') === 0) $event["DTEND"]=new \datetime($value);
}
$event["SUMMARY"] = (isset($event["SUMMARY"])?$event["SUMMARY"]:"");
$event["DESCRIPTION"] = (isset($event["DESCRIPTION"])?$event["DESCRIPTION"]:"");
$event["ALLDAY"] = (isset($event["ALLDAY"])?$event["ALLDAY"]:"");
$event["SEQUENCE"] = (isset($event["SEQUENCE"])?$event["SEQUENCE"]:"");
$event["UID"] = (isset($event["UID"])?$event["UID"]:"");
$event["DURATION"] = (isset($event["DURATION"])?$event["DURATION"]:"");
if($event["UID"]!="") {
// On sauvegarde l'uid
array_push($tbuid,$event["UID"]);
if(!isset($event["DTEND"])&&isset($event["DURATION"])) {
$event["DTEND"]=clone $event["DTSTART"];
$event["DTEND"]->add(new \DateInterval($event["DURATION"]));
}
// Allday ?
$allday=0;
if($event["ALLDAY"]==1||$event["SEQUENCE"]>=1)
$allday=1;
// Cas particulier
if($allday==0&&$event["SEQUENCE"]==0) {
if($event["DTSTART"]->format('H:i:s')=='00:00:00'&&$event["DTEND"]->format('H:i:s')=='00:00:00')
$allday=1;
}
// On recherche l'uid dans le calendrier
$toset=false;
$calendarevent=$this->em->getRepository("CadolesPortalBundle:Calendarevent")->findOneBy(["uid"=>$event["UID"]]);
if($calendarevent) {
if($event["SUMMARY"]!=$calendarevent->getName()||$event["DESCRIPTION"]!=$calendarevent->getDescription()||$event["DTSTART"]!=$calendarevent->getStart()||$event["DTEND"]!=$calendarevent->getEnd()||$allday!=$calendarevent->getAllday()) {
$toset=true;
}
}
else {
$toset=true;
$calendarevent=new Calendarevent();
}
if($toset) {
$calendarevent->setName($event["SUMMARY"]);
$calendarevent->setDescription($event["DESCRIPTION"]);
$calendarevent->setStart($event["DTSTART"]);
$calendarevent->setEnd($event["DTEND"]);
$calendarevent->setAllday($allday);
$calendarevent->setUid($event["UID"]);
$calendarevent->setCalendar($calendar);
$this->em->persist($calendarevent);
$this->em->flush();
}
}
}
// Purge des évènements plus présent
$calendarevents=$this->em->getRepository("CadolesPortalBundle:Calendarevent")->findBy(["calendar"=>$calendar]);
foreach($calendarevents as $calendarevent) {
if(!in_array($calendarevent->getUid(),$tbuid)) {
$this->em->remove($calendarevent);
$this->em->flush();
}
}
}
break;
}
}
$this->writeln('');
return 1;
}
private function icsToArray($icsFile) {
$icsData = explode("BEGIN:", $icsFile);
foreach($icsData as $key => $value) {
$icsDatesMeta[$key] = explode("\n", $value);
}
foreach($icsDatesMeta as $key => $value) {
foreach($value as $subKey => $subValue) {
if ($subValue != "") {
if ($key != 0 && $subKey == 0) {
$icsDates[$key]["BEGIN"] = $subValue;
} else {
//dump($subValue);
$subValueArr = explode(":", $subValue, 2);
//dump($subValueArr);
if(key_exists(1,$subValueArr)) $icsDates[$key][$subValueArr[0]] = $subValueArr[1];
}
}
}
}
return $icsDates;
}
private function writelnred($string) {
$this->output->writeln('<fg=red>'.$string.'</>');
$this->filesystem->appendToFile($this->rootlog.'cron.log', $string."\n");
}
private function writeln($string) {
$this->output->writeln($string);
$this->filesystem->appendToFile($this->rootlog.'cron.log', $string."\n");
}
}