Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 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.
/mod/assign/ -> lib.php (source)

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 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   * This file contains the moodle hooks for the assign module.
  19   *
  20   * It delegates most functions to the assignment class.
  21   *
  22   * @package   mod_assign
  23   * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
  24   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  25   */
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  /**
  29   * Adds an assignment instance
  30   *
  31   * This is done by calling the add_instance() method of the assignment type class
  32   * @param stdClass $data
  33   * @param mod_assign_mod_form $form
  34   * @return int The instance id of the new assignment
  35   */
  36  function assign_add_instance(stdClass $data, mod_assign_mod_form $form = null) {
  37      global $CFG;
  38      require_once($CFG->dirroot . '/mod/assign/locallib.php');
  39  
  40      $assignment = new assign(context_module::instance($data->coursemodule), null, null);
  41      return $assignment->add_instance($data, true);
  42  }
  43  
  44  /**
  45   * delete an assignment instance
  46   * @param int $id
  47   * @return bool
  48   */
  49  function assign_delete_instance($id) {
  50      global $CFG;
  51      require_once($CFG->dirroot . '/mod/assign/locallib.php');
  52      $cm = get_coursemodule_from_instance('assign', $id, 0, false, MUST_EXIST);
  53      $context = context_module::instance($cm->id);
  54  
  55      $assignment = new assign($context, null, null);
  56      return $assignment->delete_instance();
  57  }
  58  
  59  /**
  60   * This function is used by the reset_course_userdata function in moodlelib.
  61   * This function will remove all assignment submissions and feedbacks in the database
  62   * and clean up any related data.
  63   *
  64   * @param stdClass $data the data submitted from the reset course.
  65   * @return array
  66   */
  67  function assign_reset_userdata($data) {
  68      global $CFG, $DB;
  69      require_once($CFG->dirroot . '/mod/assign/locallib.php');
  70  
  71      $status = array();
  72      $params = array('courseid'=>$data->courseid);
  73      $sql = "SELECT a.id FROM {assign} a WHERE a.course=:courseid";
  74      $course = $DB->get_record('course', array('id'=>$data->courseid), '*', MUST_EXIST);
  75      if ($assigns = $DB->get_records_sql($sql, $params)) {
  76          foreach ($assigns as $assign) {
  77              $cm = get_coursemodule_from_instance('assign',
  78                                                   $assign->id,
  79                                                   $data->courseid,
  80                                                   false,
  81                                                   MUST_EXIST);
  82              $context = context_module::instance($cm->id);
  83              $assignment = new assign($context, $cm, $course);
  84              $status = array_merge($status, $assignment->reset_userdata($data));
  85          }
  86      }
  87      return $status;
  88  }
  89  
  90  /**
  91   * This standard function will check all instances of this module
  92   * and make sure there are up-to-date events created for each of them.
  93   * If courseid = 0, then every assignment event in the site is checked, else
  94   * only assignment events belonging to the course specified are checked.
  95   *
  96   * @param int $courseid
  97   * @param int|stdClass $instance Assign module instance or ID.
  98   * @param int|stdClass $cm Course module object or ID (not used in this module).
  99   * @return bool
 100   */
 101  function assign_refresh_events($courseid = 0, $instance = null, $cm = null) {
 102      global $CFG, $DB;
 103      require_once($CFG->dirroot . '/mod/assign/locallib.php');
 104  
 105      // If we have instance information then we can just update the one event instead of updating all events.
 106      if (isset($instance)) {
 107          if (!is_object($instance)) {
 108              $instance = $DB->get_record('assign', array('id' => $instance), '*', MUST_EXIST);
 109          }
 110          if (isset($cm)) {
 111              if (!is_object($cm)) {
 112                  assign_prepare_update_events($instance);
 113                  return true;
 114              } else {
 115                  $course = get_course($instance->course);
 116                  assign_prepare_update_events($instance, $course, $cm);
 117                  return true;
 118              }
 119          }
 120      }
 121  
 122      if ($courseid) {
 123          // Make sure that the course id is numeric.
 124          if (!is_numeric($courseid)) {
 125              return false;
 126          }
 127          if (!$assigns = $DB->get_records('assign', array('course' => $courseid))) {
 128              return false;
 129          }
 130          // Get course from courseid parameter.
 131          if (!$course = $DB->get_record('course', array('id' => $courseid), '*')) {
 132              return false;
 133          }
 134      } else {
 135          if (!$assigns = $DB->get_records('assign')) {
 136              return false;
 137          }
 138      }
 139      foreach ($assigns as $assign) {
 140          assign_prepare_update_events($assign);
 141      }
 142  
 143      return true;
 144  }
 145  
 146  /**
 147   * This actually updates the normal and completion calendar events.
 148   *
 149   * @param  stdClass $assign Assignment object (from DB).
 150   * @param  stdClass $course Course object.
 151   * @param  stdClass $cm Course module object.
 152   */
 153  function assign_prepare_update_events($assign, $course = null, $cm = null) {
 154      global $DB;
 155      if (!isset($course)) {
 156          // Get course and course module for the assignment.
 157          list($course, $cm) = get_course_and_cm_from_instance($assign->id, 'assign', $assign->course);
 158      }
 159      // Refresh the assignment's calendar events.
 160      $context = context_module::instance($cm->id);
 161      $assignment = new assign($context, $cm, $course);
 162      $assignment->update_calendar($cm->id);
 163      // Refresh the calendar events also for the assignment overrides.
 164      $overrides = $DB->get_records('assign_overrides', ['assignid' => $assign->id], '',
 165                                    'id, groupid, userid, duedate, sortorder');
 166      foreach ($overrides as $override) {
 167          if (empty($override->userid)) {
 168              unset($override->userid);
 169          }
 170          if (empty($override->groupid)) {
 171              unset($override->groupid);
 172          }
 173          assign_update_events($assignment, $override);
 174      }
 175  }
 176  
 177  /**
 178   * Removes all grades from gradebook
 179   *
 180   * @param int $courseid The ID of the course to reset
 181   * @param string $type Optional type of assignment to limit the reset to a particular assignment type
 182   */
 183  function assign_reset_gradebook($courseid, $type='') {
 184      global $CFG, $DB;
 185  
 186      $params = array('moduletype'=>'assign', 'courseid'=>$courseid);
 187      $sql = 'SELECT a.*, cm.idnumber as cmidnumber, a.course as courseid
 188              FROM {assign} a, {course_modules} cm, {modules} m
 189              WHERE m.name=:moduletype AND m.id=cm.module AND cm.instance=a.id AND a.course=:courseid';
 190  
 191      if ($assignments = $DB->get_records_sql($sql, $params)) {
 192          foreach ($assignments as $assignment) {
 193              assign_grade_item_update($assignment, 'reset');
 194          }
 195      }
 196  }
 197  
 198  /**
 199   * Implementation of the function for printing the form elements that control
 200   * whether the course reset functionality affects the assignment.
 201   * @param moodleform $mform form passed by reference
 202   */
 203  function assign_reset_course_form_definition(&$mform) {
 204      $mform->addElement('header', 'assignheader', get_string('modulenameplural', 'assign'));
 205      $name = get_string('deleteallsubmissions', 'assign');
 206      $mform->addElement('advcheckbox', 'reset_assign_submissions', $name);
 207      $mform->addElement('advcheckbox', 'reset_assign_user_overrides',
 208          get_string('removealluseroverrides', 'assign'));
 209      $mform->addElement('advcheckbox', 'reset_assign_group_overrides',
 210          get_string('removeallgroupoverrides', 'assign'));
 211  }
 212  
 213  /**
 214   * Course reset form defaults.
 215   * @param  object $course
 216   * @return array
 217   */
 218  function assign_reset_course_form_defaults($course) {
 219      return array('reset_assign_submissions' => 1,
 220              'reset_assign_group_overrides' => 1,
 221              'reset_assign_user_overrides' => 1);
 222  }
 223  
 224  /**
 225   * Update an assignment instance
 226   *
 227   * This is done by calling the update_instance() method of the assignment type class
 228   * @param stdClass $data
 229   * @param stdClass $form - unused
 230   * @return object
 231   */
 232  function assign_update_instance(stdClass $data, $form) {
 233      global $CFG;
 234      require_once($CFG->dirroot . '/mod/assign/locallib.php');
 235      $context = context_module::instance($data->coursemodule);
 236      $assignment = new assign($context, null, null);
 237      return $assignment->update_instance($data);
 238  }
 239  
 240  /**
 241   * This function updates the events associated to the assign.
 242   * If $override is non-zero, then it updates only the events
 243   * associated with the specified override.
 244   *
 245   * @param assign $assign the assign object.
 246   * @param object $override (optional) limit to a specific override
 247   */
 248  function assign_update_events($assign, $override = null) {
 249      global $CFG, $DB;
 250  
 251      require_once($CFG->dirroot . '/calendar/lib.php');
 252  
 253      $assigninstance = $assign->get_instance();
 254  
 255      // Load the old events relating to this assign.
 256      $conds = array('modulename' => 'assign', 'instance' => $assigninstance->id);
 257      if (!empty($override)) {
 258          // Only load events for this override.
 259          if (isset($override->userid)) {
 260              $conds['userid'] = $override->userid;
 261          } else if (isset($override->groupid)) {
 262              $conds['groupid'] = $override->groupid;
 263          } else {
 264              // This is not a valid override, it may have been left from a bad import or restore.
 265              $conds['groupid'] = $conds['userid'] = 0;
 266          }
 267      }
 268      $oldevents = $DB->get_records('event', $conds, 'id ASC');
 269  
 270      // Now make a to-do list of all that needs to be updated.
 271      if (empty($override)) {
 272          // We are updating the primary settings for the assignment, so we need to add all the overrides.
 273          $overrides = $DB->get_records('assign_overrides', array('assignid' => $assigninstance->id), 'id ASC');
 274          // It is necessary to add an empty stdClass to the beginning of the array as the $oldevents
 275          // list contains the original (non-override) event for the module. If this is not included
 276          // the logic below will end up updating the wrong row when we try to reconcile this $overrides
 277          // list against the $oldevents list.
 278          array_unshift($overrides, new stdClass());
 279      } else {
 280          // Just do the one override.
 281          $overrides = array($override);
 282      }
 283  
 284      if (!empty($assign->get_course_module())) {
 285          $cmid = $assign->get_course_module()->id;
 286      } else {
 287          $cmid = get_coursemodule_from_instance('assign', $assigninstance->id, $assigninstance->course)->id;
 288      }
 289  
 290      foreach ($overrides as $current) {
 291          $groupid   = isset($current->groupid) ? $current->groupid : 0;
 292          $userid    = isset($current->userid) ? $current->userid : 0;
 293          $duedate = isset($current->duedate) ? $current->duedate : $assigninstance->duedate;
 294  
 295          // Only add 'due' events for an override if they differ from the assign default.
 296          $addclose = empty($current->id) || !empty($current->duedate);
 297  
 298          $event = new stdClass();
 299          $event->type = CALENDAR_EVENT_TYPE_ACTION;
 300          $event->description = format_module_intro('assign', $assigninstance, $cmid, false);
 301          $event->format = FORMAT_HTML;
 302          // Events module won't show user events when the courseid is nonzero.
 303          $event->courseid    = ($userid) ? 0 : $assigninstance->course;
 304          $event->groupid     = $groupid;
 305          $event->userid      = $userid;
 306          $event->modulename  = 'assign';
 307          $event->instance    = $assigninstance->id;
 308          $event->timestart   = $duedate;
 309          $event->timeduration = 0;
 310          $event->timesort    = $event->timestart + $event->timeduration;
 311          $event->visible     = instance_is_visible('assign', $assigninstance);
 312          $event->eventtype   = ASSIGN_EVENT_TYPE_DUE;
 313          $event->priority    = null;
 314  
 315          // Determine the event name and priority.
 316          if ($groupid) {
 317              // Group override event.
 318              $params = new stdClass();
 319              $params->assign = $assigninstance->name;
 320              $params->group = groups_get_group_name($groupid);
 321              if ($params->group === false) {
 322                  // Group doesn't exist, just skip it.
 323                  continue;
 324              }
 325              $eventname = get_string('overridegroupeventname', 'assign', $params);
 326              // Set group override priority.
 327              if (isset($current->sortorder)) {
 328                  $event->priority = $current->sortorder;
 329              }
 330          } else if ($userid) {
 331              // User override event.
 332              $params = new stdClass();
 333              $params->assign = $assigninstance->name;
 334              $eventname = get_string('overrideusereventname', 'assign', $params);
 335              // Set user override priority.
 336              $event->priority = CALENDAR_EVENT_USER_OVERRIDE_PRIORITY;
 337          } else {
 338              // The parent event.
 339              $eventname = $assigninstance->name;
 340          }
 341  
 342          if ($duedate && $addclose) {
 343              if ($oldevent = array_shift($oldevents)) {
 344                  $event->id = $oldevent->id;
 345              } else {
 346                  unset($event->id);
 347              }
 348              $event->name      = $eventname.' ('.get_string('duedate', 'assign').')';
 349              calendar_event::create($event, false);
 350          }
 351      }
 352  
 353      // Delete any leftover events.
 354      foreach ($oldevents as $badevent) {
 355          $badevent = calendar_event::load($badevent);
 356          $badevent->delete();
 357      }
 358  }
 359  
 360  /**
 361   * Return the list if Moodle features this module supports
 362   *
 363   * @param string $feature FEATURE_xx constant for requested feature
 364   * @return mixed True if module supports feature, null if doesn't know
 365   */
 366  function assign_supports($feature) {
 367      switch($feature) {
 368          case FEATURE_GROUPS:
 369              return true;
 370          case FEATURE_GROUPINGS:
 371              return true;
 372          case FEATURE_MOD_INTRO:
 373              return true;
 374          case FEATURE_COMPLETION_TRACKS_VIEWS:
 375              return true;
 376          case FEATURE_COMPLETION_HAS_RULES:
 377              return true;
 378          case FEATURE_GRADE_HAS_GRADE:
 379              return true;
 380          case FEATURE_GRADE_OUTCOMES:
 381              return true;
 382          case FEATURE_BACKUP_MOODLE2:
 383              return true;
 384          case FEATURE_SHOW_DESCRIPTION:
 385              return true;
 386          case FEATURE_ADVANCED_GRADING:
 387              return true;
 388          case FEATURE_PLAGIARISM:
 389              return true;
 390          case FEATURE_COMMENT:
 391              return true;
 392  
 393          default:
 394              return null;
 395      }
 396  }
 397  
 398  /**
 399   * extend an assigment navigation settings
 400   *
 401   * @param settings_navigation $settings
 402   * @param navigation_node $navref
 403   * @return void
 404   */
 405  function assign_extend_settings_navigation(settings_navigation $settings, navigation_node $navref) {
 406      global $PAGE, $DB;
 407  
 408      // We want to add these new nodes after the Edit settings node, and before the
 409      // Locally assigned roles node. Of course, both of those are controlled by capabilities.
 410      $keys = $navref->get_children_key_list();
 411      $beforekey = null;
 412      $i = array_search('modedit', $keys);
 413      if ($i === false and array_key_exists(0, $keys)) {
 414          $beforekey = $keys[0];
 415      } else if (array_key_exists($i + 1, $keys)) {
 416          $beforekey = $keys[$i + 1];
 417      }
 418  
 419      $cm = $PAGE->cm;
 420      if (!$cm) {
 421          return;
 422      }
 423  
 424      $context = $cm->context;
 425      $course = $PAGE->course;
 426  
 427      if (!$course) {
 428          return;
 429      }
 430  
 431      if (has_capability('mod/assign:manageoverrides', $PAGE->cm->context)) {
 432          $url = new moodle_url('/mod/assign/overrides.php', array('cmid' => $PAGE->cm->id));
 433          $node = navigation_node::create(get_string('groupoverrides', 'assign'),
 434              new moodle_url($url, array('mode' => 'group')),
 435              navigation_node::TYPE_SETTING, null, 'mod_assign_groupoverrides');
 436          $navref->add_node($node, $beforekey);
 437  
 438          $node = navigation_node::create(get_string('useroverrides', 'assign'),
 439              new moodle_url($url, array('mode' => 'user')),
 440              navigation_node::TYPE_SETTING, null, 'mod_assign_useroverrides');
 441          $navref->add_node($node, $beforekey);
 442      }
 443  
 444      // Link to gradebook.
 445      if (has_capability('gradereport/grader:view', $cm->context) &&
 446              has_capability('moodle/grade:viewall', $cm->context)) {
 447          $link = new moodle_url('/grade/report/grader/index.php', array('id' => $course->id));
 448          $linkname = get_string('viewgradebook', 'assign');
 449          $node = $navref->add($linkname, $link, navigation_node::TYPE_SETTING);
 450      }
 451  
 452      // Link to download all submissions.
 453      if (has_any_capability(array('mod/assign:grade', 'mod/assign:viewgrades'), $context)) {
 454          $link = new moodle_url('/mod/assign/view.php', array('id' => $cm->id, 'action'=>'grading'));
 455          $node = $navref->add(get_string('viewgrading', 'assign'), $link, navigation_node::TYPE_SETTING);
 456  
 457          $link = new moodle_url('/mod/assign/view.php', array('id' => $cm->id, 'action'=>'downloadall'));
 458          $node = $navref->add(get_string('downloadall', 'assign'), $link, navigation_node::TYPE_SETTING);
 459      }
 460  
 461      if (has_capability('mod/assign:revealidentities', $context)) {
 462          $dbparams = array('id'=>$cm->instance);
 463          $assignment = $DB->get_record('assign', $dbparams, 'blindmarking, revealidentities');
 464  
 465          if ($assignment && $assignment->blindmarking && !$assignment->revealidentities) {
 466              $urlparams = array('id' => $cm->id, 'action'=>'revealidentities');
 467              $url = new moodle_url('/mod/assign/view.php', $urlparams);
 468              $linkname = get_string('revealidentities', 'assign');
 469              $node = $navref->add($linkname, $url, navigation_node::TYPE_SETTING);
 470          }
 471      }
 472  }
 473  
 474  /**
 475   * Add a get_coursemodule_info function in case any assignment type wants to add 'extra' information
 476   * for the course (see resource).
 477   *
 478   * Given a course_module object, this function returns any "extra" information that may be needed
 479   * when printing this activity in a course listing.  See get_array_of_activities() in course/lib.php.
 480   *
 481   * @param stdClass $coursemodule The coursemodule object (record).
 482   * @return cached_cm_info An object on information that the courses
 483   *                        will know about (most noticeably, an icon).
 484   */
 485  function assign_get_coursemodule_info($coursemodule) {
 486      global $CFG, $DB;
 487  
 488      $dbparams = array('id'=>$coursemodule->instance);
 489      $fields = 'id, name, alwaysshowdescription, allowsubmissionsfromdate, intro, introformat, completionsubmit';
 490      if (! $assignment = $DB->get_record('assign', $dbparams, $fields)) {
 491          return false;
 492      }
 493  
 494      $result = new cached_cm_info();
 495      $result->name = $assignment->name;
 496      if ($coursemodule->showdescription) {
 497          if ($assignment->alwaysshowdescription || time() > $assignment->allowsubmissionsfromdate) {
 498              // Convert intro to html. Do not filter cached version, filters run at display time.
 499              $result->content = format_module_intro('assign', $assignment, $coursemodule->id, false);
 500          }
 501      }
 502  
 503      // Populate the custom completion rules as key => value pairs, but only if the completion mode is 'automatic'.
 504      if ($coursemodule->completion == COMPLETION_TRACKING_AUTOMATIC) {
 505          $result->customdata['customcompletionrules']['completionsubmit'] = $assignment->completionsubmit;
 506      }
 507  
 508      return $result;
 509  }
 510  
 511  /**
 512   * Callback which returns human-readable strings describing the active completion custom rules for the module instance.
 513   *
 514   * @param cm_info|stdClass $cm object with fields ->completion and ->customdata['customcompletionrules']
 515   * @return array $descriptions the array of descriptions for the custom rules.
 516   */
 517  function mod_assign_get_completion_active_rule_descriptions($cm) {
 518      // Values will be present in cm_info, and we assume these are up to date.
 519      if (empty($cm->customdata['customcompletionrules'])
 520          || $cm->completion != COMPLETION_TRACKING_AUTOMATIC) {
 521          return [];
 522      }
 523  
 524      $descriptions = [];
 525      foreach ($cm->customdata['customcompletionrules'] as $key => $val) {
 526          switch ($key) {
 527              case 'completionsubmit':
 528                  if (!empty($val)) {
 529                      $descriptions[] = get_string('completionsubmit', 'assign');
 530                  }
 531                  break;
 532              default:
 533                  break;
 534          }
 535      }
 536      return $descriptions;
 537  }
 538  
 539  /**
 540   * Return a list of page types
 541   * @param string $pagetype current page type
 542   * @param stdClass $parentcontext Block's parent context
 543   * @param stdClass $currentcontext Current context of block
 544   */
 545  function assign_page_type_list($pagetype, $parentcontext, $currentcontext) {
 546      $modulepagetype = array(
 547          'mod-assign-*' => get_string('page-mod-assign-x', 'assign'),
 548          'mod-assign-view' => get_string('page-mod-assign-view', 'assign'),
 549      );
 550      return $modulepagetype;
 551  }
 552  
 553  /**
 554   * @deprecated since Moodle 3.3, when the block_course_overview block was removed.
 555   */
 556  function assign_print_overview() {
 557      throw new coding_exception('assign_print_overview() can not be used any more and is obsolete.');
 558  }
 559  
 560  /**
 561   * @deprecated since Moodle 3.3, when the block_course_overview block was removed.
 562   */
 563  function assign_get_mysubmission_details_for_print_overview() {
 564      throw new coding_exception('assign_get_mysubmission_details_for_print_overview() can not be used any more and is obsolete.');
 565  }
 566  
 567  /**
 568   * @deprecated since Moodle 3.3, when the block_course_overview block was removed.
 569   */
 570  function assign_get_grade_details_for_print_overview() {
 571      throw new coding_exception('assign_get_grade_details_for_print_overview() can not be used any more and is obsolete.');
 572  }
 573  
 574  /**
 575   * Print recent activity from all assignments in a given course
 576   *
 577   * This is used by the recent activity block
 578   * @param mixed $course the course to print activity for
 579   * @param bool $viewfullnames boolean to determine whether to show full names or not
 580   * @param int $timestart the time the rendering started
 581   * @return bool true if activity was printed, false otherwise.
 582   */
 583  function assign_print_recent_activity($course, $viewfullnames, $timestart) {
 584      global $CFG, $USER, $DB, $OUTPUT;
 585      require_once($CFG->dirroot . '/mod/assign/locallib.php');
 586  
 587      // Do not use log table if possible, it may be huge.
 588  
 589      $dbparams = array($timestart, $course->id, 'assign', ASSIGN_SUBMISSION_STATUS_SUBMITTED);
 590      $namefields = user_picture::fields('u', null, 'userid');
 591      if (!$submissions = $DB->get_records_sql("SELECT asb.id, asb.timemodified, cm.id AS cmid, um.id as recordid,
 592                                                       $namefields
 593                                                  FROM {assign_submission} asb
 594                                                       JOIN {assign} a      ON a.id = asb.assignment
 595                                                       JOIN {course_modules} cm ON cm.instance = a.id
 596                                                       JOIN {modules} md        ON md.id = cm.module
 597                                                       JOIN {user} u            ON u.id = asb.userid
 598                                                  LEFT JOIN {assign_user_mapping} um ON um.userid = u.id AND um.assignment = a.id
 599                                                 WHERE asb.timemodified > ? AND
 600                                                       asb.latest = 1 AND
 601                                                       a.course = ? AND
 602                                                       md.name = ? AND
 603                                                       asb.status = ?
 604                                              ORDER BY asb.timemodified ASC", $dbparams)) {
 605           return false;
 606      }
 607  
 608      $modinfo = get_fast_modinfo($course);
 609      $show    = array();
 610      $grader  = array();
 611  
 612      $showrecentsubmissions = get_config('assign', 'showrecentsubmissions');
 613  
 614      foreach ($submissions as $submission) {
 615          if (!array_key_exists($submission->cmid, $modinfo->get_cms())) {
 616              continue;
 617          }
 618          $cm = $modinfo->get_cm($submission->cmid);
 619          if (!$cm->uservisible) {
 620              continue;
 621          }
 622          if ($submission->userid == $USER->id) {
 623              $show[] = $submission;
 624              continue;
 625          }
 626  
 627          $context = context_module::instance($submission->cmid);
 628          // The act of submitting of assignment may be considered private -
 629          // only graders will see it if specified.
 630          if (empty($showrecentsubmissions)) {
 631              if (!array_key_exists($cm->id, $grader)) {
 632                  $grader[$cm->id] = has_capability('moodle/grade:viewall', $context);
 633              }
 634              if (!$grader[$cm->id]) {
 635                  continue;
 636              }
 637          }
 638  
 639          $groupmode = groups_get_activity_groupmode($cm, $course);
 640  
 641          if ($groupmode == SEPARATEGROUPS &&
 642                  !has_capability('moodle/site:accessallgroups',  $context)) {
 643              if (isguestuser()) {
 644                  // Shortcut - guest user does not belong into any group.
 645                  continue;
 646              }
 647  
 648              // This will be slow - show only users that share group with me in this cm.
 649              if (!$modinfo->get_groups($cm->groupingid)) {
 650                  continue;
 651              }
 652              $usersgroups =  groups_get_all_groups($course->id, $submission->userid, $cm->groupingid);
 653              if (is_array($usersgroups)) {
 654                  $usersgroups = array_keys($usersgroups);
 655                  $intersect = array_intersect($usersgroups, $modinfo->get_groups($cm->groupingid));
 656                  if (empty($intersect)) {
 657                      continue;
 658                  }
 659              }
 660          }
 661          $show[] = $submission;
 662      }
 663  
 664      if (empty($show)) {
 665          return false;
 666      }
 667  
 668      echo $OUTPUT->heading(get_string('newsubmissions', 'assign') . ':', 6);
 669  
 670      foreach ($show as $submission) {
 671          $cm = $modinfo->get_cm($submission->cmid);
 672          $context = context_module::instance($submission->cmid);
 673          $assign = new assign($context, $cm, $cm->course);
 674          $link = $CFG->wwwroot.'/mod/assign/view.php?id='.$cm->id;
 675          // Obscure first and last name if blind marking enabled.
 676          if ($assign->is_blind_marking()) {
 677              $submission->firstname = get_string('participant', 'mod_assign');
 678              if (empty($submission->recordid)) {
 679                  $submission->recordid = $assign->get_uniqueid_for_user($submission->userid);
 680              }
 681              $submission->lastname = $submission->recordid;
 682          }
 683          print_recent_activity_note($submission->timemodified,
 684                                     $submission,
 685                                     $cm->name,
 686                                     $link,
 687                                     false,
 688                                     $viewfullnames);
 689      }
 690  
 691      return true;
 692  }
 693  
 694  /**
 695   * Returns all assignments since a given time.
 696   *
 697   * @param array $activities The activity information is returned in this array
 698   * @param int $index The current index in the activities array
 699   * @param int $timestart The earliest activity to show
 700   * @param int $courseid Limit the search to this course
 701   * @param int $cmid The course module id
 702   * @param int $userid Optional user id
 703   * @param int $groupid Optional group id
 704   * @return void
 705   */
 706  function assign_get_recent_mod_activity(&$activities,
 707                                          &$index,
 708                                          $timestart,
 709                                          $courseid,
 710                                          $cmid,
 711                                          $userid=0,
 712                                          $groupid=0) {
 713      global $CFG, $COURSE, $USER, $DB;
 714  
 715      require_once($CFG->dirroot . '/mod/assign/locallib.php');
 716  
 717      if ($COURSE->id == $courseid) {
 718          $course = $COURSE;
 719      } else {
 720          $course = $DB->get_record('course', array('id'=>$courseid));
 721      }
 722  
 723      $modinfo = get_fast_modinfo($course);
 724  
 725      $cm = $modinfo->get_cm($cmid);
 726      $params = array();
 727      if ($userid) {
 728          $userselect = 'AND u.id = :userid';
 729          $params['userid'] = $userid;
 730      } else {
 731          $userselect = '';
 732      }
 733  
 734      if ($groupid) {
 735          $groupselect = 'AND gm.groupid = :groupid';
 736          $groupjoin   = 'JOIN {groups_members} gm ON  gm.userid=u.id';
 737          $params['groupid'] = $groupid;
 738      } else {
 739          $groupselect = '';
 740          $groupjoin   = '';
 741      }
 742  
 743      $params['cminstance'] = $cm->instance;
 744      $params['timestart'] = $timestart;
 745      $params['submitted'] = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
 746  
 747      $userfields = user_picture::fields('u', null, 'userid');
 748  
 749      if (!$submissions = $DB->get_records_sql('SELECT asb.id, asb.timemodified, ' .
 750                                                       $userfields .
 751                                               '  FROM {assign_submission} asb
 752                                                  JOIN {assign} a ON a.id = asb.assignment
 753                                                  JOIN {user} u ON u.id = asb.userid ' .
 754                                            $groupjoin .
 755                                              '  WHERE asb.timemodified > :timestart AND
 756                                                       asb.status = :submitted AND
 757                                                       a.id = :cminstance
 758                                                       ' . $userselect . ' ' . $groupselect .
 759                                              ' ORDER BY asb.timemodified ASC', $params)) {
 760           return;
 761      }
 762  
 763      $groupmode       = groups_get_activity_groupmode($cm, $course);
 764      $cmcontext      = context_module::instance($cm->id);
 765      $grader          = has_capability('moodle/grade:viewall', $cmcontext);
 766      $accessallgroups = has_capability('moodle/site:accessallgroups', $cmcontext);
 767      $viewfullnames   = has_capability('moodle/site:viewfullnames', $cmcontext);
 768  
 769  
 770      $showrecentsubmissions = get_config('assign', 'showrecentsubmissions');
 771      $show = array();
 772      foreach ($submissions as $submission) {
 773          if ($submission->userid == $USER->id) {
 774              $show[] = $submission;
 775              continue;
 776          }
 777          // The act of submitting of assignment may be considered private -
 778          // only graders will see it if specified.
 779          if (empty($showrecentsubmissions)) {
 780              if (!$grader) {
 781                  continue;
 782              }
 783          }
 784  
 785          if ($groupmode == SEPARATEGROUPS and !$accessallgroups) {
 786              if (isguestuser()) {
 787                  // Shortcut - guest user does not belong into any group.
 788                  continue;
 789              }
 790  
 791              // This will be slow - show only users that share group with me in this cm.
 792              if (!$modinfo->get_groups($cm->groupingid)) {
 793                  continue;
 794              }
 795              $usersgroups =  groups_get_all_groups($course->id, $submission->userid, $cm->groupingid);
 796              if (is_array($usersgroups)) {
 797                  $usersgroups = array_keys($usersgroups);
 798                  $intersect = array_intersect($usersgroups, $modinfo->get_groups($cm->groupingid));
 799                  if (empty($intersect)) {
 800                      continue;
 801                  }
 802              }
 803          }
 804          $show[] = $submission;
 805      }
 806  
 807      if (empty($show)) {
 808          return;
 809      }
 810  
 811      if ($grader) {
 812          require_once($CFG->libdir.'/gradelib.php');
 813          $userids = array();
 814          foreach ($show as $id => $submission) {
 815              $userids[] = $submission->userid;
 816          }
 817          $grades = grade_get_grades($courseid, 'mod', 'assign', $cm->instance, $userids);
 818      }
 819  
 820      $aname = format_string($cm->name, true);
 821      foreach ($show as $submission) {
 822          $activity = new stdClass();
 823  
 824          $activity->type         = 'assign';
 825          $activity->cmid         = $cm->id;
 826          $activity->name         = $aname;
 827          $activity->sectionnum   = $cm->sectionnum;
 828          $activity->timestamp    = $submission->timemodified;
 829          $activity->user         = new stdClass();
 830          if ($grader) {
 831              $activity->grade = $grades->items[0]->grades[$submission->userid]->str_long_grade;
 832          }
 833  
 834          $userfields = explode(',', user_picture::fields());
 835          foreach ($userfields as $userfield) {
 836              if ($userfield == 'id') {
 837                  // Aliased in SQL above.
 838                  $activity->user->{$userfield} = $submission->userid;
 839              } else {
 840                  $activity->user->{$userfield} = $submission->{$userfield};
 841              }
 842          }
 843          $activity->user->fullname = fullname($submission, $viewfullnames);
 844  
 845          $activities[$index++] = $activity;
 846      }
 847  
 848      return;
 849  }
 850  
 851  /**
 852   * Print recent activity from all assignments in a given course
 853   *
 854   * This is used by course/recent.php
 855   * @param stdClass $activity
 856   * @param int $courseid
 857   * @param bool $detail
 858   * @param array $modnames
 859   */
 860  function assign_print_recent_mod_activity($activity, $courseid, $detail, $modnames) {
 861      global $CFG, $OUTPUT;
 862  
 863      echo '<table border="0" cellpadding="3" cellspacing="0" class="assignment-recent">';
 864  
 865      echo '<tr><td class="userpicture" valign="top">';
 866      echo $OUTPUT->user_picture($activity->user);
 867      echo '</td><td>';
 868  
 869      if ($detail) {
 870          $modname = $modnames[$activity->type];
 871          echo '<div class="title">';
 872          echo $OUTPUT->image_icon('icon', $modname, 'assign');
 873          echo '<a href="' . $CFG->wwwroot . '/mod/assign/view.php?id=' . $activity->cmid . '">';
 874          echo $activity->name;
 875          echo '</a>';
 876          echo '</div>';
 877      }
 878  
 879      if (isset($activity->grade)) {
 880          echo '<div class="grade">';
 881          echo get_string('grade').': ';
 882          echo $activity->grade;
 883          echo '</div>';
 884      }
 885  
 886      echo '<div class="user">';
 887      echo "<a href=\"$CFG->wwwroot/user/view.php?id={$activity->user->id}&amp;course=$courseid\">";
 888      echo "{$activity->user->fullname}</a>  - " . userdate($activity->timestamp);
 889      echo '</div>';
 890  
 891      echo '</td></tr></table>';
 892  }
 893  
 894  /**
 895   * @deprecated since Moodle 3.8
 896   */
 897  function assign_scale_used() {
 898      throw new coding_exception('assign_scale_used() can not be used anymore. Plugins can implement ' .
 899          '<modname>_scale_used_anywhere, all implementations of <modname>_scale_used are now ignored');
 900  }
 901  
 902  /**
 903   * Checks if scale is being used by any instance of assignment
 904   *
 905   * This is used to find out if scale used anywhere
 906   * @param int $scaleid
 907   * @return boolean True if the scale is used by any assignment
 908   */
 909  function assign_scale_used_anywhere($scaleid) {
 910      global $DB;
 911  
 912      if ($scaleid and $DB->record_exists('assign', array('grade'=>-$scaleid))) {
 913          return true;
 914      } else {
 915          return false;
 916      }
 917  }
 918  
 919  /**
 920   * List the actions that correspond to a view of this module.
 921   * This is used by the participation report.
 922   *
 923   * Note: This is not used by new logging system. Event with
 924   *       crud = 'r' and edulevel = LEVEL_PARTICIPATING will
 925   *       be considered as view action.
 926   *
 927   * @return array
 928   */
 929  function assign_get_view_actions() {
 930      return array('view submission', 'view feedback');
 931  }
 932  
 933  /**
 934   * List the actions that correspond to a post of this module.
 935   * This is used by the participation report.
 936   *
 937   * Note: This is not used by new logging system. Event with
 938   *       crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
 939   *       will be considered as post action.
 940   *
 941   * @return array
 942   */
 943  function assign_get_post_actions() {
 944      return array('upload', 'submit', 'submit for grading');
 945  }
 946  
 947  /**
 948   * Returns all other capabilities used by this module.
 949   * @return array Array of capability strings
 950   */
 951  function assign_get_extra_capabilities() {
 952      return ['gradereport/grader:view', 'moodle/grade:viewall'];
 953  }
 954  
 955  /**
 956   * Create grade item for given assignment.
 957   *
 958   * @param stdClass $assign record with extra cmidnumber
 959   * @param array $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
 960   * @return int 0 if ok, error code otherwise
 961   */
 962  function assign_grade_item_update($assign, $grades=null) {
 963      global $CFG;
 964      require_once($CFG->libdir.'/gradelib.php');
 965  
 966      if (!isset($assign->courseid)) {
 967          $assign->courseid = $assign->course;
 968      }
 969  
 970      $params = array('itemname'=>$assign->name, 'idnumber'=>$assign->cmidnumber);
 971  
 972      // Check if feedback plugin for gradebook is enabled, if yes then
 973      // gradetype = GRADE_TYPE_TEXT else GRADE_TYPE_NONE.
 974      $gradefeedbackenabled = false;
 975  
 976      if (isset($assign->gradefeedbackenabled)) {
 977          $gradefeedbackenabled = $assign->gradefeedbackenabled;
 978      } else if ($assign->grade == 0) { // Grade feedback is needed only when grade == 0.
 979          require_once($CFG->dirroot . '/mod/assign/locallib.php');
 980          $mod = get_coursemodule_from_instance('assign', $assign->id, $assign->courseid);
 981          $cm = context_module::instance($mod->id);
 982          $assignment = new assign($cm, null, null);
 983          $gradefeedbackenabled = $assignment->is_gradebook_feedback_enabled();
 984      }
 985  
 986      if ($assign->grade > 0) {
 987          $params['gradetype'] = GRADE_TYPE_VALUE;
 988          $params['grademax']  = $assign->grade;
 989          $params['grademin']  = 0;
 990  
 991      } else if ($assign->grade < 0) {
 992          $params['gradetype'] = GRADE_TYPE_SCALE;
 993          $params['scaleid']   = -$assign->grade;
 994  
 995      } else if ($gradefeedbackenabled) {
 996          // $assign->grade == 0 and feedback enabled.
 997          $params['gradetype'] = GRADE_TYPE_TEXT;
 998      } else {
 999          // $assign->grade == 0 and no feedback enabled.
1000          $params['gradetype'] = GRADE_TYPE_NONE;
1001      }
1002  
1003      if ($grades  === 'reset') {
1004          $params['reset'] = true;
1005          $grades = null;
1006      }
1007  
1008      return grade_update('mod/assign',
1009                          $assign->courseid,
1010                          'mod',
1011                          'assign',
1012                          $assign->id,
1013                          0,
1014                          $grades,
1015                          $params);
1016  }
1017  
1018  /**
1019   * Return grade for given user or all users.
1020   *
1021   * @param stdClass $assign record of assign with an additional cmidnumber
1022   * @param int $userid optional user id, 0 means all users
1023   * @return array array of grades, false if none
1024   */
1025  function assign_get_user_grades($assign, $userid=0) {
1026      global $CFG;
1027  
1028      require_once($CFG->dirroot . '/mod/assign/locallib.php');
1029  
1030      $cm = get_coursemodule_from_instance('assign', $assign->id, 0, false, MUST_EXIST);
1031      $context = context_module::instance($cm->id);
1032      $assignment = new assign($context, null, null);
1033      $assignment->set_instance($assign);
1034      return $assignment->get_user_grades_for_gradebook($userid);
1035  }
1036  
1037  /**
1038   * Update activity grades.
1039   *
1040   * @param stdClass $assign database record
1041   * @param int $userid specific user only, 0 means all
1042   * @param bool $nullifnone - not used
1043   */
1044  function assign_update_grades($assign, $userid=0, $nullifnone=true) {
1045      global $CFG;
1046      require_once($CFG->libdir.'/gradelib.php');
1047  
1048      if ($assign->grade == 0) {
1049          assign_grade_item_update($assign);
1050  
1051      } else if ($grades = assign_get_user_grades($assign, $userid)) {
1052          foreach ($grades as $k => $v) {
1053              if ($v->rawgrade == -1) {
1054                  $grades[$k]->rawgrade = null;
1055              }
1056          }
1057          assign_grade_item_update($assign, $grades);
1058  
1059      } else {
1060          assign_grade_item_update($assign);
1061      }
1062  }
1063  
1064  /**
1065   * List the file areas that can be browsed.
1066   *
1067   * @param stdClass $course
1068   * @param stdClass $cm
1069   * @param stdClass $context
1070   * @return array
1071   */
1072  function assign_get_file_areas($course, $cm, $context) {
1073      global $CFG;
1074      require_once($CFG->dirroot . '/mod/assign/locallib.php');
1075  
1076      $areas = array(ASSIGN_INTROATTACHMENT_FILEAREA => get_string('introattachments', 'mod_assign'));
1077  
1078      $assignment = new assign($context, $cm, $course);
1079      foreach ($assignment->get_submission_plugins() as $plugin) {
1080          if ($plugin->is_visible()) {
1081              $pluginareas = $plugin->get_file_areas();
1082  
1083              if ($pluginareas) {
1084                  $areas = array_merge($areas, $pluginareas);
1085              }
1086          }
1087      }
1088      foreach ($assignment->get_feedback_plugins() as $plugin) {
1089          if ($plugin->is_visible()) {
1090              $pluginareas = $plugin->get_file_areas();
1091  
1092              if ($pluginareas) {
1093                  $areas = array_merge($areas, $pluginareas);
1094              }
1095          }
1096      }
1097  
1098      return $areas;
1099  }
1100  
1101  /**
1102   * File browsing support for assign module.
1103   *
1104   * @param file_browser $browser
1105   * @param object $areas
1106   * @param object $course
1107   * @param object $cm
1108   * @param object $context
1109   * @param string $filearea
1110   * @param int $itemid
1111   * @param string $filepath
1112   * @param string $filename
1113   * @return object file_info instance or null if not found
1114   */
1115  function assign_get_file_info($browser,
1116                                $areas,
1117                                $course,
1118                                $cm,
1119                                $context,
1120                                $filearea,
1121                                $itemid,
1122                                $filepath,
1123                                $filename) {
1124      global $CFG;
1125      require_once($CFG->dirroot . '/mod/assign/locallib.php');
1126  
1127      if ($context->contextlevel != CONTEXT_MODULE) {
1128          return null;
1129      }
1130  
1131      $urlbase = $CFG->wwwroot.'/pluginfile.php';
1132      $fs = get_file_storage();
1133      $filepath = is_null($filepath) ? '/' : $filepath;
1134      $filename = is_null($filename) ? '.' : $filename;
1135  
1136      // Need to find where this belongs to.
1137      $assignment = new assign($context, $cm, $course);
1138      if ($filearea === ASSIGN_INTROATTACHMENT_FILEAREA) {
1139          if (!has_capability('moodle/course:managefiles', $context)) {
1140              // Students can not peak here!
1141              return null;
1142          }
1143          if (!($storedfile = $fs->get_file($assignment->get_context()->id,
1144                                            'mod_assign', $filearea, 0, $filepath, $filename))) {
1145              return null;
1146          }
1147          return new file_info_stored($browser,
1148                          $assignment->get_context(),
1149                          $storedfile,
1150                          $urlbase,
1151                          $filearea,
1152                          $itemid,
1153                          true,
1154                          true,
1155                          false);
1156      }
1157  
1158      $pluginowner = null;
1159      foreach ($assignment->get_submission_plugins() as $plugin) {
1160          if ($plugin->is_visible()) {
1161              $pluginareas = $plugin->get_file_areas();
1162  
1163              if (array_key_exists($filearea, $pluginareas)) {
1164                  $pluginowner = $plugin;
1165                  break;
1166              }
1167          }
1168      }
1169      if (!$pluginowner) {
1170          foreach ($assignment->get_feedback_plugins() as $plugin) {
1171              if ($plugin->is_visible()) {
1172                  $pluginareas = $plugin->get_file_areas();
1173  
1174                  if (array_key_exists($filearea, $pluginareas)) {
1175                      $pluginowner = $plugin;
1176                      break;
1177                  }
1178              }
1179          }
1180      }
1181  
1182      if (!$pluginowner) {
1183          return null;
1184      }
1185  
1186      $result = $pluginowner->get_file_info($browser, $filearea, $itemid, $filepath, $filename);
1187      return $result;
1188  }
1189  
1190  /**
1191   * Prints the complete info about a user's interaction with an assignment.
1192   *
1193   * @param stdClass $course
1194   * @param stdClass $user
1195   * @param stdClass $coursemodule
1196   * @param stdClass $assign the database assign record
1197   *
1198   * This prints the submission summary and feedback summary for this student.
1199   */
1200  function assign_user_complete($course, $user, $coursemodule, $assign) {
1201      global $CFG;
1202      require_once($CFG->dirroot . '/mod/assign/locallib.php');
1203  
1204      $context = context_module::instance($coursemodule->id);
1205  
1206      $assignment = new assign($context, $coursemodule, $course);
1207  
1208      echo $assignment->view_student_summary($user, false);
1209  }
1210  
1211  /**
1212   * Rescale all grades for this activity and push the new grades to the gradebook.
1213   *
1214   * @param stdClass $course Course db record
1215   * @param stdClass $cm Course module db record
1216   * @param float $oldmin
1217   * @param float $oldmax
1218   * @param float $newmin
1219   * @param float $newmax
1220   */
1221  function assign_rescale_activity_grades($course, $cm, $oldmin, $oldmax, $newmin, $newmax) {
1222      global $DB;
1223  
1224      if ($oldmax <= $oldmin) {
1225          // Grades cannot be scaled.
1226          return false;
1227      }
1228      $scale = ($newmax - $newmin) / ($oldmax - $oldmin);
1229      if (($newmax - $newmin) <= 1) {
1230          // We would lose too much precision, lets bail.
1231          return false;
1232      }
1233  
1234      $params = array(
1235          'p1' => $oldmin,
1236          'p2' => $scale,
1237          'p3' => $newmin,
1238          'a' => $cm->instance
1239      );
1240  
1241      // Only rescale grades that are greater than or equal to 0. Anything else is a special value.
1242      $sql = 'UPDATE {assign_grades} set grade = (((grade - :p1) * :p2) + :p3) where assignment = :a and grade >= 0';
1243      $dbupdate = $DB->execute($sql, $params);
1244      if (!$dbupdate) {
1245          return false;
1246      }
1247  
1248      // Now re-push all grades to the gradebook.
1249      $dbparams = array('id' => $cm->instance);
1250      $assign = $DB->get_record('assign', $dbparams);
1251      $assign->cmidnumber = $cm->idnumber;
1252  
1253      assign_update_grades($assign);
1254  
1255      return true;
1256  }
1257  
1258  /**
1259   * Print the grade information for the assignment for this user.
1260   *
1261   * @param stdClass $course
1262   * @param stdClass $user
1263   * @param stdClass $coursemodule
1264   * @param stdClass $assignment
1265   */
1266  function assign_user_outline($course, $user, $coursemodule, $assignment) {
1267      global $CFG;
1268      require_once($CFG->libdir.'/gradelib.php');
1269      require_once($CFG->dirroot.'/grade/grading/lib.php');
1270  
1271      $gradinginfo = grade_get_grades($course->id,
1272                                          'mod',
1273                                          'assign',
1274                                          $assignment->id,
1275                                          $user->id);
1276  
1277      $gradingitem = $gradinginfo->items[0];
1278      $gradebookgrade = $gradingitem->grades[$user->id];
1279  
1280      if (empty($gradebookgrade->str_long_grade)) {
1281          return null;
1282      }
1283      $result = new stdClass();
1284      if (!$gradingitem->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
1285          $result->info = get_string('outlinegrade', 'assign', $gradebookgrade->str_long_grade);
1286      } else {
1287          $result->info = get_string('grade') . ': ' . get_string('hidden', 'grades');
1288      }
1289      $result->time = $gradebookgrade->dategraded;
1290  
1291      return $result;
1292  }
1293  
1294  /**
1295   * Obtains the automatic completion state for this module based on any conditions
1296   * in assign settings.
1297   *
1298   * @param object $course Course
1299   * @param object $cm Course-module
1300   * @param int $userid User ID
1301   * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
1302   * @return bool True if completed, false if not, $type if conditions not set.
1303   */
1304  function assign_get_completion_state($course, $cm, $userid, $type) {
1305      global $CFG, $DB;
1306      require_once($CFG->dirroot . '/mod/assign/locallib.php');
1307  
1308      $assign = new assign(null, $cm, $course);
1309  
1310      // If completion option is enabled, evaluate it and return true/false.
1311      if ($assign->get_instance()->completionsubmit) {
1312          if ($assign->get_instance()->teamsubmission) {
1313              $submission = $assign->get_group_submission($userid, 0, false);
1314          } else {
1315              $submission = $assign->get_user_submission($userid, false);
1316          }
1317          return $submission && $submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1318      } else {
1319          // Completion option is not enabled so just return $type.
1320          return $type;
1321      }
1322  }
1323  
1324  /**
1325   * Serves intro attachment files.
1326   *
1327   * @param mixed $course course or id of the course
1328   * @param mixed $cm course module or id of the course module
1329   * @param context $context
1330   * @param string $filearea
1331   * @param array $args
1332   * @param bool $forcedownload
1333   * @param array $options additional options affecting the file serving
1334   * @return bool false if file not found, does not return if found - just send the file
1335   */
1336  function assign_pluginfile($course,
1337                  $cm,
1338                  context $context,
1339                  $filearea,
1340                  $args,
1341                  $forcedownload,
1342                  array $options=array()) {
1343      global $CFG;
1344  
1345      if ($context->contextlevel != CONTEXT_MODULE) {
1346          return false;
1347      }
1348  
1349      require_login($course, false, $cm);
1350      if (!has_capability('mod/assign:view', $context)) {
1351          return false;
1352      }
1353  
1354      require_once($CFG->dirroot . '/mod/assign/locallib.php');
1355      $assign = new assign($context, $cm, $course);
1356  
1357      if ($filearea !== ASSIGN_INTROATTACHMENT_FILEAREA) {
1358          return false;
1359      }
1360      if (!$assign->show_intro()) {
1361          return false;
1362      }
1363  
1364      $itemid = (int)array_shift($args);
1365      if ($itemid != 0) {
1366          return false;
1367      }
1368  
1369      $relativepath = implode('/', $args);
1370  
1371      $fullpath = "/{$context->id}/mod_assign/$filearea/$itemid/$relativepath";
1372  
1373      $fs = get_file_storage();
1374      if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1375          return false;
1376      }
1377      send_stored_file($file, 0, 0, $forcedownload, $options);
1378  }
1379  
1380  /**
1381   * Serve the grading panel as a fragment.
1382   *
1383   * @param array $args List of named arguments for the fragment loader.
1384   * @return string
1385   */
1386  function mod_assign_output_fragment_gradingpanel($args) {
1387      global $CFG;
1388  
1389      $context = $args['context'];
1390  
1391      if ($context->contextlevel != CONTEXT_MODULE) {
1392          return null;
1393      }
1394      require_once($CFG->dirroot . '/mod/assign/locallib.php');
1395      $assign = new assign($context, null, null);
1396  
1397      $userid = clean_param($args['userid'], PARAM_INT);
1398      $attemptnumber = clean_param($args['attemptnumber'], PARAM_INT);
1399      $formdata = array();
1400      if (!empty($args['jsonformdata'])) {
1401          $serialiseddata = json_decode($args['jsonformdata']);
1402          parse_str($serialiseddata, $formdata);
1403      }
1404      $viewargs = array(
1405          'userid' => $userid,
1406          'attemptnumber' => $attemptnumber,
1407          'formdata' => $formdata
1408      );
1409  
1410      return $assign->view('gradingpanel', $viewargs);
1411  }
1412  
1413  /**
1414   * Check if the module has any update that affects the current user since a given time.
1415   *
1416   * @param  cm_info $cm course module data
1417   * @param  int $from the time to check updates from
1418   * @param  array $filter  if we need to check only specific updates
1419   * @return stdClass an object with the different type of areas indicating if they were updated or not
1420   * @since Moodle 3.2
1421   */
1422  function assign_check_updates_since(cm_info $cm, $from, $filter = array()) {
1423      global $DB, $USER, $CFG;
1424      require_once($CFG->dirroot . '/mod/assign/locallib.php');
1425  
1426      $updates = new stdClass();
1427      $updates = course_check_module_updates_since($cm, $from, array(ASSIGN_INTROATTACHMENT_FILEAREA), $filter);
1428  
1429      // Check if there is a new submission by the user or new grades.
1430      $select = 'assignment = :id AND userid = :userid AND (timecreated > :since1 OR timemodified > :since2)';
1431      $params = array('id' => $cm->instance, 'userid' => $USER->id, 'since1' => $from, 'since2' => $from);
1432      $updates->submissions = (object) array('updated' => false);
1433      $submissions = $DB->get_records_select('assign_submission', $select, $params, '', 'id');
1434      if (!empty($submissions)) {
1435          $updates->submissions->updated = true;
1436          $updates->submissions->itemids = array_keys($submissions);
1437      }
1438  
1439      $updates->grades = (object) array('updated' => false);
1440      $grades = $DB->get_records_select('assign_grades', $select, $params, '', 'id');
1441      if (!empty($grades)) {
1442          $updates->grades->updated = true;
1443          $updates->grades->itemids = array_keys($grades);
1444      }
1445  
1446      // Now, teachers should see other students updates.
1447      if (has_capability('mod/assign:viewgrades', $cm->context)) {
1448          $params = array('id' => $cm->instance, 'since1' => $from, 'since2' => $from);
1449          $select = 'assignment = :id AND (timecreated > :since1 OR timemodified > :since2)';
1450  
1451          if (groups_get_activity_groupmode($cm) == SEPARATEGROUPS) {
1452              $groupusers = array_keys(groups_get_activity_shared_group_members($cm));
1453              if (empty($groupusers)) {
1454                  return $updates;
1455              }
1456              list($insql, $inparams) = $DB->get_in_or_equal($groupusers, SQL_PARAMS_NAMED);
1457              $select .= ' AND userid ' . $insql;
1458              $params = array_merge($params, $inparams);
1459          }
1460  
1461          $updates->usersubmissions = (object) array('updated' => false);
1462          $submissions = $DB->get_records_select('assign_submission', $select, $params, '', 'id');
1463          if (!empty($submissions)) {
1464              $updates->usersubmissions->updated = true;
1465              $updates->usersubmissions->itemids = array_keys($submissions);
1466          }
1467  
1468          $updates->usergrades = (object) array('updated' => false);
1469          $grades = $DB->get_records_select('assign_grades', $select, $params, '', 'id');
1470          if (!empty($grades)) {
1471              $updates->usergrades->updated = true;
1472              $updates->usergrades->itemids = array_keys($grades);
1473          }
1474      }
1475  
1476      return $updates;
1477  }
1478  
1479  /**
1480   * Is the event visible?
1481   *
1482   * This is used to determine global visibility of an event in all places throughout Moodle. For example,
1483   * the ASSIGN_EVENT_TYPE_GRADINGDUE event will not be shown to students on their calendar.
1484   *
1485   * @param calendar_event $event
1486   * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default).
1487   * @return bool Returns true if the event is visible to the current user, false otherwise.
1488   */
1489  function mod_assign_core_calendar_is_event_visible(calendar_event $event, $userid = 0) {
1490      global $CFG, $USER;
1491  
1492      require_once($CFG->dirroot . '/mod/assign/locallib.php');
1493  
1494      if (empty($userid)) {
1495          $userid = $USER->id;
1496      }
1497  
1498      $cm = get_fast_modinfo($event->courseid, $userid)->instances['assign'][$event->instance];
1499      $context = context_module::instance($cm->id);
1500  
1501      $assign = new assign($context, $cm, null);
1502  
1503      if ($event->eventtype == ASSIGN_EVENT_TYPE_GRADINGDUE) {
1504          return $assign->can_grade($userid);
1505      } else {
1506          return true;
1507      }
1508  }
1509  
1510  /**
1511   * This function receives a calendar event and returns the action associated with it, or null if there is none.
1512   *
1513   * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event
1514   * is not displayed on the block.
1515   *
1516   * @param calendar_event $event
1517   * @param \core_calendar\action_factory $factory
1518   * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default).
1519   * @return \core_calendar\local\event\entities\action_interface|null
1520   */
1521  function mod_assign_core_calendar_provide_event_action(calendar_event $event,
1522                                                         \core_calendar\action_factory $factory,
1523                                                         $userid = 0) {
1524  
1525      global $CFG, $USER;
1526  
1527      require_once($CFG->dirroot . '/mod/assign/locallib.php');
1528  
1529      if (empty($userid)) {
1530          $userid = $USER->id;
1531      }
1532  
1533      $cm = get_fast_modinfo($event->courseid, $userid)->instances['assign'][$event->instance];
1534      $context = context_module::instance($cm->id);
1535  
1536      $completion = new \completion_info($cm->get_course());
1537  
1538      $completiondata = $completion->get_data($cm, false, $userid);
1539  
1540      if ($completiondata->completionstate != COMPLETION_INCOMPLETE) {
1541          return null;
1542      }
1543  
1544      $assign = new assign($context, $cm, null);
1545  
1546      // Apply overrides.
1547      $assign->update_effective_access($userid);
1548  
1549      if ($event->eventtype == ASSIGN_EVENT_TYPE_GRADINGDUE) {
1550          $name = get_string('grade');
1551          $url = new \moodle_url('/mod/assign/view.php', [
1552              'id' => $cm->id,
1553              'action' => 'grader'
1554          ]);
1555          $actionable = $assign->can_grade($userid) && (time() >= $assign->get_instance()->allowsubmissionsfromdate);
1556          $itemcount = $actionable ? $assign->count_submissions_need_grading() : 0;
1557      } else {
1558          $usersubmission = $assign->get_user_submission($userid, false);
1559          if ($usersubmission && $usersubmission->status === ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
1560              // The user has already submitted.
1561              // We do not want to change the text to edit the submission, we want to remove the event from the Dashboard entirely.
1562              return null;
1563          }
1564  
1565          $participant = $assign->get_participant($userid);
1566  
1567          if (!$participant) {
1568              // If the user is not a participant in the assignment then they have
1569              // no action to take. This will filter out the events for teachers.
1570              return null;
1571          }
1572  
1573          // The user has not yet submitted anything. Show the addsubmission link.
1574          $name = get_string('addsubmission', 'assign');
1575          $url = new \moodle_url('/mod/assign/view.php', [
1576              'id' => $cm->id,
1577              'action' => 'editsubmission'
1578          ]);
1579          $itemcount = 1;
1580          $actionable = $assign->is_any_submission_plugin_enabled() && $assign->can_edit_submission($userid, $userid);
1581      }
1582  
1583      return $factory->create_instance(
1584          $name,
1585          $url,
1586          $itemcount,
1587          $actionable
1588      );
1589  }
1590  
1591  /**
1592   * Callback function that determines whether an action event should be showing its item count
1593   * based on the event type and the item count.
1594   *
1595   * @param calendar_event $event The calendar event.
1596   * @param int $itemcount The item count associated with the action event.
1597   * @return bool
1598   */
1599  function mod_assign_core_calendar_event_action_shows_item_count(calendar_event $event, $itemcount = 0) {
1600      // List of event types where the action event's item count should be shown.
1601      $eventtypesshowingitemcount = [
1602          ASSIGN_EVENT_TYPE_GRADINGDUE
1603      ];
1604      // For mod_assign, item count should be shown if the event type is 'gradingdue' and there is one or more item count.
1605      return in_array($event->eventtype, $eventtypesshowingitemcount) && $itemcount > 0;
1606  }
1607  
1608  /**
1609   * This function calculates the minimum and maximum cutoff values for the timestart of
1610   * the given event.
1611   *
1612   * It will return an array with two values, the first being the minimum cutoff value and
1613   * the second being the maximum cutoff value. Either or both values can be null, which
1614   * indicates there is no minimum or maximum, respectively.
1615   *
1616   * If a cutoff is required then the function must return an array containing the cutoff
1617   * timestamp and error string to display to the user if the cutoff value is violated.
1618   *
1619   * A minimum and maximum cutoff return value will look like:
1620   * [
1621   *     [1505704373, 'The due date must be after the sbumission start date'],
1622   *     [1506741172, 'The due date must be before the cutoff date']
1623   * ]
1624   *
1625   * If the event does not have a valid timestart range then [false, false] will
1626   * be returned.
1627   *
1628   * @param calendar_event $event The calendar event to get the time range for
1629   * @param stdClass $instance The module instance to get the range from
1630   * @return array
1631   */
1632  function mod_assign_core_calendar_get_valid_event_timestart_range(\calendar_event $event, \stdClass $instance) {
1633      global $CFG;
1634  
1635      require_once($CFG->dirroot . '/mod/assign/locallib.php');
1636  
1637      $courseid = $event->courseid;
1638      $modulename = $event->modulename;
1639      $instanceid = $event->instance;
1640      $coursemodule = get_fast_modinfo($courseid)->instances[$modulename][$instanceid];
1641      $context = context_module::instance($coursemodule->id);
1642      $assign = new assign($context, null, null);
1643      $assign->set_instance($instance);
1644  
1645      return $assign->get_valid_calendar_event_timestart_range($event);
1646  }
1647  
1648  /**
1649   * This function will update the assign module according to the
1650   * event that has been modified.
1651   *
1652   * @throws \moodle_exception
1653   * @param \calendar_event $event
1654   * @param stdClass $instance The module instance to get the range from
1655   */
1656  function mod_assign_core_calendar_event_timestart_updated(\calendar_event $event, \stdClass $instance) {
1657      global $CFG, $DB;
1658  
1659      require_once($CFG->dirroot . '/mod/assign/locallib.php');
1660  
1661      if (empty($event->instance) || $event->modulename != 'assign') {
1662          return;
1663      }
1664  
1665      if ($instance->id != $event->instance) {
1666          return;
1667      }
1668  
1669      if (!in_array($event->eventtype, [ASSIGN_EVENT_TYPE_DUE, ASSIGN_EVENT_TYPE_GRADINGDUE])) {
1670          return;
1671      }
1672  
1673      $courseid = $event->courseid;
1674      $modulename = $event->modulename;
1675      $instanceid = $event->instance;
1676      $modified = false;
1677      $coursemodule = get_fast_modinfo($courseid)->instances[$modulename][$instanceid];
1678      $context = context_module::instance($coursemodule->id);
1679  
1680      // The user does not have the capability to modify this activity.
1681      if (!has_capability('moodle/course:manageactivities', $context)) {
1682          return;
1683      }
1684  
1685      $assign = new assign($context, $coursemodule, null);
1686      $assign->set_instance($instance);
1687  
1688      if ($event->eventtype == ASSIGN_EVENT_TYPE_DUE) {
1689          // This check is in here because due date events are currently
1690          // the only events that can be overridden, so we can save a DB
1691          // query if we don't bother checking other events.
1692          if ($assign->is_override_calendar_event($event)) {
1693              // This is an override event so we should ignore it.
1694              return;
1695          }
1696  
1697          $newduedate = $event->timestart;
1698  
1699          if ($newduedate != $instance->duedate) {
1700              $instance->duedate = $newduedate;
1701              $modified = true;
1702          }
1703      } else if ($event->eventtype == ASSIGN_EVENT_TYPE_GRADINGDUE) {
1704          $newduedate = $event->timestart;
1705  
1706          if ($newduedate != $instance->gradingduedate) {
1707              $instance->gradingduedate = $newduedate;
1708              $modified = true;
1709          }
1710      }
1711  
1712      if ($modified) {
1713          $instance->timemodified = time();
1714          // Persist the assign instance changes.
1715          $DB->update_record('assign', $instance);
1716          $assign->update_calendar($coursemodule->id);
1717          $event = \core\event\course_module_updated::create_from_cm($coursemodule, $context);
1718          $event->trigger();
1719      }
1720  }
1721  
1722  /**
1723   * Return a list of all the user preferences used by mod_assign.
1724   *
1725   * @return array
1726   */
1727  function mod_assign_user_preferences() {
1728      $preferences = array();
1729      $preferences['assign_filter'] = array(
1730          'type' => PARAM_ALPHA,
1731          'null' => NULL_NOT_ALLOWED,
1732          'default' => ''
1733      );
1734      $preferences['assign_workflowfilter'] = array(
1735          'type' => PARAM_ALPHA,
1736          'null' => NULL_NOT_ALLOWED,
1737          'default' => ''
1738      );
1739      $preferences['assign_markerfilter'] = array(
1740          'type' => PARAM_ALPHANUMEXT,
1741          'null' => NULL_NOT_ALLOWED,
1742          'default' => ''
1743      );
1744  
1745      return $preferences;
1746  }
1747  
1748  /**
1749   * Given an array with a file path, it returns the itemid and the filepath for the defined filearea.
1750   *
1751   * @param  string $filearea The filearea.
1752   * @param  array  $args The path (the part after the filearea and before the filename).
1753   * @return array The itemid and the filepath inside the $args path, for the defined filearea.
1754   */
1755  function mod_assign_get_path_from_pluginfile(string $filearea, array $args) : array {
1756      // Assign never has an itemid (the number represents the revision but it's not stored in database).
1757      array_shift($args);
1758  
1759      // Get the filepath.
1760      if (empty($args)) {
1761          $filepath = '/';
1762      } else {
1763          $filepath = '/' . implode('/', $args) . '/';
1764      }
1765  
1766      return [
1767          'itemid' => 0,
1768          'filepath' => $filepath,
1769      ];
1770  }