diff --git a/mod/data/classes/privacy/datafield_provider.php b/mod/data/classes/privacy/datafield_provider.php new file mode 100644 index 0000000000000..bc4f407a78097 --- /dev/null +++ b/mod/data/classes/privacy/datafield_provider.php @@ -0,0 +1,72 @@ +. + +/** + * Contains interface datafield_provider + * + * @package mod_data + * @copyright 2018 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_data\privacy; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Interface datafield_provider, all datafield plugins need to implement it + * + * @package mod_data + * @copyright 2018 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +interface datafield_provider extends \core_privacy\local\request\plugin\subplugin_provider { + + /** + * Exports data about one record in {data_content} table. + * + * Datafield plugins providers should implement this method to: + * - preprocess references to files in the response (examples - textarea, picture, file) + * - make content more human-readable (example - replace values separators in multimenu, format date in date) + * - add more information about the field itself (example - list all options for menu, multimenu, radio) + * + * Sample implementation (from datafield_textarea): + * + * $defaultvalue->content = writer::with_context($context) + * ->rewrite_pluginfile_urls([$recordobj->id, $contentobj->id], 'mod_data', 'content', $contentobj->id, + * $defaultvalue->content); + * writer::with_context($context)->export_data([$recordobj->id, $contentobj->id], $defaultvalue); + * + * @param \context_module $context + * @param \stdClass $recordobj record from DB table {data_records} + * @param \stdClass $fieldobj record from DB table {data_fields} + * @param \stdClass $contentobj record from DB table {data_content} + * @param \stdClass $defaultvalue pre-populated default value that most of plugins will use + */ + public static function export_data_content($context, $recordobj, $fieldobj, $contentobj, $defaultvalue); + + /** + * Allows plugins to delete locally stored data. + * + * Usually datafield plugins do not store anything and this method will be empty. + * + * @param \context_module $context + * @param \stdClass $recordobj record from DB table {data_records} + * @param \stdClass $fieldobj record from DB table {data_fields} + * @param \stdClass $contentobj record from DB table {data_content} + */ + public static function delete_data_content($context, $recordobj, $fieldobj, $contentobj); +} diff --git a/mod/data/classes/privacy/provider.php b/mod/data/classes/privacy/provider.php new file mode 100644 index 0000000000000..5500806fbfd96 --- /dev/null +++ b/mod/data/classes/privacy/provider.php @@ -0,0 +1,445 @@ +. + +/** + * Privacy Subsystem implementation for mod_data. + * + * @package mod_data + * @copyright 2018 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_data\privacy; + +use core_privacy\local\metadata\collection; +use core_privacy\local\request\approved_contextlist; +use core_privacy\local\request\contextlist; +use core_privacy\local\request\helper; +use core_privacy\local\request\transform; +use core_privacy\local\request\writer; +use core_privacy\manager; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Implementation of the privacy subsystem plugin provider for the database activity module. + * + * @package mod_data + * @copyright 2018 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class provider implements + // This plugin stores personal data. + \core_privacy\local\metadata\provider, + + // This plugin is a core_user_data_provider. + \core_privacy\local\request\plugin\provider { + + /** @var array stores list of records marked for deletion */ + protected static $deletedrecords = []; + + /** + * Return the fields which contain personal data. + * + * @param collection $collection a reference to the collection to use to store the metadata. + * @return collection the updated collection of metadata items. + */ + public static function get_metadata(collection $collection) : collection { + $collection->add_database_table( + 'data_records', + [ + 'userid' => 'privacy:metadata:data_records:userid', + 'groupid' => 'privacy:metadata:data_records:groupid', + 'timecreated' => 'privacy:metadata:data_records:timecreated', + 'timemodified' => 'privacy:metadata:data_records:timemodified', + 'approved' => 'privacy:metadata:data_records:approved', + ], + 'privacy:metadata:data_records' + ); + $collection->add_database_table( + 'data_content', + [ + 'fieldid' => 'privacy:metadata:data_content:fieldid', + 'content' => 'privacy:metadata:data_content:content', + 'content1' => 'privacy:metadata:data_content:content1', + 'content2' => 'privacy:metadata:data_content:content2', + 'content3' => 'privacy:metadata:data_content:content3', + 'content4' => 'privacy:metadata:data_content:content4', + ], + 'privacy:metadata:data_content' + ); + + // Link to subplugins. + $collection->add_plugintype_link('datafield', [], 'privacy:metadata:datafieldnpluginsummary'); + + // Subsystems used. + $collection->link_subsystem('core_comment', 'privacy:metadata:commentpurpose'); + $collection->link_subsystem('core_files', 'privacy:metadata:filepurpose'); + $collection->link_subsystem('core_tag', 'privacy:metadata:tagpurpose'); + $collection->link_subsystem('core_rating', 'privacy:metadata:ratingpurpose'); + + return $collection; + } + + /** + * Get the list of contexts that contain user information for the specified user. + * + * @param int $userid the userid. + * @return contextlist the list of contexts containing user info for the user. + */ + public static function get_contexts_for_userid(int $userid) : contextlist { + // Fetch all data records. + $sql = "SELECT c.id + FROM {context} c + INNER JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel + INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname + INNER JOIN {data} d ON d.id = cm.instance + INNER JOIN {data_records} dr ON dr.dataid = d.id + LEFT JOIN {comments} com ON com.commentarea=:commentarea and com.itemid = dr.id + LEFT JOIN {rating} r ON r.contextid = c.id AND r.itemid = dr.id AND r.component = :moddata AND r.ratingarea = :ratingarea + WHERE dr.userid = :userid OR com.userid = :userid1 OR r.userid = :userid2"; + + $params = [ + 'modname' => 'data', + 'contextlevel' => CONTEXT_MODULE, + 'userid' => $userid, + 'userid1' => $userid, + 'userid2' => $userid, + 'commentarea' => 'database_entry', + 'moddata' => 'mod_data', + 'ratingarea' => 'entry', + ]; + $contextlist = new contextlist(); + $contextlist->add_from_sql($sql, $params); + + return $contextlist; + } + + /** + * Creates an object from all fields in the $record where key starts with $prefix + * + * @param \stdClass $record + * @param string $prefix + * @param array $additionalfields + * @return \stdClass + */ + protected static function extract_object_from_record($record, $prefix, $additionalfields = []) { + $object = new \stdClass(); + foreach ($record as $key => $value) { + if (preg_match('/^'.preg_quote($prefix, '/').'(.*)/', $key, $matches)) { + $object->{$matches[1]} = $value; + } + } + if ($additionalfields) { + foreach ($additionalfields as $key => $value) { + $object->$key = $value; + } + } + return $object; + } + + /** + * Export one field answer in a record in database activity module + * + * @param \context $context + * @param \stdClass $recordobj record from DB table {data_records} + * @param \stdClass $fieldobj record from DB table {data_fields} + * @param \stdClass $contentobj record from DB table {data_content} + */ + protected static function export_data_content($context, $recordobj, $fieldobj, $contentobj) { + $value = (object)[ + 'field' => [ + // Name and description are displayed in mod_data without applying format_string(). + 'name' => $fieldobj->name, + 'description' => $fieldobj->description, + 'type' => $fieldobj->type, + 'required' => transform::yesno($fieldobj->required), + ], + 'content' => $contentobj->content + ]; + foreach (['content1', 'content2', 'content3', 'content4'] as $key) { + if ($contentobj->$key !== null) { + $value->$key = $contentobj->$key; + } + } + $classname = manager::get_provider_classname_for_component('datafield_' . $fieldobj->type); + if (class_exists($classname) && is_subclass_of($classname, datafield_provider::class)) { + component_class_callback($classname, 'export_data_content', + [$context, $recordobj, $fieldobj, $contentobj, $value]); + } else { + // Data field plugin does not implement datafield_provider, just export default value. + writer::with_context($context)->export_data([$recordobj->id, $contentobj->id], $value); + } + writer::with_context($context)->export_area_files([$recordobj->id, $contentobj->id], 'mod_data', + 'content', $contentobj->id); + } + + /** + * SQL query that returns all fields from {data_content}, {data_fields} and {data_records} tables + * + * @return string + */ + protected static function sql_fields() { + return 'd.id AS dataid, dc.id AS contentid, dc.fieldid, df.type AS fieldtype, df.name AS fieldname, + df.description AS fielddescription, df.required AS fieldrequired, + df.param1 AS fieldparam1, df.param2 AS fieldparam2, df.param3 AS fieldparam3, df.param4 AS fieldparam4, + df.param5 AS fieldparam5, df.param6 AS fieldparam6, df.param7 AS fieldparam7, df.param8 AS fieldparam8, + df.param9 AS fieldparam9, df.param10 AS fieldparam10, + dc.content AS contentcontent, dc.content1 AS contentcontent1, dc.content2 AS contentcontent2, + dc.content3 AS contentcontent3, dc.content4 AS contentcontent4, + dc.recordid, dr.timecreated AS recordtimecreated, dr.timemodified AS recordtimemodified, + dr.approved AS recordapproved, dr.groupid AS recordgroupid, dr.userid AS recorduserid'; + } + + /** + * Export personal data for the given approved_contextlist. User and context information is contained within the contextlist. + * + * @param approved_contextlist $contextlist a list of contexts approved for export. + */ + public static function export_user_data(approved_contextlist $contextlist) { + global $DB; + + if (!$contextlist->count()) { + return; + } + + $user = $contextlist->get_user(); + + list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED); + $sql = "SELECT cm.id AS cmid, d.name AS dataname, cm.course AS courseid, " . self::sql_fields() . " + FROM {context} ctx + JOIN {course_modules} cm ON cm.id = ctx.instanceid + JOIN {modules} m ON m.id = cm.module AND m.name = :modname + JOIN {data} d ON d.id = cm.instance + JOIN {data_records} dr ON dr.dataid = d.id + JOIN {data_content} dc ON dc.recordid = dr.id + JOIN {data_fields} df ON df.id = dc.fieldid + WHERE ctx.id {$contextsql} AND ctx.contextlevel = :contextlevel + AND dr.userid = :userid OR + EXISTS (SELECT 1 FROM {comments} com WHERE com.commentarea=:commentarea + AND com.itemid = dr.id AND com.userid = :userid1) OR + EXISTS (SELECT 1 FROM {rating} r WHERE r.contextid = ctx.id AND r.itemid = dr.id AND r.component = :moddata + AND r.ratingarea = :ratingarea AND r.userid = :userid2) + ORDER BY cm.id, dr.id, dc.fieldid"; + $rs = $DB->get_recordset_sql($sql, $contextparams + ['contextlevel' => CONTEXT_MODULE, + 'modname' => 'data', 'userid' => $user->id, 'userid1' => $user->id, 'commentarea' => 'database_entry', + 'userid2' => $user->id, 'ratingarea' => 'entry', 'moddata' => 'mod_data']); + + $context = null; + $recordobj = null; + foreach ($rs as $row) { + if (!$context || $context->instanceid != $row->cmid) { + // This row belongs to the different data module than the previous row. + // Export the data for the previous module. + self::export_data($context, $user); + // Start new data module. + $context = \context_module::instance($row->cmid); + } + + if (!$recordobj || $row->recordid != $recordobj->id) { + // Export previous data record. + self::export_data_record($context, $user, $recordobj); + // Prepare for exporting new data record. + $recordobj = self::extract_object_from_record($row, 'record', ['dataid' => $row->dataid]); + } + $fieldobj = self::extract_object_from_record($row, 'field', ['dataid' => $row->dataid]); + $contentobj = self::extract_object_from_record($row, 'content', + ['fieldid' => $fieldobj->id, 'recordid' => $recordobj->id]); + self::export_data_content($context, $recordobj, $fieldobj, $contentobj); + } + $rs->close(); + self::export_data_record($context, $user, $recordobj); + self::export_data($context, $user); + } + + /** + * Export one entry in the database activity module (one record in {data_records} table) + * + * @param \context $context + * @param \stdClass $user + * @param \stdClass $recordobj + */ + protected static function export_data_record($context, $user, $recordobj) { + if (!$recordobj) { + return; + } + $data = [ + 'userid' => transform::user($user->id), + 'groupid' => $recordobj->groupid, + 'timecreated' => transform::datetime($recordobj->timecreated), + 'timemodified' => transform::datetime($recordobj->timemodified), + 'approved' => transform::yesno($recordobj->approved), + ]; + // Data about the record. + writer::with_context($context)->export_data([$recordobj->id], (object)$data); + // Related tags. + \core_tag\privacy\provider::export_item_tags($user->id, $context, [$recordobj->id], + 'mod_data', 'data_records', $recordobj->id); + // Export comments. For records that were not made by this user export only this user's comments, for own records + // export comments made by everybody. + \core_comment\privacy\provider::export_comments($context, 'mod_data', 'database_entry', $recordobj->id, + [$recordobj->id], $recordobj->userid != $user->id); + // Export ratings. For records that were not made by this user export only this user's ratings, for own records + // export ratings from everybody. + \core_rating\privacy\provider::export_area_ratings($user->id, $context, [$recordobj->id], 'entry', + $recordobj->id, $recordobj->userid != $user->id); + } + + /** + * Export basic info about database activity module + * + * @param \context $context + * @param \stdClass $user + */ + protected static function export_data($context, $user) { + if (!$context) { + return; + } + $contextdata = helper::get_context_data($context, $user); + helper::export_context_files($context, $user); + writer::with_context($context)->export_data([], $contextdata); + } + + /** + * Delete all data for all users in the specified context. + * + * @param \context $context the context to delete in. + */ + public static function delete_data_for_all_users_in_context(\context $context) { + global $DB; + + if (!$context instanceof \context_module) { + return; + } + + $sql = "SELECT " . self::sql_fields() . " + FROM {course_modules} cm + JOIN {modules} m ON m.id = cm.module AND m.name = :modname + JOIN {data} d ON d.id = cm.instance + JOIN {data_records} dr ON dr.dataid = d.id + LEFT JOIN {data_content} dc ON dc.recordid = dr.id + LEFT JOIN {data_fields} df ON df.id = dc.fieldid + WHERE cm.id = :cmid + ORDER BY dr.id"; + $rs = $DB->get_recordset_sql($sql, ['cmid' => $context->instanceid, 'modname' => 'data']); + foreach ($rs as $row) { + self::mark_data_content_for_deletion($context, $row); + } + $rs->close(); + + self::delete_data_records($context); + } + + /** + * Delete all user data for the specified user, in the specified contexts. + * + * @param approved_contextlist $contextlist a list of contexts approved for deletion. + */ + public static function delete_data_for_user(approved_contextlist $contextlist) { + global $DB; + + if (empty($contextlist->count())) { + return; + } + + $user = $contextlist->get_user(); + + foreach ($contextlist->get_contexts() as $context) { + $sql = "SELECT " . self::sql_fields() . " + FROM {context} ctx + JOIN {course_modules} cm ON cm.id = ctx.instanceid + JOIN {modules} m ON m.id = cm.module AND m.name = :modname + JOIN {data} d ON d.id = cm.instance + JOIN {data_records} dr ON dr.dataid = d.id AND dr.userid = :userid + LEFT JOIN {data_content} dc ON dc.recordid = dr.id + LEFT JOIN {data_fields} df ON df.id = dc.fieldid + WHERE ctx.id = :ctxid AND ctx.contextlevel = :contextlevel + ORDER BY dr.id"; + $rs = $DB->get_recordset_sql($sql, ['ctxid' => $context->id, 'contextlevel' => CONTEXT_MODULE, + 'modname' => 'data', 'userid' => $user->id]); + foreach ($rs as $row) { + self::mark_data_content_for_deletion($context, $row); + } + $rs->close(); + self::delete_data_records($context); + } + + // Additionally remove comments this user made on other entries. + \core_comment\privacy\provider::delete_comments_for_user($contextlist, 'mod_data', 'entry'); + + // We do not delete ratings made by this user on other records because it may change grades. + } + + /** + * Marks a data_record/data_content for deletion + * + * Also invokes callback from datafield plugin in case it stores additional data that needs to be deleted + * + * @param \context $context + * @param \stdClass $row result of SQL query - tables data_content, data_record, data_fields join together + */ + protected static function mark_data_content_for_deletion($context, $row) { + $recordobj = self::extract_object_from_record($row, 'record', ['dataid' => $row->dataid]); + if ($row->contentid && $row->fieldid) { + $fieldobj = self::extract_object_from_record($row, 'field', ['dataid' => $row->dataid]); + $contentobj = self::extract_object_from_record($row, 'content', + ['fieldid' => $fieldobj->id, 'recordid' => $recordobj->id]); + + // Allow datafield plugin to implement their own deletion. + $classname = manager::get_provider_classname_for_component('datafield_' . $fieldobj->type); + if (class_exists($classname) && is_subclass_of($classname, datafield_provider::class)) { + component_class_callback($classname, 'delete_data_content', + [$context, $recordobj, $fieldobj, $contentobj]); + } + } + + self::$deletedrecords[$recordobj->id] = $recordobj->id; + } + + /** + * Deletes records marked for deletion and all associated data + * + * Should be executed after all records were marked by {@link mark_data_content_for_deletion()} + * + * Deletes records from data_content and data_records tables, associated files, tags, comments and ratings. + * + * @param \context $context + */ + protected static function delete_data_records($context) { + global $DB; + if (empty(self::$deletedrecords)) { + return; + } + + list($sql, $params) = $DB->get_in_or_equal(self::$deletedrecords, SQL_PARAMS_NAMED); + + // Delete files. + get_file_storage()->delete_area_files_select($context->id, 'mod_data', 'data_records', + "IN (SELECT dc.id FROM {data_content} dc WHERE dc.recordid $sql)", $params); + // Delete from data_content. + $DB->delete_records_select('data_content', 'recordid ' . $sql, $params); + // Delete from data_records. + $DB->delete_records_select('data_records', 'id ' . $sql, $params); + // Delete tags. + \core_tag\privacy\provider::delete_item_tags_select($context, 'mod_data', 'data_records', $sql, $params); + // Delete comments. + \core_comment\privacy\provider::delete_comments_for_all_users_select($context, 'mod_data', 'entry', $sql, $params); + // Delete ratings. + \core_rating\privacy\provider::delete_ratings_select($context, 'mod_data', 'entry', $sql, $params); + + self::$deletedrecords = []; + } +} diff --git a/mod/data/field/checkbox/classes/privacy/provider.php b/mod/data/field/checkbox/classes/privacy/provider.php index 2fb2b3eecd4b7..3da14eb09b950 100644 --- a/mod/data/field/checkbox/classes/privacy/provider.php +++ b/mod/data/field/checkbox/classes/privacy/provider.php @@ -21,6 +21,9 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace datafield_checkbox\privacy; +use core_privacy\local\request\writer; +use mod_data\privacy\datafield_provider; + defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for datafield_checkbox implementing null_provider. @@ -28,7 +31,8 @@ * @copyright 2018 Carlos Escobedo * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class provider implements \core_privacy\local\metadata\null_provider { +class provider implements \core_privacy\local\metadata\null_provider, + datafield_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. @@ -38,4 +42,31 @@ class provider implements \core_privacy\local\metadata\null_provider { public static function get_reason() : string { return 'privacy:metadata'; } + + /** + * Exports data about one record in {data_content} table. + * + * @param \context_module $context + * @param \stdClass $recordobj record from DB table {data_records} + * @param \stdClass $fieldobj record from DB table {data_fields} + * @param \stdClass $contentobj record from DB table {data_content} + * @param \stdClass $defaultvalue pre-populated default value that most of plugins will use + */ + public static function export_data_content($context, $recordobj, $fieldobj, $contentobj, $defaultvalue) { + $defaultvalue->field['options'] = preg_split('/\s*\n\s*/', trim($fieldobj->param1), -1, PREG_SPLIT_NO_EMPTY); + $defaultvalue->content = explode('##', $defaultvalue->content); + writer::with_context($context)->export_data([$recordobj->id, $contentobj->id], $defaultvalue); + } + + /** + * Allows plugins to delete locally stored data. + * + * @param \context_module $context + * @param \stdClass $recordobj record from DB table {data_records} + * @param \stdClass $fieldobj record from DB table {data_fields} + * @param \stdClass $contentobj record from DB table {data_content} + */ + public static function delete_data_content($context, $recordobj, $fieldobj, $contentobj) { + + } } \ No newline at end of file diff --git a/mod/data/field/checkbox/lang/en/datafield_checkbox.php b/mod/data/field/checkbox/lang/en/datafield_checkbox.php index dedc533a5d21e..6c2d8bf27b6b6 100644 --- a/mod/data/field/checkbox/lang/en/datafield_checkbox.php +++ b/mod/data/field/checkbox/lang/en/datafield_checkbox.php @@ -26,4 +26,4 @@ $string['pluginname'] = 'Checkbox'; $string['fieldtypelabel'] = 'Checkbox field'; -$string['privacy:metadata'] = 'The Checkbox field component does not store any personal data.'; +$string['privacy:metadata'] = 'The Checkbox field component does not store any personal data itself, it uses tables defined in mod_data.'; diff --git a/mod/data/field/date/classes/privacy/provider.php b/mod/data/field/date/classes/privacy/provider.php index 9000c226b6094..478ac0d6199f8 100644 --- a/mod/data/field/date/classes/privacy/provider.php +++ b/mod/data/field/date/classes/privacy/provider.php @@ -21,6 +21,10 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace datafield_date\privacy; +use core_privacy\local\request\transform; +use core_privacy\local\request\writer; +use mod_data\privacy\datafield_provider; + defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for datafield_date implementing null_provider. @@ -28,7 +32,8 @@ * @copyright 2018 Carlos Escobedo * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class provider implements \core_privacy\local\metadata\null_provider { +class provider implements \core_privacy\local\metadata\null_provider, + datafield_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. @@ -38,4 +43,30 @@ class provider implements \core_privacy\local\metadata\null_provider { public static function get_reason() : string { return 'privacy:metadata'; } + + /** + * Exports data about one record in {data_content} table. + * + * @param \context_module $context + * @param \stdClass $recordobj record from DB table {data_records} + * @param \stdClass $fieldobj record from DB table {data_fields} + * @param \stdClass $contentobj record from DB table {data_content} + * @param \stdClass $defaultvalue pre-populated default value that most of plugins will use + */ + public static function export_data_content($context, $recordobj, $fieldobj, $contentobj, $defaultvalue) { + $defaultvalue->content = transform::date($defaultvalue->content); + writer::with_context($context)->export_data([$recordobj->id, $contentobj->id], $defaultvalue); + } + + /** + * Allows plugins to delete locally stored data. + * + * @param \context_module $context + * @param \stdClass $recordobj record from DB table {data_records} + * @param \stdClass $fieldobj record from DB table {data_fields} + * @param \stdClass $contentobj record from DB table {data_content} + */ + public static function delete_data_content($context, $recordobj, $fieldobj, $contentobj) { + + } } \ No newline at end of file diff --git a/mod/data/field/date/lang/en/datafield_date.php b/mod/data/field/date/lang/en/datafield_date.php index 031c221b2156b..304ac62e50abc 100644 --- a/mod/data/field/date/lang/en/datafield_date.php +++ b/mod/data/field/date/lang/en/datafield_date.php @@ -26,4 +26,4 @@ $string['pluginname'] = 'Date'; $string['fieldtypelabel'] = 'Date field'; -$string['privacy:metadata'] = 'The Date field component does not store any personal data.'; +$string['privacy:metadata'] = 'The Date field component does not store any personal data, it uses tables defined in mod_data.'; diff --git a/mod/data/field/file/classes/privacy/provider.php b/mod/data/field/file/classes/privacy/provider.php index 334c78fb1c193..ebb757dfe93ae 100644 --- a/mod/data/field/file/classes/privacy/provider.php +++ b/mod/data/field/file/classes/privacy/provider.php @@ -21,6 +21,9 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace datafield_file\privacy; +use mod_data\privacy\datafield_provider; +use core_privacy\local\request\writer; + defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for datafield_file implementing null_provider. @@ -28,7 +31,8 @@ * @copyright 2018 Carlos Escobedo * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class provider implements \core_privacy\local\metadata\null_provider { +class provider implements \core_privacy\local\metadata\null_provider, + datafield_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. @@ -38,4 +42,37 @@ class provider implements \core_privacy\local\metadata\null_provider { public static function get_reason() : string { return 'privacy:metadata'; } + + /** + * Exports data about one record in {data_content} table. + * + * @param \context_module $context + * @param \stdClass $recordobj record from DB table {data_records} + * @param \stdClass $fieldobj record from DB table {data_fields} + * @param \stdClass $contentobj record from DB table {data_content} + * @param \stdClass $defaultvalue pre-populated default value that most of plugins will use + */ + public static function export_data_content($context, $recordobj, $fieldobj, $contentobj, $defaultvalue) { + if ($fieldobj->param3) { + $defaultvalue->field['maxbytes'] = $fieldobj->param3; + } + // Change file name to file path. + $defaultvalue->file = writer::with_context($context) + ->rewrite_pluginfile_urls([$recordobj->id, $contentobj->id], 'mod_data', 'content', $contentobj->id, + '@@PLUGINFILE@@/' . $defaultvalue->content); + unset($defaultvalue->content); + writer::with_context($context)->export_data([$recordobj->id, $contentobj->id], $defaultvalue); + } + + /** + * Allows plugins to delete locally stored data. + * + * @param \context_module $context + * @param \stdClass $recordobj record from DB table {data_records} + * @param \stdClass $fieldobj record from DB table {data_fields} + * @param \stdClass $contentobj record from DB table {data_content} + */ + public static function delete_data_content($context, $recordobj, $fieldobj, $contentobj) { + + } } \ No newline at end of file diff --git a/mod/data/field/file/lang/en/datafield_file.php b/mod/data/field/file/lang/en/datafield_file.php index 43bb0b54dae27..4aaa5ba23bba3 100644 --- a/mod/data/field/file/lang/en/datafield_file.php +++ b/mod/data/field/file/lang/en/datafield_file.php @@ -26,4 +26,4 @@ $string['pluginname'] = 'File'; $string['fieldtypelabel'] = 'File field'; -$string['privacy:metadata'] = 'The File field component does not store any personal data.'; +$string['privacy:metadata'] = 'The File field component does not store any personal data, it uses tables defined in mod_data.'; diff --git a/mod/data/field/latlong/classes/privacy/provider.php b/mod/data/field/latlong/classes/privacy/provider.php index 693a3b8259a68..9c7013e87a2f1 100644 --- a/mod/data/field/latlong/classes/privacy/provider.php +++ b/mod/data/field/latlong/classes/privacy/provider.php @@ -21,6 +21,9 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace datafield_latlong\privacy; +use core_privacy\local\request\writer; +use mod_data\privacy\datafield_provider; + defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for datafield_latlong implementing null_provider. @@ -28,7 +31,8 @@ * @copyright 2018 Carlos Escobedo * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class provider implements \core_privacy\local\metadata\null_provider { +class provider implements \core_privacy\local\metadata\null_provider, + datafield_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. @@ -38,4 +42,41 @@ class provider implements \core_privacy\local\metadata\null_provider { public static function get_reason() : string { return 'privacy:metadata'; } + + /** + * Exports data about one record in {data_content} table. + * + * @param \context_module $context + * @param \stdClass $recordobj record from DB table {data_records} + * @param \stdClass $fieldobj record from DB table {data_fields} + * @param \stdClass $contentobj record from DB table {data_content} + * @param \stdClass $defaultvalue pre-populated default value that most of plugins will use + */ + public static function export_data_content($context, $recordobj, $fieldobj, $contentobj, $defaultvalue) { + $defaultvalue->field['services'] = explode(',', $fieldobj->param1); + if ($fieldobj->param2 > 0) { + $defaultvalue->field['label'] = 'Content of field '.$fieldobj->param2; + } else if ($fieldobj->param2 == -2) { + $defaultvalue->field['label'] = 'lattitude/longitude'; + } else { + $defaultvalue->field['label'] = 'item #'; + } + $defaultvalue->lattitude = $contentobj->content; + $defaultvalue->longitude = $contentobj->content1; + unset($defaultvalue->content); + unset($defaultvalue->content1); + writer::with_context($context)->export_data([$recordobj->id, $contentobj->id], $defaultvalue); + } + + /** + * Allows plugins to delete locally stored data. + * + * @param \context_module $context + * @param \stdClass $recordobj record from DB table {data_records} + * @param \stdClass $fieldobj record from DB table {data_fields} + * @param \stdClass $contentobj record from DB table {data_content} + */ + public static function delete_data_content($context, $recordobj, $fieldobj, $contentobj) { + + } } \ No newline at end of file diff --git a/mod/data/field/latlong/lang/en/datafield_latlong.php b/mod/data/field/latlong/lang/en/datafield_latlong.php index 39aca19cfa24c..a3a7a33ed9a5e 100644 --- a/mod/data/field/latlong/lang/en/datafield_latlong.php +++ b/mod/data/field/latlong/lang/en/datafield_latlong.php @@ -26,4 +26,4 @@ $string['pluginname'] = 'Latlong'; $string['fieldtypelabel'] = 'Latitude/longitude field'; -$string['privacy:metadata'] = 'The Latitude/longitude field component does not store any personal data.'; +$string['privacy:metadata'] = 'The Latitude/longitude field component does not store any personal data, it uses tables defined in mod_data.'; diff --git a/mod/data/field/menu/classes/privacy/provider.php b/mod/data/field/menu/classes/privacy/provider.php index 549a3f120ecfe..08c419ad4a2a9 100644 --- a/mod/data/field/menu/classes/privacy/provider.php +++ b/mod/data/field/menu/classes/privacy/provider.php @@ -21,6 +21,9 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace datafield_menu\privacy; +use core_privacy\local\request\writer; +use mod_data\privacy\datafield_provider; + defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for datafield_menu implementing null_provider. @@ -28,7 +31,8 @@ * @copyright 2018 Carlos Escobedo * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class provider implements \core_privacy\local\metadata\null_provider { +class provider implements \core_privacy\local\metadata\null_provider, + datafield_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. @@ -38,4 +42,30 @@ class provider implements \core_privacy\local\metadata\null_provider { public static function get_reason() : string { return 'privacy:metadata'; } + + /** + * Exports data about one record in {data_content} table. + * + * @param \context_module $context + * @param \stdClass $recordobj record from DB table {data_records} + * @param \stdClass $fieldobj record from DB table {data_fields} + * @param \stdClass $contentobj record from DB table {data_content} + * @param \stdClass $defaultvalue pre-populated default value that most of plugins will use + */ + public static function export_data_content($context, $recordobj, $fieldobj, $contentobj, $defaultvalue) { + $defaultvalue->field['options'] = preg_split('/\s*\n\s*/', trim($fieldobj->param1), -1, PREG_SPLIT_NO_EMPTY); + writer::with_context($context)->export_data([$recordobj->id, $contentobj->id], $defaultvalue); + } + + /** + * Allows plugins to delete locally stored data. + * + * @param \context_module $context + * @param \stdClass $recordobj record from DB table {data_records} + * @param \stdClass $fieldobj record from DB table {data_fields} + * @param \stdClass $contentobj record from DB table {data_content} + */ + public static function delete_data_content($context, $recordobj, $fieldobj, $contentobj) { + + } } \ No newline at end of file diff --git a/mod/data/field/menu/lang/en/datafield_menu.php b/mod/data/field/menu/lang/en/datafield_menu.php index 04e2b99dbb45e..c6ebec58d8de4 100644 --- a/mod/data/field/menu/lang/en/datafield_menu.php +++ b/mod/data/field/menu/lang/en/datafield_menu.php @@ -26,4 +26,4 @@ $string['pluginname'] = 'Menu'; $string['fieldtypelabel'] = 'Menu field'; -$string['privacy:metadata'] = 'The Menu field component does not store any personal data.'; +$string['privacy:metadata'] = 'The Menu field component does not store any personal data, it uses tables defined in mod_data.'; diff --git a/mod/data/field/multimenu/classes/privacy/provider.php b/mod/data/field/multimenu/classes/privacy/provider.php index 7e2a8143d1f1e..42f109e4dcf42 100644 --- a/mod/data/field/multimenu/classes/privacy/provider.php +++ b/mod/data/field/multimenu/classes/privacy/provider.php @@ -21,6 +21,9 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace datafield_multimenu\privacy; +use core_privacy\local\request\writer; +use mod_data\privacy\datafield_provider; + defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for datafield_multimenu implementing null_provider. @@ -28,7 +31,8 @@ * @copyright 2018 Carlos Escobedo * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class provider implements \core_privacy\local\metadata\null_provider { +class provider implements \core_privacy\local\metadata\null_provider, + datafield_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. @@ -38,4 +42,34 @@ class provider implements \core_privacy\local\metadata\null_provider { public static function get_reason() : string { return 'privacy:metadata'; } + + /** + * Exports data about one record in {data_content} table. + * + * @param \context_module $context + * @param \stdClass $recordobj record from DB table {data_records} + * @param \stdClass $fieldobj record from DB table {data_fields} + * @param \stdClass $contentobj record from DB table {data_content} + * @param \stdClass $defaultvalue pre-populated default value that most of plugins will use + */ + public static function export_data_content($context, $recordobj, $fieldobj, $contentobj, $defaultvalue) { + $defaultvalue->field['options'] = preg_split('/\s*\n\s*/', trim($fieldobj->param1), -1, PREG_SPLIT_NO_EMPTY); + for ($i = 1; $i <= 10; $i++) { + unset($defaultvalue->field['param' . $i]); + } + $defaultvalue->content = explode('##', $defaultvalue->content); + writer::with_context($context)->export_data([$recordobj->id, $contentobj->id], $defaultvalue); + } + + /** + * Allows plugins to delete locally stored data. + * + * @param \context_module $context + * @param \stdClass $recordobj record from DB table {data_records} + * @param \stdClass $fieldobj record from DB table {data_fields} + * @param \stdClass $contentobj record from DB table {data_content} + */ + public static function delete_data_content($context, $recordobj, $fieldobj, $contentobj) { + + } } \ No newline at end of file diff --git a/mod/data/field/multimenu/lang/en/datafield_multimenu.php b/mod/data/field/multimenu/lang/en/datafield_multimenu.php index 6c302852e6fe8..847a49e2a1d34 100644 --- a/mod/data/field/multimenu/lang/en/datafield_multimenu.php +++ b/mod/data/field/multimenu/lang/en/datafield_multimenu.php @@ -26,4 +26,4 @@ $string['pluginname'] = 'Multimenu'; $string['fieldtypelabel'] = 'Multiple-selection menu field'; -$string['privacy:metadata'] = 'The Multiple-selection menu field component does not store any personal data.'; +$string['privacy:metadata'] = 'The Multiple-selection menu field component does not store any personal data, it uses tables defined in mod_data.'; diff --git a/mod/data/field/number/classes/privacy/provider.php b/mod/data/field/number/classes/privacy/provider.php index 90d2a76a92fe8..c7a2a7361fa95 100644 --- a/mod/data/field/number/classes/privacy/provider.php +++ b/mod/data/field/number/classes/privacy/provider.php @@ -21,6 +21,9 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace datafield_number\privacy; +use core_privacy\local\request\writer; +use mod_data\privacy\datafield_provider; + defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for datafield_number implementing null_provider. @@ -28,7 +31,8 @@ * @copyright 2018 Carlos Escobedo * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class provider implements \core_privacy\local\metadata\null_provider { +class provider implements \core_privacy\local\metadata\null_provider, + datafield_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. @@ -38,4 +42,32 @@ class provider implements \core_privacy\local\metadata\null_provider { public static function get_reason() : string { return 'privacy:metadata'; } + + /** + * Exports data about one record in {data_content} table. + * + * @param \context_module $context + * @param \stdClass $recordobj record from DB table {data_records} + * @param \stdClass $fieldobj record from DB table {data_fields} + * @param \stdClass $contentobj record from DB table {data_content} + * @param \stdClass $defaultvalue pre-populated default value that most of plugins will use + */ + public static function export_data_content($context, $recordobj, $fieldobj, $contentobj, $defaultvalue) { + if (preg_match("/^\d+$/", trim($fieldobj->param1))) { + $defaultvalue->field['decimals'] = trim($fieldobj->param1); + } + writer::with_context($context)->export_data([$recordobj->id, $contentobj->id], $defaultvalue); + } + + /** + * Allows plugins to delete locally stored data. + * + * @param \context_module $context + * @param \stdClass $recordobj record from DB table {data_records} + * @param \stdClass $fieldobj record from DB table {data_fields} + * @param \stdClass $contentobj record from DB table {data_content} + */ + public static function delete_data_content($context, $recordobj, $fieldobj, $contentobj) { + + } } \ No newline at end of file diff --git a/mod/data/field/number/lang/en/datafield_number.php b/mod/data/field/number/lang/en/datafield_number.php index a6f4f3556086e..27659f38f5a0b 100644 --- a/mod/data/field/number/lang/en/datafield_number.php +++ b/mod/data/field/number/lang/en/datafield_number.php @@ -26,4 +26,4 @@ $string['pluginname'] = 'Number'; $string['fieldtypelabel'] = 'Number field'; -$string['privacy:metadata'] = 'The Number field component does not store any personal data.'; +$string['privacy:metadata'] = 'The Number field component does not store any personal data, it uses tables defined in mod_data.'; diff --git a/mod/data/field/picture/classes/privacy/provider.php b/mod/data/field/picture/classes/privacy/provider.php index 4fcf73358143c..d28fcdb7a37df 100644 --- a/mod/data/field/picture/classes/privacy/provider.php +++ b/mod/data/field/picture/classes/privacy/provider.php @@ -21,6 +21,9 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace datafield_picture\privacy; +use core_privacy\local\request\writer; +use mod_data\privacy\datafield_provider; + defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for datafield_picture implementing null_provider. @@ -28,7 +31,8 @@ * @copyright 2018 Carlos Escobedo * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class provider implements \core_privacy\local\metadata\null_provider { +class provider implements \core_privacy\local\metadata\null_provider, + datafield_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. @@ -38,4 +42,48 @@ class provider implements \core_privacy\local\metadata\null_provider { public static function get_reason() : string { return 'privacy:metadata'; } + + /** + * Exports data about one record in {data_content} table. + * + * @param \context_module $context + * @param \stdClass $recordobj record from DB table {data_records} + * @param \stdClass $fieldobj record from DB table {data_fields} + * @param \stdClass $contentobj record from DB table {data_content} + * @param \stdClass $defaultvalue pre-populated default value that most of plugins will use + */ + public static function export_data_content($context, $recordobj, $fieldobj, $contentobj, $defaultvalue) { + if ($fieldobj->param1) { + $defaultvalue->field['width'] = $fieldobj->param1; + } + if ($fieldobj->param2) { + $defaultvalue->field['height'] = $fieldobj->param2; + } + if ($fieldobj->param3) { + $defaultvalue->field['maxbytes'] = $fieldobj->param3; + } + + // Change file name to file path. + $defaultvalue->file = writer::with_context($context) + ->rewrite_pluginfile_urls([$recordobj->id, $contentobj->id], 'mod_data', 'content', $contentobj->id, + '@@PLUGINFILE@@/' . $defaultvalue->content); + if (isset($defaultvalue->content1)) { + $defaultvalue->alttext = $defaultvalue->content1; + } + unset($defaultvalue->content); + unset($defaultvalue->content1); + writer::with_context($context)->export_data([$recordobj->id, $contentobj->id], $defaultvalue); + } + + /** + * Allows plugins to delete locally stored data. + * + * @param \context_module $context + * @param \stdClass $recordobj record from DB table {data_records} + * @param \stdClass $fieldobj record from DB table {data_fields} + * @param \stdClass $contentobj record from DB table {data_content} + */ + public static function delete_data_content($context, $recordobj, $fieldobj, $contentobj) { + + } } \ No newline at end of file diff --git a/mod/data/field/picture/lang/en/datafield_picture.php b/mod/data/field/picture/lang/en/datafield_picture.php index c22d1fabeb763..ea29945adfecd 100644 --- a/mod/data/field/picture/lang/en/datafield_picture.php +++ b/mod/data/field/picture/lang/en/datafield_picture.php @@ -26,4 +26,4 @@ $string['pluginname'] = 'Picture'; $string['fieldtypelabel'] = 'Picture field'; -$string['privacy:metadata'] = 'The Picture field component does not store any personal data.'; +$string['privacy:metadata'] = 'The Picture field component does not store any personal data, it uses tables defined in mod_data.'; diff --git a/mod/data/field/radiobutton/classes/privacy/provider.php b/mod/data/field/radiobutton/classes/privacy/provider.php index e4403d003b178..25926ba151a31 100644 --- a/mod/data/field/radiobutton/classes/privacy/provider.php +++ b/mod/data/field/radiobutton/classes/privacy/provider.php @@ -21,6 +21,9 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace datafield_radiobutton\privacy; +use core_privacy\local\request\writer; +use mod_data\privacy\datafield_provider; + defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for datafield_radiobutton implementing null_provider. @@ -28,7 +31,8 @@ * @copyright 2018 Carlos Escobedo * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class provider implements \core_privacy\local\metadata\null_provider { +class provider implements \core_privacy\local\metadata\null_provider, + datafield_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. @@ -38,4 +42,30 @@ class provider implements \core_privacy\local\metadata\null_provider { public static function get_reason() : string { return 'privacy:metadata'; } + + /** + * Exports data about one record in {data_content} table. + * + * @param \context_module $context + * @param \stdClass $recordobj record from DB table {data_records} + * @param \stdClass $fieldobj record from DB table {data_fields} + * @param \stdClass $contentobj record from DB table {data_content} + * @param \stdClass $defaultvalue pre-populated default value that most of plugins will use + */ + public static function export_data_content($context, $recordobj, $fieldobj, $contentobj, $defaultvalue) { + $defaultvalue->field['options'] = preg_split('/\s*\n\s*/', trim($fieldobj->param1), -1, PREG_SPLIT_NO_EMPTY); + writer::with_context($context)->export_data([$recordobj->id, $contentobj->id], $defaultvalue); + } + + /** + * Allows plugins to delete locally stored data. + * + * @param \context_module $context + * @param \stdClass $recordobj record from DB table {data_records} + * @param \stdClass $fieldobj record from DB table {data_fields} + * @param \stdClass $contentobj record from DB table {data_content} + */ + public static function delete_data_content($context, $recordobj, $fieldobj, $contentobj) { + + } } \ No newline at end of file diff --git a/mod/data/field/radiobutton/lang/en/datafield_radiobutton.php b/mod/data/field/radiobutton/lang/en/datafield_radiobutton.php index a37c4f7cf3c37..b20b158db9732 100644 --- a/mod/data/field/radiobutton/lang/en/datafield_radiobutton.php +++ b/mod/data/field/radiobutton/lang/en/datafield_radiobutton.php @@ -26,4 +26,4 @@ $string['pluginname'] = 'Radio button'; $string['fieldtypelabel'] = 'Radio button field'; -$string['privacy:metadata'] = 'The Radio button field component does not store any personal data.'; +$string['privacy:metadata'] = 'The Radio button field component does not store any personal data, it uses tables defined in mod_data.'; diff --git a/mod/data/field/text/classes/privacy/provider.php b/mod/data/field/text/classes/privacy/provider.php index b010937f470a0..923e517b2e7c6 100644 --- a/mod/data/field/text/classes/privacy/provider.php +++ b/mod/data/field/text/classes/privacy/provider.php @@ -21,6 +21,9 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace datafield_text\privacy; +use core_privacy\local\request\writer; +use mod_data\privacy\datafield_provider; + defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for datafield_text implementing null_provider. @@ -28,7 +31,8 @@ * @copyright 2018 Carlos Escobedo * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class provider implements \core_privacy\local\metadata\null_provider { +class provider implements \core_privacy\local\metadata\null_provider, + datafield_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. @@ -38,4 +42,29 @@ class provider implements \core_privacy\local\metadata\null_provider { public static function get_reason() : string { return 'privacy:metadata'; } + + /** + * Exports data about one record in {data_content} table. + * + * @param \context_module $context + * @param \stdClass $recordobj record from DB table {data_records} + * @param \stdClass $fieldobj record from DB table {data_fields} + * @param \stdClass $contentobj record from DB table {data_content} + * @param \stdClass $defaultvalue pre-populated default value that most of plugins will use + */ + public static function export_data_content($context, $recordobj, $fieldobj, $contentobj, $defaultvalue) { + writer::with_context($context)->export_data([$recordobj->id, $contentobj->id], $defaultvalue); + } + + /** + * Allows plugins to delete locally stored data. + * + * @param \context_module $context + * @param \stdClass $recordobj record from DB table {data_records} + * @param \stdClass $fieldobj record from DB table {data_fields} + * @param \stdClass $contentobj record from DB table {data_content} + */ + public static function delete_data_content($context, $recordobj, $fieldobj, $contentobj) { + + } } \ No newline at end of file diff --git a/mod/data/field/text/lang/en/datafield_text.php b/mod/data/field/text/lang/en/datafield_text.php index 7b9c68ee186c9..b4e3c2d36318b 100644 --- a/mod/data/field/text/lang/en/datafield_text.php +++ b/mod/data/field/text/lang/en/datafield_text.php @@ -26,4 +26,4 @@ $string['pluginname'] = 'Text input'; $string['fieldtypelabel'] = 'Text field'; -$string['privacy:metadata'] = 'The Text field component does not store any personal data.'; +$string['privacy:metadata'] = 'The Text field component does not store any personal data, it uses tables defined in mod_data.'; diff --git a/mod/data/field/textarea/classes/privacy/provider.php b/mod/data/field/textarea/classes/privacy/provider.php index f0a997a48b946..4ad607fe540c0 100644 --- a/mod/data/field/textarea/classes/privacy/provider.php +++ b/mod/data/field/textarea/classes/privacy/provider.php @@ -21,6 +21,11 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace datafield_textarea\privacy; + +use core_privacy\local\request\transform; +use core_privacy\local\request\writer; +use mod_data\privacy\datafield_provider; + defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for datafield_textarea implementing null_provider. @@ -28,7 +33,8 @@ * @copyright 2018 Carlos Escobedo * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class provider implements \core_privacy\local\metadata\null_provider { +class provider implements \core_privacy\local\metadata\null_provider, + datafield_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. @@ -38,4 +44,42 @@ class provider implements \core_privacy\local\metadata\null_provider { public static function get_reason() : string { return 'privacy:metadata'; } + + /** + * Exports data about one record in {data_content} table. + * + * @param \context_module $context + * @param \stdClass $recordobj record from DB table {data_records} + * @param \stdClass $fieldobj record from DB table {data_fields} + * @param \stdClass $contentobj record from DB table {data_content} + * @param \stdClass $defaultvalue pre-populated default value that most of plugins will use + */ + public static function export_data_content($context, $recordobj, $fieldobj, $contentobj, $defaultvalue) { + $subcontext = [$recordobj->id, $contentobj->id]; + $defaultvalue->content = writer::with_context($context) + ->rewrite_pluginfile_urls($subcontext, 'mod_data', 'content', $contentobj->id, + $defaultvalue->content); + $defaultvalue->contentformat = $defaultvalue->content1; + unset($defaultvalue->content1); + + $defaultvalue->field['autolink'] = transform::yesno($fieldobj->param1); + $defaultvalue->field['rows'] = $fieldobj->param3; + $defaultvalue->field['cols'] = $fieldobj->param2; + if ($fieldobj->param5) { + $defaultvalue->field['maxbytes'] = $fieldobj->param5; + } + writer::with_context($context)->export_data($subcontext, $defaultvalue); + } + + /** + * Allows plugins to delete locally stored data. + * + * @param \context_module $context + * @param \stdClass $recordobj record from DB table {data_records} + * @param \stdClass $fieldobj record from DB table {data_fields} + * @param \stdClass $contentobj record from DB table {data_content} + */ + public static function delete_data_content($context, $recordobj, $fieldobj, $contentobj) { + + } } \ No newline at end of file diff --git a/mod/data/field/textarea/lang/en/datafield_textarea.php b/mod/data/field/textarea/lang/en/datafield_textarea.php index 4f391444aea8a..00a3785163aa1 100644 --- a/mod/data/field/textarea/lang/en/datafield_textarea.php +++ b/mod/data/field/textarea/lang/en/datafield_textarea.php @@ -28,4 +28,4 @@ $string['maxbytes_desc'] = 'If set to zero will be unlimited by default'; $string['pluginname'] = 'Text area'; $string['fieldtypelabel'] = 'Textarea field'; -$string['privacy:metadata'] = 'The Textarea field component does not store any personal data.'; +$string['privacy:metadata'] = 'The Textarea field component does not store any personal data, it uses tables defined in mod_data.'; diff --git a/mod/data/field/url/classes/privacy/provider.php b/mod/data/field/url/classes/privacy/provider.php index 7a8c3efc9d4ae..75aeb56e4e5df 100644 --- a/mod/data/field/url/classes/privacy/provider.php +++ b/mod/data/field/url/classes/privacy/provider.php @@ -21,6 +21,10 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace datafield_url\privacy; +use core_privacy\local\request\transform; +use core_privacy\local\request\writer; +use mod_data\privacy\datafield_provider; + defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for datafield_url implementing null_provider. @@ -28,7 +32,8 @@ * @copyright 2018 Carlos Escobedo * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class provider implements \core_privacy\local\metadata\null_provider { +class provider implements \core_privacy\local\metadata\null_provider, + datafield_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. @@ -38,4 +43,38 @@ class provider implements \core_privacy\local\metadata\null_provider { public static function get_reason() : string { return 'privacy:metadata'; } + + /** + * Exports data about one record in {data_content} table. + * + * @param \context_module $context + * @param \stdClass $recordobj record from DB table {data_records} + * @param \stdClass $fieldobj record from DB table {data_fields} + * @param \stdClass $contentobj record from DB table {data_content} + * @param \stdClass $defaultvalue pre-populated default value that most of plugins will use + */ + public static function export_data_content($context, $recordobj, $fieldobj, $contentobj, $defaultvalue) { + $defaultvalue->field['autolink'] = transform::yesno($fieldobj->param1); + if (!empty($fieldobj->param2)) { + $defaultvalue->field['forcetext'] = $fieldobj->param2; + } + $defaultvalue->field['blanktarget'] = transform::yesno($fieldobj->param3); + $defaultvalue->url = $contentobj->content; + $defaultvalue->text = $contentobj->content1; + unset($defaultvalue->content); + unset($defaultvalue->content1); + writer::with_context($context)->export_data([$recordobj->id, $contentobj->id], $defaultvalue); + } + + /** + * Allows plugins to delete locally stored data. + * + * @param \context_module $context + * @param \stdClass $recordobj record from DB table {data_records} + * @param \stdClass $fieldobj record from DB table {data_fields} + * @param \stdClass $contentobj record from DB table {data_content} + */ + public static function delete_data_content($context, $recordobj, $fieldobj, $contentobj) { + + } } \ No newline at end of file diff --git a/mod/data/field/url/lang/en/datafield_url.php b/mod/data/field/url/lang/en/datafield_url.php index 0aff7a57c40a7..b17b690501779 100644 --- a/mod/data/field/url/lang/en/datafield_url.php +++ b/mod/data/field/url/lang/en/datafield_url.php @@ -27,4 +27,4 @@ $string['pluginname'] = 'URL'; $string['openlinkinnewwindow'] = 'Open link in new window'; $string['fieldtypelabel'] = 'URL field'; -$string['privacy:metadata'] = 'The URL field component does not store any personal data.'; +$string['privacy:metadata'] = 'The URL field component does not store any personal data, it uses tables defined in mod_data.'; diff --git a/mod/data/lang/en/data.php b/mod/data/lang/en/data.php index 3e6851fd82f22..f6f186f4357d7 100644 --- a/mod/data/lang/en/data.php +++ b/mod/data/lang/en/data.php @@ -300,6 +300,24 @@ $string['portfolionotfile'] = 'Export to a portfolio rather than a file (csv and leap2a only)'; $string['presetinfo'] = 'Saving as a preset will publish this template. Other users may be able to use it in their databases.'; $string['presets'] = 'Presets'; +$string['privacy:metadata:commentpurpose'] = 'Comments on database records'; +$string['privacy:metadata:data_content'] = 'Represents one answer to one field in database activity module'; +$string['privacy:metadata:data_content:fieldid'] = 'Field definition id'; +$string['privacy:metadata:data_content:content'] = 'Content'; +$string['privacy:metadata:data_content:content1'] = 'Additional content 1'; +$string['privacy:metadata:data_content:content2'] = 'Additional content 2'; +$string['privacy:metadata:data_content:content3'] = 'Additional content 3'; +$string['privacy:metadata:data_content:content4'] = 'Additional content 4'; +$string['privacy:metadata:data_records'] = 'Represent records in database acitivity module'; +$string['privacy:metadata:data_records:userid'] = 'User who created the record'; +$string['privacy:metadata:data_records:groupid'] = 'Group'; +$string['privacy:metadata:data_records:timecreated'] = 'Time when record was created'; +$string['privacy:metadata:data_records:timemodified'] = 'Time when record was last modified'; +$string['privacy:metadata:data_records:approved'] = 'Approval status'; +$string['privacy:metadata:datafieldnpluginsummary'] = 'Fields for database activity module'; +$string['privacy:metadata:filepurpose'] = 'File attached to the database record'; +$string['privacy:metadata:tagpurpose'] = 'Tags on database records'; +$string['privacy:metadata:ratingpurpose'] = 'Ratings on database records'; $string['radiobutton'] = 'Radio buttons'; $string['recordapproved'] = 'Entry approved'; $string['recorddeleted'] = 'Entry deleted'; diff --git a/mod/data/tests/privacy_provider_test.php b/mod/data/tests/privacy_provider_test.php new file mode 100644 index 0000000000000..c85c99477105f --- /dev/null +++ b/mod/data/tests/privacy_provider_test.php @@ -0,0 +1,243 @@ +. + +/** + * Privacy provider tests. + * + * @package mod_data + * @copyright 2018 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use core_privacy\local\metadata\collection; +use mod_data\privacy\provider; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Privacy provider tests class. + * + * @package mod_data + * @copyright 2018 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_data_privacy_provider_testcase extends \core_privacy\tests\provider_testcase { + /** @var stdClass The student object. */ + protected $student; + /** @var stdClass The student object. */ + protected $student2; + + /** @var stdClass The data object. */ + protected $datamodule; + + /** @var stdClass The course object. */ + protected $course; + + /** + * {@inheritdoc} + */ + protected function setUp() { + $this->resetAfterTest(); + + global $DB; + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + $params = [ + 'course' => $course->id, + 'name' => 'Database module', + 'comments' => 1, + 'assessed' => 1, + ]; + + // The database activity. + $datamodule = $this->get_generator()->create_instance($params); + + $fieldtypes = array('checkbox', 'date', 'menu', 'multimenu', 'number', 'radiobutton', 'text', 'textarea', 'url', + 'latlong', 'file', 'picture'); + // Creating test Fields with default parameter values. + foreach ($fieldtypes as $count => $fieldtype) { + // Creating variables dynamically. + $fieldname = 'field' . $count; + $record = new \stdClass(); + $record->name = $fieldname; + $record->description = $fieldname . ' descr'; + $record->type = $fieldtype; + + ${$fieldname} = $this->get_generator()->create_field($record, $datamodule); + } + + $cm = get_coursemodule_from_instance('data', $datamodule->id); + + // Create a student. + $student1 = $generator->create_user(); + $student2 = $generator->create_user(); + $studentrole = $DB->get_record('role', ['shortname' => 'student']); + $generator->enrol_user($student1->id, $course->id, $studentrole->id); + $generator->enrol_user($student2->id, $course->id, $studentrole->id); + + // Add records. + $this->setUser($student1); + $record1id = $this->generate_data_record($datamodule); + $this->generate_data_record($datamodule); + + $this->setUser($student2); + $this->generate_data_record($datamodule); + $this->generate_data_record($datamodule); + $this->generate_data_record($datamodule); + + $this->student = $student1; + $this->student2 = $student2; + $this->datamodule = $datamodule; + $this->course = $course; + } + + /** + * Get mod_data generator + * + * @return mod_data_generator + */ + protected function get_generator() { + return $this->getDataGenerator()->get_plugin_generator('mod_data'); + } + + /** + * Generates one record in the database module as the current student + * + * @param stdClass $datamodule + * @return mixed + */ + protected function generate_data_record($datamodule) { + global $DB; + + static $counter = 0; + $counter++; + + $contents = array(); + $contents[] = array('opt1', 'opt2', 'opt3', 'opt4'); + $contents[] = sprintf("%02f", $counter) . '-01-2000'; + $contents[] = 'menu1'; + $contents[] = array('multimenu1', 'multimenu2', 'multimenu3', 'multimenu4'); + $contents[] = 5 * $counter; + $contents[] = 'radioopt1'; + $contents[] = 'text for testing' . $counter; + $contents[] = "

text area testing $counter

"; + $contents[] = array('example.url', 'sampleurl' . $counter); + $contents[] = [-31.9489873, 115.8382036]; // Latlong. + $contents[] = "Filename{$counter}.pdf"; // File - filename. + $contents[] = array("Cat{$counter}.jpg", 'Cat' . $counter); // Picture - filename with alt text. + $count = 0; + $fieldcontents = array(); + $fields = $DB->get_records('data_fields', array('dataid' => $datamodule->id), 'id'); + foreach ($fields as $fieldrecord) { + $fieldcontents[$fieldrecord->id] = $contents[$count++]; + } + $tags = ['Cats', 'mice' . $counter]; + return $this->get_generator()->create_entry($datamodule, $fieldcontents, 0, $tags); + } + + /** + * Test for provider::get_metadata(). + */ + public function test_get_metadata() { + $collection = new collection('mod_data'); + $newcollection = provider::get_metadata($collection); + $itemcollection = $newcollection->get_collection(); + $this->assertCount(7, $itemcollection); + + $table = reset($itemcollection); + $this->assertEquals('data_records', $table->get_name()); + + $table = next($itemcollection); + $this->assertEquals('data_content', $table->get_name()); + } + + /** + * Test for provider::get_contexts_for_userid(). + */ + public function test_get_contexts_for_userid() { + $cm = get_coursemodule_from_instance('data', $this->datamodule->id); + + $contextlist = provider::get_contexts_for_userid($this->student->id); + $this->assertCount(1, $contextlist); + $contextforuser = $contextlist->current(); + $cmcontext = context_module::instance($cm->id); + $this->assertEquals($cmcontext->id, $contextforuser->id); + } + + /** + * Get test privacy writer + * + * @param context $context + * @return \core_privacy\tests\request\content_writer + */ + protected function get_writer($context) { + return \core_privacy\local\request\writer::with_context($context); + } + + /** + * Test for provider::export_user_data(). + */ + public function test_export_for_context() { + global $DB; + $cm = get_coursemodule_from_instance('data', $this->datamodule->id); + $cmcontext = context_module::instance($cm->id); + $records = $DB->get_records_select('data_records', 'userid = :userid ORDER BY id', ['userid' => $this->student->id]); + $record = reset($records); + $contents = $DB->get_records('data_content', ['recordid' => $record->id]); + + // Export all of the data for the context. + $this->export_context_data_for_user($this->student->id, $cmcontext, 'mod_data'); + $writer = $this->get_writer($cmcontext); + $data = $writer->get_data([$record->id]); + $this->assertNotEmpty($data); + foreach ($contents as $content) { + $data = $writer->get_data([$record->id, $content->id]); + $this->assertNotEmpty($data); + $hasfile = in_array($data->field['type'], ['file', 'picture']); + $this->assertEquals($hasfile, !empty($writer->get_files([$record->id, $content->id]))); + } + $tags = $writer->get_related_data([$record->id], 'tags'); + $this->assertNotEmpty($tags); + } + + /** + * Test for provider::delete_data_for_all_users_in_context(). + */ + public function test_delete_data_for_all_users_in_context() { + $cm = get_coursemodule_from_instance('data', $this->datamodule->id); + $cmcontext = context_module::instance($cm->id); + + provider::delete_data_for_all_users_in_context($cmcontext); + + $appctxt = new \core_privacy\local\request\approved_contextlist($this->student, 'mod_data', [$cmcontext->id]); + provider::export_user_data($appctxt); + $this->assertFalse($this->get_writer($cmcontext)->has_any_data()); + } + + /** + * Test for provider::delete_data_for_user(). + */ + public function test_delete_data_for_user() { + $cm = get_coursemodule_from_instance('data', $this->datamodule->id); + $cmcontext = context_module::instance($cm->id); + + $appctxt = new \core_privacy\local\request\approved_contextlist($this->student, 'mod_data', [$cmcontext->id]); + provider::delete_data_for_user($appctxt); + + provider::export_user_data($appctxt); + $this->assertFalse($this->get_writer($cmcontext)->has_any_data()); + } +}