/mod/assign/locallib.php
PHP | 9806 lines | 6471 code | 1222 blank | 2113 comment | 1410 complexity | 142664dd9affcbf3e2dbedfeac72cd8b MD5 | raw file
Possible License(s): MIT, AGPL-3.0, MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, Apache-2.0, LGPL-2.1, BSD-3-Clause
Large files files are truncated, but you can click here to view the full file
- <?php
- // This file is part of Moodle - http://moodle.org/
- //
- // Moodle is free software: you can redistribute it and/or modify
- // it under the terms of the GNU General Public License as published by
- // the Free Software Foundation, either version 3 of the License, or
- // (at your option) any later version.
- //
- // Moodle is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU General Public License for more details.
- //
- // You should have received a copy of the GNU General Public License
- // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
- /**
- * This file contains the definition for the class assignment
- *
- * This class provides all the functionality for the new assign module.
- *
- * @package mod_assign
- * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
- defined('MOODLE_INTERNAL') || die();
- // Assignment submission statuses.
- define('ASSIGN_SUBMISSION_STATUS_NEW', 'new');
- define('ASSIGN_SUBMISSION_STATUS_REOPENED', 'reopened');
- define('ASSIGN_SUBMISSION_STATUS_DRAFT', 'draft');
- define('ASSIGN_SUBMISSION_STATUS_SUBMITTED', 'submitted');
- // Search filters for grading page.
- define('ASSIGN_FILTER_NONE', 'none');
- define('ASSIGN_FILTER_SUBMITTED', 'submitted');
- define('ASSIGN_FILTER_NOT_SUBMITTED', 'notsubmitted');
- define('ASSIGN_FILTER_SINGLE_USER', 'singleuser');
- define('ASSIGN_FILTER_REQUIRE_GRADING', 'requiregrading');
- define('ASSIGN_FILTER_GRANTED_EXTENSION', 'grantedextension');
- // Marker filter for grading page.
- define('ASSIGN_MARKER_FILTER_NO_MARKER', -1);
- // Reopen attempt methods.
- define('ASSIGN_ATTEMPT_REOPEN_METHOD_NONE', 'none');
- define('ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL', 'manual');
- define('ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS', 'untilpass');
- // Special value means allow unlimited attempts.
- define('ASSIGN_UNLIMITED_ATTEMPTS', -1);
- // Special value means no grade has been set.
- define('ASSIGN_GRADE_NOT_SET', -1);
- // Grading states.
- define('ASSIGN_GRADING_STATUS_GRADED', 'graded');
- define('ASSIGN_GRADING_STATUS_NOT_GRADED', 'notgraded');
- // Marking workflow states.
- define('ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED', 'notmarked');
- define('ASSIGN_MARKING_WORKFLOW_STATE_INMARKING', 'inmarking');
- define('ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW', 'readyforreview');
- define('ASSIGN_MARKING_WORKFLOW_STATE_INREVIEW', 'inreview');
- define('ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE', 'readyforrelease');
- define('ASSIGN_MARKING_WORKFLOW_STATE_RELEASED', 'released');
- /** ASSIGN_MAX_EVENT_LENGTH = 432000 ; 5 days maximum */
- define("ASSIGN_MAX_EVENT_LENGTH", "432000");
- // Name of file area for intro attachments.
- define('ASSIGN_INTROATTACHMENT_FILEAREA', 'introattachment');
- // Event types.
- define('ASSIGN_EVENT_TYPE_DUE', 'due');
- define('ASSIGN_EVENT_TYPE_GRADINGDUE', 'gradingdue');
- define('ASSIGN_EVENT_TYPE_OPEN', 'open');
- define('ASSIGN_EVENT_TYPE_CLOSE', 'close');
- require_once($CFG->libdir . '/accesslib.php');
- require_once($CFG->libdir . '/formslib.php');
- require_once($CFG->dirroot . '/repository/lib.php');
- require_once($CFG->dirroot . '/mod/assign/mod_form.php');
- require_once($CFG->libdir . '/gradelib.php');
- require_once($CFG->dirroot . '/grade/grading/lib.php');
- require_once($CFG->dirroot . '/mod/assign/feedbackplugin.php');
- require_once($CFG->dirroot . '/mod/assign/submissionplugin.php');
- require_once($CFG->dirroot . '/mod/assign/renderable.php');
- require_once($CFG->dirroot . '/mod/assign/gradingtable.php');
- require_once($CFG->libdir . '/portfolio/caller.php');
- use \mod_assign\output\grading_app;
- /**
- * Standard base class for mod_assign (assignment types).
- *
- * @package mod_assign
- * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
- class assign {
- /** @var stdClass the assignment record that contains the global settings for this assign instance */
- private $instance;
- /** @var array $var array an array containing per-user assignment records, each having calculated properties (e.g. dates) */
- private $userinstances = [];
- /** @var grade_item the grade_item record for this assign instance's primary grade item. */
- private $gradeitem;
- /** @var context the context of the course module for this assign instance
- * (or just the course if we are creating a new one)
- */
- private $context;
- /** @var stdClass the course this assign instance belongs to */
- private $course;
- /** @var stdClass the admin config for all assign instances */
- private $adminconfig;
- /** @var assign_renderer the custom renderer for this module */
- private $output;
- /** @var cm_info the course module for this assign instance */
- private $coursemodule;
- /** @var array cache for things like the coursemodule name or the scale menu -
- * only lives for a single request.
- */
- private $cache;
- /** @var array list of the installed submission plugins */
- private $submissionplugins;
- /** @var array list of the installed feedback plugins */
- private $feedbackplugins;
- /** @var string action to be used to return to this page
- * (without repeating any form submissions etc).
- */
- private $returnaction = 'view';
- /** @var array params to be used to return to this page */
- private $returnparams = array();
- /** @var string modulename prevents excessive calls to get_string */
- private static $modulename = null;
- /** @var string modulenameplural prevents excessive calls to get_string */
- private static $modulenameplural = null;
- /** @var array of marking workflow states for the current user */
- private $markingworkflowstates = null;
- /** @var bool whether to exclude users with inactive enrolment */
- private $showonlyactiveenrol = null;
- /** @var string A key used to identify 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 when team submissions are enabled. The cache key will be the user. */
- private $usersubmissiongroups = 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();
- /**
- * @var stdClass The most recent team submission. Used to determine additional attempt numbers and whether
- * to update the gradebook.
- */
- private $mostrecentteamsubmission = null;
- /** @var array Array of error messages encountered during the execution of assignment related operations. */
- private $errors = array();
- /**
- * Constructor for the base assign class.
- *
- * Note: For $coursemodule you can supply a stdclass if you like, but it
- * will be more efficient to supply a cm_info object.
- *
- * @param mixed $coursemodulecontext context|null the course module context
- * (or the course context if the coursemodule has not been
- * created yet).
- * @param mixed $coursemodule the current course module if it was already loaded,
- * otherwise this class will load one from the context as required.
- * @param mixed $course the current course if it was already loaded,
- * otherwise this class will load one from the context as required.
- */
- public function __construct($coursemodulecontext, $coursemodule, $course) {
- global $SESSION;
- $this->context = $coursemodulecontext;
- $this->course = $course;
- // Ensure that $this->coursemodule is a cm_info object (or null).
- $this->coursemodule = cm_info::create($coursemodule);
- // Temporary cache only lives for a single request - used to reduce db lookups.
- $this->cache = array();
- $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);
- if (!isset($SESSION->mod_assign_useridlist)) {
- $SESSION->mod_assign_useridlist = [];
- }
- }
- /**
- * Set the action and parameters that can be used to return to the current page.
- *
- * @param string $action The action for the current page
- * @param array $params An array of name value pairs which form the parameters
- * to return to the current page.
- * @return void
- */
- public function register_return_link($action, $params) {
- global $PAGE;
- $params['action'] = $action;
- $cm = $this->get_course_module();
- if ($cm) {
- $currenturl = new moodle_url('/mod/assign/view.php', array('id' => $cm->id));
- } else {
- $currenturl = new moodle_url('/mod/assign/index.php', array('id' => $this->get_course()->id));
- }
- $currenturl->params($params);
- $PAGE->set_url($currenturl);
- }
- /**
- * Return an action that can be used to get back to the current page.
- *
- * @return string action
- */
- public function get_return_action() {
- global $PAGE;
- // Web services don't set a URL, we should avoid debugging when ussing the url object.
- if (!WS_SERVER) {
- $params = $PAGE->url->params();
- }
- if (!empty($params['action'])) {
- return $params['action'];
- }
- return '';
- }
- /**
- * Based on the current assignment settings should we display the intro.
- *
- * @return bool showintro
- */
- public function show_intro() {
- if ($this->get_instance()->alwaysshowdescription ||
- time() > $this->get_instance()->allowsubmissionsfromdate) {
- return true;
- }
- return false;
- }
- /**
- * Return a list of parameters that can be used to get back to the current page.
- *
- * @return array params
- */
- public function get_return_params() {
- global $PAGE;
- $params = array();
- if (!WS_SERVER) {
- $params = $PAGE->url->params();
- }
- unset($params['id']);
- unset($params['action']);
- return $params;
- }
- /**
- * Set the submitted form data.
- *
- * @param stdClass $data The form data (instance)
- */
- public function set_instance(stdClass $data) {
- $this->instance = $data;
- }
- /**
- * Set the context.
- *
- * @param context $context The new context
- */
- public function set_context(context $context) {
- $this->context = $context;
- }
- /**
- * Set the course data.
- *
- * @param stdClass $course The course data
- */
- public function set_course(stdClass $course) {
- $this->course = $course;
- }
- /**
- * Set error message.
- *
- * @param string $message The error message
- */
- protected function set_error_message(string $message) {
- $this->errors[] = $message;
- }
- /**
- * Get error messages.
- *
- * @return array The array of error messages
- */
- protected function get_error_messages(): array {
- return $this->errors;
- }
- /**
- * Get list of feedback plugins installed.
- *
- * @return array
- */
- public function get_feedback_plugins() {
- return $this->feedbackplugins;
- }
- /**
- * Get list of submission plugins installed.
- *
- * @return array
- */
- public function get_submission_plugins() {
- return $this->submissionplugins;
- }
- /**
- * Is blind marking enabled and reveal identities not set yet?
- *
- * @return bool
- */
- public function is_blind_marking() {
- return $this->get_instance()->blindmarking && !$this->get_instance()->revealidentities;
- }
- /**
- * Is hidden grading enabled?
- *
- * This just checks the assignment settings. Remember to check
- * the user has the 'showhiddengrader' capability too
- *
- * @return bool
- */
- public function is_hidden_grader() {
- return $this->get_instance()->hidegrader;
- }
- /**
- * Does an assignment have submission(s) or grade(s) already?
- *
- * @return bool
- */
- public function has_submissions_or_grades() {
- $allgrades = $this->count_grades();
- $allsubmissions = $this->count_submissions();
- if (($allgrades == 0) && ($allsubmissions == 0)) {
- return false;
- }
- return true;
- }
- /**
- * Get a specific submission plugin by its type.
- *
- * @param string $subtype assignsubmission | assignfeedback
- * @param string $type
- * @return mixed assign_plugin|null
- */
- public function get_plugin_by_type($subtype, $type) {
- $shortsubtype = substr($subtype, strlen('assign'));
- $name = $shortsubtype . 'plugins';
- if ($name != 'feedbackplugins' && $name != 'submissionplugins') {
- return null;
- }
- $pluginlist = $this->$name;
- foreach ($pluginlist as $plugin) {
- if ($plugin->get_type() == $type) {
- return $plugin;
- }
- }
- return null;
- }
- /**
- * Get a feedback plugin by type.
- *
- * @param string $type - The type of plugin e.g comments
- * @return mixed assign_feedback_plugin|null
- */
- public function get_feedback_plugin_by_type($type) {
- return $this->get_plugin_by_type('assignfeedback', $type);
- }
- /**
- * Get a submission plugin by type.
- *
- * @param string $type - The type of plugin e.g comments
- * @return mixed assign_submission_plugin|null
- */
- public function get_submission_plugin_by_type($type) {
- return $this->get_plugin_by_type('assignsubmission', $type);
- }
- /**
- * Load the plugins from the sub folders under subtype.
- *
- * @param string $subtype - either submission or feedback
- * @return array - The sorted list of plugins
- */
- public function load_plugins($subtype) {
- global $CFG;
- $result = array();
- $names = core_component::get_plugin_list($subtype);
- foreach ($names as $name => $path) {
- if (file_exists($path . '/locallib.php')) {
- require_once($path . '/locallib.php');
- $shortsubtype = substr($subtype, strlen('assign'));
- $pluginclass = 'assign_' . $shortsubtype . '_' . $name;
- $plugin = new $pluginclass($this, $name);
- if ($plugin instanceof assign_plugin) {
- $idx = $plugin->get_sort_order();
- while (array_key_exists($idx, $result)) {
- $idx +=1;
- }
- $result[$idx] = $plugin;
- }
- }
- }
- ksort($result);
- return $result;
- }
- /**
- * Display the assignment, used by view.php
- *
- * The assignment is displayed differently depending on your role,
- * the settings for the assignment and the status of the assignment.
- *
- * @param string $action The current action if any.
- * @param array $args Optional arguments to pass to the view (instead of getting them from GET and POST).
- * @return string - The page output.
- */
- public function view($action='', $args = array()) {
- global $PAGE;
- $o = '';
- $mform = null;
- $notices = array();
- $nextpageparams = array();
- if (!empty($this->get_course_module()->id)) {
- $nextpageparams['id'] = $this->get_course_module()->id;
- }
- // Handle form submissions first.
- if ($action == 'savesubmission') {
- $action = 'editsubmission';
- if ($this->process_save_submission($mform, $notices)) {
- $action = 'redirect';
- if ($this->can_grade()) {
- $nextpageparams['action'] = 'grading';
- } else {
- $nextpageparams['action'] = 'view';
- }
- }
- } else if ($action == 'editprevioussubmission') {
- $action = 'editsubmission';
- if ($this->process_copy_previous_attempt($notices)) {
- $action = 'redirect';
- $nextpageparams['action'] = 'editsubmission';
- }
- } else if ($action == 'lock') {
- $this->process_lock_submission();
- $action = 'redirect';
- $nextpageparams['action'] = 'grading';
- } else if ($action == 'removesubmission') {
- $this->process_remove_submission();
- $action = 'redirect';
- if ($this->can_grade()) {
- $nextpageparams['action'] = 'grading';
- } else {
- $nextpageparams['action'] = 'view';
- }
- } else if ($action == 'addattempt') {
- $this->process_add_attempt(required_param('userid', PARAM_INT));
- $action = 'redirect';
- $nextpageparams['action'] = 'grading';
- } else if ($action == 'reverttodraft') {
- $this->process_revert_to_draft();
- $action = 'redirect';
- $nextpageparams['action'] = 'grading';
- } else if ($action == 'unlock') {
- $this->process_unlock_submission();
- $action = 'redirect';
- $nextpageparams['action'] = 'grading';
- } else if ($action == 'setbatchmarkingworkflowstate') {
- $this->process_set_batch_marking_workflow_state();
- $action = 'redirect';
- $nextpageparams['action'] = 'grading';
- } else if ($action == 'setbatchmarkingallocation') {
- $this->process_set_batch_marking_allocation();
- $action = 'redirect';
- $nextpageparams['action'] = 'grading';
- } else if ($action == 'confirmsubmit') {
- $action = 'submit';
- if ($this->process_submit_for_grading($mform, $notices)) {
- $action = 'redirect';
- $nextpageparams['action'] = 'view';
- } else if ($notices) {
- $action = 'viewsubmitforgradingerror';
- }
- } else if ($action == 'submitotherforgrading') {
- if ($this->process_submit_other_for_grading($mform, $notices)) {
- $action = 'redirect';
- $nextpageparams['action'] = 'grading';
- } else {
- $action = 'viewsubmitforgradingerror';
- }
- } else if ($action == 'gradingbatchoperation') {
- $action = $this->process_grading_batch_operation($mform);
- if ($action == 'grading') {
- $action = 'redirect';
- $nextpageparams['action'] = 'grading';
- }
- } else if ($action == 'submitgrade') {
- if (optional_param('saveandshownext', null, PARAM_RAW)) {
- // Save and show next.
- $action = 'grade';
- if ($this->process_save_grade($mform)) {
- $action = 'redirect';
- $nextpageparams['action'] = 'grade';
- $nextpageparams['rownum'] = optional_param('rownum', 0, PARAM_INT) + 1;
- $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', $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', $this->get_useridlist_key_id(), PARAM_ALPHANUM);
- } else if (optional_param('savegrade', null, PARAM_RAW)) {
- // Save changes button.
- $action = 'grade';
- if ($this->process_save_grade($mform)) {
- $action = 'redirect';
- $nextpageparams['action'] = 'savegradingresult';
- }
- } else {
- // Cancel button.
- $action = 'redirect';
- $nextpageparams['action'] = 'grading';
- }
- } else if ($action == 'quickgrade') {
- $message = $this->process_save_quick_grades();
- $action = 'quickgradingresult';
- } else if ($action == 'saveoptions') {
- $this->process_save_grading_options();
- $action = 'redirect';
- $nextpageparams['action'] = 'grading';
- } else if ($action == 'saveextension') {
- $action = 'grantextension';
- if ($this->process_save_extension($mform)) {
- $action = 'redirect';
- $nextpageparams['action'] = 'grading';
- }
- } else if ($action == 'revealidentitiesconfirm') {
- $this->process_reveal_identities();
- $action = 'redirect';
- $nextpageparams['action'] = 'grading';
- }
- $returnparams = array('rownum'=>optional_param('rownum', 0, PARAM_INT),
- 'useridlistid' => optional_param('useridlistid', $this->get_useridlist_key_id(), PARAM_ALPHANUM));
- $this->register_return_link($action, $returnparams);
- // Include any page action as part of the body tag CSS id.
- if (!empty($action)) {
- $PAGE->set_pagetype('mod-assign-' . $action);
- }
- // Now show the right view page.
- if ($action == 'redirect') {
- $nextpageurl = new moodle_url('/mod/assign/view.php', $nextpageparams);
- $messages = '';
- $messagetype = \core\output\notification::NOTIFY_INFO;
- $errors = $this->get_error_messages();
- if (!empty($errors)) {
- $messages = html_writer::alist($errors, ['class' => 'mb-1 mt-1']);
- $messagetype = \core\output\notification::NOTIFY_ERROR;
- }
- redirect($nextpageurl, $messages, null, $messagetype);
- return;
- } else if ($action == 'savegradingresult') {
- $message = get_string('gradingchangessaved', 'assign');
- $o .= $this->view_savegrading_result($message);
- } else if ($action == 'quickgradingresult') {
- $mform = null;
- $o .= $this->view_quickgrading_result($message);
- } else if ($action == 'gradingpanel') {
- $o .= $this->view_single_grading_panel($args);
- } else if ($action == 'grade') {
- $o .= $this->view_single_grade_page($mform);
- } else if ($action == 'viewpluginassignfeedback') {
- $o .= $this->view_plugin_content('assignfeedback');
- } else if ($action == 'viewpluginassignsubmission') {
- $o .= $this->view_plugin_content('assignsubmission');
- } else if ($action == 'editsubmission') {
- $o .= $this->view_edit_submission_page($mform, $notices);
- } else if ($action == 'grader') {
- $o .= $this->view_grader();
- } else if ($action == 'grading') {
- $o .= $this->view_grading_page();
- } else if ($action == 'downloadall') {
- $o .= $this->download_submissions();
- } else if ($action == 'submit') {
- $o .= $this->check_submit_for_grading($mform);
- } else if ($action == 'grantextension') {
- $o .= $this->view_grant_extension($mform);
- } else if ($action == 'revealidentities') {
- $o .= $this->view_reveal_identities_confirm($mform);
- } else if ($action == 'removesubmissionconfirm') {
- $o .= $this->view_remove_submission_confirm();
- } else if ($action == 'plugingradingbatchoperation') {
- $o .= $this->view_plugin_grading_batch_operation($mform);
- } else if ($action == 'viewpluginpage') {
- $o .= $this->view_plugin_page();
- } else if ($action == 'viewcourseindex') {
- $o .= $this->view_course_index();
- } else if ($action == 'viewbatchsetmarkingworkflowstate') {
- $o .= $this->view_batch_set_workflow_state($mform);
- } else if ($action == 'viewbatchmarkingallocation') {
- $o .= $this->view_batch_markingallocation($mform);
- } else if ($action == 'viewsubmitforgradingerror') {
- $o .= $this->view_error_page(get_string('submitforgrading', 'assign'), $notices);
- } else if ($action == 'fixrescalednullgrades') {
- $o .= $this->view_fix_rescaled_null_grades();
- } else {
- $o .= $this->view_submission_page();
- }
- return $o;
- }
- /**
- * Add this instance to the database.
- *
- * @param stdClass $formdata The data submitted from the form
- * @param bool $callplugins This is used to skip the plugin code
- * when upgrading an old assignment to a new one (the plugins get called manually)
- * @return mixed false if an error occurs or the int id of the new instance
- */
- public function add_instance(stdClass $formdata, $callplugins) {
- global $DB;
- $adminconfig = $this->get_admin_config();
- $err = '';
- // Add the database record.
- $update = new stdClass();
- $update->name = $formdata->name;
- $update->timemodified = time();
- $update->timecreated = time();
- $update->course = $formdata->course;
- $update->courseid = $formdata->course;
- $update->intro = $formdata->intro;
- $update->introformat = $formdata->introformat;
- $update->alwaysshowdescription = !empty($formdata->alwaysshowdescription);
- $update->submissiondrafts = $formdata->submissiondrafts;
- $update->requiresubmissionstatement = $formdata->requiresubmissionstatement;
- $update->sendnotifications = $formdata->sendnotifications;
- $update->sendlatenotifications = $formdata->sendlatenotifications;
- $update->sendstudentnotifications = $adminconfig->sendstudentnotifications;
- if (isset($formdata->sendstudentnotifications)) {
- $update->sendstudentnotifications = $formdata->sendstudentnotifications;
- }
- $update->duedate = $formdata->duedate;
- $update->cutoffdate = $formdata->cutoffdate;
- $update->gradingduedate = $formdata->gradingduedate;
- $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate;
- $update->grade = $formdata->grade;
- $update->completionsubmit = !empty($formdata->completionsubmit);
- $update->teamsubmission = $formdata->teamsubmission;
- $update->requireallteammemberssubmit = $formdata->requireallteammemberssubmit;
- if (isset($formdata->teamsubmissiongroupingid)) {
- $update->teamsubmissiongroupingid = $formdata->teamsubmissiongroupingid;
- }
- $update->blindmarking = $formdata->blindmarking;
- if (isset($formdata->hidegrader)) {
- $update->hidegrader = $formdata->hidegrader;
- }
- $update->attemptreopenmethod = ASSIGN_ATTEMPT_REOPEN_METHOD_NONE;
- if (!empty($formdata->attemptreopenmethod)) {
- $update->attemptreopenmethod = $formdata->attemptreopenmethod;
- }
- if (!empty($formdata->maxattempts)) {
- $update->maxattempts = $formdata->maxattempts;
- }
- if (isset($formdata->preventsubmissionnotingroup)) {
- $update->preventsubmissionnotingroup = $formdata->preventsubmissionnotingroup;
- }
- $update->markingworkflow = $formdata->markingworkflow;
- $update->markingallocation = $formdata->markingallocation;
- if (empty($update->markingworkflow)) { // If marking workflow is disabled, make sure allocation is disabled.
- $update->markingallocation = 0;
- }
- $returnid = $DB->insert_record('assign', $update);
- $this->instance = $DB->get_record('assign', array('id'=>$returnid), '*', MUST_EXIST);
- // Cache the course record.
- $this->course = $DB->get_record('course', array('id'=>$formdata->course), '*', MUST_EXIST);
- $this->save_intro_draft_files($formdata);
- if ($callplugins) {
- // Call save_settings hook for submission plugins.
- foreach ($this->submissionplugins as $plugin) {
- if (!$this->update_plugin_instance($plugin, $formdata)) {
- print_error($plugin->get_error());
- return false;
- }
- }
- foreach ($this->feedbackplugins as $plugin) {
- if (!$this->update_plugin_instance($plugin, $formdata)) {
- print_error($plugin->get_error());
- return false;
- }
- }
- // In the case of upgrades the coursemodule has not been set,
- // so we need to wait before calling these two.
- $this->update_calendar($formdata->coursemodule);
- if (!empty($formdata->completionexpected)) {
- \core_completion\api::update_completion_date_event($formdata->coursemodule, 'assign', $this->instance,
- $formdata->completionexpected);
- }
- $this->update_gradebook(false, $formdata->coursemodule);
- }
- $update = new stdClass();
- $update->id = $this->get_instance()->id;
- $update->nosubmissions = (!$this->is_any_submission_plugin_enabled()) ? 1: 0;
- $DB->update_record('assign', $update);
- return $returnid;
- }
- /**
- * Delete all grades from the gradebook for this assignment.
- *
- * @return bool
- */
- protected function delete_grades() {
- global $CFG;
- $result = grade_update('mod/assign',
- $this->get_course()->id,
- 'mod',
- 'assign',
- $this->get_instance()->id,
- 0,
- null,
- array('deleted'=>1));
- return $result == GRADE_UPDATE_OK;
- }
- /**
- * Delete this instance from the database.
- *
- * @return bool false if an error occurs
- */
- public function delete_instance() {
- global $DB;
- $result = true;
- foreach ($this->submissionplugins as $plugin) {
- if (!$plugin->delete_instance()) {
- print_error($plugin->get_error());
- $result = false;
- }
- }
- foreach ($this->feedbackplugins as $plugin) {
- if (!$plugin->delete_instance()) {
- print_error($plugin->get_error());
- $result = false;
- }
- }
- // Delete files associated with this assignment.
- $fs = get_file_storage();
- if (! $fs->delete_area_files($this->context->id) ) {
- $result = false;
- }
- $this->delete_all_overrides();
- // Delete_records will throw an exception if it fails - so no need for error checking here.
- $DB->delete_records('assign_submission', array('assignment' => $this->get_instance()->id));
- $DB->delete_records('assign_grades', array('assignment' => $this->get_instance()->id));
- $DB->delete_records('assign_plugin_config', array('assignment' => $this->get_instance()->id));
- $DB->delete_records('assign_user_flags', array('assignment' => $this->get_instance()->id));
- $DB->delete_records('assign_user_mapping', array('assignment' => $this->get_instance()->id));
- // Delete items from the gradebook.
- if (! $this->delete_grades()) {
- $result = false;
- }
- // Delete the instance.
- // We must delete the module record after we delete the grade item.
- $DB->delete_records('assign', array('id'=>$this->get_instance()->id));
- return $result;
- }
- /**
- * Deletes a assign override from the database and clears any corresponding calendar events
- *
- * @param int $overrideid The id of the override being deleted
- * @return bool true on success
- */
- public function delete_override($overrideid) {
- global $CFG, $DB;
- require_once($CFG->dirroot . '/calendar/lib.php');
- $cm = $this->get_course_module();
- if (empty($cm)) {
- $instance = $this->get_instance();
- $cm = get_coursemodule_from_instance('assign', $instance->id, $instance->course);
- }
- $override = $DB->get_record('assign_overrides', array('id' => $overrideid), '*', MUST_EXIST);
- // Delete the events.
- $conds = array('modulename' => 'assign', 'instance' => $this->get_instance()->id);
- if (isset($override->userid)) {
- $conds['userid'] = $override->userid;
- } else {
- $conds['groupid'] = $override->groupid;
- }
- $events = $DB->get_records('event', $conds);
- foreach ($events as $event) {
- $eventold = calendar_event::load($event);
- $eventold->delete();
- }
- $DB->delete_records('assign_overrides', array('id' => $overrideid));
- // Set the common parameters for one of the events we will be triggering.
- $params = array(
- 'objectid' => $override->id,
- 'context' => context_module::instance($cm->id),
- 'other' => array(
- 'assignid' => $override->assignid
- )
- );
- // Determine which override deleted event to fire.
- if (!empty($override->userid)) {
- $params['relateduserid'] = $override->userid;
- $event = \mod_assign\event\user_override_deleted::create($params);
- } else {
- $params['other']['groupid'] = $override->groupid;
- $event = \mod_assign\event\group_override_deleted::create($params);
- }
- // Trigger the override deleted event.
- $event->add_record_snapshot('assign_overrides', $override);
- $event->trigger();
- return true;
- }
- /**
- * Deletes all assign overrides from the database and clears any corresponding calendar events
- */
- public function delete_all_overrides() {
- global $DB;
- $overrides = $DB->get_records('assign_overrides', array('assignid' => $this->get_instance()->id), 'id');
- foreach ($overrides as $override) {
- $this->delete_override($override->id);
- }
- }
- /**
- * Updates the assign properties with override information for a user.
- *
- * Algorithm: For each assign setting, if there is a matching user-specific override,
- * then use that otherwise, if there are group-specific overrides, return the most
- * lenient combination of them. If neither applies, leave the assign setting unchanged.
- *
- * @param int $userid The userid.
- */
- public function update_effective_access($userid) {
- $override = $this->override_exists($userid);
- // Merge with assign defaults.
- $keys = array('duedate', 'cutoffdate', 'allowsubmissionsfromdate');
- foreach ($keys as $key) {
- if (isset($override->{$key})) {
- $this->get_instance($userid)->{$key} = $override->{$key};
- }
- }
- }
- /**
- * Returns whether an assign has any overrides.
- *
- * @return true if any, false if not
- */
- public function has_overrides() {
- global $DB;
- $override = $DB->record_exists('assign_overrides', array('assignid' => $this->get_instance()->id));
- if ($override) {
- return true;
- }
- return false;
- }
- /**
- * Returns user override
- *
- * Algorithm: For each assign setting, if there is a matching user-specific override,
- * then use that otherwise, if there are group-specific overrides, use the one with the
- * lowest sort order. If neither applies, leave the assign setting unchanged.
- *
- * @param int $userid The userid.
- * @return stdClass The override
- */
- public function override_exists($userid) {
- global $DB;
- // Gets an assoc array containing the keys for defined user overrides only.
- $getuseroverride = function($userid) use ($DB) {
- $useroverride = $DB->get_record('assign_overrides', ['assignid' => $this->get_instance()->id, 'userid' => $userid]);
- return $useroverride ? get_object_vars($useroverride) : [];
- };
- // Gets an assoc array containing the keys for defined group overrides only.
- $getgroupoverride = function($userid) use ($DB) {
- $groupings = groups_get_user_groups($this->get_instance()->course, $userid);
- if (empty($groupings[0])) {
- return [];
- }
- // Select all overrides that apply to the User's groups.
- list($extra, $params) = $DB->get_in_or_equal(array_values($groupings[0]));
- $sql = "SELECT * FROM {assign_overrides}
- WHERE groupid $extra AND assignid = ? ORDER BY sortorder ASC";
- $params[] = $this->get_instance()->id;
- $groupoverride = $DB->get_record_sql($sql, $params, IGNORE_MULTIPLE);
- return $groupoverride ? get_object_vars($groupoverride) : [];
- };
- // Later arguments clobber earlier ones with array_merge. The two helper functions
- // return arrays containing keys for only the defined overrides. So we get the
- // desired behaviour as per the algorithm.
- return (object)array_merge(
- ['duedate' => null, 'cutoffdate' => null, 'allowsubmissionsfromdate' => null],
- $getgroupoverride($userid),
- $getuseroverride($userid)
- );
- }
- /**
- * Check if the given calendar_event is either a user or group override
- * event.
- *
- * @return bool
- */
- public function is_override_calendar_event(\calendar_event $event) {
- global $DB;
- if (!isset($event->modulename)) {
- return false;
- }
- if ($event->modulename != 'assign') {
- return false;
- }
- if (!isset($event->instance)) {
- return false;
- }
- if (!isset($event->userid) && !isset($event->groupid)) {
- return false;
- }
- $overrideparams = [
- 'assignid' => $event->instance
- ];
- if (isset($event->groupid)) {
- $overrideparams['groupid'] = $event->groupid;
- } else if (isset($event->userid)) {
- $overrideparams['userid'] = $event->userid;
- }
- if ($DB->get_record('assign_overrides', $overrideparams)) {
- return true;
- } else {
- return false;
- }
- }
- /**
- * This function calculates the minimum and maximum cutoff values for the timestart of
- * the given event.
- *
- * It will return an array with two values, the first being the minimum cutoff value and
- * the second being the maximum cutoff value. Either or both values can be null, which
- * indicates there is no minimum or maximum, respectively.
- *
- * If a cutoff is required then the function must return an array containing the cutoff
- * timestamp and error string to display to the user if the cutoff value is violated.
- *
- * A minimum and maximum cutoff return value will look like:
- * [
- * [1505704373, 'The due date must be after the sbumission start date'],
- * [1506741172, 'The due date must be before the cutoff date']
- * ]
- *
- * If the event does not have a valid timestart range then [false, false] will
- * be returned.
- *
- * @param calendar_event $event The calendar event to get the time range for
- * @return array
- */
- function get_valid_calendar_event_timestart_range(\calendar_event $event) {
- $instance = $this->get_instance();
- $submissionsfromdate = $instance->allowsubmissionsfromdate;
- $cutoffdate = $instance->cutoffdate;
- $duedate = $instance->duedate;
- $gradingduedate = $instance->gradingduedate;
- $mindate = null;
- $maxdate = null;
- if ($event->eventtype == ASSIGN_EVENT_TYPE_DUE) {
- // This check is in here because due date events are currently
- // the only events that can be overridden, so we can save a DB
- // query if we don't bother checking other events.
- if ($this->is_override_calendar_event($event)) {
- // This is an override event so there is no valid timestart
- // range to set it to.
- return [false, false];
- }
- if ($submissionsfromdate) {
- $mindate = [
- $submissionsfromdate,
- get_string('duedatevalidation', 'assign'),
- ];
- }
- if ($cutoffdate) {
- $maxdate = [
- $cutoffdate,
- get_string('cutoffdatevalidation', 'assign'),
- ];
- }
- if ($gradingduedate) {
- // If we don't have a cutoff date or we've got a grading due date
- // that is earlier than the cutoff then we should use that as the
- // upper limit for the due date.
- if (!$cutoffdate || $gradingduedate < $cutoffdate) {
- $maxdate = [
- $gradingduedate,
- get_string('gradingdueduedatevalidation', 'assign'),
- ];
- }
- }
- } else if ($event->eventtype == ASSIGN_EVENT_TYPE_GRADINGDUE) {
- if ($duedate) {
- $mindate = [
- $duedate,
- get_string('gradingdueduedatevalidation', 'assign'),
- ];
- } else if ($submissionsfromdate) {
- $mindate = [
- $submissionsfromdate,
- get_string('gradingduefromdatevalidation', 'assign'),
- ];
- }
- }
- return [$mindate, $maxdate];
- }
- /**
- * Actual implementation of the reset course functionality, delete all the
- * assignment submissions for course $data->courseid.
- *
- * @param stdClass $data the data submitted from the reset course.
- * @return array status array
- */
- public function reset_userdata($data) {
- global $CFG, $DB;
- $componentstr = get_string('modulenameplural', 'assign');
- $status = array();
- $fs = get_file_storage();
- if (!empty($data->reset_assign_submissions)) {
- // Delete files associated with this assignment.
- foreach ($this->submissionplugins as $plugin) {
- $fileareas = array();
- $plugincomponent = $plugin->get_subtype() . '_' . $plugin->get_type();
- $fileareas = $plugin->get_file_areas();
- foreach ($fileareas as $filearea => $notused) {
- $fs->delete_area_files($this->context->id, $plugincomponent, $filearea);
- }
- if (!$plugin->delete_instance()) {
- $status[] = array('component'=>$componentstr,
- 'item'=>get_string('deleteallsubmissions', 'assign'),
- 'error'=>$plugin->get_error());
- }
- }
- foreach ($this->feedbackplugins as $plugin) {
- $fileareas = array();
- $plugincomponent = $plugin->get_subtype() . '_' . $plugin->get_type();
- $fileareas = $plugin->get_file_areas();
- foreach ($fileareas as $filearea => $notused) {
- $fs->delete_area_files($this->context->id, $plugincomponent, $filearea);
- }
- if (!$plugin->delete_instance()) {
- $status[] = array('component'=>$componentstr,
- 'item'=>get_string('deleteallsubmissions', 'assign'),
- 'error'=>$plugin->get_error());
- }
- }
- $assignids = $DB->get_records('assign', array('course' => $data->courseid), '', 'id');
- list($sql, $params) = $DB->get_in_or_equal(array_keys($assignids));
- $DB->delete_records_select('assign_submission', "assignment $sql", $params);
- $DB->delete_records_select('assign_user_flags', "assignment $sql", $params);
- $status[] = array('component'=>$componentstr,
- 'item'=>get_string('deleteallsubmissions', 'assign'),
- 'error'=>false);
- if (!empty($data->reset_gradebook_grades)) {
- $DB->delete_records_select('assign_grades', "assignment $sql", $params);
- // Remove all grades from gradebook.
- require_once($CFG->dirroot.'/mod/assign/lib.php');
- assign_reset_gradebook($data->courseid);
- }
- // Reset revealidentities for assign if blindmarking is enabled.
- if ($this->get_instance()->blindmarking) {
- $DB->set_field('assign', 'revealidentities', 0, array('id' => $this->get_instance()->id));
- }
- }
- // Remove user overrides.
- if (!empty($data->reset_assign_user_overrides)) {
- $DB->delete_records_select('assign_overrides',
- 'assignid IN (SELECT id FROM {assign} WHERE course = ?) AND userid IS NOT NULL', array($data->courseid));
- $status[] = array(
- 'component' => $componentstr,
- 'item' => get_string('useroverridesdeleted', 'assign'),
- 'error' => false);
- }
- // Remove group overrides.
- if (!empty($data->reset_assign_group_overrides)) {
- $DB->delete_records_select('assign_overrides',
- 'assignid IN (SELECT id FROM {assign} WHERE course = ?) AND groupid IS NOT NULL', array($data->courseid));
- $status[] = array(
- 'component' => $componentstr,
- 'item' => get_string('groupoverridesdeleted', 'assign'),
- 'error' => false);
- }
- // Updating dates - shift may be negative too.
- if ($data->timeshift) {
- $DB->execute("UPDATE {assign_overrides}
- SET allowsubmissionsfromdate = allowsubmissionsfromdate + ?
- WHERE assignid = ? AND allowsubmissionsfromdate <> 0",
- array($data->timeshift, $this->get_instance()->id));
- $DB->execute("UPDATE {assign_overrides}
- SET duedate = duedate + ?
- WHERE assignid = ? AND duedate <> 0",
- array($data->timeshift, $this->get_instance()->id));
- $DB->execute("UPDATE {assign_overrides}
- SET cutoffdate = cutoffdate + ?
- WHERE assignid =? AND cutoffdate <> 0",
- array($data->timeshift, $this->get_instance()->id));
- // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset.
- // See MDL-9367.
- shift_course_mod_dates('assign',
- array('duedate', 'allowsubmissionsfromdate', 'cutoffdate'),
- $data->timeshift,
- $data->courseid, $this->get_instance()->id);
- $status[] = array('component'=>$componentstr,
- 'item'=>get_string('datechanged'),
- 'error'=>false);
- }
- return $status;
- }
- /**
- * Update the settings for a single plugin.
- *
- * @param assign_plugin $plugin The plugin to update
- * @param stdClass $formdata The form data
- * @return bool false if an error occurs
- */
- protected function update_plugin_instance(assign_plugin $plugin, stdClass $formdata) {
- if ($plugin->is_visible()) {
- $enabledname = $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled';
- if (!empty($formdata->$enabledname)) {
- $plugin->enable();
- if (!$plugin->save_settings($formdata)) {
- print_error($plugin->get_error());
- return false;
- }
- } else {
- $plugin->disable();
- }
- }
- return true;
- }
- /**
- * Update the gradebook information for this assignment.
- *
- * @param bool $reset If true, will reset all grades in the gradbook for this assignment
- * @param int $coursemoduleid This is required because it might not exist in the database yet
- * @return bool
- */
- public function update_gradebook($reset, $coursemoduleid) {
- global $CFG;
- require_once($CFG->dirroot.'/mod/assign/lib.php');
- $assign = clone $this->get_instance();
- $assign->cmidnumber = $coursemodule…
Large files files are truncated, but you can click here to view the full file