Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.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   * Contains the class used for the displaying the expired contexts table.
  19   *
  20   * @package    tool_dataprivacy
  21   * @copyright  2018 Jun Pataleta <jun@moodle.com>
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  namespace tool_dataprivacy\output;
  25  defined('MOODLE_INTERNAL') || die();
  26  
  27  require_once($CFG->libdir . '/tablelib.php');
  28  
  29  use coding_exception;
  30  use context_helper;
  31  use dml_exception;
  32  use Exception;
  33  use html_writer;
  34  use pix_icon;
  35  use stdClass;
  36  use table_sql;
  37  use tool_dataprivacy\api;
  38  use tool_dataprivacy\expired_context;
  39  use tool_dataprivacy\external\purpose_exporter;
  40  use tool_dataprivacy\purpose;
  41  
  42  defined('MOODLE_INTERNAL') || die;
  43  
  44  /**
  45   * The class for displaying the expired contexts table.
  46   *
  47   * @copyright  2018 Jun Pataleta <jun@moodle.com>
  48   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  49   */
  50  class expired_contexts_table extends table_sql {
  51  
  52      /** @var int The context level acting as a filter for this table. */
  53      protected $contextlevel = null;
  54  
  55      /**
  56       * @var bool $selectall Has the user selected all users on the page? True by default.
  57       */
  58      protected $selectall = true;
  59  
  60      /** @var purpose[] Array of purposes by their id. */
  61      protected $purposes = [];
  62  
  63      /** @var purpose[] Map of context => purpose. */
  64      protected $purposemap = [];
  65  
  66      /** @var array List of roles. */
  67      protected $roles = [];
  68  
  69      /**
  70       * expired_contexts_table constructor.
  71       *
  72       * @param int|null $contextlevel
  73       * @throws coding_exception
  74       */
  75      public function __construct($contextlevel = null) {
  76          parent::__construct('expired-contexts-table');
  77  
  78          $this->contextlevel = $contextlevel;
  79  
  80          $columnheaders = [
  81              'name' => get_string('name'),
  82              'info' => get_string('info'),
  83              'purpose' => get_string('purpose', 'tool_dataprivacy'),
  84              'category' => get_string('category', 'tool_dataprivacy'),
  85              'retentionperiod' => get_string('retentionperiod', 'tool_dataprivacy'),
  86              'tobedeleted' => get_string('tobedeleted', 'tool_dataprivacy'),
  87              'timecreated' => get_string('expiry', 'tool_dataprivacy'),
  88          ];
  89          $checkboxattrs = [
  90              'title' => get_string('selectall'),
  91              'data-action' => 'selectall'
  92          ];
  93          $columnheaders['select'] = html_writer::checkbox('selectall', 1, true, null, $checkboxattrs);
  94  
  95          $this->define_columns(array_keys($columnheaders));
  96          $this->define_headers(array_values($columnheaders));
  97          $this->no_sorting('name');
  98          $this->no_sorting('select');
  99          $this->no_sorting('info');
 100          $this->no_sorting('purpose');
 101          $this->no_sorting('category');
 102          $this->no_sorting('retentionperiod');
 103          $this->no_sorting('tobedeleted');
 104  
 105          // Make this table sorted by first name by default.
 106          $this->sortable(true, 'timecreated');
 107  
 108          // We use roles in several places.
 109          $this->roles = role_get_names();
 110      }
 111  
 112      /**
 113       * The context name column.
 114       *
 115       * @param stdClass $expiredctx The row data.
 116       * @return string
 117       * @throws coding_exception
 118       */
 119      public function col_name($expiredctx) {
 120          global $OUTPUT;
 121          $context = context_helper::instance_by_id($expiredctx->get('contextid'));
 122          $parent = $context->get_parent_context();
 123          $contextdata = (object)[
 124              'name' => $context->get_context_name(false, true),
 125              'parent' => $parent->get_context_name(false, true),
 126          ];
 127          $fullcontexts = $context->get_parent_contexts(true);
 128          $contextsinpath = [];
 129          foreach ($fullcontexts as $contextinpath) {
 130              $contextsinpath[] = $contextinpath->get_context_name(false, true);
 131          }
 132          $infoicon = new pix_icon('i/info', implode(' / ', array_reverse($contextsinpath)));
 133          $infoiconhtml = $OUTPUT->render($infoicon);
 134          $name = html_writer::span(get_string('nameandparent', 'tool_dataprivacy', $contextdata), 'mr-1');
 135  
 136          return  $name . $infoiconhtml;
 137      }
 138  
 139      /**
 140       * The context information column.
 141       *
 142       * @param stdClass $expiredctx The row data.
 143       * @return string
 144       * @throws coding_exception
 145       */
 146      public function col_info($expiredctx) {
 147          global $OUTPUT;
 148  
 149          $context = context_helper::instance_by_id($expiredctx->get('contextid'));
 150  
 151          $children = $context->get_child_contexts();
 152          if (empty($children)) {
 153              return get_string('none');
 154          } else {
 155              $childnames = [];
 156              foreach ($children as $child) {
 157                  $childnames[] = $child->get_context_name(false, true);
 158              }
 159              $infoicon = new pix_icon('i/info', implode(', ', $childnames));
 160              $infoiconhtml = $OUTPUT->render($infoicon);
 161              $name = html_writer::span(get_string('nchildren', 'tool_dataprivacy', count($children)), 'mr-1');
 162  
 163              return  $name . $infoiconhtml;
 164          }
 165      }
 166  
 167      /**
 168       * The category name column.
 169       *
 170       * @param stdClass $expiredctx The row data.
 171       * @return mixed
 172       * @throws coding_exception
 173       * @throws dml_exception
 174       */
 175      public function col_category($expiredctx) {
 176          $context = context_helper::instance_by_id($expiredctx->get('contextid'));
 177          $category = api::get_effective_context_category($context);
 178  
 179          return s($category->get('name'));
 180      }
 181  
 182      /**
 183       * The purpose column.
 184       *
 185       * @param stdClass $expiredctx The row data.
 186       * @return string
 187       * @throws coding_exception
 188       */
 189      public function col_purpose($expiredctx) {
 190          $purpose = $this->get_purpose_for_expiry($expiredctx);
 191  
 192          return s($purpose->get('name'));
 193      }
 194  
 195      /**
 196       * The retention period column.
 197       *
 198       * @param stdClass $expiredctx The row data.
 199       * @return string
 200       */
 201      public function col_retentionperiod($expiredctx) {
 202          $purpose = $this->get_purpose_for_expiry($expiredctx);
 203  
 204          $expiries = [];
 205  
 206          $expiry = html_writer::tag('dt', get_string('default'), ['class' => 'col-sm-3']);
 207          if ($expiredctx->get('defaultexpired')) {
 208              $expiries[get_string('default')] = get_string('expiredrolewithretention', 'tool_dataprivacy', (object) [
 209                      'retention' => api::format_retention_period(new \DateInterval($purpose->get('retentionperiod'))),
 210                  ]);
 211          } else {
 212              $expiries[get_string('default')] = get_string('unexpiredrolewithretention', 'tool_dataprivacy', (object) [
 213                      'retention' => api::format_retention_period(new \DateInterval($purpose->get('retentionperiod'))),
 214                  ]);
 215          }
 216  
 217          if (!$expiredctx->is_fully_expired()) {
 218              $purposeoverrides = $purpose->get_purpose_overrides();
 219  
 220              foreach ($expiredctx->get('unexpiredroles') as $roleid) {
 221                  $role = $this->roles[$roleid];
 222                  $override = $purposeoverrides[$roleid];
 223  
 224                  $expiries[$role->localname] = get_string('unexpiredrolewithretention', 'tool_dataprivacy', (object) [
 225                          'retention' => api::format_retention_period(new \DateInterval($override->get('retentionperiod'))),
 226                      ]);
 227              }
 228  
 229              foreach ($expiredctx->get('expiredroles') as $roleid) {
 230                  $role = $this->roles[$roleid];
 231                  $override = $purposeoverrides[$roleid];
 232  
 233                  $expiries[$role->localname] = get_string('expiredrolewithretention', 'tool_dataprivacy', (object) [
 234                          'retention' => api::format_retention_period(new \DateInterval($override->get('retentionperiod'))),
 235                      ]);
 236              }
 237          }
 238  
 239          $output = array_map(function($rolename, $expiry) {
 240              $return = html_writer::tag('dt', $rolename, ['class' => 'col-sm-3']);
 241              $return .= html_writer::tag('dd', $expiry, ['class' => 'col-sm-9']);
 242  
 243              return $return;
 244          }, array_keys($expiries), $expiries);
 245  
 246          return html_writer::tag('dl', implode($output), ['class' => 'row']);
 247      }
 248  
 249      /**
 250       * The timecreated a.k.a. the context expiry date column.
 251       *
 252       * @param stdClass $expiredctx The row data.
 253       * @return string
 254       */
 255      public function col_timecreated($expiredctx) {
 256          return userdate($expiredctx->get('timecreated'));
 257      }
 258  
 259      /**
 260       * Generate the select column.
 261       *
 262       * @param stdClass $expiredctx The row data.
 263       * @return string
 264       */
 265      public function col_select($expiredctx) {
 266          $id = $expiredctx->get('id');
 267          return html_writer::checkbox('expiredcontext_' . $id, $id, $this->selectall, '', ['class' => 'selectcontext']);
 268      }
 269  
 270      /**
 271       * Formatting for the 'tobedeleted' column which indicates in a friendlier fashion whose data will be removed.
 272       *
 273       * @param stdClass $expiredctx The row data.
 274       * @return string
 275       */
 276      public function col_tobedeleted($expiredctx) {
 277          if ($expiredctx->is_fully_expired()) {
 278              return get_string('defaultexpired', 'tool_dataprivacy');
 279          }
 280  
 281          $purpose = $this->get_purpose_for_expiry($expiredctx);
 282  
 283          $a = (object) [];
 284  
 285          $expiredroles = [];
 286          foreach ($expiredctx->get('expiredroles') as $roleid) {
 287              $expiredroles[] = html_writer::tag('li', $this->roles[$roleid]->localname);
 288          }
 289          $a->expired = html_writer::tag('ul', implode($expiredroles));
 290  
 291          $unexpiredroles = [];
 292          foreach ($expiredctx->get('unexpiredroles') as $roleid) {
 293              $unexpiredroles[] = html_writer::tag('li', $this->roles[$roleid]->localname);
 294          }
 295          $a->unexpired = html_writer::tag('ul', implode($unexpiredroles));
 296  
 297          if ($expiredctx->get('defaultexpired')) {
 298              return get_string('defaultexpiredexcept', 'tool_dataprivacy', $a);
 299          } else if (empty($unexpiredroles)) {
 300              return get_string('defaultunexpired', 'tool_dataprivacy', $a);
 301          } else {
 302              return get_string('defaultunexpiredwithexceptions', 'tool_dataprivacy', $a);
 303          }
 304      }
 305  
 306      /**
 307       * Query the database for results to display in the table.
 308       *
 309       * @param int $pagesize size of page for paginated displayed table.
 310       * @param bool $useinitialsbar do you want to use the initials bar.
 311       * @throws dml_exception
 312       * @throws coding_exception
 313       */
 314      public function query_db($pagesize, $useinitialsbar = true) {
 315          // Only count expired contexts that are awaiting confirmation.
 316          $total = expired_context::get_record_count_by_contextlevel($this->contextlevel, expired_context::STATUS_EXPIRED);
 317          $this->pagesize($pagesize, $total);
 318  
 319          $sort = $this->get_sql_sort();
 320          if (empty($sort)) {
 321              $sort = 'timecreated';
 322          }
 323  
 324          // Only load expired contexts that are awaiting confirmation.
 325          $expiredcontexts = expired_context::get_records_by_contextlevel($this->contextlevel, expired_context::STATUS_EXPIRED,
 326              $sort, $this->get_page_start(), $this->get_page_size());
 327  
 328          $this->rawdata = [];
 329          $contextids = [];
 330          foreach ($expiredcontexts as $persistent) {
 331              $this->rawdata[] = $persistent;
 332              $contextids[] = $persistent->get('contextid');
 333          }
 334  
 335          $this->preload_contexts($contextids);
 336  
 337          // Set initial bars.
 338          if ($useinitialsbar) {
 339              $this->initialbars($total > $pagesize);
 340          }
 341      }
 342  
 343      /**
 344       * Override default implementation to display a more meaningful information to the user.
 345       */
 346      public function print_nothing_to_display() {
 347          global $OUTPUT;
 348          echo $this->render_reset_button();
 349          $this->print_initials_bar();
 350          echo $OUTPUT->notification(get_string('noexpiredcontexts', 'tool_dataprivacy'), 'warning');
 351      }
 352  
 353      /**
 354       * Override the table's show_hide_link method to prevent the show/hide link for the select column from rendering.
 355       *
 356       * @param string $column the column name, index into various names.
 357       * @param int $index numerical index of the column.
 358       * @return string HTML fragment.
 359       */
 360      protected function show_hide_link($column, $index) {
 361          if ($index < 6) {
 362              return parent::show_hide_link($column, $index);
 363          }
 364          return '';
 365      }
 366  
 367      /**
 368       * Get the purpose for the specified expired context.
 369       *
 370       * @param   expired_context $expiredcontext
 371       * @return  purpose
 372       */
 373      protected function get_purpose_for_expiry(expired_context $expiredcontext) : purpose {
 374          $context = context_helper::instance_by_id($expiredcontext->get('contextid'));
 375  
 376          if (empty($this->purposemap[$context->id])) {
 377              $purpose = api::get_effective_context_purpose($context);
 378              $this->purposemap[$context->id] = $purpose->get('id');
 379  
 380              if (empty($this->purposes[$purpose->get('id')])) {
 381                  $this->purposes[$purpose->get('id')] = $purpose;
 382              }
 383          }
 384  
 385          return $this->purposes[$this->purposemap[$context->id]];
 386      }
 387  
 388      /**
 389       * Preload context records given a set of contextids.
 390       *
 391       * @param   array   $contextids
 392       */
 393      protected function preload_contexts(array $contextids) {
 394          global $DB;
 395  
 396          if (empty($contextids)) {
 397              return;
 398          }
 399  
 400          $ctxfields = \context_helper::get_preload_record_columns_sql('ctx');
 401          list($insql, $inparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED);
 402          $sql = "SELECT {$ctxfields} FROM {context} ctx WHERE ctx.id {$insql}";
 403          $contextlist = $DB->get_recordset_sql($sql, $inparams);
 404          foreach ($contextlist as $contextdata) {
 405              \context_helper::preload_from_record($contextdata);
 406          }
 407          $contextlist->close();
 408  
 409      }
 410  }