1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Provides {@link tool_policy\output\acceptances_filter} class. 19 * 20 * @package tool_policy 21 * @category output 22 * @copyright 2018 Marina Glancy 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 namespace tool_policy\output; 27 28 use tool_policy\api; 29 use tool_policy\policy_version; 30 31 defined('MOODLE_INTERNAL') || die(); 32 33 /** 34 * Implements the widget allowing to filter the acceptance records. 35 * 36 * @copyright 2018 Marina Glancy 37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 38 */ 39 class acceptances_filter implements \templatable, \renderable { 40 41 /** @var array $filtersapplied The list of selected filter options. */ 42 protected $filtersapplied; 43 44 /** @var string $searchstring */ 45 protected $searchstrings; 46 47 /** @var array list of available versions */ 48 protected $versions = null; 49 50 /** @var array list of available roles for the filter */ 51 protected $roles; 52 53 /** @var array cached list of all available policies, to retrieve use {@link self::get_avaliable_policies()} */ 54 protected $policies; 55 56 /** @var int */ 57 const FILTER_SEARCH_STRING = 0; 58 59 /** @var int */ 60 const FILTER_POLICYID = 1; 61 62 /** @var int */ 63 const FILTER_VERSIONID = 2; 64 65 /** @var int */ 66 const FILTER_CAPABILITY_ACCEPT = 3; 67 68 /** @var int */ 69 const FILTER_STATUS = 4; 70 71 /** @var int */ 72 const FILTER_ROLE = 5; 73 74 /** 75 * Constructor. 76 * 77 * @param array $policyid Specified policy id 78 * @param array $versionid Specified version id 79 * @param array $filtersapplied The list of selected filter option values. 80 */ 81 public function __construct($policyid, $versionid, $filtersapplied) { 82 $this->filtersapplied = []; 83 $this->roles = get_assignable_roles(\context_system::instance()); 84 if ($policyid) { 85 $this->add_filter(self::FILTER_POLICYID, $policyid); 86 } 87 if ($versionid) { 88 $this->add_filter(self::FILTER_VERSIONID, $versionid); 89 } 90 foreach ($filtersapplied as $filter) { 91 if (preg_match('/^([1-9]\d*):(\d+)$/', $filter, $parts)) { 92 // This is a pre-set filter (policy, version, status, etc.). 93 $allowmultiple = false; 94 switch ((int)$parts[1]) { 95 case self::FILTER_POLICYID: 96 case self::FILTER_VERSIONID: 97 case self::FILTER_CAPABILITY_ACCEPT: 98 case self::FILTER_STATUS: 99 $value = (int)$parts[2]; 100 break; 101 case self::FILTER_ROLE: 102 $value = (int)$parts[2]; 103 if (!array_key_exists($value, $this->roles)) { 104 continue 2; 105 } 106 $allowmultiple = true; 107 break; 108 default: 109 // Unrecognised filter. 110 continue 2; 111 } 112 113 $this->add_filter((int)$parts[1], $value, $allowmultiple); 114 } else if (trim($filter) !== '') { 115 // This is a search string. 116 $this->add_filter(self::FILTER_SEARCH_STRING, trim($filter), true); 117 } 118 } 119 } 120 121 /** 122 * Adds an applied filter 123 * 124 * @param mixed $key 125 * @param mixed $value 126 * @param bool $allowmultiple 127 */ 128 protected function add_filter($key, $value, $allowmultiple = false) { 129 if ($allowmultiple || empty($this->get_filter_values($key))) { 130 $this->filtersapplied[] = [$key, $value]; 131 } 132 } 133 134 /** 135 * Is there a filter by policy 136 * 137 * @return null|int null if there is no filter, otherwise the policy id 138 */ 139 public function get_policy_id_filter() { 140 return $this->get_filter_value(self::FILTER_POLICYID); 141 } 142 143 /** 144 * Is there a filter by version 145 * 146 * @return null|int null if there is no filter, otherwise the version id 147 */ 148 public function get_version_id_filter() { 149 return $this->get_filter_value(self::FILTER_VERSIONID); 150 } 151 152 /** 153 * Are there filters by search strings 154 * 155 * @return string[] array of string filters 156 */ 157 public function get_search_strings() { 158 return $this->get_filter_values(self::FILTER_SEARCH_STRING); 159 } 160 161 /** 162 * Is there a filter by status (agreed/not agreed). 163 * 164 * @return null|0|1 null if there is no filter, 0/1 if there is a filter by status 165 */ 166 public function get_status_filter() { 167 return $this->get_filter_value(self::FILTER_STATUS); 168 } 169 170 /** 171 * Are there filters by role 172 * 173 * @return array list of role ids 174 */ 175 public function get_role_filters() { 176 return $this->get_filter_values(self::FILTER_ROLE); 177 } 178 179 /** 180 * Is there a filter by capability (can accept/cannot accept). 181 * 182 * @return null|0|1 null if there is no filter, 0/1 if there is a filter by capability 183 */ 184 public function get_capability_accept_filter() { 185 return $this->get_filter_value(self::FILTER_CAPABILITY_ACCEPT); 186 } 187 188 /** 189 * Get all values of the applied filter 190 * 191 * @param string $filtername 192 * @return array 193 */ 194 protected function get_filter_values($filtername) { 195 $values = []; 196 foreach ($this->filtersapplied as $filter) { 197 if ($filter[0] == $filtername) { 198 $values[] = $filter[1]; 199 } 200 } 201 return $values; 202 } 203 204 /** 205 * Get one value of the applied filter 206 * 207 * @param string $filtername 208 * @param string $default 209 * @return mixed 210 */ 211 protected function get_filter_value($filtername, $default = null) { 212 if ($values = $this->get_filter_values($filtername)) { 213 $value = reset($values); 214 return $value; 215 } 216 return $default; 217 } 218 219 /** 220 * Returns all policies that have versions with possible acceptances (excl. drafts and guest-only versions) 221 * 222 * @return array|null 223 */ 224 public function get_avaliable_policies() { 225 if ($this->policies === null) { 226 $this->policies = []; 227 foreach (\tool_policy\api::list_policies() as $policy) { 228 // Make a list of all versions that are not draft and are not guest-only. 229 $policy->versions = []; 230 if ($policy->currentversion && $policy->currentversion->audience != policy_version::AUDIENCE_GUESTS) { 231 $policy->versions[$policy->currentversion->id] = $policy->currentversion; 232 } else { 233 $policy->currentversion = null; 234 } 235 foreach ($policy->archivedversions as $version) { 236 if ($version->audience != policy_version::AUDIENCE_GUESTS) { 237 $policy->versions[$version->id] = $version; 238 } 239 } 240 if ($policy->versions) { 241 $this->policies[$policy->id] = $policy; 242 } 243 } 244 } 245 return $this->policies; 246 } 247 248 /** 249 * List of policies that match current filters 250 * 251 * @return array of versions to display indexed by versionid 252 */ 253 public function get_versions() { 254 if ($this->versions === null) { 255 $policyid = $this->get_policy_id_filter(); 256 $versionid = $this->get_version_id_filter(); 257 $this->versions = []; 258 foreach ($this->get_avaliable_policies() as $policy) { 259 if ($policyid && $policy->id != $policyid) { 260 continue; 261 } 262 if ($versionid) { 263 if (array_key_exists($versionid, $policy->versions)) { 264 $this->versions[$versionid] = $policy->versions[$versionid]; 265 break; // No need to keep searching. 266 } 267 } else if ($policy->currentversion) { 268 $this->versions[$policy->currentversion->id] = $policy->currentversion; 269 } 270 } 271 } 272 return $this->versions; 273 } 274 275 /** 276 * Validates if policyid and versionid are valid (if specified) 277 */ 278 public function validate_ids() { 279 $policyid = $this->get_policy_id_filter(); 280 $versionid = $this->get_version_id_filter(); 281 if ($policyid || $versionid) { 282 $found = array_filter($this->get_avaliable_policies(), function($policy) use ($policyid, $versionid) { 283 return (!$policyid || $policy->id == $policyid) && 284 (!$versionid || array_key_exists($versionid, $policy->versions)); 285 }); 286 if (!$found) { 287 // Throw exception that policy/version is not found. 288 throw new \moodle_exception('errorpolicyversionnotfound', 'tool_policy'); 289 } 290 } 291 } 292 293 /** 294 * If policyid or versionid is specified return one single policy that needs to be shown 295 * 296 * If neither policyid nor versionid is specified this method returns null. 297 * 298 * When versionid is specified this method will always return an object (this is validated in {@link self::validate_ids()} 299 * When only policyid is specified this method either returns the current version of the policy or null if there is 300 * no current version (for example, it is an old policy). 301 * 302 * @return mixed|null 303 */ 304 public function get_single_version() { 305 if ($this->get_version_id_filter() || $this->get_policy_id_filter()) { 306 $versions = $this->get_versions(); 307 return reset($versions); 308 } 309 return null; 310 } 311 312 /** 313 * Returns URL of the acceptances page with all current filters applied 314 * 315 * @return \moodle_url 316 */ 317 public function get_url() { 318 $urlparams = []; 319 if ($policyid = $this->get_policy_id_filter()) { 320 $urlparams['policyid'] = $policyid; 321 } 322 if ($versionid = $this->get_version_id_filter()) { 323 $urlparams['versionid'] = $versionid; 324 } 325 $i = 0; 326 foreach ($this->filtersapplied as $filter) { 327 if ($filter[0] != self::FILTER_POLICYID && $filter[0] != self::FILTER_VERSIONID) { 328 if ($filter[0] == self::FILTER_SEARCH_STRING) { 329 $urlparams['unified-filters['.($i++).']'] = $filter[1]; 330 } else { 331 $urlparams['unified-filters['.($i++).']'] = join(':', $filter); 332 } 333 } 334 } 335 return new \moodle_url('/admin/tool/policy/acceptances.php', $urlparams); 336 } 337 338 /** 339 * Creates an option name for the smart select for the version 340 * 341 * @param \stdClass $version 342 * @return string 343 */ 344 protected function get_version_option_for_filter($version) { 345 if ($version->status == policy_version::STATUS_ACTIVE) { 346 $a = (object)[ 347 'name' => format_string($version->revision), 348 'status' => get_string('status'.policy_version::STATUS_ACTIVE, 'tool_policy'), 349 ]; 350 return get_string('filterrevisionstatus', 'tool_policy', $a); 351 } else { 352 return get_string('filterrevision', 'tool_policy', $version->revision); 353 } 354 } 355 356 /** 357 * Build list of filters available for this page 358 * 359 * @return array [$availablefilters, $selectedoptions] 360 */ 361 protected function build_available_filters() { 362 $selectedoptions = []; 363 $availablefilters = []; 364 365 $versionid = $this->get_version_id_filter(); 366 $policyid = $versionid ? $this->get_single_version()->policyid : $this->get_policy_id_filter(); 367 368 // Policies. 369 $policies = $this->get_avaliable_policies(); 370 if ($policyid) { 371 // If policy is selected, display only the current policy in the selector. 372 $selectedoptions[] = $key = self::FILTER_POLICYID . ':' . $policyid; 373 $version = $versionid ? $policies[$policyid]->versions[$versionid] : reset($policies[$policyid]->versions); 374 $availablefilters[$key] = get_string('filterpolicy', 'tool_policy', $version->name); 375 } else { 376 // If no policy/version is selected display the list of all policies. 377 foreach ($policies as $policy) { 378 $firstversion = reset($policy->versions); 379 $key = self::FILTER_POLICYID . ':' . $policy->id; 380 $availablefilters[$key] = get_string('filterpolicy', 'tool_policy', $firstversion->name); 381 } 382 } 383 384 // Versions. 385 if ($versionid) { 386 $singleversion = $this->get_single_version(); 387 $selectedoptions[] = $key = self::FILTER_VERSIONID . ':' . $singleversion->id; 388 $availablefilters[$key] = $this->get_version_option_for_filter($singleversion); 389 } else if ($policyid) { 390 foreach ($policies[$policyid]->versions as $version) { 391 $key = self::FILTER_VERSIONID . ':' . $version->id; 392 $availablefilters[$key] = $this->get_version_option_for_filter($version); 393 } 394 } 395 396 // Permissions. 397 $permissions = [ 398 self::FILTER_CAPABILITY_ACCEPT . ':1' => get_string('filtercapabilityyes', 'tool_policy'), 399 self::FILTER_CAPABILITY_ACCEPT . ':0' => get_string('filtercapabilityno', 'tool_policy'), 400 ]; 401 if (($currentpermission = $this->get_capability_accept_filter()) !== null) { 402 $selectedoptions[] = $key = self::FILTER_CAPABILITY_ACCEPT . ':' . $currentpermission; 403 $permissions = array_intersect_key($permissions, [$key => true]); 404 } 405 $availablefilters += $permissions; 406 407 // Status. 408 $statuses = [ 409 self::FILTER_STATUS.':2' => get_string('filterstatusdeclined', 'tool_policy'), 410 self::FILTER_STATUS.':1' => get_string('filterstatusyes', 'tool_policy'), 411 self::FILTER_STATUS.':0' => get_string('filterstatuspending', 'tool_policy'), 412 ]; 413 if (($currentstatus = $this->get_status_filter()) !== null) { 414 $selectedoptions[] = $key = self::FILTER_STATUS . ':' . $currentstatus; 415 $statuses = array_intersect_key($statuses, [$key => true]); 416 } 417 $availablefilters += $statuses; 418 419 // Roles. 420 $currentroles = $this->get_role_filters(); 421 foreach ($this->roles as $roleid => $rolename) { 422 $key = self::FILTER_ROLE . ':' . $roleid; 423 $availablefilters[$key] = get_string('filterrole', 'tool_policy', $rolename); 424 if (in_array($roleid, $currentroles)) { 425 $selectedoptions[] = $key; 426 } 427 } 428 429 // Search string. 430 foreach ($this->get_search_strings() as $str) { 431 $selectedoptions[] = $str; 432 $availablefilters[$str] = $str; 433 } 434 435 return [$availablefilters, $selectedoptions]; 436 } 437 438 /** 439 * Function to export the renderer data in a format that is suitable for a mustache template. 440 * 441 * @param renderer_base $output Used to do a final render of any components that need to be rendered for export. 442 * @return \stdClass|array 443 */ 444 public function export_for_template(\renderer_base $output) { 445 $data = new \stdClass(); 446 $data->action = (new \moodle_url('/admin/tool/policy/acceptances.php'))->out(false); 447 448 $data->filteroptions = []; 449 $originalfilteroptions = []; 450 list($avilablefilters, $selectedoptions) = $this->build_available_filters(); 451 foreach ($avilablefilters as $value => $label) { 452 $selected = in_array($value, $selectedoptions); 453 $filteroption = (object)[ 454 'value' => $value, 455 'label' => $label 456 ]; 457 $originalfilteroptions[] = $filteroption; 458 $filteroption->selected = $selected; 459 $data->filteroptions[] = $filteroption; 460 } 461 $data->originaloptionsjson = json_encode($originalfilteroptions); 462 return $data; 463 } 464 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body