diff --git a/htdocs/davical.php b/htdocs/davical.php
new file mode 100644
index 00000000..465501d8
--- /dev/null
+++ b/htdocs/davical.php
@@ -0,0 +1,49 @@
+LoginRequired();
+
+param_to_global('action', '{(edit|view|browse)}', 'action');
+param_to_global('component', '{[a-z0-9-_]+}', 't');
+param_to_global('id', '{[a-z0-9-_]+}', 'id');
+
+$c->stylesheets[] = 'css/'.$action.'.css';
+$c->scripts[] = 'js/'.$action.'.js';
+
+require_once('interactive-page.php');
+
+$page_elements = array();
+$code_file = sprintf( 'ui/%s-%s.php', $component, $action );
+if ( ! @include_once( $code_file ) ) {
+ $c->messages[] = sprintf('No page found to %s %s%s%s', $action, ($action == 'browse' ? '' : 'a '), $component, ($action == 'browse' ? 's' : ''));
+ include('page-header.php');
+ include('page-footer.php');
+ exit(0);
+}
+
+include('page-header.php');
+
+/**
+* Page elements could be an array of viewers, browsers or something else
+* that supports the Render() method... or a non-object which we assume is
+* just a string of text that we echo.
+*/
+$heading_level = null;
+foreach( $page_elements AS $k => $page_element ) {
+ if ( is_object($page_element) ) {
+ echo $page_element->Render($heading_level);
+ $heading_level = 'h2';
+ }
+ else {
+ echo $page_element;
+ }
+}
+
+if (function_exists("post_render_function")) {
+ post_render_function();
+}
+
+include('page-footer.php');
diff --git a/inc/classEditor.php b/inc/classEditor.php
new file mode 100644
index 00000000..54bab1f6
--- /dev/null
+++ b/inc/classEditor.php
@@ -0,0 +1,478 @@
+
+* @copyright Catalyst IT Ltd, Morphoss Ltd
+* @license http://gnu.org/copyleft/gpl.html GNU GPL v2
+*/
+
+require_once("DataUpdate.php");
+require_once("DataEntry.php");
+
+/**
+* A class for the fields in the editor
+* @package apms
+*/
+class EditorField
+{
+ var $Field;
+ var $Sql;
+ var $Value;
+ var $Attributes;
+ var $LookupSql;
+ var $OptionList;
+
+ function EditorField( $field, $sql="", $lookup_sql="" ) {
+ global $session;
+ $this->Field = $field;
+ $this->Sql = $sql;
+ $this->LookupSql = $lookup_sql;
+ $this->Attributes = array();
+ $session->Log("DBG: New field '%s' SQL='%s', Lookup='%s'", $field, $sql, $lookup_sql );
+ }
+
+ function Set($value) {
+ $this->Value = $value;
+ }
+
+ function SetSql( $sql ) {
+ $this->Sql = $sql;
+ }
+
+ function SetLookup( $lookup_sql ) {
+ $this->LookupSql = $lookup_sql;
+ }
+
+ function SetOptionList( $options, $current = null, $parameters = null) {
+ if ( gettype($options) == 'array' ) {
+ $this->OptionList = '';
+
+ if ( is_array($parameters) ) {
+ if ( isset($parameters['maxwidth']) ) $maxwidth = max(4,intval($parameters['maxwidth']));
+ if ( isset($parameters['translate']) ) $translate = true;
+ }
+
+ foreach( $options AS $k => $v ) {
+ if (is_array($current)) {
+ $selected = ( ( in_array($k,$current,true) || in_array($v,$current,true)) ? ' selected="selected"' : '' );
+ }
+ else {
+ $selected = ( ( "$k" == "$current" || "$v" == "$current" ) ? ' selected="selected"' : '' );
+ }
+ if ( isset($translate) ) $v = translate( $v );
+ if ( isset($maxwidth) ) $v = substr( $v, 0, $maxwidth);
+ $this->OptionList .= "";
+ }
+ }
+ else {
+ $this->OptionList = $options;
+ }
+ }
+
+ function GetTarget() {
+ if ( $this->Sql == "" ) return $this->Field;
+ return "$this->Sql AS $this->Field";
+ }
+
+ function AddAttribute( $k, $v ) {
+ $this->Attributes[$k] = $v;
+ }
+
+ function RenderAttributes() {
+ $attributes = "";
+ if ( count($this->Attributes) == 0 ) return $attributes;
+ foreach( $this->Attributes AS $k => $v ) {
+ $attributes .= " $k=\"" . str_replace('"', '\\"', $v) . '"';
+ }
+ return $attributes;
+ }
+}
+
+
+
+/**
+* The class for the Editor form in full
+* @package apms
+*/
+class Editor
+{
+ var $Title;
+ var $Action;
+ var $Fields;
+ var $OrderedFields;
+ var $BaseTable;
+ var $Joins;
+ var $Where;
+ var $NewWhere;
+ var $Order;
+ var $Limit;
+ var $Query;
+ var $Template;
+ var $RecordAvailable;
+ var $Record;
+ var $SubmitName;
+ var $Id;
+
+ function Editor( $title = "", $fields = null ) {
+ global $c, $session, $form_id_increment;
+ $this->Title = $title;
+ $this->Order = "";
+ $this->Limit = "";
+ $this->Template = "";
+ $this->RecordAvailable = false;
+ $this->SubmitName = 'submit';
+ $form_id_increment = (isset($form_id_increment)? ++$form_id_increment : 1);
+ $this->Id = 'editor_'.$form_id_increment;
+
+ if ( isset($fields) ) {
+ if ( is_array($fields) ) {
+ foreach( $fields AS $k => $v ) {
+ $this->AddField($v);
+ }
+ }
+ else if ( is_string($fields) ) {
+ // We've been given a table name, so get all fields for it.
+ $this->BaseTable = $fields;
+ $field_list = get_fields($fields);
+ foreach( $field_list AS $k => $v ) {
+ $this->AddField($k);
+ }
+ }
+ }
+ dbg_error_log("classEditor", "DBG: New editor called $title");
+ }
+
+ function &AddField( $field, $sql="", $lookup_sql="" ) {
+ $this->Fields[$field] = new EditorField( $field, $sql, $lookup_sql );
+ $this->OrderedFields[] = $field;
+ return $this->Fields[$field];
+ }
+
+ function SetSql( $field, $sql ) {
+ $this->Fields[$field]->SetSql( $sql );
+ }
+
+ function SetLookup( $field, $lookup_sql ) {
+ $this->Fields[$field]->SetLookup( $lookup_sql );
+ }
+
+ function Value( $value_field_name ) {
+ if ( !isset($this->Record->{$value_field_name}) ) return null;
+ return $this->Record->{$value_field_name};
+ }
+
+ function Assign( $value_field_name, $new_value ) {
+ $this->Record->{$value_field_name} = $new_value;
+ }
+
+ function Id( $id = null ) {
+ if ( isset($id) ) $this->Id = preg_replace( '#[^a-z0-9_+-]#', '', $id);
+ return $this->Id;
+ }
+
+ function SetOptionList( $field, $options, $current = null, $parameters = null) {
+ $this->Fields[$field]->SetOptionList( $options, $current, $parameters );
+ }
+
+ function AddAttribute( $field, $k, $v ) {
+ $this->Fields[$field]->AddAttribute($k,$v);
+
+ }
+
+ function SetBaseTable( $base_table ) {
+ $this->BaseTable = $base_table;
+ }
+
+ function SetJoins( $join_list ) {
+ $this->Joins = $join_list;
+ }
+
+
+ /**
+ * Accessor for the Title for the browse, which could set the title also.
+ *
+ * @param string $new_title The new title for the browser
+ * @return string The current title for the browser
+ */
+ function Title( $new_title = null ) {
+ if ( isset($new_title) ) $this->Title = $new_title;
+ return $this->Title;
+ }
+
+
+ function SetSubmitName( $new_submit ) {
+ $this->SubmitName = $new_submit;
+ }
+
+ function IsSubmit() {
+ return isset($_POST[$this->SubmitName]);
+ }
+
+ function IsUpdate() {
+ $is_update = $this->Available();
+ if ( isset( $_POST['_editor_action']) && isset( $_POST['_editor_action'][$this->Id]) ) {
+ $is_update = ( $_POST['_editor_action'][$this->Id] == 'update' );
+ dbg_error_log("ERROR", "Checking update: %s => %d", $_POST['_editor_action'][$this->Id], $is_update );
+ }
+ return $is_update;
+ }
+
+ function SetWhere( $where_clause ) {
+ $this->Where = $where_clause;
+ }
+
+ function WhereNewRecord( $where_clause ) {
+ $this->NewWhere = $where_clause;
+ }
+
+ function MoreWhere( $operator, $more_where ) {
+ if ( $this->Where == "" ) {
+ $this->Where = $more_where;
+ return;
+ }
+ $this->Where = "$this->Where $operator $more_where";
+ }
+
+ function AndWhere( $more_where ) {
+ $this->MoreWhere("AND",$more_where);
+ }
+
+ function OrWhere( $more_where ) {
+ $this->MoreWhere("OR",$more_where);
+ }
+
+ function SetTemplate( $template ) {
+ $this->Template = $template;
+ }
+
+ function Layout( $template ) {
+ if ( strstr( $template, '##form##' ) === false && stristr( $template, '
+EOTEMPLATE;
+
+$editor->SetTemplate( $template );
+$page_elements[] = $editor;
+
+
+$c->stylesheets[] = 'css/browse.css';
+$c->scripts[] = 'js/browse.js';
+
+$browser = new Browser(translate('Collection Grants'));
+
+$browser->AddColumn( 'to_principal', translate('To ID'), 'right', '##principal_link##' );
+$rowurl = $c->base_url . '/davical.php?action=edit&t=principal&id=';
+$browser->AddHidden( 'principal_link', "'' || to_principal || ''" );
+$browser->AddColumn( 'displayname', translate('Display Name') );
+$browser->AddColumn( 'privs', translate('Privileges'), '', '', 'privileges_list(privileges)' );
+$browser->AddColumn( 'members', translate('Has Members'), '', '', 'has_members_list(principal_id)' );
+
+$browser->SetOrdering( 'displayname', 'A' );
+
+$browser->SetJoins( "grants LEFT JOIN dav_principal ON (to_principal = principal_id) " );
+$browser->SetWhere( 'by_collection = '.$id );
+
+if ( $c->enable_row_linking ) {
+ $browser->RowFormat( '', '
', '#even' );
+}
+else {
+ $browser->RowFormat( '', '
', '#even' );
+}
+$browser->DoQuery();
+$page_elements[] = $browser;
+
+
+
diff --git a/inc/ui/principal-browse.php b/inc/ui/principal-browse.php
new file mode 100644
index 00000000..787fedb3
--- /dev/null
+++ b/inc/ui/principal-browse.php
@@ -0,0 +1,46 @@
+Title(translate('User Calendar Principals')); break;
+ case 2: $browser->Title(translate('Resource Calendar Principals')); break;
+ case 3: $browser->Title(translate('Group Principals')); break;
+ }
+}
+
+$browser->AddColumn( 'principal_id', translate('ID'), 'right', '##principal_link##' );
+$browser->AddColumn( 'username', translate('Name') );
+$rowurl = $c->base_url . '/davical.php?action=edit&t=principal&id=';
+$browser->AddHidden( 'principal_link', "'' || principal_id || ''" );
+$browser->AddColumn( 'displayname', translate('Display Name') );
+$browser->AddColumn( 'email', translate('EMail') );
+$browser->AddColumn( 'member_of', translate('Is Member of'), '', '', 'is_member_of_list(principal_id)' );
+
+if ( !isset($principal_type) || $principal_type == 3 ) {
+ $browser->AddColumn( 'members', translate('Has Members'), '', '', 'has_members_list(principal_id)' );
+}
+
+$browser->SetOrdering( 'username', 'A' );
+
+$browser->SetJoins( "dav_principal " );
+$browser->SetWhere( 'user_active' );
+if ( isset($principal_type) ) {
+ $browser->AndWhere( 'type_id = '.$principal_type );
+}
+
+
+$c->page_title = $browser->Title();
+
+if ( $c->enable_row_linking ) {
+ $browser->RowFormat( '', '
', '#even' );
+}
+else {
+ $browser->RowFormat( '', '
', '#even' );
+}
+
+$browser->DoQuery();
+$page_elements[] = $browser;
+
+
diff --git a/inc/ui/principal-edit.php b/inc/ui/principal-edit.php
new file mode 100644
index 00000000..5aca7532
--- /dev/null
+++ b/inc/ui/principal-edit.php
@@ -0,0 +1,263 @@
+AddField( 'date_format_type', null, "SELECT 'E', 'European' UNION SELECT 'U', 'US Format' UNION SELECT 'I', 'ISO Format'" );
+$editor->AddField( 'type_id', null, 'SELECT principal_type_id, principal_type_desc FROM principal_type ORDER BY principal_type_id' );
+param_to_global('id', 'int', 'old_id', 'principal_id' );
+$editor->SetWhere( 'principal_id='.$id );
+
+$privilege_names = array( 'read', 'write-properties', 'write-content', 'unlock', 'read-acl', 'read-current-user-privilege-set',
+ 'bind', 'unbind', 'write-acl', 'read-free-busy', 'schedule-deliver-invite', 'schedule-deliver-reply',
+ 'schedule-query-freebusy', 'schedule-send-invite', 'schedule-send-reply', 'schedule-send-freebusy' );
+
+$pwstars = '@@@@@@@@@@';
+if ( $editor->IsSubmit() ) {
+ $editor->WhereNewRecord( "principal_id=(SELECT CURRVAL('dav_id_seq'))" );
+ unset($_POST['password']);
+ if ( $_POST['newpass1'] != '' && $_POST['newpass1'] != $pwstars ) {
+ if ( $_POST['newpass1'] == $_POST['newpass2'] ) {
+ $_POST['password'] = $_POST['newpass1'];
+ }
+ else {
+ $c->messages[] = "Password not updated. The supplied passwords do not match.";
+ }
+ }
+ if ( isset($_POST['default_privileges']) ) {
+ $privilege_bitpos = array_flip($privilege_names);
+ $priv_names = array_keys($_POST['default_privileges']);
+ $privs = privilege_to_bits($priv_names);
+ $_POST['default_privileges'] = sprintf('%024s',decbin($privs));
+ $editor->Assign('default_privileges', $privs_dec);
+ }
+ $editor->Write();
+}
+else {
+ $editor->GetRecord();
+}
+if ( $editor->Available() ) {
+ $c->page_title = $editor->Title(translate('Principal').': '.$editor->Value('fullname'));
+}
+else {
+ $c->page_title = $editor->Title(translate('Create New Principal'));
+ $privs = decbin(privilege_to_bits($c->default_privileges));
+ $editor->Assign('default_privileges', $privs);
+}
+
+$privilege_xlate = array(
+ 'read' => translate('Read'),
+ 'write-properties' => translate('Write Metadata'),
+ 'write-content' => translate('Write Data'),
+ 'unlock' => translate('Override a Lock'),
+ 'read-acl' => translate('Read Access Controls'),
+ 'read-current-user-privilege-set' => translate('Read Current User\'s Access'),
+ 'bind' => translate('Create Resources'),
+ 'unbind' => translate('Delete Resources'),
+ 'write-acl' => translate('Write Access Controls'),
+ 'read-free-busy' => translate('Read Free/Busy Information'),
+ 'schedule-deliver-invite' => translate('Scheduling: Deliver an Invitation'),
+ 'schedule-deliver-reply' => translate('Scheduling: Deliver a Reply'),
+ 'schedule-query-freebusy' => translate('Scheduling: Query free/busy'),
+ 'schedule-send-invite' => translate('Scheduling: Send an Invitation'),
+ 'schedule-send-reply' => translate('Scheduling: Send a Reply'),
+ 'schedule-send-freebusy' => translate('Scheduling: Send free/busy')
+);
+
+
+$default_privileges = bindec($editor->Value('default_privileges'));
+$privileges_set = '';
+for( $i=0; $i'.$privilege_xlate[$privilege_names[$i]].''."\n";
+}
+$privileges_set .= '
';
+
+$prompt_principal_id = translate('Principal ID');
+$prompt_username = translate('Username');
+$prompt_password_1 = translate('Change Password');
+$prompt_password_1 = translate('Confirm Password');
+$prompt_fullname = translate('Fullname');
+$prompt_email = translate('Email Address');
+$prompt_date_format = translate('Date Format Style');
+$prompt_type = translate('Principal Type');
+$prompt_privileges = translate('Default Privileges');
+
+$id = $editor->Value('principal_id');
+$template = <<
+function toggle_privileges() {
+ var argv = toggle_privileges.arguments;
+ var argc = argv.length;
+
+ if ( argc < 1 ) {
+ return;
+ }
+
+ var set_to = -1;
+ if ( argv[0] == 'all' ) {
+ var fieldcount = document.forms[0].elements.length;
+ for (var i = 0; i < fieldcount; i++) {
+ var fieldname = document.forms[0].elements[i].name;
+ if ( fieldname.match( /^default_privileges/ ) ) {
+ if ( set_to == -1 ) {
+ set_to = ( document.forms[0].elements[i].checked ? 0 : 1 );
+ }
+ document.forms[0].elements[i].checked = set_to;
+ }
+ }
+ }
+ else {
+ for (var i = 0; i < argc; i++) {
+ var f = document.getElementById( 'priv_checkbox_' + argv[i]);
+ if ( set_to == -1 ) {
+ set_to = ( f.checked ? 0 : 1 );
+ }
+ f.checked = set_to;
+ }
+ }
+}
+
+
+
+
+EOTEMPLATE;
+
+$editor->SetTemplate( $template );
+$page_elements[] = $editor;
+
+
+$browser = new Browser(translate('Group Memberships'));
+$c->stylesheets[] = 'css/browse.css';
+$c->scripts[] = 'js/browse.js';
+
+$browser->AddColumn( 'group_id', translate('ID'), 'right', '##principal_link##' );
+$rowurl = $c->base_url . '/davical.php?action=edit&t=principal&id=';
+$browser->AddHidden( 'principal_link', "'' || principal_id || ''" );
+$browser->AddColumn( 'displayname', translate('Display Name') );
+$browser->AddColumn( 'member_of', translate('Is Member of'), '', '', 'is_member_of_list(principal_id)' );
+$browser->AddColumn( 'members', translate('Has Members'), '', '', 'has_members_list(principal_id)' );
+
+$browser->SetOrdering( 'displayname', 'A' );
+
+$browser->SetJoins( "group_member LEFT JOIN dav_principal ON (group_id = principal_id) " );
+$browser->SetWhere( 'user_active AND member_id = '.$id );
+
+if ( $c->enable_row_linking ) {
+ $browser->RowFormat( '', '
', '#even' );
+}
+else {
+ $browser->RowFormat( '', '
', '#even' );
+}
+$browser->DoQuery();
+$page_elements[] = $browser;
+
+
+if ( $editor->Value('type_id') == 3 ) {
+ $browser = new Browser(translate('Group Members'));
+
+ $browser->AddColumn( 'group_id', translate('ID'), 'right', '##principal_link##' );
+ $rowurl = $c->base_url . '/davical.php?action=edit&t=principal&id=';
+ $browser->AddHidden( 'principal_link', "'' || principal_id || ''" );
+ $browser->AddColumn( 'displayname', translate('Display Name') );
+ $browser->AddColumn( 'member_of', translate('Is Member of'), '', '', 'is_member_of_list(principal_id)' );
+ $browser->AddColumn( 'members', translate('Has Members'), '', '', 'has_members_list(principal_id)' );
+
+ $browser->SetOrdering( 'displayname', 'A' );
+
+ $browser->SetJoins( "group_member LEFT JOIN dav_principal ON (member_id = principal_id) " );
+ $browser->SetWhere( 'user_active AND group_id = '.$id );
+
+ if ( $c->enable_row_linking ) {
+ $browser->RowFormat( '', '
', '#even' );
+ }
+ else {
+ $browser->RowFormat( '', '
', '#even' );
+ }
+ $browser->DoQuery();
+ $page_elements[] = $browser;
+}
+
+
+$browser = new Browser(translate('Principal Grants'));
+
+$browser->AddColumn( 'to_principal', translate('To ID'), 'right', '##principal_link##' );
+$rowurl = $c->base_url . '/davical.php?action=edit&t=principal&id=';
+$browser->AddHidden( 'principal_link', "'' || to_principal || ''" );
+$browser->AddColumn( 'displayname', translate('Display Name') );
+$browser->AddColumn( 'privs', translate('Privileges'), '', '', 'privileges_list(privileges)' );
+$browser->AddColumn( 'members', translate('Has Members'), '', '', 'has_members_list(principal_id)' );
+
+$browser->SetOrdering( 'displayname', 'A' );
+
+$browser->SetJoins( "grants LEFT JOIN dav_principal ON (to_principal = principal_id) " );
+$browser->SetWhere( 'by_principal = '.$id );
+
+if ( $c->enable_row_linking ) {
+ $browser->RowFormat( '', '
', '#even' );
+}
+else {
+ $browser->RowFormat( '', '
', '#even' );
+}
+$browser->DoQuery();
+$page_elements[] = $browser;
+
+
+$browser = new Browser(translate('Principal Collections'));
+
+$browser->AddColumn( 'collection_id', translate('ID'), 'right', '##collection_link##' );
+$rowurl = $c->base_url . '/davical.php?action=edit&t=collection&id=';
+$browser->AddHidden( 'collection_link', "'' || collection_id || ''" );
+$browser->AddColumn( 'dav_name', translate('Path') );
+$browser->AddColumn( 'dav_displayname', translate('Display Name') );
+$browser->AddColumn( 'privs', translate('Privileges'), '', '', 'privileges_list(default_privileges)' );
+
+$browser->SetOrdering( 'dav_name', 'A' );
+
+$browser->SetJoins( "collection " );
+$browser->SetWhere( 'user_no = '.intval($editor->Value('user_no')) );
+
+if ( $c->enable_row_linking ) {
+ $browser->RowFormat( '', '
', '#even' );
+}
+else {
+ $browser->RowFormat( '', '
', '#even' );
+}
+$browser->DoQuery();
+$page_elements[] = $browser;
+
+