Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.
   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  }