From 6323202895a367e7386749b3a09cfb54bae5b48a Mon Sep 17 00:00:00 2001 From: "Michael J. Rubinsky" Date: Sat, 12 Sep 2009 11:06:36 -0400 Subject: [PATCH] More additions based on some discussion with Gunnar in IRC: - Allow specifying if a resource is required or optional - Add a response_type property to resources. Specifies if REQUESTS are either always accepted, always denied, automatically accepted/denied based on actual availability, handled manually (still todo), or simply ignored (set response to NONE). Also, try to clean up the implementation - trying to make it more likely to be able to be extened for kolab. No idea if this helps of not though ;) --- kronolith/attendees.php | 50 +++++++++++++-- kronolith/lib/Driver/Resource.php | 20 +++--- kronolith/lib/Driver/Sql.php | 2 + kronolith/lib/Forms/CreateResource.php | 12 +++- kronolith/lib/Forms/EditResource.php | 10 ++- kronolith/lib/FreeBusy/View.php | 72 +++++++++++++++++++--- kronolith/lib/Kronolith.php | 30 ++++++++- kronolith/lib/Resource.php | 22 +++++++ kronolith/lib/Resource/Base.php | 19 ++++-- kronolith/lib/Resource/Single.php | 15 ++++- kronolith/resources/edit.php | 2 + .../scripts/upgrades/2009-08-17_add_resources.sql | 2 + kronolith/templates/attendees/attendees.inc | 27 ++++---- 13 files changed, 238 insertions(+), 45 deletions(-) create mode 100644 kronolith/lib/Resource.php diff --git a/kronolith/attendees.php b/kronolith/attendees.php index d0fce0da0..5f01e532b 100644 --- a/kronolith/attendees.php +++ b/kronolith/attendees.php @@ -29,7 +29,6 @@ if (Horde_Util::getFormData('clearAll')) { $actionID = 'clear'; } $actionValue = Horde_Util::getFormData('actionValue'); - // Perform the specified action, if there is one. switch ($actionID) { case 'add': @@ -105,9 +104,20 @@ case 'add': if (!empty($newResource)) { $resource = Kronolith::getDriver('Resource')->getResource($newResource); + /* If we know we always accept/deny mark it that way now. */ + /* @TODO: Should we attempt to get the proposed event time from the event form and pass it here? */ + $type = $resource->getResponseType(); + if ($type == Kronolith_Resource::RESPONSETYPE_ALWAYS_ACCEPT) { + $response = Kronolith::RESPONSE_ACCEPTED; + } elseif ($type == Kronolith_Resource::RESPONSETYPE_ALWAYS_DECLINE) { + $response = Kronolith::RESPONSE_DECLINED; + } else { + $response = Kronolith::RESPONSE_NONE; + } + $resources[$newResource] = array( - 'attendance' => Kronolith::PART_IGNORE, - 'response' => Kronolith::RESPONSE_NONE, + 'attendance' => Kronolith::PART_REQUIRED, + 'response' => $response, 'name' => $resource->get('name'), ); @@ -153,6 +163,15 @@ case 'removeResource': } break; +case 'changeResourceResp': + //@TODO: What else to do here? Dissallow if responsetype is auto? + list($partval, $partname) = explode(' ', $actionValue, 2); + if (isset($resources[$partname])) { + $resources[$partname]['response'] = $partval; + $_SESSION['kronolith']['resources'] = $resources; + } + break; + case 'changeatt': // Change the attendance status of an attendee list($partval, $partname) = explode(' ', $actionValue, 2); @@ -162,6 +181,15 @@ case 'changeatt': } break; +case 'changeResourceAtt': + // Change attendance status of a resource + list($partval, $partname) = explode(' ', $actionValue, 2); + if (isset($resources[$partname])) { + $resources[$partname]['attendance'] = $partval; + $_SESSION['kronolith']['resources'] = $resources; + } + break; + case 'changeresp': // Change the response status of an attendee list($partval, $partname) = explode(' ', $actionValue, 2); @@ -272,9 +300,19 @@ foreach ($attendees as $email => $status) { if (count($resources)) { $driver = Kronolith::getDriver('Resource'); foreach ($resources as $r_id => $resource) { - $r = $driver->getResource($r_id); - $vfb = $r->getFreeBusy(null, null, true); - $attendee_view->addResourceMember($vfb); + try { + $r = $driver->getResource($r_id); + $vfb = $r->getFreeBusy(null, null, true); + if ($resource['attendance'] == Kronolith::PART_REQUIRED) { + $attendee_view->addRequiredResourceMember($vfb); + } else { + $attendee_view->addOptionalResourceMember($vfb); + } + } catch (Horde_Exception $e) { + $notification->push( + sprintf(_("Error retrieving free/busy information for %s: %s"), + $r_id, $e->getMessage())); + } } } $date = sprintf("%02d%02d%02d000000", Horde_Util::getFormData('year'), Horde_Util::getFormData('month'), Horde_Util::getFormData('mday')); diff --git a/kronolith/lib/Driver/Resource.php b/kronolith/lib/Driver/Resource.php index 6a8f50d70..5aa02a32d 100644 --- a/kronolith/lib/Driver/Resource.php +++ b/kronolith/lib/Driver/Resource.php @@ -1,7 +1,7 @@ getId()) { - $query = 'UPDATE kronolith_resources SET resource_name = ?, resource_calendar = ?, resource_category = ? WHERE resource_id = ?'; - $values = array($resource->get('name'), $resource->get('calendar'), $resource->get('category'), $resource->getId()); + $query = 'UPDATE kronolith_resources SET resource_name = ?, resource_calendar = ?, resource_category = ? , resource_description = ?, resource_response_type = ?, resource_max_reservations = ? WHERE resource_id = ?'; + $values = array($resource->get('name'), $resource->get('calendar'), $resource->get('category'), $resource->get('description'), $resource->get('response_type'), $resource->get('max_reservations'), $resource->getId()); $result = $this->_write_db->query($query, $values); if ($result instanceof PEAR_Error) { throw new Horde_Exception($result->getMessage()); } } else { - $query = 'INSERT INTO kronolith_resources (resource_id, resource_name, resource_calendar, resource_category)'; - $cols_values = ' VALUES (?, ?, ?, ?)'; + $query = 'INSERT INTO kronolith_resources (resource_id, resource_name, resource_calendar, resource_category, resource_description, resource_response_type, resource_max_reservations)'; + $cols_values = ' VALUES (?, ?, ?, ?, ?, ?, ?)'; $id = $this->_db->nextId('kronolity_resources'); - $values = array($id, $resource->get('name'), $resource->get('calendar'), $resource->get('category')); + $values = array($id, $resource->get('name'), $resource->get('calendar'), $resource->get('category'), $resource->get('description'), $resource->get('response_type'), $resource->get('max_reservations')); $result = $this->_write_db->query($query . $cols_values, $values); if (!($result instanceof PEAR_Error)) { return true; @@ -392,7 +393,6 @@ class Kronolith_Driver_Resource extends Kronolith_Driver_Sql public function delete($resource) { if (!$resource->get('calendar') || !$resource->getId()) { - var_dump($resource); throw new Horde_Exception(_("Resource not valid.")); } @@ -452,7 +452,7 @@ class Kronolith_Driver_Resource extends Kronolith_Driver_Sql */ public function getResource($id) { - $query = 'SELECT resource_id, resource_name, resource_calendar, resource_category FROM kronolith_resources WHERE resource_id = ?'; + $query = 'SELECT resource_id, resource_name, resource_calendar, resource_category, resource_description, resource_response_type, resource_max_reservations FROM kronolith_resources WHERE resource_id = ?'; $results = $this->_db->getRow($query, array($id), DB_FETCHMODE_ASSOC); if ($results instanceof PEAR_Error) { @@ -496,7 +496,7 @@ class Kronolith_Driver_Resource extends Kronolith_Driver_Sql */ public function listResources($params = array()) { - $query = 'SELECT resource_id, resource_name, resource_calendar, resource_category FROM kronolith_resources'; + $query = 'SELECT resource_id, resource_name, resource_calendar, resource_category, resource_description, resource_response_type, resource_max_reservations FROM kronolith_resources'; $results = $this->_db->getAll($query, null, DB_FETCHMODE_ASSOC); if ($results instanceof PEAR_Error) { throw new Horde_Exception($results->getMessage()); diff --git a/kronolith/lib/Driver/Sql.php b/kronolith/lib/Driver/Sql.php index 060bb6587..8fc97a21a 100644 --- a/kronolith/lib/Driver/Sql.php +++ b/kronolith/lib/Driver/Sql.php @@ -735,6 +735,8 @@ class Kronolith_Driver_Sql extends Kronolith_Driver } /* Remove the event from any resources that are attached to it */ + //@TODO: Not sure this belongs _here_, but not sure about having to + // call this _everywhere_ we delete an event? $resources = $event->getResources(); if (count($resources)) { $rd = Kronolith::getDriver('Resource'); diff --git a/kronolith/lib/Forms/CreateResource.php b/kronolith/lib/Forms/CreateResource.php index 0e15600e7..d0c89fa07 100644 --- a/kronolith/lib/Forms/CreateResource.php +++ b/kronolith/lib/Forms/CreateResource.php @@ -27,9 +27,16 @@ class Kronolith_CreateResourceForm extends Horde_Form { function Kronolith_CreateResourceForm(&$vars) { parent::Horde_Form($vars, _("Create Resource")); + $responses = array(Kronolith_Resource::RESPONSETYPE_ALWAYS_ACCEPT => _("Always Accept"), + Kronolith_Resource::RESPONSETYPE_ALWAYS_DECLINE => _("Always Decline"), + Kronolith_Resource::RESPONSETYPE_AUTO => _("Automatically"), + Kronolith_Resource::RESPONSETYPE_MANUAL => _("Manual"), + Kronolith_Resource::RESPONSETYPE_NONE => _("None")); $this->addVariable(_("Name"), 'name', 'text', true); $this->addVariable(_("Description"), 'description', 'longtext', false, false, null, array(4, 60)); + $this->addVariable(_("Response type"), 'responsetype', 'enum', true, false, null, array('enum' => $responses)); + $this->addVariable(_("Maximum number of overlapping reservations"), 'maxreservations', 'number', true); $this->addVariable(_("Category"), 'category', 'text', false); $this->setButtons(array(_("Create"))); } @@ -37,7 +44,10 @@ class Kronolith_CreateResourceForm extends Horde_Form { function execute() { $new = array('name' => $this->_vars->get('name'), - 'category' =>$this->_vars->get('category')); + 'category' => $this->_vars->get('category'), + 'description' => $this->_vars->get('description'), + 'response_type' => $this->_vars->get('response_type'), + 'max_reservations' => $this->_vars->get('max_reservations')); $resource = new Kronolith_Resource_Single($new); return $results = Kronolith::addResource($resource); diff --git a/kronolith/lib/Forms/EditResource.php b/kronolith/lib/Forms/EditResource.php index 4ca875f6c..5e0c4a5c8 100644 --- a/kronolith/lib/Forms/EditResource.php +++ b/kronolith/lib/Forms/EditResource.php @@ -32,10 +32,16 @@ class Kronolith_EditResourceForm extends Horde_Form { { $this->_resource = &$resource; parent::Horde_Form($vars, sprintf(_("Edit %s"), $resource->get('name'))); - + $responses = array(Kronolith_Resource::RESPONSETYPE_ALWAYS_ACCEPT => _("Always Accept"), + Kronolith_Resource::RESPONSETYPE_ALWAYS_DECLINE => _("Always Decline"), + Kronolith_Resource::RESPONSETYPE_AUTO => _("Automatically"), + Kronolith_Resource::RESPONSETYPE_MANUAL => _("Manual"), + Kronolith_Resource::RESPONSETYPE_NONE => _("None")); $this->addHidden('', 'c', 'text', true); $this->addVariable(_("Name"), 'name', 'text', true); $this->addVariable(_("Description"), 'description', 'longtext', false, false, null, array(4, 60)); + $this->addVariable(_("Response type"), 'responsetype', 'enum', true, false, null, array('enum' => $responses)); + $this->addVariable(_("Maximum number of overlapping reservations"), 'maxreservations', 'number', true); $this->addVariable(_("Category"), 'category', 'text', false); $this->setButtons(array(_("Save"))); } @@ -47,6 +53,8 @@ class Kronolith_EditResourceForm extends Horde_Form { $this->_resource->set('name', $new_name); $this->_resource->set('description', $this->_vars->get('description')); $this->_resource->set('category', $this->_vars->get('category')); + $this->_resource->set('response_type', $this->_vars->get('responsetype')); + $this->_resource->set('max_reservations', $this->_vars->get('maxreservations')); if ($original_name != $new_name) { $result = Kronolith::getDriver()->rename($original_name, $new_name); if (is_a($result, 'PEAR_Error')) { diff --git a/kronolith/lib/FreeBusy/View.php b/kronolith/lib/FreeBusy/View.php index 8788c5536..015ca4f5b 100644 --- a/kronolith/lib/FreeBusy/View.php +++ b/kronolith/lib/FreeBusy/View.php @@ -14,7 +14,8 @@ class Kronolith_FreeBusy_View { var $_requiredMembers = array(); var $_optionalMembers = array(); - var $_resourceMembers = array(); + var $_requiredResourceMembers = array(); + var $_optionalResourceMembers = array(); var $_timeBlocks = array(); var $_startHour; @@ -33,9 +34,14 @@ class Kronolith_FreeBusy_View { $this->_optionalMembers[] = clone $vFreebusy; } - function addResourceMember($vFreebusy) + function addOptionalResourceMember($vFreebusy) { - $this->_resourceMembers[] = clone $vFreebusy; + $this->_optionalResourceMembers[] = clone $vFreebusy; + } + + function addRequiredResourceMember($vFreebusy) + { + $this->_requiredResourceMembers[] = clone $vFreebusy; } function render($day = null) @@ -48,18 +54,28 @@ class Kronolith_FreeBusy_View { $this->_render($day); $vCal = new Horde_iCalendar(); + + /* Required members */ $required = &Horde_iCalendar::newComponent('vfreebusy', $vCal); foreach ($this->_requiredMembers as $member) { $required->merge($member, false); } + foreach ($this->_requiredResourceMembers as $member) { + $required->merge($member, false); + } $required->simplify(); + /* Optional members */ $optional = &Horde_iCalendar::newComponent('vfreebusy', $vCal); foreach ($this->_optionalMembers as $member) { $optional->merge($member, false); } + foreach ($this->_optionalResourceMembers as $member) { + $optional->merge($member, false); + } $optional->simplify(); + /* Optimal time calculation */ $optimal = &Horde_iCalendar::newComponent('vfreebusy', $vCal); $optimal->merge($required, false); $optimal->merge($optional); @@ -124,11 +140,20 @@ class Kronolith_FreeBusy_View { $html .= $template->fetch(KRONOLITH_TEMPLATES . '/fbview/section.html'); } - // Resources - if (count($this->_resourceMembers) > 0) { + // Required Resources + //if (count($this->_requiredResourceMembers) > 0) { + if (count($this->_requiredResourceMembers) > 0 || count($this->_optionalResourceMembers) > 0) { $template = new Horde_Template(); $rows = ''; - foreach ($this->_resourceMembers as $member) { + foreach ($this->_requiredResourceMembers as $member) { + $member->simplify(); + $blocks = $this->_getBlocks($member, $member->getBusyPeriods(), 'busyblock.html', _("Busy")); + $template = new Horde_Template(); + $template->set('blocks', $blocks); + $template->set('name', $member->getName()); + $rows .= $template->fetch(KRONOLITH_TEMPLATES . '/fbview/row.html'); + } + foreach ($this->_optionalResourceMembers as $member) { $member->simplify(); $blocks = $this->_getBlocks($member, $member->getBusyPeriods(), 'busyblock.html', _("Busy")); $template = new Horde_Template(); @@ -137,7 +162,7 @@ class Kronolith_FreeBusy_View { $rows .= $template->fetch(KRONOLITH_TEMPLATES . '/fbview/row.html'); } $template = new Horde_Template(); - $template->set('title', _("Resources")); + $template->set('title', _("Required Resources")); $template->set('rows', $rows); $template->set('span', count($this->_timeBlocks)); $template->set('hours', $hours_html); @@ -145,6 +170,28 @@ class Kronolith_FreeBusy_View { $html .= $template->fetch(KRONOLITH_TEMPLATES . '/fbview/section.html'); } +// // Optional Resources +// if (count($this->_optionalResourceMembers) > 0) { +// $template = new Horde_Template(); +// $rows = ''; +// foreach ($this->_optionalResourceMembers as $member) { +// $member->simplify(); +// $blocks = $this->_getBlocks($member, $member->getBusyPeriods(), 'busyblock.html', _("Busy")); +// $template = new Horde_Template(); +// $template->set('blocks', $blocks); +// $template->set('name', $member->getName()); +// $rows .= $template->fetch(KRONOLITH_TEMPLATES . '/fbview/row.html'); +// } +// $template = new Horde_Template(); +// $template->set('title', _("Optional Resources")); +// $template->set('rows', $rows); +// $template->set('span', count($this->_timeBlocks)); +// $template->set('hours', $hours_html); +// $template->set('legend', ''); +// $html .= $template->fetch(KRONOLITH_TEMPLATES . '/fbview/section.html'); +// } + + // Possible meeting times. $optimal->setAttribute('ORGANIZER', _("All Attendees")); $blocks = $this->_getBlocks($optimal, @@ -167,6 +214,17 @@ class Kronolith_FreeBusy_View { $template->set('blocks', $blocks); $rows .= $template->fetch(KRONOLITH_TEMPLATES . '/fbview/row.html'); + // Possible meeting times. +// $resource->setAttribute('ORGANIZER', _("Required Attendees")); +// $blocks = $this->_getBlocks($required, +// $required->getFreePeriods($this->_start->timestamp(), $this->_end->timestamp()), +// 'meetingblock.html', _("Required Attendees")); +// +// $template = new Horde_Template(); +// $template->set('name', _("Required Attendees")); +// $template->set('blocks', $blocks); +// $rows .= $template->fetch(KRONOLITH_TEMPLATES . '/fbview/row.html'); + // Reset locale. setlocale(LC_NUMERIC, $lc); diff --git a/kronolith/lib/Kronolith.php b/kronolith/lib/Kronolith.php index b0b29039d..16978448b 100644 --- a/kronolith/lib/Kronolith.php +++ b/kronolith/lib/Kronolith.php @@ -2087,13 +2087,39 @@ class Kronolith static public function checkResources($event) { foreach ($event->getResources() as $id => $resource) { + + /* Get the resource */ $r = Kronolith::getDriver('Resource')->getResource($id); - if ($r->isFree($event)) { + + /* Determine if we have to calculate, or just auto-reply */ + $type = $r->getResponseType(); + switch($type) { + case Kronolith_Resource::RESPONSETYPE_ALWAYS_ACCEPT: $r->addEvent($event); $event->addResource($r, Kronolith::RESPONSE_ACCEPTED); - } else { + break; + case Kronolith_Resource::RESPONSETYPE_AUTO: + if ($r->isFree($event)) { + $r->addEvent($event); + $event->addResource($r, Kronolith::RESPONSE_ACCEPTED); + } else { + $event->addResource($r, Kronolith::RESPONSE_DECLINED); + } + break; + + case Kronolith_Resource::RESPONSETYPE_ALWAYS_DECLINE: $event->addResource($r, Kronolith::RESPONSE_DECLINED); + break; + + case Kronolith_Resource::RESPONSETYPE_NONE: + $event->addResource($r, Kronolith::RESPONSE_NONE); + break; + + case Kronolith_Resource::RESPONSETYPE_MANUAL: + //??? + break; } + } } diff --git a/kronolith/lib/Resource.php b/kronolith/lib/Resource.php new file mode 100644 index 000000000..356964e89 --- /dev/null +++ b/kronolith/lib/Resource.php @@ -0,0 +1,22 @@ +_params[$property] = $value; - } + //} } /** @@ -167,4 +170,10 @@ abstract class Kronolith_Resource_Base */ abstract public function setId($id); + /** + * Get ResponseType for this resource. + * @return unknown_type + */ + abstract public function getResponseType(); + } \ No newline at end of file diff --git a/kronolith/lib/Resource/Single.php b/kronolith/lib/Resource/Single.php index c8e181819..f686b4ad6 100644 --- a/kronolith/lib/Resource/Single.php +++ b/kronolith/lib/Resource/Single.php @@ -36,6 +36,7 @@ class Kronolith_Resource_Single extends Kronolith_Resource_Base /* Check for conflicts, ignoring the conflict if it's for the * same event that is passed. */ $uid = $event->getUID(); + $conflicts = 0; foreach ($busy as $events) { foreach ($events as $e) { if (!($e->hasStatus(Kronolith::STATUS_CANCELLED) || @@ -44,7 +45,12 @@ class Kronolith_Resource_Single extends Kronolith_Resource_Base if (!($e->start->compareDateTime($event->end) >= 1 || $e->end->compareDateTime($event->start) <= -1)) { - return false; + + /* Conflict, but check to see if we are allowed mulitiple */ + if (++$conflicts >= $this->get('max_reservations')) { + return false; + } + } } } @@ -64,7 +70,7 @@ class Kronolith_Resource_Single extends Kronolith_Resource_Base public function addEvent($event) { /* Get a driver for this resource's calendar */ - $driver = Kronolith::getDriver('Resource', $this->get('calendar')); + $driver = $this->getDriver(); /* Make sure it's not already attached. */ $uid = $event->getUID(); @@ -123,4 +129,9 @@ class Kronolith_Resource_Single extends Kronolith_Resource_Base } } + public function getResponseType() + { + return $this->get('response_type'); + } + } \ No newline at end of file diff --git a/kronolith/resources/edit.php b/kronolith/resources/edit.php index 37306c83c..8db003ba2 100644 --- a/kronolith/resources/edit.php +++ b/kronolith/resources/edit.php @@ -53,6 +53,8 @@ if ($form->validate($vars)) { $vars->set('name', $resource->get('name')); $vars->set('description', $resource->get('description')); $vars->set('category', $resource->get('category')); +$vars->set('maxreservations', $resource->get('max_reservations')); +$vars->set('responsetype', $resource->get('response_type')); $title = $form->getTitle(); require KRONOLITH_TEMPLATES . '/common-header.inc'; diff --git a/kronolith/scripts/upgrades/2009-08-17_add_resources.sql b/kronolith/scripts/upgrades/2009-08-17_add_resources.sql index bc228525d..a66d24aad 100644 --- a/kronolith/scripts/upgrades/2009-08-17_add_resources.sql +++ b/kronolith/scripts/upgrades/2009-08-17_add_resources.sql @@ -6,6 +6,8 @@ CREATE TABLE kronolith_resources ( resource_calendar VARCHAR(255), resource_description TEXT, resource_category VARCHAR(255), + resource_response_type INT DEFAULT 0, + resource_max_reservations INT DEFAULT 1, PRIMARY KEY (resource_id) ); diff --git a/kronolith/templates/attendees/attendees.inc b/kronolith/templates/attendees/attendees.inc index 29a2490e7..1ccc2ac3a 100644 --- a/kronolith/templates/attendees/attendees.inc +++ b/kronolith/templates/attendees/attendees.inc @@ -81,7 +81,8 @@ function switchDateView(view, date)   - + + @@ -91,17 +92,21 @@ function switchDateView(view, date) getImageDir('horde')) ?> + + + + - + -- 2.11.0