diff --git a/mod/assign/lib.php b/mod/assign/lib.php index ae6d0c50f35..e89ba2438dc 100644 --- a/mod/assign/lib.php +++ b/mod/assign/lib.php @@ -252,6 +252,7 @@ function assign_update_events($assign, $override = null) { $addclose = empty($current->id) || !empty($current->duedate); $event = new stdClass(); + $event->type = CALENDAR_EVENT_TYPE_ACTION; $event->description = format_module_intro('assign', $assigninstance, $cmid); // Events module won't show user events when the courseid is nonzero. $event->courseid = ($userid) ? 0 : $assigninstance->course; @@ -261,8 +262,9 @@ function assign_update_events($assign, $override = null) { $event->instance = $assigninstance->id; $event->timestart = $duedate; $event->timeduration = 0; + $event->timesort = $event->timestart + $event->timeduration; $event->visible = instance_is_visible('assign', $assigninstance); - $event->eventtype = 'due'; + $event->eventtype = ASSIGN_EVENT_TYPE_OPEN; // Determine the event name and priority. if ($groupid) { @@ -299,7 +301,7 @@ function assign_update_events($assign, $override = null) { } $event->name = $eventname.' ('.get_string('duedate', 'assign').')'; $event->timestart = $duedate; - $event->eventtype = 'due'; + $event->eventtype = ASSIGN_EVENT_TYPE_CLOSE; \core_calendar\event::create($event); } } @@ -1707,15 +1709,68 @@ function assign_check_updates_since(cm_info $cm, $from, $filter = array()) { return $updates; } -//XXX: Rubbish implementation -function mod_assign_core_calendar_provide_event_action( - \core_calendar\event $event, - \core_calendar\action_factory $factory -) { +/** + * Is the event visible? + * + * @param \core_calendar\event $event + * @return bool Returns true if the event is visible to the current user, false otherwise. + */ +function mod_assign_core_calendar_is_event_visible(\core_calendar\event $event) { + global $USER; + + $cm = get_fast_modinfo($event->courseid)->instances['assign'][$event->instance]; + $context = context_module::instance($cm->id); + + $assign = new assign($context, $cm, null); + + if ($event->eventtype == ASSIGN_EVENT_TYPE_GRADINGDUE) { + return $assign->can_grade(); + } else { + return !$assign->can_grade() && $assign->can_view_submission($USER->id); + } +} + +/** + * Handles creating actions for events. + * + * @param \core_calendar\event $event + * @param \core_calendar\action_factory $factory + * @return \core_calendar\local\event\value_objects\action|\core_calendar\local\interfaces\action_interface|null + */ +function mod_assign_core_calendar_provide_event_action(\core_calendar\event $event, + \core_calendar\action_factory $factory) { + + global $CFG, $USER; + + require_once($CFG->dirroot . '/mod/assign/locallib.php'); + + $cm = get_fast_modinfo($event->courseid)->instances['assign'][$event->instance]; + $context = context_module::instance($cm->id); + + $assign = new assign($context, $cm, null); + + if ($event->eventtype == ASSIGN_EVENT_TYPE_GRADINGDUE) { + $name = get_string('grade'); + $url = new \moodle_url('/mod/assign/view.php', [ + 'id' => $cm->id, + 'action' => 'grader' + ]); + $itemcount = $assign->count_submissions_need_grading(); + $actionable = $assign->can_grade() && (time() >= $assign->get_instance()->allowsubmissionsfromdate); + } else { + $name = get_string('addsubmission', 'assign'); + $url = new \moodle_url('/mod/assign/view.php', [ + 'id' => $cm->id, + 'action' => 'editsubmission' + ]); + $itemcount = 1; + $actionable = $assign->is_any_submission_plugin_enabled() && $assign->can_edit_submission($USER->id); + } + return $factory->create_instance( - 'chef', - new \moodle_url('http://example.com'), - 3, - true + $name, + $url, + $itemcount, + $actionable ); } diff --git a/mod/assign/locallib.php b/mod/assign/locallib.php index 21c960a546d..4ca21b3fcc0 100644 --- a/mod/assign/locallib.php +++ b/mod/assign/locallib.php @@ -68,6 +68,12 @@ 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'); @@ -1159,12 +1165,43 @@ class assign { // Special case for add_instance as the coursemodule has not been set yet. $instance = $this->get_instance(); - $eventtype = 'due'; + // Start with creating the event. + $event = new stdClass(); + $event->modulename = 'assign'; + $event->courseid = $instance->course; + $event->groupid = 0; + $event->userid = 0; + $event->instance = $instance->id; + $event->name = $instance->name; + $event->type = CALENDAR_EVENT_TYPE_ACTION; + // Convert the links to pluginfile. It is a bit hacky but at this stage the files + // might not have been saved in the module area yet. + $intro = $instance->intro; + if ($draftid = file_get_submitted_draft_itemid('introeditor')) { + $intro = file_rewrite_urls_to_pluginfile($intro, $draftid); + } + + // We need to remove the links to files as the calendar is not ready + // to support module events with file areas. + $intro = strip_pluginfile_content($intro); + if ($this->show_intro()) { + $event->description = array( + 'text' => $intro, + 'format' => $instance->introformat + ); + } else { + $event->description = array( + 'text' => '', + 'format' => $instance->introformat + ); + } + + $eventtype = ASSIGN_EVENT_TYPE_DUE; if ($instance->duedate) { - $event = new stdClass(); - - // Fetch the original due date event. It will have a non-zero course ID and a zero group ID. + $event->eventtype = $eventtype; + $event->timestart = $instance->duedate; + $event->timesort = $instance->duedate; $select = "modulename = :modulename AND instance = :instance AND eventtype = :eventtype @@ -1172,50 +1209,41 @@ class assign { AND courseid <> 0"; $params = array('modulename' => 'assign', 'instance' => $instance->id, 'eventtype' => $eventtype); $event->id = $DB->get_field_select('event', 'id', $select, $params); - $event->name = $instance->name; - $event->timestart = $instance->duedate; - - // Convert the links to pluginfile. It is a bit hacky but at this stage the files - // might not have been saved in the module area yet. - $intro = $instance->intro; - if ($draftid = file_get_submitted_draft_itemid('introeditor')) { - $intro = file_rewrite_urls_to_pluginfile($intro, $draftid); - } - - // We need to remove the links to files as the calendar is not ready - // to support module events with file areas. - $intro = strip_pluginfile_content($intro); - if ($this->show_intro()) { - $event->description = array( - 'text' => $intro, - 'format' => $instance->introformat - ); - } else { - $event->description = array( - 'text' => '', - 'format' => $instance->introformat - ); - } + // Now process the event. if ($event->id) { $calendarevent = \core_calendar\event::load($event->id); $calendarevent->update($event); } else { - unset($event->id); - $event->courseid = $instance->course; - $event->groupid = 0; - $event->userid = 0; - $event->modulename = 'assign'; - $event->instance = $instance->id; - $event->eventtype = $eventtype; - $event->timeduration = 0; \core_calendar\event::create($event); } } else { - $DB->delete_records('event', array('modulename' => 'assign', 'instance' => $instance->id, 'eventtype' => $eventtype)); + $DB->delete_records('event', array('modulename' => 'assign', 'instance' => $instance->id, + 'eventtype' => $eventtype)); } - } + $eventtype = ASSIGN_EVENT_TYPE_GRADINGDUE; + if ($instance->gradingduedate) { + $event->eventtype = $eventtype; + $event->timestart = $instance->gradingduedate; + $event->timesort = $instance->gradingduedate; + $event->id = $DB->get_field('event', 'id', array('modulename' => 'assign', + 'instance' => $instance->id, 'eventtype' => $event->eventtype)); + + // Now process the event. + if ($event->id) { + $calendarevent = \core_calendar\event::load($event->id); + $calendarevent->update($event); + } else { + \core_calendar\event::create($event); + } + } else { + $DB->delete_records('event', array('modulename' => 'assign', 'instance' => $instance->id, + 'eventtype' => $eventtype)); + } + + return true; + } /** * Update this instance in the database. @@ -3076,7 +3104,7 @@ class assign { * Throw an error if the permissions to view this users submission are missing. * * @throws required_capability_exception - * @return void + * @return none */ public function require_view_submission($userid) { if (!$this->can_view_submission($userid)) { @@ -3088,7 +3116,7 @@ class assign { * Throw an error if the permissions to view grades in this assignment are missing. * * @throws required_capability_exception - * @return void + * @return none */ public function require_view_grades() { if (!$this->can_view_grades()) { diff --git a/mod/assign/tests/lib_test.php b/mod/assign/tests/lib_test.php index a67160203c5..0724da33586 100644 --- a/mod/assign/tests/lib_test.php +++ b/mod/assign/tests/lib_test.php @@ -380,4 +380,191 @@ class mod_assign_lib_testcase extends mod_assign_base_testcase { $this->assertFalse(assign_refresh_events('aaa')); } + public function test_assign_core_calendar_is_event_visible_duedate_event_as_teacher() { + $this->setAdminUser(); + + // Create an assignment. + $assign = $this->create_instance(); + + // Create a calendar event. + $event = $this->create_action_event($assign->get_instance()->id, ASSIGN_EVENT_TYPE_DUE); + + // Set the user to a teacher. + $this->setUser($this->editingteachers[0]); + + // The teacher should not care about the due date event. + $this->assertFalse(mod_assign_core_calendar_is_event_visible($event)); + } + + public function test_assign_core_calendar_is_event_visible_duedate_event_as_student() { + $this->setAdminUser(); + + // Create an assignment. + $assign = $this->create_instance(array('assignsubmission_onlinetext_enabled' => 1)); + + // Create a calendar event. + $event = $this->create_action_event($assign->get_instance()->id, ASSIGN_EVENT_TYPE_DUE); + + // Set the user to a student. + $this->setUser($this->students[0]); + + // The student should care about the due date event. + $this->assertTrue(mod_assign_core_calendar_is_event_visible($event)); + } + + public function test_assign_core_calendar_is_event_visible_gradingduedate_event_as_teacher() { + $this->setAdminUser(); + + // Create an assignment. + $assign = $this->create_instance(); + + // Create a calendar event. + $event = $this->create_action_event($assign->get_instance()->id, ASSIGN_EVENT_TYPE_GRADINGDUE); + + // Set the user to a teacher. + $this->setUser($this->editingteachers[0]); + + // The teacher should care about the grading due date event. + $this->assertTrue(mod_assign_core_calendar_is_event_visible($event)); + } + + public function test_assign_core_calendar_is_event_visible_gradingduedate_event_as_student() { + $this->setAdminUser(); + + // Create an assignment. + $assign = $this->create_instance(); + + // Create a calendar event. + $event = $this->create_action_event($assign->get_instance()->id, ASSIGN_EVENT_TYPE_GRADINGDUE); + + // Set the user to a student. + $this->setUser($this->students[0]); + + // The student should not care about the grading due date event. + $this->assertFalse(mod_assign_core_calendar_is_event_visible($event)); + } + + public function test_assign_core_calendar_provide_event_action_duedate_as_teacher() { + $this->setAdminUser(); + + // Create an assignment. + $assign = $this->create_instance(array('assignsubmission_onlinetext_enabled' => 1)); + + // Create a calendar event. + $event = $this->create_action_event($assign->get_instance()->id, ASSIGN_EVENT_TYPE_DUE); + + // Create an action factory. + $factory = new \core_calendar\action_factory(); + + // Set the user to a teacher. + $this->setUser($this->teachers[0]); + + // Decorate action event. + $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory); + + // Confirm the event was decorated. + $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent); + $this->assertEquals(get_string('addsubmission', 'assign'), $actionevent->get_name()); + $this->assertInstanceOf('moodle_url', $actionevent->get_url()); + $this->assertEquals(1, $actionevent->get_item_count()); + $this->assertFalse($actionevent->is_actionable()); + } + + public function test_assign_core_calendar_provide_event_action_duedate_as_student() { + $this->setAdminUser(); + + // Create an assignment. + $assign = $this->create_instance(array('assignsubmission_onlinetext_enabled' => 1)); + + // Create a calendar event. + $event = $this->create_action_event($assign->get_instance()->id, ASSIGN_EVENT_TYPE_DUE); + + // Create an action factory. + $factory = new \core_calendar\action_factory(); + + // Set the user to a student. + $this->setUser($this->students[0]); + + // Decorate action event. + $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory); + + // Confirm the event was decorated. + $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent); + $this->assertEquals(get_string('addsubmission', 'assign'), $actionevent->get_name()); + $this->assertInstanceOf('moodle_url', $actionevent->get_url()); + $this->assertEquals(1, $actionevent->get_item_count()); + $this->assertTrue($actionevent->is_actionable()); + } + + public function test_assign_core_calendar_provide_event_action_gradingduedate_as_teacher() { + $this->setAdminUser(); + + // Create an assignment. + $assign = $this->create_instance(); + + // Create a calendar event. + $event = $this->create_action_event($assign->get_instance()->id, ASSIGN_EVENT_TYPE_GRADINGDUE); + + // Create an action factory. + $factory = new \core_calendar\action_factory(); + + // Set the user to a teacher. + $this->setUser($this->editingteachers[0]); + + // Decorate action event. + $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory); + + // Confirm the event was decorated. + $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent); + $this->assertEquals(get_string('grade'), $actionevent->get_name()); + $this->assertInstanceOf('moodle_url', $actionevent->get_url()); + $this->assertEquals(0, $actionevent->get_item_count()); + $this->assertTrue($actionevent->is_actionable()); + } + + public function test_assign_core_calendar_provide_event_action_gradingduedate_as_student() { + $this->setAdminUser(); + + // Create an assignment. + $assign = $this->create_instance(); + + // Create a calendar event. + $event = $this->create_action_event($assign->get_instance()->id, ASSIGN_EVENT_TYPE_GRADINGDUE); + + // Create an action factory. + $factory = new \core_calendar\action_factory(); + + // Set the user to a student. + $this->setUser($this->students[0]); + + // Decorate action event. + $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory); + + // Confirm the event was decorated. + $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent); + $this->assertEquals(get_string('grade'), $actionevent->get_name()); + $this->assertInstanceOf('moodle_url', $actionevent->get_url()); + $this->assertEquals(0, $actionevent->get_item_count()); + $this->assertFalse($actionevent->is_actionable()); + } + + /** + * Creates an action event. + * + * @param int $instanceid The assign id. + * @param string $eventtype The event type. eg. ASSIGN_EVENT_TYPE_DUE. + * @return bool|\core_calendar\event + */ + private function create_action_event($instanceid, $eventtype) { + $event = new stdClass(); + $event->name = 'Calendar event'; + $event->modulename = 'assign'; + $event->courseid = $this->course->id; + $event->instance = $instanceid; + $event->type = CALENDAR_EVENT_TYPE_ACTION; + $event->eventtype = $eventtype; + $event->timestart = time(); + + return \core_calendar\event::create($event); + } }