From 0d37c8413f0a839e433d85abc8b91eab395ffefa Mon Sep 17 00:00:00 2001 From: nathangray Date: Tue, 19 Jul 2016 14:09:58 -0600 Subject: [PATCH] Add resource categories to owner / participant search results In the sidebox menu, resource categories are returned and selectable and will show events for all resources in that category. In the edit popup, if the category name matches the search string, all resources of that category are returned and user can select them as if they would have searched for the name of the resource. --- calendar/inc/class.calendar_bo.inc.php | 87 +++++++++++++++ ...ss.calendar_owner_etemplate_widget.inc.php | 12 ++- calendar/js/et2_widget_event.js | 26 ++++- resources/inc/class.resources_bo.inc.php | 101 +++++++++++++++++- resources/inc/class.resources_hooks.inc.php | 2 +- 5 files changed, 220 insertions(+), 8 deletions(-) diff --git a/calendar/inc/class.calendar_bo.inc.php b/calendar/inc/class.calendar_bo.inc.php index 1e07b3c92c..bc644eb398 100644 --- a/calendar/inc/class.calendar_bo.inc.php +++ b/calendar/inc/class.calendar_bo.inc.php @@ -327,6 +327,77 @@ class calendar_bo return $data; } + /** + * returns info about mailing lists as participants + * + * @param int|array $ids single mailing list ID or array of id's + * @return array + */ + static function mailing_lists($ids) + { + if(!is_array($ids)) + { + $ids = array($ids); + } + $data = array(); + + // Email list + $contacts_obj = new Api\Contacts(); + $bo = new calendar_bo(); + foreach($ids as $id) + { + $list = $contacts_obj->read_list((int)$id); + + $data[] = array( + 'res_id' => $id, + 'rights' => self::ACL_READ_FOR_PARTICIPANTS, + 'name' => $list['list_name'], + 'resources' => $bo->enum_mailing_list('l'.$id, false, false) + ); + } + + return $data; + } + + /** + * Enumerates the contacts in a contact list, and returns the list of contact IDs + * + * This is used to enable mailing lists as owner/participant + * + * @param string $id Mailing list participant ID, which is the mailing list + * ID prefixed with 'l' + * @param boolean $ignore_acl = false Flag to skip ACL checks + * @param boolean $use_freebusy =true should freebusy rights are taken into account, default true, can be set to false eg. for a search + * + * @return array + */ + public function enum_mailing_list($id, $ignore_acl= false, $use_freebusy = true) + { + $contact_list = array(); + $contacts = new Api\Contacts(); + if($contacts->check_list((int)substr($id,1), ACL::READ)) + { + $options = array('list' => substr($id,1)); + $lists = $contacts->search('',true,'','','',false,'AND',false,$options); + if(!$lists) + { + return $contact_list; + } + foreach($lists as &$contact) + { + $contact = 'c'.$contact['id']; + if ($ignore_acl || $this->check_perms(ACL::READ|self::ACL_READ_FOR_PARTICIPANTS|($use_freebusy?self::ACL_FREEBUSY:0),0,$contact)) + { + if ($contact && !in_array($contact,$contact_list)) // already added? + { + $contact_list[] = $contact; + } + } + } + } + return $contact_list; + } + /** * Add group-members as participants with status 'G' * @@ -378,6 +449,22 @@ class calendar_bo { if ($user && !in_array($user,$users)) // already added? { + // General expansion check + if (!is_numeric($user) && $this->resources[$user[0]]['info']) + { + $info = $this->resource_info($user); + if($info && $info['resources']) + { + foreach($info['resources'] as $_user) + { + if($_user && !in_array($_user, $users)) + { + $users[] = $_user; + } + } + continue; + } + } $users[] = $user; } } diff --git a/calendar/inc/class.calendar_owner_etemplate_widget.inc.php b/calendar/inc/class.calendar_owner_etemplate_widget.inc.php index babe7d62d9..0e2054c0cc 100644 --- a/calendar/inc/class.calendar_owner_etemplate_widget.inc.php +++ b/calendar/inc/class.calendar_owner_etemplate_widget.inc.php @@ -78,9 +78,14 @@ class calendar_owner_etemplate_widget extends Etemplate\Widget\Taglist foreach($value as &$owner) { $label = self::get_owner_label($owner); + $info = array(); if(!is_numeric($owner)) { $resource = $bo->resources[substr($owner, 0,1)]; + if($resource['info']) + { + $info = $bo->resource_info($owner); + } } else if (!in_array($owner, array_keys($accounts))) { @@ -90,7 +95,7 @@ class calendar_owner_etemplate_widget extends Etemplate\Widget\Taglist { continue; } - $sel_options[] = array('value' => $owner, 'label' => $label, 'app' => lang($resource['app'])); + $sel_options[] = array('value' => $owner, 'label' => $label, 'app' => lang($resource['app'])) + $info; } } @@ -135,6 +140,7 @@ class calendar_owner_etemplate_widget extends Etemplate\Widget\Taglist $bo = new calendar_bo(); $query = $_REQUEST['query']; + // Arbitrarily limited to 50 / resource $options = array('start' => 0, 'num_rows' => 50) + array_diff_key($_REQUEST, array_flip(array('menuaction','query'))); @@ -153,6 +159,10 @@ class calendar_owner_etemplate_widget extends Etemplate\Widget\Taglist $_results += Api\Accounts::link_query($query, $account_options); if (!empty($_REQUEST['checkgrants'])) $_results = array_intersect_key($_results, $GLOBALS['egw']->acl->get_grants('calendar')); } + else if ($data['app'] && $data['search']) + { + $_results = call_user_func_array($data['search'], array($query, $options)); + } else if ($data['app'] && Link::get_registry($data['app'], 'query')) { $_results = Link::query($data['app'], $query,$options); diff --git a/calendar/js/et2_widget_event.js b/calendar/js/et2_widget_event.js index 323f30eb4b..291b1c4ca4 100644 --- a/calendar/js/et2_widget_event.js +++ b/calendar/js/et2_widget_event.js @@ -918,15 +918,37 @@ et2_calendar_event.owner_check = function owner_check(event, parent, owner_too) { owner_too = app.calendar.state.status_filter === 'owner'; } + var options = false + if(app.calendar && app.calendar.sidebox_et2 && app.calendar.sidebox_et2.getWidgetById('owner')) + { + options = app.calendar.sidebox_et2.getWidgetById('owner').taglist.getSelection(); + } + else + { + options = this.getArrayMgr("sel_options").getRoot().getEntry('owner'); + } if(event.participants && parent.options.owner) { - var parent_owner = typeof parent.options.owner !== 'object' ? + var parent_owner = jQuery.extend([], typeof parent.options.owner !== 'object' ? [parent.options.owner] : - parent.options.owner; + parent.options.owner); owner_match = false; var length = parent_owner.length; for(var i = 0; i < length; i++ ) { + // Handle grouped resources like mailing lists, they won't match so + // we need the list - pull it from sidebox owner + if(isNaN(parent_owner[i]) && options && options.find) + { + var resource = options.find(function(element) {return element.id == parent_owner[i];}) || {}; + if(resource && resource.resources) + { + parent_owner.splice(i,1); + parent_owner = parent_owner.concat(resource.resources); + continue; + } + } + if (parseInt(parent_owner[i]) < 0) { // Add in groups, if we can get them (this is syncronous) diff --git a/resources/inc/class.resources_bo.inc.php b/resources/inc/class.resources_bo.inc.php index 83169666f8..08b47bd725 100755 --- a/resources/inc/class.resources_bo.inc.php +++ b/resources/inc/class.resources_bo.inc.php @@ -519,21 +519,113 @@ class resources_bo return $acc_list; } + /** + * Search for resources for calendar to select as participants + * + * Search and options match Link::query() + * + * Resources return actual resources as well as categories that match + * + * @param String $search - Search string + * @param Array $options - search options + * @see Link::query() + * + * @return Array List of ID => Title entries matching search string + */ + public static function calendar_search($search, $options) + { + // Resources + $list = Link::query('resources', $search, $options); + + // Categories + $bo = new resources_bo(); + $cats = $bo->acl->get_cats(Acl::READ); + foreach($cats as $cat_id => $cat) + { + if($cat && stripos($cat, $search) !== FALSE) + { + // Get resources for that category + $resources = $bo->get_resources_by_category($cat_id); + + // Edit dialog sends exec as an option, don't add categories + if(count($resources) && !$options['exec']) + { + $list['cat-'.$cat_id] = array( + 'label' => $cat, + 'resources' => $resources, + ); + } + else if ($resources && $options['exec']) + { + array_map( + function($id,$name) use (&$list) { $list[''+$id] = $name;}, + array_keys($resources), $resources + ); + } + } + } + + return $list; + } + + /** + * Get a list of resources (ID => name) matching a single category ID + * @param int $cat_id + * @return array() + */ + public function get_resources_by_category($cat_id) + { + $resources = array(); + $filter = array( + 'cat_id' => $cat_id, + //'accessory_of' => '-1' + 'deleted' => null + ); + $only_keys = 'res_id,name'; + $data = $this->so->search(array(),$only_keys,$order_by='name',$extra_cols='',$wildcard='%',$empty,$op='OR',$limit,$filter); + foreach($data as $resource) + { + $resources[$resource['res_id']] = $resource['name']; + } + + return $resources; + } + /** * returns info about resource for calender * @author Cornelius Weiss - * @param int|array $res_id single id or array $num => $res_id + * @param int|array|string $res_id single id, array $num => $res_id or + * 'cat-' for the whole category * @return array */ function get_calendar_info($res_id) { - //echo "

resources_bo::get_calendar_info(".print_r($res_id,true).")

\n"; + //error_log(__METHOD__ . "(".print_r($res_id,true).")"); + + // Resource category + if(is_string($res_id) && strpos($res_id, 'cat-') === 0) + { + $cat_id = (int)substr($res_id, 4); + if(!$this->acl->is_permitted($cat_id, Acl::READ)) + { + return array(); + } + return array( array( + 'name' => $this->acl->get_cat_name($cat_id), + 'rights' => $this->acl->get_permissions($cat_id), + 'resources' => array_map( + function($id) { return 'r'.$id;}, + array_keys($this->get_resources_by_category($cat_id)) + ) + )); + } + if(!is_array($res_id) && $res_id < 1) return; $data = $this->so->search(array('res_id' => $res_id),self::TITLE_COLS.',useable'); if (!is_array($data)) { - error_log(__METHOD__." No Calendar Data found for Resource with id $res_id"); + //error_log(__METHOD__." No Calendar Data found for Resource with id $res_id"); return array(); } foreach($data as $num => &$resource) @@ -611,10 +703,11 @@ class resources_bo { $filter['accessory_of'] = $options['accessory_of']; } + $list = array(); $data = $this->so->search($criteria,$only_keys,$order_by='name',$extra_cols='',$wildcard='%',$empty,$op='OR',$limit,$filter); // maybe we need to check disponibility of the searched resources in the calendar if $pattern ['exec'] contains some extra args $show_conflict=False; - if ($options['exec'] && $GLOBALS['egw_info']['preferences']['calendar']['defaultresource_sel'] !== 'resources') + if ($data && $options['exec'] && $GLOBALS['egw_info']['preferences']['calendar']['defaultresource_sel'] !== 'resources') { // we'll use a cache for resources info taken from database static $res_info_cache = array(); diff --git a/resources/inc/class.resources_hooks.inc.php b/resources/inc/class.resources_hooks.inc.php index 57f41d360d..3eb73def35 100644 --- a/resources/inc/class.resources_hooks.inc.php +++ b/resources/inc/class.resources_hooks.inc.php @@ -101,7 +101,7 @@ class resources_hooks function calendar_resources($args) { return array( - 'widget' => 'resources_select',// widget to use for the selection of resources + 'search' => 'resources_bo::calendar_search',// method to use for the selection of resources, otherwise Link system is used 'info' => 'resources.resources_bo.get_calendar_info',// info method, returns array with id, type & name for a given id 'max_quantity' => 'useable',// if set, key for max. quantity in array returned by info method 'new_status' => 'resources.resources_bo.get_calendar_new_status',// method returning the status for new items, else 'U' is used