aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorJack Bates <jablko@users.sourceforge.net>2006-04-13 05:10:24 +0000
committerJack Bates <jablko@users.sourceforge.net>2006-04-13 05:10:24 +0000
commit428ef55248c513015bc3233cf62c0e9db0dfbb3a (patch)
tree05ded9ab63ef157aded0ca1075607ae7da49945c /lib
parent4ec912ca0ff14694f7cc8ae7d6a01d084847a5f9 (diff)
downloadphpicalendar-428ef55248c513015bc3233cf62c0e9db0dfbb3a.tar.gz
phpicalendar-428ef55248c513015bc3233cf62c0e9db0dfbb3a.tar.bz2
phpicalendar-428ef55248c513015bc3233cf62c0e9db0dfbb3a.zip
* Almost working preliminary REPORT support
* ReportParser successfully parses calendar-data request values * _componentParser almost parses iCalendar files & limits by calendar-data request value * TODO Determine whether _componentParser is rejecting valid iCalendar files * TODO Reduce duplicate code by factoring special property handling out of propfind_response_helper * TODO Push filtering parser into bennu?
Diffstat (limited to 'lib')
-rw-r--r--lib/HTTP/CalDAV/Server.php491
-rw-r--r--lib/HTTP/CalDAV/Tools/ReportParser.php251
-rw-r--r--lib/bennu/bennu.class.php59
-rw-r--r--lib/bennu/iCalendar_components.php410
-rw-r--r--lib/bennu/iCalendar_parameters.php240
-rw-r--r--lib/bennu/iCalendar_properties.php1299
-rw-r--r--lib/bennu/iCalendar_rfc2445.php785
7 files changed, 3516 insertions, 19 deletions
diff --git a/lib/HTTP/CalDAV/Server.php b/lib/HTTP/CalDAV/Server.php
index eabc27b..ab478d4 100644
--- a/lib/HTTP/CalDAV/Server.php
+++ b/lib/HTTP/CalDAV/Server.php
@@ -20,11 +20,13 @@
* @author Jack Bates <ms419@freezone.co.uk>
* @copyright 2006 The PHP Group
* @license PHP License 3.0 http://www.php.net/license/3_0.txt
- * @version CVS: $Id: Server.php,v 1.1 2006/04/09 19:43:59 jablko Exp $
+ * @version CVS: $Id: Server.php,v 1.2 2006/04/13 05:10:24 jablko Exp $
* @link http://pear.php.net/package/HTTP_CalDAV_Server
* @see HTTP_WebDAV_Server
*/
+require_once 'Tools/ReportParser.php';
+
/**
* CalDav Server class
*
@@ -35,12 +37,25 @@
* @author Jack Bates <ms419@freezone.co.uk>
* @copyright 2006 The PHP Group
* @license PHP License 3.0 http://www.php.net/license/3_0.txt
- * @version CVS: $Id: Server.php,v 1.1 2006/04/09 19:43:59 jablko Exp $
+ * @version CVS: $Id: Server.php,v 1.2 2006/04/13 05:10:24 jablko Exp $
* @link http://pear.php.net/package/HTTP_CalDAV_Server
* @see HTTP_WebDAV_Server
*/
class HTTP_CalDAV_Server extends HTTP_WebDAV_Server
{
+ /**
+ * Make a property in the CalDAV namespace
+ *
+ * @param string property name
+ * @param string property value
+ * @return array string property namespace
+ * string property name
+ * string property value
+ */
+ function calDavProp($name, $value=null, $status=null) {
+ return $this->mkprop('urn:ietf:params:xml:ns:caldav', $name, $value,
+ $status);
+ }
/**
* REPORT request helper - prepares data-structures from REPORT requests
@@ -52,6 +67,20 @@ class HTTP_CalDAV_Server extends HTTP_WebDAV_Server
function report_request_helper(&$options)
{
$options = array();
+ $options['path'] = $this->path;
+
+ $options['depth'] = 'infinity';
+ if (isset($_SERVER['HTTP_DEPTH'])) {
+ $options['depth'] = $_SERVER['HTTP_DEPTH'];
+ }
+
+ $parser = new ReportParser('php://input');
+ if (!$parser->success) {
+ $this->http_status('400 Bad Request');
+ return;
+ }
+
+ $options['props'] = $parser->props;
return true;
}
@@ -63,8 +92,168 @@ class HTTP_CalDAV_Server extends HTTP_WebDAV_Server
* @return void
* @access public
*/
- function report_response_helper($options, $responses)
+ function report_response_helper($options, $files)
{
+ $responses = array();
+
+ // now loop over all returned files
+ foreach ($files as $file) {
+
+ // collect namespaces here
+ $ns_hash = array('urn:ietf:params:xml:ns:caldav' => 'C');
+
+ $response = array();
+
+ $response['href'] = $this->getHref($file['path']);
+ if (isset($file['href'])) {
+ $response['href'] = $file['href'];
+ }
+
+ $response['propstat'] = array();
+
+ // nothing to do if no properties were returend
+ if (isset($file['props']) && is_array($file['props'])) {
+
+ // now loop over all returned properties
+ foreach ($file['props'] as $prop) {
+ $status = '200 OK';
+
+ // as a convenience feature we do not require user handlers
+ // restrict returned properties to the requested ones
+ // here we ignore unrequested entries
+ switch ($options['props']) {
+ case 'propname':
+
+ // only names of all existing properties were requested
+ // so remove values
+ unset($prop['value']);
+
+ case 'allprop':
+ if (isset($prop['status'])) {
+ $status = $prop['status'];
+ }
+
+ if (!isset($response['propstat'][$status])) {
+ $response['propstat'][$status] = array();
+ }
+
+ $response['propstat'][$status][] = $prop;
+ break;
+
+ default:
+
+ // search property name in requested properties
+ foreach($options['props'] as $reqprop) {
+ if ($reqprop['name'] == $prop['name'] &&
+ $reqprop['ns'] == $prop['ns']) {
+ if (isset($prop['status'])) {
+ $status = $prop['status'];
+ }
+
+ if (!isset($response['propstat'][$status])) {
+ $response['propstat'][$status] = array();
+ }
+
+ $response['propstat'][$status][] = $prop;
+ break (2);
+ }
+ }
+
+ continue (2);
+ }
+
+ // namespace handling
+ if (empty($prop['ns']) || // empty namespace
+ $prop['ns'] == 'DAV:' || // default namespace
+ isset($ns_hash[$prop['ns']])) { // already known
+ continue;
+ }
+
+ // register namespace
+ $ns_hash[$prop['ns']] = 'ns' . count($ns_hash);
+ }
+ }
+
+ // also need empty entries for properties requested
+ // but for which no values where returned
+ if (isset($options['props']) && is_array($options['props'])) {
+
+ // now loop over all requested properties
+ foreach ($options['props'] as $reqprop) {
+ $status = '404 Not Found';
+
+ // check if property exists in result
+ foreach ($file['props'] as $prop) {
+ if ($reqprop['name'] == $prop['name'] &&
+ $reqprop['ns'] == $prop['ns']) {
+ continue (2);
+ }
+ }
+
+ if ($reqprop['name'] == 'lockdiscovery' &&
+ $reqprop['ns'] == 'DAV:' &&
+ method_exists($this, 'getLocks')) {
+
+ $status = '200 OK';
+ if (!isset($response['propstat'][$status])) {
+ $response['propstat'][$status] = array();
+ }
+
+ $response['propstat'][$status][] =
+ $this->mkprop('DAV:', 'lockdiscovery',
+ $this->getLocks($file['path']));
+ continue;
+ }
+
+ if ($reqprop['name'] == 'calendar-data' &&
+ $reqprop['ns'] == 'urn:ietf:params:xml:ns:caldav' &&
+ method_exists($this, 'get')) {
+
+ $prop = $this->_calendarData($file['path'],
+ $reqprop['value']);
+ if (isset($prop)) {
+ $status = '200 OK';
+ if (isset($prop['status'])) {
+ $status = $prop['status'];
+ }
+ } else {
+ $prop = HTTP_CalDAV_Server::calDavProp(
+ 'calendar-data');
+ }
+
+ if (!isset($response['propstat'][$status])) {
+ $response['propstat'][$status] = array();
+ }
+
+ $response['propstat'][$status][] = $prop;
+ continue;
+ }
+
+ if (!isset($response['propstat'][$status])) {
+ $response['propstat'][$status] = array();
+ }
+
+ // add empty value for this property
+ $response['propstat'][$status][] =
+ $this->mkprop($reqprop['ns'], $reqprop['name'],
+ null);
+
+ // namespace handling
+ if (empty($reqprop['ns']) || // empty namespace
+ $reqprop['ns'] == 'DAV:' || // default namespace
+ isset($ns_hash[$reqprop['ns']])) { // already known
+ continue;
+ }
+
+ // register namespace
+ $ns_hash[$reqprop['ns']] = 'ns' . count($ns_hash);
+ }
+ }
+
+ $response['ns_hash'] = $ns_hash;
+ $responses[] = $response;
+ }
+
$this->_multistatus($responses);
}
@@ -77,31 +266,295 @@ class HTTP_CalDAV_Server extends HTTP_WebDAV_Server
*/
function report_wrapper()
{
- // prepare data-structure from REPORT request
+ /* Prepare data-structure from REPORT request */
if (!$this->report_request_helper($options)) {
return;
}
- // call user handler
- if (!$this->report($options, $responses)) {
+ /* Call user handler */
+ if (method_exists($this, 'report')) {
+ if (!$this->report($options, $files)) {
+ return;
+ }
+ } else {
+
+ /* Empulate REPORT using PROPFIND */
+ if (!$this->propfind($options, $files)) {
+ return;
+ }
+ }
+
+ /* Format REPORT response */
+ $this->report_response_helper($options, $files);
+ }
+
+ function _calendarData($path, $data=null, $filter=null)
+ {
+ if (is_array($data['comps']) &&
+ !isset($data['comps']['VCALENDAR'])) {
+ return HTTP_CalDAV_Server::calDavProp('calendar-data');
+ }
+
+ $options = array();
+ $options['path'] = $path;
+
+ $status = $this->get($options);
+ if (empty($status)) {
+ $status = '403 Forbidden';
+ }
+
+ if ($status !== true) {
+ return HTTP_CalDAV_Server::calDavProp('calendar-data', null,
+ $status);
+ }
+
+ if ($options['mimetype'] != 'text/calendar') {
+ return HTTP_CalDAV_Server::calDavProp('calendar-data', null,
+ '403 Forbidden');
+ }
+
+ if ($options['stream']) {
+ $handle = $options['stream'];
+ } else if ($options['data']) {
+ // What about data?
+ } else {
return;
}
- // format REPORT response
- $this->report_response_helper($options, $responses);
+ if (($line = fgets($handle, 4096)) === false) {
+ return;
+ }
+
+ if (trim($line) != 'BEGIN:VCALENDAR') {
+ return;
+ }
+
+ if (!($value = HTTP_CalDAV_Server::_parseComponent($handle,
+ 'VCALENDAR', is_array($data['comps']) ?
+ $data['comps']['VCALENDAR'] : null))) {
+ return;
+ }
+
+ return HTTP_CalDAV_Server::calDavProp('calendar-data', $value);
}
- /**
- * Make a property in the CalDAV namespace
- *
- * @param string property name
- * @param string property value
- * @return array string property namespace
- * string property name
- * string property value
- */
- function calDavProp($name, $value=null) {
- return $this->mkprop('urn:ietf:params:xml:ns:caldav', $name, $value);
+ function _parseComponent($handle, $name, $data=null, $filter=null)
+ {
+ $className = 'iCalendar_' . ltrim(strtolower($name), 'v');
+ if ($name == 'VCALENDAR') {
+ $className = 'iCalendar';
+ }
+
+ if (!class_exists($className)) {
+ return;
+ }
+ $component = new $className;
+
+ while (($line = fgets($handle, 4096)) !== false) {
+ $line = explode(':', trim($line));
+
+ if ($line[0] == 'END') {
+ if ($line[1] != $name) {
+ return;
+ }
+
+ return $component;
+ }
+
+ if ($line[0] == 'BEGIN') {
+ if (is_array($data['comps']) &&
+ !isset($data['comps'][$line[1]])) {
+ while (($l = fgets($handle, 4096)) !== false) {
+ if (trim($l) == "END:$line[1]") {
+ continue (2);
+ }
+ }
+
+ return;
+ }
+
+ if (!($childComponent = HTTP_CalDAV_Server::_parseComponent(
+ $handle, $line[1], is_array($data['comps']) ?
+ $data['comps'][$line[1]] : null))) {
+ while (($l = fgets($handle, 4096)) !== false) {
+ if (trim($l) == "END:$line[1]") {
+ continue (2);
+ }
+ }
+
+ return;
+ }
+
+ if (!$component->add_component($childComponent)) {
+ return;
+ }
+
+ continue;
+ }
+
+ $line[0] = explode(';=', $line[0]);
+ $prop_name = array_shift($line[0]);
+ if (is_array($data['props']) &&
+ !in_array($prop_name, $data['props'])) {
+ continue;
+ }
+
+ $params = array();
+ while (($param_name = array_shift($line[0])) &&
+ ($param_value = array_shift($line[0]))) {
+ $params[$param_name] = $param_value;
+ }
+ $component->add_property($prop_name, $line[1], $params);
+ }
+ }
+
+ function _multistatus($responses)
+ {
+ // now we generate the response header...
+ $this->http_status('207 Multi-Status');
+ header('Content-Type: text/xml; charset="utf-8"');
+
+ // ...and payload
+ echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
+ echo "<D:multistatus xmlns:D=\"DAV:\">\n";
+
+ foreach ($responses as $response) {
+
+ // ignore empty or incomplete entries
+ if (!is_array($response) || empty($response)) {
+ continue;
+ }
+
+ $ns_defs = array();
+ foreach ($response['ns_hash'] as $name => $prefix) {
+ $ns_defs[] = "xmlns:$prefix=\"$name\"";
+ }
+ echo ' <D:response ' . implode(' ', $ns_defs) . ">\n";
+ echo " <D:href>$response[href]</D:href>\n";
+
+ // report all found properties and their values (if any)
+ // nothing to do if no properties were returend for a file
+ if (isset($response['propstat']) &&
+ is_array($response['propstat'])) {
+
+ foreach ($response['propstat'] as $status => $props) {
+ echo " <D:propstat>\n";
+ echo " <D:prop>\n";
+
+ foreach ($props as $prop) {
+ if (!is_array($prop) || !isset($prop['name'])) {
+ continue;
+ }
+
+ // empty properties (cannot use empty for check as '0'
+ // is a legal value here)
+ if (!isset($prop['value']) || empty($prop['value']) &&
+ $prop['value'] !== 0) {
+ if ($prop['ns'] == 'DAV:') {
+ echo " <D:$prop[name]/>\n";
+ continue;
+ }
+
+ if (!empty($prop['ns'])) {
+ echo ' <' .
+ $response['ns_hash'][$prop['ns']] .
+ ":$prop[name]/>\n";
+ continue;
+ }
+
+ echo " <$prop[name] xmlns=\"\"/>";
+ continue;
+ }
+
+ // some WebDAV properties need special treatment
+ if ($prop['ns'] == 'DAV:') {
+
+ switch ($prop['name']) {
+ case 'creationdate':
+ echo " <D:creationdate ns0:dt=\"dateTime.tz\">\n";
+ echo ' ' . gmdate('Y-m-d\TH:i:s\Z', $prop['value']) . "\n";
+ echo " </D:creationdate>\n";
+ break;
+
+ case 'getlastmodified':
+ echo " <D:getlastmodified ns0:dt=\"dateTime.rfc1123\">\n";
+ echo ' ' . gmdate('D, d M Y H:i:s', $prop['value']) . " GMT\n";
+ echo " </D:getlastmodified>\n";
+ break;
+
+ case 'resourcetype':
+ echo " <D:resourcetype>\n";
+ echo " <D:$prop[value]/>\n";
+ echo " </D:resourcetype>\n";
+ break;
+
+ case 'supportedlock':
+
+ if (is_array($prop[value])) {
+ $prop[value] = $this->_lockentries($prop[value]);
+ }
+ echo " <D:supportedlock>\n";
+ echo " $prop[value]\n";
+ echo " </D:supportedlock>\n";
+ break;
+
+ case 'lockdiscovery':
+
+ if (is_array($prop[value])) {
+ $prop[value] = $this->_activelocks($prop[value]);
+ }
+ echo " <D:lockdiscovery>\n";
+ echo " $prop[value]\n";
+ echo " </D:lockdiscovery>\n";
+ break;
+
+ default:
+ echo " <D:$prop[name]>\n";
+ echo ' ' . $this->_prop_encode(htmlspecialchars($prop['value'])) . "\n";
+ echo " </D:$prop[name]>\n";
+ }
+
+ continue;
+ }
+
+ if ($prop['name'] == 'calendar-data' &&
+ is_object($prop['value'])) {
+ $prop['value'] = $prop['value']->serialize();
+ }
+
+ if (!empty($prop['ns'])) {
+ echo ' <' . $response['ns_hash'][$prop['ns']] . ":$prop[name]>\n";
+ echo ' ' . $this->_prop_encode(htmlspecialchars($prop['value'])) . "\n";
+ echo ' </' . $response['ns_hash'][$prop['ns']] . ":$prop[name]>\n";
+
+ continue;
+ }
+
+ echo " <$prop[name] xmlns=\"\">\n";
+ echo ' ' . $this->_prop_encode(htmlspecialchars($prop['value'])) . "\n";
+ echo " </$prop[name]>\n";
+ }
+
+ echo " </D:prop>\n";
+ echo " <D:status>HTTP/1.1 $status</D:status>\n";
+ echo " </D:propstat>\n";
+ }
+ }
+
+ if (isset($response['status'])) {
+ echo " <D:status>HTTP/1.1 $status</D:status>\n";
+ }
+
+ if (isset($response['responsedescription'])) {
+ echo " <D:responsedescription>\n";
+ echo ' ' . $this->_prop_encode(htmlspecialchars($response['responsedescription'])) . "\n";
+ echo " </D:responsedescription>\n";
+ }
+
+ echo " </D:response>\n";
+ }
+
+ echo "</D:multistatus>\n";
}
}
diff --git a/lib/HTTP/CalDAV/Tools/ReportParser.php b/lib/HTTP/CalDAV/Tools/ReportParser.php
new file mode 100644
index 0000000..9d9c9c7
--- /dev/null
+++ b/lib/HTTP/CalDAV/Tools/ReportParser.php
@@ -0,0 +1,251 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Helper for parsing REPORT request bodies
+ *
+ * Long description for file (if any)...
+ *
+ * PHP versions 4 & 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License & are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately
+ *
+ * @category HTTP
+ * @package HTTP_CalDAV_Server
+ * @author Jack Bates <ms419@freezone.co.uk>
+ * @copyright 2006 The PHP Group
+ * @license PHP License 3.0 http://www.php.net/license/3_0.txt
+ * @version CVS: $Id: ReportParser.php,v 1.1 2006/04/13 05:10:24 jablko Exp $
+ * @link http://pear.php.net/package/HTTP_CalDAV_Server
+ * @see HTTP_WebDAV_Server
+ */
+
+/**
+ * Helper for parsing REPORT request bodies
+ *
+ * Long description
+ *
+ * @category HTTP
+ * @package HTTP_CalDAV_Server
+ * @author Jack Bates <ms419@freezone.co.uk>
+ * @copyright 2006 The PHP Group
+ * @license PHP License 3.0 http://www.php.net/license/3_0.txt
+ * @version CVS: $Id: ReportParser.php,v 1.1 2006/04/13 05:10:24 jablko Exp $
+ * @link http://pear.php.net/package/HTTP_CalDAV_Server
+ * @see HTTP_WebDAV_Server
+ */
+class ReportParser
+{
+ /**
+ * Success state flag
+ *
+ * @var bool
+ * @access public
+ */
+ var $success = false;
+
+ /**
+ * Name of the requested report
+ *
+ * @var string
+ * @access public
+ */
+ var $report;
+
+ /**
+ * Found properties are collected here
+ *
+ * @var array
+ * @access public
+ */
+ var $props = array();
+
+ /**
+ * Stack of ancestor tag names
+ *
+ * @var array
+ * @access private
+ */
+ var $_names = array();
+
+ /**
+ * Stack of component data
+ *
+ * @var array
+ * @access private
+ */
+ var $_comps = array();
+
+ /**
+ * Constructor
+ *
+ * @param string path to report input data
+ * @access public
+ */
+ function ReportParser($input)
+ {
+ $handle = fopen($input, 'r');
+ if (!$handle) {
+ return;
+ }
+
+ $parser = xml_parser_create_ns('UTF-8', ' ');
+ xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
+ xml_set_element_handler($parser, array(&$this, '_startElement'),
+ array(&$this, '_endElement'));
+
+ $this->success = true;
+ while (($line = fgets($handle, 4096)) !== false) {
+ $this->success = xml_parse($parser, $line);
+ if (!$this->success) {
+ return;
+ }
+ }
+
+ if (!feof($handle)) {
+ $this->success = false;
+ return;
+ }
+
+ xml_parser_free($parser);
+ fclose($handle);
+
+ if (empty($this->props)) {
+ $this->props = 'allprop';
+ }
+ }
+
+ /**
+ * Start tag handler
+ *
+ * @param object parser
+ * @param string tag name
+ * @param array tag attributes
+ * @access private
+ */
+ function _startElement($parser, $name, $attrs)
+ {
+ $nameComponents = explode(' ', $name);
+ if (count($nameComponents) > 2) {
+ $this->success = false;
+ return;
+ }
+
+ if (count($nameComponents) == 2) {
+ list ($ns, $name) = $nameComponents;
+ if (empty($ns)) {
+ $this->success = false;
+ return;
+ }
+ }
+
+ if (empty($this->_names)) {
+ $this->report = $name;
+ $this->_names[] = $name;
+ return;
+ }
+
+ if (count($this->_names) == 1 &&
+ ($name == 'allprop' || $name == 'propname')) {
+ $this->props = $name;
+ $this->_names[] = $name;
+ return;
+ }
+
+ if (count($this->_names) == 2 && end($this->_names) == 'prop') {
+ $prop = array('name' => $name);
+
+ if ($ns) {
+ $prop['ns'] = $ns;
+ }
+
+ if ($name == 'calendar-data') {
+ $prop['value'] = array();
+ $this->_comps[] =& $prop['value'];
+ }
+
+ $this->props[] = $prop;
+ $this->_names[] = $name;
+ return;
+ }
+
+ if ($name == 'comp') {
+ end($this->_comps);
+
+ // Gross - end returns a copy of the last value
+ $comp =& $this->_comps[key($this->_comps)];
+
+ if (!is_array($comp['comps'])) {
+ $comp['comps'] = array();
+ }
+
+ $comp['comps'][$attrs['name']] = array();
+ $this->_comps[] =& $comp['comps'][$attrs['name']];
+ $this->_names[] = $name;
+ return;
+ }
+
+ if (end($this->_names) == 'comp' && $name == 'prop') {
+ end($this->_comps);
+
+ // Gross - end returns a copy of the last value
+ $comp =& $this->_comps[key($this->_comps)];
+
+ if (!is_array($comp['props'])) {
+ $comp['props'] = array();
+ }
+
+ $comp['props'][] = $attrs['name'];
+ $this->_names[] = $name;
+ return;
+ }
+
+ $this->_names[] = $name;
+ }
+
+ /**
+ * End tag handler
+ *
+ * @param object parser
+ * @param string tag name
+ * @param array tag attributes
+ * @access private
+ */
+ function _endElement($parser, $name) {
+ $nameComponents = explode(' ', $name);
+ if (count($nameComponents) > 2) {
+ $this->success = false;
+ return;
+ }
+
+ if (count($nameComponents) == 2) {
+ list ($ns, $name) = $nameComponents;
+ if (empty($ns)) {
+ $this->success = false;
+ return;
+ }
+ }
+
+ // Any need to pop at end of calendar-data?
+ if ($name == 'comp') {
+ array_pop($this->_comps);
+ }
+
+ array_pop($this->_names);
+ }
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * c-handling-comment-ender-p: nil
+ * End:
+ */
+
+?>
diff --git a/lib/bennu/bennu.class.php b/lib/bennu/bennu.class.php
new file mode 100644
index 0000000..f4bb219
--- /dev/null
+++ b/lib/bennu/bennu.class.php
@@ -0,0 +1,59 @@
+<?php // $Id: bennu.class.php,v 1.1 2006/04/13 05:10:24 jablko Exp $
+
+/**
+ * BENNU - PHP iCalendar library
+ * (c) 2005-2006 Ioannis Papaioannou (pj@moodle.org). All rights reserved.
+ *
+ * Released under the LGPL.
+ *
+ * See http://bennu.sourceforge.net/ for more information and downloads.
+ *
+ * @author Ioannis Papaioannou
+ * @version $Id: bennu.class.php,v 1.1 2006/04/13 05:10:24 jablko Exp $
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+class Bennu {
+ function timestamp_to_datetime($t = NULL) {
+ if($t === NULL) {
+ $t = time();
+ }
+ return gmstrftime('%Y%m%dT%H%M%SZ', $t);
+ }
+
+ function generate_guid() {
+ // Implemented as per the Network Working Group draft on UUIDs and GUIDs
+
+ // These two octets get special treatment
+ $time_hi_and_version = sprintf('%02x', (1 << 6) + mt_rand(0, 15)); // 0100 plus 4 random bits
+ $clock_seq_hi_and_reserved = sprintf('%02x', (1 << 7) + mt_rand(0, 63)); // 10 plus 6 random bits
+
+ // Need another 14 random octects
+ $pool = '';
+ for($i = 0; $i < 7; ++$i) {
+ $pool .= sprintf('%04x', mt_rand(0, 65535));
+ }
+
+ // time_low = 4 octets
+ $random = substr($pool, 0, 8).'-';
+
+ // time_mid = 2 octets
+ $random .= substr($pool, 8, 4).'-';
+
+ // time_high_and_version = 2 octets
+ $random .= $time_hi_and_version.substr($pool, 12, 2).'-';
+
+ // clock_seq_high_and_reserved = 1 octet
+ $random .= $clock_seq_hi_and_reserved;
+
+ // clock_seq_low = 1 octet
+ $random .= substr($pool, 13, 2).'-';
+
+ // node = 6 octets
+ $random .= substr($pool, 14, 12);
+
+ return $random;
+ }
+}
+
+?>
diff --git a/lib/bennu/iCalendar_components.php b/lib/bennu/iCalendar_components.php
new file mode 100644
index 0000000..13934cb
--- /dev/null
+++ b/lib/bennu/iCalendar_components.php
@@ -0,0 +1,410 @@
+<?php // $Id: iCalendar_components.php,v 1.1 2006/04/13 05:10:24 jablko Exp $
+
+/**
+ * BENNU - PHP iCalendar library
+ * (c) 2005-2006 Ioannis Papaioannou (pj@moodle.org). All rights reserved.
+ *
+ * Released under the LGPL.
+ *
+ * See http://bennu.sourceforge.net/ for more information and downloads.
+ *
+ * @author Ioannis Papaioannou
+ * @version $Id: iCalendar_components.php,v 1.1 2006/04/13 05:10:24 jablko Exp $
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+class iCalendar_component {
+ var $name = NULL;
+ var $properties = NULL;
+ var $components = NULL;
+ var $valid_properties = NULL;
+ var $valid_components = NULL;
+
+ function iCalendar_component() {
+ $this->construct();
+ }
+
+ function construct() {
+ // Initialize the components array
+ if(empty($this->components)) {
+ $this->components = array();
+ foreach($this->valid_components as $name) {
+ $this->components[$name] = array();
+ }
+ }
+ }
+
+ function get_name() {
+ return $this->name;
+ }
+
+ function add_property($name, $value = NULL, $parameters = NULL) {
+
+ // Uppercase first of all
+ $name = strtoupper($name);
+
+ // Are we trying to add a valid property?
+ $xname = false;
+ if(!isset($this->valid_properties[$name])) {
+ // If not, is it an x-name as per RFC 2445?
+ if(!rfc2445_is_xname($name)) {
+ return false;
+ }
+ // Since this is an xname, all components are supposed to allow this property
+ $xname = true;
+ }
+
+ // Create a property object of the correct class
+ if($xname) {
+ $property = new iCalendar_property_x;
+ $property->set_name($name);
+ }
+ else {
+ $classname = 'iCalendar_property_'.strtolower(str_replace('-', '_', $name));
+ $property = new $classname;
+ }
+
+ // If $value is NULL, then this property must define a default value.
+ if($value === NULL) {
+ $value = $property->default_value();
+ if($value === NULL) {
+ return false;
+ }
+ }
+
+ // Set this property's parent component to ourselves, because some
+ // properties behave differently according to what component they apply to.
+ $property->set_parent_component($this->name);
+
+ // Set parameters before value; this helps with some properties which
+ // accept a VALUE parameter, and thus change their default value type.
+
+ // The parameters must be valid according to property specifications
+ if(!empty($parameters)) {
+ foreach($parameters as $paramname => $paramvalue) {
+ if(!$property->set_parameter($paramname, $paramvalue)) {
+ return false;
+ }
+ }
+
+ // Some parameters interact among themselves (e.g. ENCODING and VALUE)
+ // so make sure that after the dust settles, these invariants hold true
+ if(!$property->invariant_holds()) {
+ return false;
+ }
+ }
+
+ // $value MUST be valid according to the property data type
+ if(!$property->set_value($value)) {
+ return false;
+ }
+
+ // If this property is restricted to only once, blindly overwrite value
+ if(!$xname && $this->valid_properties[$name] & RFC2445_ONCE) {
+ $this->properties[$name] = array($property);
+ }
+
+ // Otherwise add it to the instance array for this property
+ else {
+ $this->properties[$name][] = $property;
+ }
+
+ // Finally: after all these, does the component invariant hold?
+ if(!$this->invariant_holds()) {
+ // If not, completely undo the property addition
+ array_pop($this->properties[$name]);
+ if(empty($this->properties[$name])) {
+ unset($this->properties[$name]);
+ }
+ return false;
+ }
+
+ return true;
+
+ }
+
+ function add_component($component) {
+
+ // With the detailed interface, you can add only components with this function
+ if(!is_object($component) || !is_subclass_of($component, 'iCalendar_component')) {
+ return false;
+ }
+
+ $name = $component->get_name();
+
+ // Only valid components as specified by this component are allowed
+ if(!in_array($name, $this->valid_components)) {
+ return false;
+ }
+
+ // Add it
+ $this->components[$name][] = $component;
+
+ return true;
+ }
+
+ function get_property_list($name) {
+ }
+
+ function invariant_holds() {
+ return true;
+ }
+
+ function is_valid() {
+ // If we have any child components, check that they are all valid
+ if(!empty($this->components)) {
+ foreach($this->components as $component => $instances) {
+ foreach($instances as $number => $instance) {
+ if(!$instance->is_valid()) {
+ return false;
+ }
+ }
+ }
+ }
+
+ // Finally, check the valid property list for any mandatory properties
+ // that have not been set and do not have a default value
+ foreach($this->valid_properties as $property => $propdata) {
+ if(($propdata & RFC2445_REQUIRED) && empty($this->properties[$property])) {
+ $classname = 'iCalendar_property_'.strtolower(str_replace('-', '_', $property));
+ $object = new $classname;
+ if($object->default_value() === NULL) {
+ return false;
+ }
+ unset($object);
+ }
+ }
+
+ return true;
+ }
+
+ function serialize() {
+ // Check for validity of the object
+ if(!$this->is_valid()) {
+ return false;
+ }
+
+ // Maybe the object is valid, but there are some required properties that
+ // have not been given explicit values. In that case, set them to defaults.
+ foreach($this->valid_properties as $property => $propdata) {
+ if(($propdata & RFC2445_REQUIRED) && empty($this->properties[$property])) {
+ $this->add_property($property);
+ }
+ }
+
+ // Start tag
+ $string = rfc2445_fold('BEGIN:'.$this->name) . RFC2445_CRLF;
+
+ // List of properties
+ if(!empty($this->properties)) {
+ foreach($this->properties as $name => $properties) {
+ foreach($properties as $property) {
+ $string .= $property->serialize();
+ }
+ }
+ }
+
+ // List of components
+ if(!empty($this->components)) {
+ foreach($this->components as $name => $components) {
+ foreach($components as $component) {
+ $string .= $component->serialize();
+ }
+ }
+ }
+
+ // End tag
+ $string .= rfc2445_fold('END:'.$this->name) . RFC2445_CRLF;
+
+ return $string;
+ }
+
+}
+
+class iCalendar extends iCalendar_component {
+ var $name = 'VCALENDAR';
+
+ function construct() {
+ $this->valid_properties = array(
+ 'CALSCALE' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'METHOD' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'PRODID' => RFC2445_REQUIRED | RFC2445_ONCE,
+ 'VERSION' => RFC2445_REQUIRED | RFC2445_ONCE,
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+
+ $this->valid_components = array(
+ 'VEVENT'
+ // TODO: add support for the other component types
+ //, 'VTODO', 'VJOURNAL', 'VFREEBUSY', 'VTIMEZONE', 'VALARM'
+ );
+ parent::construct();
+ }
+
+}
+
+class iCalendar_event extends iCalendar_component {
+
+ var $name = 'VEVENT';
+ var $properties;
+
+ function construct() {
+
+ $this->valid_components = array('VALARM');
+
+ $this->valid_properties = array(
+ 'CLASS' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'CREATED' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'DESCRIPTION' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ // Standard ambiguous here: in 4.6.1 it says that DTSTAMP in optional,
+ // while in 4.8.7.2 it says it's REQUIRED. Go with REQUIRED.
+ 'DTSTAMP' => RFC2445_REQUIRED | RFC2445_ONCE,
+ // Standard ambiguous here: in 4.6.1 it says that DTSTART in optional,
+ // while in 4.8.2.4 it says it's REQUIRED. Go with REQUIRED.
+ 'DTSTART' => RFC2445_REQUIRED | RFC2445_ONCE,
+ 'GEO' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'LAST-MODIFIED' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'LOCATION' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'ORGANIZER' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'PRIORITY' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'SEQUENCE' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'STATUS' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'SUMMARY' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'TRANSP' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ // Standard ambiguous here: in 4.6.1 it says that UID in optional,
+ // while in 4.8.4.7 it says it's REQUIRED. Go with REQUIRED.
+ 'UID' => RFC2445_REQUIRED | RFC2445_ONCE,
+ 'URL' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'RECURRENCE-ID' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'DTEND' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'DURATION' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'ATTACH' => RFC2445_OPTIONAL,
+ 'ATTENDEE' => RFC2445_OPTIONAL,
+ 'CATEGORIES' => RFC2445_OPTIONAL,
+ 'COMMENT' => RFC2445_OPTIONAL,
+ 'CONTACT' => RFC2445_OPTIONAL,
+ 'EXDATE' => RFC2445_OPTIONAL,
+ 'EXRULE' => RFC2445_OPTIONAL,
+ 'REQUEST-STATUS' => RFC2445_OPTIONAL,
+ 'RELATED-TO' => RFC2445_OPTIONAL,
+ 'RESOURCES' => RFC2445_OPTIONAL,
+ 'RDATE' => RFC2445_OPTIONAL,
+ 'RRULE' => RFC2445_OPTIONAL,
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+
+ parent::construct();
+ }
+
+ function invariant_holds() {
+ // DTEND and DURATION must not appear together
+ if(isset($this->properties['DTEND']) && isset($this->properties['DURATION'])) {
+ return false;
+ }
+
+
+ if(isset($this->properties['DTEND']) && isset($this->properties['DTSTART'])) {
+ // DTEND must be later than DTSTART
+ // The standard is not clear on how to hande different value types though
+ // TODO: handle this correctly even if the value types are different
+ if($this->properties['DTEND'][0]->value <= $this->properties['DTSTART'][0]->value) {
+ return false;
+ }
+
+ // DTEND and DTSTART must have the same value type
+ if($this->properties['DTEND'][0]->val_type != $this->properties['DTSTART'][0]->val_type) {
+ return false;
+ }
+
+ }
+ return true;
+ }
+
+}
+
+class iCalendar_todo extends iCalendar_component {
+ var $name = 'VTODO';
+ var $properties;
+
+ function construct() {
+
+ $this->properties = array(
+ 'class' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'completed' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'created' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'description' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'dtstamp' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'dtstart' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'geo' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'last-modified' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'location' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'organizer' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'percent' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'priority' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'recurid' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'sequence' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'status' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'summary' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'uid' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'url' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'due' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'duration' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'attach' => RFC2445_OPTIONAL,
+ 'attendee' => RFC2445_OPTIONAL,
+ 'categories' => RFC2445_OPTIONAL,
+ 'comment' => RFC2445_OPTIONAL,
+ 'contact' => RFC2445_OPTIONAL,
+ 'exdate' => RFC2445_OPTIONAL,
+ 'exrule' => RFC2445_OPTIONAL,
+ 'rstatus' => RFC2445_OPTIONAL,
+ 'related' => RFC2445_OPTIONAL,
+ 'resources' => RFC2445_OPTIONAL,
+ 'rdate' => RFC2445_OPTIONAL,
+ 'rrule' => RFC2445_OPTIONAL,
+ 'xprop' => RFC2445_OPTIONAL
+ );
+
+ parent::construct();
+ // TODO:
+ // either 'due' or 'duration' may appear in a 'eventprop', but 'due'
+ // and 'duration' MUST NOT occur in the same 'eventprop'
+ }
+}
+
+class iCalendar_journal extends iCalendar_component {
+ // TODO: implement
+}
+
+class iCalendar_freebusy extends iCalendar_component {
+ // TODO: implement
+}
+
+class iCalendar_alarm extends iCalendar_component {
+ // TODO: implement
+}
+
+class iCalendar_timezone extends iCalendar_component {
+ var $name = 'VTIMEZONE';
+ var $properties;
+
+ function construct() {
+
+ $this->properties = array(
+ 'tzid' => RFC2445_REQUIRED | RFC2445_ONCE,
+ 'last-modified' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'tzurl' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ // TODO: the next two are components of their own!
+ 'standardc' => RFC2445_REQUIRED,
+ 'daylightc' => RFC2445_REQUIRED,
+ 'x-prop' => RFC2445_OPTIONAL
+ );
+
+ parent::construct();
+ }
+
+}
+
+// REMINDER: DTEND must be later than DTSTART for all components which support both
+// REMINDER: DUE must be later than DTSTART for all components which support both
+
+?> \ No newline at end of file
diff --git a/lib/bennu/iCalendar_parameters.php b/lib/bennu/iCalendar_parameters.php
new file mode 100644
index 0000000..ab96654
--- /dev/null
+++ b/lib/bennu/iCalendar_parameters.php
@@ -0,0 +1,240 @@
+<?php // $Id: iCalendar_parameters.php,v 1.1 2006/04/13 05:10:24 jablko Exp $
+
+/**
+ * BENNU - PHP iCalendar library
+ * (c) 2005-2006 Ioannis Papaioannou (pj@moodle.org). All rights reserved.
+ *
+ * Released under the LGPL.
+ *
+ * See http://bennu.sourceforge.net/ for more information and downloads.
+ *
+ * @author Ioannis Papaioannou
+ * @version $Id: iCalendar_parameters.php,v 1.1 2006/04/13 05:10:24 jablko Exp $
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+class iCalendar_parameter {
+ function multiple_values_allowed($parameter) {
+ switch($parameter) {
+ case 'DELEGATED-FROM':
+ case 'DELEGATED-TO':
+ case 'MEMBER':
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ function default_value($parameter) {
+ switch($parameter) {
+ case 'CUTYPE': return 'INDIVIDUAL';
+ case 'FBTYPE': return 'BUSY';
+ case 'PARTSTAT': return 'NEEDS-ACTION';
+ case 'RELATED': return 'START';
+ case 'RELTYPE': return 'PARENT';
+ case 'ROLE': return 'REQ-PARTICIPANT';
+ case 'RSVP': return 'FALSE';
+ default: return NULL;
+ }
+ }
+
+ function is_valid_value(&$parent_property, $parameter, $value) {
+ switch($parameter) {
+ // These must all be a URI
+ case 'ALTREP':
+ case 'DIR':
+ return rfc2445_is_valid_value($value, RFC2445_TYPE_URI);
+ break;
+
+ // These must be CAL-ADDRESS, which is equivalent to URI
+ case 'DELEGATED-FROM':
+ case 'DELEGATED-TO':
+ case 'MEMBER':
+ case 'SENT-BY':
+ return rfc2445_is_valid_value($value, RFC2445_TYPE_CAL_ADDRESS);
+ break;
+
+ // These are textual parameters, so the MUST NOT contain double quotes
+ case 'CN':
+ return (strpos($value, '"') === false);
+ break;
+
+ // These have enumerated legal values
+ case 'CUTYPE':
+ $value = strtoupper($value);
+ return ($value == 'INDIVIDUAL' || $value == 'GROUP' || $value == 'RESOURCE' || $value == 'ROOM' || $value == 'UNKNOWN' || rfc2445_is_xname($value));
+ break;
+
+ case 'ENCODING':
+ $value = strtoupper($value);
+ return ($value == '8BIT' || $value == 'BASE64' || rfc2445_is_xname($value));
+ break;
+
+ case 'FBTYPE':
+ $value = strtoupper($value);
+ return ($value == 'FREE' || $value == 'BUSY' || $value == 'BUSY-UNAVAILABLE' || $value == 'BUSY-TENTATIVE' || rfc2445_is_xname($value));
+ break;
+
+ case 'FMTTYPE':
+ $fmttypes = array(
+ 'TEXT' => array('PLAIN', 'RICHTEXT', 'ENRICHED', 'TAB-SEPARATED-VALUES', 'HTML', 'SGML',
+ 'VND.LATEX-Z', 'VND.FMI.FLEXSTOR'),
+ 'MULTIPART' => array('MIXED', 'ALTERNATIVE', 'DIGEST', 'PARALLEL', 'APPLEDOUBLE', 'HEADER-SET',
+ 'FORM-DATA', 'RELATED', 'REPORT', 'VOICE-MESSAGE', 'SIGNED', 'ENCRYPTED',
+ 'BYTERANGES'),
+ 'MESSAGE' => array('RFC822', 'PARTIAL', 'EXTERNAL-BODY', 'NEWS', 'HTTP'),
+ 'APPLICATION' => array('OCTET-STREAM', 'POSTSCRIPT', 'ODA', 'ATOMICMAIL', 'ANDREW-INSET', 'SLATE',
+ 'WITA', 'DEC-DX', 'DCA-RFT', 'ACTIVEMESSAGE', 'RTF', 'APPLEFILE',
+ 'MAC-BINHEX40', 'NEWS-MESSAGE-ID', 'NEWS-TRANSMISSION', 'WORDPERFECT5.1',
+ 'PDF', 'ZIP', 'MACWRITEII', 'MSWORD', 'REMOTE-PRINTING', 'MATHEMATICA',
+ 'CYBERCASH', 'COMMONGROUND', 'IGES', 'RISCOS', 'ESHOP', 'X400-BP', 'SGML',
+ 'CALS-1840', 'PGP-ENCRYPTED', 'PGP-SIGNATURE', 'PGP-KEYS', 'VND.FRAMEMAKER',
+ 'VND.MIF', 'VND.MS-EXCEL', 'VND.MS-POWERPOINT', 'VND.MS-PROJECT',
+ 'VND.MS-WORKS', 'VND.MS-TNEF', 'VND.SVD', 'VND.MUSIC-NIFF', 'VND.MS-ARTGALRY',
+ 'VND.TRUEDOC', 'VND.KOAN', 'VND.STREET-STREAM', 'VND.FDF',
+ 'SET-PAYMENT-INITIATION', 'SET-PAYMENT', 'SET-REGISTRATION-INITIATION',
+ 'SET-REGISTRATION', 'VND.SEEMAIL', 'VND.BUSINESSOBJECTS',
+ 'VND.MERIDIAN-SLINGSHOT', 'VND.XARA', 'SGML-OPEN-CATALOG', 'VND.RAPID',
+ 'VND.ENLIVEN', 'VND.JAPANNET-REGISTRATION-WAKEUP',
+ 'VND.JAPANNET-VERIFICATION-WAKEUP', 'VND.JAPANNET-PAYMENT-WAKEUP',
+ 'VND.JAPANNET-DIRECTORY-SERVICE', 'VND.INTERTRUST.DIGIBOX', 'VND.INTERTRUST.NNCP'),
+ 'IMAGE' => array('JPEG', 'GIF', 'IEF', 'G3FAX', 'TIFF', 'CGM', 'NAPLPS', 'VND.DWG', 'VND.SVF',
+ 'VND.DXF', 'PNG', 'VND.FPX', 'VND.NET-FPX'),
+ 'AUDIO' => array('BASIC', '32KADPCM', 'VND.QCELP'),
+ 'VIDEO' => array('MPEG', 'QUICKTIME', 'VND.VIVO', 'VND.MOTOROLA.VIDEO', 'VND.MOTOROLA.VIDEOP')
+ );
+ $value = strtoupper($value);
+ if(rfc2445_is_xname($value)) {
+ return true;
+ }
+ @list($type, $subtype) = explode('/', $value);
+ if(empty($type) || empty($subtype)) {
+ return false;
+ }
+ if(!isset($fmttypes[$type]) || !in_array($subtype, $fmttypes[$type])) {
+ return false;
+ }
+ return true;
+ break;
+
+ case 'LANGUAGE':
+ $value = strtoupper($value);
+ $parts = explode('-', $value);
+ foreach($parts as $part) {
+ if(empty($part)) {
+ return false;
+ }
+ if(strspn($part, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') != strlen($part)) {
+ return false;
+ }
+ }
+ return true;
+ break;
+
+ case 'PARTSTAT':
+ $value = strtoupper($value);
+ switch($parent_property->parent_component) {
+ case 'VEVENT':
+ return ($value == 'NEEDS-ACTION' || $value == 'ACCEPTED' || $value == 'DECLINED' || $value == 'TENTATIVE'
+ || $value == 'DELEGATED' || rfc2445_is_xname($value));
+ break;
+ case 'VTODO':
+ return ($value == 'NEEDS-ACTION' || $value == 'ACCEPTED' || $value == 'DECLINED' || $value == 'TENTATIVE'
+ || $value == 'DELEGATED' || $value == 'COMPLETED' || $value == 'IN-PROCESS' || rfc2445_is_xname($value));
+ break;
+ case 'VJOURNAL':
+ return ($value == 'NEEDS-ACTION' || $value == 'ACCEPTED' || $value == 'DECLINED' || rfc2445_is_xname($value));
+ break;
+ }
+ return false;
+ break;
+
+ case 'RANGE':
+ $value = strtoupper($value);
+ return ($value == 'THISANDPRIOR' || $value == 'THISANDFUTURE');
+ break;
+
+ case 'RELATED':
+ $value = strtoupper($value);
+ return ($value == 'START' || $value == 'END');
+ break;
+
+ case 'RELTYPE':
+ $value = strtoupper($value);
+ return ($value == 'PARENT' || $value == 'CHILD' || $value == 'SIBLING' || rfc2445_is_xname($value));
+ break;
+
+ case 'ROLE':
+ $value = strtoupper($value);
+ return ($value == 'CHAIR' || $value == 'REQ-PARTICIPANT' || $value == 'OPT-PARTICIPANT' || $value == 'NON-PARTICIPANT' || rfc2445_is_xname($value));
+ break;
+
+ case 'RSVP':
+ $value = strtoupper($value);
+ return ($value == 'TRUE' || $value == 'FALSE');
+ break;
+
+ case 'TZID':
+ if(empty($value)) {
+ return false;
+ }
+ return (strcspn($value, '";:,') == strlen($value));
+ break;
+
+ case 'VALUE':
+ $value = strtoupper($value);
+ return ($value == 'BINARY' || $value == 'BOOLEAN' || $value == 'CAL-ADDRESS' || $value == 'DATE' ||
+ $value == 'DATE-TIME' || $value == 'DURATION' || $value == 'FLOAT' || $value == 'INTEGER' ||
+ $value == 'PERIOD' || $value == 'RECUR' || $value == 'TEXT' || $value == 'TIME' ||
+ $value == 'URI' || $value == 'UTC-OFFSET' || rfc2445_is_xname($value));
+ break;
+ }
+ }
+
+ function do_value_formatting($parameter, $value) {
+ switch($parameter) {
+ // Parameters of type CAL-ADDRESS or URI MUST be double-quoted
+ case 'ALTREP':
+ case 'DIR':
+ case 'DELEGATED-FROM':
+ case 'DELEGATED-TO':
+ case 'MEMBER':
+ case 'SENT-BY':
+ return '"'.$value.'"';
+ break;
+
+ // Textual parameter types must be double quoted if they contain COLON, SEMICOLON
+ // or COMMA. Quoting always sounds easier and standards-conformant though.
+ case 'CN':
+ return '"'.$value.'"';
+ break;
+
+ // Parameters with enumerated legal values, just make them all caps
+ case 'CUTYPE':
+ case 'ENCODING':
+ case 'FBTYPE':
+ case 'FMTTYPE':
+ case 'LANGUAGE':
+ case 'PARTSTAT':
+ case 'RANGE':
+ case 'RELATED':
+ case 'RELTYPE':
+ case 'ROLE':
+ case 'RSVP':
+ case 'VALUE':
+ return strtoupper($value);
+ break;
+
+ // Parameters we shouldn't be messing with
+ case 'TZID':
+ return $value;
+ break;
+ }
+ }
+
+ function undo_value_formatting($parameter, $value) {
+ }
+
+}
+
+?>
diff --git a/lib/bennu/iCalendar_properties.php b/lib/bennu/iCalendar_properties.php
new file mode 100644
index 0000000..bec729e
--- /dev/null
+++ b/lib/bennu/iCalendar_properties.php
@@ -0,0 +1,1299 @@
+<?php // $Id: iCalendar_properties.php,v 1.1 2006/04/13 05:10:24 jablko Exp $
+
+/**
+ * BENNU - PHP iCalendar library
+ * (c) 2005-2006 Ioannis Papaioannou (pj@moodle.org). All rights reserved.
+ *
+ * Released under the LGPL.
+ *
+ * See http://bennu.sourceforge.net/ for more information and downloads.
+ *
+ * @author Ioannis Papaioannou
+ * @version $Id: iCalendar_properties.php,v 1.1 2006/04/13 05:10:24 jablko Exp $
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+class iCalendar_property {
+ // Properties can have parameters, but cannot have other properties or components
+
+ var $parent_component = NULL;
+ var $value = NULL;
+ var $parameters = NULL;
+ var $valid_parameters = NULL;
+
+ // These are common for 95% of properties, so define them here and override as necessary
+ var $val_multi = false;
+ var $val_default = NULL;
+
+ function iCalendar_property() {
+ $this->construct();
+ }
+
+ function construct() {
+ $this->parameters = array();
+ }
+
+ // If some property needs extra care with its parameters, override this
+ // IMPORTANT: the parameter name MUST BE CAPITALIZED!
+ function is_valid_parameter($parameter, $value) {
+
+ if(is_array($value)) {
+ if(!iCalendar_parameter::multiple_values_allowed($parameter)) {
+ return false;
+ }
+ foreach($value as $item) {
+ if(!iCalendar_parameter::is_valid_value($this, $parameter, $item)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ return iCalendar_parameter::is_valid_value($this, $parameter, $value);
+ }
+
+ function invariant_holds() {
+ return true;
+ }
+
+ // If some property is very picky about its values, it should do the work itself
+ // Only data type validation is done here
+ function is_valid_value($value) {
+ if(is_array($value)) {
+ if(!$this->val_multi) {
+ return false;
+ }
+ else {
+ foreach($value as $oneval) {
+ if(!rfc2445_is_valid_value($oneval, $this->val_type)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ return rfc2445_is_valid_value($value, $this->val_type);
+ }
+
+ function default_value() {
+ return $this->val_default;
+ }
+
+ function set_parent_component($componentname) {
+ if(class_exists('iCalendar_'.strtolower(substr($componentname, 1)))) {
+ $this->parent_component = strtoupper($componentname);
+ return true;
+ }
+
+ return false;
+ }
+
+ function set_value($value) {
+ if($this->is_valid_value($value)) {
+ // This transparently formats any value type according to the iCalendar specs
+ if(is_array($value)) {
+ foreach($value as $key => $item) {
+ $value[$key] = rfc2445_do_value_formatting($item, $this->val_type);
+ }
+ $this->value = implode(',', $value);
+ }
+ else {
+ $this->value = rfc2445_do_value_formatting($value, $this->val_type);
+ }
+
+ return true;
+ }
+ return false;
+ }
+
+ function get_value() {
+ // First of all, assume that we have multiple values
+ $valarray = explode('\\,', $this->value);
+
+ // Undo transparent formatting
+ $replace_function = create_function('$a', 'return rfc2445_undo_value_formatting($a, '.$this->val_type.');');
+ $valarray = array_map($replace_function, $valarray);
+
+ // Now, if this property cannot have multiple values, don't return as an array
+ if(!$this->val_multi) {
+ return $valarray[0];
+ }
+
+ // Otherwise return an array even if it has one element, for uniformity
+ return $valarray;
+
+ }
+
+ function set_parameter($name, $value) {
+
+ // Uppercase
+ $name = strtoupper($name);
+
+ // Are we trying to add a valid parameter?
+ $xname = false;
+ if(!isset($this->valid_parameters[$name])) {
+ // If not, is it an x-name as per RFC 2445?
+ if(!rfc2445_is_xname($name)) {
+ return false;
+ }
+ // No more checks -- all components are supposed to allow x-name parameters
+ $xname = true;
+ }
+
+ if(!$this->is_valid_parameter($name, $value)) {
+ return false;
+ }
+
+ if(is_array($value)) {
+ foreach($value as $key => $element) {
+ $value[$key] = iCalendar_parameter::do_value_formatting($name, $element);
+ }
+ }
+ else {
+ $value = iCalendar_parameter::do_value_formatting($name, $value);
+ }
+
+ $this->parameters[$name] = $value;
+
+ // Special case: if we just changed the VALUE parameter, reflect this
+ // in the object's status so that it only accepts correct type values
+ if($name == 'VALUE') {
+ // TODO: what if this invalidates an already-set value?
+ $this->val_type = constant('RFC2445_TYPE_'.str_replace('-', '_', $value));
+ }
+
+ return true;
+
+ }
+
+ function get_parameter($name) {
+
+ // Uppercase
+ $name = strtoupper($name);
+
+ if(isset($this->parameters[$name])) {
+ // If there are any double quotes in the value, invisibly strip them
+ if(is_array($this->parameters[$name])) {
+ foreach($this->parameters[$name] as $key => $value) {
+ if(substr($value, 0, 1) == '"') {
+ $this->parameters[$name][$key] = substr($value, 1, strlen($value) - 2);
+ }
+ }
+ return $this->parameters[$name];
+ }
+
+ else {
+ if(substr($this->parameters[$name], 0, 1) == '"') {
+ return substr($this->parameters[$name], 1, strlen($this->parameters[$name]) - 2);
+ }
+ }
+ }
+
+ return NULL;
+ }
+
+ function serialize() {
+ $string = $this->name;
+
+ if(!empty($this->parameters)) {
+ foreach($this->parameters as $name => $value) {
+ $string .= ';'.$name.'=';
+ if(is_array($value)) {
+ $string .= implode(',', $value);
+ }
+ else {
+ $string .= $value;
+ }
+ }
+ }
+
+ $string .= ':'.$this->value;
+
+ return rfc2445_fold($string) . RFC2445_CRLF;
+ }
+}
+
+// 4.7 Calendar Properties
+// -----------------------
+
+class iCalendar_property_calscale extends iCalendar_property {
+
+ var $name = 'CALSCALE';
+ var $val_type = RFC2445_TYPE_TEXT;
+
+ function construct() {
+ $this->valid_parameters = array(
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+
+ function is_valid_value($value) {
+ // This is case-sensitive
+ return ($value === 'GREGORIAN');
+ }
+}
+
+class iCalendar_property_method extends iCalendar_property {
+
+ var $name = 'METHOD';
+ var $val_type = RFC2445_TYPE_TEXT;
+
+ function construct() {
+ $this->valid_parameters = array(
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+
+ function is_valid_value($value) {
+ // This is case-sensitive
+ // Methods from RFC 2446
+ $methods = array('PUBLISH', 'REQUEST', 'REPLY', 'ADD', 'CANCEL', 'REFRESH', 'COUNTER', 'DECLINECOUNTER');
+ return in_array($value, $methods);
+ }
+}
+
+class iCalendar_property_prodid extends iCalendar_property {
+
+ var $name = 'PRODID';
+ var $val_type = RFC2445_TYPE_TEXT;
+ var $val_default = NULL;
+
+ function construct() {
+ $this->val_default = '-//John Papaioannou/NONSGML Bennu '._BENNU_VERSION.'//EN';
+
+ $this->valid_parameters = array(
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+}
+
+class iCalendar_property_version extends iCalendar_property {
+
+ var $name = 'VERSION';
+ var $val_type = RFC2445_TYPE_TEXT;
+ var $val_default = '2.0';
+
+ function construct() {
+ $this->valid_parameters = array(
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+
+ function is_valid_value($value) {
+ return($value === '2.0' || $value === 2.0);
+ }
+
+}
+
+// 4.8.1 Descriptive Component Properties
+// --------------------------------------
+
+class iCalendar_property_attach extends iCalendar_property {
+
+ var $name = 'ATTACH';
+ var $val_type = RFC2445_TYPE_URI;
+
+ function construct() {
+ $this->valid_parameters = array(
+ 'FMTTYPE' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'ENCODING' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'VALUE' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+
+ function invariant_holds() {
+ if(isset($this->parameters['ENCODING']) && !isset($this->parameters['VALUE'])) {
+ return false;
+ }
+ if(isset($this->parameters['VALUE']) && !isset($this->parameters['ENCODING'])) {
+ return false;
+ }
+
+ return true;
+ }
+
+ function is_valid_parameter($parameter, $value) {
+
+ $parameter = strtoupper($parameter);
+
+ if(!parent::is_valid_parameter($parameter, $value)) {
+ return false;
+ }
+
+ if($parameter === 'ENCODING' && strtoupper($value) != 'BASE64') {
+ return false;
+ }
+
+ if($parameter === 'VALUE' && strtoupper($value) != 'BINARY') {
+ return false;
+ }
+
+ return true;
+ }
+}
+
+class iCalendar_property_categories extends iCalendar_property {
+
+ var $name = 'CATEGORIES';
+ var $val_type = RFC2445_TYPE_TEXT;
+ var $val_multi = true;
+
+ function construct() {
+ $this->valid_parameters = array(
+ 'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+}
+
+class iCalendar_property_class extends iCalendar_property {
+
+ var $name = 'CLASS';
+ var $val_type = RFC2445_TYPE_TEXT;
+ var $val_default = 'PUBLIC';
+
+ function construct() {
+ $this->valid_parameters = array(
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+
+ function is_valid_value($value) {
+ // If this is not an xname, it is case-sensitive
+ return ($value === 'PUBLIC' || $value === 'PRIVATE' || $value === 'CONFIDENTIAL' || rfc2445_is_xname(strtoupper($value)));
+ }
+}
+
+class iCalendar_property_comment extends iCalendar_property {
+
+ var $name = 'COMMENT';
+ var $val_type = RFC2445_TYPE_TEXT;
+
+ function construct() {
+ $this->valid_parameters = array(
+ 'ALTREP' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+}
+
+class iCalendar_property_description extends iCalendar_property {
+
+ var $name = 'DESCRIPTION';
+ var $val_type = RFC2445_TYPE_TEXT;
+
+ function construct() {
+ $this->valid_parameters = array(
+ 'ALTREP' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+}
+
+class iCalendar_property_geo extends iCalendar_property {
+
+ var $name = 'GEO';
+ var $val_type = RFC2445_TYPE_TEXT;
+
+ function construct() {
+ $this->valid_parameters = array(
+ 'ALTREP' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+
+ function is_valid_value($value) {
+ // This MUST be two floats separated by a semicolon
+ if(!is_string($value)) {
+ return false;
+ }
+
+ $floats = explode(';', $value);
+ if(count($floats) != 2) {
+ return false;
+ }
+
+ return rfc2445_is_valid_value($floats[0], RFC2445_TYPE_FLOAT) && rfc2445_is_valid_value($floats[1], RFC2445_TYPE_FLOAT);
+ }
+
+ function set_value($value) {
+ // Must override this, otherwise the semicolon separating
+ // the two floats would get auto-quoted, which is illegal
+ if($this->is_valid_value($value)) {
+ $this->value = $value;
+ return true;
+ }
+
+ return false;
+ }
+
+}
+
+class iCalendar_property_location extends iCalendar_property {
+
+ var $name = 'LOCATION';
+ var $val_type = RFC2445_TYPE_TEXT;
+
+ function construct() {
+ $this->valid_parameters = array(
+ 'ALTREP' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+}
+
+class iCalendar_property_percent_complete extends iCalendar_property {
+
+ var $name = 'PERCENT-COMPLETE';
+ var $val_type = RFC2445_TYPE_INTEGER;
+
+ function construct() {
+ $this->valid_parameters = array(
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+
+ function is_valid_value($value) {
+ // Only integers between 0 and 100 inclusive allowed
+ if(!parent::is_valid_value($value)) {
+ return false;
+ }
+ $value = intval($value);
+ return ($value >= 0 && $value <= 100);
+ }
+
+}
+
+class iCalendar_property_priority extends iCalendar_property {
+
+ var $name = 'PRIORITY';
+ var $val_type = RFC2445_TYPE_INTEGER;
+
+ function construct() {
+ $this->valid_parameters = array(
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+
+ function is_valid_value($value) {
+ // Only integers between 0 and 9 inclusive allowed
+ if(!parent::is_valid_value($value)) {
+ return false;
+ }
+
+ $value = intval($value);
+ return ($value >= 0 && $value <= 9);
+ }
+}
+
+class iCalendar_property_resources extends iCalendar_property {
+
+ var $name = 'RESOURCES';
+ var $val_type = RFC2445_TYPE_TEXT;
+ var $val_multi = true;
+
+ function construct() {
+ $this->valid_parameters = array(
+ 'ALTREP' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+}
+
+class iCalendar_property_status extends iCalendar_property {
+
+ var $name = 'STATUS';
+ var $val_type = RFC2445_TYPE_TEXT;
+
+ function construct() {
+ $this->valid_parameters = array(
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+
+ function is_valid_value($value) {
+ // This is case-sensitive
+ switch ($this->parent_component) {
+ case 'VEVENT':
+ $allowed = array('TENTATIVE', 'CONFIRMED', 'CANCELLED');
+ break;
+ case 'VTODO':
+ $allowed = array('NEEDS-ACTION', 'COMPLETED', 'IN-PROCESS', 'CANCELLED');
+ break;
+ case 'VJOURNAL':
+ $allowed = array('DRAFT', 'FINAL', 'CANCELLED');
+ break;
+ }
+ return in_array($value, $allowed);
+
+ }
+
+}
+
+class iCalendar_property_summary extends iCalendar_property {
+
+ var $name = 'SUMMARY';
+ var $val_type = RFC2445_TYPE_TEXT;
+
+ function construct() {
+ $this->valid_parameters = array(
+ 'ALTREP' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+}
+
+// 4.8.2 Date and Time Component Properties
+// ----------------------------------------
+
+class iCalendar_property_completed extends iCalendar_property {
+
+ var $name = 'COMPLETED';
+ var $val_type = RFC2445_TYPE_DATE_TIME;
+
+ function construct() {
+ $this->valid_parameters = array(
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+
+ function is_valid_value($value) {
+ if(!parent::is_valid_value($value)) {
+ return false;
+ }
+ // Time MUST be in UTC format
+ return(substr($value, -1) == 'Z');
+ }
+}
+
+class iCalendar_property_dtend extends iCalendar_property {
+
+ var $name = 'DTEND';
+ var $val_type = RFC2445_TYPE_DATE_TIME;
+
+ function construct() {
+ $this->valid_parameters = array(
+ 'VALUE' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'TZID' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+
+ function is_valid_value($value) {
+ if(!parent::is_valid_value($value)) {
+ return false;
+ }
+
+ // If present in a FREEBUSY component, must be in UTC format
+ if($this->parent_component == 'VFREEBUSY' && substr($value, -1) != 'Z') {
+ return false;
+ }
+
+ return true;
+
+ }
+
+ function is_valid_parameter($parameter, $value) {
+
+ $parameter = strtoupper($parameter);
+
+ if(!parent::is_valid_parameter($parameter, $value)) {
+ return false;
+ }
+ if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME')) {
+ return false;
+ }
+
+ return true;
+ }
+}
+
+class iCalendar_property_due extends iCalendar_property {
+
+ var $name = 'DUE';
+ var $val_type = RFC2445_TYPE_DATE_TIME;
+
+ function construct() {
+ $this->valid_parameters = array(
+ 'VALUE' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'TZID' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+
+ function is_valid_value($value) {
+ if(!parent::is_valid_value($value)) {
+ return false;
+ }
+
+ // If present in a FREEBUSY component, must be in UTC format
+ if($this->parent_component == 'VFREEBUSY' && substr($value, -1) != 'Z') {
+ return false;
+ }
+
+ return true;
+
+ }
+
+ function is_valid_parameter($parameter, $value) {
+
+ $parameter = strtoupper($parameter);
+
+ if(!parent::is_valid_parameter($parameter, $value)) {
+ return false;
+ }
+ if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME')) {
+ return false;
+ }
+
+ return true;
+ }
+}
+
+class iCalendar_property_dtstart extends iCalendar_property {
+
+ var $name = 'DTSTART';
+ var $val_type = RFC2445_TYPE_DATE_TIME;
+
+ function construct() {
+ $this->valid_parameters = array(
+ 'VALUE' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'TZID' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+
+ // TODO: unimplemented stuff when parent is a VTIMEZONE component
+
+ function is_valid_value($value) {
+ if(!parent::is_valid_value($value)) {
+ return false;
+ }
+
+ // If present in a FREEBUSY component, must be in UTC format
+ if($this->parent_component == 'VFREEBUSY' && substr($value, -1) != 'Z') {
+ return false;
+ }
+
+ return true;
+ }
+
+ function is_valid_parameter($parameter, $value) {
+
+ $parameter = strtoupper($parameter);
+
+ if(!parent::is_valid_parameter($parameter, $value)) {
+ return false;
+ }
+ if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME')) {
+ return false;
+ }
+
+ return true;
+ }
+}
+
+class iCalendar_property_duration extends iCalendar_property {
+
+ var $name = 'DURATION';
+ var $val_type = RFC2445_TYPE_DURATION;
+
+ function construct() {
+ $this->valid_parameters = array(
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+
+ function is_valid_value($value) {
+ if(!parent::is_valid_value($value)) {
+ return false;
+ }
+
+ // Value must be positive
+ return ($value{0} != '-');
+ }
+}
+
+class iCalendar_property_freebusy extends iCalendar_property {
+
+ var $name = 'FREEBUSY';
+ var $val_type = RFC2445_TYPE_PERIOD;
+ var $val_multi = true;
+
+ function construct() {
+ $this->valid_parameters = array(
+ 'FBTYPE' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+
+ function is_valid_value($value) {
+ if(!parent::is_valid_value($value)) {
+ return false;
+ }
+
+ $pos = strpos($value, '/'); // We know there's only one / in there
+ if($value{$pos - 1} != 'Z') {
+ // Start time MUST be in UTC
+ return false;
+ }
+ if($value{$pos + 1} != 'P' && $substr($value, -1) != 'Z') {
+ // If the second part is not a period, it MUST be in UTC
+ return false;
+ }
+
+ return true;
+ }
+
+ // TODO: these properties SHOULD be shorted in ascending order (by start time and end time as tiebreak)
+}
+
+class iCalendar_property_transp extends iCalendar_property {
+
+ var $name = 'TRANSP';
+ var $val_type = RFC2445_TYPE_TEXT;
+ var $val_default = 'OPAQUE';
+
+ function construct() {
+ $this->valid_parameters = array(
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+
+ function is_valid_value($value) {
+ return ($value === 'TRANSPARENT' || $value === 'OPAQUE');
+ }
+}
+
+// TODO: 4.8.3 timezone component properties
+
+
+// 4.8.4 Relationship Component Properties
+// ---------------------------------------
+
+class iCalendar_property_attendee extends iCalendar_property {
+
+ var $name = 'ATTENDEE';
+ var $val_type = RFC2445_TYPE_CAL_ADDRESS;
+
+ // TODO: MUST NOT be specified when the calendar object has METHOD=PUBLISH
+ // TODO: standard has lots of detail here, make triple sure that we eventually conform
+
+ function construct() {
+ $this->valid_parameters = array(
+ 'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'CN' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'ROLE' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'PARTSTAT' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'RSVP' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'CUTYPE' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'MEMBER' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'DELEGATED-TO' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'DELEGATED-FROM' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'SENT-BY' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'DIR' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+
+ function set_parent_component($componentname) {
+ if(!parent::set_parent_component($componentname)) {
+ return false;
+ }
+
+ if($this->parent_component == 'VFREEBUSY' || $this->parent_component == 'VALARM') {
+ // Most parameters become invalid in this case, the full allowed set is now:
+ $this->valid_parameters = array(
+ 'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+
+ return false;
+ }
+
+}
+
+class iCalendar_property_contact extends iCalendar_property {
+
+ var $name = 'CONTACT';
+ var $val_type = RFC2445_TYPE_TEXT;
+
+ function construct() {
+ $this->valid_parameters = array(
+ 'ALTREP' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+}
+
+class iCalendar_property_organizer extends iCalendar_property {
+
+ var $name = 'ORGANIZER';
+ var $val_type = RFC2445_TYPE_CAL_ADDRESS;
+
+ function construct() {
+ $this->valid_parameters = array(
+ 'CN' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'DIR' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'SENT-BY' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+
+ // TODO:
+/*
+ Conformance: This property MUST be specified in an iCalendar object
+ that specifies a group scheduled calendar entity. This property MUST
+ be specified in an iCalendar object that specifies the publication of
+ a calendar user's busy time. This property MUST NOT be specified in
+ an iCalendar object that specifies only a time zone definition or
+ that defines calendar entities that are not group scheduled entities,
+ but are entities only on a single user's calendar.
+*/
+
+}
+
+class iCalendar_property_recurrence_id extends iCalendar_property {
+
+ // TODO: can only be specified when defining recurring components in the calendar
+/*
+ Conformance: This property can be specified in an iCalendar object
+ containing a recurring calendar component.
+
+ Description: The full range of calendar components specified by a
+ recurrence set is referenced by referring to just the "UID" property
+ value corresponding to the calendar component. The "RECURRENCE-ID"
+ property allows the reference to an individual instance within the
+ recurrence set.
+*/
+
+ var $name = 'RECURRENCE-ID';
+ var $val_type = RFC2445_TYPE_DATE_TIME;
+
+ function construct() {
+ $this->valid_parameters = array(
+ 'RANGE' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'TZID' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'VALUE' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+
+ function is_valid_parameter($parameter, $value) {
+
+ $parameter = strtoupper($parameter);
+
+ if(!parent::is_valid_parameter($parameter, $value)) {
+ return false;
+ }
+ if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME')) {
+ return false;
+ }
+
+ return true;
+ }
+
+}
+
+class iCalendar_property_related_to extends iCalendar_property {
+
+ var $name = 'RELATED-TO';
+ var $val_type = RFC2445_TYPE_TEXT;
+
+ // TODO: the value of this property must reference another component's UID
+
+ function construct() {
+ $this->valid_parameters = array(
+ 'RELTYPE' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+}
+
+class iCalendar_property_url extends iCalendar_property {
+
+ var $name = 'URL';
+ var $val_type = RFC2445_TYPE_URI;
+
+ function construct() {
+ $this->valid_parameters = array(
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+}
+
+class iCalendar_property_uid extends iCalendar_property {
+
+ var $name = 'UID';
+ var $val_type = RFC2445_TYPE_TEXT;
+
+ function construct() {
+ $this->valid_parameters = array(
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+
+ // The exception to the rule: this is not a static value, so we
+ // generate it on-the-fly here. Guaranteed to be different for
+ // each instance of this property, too. Nice.
+ $this->val_default = Bennu::generate_guid();
+ }
+}
+
+// 4.8.5 Recurrence Component Properties
+// -------------------------------------
+
+class iCalendar_property_exdate extends iCalendar_property {
+
+ var $name = 'EXDATE';
+ var $val_type = RFC2445_TYPE_DATE_TIME;
+ var $val_multi = true;
+
+ function construct() {
+ $this->valid_parameters = array(
+ 'TZID' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'VALUE' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+
+ function is_valid_parameter($parameter, $value) {
+
+ $parameter = strtoupper($parameter);
+
+ if(!parent::is_valid_parameter($parameter, $value)) {
+ return false;
+ }
+ if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME')) {
+ return false;
+ }
+
+ return true;
+ }
+
+}
+
+class iCalendar_property_exrule extends iCalendar_property {
+
+ var $name = 'EXRULE';
+ var $val_type = RFC2445_TYPE_RECUR;
+
+ function construct() {
+ $this->valid_parameters = array(
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+}
+
+class iCalendar_property_rdate extends iCalendar_property {
+
+ var $name = 'RDATE';
+ var $val_type = RFC2445_TYPE_DATE_TIME;
+ var $val_multi = true;
+
+ function construct() {
+ $this->valid_parameters = array(
+ 'TZID' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ 'VALUE' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+
+ function is_valid_parameter($parameter, $value) {
+
+ $parameter = strtoupper($parameter);
+
+ if(!parent::is_valid_parameter($parameter, $value)) {
+ return false;
+ }
+ if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME' || $value == 'PERIOD')) {
+ return false;
+ }
+
+ return true;
+ }
+
+}
+
+class iCalendar_property_rrule extends iCalendar_property {
+
+ var $name = 'RRULE';
+ var $val_type = RFC2445_TYPE_RECUR;
+
+ function construct() {
+ $this->valid_parameters = array(
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+}
+
+// TODO: 4.8.6 Alarm Component Properties
+
+// 4.8.7 Change Management Component Properties
+// --------------------------------------------
+
+class iCalendar_property_created extends iCalendar_property {
+
+ var $name = 'CREATED';
+ var $val_type = RFC2445_TYPE_DATE_TIME;
+
+ function construct() {
+ $this->valid_parameters = array(
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+
+ function is_valid_value($value) {
+ if(!parent::is_valid_value($value)) {
+ return false;
+ }
+ // Time MUST be in UTC format
+ return(substr($value, -1) == 'Z');
+ }
+}
+
+class iCalendar_property_dtstamp extends iCalendar_property {
+
+ var $name = 'DTSTAMP';
+ var $val_type = RFC2445_TYPE_DATE_TIME;
+
+ function construct() {
+ $this->valid_parameters = array(
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+
+ function is_valid_value($value) {
+ if(!parent::is_valid_value($value)) {
+ return false;
+ }
+ // Time MUST be in UTC format
+ return(substr($value, -1) == 'Z');
+ }
+}
+
+class iCalendar_property_last_modified extends iCalendar_property {
+
+ var $name = 'LAST-MODIFIED';
+ var $val_type = RFC2445_TYPE_DATE_TIME;
+
+ function construct() {
+ $this->valid_parameters = array(
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+
+ function is_valid_value($value) {
+ if(!parent::is_valid_value($value)) {
+ return false;
+ }
+ // Time MUST be in UTC format
+ return(substr($value, -1) == 'Z');
+ }
+}
+
+class iCalendar_property_sequence extends iCalendar_property {
+
+ var $name = 'SEQUENCE';
+ var $val_type = RFC2445_TYPE_INTEGER;
+ var $val_default = 0;
+
+ function construct() {
+ $this->valid_parameters = array(
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+
+ function is_valid_value($value) {
+ if(!parent::is_valid_value($value)) {
+ return false;
+ }
+ $value = intval($value);
+ return ($value >= 0);
+ }
+}
+
+// 4.8.8 Miscellaneous Component Properties
+// ----------------------------------------
+
+class iCalendar_property_x extends iCalendar_property {
+
+ var $name = RFC2445_XNAME;
+ var $val_type = NULL;
+
+ function construct() {
+ $this->valid_parameters = array(
+ 'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+
+ function set_name($name) {
+
+ $name = strtoupper($name);
+
+ if(rfc2445_is_xname($name)) {
+ $this->name = $name;
+ return true;
+ }
+
+ return false;
+ }
+}
+
+class iCalendar_property_request_status extends iCalendar_property {
+
+ // IMPORTANT NOTE: This property value includes TEXT fields
+ // separated by semicolons. Unfortunately, auto-value-formatting
+ // cannot be used in this case. As an exception, the value passed
+ // to this property MUST be already escaped.
+
+ var $name = 'REQUEST-STATUS';
+ var $val_type = RFC2445_TYPE_TEXT;
+
+ function construct() {
+ $this->valid_parameters = array(
+ 'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE,
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+
+ function is_valid_value($value) {
+ if(!is_string($value) || empty($value)) {
+ return false;
+ }
+
+ $len = strlen($value);
+ $parts = array();
+ $from = 0;
+ $escch = false;
+
+ for($i = 0; $i < $len; ++$i) {
+ if($value{$i} == ';' && !$escch) {
+ // Token completed
+ $parts[] = substr($value, $from, $i - $from);
+ $from = $i + 1;
+ continue;
+ }
+ $escch = ($value{$i} == '\\');
+ }
+ // Add one last token with the remaining text; if the value
+ // ended with a ';' it was illegal, so check that this token
+ // is not the empty string.
+ $parts[] = substr($value, $from);
+
+ $count = count($parts);
+
+ // May have 2 or 3 tokens (last one is optional)
+ if($count != 2 && $count != 3) {
+ return false;
+ }
+
+ // REMEMBER: if ANY part is empty, we have an illegal value
+
+ // First token must be hierarchical numeric status (3 levels max)
+ if(strlen($parts[0]) == 0) {
+ return false;
+ }
+
+ if($parts[0]{0} < '1' || $parts[0]{0} > '4') {
+ return false;
+ }
+
+ $len = strlen($parts[0]);
+
+ // Max 3 levels, and can't end with a period
+ if($len > 5 || $parts[0]{$len - 1} == '.') {
+ return false;
+ }
+
+ for($i = 1; $i < $len; ++$i) {
+ if(($i & 1) == 1 && $parts[0]{$i} != '.') {
+ // Even-indexed chars must be periods
+ return false;
+ }
+ else if(($i & 1) == 0 && ($parts[0]{$i} < '0' || $parts[0]{$i} > '9')) {
+ // Odd-indexed chars must be numbers
+ return false;
+ }
+ }
+
+ // Second and third tokens must be TEXT, and already escaped, so
+ // they are not allowed to have UNESCAPED semicolons, commas, slashes,
+ // or any newlines at all
+
+ for($i = 1; $i < $count; ++$i) {
+ if(strpos($parts[$i], "\n") !== false) {
+ return false;
+ }
+
+ $len = strlen($parts[$i]);
+ if($len == 0) {
+ // Cannot be empty
+ return false;
+ }
+
+ $parts[$i] .= '#'; // This guard token saves some conditionals in the loop
+
+ for($j = 0; $j < $len; ++$j) {
+ $thischar = $parts[$i]{$j};
+ $nextchar = $parts[$i]{$j + 1};
+ if($thischar == '\\') {
+ // Next char must now be one of ";,\nN"
+ if($nextchar != ';' && $nextchar != ',' && $nextchar != '\\' &&
+ $nextchar != 'n' && $nextchar != 'N') {
+ return false;
+ }
+
+ // OK, this escaped sequence is correct, bypass next char
+ ++$j;
+ continue;
+ }
+ if($thischar == ';' || $thischar == ',' || $thischar == '\\') {
+ // This wasn't escaped as it should
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ function set_value($value) {
+ // Must override this, otherwise the value would be quoted again
+ if($this->is_valid_value($value)) {
+ $this->value = $value;
+ return true;
+ }
+
+ return false;
+ }
+
+}
+
+
+#######################
+/*
+class iCalendar_property_class extends iCalendar_property {
+
+ var $name = 'CLASS';
+ var $val_type = RFC2445_TYPE_TEXT;
+
+ function construct() {
+ $this->valid_parameters = array(
+ RFC2445_XNAME => RFC2445_OPTIONAL
+ );
+ }
+}
+*/
+
+?>
diff --git a/lib/bennu/iCalendar_rfc2445.php b/lib/bennu/iCalendar_rfc2445.php
new file mode 100644
index 0000000..e73d863
--- /dev/null
+++ b/lib/bennu/iCalendar_rfc2445.php
@@ -0,0 +1,785 @@
+<?php // $Id: iCalendar_rfc2445.php,v 1.1 2006/04/13 05:10:24 jablko Exp $
+
+/**
+ * BENNU - PHP iCalendar library
+ * (c) 2005-2006 Ioannis Papaioannou (pj@moodle.org). All rights reserved.
+ *
+ * Released under the LGPL.
+ *
+ * See http://bennu.sourceforge.net/ for more information and downloads.
+ *
+ * @author Ioannis Papaioannou
+ * @version $Id: iCalendar_rfc2445.php,v 1.1 2006/04/13 05:10:24 jablko Exp $
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+/*
+
+ All names of properties, property parameters, enumerated property
+ values and property parameter values are case-insensitive. However,
+ all other property values are case-sensitive, unless otherwise
+ stated.
+
+*/
+
+define('RFC2445_CRLF', "\r\n");
+define('RFC2445_WSP', "\t ");
+define('RFC2445_WEEKDAYS', 'MO,TU,WE,TH,FR,SA,SU');
+define('RFC2445_FOLDED_LINE_LENGTH', 75);
+
+define('RFC2445_REQUIRED', 0x01);
+define('RFC2445_OPTIONAL', 0x02);
+define('RFC2445_ONCE', 0x04);
+
+define('RFC2445_PROP_FLAGS', 0);
+define('RFC2445_PROP_TYPE', 1);
+define('RFC2445_PROP_DEFAULT', 2);
+
+define('RFC2445_XNAME', 'X-');
+
+define('RFC2445_TYPE_BINARY', 0);
+define('RFC2445_TYPE_BOOLEAN', 1);
+define('RFC2445_TYPE_CAL_ADDRESS', 2);
+define('RFC2445_TYPE_DATE', 3);
+define('RFC2445_TYPE_DATE_TIME', 4);
+define('RFC2445_TYPE_DURATION', 5);
+define('RFC2445_TYPE_FLOAT', 6);
+define('RFC2445_TYPE_INTEGER', 7);
+define('RFC2445_TYPE_PERIOD', 8);
+define('RFC2445_TYPE_RECUR', 9);
+define('RFC2445_TYPE_TEXT', 10);
+define('RFC2445_TYPE_TIME', 11);
+define('RFC2445_TYPE_URI', 12); // CAL_ADDRESS === URI
+define('RFC2445_TYPE_UTC_OFFSET', 13);
+
+
+function rfc2445_fold($string) {
+ if(strlen($string) <= RFC2445_FOLDED_LINE_LENGTH) {
+ return $string;
+ }
+
+ $retval = '';
+
+ while(strlen($string) > RFC2445_FOLDED_LINE_LENGTH) {
+ $retval .= substr($string, 0, RFC2445_FOLDED_LINE_LENGTH - 1) . RFC2445_CRLF . ' ';
+ $string = substr($string, RFC2445_FOLDED_LINE_LENGTH - 1);
+ }
+
+ $retval .= $string;
+
+ return $retval;
+
+}
+
+function rfc2445_unfold($string) {
+ for($i = 0; $i < strlen(RFC2445_WSP); ++$i) {
+ $string = str_replace(RFC2445_CRLF.substr(RFC2445_WSP, $i, 1), '', $string);
+ }
+
+ return $string;
+}
+
+function rfc2445_is_xname($name) {
+
+ // If it's less than 3 chars, it cannot be legal
+ if(strlen($name) < 3) {
+ return false;
+ }
+
+ // If it contains an illegal char anywhere, reject it
+ if(strspn($name, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-') != strlen($name)) {
+ return false;
+ }
+
+ // To be legal, it must still start with "X-"
+ return substr($name, 0, 2) === 'X-';
+}
+
+function rfc2445_is_valid_value($value, $type) {
+
+ // This branch should only be taken with xname values
+ if($type === NULL) {
+ return true;
+ }
+
+ switch($type) {
+ case RFC2445_TYPE_CAL_ADDRESS:
+ case RFC2445_TYPE_URI:
+ if(!is_string($value)) {
+ return false;
+ }
+
+ $valid_schemes = array('ftp', 'http', 'ldap', 'gopher', 'mailto', 'news', 'nntp', 'telnet', 'wais', 'file', 'prospero');
+
+ $pos = strpos($value, ':');
+ if(!$pos) {
+ return false;
+ }
+
+ $scheme = strtolower(substr($value, 0, $pos));
+ $remain = substr($value, $pos + 1);
+
+ if(!in_array($scheme, $valid_schemes)) {
+ return false;
+ }
+
+ if($scheme === 'mailto') {
+ $regexp = '^[a-zA-Z0-9]+[_a-zA-Z0-9\-]*(\.[_a-z0-9\-]+)*@(([0-9a-zA-Z\-]+\.)+[a-zA-Z][0-9a-zA-Z\-]+|([0-9]{1,3}\.){3}[0-9]{1,3})$';
+ }
+ else {
+ $regexp = '^//(.+(:.*)?@)?(([0-9a-zA-Z\-]+\.)+[a-zA-Z][0-9a-zA-Z\-]+|([0-9]{1,3}\.){3}[0-9]{1,3})(:[0-9]{1,5})?(/.*)?$';
+ }
+
+ return ereg($regexp, $remain);
+ break;
+
+ case RFC2445_TYPE_BINARY:
+ if(!is_string($value)) {
+ return false;
+ }
+
+ $len = strlen($value);
+
+ if($len % 4 != 0) {
+ return false;
+ }
+
+ for($i = 0; $i < $len; ++$i) {
+ $ch = $value{$i};
+ if(!($ch >= 'a' && $ch <= 'z' || $ch >= 'A' && $ch <= 'Z' || $ch >= '0' && $ch <= '9' || $ch == '-' || $ch == '+')) {
+ if($ch == '=' && $len - $i <= 2) {
+ continue;
+ }
+ return false;
+ }
+ }
+ return true;
+ break;
+
+ case RFC2445_TYPE_BOOLEAN:
+ if(is_bool($value)) {
+ return true;
+ }
+ if(is_string($value)) {
+ $value = strtoupper($value);
+ return ($value == 'TRUE' || $value == 'FALSE');
+ }
+ return false;
+ break;
+
+ case RFC2445_TYPE_DATE:
+ if(is_int($value)) {
+ if($value < 0) {
+ return false;
+ }
+ $value = "$value";
+ }
+ else if(!is_string($value)) {
+ return false;
+ }
+
+ if(strlen($value) != 8) {
+ return false;
+ }
+
+ $y = intval(substr($value, 0, 4));
+ $m = intval(substr($value, 4, 2));
+ $d = intval(substr($value, 6, 2));
+
+ return checkdate($m, $d, $y);
+ break;
+
+ case RFC2445_TYPE_DATE_TIME:
+ if(!is_string($value) || strlen($value) < 15) {
+ return false;
+ }
+
+ return($value{8} == 'T' &&
+ rfc2445_is_valid_value(substr($value, 0, 8), RFC2445_TYPE_DATE) &&
+ rfc2445_is_valid_value(substr($value, 9), RFC2445_TYPE_TIME));
+ break;
+
+ case RFC2445_TYPE_DURATION:
+ if(!is_string($value)) {
+ return false;
+ }
+
+ $len = strlen($value);
+
+ if($len < 3) {
+ // Minimum conformant length: "P1W"
+ return false;
+ }
+
+ if($value{0} == '+' || $value{0} == '-') {
+ $value = substr($value, 1);
+ --$len; // Don't forget to update this!
+ }
+
+ if($value{0} != 'P') {
+ return false;
+ }
+
+ // OK, now break it up
+ $num = '';
+ $allowed = 'WDT';
+
+ for($i = 1; $i < $len; ++$i) {
+ $ch = $value{$i};
+ if($ch >= '0' && $ch <= '9') {
+ $num .= $ch;
+ continue;
+ }
+ if(strpos($allowed, $ch) === false) {
+ // Non-numeric character which shouldn't be here
+ return false;
+ }
+ if($num === '' && $ch != 'T') {
+ // Allowed non-numeric character, but no digits came before it
+ return false;
+ }
+
+ // OK, $ch now holds a character which tells us what $num is
+ switch($ch) {
+ case 'W':
+ // If duration in weeks is specified, this must end the string
+ return ($i == $len - 1);
+ break;
+
+ case 'D':
+ // Days specified, now if anything comes after it must be a 'T'
+ $allowed = 'T';
+ break;
+
+ case 'T':
+ // Starting to specify time, H M S are now valid delimiters
+ $allowed = 'HMS';
+ break;
+
+ case 'H':
+ $allowed = 'M';
+ break;
+
+ case 'M':
+ $allowed = 'S';
+ break;
+
+ case 'S':
+ return ($i == $len - 1);
+ break;
+ }
+
+ // If we 're going to continue, reset $num
+ $num = '';
+
+ }
+
+ // $num is kept for this reason: if we 're here, we ran out of chars
+ // therefore $num must be empty for the period to be legal
+ return ($num === '' && $ch != 'T');
+
+ break;
+
+ case RFC2445_TYPE_FLOAT:
+ if(is_float($value)) {
+ return true;
+ }
+ if(!is_string($value) || $value === '') {
+ return false;
+ }
+
+ $dot = false;
+ $int = false;
+ $len = strlen($value);
+ for($i = 0; $i < $len; ++$i) {
+ switch($value{$i}) {
+ case '-': case '+':
+ // A sign can only be seen at position 0 and cannot be the only char
+ if($i != 0 || $len == 1) {
+ return false;
+ }
+ break;
+ case '.':
+ // A second dot is an error
+ // Make sure we had at least one int before the dot
+ if($dot || !$int) {
+ return false;
+ }
+ $dot = true;
+ // Make also sure that the float doesn't end with a dot
+ if($i == $len - 1) {
+ return false;
+ }
+ break;
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ $int = true;
+ break;
+ default:
+ // Any other char is a no-no
+ return false;
+ break;
+ }
+ }
+ return true;
+ break;
+
+ case RFC2445_TYPE_INTEGER:
+ if(is_int($value)) {
+ return true;
+ }
+ if(!is_string($value) || $value === '') {
+ return false;
+ }
+
+ if($value{0} == '+' || $value{0} == '-') {
+ if(strlen($value) == 1) {
+ return false;
+ }
+ $value = substr($value, 1);
+ }
+
+ if(strspn($value, '0123456789') != strlen($value)) {
+ return false;
+ }
+
+ return ($value >= -2147483648 && $value <= 2147483647);
+ break;
+
+ case RFC2445_TYPE_PERIOD:
+ if(!is_string($value) || empty($value)) {
+ return false;
+ }
+
+ $parts = explode('/', $value);
+ if(count($parts) != 2) {
+ return false;
+ }
+
+ if(!rfc2445_is_valid_value($parts[0], RFC2445_TYPE_DATE_TIME)) {
+ return false;
+ }
+
+ // Two legal cases for the second part:
+ if(rfc2445_is_valid_value($parts[1], RFC2445_TYPE_DATE_TIME)) {
+ // It has to be after the start time, so
+ return ($parts[1] > $parts[0]);
+ }
+ else if(rfc2445_is_valid_value($parts[1], RFC2445_TYPE_DURATION)) {
+ // The period MUST NOT be negative
+ return ($parts[1]{0} != '-');
+ }
+
+ // It seems to be illegal
+ return false;
+ break;
+
+ case RFC2445_TYPE_RECUR:
+ if(!is_string($value)) {
+ return false;
+ }
+
+ $parts = explode(';', strtoupper($value));
+
+ // First of all, we need at least a FREQ and a UNTIL or COUNT part, so...
+ if(count($parts) < 2) {
+ return false;
+ }
+
+ // Let's get that into a more easily comprehensible format
+ $vars = array();
+ foreach($parts as $part) {
+
+ $pieces = explode('=', $part);
+ // There must be exactly 2 pieces, e.g. FREQ=WEEKLY
+ if(count($pieces) != 2) {
+ return false;
+ }
+
+ // It's illegal for a variable to appear twice
+ if(isset($vars[$pieces[0]])) {
+ return false;
+ }
+
+ // Sounds good
+ $vars[$pieces[0]] = $pieces[1];
+ }
+
+ // OK... now to test everything else
+
+ // FREQ must be the first thing appearing
+ reset($vars);
+ if(key($vars) != 'FREQ') {
+ return false;
+ }
+
+ // It's illegal to have both UNTIL and COUNT appear
+ if(isset($vars['UNTIL']) && isset($vars['COUNT'])) {
+ return false;
+ }
+
+ // Special case: BYWEEKNO is only valid for FREQ=YEARLY
+ if(isset($vars['BYWEEKNO']) && $vars['FREQ'] != 'YEARLY') {
+ return false;
+ }
+
+ // Special case: BYSETPOS is only valid if another BY option is specified
+ if(isset($vars['BYSETPOS'])) {
+ $options = array('BYSECOND', 'BYMINUTE', 'BYHOUR', 'BYDAY', 'BYMONTHDAY', 'BYYEARDAY', 'BYWEEKNO', 'BYMONTH');
+ $defined = array_keys($vars);
+ $common = array_intersect($options, $defined);
+ if(empty($common)) {
+ return false;
+ }
+ }
+
+ // OK, now simply check if each element has a valid value,
+ // unsetting them on the way. If at the end the array still
+ // has some elements, they are illegal.
+
+ if($vars['FREQ'] != 'SECONDLY' && $vars['FREQ'] != 'MINUTELY' && $vars['FREQ'] != 'HOURLY' &&
+ $vars['FREQ'] != 'DAILY' && $vars['FREQ'] != 'WEEKLY' &&
+ $vars['FREQ'] != 'MONTHLY' && $vars['FREQ'] != 'YEARLY') {
+ return false;
+ }
+ unset($vars['FREQ']);
+
+ // Set this, we may need it later
+ $weekdays = explode(',', RFC2445_WEEKDAYS);
+
+ if(isset($vars['UNTIL'])) {
+ if(rfc2445_is_valid_value($vars['UNTIL'], RFC2445_TYPE_DATE_TIME)) {
+ // The time MUST be in UTC format
+ if(!(substr($vars['UNTIL'], -1) == 'Z')) {
+ return false;
+ }
+ }
+ else if(!rfc2445_is_valid_value($vars['UNTIL'], RFC2445_TYPE_DATE_TIME)) {
+ return false;
+ }
+ }
+ unset($vars['UNTIL']);
+
+
+ if(isset($vars['COUNT'])) {
+ if(empty($vars['COUNT'])) {
+ // This also catches the string '0', which makes no sense
+ return false;
+ }
+ if(strspn($vars['COUNT'], '0123456789') != strlen($vars['COUNT'])) {
+ return false;
+ }
+ }
+ unset($vars['COUNT']);
+
+
+ if(isset($vars['INTERVAL'])) {
+ if(empty($vars['INTERVAL'])) {
+ // This also catches the string '0', which makes no sense
+ return false;
+ }
+ if(strspn($vars['INTERVAL'], '0123456789') != strlen($vars['INTERVAL'])) {
+ return false;
+ }
+ }
+ unset($vars['INTERVAL']);
+
+
+ if(isset($vars['BYSECOND'])) {
+ if($vars['BYSECOND'] == '') {
+ return false;
+ }
+ // Comma also allowed
+ if(strspn($vars['BYSECOND'], '0123456789,') != strlen($vars['BYSECOND'])) {
+ return false;
+ }
+ $secs = explode(',', $vars['BYSECOND']);
+ foreach($secs as $sec) {
+ if($sec == '' || $sec < 0 || $sec > 59) {
+ return false;
+ }
+ }
+ }
+ unset($vars['BYSECOND']);
+
+
+ if(isset($vars['BYMINUTE'])) {
+ if($vars['BYMINUTE'] == '') {
+ return false;
+ }
+ // Comma also allowed
+ if(strspn($vars['BYMINUTE'], '0123456789,') != strlen($vars['BYMINUTE'])) {
+ return false;
+ }
+ $mins = explode(',', $vars['BYMINUTE']);
+ foreach($mins as $min) {
+ if($min == '' || $min < 0 || $min > 59) {
+ return false;
+ }
+ }
+ }
+ unset($vars['BYMINUTE']);
+
+
+ if(isset($vars['BYHOUR'])) {
+ if($vars['BYHOUR'] == '') {
+ return false;
+ }
+ // Comma also allowed
+ if(strspn($vars['BYHOUR'], '0123456789,') != strlen($vars['BYHOUR'])) {
+ return false;
+ }
+ $hours = explode(',', $vars['BYHOUR']);
+ foreach($hours as $hour) {
+ if($hour == '' || $hour < 0 || $hour > 23) {
+ return false;
+ }
+ }
+ }
+ unset($vars['BYHOUR']);
+
+
+ if(isset($vars['BYDAY'])) {
+ if(empty($vars['BYDAY'])) {
+ return false;
+ }
+
+ // First off, split up all values we may have
+ $days = explode(',', $vars['BYDAY']);
+
+ foreach($days as $day) {
+ $daypart = substr($day, -2);
+ if(!in_array($daypart, $weekdays)) {
+ return false;
+ }
+
+ if(strlen($day) > 2) {
+ $intpart = substr($day, 0, strlen($day) - 2);
+ if(!rfc2445_is_valid_value($intpart, RFC2445_TYPE_INTEGER)) {
+ return false;
+ }
+ if(intval($intpart) == 0) {
+ return false;
+ }
+ }
+ }
+ }
+ unset($vars['BYDAY']);
+
+
+ if(isset($vars['BYMONTHDAY'])) {
+ if(empty($vars['BYMONTHDAY'])) {
+ return false;
+ }
+ $mdays = explode(',', $vars['BYMONTHDAY']);
+ foreach($mdays as $mday) {
+ if(!rfc2445_is_valid_value($mday, RFC2445_TYPE_INTEGER)) {
+ return false;
+ }
+ $mday = abs(intval($mday));
+ if($mday == 0 || $mday > 31) {
+ return false;
+ }
+ }
+ }
+ unset($vars['BYMONTHDAY']);
+
+
+ if(isset($vars['BYYEARDAY'])) {
+ if(empty($vars['BYYEARDAY'])) {
+ return false;
+ }
+ $ydays = explode(',', $vars['BYYEARDAY']);
+ foreach($ydays as $yday) {
+ if(!rfc2445_is_valid_value($yday, RFC2445_TYPE_INTEGER)) {
+ return false;
+ }
+ $yday = abs(intval($yday));
+ if($yday == 0 || $yday > 366) {
+ return false;
+ }
+ }
+ }
+ unset($vars['BYYEARDAY']);
+
+
+ if(isset($vars['BYWEEKNO'])) {
+ if(empty($vars['BYWEEKNO'])) {
+ return false;
+ }
+ $weeknos = explode(',', $vars['BYWEEKNO']);
+ foreach($weeknos as $weekno) {
+ if(!rfc2445_is_valid_value($weekno, RFC2445_TYPE_INTEGER)) {
+ return false;
+ }
+ $weekno = abs(intval($weekno));
+ if($weekno == 0 || $weekno > 53) {
+ return false;
+ }
+ }
+ }
+ unset($vars['BYWEEKNO']);
+
+
+ if(isset($vars['BYMONTH'])) {
+ if(empty($vars['BYMONTH'])) {
+ return false;
+ }
+ // Comma also allowed
+ if(strspn($vars['BYMONTH'], '0123456789,') != strlen($vars['BYMONTH'])) {
+ return false;
+ }
+ $months = explode(',', $vars['BYMONTH']);
+ foreach($months as $month) {
+ if($month == '' || $month < 1 || $month > 12) {
+ return false;
+ }
+ }
+ }
+ unset($vars['BYMONTH']);
+
+
+ if(isset($vars['BYSETPOS'])) {
+ if(empty($vars['BYSETPOS'])) {
+ return false;
+ }
+ $sets = explode(',', $vars['BYSETPOS']);
+ foreach($sets as $set) {
+ if(!rfc2445_is_valid_value($set, RFC2445_TYPE_INTEGER)) {
+ return false;
+ }
+ $set = abs(intval($set));
+ if($set == 0 || $set > 366) {
+ return false;
+ }
+ }
+ }
+ unset($vars['BYSETPOS']);
+
+
+ if(isset($vars['WKST'])) {
+ if(!in_array($vars['WKST'], $weekdays)) {
+ return false;
+ }
+ }
+ unset($vars['WKST']);
+
+
+ // Any remaining vars must be x-names
+ if(empty($vars)) {
+ return true;
+ }
+
+ foreach($vars as $name => $var) {
+ if(!rfc2445_is_xname($name)) {
+ return false;
+ }
+ }
+
+ // At last, all is OK!
+ return true;
+
+ break;
+
+ case RFC2445_TYPE_TEXT:
+ return true;
+ break;
+
+ case RFC2445_TYPE_TIME:
+ if(is_int($value)) {
+ if($value < 0) {
+ return false;
+ }
+ $value = "$value";
+ }
+ else if(!is_string($value)) {
+ return false;
+ }
+
+ if(strlen($value) == 7) {
+ if(strtoupper(substr($value, -1)) != 'Z') {
+ return false;
+ }
+ $value = substr($value, 0, 6);
+ }
+ if(strlen($value) != 6) {
+ return false;
+ }
+
+ $h = intval(substr($value, 0, 2));
+ $m = intval(substr($value, 2, 2));
+ $s = intval(substr($value, 4, 2));
+
+ return ($h <= 23 && $m <= 59 && $s <= 60);
+ break;
+
+ case RFC2445_TYPE_UTC_OFFSET:
+ if(is_int($value)) {
+ if($value >= 0) {
+ $value = "+$value";
+ }
+ else {
+ $value = "$value";
+ }
+ }
+ else if(!is_string($value)) {
+ return false;
+ }
+
+ if(strlen($value) == 7) {
+ $s = intval(substr($value, 5, 2));
+ $value = substr($value, 0, 5);
+ }
+ if(strlen($value) != 5 || $value == "-0000") {
+ return false;
+ }
+
+ if($value{0} != '+' && $value{0} != '-') {
+ return false;
+ }
+
+ $h = intval(substr($value, 1, 2));
+ $m = intval(substr($value, 3, 2));
+
+ return ($h <= 23 && $m <= 59 && $s <= 59);
+ break;
+ }
+
+ // TODO: remove this assertion
+ trigger_error('bad code path', E_USER_WARNING);
+ var_dump($type);
+ return false;
+}
+
+function rfc2445_do_value_formatting($value, $type) {
+ // Note: this does not only do formatting; it also does conversion to string!
+ switch($type) {
+ case RFC2445_TYPE_CAL_ADDRESS:
+ case RFC2445_TYPE_URI:
+ // Enclose in double quotes
+ $value = '"'.$value.'"';
+ break;
+ case RFC2445_TYPE_TEXT:
+ // Escape entities
+ $value = strtr($value, array("\n" => '\\n', '\\' => '\\\\', ',' => '\\,', ';' => '\\;'));
+ break;
+ }
+ return $value;
+}
+
+function rfc2445_undo_value_formatting($value, $type) {
+ switch($type) {
+ case RFC2445_TYPE_CAL_ADDRESS:
+ case RFC2445_TYPE_URI:
+ // Trim beginning and end double quote
+ $value = substr($value, 1, strlen($value) - 2);
+ break;
+ case RFC2445_TYPE_TEXT:
+ // Unescape entities
+ $value = strtr($value, array('\\n' => "\n", '\\N' => "\n", '\\\\' => '\\', '\\,' => ',', '\\;' => ';'));
+ break;
+ }
+ return $value;
+}
+
+?>

© 2014-2024 Faster IT GmbH | imprint | privacy policy