<?php
namespace ICalendarOrg;
class ZCiCal
{
public ?ZCiCalNode $curnode = null;
public ?ZCiCalNode $tree = null;
public function __construct(string $data = '', int $maxevents = 1_000_000, int $startevent = 0)
{
if ('' != $data)
{
$data = \str_replace(["\r\n", "\n\r", "\r"], "\n", $data);
$lines = \explode("\n", $data);
$linecount = 0;
$eventcount = 0;
$eventpos = 0;
$lastdatakey = null;
foreach ($lines as $line)
{
if ('BEGIN:' == \substr($line, 0, 6))
{
$name = \substr($line, 6);
$lastdatakey = null;
if ('VEVENT' == $name)
{
if ($eventcount < $maxevents && $eventpos >= $startevent)
{
$this->curnode = new \ICalendarOrg\ZCiCalNode($name, $this->curnode);
if (null == $this->tree)
{
$this->tree = $this->curnode;
}
}
}
else
{
$this->curnode = new \ICalendarOrg\ZCiCalNode($name, $this->curnode);
if (null == $this->tree)
{
$this->tree = $this->curnode;
}
}
}
elseif ('END:' == \substr($line, 0, 4))
{
$name = \substr($line, 4);
$lastdatakey = null;
if ('VEVENT' == $name)
{
if ($eventcount < $maxevents && $eventpos >= $startevent)
{
$eventcount++;
if ($this->curnode->getName() != $name)
{
throw new \Exception("Can't read iCal file structure, expecting " . $this->curnode->getName() . ' but reading $name instead');
}
if (null != $this->curnode->getParent())
{
$this->curnode = $this->curnode->getParent();
}
}
$eventpos++;
}
else
{
if ($this->curnode->getName() != $name)
{
throw new \Exception("Can't read iCal file structure, expecting " . $this->curnode->getName() . ' but reading $name instead');
}
if (null != $this->curnode->getParent())
{
$this->curnode = $this->curnode->getParent();
}
}
}
elseif (' ' == \substr($line, 0, 1))
{
if (null !== $lastdatakey) {
$this->curnode->data[$lastdatakey]->values[
\count($this->curnode->data[$lastdatakey]->values) - 1
] .= \ltrim($line);
}
}
else
{
$datanode = new \ICalendarOrg\ZCiCalDataNode($line);
$lastdatakey = $datanode->getName();
if ('VEVENT' == $this->curnode->getName())
{
if ($eventcount < $maxevents && $eventpos >= $startevent)
{
if ('EXDATE' == $datanode->getName())
{
if (! \array_key_exists($datanode->getName(), $this->curnode->data))
{
$this->curnode->data[$datanode->getName()] = $datanode;
}
else
{
$this->curnode->data[$datanode->getName()]->values[] = $datanode->values[0];
}
}
else
{
if (! \array_key_exists($datanode->getName(), $this->curnode->data))
{
$this->curnode->data[$datanode->getName()] = $datanode;
}
else
{
if (! \is_array($this->curnode->data[$datanode->getName()]))
{
$tnode = $this->curnode->data[$datanode->getName()];
$this->curnode->data[$datanode->getName()] = [];
$this->curnode->data[$datanode->getName()][] = $tnode;
}
$this->curnode->data[$datanode->getName()][] = $datanode;
}
}
}
}
else
{
if ('EXDATE' == $datanode->getName())
{
if (! \array_key_exists($datanode->getName(), $this->curnode->data))
{
$this->curnode->data[$datanode->getName()] = $datanode;
}
else
{
$this->curnode->data[$datanode->getName()]->values[] = $datanode->values[0];
}
}
else
{
if (! \array_key_exists($datanode->getName(), $this->curnode->data))
{
$this->curnode->data[$datanode->getName()] = $datanode;
}
else
{
if (! \is_array($this->curnode->data[$datanode->getName()]))
{
$tnode = $this->curnode->data[$datanode->getName()];
$this->curnode->data[$datanode->getName()] = [];
$this->curnode->data[$datanode->getName()][] = $tnode;
}
$this->curnode->data[$datanode->getName()][] = $datanode;
}
}
}
}
$linecount++;
}
}
else
{
$name = 'VCALENDAR';
$this->curnode = new \ICalendarOrg\ZCiCalNode($name, $this->curnode);
$this->tree = $this->curnode;
$this->addDataNode(new \ICalendarOrg\ZCiCalDataNode('VERSION:2.0'));
$this->addDataNode(new \ICalendarOrg\ZCiCalDataNode('PRODID:-//ZContent.net//ZapCalLib 1.0//EN'));
$this->addDataNode(new \ICalendarOrg\ZCiCalDataNode('CALSCALE:GREGORIAN'));
$this->addDataNode(new \ICalendarOrg\ZCiCalDataNode('METHOD:PUBLISH'));
}
}
public function addDataNode(\ICalendarOrg\ZCiCalDataNode $dataNode, ?\ICalendarOrg\ZCiCalDataNode $beforeNode = null) : ZCiCal
{
if (! $beforeNode)
{
$this->tree->data[$dataNode->getName()] = $dataNode;
return $this;
}
$insertPosition = \array_search($beforeNode->getName(), \array_keys($this->tree->data));
if (false === $insertPosition)
{
throw new \UnexpectedValueException('Node ' . $beforeNode->getName() . ' not found in ' . __CLASS__);
}
$this->tree->data = \array_slice($this->tree->data, 0, $insertPosition) + [$dataNode->getName() => $dataNode] + \array_slice($this->tree->data, $insertPosition);
return $this;
}
public function countEvents() : int
{
$count = 0;
if (isset($this->tree->child))
{
foreach ($this->tree->child as $child)
{
if ('VEVENT' == $child->getName())
{
$count++;
}
}
}
return $count;
}
public function countVenues() : int
{
$count = 0;
if (isset($this->tree->child))
{
foreach ($this->tree->child as $child)
{
if ('VVENUE' == $child->getName())
{
$count++;
}
}
}
return $count;
}
public function export() : string
{
return $this->tree->export($this->tree);
}
public static function formatContent(string $content) : string
{
$content = \str_replace(['\\', ',', ';'], ['\\\\', '\\,', '\\;'], $content);
return $content;
}
public function getFirstChild($thisnode) : ?object
{
if ((\is_countable($thisnode->child) ? \count($thisnode->child) : 0) > 0)
{
return $thisnode->child[0];
}
return null;
}
public function getFirstEvent() : ?object
{
if ($this->countEvents() > 0)
{
$child = $this->tree->child[0];
$event = false;
while (! $event && null != $child)
{
if ('VEVENT' == $child->getName())
{
$event = true;
}
else
{
$child = $child->next;
}
}
return $child;
}
return null;
}
public function getFirstVenue() : ?object
{
if ($this->countVenues() > 0)
{
$child = $this->tree->child[0];
$event = false;
while (! $event && null != $child)
{
if ('VVENUE' == $child->getName())
{
$event = true;
}
else
{
$child = $child->next;
}
}
return $child;
}
return null;
}
public function getNextEvent($event)
{
do
{
$event = $event->next;
} while (null != $event && 'VEVENT' != $event->getName());
return $event;
}
public function getNextSibling($thisnode)
{
return $thisnode->next;
}
public function getNextVenue($venue)
{
do
{
$venue = $venue->next;
} while (null != $venue && 'VVENUE' != $venue->getName());
return $venue;
}
public function getPrevSibling($thisnode)
{
return $thisnode->prev;
}
public static function getTZValues(ZCiCalNode $node) : array
{
$tzvalues = [];
$tnode = $node->data['TZOFFSETFROM'];
if (null != $tnode)
{
$tzvalues['tzoffsetfrom'] = $tnode->getValues();
}
$tnode = $node->data['TZOFFSETTO'];
if (null != $tnode)
{
$tzvalues['tzoffsetto'] = $tnode->getValues();
}
$tnode = $node->data['TZNAME'];
if (null != $tnode)
{
$tzvalues['tzname'] = $tnode->getValues();
}
else
{
$tzvalues['tzname'] = '';
}
$tnode = $node->data['DTSTART'];
if (null != $tnode)
{
$tzvalues['dtstart'] = \ICalendarOrg\ZDateHelper::fromiCaltoUnixDateTime($tnode->getValues());
}
$tnode = $node->data['RRULE'];
if (null != $tnode)
{
$tzvalues['rrule'] = $tnode->getValues();
}
else
{
$date = \getdate($tzvalues['dtstart']);
$month = $date['mon'];
$day = $date['mday'];
$tzvalues['rrule'] = 'FREQ=YEARLY;INTERVAL=1;BYMONTH=$month;BYMONTHDAY=$day';
}
return $tzvalues;
}
}