<?php /** * JEvents Component for Joomla! 3.x * * @version $Id: iCalICSFile.php 3474 2012-04-03 13:40:53Z geraintedwards $ * @package JEvents * @copyright Copyright (C) 2008-2019 GWE Systems Ltd, 2006-2008 JEvents Project Group * @license GNU/GPLv2, see http://www.gnu.org/licenses/gpl-2.0.html * @link http://www.jevents.net */ // no direct access defined( '_JEXEC' ) or die( 'Restricted access' ); use Joomla\String\StringHelper; class iCalICSFile extends JTable { /** @var int Primary key */ var $ics_id = null; var $filename = ""; var $srcURL = ""; var $state = 1; var $access = 0; var $catid = 0; var $label =""; var $created; var $refreshed; // is default ical of its type var $isdefault = 0; var $overlaps = 0; var $created_by = 0; // if true allows for front end refresh via cronjob var $autorefresh = 0; // if true imports ignore embedded cateogry info var $ignoreembedcat= 0; var $icaltype; /** * This holds the raw data as an array * * @var array */ var $data; var $rrule = null; var $vevent; /** * Null Constructor */ public function __construct( &$db ) { parent::__construct( '#__jevents_icsfile', 'ics_id', $db ); $this->access = intval(JEVHelper::getBaseAccess()); } public function _setup($icsid,$catid,$access=0,$state=1, $autorefresh=0, $ignoreembedcat=0){ if ($icsid>0) $this->ics_id = $icsid; $this->created = date( 'Y-m-d H:i:s' ); $this->refreshed = $this->created; $this->catid = $catid; $this->access = $access; $this->state = $state; $this->autorefresh = $autorefresh; $this->ignoreembedcat = $ignoreembedcat; } public function editICalendar($icsid,$catid,$access=0,$state=1, $label=""){ $db = JFactory::getDbo(); $temp = new iCalICSFile($db); $temp->_setup($icsid,$catid,$access,$state); $temp->filename="_from_scratch_"; $temp->icaltype=2; $temp->label = empty($label) ? 'Scratch-'.md5(JevDate::mktime()) : $label; $temp->srcURL =""; $rawText = <<<RAWTEXT BEGIN:VCALENDAR PRODID:-//JEvents Project//JEvents Calendar 1.5.0//EN VERSION:2.0 CALSCALE:GREGORIAN METHOD:PUBLISH X-WR-CALNAME:$label X-WR-TIMEZONE:Europe/London BEGIN:VTIMEZONE TZID:Europe/London X-LIC-LOCATION:Europe/London BEGIN:DAYLIGHT TZOFFSETFROM:+0000 TZOFFSETTO:+0100 TZNAME:BST DTSTART:19700329T010000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU END:DAYLIGHT BEGIN:STANDARD TZOFFSETFROM:+0100 TZOFFSETTO:+0000 TZNAME:GMT DTSTART:19701025T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU END:STANDARD END:VTIMEZONE END:VCALENDAR RAWTEXT; $temp->_icalInfo =& JEVHelper::iCalInstance("", $rawText); return $temp; } public static function newICSFileFromURL($uploadURL,$icsid,$catid,$access=0,$state=1, $label="", $autorefresh=0, $ignoreembedcat=0){ $db = JFactory::getDbo(); $temp = new iCalICSFile($db); $temp->_setup($icsid,$catid,$access,$state,$autorefresh,$ignoreembedcat); if ($access==0){ $temp->access = intval(JEVHelper::getBaseAccess()); } if (false !== stripos($uploadURL, 'webcal://')){ $headers = @get_headers( $uploadURL); if ($headers && $headers[0] == 'HTTP/1.1 200 OK') { $uploadURL = $uploadURL; } else { $headers = get_headers( str_replace('webcal://', 'https://', $uploadURL)); if ($headers && $headers[0] == 'HTTP/1.1 200 OK') { $uploadURL = str_replace('webcal://', 'https://', $uploadURL); } else { $uploadURL = str_replace('webcal://', 'http://', $uploadURL); } } } /* $urlParts = parse_url($uploadURL); $pathParts = pathinfo($urlParts['path']); if (isset($pathParts['basename'])) $temp->filename = $pathParts['basename']; else $temp->filename = $uploadURL; */ $temp->filename = 'Remote-' . md5($uploadURL); $temp->icaltype=0; // i.e. from URL if ($label!="") $temp->label = $label; else $temp->label = $temp->filename; $temp->srcURL = $uploadURL; // Store the ical in the registry so we can retrieve the access level $registry = JRegistry::getInstance("jevents"); $registry->set("jevents.icsfile", $temp); if (false === ($temp->_icalInfo = JEVHelper::iCalInstance($uploadURL)) ) { return false; } return $temp; } public static function newICSFileFromFile($file,$icsid,$catid,$access=0,$state=1, $label="", $autorefresh=0, $ignoreembedcat=0){ $db = JFactory::getDbo(); $temp = new iCalICSFile($db); $temp->_setup($icsid,$catid,$access,$state,$autorefresh,$ignoreembedcat); if ($access==0){ $temp->access = intval(JEVHelper::getBaseAccess()); } $temp->srcURL = ""; $temp->filename = $file['name']; $temp->icaltype=1; // i.e. from file if ($label!="") $temp->label = $label; else $temp->label = $temp->filename; if (false === ($temp->_icalInfo =& JEVHelper::iCalInstance($file['tmp_name']))) { return false; } return $temp; } /** * Used to create Ical from raw strring */ public function newICSFileFromString($rawtext,$icsid,$catid,$access=0,$state=1, $label="", $autorefresh=0, $ignoreembedcat=0){ $db = JFactory::getDbo(); $temp = null; $temp = new iCalICSFile($db); if ($icsid>0){ $temp->load($icsid); $temp->icaltype=2; // i.e. from file } else { $temp->_setup($icsid,$catid,$access,$state,$autorefresh,$ignoreembedcat); $temp->srcURL = ""; $temp->filename = "_from_events_cat".$catid; $temp->icaltype=2; // i.e. from file if ($label!="") $temp->label = $label; else $temp->label = $temp->filename; } $temp->_icalInfo =& JEVHelper::iCalInstance("",$rawtext); return $temp; } /** * Method that updates details about the ical but does not touch the events contained * */ public function updateDetails(){ if (parent::store() && $this->isdefault==1 && $this->icaltype==2){ // set all the others to 0 $db = JFactory::getDbo(); $sql = "UPDATE #__jevents_icsfile SET isdefault=0 WHERE icaltype=2 AND ics_id<>".$this->ics_id; $db->setQuery($sql); $db->execute(); } } /** * override store function to return id to pass to iCalEvent and store the events too! * * @param int $catid - forced category for the underlying events */ public function store($catid=false , $cleanup=true , $flush =true) { $user = JFactory::getUser(); $guest = $user->get('guest'); @ini_set("memory_limit","256M"); @ini_set("max_execution_time","300"); // clean out the cache $cache = JFactory::getCache('com_jevents'); $cache->clean(JEV_COM_COMPONENT); static $categories; if (is_null($categories)){ $sql = "SELECT * FROM #__categories WHERE extension='com_jevents'"; $db = JFactory::getDbo(); $db->setQuery($sql); $categories = $db->loadObjectList('title'); } if (!$catid){ $catid = $this->catid; } if ($id = $this->isDuplicate()){ $this->ics_id = $id; // TODO return warning about duplicate file name VERY IMPORTANT TO DECIDE WHAT TO DO // UIDs for the vcalendar itself are not compulsory } // There is a better way to find // duplicate key info trap repeated insertions - I should if (!parent::store()){ echo "failed to store icsFile<br/>"; } else if ($this->isdefault==1 && $this->icaltype==2){ // set all the others to 0 $db = JFactory::getDbo(); $sql = "UPDATE #__jevents_icsfile SET isdefault=0 WHERE icaltype=2 AND ics_id<>".$this->ics_id; $db->setQuery($sql); $db->execute(); } // find the full set of ids currently in the calendar so taht we can remove cancelled ones $db = JFactory::getDbo(); $sql = "SELECT ev_id, uid, lockevent FROM #__jevents_vevent WHERE icsid=".$this->ics_id ;//. " AND catid=".$catid; $db->setQuery($sql); $existingevents = $db->loadObjectList('ev_id'); // insert the data - this will need to deal with multiple rrule values foreach ($this->_icalInfo->vevents as & $vevent) { if (!$vevent->isCancelled() && !$vevent->isRecurrence()){ // if existing category then use it if (!$this->ignoreembedcat && JString::strlen($vevent->_detail->categories)>0){ $evcat = explode(",",$vevent->_detail->categories); if (count($evcat)>0) { include_once(JEV_ADMINLIBS."categoryClass.php"); foreach ($evcat as $ct){ // if no such category then create it/them if(!array_key_exists(trim($ct),$categories)){ $cat = new JEventsCategory($db); $cat->bind(array("title"=>trim($ct))); $cat->published=1; $cat->check(); if (!$cat->store()){ //var_dump($cat->getErrors()); die(JText::plural('COM_JEVENTS_MANAGE_CALENDARS_ICAL_IMPORT_FAILED_TO_CREATE_CAT', $ct)); } } } // must reset the list of categories now $sql = "SELECT * FROM #__categories WHERE extension='com_jevents'"; $db->setQuery($sql); $categories = $db->loadObjectList('title'); $params = JComponentHelper::getParams(JEV_COM_COMPONENT); if ($params->get("multicategory",0) ){ $vevent->catid = array(); foreach ($evcat as $ct){ $vevent->catid[] = $categories[trim($ct)]->id; } } else { $vevent->catid = $categories[trim($evcat[0])]->id; } } } else { $params = JComponentHelper::getParams(JEV_COM_COMPONENT); if ($params->get("multicategory",0) && !is_array($catid)){ $vevent->catid = array($catid); } else { $vevent->catid = $catid; } } // These now gets picked up in the event //$vevent->access = $this->access; $vevent->icsid = $this->ics_id; // The refreshed field is used to track dropped events on reload $vevent->refreshed = $this->refreshed; // make sure I don't add the same events more than once if ($matchingEvent = $vevent->matchingEventDetails()){ $vevent->ev_id = $matchingEvent->ev_id; $vevent->_detail->evdet_id = $matchingEvent->evdet_id; unset($existingevents[$vevent->ev_id]); } else { $vevent->state = $this->state; } $vevent->lockevent = 0; // if the event is locked then skip this row if ($matchingEvent && $matchingEvent->lockevent){ $vevent->lockevent = 1; continue; } // handle events running over midnight $params = JComponentHelper::getParams(JEV_COM_COMPONENT); $icalmultiday = $params->get("icalmultiday",0); $icalmultiday24h = $params->get("icalmultiday24h",0); // These booleans are the wrong way around. $vevent->_detail->multiday = $icalmultiday?0:1; if ($vevent->_detail->dtend-$vevent->_detail->dtstart < 86400) { $vevent->_detail->multiday = $icalmultiday24h?0:1; } // force creator if appropriate if ($this->created_by>0){ $vevent->created_by = $this->created_by; // force override of creator $vevent->store(false, true); } else { $vevent->store(); } // trigger post save plugins e.g. AutoTweet $dispatcher = JEventDispatcher::getInstance(); JPluginHelper::importPlugin("jevents"); if ($matchingEvent) { JRequest::setVar("evid", $vevent->ev_id); } else { JRequest::setVar("evid", 0); } $repetitions = $vevent->getRepetitions(true); $vevent->storeRepetitions(); if (isset($vevent->state) && !isset($vevent->published)) { $vevent->published = $vevent->state ; } // not a dry run of course! $res = JFactory::getApplication()->triggerEvent( 'onAfterSaveEvent' , array(&$vevent, false)); // Save memory by clearing out the repetitions we no longer need $repetitions = null; $vevent->_repetitions = null; //$vevent=null; //echo "Event Data read in<br/>"; //echo "memory = ".memory_get_usage()." ".memory_get_usage(true)."<br/>"; if ($flush){ ob_flush(); flush(); } } } unset($vevent); // Having stored all the repetitions - remove the cancelled instances // this should be done as a batch but for now I'll do them one at a time foreach ($this->_icalInfo->vevents as $vevent) { // if the event is locked then skip this row if (!is_null($vevent) && $vevent->lockevent) continue; if (!is_null($vevent) && ($vevent->isCancelled() || $vevent->isRecurrence())){ // if existing category then use it if (JString::strlen($vevent->_detail->categories)>0){ if (count($evcat)>0) { include_once(JEV_ADMINLIBS."categoryClass.php"); foreach ($evcat as $ct){ // if no such category then create it/them if(!array_key_exists($ct,$categories)){ $cat = new JEventsCategory($db); $cat->bind(array("title"=>$ct)); $cat->published=1; $cat->check(); $cat->store(); } } // must reset the list of categories now $sql = "SELECT * FROM #__categories WHERE extension='com_jevents'"; $db->setQuery($sql); $categories = $db->loadObjectList('title'); $params = JComponentHelper::getParams(JEV_COM_COMPONENT); if ($params->get("multicategory",0) && count($evcat)>1){ $vevent->catid = array(); foreach ($evcat as $ct){ $vevent->catid[] = $categories[$ct]->id; } } else { $vevent->catid = $categories[$evcat[0]]->id; } } } else { $params = JComponentHelper::getParams(JEV_COM_COMPONENT); if ($params->get("multicategory",0) ){ $vevent->catid = array($catid); } else { $vevent->catid = $catid; } } $vevent->access = $this->access; $vevent->state = $this->state; $vevent->icsid = $this->ics_id; // make sure I don't add the same events more than once if ($matchingEvent = $vevent->matchingEventDetails()){ $vevent->ev_id = $matchingEvent->ev_id; } if ($vevent->isCancelled()) { $vevent->cancelRepetition(); } else { // replace event that is only 'adjusted' with the correct settings $vevent->adjustRepetition($matchingEvent); } } } // Now remove existing events that have been deleted if ($cleanup){ if(count($existingevents)>0){ $todelete = array(); foreach ($existingevents as $event) { $todelete[]= $event->ev_id; } $veventidstring = implode(",",$todelete); $query = "SELECT DISTINCT (eventdetail_id) FROM #__jevents_repetition WHERE eventid IN ($veventidstring)"; $db->setQuery( $query); $detailids = $db->loadColumn(); $detailidstring = implode(",",$detailids); $query = "DELETE FROM #__jevents_rrule WHERE eventid IN ($veventidstring)"; $db->setQuery( $query); $db->execute(); $query = "DELETE FROM #__jevents_repetition WHERE eventid IN ($veventidstring)"; $db->setQuery( $query); $db->execute(); $query = "DELETE FROM #__jevents_exception WHERE eventid IN ($veventidstring)"; $db->setQuery( $query); $db->execute(); if (JString::strlen($detailidstring)>0){ $query = "DELETE FROM #__jevents_vevdetail WHERE evdet_id IN ($detailidstring)"; $db->setQuery( $query); $db->execute(); } $query = "DELETE FROM #__jevents_vevent WHERE ev_id IN ($veventidstring)"; $db->setQuery( $query); $db->execute(); $ex_count = count($existingevents); if($guest !== 1) { JFactory::getApplication()->enqueueMessage(JText::plural('COM_JEVENTS_MANAGE_CALENDARS_ICAL_IMPORT_DELETED_EVENTS', $ex_count)); } } } $count = count($this->_icalInfo->vevents) ; unset($this->_icalInfo->vevents); if($guest !== 1) { JFactory::getApplication()->enqueueMessage(JText::plural('COM_JEVENTS_MANAGE_CALENDARS_ICAL_IMPORT_N_EVENTS_PROCESSED', $count)); } } // find if icsFile already imported public function isDuplicate(){ $sql = "SELECT ics_id from #__jevents_icsfile as ics WHERE ics.label = " . $this->_db->quote($this->label) ; $this->_db->setQuery($sql); $matches = $this->_db->loadObjectList(); if (count($matches)>0 && isset($matches[0]->ics_id)) { return $matches[0]->ics_id; } return false; } // Method to store the events WITHOUT storing the calendar itself - used in frontend imports public function storeEvents($catid=false , $flush =true) { // clean out the cache $cache = JFactory::getCache('com_jevents'); $cache->clean(JEV_COM_COMPONENT); $params = JComponentHelper::getParams(JEV_COM_COMPONENT); static $categories; if (is_null($categories)){ $db = JFactory::getDbo(); $sql = "SELECT * FROM #__categories WHERE extension='com_jevents'"; $db->setQuery($sql); $categories = $db->loadObjectList('title'); } if (!$catid){ $catid = $this->catid; } // insert the data - this will need to deal with multiple rrule values foreach ($this->_icalInfo->vevents as & $vevent) { // Do I need to remove a duplicate event? $sql = "SELECT * FROM #__jevents_vevent WHERE uid=".$db->Quote($vevent->uid); $db->setQuery($sql); $duplicate = $db->loadObject(); if ($duplicate && !$vevent->isCancelled() && !$vevent->isRecurrence()){ $veventidstring = $duplicate->ev_id; // TODO the ruccurences should take care of all of these?? // This would fail if all recurrances have been 'adjusted' $query = "SELECT DISTINCT (eventdetail_id) FROM #__jevents_repetition WHERE eventid IN ($veventidstring)"; $db->setQuery( $query); $detailids = $db->loadColumn(); $detailidstring = implode(",",$detailids); $query = "DELETE FROM #__jevents_rrule WHERE eventid IN ($veventidstring)"; $db->setQuery( $query); $db->execute(); $query = "DELETE FROM #__jevents_repetition WHERE eventid IN ($veventidstring)"; $db->setQuery( $query); $db->execute(); $query = "DELETE FROM #__jevents_exception WHERE eventid IN ($veventidstring)"; $db->setQuery( $query); $db->execute(); if (JString::strlen($detailidstring)>0){ $query = "DELETE FROM #__jevents_vevdetail WHERE evdet_id IN ($detailidstring)"; $db->setQuery( $query); $db->execute(); // I also need to clean out associated custom data // just incase we don't have jevents plugins registered yet JPluginHelper::importPlugin("jevents"); $res = JFactory::getApplication()->triggerEvent( 'onDeleteEventDetails' , array($detailidstring)); } $query = "DELETE FROM #__jevents_vevent WHERE ev_id IN ($veventidstring)"; $db->setQuery( $query); $db->execute(); // I also need to delete custom data $res = JFactory::getApplication()->triggerEvent( 'onDeleteCustomEvent' , array(&$veventidstring)); } if (!$vevent->isCancelled() && !$vevent->isRecurrence()){ // if existing category then use it if (!$this->ignoreembedcat && JString::strlen($vevent->_detail->categories)>0){ $evcat = explode(",",$vevent->_detail->categories); if (count($evcat)>0 && array_key_exists($evcat[0],$categories)){ if ($params->get("multicategory",0) && count($evcat)>1){ $vevent->catid = array(); foreach ($evcat as $ct){ $vevent->catid[] = $categories[trim($ct)]->id; } } if ($params->get("multicategory",0) && count($evcat)==1){ $vevent->catid = array(); $vevent->catid[] = $categories[$evcat[0]]->id; } else { $vevent->catid = $categories[$evcat[0]]->id; } } // if no such category then create it else if (count($evcat)>0) { include_once(JEV_ADMINLIBS."categoryClass.php"); $cat = new JEventsCategory($db); $cat->bind(array("title"=>$evcat[0])); $cat->published=1; $cat->store(); if ($params->get("multicategory",0)){ $vevent->catid[] = $cat->id; } else { $vevent->catid = $cat->id; } // must reset the list of categories now $sql = "SELECT * FROM #__categories WHERE extension='com_jevents'"; $db->setQuery($sql); $categories = $db->loadObjectList('title'); } } else { if ($params->get("multicategory",0)){ $vevent->catid[] = $catid; } else { $vevent->catid = $catid; } } $vevent->icsid = $this->ics_id; // The refreshed field is used to track dropped events on reload $vevent->refreshed = $this->refreshed; // handle events running over midnight $params = JComponentHelper::getParams(JEV_COM_COMPONENT); $icalmultiday = $params->get("icalmultiday",0); $icalmultiday24h = $params->get("icalmultiday24h",0); // These booleans are the wrong way around. $vevent->_detail->multiday = $icalmultiday?0:1; if ($vevent->_detail->dtend-$vevent->_detail->dtstart < 86400) { $vevent->_detail->multiday = $icalmultiday24h?0:1; } // force creator if appropriate if ($this->created_by>0){ $vevent->created_by = $this->created_by; // force override of creator $vevent->store(false, true); } else { $vevent->store(); } $repetitions = $vevent->getRepetitions(true); $vevent->storeRepetitions(); // Save memory by clearing out the repetitions we no longer need $vevent->_repetitions = null; $repetitions = null; echo "Event Data read in<br/>"; //echo "memory = ".memory_get_usage()." ".memory_get_usage(true)."<br/>"; if ($flush){ ob_flush(); flush(); } } } unset($vevent); // Having stored all the repetitions - remove the cancelled instances // this should be done as a batch but for now I'll do them one at a time foreach ($this->_icalInfo->vevents as $vevent) { if (!is_null($vevent) && ($vevent->isCancelled() || $vevent->isRecurrence())){ // if existing category then use it if (JString::strlen($vevent->_detail->categories)>0){ $evcat = explode(",",$vevent->_detail->categories); if (count($evcat)>0 && array_key_exists($evcat[0],$categories)){ if ($params->get("multicategory",0) && count($evcat)>1){ $vevent->catid = array(); foreach ($evcat as $ct){ $vevent->catid[] = $categories[$ct]->id; } } else { $vevent->catid = $categories[$evcat[0]]->id; } } // if no such category then create it else if (count($evcat)>0) { include_once(JEV_ADMINLIBS."categoryClass.php"); $cat = new JEventsCategory($db); $cat->bind(array("title"=>$evcat[0])); $cat->published=1; $cat->check(); $cat->store(); if ($params->get("multicategory",0)){ $vevent->catid[] = $cat->id; } else { $vevent->catid = $cat->id; } // must reset the list of categories now $sql = "SELECT * FROM #__categories WHERE extension='com_jevents'"; $db->setQuery($sql); $categories = $db->loadObjectList('title'); } } else { if ($params->get("multicategory",0)){ $vevent->catid[] = $catid; } else { $vevent->catid = $catid; } } $vevent->access = $this->access; $vevent->state = $this->state; $vevent->icsid = $this->ics_id; // make sure I don't add the same events more than once if ($matchingEvent = $vevent->matchingEventDetails()){ $vevent->ev_id = $matchingEvent->ev_id; } if ($vevent->isCancelled()) { $vevent->cancelRepetition(); } else { // replace event that is only 'adjusted' with the correct settings $vevent->adjustRepetition($matchingEvent); } } } return count($this->_icalInfo->vevents); } }