Skip to content

Commit

Permalink
MDL-61899 tool_dataprivacy: Add lawful bases fields
Browse files Browse the repository at this point in the history
Includes MDL-61935

* New fields when defining purposes that let the Privacy Officer
set the lawful bases of personal data collection and reason(s)
for the exemption of prohibition from processing sensitive personal
data.
  • Loading branch information
junpataleta authored and stronk7 committed Apr 18, 2018
1 parent e60058f commit 5750d23
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 13 deletions.
80 changes: 69 additions & 11 deletions admin/tool/dataprivacy/classes/external/purpose_exporter.php
Expand Up @@ -24,7 +24,14 @@
namespace tool_dataprivacy\external;
defined('MOODLE_INTERNAL') || die();

use coding_exception;
use core\external\persistent_exporter;
use DateInterval;
use Exception;
use external_single_structure;
use external_value;
use renderer_base;
use tool_dataprivacy\purpose;

/**
* Class for exporting field data.
Expand All @@ -40,7 +47,7 @@ class purpose_exporter extends persistent_exporter {
* @return string
*/
protected static function define_class() {
return \tool_dataprivacy\purpose::class;
return purpose::class;
}

/**
Expand All @@ -60,25 +67,74 @@ protected static function define_related() {
* @return array
*/
protected static function define_other_properties() {
return array(
'formattedretentionperiod' => array(
$namedescriptiontype = [
'name' => [
'type' => PARAM_TEXT,
],
'description' => [
'type' => PARAM_TEXT,
],
];
return [
'formattedretentionperiod' => [
'type' => PARAM_TEXT
),
);
],
'formattedlawfulbases' => [
'type' => $namedescriptiontype,
'multiple' => true
],
'formattedsensitivedatareasons' => [
'type' => $namedescriptiontype,
'multiple' => true,
'optional' => true
],
];
}

/**
* Return other properties.
*
* @param \renderer_base $output
* @throws coding_exception
* @throws dml_exception
* @param renderer_base $output
* @return array
* @throws coding_exception
* @throws Exception
*/
protected function get_other_values(\renderer_base $output) {
protected function get_other_values(renderer_base $output) {
$values = [];

$formattedbases = [];
$lawfulbases = explode(',', $this->persistent->get('lawfulbases'));
if (!empty($lawfulbases)) {
foreach ($lawfulbases as $basis) {
if (empty(trim($basis))) {
continue;
}
$formattedbases[] = (object)[
'name' => get_string($basis . '_name', 'tool_dataprivacy'),
'description' => get_string($basis . '_description', 'tool_dataprivacy')
];
}
}
$values['formattedlawfulbases'] = $formattedbases;

$formattedsensitivereasons = [];
$sensitivereasons = explode(',', $this->persistent->get('sensitivedatareasons'));
if (!empty($sensitivereasons)) {
foreach ($sensitivereasons as $reason) {
if (empty(trim($reason))) {
continue;
}
$formattedsensitivereasons[] = (object)[
'name' => get_string($reason . '_name', 'tool_dataprivacy'),
'description' => get_string($reason . '_description', 'tool_dataprivacy')
];
}
}
$values['formattedsensitivedatareasons'] = $formattedsensitivereasons;

$retentionperiod = $this->persistent->get('retentionperiod');
if ($retentionperiod) {
$interval = new \DateInterval($retentionperiod);
$interval = new DateInterval($retentionperiod);

// It is one or another.
if ($interval->y) {
Expand All @@ -93,6 +149,8 @@ protected function get_other_values(\renderer_base $output) {
} else {
$formattedtime = get_string('retentionperiodnotdefined', 'tool_dataprivacy');
}
return ['formattedretentionperiod' => $formattedtime];
$values['formattedretentionperiod'] = $formattedtime;

return $values;
}
}
40 changes: 39 additions & 1 deletion admin/tool/dataprivacy/classes/form/purpose.php
Expand Up @@ -26,6 +26,7 @@
defined('MOODLE_INTERNAL') || die();

use core\form\persistent;
use tool_dataprivacy\purpose as purpose_persistent;

/**
* Data purpose form.
Expand All @@ -37,14 +38,16 @@
class purpose extends persistent {

/**
* @var The persistent class.
* @var string The persistent class.
*/
protected static $persistentclass = 'tool_dataprivacy\\purpose';

/**
* Define the form - called by parent constructor
*/
public function definition() {
global $DB;

$mform = $this->_form;

$mform->addElement('text', 'name', get_string('name'), 'maxlength="100"');
Expand All @@ -55,6 +58,29 @@ public function definition() {
$mform->addElement('editor', 'description', get_string('description'), null, ['autosave' => false]);
$mform->setType('description', PARAM_CLEANHTML);

// Field for selecting lawful bases (from GDPR Article 6.1).
$lawfulbases = [];
foreach (purpose_persistent::GDPR_ART_6_1_ITEMS as $article) {
$key = 'gdpr_art_6_1_' . $article;
$lawfulbases[$key] = get_string($key . '_name', 'tool_dataprivacy');
}
$options = array(
'multiple' => true,
);
$mform->addElement('autocomplete', 'lawfulbases', get_string('lawfulbases', 'tool_dataprivacy'), $lawfulbases, $options);
$mform->addRule('lawfulbases', get_string('required'), 'required', null, 'server');
$mform->addHelpButton('lawfulbases', 'lawfulbases', 'tool_dataprivacy');

// Optional field for selecting reasons for collecting sensitive personal data (from GDPR Article 9.2).
$sensitivereasons = [];
foreach (purpose_persistent::GDPR_ART_9_2_ITEMS as $article) {
$key = 'gdpr_art_9_2_' . $article;
$sensitivereasons[$key] = get_string($key . '_name', 'tool_dataprivacy');
}
$mform->addElement('autocomplete', 'sensitivedatareasons', get_string('sensitivedatareasons', 'tool_dataprivacy'),
$sensitivereasons, $options);
$mform->addHelpButton('sensitivedatareasons', 'sensitivedatareasons', 'tool_dataprivacy');

$number = $mform->createElement('text', 'retentionperiodnumber', null, ['size' => 8]);
$unitoptions = [
'Y' => get_string('years'),
Expand Down Expand Up @@ -88,6 +114,13 @@ public function definition() {
protected static function convert_fields(\stdClass $data) {
$data = parent::convert_fields($data);

if (is_array($data->lawfulbases)) {
$data->lawfulbases = implode(',', $data->lawfulbases);
}
if (is_array($data->sensitivedatareasons)) {
$data->sensitivedatareasons = implode(',', $data->sensitivedatareasons);
}

// A single value.
$data->retentionperiod = 'P' . $data->retentionperiodnumber . $data->retentionperiodunit;
unset($data->retentionperiodnumber);
Expand All @@ -103,6 +136,11 @@ protected static function convert_fields(\stdClass $data) {
protected function get_default_data() {
$data = parent::get_default_data();

$data->lawfulbases = explode(',', $data->lawfulbases);
if (!empty($data->sensitivedatareasons)) {
$data->sensitivedatareasons = explode(',', $data->sensitivedatareasons);
}

// Convert the single properties into number and unit.
$strlen = strlen($data->retentionperiod);
$data->retentionperiodnumber = substr($data->retentionperiod, 1, $strlen - 2);
Expand Down
16 changes: 16 additions & 0 deletions admin/tool/dataprivacy/classes/purpose.php
Expand Up @@ -42,6 +42,12 @@ class purpose extends \core\persistent {
*/
const TABLE = 'tool_dataprivacy_purpose';

/** Items under GDPR Article 6.1. */
const GDPR_ART_6_1_ITEMS = ['a', 'b', 'c', 'd', 'e', 'f'];

/** Items under GDPR Article 9.2. */
const GDPR_ART_9_2_ITEMS = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'];

/**
* Extended constructor to fetch from the cache if available.
*
Expand Down Expand Up @@ -96,6 +102,16 @@ protected static function define_properties() {
'type' => PARAM_INT,
'default' => FORMAT_HTML
),
'lawfulbases' => array(
'type' => PARAM_TEXT,
'description' => 'Comma-separated IDs matching records in tool_dataprivacy_lawfulbasis.',
),
'sensitivedatareasons' => array(
'type' => PARAM_TEXT,
'description' => 'Comma-separated IDs matching records in tool_dataprivacy_sensitive',
'null' => NULL_ALLOWED,
'default' => ''
),
'retentionperiod' => array(
'type' => PARAM_ALPHANUM,
'description' => 'Retention period. ISO_8601 durations format (as in DateInterval format).',
Expand Down
2 changes: 2 additions & 0 deletions admin/tool/dataprivacy/db/install.xml
Expand Up @@ -32,6 +32,8 @@
<FIELD NAME="name" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="description" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="descriptionformat" TYPE="int" LENGTH="1" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="lawfulbases" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="Comma-separated IDs matching records in tool_dataprivacy_lawfulbasis"/>
<FIELD NAME="sensitivedatareasons" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Comma-separated IDs matching records in tool_dataprivacy_sensitive"/>
<FIELD NAME="retentionperiod" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="protected" TYPE="int" LENGTH="1" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="usermodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
Expand Down
38 changes: 37 additions & 1 deletion admin/tool/dataprivacy/lang/en/tool_dataprivacy.php
Expand Up @@ -97,14 +97,48 @@
$string['errorsendingmessagetodpo'] = 'An error was encountered while trying to send a message to {$a}.';
$string['expiredretentionperiodtask'] = 'Expired retention period';
$string['expiry'] = 'Expiry';
$string['frontpagecourse'] = 'Front page course';
$string['expandplugin'] = 'Expand and collapse plugin.';
$string['expandplugintype'] = 'Expand and collapse plugin type.';
$string['explanationtitle'] = 'Icons used on this page and what they mean.';
$string['external'] = 'External';
$string['externalexplanation'] = 'An additional plugin installed on this site.';
$string['frontpagecourse'] = 'Front page course';
$string['gdpr_art_6_1_a_description'] = 'The data subject has given consent to the processing of his or her personal data for one or more specific purposes';
$string['gdpr_art_6_1_a_name'] = 'Consent (GDPR Art. 6.1(a))';
$string['gdpr_art_6_1_b_description'] = 'Processing is necessary for the performance of a contract to which the data subject is party or in order to take steps at the request of the data subject prior to entering into a contract';
$string['gdpr_art_6_1_b_name'] = 'Contract (GDPR Art. 6.1(b))';
$string['gdpr_art_6_1_c_description'] = 'Processing is necessary for compliance with a legal obligation to which the controller is subject';
$string['gdpr_art_6_1_c_name'] = 'Legal obligation (GDPR Art 6.1(c))';
$string['gdpr_art_6_1_d_description'] = 'Processing is necessary in order to protect the vital interests of the data subject or of another natural person';
$string['gdpr_art_6_1_d_name'] = 'Vital interests (GDPR Art. 6.1(d))';
$string['gdpr_art_6_1_e_description'] = 'Processing is necessary for the performance of a task carried out in the public interest or in the exercise of official authority vested in the controller';
$string['gdpr_art_6_1_e_name'] = 'Public task (GDPR Art. 6.1(e))';
$string['gdpr_art_6_1_f_description'] = 'Processing is necessary for the purposes of the legitimate interests pursued by the controller or by a third party, except where such interests are overridden by the interests or fundamental rights and freedoms of the data subject which require protection of personal data, in particular where the data subject is a child';
$string['gdpr_art_6_1_f_name'] = 'Legitimate interests (GDPR Art. 6.1(f))';
$string['gdpr_art_9_2_a_description'] = 'The data subject has given explicit consent to the processing of those personal data for one or more specified purposes, except where Union or Member State law provide that the prohibition referred to in paragraph 1 of GDPR Article 9 may not be lifted by the data subject';
$string['gdpr_art_9_2_a_name'] = 'Explicit consent (GDPR Art. 9.2(a))';
$string['gdpr_art_9_2_b_description'] = 'Processing is necessary for the purposes of carrying out the obligations and exercising specific rights of the controller or of the data subject in the field of employment and social security and social protection law in so far as it is authorised by Union or Member State law or a collective agreement pursuant to Member State law providing for appropriate safeguards for the fundamental rights and the interests of the data subject';
$string['gdpr_art_9_2_b_name'] = 'Employment and social security/protection law (GDPR Art. 9.2(b))';
$string['gdpr_art_9_2_c_description'] = 'Processing is necessary to protect the vital interests of the data subject or of another natural person where the data subject is physically or legally incapable of giving consent';
$string['gdpr_art_9_2_c_name'] = 'Protection of vital interests (GDPR Art. 9.2(c))';
$string['gdpr_art_9_2_d_description'] = 'Processing is carried out in the course of its legitimate activities with appropriate safeguards by a foundation, association or any other not-for-profit body with a political, philosophical, religious or trade-union aim and on condition that the processing relates solely to the members or to former members of the body or to persons who have regular contact with it in connection with its purposes and that the personal data are not disclosed outside that body without the consent of the data subjects';
$string['gdpr_art_9_2_d_name'] = 'Legitimate activities regarding the members/close contacts of a foundation, association or other not-for-profit body (GDPR Art. 9.2(d))';
$string['gdpr_art_9_2_e_description'] = 'Processing relates to personal data which are manifestly made public by the data subject';
$string['gdpr_art_9_2_e_name'] = 'Data made public by the data subject (GDPR Art. 9.2(e))';
$string['gdpr_art_9_2_f_description'] = 'Processing is necessary for the establishment, exercise or defence of legal claims or whenever courts are acting in their judicial capacity';
$string['gdpr_art_9_2_f_name'] = 'Legal claims and court actions (GDPR Art. 9.2(f))';
$string['gdpr_art_9_2_g_description'] = 'Processing is necessary for reasons of substantial public interest, on the basis of Union or Member State law which shall be proportionate to the aim pursued, respect the essence of the right to data protection and provide for suitable and specific measures to safeguard the fundamental rights and the interests of the data subject';
$string['gdpr_art_9_2_g_name'] = 'Substantial public interest (GDPR Art. 9.2(g))';
$string['gdpr_art_9_2_h_description'] = 'Processing is necessary for the purposes of preventive or occupational medicine, for the assessment of the working capacity of the employee, medical diagnosis, the provision of health or social care or treatment or the management of health or social care systems and services on the basis of Union or Member State law or pursuant to contract with a health professional and subject to the conditions and safeguards referred to in paragraph 3 of GDPR Article 9';
$string['gdpr_art_9_2_h_name'] = 'Medical purposes (GDPR Art. 9.2(h))';
$string['gdpr_art_9_2_i_description'] = 'Processing is necessary for reasons of public interest in the area of public health, such as protecting against serious cross-border threats to health or ensuring high standards of quality and safety of health care and of medicinal products or medical devices, on the basis of Union or Member State law which provides for suitable and specific measures to safeguard the rights and freedoms of the data subject, in particular professional secrecy';
$string['gdpr_art_9_2_i_name'] = 'Public health (GDPR Art. 9.2(i))';
$string['gdpr_art_9_2_j_description'] = 'Processing is necessary for archiving purposes in the public interest, scientific or historical research purposes or statistical purposes in accordance with Article 89(1) based on Union or Member State law which shall be proportionate to the aim pursued, respect the essence of the right to data protection and provide for suitable and specific measures to safeguard the fundamental rights and the interests of the data subject';
$string['gdpr_art_9_2_j_name'] = 'Public interest, or scientific/historical/statistical research (GDPR Art. 9.2(j))';
$string['hide'] = 'Collapse all';
$string['inherit'] = 'Inherit';
$string['lawfulbases'] = 'Lawful bases';
$string['lawfulbases_help'] = 'Select at least one option that will serve as the lawful basis for processing personal data. For details on these lawful bases, please see <a href="https://gdpr-info.eu/art-6-gdpr/" target="_blank">GDPR Art. 6.1</a>';
$string['messageprovider:contactdataprotectionofficer'] = 'Data requests';
$string['messageprovider:datarequestprocessingresults'] = 'Data request processing results';
$string['message'] = 'Message';
Expand Down Expand Up @@ -169,6 +203,8 @@
$string['retentionperiodnotdefined'] = 'No retention period was defined';
$string['retentionperiodzero'] = 'No retention period';
$string['send'] = 'Send';
$string['sensitivedatareasons'] = 'Sensitive personal data processing reasons';
$string['sensitivedatareasons_help'] = 'Select one or more applicable reasons that exempts the prohibition of processing sensitive personal data tied to this purpose. For more information, please see <a href="https://gdpr-info.eu/art-9-gdpr/" target="_blank">GDPR Art. 9.2</a>';
$string['setdefaults'] = 'Set defaults';
$string['statusapproved'] = 'Approved';
$string['statusawaitingapproval'] = 'Awaiting approval';
Expand Down
20 changes: 20 additions & 0 deletions admin/tool/dataprivacy/templates/purposes.mustache
Expand Up @@ -70,6 +70,8 @@
<tr>
<th scope="col">{{#str}}name{{/str}}</th>
<th scope="col">{{#str}}description{{/str}}</th>
<th scope="col">{{#str}}lawfulbases, tool_dataprivacy{{/str}}</th>
<th scope="col">{{#str}}sensitivedatareasons, tool_dataprivacy{{/str}}</th>
<th scope="col">{{#str}}retentionperiod, tool_dataprivacy{{/str}}</th>
<th scope="col">{{#str}}protected, tool_dataprivacy{{/str}}</th>
<th scope="col">{{#str}}actions{{/str}}</th>
Expand All @@ -80,6 +82,24 @@
<tr data-purposeid="{{id}}">
<td>{{{name}}}</td>
<td>{{{description}}}</td>
<td>
<ul>
{{#formattedlawfulbases}}
<li>
<span>{{name}}{{# pix }} i/info, core, {{description}} {{/ pix }}</span>
</li>
{{/formattedlawfulbases}}
</ul>
</td>
<td>
<ul>
{{#formattedsensitivedatareasons}}
<li>
<span>{{name}}{{# pix }} i/info, core, {{description}} {{/ pix }}</span>
</li>
{{/formattedsensitivedatareasons}}
</ul>
</td>
<td>{{formattedretentionperiod}}</td>
<td>
{{#protected}}
Expand Down

0 comments on commit 5750d23

Please sign in to comment.