calendarimporter/php/ical/lib/iCal.XML.inc.php

898 lines
37 KiB
PHP

<?php
/*********************************************************************************/
/**
*
* iCalcreator, a PHP rfc2445/rfc5545 solution.
*
* @copyright Copyright (c) 2007-2015 Kjell-Inge Gustafsson, kigkonsult, All rights reserved
* @link http://kigkonsult.se/iCalcreator/index.php
* @license http://kigkonsult.se/downloads/dl.php?f=LGPL
* @package iCalcreator
* @version 2.22
*/
/**
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*********************************************************************************/
/* iCalcreator XML (rfc6321) helper functions */
/*********************************************************************************/
/**
* format iCal XML output, rfc6321, using PHP SimpleXMLElement
*
* @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
* @since 2.18.1 - 2013-08-18
* @param object $calendar iCalcreator vcalendar instance reference
* @uses ICALCREATOR_VERSION
* @uses vcalendar::getProperty()
* @uses _addXMLchild()
* @uses vcalendar::getConfig()
* @uses vcalendar::getComponent()
* @uses calendarComponent::$objName
* @uses calendarComponent::getProperty()
* @return string
*/
function iCal2XML( $calendar ) {
/** fix an SimpleXMLElement instance and create root element */
$xmlstr = '<?xml version="1.0" encoding="utf-8"?><icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0">';
$xmlstr .= '<!-- created '.gmdate( 'Ymd\THis\Z' );
$xmlstr .= ' using kigkonsult.se '.ICALCREATOR_VERSION.' iCal2XMl (rfc6321) -->';
$xmlstr .= '</icalendar>';
$xml = new SimpleXMLElement( $xmlstr );
$vcalendar = $xml->addChild( 'vcalendar' );
/** fix calendar properties */
$properties = $vcalendar->addChild( 'properties' );
$calProps = array( 'version', 'prodid', 'calscale', 'method' );
foreach( $calProps as $calProp ) {
if( FALSE !== ( $content = $calendar->getProperty( $calProp )))
_addXMLchild( $properties, $calProp, 'text', $content );
}
while( FALSE !== ( $content = $calendar->getProperty( FALSE, FALSE, TRUE )))
_addXMLchild( $properties, $content[0], 'unknown', $content[1]['value'], $content[1]['params'] );
$langCal = $calendar->getConfig( 'language' );
/** prepare to fix components with properties */
$components = $vcalendar->addChild( 'components' );
/** fix component properties */
while( FALSE !== ( $component = $calendar->getComponent())) {
$compName = $component->objName;
$child = $components->addChild( $compName );
$properties = $child->addChild( 'properties' );
$langComp = $component->getConfig( 'language' );
$props = $component->getConfig( 'setPropertyNames' );
foreach( $props as $prop ) {
switch( strtolower( $prop )) {
case 'attach': // may occur multiple times, below
while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
$type = ( isset( $content['params']['VALUE'] ) && ( 'BINARY' == $content['params']['VALUE'] )) ? 'binary' : 'uri';
unset( $content['params']['VALUE'] );
_addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
}
break;
case 'attendee':
while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
if( isset( $content['params']['CN'] ) && !isset( $content['params']['LANGUAGE'] )) {
if( $langComp )
$content['params']['LANGUAGE'] = $langComp;
elseif( $langCal )
$content['params']['LANGUAGE'] = $langCal;
}
_addXMLchild( $properties, $prop, 'cal-address', $content['value'], $content['params'] );
}
break;
case 'exdate':
while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
$type = ( isset( $content['params']['VALUE'] ) && ( 'DATE' == $content['params']['VALUE'] )) ? 'date' : 'date-time';
unset( $content['params']['VALUE'] );
_addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
}
break;
case 'freebusy':
while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
if( is_array( $content ) && isset( $content['value']['fbtype'] )) {
$content['params']['FBTYPE'] = $content['value']['fbtype'];
unset( $content['value']['fbtype'] );
}
_addXMLchild( $properties, $prop, 'period', $content['value'], $content['params'] );
}
break;
case 'request-status':
while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
if( !isset( $content['params']['LANGUAGE'] )) {
if( $langComp )
$content['params']['LANGUAGE'] = $langComp;
elseif( $langCal )
$content['params']['LANGUAGE'] = $langCal;
}
_addXMLchild( $properties, $prop, 'rstatus', $content['value'], $content['params'] );
}
break;
case 'rdate':
while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
$type = 'date-time';
if( isset( $content['params']['VALUE'] )) {
if( 'DATE' == $content['params']['VALUE'] )
$type = 'date';
elseif( 'PERIOD' == $content['params']['VALUE'] )
$type = 'period';
}
unset( $content['params']['VALUE'] );
_addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
}
break;
case 'categories':
case 'comment':
case 'contact':
case 'description':
case 'related-to':
case 'resources':
while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
if(( 'related-to' != $prop ) && !isset( $content['params']['LANGUAGE'] )) {
if( $langComp )
$content['params']['LANGUAGE'] = $langComp;
elseif( $langCal )
$content['params']['LANGUAGE'] = $langCal;
}
_addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
}
break;
case 'x-prop':
while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
_addXMLchild( $properties, $content[0], 'unknown', $content[1]['value'], $content[1]['params'] );
break;
case 'created': // single occurence below, if set
case 'completed':
case 'dtstamp':
case 'last-modified':
$utcDate = TRUE;
case 'dtstart':
case 'dtend':
case 'due':
case 'recurrence-id':
if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
$type = ( isset( $content['params']['VALUE'] ) && ( 'DATE' == $content['params']['VALUE'] )) ? 'date' : 'date-time';
unset( $content['params']['VALUE'] );
if(( isset( $content['params']['TZID'] ) && empty( $content['params']['TZID'] )) || @is_null( $content['params']['TZID'] ))
unset( $content['params']['TZID'] );
_addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
}
unset( $utcDate );
break;
case 'duration':
if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
_addXMLchild( $properties, $prop, 'duration', $content['value'], $content['params'] );
break;
case 'exrule':
case 'rrule':
while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
_addXMLchild( $properties, $prop, 'recur', $content['value'], $content['params'] );
break;
case 'class':
case 'location':
case 'status':
case 'summary':
case 'transp':
case 'tzid':
case 'uid':
if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
if((( 'location' == $prop ) || ( 'summary' == $prop )) && !isset( $content['params']['LANGUAGE'] )) {
if( $langComp )
$content['params']['LANGUAGE'] = $langComp;
elseif( $langCal )
$content['params']['LANGUAGE'] = $langCal;
}
_addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
}
break;
case 'geo':
if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
_addXMLchild( $properties, $prop, 'geo', $content['value'], $content['params'] );
break;
case 'organizer':
if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
if( isset( $content['params']['CN'] ) && !isset( $content['params']['LANGUAGE'] )) {
if( $langComp )
$content['params']['LANGUAGE'] = $langComp;
elseif( $langCal )
$content['params']['LANGUAGE'] = $langCal;
}
_addXMLchild( $properties, $prop, 'cal-address', $content['value'], $content['params'] );
}
break;
case 'percent-complete':
case 'priority':
case 'sequence':
if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
_addXMLchild( $properties, $prop, 'integer', $content['value'], $content['params'] );
break;
case 'tzurl':
case 'url':
if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
_addXMLchild( $properties, $prop, 'uri', $content['value'], $content['params'] );
break;
} // end switch( $prop )
} // end foreach( $props as $prop )
/** fix subComponent properties, if any */
while( FALSE !== ( $subcomp = $component->getComponent())) {
$subCompName = $subcomp->objName;
$child2 = $child->addChild( $subCompName );
$properties = $child2->addChild( 'properties' );
$langComp = $subcomp->getConfig( 'language' );
$subCompProps = $subcomp->getConfig( 'setPropertyNames' );
foreach( $subCompProps as $prop ) {
switch( strtolower( $prop )) {
case 'attach': // may occur multiple times, below
while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
$type = ( isset( $content['params']['VALUE'] ) && ( 'BINARY' == $content['params']['VALUE'] )) ? 'binary' : 'uri';
unset( $content['params']['VALUE'] );
_addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
}
break;
case 'attendee':
while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
if( isset( $content['params']['CN'] ) && !isset( $content['params']['LANGUAGE'] )) {
if( $langComp )
$content['params']['LANGUAGE'] = $langComp;
elseif( $langCal )
$content['params']['LANGUAGE'] = $langCal;
}
_addXMLchild( $properties, $prop, 'cal-address', $content['value'], $content['params'] );
}
break;
case 'comment':
case 'tzname':
while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
if( !isset( $content['params']['LANGUAGE'] )) {
if( $langComp )
$content['params']['LANGUAGE'] = $langComp;
elseif( $langCal )
$content['params']['LANGUAGE'] = $langCal;
}
_addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
}
break;
case 'rdate':
while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
$type = 'date-time';
if( isset( $content['params']['VALUE'] )) {
if( 'DATE' == $content['params']['VALUE'] )
$type = 'date';
elseif( 'PERIOD' == $content['params']['VALUE'] )
$type = 'period';
}
unset( $content['params']['VALUE'] );
_addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
}
break;
case 'x-prop':
while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
_addXMLchild( $properties, $content[0], 'unknown', $content[1]['value'], $content[1]['params'] );
break;
case 'action': // single occurence below, if set
case 'description':
case 'summary':
if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
if(( 'action' != $prop ) && !isset( $content['params']['LANGUAGE'] )) {
if( $langComp )
$content['params']['LANGUAGE'] = $langComp;
elseif( $langCal )
$content['params']['LANGUAGE'] = $langCal;
}
_addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
}
break;
case 'dtstart':
if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
unset( $content['value']['tz'], $content['params']['VALUE'] ); // always local time
_addXMLchild( $properties, $prop, 'date-time', $content['value'], $content['params'] );
}
break;
case 'duration':
if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
_addXMLchild( $properties, $prop, 'duration', $content['value'], $content['params'] );
break;
case 'repeat':
if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
_addXMLchild( $properties, $prop, 'integer', $content['value'], $content['params'] );
break;
case 'trigger':
if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
if( isset( $content['value']['year'] ) &&
isset( $content['value']['month'] ) &&
isset( $content['value']['day'] ))
$type = 'date-time';
else {
$type = 'duration';
if( !isset( $content['value']['relatedStart'] ) || ( TRUE !== $content['value']['relatedStart'] ))
$content['params']['RELATED'] = 'END';
}
_addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
}
break;
case 'tzoffsetto':
case 'tzoffsetfrom':
if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
_addXMLchild( $properties, $prop, 'utc-offset', $content['value'], $content['params'] );
break;
case 'rrule':
while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
_addXMLchild( $properties, $prop, 'recur', $content['value'], $content['params'] );
break;
} // switch( $prop )
} // end foreach( $subCompProps as $prop )
} // end while( FALSE !== ( $subcomp = $component->getComponent()))
} // end while( FALSE !== ( $component = $calendar->getComponent()))
return $xml->asXML();
}
/**
* Add children to a SimpleXMLelement
*
* @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
* @since 2.18.10 - 2013-09-04
* @param object $parent reference to a SimpleXMLelement node
* @param string $name new element node name
* @param string $type content type, subelement(-s) name
* @param string $content new subelement content
* @param array $params new element 'attributes'
* @uses iCalUtilityFunctions::_duration2str()
* @uses iCalUtilityFunctions::_geo2str2()
* @uses iCalUtilityFunctions::$geoLatFmt
* @uses iCalUtilityFunctions::$geoLongFmt
* @return void
*/
function _addXMLchild( & $parent, $name, $type, $content, $params=array()) {
static $fmtYmd = '%04d-%02d-%02d';
static $fmtYmdHis = '%04d-%02d-%02dT%02d:%02d:%02d';
/** create new child node */
$name = strtolower( $name );
$child = $parent->addChild( $name );
if( !empty( $params )) {
$parameters = $child->addChild( 'parameters' );
foreach( $params as $param => $parVal ) {
if( 'VALUE' == $param )
continue;
$param = strtolower( $param );
if( 'x-' == substr( $param, 0, 2 )) {
$p1 = $parameters->addChild( $param );
$p2 = $p1->addChild( 'unknown', htmlspecialchars( $parVal ));
}
else {
$p1 = $parameters->addChild( $param );
switch( $param ) {
case 'altrep':
case 'dir': $ptype = 'uri'; break;
case 'delegated-from':
case 'delegated-to':
case 'member':
case 'sent-by': $ptype = 'cal-address'; break;
case 'rsvp': $ptype = 'boolean'; break ;
default: $ptype = 'text'; break;
}
if( is_array( $parVal )) {
foreach( $parVal as $pV )
$p2 = $p1->addChild( $ptype, htmlspecialchars( $pV ));
}
else
$p2 = $p1->addChild( $ptype, htmlspecialchars( $parVal ));
}
}
} // end if( !empty( $params ))
if(( empty( $content ) && ( '0' != $content )) || ( !is_array( $content) && ( '-' != substr( $content, 0, 1 ) && ( 0 > $content ))))
return;
/** store content */
switch( $type ) {
case 'binary':
$v = $child->addChild( $type, $content );
break;
case 'boolean':
break;
case 'cal-address':
$v = $child->addChild( $type, $content );
break;
case 'date':
if( array_key_exists( 'year', $content ))
$content = array( $content );
foreach( $content as $date ) {
$str = sprintf( $fmtYmd, (int) $date['year'], (int) $date['month'], (int) $date['day'] );
$v = $child->addChild( $type, $str );
}
break;
case 'date-time':
if( array_key_exists( 'year', $content ))
$content = array( $content );
foreach( $content as $dt ) {
if( !isset( $dt['hour'] )) $dt['hour'] = 0;
if( !isset( $dt['min'] )) $dt['min'] = 0;
if( !isset( $dt['sec'] )) $dt['sec'] = 0;
$str = sprintf( $fmtYmdHis, (int) $dt['year'], (int) $dt['month'], (int) $dt['day'], (int) $dt['hour'], (int) $dt['min'], (int) $dt['sec'] );
if( isset( $dt['tz'] ) && ( 'Z' == $dt['tz'] ))
$str .= 'Z';
$v = $child->addChild( $type, $str );
}
break;
case 'duration':
$output = (( 'trigger' == $name ) && ( FALSE !== $content['before'] )) ? '-' : '';
$v = $child->addChild( $type, $output.iCalUtilityFunctions::_duration2str( $content ) );
break;
case 'geo':
if( !empty( $content )) {
$v1 = $child->addChild( 'latitude', iCalUtilityFunctions::_geo2str2( $content['latitude'], iCalUtilityFunctions::$geoLatFmt ));
$v1 = $child->addChild( 'longitude', iCalUtilityFunctions::_geo2str2( $content['longitude'], iCalUtilityFunctions::$geoLongFmt ));
}
break;
case 'integer':
$v = $child->addChild( $type, (string) $content );
break;
case 'period':
if( !is_array( $content ))
break;
foreach( $content as $period ) {
$v1 = $child->addChild( $type );
$str = sprintf( $fmtYmdHis, (int) $period[0]['year'], (int) $period[0]['month'], (int) $period[0]['day'], (int) $period[0]['hour'], (int) $period[0]['min'], (int) $period[0]['sec'] );
if( isset( $period[0]['tz'] ) && ( 'Z' == $period[0]['tz'] ))
$str .= 'Z';
$v2 = $v1->addChild( 'start', $str );
if( array_key_exists( 'year', $period[1] )) {
$str = sprintf( $fmtYmdHis, (int) $period[1]['year'], (int) $period[1]['month'], (int) $period[1]['day'], (int) $period[1]['hour'], (int) $period[1]['min'], (int) $period[1]['sec'] );
if( isset($period[1]['tz'] ) && ( 'Z' == $period[1]['tz'] ))
$str .= 'Z';
$v2 = $v1->addChild( 'end', $str );
}
else
$v2 = $v1->addChild( 'duration', iCalUtilityFunctions::_duration2str( $period[1] ));
}
break;
case 'recur':
$content = array_change_key_case( $content );
foreach( $content as $rulelabel => $rulevalue ) {
switch( $rulelabel ) {
case 'until':
if( isset( $rulevalue['hour'] ))
$str = sprintf( $fmtYmdHis, (int) $rulevalue['year'], (int) $rulevalue['month'], (int) $rulevalue['day'], (int) $rulevalue['hour'], (int) $rulevalue['min'], (int) $rulevalue['sec'] ).'Z';
else
$str = sprintf( $fmtYmd, (int) $rulevalue['year'], (int) $rulevalue['month'], (int) $rulevalue['day'] );
$v = $child->addChild( $rulelabel, $str );
break;
case 'bysecond':
case 'byminute':
case 'byhour':
case 'bymonthday':
case 'byyearday':
case 'byweekno':
case 'bymonth':
case 'bysetpos': {
if( is_array( $rulevalue )) {
foreach( $rulevalue as $vix => $valuePart )
$v = $child->addChild( $rulelabel, $valuePart );
}
else
$v = $child->addChild( $rulelabel, $rulevalue );
break;
}
case 'byday': {
if( isset( $rulevalue['DAY'] )) {
$str = ( isset( $rulevalue[0] )) ? $rulevalue[0] : '';
$str .= $rulevalue['DAY'];
$p = $child->addChild( $rulelabel, $str );
}
else {
foreach( $rulevalue as $valuePart ) {
if( isset( $valuePart['DAY'] )) {
$str = ( isset( $valuePart[0] )) ? $valuePart[0] : '';
$str .= $valuePart['DAY'];
$p = $child->addChild( $rulelabel, $str );
}
else
$p = $child->addChild( $rulelabel, $valuePart );
}
}
break;
}
case 'freq':
case 'count':
case 'interval':
case 'wkst':
default:
$p = $child->addChild( $rulelabel, $rulevalue );
break;
} // end switch( $rulelabel )
} // end foreach( $content as $rulelabel => $rulevalue )
break;
case 'rstatus':
$v = $child->addChild( 'code', number_format( (float) $content['statcode'], 2, '.', ''));
$v = $child->addChild( 'description', htmlspecialchars( $content['text'] ));
if( isset( $content['extdata'] ))
$v = $child->addChild( 'data', htmlspecialchars( $content['extdata'] ));
break;
case 'text':
if( !is_array( $content ))
$content = array( $content );
foreach( $content as $part )
$v = $child->addChild( $type, htmlspecialchars( $part ));
break;
case 'time':
break;
case 'uri':
$v = $child->addChild( $type, $content );
break;
case 'utc-offset':
if( in_array( substr( $content, 0, 1 ), array( '-', '+' ))) {
$str = substr( $content, 0, 1 );
$content = substr( $content, 1 );
}
else
$str = '+';
$str .= substr( $content, 0, 2 ).':'.substr( $content, 2, 2 );
if( 4 < strlen( $content ))
$str .= ':'.substr( $content, 4 );
$v = $child->addChild( $type, $str );
break;
case 'unknown':
default:
if( is_array( $content ))
$content = implode( '', $content );
$v = $child->addChild( 'unknown', htmlspecialchars( $content ));
break;
}
}
/**
* parse xml file into iCalcreator instance
*
* @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
* @since 2.16.22 - 2013-06-18
* @param string $xmlfile
* @param array $iCalcfg iCalcreator config array (opt)
* @return mixediCalcreator instance or FALSE on error
*/
function XMLfile2iCal( $xmlfile, $iCalcfg=array()) {
if( FALSE === ( $xmlstr = file_get_contents( $xmlfile )))
return FALSE;
return xml2iCal( $xmlstr, $iCalcfg );
}
/**
* parse xml string into iCalcreator instance, alias of XML2iCal
*
* @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
* @since 2.16.22 - 2013-06-18
* @param string $xmlstr
* @param array $iCalcfg iCalcreator config array (opt)
* @return mixed iCalcreator instance or FALSE on error
*/
function XMLstr2iCal( $xmlstr, $iCalcfg=array()) {
return XML2iCal( $xmlstr, $iCalcfg);
}
/**
* parse xml string into iCalcreator instance
*
* @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
* @since 2.16.22 - 2013-06-20
* @param string $xmlstr
* @param array $iCalcfg iCalcreator config array (opt)
* @uses vcalendar::vcalendar()
* @uses XMLgetComps()
* @return mixed iCalcreator instance or FALSE on error
*/
function XML2iCal( $xmlstr, $iCalcfg=array()) {
$xmlstr = str_replace( array( "\r\n", "\n\r", "\n", "\r" ), '', $xmlstr );
$xml = XMLgetTagContent1( $xmlstr, 'vcalendar', $endIx );
$iCal = new vcalendar( $iCalcfg );
XMLgetComps( $iCal, $xmlstr );
unset( $xmlstr );
return $iCal;
}
/**
* parse XML string into iCalcreator components
*
* @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
* @since 2.21.11 - 2015-03-21
* @param object $iCal iCalcreator vcalendar or component object instance
* @param string $xml
* @uses iCalUtilityFunctions::$allComps
* @uses XMLgetTagContent1()
* @uses XMLgetProps()
* @uses XMLgetTagContent2()
* @uses vcalendar::newComponent()
* @uses iCalUtilityFunctions::$allComps
* @uses XMLgetComps()
* @return object
*/
function XMLgetComps( $iCal, $xml ) {
$sx = 0;
while(( FALSE !== substr( $xml, ( $sx + 11 ), 1 )) &&
( '<properties>' != substr( $xml, $sx, 12 )) && ( '<components>' != substr( $xml, $sx, 12 )))
$sx += 1;
if( FALSE === substr( $xml, ( $sx + 11 ), 1 ))
return FALSE;
if( '<properties>' == substr( $xml, $sx, 12 )) {
$xml2 = XMLgetTagContent1( $xml, 'properties', $endIx );
XMLgetProps( $iCal, $xml2 );
$xml = substr( $xml, $endIx );
}
if( '<components>' == substr( $xml, 0, 12 ))
$xml = XMLgetTagContent1( $xml, 'components', $endIx );
while( ! empty( $xml )) {
$xml2 = XMLgetTagContent2( $xml, $tagName, $endIx );
if( in_array( strtolower( $tagName ), iCalUtilityFunctions::$allComps ) &&
( FALSE !== ( $subComp = $iCal->newComponent( $tagName ))))
XMLgetComps( $subComp, $xml2 );
$xml = substr( $xml, $endIx);
}
unset( $xml );
return $iCal;
}
/**
* parse XML into iCalcreator properties
*
* @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
* @since 2.21.11 - 2015-03-21
* @param array $iCal iCalcreator calendar/component instance
* @param string $xml
* @uses XMLgetTagContent2()
* @uses vcalendar::setProperty()
* @uses calendarComponent::setproperty()
* @uses XMLgetTagContent1()
* @uses vcalendar::setProperty()
* @return void
*/
function XMLgetProps( $iCal, $xml) {
while( ! empty( $xml )) {
$xml2 = XMLgetTagContent2( $xml, $propName, $endIx );
$propName = strtoupper( $propName );
if( empty( $xml2 ) && ( '0' != $xml2 )) {
$iCal->setProperty( $propName );
$xml = substr( $xml, $endIx);
continue;
}
$params = array();
if( '<parameters/>' == substr( $xml2, 0, 13 ))
$xml2 = substr( $xml2, 13 );
elseif( '<parameters>' == substr( $xml2, 0, 12 )) {
$xml3 = XMLgetTagContent1( $xml2, 'parameters', $endIx2 );
while( ! empty( $xml3 )) {
$xml4 = XMLgetTagContent2( $xml3, $paramKey, $endIx3 );
$pType = FALSE; // skip parameter valueType
$paramKey = strtoupper( $paramKey );
static $mParams = array( 'DELEGATED-FROM', 'DELEGATED-TO', 'MEMBER' );
if( in_array( $paramKey, $mParams )) {
while( ! empty( $xml4 )) {
if( ! isset( $params[$paramKey] ))
$params[$paramKey] = array( XMLgetTagContent1( $xml4, 'cal-address', $endIx4 ));
else
$params[$paramKey][] = XMLgetTagContent1( $xml4, 'cal-address', $endIx4 );
$xml4 = substr( $xml4, $endIx4 );
}
}
else {
if( ! isset( $params[$paramKey] ))
$params[$paramKey] = html_entity_decode( XMLgetTagContent2( $xml4, $pType, $endIx4 ));
else
$params[$paramKey] .= ','.html_entity_decode( XMLgetTagContent2( $xml4, $pType, $endIx4 ));
}
$xml3 = substr( $xml3, $endIx3 );
}
$xml2 = substr( $xml2, $endIx2 );
} // if( '<parameters>' == substr( $xml2, 0, 12 ))
$valueType = FALSE;
$value = ( ! empty( $xml2 ) || ( '0' == $xml2 )) ? XMLgetTagContent2( $xml2, $valueType, $endIx3 ) : '';
switch( $propName ) {
case 'CATEGORIES':
case 'RESOURCES':
$tValue = array();
while( ! empty( $xml2 )) {
$tValue[] = html_entity_decode( XMLgetTagContent2( $xml2, $valueType, $endIx4 ));
$xml2 = substr( $xml2, $endIx4 );
}
$value = $tValue;
break;
case 'EXDATE': // multiple single-date(-times) may exist
case 'RDATE':
if( 'period' != $valueType ) {
if( 'date' == $valueType )
$params['VALUE'] = 'DATE';
$t = array();
while( ! empty( $xml2 ) && ( '<date' == substr( $xml2, 0, 5 ))) {
$t[] = XMLgetTagContent2( $xml2, $pType, $endIx4 );
$xml2 = substr( $xml2, $endIx4 );
}
$value = $t;
break;
}
case 'FREEBUSY':
if( 'RDATE' == $propName )
$params['VALUE'] = 'PERIOD';
$value = array();
while( ! empty( $xml2 ) && ( '<period>' == substr( $xml2, 0, 8 ))) {
$xml3 = XMLgetTagContent1( $xml2, 'period', $endIx4 ); // period
$t = array();
while( ! empty( $xml3 )) {
$t[] = XMLgetTagContent2( $xml3, $pType, $endIx5 ); // start - end/duration
$xml3 = substr( $xml3, $endIx5 );
}
$value[] = $t;
$xml2 = substr( $xml2, $endIx4 );
}
break;
case 'TZOFFSETTO':
case 'TZOFFSETFROM':
$value = str_replace( ':', '', $value );
break;
case 'GEO':
$tValue = array( 'latitude' => $value );
$tValue['longitude'] = XMLgetTagContent1( substr( $xml2, $endIx3 ), 'longitude', $endIx3 );
$value = $tValue;
break;
case 'EXRULE':
case 'RRULE':
$tValue = array( $valueType => $value );
$xml2 = substr( $xml2, $endIx3 );
$valueType = FALSE;
while( ! empty( $xml2 )) {
$t = XMLgetTagContent2( $xml2, $valueType, $endIx4 );
switch( $valueType ) {
case 'freq':
case 'count':
case 'until':
case 'interval':
case 'wkst':
$tValue[$valueType] = $t;
break;
case 'byday':
if( 2 == strlen( $t ))
$tValue[$valueType][] = array( 'DAY' => $t );
else {
$day = substr( $t, -2 );
$key = substr( $t, 0, ( strlen( $t ) - 2 ));
$tValue[$valueType][] = array( $key, 'DAY' => $day );
}
break;
default:
$tValue[$valueType][] = $t;
}
$xml2 = substr( $xml2, $endIx4 );
}
$value = $tValue;
break;
case 'REQUEST-STATUS':
$tValue = array();
while( ! empty( $xml2 )) {
$t = html_entity_decode( XMLgetTagContent2( $xml2, $valueType, $endIx4 ));
$tValue[$valueType] = $t;
$xml2 = substr( $xml2, $endIx4 );
}
if( ! empty( $tValue ))
$value = $tValue;
else
$value = array( 'code' => null, 'description' => null );
break;
default:
switch( $valueType ) {
case 'binary': $params['VALUE'] = 'BINARY'; break;
case 'date': $params['VALUE'] = 'DATE'; break;
case 'date-time': $params['VALUE'] = 'DATE-TIME'; break;
case 'text':
case 'unknown': $value = html_entity_decode( $value ); break;
}
break;
} // end switch( $propName )
if( 'FREEBUSY' == $propName ) {
$fbtype = $params['FBTYPE'];
unset( $params['FBTYPE'] );
$iCal->setProperty( $propName, $fbtype, $value, $params );
}
elseif( 'GEO' == $propName )
$iCal->setProperty( $propName, $value['latitude'], $value['longitude'], $params );
elseif( 'REQUEST-STATUS' == $propName ) {
if( !isset( $value['data'] ))
$value['data'] = FALSE;
$iCal->setProperty( $propName, $value['code'], $value['description'], $value['data'], $params );
}
else {
if( empty( $value ) && ( is_array( $value ) || ( '0' > $value )))
$value = '';
$iCal->setProperty( $propName, $value, $params );
}
$xml = substr( $xml, $endIx);
} // end while( ! empty( $xml ))
}
/**
* fetch a specific XML tag content
*
* @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
* @since 2.16.22 - 2013-06-20
* @param string $xml
* @param string $tagName
* @param int $endIx
* @return mixed
*/
function XMLgetTagContent1( $xml, $tagName, & $endIx=0 ) {
$strlen = strlen( $tagName );
$sx1 = 0;
while( FALSE !== substr( $xml, $sx1, 1 )) {
if(( FALSE !== substr( $xml, ( $sx1 + $strlen + 1 ), 1 )) &&
( strtolower( "<$tagName>" ) == strtolower( substr( $xml, $sx1, ( $strlen + 2 )))))
break;
if(( FALSE !== substr( $xml, ( $sx1 + $strlen + 3 ), 1 )) &&
( strtolower( "<$tagName />" ) == strtolower( substr( $xml, $sx1, ( $strlen + 4 ))))) { // empty tag
$endIx = $strlen + 5;
return '';
}
if(( FALSE !== substr( $xml, ( $sx1 + $strlen + 2 ), 1 )) &&
( strtolower( "<$tagName/>" ) == strtolower( substr( $xml, $sx1, ( $strlen + 3 ))))) { // empty tag
$endIx = $strlen + 4;
return '';
}
$sx1 += 1;
}
if( FALSE === substr( $xml, $sx1, 1 )) {
$endIx = ( empty( $sx )) ? 0 : $sx - 1;
return '';
}
if( FALSE === ( $pos = stripos( $xml, "</$tagName>" ))) { // missing end tag??
$endIx = strlen( $xml ) + 1;
return '';
}
$endIx = $pos + $strlen + 3;
return substr( $xml, ( $sx1 + $strlen + 2 ), ( $pos - $sx1 - 2 - $strlen ));
}
/**
* fetch next (unknown) XML tagname AND content
*
* @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
* @since 2.16.22 - 2013-06-20
* @param string $xml
* @param string $tagName
* @param int $endIx
* @return mixed
*/
function XMLgetTagContent2( $xml, & $tagName, & $endIx ) {
$endIx = strlen( $xml ) + 1; // just in case.. .
$sx1 = 0;
while( FALSE !== substr( $xml, $sx1, 1 )) {
if( '<' == substr( $xml, $sx1, 1 )) {
if(( FALSE !== substr( $xml, ( $sx1 + 3 ), 1 )) && ( '<!--' == substr( $xml, $sx1, 4 ))) // skip comment
$sx1 += 1;
else
break; // tagname start here
}
else
$sx1 += 1;
}
$sx2 = $sx1;
while( FALSE !== substr( $xml, $sx2 )) {
if(( FALSE !== substr( $xml, ( $sx2 + 1 ), 1 )) && ( '/>' == substr( $xml, $sx2, 2 ))) { // empty tag
$tagName = trim( substr( $xml, ( $sx1 + 1 ), ( $sx2 - $sx1 - 1 )));
$endIx = $sx2 + 2;
return '';
}
if( '>' == substr( $xml, $sx2, 1 )) // tagname ends here
break;
$sx2 += 1;
}
$tagName = substr( $xml, ( $sx1 + 1 ), ( $sx2 - $sx1 - 1 ));
$endIx = $sx2 + 1;
if( FALSE === substr( $xml, $sx2, 1 )) {
return '';
}
$strlen = strlen( $tagName );
if(( 'duration' == $tagName ) &&
( FALSE !== ( $pos1 = stripos( $xml, "<duration>", $sx1+1 ))) &&
( FALSE !== ( $pos2 = stripos( $xml, "</duration>", $pos1+1 ))) &&
( FALSE !== ( $pos3 = stripos( $xml, "</duration>", $pos2+1 ))) &&
( $pos1 < $pos2 ) && ( $pos2 < $pos3 ))
$pos = $pos3;
elseif( FALSE === ( $pos = stripos( $xml, "</$tagName>", $sx2 )))
return '';
$endIx = $pos + $strlen + 3;
return substr( $xml, ( $sx1 + $strlen + 2 ), ( $pos - $strlen - 2 ));
}