| 1 |
<?php |
| 2 |
/** |
| 3 |
* Zap Calendar ical Helper Class |
| 4 |
* |
| 5 |
* @copyright Copyright (C) 2006 - 2016 by Dan Cogliano |
| 6 |
* @license GNU General Public License version 2 or later; see LICENSE.txt |
| 7 |
*/ |
| 8 |
|
| 9 |
// no direct access |
| 10 |
defined('_ZAPCAL') or die( 'Restricted access' ); |
| 11 |
|
| 12 |
/** |
| 13 |
* Object for storing an unfolded iCalendar line |
| 14 |
* |
| 15 |
* The ZCiCalDataNode class contains data from an unfolded iCalendar line |
| 16 |
* |
| 17 |
*/ |
| 18 |
|
| 19 |
class ZCiCalDataNode { |
| 20 |
/** |
| 21 |
* The name of the node |
| 22 |
* |
| 23 |
* @var string |
| 24 |
*/ |
| 25 |
var $name = ""; |
| 26 |
|
| 27 |
/** |
| 28 |
* Node parameters (before the colon ":") |
| 29 |
* |
| 30 |
* @var array |
| 31 |
*/ |
| 32 |
var $parameter=array(); |
| 33 |
|
| 34 |
/** |
| 35 |
* Node values (after the colon ":") |
| 36 |
* |
| 37 |
* @var array |
| 38 |
*/ |
| 39 |
var $value=array(); |
| 40 |
|
| 41 |
/** |
| 42 |
* Create an object from an unfolded iCalendar line |
| 43 |
* |
| 44 |
* @param string $line An unfolded iCalendar line |
| 45 |
* |
| 46 |
* @return void |
| 47 |
* |
| 48 |
*/ |
| 49 |
// function ZCiCalDataNode( $line ) { |
| 50 |
public function __construct( $line ) { //FixIn: 2.0.1.5 |
| 51 |
//echo "ZCiCalDataNode($line)<br/>\n"; |
| 52 |
//separate line into parameters and value |
| 53 |
// look for colon separating name or parameter and value |
| 54 |
// first change any escaped colons temporarily to make it easier |
| 55 |
$tline = str_replace("\\:", "`~", $line); |
| 56 |
// see if first colon is inside a quoted string |
| 57 |
$i = 0; |
| 58 |
$datafind = false; |
| 59 |
$inquotes = false; |
| 60 |
while(!$datafind && ($i < strlen($tline))) { |
| 61 |
//echo "$i: " . $tline{$i} . ", ord() = " . ord($tline{$i}) . "<br>\n"; |
| 62 |
if(!$inquotes && $tline[$i] == ':') //FixIn: 2.0.17.1 |
| 63 |
$datafind=true; |
| 64 |
else{ |
| 65 |
$i += 1; |
| 66 |
if(substr($tline,$i,1) == '"') |
| 67 |
$inquotes = !$inquotes; |
| 68 |
} |
| 69 |
} |
| 70 |
if($datafind){ |
| 71 |
$value = str_replace("`~","\\:",substr($line,$i+1)); |
| 72 |
// fix escaped characters (don't see double quotes in spec but Apple apparently uses it in iCal) |
| 73 |
$value = str_replace(array('\\N' , '\\n', '\\"' ), array("\n", "\n" , '"'), $value); |
| 74 |
$tvalue = str_replace("\\,", "`~", $value); |
| 75 |
//echo "value: " . $tvalue . "<br>\n"; |
| 76 |
$tvalue = explode(",",$tvalue); |
| 77 |
$value = str_replace("`~","\\,",$tvalue); |
| 78 |
$this->value = $value; |
| 79 |
} |
| 80 |
|
| 81 |
$parameter = trim(substr($line,0,$i)); |
| 82 |
|
| 83 |
$parameter = str_replace("\\;", "`~", $parameter); |
| 84 |
$parameters = explode(";", $parameter); |
| 85 |
$parameters = str_replace("`~", "\\;", $parameters); |
| 86 |
$this->name = array_shift($parameters); |
| 87 |
foreach($parameters as $parameter){ |
| 88 |
$pos = strpos($parameter,"="); |
| 89 |
if($pos > 0){ |
| 90 |
$param = substr($parameter,0,$pos); |
| 91 |
$paramvalue = substr($parameter,$pos+1); |
| 92 |
$tvalue = str_replace("\\,", "`~", $paramvalue); |
| 93 |
//$tvalue = explode(",",$tvalue); |
| 94 |
$paramvalue = str_replace("`~","\\,",$tvalue); |
| 95 |
$this->parameter[strtolower($param)] = $paramvalue; |
| 96 |
//$this->paramvalue[] = $paramvalue; |
| 97 |
} |
| 98 |
} |
| 99 |
} |
| 100 |
|
| 101 |
/** |
| 102 |
* getName() |
| 103 |
* |
| 104 |
* Return the name of the object |
| 105 |
* |
| 106 |
* @return string |
| 107 |
*/ |
| 108 |
function getName(){ |
| 109 |
return $this->name; |
| 110 |
} |
| 111 |
|
| 112 |
/** |
| 113 |
* Get $ith parameter from array |
| 114 |
* @param int $i |
| 115 |
* |
| 116 |
* @return var |
| 117 |
*/ |
| 118 |
function getParameter($i){ |
| 119 |
return $this->parameter[$i]; |
| 120 |
} |
| 121 |
|
| 122 |
/** |
| 123 |
* Get parameter array |
| 124 |
* |
| 125 |
* @return array |
| 126 |
*/ |
| 127 |
function getParameters(){ |
| 128 |
return $this->parameter; |
| 129 |
} |
| 130 |
|
| 131 |
/** |
| 132 |
* Get comma separated values |
| 133 |
* |
| 134 |
* @return string |
| 135 |
*/ |
| 136 |
function getValues(){ |
| 137 |
return implode(",",$this->value); |
| 138 |
} |
| 139 |
} |
| 140 |
|
| 141 |
/** |
| 142 |
* Object for storing a list of unfolded iCalendar lines (ZCiCalDataNode objects) |
| 143 |
* |
| 144 |
*/ |
| 145 |
|
| 146 |
class ZCiCalNode { |
| 147 |
var $name=""; |
| 148 |
var $parentnode=null; |
| 149 |
var $child= array(); |
| 150 |
var $data= array(); |
| 151 |
var $next=null; |
| 152 |
var $prev=null; |
| 153 |
|
| 154 |
/** |
| 155 |
* Create ZCiCalNode |
| 156 |
* |
| 157 |
* @param string $_name Name of node |
| 158 |
* |
| 159 |
* @param object $_parent Parent node for this node |
| 160 |
* |
| 161 |
* @param bool $first Is this the first child for this parent? |
| 162 |
*/ |
| 163 |
// function ZCiCalNode( $_name, & $_parent, $first = false) { |
| 164 |
public function __construct( $_name, & $_parent, $first = false) { //FixIn: 2.0.1.5 |
| 165 |
$this->name = $_name; |
| 166 |
$this->parentnode = $_parent; |
| 167 |
if($_parent != null){ |
| 168 |
if(count($this->parentnode->child) > 0) { |
| 169 |
if($first) |
| 170 |
{ |
| 171 |
$first = & $this->parentnode->child[0]; |
| 172 |
$first->prev = & $this; |
| 173 |
$this->next = & $first; |
| 174 |
} |
| 175 |
else |
| 176 |
{ |
| 177 |
$prev =& $this->parentnode->child[count($this->parentnode->child)-1]; |
| 178 |
$prev->next =& $this; |
| 179 |
$this->prev =& $prev; |
| 180 |
} |
| 181 |
} |
| 182 |
if($first) |
| 183 |
{ |
| 184 |
array_unshift($this->parentnode->child, $this); |
| 185 |
} |
| 186 |
else |
| 187 |
{ |
| 188 |
$this->parentnode->child[] =& $this; |
| 189 |
} |
| 190 |
} |
| 191 |
/* |
| 192 |
echo "creating " . $this->getName(); |
| 193 |
if($_parent != null) |
| 194 |
echo " child of " . $_parent->getName() . "/" . count($this->parentnode->child); |
| 195 |
echo "<br/>"; |
| 196 |
*/ |
| 197 |
} |
| 198 |
|
| 199 |
/** |
| 200 |
* Return the name of the object |
| 201 |
* |
| 202 |
* @return string |
| 203 |
*/ |
| 204 |
function getName() { |
| 205 |
return $this->name; |
| 206 |
} |
| 207 |
|
| 208 |
/** |
| 209 |
* Add node to list |
| 210 |
* |
| 211 |
* @param object $node |
| 212 |
* |
| 213 |
*/ |
| 214 |
function addNode($node) { |
| 215 |
$this->data[$node->getName()] = $node; |
| 216 |
} |
| 217 |
|
| 218 |
/** |
| 219 |
* Get Attribute |
| 220 |
* |
| 221 |
* @param int $i array id of attribute to get |
| 222 |
* |
| 223 |
* @return string |
| 224 |
*/ |
| 225 |
function getAttrib($i) { |
| 226 |
return $this->attrib[$i]; |
| 227 |
} |
| 228 |
|
| 229 |
/** |
| 230 |
* Set Attribute |
| 231 |
* |
| 232 |
* @param string $value value of attribute to set |
| 233 |
* |
| 234 |
*/ |
| 235 |
function setAttrib($value) { |
| 236 |
$this->attrib[] = $value; |
| 237 |
} |
| 238 |
|
| 239 |
/** |
| 240 |
* Get the parent object of this object |
| 241 |
* |
| 242 |
* @return object parent of this object |
| 243 |
*/ |
| 244 |
function &getParent() { |
| 245 |
return $this->parentnode; |
| 246 |
} |
| 247 |
|
| 248 |
/** |
| 249 |
* Get the first child of this object |
| 250 |
* |
| 251 |
* @return object The first child |
| 252 |
*/ |
| 253 |
function &getFirstChild(){ |
| 254 |
static $nullguard = null; |
| 255 |
if(count($this->child) > 0) { |
| 256 |
//echo "moving from " . $this->getName() . " to " . $this->child[0]->getName() . "<br/>"; |
| 257 |
return $this->child[0]; |
| 258 |
} |
| 259 |
else |
| 260 |
return $nullguard; |
| 261 |
} |
| 262 |
|
| 263 |
/** |
| 264 |
* Print object tree in HTML for debugging purposes |
| 265 |
* |
| 266 |
* @param object $node select part of tree to print, or leave blank for full tree |
| 267 |
* |
| 268 |
* @param int $level Level of recursion (usually leave this blank) |
| 269 |
* |
| 270 |
* @return string - HTML formatted display of object tree |
| 271 |
*/ |
| 272 |
function printTree(& $node=null, $level=1){ |
| 273 |
$level += 1; |
| 274 |
$html = ""; |
| 275 |
if($node == null) |
| 276 |
$node = $this->parentnode; |
| 277 |
if($level > 5) |
| 278 |
{ |
| 279 |
die("levels nested too deep<br/>\n"); |
| 280 |
//return; |
| 281 |
} |
| 282 |
for($i = 0 ; $i < $level; $i ++) |
| 283 |
$html .= "+"; |
| 284 |
$html .= $node->getName() . "<br/>\n"; |
| 285 |
foreach ($node->child as $c){ |
| 286 |
$html .= $node->printTree($c,$level); |
| 287 |
} |
| 288 |
$level -= 1; |
| 289 |
return $html; |
| 290 |
} |
| 291 |
|
| 292 |
|
| 293 |
// FixIn: 2025-08-11. 12:52 |
| 294 |
/** |
| 295 |
* Find a safe byte index to cut $s at, not exceeding $maxBytes. |
| 296 |
* - Prefer last ' ' or ',' or ';' <= $maxBytes |
| 297 |
* - Avoid cutting immediately after a backslash (to keep escapes like "\n" intact) |
| 298 |
* - Ensure we don't split a UTF-8 code point |
| 299 |
* Returns byte length to keep. |
| 300 |
*/ |
| 301 |
private static function _utf8_safe_cut_pos( $s, $maxBytes ) { |
| 302 |
$len = strlen( $s ); |
| 303 |
if ( $len <= $maxBytes ) { |
| 304 |
return $len; |
| 305 |
} |
| 306 |
|
| 307 |
// Start with hard limit |
| 308 |
$cut = $maxBytes; |
| 309 |
|
| 310 |
// Prefer last breakable char within the current window |
| 311 |
$window = substr( $s, 0, $cut ); |
| 312 |
$lastSpace = strrpos( $window, ' ' ); |
| 313 |
$lastComma = strrpos( $window, ',' ); |
| 314 |
$lastSemi = strrpos( $window, ';' ); |
| 315 |
$pref = max( $lastSpace === false ? - 1 : $lastSpace, $lastComma === false ? - 1 |
| 316 |
: $lastComma, $lastSemi === false ? - 1 : $lastSemi ); |
| 317 |
|
| 318 |
if ( $pref >= 0 ) { |
| 319 |
$cut = $pref + 1; // cut *after* the delimiter/space so it stays on line |
| 320 |
} |
| 321 |
|
| 322 |
// Avoid cutting right after backslash (don’t break "\n", "\,", "\;", "\\") |
| 323 |
if ( $cut > 0 && $s[ $cut - 1 ] === '\\' ) { |
| 324 |
$cut -= 1; |
| 325 |
if ( $cut <= 0 ) { |
| 326 |
$cut = $maxBytes; |
| 327 |
} // fallback |
| 328 |
} |
| 329 |
|
| 330 |
// Ensure we don't chop inside a UTF-8 multibyte char |
| 331 |
// Move back until at start of a UTF-8 code point |
| 332 |
while ( $cut > 0 && ( ord( $s[ $cut ] ) & 0xC0 ) === 0x80 ) { |
| 333 |
$cut --; |
| 334 |
} |
| 335 |
if ( $cut <= 0 ) { |
| 336 |
$cut = $maxBytes; |
| 337 |
} // ultimate fallback |
| 338 |
|
| 339 |
return $cut; |
| 340 |
} |
| 341 |
|
| 342 |
/** |
| 343 |
* Fold one logical line into RFC 5545 physical lines. |
| 344 |
* $firstWidth = 75 octets, $contWidth = 74 octets. |
| 345 |
*/ |
| 346 |
private static function _fold_ical_line( $line, $firstWidth = 75, $contWidth = 74 ) { |
| 347 |
$out = ''; |
| 348 |
$first = true; |
| 349 |
while ( strlen( $line ) > 0 ) { |
| 350 |
$width = $first ? $firstWidth : $contWidth; |
| 351 |
$cut = self::_utf8_safe_cut_pos( $line, $width ); |
| 352 |
$chunk = substr( $line, 0, $cut ); |
| 353 |
$line = substr( $line, $cut ); |
| 354 |
if ( ! $first ) { |
| 355 |
$out .= ' '; |
| 356 |
} |
| 357 |
$out .= $chunk . "\r\n"; |
| 358 |
$first = false; |
| 359 |
} |
| 360 |
|
| 361 |
return $out; |
| 362 |
} |
| 363 |
|
| 364 |
|
| 365 |
/** |
| 366 |
* export tree to icalendar format |
| 367 |
* |
| 368 |
* @param object $node Top level node to export |
| 369 |
* |
| 370 |
* @param int $level Level of recursion (usually leave this blank) |
| 371 |
* |
| 372 |
* @return string iCalendar formatted output |
| 373 |
*/ |
| 374 |
function export(& $node=null, $level=0){ |
| 375 |
$txtstr = ""; |
| 376 |
if($node == null) |
| 377 |
$node = $this; |
| 378 |
if($level > 5) |
| 379 |
{ |
| 380 |
//die("levels nested too deep<br/>\n"); |
| 381 |
throw new Exception("levels nested too deep"); |
| 382 |
} |
| 383 |
$txtstr .= "BEGIN:" . $node->getName() . "\r\n"; |
| 384 |
foreach ($node->data as $d){ |
| 385 |
$p = ""; |
| 386 |
$params = @$d->getParameters(); |
| 387 |
if(count($params) > 0) |
| 388 |
{ |
| 389 |
foreach($params as $key => $value){ |
| 390 |
$p .= ";" . strtoupper($key) . "=" . $value; |
| 391 |
} |
| 392 |
} |
| 393 |
$values = $d->getValues(); |
| 394 |
|
| 395 |
$line = $d->getName() . $p . ":" . $values; |
| 396 |
$line = str_replace( array( "<br>", "<BR>", "<br/>", "<BR/" ), "\\n", $line ); |
| 397 |
$line = str_replace( array( "\r\n", "\n\r", "\n", "\r" ), '\n', $line ); |
| 398 |
|
| 399 |
// FixIn: 2025-08-11 12:53. |
| 400 |
$txtstr .= self::_fold_ical_line( $line ); |
| 401 |
} |
| 402 |
foreach ($node->child as $c){ |
| 403 |
$txtstr .= $node->export($c,$level + 1); |
| 404 |
} |
| 405 |
$txtstr .= "END:" . $node->getName() . "\r\n"; |
| 406 |
return $txtstr; |
| 407 |
} |
| 408 |
|
| 409 |
} |
| 410 |
|
| 411 |
/** |
| 412 |
* |
| 413 |
* The main iCalendar object containing ZCiCalDataNodes and ZCiCalNodes. |
| 414 |
* |
| 415 |
*/ |
| 416 |
class ZCiCal { |
| 417 |
var $tree=null; |
| 418 |
var $curnode=null; |
| 419 |
|
| 420 |
/** |
| 421 |
* The main iCalendar object containing ZCiCalDataNodes and ZCiCalNodes. |
| 422 |
* |
| 423 |
* use maxevents and startevent to read events in multiple passes (to save memory) |
| 424 |
* |
| 425 |
* @param string $data icalendar feed string (empty if creating new feed) |
| 426 |
* |
| 427 |
* @param int $maxevents maximum # of events to read |
| 428 |
* |
| 429 |
* @param int $startevent starting event to read |
| 430 |
* |
| 431 |
* @return void |
| 432 |
* |
| 433 |
* |
| 434 |
*/ |
| 435 |
// function ZCiCal ($data = "", $maxevents = 1000000, $startevent = 0) { |
| 436 |
public function __construct( $data = "", $maxevents = 1000000, $startevent = 0) { //FixIn: 2.0.1.5 |
| 437 |
if($data != ""){ |
| 438 |
// unfold lines |
| 439 |
// first change all eol chars to "\n" |
| 440 |
$data = str_replace(array("\r\n", "\n\r", "\n", "\r"), "\n", $data); |
| 441 |
// now unfold lines |
| 442 |
//$data = str_replace(array("\n ", "\n "),"!?", $data); |
| 443 |
$data = str_replace(array("\n ", "\n "),"", $data); |
| 444 |
// replace special iCal chars |
| 445 |
$data = str_replace(array("\\\\","\,"),array("\\",","), $data); |
| 446 |
|
| 447 |
// parse each line |
| 448 |
$lines = explode("\n", $data); |
| 449 |
|
| 450 |
//echo $data; |
| 451 |
//exit; |
| 452 |
$linecount = 0; |
| 453 |
$eventcount = 0; |
| 454 |
$eventpos = 0; |
| 455 |
foreach($lines as $line) { |
| 456 |
//$line = str_replace("!?", "\n", $line); // add nl back into descriptions |
| 457 |
// echo ($linecount + 1) . ": " . $line . "<br/>"; |
| 458 |
if(substr($line,0,6) == "BEGIN:") { |
| 459 |
// start new object |
| 460 |
$name = substr($line,6); |
| 461 |
if($name == "VEVENT") |
| 462 |
{ |
| 463 |
if($eventcount < $maxevents && $eventpos >= $startevent) |
| 464 |
{ |
| 465 |
$this->curnode = new ZCiCalNode($name, $this->curnode); |
| 466 |
if($this->tree == null) |
| 467 |
$this->tree = $this->curnode; |
| 468 |
} |
| 469 |
} |
| 470 |
else |
| 471 |
{ |
| 472 |
$this->curnode = new ZCiCalNode($name, $this->curnode); |
| 473 |
if($this->tree == null) |
| 474 |
$this->tree = $this->curnode; |
| 475 |
} |
| 476 |
//echo "new node: " . $this->curnode->name . "<br/>\n"; |
| 477 |
/* |
| 478 |
if($this->curnode->getParent() != null) |
| 479 |
echo "parent of " . $this->curnode->getName() . " is " . $this->curnode->getParent()->getName() . "<br/>"; |
| 480 |
else |
| 481 |
echo "parent of " . $this->curnode->getName() . " is null<br/>"; |
| 482 |
*/ |
| 483 |
} |
| 484 |
else if(substr($line,0,4) == "END:") { |
| 485 |
$name = substr($line,4); |
| 486 |
if($name == "VEVENT") |
| 487 |
{ |
| 488 |
if($eventcount < $maxevents && $eventpos >= $startevent) |
| 489 |
{ |
| 490 |
$eventcount++; |
| 491 |
if($this->curnode->getName() != $name) { |
| 492 |
//panic, mismatch in iCal structure |
| 493 |
//die("Can't read iCal file structure, expecting " . $this->curnode->getName() . " but reading $name instead"); |
| 494 |
throw new Exception("Can't read iCal file structure, expecting " . $this->curnode->getName() . " but reading $name instead"); |
| 495 |
} |
| 496 |
if($this->curnode->getParent() != null) { |
| 497 |
//echo "moving up from " . $this->curnode->getName() ; |
| 498 |
$this->curnode = & $this->curnode->getParent(); |
| 499 |
//echo " to " . $this->curnode->getName() . "<br/>"; |
| 500 |
//echo $this->curnode->getName() . " has " . count($this->curnode->child) . " children<br/>"; |
| 501 |
} |
| 502 |
} |
| 503 |
$eventpos++; |
| 504 |
} |
| 505 |
else |
| 506 |
{ |
| 507 |
if($this->curnode->getName() != $name) { |
| 508 |
//panic, mismatch in iCal structure |
| 509 |
//die("Can't read iCal file structure, expecting " . $this->curnode->getName() . " but reading $name instead"); |
| 510 |
throw new Exception("Can't read iCal file structure, expecting " . $this->curnode->getName() . " but reading $name instead"); |
| 511 |
} |
| 512 |
if($this->curnode->getParent() != null) { |
| 513 |
//echo "moving up from " . $this->curnode->getName() ; |
| 514 |
$this->curnode = & $this->curnode->getParent(); |
| 515 |
//echo " to " . $this->curnode->getName() . "<br/>"; |
| 516 |
//echo $this->curnode->getName() . " has " . count($this->curnode->child) . " children<br/>"; |
| 517 |
} |
| 518 |
} |
| 519 |
} |
| 520 |
else { |
| 521 |
$datanode = new ZCiCalDataNode($line); |
| 522 |
if($this->curnode->getName() == "VEVENT") |
| 523 |
{ |
| 524 |
if($eventcount < $maxevents && $eventpos >= $startevent) |
| 525 |
{ |
| 526 |
if($datanode->getName() == "EXDATE") |
| 527 |
{ |
| 528 |
if(!array_key_exists($datanode->getName(),$this->curnode->data)) |
| 529 |
{ |
| 530 |
$this->curnode->data[$datanode->getName()] = $datanode; |
| 531 |
} |
| 532 |
else |
| 533 |
{ |
| 534 |
$this->curnode->data[$datanode->getName()]->value[] = $datanode->value[0]; |
| 535 |
} |
| 536 |
} |
| 537 |
else |
| 538 |
{ |
| 539 |
$this->curnode->data[$datanode->getName()] = $datanode; |
| 540 |
} |
| 541 |
} |
| 542 |
} |
| 543 |
else |
| 544 |
{ |
| 545 |
if($datanode->getName() == "EXDATE") |
| 546 |
{ |
| 547 |
if(!array_key_exists($datanode->getName(),$this->curnode->data)) |
| 548 |
{ |
| 549 |
$this->curnode->data[$datanode->getName()] = $datanode; |
| 550 |
} |
| 551 |
else |
| 552 |
{ |
| 553 |
$this->curnode->data[$datanode->getName()]->value[] = $datanode->value[0]; |
| 554 |
} |
| 555 |
} |
| 556 |
else |
| 557 |
{ |
| 558 |
$this->curnode->data[$datanode->getName()] = $datanode; |
| 559 |
} |
| 560 |
} |
| 561 |
} |
| 562 |
$linecount++; |
| 563 |
} |
| 564 |
} |
| 565 |
else { |
| 566 |
$name = "VCALENDAR"; |
| 567 |
$this->curnode = new ZCiCalNode($name, $this->curnode); |
| 568 |
$this->tree = $this->curnode; |
| 569 |
$datanode = new ZCiCalDataNode("VERSION:2.0"); |
| 570 |
$this->curnode->data[$datanode->getName()] = $datanode; |
| 571 |
|
| 572 |
$datanode = new ZCiCalDataNode("PRODID:-//ZContent.net//Zap Calendar 1.0//EN"); |
| 573 |
$this->curnode->data[$datanode->getName()] = $datanode; |
| 574 |
$datanode = new ZCiCalDataNode("CALSCALE:GREGORIAN"); |
| 575 |
$this->curnode->data[$datanode->getName()] = $datanode; |
| 576 |
$datanode = new ZCiCalDataNode("METHOD:PUBLISH"); |
| 577 |
$this->curnode->data[$datanode->getName()] = $datanode; |
| 578 |
} |
| 579 |
} |
| 580 |
|
| 581 |
/** |
| 582 |
* CountEvents() |
| 583 |
* |
| 584 |
* Return the # of VEVENTs in the object |
| 585 |
* |
| 586 |
* @return int |
| 587 |
*/ |
| 588 |
|
| 589 |
function countEvents() { |
| 590 |
$count = 0; |
| 591 |
if(isset($this->tree->child)){ |
| 592 |
foreach($this->tree->child as $child){ |
| 593 |
if($child->getName() == "VEVENT") |
| 594 |
$count++; |
| 595 |
} |
| 596 |
} |
| 597 |
return $count; |
| 598 |
} |
| 599 |
|
| 600 |
/** |
| 601 |
* CountVenues() |
| 602 |
* |
| 603 |
* Return the # of VVENUEs in the object |
| 604 |
* |
| 605 |
* @return int |
| 606 |
*/ |
| 607 |
|
| 608 |
function countVenues() { |
| 609 |
$count = 0; |
| 610 |
if(isset($this->tree->child)){ |
| 611 |
foreach($this->tree->child as $child){ |
| 612 |
if($child->getName() == "VVENUE") |
| 613 |
$count++; |
| 614 |
} |
| 615 |
} |
| 616 |
return $count; |
| 617 |
} |
| 618 |
|
| 619 |
/** |
| 620 |
* Export object to string |
| 621 |
* |
| 622 |
* This function exports all objects to an iCalendar string |
| 623 |
* |
| 624 |
* @return string an iCalendar formatted string |
| 625 |
*/ |
| 626 |
|
| 627 |
function export() { |
| 628 |
return $this->tree->export($this->tree); |
| 629 |
} |
| 630 |
|
| 631 |
/** |
| 632 |
* Get first event in object list |
| 633 |
* Use getNextEvent() to navigate through list |
| 634 |
* |
| 635 |
* @return object The first event, or null |
| 636 |
*/ |
| 637 |
function &getFirstEvent() { |
| 638 |
static $nullguard = null; |
| 639 |
if ($this->countEvents() > 0){ |
| 640 |
$child = $this->tree->child[0]; |
| 641 |
$event=false; |
| 642 |
while(!$event && $child != null){ |
| 643 |
if($child->getName() == "VEVENT") |
| 644 |
$event = true; |
| 645 |
else |
| 646 |
$child = $child->next; |
| 647 |
} |
| 648 |
return $child; |
| 649 |
} |
| 650 |
else |
| 651 |
return $nullguard; |
| 652 |
} |
| 653 |
|
| 654 |
/** |
| 655 |
* Get next event in object list |
| 656 |
* |
| 657 |
* @param object $event The current event object |
| 658 |
* |
| 659 |
* @return object Returns the next event or null if past last event |
| 660 |
*/ |
| 661 |
function &getNextEvent($event){ |
| 662 |
do{ |
| 663 |
$event = $event->next; |
| 664 |
} while($event != null && $event->getName() != "VEVENT"); |
| 665 |
return $event; |
| 666 |
} |
| 667 |
|
| 668 |
/** |
| 669 |
* Get first venue in object list |
| 670 |
* Use getNextVenue() to navigate through list |
| 671 |
* |
| 672 |
* @return object The first venue, or null |
| 673 |
*/ |
| 674 |
function &getFirstVenue() { |
| 675 |
static $nullguard = null; |
| 676 |
if ($this->countVenues() > 0){ |
| 677 |
$child = $this->tree->child[0]; |
| 678 |
$event=false; |
| 679 |
while(!$event && $child != null){ |
| 680 |
if($child->getName() == "VVENUE") |
| 681 |
$event = true; |
| 682 |
else |
| 683 |
$child = $child->next; |
| 684 |
} |
| 685 |
return $child; |
| 686 |
} |
| 687 |
else |
| 688 |
return $nullguard; |
| 689 |
} |
| 690 |
|
| 691 |
/** |
| 692 |
* Get next venue in object list |
| 693 |
* |
| 694 |
* @param object $venue The current venue object |
| 695 |
* |
| 696 |
* @return object Returns the next venue or null if past last venue |
| 697 |
*/ |
| 698 |
function &getNextVenue($venue){ |
| 699 |
do{ |
| 700 |
$venue = $venue->next; |
| 701 |
} while($venue != null && $venue->getName() != "VVENUE"); |
| 702 |
return $venue; |
| 703 |
} |
| 704 |
|
| 705 |
/** |
| 706 |
* Get first child in object list |
| 707 |
* Use getNextSibling() and getPreviousSibling() to navigate through list |
| 708 |
* |
| 709 |
* @param object $thisnode The parent object |
| 710 |
* |
| 711 |
* @return object The child object |
| 712 |
*/ |
| 713 |
function &getFirstChild(& $thisnode){ |
| 714 |
$nullvalue = null; |
| 715 |
if(count($thisnode->child) > 0) { |
| 716 |
//echo "moving from " . $thisnode->getName() . " to " . $thisnode->child[0]->getName() . "<br/>"; |
| 717 |
return $thisnode->child[0]; |
| 718 |
} |
| 719 |
else |
| 720 |
return $nullvalue; |
| 721 |
} |
| 722 |
|
| 723 |
/** |
| 724 |
* Get next sibling in object list |
| 725 |
* |
| 726 |
* @param object $thisnode The current object |
| 727 |
* |
| 728 |
* @return object Returns the next sibling |
| 729 |
*/ |
| 730 |
function &getNextSibling(& $thisnode){ |
| 731 |
return $thisnode->next; |
| 732 |
} |
| 733 |
|
| 734 |
/** |
| 735 |
* Get previous sibling in object list |
| 736 |
* |
| 737 |
* @param object $thisnode The current object |
| 738 |
* |
| 739 |
* @return object Returns the previous sibling |
| 740 |
*/ |
| 741 |
function &getPrevSibling(& $thisnode){ |
| 742 |
return $thisnode->prev; |
| 743 |
} |
| 744 |
|
| 745 |
/** |
| 746 |
* Read date/time in iCal formatted string |
| 747 |
* |
| 748 |
* @param string iCal formated date/time string |
| 749 |
* |
| 750 |
* @return int Unix timestamp |
| 751 |
*/ |
| 752 |
|
| 753 |
function toUnixDateTime($datetime){ |
| 754 |
$year = substr($datetime,0,4); |
| 755 |
$month = substr($datetime,4,2); |
| 756 |
$day = substr($datetime,6,2); |
| 757 |
$hour = 0; |
| 758 |
$minute = 0; |
| 759 |
$second = 0; |
| 760 |
if(strlen($datetime) > 8 && $datetime[8] == "T") { //FixIn: 2.0.17.1 |
| 761 |
$hour = substr($datetime,9,2); |
| 762 |
$minute = substr($datetime,11,2); |
| 763 |
$second = substr($datetime,13,2); |
| 764 |
} |
| 765 |
$d1 = mktime($hour, $minute, $second, $month, $day, $year); |
| 766 |
|
| 767 |
} |
| 768 |
|
| 769 |
/** |
| 770 |
* fromUnixDateTime() |
| 771 |
* |
| 772 |
* Take Unix timestamp and format to iCal date/time string |
| 773 |
* |
| 774 |
* @param int $datetime Unix timestamp, leave blank for current date/time |
| 775 |
* |
| 776 |
* @return string formatted iCal date/time string |
| 777 |
*/ |
| 778 |
|
| 779 |
static function fromUnixDateTime($datetime = null){ |
| 780 |
date_default_timezone_set('UTC'); |
| 781 |
if($datetime == null) |
| 782 |
$datetime = time(); |
| 783 |
return date("Ymd\THis",$datetime); |
| 784 |
} |
| 785 |
|
| 786 |
|
| 787 |
/** |
| 788 |
* fromUnixDate() |
| 789 |
* |
| 790 |
* Take Unix timestamp and format to iCal date string |
| 791 |
* |
| 792 |
* @param int $datetime Unix timestamp, leave blank for current date/time |
| 793 |
* |
| 794 |
* @return string formatted iCal date string |
| 795 |
*/ |
| 796 |
|
| 797 |
static function fromUnixDate($datetime = null){ |
| 798 |
date_default_timezone_set('UTC'); |
| 799 |
if($datetime == null) |
| 800 |
$datetime = time(); |
| 801 |
return date("Ymd",$datetime); |
| 802 |
} |
| 803 |
|
| 804 |
/** |
| 805 |
* Format into iCal time format from SQL date or SQL date-time format |
| 806 |
* |
| 807 |
* @param string $datetime SQL date or SQL date-time string |
| 808 |
* |
| 809 |
* @return string iCal formatted string |
| 810 |
*/ |
| 811 |
static function fromSqlDateTime($datetime = ""){ |
| 812 |
if($datetime == "") |
| 813 |
$datetime = ZDateHelper::toSqlDateTime(); |
| 814 |
if(strlen($datetime) > 10) |
| 815 |
return sprintf('%04d%02d%02dT%02d%02d%02d',substr($datetime,0,4),substr($datetime,5,2),substr($datetime,8,2), |
| 816 |
substr($datetime,11,2),substr($datetime,14,2),substr($datetime,17,2)); |
| 817 |
else |
| 818 |
return sprintf('%04d%02d%02d',substr($datetime,0,4),substr($datetime,5,2),substr($datetime,8,2)); |
| 819 |
} |
| 820 |
|
| 821 |
/** |
| 822 |
* Format iCal time format to either SQL date or SQL date-time format |
| 823 |
* |
| 824 |
* @param string $datetime icalendar formatted date or date-time |
| 825 |
* |
| 826 |
* @return string SQL date or SQL date-time string |
| 827 |
*/ |
| 828 |
static function toSqlDateTime($datetime = ""){ |
| 829 |
if($datetime == "") |
| 830 |
return ZDateHelper::toSqlDateTime(); |
| 831 |
if(strlen($datetime) > 10) |
| 832 |
return sprintf('%04d-%02d-%02d %02d:%02d:%02d',substr($datetime,0,4),substr($datetime,5,2),substr($datetime,8,2), |
| 833 |
substr($datetime,11,2),substr($datetime,14,2),substr($datetime,17,2)); |
| 834 |
else |
| 835 |
return sprintf('%04d-%02d-%02d',substr($datetime,0,4),substr($datetime,5,2),substr($datetime,8,2)); |
| 836 |
} |
| 837 |
|
| 838 |
/** |
| 839 |
* Pull timezone data from node and put in array |
| 840 |
* |
| 841 |
* Returning array contains the following array keys: tzoffsetfrom, tzoffsetto, tzname, dtstart, rrule |
| 842 |
* |
| 843 |
* @param array $node timezone object |
| 844 |
* |
| 845 |
* @return array |
| 846 |
*/ |
| 847 |
static function getTZValues($node){ |
| 848 |
$tzvalues = array(); |
| 849 |
|
| 850 |
$tnode = @$node->data['TZOFFSETFROM']; |
| 851 |
if($tnode != null){ |
| 852 |
$tzvalues["tzoffsetfrom"] = $tnode->getValues(); |
| 853 |
} |
| 854 |
|
| 855 |
$tnode = @$node->data['TZOFFSETTO']; |
| 856 |
if($tnode != null){ |
| 857 |
$tzvalues["tzoffsetto"] = $tnode->getValues(); |
| 858 |
} |
| 859 |
|
| 860 |
$tnode = @$node->data['TZNAME']; |
| 861 |
if($tnode != null){ |
| 862 |
$tzvalues["tzname"] = $tnode->getValues(); |
| 863 |
} |
| 864 |
else |
| 865 |
$tzvalues["tzname"] = ""; |
| 866 |
|
| 867 |
$tnode = @$node->data['DTSTART']; |
| 868 |
if($tnode != null){ |
| 869 |
$tzvalues["dtstart"] = ZDateHelper::fromiCaltoUnixDateTime($tnode->getValues()); |
| 870 |
} |
| 871 |
|
| 872 |
$tnode = @$node->data['RRULE']; |
| 873 |
if($tnode != null){ |
| 874 |
$tzvalues["rrule"] = $tnode->getValues(); |
| 875 |
//echo "rule: " . $tzvalues["rrule"] . "<br/>\n"; |
| 876 |
} |
| 877 |
else{ |
| 878 |
// no rule specified, let's create one from based on the date |
| 879 |
$date = getdate($tzvalues["dtstart"]); |
| 880 |
$month = $date["mon"]; |
| 881 |
$day = $date["mday"]; |
| 882 |
$tzvalues["rrule"] = "FREQ=YEARLY;INTERVAL=1;BYMONTH=$month;BYMONTHDAY=$day"; |
| 883 |
} |
| 884 |
|
| 885 |
return $tzvalues; |
| 886 |
} |
| 887 |
|
| 888 |
/** |
| 889 |
* Escape slashes, commas and semicolons in strings |
| 890 |
* |
| 891 |
* @param string $content |
| 892 |
* |
| 893 |
* @return escaped string |
| 894 |
*/ |
| 895 |
static function formatContent($content) |
| 896 |
{ |
| 897 |
$content = str_replace(array('\\' , ',' , ';' ), array('\\\\' , '\\,' , '\\;' ),$content); |
| 898 |
return $content; |
| 899 |
} |
| 900 |
|
| 901 |
} |