* @copyright Copyright (C) 2006 - 2017 by Dan Cogliano * @license GNU GPLv3 * @link http://icalendar.org/php-library.html */ // No direct access defined('_ZAPCAL') or die( 'Restricted access' ); /** * Object for storing an unfolded iCalendar line * * The ZCiCalDataNode class contains data from an unfolded iCalendar line * */ class ZCiCalDataNode { /** * The name of the node * * @var string */ var $name = ""; /** * Node parameters (before the colon ":") * * @var array */ var $parameter=array(); /** * Node values (after the colon ":") * * @var array */ var $value=array(); /** * Create an object from an unfolded iCalendar line * * @param string $line An unfolded iCalendar line * * @return void * */ function __construct( $line ) { //echo "ZCiCalDataNode($line)
\n"; //separate line into parameters and value // look for colon separating name or parameter and value // first change any escaped colons temporarily to make it easier $tline = str_replace("\\:", "`~", $line); // see if first colon is inside a quoted string $i = 0; $datafind = false; $inquotes = false; while(!$datafind && ($i < strlen($tline))) { //echo "$i: " . $tline[$i] . ", ord() = " . ord($tline[$i]) . "
\n"; if(!$inquotes && $tline[$i] == ':') $datafind=true; else{ $i += 1; if(substr($tline,$i,1) == '"') $inquotes = !$inquotes; } } if($datafind){ $value = str_replace("`~","\\:",substr($line,$i+1)); // fix escaped characters (don't see double quotes in spec but Apple apparently uses it in iCal) $value = str_replace(array('\\N' , '\\n', '\\"' ), array("\n", "\n" , '"'), $value); $tvalue = str_replace("\\,", "`~", $value); //echo "value: " . $tvalue . "
\n"; $tvalue = explode(",",$tvalue); $value = str_replace("`~","\\,",$tvalue); $this->value = $value; } $parameter = trim(substr($line,0,$i)); $parameter = str_replace("\\;", "`~", $parameter); $parameters = explode(";", $parameter); $parameters = str_replace("`~", "\\;", $parameters); $this->name = array_shift($parameters); foreach($parameters as $parameter){ $pos = strpos($parameter,"="); if($pos > 0){ $param = substr($parameter,0,$pos); $paramvalue = substr($parameter,$pos+1); $tvalue = str_replace("\\,", "`~", $paramvalue); //$tvalue = explode(",",$tvalue); $paramvalue = str_replace("`~","\\,",$tvalue); $this->parameter[strtolower($param)] = $paramvalue; //$this->paramvalue[] = $paramvalue; } } } /** * getName() * * Return the name of the object * * @return string */ function getName(){ return $this->name; } /** * Get $ith parameter from array * @param int $i * * @return var */ function getParameter($i){ return $this->parameter[$i]; } /** * Get parameter array * * @return array */ function getParameters(){ return $this->parameter; } /** * Get comma separated values * * @return string */ function getValues(){ return implode(",",$this->value); } } /** * Object for storing a list of unfolded iCalendar lines (ZCiCalDataNode objects) * * @property object $parentnode Parent of this node * * @property array $child Array of children for this node * * @property data $data Array of data for this node * * @property object $next Next sibling of this node * * @property object $prev Previous sibling of this node */ class ZCiCalNode { /** * The name of the node * * @var string */ var $name=""; /** * The parent of this node * * @var object */ var $parentnode=null; /** * Array of children for this node * * @var array */ var $child= array(); /** * Array of $data for this node * * @var array */ var $data= array(); /** * Next sibling of this node * * @var object */ var $next=null; /** * Previous sibling of this node * * @var object */ var $prev=null; /** * Create ZCiCalNode * * @param string $_name Name of node * * @param object $_parent Parent node for this node * * @param bool $first Is this the first child for this parent? */ function __construct( $_name, & $_parent, $first = false) { $this->name = $_name; $this->parentnode = $_parent; if($_parent != null){ if(count($this->parentnode->child) > 0) { if($first) { $first = & $this->parentnode->child[0]; $first->prev = & $this; $this->next = & $first; } else { $prev =& $this->parentnode->child[count($this->parentnode->child)-1]; $prev->next =& $this; $this->prev =& $prev; } } if($first) { array_unshift($this->parentnode->child, $this); } else { $this->parentnode->child[] =& $this; } } /* echo "creating " . $this->getName(); if($_parent != null) echo " child of " . $_parent->getName() . "/" . count($this->parentnode->child); echo "
"; */ } /** * Return the name of the object * * @return string */ function getName() { return $this->name; } /** * Add node to list * * @param object $node * */ function addNode($node) { if(array_key_exists($node->getName(), $this->data)) { if(!is_array($this->data[$node->getName()])) { $this->data[$node->getName()] = array($this->data[$node->getName()]); } $this->data[$node->getName()][] = $node; } else { $this->data[$node->getName()] = $node; } } /** * Get Attribute * * @param int $i array id of attribute to get * * @return string */ function getAttrib($i) { return $this->attrib[$i]; } /** * Set Attribute * * @param string $value value of attribute to set * */ function setAttrib($value) { $this->attrib[] = $value; } /** * Get the parent object of this object * * @return object parent of this object */ function &getParent() { return $this->parentnode; } /** * Get the first child of this object * * @return object The first child */ function &getFirstChild(){ static $nullguard = null; if(count($this->child) > 0) { //echo "moving from " . $this->getName() . " to " . $this->child[0]->getName() . "
"; return $this->child[0]; } else return $nullguard; } /** * Print object tree in HTML for debugging purposes * * @param object $node select part of tree to print, or leave blank for full tree * * @param int $level Level of recursion (usually leave this blank) * * @return string - HTML formatted display of object tree */ function printTree(& $node=null, $level=1){ $level += 1; $html = ""; if($node == null) $node = $this->parentnode; if($level > 5) { die("levels nested too deep
\n"); //return; } for($i = 0 ; $i < $level; $i ++) $html .= "+"; $html .= $node->getName() . "
\n"; foreach ($node->child as $c){ $html .= $node->printTree($c,$level); } $level -= 1; return $html; } /** * export tree to icalendar format * * @param object $node Top level node to export * * @param int $level Level of recursion (usually leave this blank) * * @return string iCalendar formatted output */ function export(& $node=null, $level=0){ $txtstr = ""; if($node == null) $node = $this; if($level > 5) { //die("levels nested too deep
\n"); throw new Exception("levels nested too deep"); } $txtstr .= "BEGIN:" . $node->getName() . "\r\n"; if(property_exists($node,"data")) foreach ($node->data as $d){ if(is_array($d)) { foreach ($d as $c) { //$txtstr .= $node->export($c,$level + 1); $p = ""; $params = @$c->getParameters(); if(count($params) > 0) { foreach($params as $key => $value){ $p .= ";" . strtoupper($key) . "=" . $value; } } $txtstr .= $this->printDataLine($c, $p); } } else { $p = ""; $params = @$d->getParameters(); if(count($params) > 0) { foreach($params as $key => $value){ $p .= ";" . strtoupper($key) . "=" . $value; } } $txtstr .= $this->printDataLine($d, $p); /* $values = $d->getValues(); // don't think we need this, Sunbird does not like it in the EXDATE field //$values = str_replace(",", "\\,", $values); $line = $d->getName() . $p . ":" . $values; $line = str_replace(array("
","
","
","
0) { $linewidth = ($linecount == 0? 75 : 74); $linesize = (strlen($line) > $linewidth? $linewidth: strlen($line)); if($linecount > 0) $txtstr .= " "; $txtstr .= substr($line,0,$linesize) . "\r\n"; $linecount += 1; $line = substr($line,$linewidth); } */ } //echo $line . "\n"; } if(property_exists($node,"child")) foreach ($node->child as $c){ $txtstr .= $node->export($c,$level + 1); } $txtstr .= "END:" . $node->getName() . "\r\n"; return $txtstr; } /** * print an attribute line * @param object $d attributes * @param object $p properties * */ function printDataLine($d, $p) { $txtstr = ""; $values = $d->getValues(); // don't think we need this, Sunbird does not like it in the EXDATE field //$values = str_replace(",", "\\,", $values); $line = $d->getName() . $p . ":" . $values; $line = str_replace(array("
","
","
","
0) { $linewidth = ($linecount == 0? 75 : 74); $linesize = (strlen($line) > $linewidth? $linewidth: strlen($line)); if($linecount > 0) $txtstr .= " "; $txtstr .= substr($line,0,$linesize) . "\r\n"; $linecount += 1; $line = substr($line,$linewidth); } return $txtstr; } } /** * * The main iCalendar object containing ZCiCalDataNodes and ZCiCalNodes. * */ class ZCiCal { /** * The root node of the object tree * * @var object */ var $tree=null; /** * The most recently created node in the tree * * @var object */ var $curnode=null; /** * The main iCalendar object containing ZCiCalDataNodes and ZCiCalNodes. * * use maxevents and startevent to read events in multiple passes (to save memory) * * @param string $data icalendar feed string (empty if creating new feed) * * @param int $maxevents maximum # of events to read * * @param int $startevent starting event to read * * @return void * * */ function __construct($data = "", $maxevents = 1000000, $startevent = 0) { if($data != ""){ // unfold lines // first change all eol chars to "\n" $data = str_replace(array("\r\n", "\n\r", "\n", "\r"), "\n", $data); // now unfold lines //$data = str_replace(array("\n ", "\n "),"!?", $data); $data = str_replace(array("\n ", "\n "),"", $data); // replace special iCal chars $data = str_replace(array("\\\\","\,"),array("\\",","), $data); // parse each line $lines = explode("\n", $data); $linecount = 0; $eventcount = 0; $eventpos = 0; foreach($lines as $line) { //$line = str_replace("!?", "\n", $line); // add nl back into descriptions // echo ($linecount + 1) . ": " . $line . "
"; if(substr($line,0,6) == "BEGIN:") { // start new object $name = substr($line,6); if($name == "VEVENT") { if($eventcount < $maxevents && $eventpos >= $startevent) { $this->curnode = new ZCiCalNode($name, $this->curnode); if($this->tree == null) $this->tree = $this->curnode; } } else { $this->curnode = new ZCiCalNode($name, $this->curnode); if($this->tree == null) $this->tree = $this->curnode; } //echo "new node: " . $this->curnode->name . "
\n"; /* if($this->curnode->getParent() != null) echo "parent of " . $this->curnode->getName() . " is " . $this->curnode->getParent()->getName() . "
"; else echo "parent of " . $this->curnode->getName() . " is null
"; */ } else if(substr($line,0,4) == "END:") { $name = substr($line,4); if($name == "VEVENT") { if($eventcount < $maxevents && $eventpos >= $startevent) { $eventcount++; if($this->curnode->getName() != $name) { //panic, mismatch in iCal structure //die("Can't read iCal file structure, expecting " . $this->curnode->getName() . " but reading $name instead"); throw new Exception("Can't read iCal file structure, expecting " . $this->curnode->getName() . " but reading $name instead"); } if($this->curnode->getParent() != null) { //echo "moving up from " . $this->curnode->getName() ; $this->curnode = & $this->curnode->getParent(); //echo " to " . $this->curnode->getName() . "
"; //echo $this->curnode->getName() . " has " . count($this->curnode->child) . " children
"; } } $eventpos++; } else { if($this->curnode->getName() != $name) { //panic, mismatch in iCal structure //die("Can't read iCal file structure, expecting " . $this->curnode->getName() . " but reading $name instead"); throw new Exception("Can't read iCal file structure, expecting " . $this->curnode->getName() . " but reading $name instead"); } if($this->curnode->getParent() != null) { //echo "moving up from " . $this->curnode->getName() ; $this->curnode = & $this->curnode->getParent(); //echo " to " . $this->curnode->getName() . "
"; //echo $this->curnode->getName() . " has " . count($this->curnode->child) . " children
"; } } } else { $datanode = new ZCiCalDataNode($line); if($this->curnode->getName() == "VEVENT") { if($eventcount < $maxevents && $eventpos >= $startevent) { if($datanode->getName() == "EXDATE") { if(!array_key_exists($datanode->getName(),$this->curnode->data)) { $this->curnode->data[$datanode->getName()] = $datanode; } else { $this->curnode->data[$datanode->getName()]->value[] = $datanode->value[0]; } } else { if(!array_key_exists($datanode->getName(),$this->curnode->data)) { $this->curnode->data[$datanode->getName()] = $datanode; } else { $tnode = $this->curnode->data[$datanode->getName()]; $this->curnode->data[$datanode->getName()] = array(); $this->curnode->data[$datanode->getName()][] = $tnode; $this->curnode->data[$datanode->getName()][] = $datanode; } } } } else { if($datanode->getName() == "EXDATE") { if(!array_key_exists($datanode->getName(),$this->curnode->data)) { $this->curnode->data[$datanode->getName()] = $datanode; } else { $this->curnode->data[$datanode->getName()]->value[] = $datanode->value[0]; } } else { if(!array_key_exists($datanode->getName(),$this->curnode->data)) { $this->curnode->data[$datanode->getName()] = $datanode; } else { $tnode = $this->curnode->data[$datanode->getName()]; $this->curnode->data[$datanode->getName()] = array(); $this->curnode->data[$datanode->getName()][] = $tnode; $this->curnode->data[$datanode->getName()][] = $datanode; } } } } $linecount++; } } else { $name = "VCALENDAR"; $this->curnode = new ZCiCalNode($name, $this->curnode); $this->tree = $this->curnode; $datanode = new ZCiCalDataNode("VERSION:2.0"); $this->curnode->data[$datanode->getName()] = $datanode; $datanode = new ZCiCalDataNode("PRODID:-//ZContent.net//ZapCalLib 1.0//EN"); $this->curnode->data[$datanode->getName()] = $datanode; $datanode = new ZCiCalDataNode("CALSCALE:GREGORIAN"); $this->curnode->data[$datanode->getName()] = $datanode; $datanode = new ZCiCalDataNode("METHOD:PUBLISH"); $this->curnode->data[$datanode->getName()] = $datanode; } } /** * CountEvents() * * Return the # of VEVENTs in the object * * @return int */ function countEvents() { $count = 0; if(isset($this->tree->child)){ foreach($this->tree->child as $child){ if($child->getName() == "VEVENT") $count++; } } return $count; } /** * CountVenues() * * Return the # of VVENUEs in the object * * @return int */ function countVenues() { $count = 0; if(isset($this->tree->child)){ foreach($this->tree->child as $child){ if($child->getName() == "VVENUE") $count++; } } return $count; } /** * Export object to string * * This function exports all objects to an iCalendar string * * @return string an iCalendar formatted string */ function export() { return $this->tree->export($this->tree); } /** * Get first event in object list * Use getNextEvent() to navigate through list * * @return object The first event, or null */ function &getFirstEvent() { static $nullguard = null; if ($this->countEvents() > 0){ $child = $this->tree->child[0]; $event=false; while(!$event && $child != null){ if($child->getName() == "VEVENT") $event = true; else $child = $child->next; } return $child; } else return $nullguard; } /** * Get next event in object list * * @param object $event The current event object * * @return object Returns the next event or null if past last event */ function &getNextEvent($event){ do{ $event = $event->next; } while($event != null && $event->getName() != "VEVENT"); return $event; } /** * Get first venue in object list * Use getNextVenue() to navigate through list * * @return object The first venue, or null */ function &getFirstVenue() { static $nullguard = null; if ($this->countVenues() > 0){ $child = $this->tree->child[0]; $event=false; while(!$event && $child != null){ if($child->getName() == "VVENUE") $event = true; else $child = $child->next; } return $child; } else return $nullguard; } /** * Get next venue in object list * * @param object $venue The current venue object * * @return object Returns the next venue or null if past last venue */ function &getNextVenue($venue){ do{ $venue = $venue->next; } while($venue != null && $venue->getName() != "VVENUE"); return $venue; } /** * Get first child in object list * Use getNextSibling() and getPreviousSibling() to navigate through list * * @param object $thisnode The parent object * * @return object The child object */ function &getFirstChild(& $thisnode){ $nullvalue = null; if(count($thisnode->child) > 0) { //echo "moving from " . $thisnode->getName() . " to " . $thisnode->child[0]->getName() . "
"; return $thisnode->child[0]; } else return $nullvalue; } /** * Get next sibling in object list * * @param object $thisnode The current object * * @return object Returns the next sibling */ function &getNextSibling(& $thisnode){ return $thisnode->next; } /** * Get previous sibling in object list * * @param object $thisnode The current object * * @return object Returns the previous sibling */ function &getPrevSibling(& $thisnode){ return $thisnode->prev; } /** * Read date/time in iCal formatted string * * @param string iCal formated date/time string * * @return int Unix timestamp * @deprecated Use ZDateHelper::toUnixDateTime() instead */ function toUnixDateTime($datetime){ $year = substr($datetime,0,4); $month = substr($datetime,4,2); $day = substr($datetime,6,2); $hour = 0; $minute = 0; $second = 0; if(strlen($datetime) > 8 && $datetime[8] == "T") { $hour = substr($datetime,9,2); $minute = substr($datetime,11,2); $second = substr($datetime,13,2); } $d1 = mktime($hour, $minute, $second, $month, $day, $year); } /** * fromUnixDateTime() * * Take Unix timestamp and format to iCal date/time string * * @param int $datetime Unix timestamp, leave blank for current date/time * * @return string formatted iCal date/time string * @deprecated Use ZDateHelper::fromUnixDateTimetoiCal() instead */ static function fromUnixDateTime($datetime = null){ date_default_timezone_set('UTC'); if($datetime == null) $datetime = time(); return date("Ymd\THis",$datetime); } /** * fromUnixDate() * * Take Unix timestamp and format to iCal date string * * @param int $datetime Unix timestamp, leave blank for current date/time * * @return string formatted iCal date string * @deprecated Use ZDateHelper::fromUnixDateTimetoiCal() instead */ static function fromUnixDate($datetime = null){ date_default_timezone_set('UTC'); if($datetime == null) $datetime = time(); return date("Ymd",$datetime); } /** * Format into iCal time format from SQL date or SQL date-time format * * @param string $datetime SQL date or SQL date-time string * * @return string iCal formatted string * @deprecated Use ZDateHelper::fromSqlDateTime() instead */ static function fromSqlDateTime($datetime = ""){ if($datetime == "") $datetime = ZDateHelper::toSqlDateTime(); if(strlen($datetime) > 10) return sprintf('%04d%02d%02dT%02d%02d%02d',substr($datetime,0,4),substr($datetime,5,2),substr($datetime,8,2), substr($datetime,11,2),substr($datetime,14,2),substr($datetime,17,2)); else return sprintf('%04d%02d%02d',substr($datetime,0,4),substr($datetime,5,2),substr($datetime,8,2)); } /** * Format iCal time format to either SQL date or SQL date-time format * * @param string $datetime icalendar formatted date or date-time * @return string SQL date or SQL date-time string * @deprecated Use ZDateHelper::toSqlDateTime() instead */ static function toSqlDateTime($datetime = ""){ if($datetime == "") return ZDateHelper::toSqlDateTime(); if(strlen($datetime) > 10) return sprintf('%04d-%02d-%02d %02d:%02d:%02d',substr($datetime,0,4),substr($datetime,5,2),substr($datetime,8,2), substr($datetime,11,2),substr($datetime,14,2),substr($datetime,17,2)); else return sprintf('%04d-%02d-%02d',substr($datetime,0,4),substr($datetime,5,2),substr($datetime,8,2)); } /** * Pull timezone data from node and put in array * * Returning array contains the following array keys: tzoffsetfrom, tzoffsetto, tzname, dtstart, rrule * * @param array $node timezone object * * @return array */ static function getTZValues($node){ $tzvalues = array(); $tnode = @$node->data['TZOFFSETFROM']; if($tnode != null){ $tzvalues["tzoffsetfrom"] = $tnode->getValues(); } $tnode = @$node->data['TZOFFSETTO']; if($tnode != null){ $tzvalues["tzoffsetto"] = $tnode->getValues(); } $tnode = @$node->data['TZNAME']; if($tnode != null){ $tzvalues["tzname"] = $tnode->getValues(); } else $tzvalues["tzname"] = ""; $tnode = @$node->data['DTSTART']; if($tnode != null){ $tzvalues["dtstart"] = ZDateHelper::fromiCaltoUnixDateTime($tnode->getValues()); } $tnode = @$node->data['RRULE']; if($tnode != null){ $tzvalues["rrule"] = $tnode->getValues(); //echo "rule: " . $tzvalues["rrule"] . "
\n"; } else{ // no rule specified, let's create one from based on the date $date = getdate($tzvalues["dtstart"]); $month = $date["mon"]; $day = $date["mday"]; $tzvalues["rrule"] = "FREQ=YEARLY;INTERVAL=1;BYMONTH=$month;BYMONTHDAY=$day"; } return $tzvalues; } /** * Escape slashes, commas and semicolons in strings * * @param string $content * * @return string */ static function formatContent($content) { $content = str_replace(array('\\' , ',' , ';' ), array('\\\\' , '\\,' , '\\;' ),$content); return $content; } } ?>