registerXPathNameSpace('cal','urn:ietf:params:xml:ns:xcal'); // Check if there's only 1 component $components = array('vevent','vtodo','vjournal','vfreebusy'); $componentsFound = array(); foreach($components as $component) { $test = $xcal->xpath('/cal:iCalendar/cal:vcalendar/cal:' . $component); if (is_array($test)) $componentsFound = array_merge($componentsFound, $test); } if (count($componentsFound)>1) { throw new Sabre_CalDAV_Exception_InvalidICalendarObject('Only 1 of VEVENT, VTODO, VJOURNAL or VFREEBUSY may be specified per calendar object'); } if (count($componentsFound)<1) { throw new Sabre_CalDAV_Exception_InvalidICalendarObject('One VEVENT, VTODO, VJOURNAL or VFREEBUSY must be specified. 0 found.'); } $component = $componentsFound[0]; // Check if the component is allowed $name = $component->getName(); if (!in_array(strtoupper($name),$allowedComponents)) { throw new Sabre_CalDAV_Exception_InvalidICalendarObject(strtoupper($name) . ' is not allowed in this calendar.'); } if (count($xcal->xpath('/cal:iCalendar/cal:vcalendar/cal:method'))>0) { throw new Sabre_CalDAV_Exception_InvalidICalendarObject('The METHOD property is not allowed in calendar objects'); } return true; } /** * Converts ICalendar data to XML. * * Properties are converted to lowercase xml elements. Parameters are; * converted to attributes. BEGIN:VEVENT is converted to and * END:VEVENT as well as other components. * * It's a very loose parser. If any line does not conform to the spec, it * will simply be ignored. It will try to detect if \r\n or \n line endings * are used. * * @todo Currently quoted attributes are not parsed correctly. * @see http://tools.ietf.org/html/draft-royer-calsch-xcal-03 * @param string $icalData * @return string. */ static function toXCAL($icalData) { // Detecting line endings $lb="\r\n"; if (strpos($icalData,"\r\n")!==false) $lb = "\r\n"; elseif (strpos($icalData,"\n")!==false) $lb = "\n"; // Splitting up items per line $lines = explode($lb,$icalData); // Properties can be folded over 2 lines. In this case the second // line will be preceeded by a space or tab. $lines2 = array(); foreach($lines as $line) { if (!$line) continue; if ($line[0]===" " || $line[0]==="\t") { $lines2[count($lines2)-1].=substr($line,1); continue; } $lines2[]=$line; } $xml = '' . "\n"; $xml.= "\n"; $spaces = 2; foreach($lines2 as $line) { $matches = array(); // This matches PROPERTYNAME;ATTRIBUTES:VALUE if (!preg_match('/^([^:^;]*)(?:;([^:]*))?:(.*)$/',$line,$matches)) continue; $propertyName = strtolower($matches[1]); $attributes = $matches[2]; $value = $matches[3]; // If the line was in the format BEGIN:COMPONENT or END:COMPONENT, we need to special case it. if ($propertyName === 'begin') { $xml.=str_repeat(" ",$spaces); $xml.='<' . strtolower($value) . ">\n"; $spaces+=2; continue; } elseif ($propertyName === 'end') { $spaces-=2; $xml.=str_repeat(" ",$spaces); $xml.='\n"; continue; } $xml.=str_repeat(" ",$spaces); $xml.='<' . $propertyName; if ($attributes) { // There can be multiple attributes $attributes = explode(';',$attributes); foreach($attributes as $att) { list($attName,$attValue) = explode('=',$att,2); $attName = strtolower($attName); if ($attName === 'language') $attName='xml:lang'; $xml.=' ' . $attName . '="' . htmlspecialchars($attValue) . '"'; } } $xml.='>'. htmlspecialchars(trim($value)) . '\n"; } $xml.=""; return $xml; } }