Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Privacy class for requesting user data.
  19   *
  20   * @package    mod_assign
  21   * @copyright  2018 Adrian Greeve <adrian@moodle.com>
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace mod_assign\privacy;
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  require_once($CFG->dirroot . '/mod/assign/locallib.php');
  30  
  31  use \core_privacy\local\metadata\collection;
  32  use \core_privacy\local\request\contextlist;
  33  use \core_privacy\local\request\writer;
  34  use \core_privacy\local\request\approved_contextlist;
  35  use \core_privacy\local\request\transform;
  36  use \core_privacy\local\request\helper;
  37  use \core_privacy\local\request\userlist;
  38  use \core_privacy\local\request\approved_userlist;
  39  use \core_privacy\manager;
  40  
  41  /**
  42   * Privacy class for requesting user data.
  43   *
  44   * @package    mod_assign
  45   * @copyright  2018 Adrian Greeve <adrian@moodle.com>
  46   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  47   */
  48  class provider implements
  49          \core_privacy\local\metadata\provider,
  50          \core_privacy\local\request\plugin\provider,
  51          \core_privacy\local\request\user_preference_provider,
  52          \core_privacy\local\request\core_userlist_provider {
  53  
  54      /** Interface for all assign submission sub-plugins. */
  55      const ASSIGNSUBMISSION_INTERFACE = 'mod_assign\privacy\assignsubmission_provider';
  56  
  57      /** Interface for all assign submission sub-plugins. This allows for deletion of users with a context. */
  58      const ASSIGNSUBMISSION_USER_INTERFACE = 'mod_assign\privacy\assignsubmission_user_provider';
  59  
  60      /** Interface for all assign feedback sub-plugins. This allows for deletion of users with a context. */
  61      const ASSIGNFEEDBACK_USER_INTERFACE = 'mod_assign\privacy\assignfeedback_user_provider';
  62  
  63      /** Interface for all assign feedback sub-plugins. */
  64      const ASSIGNFEEDBACK_INTERFACE = 'mod_assign\privacy\assignfeedback_provider';
  65  
  66      /**
  67       * Provides meta data that is stored about a user with mod_assign
  68       *
  69       * @param  collection $collection A collection of meta data items to be added to.
  70       * @return  collection Returns the collection of metadata.
  71       */
  72      public static function get_metadata(collection $collection) : collection {
  73          $assigngrades = [
  74                  'userid' => 'privacy:metadata:userid',
  75                  'timecreated' => 'privacy:metadata:timecreated',
  76                  'timemodified' => 'timemodified',
  77                  'grader' => 'privacy:metadata:grader',
  78                  'grade' => 'privacy:metadata:grade',
  79                  'attemptnumber' => 'attemptnumber'
  80          ];
  81          $assignoverrides = [
  82                  'groupid' => 'privacy:metadata:groupid',
  83                  'userid' => 'privacy:metadata:userid',
  84                  'allowsubmissionsfromdate' => 'allowsubmissionsfromdate',
  85                  'duedate' => 'duedate',
  86                  'cutoffdate' => 'cutoffdate'
  87          ];
  88          $assignsubmission = [
  89                  'userid' => 'privacy:metadata:userid',
  90                  'timecreated' => 'privacy:metadata:timecreated',
  91                  'timemodified' => 'timemodified',
  92                  'status' => 'gradingstatus',
  93                  'groupid' => 'privacy:metadata:groupid',
  94                  'attemptnumber' => 'attemptnumber',
  95                  'latest' => 'privacy:metadata:latest'
  96          ];
  97          $assignuserflags = [
  98                  'userid' => 'privacy:metadata:userid',
  99                  'assignment' => 'privacy:metadata:assignmentid',
 100                  'locked' => 'locksubmissions',
 101                  'mailed' => 'privacy:metadata:mailed',
 102                  'extensionduedate' => 'extensionduedate',
 103                  'workflowstate' => 'markingworkflowstate',
 104                  'allocatedmarker' => 'allocatedmarker'
 105          ];
 106          $assignusermapping = [
 107                  'assignment' => 'privacy:metadata:assignmentid',
 108                  'userid' => 'privacy:metadata:userid'
 109          ];
 110          $collection->add_database_table('assign_grades', $assigngrades, 'privacy:metadata:assigngrades');
 111          $collection->add_database_table('assign_overrides', $assignoverrides, 'privacy:metadata:assignoverrides');
 112          $collection->add_database_table('assign_submission', $assignsubmission, 'privacy:metadata:assignsubmissiondetail');
 113          $collection->add_database_table('assign_user_flags', $assignuserflags, 'privacy:metadata:assignuserflags');
 114          $collection->add_database_table('assign_user_mapping', $assignusermapping, 'privacy:metadata:assignusermapping');
 115          $collection->add_user_preference('assign_perpage', 'privacy:metadata:assignperpage');
 116          $collection->add_user_preference('assign_filter', 'privacy:metadata:assignfilter');
 117          $collection->add_user_preference('assign_markerfilter', 'privacy:metadata:assignmarkerfilter');
 118          $collection->add_user_preference('assign_workflowfilter', 'privacy:metadata:assignworkflowfilter');
 119          $collection->add_user_preference('assign_quickgrading', 'privacy:metadata:assignquickgrading');
 120          $collection->add_user_preference('assign_downloadasfolders', 'privacy:metadata:assigndownloadasfolders');
 121  
 122          // Link to subplugins.
 123          $collection->add_plugintype_link('assignsubmission', [],'privacy:metadata:assignsubmissionpluginsummary');
 124          $collection->add_plugintype_link('assignfeedback', [], 'privacy:metadata:assignfeedbackpluginsummary');
 125          $collection->add_subsystem_link('core_message', [], 'privacy:metadata:assignmessageexplanation');
 126  
 127          return $collection;
 128      }
 129  
 130      /**
 131       * Returns all of the contexts that has information relating to the userid.
 132       *
 133       * @param  int $userid The user ID.
 134       * @return contextlist an object with the contexts related to a userid.
 135       */
 136      public static function get_contexts_for_userid(int $userid) : contextlist {
 137          $params = ['modulename' => 'assign',
 138                     'contextlevel' => CONTEXT_MODULE,
 139                     'userid' => $userid,
 140                     'graderid' => $userid,
 141                     'aouserid' => $userid,
 142                     'asnuserid' => $userid,
 143                     'aufuserid' => $userid,
 144                     'aumuserid' => $userid];
 145  
 146          $sql = "SELECT ctx.id
 147                    FROM {course_modules} cm
 148                    JOIN {modules} m ON cm.module = m.id AND m.name = :modulename
 149                    JOIN {assign} a ON cm.instance = a.id
 150                    JOIN {context} ctx ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextlevel
 151                    JOIN {assign_grades} ag ON a.id = ag.assignment AND (ag.userid = :userid OR ag.grader = :graderid)";
 152  
 153          $contextlist = new contextlist();
 154          $contextlist->add_from_sql($sql, $params);
 155  
 156          $sql = "SELECT ctx.id
 157                    FROM {course_modules} cm
 158                    JOIN {modules} m ON cm.module = m.id AND m.name = :modulename
 159                    JOIN {assign} a ON cm.instance = a.id
 160                    JOIN {context} ctx ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextlevel
 161                    JOIN {assign_overrides} ao ON a.id = ao.assignid
 162                   WHERE ao.userid = :aouserid";
 163  
 164          $contextlist->add_from_sql($sql, $params);
 165  
 166          $sql = "SELECT ctx.id
 167                    FROM {course_modules} cm
 168                    JOIN {modules} m ON cm.module = m.id AND m.name = :modulename
 169                    JOIN {assign} a ON cm.instance = a.id
 170                    JOIN {context} ctx ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextlevel
 171                    JOIN {assign_submission} asn ON a.id = asn.assignment
 172                   WHERE asn.userid = :asnuserid";
 173  
 174          $contextlist->add_from_sql($sql, $params);
 175  
 176          $sql = "SELECT ctx.id
 177                    FROM {course_modules} cm
 178                    JOIN {modules} m ON cm.module = m.id AND m.name = :modulename
 179                    JOIN {assign} a ON cm.instance = a.id
 180                    JOIN {context} ctx ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextlevel
 181                    JOIN {assign_user_flags} auf ON a.id = auf.assignment
 182                   WHERE auf.userid = :aufuserid";
 183  
 184          $contextlist->add_from_sql($sql, $params);
 185  
 186          $sql = "SELECT ctx.id
 187                    FROM {course_modules} cm
 188                    JOIN {modules} m ON cm.module = m.id AND m.name = :modulename
 189                    JOIN {assign} a ON cm.instance = a.id
 190                    JOIN {context} ctx ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextlevel
 191                    JOIN {assign_user_mapping} aum ON a.id = aum.assignment
 192                   WHERE aum.userid = :aumuserid";
 193  
 194          $contextlist->add_from_sql($sql, $params);
 195  
 196          manager::plugintype_class_callback('assignfeedback', self::ASSIGNFEEDBACK_INTERFACE,
 197                  'get_context_for_userid_within_feedback', [$userid, $contextlist]);
 198          manager::plugintype_class_callback('assignsubmission', self::ASSIGNSUBMISSION_INTERFACE,
 199                  'get_context_for_userid_within_submission', [$userid, $contextlist]);
 200  
 201          return $contextlist;
 202      }
 203  
 204      /**
 205       * Get the list of users who have data within a context.
 206       *
 207       * @param   userlist    $userlist   The userlist containing the list of users who have data in this context/plugin combination.
 208       */
 209      public static function get_users_in_context(userlist $userlist) {
 210  
 211          $context = $userlist->get_context();
 212          if ($context->contextlevel != CONTEXT_MODULE) {
 213              return;
 214          }
 215  
 216          $params = [
 217              'modulename' => 'assign',
 218              'contextid' => $context->id,
 219              'contextlevel' => CONTEXT_MODULE
 220          ];
 221  
 222          $sql = "SELECT g.userid, g.grader
 223                    FROM {context} ctx
 224                    JOIN {course_modules} cm ON cm.id = ctx.instanceid
 225                    JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
 226                    JOIN {assign} a ON a.id = cm.instance
 227                    JOIN {assign_grades} g ON a.id = g.assignment
 228                   WHERE ctx.id = :contextid AND ctx.contextlevel = :contextlevel";
 229          $userlist->add_from_sql('userid', $sql, $params);
 230          $userlist->add_from_sql('grader', $sql, $params);
 231  
 232          $sql = "SELECT o.userid
 233                    FROM {context} ctx
 234                    JOIN {course_modules} cm ON cm.id = ctx.instanceid
 235                    JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
 236                    JOIN {assign} a ON a.id = cm.instance
 237                    JOIN {assign_overrides} o ON a.id = o.assignid
 238                   WHERE ctx.id = :contextid AND ctx.contextlevel = :contextlevel";
 239          $userlist->add_from_sql('userid', $sql, $params);
 240  
 241          $sql = "SELECT s.userid
 242                    FROM {context} ctx
 243                    JOIN {course_modules} cm ON cm.id = ctx.instanceid
 244                    JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
 245                    JOIN {assign} a ON a.id = cm.instance
 246                    JOIN {assign_submission} s ON a.id = s.assignment
 247                   WHERE ctx.id = :contextid AND ctx.contextlevel = :contextlevel";
 248          $userlist->add_from_sql('userid', $sql, $params);
 249  
 250          $sql = "SELECT uf.userid
 251                    FROM {context} ctx
 252                    JOIN {course_modules} cm ON cm.id = ctx.instanceid
 253                    JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
 254                    JOIN {assign} a ON a.id = cm.instance
 255                    JOIN {assign_user_flags} uf ON a.id = uf.assignment
 256                   WHERE ctx.id = :contextid AND ctx.contextlevel = :contextlevel";
 257          $userlist->add_from_sql('userid', $sql, $params);
 258  
 259          $sql = "SELECT um.userid
 260                    FROM {context} ctx
 261                    JOIN {course_modules} cm ON cm.id = ctx.instanceid
 262                    JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
 263                    JOIN {assign} a ON a.id = cm.instance
 264                    JOIN {assign_user_mapping} um ON a.id = um.assignment
 265                   WHERE ctx.id = :contextid AND ctx.contextlevel = :contextlevel";
 266          $userlist->add_from_sql('userid', $sql, $params);
 267  
 268          manager::plugintype_class_callback('assignsubmission', self::ASSIGNSUBMISSION_USER_INTERFACE,
 269                  'get_userids_from_context', [$userlist]);
 270          manager::plugintype_class_callback('assignfeedback', self::ASSIGNFEEDBACK_USER_INTERFACE,
 271                  'get_userids_from_context', [$userlist]);
 272      }
 273  
 274      /**
 275       * Write out the user data filtered by contexts.
 276       *
 277       * @param approved_contextlist $contextlist contexts that we are writing data out from.
 278       */
 279      public static function export_user_data(approved_contextlist $contextlist) {
 280          foreach ($contextlist->get_contexts() as $context) {
 281              // Check that the context is a module context.
 282              if ($context->contextlevel != CONTEXT_MODULE) {
 283                  continue;
 284              }
 285              $user = $contextlist->get_user();
 286              $assigndata = helper::get_context_data($context, $user);
 287              helper::export_context_files($context, $user);
 288  
 289              writer::with_context($context)->export_data([], $assigndata);
 290              $assign = new \assign($context, null, null);
 291  
 292              // I need to find out if I'm a student or a teacher.
 293              if ($userids = self::get_graded_users($user->id, $assign)) {
 294                  // Return teacher info.
 295                  $currentpath = [get_string('privacy:studentpath', 'mod_assign')];
 296                  foreach ($userids as $studentuserid) {
 297                      $studentpath = array_merge($currentpath, [$studentuserid->id]);
 298                      static::export_submission($assign, $studentuserid, $context, $studentpath, true);
 299                  }
 300              }
 301  
 302              static::export_overrides($context, $assign, $user);
 303              static::export_submission($assign, $user, $context, []);
 304              // Meta data.
 305              self::store_assign_user_flags($context, $assign, $user->id);
 306              if ($assign->is_blind_marking()) {
 307                  $uniqueid = $assign->get_uniqueid_for_user_static($assign->get_instance()->id, $contextlist->get_user()->id);
 308                  if ($uniqueid) {
 309                      writer::with_context($context)
 310                              ->export_metadata([get_string('blindmarking', 'mod_assign')], 'blindmarkingid', $uniqueid,
 311                                      get_string('privacy:blindmarkingidentifier', 'mod_assign'));
 312                  }
 313              }
 314          }
 315      }
 316  
 317      /**
 318       * Delete all use data which matches the specified context.
 319       *
 320       * @param \context $context The module context.
 321       */
 322      public static function delete_data_for_all_users_in_context(\context $context) {
 323          global $DB;
 324  
 325          if ($context->contextlevel == CONTEXT_MODULE) {
 326              $cm = get_coursemodule_from_id('assign', $context->instanceid);
 327              if ($cm) {
 328                  // Get the assignment related to this context.
 329                  $assign = new \assign($context, null, null);
 330                  // What to do first... Get sub plugins to delete their stuff.
 331                  $requestdata = new assign_plugin_request_data($context, $assign);
 332                  manager::plugintype_class_callback('assignsubmission', self::ASSIGNSUBMISSION_INTERFACE,
 333                      'delete_submission_for_context', [$requestdata]);
 334                  $requestdata = new assign_plugin_request_data($context, $assign);
 335                  manager::plugintype_class_callback('assignfeedback', self::ASSIGNFEEDBACK_INTERFACE,
 336                      'delete_feedback_for_context', [$requestdata]);
 337                  $DB->delete_records('assign_grades', ['assignment' => $assign->get_instance()->id]);
 338  
 339                  // Delete advanced grading information.
 340                  $gradingmanager = get_grading_manager($context, 'mod_assign', 'submissions');
 341                  $controller = $gradingmanager->get_active_controller();
 342                  if (isset($controller)) {
 343                      \core_grading\privacy\provider::delete_instance_data($context);
 344                  }
 345  
 346                  // Time to roll my own method for deleting overrides.
 347                  static::delete_overrides_for_users($assign);
 348                  $DB->delete_records('assign_submission', ['assignment' => $assign->get_instance()->id]);
 349                  $DB->delete_records('assign_user_flags', ['assignment' => $assign->get_instance()->id]);
 350                  $DB->delete_records('assign_user_mapping', ['assignment' => $assign->get_instance()->id]);
 351              }
 352          }
 353      }
 354  
 355      /**
 356       * Delete all user data for the specified user, in the specified contexts.
 357       *
 358       * @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
 359       */
 360      public static function delete_data_for_user(approved_contextlist $contextlist) {
 361          global $DB;
 362  
 363          $user = $contextlist->get_user();
 364  
 365          foreach ($contextlist as $context) {
 366              if ($context->contextlevel != CONTEXT_MODULE) {
 367                  continue;
 368              }
 369              // Get the assign object.
 370              $assign = new \assign($context, null, null);
 371              $assignid = $assign->get_instance()->id;
 372  
 373              $submissions = $DB->get_records('assign_submission', ['assignment' => $assignid, 'userid' => $user->id]);
 374              foreach ($submissions as $submission) {
 375                  $requestdata = new assign_plugin_request_data($context, $assign, $submission, [], $user);
 376                  manager::plugintype_class_callback('assignsubmission', self::ASSIGNSUBMISSION_INTERFACE,
 377                          'delete_submission_for_userid', [$requestdata]);
 378              }
 379  
 380              $grades = $DB->get_records('assign_grades', ['assignment' => $assignid, 'userid' => $user->id]);
 381              $gradingmanager = get_grading_manager($context, 'mod_assign', 'submissions');
 382              $controller = $gradingmanager->get_active_controller();
 383              foreach ($grades as $grade) {
 384                  $requestdata = new assign_plugin_request_data($context, $assign, $grade, [], $user);
 385                  manager::plugintype_class_callback('assignfeedback', self::ASSIGNFEEDBACK_INTERFACE,
 386                          'delete_feedback_for_grade', [$requestdata]);
 387                  // Delete advanced grading information.
 388                  if (isset($controller)) {
 389                      \core_grading\privacy\provider::delete_instance_data($context, $grade->id);
 390                  }
 391              }
 392  
 393              static::delete_overrides_for_users($assign, [$user->id]);
 394              $DB->delete_records('assign_user_flags', ['assignment' => $assignid, 'userid' => $user->id]);
 395              $DB->delete_records('assign_user_mapping', ['assignment' => $assignid, 'userid' => $user->id]);
 396              $DB->delete_records('assign_grades', ['assignment' => $assignid, 'userid' => $user->id]);
 397              $DB->delete_records('assign_submission', ['assignment' => $assignid, 'userid' => $user->id]);
 398          }
 399      }
 400  
 401      /**
 402       * Delete multiple users within a single context.
 403       *
 404       * @param  approved_userlist $userlist The approved context and user information to delete information for.
 405       */
 406      public static function delete_data_for_users(approved_userlist $userlist) {
 407          global $DB;
 408  
 409          $context = $userlist->get_context();
 410          if ($context->contextlevel != CONTEXT_MODULE) {
 411              return;
 412          }
 413  
 414          $userids = $userlist->get_userids();
 415  
 416          $assign = new \assign($context, null, null);
 417          $assignid = $assign->get_instance()->id;
 418          $requestdata = new assign_plugin_request_data($context, $assign);
 419          $requestdata->set_userids($userids);
 420          $requestdata->populate_submissions_and_grades();
 421          manager::plugintype_class_callback('assignsubmission', self::ASSIGNSUBMISSION_USER_INTERFACE, 'delete_submissions',
 422                  [$requestdata]);
 423          manager::plugintype_class_callback('assignfeedback', self::ASSIGNFEEDBACK_USER_INTERFACE, 'delete_feedback_for_grades',
 424                  [$requestdata]);
 425  
 426          // Update this function to delete advanced grading information.
 427          $gradingmanager = get_grading_manager($context, 'mod_assign', 'submissions');
 428          $controller = $gradingmanager->get_active_controller();
 429          if (isset($controller)) {
 430              $gradeids = $requestdata->get_gradeids();
 431              // Careful here, if no gradeids are provided then all data is deleted for the context.
 432              if (!empty($gradeids)) {
 433                  \core_grading\privacy\provider::delete_data_for_instances($context, $gradeids);
 434              }
 435          }
 436  
 437          static::delete_overrides_for_users($assign, $userids);
 438          list($sql, $params) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
 439          $params['assignment'] = $assignid;
 440          $DB->delete_records_select('assign_user_flags', "assignment = :assignment AND userid $sql", $params);
 441          $DB->delete_records_select('assign_user_mapping', "assignment = :assignment AND userid $sql", $params);
 442          $DB->delete_records_select('assign_grades', "assignment = :assignment AND userid $sql", $params);
 443          $DB->delete_records_select('assign_submission', "assignment = :assignment AND userid $sql", $params);
 444      }
 445  
 446      /**
 447       * Deletes assignment overrides in bulk
 448       *
 449       * @param  \assign $assign  The assignment object
 450       * @param  array   $userids An array of user IDs
 451       */
 452      protected static function delete_overrides_for_users(\assign $assign, array $userids = []) {
 453          global $DB;
 454          $assignid = $assign->get_instance()->id;
 455  
 456          $usersql = '';
 457          $params = ['assignid' => $assignid];
 458          if (!empty($userids)) {
 459              list($usersql, $userparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
 460              $params = array_merge($params, $userparams);
 461              $overrides = $DB->get_records_select('assign_overrides', "assignid = :assignid AND userid $usersql", $params);
 462          } else {
 463              $overrides = $DB->get_records('assign_overrides', $params);
 464          }
 465          if (!empty($overrides)) {
 466              $params = ['modulename' => 'assign', 'instance' => $assignid];
 467              if (!empty($userids)) {
 468                  $params = array_merge($params, $userparams);
 469                  $DB->delete_records_select('event', "modulename = :modulename AND instance = :instance AND userid $usersql",
 470                          $params);
 471                  // Setting up for the next query.
 472                  $params = $userparams;
 473                  $usersql = "AND userid $usersql";
 474              } else {
 475                  $DB->delete_records('event', $params);
 476                  // Setting up for the next query.
 477                  $params = [];
 478              }
 479              list($overridesql, $overrideparams) = $DB->get_in_or_equal(array_keys($overrides), SQL_PARAMS_NAMED);
 480              $params = array_merge($params, $overrideparams);
 481              $DB->delete_records_select('assign_overrides', "id $overridesql $usersql", $params);
 482          }
 483      }
 484  
 485      /**
 486       * Find out if this user has graded any users.
 487       *
 488       * @param  int $userid The user ID (potential teacher).
 489       * @param  assign $assign The assignment object.
 490       * @return array If successful an array of objects with userids that this user graded, otherwise false.
 491       */
 492      protected static function get_graded_users(int $userid, \assign $assign) {
 493          $params = ['grader' => $userid, 'assignid' => $assign->get_instance()->id];
 494  
 495          $sql = "SELECT DISTINCT userid AS id
 496                    FROM {assign_grades}
 497                   WHERE grader = :grader AND assignment = :assignid";
 498  
 499          $useridlist = new useridlist($userid, $assign->get_instance()->id);
 500          $useridlist->add_from_sql($sql, $params);
 501  
 502          // Call sub-plugins to see if they have information not already collected.
 503          manager::plugintype_class_callback('assignsubmission', self::ASSIGNSUBMISSION_INTERFACE, 'get_student_user_ids',
 504                  [$useridlist]);
 505          manager::plugintype_class_callback('assignfeedback', self::ASSIGNFEEDBACK_INTERFACE, 'get_student_user_ids', [$useridlist]);
 506  
 507          $userids = $useridlist->get_userids();
 508          return ($userids) ? $userids : false;
 509      }
 510  
 511      /**
 512       * Writes out various user meta data about the assignment.
 513       *
 514       * @param  \context $context The context of this assignment.
 515       * @param  \assign $assign The assignment object.
 516       * @param  int $userid The user ID
 517       */
 518      protected static function store_assign_user_flags(\context $context, \assign $assign, int $userid) {
 519          $datatypes = ['locked' => get_string('locksubmissions', 'mod_assign'),
 520                        'mailed' => get_string('privacy:metadata:mailed', 'mod_assign'),
 521                        'extensionduedate' => get_string('extensionduedate', 'mod_assign'),
 522                        'workflowstate' => get_string('markingworkflowstate', 'mod_assign'),
 523                        'allocatedmarker' => get_string('allocatedmarker_help', 'mod_assign')];
 524          $userflags = (array)$assign->get_user_flags($userid, false);
 525  
 526          foreach ($datatypes as $key => $description) {
 527              if (isset($userflags[$key]) && !empty($userflags[$key])) {
 528                  $value = $userflags[$key];
 529                  if ($key == 'locked' || $key == 'mailed') {
 530                      $value = transform::yesno($value);
 531                  } else if ($key == 'extensionduedate') {
 532                      $value = transform::datetime($value);
 533                  }
 534                  writer::with_context($context)->export_metadata([], $key, $value, $description);
 535              }
 536          }
 537      }
 538  
 539      /**
 540       * Formats and then exports the user's grade data.
 541       *
 542       * @param  \stdClass $grade The assign grade object
 543       * @param  \context $context The context object
 544       * @param  array $currentpath Current directory path that we are exporting to.
 545       */
 546      protected static function export_grade_data(\stdClass $grade, \context $context, array $currentpath) {
 547          $gradedata = (object)[
 548              'timecreated' => transform::datetime($grade->timecreated),
 549              'timemodified' => transform::datetime($grade->timemodified),
 550              'grader' => transform::user($grade->grader),
 551              'grade' => $grade->grade,
 552              'attemptnumber' => ($grade->attemptnumber + 1)
 553          ];
 554          writer::with_context($context)
 555                  ->export_data(array_merge($currentpath, [get_string('privacy:gradepath', 'mod_assign')]), $gradedata);
 556      }
 557  
 558      /**
 559       * Formats and then exports the user's submission data.
 560       *
 561       * @param  \stdClass $submission The assign submission object
 562       * @param  \context $context The context object
 563       * @param  array $currentpath Current directory path that we are exporting to.
 564       */
 565      protected static function export_submission_data(\stdClass $submission, \context $context, array $currentpath) {
 566          $submissiondata = (object)[
 567              'timecreated' => transform::datetime($submission->timecreated),
 568              'timemodified' => transform::datetime($submission->timemodified),
 569              'status' => get_string('submissionstatus_' . $submission->status, 'mod_assign'),
 570              'groupid' => $submission->groupid,
 571              'attemptnumber' => ($submission->attemptnumber + 1),
 572              'latest' => transform::yesno($submission->latest)
 573          ];
 574          writer::with_context($context)
 575                  ->export_data(array_merge($currentpath, [get_string('privacy:submissionpath', 'mod_assign')]), $submissiondata);
 576      }
 577  
 578      /**
 579       * Stores the user preferences related to mod_assign.
 580       *
 581       * @param  int $userid The user ID that we want the preferences for.
 582       */
 583      public static function export_user_preferences(int $userid) {
 584          $context = \context_system::instance();
 585          $assignpreferences = [
 586              'assign_perpage' => ['string' => get_string('privacy:metadata:assignperpage', 'mod_assign'), 'bool' => false],
 587              'assign_filter' => ['string' => get_string('privacy:metadata:assignfilter', 'mod_assign'), 'bool' => false],
 588              'assign_markerfilter' => ['string' => get_string('privacy:metadata:assignmarkerfilter', 'mod_assign'), 'bool' => true],
 589              'assign_workflowfilter' => ['string' => get_string('privacy:metadata:assignworkflowfilter', 'mod_assign'),
 590                      'bool' => true],
 591              'assign_quickgrading' => ['string' => get_string('privacy:metadata:assignquickgrading', 'mod_assign'), 'bool' => true],
 592              'assign_downloadasfolders' => ['string' => get_string('privacy:metadata:assigndownloadasfolders', 'mod_assign'),
 593                      'bool' => true]
 594          ];
 595          foreach ($assignpreferences as $key => $preference) {
 596              $value = get_user_preferences($key, null, $userid);
 597              if ($preference['bool']) {
 598                  $value = transform::yesno($value);
 599              }
 600              if (isset($value)) {
 601                  writer::with_context($context)->export_user_preference('mod_assign', $key, $value, $preference['string']);
 602              }
 603          }
 604      }
 605  
 606      /**
 607       * Export overrides for this assignment.
 608       *
 609       * @param  \context $context Context
 610       * @param  \assign $assign The assign object.
 611       * @param  \stdClass $user The user object.
 612       */
 613      public static function export_overrides(\context $context, \assign $assign, \stdClass $user) {
 614  
 615          $overrides = $assign->override_exists($user->id);
 616          // Overrides returns an array with data in it, but an override with actual data will have the assign ID set.
 617          if (isset($overrides->assignid)) {
 618              $data = new \stdClass();
 619              if (!empty($overrides->duedate)) {
 620                  $data->duedate = transform::datetime($overrides->duedate);
 621              }
 622              if (!empty($overrides->cutoffdate)) {
 623                  $data->cutoffdate = transform::datetime($overrides->cutoffdate);
 624              }
 625              if (!empty($overrides->allowsubmissionsfromdate)) {
 626                  $data->allowsubmissionsfromdate = transform::datetime($overrides->allowsubmissionsfromdate);
 627              }
 628              if (!empty($data)) {
 629                  writer::with_context($context)->export_data([get_string('overrides', 'mod_assign')], $data);
 630              }
 631          }
 632      }
 633  
 634      /**
 635       * Exports assignment submission data for a user.
 636       *
 637       * @param  \assign         $assign           The assignment object
 638       * @param  \stdClass        $user             The user object
 639       * @param  \context_module $context          The context
 640       * @param  array           $path             The path for exporting data
 641       * @param  bool|boolean    $exportforteacher A flag for if this is exporting data as a teacher.
 642       */
 643      protected static function export_submission(\assign $assign, \stdClass $user, \context_module $context, array $path,
 644              bool $exportforteacher = false) {
 645          $submissions = $assign->get_all_submissions($user->id);
 646          $teacher = ($exportforteacher) ? $user : null;
 647          $gradingmanager = get_grading_manager($context, 'mod_assign', 'submissions');
 648          $controller = $gradingmanager->get_active_controller();
 649          foreach ($submissions as $submission) {
 650              // Attempt numbers start at zero, which is fine for programming, but doesn't make as much sense
 651              // for users.
 652              $submissionpath = array_merge($path,
 653                      [get_string('privacy:attemptpath', 'mod_assign', ($submission->attemptnumber + 1))]);
 654  
 655              $params = new assign_plugin_request_data($context, $assign, $submission, $submissionpath ,$teacher);
 656              manager::plugintype_class_callback('assignsubmission', self::ASSIGNSUBMISSION_INTERFACE,
 657                      'export_submission_user_data', [$params]);
 658              if (!isset($teacher)) {
 659                  self::export_submission_data($submission, $context, $submissionpath);
 660              }
 661              $grade = $assign->get_user_grade($user->id, false, $submission->attemptnumber);
 662              if ($grade) {
 663                  $params = new assign_plugin_request_data($context, $assign, $grade, $submissionpath, $teacher);
 664                  manager::plugintype_class_callback('assignfeedback', self::ASSIGNFEEDBACK_INTERFACE, 'export_feedback_user_data',
 665                          [$params]);
 666  
 667                  self::export_grade_data($grade, $context, $submissionpath);
 668                  // Check for advanced grading and retrieve that information.
 669                  if (isset($controller)) {
 670                      \core_grading\privacy\provider::export_item_data($context, $grade->id, $submissionpath);
 671                  }
 672              }
 673          }
 674      }
 675  }