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