/** * Code to output a standardised report filtering panel. * * Filters can be saved and loaded by each user. Additionally, filters can define permissions to a certain task, e.g. they can be used to define the * context within which someone can verify. In this case they provide the "outer limit" of the available records. * Requires a [map] control on the page. If you don't want a map, the current option is to include one anyway and use css to hide the #map-container div. * * @param array $readAuth Pass read authorisation tokens. * @param array $options Options array with the following possibilities: * sharing - define the record sharing task that is being filtered against. Options are reporting (default), peer_review, verification, moderation, data_flow. * context_id - can also be passed as URL parameter. Force the initial selection of a particular context (a record which has defines_permissions=true in the * filters table. Set to "default" to select their profile verification settings when sharing=verification. * filter_id - can also be passed as URL parameter. Force the initial selection of a particular filter record in the filters table. * filterTypes - allows control of the list of filter panels available, e.g. to turn one off. Associative array keyed by category * so that the filter panels can be grouped (use a blank key if not required). The array values are an array of or strings with a comma separated list * of the filter types to included in the category - options are what, where, when, who, quality, source. * filter-#name# - set the initial value of a report filter parameter #name#. * allowLoad - set to false to disable the load bar at the top of the panel. * allowSave - set to false to disable the save bar at the foot of the panel. * presets - provide an array of preset filters to provide in the filters drop down. Choose from my-records, my-groups (uses * your list of taxon groups in the user account), my-locality (uses your recording locality from the user account), * my-groups-locality (uses taxon groups and recording locality from the user account), my-queried-records, queried-records, * answered-records, accepted-records, not-accepted-records. * @param integer $website_id The current website's warehouse ID. * @param string $hiddenStuff Output parameter which will contain the hidden popup HTML that will be shown * using fancybox during filter editing. Should be appended AFTER any form element on the page as nested forms are not allowed. * @return string HTML for the report filter panel */ function report_filter_panel($readAuth, $options, $website_id, &$hiddenStuff) { if (function_exists('iform_load_helpers')) { iform_load_helpers(array('report_helper')); } else { //When running on warehouse we don't have iform_load_helpers require_once DOCROOT . 'client_helpers/report_helper.php'; } if (!empty($_POST['filter:sharing'])) { $options['sharing'] = $_POST['filter:sharing']; } $options = array_merge(array('sharing' => 'reporting', 'admin' => false, 'adminCanSetSharingTo' => array('R' => 'reporting', 'V' => 'verification'), 'allowLoad' => true, 'allowSave' => true, 'redirect_on_success' => '', 'presets' => array('my-records', 'my-queried-records', 'my-queried-or-not-accepted-records', 'my-not-reviewed-records', 'my-accepted-records', 'my-groups', 'my-locality', 'my-groups-locality'), 'entity' => 'occurrence'), $options); // Introduce some extra quick filters useful for verifiers. if ($options['sharing'] === 'verification') { $options['presets'] = array_merge(array('queried-records', 'answered-records', 'accepted-records', 'not-accepted-records'), $options['presets']); } if ($options['entity'] === 'sample') { unset($options['presets']['my-groups']); unset($options['presets']['my-groups-locality']); } //If in the warehouse we don't need to worry about the iform master list. if (function_exists('variable_get')) { $options = array_merge(array('taxon_list_id' => variable_get('iform_master_checklist_id', 0)), $options); } $options['sharing'] = report_filters_sharing_code_to_full_term($options['sharing']); $options['sharingCode'] = report_filters_full_term_to_sharing_code($options['sharing']); if (!preg_match('/^(reporting|peer_review|verification|data_flow|moderation)$/', $options['sharing'])) { return 'The @sharing option must be one of reporting, peer_review, verification, data_flow or moderation (currently ' . $options['sharing'] . ').'; } report_helper::add_resource('reportfilters'); report_helper::add_resource('validation'); report_helper::add_resource('fancybox'); if (function_exists('hostsite_add_library')) { hostsite_add_library('collapse'); } $filterData = report_filters_load_existing($readAuth, $options['sharingCode']); $existing = ''; $contexts = ''; // add some preset filters in //If in the warehouse we don't need to worry about user specific preferences when setting up milestones. if (function_exists('hostsite_get_user_field')) { foreach ($options['presets'] as $preset) { $title = false; switch ($preset) { case 'my-records': if (hostsite_get_user_field('id')) { $title = lang::get('My records'); } break; case 'my-queried-records': if (hostsite_get_user_field('id')) { $title = lang::get('My queried records'); } break; case 'my-queried-or-not-accepted-records': if (hostsite_get_user_field('id')) { $title = lang::get('My not accepted or queried records'); } break; case 'my-not-reviewed-records': if (hostsite_get_user_field('id')) { $title = lang::get('My not reviewed records'); } break; case 'my-accepted-records': if (hostsite_get_user_field('id')) { $title = lang::get('My accepted records'); } break; case 'my-groups': if (hostsite_get_user_field('taxon_groups', false, true)) { $title = lang::get('Records in species groups I like to record'); } break; case 'my-locality': if (hostsite_get_user_field('location')) { $title = lang::get('Records in the locality I generally record in'); } break; case 'my-groups-locality': if (hostsite_get_user_field('taxon_groups', false, true) && hostsite_get_user_field('location')) { $title = lang::get('Records of my species groups in my locality'); } break; case 'queried-records': $title = lang::get('Queried records'); break; case 'answered-records': $title = lang::get('Records with answers'); break; case 'accepted-records': $title = lang::get('Accepted records'); break; case 'not-accepted-records': $title = lang::get('Not accepted records'); break; default: throw new exception("Unsupported preset {$preset} for the filter panel"); } if ($title) { $presetFilter = array('id' => $preset, 'title' => $title, 'defines_permissions' => 'f'); $filterData[] = $presetFilter; } } if (count($options['presets'])) { if ($groups = hostsite_get_user_field('taxon_groups', false, true)) { data_entry_helper::$javascript .= "indiciaData.userPrefsTaxonGroups='" . implode(',', $groups) . "';\n"; } if ($location = hostsite_get_user_field('location')) { data_entry_helper::$javascript .= "indiciaData.userPrefsLocation=" . $location . ";\n"; } } $contextDefs = array(); if ($options['sharing'] === 'verification') { // apply legacy verification settings from their profile $location_id = hostsite_get_user_field('location_expertise'); $taxon_group_ids = hostsite_get_user_field('taxon_groups_expertise', false, true); $survey_ids = hostsite_get_user_field('surveys_expertise', false, true); if ($location_id || $taxon_group_ids || $survey_ids) { $selected = !empty($options['context_id']) && $options['context_id'] === 'default' ? 'selected="selected" ' : ''; $contexts .= "<option value=\"default\" {$selected}>" . lang::get('My verification records') . "</option>"; $def = array(); if ($location_id) { // user profile geographic limits should always be based on an indexed location. $def['indexed_location_id'] = $location_id; } if ($taxon_group_ids) { $def['taxon_group_list'] = implode(',', $taxon_group_ids); $def['taxon_group_names'] = array(); $groups = data_entry_helper::get_population_data(array('table' => 'taxon_group', 'extraParams' => $readAuth + array('id' => $taxon_group_ids))); foreach ($groups as $group) { $def['taxon_group_names'][$group['id']] = $group['title']; } } if ($survey_ids) { $def['survey_list'] = implode(',', array_filter($survey_ids)); } $contextDefs['default'] = $def; } } } if (!empty($_GET['context_id'])) { $options['context_id'] = $_GET['context_id']; } if (!empty($_GET['filter_id'])) { $options['filter_id'] = $_GET['filter_id']; } if (!empty($_GET['filters_user_id'])) { $options['filters_user_id'] = $_GET['filters_user_id']; } foreach ($filterData as $filter) { if ($filter['defines_permissions'] === 't') { $selected = !empty($options['context_id']) && $options['context_id'] == $filter['id'] ? 'selected="selected" ' : ''; $contexts .= "<option value=\"{$filter['id']}\" {$selected}>{$filter['title']}</option>"; $contextDefs[$filter['id']] = json_decode($filter['definition']); } else { $selected = !empty($options['filter_id']) && $options['filter_id'] == $filter['id'] ? 'selected="selected" ' : ''; $existing .= "<option value=\"{$filter['id']}\" {$selected}>{$filter['title']}</option>"; } } $r = '<div id="standard-params" class="ui-widget">'; if ($options['allowSave'] && $options['admin']) { if (empty($_GET['filters_user_id'])) { // new filter to create, so sharing type can be edited $reload = data_entry_helper::get_reload_link_parts(); $reloadPath = $reload['path']; if (count($reload['params'])) { $reloadPath .= '?' . data_entry_helper::array_to_query_string($reload['params']); } $r .= "<form action=\"{$reloadPath}\" method=\"post\" >"; $r .= data_entry_helper::select(array('label' => lang::get('Select filter type'), 'fieldname' => 'filter:sharing', 'lookupValues' => $options['adminCanSetSharingTo'], 'afterControl' => '<input type="submit" value="Go"/>', 'default' => $options['sharingCode'])); $r .= '</form>'; } else { // existing filter to edit, type is therefore fixed. JS will fill these values in. $r .= '<p>' . lang::get('This filter is for <span id="sharing-type-label"></span>.') . '</p>'; $r .= data_entry_helper::hidden_text(array('fieldname' => 'filter:sharing')); } } if ($options['allowLoad']) { $r .= '<div class="header ui-toolbar ui-widget-header ui-helper-clearfix"><div><span id="active-filter-label">' . lang::get('New report') . '</span></div><span class="changed" style="display:none" title="This filter has been changed">*</span>'; $r .= '<div>'; if ($contexts) { data_entry_helper::$javascript .= "indiciaData.filterContextDefs = " . json_encode($contextDefs) . ";\n"; if (count($contextDefs) > 1) { $r .= '<label for="context-filter">' . lang::get('Context:') . "</label><select id=\"context-filter\">{$contexts}</select>"; } else { $keys = array_keys($contextDefs); $r .= '<input type="hidden" id="context-filter" value="' . $keys[0] . '" />'; } } $r .= '<label for="select-filter">' . lang::get('Filter:') . '</label><select id="select-filter"><option value="" selected="selected">' . lang::get('Select filter') . "...</option>{$existing}</select>"; $r .= '<button type="button" id="filter-apply">' . lang::get('Apply') . '</button>'; $r .= '<button type="button" id="filter-reset" class="disabled">' . lang::get('Reset') . '</button>'; $r .= '<button type="button" id="filter-build">' . lang::get('Create a filter') . '</button></div>'; $r .= '</div>'; $r .= '<div id="filter-details" style="display: none">'; $r .= '<img src="' . data_entry_helper::$images_path . 'nuvola/close-22px.png" width="22" height="22" alt="Close filter builder" title="Close filter builder" class="button" id="filter-done"/>' . "\n"; } else { $r .= '<div id="filter-details">'; if (!empty($options['filter_id'])) { $r .= "<input type=\"hidden\" id=\"select-filter\" value=\"{$options['filter_id']}\"/>"; } elseif (!empty($options['filters_user_id'])) { $r .= "<input type=\"hidden\" id=\"select-filters-user\" value=\"{$options['filters_user_id']}\"/>"; } } $r .= '<div id="filter-panes">'; if ($options['entity'] === 'occurrence') { $filters = array('filter_what' => new filter_what(), 'filter_where' => new filter_where(), 'filter_when' => new filter_when(), 'filter_who' => new filter_who(), 'filter_occurrence_id' => new filter_occurrence_id(), 'filter_quality' => new filter_quality(), 'filter_source' => new filter_source()); } elseif ($options['entity'] === 'sample') { $filters = array('filter_where' => new filter_where(), 'filter_when' => new filter_when(), 'filter_who' => new filter_who(), 'filter_sample_id' => new filter_sample_id(), 'filter_quality' => new filter_quality_sample(), 'filter_source' => new filter_source()); } if (!empty($options['filterTypes'])) { $filterModules = array(); foreach ($options['filterTypes'] as $category => $list) { // $list can be an array or comma separated list if (is_array($list)) { $list = implode(',', $list); } $paneNames = 'filter_' . str_replace(',', ',filter_', $list); $paneList = explode(',', $paneNames); $filterModules[$category] = array_intersect_key($filters, array_fill_keys($paneList, 1)); } } else { $filterModules = array('' => $filters); } foreach ($filterModules as $category => $list) { if ($category) { $r .= '<fieldset class="collapsible collapsed">' . '<legend>' . '<span class="fieldset-legend">' . $category . '</span>' . '</legend>' . '<div class="fieldset-wrapper">'; } foreach ($list as $moduleName => $module) { $r .= "<div class=\"pane\" id=\"pane-{$moduleName}\"><a class=\"fb-filter-link\" href=\"#controls-{$moduleName}\"><span class=\"pane-title\">" . $module->get_title() . '</span>'; $r .= '<span class="filter-desc"></span></a>'; $r .= "</div>"; } if ($category) { $r .= '</div></fieldset>'; } } $r .= '</div>'; // filter panes $r .= '<div class="toolbar">'; if ($options['allowSave']) { $r .= '<label for="filter:title">' . lang::get('Save filter as') . ':</label> <input id="filter:title" class="control-width-5"/>'; if ($options['admin']) { $r .= '<br/>'; if (empty($options['adminCanSetSharingTo'])) { throw new exception('Report standard params panel in admin mode so adminCanSetSharingTo option must be populated.'); } $r .= data_entry_helper::autocomplete(array('label' => 'For who?', 'fieldname' => 'filters_user:user_id', 'table' => 'user', 'valueField' => 'id', 'captionField' => 'person_name', 'formatFunction' => "function(item) { return item.person_name + ' (' + item.email_address + ')'; }", 'extraParams' => $readAuth + array('view' => 'detail'), 'class' => 'control-width-5')); $r .= data_entry_helper::textarea(array('label' => 'Description', 'fieldname' => 'filter:description')); } $r .= '<img src="' . data_entry_helper::$images_path . 'nuvola/save-22px.png" width="22" height="22" alt="Save filter" title="Save filter" class="button" id="filter-save"/>'; $r .= '<img src="' . data_entry_helper::$images_path . 'trash-22px.png" width="22" height="22" alt="Bin this filter" title="Bin this filter" class="button disabled" id="filter-delete"/>'; } $r .= '</div></div>'; // toolbar + clearfix if (!empty($options['filters_user_id'])) { // if we are preloading based on a filter user ID, we need to get the information now so that the sharing mode can be known // when loading controls $fu = data_entry_helper::get_population_data(array('table' => 'filters_user', 'extraParams' => $readAuth + array('id' => $options['filters_user_id']), 'caching' => false)); if (count($fu) !== 1) { throw new exception('Could not find filter user record'); } $options['sharing'] = report_filters_sharing_code_to_full_term($fu[0]['filter_sharing']); } report_helper::$javascript .= "indiciaData.lang={pleaseSelect:\"" . lang::get('Please select') . "\"};\n"; // create the hidden panels required to populate the popups for setting each type of filter up. $hiddenStuff = ''; foreach ($filterModules as $category => $list) { foreach ($list as $moduleName => $module) { $hiddenStuff .= "<div style=\"display: none\"><div class=\"filter-popup\" id=\"controls-{$moduleName}\"><form action=\"#\" class=\"filter-controls\"><fieldset>" . $module->get_controls($readAuth, $options) . '<button class="fb-close" type="button">Cancel</button>' . '<button class="fb-apply" type="submit">Apply</button></fieldset></form></div></div>'; $shortName = str_replace('filter_', '', $moduleName); report_helper::$javascript .= "indiciaData.lang.NoDescription{$shortName}='" . lang::get('Click to Filter ' . ucfirst($shortName)) . "';\n"; } } $r .= '</div>'; report_helper::$js_read_tokens = $readAuth; report_helper::$javascript .= "indiciaData.lang.CreateAFilter='" . lang::get('Create a filter') . "';\n"; report_helper::$javascript .= "indiciaData.lang.ModifyFilter='" . lang::get('Modify filter') . "';\n"; report_helper::$javascript .= "indiciaData.lang.FilterReport='" . lang::get('New report') . "';\n"; report_helper::$javascript .= "indiciaData.lang.FilterSaved='" . lang::get('The filter has been saved') . "';\n"; report_helper::$javascript .= "indiciaData.lang.FilterDeleted='" . lang::get('The filter has been deleted') . "';\n"; report_helper::$javascript .= "indiciaData.lang.ConfirmFilterChangedLoad='" . lang::get('Do you want to load the selected filter and lose your current changes?') . "';\n"; report_helper::$javascript .= "indiciaData.lang.FilterExistsOverwrite='" . lang::get('A filter with that name already exists. Would you like to overwrite it?') . "';\n"; report_helper::$javascript .= "indiciaData.lang.AutochecksFailed='" . lang::get('Automated checks failed') . "';\n"; report_helper::$javascript .= "indiciaData.lang.AutochecksPassed='" . lang::get('Automated checks passed') . "';\n"; report_helper::$javascript .= "indiciaData.lang.HasPhotos='" . lang::get('Records which have photos') . "';\n"; report_helper::$javascript .= "indiciaData.lang.ConfirmFilterDelete='" . lang::get('Are you sure you want to permanently delete the {title} filter?') . "';\n"; report_helper::$javascript .= "indiciaData.lang.MyRecords='" . lang::get('My records only') . "';\n"; if (function_exists('iform_ajaxproxy_url')) { report_helper::$javascript .= "indiciaData.filterPostUrl='" . iform_ajaxproxy_url(null, 'filter') . "';\n"; report_helper::$javascript .= "indiciaData.filterAndUserPostUrl='" . iform_ajaxproxy_url(null, 'filter_and_user') . "';\n"; } report_helper::$javascript .= "indiciaData.filterSharing='" . strtoupper(substr($options['sharing'], 0, 1)) . "';\n"; if (function_exists('hostsite_get_user_field')) { report_helper::$javascript .= "indiciaData.user_id='" . hostsite_get_user_field('indicia_user_id') . "';\n"; } else { report_helper::$javascript .= "indiciaData.user_id='" . $_SESSION['auth_user']->id . "';\n"; } if (!empty($website_id)) { report_helper::$javascript .= "indiciaData.website_id=" . $website_id . ";\n"; } report_helper::$javascript .= "indiciaData.redirectOnSuccess='{$options['redirect_on_success']}';\n"; // load up the filter, BEFORE any AJAX load of the grid code. First fetch any URL param overrides. $getParams = array(); $optionParams = array(); foreach ($_GET as $key => $value) { if (substr($key, 0, 7) === 'filter-') { $getParams[substr($key, 7)] = $value; } } foreach ($options as $key => $value) { if (substr($key, 0, 7) === 'filter-') { $optionParams[substr($key, 7)] = $value; } } $allParams = array_merge($optionParams, $getParams); if (!empty($allParams)) { $allParams = json_encode($allParams); report_helper::$onload_javascript .= "var params = {$allParams};\n"; report_helper::$onload_javascript .= "indiciaData.filter.def=\$.extend(indiciaData.filter.def, params);\n"; report_helper::$onload_javascript .= "indiciaData.filter.orig=\$.extend({}, params);\n"; } $getParams = empty($getParams) ? '{}' : json_encode($getParams); if (!empty($options['filters_user_id'])) { report_helper::$onload_javascript .= "loadFilterUser(" . json_encode($fu[0]) . ", {$getParams});\n"; } else { report_helper::$onload_javascript .= "if (\$('#select-filter').val()) {\n" . " loadFilter(\$('#select-filter').val(), {$getParams});\n" . "} else {\n" . " applyFilterToReports(false);\n" . "}\n"; } return $r; }