From f649e1b49d23b78e2043c8720facc8e8f7beeed7 Mon Sep 17 00:00:00 2001 From: Andrew McMillan Date: Sun, 1 Oct 2006 20:46:28 +1300 Subject: [PATCH] Many changes to get user maintenance basically working, make the layout all look somewhat nicer, and start to try and work with Mulberry, including implementing MKCALENDAR and PROPFIND in at least a basic manner. --- dba/rscds.sql | 12 ++ dba/sample-data.sql | 13 +- debian/control | 2 +- htdocs/caldav.php | 4 + htdocs/css/browse.css | 43 ++++++ htdocs/js/browse.js | 46 ++++++ htdocs/rscds.css | 34 +++++ htdocs/users.php | 7 +- inc/RSCDSUser.php | 46 ++++++ inc/XMLElement.php | 20 ++- inc/caldav-MKCALENDAR.php | 31 +--- inc/caldav-OPTIONS.php | 5 +- inc/caldav-PROPFIND.php | 46 ++++-- inc/interactive-page.php | 4 +- inc/page-header.php | 4 +- inc/vEvent.php | 288 -------------------------------------- rscds.webprj | 12 +- 17 files changed, 273 insertions(+), 344 deletions(-) create mode 100644 htdocs/css/browse.css create mode 100644 htdocs/js/browse.js delete mode 100644 inc/vEvent.php 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 @@ - - - - - + + + + + + +