Implement task list permissions. Fix remote calendar management.
authorJan Schneider <jan@horde.org>
Wed, 10 Mar 2010 16:29:19 +0000 (17:29 +0100)
committerJan Schneider <jan@horde.org>
Wed, 10 Mar 2010 16:29:34 +0000 (17:29 +0100)
kronolith/js/kronolith.js
kronolith/lib/Ajax/Application.php
kronolith/lib/Kronolith.php
kronolith/templates/chunks/calendar.php
kronolith/templates/chunks/permissions.inc
kronolith/themes/screen.css

index 6db826c..ba97994 100644 (file)
@@ -2157,8 +2157,6 @@ KronolithCore = {
             firstTab = form.down('.tabset a.kronolithTabLink'),
             info;
 
-        this.updateGroupDropDown([['kronolithCinternalPGList', this.updateGroupPerms.bind(this)],
-                                  ['kronolithCinternalPGNew']]);
         form.enable();
         form.reset();
         if (firstTab) {
@@ -2167,32 +2165,40 @@ KronolithCore = {
         $('kronolithCalendarDialog').select('.kronolithCalendarDiv').invoke('hide');
         $('kronolithCalendar' + type + '1').show();
         form.select('.kronolithCalendarContinue').invoke('enable');
-        $('kronolithCinternalPBasic').show();
-        $('kronolithCinternalPAdvanced').hide();
-        $('kronolithCinternalPAllShow').disable();
-        $('kronolithCinternalPGList').disable();
-        $('kronolithCinternalPGPerms').disable();
-        $('kronolithCinternalPAdvanced').select('tr').findAll(function(tr) {
-            return tr.retrieve('remove');
-        }).invoke('remove');
+
+        if (type == 'internal' || type == 'tasklists') {
+            this.updateGroupDropDown([['kronolithC' + type + 'PGList', this.updateGroupPerms.bind(this, type)],
+                                      ['kronolithC' + type + 'PGNew']]);
+            $('kronolithC' + type + 'PBasic').show();
+            $('kronolithC' + type + 'PAdvanced').hide();
+            $('kronolithC' + type + 'PAllShow').disable();
+            $('kronolithC' + type + 'PGList').disable();
+            $('kronolithC' + type + 'PGPerms').disable();
+            $('kronolithC' + type + 'PAdvanced').select('tr').findAll(function(tr) {
+                return tr.retrieve('remove');
+            }).invoke('remove');
+        }
 
         var newCalendar = !calendar;
         if (calendar &&
             (Object.isUndefined(Kronolith.conf.calendars[type]) ||
              Object.isUndefined(Kronolith.conf.calendars[type][calendar]))) {
-            if (type == 'internal') {
-                this.doAction('getCalendar', { cal: calendar }, function(r) {
+            switch (type) {
+            case 'internal':
+            case 'tasklists':
+                this.doAction('getCalendar', { type: type, cal: calendar }, function(r) {
                     if (r.response.calendar) {
-                        Kronolith.conf.calendars.internal[calendar] = r.response.calendar;
-                        this.insertCalendarInList('internal', calendar, r.response.calendar);
+                        Kronolith.conf.calendars[type][calendar] = r.response.calendar;
+                        this.insertCalendarInList(type, calendar, r.response.calendar);
                         $('kronolithSharedCalendars').show();
-                        this.editCalendarCallback('internal|' + calendar);
+                        this.editCalendarCallback(type + '|' + calendar);
                     }
                 }.bind(this));
                 return;
-            } else if (type == 'remote') {
+            case 'remote':
                 newCalendar = true;
-            } else {
+                break;
+            default:
                 this.closeRedBox();
                 window.history.back();
                 return;
@@ -2202,17 +2208,21 @@ KronolithCore = {
         if (newCalendar) {
             switch (type) {
             case 'internal':
+                $('kronolithCalendarinternalTags').autocompleter.init();
+                // Fall through
             case 'tasklists':
                 $('kronolithCalendar' + type + 'LinkImportExport').up('span').hide();
                 break;
+            case 'remote':
+                if (calendar) {
+                    $('kronolithCalendarremoteUrl').setValue(calendar);
+                    $('kronolithCalendarremoteId').setValue(calendar);
+                }
+                break;
             }
             $('kronolithCalendar' + type + 'Id').clear();
             $('kronolithCalendar' + type + 'Color').setValue('#dddddd').setStyle({ backgroundColor: '#dddddd', color: '#000' });
             form.down('.kronolithCalendarDelete').hide();
-            if (calendar && type == 'remote') {
-                $('kronolithCalendarremoteUrl').setValue(calendar);
-            }
-            $('kronolithCalendarinternalTags').autocompleter.init();
         } else {
             info = Kronolith.conf.calendars[type][calendar];
 
@@ -2222,15 +2232,14 @@ KronolithCore = {
 
             switch (type) {
             case 'internal':
-                $('kronolithCalendarinternalDescription').setValue(info.desc);
-                $('kronolithCalendarinternalLinkImportExport').up('span').show();
-                $('kronolithCalendarinternalExport').href = Kronolith.conf.URI_CALENDAR_EXPORT + '=' + calendar;
                 $('kronolithCalendarinternalTags').autocompleter.init(Kronolith.conf.calendars.internal[calendar].tg);
-                break;
+                // Fall through
             case 'tasklists':
-                $('kronolithCalendartasklistsDescription').setValue(info.desc);
-                $('kronolithCalendartasklistsLinkImportExport').up('span').show();
-                $('kronolithCalendartasklistsExport').href = Kronolith.conf.tasks.URI_TASKLIST_EXPORT + '=' + calendar.substring(6);
+                $('kronolithCalendar' + type + 'Description').setValue(info.desc);
+                $('kronolithCalendar' + type + 'LinkImportExport').up('span').show();
+                $('kronolithCalendar' + type + 'Export').href = type == 'internal'
+                    ? Kronolith.conf.URI_CALENDAR_EXPORT + '=' + calendar
+                    : Kronolith.conf.tasks.URI_TASKLIST_EXPORT + '=' + calendar.substring(6);
                 break;
             case 'remote':
                 $('kronolithCalendarremoteUrl').setValue(calendar);
@@ -2242,9 +2251,11 @@ KronolithCore = {
         }
 
         if (newCalendar || info.owner) {
-            this.doAction('listTopTags', null, this.topTagsCallback.curry('kronolithCalendarinternalTopTags', 'kronolithCalendarTag'));
             form.down('.kronolithColorPicker').show();
-            if (type == 'internal') {
+            if (type == 'internal' || type == 'tasklists') {
+                if (type == 'internal') {
+                    this.doAction('listTopTags', null, this.topTagsCallback.curry('kronolithCalendarinternalTopTags', 'kronolithCalendarTag'));
+                }
                 form.down('.kronolithCalendarSubscribe').hide();
                 form.down('.kronolithCalendarUnsubscribe').hide();
                 if (newCalendar || calendar == Kronolith.conf.user) {
@@ -2252,23 +2263,23 @@ KronolithCore = {
                 } else {
                     form.down('.kronolithCalendarDelete').show();
                 }
+                $('kronolithCalendar' + type + 'LinkPerms').up('li').show();
+                if (!Object.isUndefined(info) && info.owner) {
+                    this.setPermsFields(type, info.perms);
+                }
             }
             form.down('.kronolithCalendarSave').show();
             form.down('.kronolithFormActions .kronolithSeparator').show();
-            if (type == 'internal' || type == 'tasklists') {
-                $('kronolithCalendar' + type + 'LinkPerms').up('li').show();
-            }
-            if (!Object.isUndefined(info) && info.owner) {
-                this.setPermsFields(info.perms);
-            }
         } else {
             form.disable();
-            $('kronolithCalendarinternalTags').autocompleter.disable();
             form.down('.kronolithColorPicker').hide();
             form.down('.kronolithCalendarDelete').hide();
             form.down('.kronolithCalendarSave').hide();
-            if (type == 'internal') {
-                if (Kronolith.conf.calendars.internal[calendar].show) {
+            if (type == 'internal' || type == 'tasklists') {
+                if (type == 'internal') {
+                    $('kronolithCalendar' + type + 'Tags').autocompleter.disable();
+                }
+                if (Kronolith.conf.calendars[type][calendar].show) {
                     form.down('.kronolithCalendarSubscribe').hide();
                     form.down('.kronolithCalendarUnsubscribe').show().enable();
                 } else {
@@ -2276,67 +2287,68 @@ KronolithCore = {
                     form.down('.kronolithCalendarUnsubscribe').hide();
                 }
                 form.down('.kronolithFormActions .kronolithSeparator').show();
+                $('kronolithCalendar' + type + 'LinkPerms').up('li').hide();
             } else {
                 form.down('.kronolithFormActions .kronolithSeparator').hide();
             }
-            if (type == 'internal' || type == 'tasklists') {
-                $('kronolithCalendar' + type + 'LinkPerms').up('li').hide();
-            }
         }
     },
 
     /**
      * Handles clicks on the radio boxes of the basic permissions screen.
      *
+     * @param string type  The calendar type, 'internal' or 'taskslists'.
      * @param string perm  The permission to activate, 'None', 'All', or
      *                     'Group'.
      */
-    permsClickHandler: function(perm)
+    permsClickHandler: function(type, perm)
     {
-        $('kronolithCinternalPAdvanced')
+        $('kronolithC' + type + 'PAdvanced')
             .select('input[type=checkbox]')
             .invoke('setValue', 0);
-        $('kronolithCinternalPAdvanced').select('tr').findAll(function(tr) {
+        $('kronolithC' + type + 'PAdvanced').select('tr').findAll(function(tr) {
             return tr.retrieve('remove');
         }).invoke('remove');
 
         switch (perm) {
         case 'None':
-            $('kronolithCinternalPAllShow').disable();
-            $('kronolithCinternalPGList').disable();
-            $('kronolithCinternalPGPerms').disable();
+            $('kronolithC' + type + 'PAllShow').disable();
+            $('kronolithC' + type + 'PGList').disable();
+            $('kronolithC' + type + 'PGPerms').disable();
             break;
         case 'All':
-            $('kronolithCinternalPAllShow').enable();
-            $('kronolithCinternalPGList').disable();
-            $('kronolithCinternalPGPerms').disable();
+            $('kronolithC' + type + 'PAllShow').enable();
+            $('kronolithC' + type + 'PGList').disable();
+            $('kronolithC' + type + 'PGPerms').disable();
             var perms = {
                 'default': Kronolith.conf.perms.read,
                 'guest': Kronolith.conf.perms.read
             };
-            if ($F('kronolithCinternalPAllShow')) {
+            if ($F('kronolithC' + type + 'PAllShow')) {
                 perms['default'] |= Kronolith.conf.perms.show;
                 perms['guest'] |= Kronolith.conf.perms.show;
             }
-            this.setPermsFields(perms);
+            this.setPermsFields(type, perms);
             break;
-        case 'Group':
-            $('kronolithCinternalPAllShow').disable();
-            $('kronolithCinternalPGList').enable();
-            $('kronolithCinternalPGPerms').enable();
-            var group = $F('kronolithCinternalPGSingle')
-                ? $F('kronolithCinternalPGSingle')
-                : $F('kronolithCinternalPGList');
-            this.insertGroupOrUser('group', group, true);
-            $('kronolithCinternalPGshow_' + group).setValue(1);
-            $('kronolithCinternalPGread_' + group).setValue(1);
-            if ($F('kronolithCinternalPGPerms') == 'edit') {
-                $('kronolithCinternalPGedit_' + group).setValue(1);
+        case 'G':
+            $('kronolithC' + type + 'PAllShow').disable();
+            $('kronolithC' + type + 'PGList').enable();
+            $('kronolithC' + type + 'PGPerms').enable();
+            var group = $F('kronolithC' + type + 'PGSingle')
+                ? $F('kronolithC' + type + 'PGSingle')
+                : $F('kronolithC' + type + 'PGList');
+            this.insertGroupOrUser(type, 'group', group, true);
+            $('kronolithC' + type + 'PGshow_' + group).setValue(1);
+            $('kronolithC' + type + 'PGread_' + group).setValue(1);
+            if ($F('kronolithC' + type + 'PGPerms') == 'edit') {
+                $('kronolithC' + type + 'PGedit_' + group).setValue(1);
             } else {
-                $('kronolithCinternalPGedit_' + group).setValue(0);
+                $('kronolithC' + type + 'PGedit_' + group).setValue(0);
+            }
+            $('kronolithC' + type + 'PGdelete_' + group).setValue(0);
+            if ($('kronolithC' + type + 'PGdelegate_' + group)) {
+                $('kronolithC' + type + 'PGdelegate_' + group).setValue(0);
             }
-            $('kronolithCinternalPGdelete_' + group).setValue(0);
-            $('kronolithCinternalPGdelegate_' + group).setValue(0);
             break;
         }
     },
@@ -2344,12 +2356,13 @@ KronolithCore = {
     /**
      * Populates the permissions field matrix.
      *
+     * @param string type   The calendar type, 'internal' or 'taskslists'.
      * @param object perms  An object with the resource permissions.
      */
-    setPermsFields: function(perms)
+    setPermsFields: function(type, perms)
     {
         if (this.groupLoading) {
-            this.setPermsFields.bind(this, perms).defer();
+            this.setPermsFields.bind(this, type, perms).defer();
             return;
         }
 
@@ -2378,7 +2391,7 @@ KronolithCore = {
                 break;
             case 'groups':
                 $H(perm.value).each(function(group) {
-                    this.insertGroupOrUser('group', group.key);
+                    this.insertGroupOrUser(type, 'group', group.key);
                     groupPerms = group.value;
                     groupId = group.key;
                 }, this);
@@ -2394,10 +2407,10 @@ KronolithCore = {
             case 'users':
                 $H(perm.value).each(function(user) {
                     if (user.key != Kronolith.conf.user) {
-                        this.insertGroupOrUser('user', user.key);
+                        this.insertGroupOrUser(type, 'user', user.key);
+                        advanced = true;
                     }
                 }, this);
-                advanced = true;
                 break;
             }
 
@@ -2410,13 +2423,13 @@ KronolithCore = {
                 case 'guest':
                 case 'creator':
                     if (baseperm.value & perm.value) {
-                        $('kronolithCinternalP' + perm.key + baseperm.key).setValue(1);
+                        $('kronolithC' + type + 'P' + perm.key + baseperm.key).setValue(1);
                     }
                     break;
                 case 'groups':
                     $H(perm.value).each(function(group) {
                         if (baseperm.value & group.value) {
-                            $('kronolithCinternalPG' + baseperm.key + '_' + group.key).setValue(1);
+                            $('kronolithC' + type + 'PG' + baseperm.key + '_' + group.key).setValue(1);
                         }
                     });
                     break;
@@ -2424,7 +2437,7 @@ KronolithCore = {
                     $H(perm.value).each(function(user) {
                         if (baseperm.value & user.value &&
                             user.key != Kronolith.conf.user) {
-                            $('kronolithCinternalPU' + baseperm.key + '_' + user.key).setValue(1);
+                            $('kronolithC' + type + 'PU' + baseperm.key + '_' + user.key).setValue(1);
                         }
                     });
                     break;
@@ -2433,27 +2446,29 @@ KronolithCore = {
         }.bind(this));
 
         if (advanced) {
-            this.activateAdvancedPerms();
+            this.activateAdvancedPerms(type);
         } else {
             switch (basic) {
             case 'all_read':
-                $('kronolithCinternalPAll').setValue(1);
-                $('kronolithCinternalPAllShow').setValue(0);
+                $('kronolithC' + type + 'PAll').setValue(1);
+                $('kronolithC' + type + 'PAllShow').setValue(0);
+                $('kronolithC' + type + 'PAllShow').enable();
                 break;
             case 'all_show':
-                $('kronolithCinternalPAll').setValue(1);
-                $('kronolithCinternalPAllShow').setValue(1);
+                $('kronolithC' + type + 'PAll').setValue(1);
+                $('kronolithC' + type + 'PAllShow').setValue(1);
+                $('kronolithC' + type + 'PAllShow').enable();
                 break;
             case 'group_read':
             case 'group_edit':
                 var setGroup = function(group) {
-                    if ($('kronolithCinternalPGList').visible()) {
-                        $('kronolithCinternalPGList').setValue(group);
-                        if ($('kronolithCinternalPGList').getValue() != group) {
+                    if ($('kronolithC' + type + 'PGList').visible()) {
+                        $('kronolithC' + type + 'PGList').setValue(group);
+                        if ($('kronolithC' + type + 'PGList').getValue() != group) {
                             // Group no longer exists.
                             this.permsClickHandler('None');
                         }
-                    } else if ($('kronolithCinternalPGSingle').getValue() != group) {
+                    } else if ($('kronolithC' + type + 'PGSingle').getValue() != group) {
                         // Group no longer exists.
                         this.permsClickHandler('None');
                     }
@@ -2463,10 +2478,11 @@ KronolithCore = {
                 } else {
                     setGroup();
                 }
-                $('kronolithCinternalPG').setValue(1);
-                $('kronolithCinternalPGPerms').setValue(basic.substring(6));
-                $('kronolithCinternalPAdvanced').hide();
-                $('kronolithCinternalPBasic').show();
+                $('kronolithC' + type + 'PG').setValue(1);
+                $('kronolithC' + type + 'PGPerms').setValue(basic.substring(6));
+                $('kronolithC' + type + 'PAdvanced').hide();
+                $('kronolithC' + type + 'PBasic').show();
+                $('kronolithC' + type + 'PGPerms').enable();
                 break;
             }
         }
@@ -2513,37 +2529,40 @@ KronolithCore = {
      * Updates the basic group permission interface after the group list has
      * been loaded.
      *
+     * @param string type  The calendar type, 'internal' or 'taskslists'.
      * @param Hash groups  The list of groups.
      */
-    updateGroupPerms: function(groups)
+    updateGroupPerms: function(type, groups)
     {
-        $('kronolithCinternalPGSingle').clear();
+        $('kronolithC' + type + 'PGSingle').clear();
         if (!groups) {
-            $('kronolithCinternalPG').up('span').hide();
+            $('kronolithC' + type + 'PG').up('span').hide();
         } else if (groups.size() == 1) {
-            $('kronolithCinternalPGName')
+            $('kronolithC' + type + 'PGName')
                 .update('&quot;' + groups.values()[0].escapeHTML() + '&quot;')
                 .show();
-            $('kronolithCinternalPGSingle').setValue(groups.keys()[0]);
-            $('kronolithCinternalPGList').hide();
+            $('kronolithC' + type + 'PGSingle').setValue(groups.keys()[0]);
+            $('kronolithC' + type + 'PGList').hide();
         } else {
-            $('kronolithCinternalPGName').hide();
-            $('kronolithCinternalPGList').show();
+            $('kronolithC' + type + 'PGName').hide();
+            $('kronolithC' + type + 'PGList').show();
         }
     },
 
     /**
      * Inserts a group or user row into the advanced permissions interface.
      *
+     * @param string type          The calendar type, 'internal' or
+     *                             'taskslists'.
      * @param what string          Either 'group' or 'user'.
      * @param group string         The group id or user name to insert.
      *                             Defaults to the value of the drop down.
      * @param notadvanced boolean  Enforces to NOT switch to the advanced
      *                             permissions screen.
      */
-    insertGroupOrUser: function(what, id, notadvanced)
+    insertGroupOrUser: function(type, what, id, notadvanced)
     {
-        var elm = $(what == 'user' ? 'kronolithCinternalPUNew' : 'kronolithCinternalPGNew');
+        var elm = $(what == 'user' ? 'kronolithC' + type + 'PUNew' : 'kronolithC' + type + 'PGNew');
         if (id) {
             elm.setValue(id);
         }
@@ -2578,18 +2597,20 @@ KronolithCore = {
         }
 
         if (!notadvanced) {
-            this.activateAdvancedPerms();
+            this.activateAdvancedPerms(type);
         }
     },
 
     /**
      * Activates the advanced permissions.
+     *
+     * @param string type  The calendar type, 'internal' or 'taskslists'.
      */
-    activateAdvancedPerms: function()
+    activateAdvancedPerms: function(type)
     {
-        [$('kronolithCinternalPNone'), $('kronolithCinternalPAll'), $('kronolithCinternalPG')].invoke('writeAttribute', 'checked', false);
-        $('kronolithCinternalPBasic').hide();
-        $('kronolithCinternalPAdvanced').show();
+        [$('kronolithC' + type + 'PNone'), $('kronolithC' + type + 'PAll'), $('kronolithC' + type + 'PG')].invoke('writeAttribute', 'checked', false);
+        $('kronolithC' + type + 'PBasic').hide();
+        $('kronolithC' + type + 'PAdvanced').show();
     },
 
     /**
@@ -2634,7 +2655,9 @@ KronolithCore = {
                                   if (r.response.perms) {
                                       cal.perms = r.response.perms;
                                   }
-                                  cal.tg = data.tags.split(',');
+                                  if (data.tags) {
+                                      cal.tg = data.tags.split(',');
+                                  }
                                   this.getCalendarList(type, cal.owner).select('div').each(function(element) {
                                       if (element.retrieve('calendar') == data.calendar) {
                                           element
@@ -2661,6 +2684,9 @@ KronolithCore = {
                                   if (r.response.perms) {
                                       cal.perms = r.response.perms;
                                   }
+                                  if (data.tags) {
+                                      cal.tg = data.tags.split(',');
+                                  }
                                   Kronolith.conf.calendars[type][r.response.calendar] = cal;
                                   this.insertCalendarInList(type, r.response.calendar, cal);
                               }
@@ -3165,28 +3191,39 @@ KronolithCore = {
 
             case 'kronolithCinternalPMore':
             case 'kronolithCinternalPLess':
-                $('kronolithCinternalPBasic').toggle();
-                $('kronolithCinternalPAdvanced').toggle();
+            case 'kronolithCtasklistsPMore':
+            case 'kronolithCtasklistsPLess':
+                var type = id.match(/kronolithC(.*)P/)[1];
+                $('kronolithC' + type + 'PBasic').toggle();
+                $('kronolithC' + type + 'PAdvanced').toggle();
                 e.stop();
                 return;
 
             case 'kronolithCinternalPNone':
             case 'kronolithCinternalPAll':
             case 'kronolithCinternalPG':
-                this.permsClickHandler(id.substring(22));
+            case 'kronolithCtasklistsPNone':
+            case 'kronolithCtasklistsPAll':
+            case 'kronolithCtasklistsPG':
+                var info = id.match(/kronolithC(.*)P(.*)/);
+                this.permsClickHandler(info[1], info[2]);
                 return;
 
             case 'kronolithCinternalPAllShow':
-                this.permsClickHandler('All');
+            case 'kronolithCtasklistsPAllShow':
+                var type = id.match(/kronolithC(.*)P/)[1];
+                this.permsClickHandler(type, 'All');
                 return;
 
             case 'kronolithCinternalPAdvanced':
+            case 'kronolithCtasklistsPAdvanced':
+                var type = id.match(/kronolithC(.*)P/)[1];
                 if (orig.tagName != 'INPUT') {
                     return;
                 }
-                this.activateAdvancedPerms();
+                this.activateAdvancedPerms(type);
                 if (orig.name.match(/u_.*||new/)) {
-                    this.insertGroupOrUser('user');
+                    this.insertGroupOrUser(type, 'user');
                 }
                 return;
 
index 18bccaf..12f06fe 100644 (file)
@@ -474,11 +474,12 @@ class Kronolith_Ajax_Application extends Horde_Ajax_Application_Base
                 }
                 try {
                     $calendar = Kronolith::addShare($info);
+                    Kronolith::readPermsForm($calendar);
+                    $result->perms = $calendar->getPermission()->data;
                 } catch (Exception $e) {
                     $GLOBALS['notification']->push($e, 'horde.error');
                     return $result;
                 }
-                Kronolith::readPermsForm($calendar);
                 $GLOBALS['notification']->push(sprintf(_("The calendar \"%s\" has been created."), $info['name']), 'horde.success');
                 $result->calendar = $calendar->getName();
                 $tagger->tag($result->calendar, $this->_vars->tags, $calendar->get('owner'), 'calendar');
@@ -488,13 +489,10 @@ class Kronolith_Ajax_Application extends Horde_Ajax_Application_Base
             // Update a calendar.
             try {
                 $calendar = $GLOBALS['kronolith_shares']->getShare($calendar_id);
-            } catch (Exception $e) {
-                $GLOBALS['notification']->push($e, 'horde.error');
-                return $result;
-            }
-            $original_name = $calendar->get('name');
-            try {
+                $original_name = $calendar->get('name');
                 Kronolith::updateShare($calendar, $info);
+                Kronolith::readPermsForm($calendar);
+                $result->perms = $calendar->getPermission()->data;
             } catch (Exception $e) {
                 $GLOBALS['notification']->push($e, 'horde.error');
                 return $result;
@@ -502,8 +500,6 @@ class Kronolith_Ajax_Application extends Horde_Ajax_Application_Base
             }
             $tagger->replaceTags($calendar->getName(), $this->_vars->tags, $calendar->get('owner'), 'calendar');
 
-            Kronolith::readPermsForm($calendar);
-            $result->perms = $calendar->getPermission()->data;
             if ($calendar->get('name') != $original_name) {
                 $GLOBALS['notification']->push(sprintf(_("The calendar \"%s\" has been renamed to \"%s\"."), $original_name, $calendar->get('name')), 'horde.success');
             } else {
@@ -525,6 +521,8 @@ class Kronolith_Ajax_Application extends Horde_Ajax_Application_Base
                 }
                 try {
                     $tasklist = $GLOBALS['registry']->tasks->addTasklist($calendar['name'], $calendar['description'], $calendar['color']);
+                    Kronolith::readPermsForm($tasklist);
+                    $result->perms = $tasklist->getPermission()->data;
                 } catch (Exception $e) {
                     $GLOBALS['notification']->push($e, 'horde.error');
                     return $result;
@@ -543,6 +541,8 @@ class Kronolith_Ajax_Application extends Horde_Ajax_Application_Base
             }
             try {
                 $GLOBALS['registry']->tasks->updateTasklist($calendar_id, $calendar);
+                Kronolith::readPermsForm($tasklists[$calendar_id]);
+                $result->perms = $tasklists[$calendar_id]->getPermission()->data;
             } catch (Exception $e) {
                 $GLOBALS['notification']->push($e, 'horde.error');
                 return $result;
index 5b3b354..8cbcf17 100644 (file)
@@ -285,6 +285,7 @@ class Kronolith
                         'fg' => self::foregroundColor($tasklist),
                         'bg' => self::backgroundColor($tasklist),
                         'show' => in_array('tasks/' . $id, $GLOBALS['display_external_calendars']),
+                        'perms' => $tasklist->getPermission()->data,
                         'edit' => $tasklist->hasPermission(Horde_Auth::getAuth(), Horde_Perms::EDIT));
                 }
             }
index 7993110..8972453 100644 (file)
@@ -57,7 +57,7 @@ asort($groups);
 </div>
 
 <div id="kronolithCalendarinternalTabPerms" class="kronolithTabsOption" style="display:none">
-<?php include dirname(__FILE__) . '/permissions.inc'; ?>
+<?php $type = 'internal'; include dirname(__FILE__) . '/permissions.inc'; ?>
 </div>
 
 <div id="kronolithCalendarinternalTabImportExport" class="kronolithTabsOption" style="display:none">
@@ -120,7 +120,7 @@ asort($groups);
 </div>
 
 <div id="kronolithCalendartasklistsTabPerms" class="kronolithTabsOption" style="display:none">
-tbd
+<?php $type = 'tasklists'; include dirname(__FILE__) . '/permissions.inc'; ?>
 </div>
 
 <div id="kronolithCalendartasklistsTabImportExport" class="kronolithTabsOption" style="display:none">
@@ -138,6 +138,8 @@ tbd
 <div class="kronolithFormActions">
   <input type="button" value="<?php echo _("Save") ?>" class="kronolithCalendarSave button ok" />
   <input type="button" value="<?php echo _("Delete") ?>" class="kronolithCalendarDelete button ko" />
+  <input type="button" value="<?php echo _("Subscribe") ?>" class="kronolithCalendarSubscribe button ok" style="display:none" />
+  <input type="button" value="<?php echo _("Unsubscribe") ?>" class="kronolithCalendarUnsubscribe button ko" style="display:none" />
   <span class="kronolithSeparator"><?php echo _("or") ?></span> <a class="kronolithFormCancel"><?php echo _("Cancel") ?></a>
 </div>
 </div>
@@ -158,7 +160,7 @@ tbd
 <div>
   <label><?php echo _("Color") ?>:<br />
     <input type="text" name="color" id="kronolithCalendarremoteColor" size="7" />
-    <?php echo Horde::url('#')->link(array('title' => _("Color Picker"), 'onclick' => 'new ColorPicker({ color: $F(\'kronolithCalendarremoteColor\'), offsetParent: Event.element(event), update: [[\'kronolithCalendarremoteColor\', \'value\'], [\'kronolithCalendarremoteColor\', \'background\']] }); return false;')) . Horde::img('colorpicker.png', _("Color Picker")) . '</a>' ?>
+    <?php echo Horde::url('#')->link(array('title' => _("Color Picker"), 'class' => 'kronolithColorPicker')) . Horde::img('colorpicker.png', _("Color Picker")) . '</a>' ?>
   </label>
 </div>
 
index 04b79b7..b2ca94e 100644 (file)
@@ -1,47 +1,47 @@
-  <div id="kronolithCinternalPBasic">
-    <div class="kronolithDialogInfo"><?php printf(_("%s Standard sharing. %s You can also set %s advanced sharing %s options."), '<strong>', '</strong>', '<strong><a href="#" id="kronolithCinternalPMore">', '</a></strong>') ?></div>
+  <div id="kronolithC<?php echo $type ?>PBasic">
+    <div class="kronolithDialogInfo"><?php printf(_("%s Standard sharing. %s You can also set %s advanced sharing %s options."), '<strong>', '</strong>', '<strong><a href="#" id="kronolithC' .  $type . 'PMore">', '</a></strong>') ?></div>
     <div>
-      <input type="radio" id="kronolithCinternalPNone" name="basic_perms" checked="checked" />
-      <label for="kronolithCinternalPNone"><?php echo _("Don't share this calendar") ?></label><br />
+      <input type="radio" id="kronolithC<?php echo $type ?>PNone" name="basic_perms" checked="checked" />
+      <label for="kronolithC<?php echo $type ?>PNone"><?php echo $type == 'internal' ? _("Don't share this calendar") : _("Don't share this task list") ?></label><br />
       <?php echo _("or share with") ?>
-      <input type="radio" id="kronolithCinternalPAll" name="basic_perms" />
-      <label for="kronolithCinternalPAll"><?php echo _("everyone") ?></label>
+      <input type="radio" id="kronolithC<?php echo $type ?>PAll" name="basic_perms" />
+      <label for="kronolithC<?php echo $type ?>PAll"><?php echo _("everyone") ?></label>
       (<?php echo _("and") ?>
-      <input type="checkbox" id="kronolithCinternalPAllShow" />
-      <?php printf(_("%s make it searchable %s by everyone too"), '<label for="kronolithCinternalPAllShow">', '</label>') ?>)<br />
+      <input type="checkbox" id="kronolithC<?php echo $type ?>PAllShow" />
+      <?php printf(_("%s make it searchable %s by everyone too"), '<label for="kronolithC<?php echo $type ?>PAllShow">', '</label>') ?>)<br />
       <span>
         <?php echo _("or share with") ?>
-        <input type="radio" id="kronolithCinternalPG" name="basic_perms" />
-        <label for="kronolithCinternalPG">
+        <input type="radio" id="kronolithC<?php echo $type ?>PG" name="basic_perms" />
+        <label for="kronolithC<?php echo $type ?>PG">
           <?php echo _("the") ?>
-          <input type="hidden" id="kronolithCinternalPGSingle"<?php if (count($groups) == 1) echo ' value="' . key($groups) . '"' ?> />
-          <span id="kronolithCinternalPGName"><?php if (count($groups) == 1) echo '&quot;' . htmlspecialchars(reset($groups)) . '&quot;' ?></span>
+          <input type="hidden" id="kronolithC<?php echo $type ?>PGSingle"<?php if (count($groups) == 1) echo ' value="' . key($groups) . '"' ?> />
+          <span id="kronolithC<?php echo $type ?>PGName"><?php if (count($groups) == 1) echo '&quot;' . htmlspecialchars(reset($groups)) . '&quot;' ?></span>
         </label>
-        <select id="kronolithCinternalPGList">
+        <select id="kronolithC<?php echo $type ?>PGList">
           <?php if (count($groups) > 1): ?>
           <?php foreach ($groups as $id => $group): ?>
           <option value="<?php echo $id ?>"><?php echo htmlspecialchars($group) ?></option>
           <?php endforeach; ?>
           <?php endif; ?>
         </select>
-        <label for="kronolithCinternalPG">
+        <label for="kronolithC<?php echo $type ?>PG">
           <?php echo _("group") ?>
         </label>
-        <?php printf(_("and %s allow them to %s"), '<label for="kronolithCinternalPGPerms">','</label>') ?>
-        <select id="kronolithCinternalPGPerms" onchange="KronolithCore.permsClickHandler('Group')">
-          <option value="read"><?php echo _("read the events") ?></option>
-          <option value="edit"><?php echo _("read and edit the events") ?></option>
+        <?php printf(_("and %s allow them to %s"), '<label for="kronolithC<?php echo $type ?>PGPerms">','</label>') ?>
+        <select id="kronolithC<?php echo $type ?>PGPerms" onchange="KronolithCore.permsClickHandler('<?php echo $type ?>', 'G')">
+          <option value="read"><?php echo $type == 'internal' ? _("read the events") : _("read the tasks") ?></option>
+          <option value="edit"><?php echo $type == 'internal' ? _("read and edit the events") : _("read and edit the tasks") ?></option>
         </select><br />
       </span>
     </div>
   </div>
-  <div id="kronolithCinternalPAdvanced" style="display:none">
-    <div class="kronolithDialogInfo"><?php printf(_("%s Advanced sharing. %s You can also return to the %s standard settings %s."), '<strong>', '</strong>', '<strong><a href="#" id="kronolithCinternalPLess">', '</a></strong>') ?></div>
+  <div id="kronolithC<?php echo $type ?>PAdvanced" class="kronolithCPAdvanced" style="display:none">
+    <div class="kronolithDialogInfo"><?php printf(_("%s Advanced sharing. %s You can also return to the %s standard settings %s."), '<strong>', '</strong>', '<strong><a href="#" id="kronolithC' . $type . 'PLess">', '</a></strong>') ?></div>
     <div>
     <table width="100%" cellspacing="0" cellpadding="0" border="0">
       <thead>
         <tr valign="middle">
-          <th colspan="2"><?php echo _("Calendar owner") ?></th>
+          <th colspan="2"><?php echo $type == 'internal' ? _("Calendar owner") : _("Task list owner") ?></th>
         </tr>
       </thead>
       <tbody>
@@ -66,7 +66,7 @@
       <thead>
         <tr valign="middle">
           <th><?php echo _("Sharing") ?></th>
-          <th colspan="5"><?php echo _("Permissions") ?></th>
+          <th colspan="<?php echo $type == 'internal' ? 5 : 4 ?>"><?php echo _("Permissions") ?></th>
         </tr>
       </thead>
 
       <tr>
         <td><?php echo _("All Authenticated Users") ?></td>
         <td>
-          <input type="checkbox" id="kronolithCinternalPdefaultshow" name="default_show" />
-          <label for="kronolithCinternalPdefaultshow"><?php echo _("Show") ?></label>
+          <input type="checkbox" id="kronolithC<?php echo $type ?>Pdefaultshow" name="default_show" />
+          <label for="kronolithC<?php echo $type ?>Pdefaultshow"><?php echo _("Show") ?></label>
         </td>
         <td>
-          <input type="checkbox" id="kronolithCinternalPdefaultread" name="default_read" />
-          <label for="kronolithCinternalPdefaultread"><?php echo _("Read") ?></label>
+          <input type="checkbox" id="kronolithC<?php echo $type ?>Pdefaultread" name="default_read" />
+          <label for="kronolithC<?php echo $type ?>Pdefaultread"><?php echo _("Read") ?></label>
         </td>
         <td>
-          <input type="checkbox" id="kronolithCinternalPdefaultedit" name="default_edit" />
-          <label for="kronolithCinternalPdefaultedit"><?php echo _("Edit") ?></label>
+          <input type="checkbox" id="kronolithC<?php echo $type ?>Pdefaultedit" name="default_edit" />
+          <label for="kronolithC<?php echo $type ?>Pdefaultedit"><?php echo _("Edit") ?></label>
         </td>
         <td>
-          <input type="checkbox" id="kronolithCinternalPdefaultdelete" name="default_delete" />
-          <label for="kronolithCinternalPdefaultdelete"><?php echo _("Delete") ?></label>
+          <input type="checkbox" id="kronolithC<?php echo $type ?>Pdefaultdelete" name="default_delete" />
+          <label for="kronolithC<?php echo $type ?>Pdefaultdelete"><?php echo _("Delete") ?></label>
         </td>
+        <?php if ($type == 'internal'): ?>
         <td>
-          <input type="checkbox" id="kronolithCinternalPdefaultdelegate" name="default_delegate" />
-          <label for="kronolithCinternalPdefaultdelegate"><?php echo _("Delegate") ?></label>
+          <input type="checkbox" id="kronolithC<?php echo $type ?>Pdefaultdelegate" name="default_delegate" />
+          <label for="kronolithC<?php echo $type ?>Pdefaultdelegate"><?php echo _("Delegate") ?></label>
         </td>
+        <?php endif; ?>
       </tr>
 
       <!-- Guest Permissions -->
       <tr>
         <td><?php echo _("Guest Permissions") ?></td>
         <td>
-          <input type="checkbox" id="kronolithCinternalPguestshow" name="guest_show" />
-          <label for="kronolithCinternalPguestshow"><?php echo _("Show") ?></label>
+          <input type="checkbox" id="kronolithC<?php echo $type ?>Pguestshow" name="guest_show" />
+          <label for="kronolithC<?php echo $type ?>Pguestshow"><?php echo _("Show") ?></label>
         </td>
         <td>
-          <input type="checkbox" id="kronolithCinternalPguestread" name="guest_read" />
-          <label for="kronolithCinternalPguestread"><?php echo _("Read") ?></label>
+          <input type="checkbox" id="kronolithC<?php echo $type ?>Pguestread" name="guest_read" />
+          <label for="kronolithC<?php echo $type ?>Pguestread"><?php echo _("Read") ?></label>
         </td>
         <td>
-          <input type="checkbox" id="kronolithCinternalPguestedit" name="guest_edit" />
-          <label for="kronolithCinternalPguestedit"><?php echo _("Edit") ?></label>
+          <input type="checkbox" id="kronolithC<?php echo $type ?>Pguestedit" name="guest_edit" />
+          <label for="kronolithC<?php echo $type ?>Pguestedit"><?php echo _("Edit") ?></label>
         </td>
         <td>
-          <input type="checkbox" id="kronolithCinternalPguestdelete" name="guest_delete" />
-          <label for="kronolithCinternalPguestdelete"><?php echo _("Delete") ?></label>
+          <input type="checkbox" id="kronolithC<?php echo $type ?>Pguestdelete" name="guest_delete" />
+          <label for="kronolithC<?php echo $type ?>Pguestdelete"><?php echo _("Delete") ?></label>
         </td>
+        <?php if ($type == 'internal'): ?>
         <td>
-          <input type="checkbox" id="kronolithCinternalPguestdelegate" name="guest_delegate" />
-          <label for="kronolithCinternalPguestdelegate"><?php echo _("Delegate") ?></label>
+          <input type="checkbox" id="kronolithC<?php echo $type ?>Pguestdelegate" name="guest_delegate" />
+          <label for="kronolithC<?php echo $type ?>Pguestdelegate"><?php echo _("Delegate") ?></label>
         </td>
+        <?php endif; ?>
       </tr>
       <?php endif; ?>
 
       <tr>
         <td><?php echo _("Object Creator") ?></td>
         <td>
-          <input type="checkbox" id="kronolithCinternalPcreatorshow"  name="creator_show" />
-          <label for="kronolithCinternalPcreatorshow"><?php echo _("Show") ?></label>
+          <input type="checkbox" id="kronolithC<?php echo $type ?>Pcreatorshow"  name="creator_show" />
+          <label for="kronolithC<?php echo $type ?>Pcreatorshow"><?php echo _("Show") ?></label>
         </td>
         <td>
-          <input type="checkbox" id="kronolithCinternalPcreatorread" name="creator_read" />
-          <label for="kronolithCinternalPcreatorread"><?php echo _("Read") ?></label>
+          <input type="checkbox" id="kronolithC<?php echo $type ?>Pcreatorread" name="creator_read" />
+          <label for="kronolithC<?php echo $type ?>Pcreatorread"><?php echo _("Read") ?></label>
         </td>
         <td>
-          <input type="checkbox" id="kronolithCinternalPcreatoredit" name="creator_edit" />
-          <label for="kronolithCinternalPcreatoredit"><?php echo _("Edit") ?></label>
+          <input type="checkbox" id="kronolithC<?php echo $type ?>Pcreatoredit" name="creator_edit" />
+          <label for="kronolithC<?php echo $type ?>Pcreatoredit"><?php echo _("Edit") ?></label>
         </td>
         <td>
-          <input type="checkbox" id="kronolithCinternalPcreatordelete" name="creator_delete" />
-          <label for="kronolithCinternalPcreatordelete"><?php echo _("Delete") ?></label>
+          <input type="checkbox" id="kronolithC<?php echo $type ?>Pcreatordelete" name="creator_delete" />
+          <label for="kronolithC<?php echo $type ?>Pcreatordelete"><?php echo _("Delete") ?></label>
         </td>
+        <?php if ($type == 'internal'): ?>
         <td>
-          <input type="checkbox" id="kronolithCinternalPcreatordelegate" name="creator_delegate" />
-          <label for="kronolithCinternalPcreatordelegate"><?php echo _("Delegate") ?></label>
+          <input type="checkbox" id="kronolithC<?php echo $type ?>Pcreatordelegate" name="creator_delegate" />
+          <label for="kronolithC<?php echo $type ?>Pcreatordelegate"><?php echo _("Delegate") ?></label>
         </td>
+        <?php endif; ?>
       </tr>
 
       <!-- User Permissions -->
       <tr>
         <td>
           <?php echo _("User:") ?>
-          <label for="kronolithCinternalPUNew" class="hidden"><?php echo _("User to add:") ?></label>
+          <label for="kronolithC<?php echo $type ?>PUNew" class="hidden"><?php echo _("User to add:") ?></label>
           <?php if ($auth->hasCapability('list') && ($GLOBALS['conf']['auth']['list_users'] == 'list' || $GLOBALS['conf']['auth']['list_users'] == 'both')): ?>
-          <select id="kronolithCinternalPUNew" name="u_names[||new]" onchange="KronolithCore.insertGroupOrUser('user')">
+          <select id="kronolithC<?php echo $type ?>PUNew" name="u_names[||new]" onchange="KronolithCore.insertGroupOrUser('<?php echo $type ?>', 'user')">
             <option value=""><?php echo _("Select a user") ?></option>
             <?php foreach ($auth->listUsers() as $user): ?>
             <?php if ($user != Horde_Auth::getAuth()): ?>
             <?php endforeach; ?>
           </select>
           <?php else: ?>
-          <input type="text" id="kronolithCinternalPUNew" name="u_names[||new]" onchange="KronolithCore.insertGroupOrUser('user')" />
+          <input type="text" id="kronolithC<?php echo $type ?>PUNew" name="u_names[||new]" onchange="KronolithCore.insertGroupOrUser('<?php echo $type ?>', 'user')" />
           <?php endif; ?>
         </td>
         <td>
-          <input type="checkbox" id="kronolithCinternalPUshow_new" name="u_show[||new]" />
-          <label for="kronolithCinternalPUshow_new"><?php echo _("Show") ?></label>
+          <input type="checkbox" id="kronolithC<?php echo $type ?>PUshow_new" name="u_show[||new]" />
+          <label for="kronolithC<?php echo $type ?>PUshow_new"><?php echo _("Show") ?></label>
         </td>
         <td>
-          <input type="checkbox" id="kronolithCinternalPUread_new" name="u_read[||new]" />
-          <label for="kronolithCinternalPUread_new"><?php echo _("Read") ?></label>
+          <input type="checkbox" id="kronolithC<?php echo $type ?>PUread_new" name="u_read[||new]" />
+          <label for="kronolithC<?php echo $type ?>PUread_new"><?php echo _("Read") ?></label>
         </td>
         <td>
-          <input type="checkbox" id="kronolithCinternalPUedit_new" name="u_edit[||new]" />
-          <label for="kronolithCinternalPUedit_new"><?php echo _("Edit") ?></label>
+          <input type="checkbox" id="kronolithC<?php echo $type ?>PUedit_new" name="u_edit[||new]" />
+          <label for="kronolithC<?php echo $type ?>PUedit_new"><?php echo _("Edit") ?></label>
         </td>
         <td>
-          <input type="checkbox" id="kronolithCinternalPUdelete_new" name="u_delete[||new]" />
-          <label for="kronolithCinternalPUdelete_new"><?php echo _("Delete") ?></label>
+          <input type="checkbox" id="kronolithC<?php echo $type ?>PUdelete_new" name="u_delete[||new]" />
+          <label for="kronolithC<?php echo $type ?>PUdelete_new"><?php echo _("Delete") ?></label>
         </td>
+        <?php if ($type == 'internal'): ?>
         <td>
-          <input type="checkbox" id="kronolithCinternalPUdelegate_new" name="u_delegate[||new]" />
-          <label for="kronolithCinternalPUdelegate_new"><?php echo _("Delegate") ?></label>
+          <input type="checkbox" id="kronolithC<?php echo $type ?>PUdelegate_new" name="u_delegate[||new]" />
+          <label for="kronolithC<?php echo $type ?>PUdelegate_new"><?php echo _("Delegate") ?></label>
         </td>
+        <?php endif; ?>
       </tr>
 
       <!-- Group Permissions -->
       <tr>
         <td>
           <?php echo _("Group:") ?>
-          <label for="kronolithCinternalPGNew" class="hidden"><?php echo _("Select a group to add:") ?></label>
-          <select id="kronolithCinternalPGNew" name="g_names[||new]" onchange="KronolithCore.insertGroupOrUser('group')">
+          <label for="kronolithC<?php echo $type ?>PGNew" class="hidden"><?php echo _("Select a group to add:") ?></label>
+          <select id="kronolithC<?php echo $type ?>PGNew" name="g_names[||new]" onchange="KronolithCore.insertGroupOrUser('<?php echo $type ?>', 'group')">
             <option value=""><?php echo _("Select a group") ?></option>
             <?php foreach ($groups as $id => $group): ?>
             <option value="<?php echo $id ?>"><?php echo htmlspecialchars($group) ?></option>
           </select>
         </td>
         <td>
-          <input type="checkbox" id="kronolithCinternalPGshow_new" name="g_show[||new]" />
-          <label for="kronolithCinternalPGshow_new"><?php echo _("Show") ?></label>
+          <input type="checkbox" id="kronolithC<?php echo $type ?>PGshow_new" name="g_show[||new]" />
+          <label for="kronolithC<?php echo $type ?>PGshow_new"><?php echo _("Show") ?></label>
         </td>
         <td>
-          <input type="checkbox" id="kronolithCinternalPGread_new" name="g_read[||new]" />
-          <label for="kronolithCinternalPGread_new"><?php echo _("Read") ?></label>
+          <input type="checkbox" id="kronolithC<?php echo $type ?>PGread_new" name="g_read[||new]" />
+          <label for="kronolithC<?php echo $type ?>PGread_new"><?php echo _("Read") ?></label>
         </td>
         <td>
-          <input type="checkbox" id="kronolithCinternalPGedit_new" name="g_edit[||new]" />
-          <label for="kronolithCinternalPGedit_new"><?php echo _("Edit") ?></label>
+          <input type="checkbox" id="kronolithC<?php echo $type ?>PGedit_new" name="g_edit[||new]" />
+          <label for="kronolithC<?php echo $type ?>PGedit_new"><?php echo _("Edit") ?></label>
         </td>
         <td>
-          <input type="checkbox" id="kronolithCinternalPGdelete_new" name="g_delete[||new]" />
-          <label for="kronolithCinternalPGdelete_new"><?php echo _("Delete") ?></label>
+          <input type="checkbox" id="kronolithC<?php echo $type ?>PGdelete_new" name="g_delete[||new]" />
+          <label for="kronolithC<?php echo $type ?>PGdelete_new"><?php echo _("Delete") ?></label>
         </td>
+        <?php if ($type == 'internal'): ?>
         <td>
-          <input type="checkbox" id="kronolithCinternalPGdelegate_new" name="g_delegate[||new]" />
-          <label for="kronolithCinternalPGdelegate_new"><?php echo _("Delegate") ?></label>
+          <input type="checkbox" id="kronolithC<?php echo $type ?>PGdelegate_new" name="g_delegate[||new]" />
+          <label for="kronolithC<?php echo $type ?>PGdelegate_new"><?php echo _("Delegate") ?></label>
         </td>
+        <?php endif; ?>
       </tr>
       </tbody>
     </table>
index a5d8788..b348089 100644 (file)
@@ -858,11 +858,6 @@ div.kronolithFormActions {
     margin-bottom: 0px;
 }
 
-/* Calendar dialog */
-#kronolithCinternalPBasic dd {
-    margin-left: 20px;
-}
-
 /* Mini calendar */
 .kronolithMinical {
     position: relative;