From 1772a3e63a40a3dacf826e30c778d36dbf1b8516 Mon Sep 17 00:00:00 2001
From: Gero Lueckemeyer <gero.lueckemeyer@hft-stuttgart.de>
Date: Thu, 16 Nov 2023 16:01:06 +0100
Subject: [PATCH] added privacy api implementation like in
 assignsubmission_file

---
 dta.zip                        | Bin 20533 -> 23694 bytes
 dta/models/DtaResult.php       |   4 +-
 dta/privacy/provider.php       | 264 +++++++++++++++++++++++++++++++++
 dta/utils/view.php             |   2 +-
 test/privacy/apicompliance.php |  99 +++++++++++++
 test/privacy/deletion.php      |  70 +++++++++
 test/privacy/export.php        |  87 +++++++++++
 7 files changed, 523 insertions(+), 3 deletions(-)
 create mode 100644 dta/privacy/provider.php
 create mode 100644 test/privacy/apicompliance.php
 create mode 100644 test/privacy/deletion.php
 create mode 100644 test/privacy/export.php

diff --git a/dta.zip b/dta.zip
index 1dc2cab2e4b76667b091b2d6c98f629ee9e195a3..afca1bf23f47123d6decf766493a73598665084d 100644
GIT binary patch
delta 5975
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
Q00I>M&B~`<|Erw;0@t4<LjV8(

delta 2849
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

diff --git a/dta/models/DtaResult.php b/dta/models/DtaResult.php
index 85e1ee7..a06e33d 100644
--- a/dta/models/DtaResult.php
+++ b/dta/models/DtaResult.php
@@ -69,7 +69,7 @@ class DtaResult {
 
     /**
      * @var $failurereason Reason of test failure if applicable, "" otherwise.
-     */    
+     */
     public $failurereason;
 
     /**
@@ -119,7 +119,7 @@ class DtaResultSummary {
 
     /**
      * @var $timestamp Result timestamp for chronological ordering and deletion of previous results.
-     */    
+     */
     public $timestamp;
 
     /**
diff --git a/dta/privacy/provider.php b/dta/privacy/provider.php
new file mode 100644
index 0000000..7f30ade
--- /dev/null
+++ b/dta/privacy/provider.php
@@ -0,0 +1,264 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * provider for data privacy
+ *
+ * @package assignsubmission_dta
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @copyright Gero Lueckemeyer and student project teams
+ */
+namespace assignsubmission_dta\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+use core_privacy\local\metadata\collection;
+use \core_privacy\local\request\writer;
+use \core_privacy\local\request\contextlist;
+use \mod_assign\privacy\assign_plugin_request_data;
+
+class provider implements
+        // This plugin does store personal user data.
+        \core_privacy\local\metadata\provider,
+        \mod_assign\privacy\assignsubmission_provider,
+        \mod_assign\privacy\assignsubmission_user_provider
+        {
+
+    /**
+     * File area for dta submission assignment.
+     */
+    const ASSIGNSUBMISSION_DTA_FILEAREA_SUBMISSION = "submissions_dta";
+
+    public static function get_metadata(collection $collection): collection {
+        $collection->add_subsystem_link(
+            'core_files',
+            [],
+            'privacy:metadata:core_files'
+        );
+        
+        $collection->add_database_table(
+            'assignsubmission_dta_summary',
+            [
+                'assignmentid' => 'privacy:metadata:assignsubmission_dta_summary:assignmentid',
+                'submissionid' => 'privacy:metadata:assignsubmission_dta_summary:submissionid',
+                'timestamp' => 'privacy:metadata:assignsubmission_dta_summary:timestamp',
+                'global_stacktrace' => 'privacy:metadata:assignsubmission_dta_summary:global_stacktrace',
+                'successful_competencies' => 'privacy:metadata:assignsubmission_dta_summary:successful_competencies',
+                'tested_competencies' => 'privacy:metadata:assignsubmission_dta_summary:tested_competencies',
+            ],
+            'privacy:metadata:assignsubmission_dta_summary'
+        );
+
+        $collection->add_database_table(
+            'assignsubmission_dta_result',
+            [
+                'assignmentid' => 'privacy:metadata:assignsubmission_dta_result:assignmentid',
+                'submissionid' => 'privacy:metadata:assignsubmission_dta_result:submissionid',
+                'package_name' => 'privacy:metadata:assignsubmission_dta_result:package_name',
+                'class_name' => 'privacy:metadata:assignsubmission_dta_result:class_name',
+                'name' => 'privacy:metadata:assignsubmission_dta_result:name',
+                'state' => 'privacy:metadata:assignsubmission_dta_result:state',
+                'failure_type' => 'privacy:metadata:assignsubmission_dta_result:failure_type',
+                'failure_reason' => 'privacy:metadata:assignsubmission_dta_result:failure_reason',
+                'stacktrace' => 'privacy:metadata:assignsubmission_dta_result:stacktrace',
+                'column_number' => 'privacy:metadata:assignsubmission_dta_result:column_number',
+                'line_number' => 'privacy:metadata:assignsubmission_dta_result:line_number',
+                'position' => 'privacy:metadata:assignsubmission_dta_result:position',
+            ],
+            'privacy:metadata:assignsubmission_dta_result'
+        );
+        
+        $collection->add_external_location_link('dta_backend', [
+                'assignmentid' => 'privacy:metadata:assignsubmission_dta_result:assignmentid',
+                'submissionid' => 'privacy:metadata:assignsubmission_dta_result:submissionid',
+                'submissioncontent' => 'privacy:metadata:core_files',
+            ], 
+            'privacy:metadata:dta_backend'
+        );
+
+        return $collection;
+    }
+
+    /**
+     * This is covered by mod_assign provider and the query on assign_submissions.
+     *
+     * @param  int $userid The user ID that we are finding contexts for.
+     * @param  contextlist $contextlist A context list to add sql and params to for contexts.
+     */
+    public static function get_context_for_userid_within_submission(int $userid, contextlist $contextlist) {
+        // This is already fetched from mod_assign.
+    }
+
+    /**
+     * This is also covered by the mod_assign provider and its queries.
+     *
+     * @param  \mod_assign\privacy\useridlist $useridlist An object for obtaining user IDs of students.
+     */
+    public static function get_student_user_ids(\mod_assign\privacy\useridlist $useridlist) {
+        // This is already fetched from mod_assign.
+    }
+
+    /**
+     * If you have tables that contain userids and you can generate entries in your tables without creating an
+     * entry in the assign_submission table then please fill in this method.
+     *
+     * @param  userlist $userlist The userlist object
+     */
+    public static function get_userids_from_context(\core_privacy\local\request\userlist $userlist) {
+        // Not required.
+    }
+
+    /**
+     * Export all user data for this plugin.
+     *
+     * @param  assign_plugin_request_data $exportdata Data used to determine which context and user to export and other useful
+     * information to help with exporting.
+     */
+    public static function export_submission_user_data(assign_plugin_request_data $exportdata) {
+        // We currently don't show submissions to teachers when exporting their data.
+        $context = $exportdata->get_context();
+        if ($exportdata->get_user() != null) {
+            return null;
+        }
+        $user = new \stdClass();
+        $assign = $exportdata->get_assign();
+        $submission = $exportdata->get_pluginobject();
+        $files = get_files($submission, $user);
+        foreach ($files as $file) {
+            $userid = $exportdata->get_pluginobject()->userid;
+            $dtaresultsummary=DBUtils::getresultsummaryfromdatabase($assign->id, $submission->id);
+            // Submitted file.
+            writer::with_context($exportdata->get_context())->export_file($exportdata->get_subcontext(), $file)
+            // DTA result.
+            ->export_related_data($dtaresultsummary);             
+
+            // Plagiarism data.
+            $coursecontext = $context->get_course_context();
+            \core_plagiarism\privacy\provider::export_plagiarism_user_data($userid, $context, $exportdata->get_subcontext(), [
+                'cmid' => $context->instanceid,
+                'course' => $coursecontext->instanceid,
+                'userid' => $userid,
+                'file' => $file
+            ]);
+        }
+    }
+
+    /**
+     * Any call to this method should delete all user data for the context defined in the deletion_criteria.
+     *
+     * @param  assign_plugin_request_data $requestdata Information useful for deleting user data.
+     */
+    public static function delete_submission_for_context(assign_plugin_request_data $requestdata) {
+        global $DB;
+
+        \core_plagiarism\privacy\provider::delete_plagiarism_for_context($requestdata->get_context());
+
+        $fs = get_file_storage();
+        $fs->delete_area_files($requestdata->get_context()->id, 'assignsubmission_dta', ASSIGNSUBMISSION_DTA_FILEAREA_SUBMISSION);
+
+        $assignmentid = $requestdata->get_assign()->get_instance()->id;
+                
+        // Delete records from assignsubmission_dta tables.
+        $DB->delete_records('assignsubmission_dta_result', ['assignmentid' => $assignmentid]);
+        $DB->delete_records('assignsubmission_dta_summary', ['assignmentid' => $assignmentid]);
+    }
+
+    /**
+     * A call to this method should delete user data (where practical) using the userid and submission.
+     *
+     * @param  assign_plugin_request_data $deletedata Details about the user and context to focus the deletion.
+     */
+    public static function delete_submission_for_userid(assign_plugin_request_data $deletedata) {
+        global $DB;
+
+        \core_plagiarism\privacy\provider::delete_plagiarism_for_user($deletedata->get_user()->id, $deletedata->get_context());
+
+        $assignmentid = $deletedata->get_assign()->get_instance()->id;
+        $submissionid = $deletedata->get_pluginobject()->id;
+
+        $fs = get_file_storage();
+        $fs->delete_area_files($deletedata->get_context()->id, 'assignsubmission_dta', ASSIGNSUBMISSION_DTA_FILEAREA_SUBMISSION,
+                $submissionid);
+
+        // Delete records from assignsubmission_dta tables. Also possible with a list as below.
+        $DB->delete_records('assignsubmission_dta_result', [
+            'assignmentid' => $assignmentid, 
+            'submissionid' => $submissionid,
+            ]);
+        $DB->delete_records('assignsubmission_dta_summary', [
+            'assignmentid' => $assignmentid, 
+            'submissionid' => $submissionid,
+            ]);
+    }
+    
+    /**
+     * Deletes all submissions for the submission ids / userids provided in a context.
+     * assign_plugin_request_data contains:
+     * - context
+     * - assign object
+     * - submission ids (pluginids)
+     * - user ids
+     * @param  assign_plugin_request_data $deletedata A class that contains the relevant information required for deletion.
+     */
+    public static function delete_submissions(assign_plugin_request_data $deletedata) {
+        global $DB;
+
+        \core_plagiarism\privacy\provider::delete_plagiarism_for_users($deletedata->get_userids(), $deletedata->get_context());
+
+        if (empty($deletedata->get_submissionids())) {
+            return;
+        }
+        $fs = get_file_storage();
+        list($sql, $params) = $DB->get_in_or_equal($deletedata->get_submissionids(), SQL_PARAMS_NAMED);
+        $fs->delete_area_files_select($deletedata->get_context()->id, 'assignsubmission_file', ASSIGNSUBMISSION_FILE_FILEAREA,
+                $sql, $params);
+
+        $params['assignid'] = $deletedata->get_assignid();
+        $DB->delete_records_select('assignsubmission_dta_result', "assignmentid = :assignid AND submissionid $sql", $params);
+        $DB->delete_records_select('assignsubmission_dta_summary', "assignmentid = :assignid AND submissionid $sql", $params);
+    }
+
+    /**
+     * Produce a list of files suitable for export that represent this feedback or submission
+     *
+     * @param stdClass $submission The submission
+     * @param stdClass $user The user record - unused
+     * @return array - return an array of files indexed by filename
+     */
+    public function get_files(stdClass $submission, stdClass $user) {
+        $result = array();
+        $fs = get_file_storage();
+
+        $files = $fs->get_area_files($this->assignment->get_context()->id,
+                                     'assignsubmission_file',
+                                     ASSIGNSUBMISSION_DTA_FILEAREA_SUBMISSION,
+                                     $submission->id,
+                                     'timemodified',
+                                     false);
+
+        foreach ($files as $file) {
+            // Do we return the full folder path or just the file name?
+            if (isset($submission->exportfullpath) && $submission->exportfullpath == false) {
+                $result[$file->get_filename()] = $file;
+            } else {
+                $result[$file->get_filepath().$file->get_filename()] = $file;
+            }
+        }
+        return $result;
+    }
+
+
+}
\ No newline at end of file
diff --git a/dta/utils/view.php b/dta/utils/view.php
index 34d0881..ebd223a 100644
--- a/dta/utils/view.php
+++ b/dta/utils/view.php
@@ -241,7 +241,7 @@ class view_submission_utils {
                 // New copy of base attributes array.
                 $resultrowattributes = $tablerowattributes;
                 $tmp = "";
-                $tmp .= html_writer::tag("td", get_string("comp" . $index, self::COMPONENT_NAME), $resultrowattributes);                
+                $tmp .= html_writer::tag("td", get_string("comp" . $index, self::COMPONENT_NAME), $resultrowattributes);
                 $tmp .= html_writer::tag("td", 100 * floatval($shown) / floatval($comp) . "% " .
                     "(" . $shown . " / " . $comp . ")", $resultrowattributes);
                 $tmp .= html_writer::tag("td", get_string("comp_expl" . $index, self::COMPONENT_NAME), $resultrowattributes);
diff --git a/test/privacy/apicompliance.php b/test/privacy/apicompliance.php
new file mode 100644
index 0000000..121ad63
--- /dev/null
+++ b/test/privacy/apicompliance.php
@@ -0,0 +1,99 @@
+<?php
+
+// Set this if you want to run the script for one component only. Otherwise leave empty.
+$CHECK_COMPONENT = '';
+
+define('CLI_SCRIPT', true);
+
+require_once('config.php');
+
+$user = \core_user::get_user(2);
+
+\core\session\manager::init_empty_session();
+\core\session\manager::set_user($user);
+
+$rc = new \ReflectionClass(\core_privacy\manager::class);
+$rcm = $rc->getMethod('get_component_list');
+$rcm->setAccessible(true);
+
+$manager = new \core_privacy\manager();
+$components = $rcm->invoke($manager);
+
+$list = (object) [
+    'good' => [],
+    'bad' => [],
+];
+
+foreach ($components as $component) {
+    if ($CHECK_COMPONENT && $component !== $CHECK_COMPONENT) {
+        continue;
+    }
+    $compliant = $manager->component_is_compliant($component);
+    if ($compliant) {
+        $list->good[] = $component;
+    } else {
+        $list->bad[] = $component;
+    }
+}
+
+echo "The following plugins are not compliant:\n";
+echo "=> " . implode("\n=> ", array_values($list->bad)) . "\n";
+
+echo "\n";
+echo "Testing the compliant plugins:\n";
+foreach ($list->good as $component) {
+    $classname = \core_privacy\manager::get_provider_classname_for_component($component);
+    echo "== {$component} ($classname) ==\n";
+    if (check_implements($component, \core_privacy\local\metadata\null_provider::class)) {
+        echo "    Claims not to store any data with reason:\n";
+        echo "      '" . get_string($classname::get_reason(), $component) . "'\n";
+    }
+    else if (check_implements($component, \core_privacy\local\metadata\provider::class)) {
+        $collection = new \core_privacy\local\metadata\collection($component);
+        $classname::get_metadata($collection);
+        $count = count($collection->get_collection());
+        echo "    Found {$count} items of metadata\n";
+        if (empty($count)) {
+            echo "!!! No metadata found!!! This an error.\n";
+        }
+
+        if (check_implements($component, \core_privacy\local\request\user_preference_provider::class)) {
+            $userprefdescribed = false;
+            foreach ($collection->get_collection() as $item) {
+                if ($item instanceof \core_privacy\local\metadata\types\user_preference) {
+                    $userprefdescribed = true;
+                    echo "     ".$item->get_name()." : ".get_string($item->get_summary(), $component) . "\n";
+                }
+            }
+            if (!$userprefdescribed) {
+                echo "!!! User preference found, but was not described in metadata\n";
+            }
+        }
+
+        if (check_implements($component, \core_privacy\local\request\core_user_data_provider::class)) {
+            // No need to check the return type - it's enforced by the interface.
+            $contextlist = $classname::get_contexts_for_userid($user->id);
+            $approvedcontextlist = new \core_privacy\local\request\approved_contextlist($user, $contextlist->get_component(), $contextlist->get_contextids());
+            if (count($approvedcontextlist)) {
+                $classname::export_user_data($approvedcontextlist);
+                echo "    Successfully ran a test export\n";
+            } else {
+                echo "    Nothing to export.\n";
+            }
+        }
+        if (check_implements($component, \core_privacy\local\request\shared_data_provider::class)) {
+            echo "    This is a shared data provider\n";
+        }
+    }
+}
+
+echo "\n\n== Done ==\n";
+
+function check_implements($component, $interface) {
+    $manager = new \core_privacy\manager();
+    $rc = new \ReflectionClass(\core_privacy\manager::class);
+    $rcm = $rc->getMethod('component_implements');
+    $rcm->setAccessible(true);
+
+    return $rcm->invoke($manager, $component, $interface);
+}
\ No newline at end of file
diff --git a/test/privacy/deletion.php b/test/privacy/deletion.php
new file mode 100644
index 0000000..8fdb75c
--- /dev/null
+++ b/test/privacy/deletion.php
@@ -0,0 +1,70 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+define('CLI_SCRIPT', true);
+require_once('config.php');
+require_once("$CFG->libdir/clilib.php");
+
+list($options, $unrecognized) = cli_get_params(
+    [
+        'username' => '',
+        'userid' => '',
+    ],
+    []
+);
+
+$user = null;
+$username = $options['username'];
+$userid = $options['userid'];
+
+if (!empty($options['username'])) {
+    $user = \core_user::get_user_by_username($options['username']);
+} else if (!empty($options['userid'])) {
+    $user = \core_user::get_user($options['userid']);
+}
+
+while (empty($user)) {
+    if (!empty($username)) {
+        echo "Unable to find a user with username '{$username}'.\n";
+        echo "Try again.\n";
+    } else if (!empty($userid)) {
+        echo "Unable to find a user with userid '{$userid}'.\n";
+        echo "Try again.\n";
+    }
+    $username = readline("Username: ");
+    $user = \core_user::get_user_by_username($username);
+}
+
+echo "Processing delete for " . fullname($user) . "\n";
+
+\core\session\manager::init_empty_session();
+\core\session\manager::set_user($user);
+
+$manager = new \core_privacy\manager();
+
+$approvedlist = new \core_privacy\local\request\contextlist_collection($user->id);
+
+$trace = new text_progress_trace();
+$contextlists = $manager->get_contexts_for_userid($user->id, $trace);
+foreach ($contextlists as $contextlist) {
+    $approvedlist->add_contextlist(new \core_privacy\local\request\approved_contextlist(
+        $user,
+        $contextlist->get_component(),
+        $contextlist->get_contextids()
+    ));
+}
+
+$manager->delete_data_for_user($approvedlist, $trace);
\ No newline at end of file
diff --git a/test/privacy/export.php b/test/privacy/export.php
new file mode 100644
index 0000000..f56d617
--- /dev/null
+++ b/test/privacy/export.php
@@ -0,0 +1,87 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Helper utility to perform a test export.
+ *
+ * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('CLI_SCRIPT', true);
+require_once('config.php');
+require_once("$CFG->libdir/clilib.php");
+
+list($options, $unrecognized) = cli_get_params(
+    [
+        'username' => '',
+        'userid' => '',
+    ],
+    []
+);
+
+$user = null;
+$username = $options['username'];
+$userid = $options['userid'];
+
+if (!empty($options['username'])) {
+    $user = \core_user::get_user_by_username($options['username']);
+} else if (!empty($options['userid'])) {
+    $user = \core_user::get_user($options['userid']);
+}
+
+while (empty($user)) {
+    if (!empty($username)) {
+        echo "Unable to find a user with username '{$username}'.\n";
+        echo "Try again.\n";
+    } else if (!empty($userid)) {
+        echo "Unable to find a user with userid '{$userid}'.\n";
+        echo "Try again.\n";
+    }
+    $username = readline("Username: ");
+    $user = \core_user::get_user_by_username($username);
+}
+
+echo "Processing export for " . fullname($user) . "\n";
+
+\core\session\manager::init_empty_session();
+\core\session\manager::set_user($user);
+
+$PAGE = new moodle_page();
+$OUTPUT = new core_renderer($PAGE, RENDERER_TARGET_GENERAL);
+
+$manager = new \core_privacy\manager();
+
+$approvedlist = new \core_privacy\local\request\contextlist_collection($user->id);
+
+$contextlists = $manager->get_contexts_for_userid($user->id);
+foreach ($contextlists as $contextlist) {
+    $approvedlist->add_contextlist(new \core_privacy\local\request\approved_contextlist(
+        $user,
+        $contextlist->get_component(),
+        $contextlist->get_contextids()
+    ));
+}
+
+$exportedcontent = $manager->export_user_data($approvedlist);
+$basedir = make_temp_directory('privacy');
+$exportpath = make_unique_writable_directory($basedir, true);
+$fp = get_file_packer();
+$fp->extract_to_pathname($exportedcontent, $exportpath);
+
+echo "\n";
+echo "== File export was uncompressed to {$exportpath}\n";
+echo "============================================================================\n";
\ No newline at end of file
-- 
GitLab