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 * This file contains the form add/update a data purpose. 19 * 20 * @package tool_dataprivacy 21 * @copyright 2018 David Monllao 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 namespace tool_dataprivacy\form; 26 defined('MOODLE_INTERNAL') || die(); 27 28 use core\form\persistent; 29 30 /** 31 * Data purpose form. 32 * 33 * @package tool_dataprivacy 34 * @copyright 2018 David Monllao 35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 36 */ 37 class purpose extends persistent { 38 39 /** 40 * @var string The persistent class. 41 */ 42 protected static $persistentclass = 'tool_dataprivacy\\purpose'; 43 44 /** 45 * @var array The list of current overrides. 46 */ 47 protected $existingoverrides = []; 48 49 /** 50 * Define the form - called by parent constructor 51 */ 52 public function definition() { 53 $mform = $this->_form; 54 55 $mform->addElement('text', 'name', get_string('name'), 'maxlength="100"'); 56 $mform->setType('name', PARAM_TEXT); 57 $mform->addRule('name', get_string('required'), 'required', null, 'server'); 58 $mform->addRule('name', get_string('maximumchars', '', 100), 'maxlength', 100, 'server'); 59 60 $mform->addElement('editor', 'description', get_string('description'), null, ['autosave' => false]); 61 $mform->setType('description', PARAM_CLEANHTML); 62 63 // Field for selecting lawful bases (from GDPR Article 6.1). 64 $this->add_field($this->get_lawful_base_field()); 65 $mform->addRule('lawfulbases', get_string('required'), 'required', null, 'server'); 66 67 // Optional field for selecting reasons for collecting sensitive personal data (from GDPR Article 9.2). 68 $this->add_field($this->get_sensitive_base_field()); 69 70 $this->add_field($this->get_retention_period_fields()); 71 $this->add_field($this->get_protected_field()); 72 73 $this->add_override_fields(); 74 75 if (!empty($this->_customdata['showbuttons'])) { 76 if (!$this->get_persistent()->get('id')) { 77 $savetext = get_string('add'); 78 } else { 79 $savetext = get_string('savechanges'); 80 } 81 $this->add_action_buttons(true, $savetext); 82 } 83 } 84 85 /** 86 * Add a fieldset to the current form. 87 * 88 * @param \stdClass $data 89 */ 90 protected function add_field(\stdClass $data) { 91 foreach ($data->fields as $field) { 92 $this->_form->addElement($field); 93 } 94 95 if (!empty($data->helps)) { 96 foreach ($data->helps as $fieldname => $helpdata) { 97 $help = array_merge([$fieldname], $helpdata); 98 call_user_func_array([$this->_form, 'addHelpButton'], $help); 99 } 100 } 101 102 if (!empty($data->types)) { 103 foreach ($data->types as $fieldname => $type) { 104 $this->_form->setType($fieldname, $type); 105 } 106 } 107 108 if (!empty($data->rules)) { 109 foreach ($data->rules as $fieldname => $ruledata) { 110 $rule = array_merge([$fieldname], $ruledata); 111 call_user_func_array([$this->_form, 'addRule'], $rule); 112 } 113 } 114 115 if (!empty($data->defaults)) { 116 foreach ($data->defaults as $fieldname => $default) { 117 $this->_form($fieldname, $default); 118 } 119 } 120 } 121 122 /** 123 * Handle addition of relevant repeated element fields for role overrides. 124 */ 125 protected function add_override_fields() { 126 $purpose = $this->get_persistent(); 127 128 if (empty($purpose->get('id'))) { 129 // It is not possible to use repeated elements in a modal form yet. 130 return; 131 } 132 133 $fields = [ 134 $this->get_role_override_id('roleoverride_'), 135 $this->get_role_field('roleoverride_'), 136 $this->get_retention_period_fields('roleoverride_'), 137 $this->get_protected_field('roleoverride_'), 138 $this->get_lawful_base_field('roleoverride_'), 139 $this->get_sensitive_base_field('roleoverride_'), 140 ]; 141 142 $options = [ 143 'type' => [], 144 'helpbutton' => [], 145 ]; 146 147 // Start by adding the title. 148 $overrideelements = [ 149 $this->_form->createElement('header', 'roleoverride', get_string('roleoverride', 'tool_dataprivacy')), 150 $this->_form->createElement( 151 'static', 152 'roleoverrideoverview', 153 '', 154 get_string('roleoverrideoverview', 'tool_dataprivacy') 155 ), 156 ]; 157 158 foreach ($fields as $fielddata) { 159 foreach ($fielddata->fields as $field) { 160 $overrideelements[] = $field; 161 } 162 163 if (!empty($fielddata->helps)) { 164 foreach ($fielddata->helps as $name => $help) { 165 if (!isset($options[$name])) { 166 $options[$name] = []; 167 } 168 $options[$name]['helpbutton'] = $help; 169 } 170 } 171 172 if (!empty($fielddata->types)) { 173 foreach ($fielddata->types as $name => $type) { 174 if (!isset($options[$name])) { 175 $options[$name] = []; 176 } 177 $options[$name]['type'] = $type; 178 } 179 } 180 181 if (!empty($fielddata->rules)) { 182 foreach ($fielddata->rules as $name => $rule) { 183 if (!isset($options[$name])) { 184 $options[$name] = []; 185 } 186 $options[$name]['rule'] = $rule; 187 } 188 } 189 190 if (!empty($fielddata->defaults)) { 191 foreach ($fielddata->defaults as $name => $default) { 192 if (!isset($options[$name])) { 193 $options[$name] = []; 194 } 195 $options[$name]['default'] = $default; 196 } 197 } 198 199 if (!empty($fielddata->advanceds)) { 200 foreach ($fielddata->advanceds as $name => $advanced) { 201 if (!isset($options[$name])) { 202 $options[$name] = []; 203 } 204 $options[$name]['advanced'] = $advanced; 205 } 206 } 207 } 208 209 $this->existingoverrides = $purpose->get_purpose_overrides(); 210 $existingoverridecount = count($this->existingoverrides); 211 212 $this->repeat_elements( 213 $overrideelements, 214 $existingoverridecount, 215 $options, 216 'overrides', 217 'addoverride', 218 1, 219 get_string('addroleoverride', 'tool_dataprivacy') 220 ); 221 } 222 223 /** 224 * Converts fields. 225 * 226 * @param \stdClass $data 227 * @return \stdClass 228 */ 229 public function filter_data_for_persistent($data) { 230 $data = parent::filter_data_for_persistent($data); 231 232 $classname = static::$persistentclass; 233 $properties = $classname::properties_definition(); 234 235 $data = (object) array_filter((array) $data, function($value, $key) use ($properties) { 236 return isset($properties[$key]); 237 }, ARRAY_FILTER_USE_BOTH); 238 239 return $data; 240 } 241 242 /** 243 * Get the field for the role name. 244 * 245 * @param string $prefix The prefix to apply to the field 246 * @return \stdClass 247 */ 248 protected function get_role_override_id(string $prefix = '') : \stdClass { 249 $fieldname = "{$prefix}id"; 250 251 $fielddata = (object) [ 252 'fields' => [], 253 ]; 254 255 $fielddata->fields[] = $this->_form->createElement('hidden', $fieldname); 256 $fielddata->types[$fieldname] = PARAM_INT; 257 258 return $fielddata; 259 } 260 261 /** 262 * Get the field for the role name. 263 * 264 * @param string $prefix The prefix to apply to the field 265 * @return \stdClass 266 */ 267 protected function get_role_field(string $prefix = '') : \stdClass { 268 $fieldname = "{$prefix}roleid"; 269 270 $fielddata = (object) [ 271 'fields' => [], 272 'helps' => [], 273 ]; 274 275 $roles = [ 276 '' => get_string('none'), 277 ]; 278 foreach (role_get_names() as $roleid => $role) { 279 $roles[$roleid] = $role->localname; 280 } 281 282 $fielddata->fields[] = $this->_form->createElement('select', $fieldname, get_string('role'), 283 $roles, 284 [ 285 'multiple' => false, 286 ] 287 ); 288 $fielddata->helps[$fieldname] = ['role', 'tool_dataprivacy']; 289 $fielddata->defaults[$fieldname] = null; 290 291 return $fielddata; 292 } 293 294 /** 295 * Get the mform field for lawful bases. 296 * 297 * @param string $prefix The prefix to apply to the field 298 * @return \stdClass 299 */ 300 protected function get_lawful_base_field(string $prefix = '') : \stdClass { 301 $fieldname = "{$prefix}lawfulbases"; 302 303 $data = (object) [ 304 'fields' => [], 305 ]; 306 307 $bases = []; 308 foreach (\tool_dataprivacy\purpose::GDPR_ART_6_1_ITEMS as $article) { 309 $key = 'gdpr_art_6_1_' . $article; 310 $bases[$key] = get_string("{$key}_name", 'tool_dataprivacy'); 311 } 312 313 $data->fields[] = $this->_form->createElement('autocomplete', $fieldname, get_string('lawfulbases', 'tool_dataprivacy'), 314 $bases, 315 [ 316 'multiple' => true, 317 ] 318 ); 319 320 $data->helps = [ 321 $fieldname => ['lawfulbases', 'tool_dataprivacy'], 322 ]; 323 324 $data->advanceds = [ 325 $fieldname => true, 326 ]; 327 328 return $data; 329 } 330 331 /** 332 * Get the mform field for sensitive bases. 333 * 334 * @param string $prefix The prefix to apply to the field 335 * @return \stdClass 336 */ 337 protected function get_sensitive_base_field(string $prefix = '') : \stdClass { 338 $fieldname = "{$prefix}sensitivedatareasons"; 339 340 $data = (object) [ 341 'fields' => [], 342 ]; 343 344 $bases = []; 345 foreach (\tool_dataprivacy\purpose::GDPR_ART_9_2_ITEMS as $article) { 346 $key = 'gdpr_art_9_2_' . $article; 347 $bases[$key] = get_string("{$key}_name", 'tool_dataprivacy'); 348 } 349 350 $data->fields[] = $this->_form->createElement( 351 'autocomplete', 352 $fieldname, 353 get_string('sensitivedatareasons', 'tool_dataprivacy'), 354 $bases, 355 [ 356 'multiple' => true, 357 ] 358 ); 359 $data->helps = [ 360 $fieldname => ['sensitivedatareasons', 'tool_dataprivacy'], 361 ]; 362 363 $data->advanceds = [ 364 $fieldname => true, 365 ]; 366 367 return $data; 368 } 369 370 /** 371 * Get the retention period fields. 372 * 373 * @param string $prefix The name of the main field, and prefix for the subfields. 374 * @return \stdClass 375 */ 376 protected function get_retention_period_fields(string $prefix = '') : \stdClass { 377 $prefix = "{$prefix}retentionperiod"; 378 $data = (object) [ 379 'fields' => [], 380 'types' => [], 381 ]; 382 383 $number = $this->_form->createElement('text', "{$prefix}number", null, ['size' => 8]); 384 $data->types["{$prefix}number"] = PARAM_INT; 385 386 $unitoptions = [ 387 'Y' => get_string('years'), 388 'M' => strtolower(get_string('months')), 389 'D' => strtolower(get_string('days')) 390 ]; 391 $unit = $this->_form->createElement('select', "{$prefix}unit", '', $unitoptions); 392 393 $data->fields[] = $this->_form->createElement( 394 'group', 395 $prefix, 396 get_string('retentionperiod', 'tool_dataprivacy'), 397 [ 398 'number' => $number, 399 'unit' => $unit, 400 ], 401 null, 402 false 403 ); 404 405 return $data; 406 } 407 408 /** 409 * Get the mform field for the protected flag. 410 * 411 * @param string $prefix The prefix to apply to the field 412 * @return \stdClass 413 */ 414 protected function get_protected_field(string $prefix = '') : \stdClass { 415 $fieldname = "{$prefix}protected"; 416 417 return (object) [ 418 'fields' => [ 419 $this->_form->createElement( 420 'advcheckbox', 421 $fieldname, 422 get_string('protected', 'tool_dataprivacy'), 423 get_string('protectedlabel', 'tool_dataprivacy') 424 ), 425 ], 426 ]; 427 } 428 429 /** 430 * Converts data to data suitable for storage. 431 * 432 * @param \stdClass $data 433 * @return \stdClass 434 */ 435 protected static function convert_fields(\stdClass $data) { 436 $data = parent::convert_fields($data); 437 438 if (!empty($data->lawfulbases) && is_array($data->lawfulbases)) { 439 $data->lawfulbases = implode(',', $data->lawfulbases); 440 } 441 if (!empty($data->sensitivedatareasons) && is_array($data->sensitivedatareasons)) { 442 $data->sensitivedatareasons = implode(',', $data->sensitivedatareasons); 443 } else { 444 // Nothing selected. Set default value of null. 445 $data->sensitivedatareasons = null; 446 } 447 448 // A single value. 449 $data->retentionperiod = 'P' . $data->retentionperiodnumber . $data->retentionperiodunit; 450 unset($data->retentionperiodnumber); 451 unset($data->retentionperiodunit); 452 453 return $data; 454 } 455 456 /** 457 * Get the default data. 458 * 459 * @return \stdClass 460 */ 461 protected function get_default_data() { 462 $data = parent::get_default_data(); 463 464 return $this->convert_existing_data_to_values($data); 465 } 466 467 /** 468 * Normalise any values stored in existing data. 469 * 470 * @param \stdClass $data 471 * @return \stdClass 472 */ 473 protected function convert_existing_data_to_values(\stdClass $data) : \stdClass { 474 $data->lawfulbases = explode(',', $data->lawfulbases); 475 if (!empty($data->sensitivedatareasons)) { 476 $data->sensitivedatareasons = explode(',', $data->sensitivedatareasons); 477 } 478 479 // Convert the single properties into number and unit. 480 $strlen = strlen($data->retentionperiod); 481 $data->retentionperiodnumber = substr($data->retentionperiod, 1, $strlen - 2); 482 $data->retentionperiodunit = substr($data->retentionperiod, $strlen - 1); 483 unset($data->retentionperiod); 484 485 return $data; 486 } 487 488 /** 489 * Fetch the role override data from the list of submitted data. 490 * 491 * @param \stdClass $data The complete set of processed data 492 * @return \stdClass[] The list of overrides 493 */ 494 public function get_role_overrides_from_data(\stdClass $data) { 495 $overrides = []; 496 if (!empty($data->overrides)) { 497 $searchkey = 'roleoverride_'; 498 499 for ($i = 0; $i < $data->overrides; $i++) { 500 $overridedata = (object) []; 501 foreach ((array) $data as $fieldname => $value) { 502 if (strpos($fieldname, $searchkey) !== 0) { 503 continue; 504 } 505 506 $overridefieldname = substr($fieldname, strlen($searchkey)); 507 $overridedata->$overridefieldname = $value[$i]; 508 } 509 510 if (empty($overridedata->roleid) || empty($overridedata->retentionperiodnumber)) { 511 // Skip this one. 512 // There is no value and it will be delete. 513 continue; 514 } 515 516 $override = static::convert_fields($overridedata); 517 518 $overrides[$i] = $override; 519 } 520 } 521 522 return $overrides; 523 } 524 525 /** 526 * Define extra validation mechanims. 527 * 528 * @param stdClass $data Data to validate. 529 * @param array $files Array of files. 530 * @param array $errors Currently reported errors. 531 * @return array of additional errors, or overridden errors. 532 */ 533 protected function extra_validation($data, $files, array &$errors) { 534 $overrides = $this->get_role_overrides_from_data($data); 535 536 // Check role overrides to ensure that: 537 // - roles are unique; and 538 // - specifeid retention periods are numeric. 539 $seenroleids = []; 540 foreach ($overrides as $id => $override) { 541 $override->purposeid = 0; 542 $persistent = new \tool_dataprivacy\purpose_override($override->id, $override); 543 544 if (isset($seenroleids[$persistent->get('roleid')])) { 545 $errors["roleoverride_roleid[{$id}]"] = get_string('duplicaterole'); 546 } 547 $seenroleids[$persistent->get('roleid')] = true; 548 549 $errors = array_merge($errors, $persistent->get_errors()); 550 } 551 552 return $errors; 553 } 554 555 /** 556 * Load in existing data as form defaults. Usually new entry defaults are stored directly in 557 * form definition (new entry form); this function is used to load in data where values 558 * already exist and data is being edited (edit entry form). 559 * 560 * @param stdClass $data 561 */ 562 public function set_data($data) { 563 $purpose = $this->get_persistent(); 564 565 $count = 0; 566 foreach ($this->existingoverrides as $override) { 567 $overridedata = $this->convert_existing_data_to_values($override->to_record()); 568 foreach ($overridedata as $key => $value) { 569 $keyname = "roleoverride_{$key}[{$count}]"; 570 $data->$keyname = $value; 571 } 572 $count++; 573 } 574 575 parent::set_data($data); 576 } 577 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body