Merge branch 'MDL-44598_master' of https://github.com/jonof/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Mon, 30 Nov 2015 22:52:45 +0000 (23:52 +0100)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Mon, 30 Nov 2015 22:52:45 +0000 (23:52 +0100)
1  2 
mod/assign/locallib.php

diff --combined mod/assign/locallib.php
@@@ -140,9 -140,6 +140,9 @@@ class assign 
      /** @var bool whether to exclude users with inactive enrolment */
      private $showonlyactiveenrol = null;
  
 +    /** @var string A key used to identify cached userlists created by this object. */
 +    private $useridlistid = null;
 +
      /** @var array cached list of participants for this assignment. The cache key will be group, showactive and the context id */
      private $participants = array();
  
      /** @var array cached list of user groups. The cache key will be the user. */
      private $usergroups = array();
  
 +    /** @var array cached list of IDs of users who share group membership with the user. The cache key will be the user. */
 +    private $sharedgroupmembers = array();
 +
      /**
       * Constructor for the base assign class.
       *
  
          $this->submissionplugins = $this->load_plugins('assignsubmission');
          $this->feedbackplugins = $this->load_plugins('assignfeedback');
 +
 +        // Extra entropy is required for uniqid() to work on cygwin.
 +        $this->useridlistid = clean_param(uniqid('', true), PARAM_ALPHANUM);
      }
  
      /**
                      $action = 'redirect';
                      $nextpageparams['action'] = 'grade';
                      $nextpageparams['rownum'] = optional_param('rownum', 0, PARAM_INT) + 1;
 -                    $nextpageparams['useridlistid'] = optional_param('useridlistid', time(), PARAM_INT);
 +                    $nextpageparams['useridlistid'] = optional_param('useridlistid', $this->get_useridlist_key_id(), PARAM_ALPHANUM);
                  }
              } else if (optional_param('nosaveandprevious', null, PARAM_RAW)) {
                  $action = 'redirect';
                  $nextpageparams['action'] = 'grade';
                  $nextpageparams['rownum'] = optional_param('rownum', 0, PARAM_INT) - 1;
 -                $nextpageparams['useridlistid'] = optional_param('useridlistid', time(), PARAM_INT);
 +                $nextpageparams['useridlistid'] = optional_param('useridlistid', $this->get_useridlist_key_id(), PARAM_ALPHANUM);
              } else if (optional_param('nosaveandnext', null, PARAM_RAW)) {
                  $action = 'redirect';
                  $nextpageparams['action'] = 'grade';
                  $nextpageparams['rownum'] = optional_param('rownum', 0, PARAM_INT) + 1;
 -                $nextpageparams['useridlistid'] = optional_param('useridlistid', time(), PARAM_INT);
 +                $nextpageparams['useridlistid'] = optional_param('useridlistid', $this->get_useridlist_key_id(), PARAM_ALPHANUM);
              } else if (optional_param('savegrade', null, PARAM_RAW)) {
                  // Save changes button.
                  $action = 'grade';
          }
  
          $returnparams = array('rownum'=>optional_param('rownum', 0, PARAM_INT),
 -                              'useridlistid'=>optional_param('useridlistid', 0, PARAM_INT));
 +                              'useridlistid' => optional_param('useridlistid', $this->get_useridlist_key_id(), PARAM_ALPHANUM));
          $this->register_return_link($action, $returnparams);
  
          // Now show the right view page.
          // Special case for add_instance as the coursemodule has not been set yet.
          $instance = $this->get_instance();
  
 +        $eventtype = 'due';
 +
          if ($instance->duedate) {
              $event = new stdClass();
  
 -            $params = array('modulename'=>'assign', 'instance'=>$instance->id);
 +            $params = array('modulename' => 'assign', 'instance' => $instance->id, 'eventtype' => $eventtype);
              $event->id = $DB->get_field('event', 'id', $params);
              $event->name = $instance->name;
              $event->timestart = $instance->duedate;
                  $event->userid      = 0;
                  $event->modulename  = 'assign';
                  $event->instance    = $instance->id;
 -                $event->eventtype   = 'due';
 +                $event->eventtype   = $eventtype;
                  $event->timeduration = 0;
                  calendar_event::create($event);
              }
          } else {
 -            $DB->delete_records('event', array('modulename'=>'assign', 'instance'=>$instance->id));
 +            $DB->delete_records('event', array('modulename' => 'assign', 'instance' => $instance->id, 'eventtype' => $eventtype));
          }
      }
  
                          s.assignment = :assignid AND
                          s.timemodified IS NOT NULL AND
                          s.status = :submitted AND
 -                        (s.timemodified > g.timemodified OR g.timemodified IS NULL OR g.grade IS NULL)';
 +                        (s.timemodified >= g.timemodified OR g.timemodified IS NULL OR g.grade IS NULL)';
  
          return $DB->count_records_sql($sql, $params);
      }
          require_once($CFG->dirroot . '/mod/assign/extensionform.php');
  
          $o = '';
-         $batchusers = optional_param('selectedusers', '', PARAM_SEQUENCE);
          $data = new stdClass();
-         $data->extensionduedate = null;
-         $userid = 0;
-         if (!$batchusers) {
-             $userid = required_param('userid', PARAM_INT);
+         $data->id = $this->get_course_module()->id;
  
-             $flags = $this->get_user_flags($userid, false);
+         $formparams = array(
+             'instance' => $this->get_instance()
+         );
+         $extrauserfields = get_extra_user_fields($this->get_context());
+         if ($mform) {
+             $submitteddata = $mform->get_data();
+             $users = $submitteddata->selectedusers;
+             $userlist = explode(',', $users);
+             $data->selectedusers = $users;
+             $data->userid = 0;
  
+             $usershtml = '';
+             $usercount = 0;
+             foreach ($userlist as $userid) {
+                 if ($usercount >= 5) {
+                     $usershtml .= get_string('moreusers', 'assign', count($userlist) - 5);
+                     break;
+                 }
+                 $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
+                 $usershtml .= $this->get_renderer()->render(new assign_user_summary($user,
+                                                                     $this->get_course()->id,
+                                                                     has_capability('moodle/site:viewfullnames',
+                                                                     $this->get_course_context()),
+                                                                     $this->is_blind_marking(),
+                                                                     $this->get_uniqueid_for_user($user->id),
+                                                                     $extrauserfields,
+                                                                     !$this->is_active_user($userid)));
+                 $usercount += 1;
+             }
+             $formparams['userscount'] = count($userlist);
+             $formparams['usershtml'] = $usershtml;
+         } else {
+             $userid = required_param('userid', PARAM_INT);
              $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
+             $flags = $this->get_user_flags($userid, false);
  
+             $data->userid = $user->id;
              if ($flags) {
                  $data->extensionduedate = $flags->extensionduedate;
              }
-             $data->userid = $userid;
-         } else {
-             $data->batchusers = $batchusers;
+             $usershtml = $this->get_renderer()->render(new assign_user_summary($user,
+                                                                 $this->get_course()->id,
+                                                                 has_capability('moodle/site:viewfullnames',
+                                                                 $this->get_course_context()),
+                                                                 $this->is_blind_marking(),
+                                                                 $this->get_uniqueid_for_user($user->id),
+                                                                 $extrauserfields,
+                                                                 !$this->is_active_user($userid)));
+             $formparams['usershtml'] = $usershtml;
          }
+         $mform = new mod_assign_extension_form(null, $formparams);
+         $mform->set_data($data);
          $header = new assign_header($this->get_instance(),
                                      $this->get_context(),
                                      $this->show_intro(),
                                      $this->get_course_module()->id,
                                      get_string('grantextension', 'assign'));
          $o .= $this->get_renderer()->render($header);
-         if (!$mform) {
-             $formparams = array($this->get_course_module()->id,
-                                 $userid,
-                                 $batchusers,
-                                 $this->get_instance(),
-                                 $data);
-             $mform = new mod_assign_extension_form(null, $formparams);
-         }
          $o .= $this->get_renderer()->render(new assign_form('extensionform', $mform));
          $o .= $this->view_footer();
          return $o;
       * @param int $userid Teh id of the user who's groups we are checking
       * @return array The group objects
       */
 -    protected function get_all_groups($userid) {
 +    public function get_all_groups($userid) {
          if (isset($this->usergroups[$userid])) {
              return $this->usergroups[$userid];
          }
          // More efficient to load this here.
          require_once($CFG->libdir.'/filelib.php');
  
 +        // Increase the server timeout to handle the creation and sending of large zip files.
 +        core_php_time_limit::raise();
 +
          $this->require_view_grades();
  
          // Load all users with submit.
          if (!$userid) {
              $userid = $USER->id;
          }
 +        $submission = null;
  
          $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid);
 -        if ($attemptnumber < 0) {
 +        if ($attemptnumber < 0 || $create) {
              // Make sure this grade matches the latest submission attempt.
              if ($this->get_instance()->teamsubmission) {
                  $submission = $this->get_group_submission($userid, 0, true);
              $grade->assignment   = $this->get_instance()->id;
              $grade->userid       = $userid;
              $grade->timecreated = time();
 -            $grade->timemodified = $grade->timecreated;
 +            // If we are "auto-creating" a grade - and there is a submission
 +            // the new grade should not have a more recent timemodified value
 +            // than the submission.
 +            if ($submission) {
 +                $grade->timemodified = $submission->timemodified;
 +            } else {
 +                $grade->timemodified = $grade->timecreated;
 +            }
              $grade->grade = -1;
              $grade->grader = $USER->id;
              if ($attemptnumber >= 0) {
  
          // If userid is passed - we are only grading a single student.
          $rownum = required_param('rownum', PARAM_INT);
 -        $useridlistid = optional_param('useridlistid', time(), PARAM_INT);
 +        $useridlistid = optional_param('useridlistid', $this->get_useridlist_key_id(), PARAM_ALPHANUM);
          $userid = optional_param('userid', 0, PARAM_INT);
          $attemptnumber = optional_param('attemptnumber', -1, PARAM_INT);
  
          $cache = cache::make_from_params(cache_store::MODE_SESSION, 'mod_assign', 'useridlist');
          if (!$userid) {
 -            if (!$useridlist = $cache->get($this->get_course_module()->id . '_' . $useridlistid)) {
 +            if (!$useridlist = $cache->get($this->get_useridlist_key($useridlistid))) {
                  $useridlist = $this->get_grading_userid_list();
              }
 -            $cache->set($this->get_course_module()->id . '_' . $useridlistid, $useridlist);
 +            $cache->set($this->get_useridlist_key($useridlistid), $useridlist);
          } else {
              $rownum = 0;
              $useridlist = array($userid);
          if ($rownum == count($useridlist) - 1) {
              $last = true;
          }
 +        // This variation on the url will link direct to this student, with no next/previous links.
 +        // The benefit is the url will be the same every time for this student, so Atto autosave drafts can match up.
 +        $returnparams = array('userid' => $userid, 'rownum' => 0, 'useridlistid' => 0);
 +        $this->register_return_link('grade', $returnparams);
 +
          $user = $DB->get_record('user', array('id' => $userid));
          if ($user) {
              $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_course_context());
              $o .= $this->get_renderer()->render($gradingtable);
          }
  
 +        if ($this->can_grade()) {
 +            // We need to cache the order of uses in the table as the person may wish to grade them.
 +            // This is done based on the row number of the user.
 +            $cache = cache::make_from_params(cache_store::MODE_SESSION, 'mod_assign', 'useridlist');
 +            $useridlist = $gradingtable->get_column_data('userid');
 +            $cache->set($this->get_useridlist_key(), $useridlist);
 +        }
 +
          $currentgroup = groups_get_activity_group($this->get_course_module(), true);
          $users = array_keys($this->list_participants($currentgroup, true));
          if (count($users) != 0 && $this->can_grade()) {
  
              if ($data->operation == 'grantextension') {
                  // Reset the form so the grant extension page will create the extension form.
-                 $mform = null;
                  return 'grantextension';
              } else if ($data->operation == 'setmarkingworkflowstate') {
                  return 'viewbatchsetmarkingworkflowstate';
  
          $cm = $this->get_course_module();
          if (groups_get_activity_groupmode($cm) == SEPARATEGROUPS) {
 -            // These arrays are indexed by groupid.
 -            $studentgroups = array_keys(groups_get_activity_allowed_groups($cm, $userid));
 -            $gradergroups = array_keys(groups_get_activity_allowed_groups($cm, $graderid));
 -
 -            return count(array_intersect($studentgroups, $gradergroups)) > 0;
 +            $sharedgroupmembers = $this->get_shared_group_members($cm, $graderid);
 +            return in_array($userid, $sharedgroupmembers);
          }
          return true;
      }
  
 +    /**
 +     * Returns IDs of the users who share group membership with the specified user.
 +     *
 +     * @param stdClass|cm_info $cm Course-module
 +     * @param int $userid User ID
 +     * @return array An array of ID of users.
 +     */
 +    public function get_shared_group_members($cm, $userid) {
 +        if (!isset($this->sharedgroupmembers[$userid])) {
 +            $this->sharedgroupmembers[$userid] = array();
 +            $groupsids = array_keys(groups_get_activity_allowed_groups($cm, $userid));
 +            foreach ($groupsids as $groupid) {
 +                $members = array_keys(groups_get_members($groupid, 'u.id'));
 +                $this->sharedgroupmembers[$userid] = array_merge($this->sharedgroupmembers[$userid], $members);
 +            }
 +        }
 +
 +        return $this->sharedgroupmembers[$userid];
 +    }
 +
      /**
       * Returns a list of teachers that should be grading given submission.
       *
          require_once($CFG->dirroot . '/mod/assign/extensionform.php');
          require_sesskey();
  
-         $batchusers = optional_param('selectedusers', '', PARAM_SEQUENCE);
-         $userid = 0;
-         if (!$batchusers) {
-             $userid = required_param('userid', PARAM_INT);
-             $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
-         }
-         $mform = new mod_assign_extension_form(null, array($this->get_course_module()->id,
-                                                            $userid,
-                                                            $batchusers,
-                                                            $this->get_instance(),
-                                                            null));
+         $formparams = array(
+             'instance' => $this->get_instance(),
+             'userscount' => 0,
+             'usershtml' => '',
+         );
+         $mform = new mod_assign_extension_form(null, $formparams);
  
          if ($mform->is_cancelled()) {
              return true;
          }
  
          if ($formdata = $mform->get_data()) {
-             if ($batchusers) {
-                 $users = explode(',', $batchusers);
+             if (!empty($formdata->selectedusers)) {
+                 $users = explode(',', $formdata->selectedusers);
                  $result = true;
                  foreach ($users as $userid) {
-                     $result = $this->save_user_extension($userid, $formdata->extensionduedate) && $result;
+                     $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
+                     $result = $this->save_user_extension($user->id, $formdata->extensionduedate) && $result;
                  }
                  return $result;
-             } else {
-                 return $this->save_user_extension($userid, $formdata->extensionduedate);
+             }
+             if (!empty($formdata->userid)) {
+                 $user = $DB->get_record('user', array('id' => $formdata->userid), '*', MUST_EXIST);
+                 return $this->save_user_extension($user->id, $formdata->extensionduedate);
              }
          }
          return false;
      }
  
          require_once($CFG->dirroot . '/mod/assign/gradingoptionsform.php');
  
          // Need submit permission to submit an assignment.
 -        require_capability('mod/assign:grade', $this->context);
 +        $this->require_view_grades();
          require_sesskey();
  
          // Is advanced grading enabled?
          $attemptnumber = $params['attemptnumber'];
          if (!$userid) {
              $cache = cache::make_from_params(cache_store::MODE_SESSION, 'mod_assign', 'useridlist');
 -            if (!$useridlist = $cache->get($this->get_course_module()->id . '_' . $useridlistid)) {
 +            if (!$useridlist = $cache->get($this->get_useridlist_key($useridlistid))) {
                  $useridlist = $this->get_grading_userid_list();
 -                $cache->set($this->get_course_module()->id . '_' . $useridlistid, $useridlist);
 +                $cache->set($this->get_useridlist_key($useridlistid), $useridlist);
              }
          } else {
              $useridlist = array($userid);
          $mform->setType('rownum', PARAM_INT);
          $mform->setConstant('rownum', $rownum);
          $mform->addElement('hidden', 'useridlistid', $useridlistid);
 -        $mform->setType('useridlistid', PARAM_INT);
 +        $mform->setType('useridlistid', PARAM_ALPHANUM);
          $mform->addElement('hidden', 'attemptnumber', $attemptnumber);
          $mform->setType('attemptnumber', PARAM_INT);
          $mform->addElement('hidden', 'ajax', optional_param('ajax', 0, PARAM_INT));
          global $USER, $CFG, $DB;
  
          $grade = $this->get_user_grade($userid, true, $attemptnumber);
 +        $originalgrade = $grade->grade;
          $gradingdisabled = $this->grading_disabled($userid);
          $gradinginstance = $this->get_grading_instance($userid, $grade, $gradingdisabled);
          if (!$gradingdisabled) {
                  }
              }
          }
 -        $this->update_grade($grade, !empty($formdata->addattempt));
 +        // We do not want to update the timemodified if no grade was added.
 +        if (!empty($formdata->addattempt) ||
 +                ($originalgrade !== null && $originalgrade != -1) ||
 +                ($grade->grade !== null && $grade->grade != -1)) {
 +            $this->update_grade($grade, !empty($formdata->addattempt));
 +        }
          // Note the default if not provided for this option is true (e.g. webservices).
          // This is for backwards compatibility.
          if (!isset($formdata->sendstudentnotifications) || $formdata->sendstudentnotifications) {
          $instance = $this->get_instance();
          $rownum = required_param('rownum', PARAM_INT);
          $attemptnumber = optional_param('attemptnumber', -1, PARAM_INT);
 -        $useridlistid = optional_param('useridlistid', time(), PARAM_INT);
 +        $useridlistid = optional_param('useridlistid', $this->get_useridlist_key_id(), PARAM_ALPHANUM);
          $userid = optional_param('userid', 0, PARAM_INT);
          $cache = cache::make_from_params(cache_store::MODE_SESSION, 'mod_assign', 'useridlist');
          if (!$userid) {
 -            if (!$useridlist = $cache->get($this->get_course_module()->id . '_' . $useridlistid)) {
 -                $useridlist = $this->get_grading_userid_list();
 -                $cache->set($this->get_course_module()->id . '_' . $useridlistid, $useridlist);
 +            if (!$useridlist = $cache->get($this->get_useridlist_key($useridlistid))) {
 +                // If the userid list is not cached we must not save, as it is possible that the user in a
 +                // given row position may not be the same now as when the grading page was generated.
 +                $url = new moodle_url('/mod/assign/view.php', array('id' => $this->get_course_module()->id));
 +                throw new moodle_exception('useridlistnotcached', 'mod_assign', $url);
              }
          } else {
              $useridlist = array($userid);
              }
          }
      }
 +
 +    /**
 +     * The id used to uniquily identify the cache for this instance of the assign object.
 +     *
 +     * @return string
 +     */
 +    public function get_useridlist_key_id() {
 +        return $this->useridlistid;
 +    }
 +
 +    /**
 +     * Generates the key that should be used for an entry in the useridlist cache.
 +     *
 +     * @param string $id Generate a key for this instance (optional)
 +     * @return string The key for the id, or new entry if no $id is passed.
 +     */
 +    public function get_useridlist_key($id = null) {
 +        if ($id === null) {
 +            $id = $this->get_useridlist_key_id();
 +        }
 +        return $this->get_course_module()->id . '_' . $id;
 +    }
  }
  
  /**