Merge branch 'MDL-52924-master' of git://github.com/jleyva/moodle
authorDavid Monllao <davidm@moodle.com>
Mon, 21 Mar 2016 01:25:19 +0000 (09:25 +0800)
committerDavid Monllao <davidm@moodle.com>
Mon, 21 Mar 2016 01:25:19 +0000 (09:25 +0800)
Conflicts:
version.php

1  2 
mod/assign/externallib.php
mod/assign/tests/externallib_test.php
version.php

@@@ -26,6 -26,7 +26,7 @@@
  defined('MOODLE_INTERNAL') || die;
  
  require_once("$CFG->libdir/externallib.php");
+ require_once("$CFG->dirroot/mod/assign/locallib.php");
  
  /**
   * Assign functions
@@@ -194,6 -195,29 +195,29 @@@ class mod_assign_external extends exter
          return $result;
      }
  
+     /**
+      * Creates a grade single structure.
+      *
+      * @return external_single_structure a grade single structure.
+      * @since  Moodle 3.1
+      */
+     private static function get_grade_structure($required = VALUE_REQUIRED) {
+         return new external_single_structure(
+             array(
+                 'id'                => new external_value(PARAM_INT, 'grade id'),
+                 'assignment'        => new external_value(PARAM_INT, 'assignment id', VALUE_OPTIONAL),
+                 'userid'            => new external_value(PARAM_INT, 'student id'),
+                 'attemptnumber'     => new external_value(PARAM_INT, 'attempt number'),
+                 'timecreated'       => new external_value(PARAM_INT, 'grade creation time'),
+                 'timemodified'      => new external_value(PARAM_INT, 'grade last modified time'),
+                 'grader'            => new external_value(PARAM_INT, 'grader'),
+                 'grade'             => new external_value(PARAM_TEXT, 'grade'),
+                 'gradefordisplay'   => new external_value(PARAM_RAW, 'grade rendered into a format suitable for display',
+                                                             VALUE_OPTIONAL),
+             ), 'grade information', $required
+         );
+     }
      /**
       * Creates an assign_grades external_single_structure
       * @return external_single_structure
      private static function assign_grades() {
          return new external_single_structure(
              array (
-                 'assignmentid'    => new external_value(PARAM_INT, 'assignment id'),
-                 'grades'   => new external_multiple_structure(new external_single_structure(
-                         array(
-                             'id'            => new external_value(PARAM_INT, 'grade id'),
-                             'userid'        => new external_value(PARAM_INT, 'student id'),
-                             'attemptnumber' => new external_value(PARAM_INT, 'attempt number'),
-                             'timecreated'   => new external_value(PARAM_INT, 'grade creation time'),
-                             'timemodified'  => new external_value(PARAM_INT, 'grade last modified time'),
-                             'grader'        => new external_value(PARAM_INT, 'grader'),
-                             'grade'         => new external_value(PARAM_TEXT, 'grade')
-                         )
-                     )
-                 )
+                 'assignmentid'  => new external_value(PARAM_INT, 'assignment id'),
+                 'grades'        => new external_multiple_structure(self::get_grade_structure())
              )
          );
      }
          return new external_function_parameters(
              array(
                  'courseids' => new external_multiple_structure(
 -                    new external_value(PARAM_INT, 'course id'),
 +                    new external_value(PARAM_INT, 'course id, empty for retrieving all the courses where the user is enroled in'),
                      '0 or more course ids',
                      VALUE_DEFAULT, array()
                  ),
                      new external_value(PARAM_CAPABILITY, 'capability'),
                      'list of capabilities used to filter courses',
                      VALUE_DEFAULT, array()
 -                )
 +                ),
 +                'includenotenrolledcourses' => new external_value(PARAM_BOOL, 'whether to return courses that the user can see
 +                                                                    even if is not enroled in. This requires the parameter courseids
 +                                                                    to not be empty.', VALUE_DEFAULT, false)
              )
          );
      }
  
      /**
 -     * Returns an array of courses the user is enrolled in, and for each course all of the assignments that the user can
 +     * Returns an array of courses the user is enrolled, and for each course all of the assignments that the user can
       * view within that course.
       *
       * @param array $courseids An optional array of course ids. If provided only assignments within the given course
 -     * will be returned. If the user is not enrolled in a given course a warning will be generated and returned.
 +     * will be returned. If the user is not enrolled in or can't view a given course a warning will be generated and returned.
       * @param array $capabilities An array of additional capability checks you wish to be made on the course context.
 +     * @param bool $includenotenrolledcourses Wheter to return courses that the user can see even if is not enroled in.
 +     * This requires the parameter $courseids to not be empty.
       * @return An array of courses and warnings.
       * @since  Moodle 2.4
       */
 -    public static function get_assignments($courseids = array(), $capabilities = array()) {
 +    public static function get_assignments($courseids = array(), $capabilities = array(), $includenotenrolledcourses = false) {
          global $USER, $DB, $CFG;
-         require_once("$CFG->dirroot/mod/assign/locallib.php");
  
          $params = self::validate_parameters(
              self::get_assignments_parameters(),
 -            array('courseids' => $courseids, 'capabilities' => $capabilities)
 +            array(
 +                'courseids' => $courseids,
 +                'capabilities' => $capabilities,
 +                'includenotenrolledcourses' => $includenotenrolledcourses
 +            )
          );
  
          $warnings = array();
 +        $courses = array();
          $fields = 'sortorder,shortname,fullname,timemodified';
 -        $courses = enrol_get_users_courses($USER->id, true, $fields);
 -        // Used to test for ids that have been requested but can't be returned.
 -        if (count($params['courseids']) > 0) {
 +
 +        // If the courseids list is empty, we return only the courses where the user is enrolled in.
 +        if (empty($params['courseids'])) {
 +            $courses = enrol_get_users_courses($USER->id, true, $fields);
 +            $courseids = array_keys($courses);
 +        } else if ($includenotenrolledcourses) {
 +            // In this case, we don't have to check here for enrolmnents. Maybe the user can see the course even if is not enrolled.
 +            $courseids = $params['courseids'];
 +        } else {
 +            // We need to check for enrolments.
 +            $mycourses = enrol_get_users_courses($USER->id, true, $fields);
 +            $mycourseids = array_keys($mycourses);
 +
              foreach ($params['courseids'] as $courseid) {
 -                if (!in_array($courseid, array_keys($courses))) {
 +                if (!in_array($courseid, $mycourseids)) {
                      unset($courses[$courseid]);
                      $warnings[] = array(
                          'item' => 'course',
                          'warningcode' => '2',
                          'message' => 'User is not enrolled or does not have requested capability'
                      );
 +                } else {
 +                    $courses[$courseid] = $mycourses[$courseid];
                  }
              }
 +            $courseids = array_keys($courses);
          }
 -        foreach ($courses as $id => $course) {
 -            if (count($params['courseids']) > 0 && !in_array($id, $params['courseids'])) {
 -                unset($courses[$id]);
 -            }
 -            $context = context_course::instance($id);
 +
 +        foreach ($courseids as $cid) {
 +
              try {
 +                $context = context_course::instance($cid);
                  self::validate_context($context);
 +
 +                // Check if this course was already loaded (by enrol_get_users_courses).
 +                if (!isset($courses[$cid])) {
 +                    $courses[$cid] = get_course($cid);
 +                }
              } catch (Exception $e) {
 -                unset($courses[$id]);
 +                unset($courses[$cid]);
                  $warnings[] = array(
                      'item' => 'course',
 -                    'itemid' => $id,
 +                    'itemid' => $cid,
                      'warningcode' => '1',
 -                    'message' => 'No access rights in course context '.$e->getMessage().$e->getTraceAsString()
 +                    'message' => 'No access rights in course context '.$e->getMessage()
                  );
                  continue;
              }
              if (count($params['capabilities']) > 0 && !has_all_capabilities($params['capabilities'], $context)) {
 -                unset($courses[$id]);
 +                unset($courses[$cid]);
              }
          }
          $extrafields='m.id as assignmentid, ' .
          );
      }
  
+     /**
+      * Return information (files and text fields) for the given plugins in the assignment.
+      *
+      * @param  assign $assign the assignment object
+      * @param  array $assignplugins array of assignment plugins (submission or feedback)
+      * @param  stdClass $item the item object (submission or grade)
+      * @return array an array containing the plugins returned information
+      */
+     private static function get_plugins_data($assign, $assignplugins, $item) {
+         global $CFG;
+         $plugins = array();
+         $fs = get_file_storage();
+         foreach ($assignplugins as $assignplugin) {
+             if (!$assignplugin->is_enabled() or !$assignplugin->is_visible()) {
+                 continue;
+             }
+             $plugin = array(
+                 'name' => $assignplugin->get_name(),
+                 'type' => $assignplugin->get_type()
+             );
+             // Subtype is 'assignsubmission', type is currently 'file' or 'onlinetext'.
+             $component = $assignplugin->get_subtype().'_'.$assignplugin->get_type();
+             $fileareas = $assignplugin->get_file_areas();
+             foreach ($fileareas as $filearea => $name) {
+                 $fileareainfo = array('area' => $filearea);
+                 $files = $fs->get_area_files(
+                     $assign->get_context()->id,
+                     $component,
+                     $filearea,
+                     $item->id,
+                     "timemodified",
+                     false
+                 );
+                 foreach ($files as $file) {
+                     $filepath = $file->get_filepath().$file->get_filename();
+                     $fileurl = file_encode_url($CFG->wwwroot . '/webservice/pluginfile.php', '/' . $assign->get_context()->id .
+                         '/' . $component. '/'. $filearea . '/' . $item->id . $filepath);
+                     $fileinfo = array(
+                         'filepath' => $filepath,
+                         'fileurl' => $fileurl
+                         );
+                     $fileareainfo['files'][] = $fileinfo;
+                 }
+                 $plugin['fileareas'][] = $fileareainfo;
+             }
+             $editorfields = $assignplugin->get_editor_fields();
+             foreach ($editorfields as $name => $description) {
+                 $editorfieldinfo = array(
+                     'name' => $name,
+                     'description' => $description,
+                     'text' => $assignplugin->get_editor_text($name, $item->id),
+                     'format' => $assignplugin->get_editor_format($name, $item->id)
+                 );
+                 $plugin['editorfields'][] = $editorfieldinfo;
+             }
+             $plugins[] = $plugin;
+         }
+         return $plugins;
+     }
      /**
       * Describes the parameters for get_submissions
       *
       */
      public static function get_submissions($assignmentids, $status = '', $since = 0, $before = 0) {
          global $DB, $CFG;
-         require_once("$CFG->dirroot/mod/assign/locallib.php");
          $params = self::validate_parameters(self::get_submissions_parameters(),
                          array('assignmentids' => $assignmentids,
                                'status' => $status,
  
          foreach ($assigns as $assign) {
              $submissions = array();
-             $submissionplugins = $assign->get_submission_plugins();
              $placeholders = array('assignid1' => $assign->get_instance()->id,
                                    'assignid2' => $assign->get_instance()->id);
  
              $submissionrecords = $DB->get_records_sql($sql, $placeholders);
  
              if (!empty($submissionrecords)) {
-                 $fs = get_file_storage();
+                 $submissionplugins = $assign->get_submission_plugins();
                  foreach ($submissionrecords as $submissionrecord) {
                      $submission = array(
                          'id' => $submissionrecord->id,
                          'timemodified' => $submissionrecord->timemodified,
                          'status' => $submissionrecord->status,
                          'attemptnumber' => $submissionrecord->attemptnumber,
-                         'groupid' => $submissionrecord->groupid
+                         'groupid' => $submissionrecord->groupid,
+                         'plugins' => self::get_plugins_data($assign, $submissionplugins, $submissionrecord)
                      );
-                     foreach ($submissionplugins as $submissionplugin) {
-                         $plugin = array(
-                             'name' => $submissionplugin->get_name(),
-                             'type' => $submissionplugin->get_type()
-                         );
-                         // Subtype is 'assignsubmission', type is currently 'file' or 'onlinetext'.
-                         $component = $submissionplugin->get_subtype().'_'.$submissionplugin->get_type();
-                         $fileareas = $submissionplugin->get_file_areas();
-                         foreach ($fileareas as $filearea => $name) {
-                             $fileareainfo = array('area' => $filearea);
-                             $files = $fs->get_area_files(
-                                 $assign->get_context()->id,
-                                 $component,
-                                 $filearea,
-                                 $submissionrecord->id,
-                                 "timemodified",
-                                 false
-                             );
-                             foreach ($files as $file) {
-                                 $filepath = $file->get_filepath().$file->get_filename();
-                                 $fileurl = file_encode_url($CFG->wwwroot . '/webservice/pluginfile.php', '/' . $assign->get_context()->id .
-                                     '/' . $component. '/'. $filearea . '/' . $submissionrecord->id . $filepath);
-                                 $fileinfo = array(
-                                     'filepath' => $filepath,
-                                     'fileurl' => $fileurl
-                                     );
-                                 $fileareainfo['files'][] = $fileinfo;
-                             }
-                             $plugin['fileareas'][] = $fileareainfo;
-                         }
-                         $editorfields = $submissionplugin->get_editor_fields();
-                         foreach ($editorfields as $name => $description) {
-                             $editorfieldinfo = array(
-                                 'name' => $name,
-                                 'description' => $description,
-                                 'text' => $submissionplugin->get_editor_text($name, $submissionrecord->id),
-                                 'format' => $submissionplugin->get_editor_format($name, $submissionrecord->id)
-                             );
-                             $plugin['editorfields'][] = $editorfieldinfo;
-                         }
-                         $submission['plugins'][] = $plugin;
-                     }
                      $submissions[] = $submission;
                  }
              } else {
      }
  
      /**
-      * Creates an assign_submissions external_single_structure
+      * Creates an assignment plugin structure.
       *
-      * @return external_single_structure
-      * @since Moodle 2.5
+      * @return external_single_structure the plugin structure
       */
-     private static function get_submissions_structure() {
+     private static function get_plugin_structure() {
          return new external_single_structure(
-             array (
-                 'assignmentid' => new external_value(PARAM_INT, 'assignment id'),
-                 'submissions' => new external_multiple_structure(
+             array(
+                 'type' => new external_value(PARAM_TEXT, 'submission plugin type'),
+                 'name' => new external_value(PARAM_TEXT, 'submission plugin name'),
+                 'fileareas' => new external_multiple_structure(
                      new external_single_structure(
-                         array(
-                             'id' => new external_value(PARAM_INT, 'submission id'),
-                             'userid' => new external_value(PARAM_INT, 'student id'),
-                             'attemptnumber' => new external_value(PARAM_INT, 'attempt number'),
-                             'timecreated' => new external_value(PARAM_INT, 'submission creation time'),
-                             'timemodified' => new external_value(PARAM_INT, 'submission last modified time'),
-                             'status' => new external_value(PARAM_TEXT, 'submission status'),
-                             'groupid' => new external_value(PARAM_INT, 'group id'),
-                             'plugins' => new external_multiple_structure(
+                         array (
+                             'area' => new external_value (PARAM_TEXT, 'file area'),
+                             'files' => new external_multiple_structure(
                                  new external_single_structure(
-                                     array(
-                                         'type' => new external_value(PARAM_TEXT, 'submission plugin type'),
-                                         'name' => new external_value(PARAM_TEXT, 'submission plugin name'),
-                                         'fileareas' => new external_multiple_structure(
-                                             new external_single_structure(
-                                                 array (
-                                                     'area' => new external_value (PARAM_TEXT, 'file area'),
-                                                     'files' => new external_multiple_structure(
-                                                         new external_single_structure(
-                                                             array (
-                                                                 'filepath' => new external_value (PARAM_TEXT, 'file path'),
-                                                                 'fileurl' => new external_value (PARAM_URL, 'file download url',
-                                                                     VALUE_OPTIONAL)
-                                                             )
-                                                         ), 'files', VALUE_OPTIONAL
-                                                     )
-                                                 )
-                                             ), 'fileareas', VALUE_OPTIONAL
-                                         ),
-                                         'editorfields' => new external_multiple_structure(
-                                             new external_single_structure(
-                                                 array(
-                                                     'name' => new external_value(PARAM_TEXT, 'field name'),
-                                                     'description' => new external_value(PARAM_TEXT, 'field description'),
-                                                     'text' => new external_value (PARAM_RAW, 'field value'),
-                                                     'format' => new external_format_value ('text')
-                                                 )
-                                             )
-                                             , 'editorfields', VALUE_OPTIONAL
-                                         )
+                                     array (
+                                         'filepath' => new external_value (PARAM_TEXT, 'file path'),
+                                         'fileurl' => new external_value (PARAM_URL, 'file download url',
+                                             VALUE_OPTIONAL)
                                      )
-                                 )
-                                 , 'plugins', VALUE_OPTIONAL
+                                 ), 'files', VALUE_OPTIONAL
                              )
                          )
+                     ), 'fileareas', VALUE_OPTIONAL
+                 ),
+                 'editorfields' => new external_multiple_structure(
+                     new external_single_structure(
+                         array(
+                             'name' => new external_value(PARAM_TEXT, 'field name'),
+                             'description' => new external_value(PARAM_TEXT, 'field description'),
+                             'text' => new external_value (PARAM_RAW, 'field value'),
+                             'format' => new external_format_value ('text')
+                         )
                      )
+                     , 'editorfields', VALUE_OPTIONAL
                  )
              )
          );
      }
  
+     /**
+      * Creates a submission structure.
+      *
+      * @return external_single_structure the submission structure
+      */
+     private static function get_submission_structure($required = VALUE_REQUIRED) {
+         return new external_single_structure(
+             array(
+                 'id' => new external_value(PARAM_INT, 'submission id'),
+                 'userid' => new external_value(PARAM_INT, 'student id'),
+                 'attemptnumber' => new external_value(PARAM_INT, 'attempt number'),
+                 'timecreated' => new external_value(PARAM_INT, 'submission creation time'),
+                 'timemodified' => new external_value(PARAM_INT, 'submission last modified time'),
+                 'status' => new external_value(PARAM_TEXT, 'submission status'),
+                 'groupid' => new external_value(PARAM_INT, 'group id'),
+                 'assignment' => new external_value(PARAM_INT, 'assignment id', VALUE_OPTIONAL),
+                 'latest' => new external_value(PARAM_INT, 'latest attempt', VALUE_OPTIONAL),
+                 'plugins' => new external_multiple_structure(self::get_plugin_structure(), 'plugins', VALUE_OPTIONAL)
+             ), 'submission info', $required
+         );
+     }
+     /**
+      * Creates an assign_submissions external_single_structure
+      *
+      * @return external_single_structure
+      * @since Moodle 2.5
+      */
+     private static function get_submissions_structure() {
+         return new external_single_structure(
+             array (
+                 'assignmentid' => new external_value(PARAM_INT, 'assignment id'),
+                 'submissions' => new external_multiple_structure(self::get_submission_structure())
+             )
+         );
+     }
      /**
       * Describes the get_submissions return value
       *
       */
      public static function set_user_flags($assignmentid, $userflags = array()) {
          global $CFG, $DB;
-         require_once($CFG->dirroot . "/mod/assign/locallib.php");
  
          $params = self::validate_parameters(self::set_user_flags_parameters(),
                                              array('assignmentid' => $assignmentid,
       */
      public static function lock_submissions($assignmentid, $userids) {
          global $CFG;
-         require_once("$CFG->dirroot/mod/assign/locallib.php");
  
          $params = self::validate_parameters(self::lock_submissions_parameters(),
                          array('assignmentid' => $assignmentid,
       */
      public static function revert_submissions_to_draft($assignmentid, $userids) {
          global $CFG;
-         require_once("$CFG->dirroot/mod/assign/locallib.php");
  
          $params = self::validate_parameters(self::revert_submissions_to_draft_parameters(),
                          array('assignmentid' => $assignmentid,
       */
      public static function unlock_submissions($assignmentid, $userids) {
          global $CFG;
-         require_once("$CFG->dirroot/mod/assign/locallib.php");
  
          $params = self::validate_parameters(self::unlock_submissions_parameters(),
                          array('assignmentid' => $assignmentid,
       */
      public static function submit_for_grading($assignmentid, $acceptsubmissionstatement) {
          global $CFG, $USER;
-         require_once("$CFG->dirroot/mod/assign/locallib.php");
  
          $params = self::validate_parameters(self::submit_for_grading_parameters(),
                                              array('assignmentid' => $assignmentid,
       */
      public static function save_user_extensions($assignmentid, $userids, $dates) {
          global $CFG;
-         require_once("$CFG->dirroot/mod/assign/locallib.php");
  
          $params = self::validate_parameters(self::save_user_extensions_parameters(),
                          array('assignmentid' => $assignmentid,
       */
      public static function reveal_identities($assignmentid) {
          global $CFG, $USER;
-         require_once("$CFG->dirroot/mod/assign/locallib.php");
  
          $params = self::validate_parameters(self::reveal_identities_parameters(),
                                              array('assignmentid' => $assignmentid));
       */
      public static function save_submission_parameters() {
          global $CFG;
-         require_once("$CFG->dirroot/mod/assign/locallib.php");
          $instance = new assign(null, null, null);
          $pluginsubmissionparams = array();
  
       */
      public static function save_submission($assignmentid, $plugindata) {
          global $CFG, $USER;
-         require_once("$CFG->dirroot/mod/assign/locallib.php");
  
          $params = self::validate_parameters(self::save_submission_parameters(),
                                              array('assignmentid' => $assignmentid,
       */
      public static function save_grade_parameters() {
          global $CFG;
-         require_once("$CFG->dirroot/mod/assign/locallib.php");
          require_once("$CFG->dirroot/grade/grading/lib.php");
          $instance = new assign(null, null, null);
          $pluginfeedbackparams = array();
                                        $plugindata = array(),
                                        $advancedgradingdata = array()) {
          global $CFG, $USER;
-         require_once("$CFG->dirroot/mod/assign/locallib.php");
  
          $params = self::validate_parameters(self::save_grade_parameters(),
                                              array('assignmentid' => $assignmentid,
       */
      public static function save_grades_parameters() {
          global $CFG;
-         require_once("$CFG->dirroot/mod/assign/locallib.php");
          require_once("$CFG->dirroot/grade/grading/lib.php");
          $instance = new assign(null, null, null);
          $pluginfeedbackparams = array();
       */
      public static function save_grades($assignmentid, $applytoall = false, $grades) {
          global $CFG, $USER;
-         require_once("$CFG->dirroot/mod/assign/locallib.php");
  
          $params = self::validate_parameters(self::save_grades_parameters(),
                                              array('assignmentid' => $assignmentid,
       */
      public static function copy_previous_attempt($assignmentid) {
          global $CFG, $USER;
-         require_once("$CFG->dirroot/mod/assign/locallib.php");
  
          $params = self::validate_parameters(self::copy_previous_attempt_parameters(),
                                              array('assignmentid' => $assignmentid));
       */
      public static function view_grading_table($assignid) {
          global $DB, $CFG;
-         require_once($CFG->dirroot . "/mod/assign/locallib.php");
  
          $params = self::validate_parameters(self::view_grading_table_parameters(),
                                              array(
       */
      public static function view_submission_status($assignid) {
          global $DB, $CFG;
-         require_once("$CFG->dirroot/mod/assign/locallib.php");
  
          $warnings = array();
          $params = array(
          );
      }
  
+     /**
+      * Describes the parameters for get_submission_status.
+      *
+      * @return external_external_function_parameters
+      * @since Moodle 3.1
+      */
+     public static function get_submission_status_parameters() {
+         return new external_function_parameters (
+             array(
+                 'assignid' => new external_value(PARAM_INT, 'assignment instance id'),
+                 'userid' => new external_value(PARAM_INT, 'user id (empty for current user)', VALUE_DEFAULT, 0),
+             )
+         );
+     }
+     /**
+      * Returns information about an assignment submission status for a given user.
+      *
+      * @param int $assignid assignment instance id
+      * @param int $userid user id (empty for current user)
+      * @return array of warnings and grading, status, feedback and previous attempts information
+      * @since Moodle 3.1
+      * @throws required_capability_exception
+      */
+     public static function get_submission_status($assignid, $userid = 0) {
+         global $USER, $DB;
+         $warnings = array();
+         $params = array(
+             'assignid' => $assignid,
+             'userid' => $userid,
+         );
+         $params = self::validate_parameters(self::get_submission_status_parameters(), $params);
+         // Request and permission validation.
+         $assign = $DB->get_record('assign', array('id' => $params['assignid']), 'id', MUST_EXIST);
+         list($course, $cm) = get_course_and_cm_from_instance($assign, 'assign');
+         $context = context_module::instance($cm->id);
+         self::validate_context($context);
+         $assign = new assign($context, $cm, $course);
+         // Default value for userid.
+         if (empty($params['userid'])) {
+             $params['userid'] = $USER->id;
+         }
+         $user = core_user::get_user($params['userid'], '*', MUST_EXIST);
+         core_user::require_active_user($user);
+         if (!$assign->can_view_submission($user->id)) {
+             throw new required_capability_exception($context, 'mod/assign:viewgrades', 'nopermission', '');
+         }
+         $gradingsummary = $lastattempt = $feedback = $previousattempts = null;
+         // Get the renderable since it contais all the info we need.
+         if ($assign->can_view_grades()) {
+             $gradingsummary = $assign->get_assign_grading_summary_renderable();
+         }
+         // Retrieve the rest of the renderable objects.
+         if (has_capability('mod/assign:submit', $assign->get_context(), $user)) {
+             $lastattempt = $assign->get_assign_submission_status_renderable($user, true);
+         }
+         $feedback = $assign->get_assign_feedback_status_renderable($user);
+         $previousattempts = $assign->get_assign_attempt_history_renderable($user);
+         // Now, build the result.
+         $result = array();
+         // First of all, grading summary, this is suitable for teachers/managers.
+         if ($gradingsummary) {
+             $result['gradingsummary'] = $gradingsummary;
+         }
+         // Did we submit anything?
+         if ($lastattempt) {
+             $submissionplugins = $assign->get_submission_plugins();
+             if (empty($lastattempt->submission)) {
+                 unset($lastattempt->submission);
+             } else {
+                 $lastattempt->submission->plugins = self::get_plugins_data($assign, $submissionplugins, $lastattempt->submission);
+             }
+             if (empty($lastattempt->teamsubmission)) {
+                 unset($lastattempt->teamsubmission);
+             } else {
+                 $lastattempt->teamsubmission->plugins = self::get_plugins_data($assign, $submissionplugins,
+                                                                                 $lastattempt->teamsubmission);
+             }
+             // We need to change the type of some of the structures retrieved from the renderable.
+             if (!empty($lastattempt->submissiongroup)) {
+                 $lastattempt->submissiongroup = $lastattempt->submissiongroup->id;
+             }
+             if (!empty($lastattempt->usergroups)) {
+                 $lastattempt->usergroups = array_keys($lastattempt->usergroups);
+             }
+             // We cannot use array_keys here.
+             if (!empty($lastattempt->submissiongroupmemberswhoneedtosubmit)) {
+                 $lastattempt->submissiongroupmemberswhoneedtosubmit = array_map(
+                                                                             function($e){
+                                                                                 return $e->id;
+                                                                             },
+                                                                             $lastattempt->submissiongroupmemberswhoneedtosubmit);
+             }
+             $result['lastattempt'] = $lastattempt;
+         }
+         // The feedback for our latest submission.
+         if ($feedback) {
+             if ($feedback->grade) {
+                 $feedbackplugins = $assign->get_feedback_plugins();
+                 $feedback->plugins = self::get_plugins_data($assign, $feedbackplugins, $feedback->grade);
+             } else {
+                 unset($feedback->plugins);
+                 unset($feedback->grade);
+             }
+             $result['feedback'] = $feedback;
+         }
+         // Retrieve only previous attempts.
+         if ($previousattempts and count($previousattempts->submissions) > 1) {
+             // Don't show the last one because it is the current submission.
+             array_pop($previousattempts->submissions);
+             // Show newest to oldest.
+             $previousattempts->submissions = array_reverse($previousattempts->submissions);
+             foreach ($previousattempts->submissions as $i => $submission) {
+                 $attempt = array();
+                 $grade = null;
+                 foreach ($previousattempts->grades as $onegrade) {
+                     if ($onegrade->attemptnumber == $submission->attemptnumber) {
+                         $grade = $onegrade;
+                         break;
+                     }
+                 }
+                 $attempt['attemptnumber'] = $submission->attemptnumber;
+                 if ($submission) {
+                     $submission->plugins = self::get_plugins_data($assign, $previousattempts->submissionplugins, $submission);
+                     $attempt['submission'] = $submission;
+                 }
+                 if ($grade) {
+                     // From object to id.
+                     $grade->grader = $grade->grader->id;
+                     $feedbackplugins = self::get_plugins_data($assign, $previousattempts->feedbackplugins, $grade);
+                     $attempt['grade'] = $grade;
+                     $attempt['feedbackplugins'] = $feedbackplugins;
+                 }
+                 $result['previousattempts'][] = $attempt;
+             }
+         }
+         $result['warnings'] = $warnings;
+         return $result;
+     }
+     /**
+      * Describes the get_submission_status return value.
+      *
+      * @return external_single_structure
+      * @since Moodle 3.1
+      */
+     public static function get_submission_status_returns() {
+         return new external_single_structure(
+             array(
+                 'gradingsummary' => new external_single_structure(
+                     array(
+                         'participantcount' => new external_value(PARAM_INT, 'Number of users who can submit.'),
+                         'submissiondraftscount' => new external_value(PARAM_INT, 'Number of submissions in draft status.'),
+                         'submissiondraftscount' => new external_value(PARAM_INT, 'Number of submissions in draft status.'),
+                         'submissionsenabled' => new external_value(PARAM_BOOL, 'Whether submissions are enabled or not.'),
+                         'submissionssubmittedcount' => new external_value(PARAM_INT, 'Number of submissions in submitted status.'),
+                         'submissionsneedgradingcount' => new external_value(PARAM_INT, 'Number of submissions that need grading.'),
+                         'warnofungroupedusers' => new external_value(PARAM_BOOL, 'Whether we need to warn people that there
+                                                                         are users without groups.'),
+                     ), 'Grading information.', VALUE_OPTIONAL
+                 ),
+                 'lastattempt' => new external_single_structure(
+                     array(
+                         'submission' => self::get_submission_structure(VALUE_OPTIONAL),
+                         'teamsubmission' => self::get_submission_structure(VALUE_OPTIONAL),
+                         'submissiongroup' => new external_value(PARAM_INT, 'The submission group id (for group submissions only).',
+                                                                 VALUE_OPTIONAL),
+                         'submissiongroupmemberswhoneedtosubmit' => new external_multiple_structure(
+                             new external_value(PARAM_INT, 'USER id.'),
+                             'List of users who still need to submit (for group submissions only).',
+                             VALUE_OPTIONAL
+                         ),
+                         'submissionsenabled' => new external_value(PARAM_BOOL, 'Whether submissions are enabled or not.'),
+                         'locked' => new external_value(PARAM_BOOL, 'Whether new submissions are locked.'),
+                         'graded' => new external_value(PARAM_BOOL, 'Whether the submission is graded.'),
+                         'canedit' => new external_value(PARAM_BOOL, 'Whether the user can edit the current submission.'),
+                         'cansubmit' => new external_value(PARAM_BOOL, 'Whether the user can submit.'),
+                         'extensionduedate' => new external_value(PARAM_INT, 'Extension due date.'),
+                         'blindmarking' => new external_value(PARAM_BOOL, 'Whether blind marking is enabled.'),
+                         'gradingstatus' => new external_value(PARAM_ALPHANUMEXT, 'Grading status.'),
+                         'usergroups' => new external_multiple_structure(
+                             new external_value(PARAM_INT, 'Group id.'), 'User groups in the course.'
+                         ),
+                     ), 'Last attempt information.', VALUE_OPTIONAL
+                 ),
+                 'feedback' => new external_single_structure(
+                     array(
+                         'grade' => self::get_grade_structure(VALUE_OPTIONAL),
+                         'gradefordisplay' => new external_value(PARAM_RAW, 'Grade rendered into a format suitable for display.'),
+                         'gradeddate' => new external_value(PARAM_INT, 'The date the user was graded.'),
+                         'plugins' => new external_multiple_structure(self::get_plugin_structure(), 'Plugins info.', VALUE_OPTIONAL),
+                     ), 'Feedback for the last attempt.', VALUE_OPTIONAL
+                 ),
+                 'previousattempts' => new external_multiple_structure(
+                     new external_single_structure(
+                         array(
+                             'attemptnumber' => new external_value(PARAM_INT, 'Attempt number.'),
+                             'submission' => self::get_submission_structure(VALUE_OPTIONAL),
+                             'grade' => self::get_grade_structure(VALUE_OPTIONAL),
+                             'feedbackplugins' => new external_multiple_structure(self::get_plugin_structure(), 'Feedback info.',
+                                                                                     VALUE_OPTIONAL),
+                         )
+                     ), 'List all the previous attempts did by the user.', VALUE_OPTIONAL
+                 ),
+                 'warnings' => new external_warnings(),
+             )
+         );
+     }
  }
@@@ -254,35 -254,6 +254,35 @@@ class mod_assign_external_testcase exte
  
          $this->assertEquals(0, count($result['courses']));
          $this->assertEquals(1, count($result['warnings']));
 +
 +        // Test with non-enrolled user, but with view capabilities.
 +        $this->setAdminUser();
 +        $result = mod_assign_external::get_assignments();
 +        $result = external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);
 +        $this->assertEquals(0, count($result['courses']));
 +        $this->assertEquals(0, count($result['warnings']));
 +
 +        // Expect no courses, because we are not using the special flag.
 +        $result = mod_assign_external::get_assignments(array($course1->id));
 +        $result = external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);
 +        $this->assertCount(0, $result['courses']);
 +
 +        // Now use the special flag to return courses where you are not enroled in.
 +        $result = mod_assign_external::get_assignments(array($course1->id), array(), true);
 +        $result = external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);
 +        $this->assertCount(1, $result['courses']);
 +
 +        $course = $result['courses'][0];
 +        $this->assertEquals('Lightwork Course 1', $course['fullname']);
 +        $this->assertEquals(1, count($course['assignments']));
 +        $assignment = $course['assignments'][0];
 +        $this->assertEquals($assign1->id, $assignment['id']);
 +        $this->assertEquals($course1->id, $assignment['course']);
 +        $this->assertEquals('lightwork assignment', $assignment['name']);
 +        $this->assertArrayNotHasKey('intro', $assignment);
 +        $this->assertArrayNotHasKey('introattachments', $assignment);
 +        $this->assertEquals(1, $assignment['markingworkflow']);
 +        $this->assertEquals(1, $assignment['markingallocation']);
      }
  
      /**
          $this->assertEquals(1, count($assignment['submissions']));
          $submission = $assignment['submissions'][0];
          $this->assertEquals($sid, $submission['id']);
-         $this->assertGreaterThanOrEqual(3, count($submission['plugins']));
-         $plugins = $submission['plugins'];
-         foreach ($plugins as $plugin) {
-             $foundonlinetext = false;
-             if ($plugin['type'] == 'onlinetext') {
-                 $foundonlinetext = true;
-                 break;
-             }
-         }
-         $this->assertTrue($foundonlinetext);
+         $this->assertCount(1, $submission['plugins']);
      }
  
      /**
          }
      }
  
+     /**
+      * Create a submission for testing the get_submission_status function.
+      * @param  boolean $submitforgrading whether to submit for grading the submission
+      * @return array an array containing all the required data for testing
+      */
+     private function create_submission_for_testing_status($submitforgrading = false) {
+         global $DB, $CFG;
+         require_once($CFG->dirroot . '/mod/assign/tests/base_test.php');
+         // Create a course and assignment and users.
+         $course = self::getDataGenerator()->create_course();
+         $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+         $params = array(
+             'course' => $course->id,
+             'assignsubmission_file_maxfiles' => 1,
+             'assignsubmission_file_maxsizebytes' => 1024 * 1024,
+             'assignsubmission_onlinetext_enabled' => 1,
+             'assignsubmission_file_enabled' => 1,
+             'submissiondrafts' => 1,
+             'assignfeedback_file_enabled' => 1,
+             'assignfeedback_comments_enabled' => 1,
+             'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
+             'sendnotifications' => 0
+         );
+         set_config('submissionreceipts', 0, 'assign');
+         $instance = $generator->create_instance($params);
+         $cm = get_coursemodule_from_instance('assign', $instance->id);
+         $context = context_module::instance($cm->id);
+         $assign = new testable_assign($context, $cm, $course);
+         $student1 = self::getDataGenerator()->create_user();
+         $student2 = self::getDataGenerator()->create_user();
+         $studentrole = $DB->get_record('role', array('shortname' => 'student'));
+         $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
+         $this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);
+         $teacher = self::getDataGenerator()->create_user();
+         $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
+         $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
+         $this->setUser($student1);
+         // Create a student1 with an online text submission.
+         // Simulate a submission.
+         $submission = $assign->get_user_submission($student1->id, true);
+         $data = new stdClass();
+         $data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(),
+                                          'text' => 'Submission text',
+                                          'format' => FORMAT_MOODLE);
+         $draftidfile = file_get_unused_draft_itemid();
+         $usercontext = context_user::instance($student1->id);
+         $filerecord = array(
+             'contextid' => $usercontext->id,
+             'component' => 'user',
+             'filearea'  => 'draft',
+             'itemid'    => $draftidfile,
+             'filepath'  => '/',
+             'filename'  => 't.txt',
+         );
+         $fs = get_file_storage();
+         $fs->create_file_from_string($filerecord, 'text contents');
+         $data->files_filemanager = $draftidfile;
+         $notices = array();
+         $assign->save_submission($data, $notices);
+         if ($submitforgrading) {
+             // Now, submit the draft for grading.
+             $notices = array();
+             $data = new stdClass;
+             $data->userid = $student1->id;
+             $assign->submit_for_grading($data, $notices);
+         }
+         return array($assign, $instance, $student1, $student2, $teacher);
+     }
+     /**
+      * Test get_submission_status for a draft submission.
+      */
+     public function test_get_submission_status_in_draft_status() {
+         $this->resetAfterTest(true);
+         list($assign, $instance, $student1, $student2, $teacher) = $this->create_submission_for_testing_status();
+         $result = mod_assign_external::get_submission_status($assign->get_instance()->id);
+         // We expect debugging because of the $PAGE object, this won't happen in a normal WS request.
+         $this->assertDebuggingCalled();
+         $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
+         // The submission is now in draft mode.
+         $this->assertCount(0, $result['warnings']);
+         $this->assertFalse(isset($result['gradingsummary']));
+         $this->assertFalse(isset($result['feedback']));
+         $this->assertFalse(isset($result['previousattempts']));
+         $this->assertTrue($result['lastattempt']['submissionsenabled']);
+         $this->assertTrue($result['lastattempt']['canedit']);
+         $this->assertTrue($result['lastattempt']['cansubmit']);
+         $this->assertFalse($result['lastattempt']['locked']);
+         $this->assertFalse($result['lastattempt']['graded']);
+         $this->assertEmpty($result['lastattempt']['extensionduedate']);
+         $this->assertFalse($result['lastattempt']['blindmarking']);
+         $this->assertCount(0, $result['lastattempt']['submissiongroupmemberswhoneedtosubmit']);
+         $this->assertEquals('notgraded', $result['lastattempt']['gradingstatus']);
+         $this->assertEquals($student1->id, $result['lastattempt']['submission']['userid']);
+         $this->assertEquals(0, $result['lastattempt']['submission']['attemptnumber']);
+         $this->assertEquals('draft', $result['lastattempt']['submission']['status']);
+         $this->assertEquals(0, $result['lastattempt']['submission']['groupid']);
+         $this->assertEquals($assign->get_instance()->id, $result['lastattempt']['submission']['assignment']);
+         $this->assertEquals(1, $result['lastattempt']['submission']['latest']);
+         $this->assertEquals('Submission text', $result['lastattempt']['submission']['plugins'][0]['editorfields'][0]['text']);
+         $this->assertEquals('/t.txt', $result['lastattempt']['submission']['plugins'][1]['fileareas'][0]['files'][0]['filepath']);
+     }
+     /**
+      * Test get_submission_status for a submitted submission.
+      */
+     public function test_get_submission_status_in_submission_status() {
+         $this->resetAfterTest(true);
+         list($assign, $instance, $student1, $student2, $teacher) = $this->create_submission_for_testing_status(true);
+         $result = mod_assign_external::get_submission_status($assign->get_instance()->id);
+         // We expect debugging because of the $PAGE object, this won't happen in a normal WS request.
+         $this->assertDebuggingCalled();
+         $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
+         $this->assertCount(0, $result['warnings']);
+         $this->assertFalse(isset($result['gradingsummary']));
+         $this->assertFalse(isset($result['feedback']));
+         $this->assertFalse(isset($result['previousattempts']));
+         $this->assertTrue($result['lastattempt']['submissionsenabled']);
+         $this->assertFalse($result['lastattempt']['canedit']);
+         $this->assertFalse($result['lastattempt']['cansubmit']);
+         $this->assertFalse($result['lastattempt']['locked']);
+         $this->assertFalse($result['lastattempt']['graded']);
+         $this->assertEmpty($result['lastattempt']['extensionduedate']);
+         $this->assertFalse($result['lastattempt']['blindmarking']);
+         $this->assertCount(0, $result['lastattempt']['submissiongroupmemberswhoneedtosubmit']);
+         $this->assertEquals('notgraded', $result['lastattempt']['gradingstatus']);
+     }
+     /**
+      * Test get_submission_status using the teacher role.
+      */
+     public function test_get_submission_status_in_submission_status_for_teacher() {
+         $this->resetAfterTest(true);
+         list($assign, $instance, $student1, $student2, $teacher) = $this->create_submission_for_testing_status(true);
+         // Now, as teacher, see the grading summary.
+         $this->setUser($teacher);
+         $result = mod_assign_external::get_submission_status($assign->get_instance()->id);
+         // We expect debugging because of the $PAGE object, this won't happen in a normal WS request.
+         $this->assertDebuggingCalled();
+         $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
+         $this->assertCount(0, $result['warnings']);
+         $this->assertFalse(isset($result['lastattempt']));
+         $this->assertFalse(isset($result['feedback']));
+         $this->assertFalse(isset($result['previousattempts']));
+         $this->assertEquals(2, $result['gradingsummary']['participantcount']);
+         $this->assertEquals(0, $result['gradingsummary']['submissiondraftscount']);
+         $this->assertEquals(1, $result['gradingsummary']['submissionsenabled']);
+         $this->assertEquals(1, $result['gradingsummary']['submissionssubmittedcount']);
+         $this->assertEquals(1, $result['gradingsummary']['submissionsneedgradingcount']);
+         $this->assertFalse($result['gradingsummary']['warnofungroupedusers']);
+     }
+     /**
+      * Test get_submission_status for a reopened submission.
+      */
+     public function test_get_submission_status_in_reopened_status() {
+         global $USER;
+         $this->resetAfterTest(true);
+         list($assign, $instance, $student1, $student2, $teacher) = $this->create_submission_for_testing_status(true);
+         $this->setUser($teacher);
+         // Grade and reopen.
+         $feedbackpluginparams = array();
+         $feedbackpluginparams['files_filemanager'] = file_get_unused_draft_itemid();
+         $feedbackeditorparams = array('text' => 'Yeeha!',
+                                         'format' => 1);
+         $feedbackpluginparams['assignfeedbackcomments_editor'] = $feedbackeditorparams;
+         $result = mod_assign_external::save_grade($instance->id,
+                                                   $student1->id,
+                                                   50.0,
+                                                   -1,
+                                                   false,
+                                                   'released',
+                                                   false,
+                                                   $feedbackpluginparams);
+         $USER->ignoresesskey = true;
+         $assign->testable_process_add_attempt($student1->id);
+         $this->setUser($student1);
+         $result = mod_assign_external::get_submission_status($assign->get_instance()->id);
+         // We expect debugging because of the $PAGE object, this won't happen in a normal WS request.
+         $this->assertDebuggingCalled();
+         $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
+         $this->assertCount(0, $result['warnings']);
+         $this->assertFalse(isset($result['gradingsummary']));
+         $this->assertTrue($result['lastattempt']['submissionsenabled']);
+         $this->assertTrue($result['lastattempt']['canedit']);
+         $this->assertFalse($result['lastattempt']['cansubmit']);
+         $this->assertFalse($result['lastattempt']['locked']);
+         $this->assertFalse($result['lastattempt']['graded']);
+         $this->assertEmpty($result['lastattempt']['extensionduedate']);
+         $this->assertFalse($result['lastattempt']['blindmarking']);
+         $this->assertCount(0, $result['lastattempt']['submissiongroupmemberswhoneedtosubmit']);
+         $this->assertEquals('notgraded', $result['lastattempt']['gradingstatus']);
+         // Check new attempt reopened.
+         $this->assertEquals($student1->id, $result['lastattempt']['submission']['userid']);
+         $this->assertEquals(1, $result['lastattempt']['submission']['attemptnumber']);
+         $this->assertEquals('reopened', $result['lastattempt']['submission']['status']);
+         $this->assertEquals(0, $result['lastattempt']['submission']['groupid']);
+         $this->assertEquals($assign->get_instance()->id, $result['lastattempt']['submission']['assignment']);
+         $this->assertEquals(1, $result['lastattempt']['submission']['latest']);
+         $this->assertCount(3, $result['lastattempt']['submission']['plugins']);
+         // Now see feedback and the attempts history (remember, is a submission reopened).
+         // Only 2 fields (no grade, no plugins data).
+         $this->assertCount(2, $result['feedback']);
+         // One previous attempt.
+         $this->assertCount(1, $result['previousattempts']);
+         $this->assertEquals(0, $result['previousattempts'][0]['attemptnumber']);
+         $this->assertEquals(50, $result['previousattempts'][0]['grade']['grade']);
+         $this->assertEquals($teacher->id, $result['previousattempts'][0]['grade']['grader']);
+         $this->assertEquals($student1->id, $result['previousattempts'][0]['grade']['userid']);
+         $this->assertEquals('Yeeha!', $result['previousattempts'][0]['feedbackplugins'][0]['editorfields'][0]['text']);
+         $submissionplugins = $result['previousattempts'][0]['submission']['plugins'];
+         $this->assertEquals('Submission text', $submissionplugins[0]['editorfields'][0]['text']);
+         $this->assertEquals('/t.txt', $submissionplugins[1]['fileareas'][0]['files'][0]['filepath']);
+     }
+     /**
+      * Test access control for get_submission_status.
+      */
+     public function test_get_submission_status_access_control() {
+         $this->resetAfterTest(true);
+         list($assign, $instance, $student1, $student2, $teacher) = $this->create_submission_for_testing_status();
+         $this->setUser($student2);
+         // Access control test.
+         $this->setExpectedException('required_capability_exception');
+         mod_assign_external::get_submission_status($assign->get_instance()->id, $student1->id);
+     }
  }
diff --combined version.php
  
  defined('MOODLE_INTERNAL') || die();
  
- $version  = 2016031700.00;              // YYYYMMDD      = weekly release date of this DEV branch.
 -$version  = 2016031000.01;              // YYYYMMDD      = weekly release date of this DEV branch.
++$version  = 2016031700.01;              // YYYYMMDD      = weekly release date of this DEV branch.
                                          //         RR    = release increments - 00 in DEV branches.
                                          //           .XX = incremental changes.
  
 -$release  = '3.1dev (Build: 20160310)'; // Human-friendly version name
 +$release  = '3.1dev (Build: 20160317)'; // Human-friendly version name
  
  $branch   = '31';                       // This version's branch.
  $maturity = MATURITY_ALPHA;             // This version's maturity level.