diff --git a/dba/rscds.sql b/dba/rscds.sql index c88afe0e..bf784d72 100644 --- a/dba/rscds.sql +++ b/dba/rscds.sql @@ -109,6 +109,18 @@ CREATE TABLE todo ( GRANT SELECT,INSERT,UPDATE,DELETE ON todo TO general; +-- Something that can look like a filesystem hierarchy where we store stuff +CREATE TABLE calendar ( + user_no INT references usr(user_no), + dav_name TEXT, + dav_etag TEXT, + created TIMESTAMP WITH TIME ZONE, + + PRIMARY KEY ( user_no, dav_name ) +); + +GRANT SELECT,INSERT,UPDATE,DELETE ON calendar TO general; + -- Each user can be related to each other user. This mechanism can also -- be used to define groups of users, since some relationships are transitive. CREATE TABLE relationship_type ( diff --git a/dba/sample-data.sql b/dba/sample-data.sql index e02641e7..7c454572 100644 --- a/dba/sample-data.sql +++ b/dba/sample-data.sql @@ -1,10 +1,16 @@ -- Some sample data to prime the database... +INSERT INTO roles ( role_no, role_name ) VALUES( 1, 'Admin'); +SELECT setval('roles_role_no_seq', 1); + INSERT INTO usr ( user_no, active, email_ok, updated, username, password, fullname, email ) VALUES( 1, TRUE, current_date, current_date, 'admin', '**nimda', 'Calendar Administrator', 'calendars@example.net' ); +INSERT INTO role_member (user_no, role_no) VALUES( 1, 1); INSERT INTO usr ( user_no, active, email_ok, updated, username, password, fullname, email ) VALUES( 2, TRUE, current_date, current_date, 'andrew', '**x', 'Andrew McMillan', 'andrew@catalyst.net.nz' ); +INSERT INTO role_member (user_no, role_no) VALUES( 2, 1); + INSERT INTO usr ( user_no, active, email_ok, updated, username, password, fullname, email ) VALUES( 10, TRUE, current_date, current_date, 'user1', '**user1', 'User 1', 'user1@example.net' ); @@ -30,11 +36,6 @@ INSERT INTO usr ( user_no, active, email_ok, updated, username, password, fullna SELECT setval('usr_user_no_seq', 1000); -INSERT INTO roles ( role_no, role_name ) VALUES( 1, 'Admin'); - -SELECT setval('roles_role_no_seq', 1); - -INSERT INTO role_member (user_no, role_no) VALUES( 1, 1); INSERT INTO relationship_type ( rt_id, rt_name, rt_isgroup, rt_inverse, confers, prefix_match ) VALUES( 1, 'Meeting Admin', TRUE, NULL, 'RW', '' ); @@ -46,7 +47,7 @@ INSERT INTO relationship_type ( rt_id, rt_name, rt_isgroup, rt_inverse, confers, VALUES( 3, 'Assistant to', FALSE, 2, 'RW', '' ); INSERT INTO relationship_type ( rt_id, rt_name, rt_isgroup, rt_inverse, confers, prefix_match ) - VALUES( 4, 'Team Member', FALSE, 4, 'R', '' ); + VALUES( 4, 'Member of team', FALSE, 4, 'R', '' ); INSERT INTO relationship_type ( rt_id, rt_name, rt_isgroup, rt_inverse, confers, prefix_match ) VALUES( 5, 'Meeting Resource', TRUE, NULL, 'RW', '' ); diff --git a/debian/control b/debian/control index 50a6ff07..e9436c20 100644 --- a/debian/control +++ b/debian/control @@ -7,7 +7,7 @@ Build-Depends: debhelper Package: rscds Architecture: all -Depends: debconf (>= 1.0.32), php4 (>= 4:4.3) | php5, php4-pgsql(>= 3:4.3.0) | php5-pgsql, postgresql-client (>= 7.4) | postgresql-client-8.0 | postgresql-client-8.1, libawl-php (>=0.3-1) +Depends: debconf (>= 1.0.32), php4 (>= 4:4.3) | php5, php4-pgsql(>= 3:4.3.0) | php5-pgsql, postgresql-client (>= 7.4) | postgresql-client-8.0 | postgresql-client-8.1, libawl-php (>=0.4-1) Description: Really Simple CalDAV Server The Really Simple CalDAV Server is designed to trivially store CalDAV calendars, such as those from Evolution, in a central diff --git a/htdocs/caldav.php b/htdocs/caldav.php index d9b6f337..424f4996 100644 --- a/htdocs/caldav.php +++ b/htdocs/caldav.php @@ -23,6 +23,10 @@ switch ( $_SERVER['REQUEST_METHOD'] ) { include_once("caldav-PROPFIND.php"); break; + case 'MKCALENDAR': + include_once("caldav-MKCALENDAR.php"); + break; + case 'PUT': include_once("caldav-PUT.php"); break; diff --git a/htdocs/css/browse.css b/htdocs/css/browse.css new file mode 100644 index 00000000..79e83489 --- /dev/null +++ b/htdocs/css/browse.css @@ -0,0 +1,43 @@ +/* CSS for browse pages in RSCDS */ + +tr.header th, td { + padding: 1px 4px; +} + +tr.header th { + font-family: Arial Narrow, sans-serif; + background-color: #a0f0b0; + color: #003010; +} + +tr.header a { + color: inherit; + text-decoration:none; + border:0; +} + +tr.r0:hover, tr.r1:hover { + background-color:#ffffc0; +} + +.r0 { + background-color: #e0fff0; +} + +.r1 { + background-color: #c0ffd0; +} + +img.order { + border:0; +} + +.right { + text-align:right; +} + +.left { + text-align:left; +} + + diff --git a/htdocs/js/browse.js b/htdocs/js/browse.js new file mode 100644 index 00000000..e1b83404 --- /dev/null +++ b/htdocs/js/browse.js @@ -0,0 +1,46 @@ +/** +* Simple function to send the browser to a given URL +*/ +function Go( url ) { + window.location=url; + return true; +} + +/** +* Make this tag into a Link to a given URL +*/ +function LinkTo( tag, url ) { + tag.style.cursor = "pointer"; + tag.setAttribute('onClick', "Go('" + url + "')"); + tag.setAttribute('onMouseOut', "window.status='';return true;"); + window.status = window.location.protocol + '//' + document.domain + url; + tag.setAttribute('onMouseover', "window.status = window.location.protocol + '//' + document.domain + '" + url + "';return true;"); + tag.setAttribute('href', url); + return true; +} + +/** +* Make this tag and all of it's contents into a clickable link, using the link target from an +* existing link target somewhere within the tag. Setting 'which1' to '1' will make the target +* match the 1st href target within the HTML of the tag. +* @param objectref tag A reference to the object which will become clickable. +* @param int which1 A one-based index to select which internal href attribute will become the target. +*/ +function LinkHref( tag, which1 ) { + var urls = tag.innerHTML.match( / href="([^"]*)"/ig ); +// alert(show_props(urls,'urls', 1)); + try { + var url = urls[which1 - 1]; + urls = url.match( /="([^"]*)"/ ); + } + catch (e) { + //alert("Here are the URLs found:\nYou appear to need to choose a different index for your LinkHref call (the second parameter). Add 1 to the index below for the correct URL shown and use that.\n\n" + show_props(urls,'urls', 0)); + return false; + } +// alert(show_props(urls,'urls', 1)); + url = urls[1]; +// alert("Linking to >>>" + url + "<<<"); + LinkTo(tag,url); + return true; +} + diff --git a/htdocs/rscds.css b/htdocs/rscds.css index a85bfa3a..bd0f4ba4 100644 --- a/htdocs/rscds.css +++ b/htdocs/rscds.css @@ -63,6 +63,40 @@ label { padding: 2px; } +.prompt { + font-family: Arial Narrow, sans-serif; + background-color: #a0f0b0; + color: #003010; +} + +.ph { + font-family:Bitstream Vera Sans, sans-serif; + color: #003010; + font-size: 120%; + background-color: #60e080; + text-align:left; + border-top: 2px white solid; + padding-top:3px; +} + + +#messages { + background-color: #602000; + color:white; + border:0; + padding:2px 6px; +} + +ul.messages, li.messages { + font-family:Bitstream Vera Sans, sans-serif; + font-weight:700; + font-size: 1.1em; +} + +li.messages { + display:inherit; +} + #menu { background-color: #084010; color: white; diff --git a/htdocs/users.php b/htdocs/users.php index 88cfd3fc..1e3ae9e9 100644 --- a/htdocs/users.php +++ b/htdocs/users.php @@ -8,12 +8,15 @@ require_once("interactive-page.php"); require_once("classBrowser.php"); $c->stylesheets[] = "css/browse.css"; + $c->scripts[] = "js/browse.js"; $browser = new Browser("Calendar Users"); - $browser->AddColumn( 'user_no', 'No.', '', '##user_link##' ); + $browser->AddColumn( 'user_no', 'No.', 'right', '##user_link##' ); $browser->AddColumn( 'username', 'Name' ); $browser->AddHidden( 'user_link', "'' || user_no || ''" ); + $browser->AddColumn( 'fullname', 'Full Name' ); + $browser->AddColumn( 'email', 'EMail' ); $browser->SetJoins( 'usr' ); @@ -23,7 +26,7 @@ require_once("interactive-page.php"); else $browser->AddOrder( 'user_no', 'A' ); - $browser->RowFormat( "\n", "\n", '#even' ); + $browser->RowFormat( "\n", "\n", '#even' ); $browser->DoQuery(); $c->page_title = "Calendar Users"; diff --git a/inc/RSCDSUser.php b/inc/RSCDSUser.php index d8a01f8e..d3d38aca 100644 --- a/inc/RSCDSUser.php +++ b/inc/RSCDSUser.php @@ -10,6 +10,10 @@ */ require_once("User.php"); +require_once("classBrowser.php"); + +$c->stylesheets[] = "css/browse.css"; +$c->scripts[] = "js/browse.js"; /** * A class for viewing and maintaining RSCDS User records @@ -53,6 +57,8 @@ class RSCDSUser extends User $html .= $this->RenderRoles($ef); + $html .= $this->RenderRelationships($ef); + $html .= "\n"; $html .= ""; @@ -66,6 +72,46 @@ class RSCDSUser extends User return $html; } + + /** + * Render the user's relationships to other users & resources + * + * @return string The string of html to be output + */ + function RenderRelationships( $ef, $title = "User Relationships" ) { + global $session, $c; + + $browser = new Browser(""); + + $browser->AddHidden( 'user_link', "'' || fullname || ''" ); + $browser->AddColumn( 'rt_name', 'Relationship' ); + $browser->AddColumn( 'fullname', 'Linked To', 'left', '##user_link##' ); + $browser->AddColumn( 'rt_isgroup', 'Group?' ); + $browser->AddHidden( 'confers', 'Confers' ); + $browser->AddColumn( 'email', 'EMail' ); + + $browser->SetJoins( 'relationship NATURAL JOIN relationship_type rt LEFT JOIN usr ON (to_user = user_no)' ); + $browser->SetWhere( "from_user = $this->user_no" ); + + $browser->SetUnion("SELECT rt.rt_name, fullname, rt.rt_isgroup, email, '' || fullname || '' AS user_link, rt.confers AS confers FROM relationship NATURAL JOIN relationship_type rt1 LEFT JOIN relationship_type rt ON (rt.rt_id = rt1.rt_inverse) LEFT JOIN usr ON (from_user = user_no) WHERE to_user = $this->user_no "); + + if ( isset( $_GET['o']) && isset($_GET['d']) ) { + $browser->AddOrder( $_GET['o'], $_GET['d'] ); + } + else + $browser->AddOrder( 'rt_name', 'A' ); + + $browser->RowFormat( "\n", "\n", '#even' ); + $browser->DoQuery(); + + $html = ( $title == "" ? "" : $ef->BreakLine($title) ); + $html .= " \n"; + $html .= $browser->Render(); + $html .= "\n"; + + return $html; + } + } ?> \ No newline at end of file diff --git a/inc/XMLElement.php b/inc/XMLElement.php index 8254044c..efdea837 100644 --- a/inc/XMLElement.php +++ b/inc/XMLElement.php @@ -23,6 +23,10 @@ class XMLElement { /** * Constructor - nothing fancy as yet. + * + * @param string The tag name of the new element + * @param mixed Either a string of content, or an array of sub-elements + * @param array An array of attribute name/value pairs */ function XMLElement( $tagname, $content=false, $attributes=false ) { $this->tagname=$tagname; @@ -60,6 +64,18 @@ class XMLElement { $this->content[] = $v; } + /** + * Add a new sub-element + * + * @param string The tag name of the new element + * @param mixed Either a string of content, or an array of sub-elements + * @param array An array of attribute name/value pairs + */ + function NewElement( $tagname, $content=false, $attributes=false ) { + if ( gettype($this->content) != "array" ) $this->content = array(); + $this->content[] = new XMLElement($tagname,$content,$attributes); + } + /** * Render the document tree into (nicely formatted) XML * @@ -72,7 +88,7 @@ class XMLElement { * Render the element attribute values */ foreach( $this->attributes AS $k => $v ) { - $r .= sprintf( ' %s="%s"', $k, $v ); + $r .= sprintf( ' %s="%s"', $k, htmlspecialchars($v) ); } } if ( (is_array($this->content) && count($this->content) > 0) || strlen($this->content) > 0 ) { @@ -95,7 +111,7 @@ class XMLElement { * * FIXME This should switch to CDATA in some situations. */ - $r .= htmlspecialchars($this->content); + $r .= htmlspecialchars($this->content, ENT_NOQUOTES ); } $r .= 'tagname.">\n"; } diff --git a/inc/caldav-MKCALENDAR.php b/inc/caldav-MKCALENDAR.php index 9d737dec..2b666f82 100644 --- a/inc/caldav-MKCALENDAR.php +++ b/inc/caldav-MKCALENDAR.php @@ -2,31 +2,14 @@ dbg_error_log("MKCALENDAR", "method handler"); -$attributes = array(); -$parser = xml_parser_create_ns('UTF-8'); -xml_parser_set_option ( $parser, XML_OPTION_SKIP_WHITE, 1 ); - -function xml_start_callback( $parser, $el_name, $el_attrs ) { - dbg_error_log( "PROPFIND", "Parsing $el_name" ); - dbg_log_array( "PROPFIND", "$el_name::attrs", $el_attrs, true ); - $attributes[$el_name] = $el_attrs; -} - -function xml_end_callback( $parser, $el_name ) { - dbg_error_log( "PROPFIND", "Finished Parsing $el_name" ); -} - -xml_set_element_handler ( $parser, 'xml_start_callback', 'xml_end_callback' ); - -$rpt_request = array(); -xml_parse_into_struct( $parser, $raw_post, $rpt_request ); -xml_parser_free($parser); - $make_path = $_SERVER['PATH_INFO']; -/** -* FIXME We kind of lie, at this point -*/ -header("HTTP/1.1 200 Created"); +$sql = "INSERT INTO calendar ( user_no, dav_name, dav_etag, created ) VALUES( ?, ?, ?, current_timestamp );"; +$qry = new PgQuery( $sql, $session->user_no, $make_path, md5($session->user_no. $make_path) ); + +if ( $qry->Exec("MKCALENDAR",__LINE__,__FILE__) ) + header("HTTP/1.1 200 Created"); +else + header("HTTP/1.1 500 Infernal Server Error"); ?> \ No newline at end of file diff --git a/inc/caldav-OPTIONS.php b/inc/caldav-OPTIONS.php index 589fcf3e..a32660b3 100644 --- a/inc/caldav-OPTIONS.php +++ b/inc/caldav-OPTIONS.php @@ -2,7 +2,8 @@ dbg_error_log("OPTIONS", "method handler"); header( "Content-type: text/plain"); // header( "Allow: OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, COPY, MOVE, PROPFIND, PROPPATCH, LOCK, UNLOCK, REPORT, ACL"); - header( "Allow: OPTIONS, GET, PUT, DELETE, REPORT, PROPFIND, COPY, MOVE"); + header( "Allow: ACL, COPY, DELETE, GET, HEAD, LOCK, MKCALENDAR, MKCOL, MOVE, OPTIONS, POST, PROPFIND, PROPPATCH, PUT, REPORT, SCHEDULE, TRACE, UNLOCK"); // header( "DAV: 1, 2, 3, access-control, calendar-access"); - header( "DAV: 1, 2, calendar-access"); +// header( "DAV: 1, 2, 3, calendar-access, calendar-schedule"); + header( "DAV: 1, 2, access-control, calendar-access, calendar-schedule"); ?> \ No newline at end of file diff --git a/inc/caldav-PROPFIND.php b/inc/caldav-PROPFIND.php index 930bc757..23b20882 100644 --- a/inc/caldav-PROPFIND.php +++ b/inc/caldav-PROPFIND.php @@ -23,6 +23,8 @@ xml_parse_into_struct( $parser, $raw_post, $rpt_request ); xml_parser_free($parser); $find_path = $_SERVER['PATH_INFO']; +list( $blank, $username, $calpath ) = split( '/', $find_path, 3); +$calpath = "/".$calpath; $href_list = array(); $attribute_list = array(); @@ -107,24 +109,42 @@ else { $url = sprintf("http://%s:%d%s%s", $_SERVER['SERVER_NAME'], $_SERVER['SERVER_PORT'], $_SERVER['SCRIPT_NAME'], $find_path ); $url = $_SERVER['SCRIPT_NAME'] . $find_path ; $url = preg_replace( '#/$#', '', $url); - for ( $i=0; $i < 2; $i++ ) { - $props = array(); + + $sql = "SELECT * FROM calendar WHERE user_no = ? AND dav_name ~ ?;"; + if ( $calpath == '' ) { + $sql = "SELECT user_no, '/' || username || '/' AS dav_name, md5( '/' || username || '/') AS dav_etag, updated AS created FROM usr WHERE user_no = $session->user_no UNION ".$sql; + } + $qry = new PgQuery($sql, $session->user_no, '^/'.$username.$calpath ); + $qry->Exec("PROPFIND",__LINE,__FILE__); + + while( $calendar = $qry->Fetch() ) { + $url = $_SERVER['SCRIPT_NAME'] . $calendar->dav_name; + $resourcetypes = array( new XMLElement("collection") ); + $contentlength = false; + if ( $calendar->dav_name != "/$username/" ) { + $resourcetypes[] = new XMLElement("calendar", false, array("xmlns" => "urn:ietf:params:xml:ns:caldav")); + $lqry = new PgQuery("SELECT sum(length(caldav_data)) FROM caldav_data WHERE user_no = ? AND dav_name ~ ?;", $session->user_no, '^/'.$username.$calpath.'[^/]+$' ); + if ( $lqry->Exec("PROPFIND",__LINE,__FILE__) && $row = $lqry->Fetch() ) { + $contentlength = $row->sum; + } + } + $prop = new XMLElement("prop"); if ( isset($attribute_list['GETCONTENTLENGTH']) ) { - $props[] = new XMLElement("getcontentlength" ); + $prop->NewElement("getcontentlength", $contentlength ); } if ( isset($attribute_list['GETCONTENTTYPE']) ) { - $props[] = new XMLElement("getcontenttype", "httpd/unix-directory" ); +// $prop->NewElement("getcontenttype", "text/calendar" ); + $prop->NewElement("getcontenttype", "httpd/unix-directory" ); } if ( isset($attribute_list['RESOURCETYPE']) ) { - $resourcetypes = array( new XMLElement("collection") ); - if ( $i == 1 ) $resourcetypes[] = new XMLElement("calendar", false, array("xmlns" => "urn:ietf:params:xml:ns:caldav")); - $props[] = new XMLElement("resourcetype", $resourcetypes ); + $prop->NewElement("resourcetype", $resourcetypes ); + } + if ( isset($attribute_list['GETETAG']) ) { + $prop->NewElement("getetag", '"'.$calendar->dav_etag.'"' ); } - $prop = new XMLElement("prop", $props ); $status = new XMLElement("status", "HTTP/1.1 200 OK" ); $propstat = new XMLElement( "propstat", array( $prop, $status) ); - if ( $i == 1 ) $url .= "/calendar"; $href = new XMLElement("href", $url ); $responses[] = new XMLElement( "response", array($href,$propstat)); @@ -133,10 +153,16 @@ else { $multistatus = new XMLElement( "multistatus", $responses, array('xmlns'=>'DAV:') ); } +dbg_log_array( "PROPFIND", "XML", $multistatus, true ); +$xmldoc = $multistatus->Render(); +$etag = md5($xmldoc); + header("HTTP/1.1 207 Multi-Status"); header("Content-type: text/xml;charset=UTF-8"); +header("DAV: 1, 2, calendar-access, calendar-schedule"); +header("ETag: \"$etag\""); echo''."\n"; -echo $multistatus->Render(); +echo $xmldoc; ?> \ No newline at end of file diff --git a/inc/interactive-page.php b/inc/interactive-page.php index 7ff371af..41b47973 100644 --- a/inc/interactive-page.php +++ b/inc/interactive-page.php @@ -6,9 +6,9 @@ $page_menu->AddOption("Home","/","Browse all users", false, 3900 ); $page_menu->AddOption("Help","/help.php","Help on something or other", false, 4500 ); $page_menu->AddOption("Logout","/?logout","Log out of the $c->system_name", false, 5400 ); -$relationship_menu = new MenuSet('submenu', 'submenu', 'submenu_active'); +// $relationship_menu = new MenuSet('submenu', 'submenu', 'submenu_active'); $user_menu = new MenuSet('submenu', 'submenu', 'submenu_active'); -$role_menu = new MenuSet('submenu', 'submenu', 'submenu_active'); +// $role_menu = new MenuSet('submenu', 'submenu', 'submenu_active'); $user_menu->AddOption("My Details","/user.php?user_no=$session->user_no","View my own user record", false, 700); diff --git a/inc/page-header.php b/inc/page-header.php index a7c0e5ec..a553368c 100644 --- a/inc/page-header.php +++ b/inc/page-header.php @@ -65,9 +65,9 @@ EOHDR; echo "
\n"; if ( isset($page_menu) && is_object($page_menu) ) { - $page_menu->AddSubMenu( $relationship_menu, "Relationships", "/relationships.php", "Browse all relationships", false, 4050 ); +// $page_menu->AddSubMenu( $relationship_menu, "Relationships", "/relationships.php", "Browse all relationships", false, 4050 ); $page_menu->AddSubMenu( $user_menu, "Users", "/users.php", "Browse all users", false, 4100 ); - $page_menu->AddSubMenu( $role_menu, "Roles", "/roles.php", "Browse all roles", false, 4300 ); +// $page_menu->AddSubMenu( $role_menu, "Roles", "/roles.php", "Browse all roles", false, 4300 ); $page_menu->MakeSomethingActive($active_menu_pattern); echo $page_menu->Render(); } diff --git a/inc/vEvent.php b/inc/vEvent.php deleted file mode 100644 index 05aaa174..00000000 --- a/inc/vEvent.php +++ /dev/null @@ -1,288 +0,0 @@ - -* @copyright Catalyst IT Ltd -* @license http://gnu.org/copyleft/gpl.html GNU GPL v2 -*/ - - -/** -* A Class for handling Events on a calendar -* -* @package awl -*/ -class vEvent { - /**#@+ - * @access private - */ - - /** - * List of participants in this event - * @var participants array - */ - var $participants = array(); - - /** - * An array of arbitrary properties - * @var properties array - */ - var $properties; - - /** - * The typical location name for the standard timezone such as "Pacific/Auckland" - * @var tz_locn string - */ - var $tz_locn; - - /** - * The type of iCalendar data VEVENT/VTODO - * @var type string - */ - var $type; - - /**#@-*/ - - /** - * The constructor takes an array of args. If there is an element called 'vevent' - * then that will be parsed into the vEvent object. Otherwise the array elements - * are converted into properties of the vEvent object directly. - */ - function vEvent( $args ) { - global $c; - - // Probably a good idea to always have values for these things... - if ( isset($c->local_tzid ) ) $this->properties['tz_id'] = $c->local_tzid; - $this->properties['dtstamp'] = date('Ymd\THis'); - $this->properties['sequence'] = 1; - $this->properties['uid'] = sprintf( "%s@%s", time() * 1000 + rand(0,1000), $c->domain_name); - - if ( !isset($args) || !is_array($args) ) return; - - if ( isset($args['vevent']) ) { - $this->BuildFromText($args['vevent']); - $this->DealWithTimeZones(); - return; - } - - foreach( $args AS $k => $v ) { - $this->properties[strtoupper($k)] = $v; - } - } - - /** - * Build the vEvent object from a text string which is a single VEVENT - * - * @var vevent string - */ - function BuildFromText( $vevent ) { - $vevent = preg_replace('/[\r\n]+ /', ' ', $vevent ); - $lines = preg_split('/[\r\n]+/', $vevent ); - $properties = array(); - - $vtimezone = ""; - $state = 0; - foreach( $lines AS $k => $v ) { - dbg_error_log( "vEvent", "LINE %03d: >>>%s<<<", $k, $v ); - - switch( $state ) { - case 0: - if ( $v == 'BEGIN:VEVENT' ) { - $state = $v; - $this->type = 'VEVENT'; - } - else if ( $v == 'BEGIN:VTODO' ) { - $state = $v; - $this->type = 'VTODO'; - } - else if ( $v == 'BEGIN:VTIMEZONE' ) $state = $v; - break; - - case 'BEGIN:VEVENT': - if ( $v == 'END:VEVENT' ) $state = 0; - break; - - case 'BEGIN:VTODO': - if ( $v == 'END:VTODO' ) $state = 0; - break; - - case 'BEGIN:VTIMEZONE': - if ( $v == 'END:VTIMEZONE' ) { - $state = 0; - $vtimezone .= $v; - } - break; - } - - if ( ($state == 'BEGIN:VEVENT' || $state == 'BEGIN:VTODO') && $state != $v ) { - list( $parameter, $value ) = preg_split('/:/', $v ); - if ( preg_match('/^DT[A-Z]+;TZID=/', $parameter) ) { - list( $parameter, $tz_id ) = preg_split('/;/', $parameter ); - $properties['TZID'] = $tz_id; - } - $properties[strtoupper($parameter)] = $value; - } - if ( $state == 'BEGIN:VTIMEZONE' ) { - $vtimezone .= $v . "\n"; - list( $parameter, $value ) = preg_split('/:/', $v ); - if ( !isset($this->tz_locn) && $parameter == 'X-LIC-LOCATION' ) { - $this->tz_locn = $value; - } - } - } - - if ( $vtimezone != "" ) { - $properties['VTIMEZONE'] = $vtimezone; - } - - $this->properties = &$properties; - } - - - /** - * Do what must be done with time zones from on file. Attempt to turn - * them into something that PostgreSQL can understand... - */ - function DealWithTimeZones() { - if ( isset($c->save_time_zone_defs) ) { - $qry = new PgQuery( "SELECT tz_locn FROM time_zone WHERE tz_id = ?;", $this->properties['TZID'] ); - if ( $qry->Exec('vEvent') && $qry->rows == 1 ) { - $row = $qry->Fetch(); - $this->tz_locn = $row->tz_locn; - } - } - - if ( !isset($this->tz_locn) ) { - // In case there was no X-LIC-LOCATION defined, let's hope there is something in the TZID - $this->tz_locn = preg_replace('/^.*([a-z]+\/[a-z]+)$/i','$1',$this->properties['TZID'] ); - } - - if ( isset($c->save_time_zone_defs) && $qry->rows != 1 ) { - $qry2 = new PgQuery( "INSERT INTO time_zone (tz_id, tz_locn, tz_spec) VALUES( ?, ?, ? );", - $this->properties['TZID'], $this->tz_locn, $this->properties['VTIMEZONE'] ); - $qry2->Exec("vEvent"); - } - } - - - /** - * Get the value of a property - */ - function Get( $key ) { - return $this->properties[strtoupper($key)]; - } - - - /** - * Put the value of a property - */ - function Put( $key, $value ) { - return $this->properties[strtoupper($key)] = $value; - } - - - /** - * Returns a PostgreSQL Date Format string suitable for returning iCal dates - */ - function SqlDateFormat() { - return "'IYYYMMDD\"T\"HH24MISS'"; - } - - - /** - * Returns a PostgreSQL Date Format string suitable for returning iCal durations - * - this doesn't work for negative intervals, but events should not have such! - */ - function SqlDurationFormat() { - return "'\"PT\"HH24\"H\"MI\"M\"'"; - } - - -/* -BEGIN:VCALENDAR -CALSCALE:GREGORIAN -PRODID:-//Ximian//NONSGML Evolution Calendar//EN -VERSION:2.0 -BEGIN:VEVENT -UID:20060918T005755Z-21151-1000-1-7@ubu -DTSTAMP:20060918T005755Z -DTSTART;TZID=/softwarestudio.org/Olson_20011030_5/Pacific/Auckland: - 20060918T153000 -DTEND;TZID=/softwarestudio.org/Olson_20011030_5/Pacific/Auckland: - 20060918T160000 -SUMMARY:Lunch -X-EVOLUTION-CALDAV-HREF:http: - //andrew@mycaldav/caldav.php/andrew/20060918T005757Z.ics -BEGIN:VALARM -X-EVOLUTION-ALARM-UID:20060918T005755Z-21149-1000-1-12@ubu -ACTION:DISPLAY -TRIGGER;VALUE=DURATION;RELATED=START:-PT15M -DESCRIPTION:Lunch -END:VALARM -END:VEVENT -BEGIN:VTIMEZONE -TZID:/softwarestudio.org/Olson_20011030_5/Pacific/Auckland -X-LIC-LOCATION:Pacific/Auckland -BEGIN:STANDARD -TZOFFSETFROM:+1300 -TZOFFSETTO:+1200 -TZNAME:NZST -DTSTART:19700315T030000 -RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=3SU;BYMONTH=3 -END:STANDARD -BEGIN:DAYLIGHT -TZOFFSETFROM:+1200 -TZOFFSETTO:+1300 -TZNAME:NZDT -DTSTART:19701004T020000 -RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=10 -END:DAYLIGHT -END:VTIMEZONE -END:VCALENDAR -*/ - /** - * Render the vEvent object as a text string which is a single VEVENT - */ - function Render( ) { - $interesting = array( "uid", "dtstamp", "dtstart", "dtend", "duration", "summary", - "location", "description", "action", "class", "transp", "sequence"); - - $result = << $v ) { - $v = strtoupper($v); - if ( isset($this->properties[$v]) ) - $result .= sprintf("%s:%s\n", $v, $this->properties[$v]); - } - $result .= <<properties['UID'], - $this->properties['DTSTART'], - $this->properties['DURATION'], - $this->properties['SUMMARY'], - $this->properties['LOCATION'] - ); -*/ - return $result; - } - - -} - -?> diff --git a/rscds.webprj b/rscds.webprj index d238aa4b..d1822129 100644 --- a/rscds.webprj +++ b/rscds.webprj @@ -43,11 +43,13 @@ - - - - - + + + + + + +